Repository navigation
[Backend][Diagnostics] Preserve generated assembly line provenance in internal assembler errors #21
Description
Summary
The internal assembler now correctly performs B-type branch relaxation, but most parser, layout, encoding, and machine-CFG failures still construct CompileError with Span::default().
As a result, a malformed or unsupported generated assembly operation can still surface as line 0, even when the assembler knows the input line, operation index, section, label, and byte offset. This was a secondary finding in #1 and remains unresolved after the primary branch-range bug was fixed.
Current evidence
- The assembler parses source lines but stores plain AsmOp values without their originating line number:
CellScript/src/codegen/assembler.rs
Lines 340 to 404 in 8ae6dc4
fn from_lines_relaxed(lines: &[String], layout: &SectionLayout) -> Result<Self> { let conservative = Self::from_lines_with_branch_mode(lines, BranchSizeMode::Conservative)?; let relaxed_text_branches = conservative.relaxed_branch_indices(layout)?; Self::from_lines_with_branch_mode(lines, BranchSizeMode::Exact(&relaxed_text_branches)) } fn from_lines_with_branch_mode(lines: &[String], branch_size_mode: BranchSizeMode<'_>) -> Result<Self> { let mut current_section = SectionKind::Text; let mut text_size = 0usize; let mut rodata_size = 0usize; let mut text_ops = Vec::new(); let mut rodata_ops = Vec::new(); let mut symbols = HashMap::new(); let mut globals = BTreeSet::new(); let mut entry_label = None; let mut fallback_entry = None; for line in lines { let Some(clean) = strip_comment(line) else { continue; }; if clean.is_empty() { continue; } if let Some(section) = parse_section_directive(clean)? { current_section = section; continue; } if clean.starts_with(".option ") || clean.starts_with(".type ") { continue; } if let Some(symbol) = clean.strip_prefix(".global ") { globals.insert(symbol.trim().to_string()); continue; } let (ops, offset) = match current_section { SectionKind::Text => (&mut text_ops, &mut text_size), SectionKind::Rodata => (&mut rodata_ops, &mut rodata_size), }; let op_index = ops.len(); if let Some(label) = clean.strip_suffix(':') { let label = label.trim().to_string(); let symbol = SymbolDef { section: current_section, offset: *offset }; if symbols.insert(label.clone(), symbol).is_some() { return Err(CompileError::new(format!("duplicate assembly label '{}'", label), crate::error::Span::default())); } if current_section == SectionKind::Text && globals.contains(&label) { if fallback_entry.is_none() { fallback_entry = Some(label.clone()); } if !label.starts_with("__") && entry_label.is_none() { entry_label = Some(label.clone()); } } ops.push(AsmOp::Label(label)); continue; } let op = parse_asm_op(clean)?; *offset += op_size(&op, *offset, current_section, op_index, branch_size_mode); ops.push(op); } - Unknown labels, unsupported instructions, invalid immediates, encoding failures, and CFG/layout errors still use Span::default(): https://github.com/CellScript-Labs/CellScript/blob/8ae6dc4a2749fa107ff42c166772600648c19bb7/src/codegen/assembler.rs
- Branch relaxation itself is covered and passing:
CellScript/src/codegen/assembler.rs
Lines 2112 to 2131 in 8ae6dc4
fn internal_assembler_relaxes_out_of_range_conditional_branch() { let mut lines = vec![ ".section .text".to_string(), ".global entry".to_string(), "entry:".to_string(), "li a0, 0".to_string(), "beqz a0, far_target".to_string(), ]; for _ in 0..1500 { lines.push("addi t0, t0, 0".to_string()); } lines.push("far_target:".to_string()); lines.push("ret".to_string()); let elf = assemble_elf_internal(&lines).expect("internal assembler should relax long conditional branches"); assert!(elf.starts_with(b"\x7fELF")); } #[test] fn internal_assembler_encodes_register_conditional_branches() { - The complete CellScript test suite is included by the CI gate at
CellScript/scripts/cellscript_gate.sh
Lines 580 to 595 in 8ae6dc4
require_cmd npm require_node_22 printf '{"status":"not-generated","reason":"test suite did not reach backend shape report generation"}\n' >"$CELLSCRIPT_BACKEND_SHAPE_REPORT" cargo_fmt_workspace --check check_canonical_cellscript_format check_example_u64_boundaries run cargo test --locked -p cellscript -- --test-threads=1 run cargo test --locked -p cellscript-artifact-checker -- --test-threads=1 check_artifact_checker_dependency_boundary run cargo test --locked -p cellscript-fiber-adapter -- --test-threads=1 run cargo test --locked -p cellscript-ckb-adapter -- --test-threads=1 run cargo test --locked -p cellscript-wasm --features wasm -- --test-threads=1 run cargo test --locked -p cellscript-ckb-sdk-builder-example -- --test-threads=1 run cargo test --locked -p cellscript-tools -- --test-threads=1 run_executable_package_scenarios all
Required diagnostic contract
Preserve at least this provenance for each parsed operation:
- generated assembly line number;
- text/rodata section;
- operation index and byte offset after layout;
- instruction/directive text or a bounded normalized representation;
- target label and resolved displacement for branch/jump failures.
Generated-assembly provenance must not be mislabeled as a CellScript source span. Where codegen has a real source/lowering-map origin, diagnostics may additionally attach it through an explicitly separate field.
All failures caused by user/compiler-produced assembly must remain ordinary stable diagnostics. No parser, layout, or encoding path should panic.
Acceptance criteria
- Parsed assembly operations retain their original generated line number.
- Parse, symbol, layout, branch/jump, immediate, register, directive, and encoding errors report the relevant assembly line.
- Machine-CFG errors identify the involved label/block/operation without pretending to have a CellScript source span.
- Human diagnostics no longer print an unexplained line 0 for an operation with known provenance.
- Machine JSON distinguishes source provenance from generated-assembly provenance.
- Tests cover duplicate/unknown labels, invalid registers/immediates/directives, out-of-range jumps, malformed memory operands, and CFG target errors.
- Existing branch-relaxation and ELF identity tests remain unchanged or are intentionally rebaselined with evidence.
- dev, ci, and backend gates pass.
Non-goals
- reconstructing a CellScript source span when lowering records do not provide one;
- exposing unbounded assembly text in diagnostics;
- changing branch-relaxation semantics;
- replacing the standalone artifact checker's independent diagnostics.
Activity
a19q3 commentedon Sep 11, 2026
Repository implementation landed on 0.30 in 1e2f642.
Assembler operations now retain generated line, bounded text, section, operation index, and layout offset. Parse, directive, register, immediate, memory, symbol, relaxation, encoding, unresolved-call, and machine-CFG errors attach this provenance. JSON exposes it under a separate generated_assembly object instead of fabricating a CellScript source span; duplicate labels also identify the original generated line.
Focused parse/symbol, encoding, out-of-range jump, and unresolved-symbol tests pass, and the full artifact-checker suite remains green. The implementation is ready for closure after the dev/CI/backend gate result is recorded; those release-facing gates were not asserted by this update.
a19q3 commentedon Sep 11, 2026
Completed on 0.30 in
1e2f6425. Internal assembler failures retain generated line, bounded text, section, operation index, and layout offset across parse/layout/encoding/unresolved-call paths, and JSON exposes this only undergenerated_assemblyrather than fabricating CellScript source spans. Focused and artifact-checker regressions passed; the capability ledger recordsremaining_work=[]atcdc5b53c. No TAC or release workflow was invoked.a19q3 commentedon Sep 19, 2026
The gate-evidence follow-up is now recorded: clean implementation candidate 74cff5fd passed
dev, fullciwithinrelease,backend, andreleaseon 2026-09-18. This includes the assembler provenance regressions and full backend audit.The accepted diagnostic contract remains generated line/text, section, operation index, and layout offset under
generated_assembly; it does not fabricate source-language span accuracy. #1's older comment has been updated to point out that this follow-up is complete. See the candidate evidence record.