feat(opdf): emit open-array debug info (ArrayKind=2 + companion slot)
Open-array parameters were emitted to the OPDF companion file as dynamic
arrays (IsDynamic=1), so the debugger read their length from the heap
header at data-4. An open array has no header — it is a (data ptr, high
index) pair on the stack — so this read returned garbage (Length=32765).
Model open arrays as a distinct kind:
- TDbgVar gains IsOpenArray + HighRbpOffset; DbgMarkParams records the
'_high' companion slot offset from the frame map.
- The recArray byte previously named IsDynamic now carries ArrayKind
(0=static, 1=dynamic, 2=open-array); open arrays emit ArrayKind=2.
- recLocalVar for an open array uses LocationExpr=5 and appends the
companion '_high' slot offset as a trailing SmallInt.
- Open arrays get a distinct canonical name ('open array of T') so they
no longer alias a same-element dynamic array's TypeID.
- Parameter and facts-path variable type records are now emitted; the
anonymous open-array type previously had no recArray written at all.
The debugger side (length read from the companion slot) lives in the
opdebugger repo. FPC's dbgopdf.pas has no open-array concept; this is a
Blaise extension to the OPDF format.
This commit is contained in:
parent
6ba1bc9b78
commit
7b8116310f
|
|
@ -3587,6 +3587,19 @@ begin
|
|||
DV.IsVarParam := P.IsVarParam;
|
||||
DV.IsConstParam := P.IsConstParam;
|
||||
end
|
||||
else if P.IsOpenArray then
|
||||
begin
|
||||
{ Open-array parameter: the named slot holds the data pointer; the
|
||||
element count is High+1, where High lives in the companion
|
||||
'<name>_high' slot. Record the companion offset so the OPDF
|
||||
emitter can locate the length — the debugger has no heap header
|
||||
to read it from. }
|
||||
V.IsOpenArray := True;
|
||||
if not FFrame.TryGetValue(P.ParamName + '_high', V.HighRbpOffset) then
|
||||
V.HighRbpOffset := 0;
|
||||
if (V.TypeDesc = nil) and (P.ResolvedType <> nil) then
|
||||
V.TypeDesc := P.ResolvedType;
|
||||
end
|
||||
else if P.IsVarParam then
|
||||
begin
|
||||
{ var/out parameter: the slot holds the ADDRESS of the caller's
|
||||
|
|
|
|||
|
|
@ -47,6 +47,12 @@ type
|
|||
(var/out params, by-reference aggregates, captured outer locals) —
|
||||
emitted as LocationExpr=3 'RBP-relative indirect'. }
|
||||
Indirect: Boolean;
|
||||
{ Open-array parameter: the slot holds the data pointer (no heap header);
|
||||
the element count is High+1, where High lives in a separate companion
|
||||
stack slot at HighRbpOffset. Emitted as LocationExpr=5 'open-array'
|
||||
with the companion offset trailing the LocationData. }
|
||||
IsOpenArray: Boolean;
|
||||
HighRbpOffset: Integer;
|
||||
end;
|
||||
|
||||
{ One statement-level line marker: the backend emitted LabelName ('.Ldbg_N')
|
||||
|
|
|
|||
|
|
@ -71,6 +71,7 @@ type
|
|||
procedure EmitGlobalVars;
|
||||
procedure EmitFunctionScopes;
|
||||
function MangledClassSym(const AClassName: string): string;
|
||||
procedure EmitFactsTypes;
|
||||
procedure EmitFunctionScopesFromFacts;
|
||||
procedure EmitFactParameter(AVar: TDbgVar; var ADeclIdx: Integer);
|
||||
procedure EmitFactLocalVar(AVar: TDbgVar; AScopeID: Integer;
|
||||
|
|
@ -122,6 +123,8 @@ const
|
|||
|
||||
LOC_RBP = 1;
|
||||
LOC_RBP_INDIRECT = 3; { frame slot holds the value's address }
|
||||
LOC_OPENARRAY = 5; { open-array: data ptr at LocationData, element count
|
||||
from companion _high slot (trailing SmallInt) }
|
||||
|
||||
PAT_FIELD = 0; { property accessor: direct field offset }
|
||||
PAT_METHOD = 1; { property accessor: method call }
|
||||
|
|
@ -255,10 +258,13 @@ begin
|
|||
CanonicalName(SA.ElementType);
|
||||
end;
|
||||
tyOpenArray:
|
||||
if AType.Name <> '' then
|
||||
Result := AType.Name
|
||||
else
|
||||
Result := 'array of ' + CanonicalName(TOpenArrayTypeDesc(AType).ElementType);
|
||||
{ An open array is a distinct kind from a dynamic array (no heap header;
|
||||
length in a companion slot). Give it a distinct canonical name so it
|
||||
gets its own TypeID and recArray(ArrayKind=2) — otherwise it would
|
||||
alias a same-element 'array of T' dynamic-array record and inherit
|
||||
ArrayKind=1. }
|
||||
Result := 'open array of ' +
|
||||
CanonicalName(TOpenArrayTypeDesc(AType).ElementType);
|
||||
else
|
||||
if AType.Name = '' then
|
||||
Result := 'Pointer'
|
||||
|
|
@ -607,7 +613,13 @@ begin
|
|||
begin
|
||||
P := TMethodParam(AMethod.Params.Items[I]);
|
||||
if P.ResolvedType <> nil then
|
||||
CName := CanonicalName(P.ResolvedType)
|
||||
begin
|
||||
CName := CanonicalName(P.ResolvedType);
|
||||
{ Ensure the parameter's type record is emitted (e.g. an open-array
|
||||
param's recArray with ArrayKind=2) — otherwise the TypeID below
|
||||
references a record that was never written. }
|
||||
EmitTypeDesc(P.ResolvedType);
|
||||
end
|
||||
else
|
||||
CName := 'Pointer';
|
||||
RecSize := 9 + Length(P.ParamName);
|
||||
|
|
@ -809,18 +821,21 @@ begin
|
|||
end
|
||||
else
|
||||
begin
|
||||
{ Open array: a (data ptr, high) pair with no heap header. ArrayKind=2
|
||||
tells the debugger to read the length from the variable's companion
|
||||
slot, not from data-4. (FPC's dbgopdf.pas has no open-array concept;
|
||||
this is a Blaise extension to the OPDF format.) }
|
||||
EmitTypeDesc(TOpenArrayTypeDesc(AType).ElementType);
|
||||
ElemName := CanonicalName(TOpenArrayTypeDesc(AType).ElementType);
|
||||
ElemID := GetOrAllocTypeID(ElemName);
|
||||
IsDyn := 1;
|
||||
RecSize := 12 + Length(CName);
|
||||
L('');
|
||||
L(' # recArray (dynamic): ' + CName);
|
||||
L(' # recArray (open): ' + CName);
|
||||
EmitRecHdr(REC_ARRAY, RecSize);
|
||||
L(' .int ' + IntToStr(GetOrAllocTypeID(CName)) + ' # TypeID');
|
||||
L(' .int ' + IntToStr(ElemID) + ' # ElementTypeID');
|
||||
L(' .byte 1 # Dimensions');
|
||||
L(' .byte ' + IntToStr(IsDyn) + ' # IsDynamic');
|
||||
L(' .byte 2 # ArrayKind');
|
||||
EmitStrField(CName);
|
||||
end;
|
||||
end;
|
||||
|
|
@ -1048,6 +1063,30 @@ begin
|
|||
if (GI.TypeDesc <> nil) and (GI.TypeDesc.Kind = tyClass) then
|
||||
EmitTypeDesc(GI.TypeDesc);
|
||||
end;
|
||||
{ Facts path: parameter/local types are often anonymous (e.g. an
|
||||
open-array param 'array of Integer') and never appear in TypeDecls,
|
||||
so emit every frame slot's type here. EmitTypeDesc dedupes against
|
||||
types already written above. }
|
||||
EmitFactsTypes();
|
||||
end;
|
||||
|
||||
procedure TOPDFEmitter.EmitFactsTypes;
|
||||
var
|
||||
I, J: Integer;
|
||||
F: TDbgFunc;
|
||||
V: TDbgVar;
|
||||
begin
|
||||
if FFacts = nil then Exit;
|
||||
for I := 0 to FFacts.Funcs.Count - 1 do
|
||||
begin
|
||||
F := TDbgFunc(FFacts.Funcs.Items[I]);
|
||||
for J := 0 to F.Vars.Count - 1 do
|
||||
begin
|
||||
V := TDbgVar(F.Vars.Items[J]);
|
||||
if V.TypeDesc <> nil then
|
||||
EmitTypeDesc(V.TypeDesc);
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TOPDFEmitter.EmitGlobalVars;
|
||||
|
|
@ -1108,13 +1147,19 @@ begin
|
|||
else
|
||||
CName := 'Pointer';
|
||||
RecSize := 15 + Length(AVar.Name);
|
||||
{ Open-array locals carry an extra trailing SmallInt: the companion
|
||||
'_high' slot offset. Account for it in the record size. }
|
||||
if AVar.IsOpenArray then
|
||||
RecSize := RecSize + 2;
|
||||
L('');
|
||||
L(' # recLocalVar: ' + AVar.Name + ' (rbp' +
|
||||
IntToStr(AVar.RbpOffset) + ')');
|
||||
EmitRecHdr(REC_LOCALVAR, RecSize);
|
||||
L(' .int ' + IntToStr(GetOrAllocTypeID(CName)) + ' # TypeID');
|
||||
L(' .int ' + IntToStr(AScopeID) + ' # ScopeID');
|
||||
if AVar.Indirect then
|
||||
if AVar.IsOpenArray then
|
||||
L(' .byte ' + IntToStr(LOC_OPENARRAY) + ' # LocationExpr (open-array)')
|
||||
else if AVar.Indirect then
|
||||
L(' .byte ' + IntToStr(LOC_RBP_INDIRECT) +
|
||||
' # LocationExpr (RBP-relative indirect)')
|
||||
else
|
||||
|
|
@ -1122,6 +1167,9 @@ begin
|
|||
L(' .word ' + IntToStr(ADeclIdx) + ' # DeclIndex');
|
||||
EmitNameLen(AVar.Name);
|
||||
L(' .word ' + IntToStr(AVar.RbpOffset) + ' # LocationData (RBP offset)');
|
||||
if AVar.IsOpenArray then
|
||||
L(' .word ' + IntToStr(AVar.HighRbpOffset) +
|
||||
' # CompanionData (_high RBP offset)');
|
||||
EmitNameData(AVar.Name);
|
||||
ADeclIdx := ADeclIdx + 1;
|
||||
end;
|
||||
|
|
|
|||
|
|
@ -55,6 +55,8 @@ type
|
|||
procedure TestOPDF_Pointer_RecType;
|
||||
procedure TestOPDF_Array_Static_RecType;
|
||||
procedure TestOPDF_Array_Static_IsDynamic;
|
||||
procedure TestOPDF_Array_OpenArray_ArrayKind;
|
||||
procedure TestOPDF_OpenArray_CompanionLocation;
|
||||
procedure TestOPDF_Set_RecType;
|
||||
procedure TestOPDF_Set_SizeInBytes;
|
||||
procedure TestOPDF_Property_RecType;
|
||||
|
|
@ -469,6 +471,66 @@ begin
|
|||
AssertTrue('IsDynamic=0 for static array', Contains(IR, '.byte 0 # IsDynamic'));
|
||||
end;
|
||||
|
||||
procedure TOPDFTests.TestOPDF_Array_OpenArray_ArrayKind;
|
||||
var
|
||||
IR: string;
|
||||
begin
|
||||
{ An open-array parameter is neither static nor dynamic: it is a (ptr, high)
|
||||
pair with no heap header. Its recArray must carry ArrayKind=2, NOT
|
||||
IsDynamic=1 (which would make the debugger read length from data-4). }
|
||||
IR := GenOPDF(
|
||||
'''
|
||||
program P;
|
||||
procedure Bar(a: array of Integer);
|
||||
begin end;
|
||||
begin end.
|
||||
''');
|
||||
AssertTrue('ArrayKind=2 for open array', Contains(IR, '.byte 2 # ArrayKind'));
|
||||
AssertTrue('open array is not flagged dynamic',
|
||||
not Contains(IR, 'array of Integer' + #10 + ' .byte 1 # ArrayKind'));
|
||||
end;
|
||||
|
||||
procedure TOPDFTests.TestOPDF_OpenArray_CompanionLocation;
|
||||
var
|
||||
L: TLexer;
|
||||
P: TParser;
|
||||
Pr: TProgram;
|
||||
A: TSemanticAnalyser;
|
||||
E: TOPDFEmitter;
|
||||
Facts: TDbgFacts;
|
||||
O: string;
|
||||
begin
|
||||
L := TLexer.Create('program P; begin end.');
|
||||
P := TParser.Create(L);
|
||||
Pr := P.Parse();
|
||||
A := TSemanticAnalyser.Create();
|
||||
Facts := BuildSampleFacts();
|
||||
try
|
||||
A.Analyse(Pr);
|
||||
E := TOPDFEmitter.Create(Pr, 'test.pas');
|
||||
try
|
||||
E.SetFacts(Facts);
|
||||
O := E.GetOutput();
|
||||
finally
|
||||
E.Free();
|
||||
end;
|
||||
{ The open-array local 'Oa' must use LocationExpr=5 (open-array) and carry
|
||||
both its data-slot offset (-32) and the companion _high offset (-40). }
|
||||
AssertTrue('open-array LocationExpr=5',
|
||||
Pos('.byte 5 # LocationExpr (open-array)', O) > 0);
|
||||
AssertTrue('open-array data offset -32',
|
||||
Pos('.word -32 # LocationData', O) > 0);
|
||||
AssertTrue('open-array companion _high offset -40',
|
||||
Pos('.word -40 # CompanionData (_high RBP offset)', O) > 0);
|
||||
finally
|
||||
Facts.Free();
|
||||
A.Free();
|
||||
Pr.Free();
|
||||
P.Free();
|
||||
L.Free();
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TOPDFTests.TestOPDF_Set_RecType;
|
||||
var
|
||||
IR: string;
|
||||
|
|
@ -649,6 +711,12 @@ begin
|
|||
V := F.AddVar('By', nil, -16);
|
||||
V.IsParam := True;
|
||||
F.AddVar('Tmp', nil, -24);
|
||||
{ Open-array parameter: data slot at -32, companion _high slot at -40.
|
||||
The OPDF emitter must locate the length via the companion offset. }
|
||||
V := F.AddVar('Oa', nil, -32);
|
||||
V.IsParam := True;
|
||||
V.IsOpenArray := True;
|
||||
V.HighRbpOffset := -40;
|
||||
LineRec := TDbgLine.Create();
|
||||
LineRec.LabelName := '.Ldbg_3';
|
||||
LineRec.Line := 10;
|
||||
|
|
|
|||
Loading…
Reference in a new issue