fix(native): vtable dispatch for virtual method calls in expression context

EmitMethodCallExpr was always emitting a direct `callq` for method calls,
ignoring VTableSlot.  Virtual calls like B.GetVal() (where B's declared
type is an abstract base) produced a direct call to the base's method
symbol, which is never emitted for abstract methods — causing link errors.

Add vtable dispatch (load vtable ptr from Self, index by VTableSlot,
indirect call) in both the <=6 and >6 arg paths of EmitMethodCallExpr,
matching what EmitMethodCallStmt already does.

This resolves the last undefined references (Streams_TInputStream_Read,
Streams_TOutputStream_Write) when native-compiling the compiler itself.
The native-compiled compiler now links and runs.
This commit is contained in:
Graeme Geldenhuys 2026-06-10 04:46:41 +01:00
parent 367a8c0a79
commit 75ab341a2d
2 changed files with 41 additions and 2 deletions

View file

@ -5282,7 +5282,14 @@ begin
for I := UserSlots - 1 downto 0 do
Self.Emit(#9'popq ' + SysVArgRegs64[I + 1]);
Self.Emit(#9'movq %r10, %rdi');
Self.Emit(#9'callq ' + Sym);
if MD.VTableSlot >= 0 then
begin
Self.Emit(#9'movq (%rdi), %rax');
Self.Emit(Format(#9'movq %d(%%rax), %%rax', [(MD.VTableSlot + 1) * 8]));
Self.Emit(#9'callq *%rax');
end
else
Self.Emit(#9'callq ' + Sym);
end
else
begin
@ -5343,7 +5350,14 @@ begin
Self.Emit(Format(#9'movq %d(%%rsp), %s', [I * 8, SysVArgRegs64[I]]));
Self.Emit(Format(#9'addq $%d, %%rsp', [6 * 8]));
Self.Emit(#9'callq ' + Sym);
if MD.VTableSlot >= 0 then
begin
Self.Emit(#9'movq (%rdi), %rax');
Self.Emit(Format(#9'movq %d(%%rax), %%rax', [(MD.VTableSlot + 1) * 8]));
Self.Emit(#9'callq *%rax');
end
else
Self.Emit(#9'callq ' + Sym);
Self.Emit(Format(#9'addq $%d, %%rsp', [CleanUp]));
end;
end;

View file

@ -287,6 +287,9 @@ type
{ Const array data emission + ConstArraySymbol reference }
procedure TestRun_Native_ConstArray_StringElements;
{ Virtual method dispatch in expression context }
procedure TestRun_Native_VirtualDispatch_Expr;
end;
implementation
@ -4330,6 +4333,28 @@ begin
'ax' + LE + 'bx' + LE + 'cx' + LE, 0);
end;
procedure TE2ENativeTests.TestRun_Native_VirtualDispatch_Expr;
begin
AssertRunsOnBoth(
'program T;'
+ 'type '
+ ' TBase = class '
+ ' function GetVal(): Integer; virtual; abstract; '
+ ' end; '
+ ' TChild = class(TBase) '
+ ' function GetVal(): Integer; override; '
+ ' end; '
+ 'function TChild.GetVal(): Integer; '
+ 'begin Result := 42 end; '
+ 'var B: TBase; '
+ 'begin '
+ ' B := TChild.Create; '
+ ' WriteLn(B.GetVal()); '
+ ' B.Free '
+ 'end.',
'42' + LE, 0);
end;
initialization
RegisterTest(TE2ENativeTests);