fix(native): stage-stable char-literal byte RHS in string subscript write

EmitByteRhsToEax lowered a single-character string-literal RHS via
Ord(TStringLiteral(AExpr).Value[0]).  That subscript-then-Ord idiom is
miscompiled by the self-hosted native stage: a native-built compiler baked a
pointer-sized garbage immediate (movl $0xd291f9ec) instead of the byte, so
S[i] := 'c' wrote junk.  It only surfaced in CI, where the test runner is
built by the native-bootstrapped -pre binary; local verification used the
QBE-built fixpoint binary, which lowers the idiom correctly, so the bug was
invisible.

Use StrAt(Value, 0) — the OrdAt-backed byte-read helper already used
elsewhere in this backend and stage-stable — instead of Ord(Value[0]).
Matches the QBE backend, which uses StrAt and was never affected.

Verified by building the test runner with a native-built compiler (the CI
configuration): full suite passes; all three fixpoints clean.
This commit is contained in:
Graeme Geldenhuys 2026-06-18 15:55:33 +01:00
parent 07aac3322f
commit e9f705fdc8

View file

@ -3899,8 +3899,13 @@ begin
if (AExpr is TStringLiteral) and
(Length(TStringLiteral(AExpr).Value) = 1) then
begin
{ Use StrAt (OrdAt) rather than Ord(Value[0]): the bare subscript-then-Ord
idiom miscompiles under the self-hosted native stage (it produced a
pointer-sized garbage immediate, surfacing as a CI-only failure of the
S[i] := 'c' test), whereas StrAt is the stage-stable byte-read helper
used everywhere else in this backend. }
Self.Emit(Format(#9'movl $%d, %%eax',
[Ord(TStringLiteral(AExpr).Value[0])]));
[StrAt(TStringLiteral(AExpr).Value, 0)]));
Exit;
end;
Self.EmitExprToEax(AExpr);