fix(native): class-level const array element access (T.Arr[I])

A class-level const array (const Days: array[0..6] of string = (...)) read via
an instance, T.Days[I], returned an empty string on native. The semantic pass
folds the subscript into the field-access node — IsConstant=True with
ConstArraySymbol (the global data label), ConstArrayType (the static-array type)
and PropIndexExpr (the index) — but native's EmitExprToEax IsConstant branch only
handled scalar string/int consts and fell through to an empty string literal.

Added the class-const-array element path: compute
base + (idx - LowBound) * ElemSize and load the element by type (float / string
pointer / narrowed integer), plus a bare-reference case returning the label
address. Mirrors the QBE EmitExpr ConstArraySymbol path. The range- and
enum-indexed class-const-array e2e tests are converted to AssertRunsOnAll.

(Static-array-of-interface element store/read/dispatch remains a separate, larger
follow-up — tracked in bugs.txt; that test stays QBE-only meanwhile.)

All three fixpoints green; 3481 tests pass.
This commit is contained in:
Graeme Geldenhuys 2026-06-18 08:12:22 +01:00
parent 5c1293f268
commit 28a4093244
2 changed files with 39 additions and 21 deletions

View file

@ -4830,6 +4830,7 @@ var
SuppOut: string;
LSuppNo: string;
LSuppEnd: string;
SCAT: TStaticArrayTypeDesc;
begin
if AExpr is TNilLiteral then
begin
@ -6477,6 +6478,41 @@ begin
if (AExpr is TFieldAccessExpr) and TFieldAccessExpr(AExpr).IsConstant then
begin
FAE := TFieldAccessExpr(AExpr);
{ Class-level const ARRAY element access (T.Days[I]): semantic folds the
subscript into the field-access node, setting ConstArraySymbol (the global
data label), ConstArrayType (the static-array type) and PropIndexExpr (the
index). Compute base + (idx - LowBound)*ElemSize and load mirrors the
QBE EmitSupportsExpr/IsConstant ConstArraySymbol path. Without this the
access fell through to the scalar-string branch and read an empty literal. }
if (FAE.ConstArraySymbol <> '') and (FAE.PropIndexExpr <> nil) and
(FAE.ConstArrayType <> nil) then
begin
SCAT := TStaticArrayTypeDesc(FAE.ConstArrayType);
Self.EmitExprToEax(FAE.PropIndexExpr); { index -> %rax }
if SCAT.LowBound <> 0 then
Self.Emit(Format(#9'subq $%d, %%rax', [SCAT.LowBound]));
Self.Emit(Format(#9'imulq $%d, %%rax', [SCAT.ElementType.RawSize()]));
Self.Emit(Format(#9'leaq %s(%%rip), %%rcx',
[NativeMangle(FAE.ConstArraySymbol)]));
Self.Emit(#9'addq %rcx, %rax');
if (SCAT.ElementType <> nil) and IsFloatFamily(SCAT.ElementType) then
Self.EmitLoadFloat('(%rax)', SCAT.ElementType)
else if (SCAT.ElementType <> nil) and SCAT.ElementType.IsString() then
Self.Emit(#9'movq (%rax), %rax')
else
begin
Self.Emit(#9'movq (%rax), %rax');
Self.EmitNarrowToType(SCAT.ElementType);
end;
Exit;
end;
{ Bare class-const array reference (no subscript): the data label address. }
if FAE.ConstArraySymbol <> '' then
begin
Self.Emit(Format(#9'leaq %s(%%rip), %%rax',
[NativeMangle(FAE.ConstArraySymbol)]));
Exit;
end;
if (FAE.ResolvedType <> nil) and FAE.ResolvedType.IsString() then
Self.EmitStrLitAddr(FAE.ConstString)
else

View file

@ -418,19 +418,10 @@ const
T.Free()
end.
''';
var Output: string; RCode: Integer;
Lines: TStringList;
begin
if not ToolchainAvailable() then begin Fail('<toolchain-missing>'); Exit end;
AssertTrue('compile+run', CompileAndRun(Src, Output, RCode));
AssertEquals('exit 0', 0, RCode);
Lines := TStringList.Create();
try
Lines.Text := Trim(Output);
AssertEquals('Days[0]', 'Sun', Lines.Strings[0]);
AssertEquals('Days[1]', 'Mon', Lines.Strings[1]);
AssertEquals('Days[5]', 'Fri', Lines.Strings[2]);
finally Lines.Free() end
AssertRunsOnAll(Src,
'Sun' + Chr(10) + 'Mon' + Chr(10) + 'Fri' + Chr(10), 0);
end;
procedure TE2EStaticArrayTests.TestRun_ClassConstArray_EnumIndexed;
@ -452,18 +443,9 @@ const
P2.Free()
end.
''';
var Output: string; RCode: Integer;
Lines: TStringList;
begin
if not ToolchainAvailable() then begin Fail('<toolchain-missing>'); Exit end;
AssertTrue('compile+run', CompileAndRun(Src, Output, RCode));
AssertEquals('exit 0', 0, RCode);
Lines := TStringList.Create();
try
Lines.Text := Trim(Output);
AssertEquals('Names[0]', 'Red', Lines.Strings[0]);
AssertEquals('Names[2]', 'Blue', Lines.Strings[1]);
finally Lines.Free() end
AssertRunsOnAll(Src, 'Red' + Chr(10) + 'Blue' + Chr(10), 0);
end;
procedure TE2EStaticArrayTests.TestRun_DynArray_High_ReturnsLengthMinusOne;