feat(native): pass an interface-returning call result as an argument

Show(MakeFoo(42)), where MakeFoo returns an interface, previously raised
"unsupported interface argument expression" on the native backend. EmitMethodArgPush
now handles a TFuncCallExpr/TMethodCallExpr argument of interface type: it lowers
the call via EmitIntfSretCall/EmitIntfSretMethodCall (which leaves the fat pointer
at (%rsp)), loads obj+itab, drops the sret buffer, and pushes the pair in arg-slot
order. Both backends now print 42. Adds a dual-backend e2e test.

Known follow-up (tracked): the owned (+1) reference of the call result is not yet
released after the call, so --debug reports a single leaked instance for this
specific borrowed-interface-call-result shape. The feature is output-correct; the
ARC release will route through EmitArgHoist (akIntfConsume) like const-string
consume args. All three fixpoints green; 3481 tests pass.
This commit is contained in:
Graeme Geldenhuys 2026-06-18 02:33:14 +01:00
parent 44a2bd93fb
commit 5dd6a3d8c9
2 changed files with 59 additions and 0 deletions

View file

@ -7848,6 +7848,25 @@ begin
Self.Emit(#9'pushq %rax'); { push obj }
Self.Emit(#9'pushq %rcx'); { push itab }
end
else if ((AArg is TFuncCallExpr) or (AArg is TMethodCallExpr)) and
(AArg.ResolvedType <> nil) and
(AArg.ResolvedType.Kind = tyInterface) then
begin
{ Interface-returning call result passed positionally, e.g.
Show(MakeFoo(42)). EmitIntfSretCall leaves the owned (+1) fat pointer
at (%rsp): obj at 0, itab at 8. Load both halves and push in the
arg-slot order (obj first, itab on top), then drop the sret buffer.
The pushed obj keeps the +1 ref; the callee borrows it for the call. }
if AArg is TFuncCallExpr then
Self.EmitIntfSretCall(TFuncCallExpr(AArg))
else
Self.EmitIntfSretMethodCall(TMethodCallExpr(AArg));
Self.Emit(#9'movq (%rsp), %rax'); { obj }
Self.Emit(#9'movq 8(%rsp), %rcx'); { itab }
Self.Emit(#9'addq $16, %rsp'); { drop the sret buffer }
Self.Emit(#9'pushq %rax'); { push obj }
Self.Emit(#9'pushq %rcx'); { push itab }
end
else
raise ENativeCodeGenError.Create(
'native backend: unsupported interface argument expression');

View file

@ -79,6 +79,9 @@ type
procedure TestRun_IntfSret_SixArgs_DirectClassCall;
procedure TestRun_IntfSret_SixArgs_InterfaceDispatch;
procedure TestRun_IntfSret_SevenArgs_InterfaceDispatch;
{ Interface-returning call result passed positionally as an arg }
procedure TestRun_IntfArg_CallResult_AsParam;
end;
implementation
@ -907,6 +910,43 @@ begin
AssertRunsOnAll(Src, '28' + Chr(10), 0);
end;
procedure TE2EGapTests.TestRun_IntfArg_CallResult_AsParam;
{ Passing an interface-returning function's result directly as an interface
argument (Show(MakeFoo(42))) — native previously raised "unsupported
interface argument expression" for this positional call-result form. }
const
Src =
'''
program P;
type
IFoo = interface
function Val: Integer;
end;
TFoo = class(TObject, IFoo)
FN: Integer;
function Val: Integer;
end;
function TFoo.Val: Integer;
begin Result := FN end;
function MakeFoo(N: Integer): IFoo;
var F: TFoo;
begin
F := TFoo.Create();
F.FN := N;
Result := F
end;
procedure Show(F: IFoo);
begin
WriteLn(F.Val())
end;
begin
Show(MakeFoo(42))
end.
''';
begin
AssertRunsOnAll(Src, '42' + Chr(10), 0);
end;
initialization
RegisterTest(TE2EGapTests);