fix(codegen): assign a method pointer to an implicit-Self field
Assigning @Obj.Method to a bare (implicit-Self) method-pointer field —
FFn := @Self.M with no Self. on the left-hand side — was mishandled by
both the QBE and native backends. The assignment emitter routes all
implicit-Self field stores through a dedicated block that returns before
the method-pointer assignment handler runs, so a 16-byte (Code, Data)
method pointer fell through to the scalar-store path:
- QBE stored only the 8-byte pointer to the source block, leaving the
Data half garbage; a later call then dispatched on a bad Self and
crashed.
- native reached the @Obj.Method address-of path, which is statement
level only, and raised "@Obj.Method must be used in assignment
context".
Add a method-pointer case to the implicit-Self field-assignment path in
each backend, mirroring the existing simple-variable and explicit-field
handlers: copy the whole 16-byte block into Self + field offset, with the
Code half resolved through the instance vtable for a virtual/override
method so the dynamic override is captured.
Explicit Self.FFn := @Self.M and the implicit-Self call FFn(...) were
already correct; only the implicit-Self assignment was affected.
Tests: cp.test.proctypes_ofobject gains an IR test asserting the
implicit-Self method-ptr field store emits a 16-byte memcpy;
cp.test.e2e.misc gains an end-to-end test that assigns via implicit Self
then calls back through the field (reading another field via Self, so a
wrong Data half would crash). Both fail before this change and pass
after.
# Conflicts:
# compiler/src/test/pascal/cp.test.e2e.misc.pas
This commit is contained in:
parent
f6c1cee9fe
commit
e2ee936663
|
|
@ -11045,6 +11045,55 @@ begin
|
||||||
(Asgn.ResolvedLhsType.Kind <> tyInterface) then
|
(Asgn.ResolvedLhsType.Kind <> tyInterface) then
|
||||||
begin
|
begin
|
||||||
ISFld := TFieldInfo(Asgn.ImplicitSelfField);
|
ISFld := TFieldInfo(Asgn.ImplicitSelfField);
|
||||||
|
{ Method-pointer field via implicit Self (bare FFn := @Obj.Method): store
|
||||||
|
the [CodePtr, ObjPtr] pair into the field's 16-byte slot. Checked before
|
||||||
|
the ARC/generic stores below, which would route the RHS through
|
||||||
|
EmitExprToEax(@Obj.Method) and raise "must be used in assignment
|
||||||
|
context". Destination base is Self + field offset; the rest mirrors the
|
||||||
|
simple-variable and explicit-field method-ptr assignment cases. }
|
||||||
|
if (ISFld.TypeDesc.Kind = tyProcedural) and
|
||||||
|
TProceduralTypeDesc(ISFld.TypeDesc).IsMethodPtr and
|
||||||
|
(Asgn.Expr is TAddrOfExpr) and
|
||||||
|
(TAddrOfExpr(Asgn.Expr).Expr is TFieldAccessExpr) and
|
||||||
|
(TFieldAccessExpr(TAddrOfExpr(Asgn.Expr).Expr).ResolvedType <> nil) and
|
||||||
|
(TFieldAccessExpr(TAddrOfExpr(Asgn.Expr).Expr).ResolvedType.Kind = tyProcedural) and
|
||||||
|
TProceduralTypeDesc(TFieldAccessExpr(TAddrOfExpr(Asgn.Expr).Expr).ResolvedType).IsMethodPtr then
|
||||||
|
begin
|
||||||
|
FAE := TFieldAccessExpr(TAddrOfExpr(Asgn.Expr).Expr);
|
||||||
|
MD := TMethodDecl(FAE.ResolvedMethod);
|
||||||
|
{ Destination field address = Self + field offset → %rcx }
|
||||||
|
Self.Emit(Format(#9'movq %s, %%rcx', [Self.VarOperand('Self')]));
|
||||||
|
if ISFld.Offset > 0 then
|
||||||
|
Self.Emit(Format(#9'addq $%d, %%rcx', [ISFld.Offset]));
|
||||||
|
{ Store the captured object pointer at offset 8 first — a virtual method
|
||||||
|
reads its code pointer from THIS instance's vtable. }
|
||||||
|
if FAE.Base <> nil then
|
||||||
|
begin
|
||||||
|
Self.Emit(#9'pushq %rcx');
|
||||||
|
Self.EmitExprToEax(FAE.Base);
|
||||||
|
Self.Emit(#9'popq %rcx');
|
||||||
|
Self.Emit(#9'movq %rax, 8(%rcx)');
|
||||||
|
end
|
||||||
|
else
|
||||||
|
begin
|
||||||
|
Self.EmitVarBaseToReg(FAE.RecordName, False, '%rax');
|
||||||
|
Self.Emit(#9'movq %rax, 8(%rcx)');
|
||||||
|
end;
|
||||||
|
{ Store the code pointer at offset 0, vtable-resolved for a
|
||||||
|
virtual/override method so @Obj.M captures the dynamic override (slot 0
|
||||||
|
is typeinfo, method N at (N+1)*8); static label otherwise. }
|
||||||
|
if MD.VTableSlot >= 0 then
|
||||||
|
begin
|
||||||
|
Self.Emit(#9'movq 8(%rcx), %rax');
|
||||||
|
Self.Emit(#9'movq (%rax), %rax');
|
||||||
|
Self.Emit(Format(#9'movq %d(%%rax), %%rax', [(MD.VTableSlot + 1) * 8]));
|
||||||
|
end
|
||||||
|
else
|
||||||
|
Self.Emit(Format(#9'leaq %s(%%rip), %%rax',
|
||||||
|
[MethodEmitNameNative(MD, MD.OwnerTypeName, FAE.FieldName)]));
|
||||||
|
Self.Emit(#9'movq %rax, (%rcx)');
|
||||||
|
Exit;
|
||||||
|
end;
|
||||||
{ ARC-managed implicit-Self field: retain the new value (unless the RHS
|
{ ARC-managed implicit-Self field: retain the new value (unless the RHS
|
||||||
already owns +1) and release the old before overwriting. %r15
|
already owns +1) and release the old before overwriting. %r15
|
||||||
(callee-saved) holds the slot address across the ARC calls. }
|
(callee-saved) holds the slot address across the ARC calls. }
|
||||||
|
|
|
||||||
|
|
@ -4249,6 +4249,19 @@ begin
|
||||||
EmitInterfaceToFieldSlots(AAssign.Expr, ObjTemp, ISAddrT, ISFld.TypeDesc);
|
EmitInterfaceToFieldSlots(AAssign.Expr, ObjTemp, ISAddrT, ISFld.TypeDesc);
|
||||||
Exit;
|
Exit;
|
||||||
end;
|
end;
|
||||||
|
{ Method-pointer field via implicit Self (bare FFn := @Obj.Method): the RHS
|
||||||
|
evaluates to the address of a 16-byte [Code, Data] block (vtable-resolved
|
||||||
|
by EmitAddrOfExpr); copy the whole block into the field slot. Must precede
|
||||||
|
the scalar store below, which would deposit only the 8-byte block pointer
|
||||||
|
and leave the Data half garbage — a later call then dispatches on a bad
|
||||||
|
Self and crashes. Mirrors the simple-variable method-ptr assignment. }
|
||||||
|
if (ISFld.TypeDesc.Kind = tyProcedural) and
|
||||||
|
TProceduralTypeDesc(ISFld.TypeDesc).IsMethodPtr then
|
||||||
|
begin
|
||||||
|
ValTemp := EmitExpr(AAssign.Expr);
|
||||||
|
EmitLine(Format(' call $memcpy(l %s, l %s, l 16)', [ObjTemp, ValTemp]));
|
||||||
|
Exit;
|
||||||
|
end;
|
||||||
ValTemp := EmitExpr(AAssign.Expr);
|
ValTemp := EmitExpr(AAssign.Expr);
|
||||||
QType := QbeTypeOf(ISFld.TypeDesc);
|
QType := QbeTypeOf(ISFld.TypeDesc);
|
||||||
if QType = 'w' then
|
if QType = 'w' then
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,7 @@ type
|
||||||
procedure TestRun_MethodPtrVirtualCapture_Field;
|
procedure TestRun_MethodPtrVirtualCapture_Field;
|
||||||
procedure TestRun_MethodPtrReturn;
|
procedure TestRun_MethodPtrReturn;
|
||||||
procedure TestRun_MethodPtrReturn_ReadsSelf;
|
procedure TestRun_MethodPtrReturn_ReadsSelf;
|
||||||
|
procedure TestRun_MethodPtrImplicitSelfFieldAssign;
|
||||||
{ Unqualified call to a procedural-typed field via implicit Self (FFn(...)
|
{ Unqualified call to a procedural-typed field via implicit Self (FFn(...)
|
||||||
with no 'Self.' prefix), as an expression and as a statement. }
|
with no 'Self.' prefix), as an expression and as a statement. }
|
||||||
procedure TestRun_ImplicitSelfProcField_Expr;
|
procedure TestRun_ImplicitSelfProcField_Expr;
|
||||||
|
|
@ -608,6 +609,36 @@ const
|
||||||
end.
|
end.
|
||||||
''';
|
''';
|
||||||
|
|
||||||
|
{ Assigning @Self.Method to a bare (implicit-Self) method-pointer field:
|
||||||
|
FFn := @Self.DoIt inside a method, with no 'Self.' on the LHS. DoIt reads
|
||||||
|
another field through Self, so a wrong Data half (the bug) would crash or
|
||||||
|
print garbage rather than 'T:x'. }
|
||||||
|
SrcMethodPtrImplicitSelfFieldAssign = '''
|
||||||
|
program Prg;
|
||||||
|
type
|
||||||
|
TProc = procedure(const S: string) of object;
|
||||||
|
TA = class
|
||||||
|
FName: string;
|
||||||
|
FFn: TProc;
|
||||||
|
procedure DoIt(const S: string);
|
||||||
|
procedure Fire;
|
||||||
|
end;
|
||||||
|
procedure TA.DoIt(const S: string);
|
||||||
|
begin WriteLn(FName + ':' + S) end;
|
||||||
|
procedure TA.Fire;
|
||||||
|
begin
|
||||||
|
FFn := @Self.DoIt;
|
||||||
|
FFn('x')
|
||||||
|
end;
|
||||||
|
var A: TA;
|
||||||
|
begin
|
||||||
|
A := TA.Create();
|
||||||
|
A.FName := 'T';
|
||||||
|
A.Fire();
|
||||||
|
A.Free()
|
||||||
|
end.
|
||||||
|
''';
|
||||||
|
|
||||||
{ Unqualified (implicit-Self) call to a procedural-typed field, as an
|
{ Unqualified (implicit-Self) call to a procedural-typed field, as an
|
||||||
expression — FFn(...) with no 'Self.' prefix. }
|
expression — FFn(...) with no 'Self.' prefix. }
|
||||||
SrcImplicitProcFieldExpr = '''
|
SrcImplicitProcFieldExpr = '''
|
||||||
|
|
@ -1028,6 +1059,12 @@ begin
|
||||||
'107' + LE + '105' + LE + '103' + LE + '109' + LE, 0);
|
'107' + LE + '105' + LE + '103' + LE + '109' + LE, 0);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
procedure TE2EMiscTests.TestRun_MethodPtrImplicitSelfFieldAssign;
|
||||||
|
begin
|
||||||
|
if not ToolchainAvailable() then begin Ignore('toolchain unavailable'); Exit; end;
|
||||||
|
AssertRunsOnAll(SrcMethodPtrImplicitSelfFieldAssign, 'T:x' + LE, 0);
|
||||||
|
end;
|
||||||
|
|
||||||
procedure TE2EMiscTests.TestRun_ImplicitSelfProcField_Expr;
|
procedure TE2EMiscTests.TestRun_ImplicitSelfProcField_Expr;
|
||||||
begin
|
begin
|
||||||
if not ToolchainAvailable() then begin Ignore('toolchain unavailable'); Exit; end;
|
if not ToolchainAvailable() then begin Ignore('toolchain unavailable'); Exit; end;
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,7 @@ type
|
||||||
procedure TestCodegen_MethodPtrField_DirectCall_LoadsCodeAndData;
|
procedure TestCodegen_MethodPtrField_DirectCall_LoadsCodeAndData;
|
||||||
procedure TestCodegen_MethodPtrVirtualCapture_LoadsFromVTable;
|
procedure TestCodegen_MethodPtrVirtualCapture_LoadsFromVTable;
|
||||||
procedure TestCodegen_MethodPtrReturn_UsesAggregateABI;
|
procedure TestCodegen_MethodPtrReturn_UsesAggregateABI;
|
||||||
|
procedure TestCodegen_MethodPtrField_ImplicitSelfAssignUsesMemcpy16;
|
||||||
|
|
||||||
{ End-to-end }
|
{ End-to-end }
|
||||||
procedure TestE2E_MethodPtr_NoArgs;
|
procedure TestE2E_MethodPtr_NoArgs;
|
||||||
|
|
@ -764,6 +765,45 @@ begin
|
||||||
StrPos('_ffi__BlaiseMethodPtr', IR) >= 0);
|
StrPos('_ffi__BlaiseMethodPtr', IR) >= 0);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
procedure TProcTypesOfObjectTests.TestCodegen_MethodPtrField_ImplicitSelfAssignUsesMemcpy16;
|
||||||
|
const
|
||||||
|
Src =
|
||||||
|
'''
|
||||||
|
program P;
|
||||||
|
type
|
||||||
|
TProc = procedure(const S: string) of object;
|
||||||
|
TA = class(TObject)
|
||||||
|
FFn: TProc;
|
||||||
|
procedure DoIt(const S: string);
|
||||||
|
procedure Fire;
|
||||||
|
end;
|
||||||
|
procedure TA.DoIt(const S: string);
|
||||||
|
begin WriteLn(S) end;
|
||||||
|
procedure TA.Fire;
|
||||||
|
begin
|
||||||
|
FFn := @Self.DoIt
|
||||||
|
end;
|
||||||
|
var A: TA;
|
||||||
|
begin
|
||||||
|
A := TA.Create();
|
||||||
|
A.Fire();
|
||||||
|
A.Free()
|
||||||
|
end.
|
||||||
|
''';
|
||||||
|
var IR: string;
|
||||||
|
begin
|
||||||
|
IR := GenIR(Src);
|
||||||
|
{ Assigning @Self.Method to a bare (implicit-Self) method-pointer field must
|
||||||
|
copy the whole 16-byte (Code, Data) block into the field slot. The buggy
|
||||||
|
codegen fell through to the scalar-store path and deposited only the 8-byte
|
||||||
|
pointer to the source block, leaving the Data half garbage — a later call
|
||||||
|
then dispatched on a bad Self and crashed. }
|
||||||
|
AssertTrue('implicit-Self method-ptr field assignment emits memcpy',
|
||||||
|
Pos('call $memcpy(', IR) > 0);
|
||||||
|
AssertTrue('memcpy length is 16',
|
||||||
|
Pos(', l 16)', IR) > 0);
|
||||||
|
end;
|
||||||
|
|
||||||
procedure TProcTypesOfObjectTests.TestE2E_MethodPtrField_DirectCall_StmtForm;
|
procedure TProcTypesOfObjectTests.TestE2E_MethodPtrField_DirectCall_StmtForm;
|
||||||
const
|
const
|
||||||
Src =
|
Src =
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue