feat(parser): external 'lib' name directive + link-library propagation
`external 'c' name 'strlen'` now records the bare library name on the declaration (TMethodDecl.ExternalLib) and hoists it into the owning unit/program's LinkLibs set, so the link layer can expand it to a -l<name> dependency. The library clause is optional and parsed before the existing `name '...'` clause, so `external name 'x'` and bare `external 'c'` both still parse. TUnitInterface.LinkLibs is serialised in the .bif META block (one EncodeStringList after ImplUsedUnits, before HasInitialization) so a unit's external-library dependencies round-trip across separate compilation. IFACE_VERSION is bumped 6 -> 7: the META layout grew, so a v6 reader must reject these .bif and recompile (without the bump a v6 binary would misparse the extra field and read past the END marker). Tests: a parser test asserting `external 'c' name 'strlen'` populates ExternalLib + the program's LinkLibs; a .bif round-trip test for TUnitInterface.LinkLibs; the magic/version assertion updated to v7; and the existing e2e strlen case. bif-coverage.status records TUnitInterface.LinkLibs as serialised.
This commit is contained in:
parent
e2ee936663
commit
c1f14b5c78
|
|
@ -863,6 +863,7 @@ type
|
|||
consumed by codegen for cross-unit references. }
|
||||
IsExternal: Boolean; { declared with 'external' directive — no body }
|
||||
ExternalName: string; { C symbol name from 'external name ''c_foo'''; empty = use Pascal name }
|
||||
ExternalLib: string; { library from 'external ''c'' name ''malloc'''; bare name, link layer expands per platform. Also recorded unit-level in LinkLibs. }
|
||||
NoStackFrame: Boolean; { declared 'nostackframe' — body is an asm
|
||||
block that owns the whole frame; codegen
|
||||
emits no compiler prologue/epilogue }
|
||||
|
|
@ -1067,6 +1068,19 @@ type
|
|||
destructor Destroy; override;
|
||||
end;
|
||||
|
||||
{ A library this compilation unit must be linked against — collected by the
|
||||
parser from every 'external ''lib''' declaration (interface OR
|
||||
implementation). Hoisted to the unit level so an implementation-private
|
||||
import still records its link dependency: the private routine stays out of
|
||||
the .bif, but the library it needs is serialised via
|
||||
TUnitInterface.LinkLibs and propagates to any program that uses the unit.
|
||||
LibName is the bare name ('c', 'kernel32') — the link layer expands it per
|
||||
platform (-l<name> on ELF, <name>.dll on PE). }
|
||||
TLinkLibDecl = class(TASTNode)
|
||||
public
|
||||
LibName: string;
|
||||
end;
|
||||
|
||||
{ ------------------------------------------------------------------ }
|
||||
{ Block and Program }
|
||||
{ ------------------------------------------------------------------ }
|
||||
|
|
@ -1082,6 +1096,7 @@ type
|
|||
GenericMethodInstances: TObjectList; { owned TGenericMethodInstance — populated by uSemantic }
|
||||
GenericIntfInstances: TObjectList; { owned TGenericInterfaceInstance — populated by uSemantic }
|
||||
GenericRecordInstances: TObjectList; { owned TGenericRecordInstance — populated by uSemantic }
|
||||
LinkLibs: TObjectList; { owned TLinkLibDecl — libraries to link, collected by the parser }
|
||||
constructor Create;
|
||||
destructor Destroy; override;
|
||||
end;
|
||||
|
|
@ -1105,6 +1120,7 @@ type
|
|||
GenericMethodInstances: TObjectList;
|
||||
GenericIntfInstances: TObjectList;
|
||||
GenericRecordInstances: TObjectList;
|
||||
LinkLibs: TObjectList; { owned TLinkLibDecl — libraries to link, collected by the parser }
|
||||
constructor Create;
|
||||
destructor Destroy; override;
|
||||
end;
|
||||
|
|
@ -1811,6 +1827,7 @@ begin
|
|||
GenericMethodInstances := TObjectList.Create(True);
|
||||
GenericIntfInstances := TObjectList.Create(True);
|
||||
GenericRecordInstances := TObjectList.Create(True);
|
||||
LinkLibs := TObjectList.Create(True);
|
||||
end;
|
||||
|
||||
destructor TConstDecl.Destroy;
|
||||
|
|
@ -1841,6 +1858,7 @@ begin
|
|||
GenericMethodInstances := TObjectList.Create(True);
|
||||
GenericIntfInstances := TObjectList.Create(True);
|
||||
GenericRecordInstances := TObjectList.Create(True);
|
||||
LinkLibs := TObjectList.Create(True);
|
||||
end;
|
||||
|
||||
destructor TUnit.Destroy;
|
||||
|
|
@ -2437,6 +2455,7 @@ begin
|
|||
Result.IsPublished := ASrc.IsPublished;
|
||||
Result.IsExternal := ASrc.IsExternal;
|
||||
Result.ExternalName := ASrc.ExternalName;
|
||||
Result.ExternalLib := ASrc.ExternalLib;
|
||||
Result.CallingConv := ASrc.CallingConv;
|
||||
Result.IsRecordMethod := ASrc.IsRecordMethod;
|
||||
{ Static (class-level) methods take no implicit Self. Dropping this in the
|
||||
|
|
|
|||
|
|
@ -55,7 +55,12 @@ type
|
|||
FUsedUnits: TStringList; { unit names from every 'uses' clause, plus implicit
|
||||
'System'. Consulted purely as a syntactic name list
|
||||
to recognise 'UnitName.Symbol' — no symbol lookup. }
|
||||
FLinkLibs: TObjectList; { non-owning ref to the current TUnit/TProgram.LinkLibs;
|
||||
external 'lib' decls append a TLinkLibDecl here as they
|
||||
are parsed (any section). nil outside a unit/program. }
|
||||
|
||||
{ Record a 'link library X' dependency on the unit/program being parsed. }
|
||||
procedure AddLinkLib(const ALibName: string; ALine, ACol: Integer);
|
||||
function IsUsedUnit(const AName: string): Boolean;
|
||||
function IsUnitPrefix(const AName: string): Boolean;
|
||||
{ Unbounded lookahead, exposed as scalar accessors (0 = FCurrent). A
|
||||
|
|
@ -261,6 +266,20 @@ begin
|
|||
end;
|
||||
|
||||
{ Case-insensitive membership test against the parsed 'uses' names. }
|
||||
{ Append a 'link library ALibName' dependency to the unit/program being parsed.
|
||||
Duplicates are tolerated — the exporter dedupes when building the .bif set. }
|
||||
procedure TParser.AddLinkLib(const ALibName: string; ALine, ACol: Integer);
|
||||
var
|
||||
LL: TLinkLibDecl;
|
||||
begin
|
||||
if (FLinkLibs = nil) or (ALibName = '') then Exit;
|
||||
LL := TLinkLibDecl.Create();
|
||||
LL.LibName := ALibName;
|
||||
LL.Line := ALine;
|
||||
LL.Col := ACol;
|
||||
FLinkLibs.Add(LL);
|
||||
end;
|
||||
|
||||
function TParser.IsUsedUnit(const AName: string): Boolean;
|
||||
var
|
||||
I: Integer;
|
||||
|
|
@ -754,6 +773,7 @@ end;
|
|||
function TParser.ParseProgram: TProgram;
|
||||
begin
|
||||
Result := TProgram.Create();
|
||||
FLinkLibs := Result.LinkLibs; { collect external 'lib' deps into the program }
|
||||
try
|
||||
Result.Line := FCurrent.Line;
|
||||
Result.Col := FCurrent.Col;
|
||||
|
|
@ -2505,6 +2525,15 @@ begin
|
|||
begin
|
||||
Result.IsExternal := True;
|
||||
Advance();
|
||||
{ optional: a library name string — external 'c' name 'malloc'.
|
||||
Records a unit-level link dependency (works for implementation
|
||||
imports too, so the private routine's library still propagates). }
|
||||
if Check(tkStringLit) then
|
||||
begin
|
||||
Result.ExternalLib := FCurrent.Value;
|
||||
AddLinkLib(FCurrent.Value, FCurrent.Line, FCurrent.Col);
|
||||
Advance();
|
||||
end;
|
||||
{ optional: name 'c_symbol' }
|
||||
if Check(tkIdent) and SameText(FCurrent.Value, 'name') then
|
||||
begin
|
||||
|
|
@ -4471,6 +4500,14 @@ begin
|
|||
begin
|
||||
Result.IsExternal := True;
|
||||
Advance();
|
||||
{ optional library name — external 'c' name 'malloc' (records a
|
||||
unit-level link dependency). }
|
||||
if Check(tkStringLit) then
|
||||
begin
|
||||
Result.ExternalLib := FCurrent.Value;
|
||||
AddLinkLib(FCurrent.Value, FCurrent.Line, FCurrent.Col);
|
||||
Advance();
|
||||
end;
|
||||
if Check(tkIdent) and SameText(FCurrent.Value, 'name') then
|
||||
begin
|
||||
Advance();
|
||||
|
|
@ -4523,6 +4560,7 @@ var
|
|||
InitStmt: TASTStmt;
|
||||
begin
|
||||
Result := TUnit.Create();
|
||||
FLinkLibs := Result.LinkLibs; { collect external 'lib' deps into the unit }
|
||||
try
|
||||
Result.Line := FCurrent.Line;
|
||||
Result.Col := FCurrent.Col;
|
||||
|
|
|
|||
|
|
@ -481,6 +481,13 @@ begin
|
|||
if AUnit.ImplUsedUnits <> nil then
|
||||
for I := 0 to AUnit.ImplUsedUnits.Count - 1 do
|
||||
AIface.ImplUsedUnits.Add(AUnit.ImplUsedUnits.Strings[I]);
|
||||
{ Link libraries — hoisted from every 'external ''lib''' decl (interface or
|
||||
implementation), deduped. Carries the unit's link dependencies into the
|
||||
.bif even when the importing routine is implementation-private. }
|
||||
if AUnit.LinkLibs <> nil then
|
||||
for I := 0 to AUnit.LinkLibs.Count - 1 do
|
||||
if AIface.LinkLibs.IndexOf(TLinkLibDecl(AUnit.LinkLibs.Items[I]).LibName) < 0 then
|
||||
AIface.LinkLibs.Add(TLinkLibDecl(AUnit.LinkLibs.Items[I]).LibName);
|
||||
AIface.HasInitialization :=
|
||||
(AUnit.InitStmts <> nil) and (AUnit.InitStmts.Count > 0);
|
||||
end;
|
||||
|
|
|
|||
|
|
@ -204,6 +204,14 @@ type
|
|||
that loads this unit from its cached .bif
|
||||
still pulls in (and links) impl-only
|
||||
dependencies. }
|
||||
LinkLibs: TStringList; { owned — bare library names this unit must be
|
||||
linked against (deduped), hoisted from every
|
||||
'external ''lib''' decl, interface OR
|
||||
implementation. An implementation-private
|
||||
import keeps its symbol out of the .bif but
|
||||
its library still propagates here, so a
|
||||
downstream program links it (-l<name> /
|
||||
<name>.dll). }
|
||||
HasInitialization: Boolean; { unit has a non-empty initialization
|
||||
section. An incremental rebuild loads this
|
||||
unit from its cached .bif and must still
|
||||
|
|
@ -402,6 +410,7 @@ begin
|
|||
Name := AName;
|
||||
UsedUnits := TStringList.Create();
|
||||
ImplUsedUnits := TStringList.Create();
|
||||
LinkLibs := TStringList.Create();
|
||||
|
||||
Types := TObjectList.Create(True);
|
||||
Consts := TObjectList.Create(True);
|
||||
|
|
@ -432,6 +441,7 @@ begin
|
|||
Consts.Free();
|
||||
Types.Free();
|
||||
|
||||
LinkLibs.Free();
|
||||
ImplUsedUnits.Free();
|
||||
UsedUnits.Free();
|
||||
inherited Destroy();
|
||||
|
|
|
|||
|
|
@ -53,7 +53,14 @@ uses
|
|||
|
||||
const
|
||||
IFACE_MAGIC = 'BLAISE-IFACE';
|
||||
IFACE_VERSION = 6; { v6 (this cycle): the `overload` directive now round-trips.
|
||||
IFACE_VERSION = 7; { v7 (this cycle): external-library link dependencies now
|
||||
round-trip. TUnitInterface.LinkLibs is serialised in
|
||||
the META block (one EncodeStringList after
|
||||
ImplUsedUnits, before HasInitialization) so a unit's
|
||||
`external 'lib' name '...'` declarations propagate the
|
||||
libraries to link. The META layout grew, so v6 readers
|
||||
must reject these .bif and recompile.
|
||||
v6: the `overload` directive now round-trips.
|
||||
TRoutineSig.IsOverload added to EncodeMethodSig/
|
||||
ReadMethodSig (one extra byte after IsStatic) and
|
||||
TMethodDecl.IsOverload added to EncodeMethodDecl/
|
||||
|
|
@ -735,6 +742,7 @@ begin
|
|||
EncodeInt64(AIface.SourceModTime) +
|
||||
EncodeStringList(AIface.UsedUnits) +
|
||||
EncodeStringList(AIface.ImplUsedUnits) +
|
||||
EncodeStringList(AIface.LinkLibs) +
|
||||
EncodeBool(AIface.HasInitialization));
|
||||
SB.AppendLine('END');
|
||||
Result := SB.ToString();
|
||||
|
|
@ -2120,6 +2128,9 @@ begin
|
|||
C := DecodeCount(AText, APos);
|
||||
for I := 1 to C do
|
||||
AIface.ImplUsedUnits.Add(ReadLpstrAt(AText, APos));
|
||||
C := DecodeCount(AText, APos);
|
||||
for I := 1 to C do
|
||||
AIface.LinkLibs.Add(ReadLpstrAt(AText, APos));
|
||||
AIface.HasInitialization := ReadLpstrAt(AText, APos) = '1';
|
||||
if ReadTag(AText, APos) <> 'END' then
|
||||
raise EIfaceFormatError.Create('META block: missing END marker');
|
||||
|
|
|
|||
|
|
@ -26,6 +26,11 @@ type
|
|||
{ Basic external call — strlen via external name }
|
||||
procedure TestRun_ExternalName_Strlen;
|
||||
|
||||
{ Library-qualified external — external 'c' name 'strlen': the bare library
|
||||
name is parsed and recorded (propagated via the unit's LinkLibs) while the
|
||||
symbol resolves as usual. }
|
||||
procedure TestRun_ExternalLibName_Strlen;
|
||||
|
||||
{ Narrow-int return masking: strlen returns size_t (64-bit) but declared
|
||||
as Byte/Word/SmallInt — upper bits must be masked/sign-extended. }
|
||||
procedure TestRun_ExternalByteReturn_MaskedCorrectly;
|
||||
|
|
@ -70,6 +75,18 @@ begin
|
|||
AssertRunsOnAll(Src, '5' + Chr(10), 0);
|
||||
end;
|
||||
|
||||
procedure TE2EExternalTests.TestRun_ExternalLibName_Strlen;
|
||||
const Src = '''
|
||||
program T;
|
||||
function c_strlen(S: PChar): Integer; external 'c' name 'strlen';
|
||||
begin
|
||||
WriteLn(c_strlen(PChar('hello')))
|
||||
end.
|
||||
''';
|
||||
begin
|
||||
AssertRunsOnAll(Src, '5' + Chr(10), 0);
|
||||
end;
|
||||
|
||||
procedure TE2EExternalTests.TestRun_ExternalByteReturn_MaskedCorrectly;
|
||||
const Src = '''
|
||||
program T;
|
||||
|
|
|
|||
|
|
@ -26,6 +26,9 @@ type
|
|||
procedure TestParse_ExternalProc_IsExternal;
|
||||
procedure TestParse_ExternalProc_ExternalNameEmpty;
|
||||
procedure TestParse_ExternalProcNamed_ExternalName;
|
||||
{ Parser — library-qualified external records ExternalLib and hoists
|
||||
the bare library name into the program's LinkLibs set. }
|
||||
procedure TestParse_ExternalLibName_RecordsLibAndLinkLib;
|
||||
{ Parser — standalone function }
|
||||
procedure TestParse_ExternalFunc_IsExternal;
|
||||
procedure TestParse_ExternalFuncNamed_ExternalName;
|
||||
|
|
@ -199,6 +202,36 @@ begin
|
|||
end;
|
||||
end;
|
||||
|
||||
procedure TExternalTests.TestParse_ExternalLibName_RecordsLibAndLinkLib;
|
||||
var
|
||||
Prog: TProgram;
|
||||
Decl: TMethodDecl;
|
||||
Lib: TLinkLibDecl;
|
||||
begin
|
||||
Prog := ParseSrc(
|
||||
'''
|
||||
program Test;
|
||||
function c_strlen(S: PChar): Integer; external 'c' name 'strlen';
|
||||
begin
|
||||
end.
|
||||
'''
|
||||
);
|
||||
try
|
||||
Decl := TMethodDecl(Prog.Block.ProcDecls.Items[0]);
|
||||
AssertTrue('IsExternal should be True', Decl.IsExternal);
|
||||
AssertEquals('ExternalName should be strlen', 'strlen', Decl.ExternalName);
|
||||
AssertEquals('ExternalLib should be c', 'c', Decl.ExternalLib);
|
||||
|
||||
{ The bare library name is hoisted into the program's LinkLibs set so
|
||||
the link layer can expand it to -l<name>. }
|
||||
AssertEquals('one link lib collected', 1, Prog.LinkLibs.Count);
|
||||
Lib := TLinkLibDecl(Prog.LinkLibs.Items[0]);
|
||||
AssertEquals('link lib name', 'c', Lib.LibName);
|
||||
finally
|
||||
Prog.Free();
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TExternalTests.TestParse_ExternalFunc_IsExternal;
|
||||
var
|
||||
Prog: TProgram;
|
||||
|
|
|
|||
|
|
@ -230,6 +230,8 @@ type
|
|||
procedure TestWrite_StartsWithMagicAndVersion;
|
||||
{ Unit name round-trips. }
|
||||
procedure TestRoundTrip_UnitNamePreserved;
|
||||
{ External-library link deps (LinkLibs) round-trip via the META block. }
|
||||
procedure TestRoundTrip_LinkLibsPreserved;
|
||||
{ Int const round-trips. }
|
||||
procedure TestRoundTrip_IntConstPreserved;
|
||||
{ String const round-trips, even with newlines + colons in the value. }
|
||||
|
|
@ -2693,12 +2695,12 @@ begin
|
|||
Iface := TUnitInterface.Create('U');
|
||||
try
|
||||
Buf := WriteUnitInterface(Iface);
|
||||
{ Blaise Pos is 0-based; match-at-start returns 0. Version is 6 since the
|
||||
`overload` directive (TRoutineSig.IsOverload + TMethodDecl.IsOverload) was
|
||||
added to the method encoded layouts (on top of v5's member Visibility,
|
||||
{ Blaise Pos is 0-based; match-at-start returns 0. Version is 7 since
|
||||
TUnitInterface.LinkLibs (external-library link deps) was added to the
|
||||
META block (on top of v6's `overload` directive, v5's member Visibility,
|
||||
v4's TRoutineSig.IsStatic, and v3's static-member facts). }
|
||||
AssertTrue('starts with magic',
|
||||
Pos('BLAISE-IFACE 6', Buf) = 0);
|
||||
Pos('BLAISE-IFACE 7', Buf) = 0);
|
||||
finally
|
||||
Iface.Free();
|
||||
end;
|
||||
|
|
@ -2723,6 +2725,29 @@ begin
|
|||
end;
|
||||
end;
|
||||
|
||||
procedure TIfaceIOTests.TestRoundTrip_LinkLibsPreserved;
|
||||
var
|
||||
Src, Dst: TUnitInterface;
|
||||
Buf: string;
|
||||
begin
|
||||
Src := TUnitInterface.Create('MyUnit');
|
||||
try
|
||||
Src.LinkLibs.Add('c');
|
||||
Src.LinkLibs.Add('m');
|
||||
Buf := WriteUnitInterface(Src);
|
||||
Dst := ReadUnitInterface(Buf);
|
||||
try
|
||||
AssertEquals('link-lib count', 2, Dst.LinkLibs.Count);
|
||||
AssertEquals('first lib', 'c', Dst.LinkLibs.Strings[0]);
|
||||
AssertEquals('second lib', 'm', Dst.LinkLibs.Strings[1]);
|
||||
finally
|
||||
Dst.Free();
|
||||
end;
|
||||
finally
|
||||
Src.Free();
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TIfaceIOTests.TestRoundTrip_IntConstPreserved;
|
||||
var
|
||||
Src, Dst: TUnitInterface;
|
||||
|
|
|
|||
|
|
@ -314,6 +314,7 @@ TUnitInterface.SourceModTime serialise
|
|||
TUnitInterface.CompilerId serialise
|
||||
TUnitInterface.UsedUnits serialise
|
||||
TUnitInterface.ImplUsedUnits serialise
|
||||
TUnitInterface.LinkLibs serialise
|
||||
TUnitInterface.HasInitialization serialise
|
||||
|
||||
# TMethodParam (uAST.pas:798)
|
||||
|
|
|
|||
Loading…
Reference in a new issue