Skip to content

Commit f9192a6

Browse files
feat: Add support for linker plugins (#1411)
Fixes #1
1 parent 311e066 commit f9192a6

34 files changed

Lines changed: 2358 additions & 223 deletions

BENCHMARKING.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@ particular:
88

99
* Wild defaults to `--gc-sections`, so for a fair comparison, that should be passed to all the linkers.
1010
* Wild defaults to `-z now`, so best to pass that to all linkers.
11-
* Wild doesn't support linker plugins and various other flags. Generally use of these flags will
12-
result in a warning.
1311

1412
## How to benchmark
1513

Cargo.lock

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ itertools = "0.14.0"
5858
jobserver = "0.1.30"
5959
leb128 = "0.2.5"
6060
libc = "0.2.171"
61+
libloading = "0.8.6"
6162
memchr = "2.6.0"
6263
memmap2 = "0.9.0"
6364
mimalloc = { version = "0.1", default-features = false }

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ The following is working with the caveat that there may be bugs:
129129
* GNU jobserver support
130130
* Very basic linker script support (section mapping, keeping sections, alignment, defining start /
131131
stop symbols).
132+
* Linker plugin LTO (requires building Wild with `--features=plugins`)
132133

133134
### What isn't yet supported?
134135

@@ -141,7 +142,6 @@ priority:
141142
* More complex linker scripts
142143
* Mac support
143144
* Windows support
144-
* LTO
145145

146146
### How can I verify that Wild was used to link a binary?
147147

libwild/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ itertools = { workspace = true }
3232
jobserver = { workspace = true }
3333
leb128 = {workspace = true}
3434
libc = { workspace = true }
35+
libloading = { workspace = true, optional = true }
3536
linker-layout = { path = "../linker-layout", version = "0.8.0" }
3637
linker-trace = { path = "../linker-trace", version = "0.8.0" }
3738
linker-utils = { path = "../linker-utils", version = "0.8.0" }
@@ -64,6 +65,9 @@ tempfile = { workspace = true }
6465
# Support for running the linker as a subprocess.
6566
fork = []
6667

68+
# Support for linker-plugins.
69+
plugins = ["libloading"]
70+
6771
# Enable support for emitting a perfetto trace.
6872
perfetto = ["perfetto-recorder/enable", "tracing/release_max_level_info"]
6973

libwild/src/args.rs

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ use object::elf::GNU_PROPERTY_X86_ISA_1_V2;
3333
use object::elf::GNU_PROPERTY_X86_ISA_1_V3;
3434
use object::elf::GNU_PROPERTY_X86_ISA_1_V4;
3535
use rayon::ThreadPoolBuilder;
36+
use std::ffi::CString;
3637
use std::fmt::Display;
3738
use std::mem::take;
3839
use std::num::NonZero;
@@ -108,6 +109,8 @@ pub struct Args {
108109
pub(crate) export_list_path: Option<PathBuf>,
109110
pub(crate) auxiliary: Vec<String>,
110111
pub(crate) enable_new_dtags: bool,
112+
pub(crate) plugin_path: Option<String>,
113+
pub(crate) plugin_args: Vec<CString>,
111114

112115
/// Symbol definitions from `--defsym` options. Each entry is (symbol_name, value_or_symbol).
113116
pub(crate) defsym: Vec<(String, DefsymValue)>,
@@ -292,6 +295,12 @@ pub struct Modifiers {
292295

293296
/// Whether archive semantics should be applied even for regular objects.
294297
pub(crate) archive_semantics: bool,
298+
299+
/// Whether the file is known to be a temporary file that will be deleted when the linker
300+
/// exits, e.g. an output file from a linker plugin. This doesn't affect linking, but is
301+
/// stored in the layout file if written so that linker-diff knows not to error if the file
302+
/// is missing.
303+
pub(crate) temporary: bool,
295304
}
296305

297306
#[derive(Debug, Eq, PartialEq)]
@@ -487,6 +496,8 @@ impl Default for Args {
487496
auxiliary: Vec::new(),
488497
numeric_experiments: Vec::new(),
489498
rpath_set: Default::default(),
499+
plugin_path: None,
500+
plugin_args: Vec::new(),
490501
}
491502
}
492503
}
@@ -722,6 +733,7 @@ impl Default for Modifiers {
722733
allow_shared: true,
723734
whole_archive: false,
724735
archive_semantics: false,
736+
temporary: false,
725737
}
726738
}
727739
}
@@ -2408,8 +2420,9 @@ fn setup_argument_parser() -> ArgumentParser {
24082420
.declare_with_param()
24092421
.long("plugin-opt")
24102422
.help("Pass options to the plugin")
2411-
.execute(|_args, _modifier_stack, _value| {
2412-
// TODO: Implement support for linker plugins.
2423+
.execute(|args, _modifier_stack, value| {
2424+
args.plugin_args
2425+
.push(CString::new(value).context("Invalid --plugin-opt argument")?);
24132426
Ok(())
24142427
});
24152428

@@ -2426,8 +2439,8 @@ fn setup_argument_parser() -> ArgumentParser {
24262439
.declare_with_param()
24272440
.long("plugin")
24282441
.help("Load plugin")
2429-
.execute(|_args, _modifier_stack, value| {
2430-
warn_unsupported(&format!("--plugin {value}"))?;
2442+
.execute(|args, _modifier_stack, value| {
2443+
args.plugin_path = Some(value.to_owned());
24312444
Ok(())
24322445
});
24332446

libwild/src/diagnostics.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ impl<'data> SymbolInfoPrinter<'data> {
4343
ResolvedFile::Dynamic(obj) => Some(obj.common.file_id),
4444
ResolvedFile::LinkerScript(obj) => Some(obj.file_id),
4545
ResolvedFile::SyntheticSymbols(obj) => Some(obj.file_id),
46+
#[cfg(feature = "plugins")]
47+
ResolvedFile::LtoInput(obj) => Some(obj.file_id),
4648
})
4749
})
4850
.collect();
@@ -136,6 +138,11 @@ impl<'data> SymbolInfoPrinter<'data> {
136138
input = " <synthetic>".to_owned();
137139
sym_debug = "Synthetic symbol".to_owned();
138140
}
141+
#[cfg(feature = "plugins")]
142+
SequencedInput::LtoInput(o) => {
143+
input = o.to_string();
144+
sym_debug = o.symbol_properties_display(symbol_id).to_string();
145+
}
139146
}
140147

141148
// Versions can be either literally within the symbol name or in separate version

libwild/src/file_kind.rs

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ use crate::bail;
44
use crate::elf;
55
use crate::error::Result;
66
use object::LittleEndian;
7+
use object::read::elf::FileHeader;
8+
use object::read::elf::SectionHeader;
79

810
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
911
pub(crate) enum FileKind {
@@ -12,6 +14,8 @@ pub(crate) enum FileKind {
1214
Archive,
1315
ThinArchive,
1416
Text,
17+
LlvmIr,
18+
GccIr,
1519
}
1620

1721
impl FileKind {
@@ -34,16 +38,68 @@ impl FileKind {
3438
}
3539

3640
match header.e_type.get(LittleEndian) {
37-
object::elf::ET_REL => Ok(FileKind::ElfObject),
41+
object::elf::ET_REL => {
42+
if is_gcc_bitcode(bytes, header).unwrap_or(false) {
43+
Ok(FileKind::GccIr)
44+
} else {
45+
Ok(FileKind::ElfObject)
46+
}
47+
}
3848
object::elf::ET_DYN => Ok(FileKind::ElfDynamic),
3949
t => bail!("Unsupported ELF kind {t}"),
4050
}
4151
} else if bytes.is_ascii() {
4252
Ok(FileKind::Text)
4353
} else if bytes.starts_with(b"BC") {
44-
bail!("LLVM IR (LTO mode) is not supported yet");
54+
Ok(FileKind::LlvmIr)
4555
} else {
4656
bail!("Couldn't identify file type");
4757
}
4858
}
59+
60+
pub(crate) fn is_compiler_ir(self) -> bool {
61+
matches!(self, FileKind::LlvmIr | FileKind::GccIr)
62+
}
63+
}
64+
65+
/// Returns whether the supplied file contents is GCC IR. Scanning the entire section table would be
66+
/// expensive. Instead, we assume that we'll find a GCC LTO section within the first few sections,
67+
/// so just scan part of the section header strings table. It's unfortunate that GCC didn't tag
68+
/// these objects in some fast-to-check way.
69+
fn is_gcc_bitcode(data: &[u8], header: &crate::elf::FileHeader) -> Option<bool> {
70+
// If we don't have plugin support, then we skip checking if the file contains GCC IR. If it is,
71+
// then we'll figure that out later on and report an error. We do this because this code has a
72+
// measurable performance impact.
73+
if !cfg!(feature = "plugins") {
74+
return Some(false);
75+
}
76+
let e = LittleEndian;
77+
let section_headers = header.section_headers(e, data).ok()?;
78+
let sh_str_index = header.shstrndx(e, data).ok()?;
79+
let strings_section_header = section_headers.get(sh_str_index as usize)?;
80+
let start_offset = strings_section_header.sh_offset(e) as usize;
81+
let len = strings_section_header.sh_size(e) as usize;
82+
// In observed GCC IR files, the LTO section names start at offset 44 and end at 454. We want to
83+
// scan roughly the middle of this range.
84+
const START: usize = 100;
85+
// The longest GCC LTO section name is 47 bytes. We scan a bit more in case the first LTO
86+
// section started later than START.
87+
const MAX_SCAN: usize = 200;
88+
let strings = data.get(start_offset + START..start_offset + (START + MAX_SCAN).min(len))?;
89+
Some(memchr::memmem::find(strings, b"\0.gnu.lto_.").is_some())
90+
}
91+
92+
impl std::fmt::Display for FileKind {
93+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
94+
let s = match self {
95+
FileKind::ElfObject => "ELF object",
96+
FileKind::ElfDynamic => "ELF dynamic",
97+
FileKind::Archive => "archive",
98+
FileKind::ThinArchive => "thin archive",
99+
FileKind::Text => "text",
100+
FileKind::LlvmIr => "LLVM-IR",
101+
FileKind::GccIr => "GCC-IR",
102+
};
103+
std::fmt::Display::fmt(s, f)
104+
}
49105
}

libwild/src/grouping.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ pub(crate) enum Group<'data> {
2222
Objects(&'data [SequencedInputObject<'data>]),
2323
LinkerScripts(Vec<SequencedLinkerScript<'data>>),
2424
SyntheticSymbols(SyntheticSymbols),
25+
#[cfg(feature = "plugins")]
26+
LtoInputs(Vec<crate::linker_plugins::LtoInput<'data>>),
2527
}
2628

2729
#[derive(Debug)]
@@ -44,6 +46,8 @@ pub(crate) enum SequencedInput<'data> {
4446
Object(&'data SequencedInputObject<'data>),
4547
LinkerScript(&'data SequencedLinkerScript<'data>),
4648
SyntheticSymbols(&'data SyntheticSymbols),
49+
#[cfg(feature = "plugins")]
50+
LtoInput(&'data crate::linker_plugins::LtoInput<'data>),
4751
}
4852

4953
impl Group<'_> {
@@ -55,6 +59,8 @@ impl Group<'_> {
5559
Group::Objects(objects) => objects[0].file_id.group(),
5660
Group::LinkerScripts(scripts) => scripts[0].file_id.group(),
5761
Group::SyntheticSymbols(s) => s.file_id.group(),
62+
#[cfg(feature = "plugins")]
63+
Group::LtoInputs(s) => s[0].file_id.group(),
5864
}
5965
}
6066

