feat(opdf): field drilldown — unit-class symbols, Self typing, record-param shadows
Three gaps between the OPDF data and a realistic multi-unit program: - Type records for unit-defined classes referenced bare symbols (vtable_TStringList, TStringList_SetText) while the emitted symbols carry the owning-unit prefix — any program using TStringList failed to LINK under --debug-opdf. New MangledClassSym mirrors the codegen's ClassSymName allowlist (System / rtl.* / blaise_* / program-scope stay bare) for VMTAddress and property accessor addresses. - Self was recorded untyped (its slot is allocated as a raw pointer), so 'print Self.FField' failed. DbgMarkParams now types Self as the owning class — pdr dereferences class-typed variables, so instance field drilldown works. The unit-less driver path now passes the program's symbol table to the backend (the analyser, which the table's uses-chain provider points at, is still alive there; the in-test fallback variant dangled and was reverted). - Value record parameters present their '_data' shadow slot (the callee's inline copy) as the parameter instead of the raw ABI pointer slot — record fields are inline at known offsets, which is exactly what field access needs. Verified with pdr against a program using classes+sysutils units: print P.X / P.Y (record param fields), Self.FCount / Self.FName (instance fields, string rendering), Msg (local string) all correct. Suite: 2955 OK; FIXPOINT_OK; NATIVE_FIXPOINT_OK.
This commit is contained in:
parent
d2d5a8de3d
commit
525037a4db
|
|
@ -962,7 +962,13 @@ begin
|
|||
CG.AppendProgram(Prog);
|
||||
end
|
||||
else
|
||||
begin
|
||||
{ Unit-less program: the backend still needs the symbol table for
|
||||
class symbol prefixes and OPDF Self typing; the analyser (the
|
||||
table's uses-chain provider) outlives codegen here. }
|
||||
CG.SetSymbolTable(Prog.SymbolTable);
|
||||
CG.Generate(Prog);
|
||||
end;
|
||||
IR := CG.GetOutput();
|
||||
{ CG (ICodeGen) is released by ARC at program scope exit. We avoid an
|
||||
explicit `CG := nil` here: the stage-1 release binary mis-compiles an
|
||||
|
|
|
|||
|
|
@ -3221,11 +3221,12 @@ procedure TX86_64Backend.DbgRecordSlot(const AName: string; AType: TTypeDesc;
|
|||
begin
|
||||
if FDbgCur = nil then Exit;
|
||||
{ Skip internal companion/bookkeeping slots: capture pointers, exception
|
||||
frames, open-array highs, record-param data shadows. }
|
||||
frames, open-array highs. '_data' record-param shadows are kept —
|
||||
DbgMarkParams presents them AS the parameter (the shadow holds the
|
||||
callee's inline record copy, which is what a debugger should show). }
|
||||
if AName = '' then Exit;
|
||||
if AName[0] = '_' then Exit;
|
||||
if (Length(AName) > 5) and (Copy(AName, Length(AName) - 5, 5) = '_high') then Exit;
|
||||
if (Length(AName) > 5) and (Copy(AName, Length(AName) - 5, 5) = '_data') then Exit;
|
||||
FDbgCur.AddVar(AName, AType, AOffset);
|
||||
end;
|
||||
|
||||
|
|
@ -3234,11 +3235,24 @@ var
|
|||
I: Integer;
|
||||
P: TMethodParam;
|
||||
V: TDbgVar;
|
||||
DV: TDbgVar;
|
||||
Sym: TSymbol;
|
||||
begin
|
||||
if (FDbgCur = nil) or (ADecl = nil) then Exit;
|
||||
V := FDbgCur.FindVar('Self');
|
||||
if V <> nil then
|
||||
begin
|
||||
V.IsParam := True;
|
||||
{ Type Self as the owning class so the debugger can drill its fields
|
||||
(the slot holds the instance pointer; pdr derefs class types). }
|
||||
if (V.TypeDesc = nil) and (ADecl.OwnerTypeName <> '') and
|
||||
(FSymTable <> nil) then
|
||||
begin
|
||||
Sym := FSymTable.Lookup(ADecl.OwnerTypeName);
|
||||
if (Sym <> nil) and (Sym.TypeDesc <> nil) then
|
||||
V.TypeDesc := Sym.TypeDesc;
|
||||
end;
|
||||
end;
|
||||
for I := 0 to ADecl.Params.Count - 1 do
|
||||
begin
|
||||
P := TMethodParam(ADecl.Params.Items[I]);
|
||||
|
|
@ -3247,6 +3261,22 @@ begin
|
|||
V.IsParam := True;
|
||||
V.IsVarParam := P.IsVarParam;
|
||||
V.IsConstParam := P.IsConstParam;
|
||||
{ Value record/static-array params: the named slot holds an ABI pointer;
|
||||
the callee's inline copy lives in the '_data' shadow slot. Present
|
||||
the shadow AS the parameter — its fields are inline at known offsets,
|
||||
which is what field drilldown needs. }
|
||||
DV := FDbgCur.FindVar(P.ParamName + '_data');
|
||||
if DV <> nil then
|
||||
begin
|
||||
FDbgCur.Vars.Delete(FDbgCur.Vars.IndexOf(V)); { drop the raw pointer slot }
|
||||
DV.Name := P.ParamName;
|
||||
DV.IsParam := True;
|
||||
DV.IsVarParam := P.IsVarParam;
|
||||
DV.IsConstParam := P.IsConstParam;
|
||||
end
|
||||
else if (V.TypeDesc = nil) and (P.ResolvedType <> nil) and
|
||||
not P.IsVarParam then
|
||||
V.TypeDesc := P.ResolvedType;
|
||||
end;
|
||||
end;
|
||||
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@ type
|
|||
procedure EmitAllTypes;
|
||||
procedure EmitGlobalVars;
|
||||
procedure EmitFunctionScopes;
|
||||
function MangledClassSym(const AClassName: string): string;
|
||||
procedure EmitFunctionScopesFromFacts;
|
||||
procedure EmitFactParameter(AVar: TDbgVar; var ADeclIdx: Integer);
|
||||
procedure EmitFactLocalVar(AVar: TDbgVar; AScopeID: Integer;
|
||||
|
|
@ -447,6 +448,40 @@ begin
|
|||
EmitFields(AType);
|
||||
end;
|
||||
|
||||
function TOPDFEmitter.MangledClassSym(const AClassName: string): string;
|
||||
var
|
||||
Sym: TSymbol;
|
||||
Owner: string;
|
||||
I: Integer;
|
||||
Ch: string;
|
||||
Pfx: string;
|
||||
begin
|
||||
Pfx := '';
|
||||
if (FProgram <> nil) and (FProgram.SymbolTable <> nil) then
|
||||
begin
|
||||
Sym := FProgram.SymbolTable.Lookup(AClassName);
|
||||
if Sym <> nil then
|
||||
begin
|
||||
Owner := Sym.OwningUnit;
|
||||
if (Owner <> '') and
|
||||
not SameText(Owner, FProgram.Name) 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 Pfx := Pfx + '_'
|
||||
else Pfx := Pfx + Ch;
|
||||
end;
|
||||
Pfx := Pfx + '_';
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
Result := Pfx + AClassName;
|
||||
end;
|
||||
|
||||
procedure TOPDFEmitter.EmitClass(AType: TRecordTypeDesc);
|
||||
var
|
||||
CName, ParentName: string;
|
||||
|
|
@ -485,7 +520,7 @@ begin
|
|||
L(' .int ' + IntToStr(GetOrAllocTypeID(CName)) + ' # TypeID');
|
||||
L(' .int ' + IntToStr(ParentID) + ' # ParentTypeID');
|
||||
if AType.HasVTable() then
|
||||
L(' .quad vtable_' + CName + ' # VMTAddress')
|
||||
L(' .quad vtable_' + MangledClassSym(CName) + ' # VMTAddress')
|
||||
else
|
||||
L(' .quad 0 # VMTAddress (no vtable)');
|
||||
L(' .int ' + IntToStr(AType.TotalSize()) + ' # InstanceSize');
|
||||
|
|
@ -901,7 +936,7 @@ begin
|
|||
L(' .quad 0 # ReadAddr (field not found)');
|
||||
end
|
||||
else if ReadType = PAT_METHOD then
|
||||
L(' .quad ' + AClass.Name + '_' + PI.ReadMethod + ' # ReadAddr (getter)')
|
||||
L(' .quad ' + MangledClassSym(AClass.Name) + '_' + PI.ReadMethod + ' # ReadAddr (getter)')
|
||||
else
|
||||
L(' .quad 0 # ReadAddr (none)');
|
||||
|
||||
|
|
@ -915,7 +950,7 @@ begin
|
|||
L(' .quad 0 # WriteAddr (field not found)');
|
||||
end
|
||||
else if WriteType = PAT_METHOD then
|
||||
L(' .quad ' + AClass.Name + '_' + PI.WriteMethod + ' # WriteAddr (setter)')
|
||||
L(' .quad ' + MangledClassSym(AClass.Name) + '_' + PI.WriteMethod + ' # WriteAddr (setter)')
|
||||
else
|
||||
L(' .quad 0 # WriteAddr (none)');
|
||||
|
||||
|
|
|
|||
|
|
@ -58,6 +58,9 @@ type
|
|||
records exact frame offsets; normal builds stay label-free. }
|
||||
procedure TestOpdf_StatementLabels_AndFacts;
|
||||
procedure TestOpdf_Off_NoDbgLabels;
|
||||
{ Self is typed as the owning class; value record params present the
|
||||
'_data' shadow slot (the inline copy) so field drilldown works. }
|
||||
procedure TestOpdf_Facts_SelfAndRecordParamTyping;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
|
@ -564,6 +567,65 @@ begin
|
|||
AssertTrue('no debug labels in a normal build', Pos('.Ldbg_', Asm_) < 0);
|
||||
end;
|
||||
|
||||
procedure TNativeConstArgTests.TestOpdf_Facts_SelfAndRecordParamTyping;
|
||||
var
|
||||
L: TLexer;
|
||||
P: TParser;
|
||||
Prog: TProgram;
|
||||
A: TSemanticAnalyser;
|
||||
CG: TCodeGenNative;
|
||||
F: TDbgFunc;
|
||||
V: TDbgVar;
|
||||
I: Integer;
|
||||
begin
|
||||
L := TLexer.Create('''
|
||||
program P;
|
||||
type
|
||||
TPt = record X, Y: Integer; end;
|
||||
TW = class
|
||||
N: Integer;
|
||||
procedure Track(Q: TPt);
|
||||
end;
|
||||
procedure TW.Track(Q: TPt);
|
||||
begin
|
||||
N := N + Q.X;
|
||||
end;
|
||||
var W: TW; G: TPt;
|
||||
begin
|
||||
W := TW.Create();
|
||||
W.Track(G);
|
||||
end.
|
||||
''');
|
||||
P := TParser.Create(L);
|
||||
Prog := P.Parse();
|
||||
P.Free(); L.Free();
|
||||
A := TSemanticAnalyser.Create();
|
||||
A.Analyse(Prog);
|
||||
CG := TCodeGenNative.Create();
|
||||
CG.SetTarget(HostTarget());
|
||||
CG.SetOpdfMode(True);
|
||||
CG.SetSymbolTable(Prog.SymbolTable);
|
||||
CG.Generate(Prog);
|
||||
A.Free();
|
||||
F := nil;
|
||||
for I := 0 to CG.GetDebugFacts().Funcs.Count - 1 do
|
||||
if TDbgFunc(CG.GetDebugFacts().Funcs.Items[I]).SymbolName = 'TW_Track' then
|
||||
F := TDbgFunc(CG.GetDebugFacts().Funcs.Items[I]);
|
||||
AssertNotNil('Track scope recorded', F);
|
||||
V := F.FindVar('Self');
|
||||
AssertNotNil('Self recorded', V);
|
||||
AssertNotNil('Self typed as the owning class', V.TypeDesc);
|
||||
AssertEquals('Self type', 'TW', V.TypeDesc.Name);
|
||||
V := F.FindVar('Q');
|
||||
AssertNotNil('record param recorded', V);
|
||||
AssertTrue('record param is a param', V.IsParam);
|
||||
AssertNotNil('record param typed', V.TypeDesc);
|
||||
AssertEquals('record param presents the inline copy type', 'TPt', V.TypeDesc.Name);
|
||||
AssertTrue('inline copy below the raw pointer slot', V.RbpOffset < -16);
|
||||
Prog.Free();
|
||||
CG.Free();
|
||||
end;
|
||||
|
||||
initialization
|
||||
RegisterTest(TNativeConstArgTests);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue