feat(native): TIsExpr, TAsExpr, TSupportsExpr + ClassSymName

Implement `X is TFoo` (class type test via _IsInstance), `X as TFoo`
(checked downcast with _Raise_InvalidCast on failure), and
`Supports(Obj, IFoo)` (interface query via _ImplementsInterface).

Add ClassSymName helper that resolves a class/interface name to its
assembly symbol with unit prefix (e.g. Exception → SysUtils_Exception),
mirroring the QBE backend's ClassSymName. Fix existing as-cast and
except-handler typeinfo references to use ClassSymName instead of bare
NativeMangle(TypeName).
This commit is contained in:
Graeme Geldenhuys 2026-06-10 00:26:14 +01:00
parent 54b19e6fee
commit 43d9d702f0
2 changed files with 149 additions and 3 deletions

View file

@ -160,6 +160,10 @@ type
AGenericInstances: TObjectList;
AGenericRecordInstances: TObjectList);
{ Resolve a class/interface name to its assembly symbol form, adding the
unit prefix when the type is defined in a non-system unit. Mirrors the
QBE backend's ClassSymName logic. }
function ClassSymName(const AClassName: string): string;
{ True when AName is a slot in the current function frame. }
function IsLocal(const AName: string): Boolean;
{ The AT&T operand addressing AName: "-N(%rbp)" for a frame local,
@ -796,6 +800,38 @@ begin
end;
end;
function TX86_64Backend.ClassSymName(const AClassName: string): string;
var
Sym: TSymbol;
Owner: string;
I: Integer;
Ch: string;
begin
Result := '';
if FSymTable <> nil then
begin
Sym := FSymTable.Lookup(AClassName);
if Sym <> nil then
begin
Owner := Sym.OwningUnit;
if (Owner <> '') and
not SameText(Owner, 'System') and
not ((Length(Owner) >= 4) and SameText(Copy(Owner, 0, 4), 'rtl.')) and
not ((Length(Owner) >= 7) and SameText(Copy(Owner, 0, 7), 'blaise_')) then
begin
for I := 0 to Length(Owner) - 1 do
begin
Ch := Copy(Owner, I, 1);
if Ch = '.' then Result := Result + '_'
else Result := Result + Ch;
end;
Result := Result + '_';
end;
end;
end;
Result := Result + NativeMangle(AClassName);
end;
function FindMethodInClassDef(AClassDef: TClassTypeDef;
const AName: string): TMethodDecl;
var
@ -1729,7 +1765,7 @@ begin
Self.EmitExprToEax(AE.Obj); { obj -> %rax }
Self.Emit(#9'pushq %rax'); { keep obj on the stack across calls }
Self.Emit(#9'movq %rax, %rdi');
Self.Emit(Format(#9'leaq typeinfo_%s(%%rip), %%rsi', [NativeMangle(AE.TypeName)]));
Self.Emit(Format(#9'leaq typeinfo_%s(%%rip), %%rsi', [Self.ClassSymName(AE.TypeName)]));
Self.Emit(#9'callq _GetItab'); { itab -> %rax }
Self.Emit(#9'pushq %rax'); { keep itab; stack now (itab, obj) }
LFail := Self.NewLabel('as_fail');
@ -3709,6 +3745,59 @@ begin
Exit;
end;
{ X is TFoo — class type test via _IsInstance / _ImplementsInterface.
Result: 0/1 in %eax. }
if AExpr is TIsExpr then
begin
Self.EmitExprToEax(TIsExpr(AExpr).Obj);
Self.Emit(#9'movq %rax, %rdi');
if (TIsExpr(AExpr).ResolvedTargetType <> nil) and
(TIsExpr(AExpr).ResolvedTargetType.Kind = tyInterface) then
begin
Self.Emit(Format(#9'leaq typeinfo_%s(%%rip), %%rsi',
[Self.ClassSymName(TIsExpr(AExpr).TypeName)]));
Self.Emit(#9'callq _ImplementsInterface');
end
else
begin
Self.Emit(Format(#9'leaq typeinfo_%s(%%rip), %%rsi',
[Self.ClassSymName(TIsExpr(AExpr).TypeName)]));
Self.Emit(#9'callq _IsInstance');
end;
Exit;
end;
{ X as TFoo — checked downcast. Calls _IsInstance; raises EInvalidCast on
failure; result = original object pointer (class-to-class). }
if AExpr is TAsExpr then
begin
ScEndLbl := Self.NewLabel('as_ok');
Self.EmitExprToEax(TAsExpr(AExpr).Obj);
Self.Emit(#9'pushq %rax');
Self.Emit(#9'movq %rax, %rdi');
Self.Emit(Format(#9'leaq typeinfo_%s(%%rip), %%rsi',
[Self.ClassSymName(TAsExpr(AExpr).TypeName)]));
Self.Emit(#9'callq _IsInstance');
Self.Emit(#9'testl %eax, %eax');
Self.Emit(#9'jnz ' + ScEndLbl);
Self.Emit(#9'callq _Raise_InvalidCast');
Self.Emit(ScEndLbl + ':');
Self.Emit(#9'popq %rax');
Exit;
end;
{ Supports(Obj, IFoo) — interface query via _ImplementsInterface.
Result: 0/1 in %eax. }
if AExpr is TSupportsExpr then
begin
Self.EmitExprToEax(TSupportsExpr(AExpr).Obj);
Self.Emit(#9'movq %rax, %rdi');
Self.Emit(Format(#9'leaq typeinfo_%s(%%rip), %%rsi',
[Self.ClassSymName(TSupportsExpr(AExpr).IntfTypeName)]));
Self.Emit(#9'callq _ImplementsInterface');
Exit;
end;
raise ENativeCodeGenError.Create(
'native backend: unsupported expression form ' + AExpr.ClassName);
end;
@ -3867,7 +3956,7 @@ begin
Self.Emit(#9'pushq %rax'); { save obj for push below }
Self.Emit(#9'movq %rax, %rdi');
Self.Emit(Format(#9'leaq typeinfo_%s(%%rip), %%rsi',
[NativeMangle(TAsExpr(AArg).TypeName)]));
[Self.ClassSymName(TAsExpr(AArg).TypeName)]));
Self.Emit(#9'callq _GetItab');
Self.Emit(#9'pushq %rax'); { itab on top; obj below }
{ Stack top: itab, below: obj — but we need obj first (lower slot).
@ -5049,7 +5138,7 @@ begin
{ _IsInstance(obj, typeinfo): returns non-zero if obj is an instance of the type. }
Self.Emit(#9'movq %r15, %rdi');
Self.Emit(#9'leaq typeinfo_' + H.TypeName + '(%rip), %rsi');
Self.Emit(#9'leaq typeinfo_' + Self.ClassSymName(H.TypeName) + '(%rip), %rsi');
Self.Emit(#9'callq _IsInstance');
Self.Emit(#9'testl %eax, %eax');
Self.Emit(#9'jnz ' + LblBody);

View file

@ -230,6 +230,9 @@ type
procedure TestRun_Native_ShortCircuit_OrSkipsRhs;
procedure TestRun_Native_ShortCircuit_AndNilGuard;
procedure TestRun_Native_ProceduralParam;
procedure TestRun_Native_IsExpr_Class;
procedure TestRun_Native_AsExpr_Class;
procedure TestRun_Native_SupportsExpr;
end;
implementation
@ -3725,6 +3728,60 @@ begin
'42' + LE, 0);
end;
procedure TE2ENativeTests.TestRun_Native_IsExpr_Class;
begin
if not ToolchainAvailable() then begin Ignore('toolchain unavailable'); Exit; end;
AssertRunsOnBoth(
'program P;'
+ 'type'
+ ' TBase = class public N: Integer; end;'
+ ' TChild = class(TBase) public S: string; end;'
+ 'var Obj: TBase;'
+ 'begin'
+ ' Obj := TChild.Create();'
+ ' if Obj is TChild then WriteLn(''child-yes'') else WriteLn(''child-no'');'
+ ' if Obj is TBase then WriteLn(''base-yes'') else WriteLn(''base-no'');'
+ ' Obj := TBase.Create();'
+ ' if Obj is TChild then WriteLn(''child-yes'') else WriteLn(''child-no'')'
+ 'end.',
'child-yes' + LE + 'base-yes' + LE + 'child-no' + LE, 0);
end;
procedure TE2ENativeTests.TestRun_Native_AsExpr_Class;
begin
if not ToolchainAvailable() then begin Ignore('toolchain unavailable'); Exit; end;
AssertRunsOnBoth(
'program P;'
+ 'type'
+ ' TBase = class public N: Integer; end;'
+ ' TChild = class(TBase) public S: string; end;'
+ 'var B: TBase; C: TChild;'
+ 'begin'
+ ' B := TChild.Create();'
+ ' TChild(B).S := ''hello'';'
+ ' C := TChild(B as TChild);'
+ ' WriteLn(C.S)'
+ 'end.',
'hello' + LE, 0);
end;
procedure TE2ENativeTests.TestRun_Native_SupportsExpr;
begin
if not ToolchainAvailable() then begin Ignore('toolchain unavailable'); Exit; end;
AssertRunsOnBoth(
'program P;'
+ 'type'
+ ' IGreet = interface procedure Greet; end;'
+ ' TFoo = class(IGreet) public procedure Greet; end;'
+ 'procedure TFoo.Greet; begin WriteLn(''hi'') end;'
+ 'var Obj: TFoo;'
+ 'begin'
+ ' Obj := TFoo.Create();'
+ ' if Supports(Obj, IGreet) then WriteLn(''supports'') else WriteLn(''no'')'
+ 'end.',
'supports' + LE, 0);
end;
initialization
RegisterTest(TE2ENativeTests);