fix(codegen): interface nil-compare and interface-method sret returns
Two interface-dispatch bugs surfaced by Andrew Haines' unify_backend_interface branch, fixed on both backends: 1. Interface value reads through a bare identifier (nil compares, most visibly 'if Result = nil' inside an interface-returning function) loaded a non-existent single slot. Interface idents now load the obj half of the fat pointer: locals via the _obj slot, globals via the Name_obj label, Result via the sret buffer pointer. QBE additionally treats tyInterface as a pointer kind in comparisons (ceql/cnel). The native backend previously compared the sret buffer ADDRESS against nil — always false, silently. 2. V := Intf.Method(...) where Method returns an interface emitted a single-slot store against the split-slot local (invalid QBE IR / wrong native code). Itab-dispatched calls returning interfaces now route through the sret convention like plain function calls: QBE grows EmitIntfSretDispatch; native grows EmitIntfSretMethodCall, wired into every interface-assignment LHS shape. Ownership follows the existing convention (callee AddRefs into the sret buffer, the caller takes the owned +1 pair). Also fixes a latent bug found while testing: assigning an interface-typed Result to a global stored the sret buffer pointer as the obj half (native PushIntfIdentPair now dereferences correctly). Still open, fail-loud (recorded in bugs.txt): TFuncCallExpr receivers of interface calls, and discarded interface-returning itab calls in statement position on the QBE path. IR tests in cp.test.interfaces; e2e tests in cp.test.e2e.imap run the registry-pattern acceptance program on both backends.
This commit is contained in:
parent
81c5804ae4
commit
c20ddd549d
|
|
@ -251,6 +251,12 @@ type
|
|||
globals are two separate .data labels, AName + '_obj'/'_itab'. }
|
||||
function IntfObjOperand(const AName: string; AIsGlobal: Boolean): string;
|
||||
function IntfItabOperand(const AName: string; AIsGlobal: Boolean): string;
|
||||
{ Push the (itab, obj) pair of a named interface ident source — itab
|
||||
first so obj ends on top; leaves obj in %rax for the ARC retain that
|
||||
follows. Handles the sret interface Result, whose slot holds a
|
||||
POINTER to the caller's 16-byte buffer rather than the fat pointer
|
||||
itself (IntfObjOperand alone would push that pointer as the obj). }
|
||||
procedure PushIntfIdentPair(AIdent: TIdentExpr);
|
||||
{ Lower one interface method call (TFieldAccessExpr.IsInterfaceCall or a
|
||||
TMethodCallExpr/Stmt whose ResolvedClassType is tyInterface): load obj +
|
||||
itab, index the itab by method slot, call the loaded pointer with obj as
|
||||
|
|
@ -398,6 +404,12 @@ type
|
|||
buffer (obj+itab) is left on the stack; caller is responsible for loading
|
||||
the slots and cleaning up (addq $16, %rsp). }
|
||||
procedure EmitIntfSretCall(ACall: TFuncCallExpr);
|
||||
{ Interface-method call (itab dispatch) RETURNING an interface: the callee
|
||||
writes the fat pointer through a hidden sret first arg (%rdi), with the
|
||||
receiver obj as the second arg (%rsi). Same caller contract as
|
||||
EmitIntfSretCall: the 16-byte buffer is left at (%rsp); the caller loads
|
||||
the slots and pops it (addq $16, %rsp). }
|
||||
procedure EmitIntfSretMethodCall(ACall: TMethodCallExpr);
|
||||
{ Indirect call: load a bare function pointer from APtrOperand (an AT&T
|
||||
memory operand, e.g. "-8(%rbp)"), set up args as for EmitCall, then
|
||||
dispatch via callq *%r10. AProcType supplies the param list for
|
||||
|
|
@ -2265,12 +2277,7 @@ begin
|
|||
end
|
||||
else if (AAsgn.Expr.ResolvedType.Kind = tyInterface) and (AAsgn.Expr is TIdentExpr) then
|
||||
begin
|
||||
Self.Emit(Format(#9'movq %s, %%rax',
|
||||
[Self.IntfItabOperand(TIdentExpr(AAsgn.Expr).Name, TIdentExpr(AAsgn.Expr).IsGlobal)]));
|
||||
Self.Emit(#9'pushq %rax');
|
||||
Self.Emit(Format(#9'movq %s, %%rax',
|
||||
[Self.IntfObjOperand(TIdentExpr(AAsgn.Expr).Name, TIdentExpr(AAsgn.Expr).IsGlobal)]));
|
||||
Self.Emit(#9'pushq %rax');
|
||||
Self.PushIntfIdentPair(TIdentExpr(AAsgn.Expr));
|
||||
Self.Emit(#9'movq %rax, %rdi');
|
||||
Self.Emit(#9'callq _ClassAddRef');
|
||||
Self.Emit(#9'movq (%r15), %rdi');
|
||||
|
|
@ -2310,6 +2317,21 @@ begin
|
|||
Self.Emit(#9'movq %rax, 8(%r15)');
|
||||
Self.Emit(#9'addq $16, %rsp');
|
||||
end
|
||||
else if (AAsgn.Expr is TMethodCallExpr) and
|
||||
(TMethodCallExpr(AAsgn.Expr).ResolvedClassType <> nil) and
|
||||
(TMethodCallExpr(AAsgn.Expr).ResolvedClassType.Kind = tyInterface) then
|
||||
begin
|
||||
{ Interface-method call (itab dispatch) returning an interface — the
|
||||
callee hands back an OWNED +1 fat pointer in the sret buffer. }
|
||||
Self.EmitIntfSretMethodCall(TMethodCallExpr(AAsgn.Expr));
|
||||
Self.Emit(#9'movq (%r15), %rdi');
|
||||
Self.Emit(#9'callq _ClassRelease');
|
||||
Self.Emit(#9'movq (%rsp), %rax');
|
||||
Self.Emit(#9'movq %rax, (%r15)');
|
||||
Self.Emit(#9'movq 8(%rsp), %rax');
|
||||
Self.Emit(#9'movq %rax, 8(%r15)');
|
||||
Self.Emit(#9'addq $16, %rsp');
|
||||
end
|
||||
else
|
||||
raise ENativeCodeGenError.Create(
|
||||
'native backend: unsupported interface-field assignment RHS');
|
||||
|
|
@ -2354,12 +2376,7 @@ begin
|
|||
end
|
||||
else if (AAsgn.Expr.ResolvedType.Kind = tyInterface) and (AAsgn.Expr is TIdentExpr) then
|
||||
begin
|
||||
Self.Emit(Format(#9'movq %s, %%rax',
|
||||
[Self.IntfItabOperand(TIdentExpr(AAsgn.Expr).Name, TIdentExpr(AAsgn.Expr).IsGlobal)]));
|
||||
Self.Emit(#9'pushq %rax');
|
||||
Self.Emit(Format(#9'movq %s, %%rax',
|
||||
[Self.IntfObjOperand(TIdentExpr(AAsgn.Expr).Name, TIdentExpr(AAsgn.Expr).IsGlobal)]));
|
||||
Self.Emit(#9'pushq %rax');
|
||||
Self.PushIntfIdentPair(TIdentExpr(AAsgn.Expr));
|
||||
Self.Emit(#9'movq %rax, %rdi');
|
||||
Self.Emit(#9'callq _ClassAddRef');
|
||||
Self.Emit(#9'movq (%r15), %rdi');
|
||||
|
|
@ -2369,6 +2386,35 @@ begin
|
|||
Self.Emit(#9'popq %rax');
|
||||
Self.Emit(#9'movq %rax, 8(%r15)');
|
||||
end
|
||||
else if (AAsgn.Expr is TFuncCallExpr) and
|
||||
(TFuncCallExpr(AAsgn.Expr).ResolvedDecl <> nil) and
|
||||
(TMethodDecl(TFuncCallExpr(AAsgn.Expr).ResolvedDecl).ResolvedReturnType <> nil) and
|
||||
(TMethodDecl(TFuncCallExpr(AAsgn.Expr).ResolvedDecl).ResolvedReturnType.Kind = tyInterface) then
|
||||
begin
|
||||
Self.EmitIntfSretCall(TFuncCallExpr(AAsgn.Expr));
|
||||
Self.Emit(#9'movq (%r15), %rdi');
|
||||
Self.Emit(#9'callq _ClassRelease');
|
||||
Self.Emit(#9'movq (%rsp), %rax');
|
||||
Self.Emit(#9'movq %rax, (%r15)');
|
||||
Self.Emit(#9'movq 8(%rsp), %rax');
|
||||
Self.Emit(#9'movq %rax, 8(%r15)');
|
||||
Self.Emit(#9'addq $16, %rsp');
|
||||
end
|
||||
else if (AAsgn.Expr is TMethodCallExpr) and
|
||||
(TMethodCallExpr(AAsgn.Expr).ResolvedClassType <> nil) and
|
||||
(TMethodCallExpr(AAsgn.Expr).ResolvedClassType.Kind = tyInterface) then
|
||||
begin
|
||||
{ Interface-method call (itab dispatch) returning an interface — the
|
||||
callee hands back an OWNED +1 fat pointer in the sret buffer. }
|
||||
Self.EmitIntfSretMethodCall(TMethodCallExpr(AAsgn.Expr));
|
||||
Self.Emit(#9'movq (%r15), %rdi');
|
||||
Self.Emit(#9'callq _ClassRelease');
|
||||
Self.Emit(#9'movq (%rsp), %rax');
|
||||
Self.Emit(#9'movq %rax, (%r15)');
|
||||
Self.Emit(#9'movq 8(%rsp), %rax');
|
||||
Self.Emit(#9'movq %rax, 8(%r15)');
|
||||
Self.Emit(#9'addq $16, %rsp');
|
||||
end
|
||||
else
|
||||
raise ENativeCodeGenError.Create(
|
||||
'native backend: unsupported sret interface Result assignment RHS');
|
||||
|
|
@ -2473,12 +2519,7 @@ begin
|
|||
else
|
||||
begin
|
||||
{ Load src obj+itab; addref new obj; release old obj; store both. }
|
||||
Self.Emit(Format(#9'movq %s, %%rax',
|
||||
[Self.IntfItabOperand(TIdentExpr(AAsgn.Expr).Name, TIdentExpr(AAsgn.Expr).IsGlobal)]));
|
||||
Self.Emit(#9'pushq %rax'); { itab }
|
||||
Self.Emit(Format(#9'movq %s, %%rax',
|
||||
[Self.IntfObjOperand(TIdentExpr(AAsgn.Expr).Name, TIdentExpr(AAsgn.Expr).IsGlobal)]));
|
||||
Self.Emit(#9'pushq %rax'); { obj; stack now (obj, itab) }
|
||||
Self.PushIntfIdentPair(TIdentExpr(AAsgn.Expr));
|
||||
Self.Emit(#9'movq %rax, %rdi');
|
||||
Self.Emit(#9'callq _ClassAddRef');
|
||||
Self.Emit(Format(#9'movq %s, %%rdi', [ObjOp])); { old obj }
|
||||
|
|
@ -2538,6 +2579,24 @@ begin
|
|||
Exit;
|
||||
end;
|
||||
|
||||
{ F := SomeIntf.Method(args) where Method (itab dispatch) returns an
|
||||
interface — same sret protocol; the callee hands back an OWNED +1 fat
|
||||
pointer, so release the old obj and store without an extra AddRef. }
|
||||
if (AAsgn.Expr is TMethodCallExpr) and
|
||||
(TMethodCallExpr(AAsgn.Expr).ResolvedClassType <> nil) and
|
||||
(TMethodCallExpr(AAsgn.Expr).ResolvedClassType.Kind = tyInterface) then
|
||||
begin
|
||||
Self.EmitIntfSretMethodCall(TMethodCallExpr(AAsgn.Expr));
|
||||
Self.Emit(Format(#9'movq %s, %%rdi', [ObjOp])); { old obj }
|
||||
Self.Emit(#9'callq _ClassRelease');
|
||||
Self.Emit(#9'movq (%rsp), %rax');
|
||||
Self.Emit(Format(#9'movq %%rax, %s', [ObjOp]));
|
||||
Self.Emit(#9'movq 8(%rsp), %rax');
|
||||
Self.Emit(Format(#9'movq %%rax, %s', [ItabOp]));
|
||||
Self.Emit(#9'addq $16, %rsp');
|
||||
Exit;
|
||||
end;
|
||||
|
||||
raise ENativeCodeGenError.Create(
|
||||
'native backend: unsupported interface-field assignment RHS');
|
||||
end;
|
||||
|
|
@ -2606,14 +2665,7 @@ begin
|
|||
Self.Emit(#9'pushq %rcx');
|
||||
end
|
||||
else
|
||||
begin
|
||||
Self.Emit(Format(#9'movq %s, %%rax',
|
||||
[Self.IntfItabOperand(TIdentExpr(AExpr).Name, TIdentExpr(AExpr).IsGlobal)]));
|
||||
Self.Emit(#9'pushq %rax');
|
||||
Self.Emit(Format(#9'movq %s, %%rax',
|
||||
[Self.IntfObjOperand(TIdentExpr(AExpr).Name, TIdentExpr(AExpr).IsGlobal)]));
|
||||
Self.Emit(#9'pushq %rax');
|
||||
end;
|
||||
Self.PushIntfIdentPair(TIdentExpr(AExpr));
|
||||
end
|
||||
else if AExpr is TFieldAccessExpr then
|
||||
begin
|
||||
|
|
@ -2829,6 +2881,29 @@ begin
|
|||
Result := AName + '_itab(%rip)';
|
||||
end;
|
||||
|
||||
procedure TX86_64Backend.PushIntfIdentPair(AIdent: TIdentExpr);
|
||||
begin
|
||||
if FSretFunc and SameText(AIdent.Name, 'Result') then
|
||||
begin
|
||||
{ sret interface Result: the slot holds the caller-buffer address —
|
||||
dereference for obj (+0) and itab (+8). }
|
||||
Self.Emit(Format(#9'movq %s, %%rax', [Self.VarOperand('Result')]));
|
||||
Self.Emit(#9'movq 8(%rax), %rcx');
|
||||
Self.Emit(#9'pushq %rcx');
|
||||
Self.Emit(#9'movq (%rax), %rax');
|
||||
Self.Emit(#9'pushq %rax');
|
||||
end
|
||||
else
|
||||
begin
|
||||
Self.Emit(Format(#9'movq %s, %%rax',
|
||||
[Self.IntfItabOperand(AIdent.Name, AIdent.IsGlobal)]));
|
||||
Self.Emit(#9'pushq %rax');
|
||||
Self.Emit(Format(#9'movq %s, %%rax',
|
||||
[Self.IntfObjOperand(AIdent.Name, AIdent.IsGlobal)]));
|
||||
Self.Emit(#9'pushq %rax');
|
||||
end;
|
||||
end;
|
||||
|
||||
{ Load an integer-family value from memory into %rax, extended to the full
|
||||
64-bit register according to AType's width and signedness. Narrower-than-
|
||||
32-bit loads use a sign/zero-extending move; 32-bit signed widens with
|
||||
|
|
@ -4223,6 +4298,24 @@ begin
|
|||
[Self.VarOperand('_cap_' + TIdentExpr(AExpr).Name)]));
|
||||
Self.EmitLoadVar('(%rcx)', Self.IntExprType(AExpr));
|
||||
end
|
||||
else if (TIdentExpr(AExpr).ResolvedType <> nil) and
|
||||
(TIdentExpr(AExpr).ResolvedType.Kind = tyInterface) then
|
||||
begin
|
||||
{ Interface ident used as a single value (nil/identity compare): load
|
||||
the obj half of the fat pointer. An sret Result slot holds a POINTER
|
||||
to the caller's 16-byte buffer — dereference it; locals keep obj at
|
||||
the slot base and globals use the _obj data label (a bare global
|
||||
symbol load would reference a label that does not exist). }
|
||||
if FSretFunc and SameText(TIdentExpr(AExpr).Name, 'Result') then
|
||||
begin
|
||||
Self.Emit(Format(#9'movq %s, %%rcx', [Self.VarOperand('Result')]));
|
||||
Self.Emit(#9'movq (%rcx), %rax');
|
||||
end
|
||||
else
|
||||
Self.Emit(Format(#9'movq %s, %%rax',
|
||||
[Self.IntfObjOperand(TIdentExpr(AExpr).Name,
|
||||
TIdentExpr(AExpr).IsGlobal)]));
|
||||
end
|
||||
else
|
||||
Self.EmitLoadVar(Self.VarOperand(TIdentExpr(AExpr).Name),
|
||||
Self.IntExprType(AExpr));
|
||||
|
|
@ -11942,6 +12035,171 @@ begin
|
|||
HK.Free();
|
||||
end;
|
||||
|
||||
procedure TX86_64Backend.EmitIntfSretMethodCall(ACall: TMethodCallExpr);
|
||||
var
|
||||
I, SlotOff, ArgN: Integer;
|
||||
Arg: TASTExpr;
|
||||
IntfD: TInterfaceTypeDesc;
|
||||
HD: TList<Integer>;
|
||||
HK: TList<Integer>;
|
||||
HTotal, Pushed: Integer;
|
||||
VFlags: string;
|
||||
IE: TIdentExpr;
|
||||
begin
|
||||
IntfD := TInterfaceTypeDesc(ACall.ResolvedClassType);
|
||||
ArgN := 0;
|
||||
if ACall.Args <> nil then ArgN := ACall.Args.Count;
|
||||
{ Same unknown-signature hoist as EmitInterfaceCall; the region sits BELOW
|
||||
the sret buffer (see EmitIntfSretCall for the layout rationale). }
|
||||
HD := TList<Integer>.Create();
|
||||
HK := TList<Integer>.Create();
|
||||
VFlags := IntfD.MethodParamVarFlagsStr(IntfD.MethodIndex(ACall.Name));
|
||||
HTotal := Self.EmitArgHoist(nil, nil, False, VFlags, ACall.Args, HD, HK);
|
||||
{ Allocate and zero the 16-byte sret buffer. }
|
||||
Self.Emit(#9'subq $16, %rsp');
|
||||
Self.Emit(#9'movq $0, (%rsp)');
|
||||
Self.Emit(#9'movq $0, 8(%rsp)');
|
||||
Pushed := 0;
|
||||
{ Push args left-to-right; interface args push obj then itab (2 slots). }
|
||||
for I := 0 to ArgN - 1 do
|
||||
begin
|
||||
Arg := TASTExpr(ACall.Args.Items[I]);
|
||||
if HK.Get(I) >= akRecCall then
|
||||
begin
|
||||
{ Hoisted record-call or string argument — reload the saved value
|
||||
(+16 for the sret buffer between %rsp and the hoist region). }
|
||||
Self.Emit(Format(#9'movq %d(%%rsp), %%rax',
|
||||
[16 + HTotal - HD.Get(I) + Pushed]));
|
||||
Self.Emit(#9'pushq %rax');
|
||||
end
|
||||
else if (Arg.ResolvedType <> nil) and (Arg.ResolvedType.Kind = tyInterface) then
|
||||
begin
|
||||
if Arg is TIdentExpr then
|
||||
begin
|
||||
if TIdentExpr(Arg).IsImplicitSelf and
|
||||
(TIdentExpr(Arg).ImplicitFieldInfo <> nil) then
|
||||
begin
|
||||
Self.Emit(Format(#9'movq %s, %%rax', [Self.VarOperand('Self')]));
|
||||
if TFieldInfo(TIdentExpr(Arg).ImplicitFieldInfo).Offset > 0 then
|
||||
Self.Emit(Format(#9'addq $%d, %%rax',
|
||||
[TFieldInfo(TIdentExpr(Arg).ImplicitFieldInfo).Offset]));
|
||||
Self.Emit(#9'movq (%rax), %rcx');
|
||||
Self.Emit(#9'movq 8(%rax), %rdx');
|
||||
Self.Emit(#9'pushq %rcx');
|
||||
Self.Emit(#9'pushq %rdx');
|
||||
end
|
||||
else
|
||||
begin
|
||||
Self.Emit(Format(#9'movq %s, %%rax',
|
||||
[Self.IntfObjOperand(TIdentExpr(Arg).Name, TIdentExpr(Arg).IsGlobal)]));
|
||||
Self.Emit(Format(#9'movq %s, %%rcx',
|
||||
[Self.IntfItabOperand(TIdentExpr(Arg).Name, TIdentExpr(Arg).IsGlobal)]));
|
||||
Self.Emit(#9'pushq %rax');
|
||||
Self.Emit(#9'pushq %rcx');
|
||||
end;
|
||||
end
|
||||
else if Arg is TFieldAccessExpr then
|
||||
begin
|
||||
Self.EmitInterfaceFieldAddr(TFieldAccessExpr(Arg), '%rax');
|
||||
Self.Emit(#9'movq (%rax), %rcx');
|
||||
Self.Emit(#9'movq 8(%rax), %rdx');
|
||||
Self.Emit(#9'pushq %rcx');
|
||||
Self.Emit(#9'pushq %rdx');
|
||||
end
|
||||
else
|
||||
raise ENativeCodeGenError.Create(
|
||||
'native backend: unsupported interface arg expression in sret interface dispatch');
|
||||
end
|
||||
else
|
||||
begin
|
||||
Self.EmitExprToEax(Arg);
|
||||
Self.Emit(#9'pushq %rax');
|
||||
end;
|
||||
if (HK.Get(I) < akRecCall) and (Arg.ResolvedType <> nil) and
|
||||
(Arg.ResolvedType.Kind = tyInterface) then
|
||||
Pushed := Pushed + 16
|
||||
else
|
||||
Pushed := Pushed + 8;
|
||||
end;
|
||||
{ Resolve the receiver: obj into %r10, itab into %rax. }
|
||||
if (ACall.ObjExpr <> nil) and (ACall.ObjExpr is TFieldAccessExpr) then
|
||||
begin
|
||||
Self.EmitInterfaceFieldAddr(TFieldAccessExpr(ACall.ObjExpr), '%r10');
|
||||
Self.Emit(#9'movq 8(%r10), %rax');
|
||||
Self.Emit(#9'movq (%r10), %r10');
|
||||
end
|
||||
else if (ACall.ObjExpr <> nil) and (ACall.ObjExpr is TIdentExpr) and
|
||||
TIdentExpr(ACall.ObjExpr).IsImplicitSelf and
|
||||
(TIdentExpr(ACall.ObjExpr).ImplicitFieldInfo <> nil) then
|
||||
begin
|
||||
{ Interface field of Self as receiver: fat pointer at Self + offset. }
|
||||
IE := TIdentExpr(ACall.ObjExpr);
|
||||
Self.Emit(Format(#9'movq %s, %%r10', [Self.VarOperand('Self')]));
|
||||
if TFieldInfo(IE.ImplicitFieldInfo).Offset > 0 then
|
||||
Self.Emit(Format(#9'addq $%d, %%r10',
|
||||
[TFieldInfo(IE.ImplicitFieldInfo).Offset]));
|
||||
Self.Emit(#9'movq 8(%r10), %rax');
|
||||
Self.Emit(#9'movq (%r10), %r10');
|
||||
end
|
||||
else if (ACall.ObjExpr <> nil) and (ACall.ObjExpr is TIdentExpr) then
|
||||
begin
|
||||
IE := TIdentExpr(ACall.ObjExpr);
|
||||
Self.Emit(Format(#9'movq %s, %%r10',
|
||||
[Self.IntfObjOperand(IE.Name, IE.IsGlobal)]));
|
||||
Self.Emit(Format(#9'movq %s, %%rax',
|
||||
[Self.IntfItabOperand(IE.Name, IE.IsGlobal)]));
|
||||
end
|
||||
else if ACall.ObjExpr <> nil then
|
||||
raise ENativeCodeGenError.Create(
|
||||
'native backend: unsupported receiver expression in sret interface dispatch')
|
||||
else if ACall.IsVarParam then
|
||||
begin
|
||||
Self.Emit(Format(#9'movq %s, %%r10', [Self.VarOperand(ACall.ObjectName)]));
|
||||
Self.Emit(#9'movq 8(%r10), %rax');
|
||||
Self.Emit(#9'movq (%r10), %r10');
|
||||
end
|
||||
else
|
||||
begin
|
||||
Self.Emit(Format(#9'movq %s, %%r10',
|
||||
[Self.IntfObjOperand(ACall.ObjectName, ACall.IsGlobal)]));
|
||||
Self.Emit(Format(#9'movq %s, %%rax',
|
||||
[Self.IntfItabOperand(ACall.ObjectName, ACall.IsGlobal)]));
|
||||
end;
|
||||
SlotOff := IntfD.MethodIndex(ACall.Name) * 8;
|
||||
if SlotOff = 0 then
|
||||
Self.Emit(#9'movq (%rax), %r11')
|
||||
else
|
||||
Self.Emit(Format(#9'movq %d(%%rax), %%r11', [SlotOff]));
|
||||
{ Pop args shifted by 2: %rdi = sret buffer, %rsi = receiver obj. }
|
||||
SlotOff := 0;
|
||||
for I := 0 to ArgN - 1 do
|
||||
begin
|
||||
Arg := TASTExpr(ACall.Args.Items[I]);
|
||||
if (Arg.ResolvedType <> nil) and (Arg.ResolvedType.Kind = tyInterface) then
|
||||
Inc(SlotOff, 2)
|
||||
else
|
||||
Inc(SlotOff);
|
||||
end;
|
||||
for I := SlotOff - 1 downto 0 do
|
||||
Self.Emit(#9'popq ' + SysVArgRegs64[I + 2]);
|
||||
Self.Emit(#9'movq %r10, %rsi');
|
||||
Self.Emit(#9'movq %rsp, %rdi');
|
||||
Self.Emit(#9'callq *%r11');
|
||||
{ Post-call: release hoisted values, then slide the buffer down over the
|
||||
hoist region so the caller's `fat pointer at (%rsp)` contract holds. }
|
||||
Self.EmitHoistEpilogue(ACall.Args, HD, HK, HTotal, 16, False);
|
||||
if HTotal > 0 then
|
||||
begin
|
||||
Self.Emit(#9'movq (%rsp), %rax');
|
||||
Self.Emit(#9'movq 8(%rsp), %rcx');
|
||||
Self.Emit(Format(#9'addq $%d, %%rsp', [16 + HTotal]));
|
||||
Self.Emit(#9'pushq %rcx');
|
||||
Self.Emit(#9'pushq %rax');
|
||||
end;
|
||||
HD.Free();
|
||||
HK.Free();
|
||||
end;
|
||||
|
||||
{ Emit a method-pointer (of-object) call through a TMethod block.
|
||||
APtrOperand is the AT&T memory operand for the 16-byte Code+Data block.
|
||||
Load Code into %r10, Data into %rdi, push/pop other args, then callq *%r10. }
|
||||
|
|
|
|||
|
|
@ -344,6 +344,11 @@ type
|
|||
the result there instead of returning it. Handles TFuncCallExpr,
|
||||
TMethodCallExpr, and TFieldAccessExpr.IsMethodCall. }
|
||||
procedure EmitRecordCallSret(AExpr: TASTExpr; const ASretAddr: string);
|
||||
{ Interface-dispatched (itab) call whose return type is an interface:
|
||||
the callee writes the fat pointer through a hidden first sret arg.
|
||||
ACall is a TMethodCallExpr (args form) or a TFieldAccessExpr with
|
||||
IsInterfaceCall set (zero-arg form). }
|
||||
procedure EmitIntfSretDispatch(ACall: TASTExpr; const ASretAddr: string);
|
||||
procedure EmitRecordReturnCallSite(const AFuncName, AVisibleArgs: string;
|
||||
ARetType: TRecordTypeDesc;
|
||||
const ADestAddr: string);
|
||||
|
|
@ -3830,6 +3835,42 @@ begin
|
|||
Exit;
|
||||
end;
|
||||
|
||||
{ Interface := call returning an interface — the callee writes the fat
|
||||
pointer (obj+itab) into a caller-supplied 16-byte sret buffer. Load both
|
||||
slots from the buffer and store into the LHS split slots with ARC. This
|
||||
must run BEFORE the interface-to-interface branch below: a zero-arg
|
||||
interface-method call is a TFieldAccessExpr, which that branch would
|
||||
otherwise mistake for a plain field read. }
|
||||
if (AAssign.ResolvedLhsType <> nil) and
|
||||
(AAssign.ResolvedLhsType.Kind = tyInterface) and
|
||||
IsInterfaceCall(AAssign.Expr) then
|
||||
begin
|
||||
SretBuf := AllocTemp();
|
||||
EmitLine(Format(' %s =l alloc8 16', [SretBuf]));
|
||||
EmitLine(Format(' call $memset(l %s, w 0, l 16)', [SretBuf]));
|
||||
EmitRecordCallSret(AAssign.Expr, SretBuf);
|
||||
ObjTemp := AllocTemp();
|
||||
ItabTemp := AllocTemp();
|
||||
EmitLine(Format(' %s =l loadl %s', [ObjTemp, SretBuf]));
|
||||
ValTemp := AllocTemp();
|
||||
EmitLine(Format(' %s =l add %s, 8', [ValTemp, SretBuf]));
|
||||
EmitLine(Format(' %s =l loadl %s', [ItabTemp, ValTemp]));
|
||||
ObjAddr := IntfObjAddr(AAssign.Name, AAssign.IsGlobal, AAssign.IsVarParam);
|
||||
ItabAddr := IntfItabAddr(AAssign.Name, AAssign.IsGlobal, AAssign.IsVarParam);
|
||||
if AAssign.IsWeakLhs then
|
||||
EmitLine(Format(' call $_WeakAssign(l %s, l %s)', [ObjAddr, ObjTemp]))
|
||||
else
|
||||
begin
|
||||
{ The callee returns an OWNED +1 fat pointer — no caller AddRef. }
|
||||
OldTemp := AllocTemp();
|
||||
EmitLine(Format(' %s =l loadl %s', [OldTemp, ObjAddr]));
|
||||
EmitLine(Format(' call $_ClassRelease(l %s)', [OldTemp]));
|
||||
EmitLine(Format(' storel %s, %s', [ObjTemp, ObjAddr]));
|
||||
end;
|
||||
EmitLine(Format(' storel %s, %s', [ItabTemp, ItabAddr]));
|
||||
Exit;
|
||||
end;
|
||||
|
||||
{ Interface-to-interface direct assignment: F := G where both sides are
|
||||
interface-typed. Copy obj and itab from G's fat pointer to F's; for
|
||||
strong F, retain the backing object and release F's prior obj ref;
|
||||
|
|
@ -3894,38 +3935,6 @@ begin
|
|||
Exit;
|
||||
end;
|
||||
|
||||
{ Interface := FuncReturningInterface() — the callee writes the fat pointer
|
||||
(obj+itab) into a caller-supplied 16-byte sret buffer. Load both slots
|
||||
from the buffer and store into the LHS split slots with ARC. }
|
||||
if (AAssign.ResolvedLhsType <> nil) and
|
||||
(AAssign.ResolvedLhsType.Kind = tyInterface) and
|
||||
IsInterfaceCall(AAssign.Expr) then
|
||||
begin
|
||||
SretBuf := AllocTemp();
|
||||
EmitLine(Format(' %s =l alloc8 16', [SretBuf]));
|
||||
EmitLine(Format(' call $memset(l %s, w 0, l 16)', [SretBuf]));
|
||||
EmitRecordCallSret(AAssign.Expr, SretBuf);
|
||||
ObjTemp := AllocTemp();
|
||||
ItabTemp := AllocTemp();
|
||||
EmitLine(Format(' %s =l loadl %s', [ObjTemp, SretBuf]));
|
||||
ValTemp := AllocTemp();
|
||||
EmitLine(Format(' %s =l add %s, 8', [ValTemp, SretBuf]));
|
||||
EmitLine(Format(' %s =l loadl %s', [ItabTemp, ValTemp]));
|
||||
ObjAddr := IntfObjAddr(AAssign.Name, AAssign.IsGlobal, AAssign.IsVarParam);
|
||||
ItabAddr := IntfItabAddr(AAssign.Name, AAssign.IsGlobal, AAssign.IsVarParam);
|
||||
if AAssign.IsWeakLhs then
|
||||
EmitLine(Format(' call $_WeakAssign(l %s, l %s)', [ObjAddr, ObjTemp]))
|
||||
else
|
||||
begin
|
||||
OldTemp := AllocTemp();
|
||||
EmitLine(Format(' %s =l loadl %s', [OldTemp, ObjAddr]));
|
||||
EmitLine(Format(' call $_ClassRelease(l %s)', [OldTemp]));
|
||||
EmitLine(Format(' storel %s, %s', [ObjTemp, ObjAddr]));
|
||||
end;
|
||||
EmitLine(Format(' storel %s, %s', [ItabTemp, ItabAddr]));
|
||||
Exit;
|
||||
end;
|
||||
|
||||
if AAssign.IsVarParam then
|
||||
begin
|
||||
PtrTemp := AllocTemp();
|
||||
|
|
@ -4607,6 +4616,16 @@ begin
|
|||
end
|
||||
else if AExpr is TMethodCallExpr then
|
||||
begin
|
||||
{ Interface dispatch: ResolvedMethod is nil by design (the target is
|
||||
resolved through the itab); the call's own resolved type carries the
|
||||
interface method's return type. }
|
||||
if (TMethodCallExpr(AExpr).ResolvedClassType <> nil) and
|
||||
(TMethodCallExpr(AExpr).ResolvedClassType.Kind = tyInterface) then
|
||||
begin
|
||||
Result := (AExpr.ResolvedType <> nil) and
|
||||
(AExpr.ResolvedType.Kind = tyInterface);
|
||||
Exit;
|
||||
end;
|
||||
if TMethodCallExpr(AExpr).ResolvedMethod = nil then Exit;
|
||||
MDecl := TMethodDecl(TMethodCallExpr(AExpr).ResolvedMethod);
|
||||
Result := (MDecl.ResolvedReturnType <> nil) and
|
||||
|
|
@ -4615,6 +4634,14 @@ begin
|
|||
else if AExpr is TFieldAccessExpr then
|
||||
begin
|
||||
FldA := TFieldAccessExpr(AExpr);
|
||||
{ Zero-arg interface method call through itab dispatch — same rule as
|
||||
the TMethodCallExpr interface-dispatch case above. }
|
||||
if FldA.IsInterfaceCall then
|
||||
begin
|
||||
Result := (AExpr.ResolvedType <> nil) and
|
||||
(AExpr.ResolvedType.Kind = tyInterface);
|
||||
Exit;
|
||||
end;
|
||||
if not FldA.IsMethodCall then Exit;
|
||||
if FldA.ResolvedMethod = nil then Exit;
|
||||
MDecl := TMethodDecl(FldA.ResolvedMethod);
|
||||
|
|
@ -5049,6 +5076,92 @@ begin
|
|||
end;
|
||||
end;
|
||||
|
||||
procedure TCodeGenQBE.EmitIntfSretDispatch(ACall: TASTExpr;
|
||||
const ASretAddr: string);
|
||||
var
|
||||
IntfDesc: TInterfaceTypeDesc;
|
||||
MethName: string;
|
||||
AArgs: TObjectList;
|
||||
MCall: TMethodCallExpr;
|
||||
FldA: TFieldAccessExpr;
|
||||
SelfTemp: string;
|
||||
VTblTemp: string;
|
||||
FPtrTemp: string;
|
||||
ArgTemp: string;
|
||||
ArgLine: string;
|
||||
SlotOff: Integer;
|
||||
I: Integer;
|
||||
begin
|
||||
AArgs := nil;
|
||||
if ACall is TMethodCallExpr then
|
||||
begin
|
||||
MCall := TMethodCallExpr(ACall);
|
||||
IntfDesc := TInterfaceTypeDesc(MCall.ResolvedClassType);
|
||||
MethName := MCall.Name;
|
||||
AArgs := MCall.Args;
|
||||
if MCall.ObjExpr <> nil then
|
||||
{ Receiver is an expression (record/class field, implicit-Self field,
|
||||
as-cast) — resolve its fat pointer rather than split slots. }
|
||||
EmitInterfaceExprPair(MCall.ObjExpr, SelfTemp, VTblTemp)
|
||||
else
|
||||
begin
|
||||
SelfTemp := AllocTemp();
|
||||
EmitLine(Format(' %s =l loadl %s',
|
||||
[SelfTemp, IntfObjAddr(MCall.ObjectName, MCall.IsGlobal, MCall.IsVarParam)]));
|
||||
VTblTemp := AllocTemp();
|
||||
EmitLine(Format(' %s =l loadl %s',
|
||||
[VTblTemp, IntfItabAddr(MCall.ObjectName, MCall.IsGlobal, MCall.IsVarParam)]));
|
||||
end;
|
||||
end
|
||||
else
|
||||
begin
|
||||
{ Zero-arg interface method call: F := G.GetThing — receiver is the
|
||||
named interface local/global/var-param G. }
|
||||
FldA := TFieldAccessExpr(ACall);
|
||||
IntfDesc := TInterfaceTypeDesc(FldA.ResolvedClassType);
|
||||
MethName := FldA.FieldName;
|
||||
SelfTemp := AllocTemp();
|
||||
EmitLine(Format(' %s =l loadl %s',
|
||||
[SelfTemp, IntfObjAddr(FldA.RecordName, FldA.IsGlobal, FldA.IsVarParam)]));
|
||||
VTblTemp := AllocTemp();
|
||||
EmitLine(Format(' %s =l loadl %s',
|
||||
[VTblTemp, IntfItabAddr(FldA.RecordName, FldA.IsGlobal, FldA.IsVarParam)]));
|
||||
end;
|
||||
SlotOff := IntfDesc.MethodIndex(MethName) * 8;
|
||||
FPtrTemp := AllocTemp();
|
||||
if SlotOff = 0 then
|
||||
EmitLine(Format(' %s =l loadl %s', [FPtrTemp, VTblTemp]))
|
||||
else
|
||||
begin
|
||||
ArgTemp := AllocTemp();
|
||||
EmitLine(Format(' %s =l add %s, %d', [ArgTemp, VTblTemp, SlotOff]));
|
||||
EmitLine(Format(' %s =l loadl %s', [FPtrTemp, ArgTemp]));
|
||||
end;
|
||||
{ Callee signature is (sret, Self, args...) — the same shape every
|
||||
interface-returning method body declares (`l %_par__sret, l %_par_Self`). }
|
||||
ArgLine := Format('l %s, l %s', [ASretAddr, SelfTemp]);
|
||||
if AArgs <> nil then
|
||||
begin
|
||||
for I := 0 to AArgs.Count - 1 do
|
||||
begin
|
||||
if IntfDesc.MethodParamIsVar(IntfDesc.MethodIndex(MethName), I) then
|
||||
ArgLine := ArgLine + Format(', l %s',
|
||||
[EmitLValueAddr(TASTExpr(AArgs.Items[I]))])
|
||||
else
|
||||
begin
|
||||
ArgTemp := EmitExpr(TASTExpr(AArgs.Items[I]));
|
||||
if (TASTExpr(AArgs.Items[I]).ResolvedType <> nil) and
|
||||
(TASTExpr(AArgs.Items[I]).ResolvedType.Kind in
|
||||
[tyPointer, tyClass, tyInterface, tyPChar, tyString]) then
|
||||
ArgLine := ArgLine + Format(', l %s', [ArgTemp])
|
||||
else
|
||||
ArgLine := ArgLine + Format(', w %s', [ArgTemp]);
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
EmitLine(Format(' call %s(%s)', [FPtrTemp, ArgLine]));
|
||||
end;
|
||||
|
||||
procedure TCodeGenQBE.EmitRecordCallSret(AExpr: TASTExpr;
|
||||
const ASretAddr: string);
|
||||
var
|
||||
|
|
@ -5116,6 +5229,14 @@ begin
|
|||
else if AExpr is TMethodCallExpr then
|
||||
begin
|
||||
MCallExpr := TMethodCallExpr(AExpr);
|
||||
if (MCallExpr.ResolvedClassType <> nil) and
|
||||
(MCallExpr.ResolvedClassType.Kind = tyInterface) then
|
||||
begin
|
||||
{ Interface-method call through itab dispatch — ResolvedMethod is nil,
|
||||
so the direct-call path below cannot be used. }
|
||||
EmitIntfSretDispatch(MCallExpr, ASretAddr);
|
||||
Exit;
|
||||
end;
|
||||
MDecl := TMethodDecl(MCallExpr.ResolvedMethod);
|
||||
RetType := TRecordTypeDesc(MDecl.ResolvedReturnType);
|
||||
if MCallExpr.ObjExpr <> nil then
|
||||
|
|
@ -5158,6 +5279,12 @@ begin
|
|||
else if AExpr is TFieldAccessExpr then
|
||||
begin
|
||||
FldAccess := TFieldAccessExpr(AExpr);
|
||||
if FldAccess.IsInterfaceCall then
|
||||
begin
|
||||
{ Zero-arg interface method call through itab dispatch. }
|
||||
EmitIntfSretDispatch(FldAccess, ASretAddr);
|
||||
Exit;
|
||||
end;
|
||||
MDecl := TMethodDecl(FldAccess.ResolvedMethod);
|
||||
RetType := TRecordTypeDesc(MDecl.ResolvedReturnType);
|
||||
if FldAccess.IsImplicitSelf then
|
||||
|
|
@ -11022,6 +11149,17 @@ begin
|
|||
{ Aggregate variable — return its storage address directly (no load). }
|
||||
Exit(VarRef(TIdentExpr(AExpr).Name, TIdentExpr(AExpr).IsGlobal));
|
||||
end
|
||||
else if (AExpr.ResolvedType <> nil) and
|
||||
(AExpr.ResolvedType.Kind = tyInterface) then
|
||||
begin
|
||||
{ Interface ident used as a single value (nil/identity compare): the
|
||||
fat pointer lives in split _obj/_itab slots — there is no single
|
||||
%_var_Name slot. Load the obj half; this also covers an interface
|
||||
Result, whose _obj slot holds the sret-buffer address. }
|
||||
EmitLine(Format(' %s =l loadl %s',
|
||||
[T, IntfObjAddr(TIdentExpr(AExpr).Name, TIdentExpr(AExpr).IsGlobal,
|
||||
False)]));
|
||||
end
|
||||
else if not TIdentExpr(AExpr).IsGlobal and
|
||||
IsPromoted(TIdentExpr(AExpr).Name) then
|
||||
begin
|
||||
|
|
@ -11224,9 +11362,9 @@ begin
|
|||
pointer-shaped (class/nil/pointer/metaclass). Identity check only;
|
||||
ordering ops on pointers are not supported. }
|
||||
if ((BinExpr.Left.ResolvedType <> nil) and
|
||||
(BinExpr.Left.ResolvedType.Kind in [tyClass, tyNil, tyPointer, tyMetaClass])) or
|
||||
(BinExpr.Left.ResolvedType.Kind in [tyClass, tyNil, tyPointer, tyMetaClass, tyInterface])) or
|
||||
((BinExpr.Right.ResolvedType <> nil) and
|
||||
(BinExpr.Right.ResolvedType.Kind in [tyClass, tyNil, tyPointer, tyMetaClass])) then
|
||||
(BinExpr.Right.ResolvedType.Kind in [tyClass, tyNil, tyPointer, tyMetaClass, tyInterface])) then
|
||||
begin
|
||||
case BinExpr.Op of
|
||||
boEQ: Op := 'ceql';
|
||||
|
|
|
|||
|
|
@ -31,6 +31,8 @@ type
|
|||
procedure TestRun_IMap_SwapImplementation_SameCallSite;
|
||||
procedure TestRun_IMap_GenericDictInUnit_LinksAndRuns;
|
||||
procedure TestRun_StaticArrayOfInterface_FatPointer;
|
||||
procedure TestRun_InterfaceResult_NilCompare_Registry;
|
||||
procedure TestRun_IntfMethodReturningIntf_ToLocal;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
|
@ -519,6 +521,115 @@ begin
|
|||
Pos('11' + #10 + '22' + #10 + '22' + #10 + '11' + #10 + '11', Output) >= 0);
|
||||
end;
|
||||
|
||||
const
|
||||
{ Registry pattern: function returning an interface with `if Result = nil`
|
||||
inside. The interface Result lives in the caller's sret buffer (split
|
||||
obj/itab) — the nil compare must read the obj half, not a single slot. }
|
||||
SrcIntfResultNilCompare =
|
||||
'''
|
||||
program P;
|
||||
type
|
||||
IDriver = interface
|
||||
function GetVal: Integer;
|
||||
end;
|
||||
TDrv = class(IDriver)
|
||||
function GetVal: Integer;
|
||||
begin
|
||||
Result := 7
|
||||
end;
|
||||
end;
|
||||
var
|
||||
GCache: IDriver;
|
||||
function GetDriver(): IDriver;
|
||||
begin
|
||||
Result := GCache;
|
||||
if Result = nil then
|
||||
begin
|
||||
WriteLn('miss');
|
||||
Result := TDrv.Create();
|
||||
GCache := Result
|
||||
end
|
||||
end;
|
||||
var
|
||||
D: IDriver;
|
||||
begin
|
||||
D := GetDriver();
|
||||
WriteLn(D.GetVal());
|
||||
D := GetDriver();
|
||||
WriteLn(D.GetVal())
|
||||
end.
|
||||
''';
|
||||
|
||||
{ Interface-method call (itab dispatch) returning an interface, assigned to
|
||||
a LOCAL interface var — both with a local interface receiver and with an
|
||||
implicit-Self interface-field receiver. }
|
||||
SrcIntfMethodReturningIntf =
|
||||
'''
|
||||
program P;
|
||||
type
|
||||
IWidget = interface
|
||||
function Tag: Integer;
|
||||
end;
|
||||
IDriver = interface
|
||||
function MakeWidget(N: Integer): IWidget;
|
||||
end;
|
||||
TWidget = class(IWidget)
|
||||
FTag: Integer;
|
||||
function Tag: Integer;
|
||||
begin
|
||||
Result := Self.FTag
|
||||
end;
|
||||
end;
|
||||
TDrv = class(IDriver)
|
||||
function MakeWidget(N: Integer): IWidget;
|
||||
var W: TWidget;
|
||||
begin
|
||||
W := TWidget.Create();
|
||||
W.FTag := N;
|
||||
Result := W
|
||||
end;
|
||||
end;
|
||||
TWorker = class
|
||||
FDriver: IDriver;
|
||||
function Run: Integer;
|
||||
var W: IWidget;
|
||||
begin
|
||||
W := FDriver.MakeWidget(21);
|
||||
Result := W.Tag()
|
||||
end;
|
||||
end;
|
||||
procedure Go();
|
||||
var
|
||||
D: IDriver;
|
||||
W: IWidget;
|
||||
K: TWorker;
|
||||
begin
|
||||
D := TDrv.Create();
|
||||
W := D.MakeWidget(42);
|
||||
WriteLn(W.Tag());
|
||||
K := TWorker.Create();
|
||||
K.FDriver := D;
|
||||
WriteLn(K.Run())
|
||||
end;
|
||||
begin
|
||||
Go()
|
||||
end.
|
||||
''';
|
||||
|
||||
procedure TE2EIMapTests.TestRun_InterfaceResult_NilCompare_Registry;
|
||||
begin
|
||||
if not ToolchainAvailable() then begin Fail('<toolchain-missing>'); Exit end;
|
||||
AssertRunsOnAll(SrcIntfResultNilCompare,
|
||||
'miss' + LineEnding + '7' + LineEnding + '7' + LineEnding, 0);
|
||||
end;
|
||||
|
||||
procedure TE2EIMapTests.TestRun_IntfMethodReturningIntf_ToLocal;
|
||||
begin
|
||||
if not ToolchainAvailable() then begin Fail('<toolchain-missing>'); Exit end;
|
||||
AssertRunsOnAll(SrcIntfMethodReturningIntf,
|
||||
'42' + LineEnding + '21' + LineEnding, 0);
|
||||
end;
|
||||
|
||||
initialization
|
||||
RegisterTest(TE2EIMapTests);
|
||||
|
||||
|
|
|
|||
|
|
@ -115,6 +115,14 @@ type
|
|||
procedure TestSemantic_InterfaceProperty_InheritedFromParent_OK;
|
||||
procedure TestCodegen_InterfacePropertyRead_DispatchesGetter;
|
||||
procedure TestCodegen_InterfacePropertyWrite_DispatchesSetter;
|
||||
|
||||
{ ------------------------------------------------------------------ }
|
||||
{ Regression — interface idents as values; interface-returning }
|
||||
{ interface-method calls }
|
||||
{ ------------------------------------------------------------------ }
|
||||
procedure TestCodegen_InterfaceResult_NilCompare_UsesObjSlot;
|
||||
procedure TestCodegen_InterfaceLocal_NilCompare_UsesObjSlot;
|
||||
procedure TestCodegen_IntfMethodReturningIntf_AssignsToSplitSlots;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
|
@ -1481,6 +1489,144 @@ begin
|
|||
Pos('call %', IR) > 0);
|
||||
end;
|
||||
|
||||
{ ------------------------------------------------------------------ }
|
||||
{ Regression — interface idents as values; interface-returning }
|
||||
{ interface-method calls }
|
||||
{ ------------------------------------------------------------------ }
|
||||
|
||||
const
|
||||
{ Function whose RETURN TYPE is an interface, with `if Result = nil` in the
|
||||
body. An interface Result has no single %_var_Result slot — only the
|
||||
split %_var_Result_obj / %_var_Result_itab pair pointing into the sret
|
||||
buffer — so the nil compare must load the obj half. }
|
||||
SrcInterfaceResultNilCompare =
|
||||
'''
|
||||
program P;
|
||||
type
|
||||
IDriver = interface
|
||||
function GetVal(): Integer;
|
||||
end;
|
||||
TDrv = class(TObject, IDriver)
|
||||
function GetVal(): Integer;
|
||||
end;
|
||||
function TDrv.GetVal(): Integer;
|
||||
begin
|
||||
Result := 7
|
||||
end;
|
||||
function GetDriver(): IDriver;
|
||||
begin
|
||||
Result := nil;
|
||||
if Result = nil then
|
||||
Result := TDrv.Create()
|
||||
end;
|
||||
begin
|
||||
end.
|
||||
''';
|
||||
|
||||
{ Plain interface LOCAL compared against nil — same obj-half rule. }
|
||||
SrcInterfaceLocalNilCompare =
|
||||
'''
|
||||
program P;
|
||||
type
|
||||
IDriver = interface
|
||||
function GetVal(): Integer;
|
||||
end;
|
||||
TDrv = class(TObject, IDriver)
|
||||
function GetVal(): Integer;
|
||||
end;
|
||||
function TDrv.GetVal(): Integer;
|
||||
begin
|
||||
Result := 7
|
||||
end;
|
||||
procedure Run();
|
||||
var
|
||||
D: IDriver;
|
||||
begin
|
||||
D := nil;
|
||||
if D = nil then
|
||||
D := TDrv.Create()
|
||||
end;
|
||||
begin
|
||||
Run()
|
||||
end.
|
||||
''';
|
||||
|
||||
{ Interface-method call (itab dispatch) RETURNING an interface, assigned to
|
||||
a LOCAL interface var. The callee writes the fat pointer through a hidden
|
||||
sret arg; the caller must store obj/itab into the split slots — never a
|
||||
single `storel %t, %_var_W`. }
|
||||
SrcIntfMethodCallReturnsInterface =
|
||||
'''
|
||||
program P;
|
||||
type
|
||||
IWidget = interface
|
||||
function Tag(): Integer;
|
||||
end;
|
||||
IDriver = interface
|
||||
function MakeWidget(N: Integer): IWidget;
|
||||
end;
|
||||
TWidget = class(TObject, IWidget)
|
||||
function Tag(): Integer;
|
||||
end;
|
||||
TDrv = class(TObject, IDriver)
|
||||
function MakeWidget(N: Integer): IWidget;
|
||||
end;
|
||||
function TWidget.Tag(): Integer;
|
||||
begin
|
||||
Result := 1
|
||||
end;
|
||||
function TDrv.MakeWidget(N: Integer): IWidget;
|
||||
begin
|
||||
Result := TWidget.Create()
|
||||
end;
|
||||
procedure Run();
|
||||
var
|
||||
D: IDriver;
|
||||
W: IWidget;
|
||||
begin
|
||||
D := TDrv.Create();
|
||||
W := D.MakeWidget(3)
|
||||
end;
|
||||
begin
|
||||
Run()
|
||||
end.
|
||||
''';
|
||||
|
||||
procedure TInterfaceTests.TestCodegen_InterfaceResult_NilCompare_UsesObjSlot;
|
||||
var
|
||||
IR: string;
|
||||
begin
|
||||
IR := GenIR(SrcInterfaceResultNilCompare);
|
||||
AssertTrue('nil compare loads the obj half of Result',
|
||||
Pos('loadl %_var_Result_obj', IR) > 0);
|
||||
AssertFalse('must not load a non-existent single %_var_Result slot',
|
||||
Pos('loadl %_var_Result' + #10, IR) > 0);
|
||||
end;
|
||||
|
||||
procedure TInterfaceTests.TestCodegen_InterfaceLocal_NilCompare_UsesObjSlot;
|
||||
var
|
||||
IR: string;
|
||||
begin
|
||||
IR := GenIR(SrcInterfaceLocalNilCompare);
|
||||
AssertTrue('nil compare loads the obj half of the local',
|
||||
Pos('loadl %_var_D_obj', IR) > 0);
|
||||
AssertFalse('must not load a non-existent single %_var_D slot',
|
||||
Pos('loadl %_var_D' + #10, IR) > 0);
|
||||
end;
|
||||
|
||||
procedure TInterfaceTests.TestCodegen_IntfMethodReturningIntf_AssignsToSplitSlots;
|
||||
var
|
||||
IR: string;
|
||||
begin
|
||||
IR := GenIR(SrcIntfMethodCallReturnsInterface);
|
||||
AssertTrue('obj half stored into the split obj slot',
|
||||
Pos(', %_var_W_obj', IR) > 0);
|
||||
AssertTrue('itab half stored into the split itab slot',
|
||||
Pos(', %_var_W_itab', IR) > 0);
|
||||
AssertFalse('must not store into a non-existent single %_var_W slot',
|
||||
Pos(', %_var_W' + #10, IR) > 0);
|
||||
end;
|
||||
|
||||
initialization
|
||||
RegisterTest(TInterfaceTests);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue