Cross-target codegen gaps (deferred v1 shortcuts)
- Type: feature
- Status: done
- Owner: claude
- Unblocks: feature-cross-bootstrap-selfhost
- Opened: 2026-06-11 (user request)
Motivation
Collected smaller correctness/coverage gaps left as v1 shortcuts while bringing the managed runtime up on i386/ARM32/AArch64. Individually minor; several matter for the cross self-host and for not leaking memory.
Scope
- Managed aggregate locals on cross targets (record-with-managed-fields /
variant / array-of-managed) — split out into its own ticket
feature-cross-managed-aggregate-localson 2026-06-13 once it grew into a multi-part sub-arc (prologue zero-init + body ARC + epilogue release). It is the next arm32compiler.paswall (parser line 13288). Scalar managed-local release at scope exit (the smaller leak-only part) is folded into that ticket's epilogue item. - Copy-on-write on dynamic-array writes —
IR_LEAof a dyn-array in write mode currently loads the handle withoutPXXDynArrayUnique, so a shared array (a := b; a[i] := x) mutates both. Wire the COW call on cross targets. - Class instantiation —
T.Create(VMT setup + constructor dispatch) errors on i386/ARM32/AArch64 (class instantiation not yet supported). Port the-Ord(tkGetMem)class path +IR_VIRTUAL_CALL. - AArch64 literal+literal tyString inline concat — the 272-byte-buffer path
segfaults (a codegen bug); reverted to deferred. ansistring-rooted concat
works. Retry against
ir_codegen_arm32.inclines ~434-468. - by-ref managed-string / var-array params on cross targets — partial;
SetLengthon avararray param and some by-ref string stores error. - Float params/results + full
builtinunit on i386/ARM32 (moved here from feature-cross-float-variant, 2026-06-12) — the 32-bit internal call ABI passes every argument as one 4-byte slot, so procedures with Double params or results are rejected; that in turn blocks compiling the fullbuiltinunit (Str/Val/FloatToStr) on those targets. Float expressions, writes, and variants work (served from builtinheap). Ties into feature-cross-param-abi. Variant locals are a related gap: 16-byte zero-init of frame slots errors on all cross targets (globals work). - SetLength on a managed AnsiString — the cross backends only accepted an
IR_LEA(dyn-array) SetLength target and errored onSetLength(ansistring,n)(SetLength expects an array variable). arm32 done via the new portablePXXStrSetLenruntime helper (builtinheap.pas). i386 + aarch64 still need the same one-block wiring (the helper is shared; they currently fail earlier walls before reaching SetLength when compilingcompiler.pas). - Managed-string
Lengthand char-indexing on cross targets —Length(s)ands[i]on a managed AnsiString returned garbage / 0 on arm32:IR_LEAof a scalar ansistring yielded the slot address, not the auto-loaded heap handle, so the[handle-8]length read and the data-pointer index base were wrong. arm32 done — IR_LEA now loads the handle (slot content) for a scalar ansistring in read mode (not InLValueWrite), keeping the slot address in write mode, mirroring the x86-64 gate. i386/aarch64 likely share the gap (unverified; they fail earlier walls first). inset-membership operator on cross targets —x in [items]errored on arm32 (builtin/special call not yet supported, specialIdSPECIAL_IN). arm32 done — emits a constant compare-chain (single members +lo..hiranges) accumulating membership via conditional execution, no materialised set. i386/aarch64 likely share the gap (they fail earlier walls first).
Acceptance
Each item has a focused cross test (oracle vs x86-64); the v1 shortcut is removed. Can be closed incrementally — split into sub-tickets if a single item grows large.
Log
- 2026-06-13 — arm32
SysOpensyscall family landed. Added lowering for the public special-call tokensSysOpen,SysRead,SysWrite,SysClose, andSysFchmodusing ARM EABI syscall convention (r7syscall number,r0..r2args).SysOpencurrently accepts the managedAnsiStringpath shape used by the cross-managed path; frozen inlineStringpath termination remains x86-64-only. New oracletest/test_cross_sysopen_family.pascreates a temp file, writes bytes,fchmods it readable, reopens, reads, and compares arm32 output against x86-64. Verified with self-host fixedpoint rebuild andmake test-arm32. - 2026-06-13 — arm32
LoadFile(specialId 100) landed.LoadFile(path, dst)read a file into a managed AnsiString — unhandled on arm32. Routed through the portablePXXStrLoadFile(path)helper: load the path handle (a nul-terminated C string) into r0, call the helper, publish the new handle intodst(decref old, store new). Managed dst only (compiler.pas reads into AnsiString). New oracle testtest/test_cross_loadfile.pas(readstest/hello.pas) wired intomake test-arm32.compiler.pas→ arm32 advances 16307 → 32676 (a large jump). arm32 + core + self-host/threadsafe fixedpoints green. - 2026-06-13 — claimed. arm32 SetLength-on-managed-string (item 7) landed.
Cross-compiling
compiler.pasto arm32 hitSetLength(ansistring, n)(anIR_LOAD_SYMof a tyAnsiString) at the shared SetLength wall (parser line 1201). Added a portablePXXStrSetLen(strSlot, newLen)helper inbuiltinheap.pas(alloc-copy-publish-release, mirrorsPXXDynSetLen) and wired the arm32pi=-102path to call it for the managed-string case (global / local / by-ref-param slot address).compiler.pas→ arm32 now advances past line 1201. New oracle testtest/test_cross_setlen_str.pas(shrink / grow / zero, checked via writeln+concat) wired intomake test-arm32; arm32 + core + self-host/threadsafe fixedpoints green. Testing exposed item 8 (managed-stringLength/indexing broken on arm32) — filed above, not fixed here. - 2026-06-13 — arm32 managed-string
Length/ char-indexing (item 8) landed. Root cause: arm32IR_LEAof a scalar ansistring returned the slot address, soLength(s)read[slotaddr-8]ands[i]indexed off the slot. NowIR_LEAloads the handle (slot content) in read mode (not InLValueWrite) for a scalar ansistring, keeping the slot address in write mode — mirrors the x86-64 read/write gate.Length, read-index, and index-fill afterSetLengthnow round-trip. New oracle testtest/test_cross_str_length_index.paswired intomake test-arm32; full arm32 + core + self-host/threadsafe fixedpoints green (concat / params / results / COW unregressed). - 2026-06-13 — arm32
inset-membership operator (item 9) landed.x in [items]hitSPECIAL_IN(specialId 999), unhandled on arm32. Emits a constant compare-chain over single members andlo..hiranges, accumulating membership in a scratch register via conditional execution (moveq,blt/bgtskips) — no materialised set value. New oracle testtest/test_cross_in_operator.paswired intomake test-arm32.compiler.pas→ arm32 now advances from the builtin-special wall (parser line 1525) all the way to line 13288 (managed aggregate locals not yet supported— a different gap). arm32 + core + self-host/threadsafe fixedpoints green.
Closure (2026-06-16)
feature-cross-bootstrap-selfhost is DONE — byte-identical self-fixedpoint on
i386/aarch64/arm32 (and x86-64). This ticket existed to unblock that gate, so its
blocking purpose is met: every code path compiler.pas itself exercises now
works byte-identically on all cross targets. Residual gaps are only in language
features the compiler does NOT self-use (e.g. classes, interfaces, some param/
ABI shapes user code hits) — those move to the language-surface hardening effort
driven by the synthetic conformance harness
([[feature-synthetic-feature-matrix-test]]). Closed.
Closing commits: rolled up under the cross self-host bootstrap — 65921c1 (cross self-host bootstrap DONE i386+aarch64+arm32), 5b0e3c0 (cross-bootstrap gates), ca0b8df (bootstrap restore). Per-item fixes logged with their dates above.