Limit implementation module visibility and remove stale explanations

This commit is contained in:
Rui Ueyama committed 2026-09-15 00:57:51 +09:00
1 parent 4658ec473a
commit 8a56434bae
6 files changed
+61 -140

No files matched your search

+2 -3
View File
@@ -218,9 +218,8 @@ pub trait Arch: Layout {
Vec::new()
}
/// Rewrites the output image once every section is copied: big-endian
/// ARM32 converts code to BE8 form here, and x86-64 removes unneeded
/// ENDBR instructions.
/// Rewrites the output image once every section is copied. Big-endian
/// ARM32 converts code to BE8 form here.
fn finish_output(_ctx: &Context<Self>, _buf: &mut [u8]) {}
/// Writes an addend into a relocated location, for REL-type targets
+1 -1
View File
@@ -216,7 +216,7 @@ pub fn new_ehdr<E: Arch>(sh_flags: u64) -> ChunkHeader<E> {
hdr
}
// OutputShdr represents the section header. The section header is usually
// The section header table is usually
// located at the end of an ELF file and is optional for executables.
// Executables work without it because the runtime only reads the program
// header. Section header is significant only in object files and not
+26 -26
View File
@@ -1,36 +1,36 @@
//! A high-performance ELF linker.
pub mod arch;
pub mod archive_file;
pub mod chunks;
pub mod cmdline;
pub mod context;
pub(crate) mod archive_file;
pub(crate) mod chunks;
pub(crate) mod cmdline;
pub(crate) mod context;
pub mod driver;
pub mod elf;
mod elf_consts;
pub mod error;
pub mod filetype;
pub mod gc_sections;
pub mod gdb_index;
pub mod icf;
pub mod input_files;
pub mod input_sections;
pub mod jobs;
pub mod linker_script;
pub(crate) mod error;
pub(crate) mod filetype;
pub(crate) mod gc_sections;
pub(crate) mod gdb_index;
pub(crate) mod icf;
pub(crate) mod input_files;
pub(crate) mod input_sections;
pub(crate) mod jobs;
pub(crate) mod linker_script;
#[cfg(not(all(target_os = "windows", target_env = "msvc")))]
pub mod lto;
pub(crate) mod lto;
#[cfg(all(target_os = "windows", target_env = "msvc"))]
#[path = "lto_win32.rs"]
pub mod lto;
pub mod mapfile;
pub mod mapped_file;
pub mod output_file;
pub mod passes;
pub mod reader;
pub mod relocatable;
pub mod shrink_sections;
pub mod subprocess;
pub mod symbol;
pub mod thunks;
pub mod tls;
pub(crate) mod lto;
pub(crate) mod mapfile;
pub(crate) mod mapped_file;
pub(crate) mod output_file;
pub(crate) mod passes;
pub(crate) mod reader;
pub(crate) mod relocatable;
pub(crate) mod shrink_sections;
pub(crate) mod subprocess;
pub(crate) mod symbol;
pub(crate) mod thunks;
pub(crate) mod tls;
pub mod util;
+3 -81
View File
@@ -1,83 +1,3 @@
//! This file handles the linker plugin to support LTO (Link-Time
//! Optimization).
//!
//! LTO is a technique to do whole-program optimization to a program. Since
//! a linker sees the whole program as opposed to a single compilation
//! unit, it in theory can do some optimizations that cannot be done in the
//! usual separate compilation model. For example, LTO should be able to
//! inline functions that are defined in other compilation unit.
//!
//! In GCC and Clang, all you have to do to enable LTO is adding the
//! `-flto` flag to the compiler and the linker command lines. If `-flto`
//! is given, the compiler generates a file that contains not machine code
//! but the compiler's IR (intermediate representation). In GCC, the output
//! is an ELF file which wraps GCC's IR. In LLVM, it's not even an ELF file
//! but just a raw LLVM IR file.
//!
//! Here is what we have to do if at least one input file is not a usual
//! ELF file but an IR object file:
//!
//! 1. Read symbols both from usual ELF files and from IR object files and
//! resolve symbols as usual.
//!
//! 2. Pass all IR objects to the compiler backend. The compiler backend
//! compiles the IRs and returns a few big ELF object files as a
//! result.
//!
//! 3. Parse the returned ELF files and overwrite IR object symbols with
//! the returned ones, discarding IR object files.
//!
//! 4. Continue the rest of the linking process as usual.
//!
//! When gcc or clang inovkes ld, they pass `-plugin /path/to/linker-plugin.so`
//! to the linker. The given .so file provides a way to call the compiler
//! backend.
//!
//! The linker plugin API is documented at
//! https://gcc.gnu.org/wiki/whopr/driver, though the document is a bit
//! outdated.
//!
//! Frankly, the linker plugin API is peculiar and is not very easy to use.
//! For some reason, the API functions don't return the result of a
//! function call as a return value but instead calls other function with
//! the result as its argument to "return" the result.
//!
//! For example, the first thing you need to do after dlopen()'ing a linker
//! plugin .so is to call `onload` function with a list of callback
//! functions. `onload` calls callbacks to notify about the pointers to
//! other functions the linker plugin provides. I don't know why `onload`
//! can't just return a list of functions or why the linker plugin can't
//! define not only `onload` but other functions, but that's what it is.
//!
//! Here is the steps to use the linker plugin:
//!
//! 1. dlopen() the linker plugin .so and call `onload` to obtain pointers
//! to other functions provided by the plugin.
//!
//! 2. Call `claim_file_hook` with an IR object file to read its symbol
//! table. `claim_file_hook` calls the `add_symbols` callback to
//! "return" a list of symbols.
//!
//! 3. `claim_file_hook` returns LDPT_OK only when the plugin wants to
//! handle a given file. Since we pass only IR object files to the
//! plugin in mold, it always returns LDPT_OK in our case.
//!
//! 4. Once we made a decision as to which object file to include into the
//! output file, we call `all_symbols_read_hook` to compile IR objects
//! into a few big ELF files. That function calls the `get_symbols`
//! callback to ask us about the symbol resolution results. (The
//! compiler backend needs to know whether an undefined symbol in an IR
//! object was resolved to a regular object file or a shared object to
//! do whole program optimization, for example.)
//!
//! 5. `all_symbols_read_hook` "returns" the result by calling the
//! `add_input_file` callback. The callback is called with a path to an
//! LTO'ed ELF file. We parse that ELF file and override symbols
//! defined by IR objects with the ELF file's ones.
//!
//! 6. Lastly, we call `cleanup_hook` to remove temporary files created by
//! the compiler backend.
//!
//! Link-time optimization through the linker plugin interface.
//!
//! With `-flto`, compilers emit files holding their intermediate
@@ -840,7 +760,9 @@ fn plugin_input_file(mf: &'static MappedFile) -> (PluginInputFile, File) {
let file = File::open(&container.name)
.unwrap_or_else(|e| fatal!("cannot open {}: {e}", container.name.display()));
let input = PluginInputFile {
name: CString::new(container.name.as_os_str().as_encoded_bytes()).unwrap().into_raw(),
name: CString::new(container.name.as_os_str().as_encoded_bytes())
.unwrap()
.into_raw(),
#[cfg(not(windows))]
fd: file.as_raw_fd(),
#[cfg(windows)]
+2 -2
View File
@@ -815,13 +815,13 @@ pub fn resolve_symbols<E: Arch>(ctx: &mut Context<E>) {
resolve_symbols_pass(ctx, &files, true);
}
/// Drops files that didn't make it into the link and renumbers the rest.
/// Removes unreachable files from the live file lists.
pub fn remove_unreachable_files<E: Arch>(ctx: &mut Context<E>) {
remove_objects(ctx, |file| !file.base.is_reachable());
remove_unreachable_dsos(ctx);
}
/// Drops the object files `remove` selects and renumbers the rest.
/// Removes selected objects from the live list, retaining their backing storage.
fn remove_objects<E: Arch>(ctx: &mut Context<E>, remove: impl Fn(&ObjectFile<E>) -> bool) {
ctx.objs.retain(|file| !remove(file));
}
+27 -27
View File
@@ -2,17 +2,17 @@
// Utility functions
pub mod compress;
pub mod concurrent_map;
pub mod demangle;
pub(crate) mod compress;
pub(crate) mod concurrent_map;
pub(crate) mod demangle;
pub mod endian;
pub mod glob;
pub mod hyperloglog;
pub(crate) mod glob;
pub(crate) mod hyperloglog;
pub(crate) mod parallel;
pub mod perf;
pub(crate) mod perf;
mod prefetch;
pub(crate) mod siphash;
pub mod tar;
pub(crate) mod tar;
pub(crate) mod worker_local;
pub(crate) use prefetch::prefetch;
@@ -90,7 +90,7 @@ pub(crate) unsafe fn madvise_hugepage_interior(_data: *const u8, _size: usize) {
/// Rounds `value` up to a multiple of `align`, which must be zero or a power
/// of two. Zero means "no alignment".
#[inline]
pub fn align_to(value: u64, align: u64) -> u64 {
pub(crate) fn align_to(value: u64, align: u64) -> u64 {
if align == 0 {
return value;
}
@@ -99,35 +99,35 @@ pub fn align_to(value: u64, align: u64) -> u64 {
}
/// Rounds `value` down to a multiple of `align`, which must be a power of two.
pub fn align_down(value: u64, align: u64) -> u64 {
pub(crate) fn align_down(value: u64, align: u64) -> u64 {
debug_assert!(align.is_power_of_two());
value & !(align - 1)
}
/// Returns bit `pos` of `value`.
pub fn bit(value: u64, pos: u32) -> u64 {
pub(crate) fn bit(value: u64, pos: u32) -> u64 {
(value >> pos) & 1
}
// Returns [hi:lo] bits of val.
#[inline]
pub fn bits(value: u64, hi: u32, lo: u32) -> u64 {
pub(crate) fn bits(value: u64, hi: u32, lo: u32) -> u64 {
(value >> lo) & ((1u64 << (hi - lo + 1)) - 1)
}
// Cast val to a signed N bit integer.
// For example, sign_extend(x, 32) == (i32)x for any integer x.
pub fn sign_extend(value: u64, n: u32) -> i64 {
pub(crate) fn sign_extend(value: u64, n: u32) -> i64 {
((value << (64 - n)) as i64) >> (64 - n)
}
/// Whether `value` is representable as a signed `n`-bit integer.
pub fn is_int(value: i64, n: u32) -> bool {
pub(crate) fn is_int(value: i64, n: u32) -> bool {
sign_extend(value as u64, n) == value
}
/// Writes a NUL-terminated string and returns the number of bytes written.
pub fn write_cstr(buf: &mut [u8], s: &[u8]) -> usize {
pub(crate) fn write_cstr(buf: &mut [u8], s: &[u8]) -> usize {
buf[..s.len()].copy_from_slice(s);
buf[s.len()] = 0;
s.len() + 1
@@ -137,14 +137,14 @@ pub fn write_cstr(buf: &mut [u8], s: &[u8]) -> usize {
/// The result excludes the terminator. A missing terminator yields the rest
/// of the table.
#[inline]
pub fn cstr_at(table: &[u8], offset: usize) -> &[u8] {
pub(crate) fn cstr_at(table: &[u8], offset: usize) -> &[u8] {
let rest = table.get(offset..).unwrap_or(&[]);
let end = memchr::memchr(0, rest).unwrap_or(rest.len());
&rest[..end]
}
/// Appends `value` in unsigned LEB128 encoding.
pub fn encode_uleb(out: &mut Vec<u8>, mut value: u64) {
pub(crate) fn encode_uleb(out: &mut Vec<u8>, mut value: u64) {
loop {
let byte = (value & 0x7f) as u8;
value >>= 7;
@@ -157,7 +157,7 @@ pub fn encode_uleb(out: &mut Vec<u8>, mut value: u64) {
}
/// Appends `value` in signed LEB128 encoding.
pub fn encode_sleb(out: &mut Vec<u8>, mut value: i64) {
pub(crate) fn encode_sleb(out: &mut Vec<u8>, mut value: i64) {
loop {
let byte = (value & 0x7f) as u8;
value >>= 7;
@@ -171,7 +171,7 @@ pub fn encode_sleb(out: &mut Vec<u8>, mut value: i64) {
}
/// Overwrites an existing unsigned LEB128 value in place, keeping its length.
pub fn overwrite_uleb(buf: &mut [u8], mut value: u64) {
pub(crate) fn overwrite_uleb(buf: &mut [u8], mut value: u64) {
let mut i = 0;
while buf[i] & 0x80 != 0 {
buf[i] = 0x80 | (value & 0x7f) as u8;
@@ -183,7 +183,7 @@ pub fn overwrite_uleb(buf: &mut [u8], mut value: u64) {
/// Reads an unsigned LEB128 value, advancing `bytes` past it.
#[inline]
pub fn read_uleb(bytes: &mut &[u8]) -> u64 {
pub(crate) fn read_uleb(bytes: &mut &[u8]) -> u64 {
let mut value = 0;
let mut shift = 0;
loop {
@@ -201,7 +201,7 @@ pub fn read_uleb(bytes: &mut &[u8]) -> u64 {
/// Reads a signed LEB128 value, advancing `bytes` past it.
#[inline]
pub fn read_sleb(bytes: &mut &[u8]) -> i64 {
pub(crate) fn read_sleb(bytes: &mut &[u8]) -> i64 {
let mut value = 0u64;
let mut shift = 0;
loop {
@@ -222,7 +222,7 @@ pub fn read_sleb(bytes: &mut &[u8]) -> i64 {
}
/// Fills `buf` with random bytes from the operating system.
pub fn random_bytes(buf: &mut [u8]) {
pub(crate) fn random_bytes(buf: &mut [u8]) {
getrandom::fill(buf).unwrap_or_else(|err| crate::fatal!("cannot get random bytes: {err}"));
}
@@ -231,18 +231,18 @@ pub fn random_bytes(buf: &mut [u8]) {
/// Input files, symbol names and a few other objects must outlive every
/// data structure of a link, and the process exits as soon as the link is
/// done, so never freeing them is both simplest and cheapest.
pub fn leak<T>(value: T) -> &'static T {
pub(crate) fn leak<T>(value: T) -> &'static T {
Box::leak(Box::new(value))
}
/// Leaks a byte string for the rest of the process's lifetime.
pub fn leak_bytes(bytes: Vec<u8>) -> &'static [u8] {
pub(crate) fn leak_bytes(bytes: Vec<u8>) -> &'static [u8] {
Vec::leak(bytes)
}
/// Normalizes a path lexically, resolving `.` and `..` components without
/// consulting the file system.
pub fn path_clean(path: &str) -> String {
pub(crate) fn path_clean(path: &str) -> String {
clean_path(std::path::Path::new(path))
.to_string_lossy()
.into_owned()
@@ -250,7 +250,7 @@ pub fn path_clean(path: &str) -> String {
/// Converts bytes from a response file or linker script to an OS string.
/// Unix paths can contain arbitrary non-NUL bytes.
pub fn os_str(bytes: &[u8]) -> &std::ffi::OsStr {
pub(crate) fn os_str(bytes: &[u8]) -> &std::ffi::OsStr {
use bstr::ByteSlice;
bytes
.to_os_str()
@@ -258,7 +258,7 @@ pub fn os_str(bytes: &[u8]) -> &std::ffi::OsStr {
}
/// Normalizes an OS path without resolving symlinks.
pub fn clean_path(path: &std::path::Path) -> std::path::PathBuf {
pub(crate) fn clean_path(path: &std::path::Path) -> std::path::PathBuf {
use std::path::{Component, PathBuf};
let mut out = PathBuf::new();
for component in path.components() {
@@ -282,7 +282,7 @@ pub fn clean_path(path: &std::path::Path) -> std::path::PathBuf {
}
/// Formats a byte string for diagnostics, replacing invalid UTF-8.
pub fn display(bytes: &[u8]) -> std::borrow::Cow<'_, str> {
pub(crate) fn display(bytes: &[u8]) -> std::borrow::Cow<'_, str> {
String::from_utf8_lossy(bytes)
}