fix(codegen): load class pointer before offsetting in var-param L-value
EmitLValueAddr handled the leaf TFieldAccessExpr form (no .Base) only
for inline-record and var-record-param cases. For a class field --
where the variable's slot stores a pointer to the heap object -- the
base was emitted as VarRef(...), so a var-param call site emitted
`add &N, offset` instead of `add (loadl N), offset`. Reads through
the var pointer hit the variable's own storage and the write never
reached the field.
EmitInstancePtr already had the symmetric branch (Fld.IsClassAccess ->
load through VarRef); add the missing case to EmitLValueAddr so calls
like `Fill(N.Value, N.IsBig)` reach the heap object.
Regression coverage:
* TVarParamTests.TestCodegen_VarParam_ClassFieldLeaf_LoadsObjectPointer
asserts the IR contains `loadl $N` and no bare `add $N, ...`.
* TE2EClasses2Tests.TestRun_VarParam_ClassFields_WritebackVisible
compiles and runs the documented reproducer and checks stdout
shows 4096 / 1 instead of 0 / 0.
This commit is contained in:
parent
7dbb981665
commit
ca622e258a
|
|
@ -3687,6 +3687,16 @@ begin
|
|||
FldAcc := TFieldAccessExpr(AExpr);
|
||||
if FldAcc.Base <> nil then
|
||||
BaseAddr := EmitInstancePtr(FldAcc.Base)
|
||||
else if FldAcc.IsClassAccess then
|
||||
begin
|
||||
{ Class field leaf: the variable's slot holds a pointer to the heap
|
||||
object — load it so the offset addition reaches the field, not a
|
||||
location adjacent to the slot itself. }
|
||||
T := AllocTemp;
|
||||
EmitLine(Format(' %s =l loadl %s',
|
||||
[T, VarRef(FldAcc.RecordName, FldAcc.IsGlobal)]));
|
||||
BaseAddr := T;
|
||||
end
|
||||
else if FldAcc.IsVarParam then
|
||||
begin
|
||||
{ Var-record param leaf: dereference the param slot. }
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ type
|
|||
procedure TestRun_Supports_ThreeArg_AssignsAndCalls;
|
||||
procedure TestRun_ConstructorOverload_PicksCorrectArity;
|
||||
procedure TestRun_MethodReadsProgramGlobal;
|
||||
procedure TestRun_VarParam_ClassFields_WritebackVisible;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
|
@ -484,6 +485,29 @@ const
|
|||
end.
|
||||
''';
|
||||
|
||||
SrcVarParamClassFields =
|
||||
'''
|
||||
program VarParamClassFields;
|
||||
procedure Fill(var V: Int64; var F: Boolean);
|
||||
begin
|
||||
V := 4096;
|
||||
F := True
|
||||
end;
|
||||
type
|
||||
TNode = class
|
||||
Value: Int64;
|
||||
IsBig: Boolean;
|
||||
end;
|
||||
var N: TNode;
|
||||
begin
|
||||
N := TNode.Create;
|
||||
Fill(N.Value, N.IsBig);
|
||||
WriteLn(N.Value);
|
||||
WriteLn(N.IsBig);
|
||||
N.Free
|
||||
end.
|
||||
''';
|
||||
|
||||
SrcCtorOverloadArity =
|
||||
'''
|
||||
program P;
|
||||
|
|
@ -853,6 +877,19 @@ begin
|
|||
AssertEquals('global read by method', '42' + LE, Output);
|
||||
end;
|
||||
|
||||
procedure TE2EClasses2Tests.TestRun_VarParam_ClassFields_WritebackVisible;
|
||||
var Output: string; RCode: Integer;
|
||||
begin
|
||||
{ Regression for bugs.txt BUG-001 — Fill(N.Value, N.IsBig) where N is a
|
||||
class reference must address into the heap object, not into the storage
|
||||
slot of N itself. Before the fix the writes landed in unrelated memory
|
||||
and the WriteLn calls printed 0 / False. }
|
||||
if not ToolchainAvailable then begin Ignore('toolchain unavailable'); Exit; end;
|
||||
AssertTrue('compile+run', CompileAndRun(SrcVarParamClassFields, Output, RCode));
|
||||
AssertEquals('exit code 0', 0, RCode);
|
||||
AssertEquals('var-param writeback visible through class field', '4096' + LE + '1' + LE, Output);
|
||||
end;
|
||||
|
||||
initialization
|
||||
RegisterTest(TE2EClasses2Tests);
|
||||
|
||||
|
|
|
|||
|
|
@ -57,6 +57,14 @@ type
|
|||
where the parameter is var-typed. }
|
||||
procedure TestSemantic_VarParam_FieldAccess_OK;
|
||||
procedure TestSemantic_VarParam_DerefField_OK;
|
||||
|
||||
{ Var-param call where the actual argument is a CLASS field
|
||||
(C.Field where C is a class reference, not a record). The address
|
||||
must be computed as (loadl C) + offset, not (&C + offset), because
|
||||
the variable holding the class reference stores a pointer to the
|
||||
heap object — adding the offset to the variable's address points
|
||||
at unrelated memory. See BUG-001 in bugs.txt. }
|
||||
procedure TestCodegen_VarParam_ClassFieldLeaf_LoadsObjectPointer;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
|
@ -400,6 +408,37 @@ begin
|
|||
Prog.Free;
|
||||
end;
|
||||
|
||||
procedure TVarParamTests.TestCodegen_VarParam_ClassFieldLeaf_LoadsObjectPointer;
|
||||
const
|
||||
SrcClassFieldVarArg =
|
||||
'''
|
||||
program ClassFieldVar;
|
||||
procedure Fill(var V: Integer);
|
||||
begin V := 4096 end;
|
||||
type
|
||||
TNode = class
|
||||
Pad: Integer;
|
||||
Value: Integer;
|
||||
end;
|
||||
var N: TNode;
|
||||
begin
|
||||
N := TNode.Create;
|
||||
Fill(N.Value)
|
||||
end.
|
||||
''';
|
||||
var
|
||||
IR: string;
|
||||
begin
|
||||
IR := GenIR(SrcClassFieldVarArg);
|
||||
{ N is a global class reference, so the slot is $N and stores a pointer
|
||||
to the heap object. Computing the address of N.Value must first load
|
||||
that pointer; `add $N, <offset>` would point at unrelated memory. }
|
||||
AssertTrue('loads the class pointer from $N before offsetting',
|
||||
Pos('loadl $N', IR) > 0);
|
||||
AssertFalse('must NOT add a field offset to $N (the slot address)',
|
||||
Pos('add $N,', IR) > 0);
|
||||
end;
|
||||
|
||||
initialization
|
||||
RegisterTest(TVarParamTests);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue