feat(native): property reads via getter methods

Emit method calls for TFieldAccessExpr nodes with PropRead set,
covering chained (Base), implicit-Self, and plain variable receivers.
Indexed properties pass the subscript as the second argument (%rsi).
This commit is contained in:
Graeme Geldenhuys 2026-06-10 03:14:47 +01:00
parent 60879d71c5
commit 383d10be4d
2 changed files with 109 additions and 2 deletions

View file

@ -108,6 +108,7 @@ type
AppendUnit order. $main calls <Unit>_init for each, in order, after
_SetArgs — mirrors the QBE backend's EmitMainHeader loop. }
FUnitInitNames: TStringList;
FCurrentUnitName: string;
{ Allocate a fresh local assembly label (".L<prefix><N>"). }
function NewLabel(const APrefix: string): string;
@ -4237,6 +4238,58 @@ begin
Exit;
end;
{ Method-backed property read: Obj.Prop or Obj.Prop[I].
Load the receiver (Self pointer) into %rdi, optional index into %rsi,
then call the getter method. Covers chained (Base <> nil), implicit-Self,
and plain variable receivers. }
if (AExpr is TFieldAccessExpr) and
(TFieldAccessExpr(AExpr).PropRead <> nil) and
(TFieldAccessExpr(AExpr).PropRead.ReadMethod <> '') then
begin
FAE := TFieldAccessExpr(AExpr);
if FAE.Base <> nil then
begin
Self.EmitExprToEax(FAE.Base);
Self.Emit(#9'movq %rax, %rdi');
end
else if FAE.IsImplicitSelf then
begin
Self.Emit(Format(#9'movq %s, %%rdi', [Self.VarOperand('Self')]));
if (FAE.ImplicitBaseInfo <> nil) and (FAE.ImplicitBaseInfo.Offset > 0) then
Self.Emit(Format(#9'addq $%d, %%rdi', [FAE.ImplicitBaseInfo.Offset]));
if FAE.IsClassAccess then
Self.Emit(#9'movq (%rdi), %rdi');
end
else
begin
if Self.IsLocal(FAE.RecordName) then
Self.Emit(Format(#9'movq %s, %%rdi', [Self.VarOperand(FAE.RecordName)]))
else
Self.Emit(Format(#9'movq %s(%%rip), %%rdi', [FAE.RecordName]));
end;
if FAE.FieldInfo <> nil then
begin
if FAE.FieldInfo.Offset > 0 then
Self.Emit(Format(#9'addq $%d, %%rdi', [FAE.FieldInfo.Offset]));
Self.Emit(#9'movq (%rdi), %rdi');
end;
if FAE.PropIndexExpr <> nil then
begin
Self.Emit(#9'pushq %rdi');
Self.EmitExprToEax(FAE.PropIndexExpr);
Self.Emit(#9'popq %rdi');
Self.Emit(#9'movq %rax, %rsi');
end;
Self.Emit(#9'callq ' + Self.ClassSymName(FAE.PropOwnerType) + '_'
+ NativeMangle(FAE.PropRead.ReadMethod));
if (FAE.ResolvedType <> nil) and
not IsIntFamily(FAE.ResolvedType) and
not IsFloatFamily(FAE.ResolvedType) and
(FAE.ResolvedType.Kind <> tyString) then
Self.EmitNarrowToType(FAE.ResolvedType);
Exit;
end;
{ Array field subscript read: R.Arr[I] where Arr is an array-typed field.
Compute the record base address, add FieldInfo.Offset to reach the array
field, then index into the array (static, dynamic, or open). }
@ -4847,8 +4900,8 @@ begin
end;
raise ENativeCodeGenError.Create(
Format('native backend: unsupported expression form %s at line %d col %d',
[AExpr.ClassName, AExpr.Line, AExpr.Col]));
Format('native backend: unsupported expression form %s at line %d col %d in %s',
[AExpr.ClassName, AExpr.Line, AExpr.Col, FCurrentUnitName]));
end;
{ Emit a class method call: load Self into %rdi, then scalar args starting at
@ -8805,6 +8858,7 @@ var
VD: TVarDecl;
UnitSym: TSymbolTable;
begin
FCurrentUnitName := AUnit.Name;
{ Register the unit's global variables (interface + implementation sections)
as data slots, mirroring EmitProgram's program-global registration. }
for I := 0 to AUnit.IntfBlock.Decls.Count - 1 do

View file

@ -264,6 +264,10 @@ type
{ Type-cast on pointer-sized types }
procedure TestRun_Native_TypeCast_PointerClass;
{ Property reads via getter methods }
procedure TestRun_Native_PropertyRead_Simple;
procedure TestRun_Native_PropertyRead_Indexed;
end;
implementation
@ -4104,6 +4108,55 @@ begin
'42' + LE, 0);
end;
procedure TE2ENativeTests.TestRun_Native_PropertyRead_Simple;
begin
if not ToolchainAvailable() then begin Ignore('toolchain unavailable'); Exit; end;
AssertRunsOnBoth(
'program P;'
+ 'type TBox = class'
+ ' FVal: Integer;'
+ ' constructor Create(V: Integer);'
+ ' function GetVal: Integer;'
+ ' property Val: Integer read GetVal;'
+ 'end;'
+ 'constructor TBox.Create(V: Integer);'
+ 'begin FVal := V end;'
+ 'function TBox.GetVal: Integer;'
+ 'begin Result := FVal end;'
+ 'var B: TBox;'
+ 'begin'
+ ' B := TBox.Create(99);'
+ ' WriteLn(B.Val);'
+ ' B.Free '
+ 'end.',
'99' + LE, 0);
end;
procedure TE2ENativeTests.TestRun_Native_PropertyRead_Indexed;
begin
if not ToolchainAvailable() then begin Ignore('toolchain unavailable'); Exit; end;
AssertRunsOnBoth(
'program P;'
+ 'type TArr = class'
+ ' FA: Integer; FB: Integer;'
+ ' constructor Create;'
+ ' function GetItem(I: Integer): Integer;'
+ ' property Items[I: Integer]: Integer read GetItem;'
+ 'end;'
+ 'constructor TArr.Create;'
+ 'begin FA := 10; FB := 30 end;'
+ 'function TArr.GetItem(I: Integer): Integer;'
+ 'begin if I = 0 then Result := FA else Result := FB end;'
+ 'var A: TArr;'
+ 'begin'
+ ' A := TArr.Create;'
+ ' WriteLn(A.Items[0]);'
+ ' WriteLn(A.Items[1]);'
+ ' A.Free '
+ 'end.',
'10' + LE + '30' + LE, 0);
end;
initialization
RegisterTest(TE2ENativeTests);