fix(codegen): native HasClassAttribute — emit the call and the attribute RTTI
HasClassAttribute(AClass, AAttrClass) was unreliable on the native backend: - The call was never emitted. The builtin had no native lowering, so it fell through to a generic path that left the first metaclass's typeinfo address in %rax and read its low byte as the Boolean result — a layout-dependent false positive (a plain class reported as having an attribute). - Even with the call, the attribute table was missing: native typeinfo hardcoded slot 7 (attrs) to nil, so a [Threaded]-marked class reported False. Fixes (blaise.codegen.native.x86_64.pas): - Add a native lowering for the HasClassAttribute builtin (gated on FC.IsBuiltinHasClassAttr): evaluate both metaclass args to typeinfo pointers in %rdi/%rsi, call _HasClassAttribute, normalise %al to 0/1. Mirrors the QBE backend. - Emit the class attribute RTTI table (count + one typeinfo pointer per attribute) as attrs_<Class> and reference it from typeinfo slot 7, nil when the class has no attributes. Mirrors the QBE backend's attrs_<Class>. This is what blaise.testing's runner uses to decide [Threaded] subprocess dispatch; the false positive forked non-threaded suites and crashed them. e2e test in cp.test.e2e.gaps (AssertRTLRunsOnAll, both backends): a plain TTestCase reports False, a [Threaded] one reports True. Full suite OK (3739); all four fixpoints pass on both build paths.
This commit is contained in:
parent
271763034b
commit
6d46af93de
|
|
@ -1925,6 +1925,7 @@ var
|
||||||
ParentStr: string;
|
ParentStr: string;
|
||||||
ImplStr: string;
|
ImplStr: string;
|
||||||
MethStr: string;
|
MethStr: string;
|
||||||
|
AttrsStr: string;
|
||||||
PubCount: Integer;
|
PubCount: Integer;
|
||||||
Line: string;
|
Line: string;
|
||||||
EmitSys: Boolean;
|
EmitSys: Boolean;
|
||||||
|
|
@ -2060,6 +2061,24 @@ begin
|
||||||
else
|
else
|
||||||
MethStr := '0';
|
MethStr := '0';
|
||||||
|
|
||||||
|
{ Class attribute RTTI table at typeinfo slot 7: a count word followed by
|
||||||
|
one typeinfo pointer per attribute. Referenced by _HasClassAttribute.
|
||||||
|
Emitted before the typeinfo so the symbol is defined when slot 7
|
||||||
|
references it; nil when the class carries no attributes. Mirrors the
|
||||||
|
QBE backend's attrs_<Class>. }
|
||||||
|
if RT.ClassAttributeCount() > 0 then
|
||||||
|
begin
|
||||||
|
Self.Emit('.balign 8');
|
||||||
|
Self.Emit('attrs_' + CSym + ':');
|
||||||
|
Self.Emit(Format(#9'.quad %d', [RT.ClassAttributeCount()]));
|
||||||
|
for J := 0 to RT.ClassAttributeCount() - 1 do
|
||||||
|
Self.Emit(#9'.quad typeinfo_' +
|
||||||
|
Self.ClassSymName(RT.ClassAttributeAt(J)));
|
||||||
|
AttrsStr := 'attrs_' + CSym;
|
||||||
|
end
|
||||||
|
else
|
||||||
|
AttrsStr := '0';
|
||||||
|
|
||||||
Self.Emit('.balign 8');
|
Self.Emit('.balign 8');
|
||||||
Self.Emit('.globl typeinfo_' + CSym);
|
Self.Emit('.globl typeinfo_' + CSym);
|
||||||
Self.Emit('typeinfo_' + CSym + ':');
|
Self.Emit('typeinfo_' + CSym + ':');
|
||||||
|
|
@ -2070,7 +2089,7 @@ begin
|
||||||
Self.Emit(Format(#9'.quad %d', [RT.TotalSize()]));
|
Self.Emit(Format(#9'.quad %d', [RT.TotalSize()]));
|
||||||
Self.Emit(#9'.quad _FieldCleanup_' + CSym);
|
Self.Emit(#9'.quad _FieldCleanup_' + CSym);
|
||||||
Self.Emit(#9'.quad vtable_' + CSym);
|
Self.Emit(#9'.quad vtable_' + CSym);
|
||||||
Self.Emit(#9'.quad 0'); { attrs }
|
Self.Emit(#9'.quad ' + AttrsStr); { attrs }
|
||||||
end;
|
end;
|
||||||
|
|
||||||
{ Typeinfo blocks for generic class instances. }
|
{ Typeinfo blocks for generic class instances. }
|
||||||
|
|
@ -5332,6 +5351,24 @@ begin
|
||||||
{ Result = method code pointer in %rax. }
|
{ Result = method code pointer in %rax. }
|
||||||
Exit;
|
Exit;
|
||||||
end;
|
end;
|
||||||
|
{ HasClassAttribute(AClass, AAttrClass): Boolean — query attribute RTTI.
|
||||||
|
Both args are metaclass expressions that lower to typeinfo pointers.
|
||||||
|
Pass them in %rdi/%rsi and call the runtime helper (result in %al).
|
||||||
|
Without this the native backend never emitted the call — the result
|
||||||
|
was the low byte of the first metaclass's typeinfo address, a layout-
|
||||||
|
dependent false positive. }
|
||||||
|
if FC.IsBuiltinHasClassAttr and (FC.Args.Count = 2) then
|
||||||
|
begin
|
||||||
|
Self.EmitExprToEax(TASTExpr(FC.Args.Items[0])); { ti_class -> %rax }
|
||||||
|
Self.Emit(#9'pushq %rax');
|
||||||
|
Self.EmitExprToEax(TASTExpr(FC.Args.Items[1])); { ti_attr -> %rax }
|
||||||
|
Self.Emit(#9'movq %rax, %rsi');
|
||||||
|
Self.Emit(#9'popq %rdi');
|
||||||
|
Self.Emit(#9'callq _HasClassAttribute');
|
||||||
|
{ Result Boolean in %al; normalise to a clean 0/1 in %rax. }
|
||||||
|
Self.Emit(#9'movzbq %al, %rax');
|
||||||
|
Exit;
|
||||||
|
end;
|
||||||
{ SizeOf(expr) → integer literal = byte size of the resolved type. }
|
{ SizeOf(expr) → integer literal = byte size of the resolved type. }
|
||||||
if SameText(FC.Name, 'SizeOf') and (FC.Args.Count = 1) then
|
if SameText(FC.Name, 'SizeOf') and (FC.Args.Count = 1) then
|
||||||
begin
|
begin
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,12 @@ type
|
||||||
{ Published RTTI + MethodAddress }
|
{ Published RTTI + MethodAddress }
|
||||||
procedure TestRun_PublishedRTTI_MethodAddress;
|
procedure TestRun_PublishedRTTI_MethodAddress;
|
||||||
|
|
||||||
|
{ HasClassAttribute attribute RTTI query: a plain class reports False, a
|
||||||
|
[Threaded]-marked class reports True. Regression: the native backend
|
||||||
|
emitted no call (result = low byte of the metaclass address, a false
|
||||||
|
positive) and emitted no attribute table in typeinfo. }
|
||||||
|
procedure TestRun_HasClassAttribute_PlainAndMarked;
|
||||||
|
|
||||||
{ Named-type alias array const (GitHub #113) }
|
{ Named-type alias array const (GitHub #113) }
|
||||||
procedure TestRun_NamedArrayAlias_IntConst;
|
procedure TestRun_NamedArrayAlias_IntConst;
|
||||||
|
|
||||||
|
|
@ -646,6 +652,26 @@ begin
|
||||||
AssertRunsOnAll(Src, 'found' + Chr(10), 0);
|
AssertRunsOnAll(Src, 'found' + Chr(10), 0);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
procedure TE2EGapTests.TestRun_HasClassAttribute_PlainAndMarked;
|
||||||
|
const Src = '''
|
||||||
|
program T;
|
||||||
|
uses blaise.testing;
|
||||||
|
type
|
||||||
|
TPlain = class(TTestCase) published procedure M; end;
|
||||||
|
[Threaded]
|
||||||
|
TMarked = class(TTestCase) published procedure M; end;
|
||||||
|
procedure TPlain.M; begin end;
|
||||||
|
procedure TMarked.M; begin end;
|
||||||
|
begin
|
||||||
|
WriteLn(HasClassAttribute(TPlain, ThreadedAttribute));
|
||||||
|
WriteLn(HasClassAttribute(TMarked, ThreadedAttribute))
|
||||||
|
end.
|
||||||
|
''';
|
||||||
|
begin
|
||||||
|
{ Uses blaise.testing (stdlib), so the RTL+stdlib search-path helper. }
|
||||||
|
AssertRTLRunsOnAll(Src, 'False' + Chr(10) + 'True' + Chr(10), 0);
|
||||||
|
end;
|
||||||
|
|
||||||
{ ---- Multi-arg WriteLn ---- }
|
{ ---- Multi-arg WriteLn ---- }
|
||||||
|
|
||||||
procedure TE2EGapTests.TestRun_StaticArrayReturn_12Bytes;
|
procedure TE2EGapTests.TestRun_StaticArrayReturn_12Bytes;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue