xtensa variant-record arms start at different offsets
program VO;
type TVar = record Tag: Integer; case Integer of 0: (A: Int64); 1: (L, H: Integer); end;
var r: TVar;
begin
WriteLn('A@', PtrUInt(@r.A) - PtrUInt(@r), ' L@', PtrUInt(@r.L) - PtrUInt(@r), ' size=', SizeOf(r));
end.
| target | output |
|---|---|
| x86-64 | A@8 L@8 size=16 |
| i386 | A@4 L@4 size=12 |
xtensa (--platform=posix --xtensa-soft-mulhigh) |
A@8 L@4 size=16 |
Every arm of a variant part must begin at the same offset. On xtensa, a value
stored through A and read through L/H (or the reverse) reads the wrong
bytes. Programs overlay variant arms on purpose, so this produces wrong values.
Found while differentially checking typed-const record baking (bug-a-a-typed-const-record-is-built-by-startup-code-not-stored-as-data): the baked image was right and matched the layout table. The layout table itself is what differs.
Test to add with the fix: assert the RELATION @r.A = @r.L rather than a
per-target offset, so the row carries no expected width.
What it was (2026-09-19)
One line, and the comment above it already stated the premise that was false:
variantBaseOff := AlignTo(curOff, TARGET_PTR_SIZE);
Laid out from a base that satisfies EVERY alignment, then shifted back down to the alignment the branches actually need...
TARGET_PTR_SIZE is 4 on xtensa, arm32 and riscv32, and TypeFieldAlign(tyInt64)
is 8 on all three. So the base satisfied every alignment on exactly the two
targets anybody measures on, and the shift that was supposed to fix up the rest
could not run: vDelta := variantBaseOff - vNewBase came out -4, and the
guard is if vDelta > 0. A negative delta is the signature of the base being
under-aligned, and it was discarded silently.
The probable mechanism named in the original ticket was right -- "the
arm-start alignment and the Int64 field placement consult different alignment
answers" -- and it was filed as unverified. It is verified now and it is
narrower than that: they consult the same answer, TypeFieldAlign, for the
FIELDS, and a different one, TARGET_PTR_SIZE, for the arm START.
Scope, measured rather than assumed
| target | before | after |
|---|---|---|
| x86-64 | A@8 L@8 size=16 |
unchanged |
| i386 | A@4 L@4 size=12 |
unchanged |
| aarch64 | A@8 L@8 size=16 |
unchanged |
| xtensa | A@8 L@4 size=16 |
A@8 L@8 size=16 |
| arm32 | A@8 L@4 size=16 |
A@8 L@8 size=16 |
| riscv32 | A@8 L@4 size=16 |
A@8 L@8 size=16 |
The ticket named xtensa because that is where it was found. Running the same probe across all six targets before touching anything is what turned one target into three -- and arm32 and riscv32 are not ESP targets, so nobody looking at this as an ESP bug would have swept them.
Log
- 2026-09-19 — resolved; this names the commit that carried the resolve, which is not always the one that carried the change — commit 823bdad3d.