(Filed by claude code)
Summary
cellc … --target riscv64-elf fails for 4 of the 7 bundled examples because the internal assembler rejects conditional branches whose displacement exceeds the RV64 B-type 13-bit signed immediate range (±4 KB). The failure propagates through cargo test --test examples, so bundled_examples_compile_to_elf is currently red on main.
The bug is independent of --target-profile — the default (spora), explicit --target-profile spora, and --target-profile ckb all trigger the same overflow on any example whose code reaches the internal assembler. CKB profile appears to "work" for some examples only because its target-profile policy gate rejects them before codegen runs.
Reproduction
git checkout main
for f in examples/{token,nft,vesting,amm_pool,multisig,timelock,launch}.cell; do
cellc "$f" --target riscv64-elf 2>&1 | tail -1 | sed "s|^|$f :: |"
done
examples/token.cell :: error: line 0: immediate '5060' does not fit 13-bit signed field
examples/nft.cell :: error: line 0: immediate '4888' does not fit 13-bit signed field
examples/vesting.cell :: error: line 0: immediate '5672' does not fit 13-bit signed field
examples/amm_pool.cell :: error: line 0: immediate '11748' does not fit 13-bit signed field
examples/multisig.cell :: (ok)
examples/timelock.cell :: (ok)
examples/launch.cell :: (ok)
Same results with explicit --target-profile spora. Under --target-profile ckb, token.cell still fails with the same 5060 overflow; the other examples are rejected by the CKB policy gate in src/lib.rs:465-605 before reaching codegen, which masks (but does not fix) the assembler bug.
cargo test --test examples -- bundled_examples_compile_to_elf
thread 'bundled_examples_compile_to_elf' panicked at tests/examples.rs:293:29:
amm_pool.cell should compile to ELF:
immediate '11748' does not fit 13-bit signed field
test result: FAILED. 9 passed; 1 failed
Expected vs. actual
- Expected: every example in
BUNDLED_EXAMPLES (tests/examples.rs:4) compiles to ELF, matching the assertion at tests/examples.rs:293.
- Actual: four examples fail in
src/codegen/mod.rs::encode_signed_bits with the 13-bit overflow error. --target riscv64-asm still works for all seven — only the ELF path is broken.
Root cause
RV64 conditional branches (beq / bne / blt / bge / bltu / bgeu, including the beqz / bnez aliases) are B-type: 13-bit signed PC-relative immediate, i.e. ±4096 bytes. The internal assembler encodes them directly with no fallback:
// src/codegen/mod.rs:5274-5282
Instruction::Beqz { rs, label } => {
let target = parsed.symbol_address(label, layout)?;
out.extend_from_slice(
&encode_b_type(0x63, 0b000, *rs, 0, relative_offset(pc, target)?)?
.to_le_bytes(),
);
}
Instruction::Bnez { rs, label } => { /* identical pattern */ }
// src/codegen/mod.rs:5534-5538
fn encode_b_type(opcode: u32, funct3: u32, rs1: u8, rs2: u8, imm: i64) -> Result<u32> {
// ...
let imm = encode_signed_bits(imm, 13)?;
Because codegen emits validation logic inline — each 32-byte type_hash comparison expands into 32 lbu/lbu/sub/beqz/<5-line fail epilogue> blocks, and every size/bounds/witness-magic check gets its own inline fail stub — the distance from a conditional branch to a shared exit label (.Lentry_witness_fail_*, .Lentry_witness_done_*, etc.) grows quickly. In amm_pool.cell this distance reaches 11748 bytes, ~3× the B-type limit.
The standard fix is branch range relaxation: when a B-type target is out of range, rewrite the branch as an inverted short branch plus a J-type jump (which has 21-bit ±1 MB range):
becomes
bnez rs, .Lskip_N
j far_label
.Lskip_N:
GAS, LLVM MC, and Keystone all do this automatically. The internal assembler is currently the only path without it.
Secondary issues uncovered
(1) Error message is missing span information
encode_signed_bits constructs the error with Span::default():
// src/codegen/mod.rs:5577-5580
return Err(CompileError::new(
format!("immediate '{}' does not fit {}-bit signed field", value, bits),
crate::error::Span::default(),
));
Result: every failure prints line 0:, regardless of which assembly line or which source position the branch originated from. The assembler has per-line information available during parsing (ParsedAssembly::from_lines); it should be threaded down into encode_instruction.
(2) CI evidently isn't running cargo test --test examples (or isn't gating on it)
A failing panic!-based test in the default test suite has landed on main (commit 10d5762 Initial CellScript standalone release). Either CI isn't running this target or the failure is being ignored. Regardless of the branch-relaxation fix, the test-gating policy needs a look.
Suggested fix
Two options, in increasing scope:
Option A — minimum viable: implement branch range relaxation
Modify encode_instruction in src/codegen/mod.rs:5274-5282 to detect out-of-range B-type branches and emit an inverted-branch-plus-jump pair. Because this changes instruction stream length, SectionLayout needs a fixup pass (conservative first pass for symbol addresses, exact second pass for emission). Standard two-pass assembler shape.
Estimated effort: ~1 day. Fixes all four failing examples. No semantic change to generated code.
Option B — recommended: share fail-handler routines (eliminates the long branches at source)
The generated assembly currently has 302 copies of the li a0, <err>; ld ra; ld fp; addi sp; ret fail epilogue across token.s alone. Collapsing these into ~8 shared handlers (one per distinct error code) and replacing fail sites with single-instruction j __fail_<code> brings every conditional branch target within a few hundred bytes. Also eliminates the equivalent issue in the 128 inlined byte-compare blocks by factoring them into a __verify_32byte_eq(ptr1, ptr2, err_code) helper.
Side effects:
- The branch-range bug disappears without needing relaxation.
- Assembly text shrinks by ~40% (observed on
token.s: 4505 → ~2600 lines).
- +~2 cycles per fail path due to the extra
j (negligible vs. CKB/Spora cycle budgets).
Estimated effort: ~1 week. Recommended as the actual fix because it addresses the underlying codegen smell rather than papering over it in the assembler.
Either way
- Plumb real spans into
encode_signed_bits / encode_b_type so future assembler errors point at the offending line.
- Add a regression test that generates a >8 KB synthetic assembly fragment with a long-distance branch and asserts it assembles.
- Verify CI is actually running
cargo test --test examples and failing the build on bundled_examples_compile_to_elf regressions.
Impact assessment
- User-visible: the documented primary deliverable (
cellc <input> --target riscv64-elf for on-chain deployment) fails silently — there is no warning in the README or cellc --help that four of the seven example programs can't actually be produced as ELF. --target riscv64-asm still works, which can mask the problem in casual testing.
- Trust: the project's own test suite is red on
main. Any downstream consumer running cargo test sees the failure immediately.
- Difficulty: low — this is a missing standard assembler feature, not an architectural issue.
Environment
- Branch:
main at 10d5762 Initial CellScript standalone release
- Platform: Linux
- Toolchain: rustc (stable),
cargo run --bin cellc
- No external RISC-V toolchain present (so the internal assembler path is exercised;
try_external_elf_toolchain at src/codegen/mod.rs:4713 returns None).
(Filed by claude code)
Summary
cellc … --target riscv64-elffails for 4 of the 7 bundled examples because the internal assembler rejects conditional branches whose displacement exceeds the RV64 B-type 13-bit signed immediate range (±4 KB). The failure propagates throughcargo test --test examples, sobundled_examples_compile_to_elfis currently red onmain.The bug is independent of
--target-profile— the default (spora), explicit--target-profile spora, and--target-profile ckball trigger the same overflow on any example whose code reaches the internal assembler. CKB profile appears to "work" for some examples only because its target-profile policy gate rejects them before codegen runs.Reproduction
Same results with explicit
--target-profile spora. Under--target-profile ckb,token.cellstill fails with the same5060overflow; the other examples are rejected by the CKB policy gate insrc/lib.rs:465-605before reaching codegen, which masks (but does not fix) the assembler bug.cargo test --test examples -- bundled_examples_compile_to_elfExpected vs. actual
BUNDLED_EXAMPLES(tests/examples.rs:4) compiles to ELF, matching the assertion attests/examples.rs:293.src/codegen/mod.rs::encode_signed_bitswith the 13-bit overflow error.--target riscv64-asmstill works for all seven — only the ELF path is broken.Root cause
RV64 conditional branches (
beq / bne / blt / bge / bltu / bgeu, including thebeqz / bnezaliases) are B-type: 13-bit signed PC-relative immediate, i.e. ±4096 bytes. The internal assembler encodes them directly with no fallback:Because codegen emits validation logic inline — each 32-byte
type_hashcomparison expands into 32lbu/lbu/sub/beqz/<5-line fail epilogue>blocks, and every size/bounds/witness-magic check gets its own inline fail stub — the distance from a conditional branch to a shared exit label (.Lentry_witness_fail_*,.Lentry_witness_done_*, etc.) grows quickly. Inamm_pool.cellthis distance reaches 11748 bytes, ~3× the B-type limit.The standard fix is branch range relaxation: when a B-type target is out of range, rewrite the branch as an inverted short branch plus a J-type jump (which has 21-bit ±1 MB range):
becomes
GAS, LLVM MC, and Keystone all do this automatically. The internal assembler is currently the only path without it.
Secondary issues uncovered
(1) Error message is missing span information
encode_signed_bitsconstructs the error withSpan::default():Result: every failure prints
line 0:, regardless of which assembly line or which source position the branch originated from. The assembler has per-line information available during parsing (ParsedAssembly::from_lines); it should be threaded down intoencode_instruction.(2) CI evidently isn't running
cargo test --test examples(or isn't gating on it)A failing
panic!-based test in the default test suite has landed onmain(commit10d5762 Initial CellScript standalone release). Either CI isn't running this target or the failure is being ignored. Regardless of the branch-relaxation fix, the test-gating policy needs a look.Suggested fix
Two options, in increasing scope:
Option A — minimum viable: implement branch range relaxation
Modify
encode_instructioninsrc/codegen/mod.rs:5274-5282to detect out-of-range B-type branches and emit an inverted-branch-plus-jump pair. Because this changes instruction stream length,SectionLayoutneeds a fixup pass (conservative first pass for symbol addresses, exact second pass for emission). Standard two-pass assembler shape.Estimated effort: ~1 day. Fixes all four failing examples. No semantic change to generated code.
Option B — recommended: share fail-handler routines (eliminates the long branches at source)
The generated assembly currently has 302 copies of the
li a0, <err>; ld ra; ld fp; addi sp; retfail epilogue acrosstoken.salone. Collapsing these into ~8 shared handlers (one per distinct error code) and replacing fail sites with single-instructionj __fail_<code>brings every conditional branch target within a few hundred bytes. Also eliminates the equivalent issue in the 128 inlined byte-compare blocks by factoring them into a__verify_32byte_eq(ptr1, ptr2, err_code)helper.Side effects:
token.s: 4505 → ~2600 lines).j(negligible vs. CKB/Spora cycle budgets).Estimated effort: ~1 week. Recommended as the actual fix because it addresses the underlying codegen smell rather than papering over it in the assembler.
Either way
encode_signed_bits/encode_b_typeso future assembler errors point at the offending line.cargo test --test examplesand failing the build onbundled_examples_compile_to_elfregressions.Impact assessment
cellc <input> --target riscv64-elffor on-chain deployment) fails silently — there is no warning in the README orcellc --helpthat four of the seven example programs can't actually be produced as ELF.--target riscv64-asmstill works, which can mask the problem in casual testing.main. Any downstream consumer runningcargo testsees the failure immediately.Environment
mainat10d5762 Initial CellScript standalone releasecargo run --bin cellctry_external_elf_toolchainatsrc/codegen/mod.rs:4713returnsNone).