From 28a40932448f424982fed35d442c550c68fea889 Mon Sep 17 00:00:00 2001 From: Graeme Geldenhuys Date: Thu, 18 Jun 2026 08:12:22 +0100 Subject: [PATCH] fix(native): class-level const array element access (T.Arr[I]) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- .../pascal/blaise.codegen.native.x86_64.pas | 36 +++++++++++++++++++ .../test/pascal/cp.test.e2e.staticarray.pas | 24 ++----------- 2 files changed, 39 insertions(+), 21 deletions(-) diff --git a/compiler/src/main/pascal/blaise.codegen.native.x86_64.pas b/compiler/src/main/pascal/blaise.codegen.native.x86_64.pas index fa3f8fd..880a333 100644 --- a/compiler/src/main/pascal/blaise.codegen.native.x86_64.pas +++ b/compiler/src/main/pascal/blaise.codegen.native.x86_64.pas @@ -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 diff --git a/compiler/src/test/pascal/cp.test.e2e.staticarray.pas b/compiler/src/test/pascal/cp.test.e2e.staticarray.pas index 448136a..97fc049 100644 --- a/compiler/src/test/pascal/cp.test.e2e.staticarray.pas +++ b/compiler/src/test/pascal/cp.test.e2e.staticarray.pas @@ -418,19 +418,10 @@ const T.Free() end. '''; -var Output: string; RCode: Integer; - Lines: TStringList; begin if not ToolchainAvailable() then begin Fail(''); 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(''); 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;