feat(debug): per-unit OPDF so incremental builds are fully debuggable
Incremental compilation is the default, but each dependency unit was compiled to its own .o with NO OPDF debug info — only the top --source unit got an .opdf section. pdr could therefore not set breakpoints, show callstacks, or inspect locals inside any dependency (RTL, stdlib, or a user's own units), which makes the default pipeline effectively undebuggable. Mirror FPC's multi-unit OPDF model (which pdr already reads): every object — each unit AND the program — carries a complete, self-contained .opdf section [32-byte header][records], and the linker concatenates the same-named sections. The header's TotalRecords is written as 0 (stream-terminated): readers consume records to section EOF and skip the embedded per-unit headers (and linker zero padding). pdr needs NO changes — it already handles exactly this layout. Layers: - uDebugOPDF.pas: EmitHeader writes TotalRecords=0 (PatchTotalRecords now a no-op); new CreateForUnit + unit-mode DoEmit that emits a unit's types / globals / constants / function scopes / line info from its IntfBlock+ImplBlock and the unit's symbol table, with NO program main scope and NO unit directory (directory is program-only; pdr ignores it). Whole-program output is unchanged apart from TotalRecords=0. MangledClassSym matches the native backend's class-symbol naming so vtable labels resolve in unit mode. - Blaise.pas: TCompileWorker.Execute appends a unit-mode OPDF section to each unit's IR when --debug-opdf and the native codegen produced debug facts, so the per-unit .o embeds its own .opdf. (QBE has no facts → no per-unit OPDF; native is the debug backend.) - blaise.assembler.x86_64.pas: accept `.section .opdf, "aw", @progbits` (new eskOpdf); `.int` alias for `.long`; `.byte`/`.word` now parse comma-separated value lists (needed for the OPDF magic + BuildID bytes). - blaise.elfwriter.pas: emit the .opdf section as SHF_ALLOC|SHF_WRITE PROGBITS so it lands in the loadable image where pdr finds it; the internal linker's section merger already concatenates .opdf by name and resolves its .quad <funclabel> relocations. Verified: pdr breaks inside a dependency unit, shows the cross-unit callstack, and inspects locals — under BOTH the internal toolchain (default) and external gas/ld, incremental AND --no-incremental. Full suite OK (3742) on both build paths; all four fixpoints pass (normal builds unaffected). Regression test TestDebugOpdf_PerUnitSection_InDependencyObject asserts the dependency .o carries an .opdf section.
This commit is contained in:
parent
18b9cb273b
commit
1e92604599
|
|
@ -29,7 +29,7 @@ uses
|
||||||
blaise.codegen.driver,
|
blaise.codegen.driver,
|
||||||
blaise.codegen.qbe.driver,
|
blaise.codegen.qbe.driver,
|
||||||
blaise.codegen.native.driver,
|
blaise.codegen.native.driver,
|
||||||
uUnitLoader, uDebugOPDF, uUnitInterface, uSemanticExport, uSemanticImport,
|
uUnitLoader, uDebugOPDF, uDebugFacts, uUnitInterface, uSemanticExport, uSemanticImport,
|
||||||
uUnitInterfaceIO, uIfaceObject, uASTDump,
|
uUnitInterfaceIO, uIfaceObject, uASTDump,
|
||||||
blaise.frontend.opts, uConfig;
|
blaise.frontend.opts, uConfig;
|
||||||
|
|
||||||
|
|
@ -363,6 +363,8 @@ var
|
||||||
WIRFile: string;
|
WIRFile: string;
|
||||||
WBifFile: string;
|
WBifFile: string;
|
||||||
WSource: TStringList;
|
WSource: TStringList;
|
||||||
|
WFacts: TDbgFacts;
|
||||||
|
WOPDF: TOPDFEmitter;
|
||||||
begin
|
begin
|
||||||
Self.Error := '';
|
Self.Error := '';
|
||||||
try
|
try
|
||||||
|
|
@ -378,6 +380,34 @@ begin
|
||||||
WCG.SetSymbolTable(Self.SymTable);
|
WCG.SetSymbolTable(Self.SymTable);
|
||||||
WCG.AppendUnit(Self.WorkUnit);
|
WCG.AppendUnit(Self.WorkUnit);
|
||||||
WIR := WCG.GetOutput();
|
WIR := WCG.GetOutput();
|
||||||
|
|
||||||
|
{ Per-unit OPDF debug info: when --debug-opdf is on AND this worker's
|
||||||
|
codegen produced exact debug facts (native backend), build a unit-mode
|
||||||
|
OPDF emitter for THIS unit and append its self-contained .opdf section
|
||||||
|
to the unit's IR. At link time the linker concatenates every unit's
|
||||||
|
and the program's .opdf section into one, so pdr can break inside any
|
||||||
|
unit. Mirrors the whole-program path in the main driver.
|
||||||
|
|
||||||
|
QBE backend: GetDebugFacts returns nil (QBE assigns frames/addresses
|
||||||
|
itself), so per-unit OPDF is skipped here. A per-unit .opdf.s sidecar
|
||||||
|
is impractical in the incremental pipeline; native is the debug backend
|
||||||
|
per CLAUDE.md, so QBE incremental units carry no per-unit OPDF. }
|
||||||
|
if Self.Opts.OPDFEnabled then
|
||||||
|
begin
|
||||||
|
WFacts := WCG.GetDebugFacts();
|
||||||
|
if WFacts <> nil then
|
||||||
|
begin
|
||||||
|
WOPDF := TOPDFEmitter.CreateForUnit(Self.WorkUnit, Self.SymTable,
|
||||||
|
Self.WorkUnit.SourceFile);
|
||||||
|
try
|
||||||
|
WOPDF.SetFacts(WFacts);
|
||||||
|
WIR := WIR + LineEnding + WOPDF.GetOutput();
|
||||||
|
finally
|
||||||
|
WOPDF.Free();
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
WCG := nil; { release the ARC handle so codegen memory is freed before lowering }
|
WCG := nil; { release the ARC handle so codegen memory is freed before lowering }
|
||||||
|
|
||||||
WIRFile := Self.OPath + Self.Driver.IRFileExt() + '.tmp';
|
WIRFile := Self.OPath + Self.Driver.IRFileExt() + '.tmp';
|
||||||
|
|
|
||||||
|
|
@ -2335,6 +2335,11 @@ begin
|
||||||
Exit
|
Exit
|
||||||
else if StartsWithStr(Args, '.bss') then
|
else if StartsWithStr(Args, '.bss') then
|
||||||
ASection := eskBss
|
ASection := eskBss
|
||||||
|
else if StartsWithStr(Args, '.opdf') then
|
||||||
|
{ OPDF debug section: alloc+write progbits data. Holds .byte/.word/.int/
|
||||||
|
.quad/.ascii records emitted by uDebugOPDF; .quad <label> lines become
|
||||||
|
R_X86_64_64 relocations via the same data-section machinery below. }
|
||||||
|
ASection := eskOpdf
|
||||||
else
|
else
|
||||||
raise EAssembler.Create('unsupported section: ' + Args);
|
raise EAssembler.Create('unsupported section: ' + Args);
|
||||||
Exit;
|
Exit;
|
||||||
|
|
@ -2390,23 +2395,44 @@ begin
|
||||||
Exit;
|
Exit;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
{ .byte and .word accept a comma-separated list of values, e.g.
|
||||||
|
`.byte 79, 80, 68, 70` (the OPDF magic) or `.byte 0,0,0,...` (BuildID).
|
||||||
|
Emit each value in order. }
|
||||||
if Dir = '.byte' then
|
if Dir = '.byte' then
|
||||||
begin
|
begin
|
||||||
P := 0;
|
P := 0;
|
||||||
Val := ParseInt(Args, P);
|
Len := Length(Args);
|
||||||
AWriter.AppendByte(ASection, Integer(Val));
|
while P < Len do
|
||||||
|
begin
|
||||||
|
while (P < Len) and ((Args[P] = Ord(' ')) or (Args[P] = Ord(#9)) or
|
||||||
|
(Args[P] = Ord(','))) do
|
||||||
|
P := P + 1;
|
||||||
|
if P >= Len then break;
|
||||||
|
Val := ParseInt(Args, P);
|
||||||
|
AWriter.AppendByte(ASection, Integer(Val));
|
||||||
|
end;
|
||||||
Exit;
|
Exit;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
if Dir = '.word' then
|
if Dir = '.word' then
|
||||||
begin
|
begin
|
||||||
P := 0;
|
P := 0;
|
||||||
Val := ParseInt(Args, P);
|
Len := Length(Args);
|
||||||
AWriter.AppendWord(ASection, Integer(Val));
|
while P < Len do
|
||||||
|
begin
|
||||||
|
while (P < Len) and ((Args[P] = Ord(' ')) or (Args[P] = Ord(#9)) or
|
||||||
|
(Args[P] = Ord(','))) do
|
||||||
|
P := P + 1;
|
||||||
|
if P >= Len then break;
|
||||||
|
Val := ParseInt(Args, P);
|
||||||
|
AWriter.AppendWord(ASection, Integer(Val));
|
||||||
|
end;
|
||||||
Exit;
|
Exit;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
if Dir = '.long' then
|
{ .int is an alias for .long: GNU as treats both as a 32-bit value on
|
||||||
|
x86-64. The OPDF debug emitter uses .int, so accept it here. }
|
||||||
|
if (Dir = '.long') or (Dir = '.int') then
|
||||||
begin
|
begin
|
||||||
DataOp := ParseOperand(Args, 0, OpEnd);
|
DataOp := ParseOperand(Args, 0, OpEnd);
|
||||||
if DataOp.Kind = opLabel then
|
if DataOp.Kind = opLabel then
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,8 @@ type
|
||||||
eskData, { .data — read-write initialised data }
|
eskData, { .data — read-write initialised data }
|
||||||
eskRodata, { .rodata — read-only data }
|
eskRodata, { .rodata — read-only data }
|
||||||
eskBss, { .bss — zero-initialised read-write data }
|
eskBss, { .bss — zero-initialised read-write data }
|
||||||
eskTbss { .tbss — thread-local zero-initialised data }
|
eskTbss, { .tbss — thread-local zero-initialised data }
|
||||||
|
eskOpdf { .opdf OPDF debug info (alloc+write, progbits) }
|
||||||
);
|
);
|
||||||
|
|
||||||
TElfSymBind = (
|
TElfSymBind = (
|
||||||
|
|
@ -490,8 +491,8 @@ var
|
||||||
I: Integer;
|
I: Integer;
|
||||||
begin
|
begin
|
||||||
inherited Create();
|
inherited Create();
|
||||||
SetLength(FSections, 5);
|
SetLength(FSections, 6);
|
||||||
for I := 0 to 4 do
|
for I := 0 to 5 do
|
||||||
FSections[I] := nil;
|
FSections[I] := nil;
|
||||||
FSymbols := TList<TElfWriterSym>.Create();
|
FSymbols := TList<TElfWriterSym>.Create();
|
||||||
FSymMap := TDictionary<string, Integer>.Create();
|
FSymMap := TDictionary<string, Integer>.Create();
|
||||||
|
|
@ -525,6 +526,7 @@ begin
|
||||||
eskRodata: Result := '.rodata';
|
eskRodata: Result := '.rodata';
|
||||||
eskBss: Result := '.bss';
|
eskBss: Result := '.bss';
|
||||||
eskTbss: Result := '.tbss';
|
eskTbss: Result := '.tbss';
|
||||||
|
eskOpdf: Result := '.opdf';
|
||||||
else
|
else
|
||||||
Result := '.text';
|
Result := '.text';
|
||||||
end;
|
end;
|
||||||
|
|
@ -865,11 +867,11 @@ var
|
||||||
then .rela.text, .rela.data, .rela.rodata,
|
then .rela.text, .rela.data, .rela.rodata,
|
||||||
then .symtab, .strtab, .shstrtab.
|
then .symtab, .strtab, .shstrtab.
|
||||||
We track which sections exist and their SHT indices. }
|
We track which sections exist and their SHT indices. }
|
||||||
SecOrder: array[0..4] of TElfSectionKind;
|
SecOrder: array[0..5] of TElfSectionKind;
|
||||||
SecPresent: array[0..4] of Boolean;
|
SecPresent: array[0..5] of Boolean;
|
||||||
SecShtIdx: array[0..4] of Integer;
|
SecShtIdx: array[0..5] of Integer;
|
||||||
NumDataSecs: Integer;
|
NumDataSecs: Integer;
|
||||||
RelaShtIdx: array[0..4] of Integer;
|
RelaShtIdx: array[0..5] of Integer;
|
||||||
NumRelaSecs: Integer;
|
NumRelaSecs: Integer;
|
||||||
SymtabIdx, StrtabIdx, ShstrtabIdx, NoteIdx: Integer;
|
SymtabIdx, StrtabIdx, ShstrtabIdx, NoteIdx: Integer;
|
||||||
TotalShNum: Integer;
|
TotalShNum: Integer;
|
||||||
|
|
@ -884,8 +886,8 @@ var
|
||||||
ShdrOff: Int64;
|
ShdrOff: Int64;
|
||||||
CurOff: Int64;
|
CurOff: Int64;
|
||||||
|
|
||||||
SecFileOff: array[0..4] of Int64;
|
SecFileOff: array[0..5] of Int64;
|
||||||
RelaFileOff: array[0..4] of Int64;
|
RelaFileOff: array[0..5] of Int64;
|
||||||
SymtabFileOff, StrtabFileOff, ShstrtabFileOff, NoteFileOff: Int64;
|
SymtabFileOff, StrtabFileOff, ShstrtabFileOff, NoteFileOff: Int64;
|
||||||
|
|
||||||
SymtabBuf: TByteBuf;
|
SymtabBuf: TByteBuf;
|
||||||
|
|
@ -893,8 +895,8 @@ var
|
||||||
SymSecIdx: Integer;
|
SymSecIdx: Integer;
|
||||||
StInfo: Integer;
|
StInfo: Integer;
|
||||||
|
|
||||||
RelaBuf: array[0..4] of TByteBuf;
|
RelaBuf: array[0..5] of TByteBuf;
|
||||||
RelaCount: array[0..4] of Integer;
|
RelaCount: array[0..5] of Integer;
|
||||||
SymRemapIdx: array of Integer;
|
SymRemapIdx: array of Integer;
|
||||||
LocalSymCount, GlobalSymCount: Integer;
|
LocalSymCount, GlobalSymCount: Integer;
|
||||||
LocalSyms, GlobalSyms: TList<Integer>;
|
LocalSyms, GlobalSyms: TList<Integer>;
|
||||||
|
|
@ -902,8 +904,8 @@ var
|
||||||
|
|
||||||
ShFlags: Int64;
|
ShFlags: Int64;
|
||||||
ShType: Integer;
|
ShType: Integer;
|
||||||
SecNameIdx: array[0..4] of Integer;
|
SecNameIdx: array[0..5] of Integer;
|
||||||
RelaNameIdx: array[0..4] of Integer;
|
RelaNameIdx: array[0..5] of Integer;
|
||||||
SymtabNameIdx, StrtabNameIdx, ShstrtabNameIdx, NoteNameIdx: Integer;
|
SymtabNameIdx, StrtabNameIdx, ShstrtabNameIdx, NoteNameIdx: Integer;
|
||||||
|
|
||||||
begin
|
begin
|
||||||
|
|
@ -912,9 +914,10 @@ begin
|
||||||
SecOrder[2] := eskRodata;
|
SecOrder[2] := eskRodata;
|
||||||
SecOrder[3] := eskBss;
|
SecOrder[3] := eskBss;
|
||||||
SecOrder[4] := eskTbss;
|
SecOrder[4] := eskTbss;
|
||||||
|
SecOrder[5] := eskOpdf;
|
||||||
|
|
||||||
NumDataSecs := 0;
|
NumDataSecs := 0;
|
||||||
for I := 0 to 4 do
|
for I := 0 to 5 do
|
||||||
begin
|
begin
|
||||||
SecPresent[I] := FSections[Ord(SecOrder[I])] <> nil;
|
SecPresent[I] := FSections[Ord(SecOrder[I])] <> nil;
|
||||||
if SecPresent[I] then
|
if SecPresent[I] then
|
||||||
|
|
@ -927,7 +930,7 @@ begin
|
||||||
end;
|
end;
|
||||||
|
|
||||||
NumRelaSecs := 0;
|
NumRelaSecs := 0;
|
||||||
for I := 0 to 4 do
|
for I := 0 to 5 do
|
||||||
begin
|
begin
|
||||||
if SecPresent[I] and (GetSection(SecOrder[I]).RelocCount > 0) then
|
if SecPresent[I] and (GetSection(SecOrder[I]).RelocCount > 0) then
|
||||||
begin
|
begin
|
||||||
|
|
@ -952,7 +955,7 @@ begin
|
||||||
Strtab := TByteBuf.Create();
|
Strtab := TByteBuf.Create();
|
||||||
Shstrtab := TByteBuf.Create();
|
Shstrtab := TByteBuf.Create();
|
||||||
SymtabBuf := TByteBuf.Create();
|
SymtabBuf := TByteBuf.Create();
|
||||||
for I := 0 to 4 do
|
for I := 0 to 5 do
|
||||||
RelaBuf[I] := nil;
|
RelaBuf[I] := nil;
|
||||||
LocalSyms := TList<Integer>.Create();
|
LocalSyms := TList<Integer>.Create();
|
||||||
GlobalSyms := TList<Integer>.Create();
|
GlobalSyms := TList<Integer>.Create();
|
||||||
|
|
@ -963,7 +966,7 @@ begin
|
||||||
|
|
||||||
{ Build .shstrtab — section name string table }
|
{ Build .shstrtab — section name string table }
|
||||||
Shstrtab.PushByte(0);
|
Shstrtab.PushByte(0);
|
||||||
for I := 0 to 4 do
|
for I := 0 to 5 do
|
||||||
begin
|
begin
|
||||||
SecNameIdx[I] := 0;
|
SecNameIdx[I] := 0;
|
||||||
RelaNameIdx[I] := 0;
|
RelaNameIdx[I] := 0;
|
||||||
|
|
@ -1007,7 +1010,7 @@ begin
|
||||||
else
|
else
|
||||||
begin
|
begin
|
||||||
SymSecIdx := 0;
|
SymSecIdx := 0;
|
||||||
for J := 0 to 4 do
|
for J := 0 to 5 do
|
||||||
if SecPresent[J] and (SecOrder[J] = Sym.Section) then
|
if SecPresent[J] and (SecOrder[J] = Sym.Section) then
|
||||||
begin
|
begin
|
||||||
SymSecIdx := SecShtIdx[J];
|
SymSecIdx := SecShtIdx[J];
|
||||||
|
|
@ -1027,7 +1030,7 @@ begin
|
||||||
else
|
else
|
||||||
begin
|
begin
|
||||||
SymSecIdx := 0;
|
SymSecIdx := 0;
|
||||||
for J := 0 to 4 do
|
for J := 0 to 5 do
|
||||||
if SecPresent[J] and (SecOrder[J] = Sym.Section) then
|
if SecPresent[J] and (SecOrder[J] = Sym.Section) then
|
||||||
begin
|
begin
|
||||||
SymSecIdx := SecShtIdx[J];
|
SymSecIdx := SecShtIdx[J];
|
||||||
|
|
@ -1040,7 +1043,7 @@ begin
|
||||||
end;
|
end;
|
||||||
|
|
||||||
{ Build .rela.* sections }
|
{ Build .rela.* sections }
|
||||||
for I := 0 to 4 do
|
for I := 0 to 5 do
|
||||||
begin
|
begin
|
||||||
RelaBuf[I] := TByteBuf.Create();
|
RelaBuf[I] := TByteBuf.Create();
|
||||||
RelaCount[I] := 0;
|
RelaCount[I] := 0;
|
||||||
|
|
@ -1060,7 +1063,7 @@ begin
|
||||||
then the section header table at the end. }
|
then the section header table at the end. }
|
||||||
CurOff := Int64(ELF64_EHDR_SIZE);
|
CurOff := Int64(ELF64_EHDR_SIZE);
|
||||||
|
|
||||||
for I := 0 to 4 do
|
for I := 0 to 5 do
|
||||||
begin
|
begin
|
||||||
SecFileOff[I] := Int64(0);
|
SecFileOff[I] := Int64(0);
|
||||||
if not SecPresent[I] then Continue;
|
if not SecPresent[I] then Continue;
|
||||||
|
|
@ -1072,7 +1075,7 @@ begin
|
||||||
CurOff := CurOff + Int64(Sec.Count);
|
CurOff := CurOff + Int64(Sec.Count);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
for I := 0 to 4 do
|
for I := 0 to 5 do
|
||||||
begin
|
begin
|
||||||
RelaFileOff[I] := Int64(0);
|
RelaFileOff[I] := Int64(0);
|
||||||
if RelaShtIdx[I] = 0 then Continue;
|
if RelaShtIdx[I] = 0 then Continue;
|
||||||
|
|
@ -1132,7 +1135,7 @@ begin
|
||||||
OutBuf.AppendBytes(Buf);
|
OutBuf.AppendBytes(Buf);
|
||||||
|
|
||||||
{ Emit section data bodies }
|
{ Emit section data bodies }
|
||||||
for I := 0 to 4 do
|
for I := 0 to 5 do
|
||||||
begin
|
begin
|
||||||
if not SecPresent[I] then Continue;
|
if not SecPresent[I] then Continue;
|
||||||
Sec := GetSection(SecOrder[I]);
|
Sec := GetSection(SecOrder[I]);
|
||||||
|
|
@ -1142,7 +1145,7 @@ begin
|
||||||
end;
|
end;
|
||||||
|
|
||||||
{ Emit .rela.* bodies }
|
{ Emit .rela.* bodies }
|
||||||
for I := 0 to 4 do
|
for I := 0 to 5 do
|
||||||
begin
|
begin
|
||||||
if RelaShtIdx[I] = 0 then Continue;
|
if RelaShtIdx[I] = 0 then Continue;
|
||||||
OutBuf.PadTo(Integer(RelaFileOff[I]));
|
OutBuf.PadTo(Integer(RelaFileOff[I]));
|
||||||
|
|
@ -1170,7 +1173,7 @@ begin
|
||||||
ElfEmitShdr(Buf, 0, SHT_NULL, 0, 0, 0, 0, 0, 0, 0, 0);
|
ElfEmitShdr(Buf, 0, SHT_NULL, 0, 0, 0, 0, 0, 0, 0, 0);
|
||||||
|
|
||||||
{ Data sections }
|
{ Data sections }
|
||||||
for I := 0 to 4 do
|
for I := 0 to 5 do
|
||||||
begin
|
begin
|
||||||
if not SecPresent[I] then Continue;
|
if not SecPresent[I] then Continue;
|
||||||
Sec := GetSection(SecOrder[I]);
|
Sec := GetSection(SecOrder[I]);
|
||||||
|
|
@ -1200,6 +1203,14 @@ begin
|
||||||
ShFlags := SHF_ALLOC or SHF_WRITE or SHF_TLS;
|
ShFlags := SHF_ALLOC or SHF_WRITE or SHF_TLS;
|
||||||
ShType := SHT_NOBITS;
|
ShType := SHT_NOBITS;
|
||||||
end;
|
end;
|
||||||
|
eskOpdf:
|
||||||
|
begin
|
||||||
|
{ OPDF debug section: alloc+write progbits, matching GNU as output
|
||||||
|
for `.section .opdf, "aw", @progbits`. SHF_ALLOC makes it ride
|
||||||
|
into the loadable image so the debugger finds it by name. }
|
||||||
|
ShFlags := SHF_ALLOC or SHF_WRITE;
|
||||||
|
ShType := SHT_PROGBITS;
|
||||||
|
end;
|
||||||
end;
|
end;
|
||||||
ElfEmitShdr(Buf, SecNameIdx[I], ShType, ShFlags, 0,
|
ElfEmitShdr(Buf, SecNameIdx[I], ShType, ShFlags, 0,
|
||||||
SecFileOff[I], Int64(Sec.Size), 0, 0,
|
SecFileOff[I], Int64(Sec.Size), 0, 0,
|
||||||
|
|
@ -1207,7 +1218,7 @@ begin
|
||||||
end;
|
end;
|
||||||
|
|
||||||
{ .rela.* sections }
|
{ .rela.* sections }
|
||||||
for I := 0 to 4 do
|
for I := 0 to 5 do
|
||||||
begin
|
begin
|
||||||
if RelaShtIdx[I] = 0 then Continue;
|
if RelaShtIdx[I] = 0 then Continue;
|
||||||
ElfEmitShdr(Buf, RelaNameIdx[I], SHT_RELA,
|
ElfEmitShdr(Buf, RelaNameIdx[I], SHT_RELA,
|
||||||
|
|
@ -1247,7 +1258,7 @@ begin
|
||||||
Strtab.Free();
|
Strtab.Free();
|
||||||
Shstrtab.Free();
|
Shstrtab.Free();
|
||||||
SymtabBuf.Free();
|
SymtabBuf.Free();
|
||||||
for I := 0 to 4 do
|
for I := 0 to 5 do
|
||||||
RelaBuf[I].Free();
|
RelaBuf[I].Free();
|
||||||
LocalSyms.Free();
|
LocalSyms.Free();
|
||||||
GlobalSyms.Free();
|
GlobalSyms.Free();
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,8 @@ type
|
||||||
TOPDFEmitter = class
|
TOPDFEmitter = class
|
||||||
private
|
private
|
||||||
FProgram: TProgram;
|
FProgram: TProgram;
|
||||||
|
FUnit: TUnit; { non-nil in unit mode; FProgram is nil then }
|
||||||
|
[Unretained] FUnitSymTable: TSymbolTable; { unit-mode symbol table (not owned) }
|
||||||
FSourceFile: string;
|
FSourceFile: string;
|
||||||
[Unretained] FFacts: TDbgFacts; { non-owned — provided by the driver }
|
[Unretained] FFacts: TDbgFacts; { non-owned — provided by the driver }
|
||||||
FOutput: TStringList;
|
FOutput: TStringList;
|
||||||
|
|
@ -67,6 +69,9 @@ type
|
||||||
procedure EmitInterface(AType: TInterfaceTypeDesc);
|
procedure EmitInterface(AType: TInterfaceTypeDesc);
|
||||||
procedure EmitProperties(AClass: TRecordTypeDesc);
|
procedure EmitProperties(AClass: TRecordTypeDesc);
|
||||||
procedure EmitConstants;
|
procedure EmitConstants;
|
||||||
|
procedure EmitConstantsFromList(AList: TObjectList);
|
||||||
|
procedure EmitTypesFromBlock(ABlock: TBlock);
|
||||||
|
procedure EmitGlobalVarsFromList(AList: TObjectList);
|
||||||
procedure EmitAllTypes;
|
procedure EmitAllTypes;
|
||||||
procedure EmitGlobalVars;
|
procedure EmitGlobalVars;
|
||||||
procedure EmitFunctionScopes;
|
procedure EmitFunctionScopes;
|
||||||
|
|
@ -86,8 +91,15 @@ type
|
||||||
function CanonicalName(AType: TTypeDesc): string;
|
function CanonicalName(AType: TTypeDesc): string;
|
||||||
function FieldsPayloadSize(ARec: TRecordTypeDesc): Integer;
|
function FieldsPayloadSize(ARec: TRecordTypeDesc): Integer;
|
||||||
procedure EmitFields(ARec: TRecordTypeDesc);
|
procedure EmitFields(ARec: TRecordTypeDesc);
|
||||||
|
function ActiveSymTable: TSymbolTable;
|
||||||
public
|
public
|
||||||
constructor Create(AProgram: TProgram; const ASourceFile: string);
|
constructor Create(AProgram: TProgram; const ASourceFile: string);
|
||||||
|
{ Unit-mode emitter: produces a complete, self-contained .opdf section for
|
||||||
|
a single unit's object file (no program directory, no 'main' scope).
|
||||||
|
The same section is concatenated by the linker with every other unit's
|
||||||
|
and the program's .opdf section. }
|
||||||
|
constructor CreateForUnit(AUnit: TUnit; ASymTable: TSymbolTable;
|
||||||
|
const ASourceFile: string);
|
||||||
{ Optional codegen facts: when set, scopes/locals/lines are emitted
|
{ Optional codegen facts: when set, scopes/locals/lines are emitted
|
||||||
from exact backend data instead of the approximate AST walk. }
|
from exact backend data instead of the approximate AST walk. }
|
||||||
procedure SetFacts(AFacts: TDbgFacts);
|
procedure SetFacts(AFacts: TDbgFacts);
|
||||||
|
|
@ -138,6 +150,8 @@ constructor TOPDFEmitter.Create(AProgram: TProgram; const ASourceFile: string);
|
||||||
begin
|
begin
|
||||||
inherited Create();
|
inherited Create();
|
||||||
FProgram := AProgram;
|
FProgram := AProgram;
|
||||||
|
FUnit := nil;
|
||||||
|
FUnitSymTable := nil;
|
||||||
FSourceFile := ASourceFile;
|
FSourceFile := ASourceFile;
|
||||||
FOutput := TStringList.Create();
|
FOutput := TStringList.Create();
|
||||||
FTypeNames := TStringList.Create();
|
FTypeNames := TStringList.Create();
|
||||||
|
|
@ -152,6 +166,37 @@ begin
|
||||||
FDone := False;
|
FDone := False;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
constructor TOPDFEmitter.CreateForUnit(AUnit: TUnit; ASymTable: TSymbolTable;
|
||||||
|
const ASourceFile: string);
|
||||||
|
begin
|
||||||
|
inherited Create();
|
||||||
|
FProgram := nil;
|
||||||
|
FUnit := AUnit;
|
||||||
|
FUnitSymTable := ASymTable;
|
||||||
|
FSourceFile := ASourceFile;
|
||||||
|
FOutput := TStringList.Create();
|
||||||
|
FTypeNames := TStringList.Create();
|
||||||
|
FTypeNames.Sorted := True;
|
||||||
|
FTypeNames.CaseSensitive := True;
|
||||||
|
FEmitted := TStringList.Create();
|
||||||
|
FEmitted.Sorted := True;
|
||||||
|
FEmitted.CaseSensitive := True;
|
||||||
|
FRecordCount := 0;
|
||||||
|
FTotRecIdx := -1;
|
||||||
|
FUnitDirRecCountIdx := -1;
|
||||||
|
FDone := False;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TOPDFEmitter.ActiveSymTable: TSymbolTable;
|
||||||
|
begin
|
||||||
|
if FUnit <> nil then
|
||||||
|
Result := FUnitSymTable
|
||||||
|
else if FProgram <> nil then
|
||||||
|
Result := FProgram.SymbolTable
|
||||||
|
else
|
||||||
|
Result := nil;
|
||||||
|
end;
|
||||||
|
|
||||||
destructor TOPDFEmitter.Destroy;
|
destructor TOPDFEmitter.Destroy;
|
||||||
begin
|
begin
|
||||||
FEmitted.Free();
|
FEmitted.Free();
|
||||||
|
|
@ -464,15 +509,20 @@ var
|
||||||
Ch: string;
|
Ch: string;
|
||||||
Pfx: string;
|
Pfx: string;
|
||||||
begin
|
begin
|
||||||
|
{ Mirror the native backend's ClassSymName exactly so the .quad vtable_<sym>
|
||||||
|
label here matches the symbol the backend actually defined. In WHOLE-
|
||||||
|
PROGRAM mode the backend has a program name and drops the prefix for
|
||||||
|
program-owned classes; in UNIT mode there is no program name, so even the
|
||||||
|
unit's own classes carry their unit prefix (vtable_<Unit>_<Class>). }
|
||||||
Pfx := '';
|
Pfx := '';
|
||||||
if (FProgram <> nil) and (FProgram.SymbolTable <> nil) then
|
if Self.ActiveSymTable() <> nil then
|
||||||
begin
|
begin
|
||||||
Sym := FProgram.SymbolTable.Lookup(AClassName);
|
Sym := Self.ActiveSymTable().Lookup(AClassName);
|
||||||
if Sym <> nil then
|
if Sym <> nil then
|
||||||
begin
|
begin
|
||||||
Owner := Sym.OwningUnit;
|
Owner := Sym.OwningUnit;
|
||||||
if (Owner <> '') and
|
if (Owner <> '') and
|
||||||
not SameText(Owner, FProgram.Name) and
|
not ((FProgram <> nil) and SameText(Owner, FProgram.Name)) and
|
||||||
not SameText(Owner, 'System') and
|
not SameText(Owner, 'System') and
|
||||||
not ((Length(Owner) >= 4) and SameText(Copy(Owner, 0, 4), 'rtl.')) 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
|
not ((Length(Owner) >= 7) and SameText(Copy(Owner, 0, 7), 'blaise_')) then
|
||||||
|
|
@ -706,7 +756,11 @@ begin
|
||||||
L(' .byte 2 # TargetArch: archX86_64');
|
L(' .byte 2 # TargetArch: archX86_64');
|
||||||
L(' .byte 8 # PointerSize: 8');
|
L(' .byte 8 # PointerSize: 8');
|
||||||
FTotRecIdx := FOutput.Count;
|
FTotRecIdx := FOutput.Count;
|
||||||
L(' .int 0 # TotalRecords (patched at end)');
|
{ TotalRecords = 0 selects stream-terminated mode: the reader does not trust
|
||||||
|
a count, it reads records until section EOF and skips any further 32-byte
|
||||||
|
'OPDF' magic headers (the next unit's block). This is what makes per-unit
|
||||||
|
.opdf sections concatenate cleanly at link time. pdr ignores this field. }
|
||||||
|
L(' .int 0 # TotalRecords (0 = stream-terminated)');
|
||||||
L(' .int 3 # Flags: HAS_DIRECTORY or DYNARRAY_LEN32');
|
L(' .int 3 # Flags: HAS_DIRECTORY or DYNARRAY_LEN32');
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
|
@ -991,16 +1045,16 @@ begin
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TOPDFEmitter.EmitConstants;
|
procedure TOPDFEmitter.EmitConstantsFromList(AList: TObjectList);
|
||||||
var
|
var
|
||||||
I: Integer;
|
I: Integer;
|
||||||
C: TConstDecl;
|
C: TConstDecl;
|
||||||
TypeID: Cardinal;
|
TypeID: Cardinal;
|
||||||
RecSize: Integer;
|
RecSize: Integer;
|
||||||
begin
|
begin
|
||||||
for I := 0 to FProgram.Block.ConstDecls.Count - 1 do
|
for I := 0 to AList.Count - 1 do
|
||||||
begin
|
begin
|
||||||
C := TConstDecl(FProgram.Block.ConstDecls.Items[I]);
|
C := TConstDecl(AList.Items[I]);
|
||||||
if C.IsFloat then
|
if C.IsFloat then
|
||||||
begin
|
begin
|
||||||
{ Floating-point constant: emit the 8-byte IEEE-754 Double via GAS
|
{ Floating-point constant: emit the 8-byte IEEE-754 Double via GAS
|
||||||
|
|
@ -1056,34 +1110,65 @@ begin
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TOPDFEmitter.EmitAllTypes;
|
procedure TOPDFEmitter.EmitConstants;
|
||||||
|
begin
|
||||||
|
if FUnit <> nil then
|
||||||
|
begin
|
||||||
|
EmitConstantsFromList(FUnit.IntfBlock.ConstDecls);
|
||||||
|
EmitConstantsFromList(FUnit.ImplBlock.ConstDecls);
|
||||||
|
end
|
||||||
|
else if FProgram <> nil then
|
||||||
|
EmitConstantsFromList(FProgram.Block.ConstDecls);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TOPDFEmitter.EmitTypesFromBlock(ABlock: TBlock);
|
||||||
var
|
var
|
||||||
I: Integer;
|
I: Integer;
|
||||||
TD: TTypeDecl;
|
TD: TTypeDecl;
|
||||||
TDesc: TTypeDesc;
|
TDesc: TTypeDesc;
|
||||||
|
ST: TSymbolTable;
|
||||||
|
begin
|
||||||
|
ST := Self.ActiveSymTable();
|
||||||
|
if ST = nil then Exit;
|
||||||
|
for I := 0 to ABlock.TypeDecls.Count - 1 do
|
||||||
|
begin
|
||||||
|
TD := TTypeDecl(ABlock.TypeDecls.Items[I]);
|
||||||
|
TDesc := ST.FindType(TD.Name);
|
||||||
|
if TDesc <> nil then
|
||||||
|
EmitTypeDesc(TDesc);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TOPDFEmitter.EmitAllTypes;
|
||||||
|
var
|
||||||
|
I: Integer;
|
||||||
GI: TGenericInstance;
|
GI: TGenericInstance;
|
||||||
|
ST: TSymbolTable;
|
||||||
|
GIList: TObjectList;
|
||||||
begin
|
begin
|
||||||
EmitUtf8Str();
|
EmitUtf8Str();
|
||||||
if FProgram.SymbolTable <> nil then
|
ST := Self.ActiveSymTable();
|
||||||
|
if ST <> nil then
|
||||||
begin
|
begin
|
||||||
EmitPrimitive(FProgram.SymbolTable.TypeInteger);
|
EmitPrimitive(ST.TypeInteger);
|
||||||
EmitPrimitive(FProgram.SymbolTable.TypeInt64);
|
EmitPrimitive(ST.TypeInt64);
|
||||||
EmitPrimitive(FProgram.SymbolTable.TypeByte);
|
EmitPrimitive(ST.TypeByte);
|
||||||
EmitPrimitive(FProgram.SymbolTable.TypeBoolean);
|
EmitPrimitive(ST.TypeBoolean);
|
||||||
end;
|
end;
|
||||||
for I := 0 to FProgram.Block.TypeDecls.Count - 1 do
|
if FUnit <> nil then
|
||||||
begin
|
begin
|
||||||
TD := TTypeDecl(FProgram.Block.TypeDecls.Items[I]);
|
EmitTypesFromBlock(FUnit.IntfBlock);
|
||||||
if FProgram.SymbolTable <> nil then
|
EmitTypesFromBlock(FUnit.ImplBlock);
|
||||||
begin
|
GIList := FUnit.GenericInstances;
|
||||||
TDesc := FProgram.SymbolTable.FindType(TD.Name);
|
end
|
||||||
if TDesc <> nil then
|
else
|
||||||
EmitTypeDesc(TDesc);
|
begin
|
||||||
end;
|
EmitTypesFromBlock(FProgram.Block);
|
||||||
|
GIList := FProgram.GenericInstances;
|
||||||
end;
|
end;
|
||||||
for I := 0 to FProgram.GenericInstances.Count - 1 do
|
for I := 0 to GIList.Count - 1 do
|
||||||
begin
|
begin
|
||||||
GI := TGenericInstance(FProgram.GenericInstances.Items[I]);
|
GI := TGenericInstance(GIList.Items[I]);
|
||||||
if (GI.TypeDesc <> nil) and (GI.TypeDesc.Kind = tyClass) then
|
if (GI.TypeDesc <> nil) and (GI.TypeDesc.Kind = tyClass) then
|
||||||
EmitTypeDesc(GI.TypeDesc);
|
EmitTypeDesc(GI.TypeDesc);
|
||||||
end;
|
end;
|
||||||
|
|
@ -1113,20 +1198,31 @@ begin
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TOPDFEmitter.EmitGlobalVars;
|
procedure TOPDFEmitter.EmitGlobalVarsFromList(AList: TObjectList);
|
||||||
var
|
var
|
||||||
I, J: Integer;
|
I, J: Integer;
|
||||||
V: TVarDecl;
|
V: TVarDecl;
|
||||||
begin
|
begin
|
||||||
for I := 0 to FProgram.Block.Decls.Count - 1 do
|
for I := 0 to AList.Count - 1 do
|
||||||
begin
|
begin
|
||||||
V := TVarDecl(FProgram.Block.Decls.Items[I]);
|
V := TVarDecl(AList.Items[I]);
|
||||||
if not V.IsGlobal then Continue;
|
if not V.IsGlobal then Continue;
|
||||||
for J := 0 to V.Names.Count - 1 do
|
for J := 0 to V.Names.Count - 1 do
|
||||||
EmitGlobalVar(V.Names.Strings[J], V.ResolvedType);
|
EmitGlobalVar(V.Names.Strings[J], V.ResolvedType);
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
procedure TOPDFEmitter.EmitGlobalVars;
|
||||||
|
begin
|
||||||
|
if FUnit <> nil then
|
||||||
|
begin
|
||||||
|
EmitGlobalVarsFromList(FUnit.IntfBlock.Decls);
|
||||||
|
EmitGlobalVarsFromList(FUnit.ImplBlock.Decls);
|
||||||
|
end
|
||||||
|
else if FProgram <> nil then
|
||||||
|
EmitGlobalVarsFromList(FProgram.Block.Decls);
|
||||||
|
end;
|
||||||
|
|
||||||
procedure TOPDFEmitter.SetFacts(AFacts: TDbgFacts);
|
procedure TOPDFEmitter.SetFacts(AFacts: TDbgFacts);
|
||||||
begin
|
begin
|
||||||
FFacts := AFacts;
|
FFacts := AFacts;
|
||||||
|
|
@ -1289,6 +1385,12 @@ begin
|
||||||
EmitFunctionScopesFromFacts();
|
EmitFunctionScopesFromFacts();
|
||||||
Exit;
|
Exit;
|
||||||
end;
|
end;
|
||||||
|
{ Non-facts (approximate AST-walk) path is whole-program only. Per-unit
|
||||||
|
incremental compilation only embeds OPDF when the backend produced exact
|
||||||
|
debug facts (native); the QBE backend produces none, so a unit-mode
|
||||||
|
emitter without facts emits no function scopes (acceptable: native is the
|
||||||
|
debug backend, see CLAUDE.md). }
|
||||||
|
if FProgram = nil then Exit;
|
||||||
Decls := TObjectList.Create(False);
|
Decls := TObjectList.Create(False);
|
||||||
Labels := TStringList.Create();
|
Labels := TStringList.Create();
|
||||||
try
|
try
|
||||||
|
|
@ -1447,9 +1549,11 @@ end;
|
||||||
|
|
||||||
procedure TOPDFEmitter.PatchTotalRecords;
|
procedure TOPDFEmitter.PatchTotalRecords;
|
||||||
begin
|
begin
|
||||||
if FTotRecIdx >= 0 then
|
{ Stream-terminated mode: TotalRecords stays 0. Readers (pdr) ignore the
|
||||||
FOutput.Strings[FTotRecIdx] := ' .int ' + IntToStr(FRecordCount) +
|
field and read records until section EOF, skipping any subsequent 32-byte
|
||||||
' # TotalRecords';
|
'OPDF' headers from concatenated per-unit blocks. Patching a real count
|
||||||
|
here would be wrong once the linker concatenates sections, so this is a
|
||||||
|
deliberate no-op. }
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TOPDFEmitter.PatchUnitDirRecordCount;
|
procedure TOPDFEmitter.PatchUnitDirRecordCount;
|
||||||
|
|
@ -1466,7 +1570,10 @@ begin
|
||||||
FDone := True;
|
FDone := True;
|
||||||
EmitSection();
|
EmitSection();
|
||||||
EmitHeader();
|
EmitHeader();
|
||||||
EmitUnitDirectory();
|
{ recUnitDirectory is emitted ONLY by the program object (pdr ignores it).
|
||||||
|
Units omit it; their records are read in stream-terminated mode. }
|
||||||
|
if FProgram <> nil then
|
||||||
|
EmitUnitDirectory();
|
||||||
EmitAllTypes();
|
EmitAllTypes();
|
||||||
EmitGlobalVars();
|
EmitGlobalVars();
|
||||||
EmitConstants();
|
EmitConstants();
|
||||||
|
|
|
||||||
|
|
@ -85,6 +85,7 @@ type
|
||||||
object's fields stayed nil. This crashed the stdlib JSON test suite
|
object's fields stayed nil. This crashed the stdlib JSON test suite
|
||||||
when its TTestCase base came from target/units. }
|
when its TTestCase base came from target/units. }
|
||||||
procedure TestIncrementalRebuild_VirtualCtorVtableSlot;
|
procedure TestIncrementalRebuild_VirtualCtorVtableSlot;
|
||||||
|
procedure TestDebugOpdf_PerUnitSection_InDependencyObject;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
implementation
|
implementation
|
||||||
|
|
@ -949,6 +950,104 @@ begin
|
||||||
AssertEquals('build2 stdout (cached ctor slot)', '7' + #10, Captured)
|
AssertEquals('build2 stdout (cached ctor slot)', '7' + #10, Captured)
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
procedure TSepCompileTests.TestDebugOpdf_PerUnitSection_InDependencyObject;
|
||||||
|
const
|
||||||
|
DepSrc =
|
||||||
|
'''
|
||||||
|
unit OpdfDep;
|
||||||
|
interface
|
||||||
|
procedure Greet(const AName: string);
|
||||||
|
implementation
|
||||||
|
procedure Greet(const AName: string);
|
||||||
|
var
|
||||||
|
Total: Integer;
|
||||||
|
Msg: string;
|
||||||
|
begin
|
||||||
|
Total := 0;
|
||||||
|
Msg := 'Hello, ';
|
||||||
|
Msg := Msg + AName;
|
||||||
|
Total := Total + 1;
|
||||||
|
WriteLn(Msg);
|
||||||
|
Total := Total + 1;
|
||||||
|
WriteLn('Count is ', Total)
|
||||||
|
end;
|
||||||
|
end.
|
||||||
|
''';
|
||||||
|
ProgSrc =
|
||||||
|
'''
|
||||||
|
program UseOpdfDep;
|
||||||
|
uses OpdfDep;
|
||||||
|
begin
|
||||||
|
Greet('World')
|
||||||
|
end.
|
||||||
|
''';
|
||||||
|
var
|
||||||
|
DepPas, ProgPas, ProgBin, CacheDir, DepObj: string;
|
||||||
|
Captured, ObjOut: string;
|
||||||
|
Rc: Integer;
|
||||||
|
Proc: TProcess;
|
||||||
|
Chunk: string;
|
||||||
|
begin
|
||||||
|
if not ToolchainAvailable() then
|
||||||
|
begin
|
||||||
|
Fail('toolchain missing — qbe or RTL not found');
|
||||||
|
Exit
|
||||||
|
end;
|
||||||
|
if not FileExists(BlaisePath()) then
|
||||||
|
begin
|
||||||
|
Fail('blaise binary missing at ' + BlaisePath());
|
||||||
|
Exit
|
||||||
|
end;
|
||||||
|
|
||||||
|
DepPas := FScratch + '/OpdfDep.pas';
|
||||||
|
ProgPas := FScratch + '/use_opdfdep.pas';
|
||||||
|
ProgBin := FScratch + '/use_opdfdep';
|
||||||
|
CacheDir := FScratch + '/units-opdf';
|
||||||
|
DepObj := CacheDir + '/opdfdep.o';
|
||||||
|
|
||||||
|
WriteFile(DepPas, DepSrc);
|
||||||
|
WriteFile(ProgPas, ProgSrc);
|
||||||
|
ForceDirectories(CacheDir);
|
||||||
|
|
||||||
|
{ Default (incremental) native build with --debug-opdf: the dependency
|
||||||
|
unit is compiled to its own .o in the cache dir. With per-unit OPDF the
|
||||||
|
worker embeds a self-contained .opdf section into that .o so pdr can break
|
||||||
|
inside OpdfDep.Greet. Before this feature only the program object carried
|
||||||
|
OPDF, so the dependency .o had no .opdf section. }
|
||||||
|
Rc := RunBlaise(['--source', ProgPas, '--output', ProgBin,
|
||||||
|
'--backend', 'native', '--debug-opdf',
|
||||||
|
'--unit-cache', CacheDir,
|
||||||
|
'--unit-path', FScratch], Captured);
|
||||||
|
AssertEquals('blaise(--debug-opdf) exit code (out: ' + Captured + ')', 0, Rc);
|
||||||
|
AssertTrue('use_opdfdep exists', FileExists(ProgBin));
|
||||||
|
AssertTrue('dependency object exists at ' + DepObj, FileExists(DepObj));
|
||||||
|
|
||||||
|
{ The unit .o must carry an .opdf section (objdump -h shows it). }
|
||||||
|
Proc := TProcess.Create(nil);
|
||||||
|
try
|
||||||
|
Proc.Executable := 'objdump';
|
||||||
|
Proc.Parameters.Add('-h');
|
||||||
|
Proc.Parameters.Add(DepObj);
|
||||||
|
Proc.Execute();
|
||||||
|
ObjOut := '';
|
||||||
|
repeat
|
||||||
|
Chunk := Proc.ReadOutput();
|
||||||
|
ObjOut := ObjOut + Chunk
|
||||||
|
until (Chunk = '') and not Proc.Running;
|
||||||
|
Proc.WaitOnExit()
|
||||||
|
finally
|
||||||
|
Proc.Free()
|
||||||
|
end;
|
||||||
|
AssertTrue('dependency .o has an .opdf section (objdump -h)',
|
||||||
|
Pos('.opdf', ObjOut) >= 0);
|
||||||
|
|
||||||
|
{ The program still runs correctly. }
|
||||||
|
Rc := RunBinary(ProgBin, Captured);
|
||||||
|
AssertEquals('use_opdfdep exit code', 0, Rc);
|
||||||
|
AssertEquals('use_opdfdep stdout', 'Hello, World' + #10 + 'Count is 2' + #10,
|
||||||
|
Captured)
|
||||||
|
end;
|
||||||
|
|
||||||
initialization
|
initialization
|
||||||
RegisterTest(TSepCompileTests);
|
RegisterTest(TSepCompileTests);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ type
|
||||||
procedure TestOPDF_SectionDeclaration;
|
procedure TestOPDF_SectionDeclaration;
|
||||||
procedure TestOPDF_HeaderMagic;
|
procedure TestOPDF_HeaderMagic;
|
||||||
procedure TestOPDF_HeaderVersion;
|
procedure TestOPDF_HeaderVersion;
|
||||||
procedure TestOPDF_TotalRecordsPatched;
|
procedure TestOPDF_TotalRecordsStreamTerminated;
|
||||||
procedure TestOPDF_Primitive_Integer;
|
procedure TestOPDF_Primitive_Integer;
|
||||||
procedure TestOPDF_Primitive_Boolean;
|
procedure TestOPDF_Primitive_Boolean;
|
||||||
procedure TestOPDF_Primitive_Int64;
|
procedure TestOPDF_Primitive_Int64;
|
||||||
|
|
@ -132,13 +132,16 @@ begin
|
||||||
AssertTrue('version word present', Contains(IR, '.word 1'));
|
AssertTrue('version word present', Contains(IR, '.word 1'));
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TOPDFTests.TestOPDF_TotalRecordsPatched;
|
procedure TOPDFTests.TestOPDF_TotalRecordsStreamTerminated;
|
||||||
var
|
var
|
||||||
IR: string;
|
IR: string;
|
||||||
begin
|
begin
|
||||||
|
{ Stream-terminated mode: TotalRecords stays 0 so per-unit .opdf sections
|
||||||
|
concatenate at link time (the reader reads to section EOF, skipping any
|
||||||
|
further 'OPDF' magic headers). pdr ignores the count entirely. }
|
||||||
IR := GenOPDF('program P; var X: Integer; begin end.');
|
IR := GenOPDF('program P; var X: Integer; begin end.');
|
||||||
AssertFalse('TotalRecords not still zero placeholder',
|
AssertTrue('TotalRecords is the stream-terminated zero',
|
||||||
Contains(IR, '.int 0 # TotalRecords'));
|
Contains(IR, '.int 0 # TotalRecords (0 = stream-terminated)'));
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TOPDFTests.TestOPDF_Primitive_Integer;
|
procedure TOPDFTests.TestOPDF_Primitive_Integer;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue