feat(link): pass external-library deps to the linker as -l<name>
The libraries collected from `external 'lib'` declarations (the program's
and every used unit's TUnitInterface.LinkLibs) are unioned into
TBackendOpts.LinkLibs and emitted on the toolchain link line as one
-l<name> each, additive to the always-needed -lm/-lpthread. Both the QBE
and native (external-linker) paths route through LinkViaToolchain and so
gain the flags.
The in-process internal linker (--linker internal) links only Blaise ELF
objects + the source-built RTL and has no concept of -l<name> system
libraries. Rather than silently drop a declared dependency and emit a
binary with unresolved symbols, LinkViaInternalLinker now fails loudly
when LinkLibs is non-empty, pointing the user at --linker external.
Tests: TestExternalLib_MissingLib_FailsLink_{QBE,Native} drive the real
compiler binary end to end (the e2e harness links with a hardcoded
`cc ... -lm -lpthread` and never calls LinkViaToolchain, so it cannot
exercise this). A program declaring `external 'nosuchlib...'` — never
called, but collected into LinkLibs at parse time — must fail the link
with the linker's "cannot find -lnosuchlib...", which only happens if the
driver actually emitted the flag.
This commit is contained in:
parent
c1f14b5c78
commit
edf4ffe374
|
|
@ -502,6 +502,7 @@ var
|
||||||
any ExportUnitInterface bugs against real
|
any ExportUnitInterface bugs against real
|
||||||
codebases. }
|
codebases. }
|
||||||
I: Integer;
|
I: Integer;
|
||||||
|
J: Integer;
|
||||||
IR: string;
|
IR: string;
|
||||||
IRFile: string;
|
IRFile: string;
|
||||||
LinkErr: string; { Driver.LinkProgram result ('' on success) }
|
LinkErr: string; { Driver.LinkProgram result ('' on success) }
|
||||||
|
|
@ -1055,6 +1056,22 @@ begin
|
||||||
if PrebuiltObjPaths.IndexOf(Loader.LinkOnlyObjects.Strings[I]) < 0 then
|
if PrebuiltObjPaths.IndexOf(Loader.LinkOnlyObjects.Strings[I]) < 0 then
|
||||||
PrebuiltObjPaths.Add(Loader.LinkOnlyObjects.Strings[I]);
|
PrebuiltObjPaths.Add(Loader.LinkOnlyObjects.Strings[I]);
|
||||||
end;
|
end;
|
||||||
|
{ Capture link-library deps (-l<name>) off the program AST and every used
|
||||||
|
unit's interface before Prog/Loader are freed below — the link step runs
|
||||||
|
after this finally block. Unioned into Opts.LinkLibs; the driver emits one
|
||||||
|
-l<name> each, additive to the RTL's -lm/-lpthread. }
|
||||||
|
if Opts.LinkLibs = nil then Opts.LinkLibs := TStringList.Create();
|
||||||
|
if (Prog <> nil) and (Prog.LinkLibs <> nil) then
|
||||||
|
for I := 0 to Prog.LinkLibs.Count - 1 do
|
||||||
|
if Opts.LinkLibs.IndexOf(TLinkLibDecl(Prog.LinkLibs.Items[I]).LibName) < 0 then
|
||||||
|
Opts.LinkLibs.Add(TLinkLibDecl(Prog.LinkLibs.Items[I]).LibName);
|
||||||
|
if Loader <> nil then
|
||||||
|
for I := 0 to Loader.PrebuiltIfaces.Count - 1 do
|
||||||
|
for J := 0 to TUnitInterface(Loader.PrebuiltIfaces.Items[I]).LinkLibs.Count - 1 do
|
||||||
|
if Opts.LinkLibs.IndexOf(
|
||||||
|
TUnitInterface(Loader.PrebuiltIfaces.Items[I]).LinkLibs.Strings[J]) < 0 then
|
||||||
|
Opts.LinkLibs.Add(
|
||||||
|
TUnitInterface(Loader.PrebuiltIfaces.Items[I]).LinkLibs.Strings[J]);
|
||||||
Units.Free();
|
Units.Free();
|
||||||
Loader.Free();
|
Loader.Free();
|
||||||
SearchPaths.Free();
|
SearchPaths.Free();
|
||||||
|
|
|
||||||
|
|
@ -98,6 +98,11 @@ type
|
||||||
runtime.start.static.<os>) replaces libc; the
|
runtime.start.static.<os>) replaces libc; the
|
||||||
linker emits a non-PIE ET_EXEC with no
|
linker emits a non-PIE ET_EXEC with no
|
||||||
PT_INTERP. docs/linux-syscall-migration.adoc }
|
PT_INTERP. docs/linux-syscall-migration.adoc }
|
||||||
|
LinkLibs: TStringList; { extra libraries to link (-l<name>), unioned by
|
||||||
|
the frontend from the program's and its used
|
||||||
|
units' 'external ''lib''' declarations
|
||||||
|
(TUnitInterface.LinkLibs). nil = none. Owned;
|
||||||
|
ARC-released with the opts object. }
|
||||||
end;
|
end;
|
||||||
|
|
||||||
TBackendDriver = class
|
TBackendDriver = class
|
||||||
|
|
@ -631,6 +636,12 @@ begin
|
||||||
Args.Add(RTLObjs.Strings[I]);
|
Args.Add(RTLObjs.Strings[I]);
|
||||||
Args.Add('-lm'); { math functions (sqrt, sin, cos, etc.) }
|
Args.Add('-lm'); { math functions (sqrt, sin, cos, etc.) }
|
||||||
Args.Add('-lpthread'); { POSIX threads (blaise_thread unit) }
|
Args.Add('-lpthread'); { POSIX threads (blaise_thread unit) }
|
||||||
|
{ Libraries declared via 'external ''lib''' in the program or any used unit
|
||||||
|
(unioned by the frontend into AOpts.LinkLibs). Emitted as -l<name> — ld
|
||||||
|
expands to lib<name>.so/.a. Additive to the always-needed -lm/-lpthread. }
|
||||||
|
if AOpts.LinkLibs <> nil then
|
||||||
|
for I := 0 to AOpts.LinkLibs.Count - 1 do
|
||||||
|
Args.Add('-l' + AOpts.LinkLibs.Strings[I]);
|
||||||
ExitCode := RunProcess(Self.ToolPath('linker', AOpts.Target), Args, Msg);
|
ExitCode := RunProcess(Self.ToolPath('linker', AOpts.Target), Args, Msg);
|
||||||
finally
|
finally
|
||||||
RTLObjs.Free();
|
RTLObjs.Free();
|
||||||
|
|
|
||||||
|
|
@ -197,6 +197,16 @@ var
|
||||||
begin
|
begin
|
||||||
Result := '';
|
Result := '';
|
||||||
|
|
||||||
|
{ The internal linker links only Blaise ELF objects (+ the source-built RTL);
|
||||||
|
it has no concept of -l<name> system libraries. A program that declares an
|
||||||
|
`external 'lib'` dependency therefore cannot be linked this way — fail loudly
|
||||||
|
rather than silently drop the library and emit a binary with unresolved
|
||||||
|
symbols. Use the external toolchain linker (the default) for such programs. }
|
||||||
|
if (AOpts.LinkLibs <> nil) and (AOpts.LinkLibs.Count > 0) then
|
||||||
|
Exit('internal linker cannot resolve external library ''' +
|
||||||
|
AOpts.LinkLibs.Strings[0] +
|
||||||
|
''' (from an ''external ''''lib'''''' declaration); use --linker external');
|
||||||
|
|
||||||
{ Build the linker with the resolved target's TLinkTarget so the emitted ELF
|
{ Build the linker with the resolved target's TLinkTarget so the emitted ELF
|
||||||
carries the right EI_OSABI / machine / load base for AOpts.Target — without
|
carries the right EI_OSABI / machine / load base for AOpts.Target — without
|
||||||
this the internal linker always produced a Linux-shaped ELF regardless of
|
this the internal linker always produced a Linux-shaped ELF regardless of
|
||||||
|
|
|
||||||
|
|
@ -75,6 +75,14 @@ type
|
||||||
procedure TestEmitAsm_WithExplicitQbeBackend_Rejected;
|
procedure TestEmitAsm_WithExplicitQbeBackend_Rejected;
|
||||||
procedure TestEmitIr_WithoutBackend_StillWorks;
|
procedure TestEmitIr_WithoutBackend_StillWorks;
|
||||||
procedure TestEmitIr_WithExplicitQbeBackend_StillWorks;
|
procedure TestEmitIr_WithExplicitQbeBackend_StillWorks;
|
||||||
|
{ ---- external 'lib' propagates a -l<name> to the real link command ---- }
|
||||||
|
{ A program declaring `external 'lib'` for a NON-EXISTENT library must fail
|
||||||
|
the link with the linker's "cannot find -l<lib>" — which only happens if
|
||||||
|
the driver actually emitted -l<lib>. The e2e harness can't test this (it
|
||||||
|
links with a hardcoded `cc ... -lm -lpthread` and never calls
|
||||||
|
LinkViaToolchain), so this drives the real compiler binary end to end. }
|
||||||
|
procedure TestExternalLib_MissingLib_FailsLink_QBE;
|
||||||
|
procedure TestExternalLib_MissingLib_FailsLink_Native;
|
||||||
{ ---- div/mod by zero raises a catchable EDivByZero (needs stdlib) ---- }
|
{ ---- div/mod by zero raises a catchable EDivByZero (needs stdlib) ---- }
|
||||||
procedure TestDivByZeroCaught_QBE;
|
procedure TestDivByZeroCaught_QBE;
|
||||||
procedure TestDivByZeroCaught_Native;
|
procedure TestDivByZeroCaught_Native;
|
||||||
|
|
@ -557,6 +565,52 @@ const
|
||||||
' end' + LineEnding +
|
' end' + LineEnding +
|
||||||
'end.';
|
'end.';
|
||||||
|
|
||||||
|
const
|
||||||
|
{ Declares an external from a library that does not exist. The function is
|
||||||
|
never called, but the parser collects the library into Prog.LinkLibs at
|
||||||
|
parse time, so the driver must still emit -lnosuchlib_blaise_xyz on the link
|
||||||
|
line — which the system linker then rejects. If the -l<name> propagation
|
||||||
|
were broken (no flag emitted), the link would SUCCEED. }
|
||||||
|
SrcMissingExternalLib =
|
||||||
|
'program P;' + LineEnding +
|
||||||
|
'function f(X: Integer): Integer;' +
|
||||||
|
' external ''nosuchlib_blaise_xyz'' name ''nope'';' + LineEnding +
|
||||||
|
'begin' + LineEnding +
|
||||||
|
' WriteLn(''unreached'')' + LineEnding +
|
||||||
|
'end.';
|
||||||
|
|
||||||
|
procedure TCLIContractTests.TestExternalLib_MissingLib_FailsLink_QBE;
|
||||||
|
var SrcPath, BinPath, Out_: string; EC: Integer;
|
||||||
|
begin
|
||||||
|
if not CompilerAvailable() then begin Ignore('<toolchain-missing>'); Exit; end;
|
||||||
|
SrcPath := WriteScratchSource(SrcMissingExternalLib);
|
||||||
|
BinPath := FScratch + 'cli_misslib_qbe_' + IntToStr(FCounter);
|
||||||
|
EC := RunCompiler(['--source', SrcPath, '--backend', 'qbe',
|
||||||
|
'--unit-path', FRTLPath, '--unit-path', FStdlibPath,
|
||||||
|
'--output', BinPath], Out_);
|
||||||
|
AssertTrue('link must fail (missing library)', EC <> 0);
|
||||||
|
AssertTrue('linker reports the -l<name> it could not find',
|
||||||
|
Pos('nosuchlib_blaise_xyz', Out_) >= 0);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TCLIContractTests.TestExternalLib_MissingLib_FailsLink_Native;
|
||||||
|
var SrcPath, BinPath, Out_: string; EC: Integer;
|
||||||
|
begin
|
||||||
|
if not CompilerAvailable() then begin Ignore('<toolchain-missing>'); Exit; end;
|
||||||
|
SrcPath := WriteScratchSource(SrcMissingExternalLib);
|
||||||
|
BinPath := FScratch + 'cli_misslib_nat_' + IntToStr(FCounter);
|
||||||
|
{ --linker external: the internal linker cannot resolve -l<name> system
|
||||||
|
libraries (it errors out separately); the toolchain linker is what emits
|
||||||
|
and consumes -l<name>. }
|
||||||
|
EC := RunCompiler(['--source', SrcPath, '--backend', 'native',
|
||||||
|
'--linker', 'external',
|
||||||
|
'--unit-path', FRTLPath, '--unit-path', FStdlibPath,
|
||||||
|
'--output', BinPath], Out_);
|
||||||
|
AssertTrue('link must fail (missing library)', EC <> 0);
|
||||||
|
AssertTrue('linker reports the -l<name> it could not find',
|
||||||
|
Pos('nosuchlib_blaise_xyz', Out_) >= 0);
|
||||||
|
end;
|
||||||
|
|
||||||
procedure TCLIContractTests.TestDivByZeroCaught_QBE;
|
procedure TCLIContractTests.TestDivByZeroCaught_QBE;
|
||||||
var Out_: string; EC: Integer;
|
var Out_: string; EC: Integer;
|
||||||
begin
|
begin
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue