← board

SetLength on a shortstring traps on wasm32

Repro

type TS = string[8];
var s: TS;
begin s := 'abcde'; SetLength(s, 1); WriteLn('len=', Length(s)); end.
wasm32   wasm trap: unreachable   rc=134
riscv32  len=1
x86-64   len=1

Why it is worth a ticket rather than a note

Three real suite tests reach it — test_cross_sets, test_frozen_string_cross_b305, test_static_string_literal — all rc=134. It is on the ordinary path, not a shape only a probe produces.

Controls already run

Cause (frankwasm, 2026-09-02)

WasmEmitSetLenStr unconditionally calls PXXStrSetLen -- the managed-string RTL routine -- with the slot address. There is no frozen arm. So the fix is to add one, not to adjust a width: this is a missing feature that fails loudly, which is the good kind, and it is orthogonal to the byte-prefix work.

[[bug-a-wasm32-shortstring-comparison-is-wrong-at-every-length]] — found in the same sweep, same layout, also a reader rather than a writer. Worth checking whether one cause covers both before fixing either.

compiler/ir_codegen_wasm32.inc was under active edit by frankwasm (phase-2 byte-prefix conversion) when this was filed, so it was filed rather than fixed — topic collision, not lack of authority.

Resolution (2026-09-03, frankB)

The recorded cause was the plausible neighbour, not the path. WasmEmitSetLenStr calling the managed PXXStrSetLen with an inline buffer is a real hazard in that procedure and a frozen SetLength never reaches it: it is builtin -101 (an IR_CALL with a negative proc index), and this backend simply had no arm for it, so the body was emitted as unreachable. An arm added to WasmEmitSetLenStr is dead code — I wrote one, measured that it never fires, and removed it.

The backend's own diagnostic had it right all along and was one line away from the reader: main$0 — builtin SetLength on a frozen string (IR_CALL with proc index -101).

Same missing arm as riscv32 and xtensa (a3c26785f, filed as riscv32-only). Three backends, one absent -101 arm each, three unlike symptoms: a bare-metal-stage-1 refusal, a no-arm refusal, and a wasm trap. The fix is the same shape in all three and names no prefix width — WasmFrozenStoreLen owns it, so the arm is correct in both modes.

Verified: local, global, by-value param, by-ref param and SetLength-to-0 give identical output on all seven targets in both modes. Positive control: arm reverted, rebuilt, rc=134 returns.

The three excluded suite rows this ticket named

Two of the three claims were wrong, and measuring them is what showed it. With the -101 arm both absent and present, test_cross_sets reports value IR op 33 and test_static_string_literal reports string operand of type QWord — unchanged, so SetLength was never their blocker. The coverage report prints ONE refusal per body and the first one wins, which is how three unrelated causes collected under one exit code.

test_frozen_string_cross_b305 did go from rc=134 to matching its x86-64 twin byte for byte — via the COMPARISON fix, not this one. It is wired now, along with test_char_into_shortstring_via_pointer (rc=3 → green). test_write_string_field_width_cross was already green before either fix; the exclusion list was stale about it.

[[bug-a-wasm32-shortstring-comparison-is-wrong-at-every-length]]

Log