Skip to content

[Backend][Diagnostics] Preserve generated assembly line provenance in internal assembler errors #21

Closed

Description

@a19q3

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:
    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:
    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
    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

  1. added
    bugSomething isn't working
    enhancementNew feature or request
    on Aug 27, 2026
  2. a19q3 commented on Sep 11, 2026

    @a19q3
    CollaboratorAuthor

    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.

  3. a19q3 commented on Sep 11, 2026

    @a19q3
    CollaboratorAuthor

    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 under generated_assembly rather than fabricating CellScript source spans. Focused and artifact-checker regressions passed; the capability ledger records remaining_work=[] at cdc5b53c. No TAC or release workflow was invoked.

  4. a19q3 commented on Sep 19, 2026

    @a19q3
    CollaboratorAuthor

    The gate-evidence follow-up is now recorded: clean implementation candidate 74cff5fd passed dev, full ci within release, backend, and release on 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingenhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions