← board

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