@@ -80,6 +86,11 @@ impl Group<'_> {
8086
}
8187
}
8288
Group::SyntheticSymbols(o) => o.symbol_id_range,
89+
#[cfg(feature = "plugins")]
90+
Group::LtoInputs(objects) => SymbolIdRange::covering(
91+
objects[0].symbol_id_range,
92+
objects[objects.len() - 1].symbol_id_range,
93+
),
8394
}
8495
}
8596

@@ -314,6 +325,8 @@ impl SequencedInput<'_> {
314325
SequencedInput::Object(o) => o.symbol_id_range,
315326
SequencedInput::LinkerScript(o) => o.symbol_id_range,
316327
SequencedInput::SyntheticSymbols(o) => o.symbol_id_range,
328+
#[cfg(feature = "plugins")]
329+
SequencedInput::LtoInput(o) => o.symbol_id_range,
317330
}
318331
}
319332

@@ -359,6 +372,8 @@ impl Display for Group<'_> {
359372
}
360373
Group::LinkerScripts(scripts) => write!(f, "{} linker script(s)", scripts.len()),
361374
Group::SyntheticSymbols(_) => write!(f, "<epilogue>"),
375+
#[cfg(feature = "plugins")]
376+
Group::LtoInputs(lto_inputs) => write!(f, "<{} lto inputs>", lto_inputs.len()),
362377
}
363378
}
364379
}
@@ -370,6 +385,8 @@ impl std::fmt::Display for SequencedInput<'_> {
370385
SequencedInput::Object(o) => std::fmt::Display::fmt(o, f),
371386
SequencedInput::LinkerScript(o) => std::fmt::Display::fmt(&o.parsed, f),
372387
SequencedInput::SyntheticSymbols(_) => std::fmt::Display::fmt("<epilogue>", f),
388+
#[cfg(feature = "plugins")]
389+
SequencedInput::LtoInput(o) => std::fmt::Display::fmt(o, f),
373390
}
374391
}
375392
}

0 commit comments

Comments
 (0)