Compare commits

...
601 Commits
Author SHA1 Message Date
Rui Ueyama 9b376bc6a9 Bump mold version to 2.42.1 2026-09-11 15:56:48 +09:00
Rui Ueyama af7d056853 Don't let an archive member shadow a later object file's definition
Initialize reachability when creating object, LTO, and shared inputs so
mandatory objects outrank unextracted archive members during the first
symbol resolution pass.

Process all --undefined-glob matches regardless of file reachability so
--gc-sections retains every matching definition. Cover archive ordering,
directly linked objects, and multiple matches in one object.
2026-09-10 14:45:33 +09:00
Rui Ueyama 5bdd5e6284 Hint uncommon symbol address cases 2026-09-10 14:45:31 +09:00
Rui Ueyama a4bcbb45ae Compute imported-symbol weakness while marking imports
The removed pass took a few milliseconds; the end-to-end change is
within noise.
2026-09-10 14:45:24 +09:00
Rui Ueyama 4a83b5d856 Cache merged output sections per worker
MergedSection::get_instance took a shared lock for every mergeable
input section, though nearly all of them resolve to the same few
output sections. Each worker now remembers up to 32 sections it has
already resolved, keyed on the same name, flags, type and entry size,
and checks them before taking the lock. The cache lives only for the
conversion pass, during which those fields are immutable.

Geometric-mean link time over the 21-program suite fell 0.87% at 32
threads on a Threadripper 7980X and 3.88% at 20 threads on an M1
Ultra, where create_merged_sections on clang-release went from 24 ms
to 10 ms.
2026-09-10 14:45:03 +09:00
Rui Ueyama 20eb664a7e Gather shared-library symbol names in parallel
Shared libraries inserted their symbol names into the symbol map one
at a time on the parsing thread, leaving a serial tail behind large
DSOs. Their unversioned names now go through the same per-thread bins
as object-file symbols, so gather_symbols creates the canonical
Symbols in parallel across hash shards. The symbol vector is reserved
up front, so the recorded slots stay valid until the gather fills them.

Geometric-mean link time over the 21-program suite fell 1.41% at 32
threads on a Threadripper 7980X, almost all of it from blender-release
(0.177 s to 0.142 s), which links against many shared libraries.
2026-09-10 14:44:31 +09:00
Rui Ueyama c72d19dcca Avoid redundant atomic OR operations
Relocation scanning requests GOT, PLT and TLS entries for the same
symbols repeatedly, and each request took exclusive ownership of the
symbol's cache line even when the bits were already set. Atomic's
operator|= now loads first and skips the read-modify-write when no
bit would change. fetch_or stays unconditional for callers that need
it.

Measured before the check moved from a symbol-specific helper into
the wrapper: geometric-mean link time over the 21-program suite fell
1.95% at 32 threads on a Threadripper 7980X and 2.71% on an M1 Ultra.
2026-09-10 14:44:16 +09:00
Rui Ueyama dad2695f3e Fix -Wrestrict warning 2026-09-08 23:16:43 +09:00
Rui Ueyama fcd9dea78f Upgrade Debian 11 to Debian 12
Debian 11 LTS has ended, and its apt-get started failing on CI.
2026-09-08 22:52:43 +09:00
Rui Ueyama ff0cb501b0 Size the CREL section table once
Allocate decoded_crel for all section indices on the first CREL section.
This avoids repeatedly growing and copying the table during section
parsing. Files without CREL allocate no table.
2026-09-08 22:11:12 +09:00
Rui Ueyama 91252d8aa5 Skip redundant COMDAT state updates for newly parsed files
Parsing already omits losing COMDAT members and marks retained sections
alive, so skip redundant state updates for newly parsed files. Keep the
member walk when reusing parsed files, when LTO can change ownership, or
with --gdb-index.
2026-09-08 22:11:12 +09:00
Rui Ueyama 4a11b7849d Use larger thread-local arena blocks
Increase the per-thread arena block from 64 KiB to 1 MiB to reduce
global reservations. Keep the local-allocation cutoff at one quarter of
a block.
2026-09-08 22:11:12 +09:00
Rui Ueyama 53715c07ee Scan global symbol names once during registration
Populate name-length caches during symbol registration. Scan global
names up to '@' or NUL, then scan the version suffix only when present,
retaining the raw length before version normalization or --wrap. This
removes a separate cache-population pass for both regular and LTO
objects.
2026-09-08 22:11:12 +09:00
Rui Ueyama 41d2f376c3 Inline small dynamic table insertion methods
Move the .dynsym, .plt and .plt.got add_symbol definitions into their
classes so the compiler can inline calls in the serial table-allocation
loop.
2026-09-08 22:11:12 +09:00
Rui Ueyama 3a58944ff9 Reuse cached version-suffix checks for COMDAT signatures
Reuse the version-suffix flag set during global-symbol registration
instead of scanning COMDAT signature names for '@' again. Handle a bare
trailing '@' separately because it does not set the flag.
2026-09-08 22:11:12 +09:00
Rui Ueyama ec7c803207 Check dynamic-symbol version conflicts in parallel
Check dynamic-symbol version conflicts in parallel, using existing
symbol-map locking and synchronized diagnostics. Keep the error
checkpoint after all checks finish.
2026-09-08 22:11:12 +09:00
Rui Ueyama f9bdf4b4de Allocate fragment symbols while reattaching sections
Create fragment symbols in the arena as section-symbol relocations are
reattached. Collect pointers locally and reserve the file's symbol
vector once before appending them in relocation order, eliminating the
preliminary counting pass.
2026-09-08 22:11:12 +09:00
Rui Ueyama e9de4def18 Populate independent symbol-table ranges concurrently
Run synthesized-symbol, object-file and shared-file symbol-table writers
concurrently. They write disjoint symbol, string and
extended-section-index ranges; initialize section symbols before
starting these writers.
2026-09-08 22:11:12 +09:00
Rui Ueyama 18d173697c Reuse dynamic string metadata in both output writers
Keep each dynamic symbol's name and string offset after sizing .dynstr,
reusing cached GNU hash sort keys when available. Both output writers
can then use independent parallel loops without repeating prefix scans
or name lookups. The cache costs 24 bytes per dynamic symbol.
2026-09-08 22:11:12 +09:00
Rui Ueyama fa1e8edc39 Cache exact symbol name lengths without enlarging Symbol
Store a signed 32-bit name length in Symbol's existing padding to avoid
rescanning long names. Saturate at INT32_MAX and scan the suffix only
for saturated lengths.
2026-09-08 22:11:12 +09:00
Rui Ueyama 841880212f Use a flat lookup table for symbol-map shards
Replace per-key unordered_map nodes with linear-probing slots containing
cached hashes and pointers to arena entries. This reduces lookup
overhead while keeping symbol addresses stable.
2026-09-08 22:11:12 +09:00
Rui Ueyama f585fee6ca Merge and lay out non-allocated strings in the background
Resolve non-allocated merged sections and assign their offsets in a
low-priority TBB task while symbol processing, GC and relocation
scanning continue. Create .comment before starting the task and join
before section sorting and sizing consume the results. Keep
allocated-fragment layout after GC, and reassign offsets if debug
sections are reordered.
2026-09-08 22:11:12 +09:00
Rui Ueyama 95137b235e Assign defined-symbol version indices in parallel
Fill .gnu.version slots in parallel after allocating the array. Each
dynamic symbol owns a distinct slot, so these writes are independent.
2026-09-08 22:11:12 +09:00
Rui Ueyama 95dbca6708 Collect GC roots in per-thread buffers
Collect newly marked root sections in per-thread vectors, then flatten
them before graph traversal. This avoids contention on the shared
concurrent vector.
2026-09-08 22:11:12 +09:00
Rui Ueyama f8cb3daf00 Stop version matching once the result is determined
Give consecutive version-script patterns with the same version index one
priority. Stop matching when no higher-priority result is possible, and
check simple substring patterns before glob patterns.
2026-09-08 22:11:12 +09:00
Rui Ueyama 0f582b0a23 Hash mergeable fragments while splitting sections
Compute fragment hashes and cardinality estimates during the string or
record scan, avoiding a second traversal of the input contents and
fragment offsets.
2026-09-08 22:11:12 +09:00
Rui Ueyama 2c402f707f Speed up consecutive debug relocations
Debug relocations often visit consecutive merged strings. Cache the last
symbol and mergeable section per thread, keyed by input section and
symbol index. Try the next fragment before binary search and prefetch
upcoming entries.
2026-09-08 22:11:12 +09:00
Rui Ueyama 046a644906 Hash GNU symbol names four bytes at a time
Process four unsigned bytes per iteration and factor the DJB recurrence
into pairs to shorten its dependency chain. Hash the remaining bytes
with the scalar loop.
2026-09-08 22:11:12 +09:00
Rui Ueyama 8566358143 Partition dynamic symbols in parallel
Add a stable partition helper that evaluates predicates and scatters
elements in parallel. Use it for the local/global and import/export
partitions in sort_dynsyms, keeping the standard algorithm for small
ranges.
2026-09-08 22:11:12 +09:00
Rui Ueyama 477db5f372 Parallelize PLT symbol string sizing
Sum PLT symbol string sizes in parallel so large PLTs do not make
symbol-table sizing wait for a single thread.
2026-09-08 22:11:12 +09:00
Rui Ueyama 9887339f72 Prefetch symbols during dynamic table allocation
Prefetch symbol records ahead of their auxiliary records to hide the two
dependent pointer loads in the serial table-allocation loop.
2026-09-08 22:11:12 +09:00
Rui Ueyama 0706198355 Allocate dynamic symbol auxiliary data in parallel
Initialize SymbolAux in the per-file collection pass, where symbol
ownership makes the writes independent. This reduces work in the serial
allocation loop.
2026-09-08 22:11:12 +09:00
Rui Ueyama cbcf79c0ef Cache dynamic symbol sort keys in a contiguous array
Cache GNU hash bucket indices and name views before sorting dynamic
symbols. This avoids repeated pointer chasing, name-length lookups and
bucket calculations in the comparison function.
2026-09-07 19:02:32 +09:00
Rui Ueyama 6060657622 Parallelize dynamic symbol and string table generation
Compute string offsets with parallel scans and write dynamic symbols and
strings in parallel. This removes serial work on large dynamic symbol
tables while preserving output order.
2026-09-07 19:02:32 +09:00
Rui UeyamaandClaude Fable 5.1 92eb17a07c Claim IR files for LTO in the command line order
The parallel file reader handed IR files to the LTO plugin in
whatever order it found them. LLVM's LTO assumes that the module
holding the prevailing copy of a COMDAT group arrives before the
modules holding the other copies, as it does with a serial linker;
otherwise the losing copies' internal symbols, such as
__cxx_global_var_init.N, become undefined references in the LTO
result. The reader now only records IR files and claims them after
all input files have been found, sorted by command line position.
Claims were already serialized, so no parallelism is lost, and the
serializing mutex and once_flag in lto-unix.cc are no longer needed.

Fixes https://github.com/rui314/mold/issues/1649

Co-Authored-By: Claude Fable 5.1 <[email protected]>
2026-09-07 09:35:25 +09:00
Rui UeyamaandClaude Fable 5.1 450832db6b Make linker-synthesized symbols local again unless exported
35f4a334 changed is_local() to derive a symbol's binding in .symtab from
its visibility. Linker-synthesized symbols never have their visibility
merged from the internal object's ELF symbols, so they went from
STB_LOCAL to STB_GLOBAL by accident. That adds _DYNAMIC, __bss_start,
_end and the like to the set of global symbols, which confuses ABI
checkers that read .symtab.

Restore the old rule for the internal object's symbols: they are local
unless we export them. The exported ones, such as __global_pointer$ on
RISC-V or __start_*/__stop_* under -z start-stop-visibility=protected,
stay global as before.

Reported by Yao Zi in https://github.com/rui314/mold/pull/1650

Fixes https://github.com/rui314/mold/issues/1651

Co-Authored-By: Claude Fable 5.1 <[email protected]>
2026-09-06 14:55:13 +09:00
Rui UeyamaandClaude Fable 5.1 d3cf4a23e0 Commit the arena's address space on demand
The 8 GiB arena was mapped read-write, so on hosts with overcommit disabled
(vm.overcommit_memory=2) Linux ignored MAP_NORESERVE and charged the whole
reservation against the commit limit, and fork() charged it again for the
child. Even a hello-world link then failed with "fork: Cannot allocate
memory". Reserve the range with PROT_NONE instead and make it writable in
64 MiB chunks as the bump pointer advances, as the Windows code already does
with MEM_COMMIT. Only the chunks in use are charged, by mmap and by fork()
alike, and transparent huge pages still back them.

Under RLIMIT_DATA, which counts writable private mappings the same way, a
trivial link now needs 0.5 GiB instead of more than 8 GiB.

Fixes https://github.com/rui314/mold/issues/1644

Co-Authored-By: Claude Fable 5.1 <[email protected]>
2026-09-05 12:33:48 +09:00
Rui UeyamaandClaude Fable 5 32626eba5d Skip an empty .sframe section
GNU assembler 2.45 emits a zero-size .sframe section for an input file
that needs no unwind info, such as glibc's Scrt1.o on Ubuntu 25.10.
parse_sframe() read a header past the end of such a section and reported
it as corrupted.

Fixes https://github.com/rui314/mold/issues/1646

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-09-02 14:36:22 +09:00
Rui Ueyama 046ca4fa79 Remove unused function 2026-09-01 14:46:04 +09:00
Rui UeyamaandClaude Fable 5 adb411d475 [LTO] Don't return a dangling pointer as the linker version string
get_api_version handed the plugin the buffer of a stack-local
std::string, which dies when the function returns. Make it static.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-09-01 13:49:46 +09:00
Rui UeyamaandClaude Fable 5 878599b795 Fix an infinite loop on a thin archive with a __.SYMDEF member
The thin archive reader skipped a __.SYMDEF symbol table without
advancing the read position, so it kept parsing the same member header
forever. Skip past the member contents like the other symbol table
branch does.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-09-01 13:49:46 +09:00
Rui UeyamaandClaude Fable 5 4ebc916bc9 Add missing relocation names to rel_to_string
R_RISCV_TLSDESC, R_RISCV_GOT32_PCREL, R_SPARC_SIZE32 and R_SPARC_JMP_IREL
were printed as "unknown (n)" in diagnostics.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-09-01 13:49:46 +09:00
Rui UeyamaandClaude Fable 5 a129618d11 Report an error for a malformed hexadecimal --defsym value
parse_defsym_value returned a default-constructed variant on a bad hex
string such as 0xz, which holds a null Symbol pointer. The null pointer
was then dereferenced when marking GC roots, crashing the linker instead
of printing a diagnostic.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-09-01 13:49:46 +09:00
Rui UeyamaandClaude Fable 5 a9af11f74a Don't attach section pieces to symbols resolved to other files
reattach_section_pieces rewrote every symbol whose local definition is in
a mergeable section, including global symbols that lost resolution to a
definition in another file. The loser then redirected the symbol to its
own section piece. Since the pass runs on all files in parallel, it was
also a data race on the shared Symbol.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-09-01 13:49:46 +09:00
Rui UeyamaandClaude Fable 5 d55b408ff0 [s390x] Fix byte order of 12-bit displacement fields
R_390_12, R_390_PC12DBL, R_390_PLT12DBL, R_390_GOT12 and R_390_GOTPLT12
OR'd their 12-bit value into the instruction through a little-endian
16-bit accessor. s390x is big-endian, so the value landed in the wrong
bytes, corrupting both the displacement and the base register field.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-09-01 13:49:46 +09:00
Rui UeyamaandClaude Fable 5 f3b74cbb6a Fix PT_NOTE creation to consult the right chunk vector
create_phdrs walks a filtered, address-sorted copy of ctx.chunks, but the
loop that merges adjacent note sections into one PT_NOTE examined
ctx.chunks with an index into that copy. The two vectors differ e.g. when
the ELF and program headers are not memory-mapped, in which case every
note section got its own PT_NOTE segment.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-09-01 13:49:46 +09:00
Rui UeyamaandClaude Fable 5 1f56c36413 Fix string overlap in huge merged sections with mixed DWARF32/64
When a merged section is laid out in two regions, 32-bit-addressable
fragments followed by the rest, the loop that rebases per-shard offsets to
section-global ones started at shard 1. That is fine for the 32-bit region,
whose shard 0 needs no adjustment, but it left shard 0 of the 64-bit region
unadjusted, so its fragments overlapped the beginning of the section. Start
at shard 0; the 32-bit fragments of that shard just get zero added.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-09-01 13:49:46 +09:00
Rui UeyamaandClaude Fable 5 e2a28d61eb [RISC-V] Fix R_RISCV_ADD8 and R_RISCV_SUB8 in allocated sections
The alloc relocation path incremented the local pointer loc instead of the
byte it points to, so the relocated byte was left untouched. The result was
wrong whenever an 8-bit label difference spanned code whose size changed
during relaxation. The non-alloc path already did this correctly.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-09-01 13:49:46 +09:00
Rui UeyamaandClaude Fable 5 19e5360530 Fix swapped arguments to the relocation range checker
The non-alloc relocation paths of i386, ARM64, s390x, SPARC64 and PPC64V1
passed the relocation value and the relocation index to check_range in the
wrong order. The intended range check was thus never performed, and if the
relocation index happened to fall outside the value range, the error path
indexed the relocation table with the relocation value, reading out of
bounds.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-09-01 13:26:59 +09:00
Rui UeyamaandClaude Fable 5 f68cb6fb18 Recognize GCC LTO objects with 65280 or more sections
If an object file has SHN_LORESERVE (0xff00) or more sections, e_shnum
is zero and the actual number of sections is stored in the first section
header's sh_size field. is_gcc_lto_obj() used the raw e_shnum, so it
scanned no sections and misclassified such LTO objects as regular
object files.

Fixes https://github.com/rui314/mold/issues/1642

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-09-01 12:12:10 +09:00
Rui Ueyama 504249d994 Merge pull request #1641 from jvoisin/bti
Support -z rewrite-endbr on arm64
2026-08-29 15:27:13 +09:00
Rui Ueyama ecf335a4e1 Update README 2026-08-29 14:40:31 +09:00
Rui UeyamaandClaude Fable 5 fbaaa75f9f Verify SHA-256 hashes of source archives in dist.sh
Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-27 17:08:32 +09:00
Rui UeyamaandClaude Fable 5 91cab12f9a Fix CI
mold demangles with the platform's __cxa_demangle, and FreeBSD's libcxxrt
spells `operator delete[]` as `operator delete []`, so the quoted name in
the test never matched there. Test literal matching of brackets with
`S::operator[](int)` instead, which every demangler spells the same way.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-27 12:27:34 +09:00
jvoisin 3d4dc7291e Support -z rewrite-endbr on arm64
So far -z rewrite-endbr was implemented only for x86-64, where it
rewrites unnecessary ENDBR64 landing pads (Intel CET) with NOPs for
functions whose addresses are not taken. arm64 has an equivalent
control-flow-integrity feature, BTI, whose landing pad is `bti c`.

This commit generalizes rewrite_endbr into a template shared by both
targets: the landing pad and NOP byte patterns are selected per-arch
(ENDBR64/NOP on x86-64, `bti c`/`nop` on arm64). The whole-program
analysis that preserves landing pads for address-taken functions is
unchanged.

Unlike x86-64, arm64 needs range extension thunks, and a thunk reaches
its target with an indirect branch (`br x16`). A function that is only
called directly can therefore still be entered indirectly once its call
is routed through a thunk, so we must keep its landing pad. We preserve
the landing pad of every thunked symbol; the thunk symbol set has
already been narrowed to the ones that actually need a thunk by
remove_redundant_thunks().

Note that on arm64 with recent glibc, `main`'s landing pad is
legitimately removed: the CRT passes a local __wrap_main stub (whose
landing pad is kept) to __libc_start_main and reaches main through a
direct branch, which does not require a landing pad.

Also note that code built with -mbranch-protection=standard (BTI +
pointer authentication) starts functions with `paciasp`, which doubles
as a landing pad, so there is no separate `bti c` to rewrite and this
option has no effect on such code.

This was locally tested on an Apple M2 machine running Asahi Linux.

Fixes #1552.
2026-08-26 17:52:23 +02:00
Rui UeyamaandClaude Fable 5 e633272dc9 Range-check R_PPC64_TOC16_DS on PPC64 ELFv2
The relocation has a 16-bit displacement from the TOC pointer, but the
ELFv2 backend applied it without a range check and silently truncated
displacements to .toc entries beyond 32 KiB, producing a load from the
wrong address. The ELFv1 backend already checked it; do the same here.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-26 19:18:56 +09:00
Rui UeyamaandClaude Fable 5 5dce8bb4fc Report .eh_frame relocations that do not fit in 32 bits
FDEs refer to their functions with 32-bit PC-relative relocations, and
.eh_frame_hdr stores 32-bit offsets, but mold applied them without any
range check. A function more than 2 GiB away from .eh_frame, which
happens with a huge text section or with -Ttext or --section-start
placing code far from the default layout, silently got a wrapped-around
FDE that the unwinder cannot use, while GNU ld and lld reject such
links. Those relocations are now range-checked like all others, and so
are the .eh_frame_hdr entries.

The aarch64 long thunk test placed code with unwind tables 4 GiB away
from .eh_frame and relied on the bogus FDEs going unnoticed. It now
compiles the far functions without unwind tables and keeps the far
section fixed while swapping the functions between the sections, so
calls in both directions are still covered.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-26 19:18:56 +09:00
Rui UeyamaandClaude Fable 5 28513b5882 Match quoted names in version scripts and dynamic lists literally
GNU ld takes a quoted name in a version script or a dynamic list as a
literal string, while an unquoted one is a glob pattern. mold dropped
the quotes and treated everything as a pattern, so `"operator
delete[](void*)"` in an `extern "C++"` block never matched anything
because `[]` parsed as an empty character class. Glob metacharacters in
a quoted name are now escaped before the name is handed to the matcher.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-26 19:17:50 +09:00
Rui UeyamaandClaude Fable 5 df35e2ba4a Accept local: and global: without a following space in version scripts
The tokenizer keeps colons within a token for the C++ scope operator,
so `local:*;` lexed as a single token that the parser then took for a
global symbol pattern named `local:*`, and nothing was localized. The
label is now split off such a token.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-26 19:17:50 +09:00
Rui UeyamaandClaude Fable 5 72a956d56e Do not interwork ARM32 branches to symbols that are not functions
mold took the instruction set of a branch target from the LSB of the
symbol value alone, but only STT_FUNC symbols encode the instruction
set that way. A Thumb BL to a plain label in hand-written assembly,
which is NOTYPE and has an even address, was rewritten to BLX and
crashed with SIGILL, and a Thumb B.W to it went through a mode-switching
thunk. Like GNU ld, we now switch the instruction set only for function
symbols and for PLT entries, which are always ARM code.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-26 19:17:50 +09:00
Rui UeyamaandClaude Fable 5 32e7a1a111 Synthesize the PPC64 _savefpr, _restfpr, _savevr and _restvr routines
With -Os, GCC calls linker-synthesized routines not only to save and
restore general-purpose registers but also floating-point and vector
registers. mold only provided the former, so such links failed with
undefined symbol errors. The new routines use the same layout and
encodings as GNU ld; the section that holds them is renamed to
.save_restore_regs accordingly.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-26 19:17:50 +09:00
Rui UeyamaandClaude Fable 5 64d7331cef Pass common symbols through to a relocatable output
With -r, common symbols are not converted to .bss, but to_output_esym()
had no case for them and wrote them out as absolute symbols whose value
was their alignment, so linking the -r output again resolved them to a
bogus fixed address. They are now emitted as SHN_COMMON symbols with the
alignment in st_value, as in the input files.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-26 19:17:50 +09:00
Rui UeyamaandClaude Fable 5 1e5d6ba386 Fix the alignment inferred from R_RISCV_ALIGN with a power-of-two addend
The addend of R_RISCV_ALIGN is the number of NOP bytes the assembler
emitted, which is the requested alignment minus the minimum instruction
size, so the alignment is the smallest power of two greater than the
addend rather than the addend rounded up to a power of two. `.balign 4`
in RVC code (addend 2) and `.balign 8` in non-RVC code (addend 4) were
therefore treated as 2- and 4-byte alignments; in a static link that
left glibc's __sigsetjmp at a 2 mod 4 address.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-26 19:17:50 +09:00
Rui Ueyama 04a455a416 Bump mimalloc version to 3.5.0
Fixes https://github.com/rui314/mold/issues/1639
2026-08-26 10:12:28 +09:00
Rui UeyamaandClaude Fable 5 d00482d2dd Allow far calls to unresolved weak functions
An unresolved weak function resolves to address 0. In a huge executable,
a call site can be more than 2 GiB away from it, so a call relocation
such as R_X86_64_PLT32 cannot encode the displacement and mold reported
an out-of-range error. Such a call is never executed because the program
has to null-check the function first, so we now skip the range check for
call relocations against such symbols and write the truncated value.
This covers x86-64, RISC-V, s390x, SPARC64 and LoongArch; ARM already
rewrites such calls to NOPs, and PowerPC calls go through thunks. Data
references keep their range checks because their results feed the null
check.

This fixes linking Godot's sanitizer build, whose .text ends 70 MB past
the 2 GiB mark.

Fixes https://github.com/rui314/mold/issues/1640

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-26 09:12:20 +09:00
Rui UeyamaandClaude Fable 5 7279781fbd Define __global_pointer$ inside the image if there's no .sdata
Without .sdata, we defined RISC-V's __global_pointer$ as absolute
address 0. Since crt1.o initializes gp with a PC-relative
`lla gp, __global_pointer$`, such an executable could not be linked
more than 2 GiB away from address 0, for example with -Ttext-segment.
We now define the symbol at the beginning of the image, like GNU ld
which defines it relative to .data or .bss in that case. The exact
value doesn't matter because we don't relax gp-relative accesses.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-26 09:09:51 +09:00
Rui Ueyama f716fb62de Fix test for some targets
Fixes https://github.com/rui314/mold/issues/1638
2026-08-23 21:24:11 +09:00
Rui UeyamaandClaude Fable 5 d9c9b906ad Count section piece symbols while parsing sections
reattach_section_pieces() traversed every relocation of every alloc
section once just to count the references to mergeable sections, so
that it could allocate their symbols at once, and then traversed them
again to create the symbols. The count needs only ELF data, so it is
now taken while the sections are parsed, where a CREL relocation table
has just been decoded and is still in cache.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-23 21:09:05 +09:00
Rui UeyamaandClaude Fable 5 364413c41b Scan response file tokens with a lookup table
The tokenizer tested each byte against six whitespace characters and
three quoting characters in turn. A table makes the scan a single
load and compare per byte, and halves the time to tokenize Chromium's
1.9 MiB response file.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-23 21:09:05 +09:00
Rui UeyamaandClaude Fable 5 8d9cdfef3d Cut per-symbol overhead in register_global_symbols()
The loop looked up every undefined symbol in the --wrap set even when
no --wrap option was given, hashing 5.1 million long C++ names on a
Chromium link, and fetched the thread's ShardedMap bin through TBB's
thread-local lookup once per symbol rather than once per file.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-23 21:08:48 +09:00
Rui UeyamaandClaude Fable 5 21042fa988 Flatten output section members in parallel
create_output_sections() copied each output section's members from its
per-file vectors into the final array on one thread and then freed the
vectors one by one, which took about 10 ms on a Chromium link. Copy
and free per file in parallel instead, computing the alignment in the
same pass.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-23 21:08:48 +09:00
Rui UeyamaandClaude Fable 5 00f3077dae Skip defined symbols without locking in claim_unresolved_symbols()
The pass took each symbol's lock once per undefined reference, 5.1
million times on a Chromium link, although nearly every reference is
to a defined symbol, which the pass leaves alone. Popular symbols are
referenced from thousands of files, so their locks bounced between
cores. Find the references to symbols without a definition first,
which needs no lock because the pass does not change defined symbols,
and take the locks only for those.

Chromium release on a Threadripper 7980X at 32 threads: the pass goes
from 21 ms to 2 ms. Output files are bit-identical.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-23 21:08:48 +09:00
Rui UeyamaandClaude Fable 5 97f9e8e416 Resolve only the affected symbols when a hidden symbol hits a DSO
When a symbol with hidden visibility resolved to a DSO, symbol
resolution started over from scratch for all symbols with that DSO
definition excluded, and Chromium has three such symbols. Resolution is
independent per symbol, so only those symbols are resolved again, and
if one of them moves to an archive member that is not reachable yet,
liveness is propagated from that member alone. The final resolution
pass after section parsing recomputes everything else as before.

Chromium release on a Threadripper 7980X at 32 threads: resolve_symbols
goes from 143 ms to 115 ms. Output files are bit-identical.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-23 21:08:48 +09:00
Rui UeyamaandClaude Fable 5 8579808892 Sort input files in parallel
Sorting the 44,683 files of a Chromium link into command line order
took 7 ms on one thread at the end of the input reading phase.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-23 21:08:48 +09:00
Rui UeyamaandClaude Fable 5 68fceadb7d Collect absolute relocations without per-member vectors
scan_abs_relocations() gathered each member's absolute relocations into
a vector of its own and then flattened the vectors on one thread. For
.data.rel.ro of a Chromium link that was 147,000 vectors holding 1.1
million entries, and the flattening and freeing took about 10 ms of
serial time. Count the entries per member first and write them to
their final positions in parallel instead.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-23 21:08:48 +09:00
Rui UeyamaandClaude Fable 5 29a43f104d Open thin archive members from their own jobs
The job that read a thin archive opened all of its members before
feeding them to the reader loop, so a large archive's members were
opened one after another on one thread. Chromium's libblink_core.a
has 2,424. The archive job now only lists the member paths, and each
member's job opens its file, in parallel with the others.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-23 21:08:48 +09:00
Rui UeyamaandClaude Fable 5 38ec6e4ad3 Register mergeable sections without a lock
Each MergeableSection added itself to its MergedSection's member list
under a mutex from the parallel conversion loop. An output has only a
few merged sections, so on a Chromium link, 32 threads took the same
mutex 150,000 times and spent most of the pass in futex calls. Collect
the members from the files after the loop instead.

Chromium release on a Threadripper 7980X at 32 threads:
create_merged_sections goes from 55 ms to 34 ms and its system time
from 0.74 s to 0.07 s. Output files are bit-identical.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-23 21:08:48 +09:00
Rui UeyamaandClaude Fable 5 4af8d3f8e9 Read small input files instead of mmap'ing them
mmap(2) takes the process's address space lock, so with tens of
thousands of input files, the calls serialize at a few microseconds
each no matter how many threads make them. Linking Chromium maps 44,683
files, and that serialization alone bounds the input reading phase at
about 220 ms, more than a third of the link. Files up to 32 KiB, which
are 64% of Chromium's inputs by count but 16% by size, are now read
into malloc'ed buffers instead; read(2) takes no such lock, and larger
files are still mmap'ed so that their pages are shared with the page
cache rather than copied.

Chromium release on a Threadripper 7980X at 32 threads: the input
reading phase goes from 228 ms to 108 ms and the whole link from 0.69 s
to 0.56 s, with peak RSS unchanged. Output files are bit-identical.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-23 21:08:48 +09:00
Rui UeyamaandClaude Fable 5 e344452b1e Speed up response file tokenization
The tokenizer pushed every byte through an ostringstream and copied
each token twice. Tokens without quotes or backslashes, which is nearly
all of them, are now substrings of the mapped file, and whitespace is
tested inline instead of through isspace().

Expanding Chromium's 1.9 MiB response file of 19,000 arguments took
33 ms of single-threaded startup on a Threadripper 7980X; it now takes
5 ms. Output files are bit-identical.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-23 21:08:48 +09:00
Rui UeyamaandClaude Fable 5 e3ae2eb7ff Process absolute relocations in parallel
scan_abs_relocations() classified an output section's absolute
relocations and copy_buf() applied them in serial loops, although
write_dynrels() already processes them in parallel shards. A PIE
Chromium has 1.1 million of them in .data.rel.ro alone, so each loop
ran for about 30 ms on one thread while the others waited.

Chromium release on a Threadripper 7980X at 32 threads: scan_relocations
goes from 84 ms to 50 ms, copy from 89 ms to 75 ms, and the whole link
from 0.90 s to 0.86 s. Output files are bit-identical.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-23 17:06:02 +09:00
Rui UeyamaandClaude Fable 5 6f0f855ce6 Test the visited bit before setting it atomically in GC
Most relocations refer to a section that another thread has already
visited, yet visit() always did an atomic fetch_or, so popular sections
bounced between cores on every reference. Check the bit with a plain
load first, as Atomic::test_and_set() does; it is never cleared during
marking, so a stale positive answer is still correct.

Chromium release on a Threadripper 7980X at 32 threads: the mark phase
goes from 65 ms to 25 ms, the whole link from 0.96 s to 0.92 s. Output
files are bit-identical.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-23 16:54:49 +09:00
Rui UeyamaandClaude Fable 5 c2822be61a Skip the DWARF 5 type unit test if GDB chokes on the input
GDB 10 aborts on GCC's DWARF 5 type units even when the executable
contains no .gdb_index section, which broke CI on Debian 11. Probe GDB
with an index-less executable first and skip the test if it cannot
print a type from it.

Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01CKdZWtcaghcU7m5zoZtMfN
2026-08-21 20:46:39 +09:00
Rui Ueyama 0d3fe4ed34 Simplify DWARF unit header parsing 2026-08-21 16:35:44 +09:00
Rui Ueyama 6b22256da3 Fix DWARF 5 type unit gdb index compatibility
Emit version 9 indexes when type units are present so GDB 12 falls
back instead of interpreting DWARF 5 type-unit offsets as
.debug_types. Keep version 7 for indexes without type units.

Allow the regression test to run with older readelf versions and skip
it under TSAN, where relocatable links hit known oneTBB races.
2026-08-21 15:32:25 +09:00
Rui UeyamaandClaude Fable 5 0ddd5b0420 Support DWARF 5 type units in gdb index
With -gdwarf-5 -fdebug-types-section, GCC moves each type into its own
type unit and emits it as a one-section COMDAT .debug_info contribution,
so an object file contains several .debug_info sections. We assumed one
.debug_info section per file containing nothing but compilation units:
type units were missing from the index, and a pubnames offset could be
interpreted relative to the wrong contribution.

Read every live .debug_info contribution instead, classifying units by
their header's unit_type. Type units are collected into the .gdb_index
type-unit table along with their signatures, and each pubnames set is
matched to its contribution through the relocation on the set header's
debug_info_offset field. COMDAT group selection has already discarded
duplicate type units by the time we read them, so exactly one copy of
each type unit is indexed.

Fixes https://github.com/rui314/mold/issues/1634

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-21 13:42:04 +09:00
Rui Ueyama 02a4923ba2 Rename a variable 2026-08-21 10:56:25 +09:00
Rui UeyamaandClaude Fable 5 8349c41980 Fix spurious duplicate symbol error with mixed LTO and non-LTO archives
If an IR file claims a COMDAT group, the LTO result provides the group's
contents. However, post-LTO COMDAT group selection started from a clean
slate, so an archive member extracted only after LTO could win a claimed
group, resurrecting a copy that collides with the LTO result.

Remember the claim on the signature symbol, and let only LTO-generated
files win such a group after LTO. LLVM keeps claimed groups in its
output; GCC emits their contents without a group, so no file wins.

Fixes https://github.com/rui314/mold/issues/1637

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-21 10:56:25 +09:00
Rui Ueyama abd0a19cfb Rename a class 2026-08-20 23:43:53 +09:00
Rui Ueyama e3e5fba05f Fix a -Wmaybe-uninitialized warning 2026-08-20 22:57:04 +09:00
Rui Ueyama cb33534e38 Simplify 2026-08-20 22:49:01 +09:00
Rui UeyamaandClaude Fable 5 c4c090bce7 Store relaxation deltas in the arena
shrink_section() fills r_deltas once, and the later passes only read it,
so it does not need to be a growable vector afterwards. Build the deltas
in a local vector and copy them into the arena. This shrinks InputSection
by eight bytes on RISC-V and LoongArch and makes the class trivially
destructible on every target, so InputSection objects can live in the
arena without an owner that runs their destructors.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-20 22:49:01 +09:00
Rui Ueyama 6e1d9b68a3 Pack InputSection state to save eight bytes
Several InputSection booleans were stored separately even though one
atomic byte is sufficient. Pack liveness, GC visitation, address
significance, decompression, and ICF-removal state into flags, and add
relaxed fetch_or/fetch_and operations for updating them.

Also remove fde_end by marking the final FDE in each section range in
existing FdeRecord padding. This reduces InputSection<X86_64> from 56 to
48 bytes without increasing FdeRecord or Symbol.
2026-08-19 16:02:44 +09:00
Rui Ueyama 7187b766f7 Reuse InputSection storage across ICF phases
The output offset, dense ICF index, and arena-relative ICF leader are
needed at disjoint points in the link. Keeping separate members wastes
space and requires a separate eligibility flag.

Store them in a union, using icf_idx == -1 to represent ineligibility.
Process the gathered ICF sections directly and retain icf_leader only for
eliminated sections after ICF, allowing leaders to restore their offsets.
2026-08-19 16:02:44 +09:00
Rui Ueyama ebb41cbd65 Reduce ICF class-counting overhead
Counting distinct ICF classes costs about as much as a digest
propagation round, but it was done after every round. Since refinement
only splits classes, convergence does not need to be checked that often.

Run three propagation rounds between class counts and move digest-buffer
swapping into propagate(). This reduces counting overhead while preserving
the final partition.
2026-08-19 16:02:44 +09:00
Rui Ueyama 30765e5020 Remove redundant command line option 2026-08-18 20:25:01 +09:00
Rui Ueyama 6a1b0f7924 Merge pull request #1636 from WaterWhisperer/fix-1632
Fix .srodata output name and LTO option spelling
2026-08-18 20:20:11 +09:00
Rui Ueyama dd3cdb3c9d Report the COMDAT owner for ODR violations
Name the object containing the prevailing COMDAT group so users can
identify the conflicting definitions.

https://github.com/rui314/mold/issues/1635
2026-08-18 17:14:50 +09:00
WaterWhisperer 5147fc580c Fix .srodata output name and LTO option spelling 2026-08-18 16:09:19 +08:00
Rui Ueyama e04dded86e Fix a crash on ODR violations
When an ODR violation leaves a relocation referring to a discarded COMDAT
section, ObjectFile::scan_relocations() reports an error, but
scan_relocations() continues with the absolute-relocation pass. That pass
assumes symbols are resolved and may dereference a null Symbol::file,
causing mold to crash instead of exiting with the diagnostic.

Add a checkpoint between the two relocation passes so mold exits before
inconsistent symbol state reaches the absolute-relocation scan.

Fixes https://github.com/rui314/mold/issues/1635
2026-08-18 15:22:46 +09:00
Rui UeyamaandClaude Fable 5 a718dbbcd5 Fix crash when linking CREL object files with -r
create_comdat_group_sections assumed that a relocation section in a
comdat group is always SHT_RELA or SHT_REL. If an input file uses the
CREL format, the relocation sections are SHT_CREL instead, so we took
the branch for ordinary member sections and dereferenced a null
pointer, as mold does not create an InputSection for a relocation
section. mold -r had been crashing on such files since CREL input
support was added in mold 2.38.0.

Recognize SHT_CREL the same way as SHT_RELA/SHT_REL so that the
member's relocation section is propagated to the output group.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-15 09:27:02 +09:00
Rui UeyamaandClaude Fable 5 a7504210dd Store input and output sections in the arena
Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-15 09:27:02 +09:00
Rui Ueyama d8f4506ab5 Store InputSection file as a pointer 2026-08-14 22:15:34 +09:00
Rui Ueyama 5ef9acad1f Cache arena allocations in per-thread blocks 2026-08-14 22:15:34 +09:00
Rui Ueyama 3d16156250 Store section fragment references as 32-bit offsets 2026-08-14 18:19:42 +09:00
Rui Ueyama c340272e63 Fix CI 2026-08-13 13:01:48 +09:00
Rui Ueyama 635956d3b7 Emit correct symbol indices for local symbols with --emit-relocs
Commit 35f4a334 changed Symbol::is_local() to check visibility and
version-script localization, but get_output_sym_idx() also relies on it
to decide whether a symbol's slot offset is relative to the local or the
global partition of .symtab. An input-local symbol has default
visibility and no version, so is_local() returned false for it, and a
relocation emitted by --emit-relocs against a named local symbol
referred to an arbitrary global symbol.

This went unnoticed on most targets because GAS usually redirects
relocations against local symbols to section symbols, which take a
different code path. On LoongArch and RISC-V, the assembler keeps them
as-is because linker relaxation may change section-relative offsets, so
relocations against symbols like .LC0 were visibly wrong.

Fixes https://github.com/rui314/mold/issues/1631
2026-08-13 12:33:34 +09:00
Rui Ueyama 5f71cbf988 Write tombstones to .eh_frame_hdr for zero-length FDEs
Compilers may emit a meaningless FDE covering a zero-length address
range, e.g. for a function fragment that hot/cold splitting has
entirely optimized away. Such an FDE begins at the same address as the
FDE of the next real function, giving the binary search table in
.eh_frame_hdr duplicate keys. If a search lands on the empty FDE
instead of the real one, the unwinder fails to find an existing
exception handler and aborts the process.

We now write a tombstone value to .eh_frame_hdr for such an FDE. The
tombstone compares greater than any real address, so a binary search
never returns it. The FDE itself is harmless because an empty address
range doesn't match any program counter.
2026-08-13 12:14:21 +09:00
Rui Ueyama 6157cc99f1 Pack AArch64 range extension thunks
AArch64 range extension thunks are small and unlikely to benefit
measurably from aligning every entry to a 16-byte boundary. Padding
every entry with brk instructions wastes meaningful space when an
output contains many thunks.

Keep each thunk group 8-byte aligned, but pack its entries tightly so
the ARM64 short and long forms occupy 12 and 24 bytes instead of 16
and 32 bytes.
2026-08-12 20:02:27 +09:00
Rui Ueyama 441dc1d1fd Update mold.1 (automated commit) 2026-08-11 04:42:53 +00:00
Rui Ueyama d12e0e4e9c Bump mold version to 2.42.0 2026-08-11 11:34:45 +09:00
Rui Ueyama f76ea9442a Speed up range extension thunk tests 2026-08-11 11:34:45 +09:00
Rui Ueyama b9d57111ed Fix a RISC-V crash issue 2026-08-11 10:39:12 +09:00
Rui Ueyama de35271267 Upgrade bundled mimalloc to 3.4.5 2026-08-11 10:39:12 +09:00
Rui Ueyama cf0e0f7996 Fix relocations against undefined weak TLS symbols
A weak TLS variable is usually guarded by a separate weak non-TLS
symbol indicating whether the TLS definition is available. A typical
use looks like this:

  extern char foo_used __attribute__((weak));
  extern __thread int foo __attribute__((weak));

  if (&foo_used)
    foo = 1;

If foo remains undefined, its access is unreachable and there is no
meaningful value to apply to its TLS relocations. Leave those
relocations unapplied.

This prevents AArch64 from rejecting an otherwise unreachable TLS
access when an R_AARCH64_TLSLE_ADD_TPREL_HI12 relocation is out of range.

https://github.com/rui314/mold/issues/1626
2026-08-11 10:39:12 +09:00
Rui Ueyama a94df206b6 Fix SH4 PLT delay slots
SH4 raises a slot-illegal exception when a PC-relative load is placed
in a branch delay slot. Our PLT entries did exactly that:

  jmp     @r0
  mov.l   2f, r1

QEMU now checks for such illegal delay-slot loads as of
https://github.com/qemu/qemu/commit/b754cb2dcde26a7bc8a9d17bb6900a0ac0dd38e2,
matching real hardware and the SH-4 ISA. Older QEMU versions accepted
this sequence because they did not enforce the restriction, so upgrading
QEMU caused dynamically linked mold output to trap when entering a PLT
slot.

Move the relocation-index load before the jump and put a nop in the
delay slot. Grow each PLT entry from 16 to 20 bytes to make room while
keeping the two literal values 4-byte aligned.
2026-08-11 10:39:12 +09:00
Rui Ueyama 1ce5b39f7e Install loongarch64 build dependencies from the main Debian archive 2026-08-11 10:14:46 +09:00
Rui Ueyama 80af3a8138 Use the official distribution site instead of unreliable mirrors 2026-08-10 21:32:11 +09:00
Rui Ueyama 799b26f8e2 Embed git hash in distribution binaries 2026-08-09 22:07:41 +09:00
Rui Ueyama a9dc01b477 Consolidate Gentoo test caches 2026-08-07 10:59:58 +09:00
Rui Ueyama ac73431954 Support -Ttext-segment 2026-08-07 10:47:29 +09:00
Rui UeyamaandClaude Fable 5 3eca43961d Fix a SIGPIPE race in two tests
`exit` in an awk script that follows readelf in a pipeline closes the
pipe as soon as the first match is found. If readelf has not finished
writing its output by then, it is killed by SIGPIPE, which pipefail
turns into a test failure. The race is timing-dependent and was
observed roughly once per thousand runs on a loaded machine.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-07 00:07:57 +09:00
Rui Ueyama 4b56395f5e Construct RELR once per output chunk 2026-08-06 22:28:26 +09:00
Rui Ueyama cf0b26045f Use ArenaPtr to refer to symbol aux records 2026-08-06 20:49:13 +09:00
Rui UeyamaandClaude Fable 5 283238426e Overhaul the Gentoo test harness
This rewrites lib/gentoo-test.sh to make a full-distro run practical
and its results easier to trust:

- Build each package with mold first, and run the GNU ld control build
  only when the mold build fails. Results are classified into success/,
  failure/ (builds with GNU ld but not with mold, i.e. a mold bug) and
  broken/ (doesn't build with either linker, so not our fault).

- Run test suites only for the package under test by scoping
  FEATURES=test through package.env. Applying it globally drags in the
  test dependencies of the entire dependency graph and makes a large
  part of the tree unresolvable.

- Let emerge apply the configuration changes it would otherwise just
  suggest (--autounmask-continue). An ephemeral container has no
  hand-maintained package.use, so without this, thousands of packages
  are unbuildable out of the box.

- Bring the container image to a consistent ~amd64 state with a world
  update at image build time. A stable stage3 base with testing
  packages on top otherwise accumulates unsolvable dependency
  conflicts, such as perl disagreeing with its installed virtuals.

- Share work between containers through host bind mounts: source
  tarballs are downloaded once into a shared DISTDIR, and binary
  package pools let each dependency be built once instead of once per
  container that needs it. The mold and GNU ld runs use separate pools
  so that a control build can never install mold-linked binaries.

- Serialize the container image build with flock. Parallel invocations
  used to race to build the same image, one per job slot.

- Work around a deadlock in portage's binary package extraction with
  lib/gentoo-gpkg-workaround.py: tar_stream_reader.close() waits for
  the external decompressor to exit without draining its stdout, which
  hangs when the undrained residue exceeds the pipe buffer.

- Refuse to run if dist/mold doesn't embed a git commit hash, since
  test results are keyed by it.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-06 19:41:05 +09:00
Rui Ueyama 8aa9429728 Fix CI 2026-08-02 16:30:01 +09:00
Rui Ueyama cdbc8db1cb Fix -Wdeprecated-enum-enum-conversion 2026-08-02 13:08:12 +09:00
Rui UeyamaandClaude Fable 5 a7b79b2a52 Prefetch hash table buckets when resolving mergeable sections
MergeableSection::resolve_contents inserts every section fragment into
the parent's ConcurrentMap. The table is typically much larger than
the cache, so each insertion stalls on a cache miss for its first
probe. Since split_contents has already computed all fragment hashes,
prefetch the bucket eight insertions ahead.

On firefox-debug at 32 threads, this cuts the CPU time of
compute_section_sizes, which performs the insertions, by about 15%
(2.7 to 2.3 CPU-seconds) and the pass's wall time from 0.11 s to
0.10 s; the end-to-end change is within noise. All 5809 tests pass.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-02 12:27:25 +09:00
Rui UeyamaandClaude Fable 5 ad038099a0 Increase ConcurrentMap shard count from 16 to 64
get_sorted_entries splits the bucket array into NUM_SHARDS pieces, and
MergedSection::compute_section_size assigns fragment offsets with one
task per shard, so the shard count caps that pass's parallelism at 16
threads even when more are available. Raise it to 64.

Probing is confined to a shard, and a probe that visits MAX_RETRY
occupied slots aborts, so MIN_NBUCKETS grows with the shard count to
keep at least MAX_RETRY buckets per shard in the smallest maps.

Since fragment offsets are assigned shard by shard, the layout of
merged sections changes slightly (inter-shard alignment padding).
Outputs remain bit-reproducible for a given linker version, which is
the guarantee mold makes.

On firefox-debug at 32 threads, compute_section_sizes goes from
0.13 s to 0.11 s; the end-to-end change is within noise. All 5809
tests pass.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-02 12:22:59 +09:00
Rui UeyamaandClaude Fable 5 c30224584a Recompute RELR layout data in parallel
RelDynSection::update_shdr runs several times per link because the
size of .relr.dyn feeds back into section layout: set_osec_offsets
calls it until the size converges, and compute_section_headers and
the final pre-copy refresh add more calls. Each call walked all
chunks serially and ended with a serial sort of every RELR offset.
On a Firefox debug link (~360k GOT slots, ~1M relative relocations),
this accounted for most of a ~0.2 s single-threaded stretch between
layout and output copy that is plainly visible in a CPU utilization
trace.

Three changes:

- GotSection::get_num_dynrels no longer materializes the full GOT
  entry array just to count relocations. Computing each entry's
  value involves a symbol address lookup, which dominated the cost.
  The counting logic mirrors get_got_entries' relocation choices.
  This is most of the win: the GOT was the longest pole on every
  call even with the collection parallelized.

- Collect per-chunk dynamic relocation counts and RELR offsets in
  parallel; chunks are independent of one another.

- Process chunks in address order when concatenating their RELR
  offsets. Each chunk emits its offsets in ascending order and
  chunks occupy disjoint address ranges, so the concatenation is
  sorted by construction and the global sort is gone.

The output is byte-identical on firefox-debug against the unpatched
linker at the same commit. All 5809 tests pass. An interleaved A/B
on firefox-debug shows the 32-thread link going from 1.18 s to
about 1.05 s (medians of five).

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-02 11:22:06 +09:00
Rui Ueyama 5b84f386ee Simplify the hidden base-version symbol fix 2026-08-01 22:03:01 +09:00
Rui Ueyama 9b101aa9c7 Merge pull request #1629 from fmaroszek/fix-hidden-base-version-symbol
Skip hidden base-version symbols in DSOs
2026-08-01 21:40:22 +09:00
Rui Ueyama 3ff882b02c Collect RELR offsets directly from output chunks
RELR needs only the addresses to which relative relocations apply. Even after
the parent commit, mold creates complete REL/RELA records, partitions them in
RelDynSection, and then discards every field except r_offset.

Have each output chunk return a compact vector of its RELR offsets. Aggregate
and sort those vectors in RelDynSection, then encode them directly. Each chunk
also records how many of its dynamic relocations moved to RELR, so it can write
the remaining records straight to its final output slice.

Android+RELR keeps a temporary array for the remaining records because the
size of their APS2 encoding affects layout.

Seven interleaved 32-thread runs with --no-fork gave these median wall times:

                      Parent       This commit
  Firefox release    0.472518 s    0.396796 s    (-16.03%)
  Chromium debug     2.081396 s    2.044275 s    ( -1.78%)

Compared with 64b92151, the accumulated improvements are 17.79% for Firefox
release and 1.68% for Chromium debug.

An earlier seven-run A/B test found no consistent cost from returning each
vector instead of appending through an output parameter:

  Firefox release:  append 0.396 s, return 0.390 s
  Chromium debug:   append 2.005 s, return 2.028 s

The differences are less than 2% and point in opposite directions.

All 471 native x86-64 tests pass, with nine expected skips. A release build for
all supported targets also passes.
2026-08-01 17:17:03 +09:00
Rui Ueyama 19ae69dd61 Write dynamic relocations into exact output slices
RelDynSection currently asks each producer for a vector and appends it to a
second vector. OutputSection also builds one vector per shard and flattens
them. Large links therefore allocate, copy, and destroy relocation records
multiple times.

Have each producer report its exact relocation count on Chunk. RelDynSection
allocates one destination array and gives each producer a pointer to its exact
slice. OutputSection records per-shard prefix counts so shards can fill
disjoint slices in parallel.

Recompute counts on every layout pass because synthetic GOT relocation counts
can change before layout converges.

Seven interleaved 32-thread runs against 64b92151, with --no-fork, gave these
median wall times:

  Firefox release:  0.482679 s -> 0.472518 s  (-2.11%)
  Chromium debug:   2.079169 s -> 2.081396 s  (+0.11%)

All 471 native x86-64 tests pass, with nine expected skips. Release builds for
x86-64 and PPC64V1 also pass.
2026-08-01 17:17:03 +09:00
Rui UeyamaandClaude Fable 5 64b92151a0 Skip an unnecessary layout pass for non-Android links
After fix_synthetic_symbols, we re-ran set_osec_offsets
unconditionally. That pass is needed only under
--pack-dyn-relocs=android, whose packed relocation format encodes
addends in variable-length SLEB128, so fixing synthetic-symbol
values can change the encoded size of .rela.dyn. No other mode
emits anything whose size depends on addends; in particular,
RELR's size depends only on relocation locations, which are fixed
by this point. Make the pass conditional on the Android mode.

On the Firefox release benchmark with 32 threads and --no-fork,
this shortens the link from 0.44 s to 0.41 s. Outputs are
byte-for-byte identical before and after for both a RELR workload
(Firefox) and a RELA workload (Chromium release).

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-31 21:45:43 +09:00
Rui Ueyama 5e33441a0d Iterate over prevailing symbols directly 2026-07-31 12:19:39 +09:00
Rui Ueyama dd06a8969b Match large glob sets with an NFA
The scalar glob matcher is faster for ordinary version scripts, but its
work grows linearly with the number of patterns. Compile sets of 64 or more
complex patterns into one bit-parallel NFA. Each state occupies one bit, so
a machine word advances 64 states at once. Keep the existing scalar and
Aho-Corasick paths unchanged for smaller and simpler pattern sets.

On 200,000 synthetic 60-byte symbol names, the standalone matcher changes
as follows:

  128 patterns: 1.00 s -> 0.197 s (5.1x faster)
  256 patterns: 1.98 s -> 0.380 s (5.2x faster)

A normal TensorFlow debug link remains on the scalar path. A sanity check
shows apply_version_script changing from 0.177 s to 0.175 s.
2026-07-31 11:59:53 +09:00
Rui Ueyama 0a59e34909 Speed up glob matching
The general glob matcher represents all patterns as one NFA and updates
several bit vectors for every byte of every symbol. Its cost therefore
depends on the total pattern length even when a version script contains only
a few complex patterns.

Compile each complex pattern into literal, star, question, and bracket
tokens. Match without recursion, remember the most recent star, and use
string_view::find for literal runs following a star. This replaces MultiGlob
and removes the Bitvector class.

Also store non-root Aho-Corasick edges as sibling lists. Most trie nodes have
one child, so this reduces a node from 1,040 to 24 bytes on a 64-bit host. A
dense root table keeps the common transition fast. Construct failure links
breadth-first, as required by the algorithm.

With symbol-table traversal held constant, the median of five 32-thread
TensorFlow debug links changes as follows:

  apply_version_script wall time: 0.503 s -> 0.179 s (-64%)
  apply_version_script CPU time:  14.137 s -> 5.467 s (-61%)
  total wall time:                 3.626 s -> 3.285 s (-9%)
2026-07-31 10:49:32 +09:00
Rui Ueyama 34a2edb995 Iterate over symbols when applying version scripts
Version script wildcard matching currently visits each object file and scans
its global symbol slots. A symbol is processed only when that file provided
the prevailing definition, so many entries are skipped. More importantly, an
entire file is one task, which leaves workers idle when large object files
reach the end of the pass.

Iterate over the symbol table instead. Its 64 shards visit every symbol once
and distribute the work more evenly. Rename ShardedMap::for_each to
parallel_for_each to make the scheduling behavior explicit.

With the same glob matcher and 32 threads, the median of five TensorFlow
debug links changes as follows:

  apply_version_script wall time: 0.699 s -> 0.503 s (-28%)
  apply_version_script CPU time:  13.456 s -> 14.137 s (+5%)
  total wall time:                 3.800 s -> 3.626 s (-5%)
2026-07-31 10:49:32 +09:00
Franciszek MaroszekandClaude Opus 5 5898fbcb6e Skip hidden base-version symbols in DSOs
A defined dynamic symbol at VER_NDX_GLOBAL with the VERSYM_HIDDEN bit set
is a legacy compat alias. GNU as emits one for `.symver foo, foo@VER`
without the `remove` modifier, so that binaries linked before the symbol
was versioned keep resolving. It must not satisfy a new unversioned
reference to `foo`; that has to bind to the default version `foo@@VER`.

SharedFile::parse cleared VERSYM_HIDDEN before classifying the symbol, so
such an alias took the "unversioned symbol" path and claimed the plain
name. With both it and the `foo@@VER` definition claiming that name,
resolve_symbols keeps whichever comes first in .dynsym, which is the
alias. No verneed entry is then emitted, and glibc refuses to hand out a
non-base version for an unversioned lookup, so the alias wins at runtime
too and the caller silently gets the old implementation.

lksctp-tools' libsctp.so.1 exports sctp_connectx this way. Linking with
mold selects the pre-SCTP_SOCKOPT_CONNECTX3 ABI, which reports success
but never fills in the caller's association ID.

Skip such symbols while parsing. They cannot be referred to by any name,
as there is no version string to form `foo@VER` from. ld.bfd reports an
undefined reference when one is the only definition, which is what
skipping produces.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-07-30 14:23:20 +02:00
Rui Ueyama 7324c2a006 Fix 32 bit host build
Fixes https://github.com/rui314/mold/issues/1628
2026-07-30 10:16:50 +09:00
Rui Ueyama 48cea99498 Fix clang compiler warning 2026-07-28 21:11:29 +09:00
Rui Ueyama 755a39ed53 Fix CI 2026-07-28 21:02:49 +09:00
Rui Ueyama bf19884539 Fix CI 2026-07-28 18:48:14 +09:00
Rui Ueyama 17abd5cdea Skip repeated thunk range checks 2026-07-28 18:03:28 +09:00
Rui Ueyama dfb03f26f8 Shrink thunks concurrently
Each provisional thunk owns independent symbol and offset vectors. Filter
unneeded entries and recompute their offsets concurrently instead of
shrinking the thunks one at a time.

On the Chromium Arm64 debug link, seven interleaved A/B runs reduce the
remove_redundant_thunks median from 0.080 to 0.073 seconds and median
wall time from 1.924 to 1.897 seconds. The output is byte-identical.
2026-07-28 16:00:37 +09:00
Rui Ueyama 474d4ce4df Sort thunk symbols concurrently
Symbol order does not affect thunk placement, so defer deterministic sorting
until all thunks for an output section have been constructed. Sort independent
thunk vectors concurrently instead of sorting each one in the serial batch
loop.

On the Chromium Arm64 debug link, nine interleaved A/B runs reduce median wall
time from 1.98 to 1.97 seconds. The compute_section_sizes median falls from
0.116 to 0.095 seconds. The output is byte-identical.
2026-07-28 16:00:31 +09:00
Rui Ueyama 4cce716e72 Reduce serial work when creating thunks
Initialize input-section offsets in parallel. Section offsets and end offsets
are monotonic, so use lower_bound to find A and C instead of walking over
millions of sections a second and third time.

On the Chromium Arm64 debug link, five interleaved A/B runs reduce median wall
time from 2.01 to 1.95 seconds. create_range_extension_thunks falls from about
0.17 to 0.11 seconds. The output is byte-identical.
2026-07-28 16:00:31 +09:00
Rui Ueyama e4303913ee Avoid contention when creating thunks
Collect newly discovered thunk targets in per-worker vectors and concatenate
them after each batch. The existing atomic flag still removes duplicates, and
the final sort preserves deterministic output without serializing every vector
insertion through one mutex.

On the Chromium Arm64 debug link, five interleaved A/B runs reduce median wall
time from 2.19 to 2.03 seconds. The compute_section_sizes phase falls from
0.341 to 0.181 seconds, and its system CPU time falls from 5.146 to 0.620
seconds. The output is byte-identical.
2026-07-28 16:00:31 +09:00
Rui Ueyama efbba44cdc Compact mergeable section fragments
Allocate MergedSection objects in the linker arena and store each fragment's
parent section as a 32-bit arena index. This shrinks SectionFragment from 24
to 16 bytes and its ConcurrentMap bucket from 64 to 32 bytes.

Chromium's x86-64 debug link contains about 2.17 million merge-map buckets.
Across five interleaved 32-thread runs, median wall time changes from 1.91 to
1.90 seconds and median peak RSS falls from 24,870,408 to 24,808,228 KiB, a
reduction of 62,180 KiB.
2026-07-28 16:00:31 +09:00
Rui Ueyama 6a9ecbad23 Build GDB index tables during layout
Build the symbol table and constant pool in a low-priority task after
debug-info offsets are fixed. The final output path then reads relocated
address ranges and copies the prepared tables.

The stages have different dependencies and useful parallelism. Reading
input name data needs only input sections and can use all available
workers. Building the table needs final .debug_info offsets but stops
scaling near 12 workers, so run it in a separate bounded arena. Reuse one
task group for both stages.

Keep the standard serial open-addressed hash construction. This preserves
deterministic output without the prototype atomic claim table and repeated
parallel probe rounds.

Against an unrestricted table arena on the same tree, the bounded arena
reduced the 10-run median from 1.935 s to 1.905 s and user CPU from
43.510 s to 42.865 s.

Chromium debug link, 32 threads, 10 interleaved run medians:

  wall:       1.975 s -> 1.900 s (-3.8%)
  user:      43.125 s -> 42.600 s (-1.2%)
  system:     9.740 s ->  9.770 s (+0.3%)
  peak RSS: 24,817,662 -> 24,889,408 KiB (+70 MiB)
2026-07-28 16:00:31 +09:00
Rui Ueyama 046101eda3 Store input section contents as a pointer
Use the ELF section header for the stored input size and keep only the
data pointer in InputSection. Decompressed sections use sh_size because
their replacement buffer has the uncompressed size.

This reduces InputSection<X86_64> from 88 to 80 bytes. Relaxed RISC-V
and LoongArch sections continue to use the original section-header size
when copying their input bytes.

Chromium debug link, 32 threads, 10 interleaved run medians:

  wall:       1.960 s -> 1.950 s (-0.5%)
  user:      42.565 s -> 42.560 s
  system:     9.775 s ->  9.615 s (-1.6%)
  peak RSS: 24,834,922 -> 24,816,480 KiB (-18 MiB)
2026-07-28 16:00:31 +09:00
Rui Ueyama 594b03674d Combine COMDAT parsing and selection
Parse each reachable object and record its COMDAT candidates in one
parallel traversal. Reuse the signature name while parsing instead of
recovering it from the selected Symbol for the GCC debug-macro check.

Chromium debug link, 32 threads, 10 interleaved run medians:

  wall:    1.965 s -> 1.960 s (-0.3%)
  user:   43.160 s -> 42.615 s (-1.3%)
  system:  9.820 s ->  9.845 s (+0.3%)
2026-07-28 16:00:30 +09:00
Rui Ueyama 5f7764124c Use 32-bit references for linker metadata
Reserve a sparse address-space arena for symbols and input files. Objects in
that arena can refer to each other with four-byte self-relative pointers,
which halves the size of symbol-reference arrays and reduces Symbol from 48
to 40 bytes. On 32-bit hosts, reserve 256 MiB instead of 8 GiB; this places a
practical limit on linker metadata without affecting 32-bit targets linked on
a 64-bit host.

Keep the standard per-shard hash tables, but allocate their Symbol values in
stable arena blocks. Store global names beside those values, derive local
names from their owner files, and represent each COMDAT group in eight bytes.
Context still destroys file objects normally so their separately allocated
data is released before the arena is unmapped.

On the Chromium x86-64 debug benchmark with 32 threads, median wall time over
eight runs falls from 1.990 to 1.970 seconds. Median user time changes from
42.880 to 43.015 seconds, while system time falls from 9.965 to 9.890 seconds.
Median peak RSS falls from 25,625,100 to 24,841,684 KiB, a reduction of
783,416 KiB.

The generated output was byte-identical.
2026-07-28 16:00:30 +09:00
Rui Ueyama fc7cbd5dad Compact ELF section tables
Chromium has 41.96 million ELF section-table slots but only 5.86 million
regular input sections. Store a 32-bit value per slot instead of an
InputSection pointer. Zero denotes an absent section, the high bit denotes a
mergeable section, and the remaining bits index stable per-file pools.

InputSectionTable owns both pools and presents the same indexed and range
interfaces used by linker passes. Moving a section to the mergeable pool is
now explicit instead of relying on a constructor that clears a pointer passed
by reference.

Chromium debug link, 32 threads, eight interleaved run medians:

  wall:      2.005 s -> 2.000 s (-0.2%)
  user:     43.595 s -> 43.375 s (-0.5%)
  system:   10.160 s -> 9.865 s (-2.9%)
  peak RSS: 26,145,360 -> 25,598,862 KiB (-534 MiB)

All section contents matched apart from .comment and the fast build-ID note.
2026-07-28 16:00:30 +09:00
Rui Ueyama 1861e3c308 Radix-sort large GDB type lists
GDB index type vectors contain fixed-width 32-bit records. Sort lists with at
least 256 records using four byte-wise radix passes, while retaining comparison
sorting for smaller lists. Reuse worker-local scratch storage so the linear
path does not allocate for every name.

Chromium debug link, 32 threads, eight interleaved run medians:

  wall:     1.965 s -> 1.955 s (-0.5%)
  user:    44.720 s -> 43.890 s (-1.9%)
  system:   9.940 s -> 10.185 s (+2.5%)

The complete 4.86 GiB output was byte-identical.
2026-07-28 16:00:30 +09:00
Rui Ueyama 1f77c59113 Share discarded local symbols
Chromium debug has 18.39 million local symbols, and 9.71 million belong to
discarded COMDAT sections. After COMDAT elimination, allocate Symbol objects
only for retained local symbols and map every discarded local symbol to one
global zero-valued placeholder. The ELF gABI requires local symbols in a
discarded COMDAT member to be discarded and forbids references to them from
outside the group, so the placeholder does not preserve per-symbol metadata.

Chromium debug link, 32 threads, six interleaved run medians:

  wall:     2.095 s -> 2.060 s (-1.7%)
  user:    48.095 s -> 46.735 s (-2.8%)
  system:  10.110 s -> 10.280 s (+1.7%)
  peak RSS: 26,946,944 -> 26,241,790 KiB (-0.67 GiB)

Chromium sections matched apart from version and fast build-ID metadata.
2026-07-28 16:00:30 +09:00
Rui Ueyama fb80d619ae Stream debug CREL relocations
Chromium debug contains 93.8 million relocations in nonallocated sections.
Its 230 MiB CREL input expands to about 2.1 GiB of ElfRel records. Decode
and apply those relocations in one pass instead of retaining the expanded
arrays. Keep exact arrays for allocated sections, whose relocations are
consumed later.

for_each_reloc calls a target-specific callback for every relocation. GCC
15 does not inline these large callbacks on its own, leaving a function call
for each ordinary REL or RELA relocation. Mark the callbacks ALWAYS_INLINE;
this has no effect on CREL's representation but avoids that regression.

Chromium debug link, 32 threads, six-run medians:

  wall:     2.215 s -> 2.115 s (-4.5%)
  user:    50.145 s -> 48.415 s (-3.5%)
  system:  11.065 s -> 10.215 s (-7.7%)
  peak RSS: 27.96 GiB -> 25.64 GiB (-2.32 GiB)

On an Asahi machine, adding ALWAYS_INLINE to the streaming implementation
changes the native clang-debug-arm64 link at 16 threads as follows:

  wall:  2.36 s -> 1.85 s (-21.6%)
  user:  35.3 s -> 27.1 s (-23.2%)

Chromium output matched apart from the fast build-ID payload. The two Asahi
outputs were byte-identical.
2026-07-28 16:00:30 +09:00
Rui Ueyama f554cfef24 Read GDB index inputs in the background
Compilation-unit headers and GNU pubnames do not depend on relocated
debug contents. Read them from per-file input sections and build the name
map in a low-priority TBB arena while foreground passes continue. Address
ranges and final serialization still wait until debug relocations are applied.

Chromium debug link, 32 threads, six-run medians:

  wall:     2.255 s -> 2.175 s (-3.5%)
  user:    49.670 s -> 49.485 s (-0.4%)
  system:  11.460 s -> 11.260 s (-1.7%)
  peak RSS: 27.95 GiB -> 27.98 GiB (+36 MiB)

The generated .gdb_index was byte-identical.
2026-07-28 16:00:30 +09:00
Rui Ueyama d9137264f6 Reuse symbols for COMDAT groups
Most regular COMDAT signatures already name a global symbol. Reuse that
Symbol as the group identity instead of interning 14.8 million signatures in
a second map. Local, section, and versioned signatures use the same symbol
table through a name-based fallback.

Symbol resolution has been cleared when COMDAT owners are selected, so
sym_idx is temporarily available. Record the lowest file priority there, mark
each group whose file has that priority as the owner, and restore sym_idx with
one symbol-map traversal before normal resolution resumes. This keeps
ownership on the signature itself and needs no dense group IDs or auxiliary
side arrays.

On the Chromium x86-64 debug benchmark with 32 threads, median wall time over
eight runs falls from 2.310 to 2.250 seconds. Median user time falls from
49.520 to 47.390 seconds, while system time changes from 11.720 to 11.935
seconds. Median peak RSS falls from 29,485,754 to 29,103,198 KiB, a reduction
of 382,556 KiB.

The generated output was byte-identical.
2026-07-28 16:00:30 +09:00
Rui Ueyama 7887246f23 Cache input symbol name lengths
ELF symbols already contain their string-table offsets. Cache only a one-byte
NameLen instead of retaining a 16-byte string_view for each input symbol.
Longer lengths use logarithmic lower bounds, so lookups scan only the uncached
suffix. Chromium has 37.8 million input symbols, making the per-symbol
reduction substantial.

On the Chromium x86-64 debug benchmark with 32 threads, median wall time over
eight runs changes from 2.320 to 2.315 seconds. Median user and system time
fall from 49.800 and 11.625 seconds to 49.645 and 11.580 seconds. Median peak
RSS falls from 30,132,552 to 29,503,344 KiB, a reduction of 629,208 KiB.

The generated output was byte-identical.
2026-07-28 16:00:30 +09:00
Rui Ueyama a222bda697 Store symbol name lengths compactly
Store short name lengths exactly in one byte and use the remaining values as
logarithmic lower bounds. Looking up a long name then scans only its suffix.
Copy a long version-prefix name into the string pool when its original storage
is not NUL-terminated. This reduces Symbol<X86_64> from 56 to 48 bytes.

On the Chromium x86-64 debug benchmark with 32 threads, median wall time over
eight runs falls from 2.355 to 2.340 seconds. Median user time falls from
50.475 to 49.875 seconds, while system time is unchanged at about 11.85
seconds. Median peak RSS falls from 30,330,582 to 30,146,730 KiB, a reduction
of 183,852 KiB.

The generated output was byte-identical.
2026-07-28 16:00:30 +09:00
Rui Ueyama 63d88b5e46 Store input section names compactly
An input section's object and section header already identify its name in
the section-header string table. Store only the 16-bit length in InputSection
and recover the pointer from that existing metadata. This reduces
InputSection<X86_64> from 104 to 88 bytes.

On the Chromium x86-64 debug benchmark with 32 threads, median wall time over
eight runs changes from 2.365 to 2.360 seconds. Median user and system time
fall from 50.940 and 12.005 seconds to 50.495 and 11.700 seconds. Median peak
RSS falls from 30,470,228 to 30,335,754 KiB, a reduction of 134,474 KiB.

The generated output was byte-identical.
2026-07-28 16:00:30 +09:00
Rui Ueyama eb615efc16 Store decoded CREL in exact arrays
A CREL header gives the relocation count before decoding starts. Add
ExactArray, which owns an uninitialized array of a fixed size and provides
indexed access, and fill it directly. Existing relocation consumers receive a
span over the array.

This avoids vector capacity slack and initialization that the decoder
immediately overwrites.

On the Chromium x86-64 debug benchmark with 32 threads, median wall time over
eight runs falls from 2.400 to 2.370 seconds. Median user and system time fall
from 51.655 and 12.115 seconds to 50.855 and 11.965 seconds. Median peak RSS
falls from 31,006,116 to 30,470,208 KiB, a reduction of 535,908 KiB.

The generated output was byte-identical.
2026-07-28 16:00:30 +09:00
Rui Ueyama 3af5464546 Speed up GDB index construction
Chromium contributes 35.8 million GDB name records. Keep each temporary
record at 16 bytes, replace its parsed form with its indexed form after
interning, and deduplicate names within each compilation unit with a small
linear hash table instead of sorting them.

Compute each string hash and length in one scan, assign constant-pool ranges
with a parallel prefix sum, and record type-vector slots while interning. Keep
the slot-claiming protocol shared by both ConcurrentMap insertion interfaces.

On the Chromium x86-64 debug benchmark with 32 threads, median wall time
over eight runs falls from 2.550 to 2.390 seconds. Median user and system time
fall from 54.735 and 12.390 seconds to 51.275 and 12.095 seconds. Median peak
RSS falls from 32,086,034 to 30,999,878 KiB, a reduction of 1,086,156 KiB.

The generated output was byte-identical.
2026-07-28 16:00:30 +09:00
Rui Ueyama 50f68848d5 Use worker-local HyperLogLog sketches
The cardinality estimators used shared atomic registers even though their
callers already partition work across threads. Give each worker a local sketch
and merge the registers after the parallel scan. Apply the same scheme to GDB
index names and mergeable-section fragments.

Chromium debug link, 32 threads, five interleaved run medians:

  wall: 2.60 s -> 2.57 s (-1.2%)
  user: 56.22 s -> 55.95 s (-0.5%)

The generated .gdb_index was byte-identical.
2026-07-28 16:00:30 +09:00
Rui Ueyama e1f1ba00e0 Avoid repeated section name scans
Section construction repeatedly recovered names from .shstrtab even when the
caller had just looked up the same name. Pass known names into constructors and
defer other lookups until a name is actually needed.

Chromium debug link, 32 threads, five interleaved run medians:

  wall: 2.61 s -> 2.60 s (-0.4%)
  user: 57.38 s -> 56.22 s (-2.0%)

The generated output was byte-identical.
2026-07-28 16:00:30 +09:00
Rui Ueyama 7813baa40f Avoid building discarded COMDAT sections
Duplicate COMDAT groups account for millions of sections in large debug
links. Read the symbol table and group membership first, choose the winning
groups after archive extraction, and only then construct input sections and
local symbols for retained members.

Let ShardedMap report whether gather created each symbol so new symbols can
be initialized in the same pass. An empty section table means that a file has
not reached section construction, avoiding a separate parsing-state flag.

Chromium debug link, 32 threads, five interleaved run medians:

  wall:     2.77 s -> 2.61 s (-5.8%)
  peak RSS: 33.2 GiB -> 30.9 GiB (-2.3 GiB)

The generated output was byte-identical.
2026-07-28 16:00:30 +09:00
Rui Ueyama ac7334246c Simplify 2026-07-28 16:00:30 +09:00
Rui Ueyama 9cb33a0e7d Merge canonical symbol flags
Canonical PLTs and copy relocations are alternative ways to give an
imported symbol an address in the output. Which one is needed follows
from the symbol type, so recording separate scan flags duplicates
information.

Use one CANONICAL action and NEEDS_CANONICAL flag, then choose the table
to populate when dynamic symbol entries are assigned.
2026-07-28 16:00:22 +09:00
Rui UeyamaandClaude Fable 5 5157cef1fc [PPC64] Reject files with an unsupported combination of ABI and endianness
We choose between the two PPC64 ABIs by endianness because ELFv1 is
normally big-endian and ELFv2 little-endian. However, that
correspondence is a convention rather than a rule. In particular, musl
uses ELFv2 on big-endian too, so linking for example Alpine ppc64
objects silently produced an ELFv1-style executable with a synthesized
.opd whose startup code crashes immediately.

We support only the usual combinations, so make get_elf_type() treat the
others as unrecognizable, just like an ELF file for an unsupported
machine. Also give unrecognizable ELF files a proper error message; we
used to print "incompatible file type: ppc64 is expected but got " with
a trailing empty string.

Fixes https://github.com/rui314/mold/issues/1498

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-26 14:43:12 +09:00
Rui UeyamaandClaude Fable 5 35f4a334a0 Do not demote non-exported globals to local symbols in .symtab
We computed the binding of a symbol in the static symbol table from its
dynamic import/export status, so in an ordinary executable, every global
symbol was emitted as STB_LOCAL. That misleads all symbol table
consumers; nm prints "t main" instead of "T main", and gdb had to work
around it for a feature that depends on symbol bindings.

Only symbols hidden by visibility or localized by a version script
should be demoted. GNU ld keeps hidden symbols STB_GLOBAL with hidden
visibility while lld converts them to STB_LOCAL; we follow lld, which
also matches our previous output for such symbols.

One wrinkle: claim_unresolved_symbols() assigned version index 0
(VER_NDX_LOCAL) to imported symbols so that their .gnu.version entries
would be 0, which would make the new is_local() misclassify them as
version-script-localized. Leave ver_idx unassigned for imported symbols
and have the .gnu.version writer emit 0 for unversioned undefined
symbols directly, so that VER_NDX_LOCAL on a symbol means only
"localized by a version script".

Fixes https://github.com/rui314/mold/issues/906

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-26 14:43:12 +09:00
Rui UeyamaandClaude Fable 5 fba0a2185d Report duplicate definitions of a versioned symbol
A default-versioned symbol `foo@@VER` can be referred to not only as
`foo` but also as `foo@VER`, so defining both `foo@@VER` and `foo@VER`
is a duplicate definition of the versioned name. We didn't diagnose it
and silently emitted a dynamic symbol table containing two definitions
of `foo@VER`, leaving it up to the dynamic loader which one a versioned
reference binds to. GNU ld and lld reject this; now we do too.

We check for duplicates by scanning the dynamic symbol table once it's
populated, because the problem manifests if and only if both symbols
are exported, and the exported set is much smaller than the set of all
symbols. GNU ld additionally reports the error for non-exported
symbols; lld doesn't, and we behave like lld.

Fixes https://github.com/rui314/mold/issues/828

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-26 14:43:12 +09:00
Rui UeyamaandClaude Fable 5 1fb2901b19 Report an error for a defined dynamic symbol with version index 0
We ignored defined dynamic symbols whose .gnu.version entry is 0
(VER_NDX_LOCAL) on the assumption that such symbols are bound locally.
That assumption is wrong; a symbol that's actually local to a DSO
doesn't appear in .dynsym at all, so such an entry is ill-formed. GNU
ld briefly emitted version index 0 for unversioned defined symbols
(https://sourceware.org/bugzilla/show_bug.cgi?id=33577), and linking
against a DSO created by such ld made us report confusing "undefined
symbol" errors for symbols that plainly exist in the DSO.

lld rejects such files as corrupted. Follow suit and report an error
pointing at the offending file and symbol instead of silently ignoring
it.

Fixes https://github.com/rui314/mold/issues/1534

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-26 13:46:57 +09:00
Rui UeyamaandClaude Fable 5 2b605279fe Report an error for a shared object in a static link
We silently accepted a shared object file given on the command line even
in the static mode and simply ignored it, producing an executable whose
references to the shared object's symbols remain unresolved. Such a
binary crashes at runtime. GNU ld and lld reject this at link time, and
so should we.

Fixes https://github.com/rui314/mold/issues/1616

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-26 13:46:57 +09:00
Rui UeyamaandClaude Fable 5 00a27307f8 Use git rev-parse to obtain the current commit hash
We used to read files under .git directly to avoid a build-time dependency
on git, but the on-disk repository format is not stable. The hash lookup
broke for repositories using the new reftable format, and it never worked
in git worktrees, where .git is a file rather than a directory.

Just ask git instead. Building from a git checkout now requires git, which
is not much to ask; building from a source tarball still works and simply
omits the hash, as before.

Fixes https://github.com/rui314/mold/issues/1621

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-26 11:36:14 +09:00
Rui UeyamaandClaude Fable 5 920a516125 Fix false ODR violation with mixed LTO and non-LTO archives
After LTO, we reset the reachability of archive members and redo symbol
resolution to re-derive which members are actually needed, because the
LTO-generated code may no longer use some of the members that were
extracted to satisfy references from IR files. However, the results of
the pre-LTO COMDAT group selection survived into the second resolution:
ComdatGroup::owner kept the first winner's priority, and the losers'
member sections remained dead.

Therefore, if a group's winner was an archive member that is not
re-extracted after LTO, no remaining file could provide the group's
sections, and we reported a false "refers to a discarded COMDAT section"
error for a perfectly valid program.

Reset the COMDAT group state in do_lto() so that the post-LTO symbol
resolution selects groups from a clean slate among the files that
actually remain in the link.

Fixes https://github.com/rui314/mold/issues/1565

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-26 10:37:15 +09:00
Rui Ueyama 4b00446c59 Support DTPREL relocations in non-allocated sections
Fixes https://github.com/rui314/mold/issues/1626
2026-07-26 00:10:27 +09:00
Rui UeyamaandClaude Fable 5 1d306dc0ac Intern symbols without a concurrent hash table
Like the previous commit did for comdat groups, symbol interning no
longer goes through a single concurrent hash table. Object file
parsing only records each global symbol name along with which slot of
the file's symbol vector it belongs to; gather_symbols() then
deduplicates the names into Symbol objects and fills in the files'
symbol pointers, processing each hash shard on a single thread with
no synchronization. Besides avoiding concurrent hash table
insertions, this hashes each name only once and no longer constructs
a temporary Symbol per lookup, which the concurrent map's insert
interface required. Linker-synthesized symbols, shared libraries and
linker scripts keep interning through a locked slow path in
get_symbol().

Linking a Chromium debug build on a Threadripper 7980X saves ~12
CPU-seconds (83 -> 71) and about 0.25 seconds of wall time at 32
threads. Output files are bit-identical.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-20 15:29:27 +09:00
Rui UeyamaandClaude Fable 5 383ff33215 Deduplicate comdat groups without a concurrent hash table
We used to intern comdat group signatures into a single concurrent
hash table as object files are parsed. The concurrent map is costly
under 32-way insertion, and every file redundantly hashed the
signature strings, which are long mangled names for C++ programs.

Signatures are now handled by a new ShardedMap class: parsing only
appends them to thread-local buffers binned by hash, and once all
files have been read, each bin is deduplicated by a single thread
with no synchronization, filling in the files' ComdatGroupRefs. A
signature is hashed once, when it's recorded. Deduplication runs in
resolve_symbols() right before comdat elimination rather than at the
end of read_input_files() so that files parsed later, such as ELF
files produced by the LTO plugin, are covered when we redo symbol
resolution. LTO comdats otherwise take a mutex-protected slow path.

Linking a Chromium debug build on a Threadripper 7980X is about 4%
faster (3.22s -> 3.06s at 32 threads), saving ~4 CPU-seconds. Output
files are bit-identical.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-20 15:07:11 +09:00
Rui UeyamaandClaude Fable 5 4ea8cb996f Read input files in parallel
We used to read input files one at a time in the command line order,
with a single loop opening and mmap'ing each file and walking archive
files. That loop, not parsing, is the bottleneck of the read phase;
for a Chromium debug build with ~45,000 input files, it takes ~1.3
seconds, about 40% of the total link time.

We now open and read files with a parallel for-each loop with the
feeder pattern; an archive file feeds one job per member. The command
line parser records the reader state (--as-needed etc.) and a
hierarchical position for each input file argument, and files are
sorted back into the command line order afterwards to assign
priorities. Linker scripts, which can mutate the linker context, are
parsed after the parallel loop, one at a time in the command line
order. LTO claims are serialized because the plugin interface has
shared state.

Linking Chromium debug on a Threadripper 7980X is ~2% faster at 32
threads and ~14% faster at 64 threads, where the serial loop
previously made additional threads a net loss. Output files are
bit-identical to before.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-20 11:37:47 +09:00
Rui UeyamaandClaude Fable 5 2d09378cfd Remove the use of mremap from LockingOutputFile
Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-19 19:10:37 +09:00
Rui UeyamaandClaude Fable 5 a8fbf52ef1 Map the output file with room to grow instead of using mremap
mremap, which we used to grow the output file mapping for
.gdb_index, is Linux-specific, so commit 9b7c5267 broke the build on
the BSDs and macOS. Instead of growing a mapping that was created
with the exact file size, we now map the output file with twice as
much address space as its size upfront. The mapping beyond the end
of the file may not be touched, but growing the file with ftruncate
makes the tail accessible, so extend() no longer needs any mmap
call, and the output buffer never moves. Reserving address space
does not consume any physical memory; pages materialize only when
they are first touched.

This also fixes the madvise(MADV_HUGEPAGE) call in extend(), which
had always failed with EINVAL because its address was not
page-aligned. That went unnoticed because an mremap'ed mapping
inherits the huge-page flag from the original mapping.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-19 18:41:18 +09:00
Rui Ueyama 848135170a Update mold.1 (automated commit) 2026-07-19 05:50:51 +00:00
Rui UeyamaandClaude Fable 5 1c11d94c02 Allocate InputSections from a per-file pool
A large link creates tens of millions of InputSection objects, one
heap allocation each, and the profiler attributed 5% of all cycles
to their construction. Allocate them from a per-file std::deque
instead and refer to them through plain pointers. Section death is
represented by a null entry as before; the object itself stays in
the pool until the file is destroyed.

This removes the InputSection constructor from the profile and
cuts the allocation count by 4x, but the end-to-end link time is
unchanged within noise on a Chromium debug build; the constructor
cost was evidently not on the critical path. The change still pays
its way by making `sections` a plain pointer vector, which drops
the unique_ptr indirection and the .get() noise throughout.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-19 12:07:52 +09:00
Rui UeyamaandClaude Fable 5 45dea59990 Discard temporary local symbols by default
Compilers emit numerous temporary local symbols such as .L.str.42
for unnamed program elements. They are compiler-internal: nothing
references them through the symbol table, and DWARF refers to their
data by section offset. Nevertheless we kept them in the output by
default, following GNU ld. A Chromium debug build contains 2.26
million of them, and they added ~180 MiB of .symtab and .strtab to
the 4.9 GiB output.

Discard them by default, as lld does. The new --discard-none option
restores the previous behavior. -r and --emit-relocs continue to
keep all local symbols, since emitted relocations may refer to
them, and RISC-V and LoongArch no longer need their special case,
as the default now covers them.

Link time is unchanged within noise; the benefit is a ~4% smaller
output for large debug builds and a symbol table that no longer
resolves addresses to compiler-internal labels.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-18 23:32:56 +09:00
Rui UeyamaandClaude Fable 5 9b7c526704 Use mremap to extend the output file mapping
Previously, data appended to the output file, such as .gdb_index,
was staged in a heap buffer or a separate memory mapping. We can
instead just grow the existing mapping with mremap(MREMAP_MAYMOVE).
Consumers of the output buffer derive pointers from ctx.buf, so the
mapping is free to move, and since filesize now includes appended
data, the build ID and the .gnu_debuglink CRC naturally cover the
entire file.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-17 22:05:41 +09:00
Rui UeyamaandClaude Fable 5 a30c483ffa Parallelize section marking for __start_/__stop_ references
When a live section refers to a __start_<name> or __stop_<name>
symbol, every section named <name> must be kept alive. The garbage
collector marked those sections one by one in the thread that
found the reference. A single reference can fan out to an enormous
number of sections: ClickHouse is built with XRay instrumentation,
which emits one xray_instr_map section per function, and the XRay
runtime references __start_xray_instr_map, so one thread marked
and visited a couple hundred thousand sections while the rest of
the machine waited for the mark phase to finish.

This was a regression introduced by 384527fd: before it,
C-identifier-named sections were unconditional GC roots and
entered the root set through a parallel loop. That commit
correctly made their retention conditional on a reference to
their marker symbols but expanded the fanout serially. Keep the
precise semantics and spread the fanout over threads with a
nested parallel loop, storing the sections of each name as a
vector instead of entries of a multimap.

This shortens ClickHouse's debug link from 1.98s to 1.45s at the
default 32 threads; its gc pass drops from 0.56s to 0.09s.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-17 18:14:48 +09:00
Rui UeyamaandClaude Fable 5 d444584f2b Allocate ConcurrentMap tables with huge pages
ConcurrentMap allocated its hash table with MAP_POPULATE, which
zeroes every page of the mapping inside the mmap system call, on
one thread. The string-merge map for a Clang debug build spans
several gigabytes, so every other thread stalled for ~170ms at the
barrier between splitting and inserting strings while one thread
pre-faulted the table.

Map the table lazily and advise MADV_HUGEPAGE instead. Populating
the table on demand from concurrent inserts spreads the fault cost
over all threads, huge pages make each fault populate 2MiB rather
than a base page, and they also cut the TLB miss rate of the
random probes into the table. Explicit parallel pre-faulting was
measured to add nothing on top of this. This shortens a Clang
debug link from 2.13s to 1.85s and a Chromium debug link from
3.38s to 3.27s at the default 32 threads.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-17 13:44:55 +09:00
Rui UeyamaandClaude Fable 5 c8d44d73ce Write .gdb_index directly to the mmap'ed output file
.gdb_index was constructed in a malloc'ed buffer that close()
appended to the output with a single buffered write, copying the
entire section (534 MiB for a Chromium debug build) through the
page cache on one thread, into disk blocks that ext4 had to
allocate inside the write path.

Add OutputFile::extend, which returns a buffer for data appended
past the mapped image. A memory-mapped output file grows with
ftruncate, preallocates the new disk blocks, and memory-maps the
appended range, so write_gdb_index constructs the section directly
in the output file, in parallel. The range gets its own mapping
because growing the existing one with mremap could move it,
invalidating pointers into it held by the caller. Output files
that cannot be mapped (special files such as stdout, the separate
debug file, and Windows) keep buffering appended data on the heap,
now private to each implementation; the shared buf2 field is gone.

The fallocate calls are now guarded by a configure-time
HAVE_FALLOCATE check rather than #ifdef __linux__.

This shortens a Chromium debug link from 3.31s to 3.18s and makes
close_file effectively free.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-17 11:50:46 +09:00
Rui UeyamaandClaude Fable 5 97867c5354 Parallelize dynamic relocation collection
RelDynSection::update_shdr collected dynamic relocations by walking
all chunks serially, and a single output section such as
.data.rel.ro can account for most of an output's dynamic
relocations (~1.5 million for a Chromium debug build, mostly
R_RELATIVE for vtables). The walk computes each relocation's offset
and addend with Symbol::get_addr, which made it a ~0.1s
single-threaded stretch between parallel passes.

Process an output section's absolute relocations in fixed-size
parallel shards and flatten the per-shard results, so the resulting
section contents are identical to the serial version. This shortens
a Chromium debug link from 3.58s to 3.49s at the default 32
threads.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-17 10:57:56 +09:00
Rui UeyamaandClaude Fable 5 e2190e9a60 Avoid string allocations in command line option matching
Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-17 09:08:22 +09:00
Rui UeyamaandClaude Fable 5 0a5bd29b56 Speed up command line option processing
parse_nonpositional_args tested each command line argument against
every option name, and each test allocated a few heap strings in
add_dashes. A command line for a large program consists almost
entirely of input file paths (a Chromium debug link passes ~19,000
paths but fewer than 100 options), so we were spending several
hundred milliseconds of serial time on failed option matches before
opening the first input file.

An argument that does not start with '-' cannot match any option,
so such arguments now go straight to the output list. This shortens
a Chromium debug link from 3.98s to 3.59s on a 64-core machine
because option processing delayed the start of the parallel parsing
phase.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-17 08:41:55 +09:00
Rui UeyamaandClaude Fable 5 be3d214c47 Fix SH4 dynamic relocations losing their addends
SH-4 object files store relocation addends in the relocated places
rather than in r_addend, and mold assumed that dynamic relocations
work the same way, emitting them with zero r_addend and the addend
written to the relocated place. The glibc dynamic linker, however,
follows the usual RELA convention: it reads the addend of a symbolic
dynamic relocation only from r_addend, and it reads the relocated
place only for R_SH_RELATIVE relocations whose r_addend is zero. Any
symbolic dynamic relocation with a non-zero addend thus lost its
addend at load time.

A typical victim is the vtable pointer of a typeinfo object in a
position-independent executable. It must point into libstdc++'s
vtable (e.g. _ZTVN10__cxxabiv117__class_type_infoE + 8), so its
dynamic relocation carries a non-zero addend. With the addend
dropped, virtual calls on such a typeinfo dispatch to wrong vtable
slots, which crashes the C++ exception machinery as soon as a
personality routine tries to match a catch clause. The icf-lsda test
caught this on sh4aeb because its CI toolchain creates PIEs by
default; the little-endian sh4 toolchain defaults to
position-dependent executables, in which the vtable is copy-relocated
and no dynamic addend is involved, so the same test passed there.

Remove the ElfRel specializations that zeroed addends for symbolic
relocations; the generic RELA record types have the identical layout
and keep addends. Relocation records emitted for -r or --emit-relocs
keep following the object-file convention, since their consumers
expect addends in the relocated places.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-15 18:10:26 +09:00
Rui UeyamaandClaude Fable 5 4ef13c9609 Speed up gdb-index construction
Creating a .gdb_index was slower than it should have been for three
reasons. read_pubnames_cu looked up compunits with a linear scan,
which made it quadratic in the number of compunits; the compunit
vector is sorted by offset, so a binary search suffices. gdb_hash
was computed for every name occurrence even though the same name
typically appears in many compunits; we now compute it only when a
name is inserted into the map for the first time. Finally, nametypes
were sorted with a stable sort, but their ordering is total, so a
plain sort produces the same result without the extra buffer.

On a Chromium debug build, this makes the write_gdb_index pass 2x
faster with --threads=1 (10.2s to 5.2s) and 28% faster at the
default 32 threads (0.69s to 0.50s). The output is bit-identical.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-15 18:10:26 +09:00
Rui UeyamaandClaude Fable 5 3699cc68e2 Speed up version script symbol matching
Symbols are matched against version script patterns with an
Aho-Corasick automaton (for single-substring patterns) or an
NFA-based glob matcher, both of which scan the entire symbol name on
every query. But almost all patterns in real version scripts are a
match-all ("local: *;"), an exact name, or a name with one trailing
or leading "*", all of which can be matched with a plain string
comparison.

Handle such patterns directly and reserve the automatons for the
ones that actually need scanning, such as "*foo*" or "a*b".

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-15 18:10:26 +09:00
Rui Ueyama bfa7117757 Merge pull request #1624 from git-staus/main
Install build dependencies with `sudo`
2026-07-15 10:50:37 +09:00
git-staus 9bf01c1f1d Install build dependencies with sudo
The `README` gives users commands to copy, but `install-build-deps.sh`
is not ran as root, even though the CMake install step is indicated to
run with `sudo`. FreeBSD seems not to come with `sudo` by default, so
modifying the install script might be unwise. Simply place it in front
of the script invocation itself in the `README`.
2026-07-15 02:10:05 +02:00
Rui UeyamaandClaude Fable 5 5ba31dc3ea Clean up ICF
Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-14 22:34:18 +09:00
Rui UeyamaandClaude Fable 5 86b42639cc Remove the affinity partitioner from the ICF propagation loop
The affinity partitioner replayed each round's chunk-to-thread
placement so that a thread would re-read the digests it wrote in the
previous round from its own cache. That no longer pays off: after the
recent ICF rework, the propagation loop streams over the digest and
edge arrays and is bound by memory bandwidth rather than cache reuse.

Linking Chromium on a Threadripper 7980X (64 cores, 8 CCDs), the
propagate timer is unchanged at 16 and 128 threads and loses only
about 2 ms (4%) at 32 threads, which is invisible in the total link
time. In exchange, the default auto partitioner retains full work
stealing, which balances better against the skewed per-section edge
counts, and the partitioner no longer needs to be threaded through
two function signatures.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-14 21:59:50 +09:00
Rui UeyamaandClaude Fable 5 a06b6234c9 Fix ICF folding functions with different exception tables
compute_digest hashes an eligible relocation target without identity,
relying on a graph edge to keep different targets distinguishable
through the propagation rounds. But gather_edges only created edges
for a section's own relocations, not for the relocations in its FDEs,
so the reference from an FDE to its LSDA never became an edge. As a
result, two functions with identical machine code whose exception
tables catch different types were considered identical, and folding
them changes which exceptions the survivor catches. The bug is as old
as mold's ICF and also affects --icf=safe, since such functions are
usually not address-taken.

Create edges for FDE relocations, mirroring exactly what
compute_digest hashes. The included test, two byte-identical
functions catching different exception types, aborts with an uncaught
exception without this fix.

On 32-bit ARM the equivalent bug still exists through a different
mechanism: exception tables are .ARM.extab sections tied to functions
via .ARM.exidx rather than FDE references, which ICF does not yet
track. The test skips ARM for now.

A Chromium debug build is unaffected (same rounds, same number of
folded sections) since Chromium builds with -fno-exceptions.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-14 20:51:11 +09:00
Rui UeyamaandClaude Fable 5 6b5df4d6cf Use sentinel-terminated index arrays in ICF
gather_sections and gather_edges each kept a per-element count array
next to a starting-index array and combined the two at the end.
Counting directly into the index array shifted by one and running the
prefix sum in place removes the count arrays, and the extra entry at
the end means edge_indices[i + 1] is valid for every vertex, so the
propagation loop no longer needs to special-case the last vertex to
find where its edge list ends. The empty-input early return in
gather_edges falls out as well.

No behavior change.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-14 20:51:11 +09:00
Rui UeyamaandClaude Fable 5 3e62b432a5 Compute ICF digests from the previous round's digests
A propagation round used to compute a vertex's next digest by hashing
its initial digest together with its neighbors' current digests,
which required keeping three digest arrays: the initial one and two
that alternate between current and next.

Hash the vertex's current digest instead of its initial one. This is
the textbook formulation of iterated partition refinement, and it
distinguishes exactly the same vertex pairs in every round: a current
digest determines the initial digest injectively (up to hash
collisions, which the whole algorithm already assumes away), so the
per-round partitions, the number of rounds and the final equivalence
classes are all unchanged.

The initial digest array disappears, saving 16 bytes per vertex, and
the current/next arrays are now two plainly named vectors that swap
after each round instead of an indexed triple with a flip-flop slot
index.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-14 20:51:11 +09:00
Rui UeyamaandClaude Fable 5 f478001cc0 Remove per-vertex convergence tracking from ICF
The propagation rounds used to record which vertices' digests had
stopped changing in a `converged` bitmap and skip rehashing them in
later rounds. That was worthwhile when rounds were expensive, but by
now a round is a cheap, uniform data-parallel pass, and the
bookkeeping costs as much as it saves: reading and comparing two
digests per vertex per round just to skip a short hash computation.

Rehash every vertex on every round instead. The convergence check was
the last user of Digest's equality operator, so that is gone too.

For a Chromium release build, the whole ICF pass got slightly faster
(0.166s to 0.152s wall, 4.7s to 4.3s of CPU time).

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-14 20:51:11 +09:00
Rui UeyamaandClaude Fable 5 7172b136c9 Remove ICF leaf premerging
Leaf sections, those with no outgoing references, used to be merged
upfront so that they don't have to participate in the propagation
rounds as graph vertices. That was worth the code when the rounds and
the convergence check were expensive, but both have since become
cheap, and the propagation machinery handles leaves naturally: a
leaf's digest never changes, so it is marked as converged after the
second round and costs almost nothing from then on.

This deletes merge_leaf_nodes, is_leaf and the icf_leaf flag, and
moves eligibility classification to gather_sections. The leader
branch in hash_symbol is gone too because no section has a leader
anymore by the time digests are computed.

Also tidy up the neighborhood: the counting pass of gather_edges
skipped symbols that point to a section fragment before asking for
their input section, but a symbol's origin is a tagged pointer that
is either a fragment or an input section, so get_input_section()
already returns nullptr for fragment symbols. Dropping the redundant
check makes the counting pass and the filling pass identical in
shape.

For a Chromium release build, the ICF pass takes the same time as
before; the propagation rounds absorb roughly what merge_leaf_nodes
used to cost. The graph gets about 30% more vertices, which costs
some transient memory.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-14 20:51:11 +09:00
Rui UeyamaandClaude Fable 5 c6f436690b Represent ICF digests as a pair of 64-bit words
A digest used to be std::array<uint8_t, 16>, so DigestMap had to
memcpy the bytes into 64-bit words before deriving the slot index and
tag from them. Defining Digest as a struct of two u64 members gives
the words names and makes the copies disappear.

icf.cc is compiled once per target, so the struct is defined in every
one of those translation units. The definitions are all identical, so
this is fine under the one-definition rule, just as the old type
alias was.

No behavior change.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-14 20:51:03 +09:00
Rui UeyamaandClaude Fable 5 923df2f0d6 Merge ICF leaf sections by digest
Leaf sections used to be merged with a dedicated hash map keyed by
raw section contents, with its own hasher and equality functor. That
equality was subtly weaker than the one the propagation rounds use:
it compared FDE bodies but ignored which CIE each FDE points to, so
two identical functions whose CIEs recorded different personality
routines could be merged incorrectly. It also ignored section flags.

Instead, compute the same digest for leaves that compute_digest
produces for graph sections and merge leaves through DigestMap, the
same structure the propagation rounds use. One definition of section
equality now covers both paths, which fixes the personality bug, and
LeafHasher, LeafEq and the last digest-keyed use of tbb's hash map in
this file disappear.

The trade-off is that leaf merging now relies on 128-bit digest
uniqueness instead of comparing contents exactly, but that is the
same assumption the rest of ICF is already built on.

For a Chromium debug build linked with --icf=all, merge_leaf_nodes
went from 0.07s to 0.10s because a leaf's contents are now hashed by
SipHash twice, once to insert and once to look up. 152 of 3.6M folded
sections are no longer merged; those are the sections the weaker
equality was conflating.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-14 20:49:55 +09:00
Rui UeyamaandClaude Fable 5 b922536782 Fold ICF leader election into digest counting
To group sections at the end of ICF, we used to build a concurrent
hash map from digest to the lowest-priority section holding it, which
duplicated most of what the digest-counting hash set already does.
That map was also slow enough to construct and destruct that we
deferred freeing it until exit.

This patch generalizes the counting set into DigestMap by adding a
section pointer to each slot. The counting rounds now elect the
lowest-priority section for each digest as a side effect, so by the
time the propagation loop converges, the final round has already
elected the leader of every equivalence class, and the grouping step
reduces to a single lookup pass. The digest-keyed tbb map and its
std::hash specialization disappear, and so does ctx.on_exit, whose
only purpose was to defer freeing that map.

For a Chromium debug build linked with --icf=all, the grouping pass
went from 0.23s to 0.01s and the propagation loop got 0.11s slower
for maintaining the leaders, making the whole ICF pass about 10%
faster (0.89s to 0.79s). The ICF fold decisions are unchanged.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-14 16:39:26 +09:00
Rui UeyamaandClaude Fable 5 22a8d49788 Count distinct ICF digests on every propagation round
To detect convergence of the ICF propagation rounds, we count the
number of distinct digests and stop when it ceases to increase. We
previously gated the count on a cheaper necessary-looking condition:
as long as the number of changed digests per round was still moving,
we assumed the graph could not have converged and skipped counting.

That assumption is wrong. A changing digest does not imply a changing
partition, because sections in the same equivalence class change
their digests in lockstep. For example, in a chain of sections that
are all distinct from the start, the partition is final after the
first round, but the changed-digest count keeps moving until the
propagation depth reaches the chain length, so the gate would delay
termination by the depth of the chain.

Since counting became cheap, we can simply count on every round and
stop as soon as the count repeats, which is the earliest round at
which convergence is provable. propagate() no longer needs to count
changed digests and report them to the caller.

For a Chromium debug build linked with --icf=all, this reduces the
number of propagation rounds from 40 to 28. The 12 saved rounds are
ones in which most sections had already converged, so the wall clock
time is unchanged. The output file is bit-identical.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-14 15:34:46 +09:00
Rui Ueyama 1ef1ea49ed Simplify 2026-07-14 15:29:47 +09:00
Rui Ueyama 0a04fb22ad Upgrade bundled mimalloc to v3.3.2 2026-07-13 19:59:18 +09:00
Rui UeyamaandClaude Fable 5 5f2d627597 Count distinct ICF digests with a hash set instead of sorting
To detect convergence of the ICF propagation rounds, we count the
number of distinct digests and stop when it ceases to increase. We
previously obtained the count by copying all digests and running a
parallel sort, which was expensive enough that we ran it only once
per 10 propagation rounds, overshooting the convergence point by up
to 9 rounds plus a full confirmation batch.

This patch replaces the sort with a concurrent open-addressing hash
set. Since the number of distinct digests cannot exceed the number of
digests, the table is allocated once at full size and never grows.
Slots are stamped with the round number, so the table needs no
clearing between rounds; and a slot match effectively compares an
entire 128-bit digest, so the count is exact under the same
hash-collision assumption the algorithm is already built on. Counting
is now cheap enough to run on every round that passes the existing
changed-digest gate, so the loop exits at the earliest round at which
convergence is provable, and the batching heuristic is gone.

For a Chromium debug build linked with --icf=all, the propagation
loop got 2.2x faster (0.73s to 0.34s), the number of propagation
rounds dropped from 59 to 40, and the whole ICF pass went from 1.29s
to 0.90s. The output file is unchanged.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-13 16:04:33 +09:00
Rui Ueyama f485f44d06 Update the MADV_HUGEPAGE comment
madvice to enable transparent hugepages works only on ext4 on recent
Linux kernels.
2026-07-11 14:14:23 +09:00
Rui Ueyama a708f03e9a Fix Windows builder 2026-07-10 18:54:55 +09:00
Rui UeyamaandClaude Fable 5 b126f83271 Ask for transparent huge pages for large mimalloc allocations
This changes the bundled mimalloc.

mimalloc calls madvise(MADV_HUGEPAGE) on suitable memory regions only
when the caller also allows large OS pages, i.e. hugetlbfs pages.
That condition makes sense for hugetlbfs, whose pages are physically
committed at map time and thus shouldn't back lazily-committed
segments, but transparent huge pages have no such cost because their
pages materialize on fault. Since both were gated on the same flag,
huge mimalloc objects, which are allocated through a code path that
never sets the flag, were never eligible for THP under the madvise
THP mode that Linux distributions ship by default. mold's largest
data structures, such as the symbol and relocation arrays, are
exactly such objects; several gigabytes of them were backed by 4 KiB
pages.

This patch makes the THP advise depend only on the allow_thp option
(enabled by default except on Android) and leaves the hugetlbfs gate
as is. mimalloc v3 effectively behaves this way already; only the v2
line has this problem.

Linking a Chromium debug build gets about 2% faster in the default
fork mode (3.37s to 3.29s) and 15% faster with --no-fork (4.05s to
3.43s) on our machine, as the heap becomes almost fully backed by
huge pages, which also makes the process exit much cheaper.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-10 07:28:34 +09:00
Rui UeyamaandClaude Fable 5 0086e97e8d Drop page table entries in parallel before exit
The kernel reclaims an exiting process's address space in a single
thread, which takes an appreciable amount of time for a process that
maps tens of gigabytes of input files. We now drop the page table
entries for input file mappings with madvise(MADV_DONTNEED) on all
threads before exiting. File contents stay in the page cache, so warm
rebuilds are unaffected.

This makes linking a Chromium debug build with --no-fork about 11%
faster (4.55s to 4.05s) on our machine. Without --no-fork, the
teardown happens in the detached child process, which now releases
its memory sooner.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-10 07:10:15 +09:00
Rui UeyamaandClaude Fable 5 e57c733e74 Re-enable transparent huge pages if a parent process disabled them
The PR_SET_THP_DISABLE prctl flag is inherited across fork and exec,
so a linker spawned from a process that has disabled transparent huge
pages for itself also runs without them, no matter what the system
THP setting says. mold allocates on the order of 10 GiB of heap for a
large link, and backing it with 4 KiB pages instead of 2 MiB pages
makes linking nearly twice as slow (6.2s vs 3.4s for a Chromium debug
build on our machine), so clear the inherited flag at startup.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-10 07:09:28 +09:00
Rui Ueyama 1adc707d49 Add unique_comdats statistic counter 2026-07-08 16:52:09 +09:00
Rui Ueyama 2ad8219352 Fix dist.sh 2026-07-08 16:37:03 +09:00
Rui Ueyama c0b29dc05e Upgrade bundled blake3 to 1.8.5 2026-07-03 10:18:38 +09:00
Rui Ueyama 2b340d400b Upgrade bundled zlib to 1.3.2 2026-07-03 10:15:29 +09:00
Rui Ueyama 7f75a1adde Upgrade bundled OneTBB to 2023.0.0 2026-07-03 10:13:15 +09:00
Rui Ueyama 930bb45339 Upgrade bundled mimalloc to 2.3.2 2026-07-03 10:11:21 +09:00
Rui Ueyama 36e80ccea5 Fix CI 2026-07-03 09:06:24 +09:00
Rui Ueyama 0ddff68d52 Merge pull request #1618 from agrieve/as-needed-2
Remove DSOs used only by GC'ed sections
2026-07-03 09:00:52 +09:00
Andrew Grieve 3d546cd068 Remove DSOs used only by GC'ed sections
lld does this by default. See: https://crbug.com/529102889

If a DSO is only referenced by sections that are garbage-collected, the
DSO's DT_NEEDED entry will be omitted.
2026-07-02 15:59:06 -04:00
Rui Ueyama f5fd48874a Revert recent changes that upgraded distros for ./dist.sh
These changes broke cross builds for ppc64le and s390x. I couldn't fix
it easily. Every fix attempt also takes a long time because of slow
qemu-user emulation.

I'll revert CMakeLists.txt and dist.sh to the original state. I don't
think we need to upgrade distros used by dist.sh as long as the binaries
produced using these images work.
2026-07-01 14:38:21 +09:00
Rui Ueyama 6b7e8d86e2 Run test scripts directly instead of via bash -x
This ensures that all test files have x bit.
2026-07-01 11:40:45 +09:00
Rui Ueyama aa50e9f88d Simplify
Now our tar version is recent enough to use the `--sort` option.
We still need to run gzip explicitly in the pipeline because we want
to pass -n (--no-name) to not save the current time as the file
timestamp in a gzip header.
2026-06-30 17:59:09 +09:00
Rui Ueyama 26ccd7aecc Simplify 2026-06-30 17:18:35 +09:00
Rui Ueyama 6b1914d062 Simplify 2026-06-30 17:08:09 +09:00
Rui Ueyama 3bb16732ae Merge pull request #1612 from a5ehren/mold_container
build: update dist.sh by refreshing all distro images
2026-06-30 16:43:19 +09:00
Rui Ueyama 81ccee4446 Merge pull request #1615 from marxin/test-permissions-2 2026-06-30 11:35:09 +09:00
Martin Liska dc5c6c5fa4 chore(tests): add missing +x for a test 2026-06-29 23:04:55 +02:00
Ehren Bendler f07bf7352f build: update dist.sh by refreshing all distro images
* update x86-64 to Debian 10. This has glibc 2.28 which matches the oldest actually used distro these days (rhel 8) and gcc-8.5 is new enough to bootstrap clang and libc++, which saves the whole gcc build step
* set the riscv64 target to use debian Trixie, which was the first stable release for that arch
* bullseye (arm,ppac,s390), trixie (riscv), and the loongarch sid snapshot all have clang-19 available
* make sure all images install the libc++-19-dev package to get libc++ static version
2026-06-29 13:21:20 -04:00
Ehren Bendler 2ec5e72691 build: add convienence flags for selecting the C++ stdlib on Linux
* MOLD_USE_LIBCXX now configures the build to look for and use llvm's libc++. This is already the default on macOS and BSD
* Added MOLD_STATIC_* flags for several libs to give more config beyond the overall MOLD_MOSTLY_STATIC
2026-06-29 13:15:49 -04:00
Rui Ueyama f4a62c7503 Fix CI 2026-06-16 17:40:10 +09:00
Rui Ueyama 075c63861d Simplify 2026-06-16 16:46:04 +09:00
Rui Ueyama 94c259a89a Merge pull request #1609 from sunyuechi/fix-empty-default-symver
Don't let "local: *" hide a foo@@ default-versioned symbol
2026-06-16 16:14:16 +09:00
Rui Ueyama 8b79fbbf02 Attempt to fix buildbot 2026-06-16 15:08:44 +09:00
Rui UeyamaandClaude Opus 4.8 ad4c489fff Rewrite relaxed relocation types for --emit-relocs
When mold relaxes an instruction, the relocation attached to it no
longer matches the rewritten code. With --emit-relocs that mismatch is
written to the output and confuses consumers such as BOLT. This change
addresses it by rewriting each emitted relocation to the type that
matches the relaxed instruction.

RISC-V and LoongArch already did this in a separate rewrite_reloc_types()
pass that could drift from apply_reloc_alloc; fold it into the latter and
delete the pass. ARM64, x86-64, i386 and s390x did not rewrite types
and now do, e.g. x86-64 GOTPCRELX => PC32, i386 GOT32X => GOTOFF, s390x
GOTENT => PC32DBL, ARM64 GOT loads => ADR_PREL_PG_HI21 and TLSDESC =>
IE/LE. Markers that GNU ld also keeps, such as x86-64 TLS and sparc64
GOTDATA_OP, are left unchanged.

Fixes https://github.com/rui314/mold/issues/1610

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-16 14:12:08 +09:00
Sun Yuechi 71cbd79f8c Don't let "local: *" hide a foo@@ default-versioned symbol
A symbol exported with an empty default version (foo@@) was treated as a
plain unversioned symbol, so a version script's "local: *" wildcard
localized it and dropped it from the dynamic symbol table; GNU ld keeps
it exported. Route foo@@ through parse_symbol_version() and assign it
VER_NDX_GLOBAL so it overrides the wildcard.

Signed-off-by: Sun Yuechi <[email protected]>
2026-06-15 19:49:36 +08:00
Rui UeyamaandClaude Fable 5 85c115e1be Sign-extend in-place addends of i386 relocations
Co-Authored-By: Claude Fable 5 <[email protected]>
2026-06-11 11:31:37 +09:00
Rui UeyamaandClaude Opus 4.8 d0aa95abe9 Support the SFrame v3 stack-unwinding format
SFrame is a compact alternative to .eh_frame used for asynchronous stack
unwinding by profilers and debuggers. As of binutils 2.46, gas emits
.sframe (Version 3) by default with -Wa,--gsframe, so object files now
routinely carry it. Like .eh_frame, .sframe can't be treated as opaque
bytes: an output section is a single header followed by a PC-sorted index
of Function Descriptor Entries (FDEs) and a blob of Frame Row Entries
(FREs), so the linker has to parse the inputs and rebuild it.

This adds SFrame Version 3 support. We parse each input .sframe into its
FDEs, drop the entries for garbage-collected or ICF-folded functions,
concatenate the survivors, sort the index by function address and rewrite
the header. The FRE data carries no relocations and is position
independent, so it is copied verbatim; only each FDE's func_start is
relocated, which we resolve to a PC-relative offset (the output always
sets SFRAME_F_FDE_FUNC_START_PCREL). A PT_GNU_SFRAME segment is emitted so
the runtime can find the section.

For a relocatable output we can't resolve addresses yet, so the merged
index is left unsorted and each func_start is emitted as a relocation in
.rela.sframe for the final link to resolve, matching GNU ld -r.

We support only Version 3. A .sframe with the wrong magic is reported as
an error, whereas a section written in an older version is ignored.

Fixes https://github.com/rui314/mold/issues/1511

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-09 20:16:15 +09:00
Rui Ueyama eb273d1c1c Refactor 2026-06-07 19:58:57 +09:00
Rui Ueyama 3e3687e64e Merge pull request #1577 from bamo/fix/no-interp-on-shared
Don't emit PT_INTERP for -shared outputs
2026-06-07 19:37:50 +09:00
Rui Ueyama 8f598a0f96 Add a missing dependency 2026-06-07 14:25:14 +09:00
Rui Ueyama 6a2f747bf9 Fix -Wformat-truncation 2026-06-07 14:25:03 +09:00
Rui Ueyama 9c24d197c1 Merge pull request #1597 from minhbq-99/fix-align
Fix PT_LOAD alignment issue
2026-06-07 12:55:57 +09:00
Rui UeyamaandClaude Opus 4.8 5beb02714c Increase thunk batch size and tie max_thunk_size to it
Large C++ debug libraries on ARM64 (e.g. deal.II's libdeal_II.g.so) can
have a single batch reference enough distinct PLT-routed DSO imports that
the first-pass thunk group exceeds the old fixed 1 MiB max_thunk_size,
tripping the assertion in create_range_extension_thunks().

Replace the branch_distance/5 formula with explicit per-target batch
sizes (32/8/16 MiB for ARM64/ARM32/PPC) and derive max_thunk_size from
the batch as batch_size/2 (16/4/8 MiB). The reservation in d_thunk_end()
already accounts for max_thunk_size, so a larger cap is safe by
construction, and batch_size/2 is about the largest reservation ARM32's
16 MiB branch range can accommodate without throttling the batch.

Add a regression test that calls 50,000 imported functions from a single
function, which on ARM64 produces a first-pass thunk group well over the
old 1 MiB limit.

Fixes https://github.com/rui314/mold/issues/1538

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-07 00:20:34 +09:00
Rui UeyamaandClaude Opus 4.8 4afb95ac5b Support SPARC64's large PLT format
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-07 00:20:30 +09:00
Rui UeyamaandClaude Opus 4.8 2d45a8eab7 Make the HWASAN test work with older Clang
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-06 19:49:37 +09:00
Rui UeyamaandClaude Opus 4.8 a0c46e95d3 Support R_AARCH64_MOVW_PREL_G* relocations
Fixes https://github.com/rui314/mold/issues/1592

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-06 18:50:54 +09:00
Rui UeyamaandClaude Opus 4.8 2a90660aa0 Fix invalid artifact name for distros containing / or :
actions/upload-artifact rejects names with a forward slash or colon, so
the test-results upload failed for the opensuse/tumbleweed matrix entry
(and would have failed for gentoo/stage3, debian:11, and the ubuntu:*
entries on a test failure). Sanitize matrix.distro by mapping / and :
to - before using it as the artifact name.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-06 18:13:22 +09:00
Rui UeyamaandClaude Opus 4.8 f147d78a56 Make --emit-relocs reflect relaxed instructions on RISC-V and LoongArch
After relaxation, the relocations attached to the relaxed instructions no
longer match the new, shorter instructions. With --emit-relocs we copied the
input relocations verbatim, emitting stale types at stale offsets.

Rewrite each relocation's offset and type in RelocSection::copy_buf to match
the relaxed code, recomputing the outcome the same way apply_reloc_alloc
recomputes its instruction rewrites. -r output is not relaxed, so its
relocations are passed through unchanged.

Fixes https://github.com/rui314/mold/issues/1584

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-06 17:31:57 +09:00
Rui Ueyama aa52f90d69 Simplify 2026-06-06 13:15:32 +09:00
Rui UeyamaandClaude Opus 4.8 7a66f71796 Set sh_entsize and sh_addralign correctly for Android packed relocs
The Android packed relocation section (SHT_ANDROID_RELA/REL) holds a
variable-length SLEB128-encoded delta stream, not a table of fixed-size
entries, so sh_entsize should be 0 and the byte stream needs no word
alignment. We were inheriting sizeof(ElfRel) and sizeof(Word) from the
plain .rela.dyn path. These fields aren't consulted at runtime (bionic
locates the blob via DT_ANDROID_RELA/RELASZ), so this only corrects the
metadata and lets the section pack without spurious padding.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-06 12:52:17 +09:00
Rui Ueyama 2708ba6201 Fix LTO COMDAT owner selection race 2026-06-06 12:52:17 +09:00
Rui Ueyama 400dfad97c Fix Android packed reloc size oscillation
Fixes https://github.com/rui314/mold/issues/1595
2026-06-06 12:52:17 +09:00
Rui Ueyama 6841e77230 Merge pull request #1568 from gruenich/feature/update-github-actions
[ci] Update GitHub Actions to their latest major releases
2026-06-05 15:48:38 +09:00
Rui Ueyama e9c3d1629f Merge pull request #1599 from agrieve/icf-plt
icf: Do not fold relocations pointing to preemptible symbols
2026-06-04 21:33:52 +09:00
Rui Ueyama b91f3363e1 Merge pull request #1600 from agrieve/icf-alignment
icf: Merge alignments of folded sections
2026-06-04 16:17:22 +09:00
Andrew Grieve 899655b2c1 icf: Merge alignments of folded sections
When folding identical sections, ICF was discarding the alignment of the
folded sections and only keeping the alignment of the chosen leader. If
a section with stricter alignment (e.g. 4-byte) was folded into a
section with looser alignment (e.g. 1-byte), the folded function could
be placed at an odd address.

On x86/x86_64, member function pointers use the lowest bit of the
address for virtual/non-virtual distinction. If a non-virtual member
function is folded and placed at an odd address, it is incorrectly
interpreted as virtual, leading to crashes. This fix ensures that we
update the leader's alignment to be the maximum alignment of all folded
sections. We do this in a global pass over all sections (both leaf and
non-leaf merged sections) before the sweep phase.

Fixes #1596
2026-06-03 15:13:31 -04:00
Andrew Grieve 17e705efaf icf: Do not fold relocations pointing to preemptible symbols
If a relocation points to a preemptible (imported) symbol, it should not
be considered identical to a relocation pointing to a local symbol, even
if their targets currently have identical bodies. Preemptible symbols
can be overridden at runtime, so we must preserve their unique identity.

Fixes #1598
2026-06-03 12:45:16 -04:00
Bui Quang Minh ad4a683ff6 Fix PT_LOAD alignment issue
With a PT_LOAD segment, we need to make sure p_offset % p_align ==
p_vaddr % p_align. In set_file_offsets, we try to assign contiguous file
offsets when sections are contiguous in memory. But when the alignment
of section changes and it's bigger than page size, we cannot do that.
The offset might need to be adjusted to follow the alignment rule.

A test binary with
__attribute__((section(".sum"))) __attribute__((aligned(8192))) void sum() {}

Section Headers:

[16] .plt.got          PROGBITS        00000000000015c0 0005c0 000008 00  AX  0   0 16
[17] .sum              PROGBITS        0000000000002000 001000 00000b 00  AX  0   0 8192

.sum section is assigned contiguous offset to its previous section. As a
result, in create_phdr, .sum is mapped to the same PT_LOAD segment as
previous sections.

Program Headers:

LOAD           0x000588 0x0000000000001588 0x0000000000001588 0x000b80 0x000b80 R E 0x2000

p_offset = 0x588
p_vaddr = 0x1588
p_align = 0x2000

So p_offset % p_align != p_vaddr % p_align, it violates the alignment
rule. As a result, if this scenario happens to a shared library, it
cannot be dlopen() on a system with glibc < 2.35 which is before this
commit:
https://github.com/bminor/glibc/commit/163f625cf9becbb82dfec63a29e566324129c0cd.

Fix it by not assigning contiguous offset when section's alignment is
different and it's bigger than page size.
2026-06-02 22:08:51 +07:00
Christoph Grüninger d557daa268 [ci] Update GitHub Actions to their latest major releases
Fixes most deprecation warnings that Node20.js will stop working
in June.
2026-05-30 22:02:11 +02:00
Rui Ueyama 4f08de1147 Simplify 2026-05-20 20:53:55 +09:00
Rui Ueyama bdb00bc558 Simplify 2026-05-20 20:27:15 +09:00
Bennett Amodio abcbd451b4 perf: cache symbol names to avoid per-call strlen
Several parse-time sites converted `symbol_strtab.data() + st_name`
implicitly to string_view, running strlen on each call. Precompute
a string_view vector parallel to elf_syms and read from it.
2026-05-20 19:40:13 +09:00
Bennett Amodio f1b4be87f6 perf: cache InputSection name to avoid per-call strlen
InputSection::name() implicitly converts a `const char *` to
string_view, which calls strlen on the shstrtab entry at every call.
Precompute the name as a string_view once in the constructor.

Linking clang Debug is ~1.3% faster; mlir-opt Debug is ~2.6% faster.
2026-05-20 19:40:09 +09:00
Rui Ueyama 441f231dbf Revert "perf: cache symbol names to avoid per-call strlen"
This reverts commit 5f272c4184 as it
broke tests.
2026-05-20 10:22:36 +09:00
Rui Ueyama 1f44c6c6df Revert "perf: cache InputSection name to avoid per-call strlen"
This reverts commit c32946dfab as it
broke tests.
2026-05-20 10:22:25 +09:00
Rui Ueyama 154e99b2ff Merge pull request #1588 from bamo/perf/cache-symbol-names
perf: cache symbol names to avoid per-call strlen
2026-05-20 10:03:35 +09:00
Rui Ueyama 048b7b2dfd Merge pull request #1587 from bamo/perf/cache-input-section-name
perf: cache InputSection name to avoid per-call strlen
2026-05-20 09:56:50 +09:00
Rui Ueyama 8b5eeb84fa Merge pull request #1589 from bamo/perf/concurrentmap-prefault
perf: pre-fault ConcurrentMap pages with MAP_POPULATE
2026-05-20 08:54:55 +09:00
Rui Ueyama 3c018ca56f Merge pull request #1586 from andreas-schwab/main
Fix ambiguous readelf option in gdb-index-dwarf5 test
2026-05-20 08:07:14 +09:00
Bennett Amodio 4a9e30defd perf: pre-fault ConcurrentMap pages with MAP_POPULATE
ConcurrentMap::insert reads ent.key before writing, so on an empty
slot the read first installs a PTE pointing at the system zero page,
then the CAS triggers wp_page_copy and a cross-CPU TLB shootdown.
Under heavy parallel insertion this shows up as ~3% of link time in
smp_call_function_many_cond plus a much larger inclusive cost in
asm_exc_page_fault.

Pass MAP_POPULATE to pre-fault the whole map in the mmap() thread.
Reads no longer install zero-page PTEs and writes no longer trigger
CoW. Linking clang-23 Debug is ~5% faster, llvm-tblgen ~7%,
clang-tblgen ~9%; max RSS rises by 0.1-0.6%. Falls back cleanly on
platforms that don't define MAP_POPULATE.
2026-05-19 14:09:59 -07:00
Bennett Amodio 5f272c4184 perf: cache symbol names to avoid per-call strlen
Several parse-time sites converted `symbol_strtab.data() + st_name`
implicitly to string_view, running strlen on each call. Precompute
a string_view vector parallel to elf_syms and read from it.
2026-05-19 09:58:36 -07:00
Bennett Amodio c32946dfab perf: cache InputSection name to avoid per-call strlen
InputSection::name() implicitly converts a `const char *` to
string_view, which calls strlen on the shstrtab entry at every call.
Precompute the name as a string_view once in the constructor.

Linking clang Debug is ~1.3% faster; mlir-opt Debug is ~2.6% faster.
2026-05-19 08:34:17 -07:00
Andreas Schwab e77e9d52b8 Fix ambiguous readelf option in gdb-index-dwarf5 test
The binutils readelf command now accepts a --debug-dir=<DIR> option which
makes --debug=gdb_index ambiguous.
2026-05-19 17:06:06 +02:00
Rui Ueyama a2e670e2a4 Merge pull request #1581 from marxin/test-permissions
chore(tests): add missing +x for 2 tests
2026-05-18 10:20:07 +09:00
Rui UeyamaandClaude Opus 4.7 019fb43e8c Simplify test
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
2026-05-18 10:14:50 +09:00
Rui UeyamaandClaude Opus 4.7 61933bfd79 Fix relative response file paths failing after -C on emulation redo
Commit 6d6a128b moved the -C chdir(2) into parse_nonpositional_args(),
which is invoked by both the speculative X86_64 mold_main() and the
re-run via redo_main(). When -C is in effect, the second invocation
re-entered expand_response_files() from the post-chdir directory, so
@rsp with a relative path could no longer be opened.

Save the cwd before parsing and restore it before redo_main() so the
re-run resolves relative paths from the original directory. Also
switch the chdir call itself to std::filesystem::current_path().

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
2026-05-18 10:13:42 +09:00
Rui Ueyama b4bb6a856d Merge pull request #1579 from bamo/fix/gdb-index-rnglistx
--gdb-index: use DW_FORM_rnglistx index instead of iterating all offset entries
2026-05-18 10:12:02 +09:00
Rui Ueyama b7bbf9c15c Merge pull request #1580 from bamo/fix/msys-windows-include
Include <windows.h> directly in mold.h
2026-05-18 10:11:29 +09:00
Martin Liska a3affe8789 chore(tests): add missing +x for 2 tests 2026-05-13 22:05:58 +02:00
Bennett Amodio 87b2889254 Include <windows.h> directly where Windows APIs are used
src/mold.h (HANDLE in MappedFile) and lib/perf.cc (FILETIME /
GetProcessTimes / GetCurrentProcess) both use Windows APIs inside
_WIN32 blocks but neither included <windows.h>. The MSYS2 UCRT64 build
worked because <tbb/enumerable_thread_specific.h> (pulled in via
lib/lib.h) used to #include <windows.h> for its TLS code.

oneTBB 2023.0.0 replaced that bulk include with forward declarations
of just the TLS functions ("declare prototypes of just the needed TLS
functions, instead of including whole windows.h"). When MSYS2 picked
up 2023.0.0, build-msys started failing with "'HANDLE' does not name
a type" at src/mold.h:224 (and the same shape of error in lib/perf.cc
after the mold.h fix landed).

Include <windows.h> explicitly in both translation units so the build
doesn't rely on transitive paths through third-party headers.

Last passing run: https://github.com/rui314/mold/actions/runs/25585325507/job/75112599227
First failing run: https://github.com/rui314/mold/actions/runs/25767054228/job/75681837767
2026-05-13 10:24:06 -07:00
Bennett Amodio 70ca8ffa53 --gdb-index: use DW_FORM_rnglistx index instead of iterating all offset entries
A compile unit's DW_AT_ranges attribute in DW_FORM_rnglistx form holds an
index into the offset table at DW_AT_rnglists_base; the corresponding
entry locates the range list for that DIE. mold was ignoring the index
and iterating every offset, picking up range lists belonging to child
DIEs (subprograms, lexical_blocks, inlined_subroutines) that use
DW_FORM_rnglistx as well.

The resulting .gdb_index address table contained child-DIE ranges nested
inside the CU's range. GDB 17 detects the overlap, prints "warning:
.gdb_index address table has a range (...) that overlaps with an earlier
range, ignoring .gdb_index", and discards the index. Older GDBs accept
the bloated table without complaint -- the duplicate entries all point
to the same CU, so lookups still resolve correctly -- which is why this
went undetected for years.

The bug only triggers when a CU contains a non-contiguous lexical_block,
which clang emits for scopes with EH landing pads from throwing RAII
destructors. Replace the loop with offsets[ranges.value], matching the
DW_FORM_sec_offset branch and GDB's own read_rnglist_index.

The new test invokes clang++ directly since DW_FORM_rnglistx is a
clang-specific behavior; gcc through 13 uses DW_FORM_sec_offset, which
mold has always handled correctly. The test skips cleanly if clang++
isn't installed or doesn't emit rnglistx for the source.

Reported by @MikeWang000000.

Fixes https://github.com/rui314/mold/issues/1578
2026-05-12 15:53:46 -07:00
Bennett Amodio 10b37a81fb Don't emit PT_INTERP for -shared outputs
When --dynamic-linker is passed alongside -shared, mold currently emits
a .interp section and a PT_INTERP program header. Both lld and BFD ld
silently drop --dynamic-linker for -shared outputs because shared
libraries are loaded by the dynamic linker rather than bootstrapped by
the kernel, so PT_INTERP serves no purpose there.

Gate .interp creation on (!shared || pie) so we keep it for regular
executables, -pie executables, and -shared -pie runnable shared
libraries (the libc.so.6-style case), and drop it only for the pure
shared-library case. The PT_INTERP program header at
output-chunks.cc:219 is conditional on ctx.interp being non-null, so
the segment vanishes automatically with no second edit.

Fixes https://github.com/rui314/mold/issues/1572
2026-05-08 16:28:51 -07:00
Rui Ueyama 45970e661d Update mold.1 (automated commit) 2026-05-08 08:06:27 +00:00
Rui Ueyama 155a8106c9 Merge pull request #1576 from bamo/feat/cli-no-warnings
Support -w / --no-warnings to suppress warnings
2026-05-08 17:05:35 +09:00
Bennett Amodio 51b9683ce7 Support -w / --no-warnings to suppress warnings
The Warn() machinery has consulted ctx.arg.suppress_warnings since
6d2dbd42 ("Preparation to support Apple ld64's -w option", March
2023), but no cmdline flag ever set it. Wire -w (the GNU/BFD
spelling) and --no-warnings (the lld long form) to set the field.

Because Warn::Warn early-returns when the flag is true, before the
ctx.arg.fatal_warnings branch sets has_error, -w also cancels
--fatal-warnings — matching lld's documented behavior.

The "warnings only" semantics match GCC, clang, and lld: real errors
still print and still fail the link. BFD ld's -w is broader and
suppresses error messages too, which would hide the cause of link
failures.

Fixes https://github.com/rui314/mold/issues/1493
2026-05-07 15:44:49 -07:00
Rui UeyamaandClaude Opus 4.7 e2d0c1a93b Accept ! as a bracket-expression negation marker in glob patterns
Linker scripts produced for lld (e.g., the version script Chrome's
build emits) use the POSIX/shell convention `[!...]` to negate a
bracket character class. mold previously accepted only `[^...]`, so
those scripts failed to match. Accept both, treating `!` and `^`
identically at the start of a bracket expression.

Fixes https://github.com/rui314/mold/issues/1575

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
2026-05-07 12:18:41 +09:00
Rui UeyamaandClaude Opus 4.7 6b0ecea236 Don't drop DSO undef references when versym is VER_NDX_LOCAL
Commit 6b8293bf changed the early `ver` computation in
SharedFile::parse so that undef symbols use the raw versym instead
of being forced to VER_NDX_GLOBAL. Some BFD ld versions (notably
binutils 2.35 on Debian 11) emit VER_NDX_LOCAL (0) as the versym
for unversioned undefined dynamic symbols, so the subsequent
`if (ver == VER_NDX_LOCAL) continue;` check silently dropped those
undef references. With the DSO's reference invisible to mold, the
matching object-file definition was never marked as exported, and
at runtime the dynamic loader failed with "undefined symbol".

Restore the old behavior: defined symbols with VER_NDX_LOCAL are
still skipped (they aren't really exposed for dynamic linking),
but undef references are always processed.

Also fix the verdef/verneed merge in the same path. vd_ndx and
vna_other share a single versym index space (a producing linker
assigns them from one counter), so a single vector covers both.
read_verdef and read_verneed are folded into one read_version_strings
that populates that shared-index vector directly, replacing the
buggy `append` concatenation.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
2026-05-06 23:32:27 +09:00
Rui UeyamaandClaude Opus 4.7 6b8293bf8c Use .gnu.version_r when registering undefined DSO symbols
For an undefined symbol in a shared library, mold treated the versym
as VER_NDX_GLOBAL and registered the symbol under its bare name. That
meant an undef foo@VER_1.0 in libbar.so was looked up as just foo, so
a non-default foo@VER_1.0 in another DSO did not satisfy it and
--no-allow-shlib-undefined wrongly reported "undefined symbol: foo".

Parse .gnu.version_r and, when an undef has a versym indexing into it,
register the symbol as name@VERSION so the existing versioned-symbol
resolution path picks up matching definitions.

Fixes https://github.com/rui314/mold/issues/1571

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
2026-05-06 18:29:32 +09:00
Rui UeyamaandClaude Opus 4.7 82b5ed48bf Fix flaky compress-debug-sections-{zlib,zstd}-level tests on Alpine
`readelf | grep -q` plus `set -o pipefail` is fragile: when grep -q
matches it stops reading immediately, readelf gets SIGPIPE on its next
write, and pipefail propagates the resulting 141. Alpine (musl) hits
this where glibc happens to escape it. Drop -q so grep drains stdin.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
2026-05-06 15:57:23 +09:00
Rui UeyamaandClaude Opus 4.7 4d313a269c Rename gc-sections.cc visit() to avoid std::visit ADL clash on gcc 10
The new StartStopMap parameter contains std::string_view, so the
unqualified call to visit() inside mark() and the recursive call inside
visit() itself were resolved to std::visit by ADL on gcc 10 (Debian 11),
breaking the build.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
2026-05-06 14:40:33 +09:00
Rui UeyamaandClaude Opus 4.7 384527fd29 Don't unconditionally retain C-identifier-named sections in --gc-sections
A section whose name is a valid C identifier was treated as a GC root,
keeping it (and everything reachable from it) alive even when nothing
referenced its synthesized __start_/__stop_ marker symbols. That could
pull in unrelated code with undefined references and cause spurious
"undefined symbol" errors that lld and ld.bfd don't produce.

Build a multimap of c-identifier-named sections up front and, during
marking, when a relocation targets __start_<name> or __stop_<name>,
mark every section named <name> alive.

Fixes https://github.com/rui314/mold/issues/1571

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
2026-05-05 17:50:33 +09:00
Rui Ueyama d947b1a6d3 Update mold.1 (automated commit) 2026-05-04 14:00:41 +00:00
Rui UeyamaandClaude Opus 4.7 9e32fce011 Fix CI
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
2026-05-04 22:53:57 +09:00
Rui UeyamaandClaude Opus 4.7 b7102d26ca Add hidden --use-android-relr-tags option
Pre-Android-12 (API 30 and earlier) bionic loaders predate the official
ELF gABI assignment of DT_RELR/DT_RELRSZ/DT_RELRENT and use legacy
OS-specific tag values DT_ANDROID_RELR (0x6fffe000), DT_ANDROID_RELRSZ
(0x6fffe001), DT_ANDROID_RELRENT (0x6fffe003), and section type
SHT_ANDROID_RELR (0x6fffff00). With --use-android-relr-tags, mold emits
those instead of the standard ones so binaries linked with
--pack-dyn-relocs=relr can be loaded on those older devices. The flag
is undocumented (not listed in --help) since it's only relevant to
shipping to legacy Android targets.

https://github.com/rui314/mold/issues/1544

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
2026-05-04 22:53:56 +09:00
Rui UeyamaandClaude Opus 4.7 59957fce3b Add --pack-dyn-relocs=android support
Implements the Android Packed Relocations Format (APS2) used by Android
binaries to compress dynamic relocations. Two new modes are accepted:
`--pack-dyn-relocs=android` encodes all dynamic relocations as APS2 in
.rela.dyn (SHT_ANDROID_RELA) and emits DT_ANDROID_RELA(SZ) instead of
DT_RELA(SZ); `--pack-dyn-relocs=android+relr` is the same but with
R_RELATIVE relocations diverted to .relr.dyn via the existing RELR path.

The encoder groups consecutive relocations sharing offset_delta and
r_info to amortize SLEB128 overhead. The encoded bytes are decoded by
Android's bionic loader and by llvm-readelf, which the new test uses to
verify correctness.

main.cc gains a final set_osec_offsets() call after fix_synthetic_symbols.
Symbol values assigned there change addends for relocations that
reference synthetic symbols, which can shift the SLEB128-encoded section
size; re-running the layout convergence keeps sh_size consistent with
the final encoding.

Fixes https://github.com/rui314/mold/issues/1544

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
2026-05-04 22:53:56 +09:00
Rui UeyamaandClaude Opus 4.7 c36c5a64fc Centralize dynamic relocation generation in RelDynSection
Previously each chunk wrote its dynamic relocations into a magic offset
in .rel.dyn (tracked via per-chunk reldyn_offset), with a separate
construct_relr pass building per-chunk relr vectors. Now each chunk
returns its dynrels via a virtual collect_dynrels(), and RelDynSection
aggregates them into a single relocs vector that it owns and writes.
RELR-eligibility is determined by checking r_offset alignment of the
collected ElfRel entries (replacing the ABS_REL_RELR kind).

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
2026-05-04 22:53:55 +09:00
Rui Ueyama e71cef7448 Update mold.1 (automated commit) 2026-04-27 06:01:09 +00:00
Rui UeyamaandClaude Opus 4.7 6695f13dce Add --compress-debug-sections=zlib:N for configurable zlib compression level
Generalize the zstd:N syntax added in the previous commit so that the
zlib compression level can also be specified. zlib levels 0 through 9
are accepted, where 0 means no compression. Plain `zlib` remains
equivalent to `zlib:1` (the previous hardcoded default).

Also drop the objcopy/zstdcat round-trip from the zstd-level test in
favor of a simpler readelf check, matching the new zlib-level test, and
add error-message coverage for out-of-range levels.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
2026-04-27 15:00:26 +09:00
Rui Ueyama ab704cf476 Update mold.1 (automated commit) 2026-04-27 05:47:52 +00:00
Rui Ueyama c028765bb7 Merge pull request #1569 from pierluigilenoci/feat/zstd-compression-level
Add --compress-debug-sections=zstd:N for configurable zstd level
2026-04-27 14:39:37 +09:00
Pierluigi Lenoci 04209593c1 refactor: use zstd:<level> syntax for consistency with lld
Change the compression level syntax from zstd-<level> to zstd:<level>
as requested by @rui314 for consistency with lld.

Signed-off-by: Pierluigi Lenoci <[email protected]>
2026-04-26 20:23:28 +02:00
Pierluigi Lenoci 56fc57198a Add --compress-debug-sections=zstd-N for configurable zstd compression level
Allow users to specify a zstd compression level (1-22) for debug section
compression via --compress-debug-sections=zstd-N, where N is the desired
level. Higher levels achieve better compression ratios at the cost of
compression speed. The plain 'zstd' option remains equivalent to 'zstd-3'
(the previous hardcoded default).

Closes: https://github.com/rui314/mold/issues/1290
Signed-off-by: Pierluigi Lenoci <[email protected]>
2026-04-24 17:15:01 +02:00
Rui UeyamaandClaude Opus 4.7 84c411b9a2 [LoongArch] Don't relax GOT load to pcaddi when symbol is misaligned
pcaddi computes PC + (imm << 2), so the relaxation from a GOT load
(pcalau12i + ld.d) to pcaddi is valid only when the symbol's address
is 4-byte aligned. Without this check, a reference to a misaligned
symbol was relaxed to a pcaddi that resolves to the address with its
low two bits cleared, producing a wrong pointer.

Fixes https://github.com/rui314/mold/issues/1567

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
2026-04-19 17:23:40 +09:00
Rui UeyamaandClaude Opus 4.7 6f8d62e21c Fix non-deterministic --gdb-index output with multiple threads
The parallel writer that fills (type, cu_idx) tuples for each name
appended entries in thread-scheduling order, so names referenced from
multiple compilation units produced different .gdb_index bytes across
runs. Sort each name's tuple array after the parallel append so the
output is reproducible while preserving parallelism.

Fixes https://github.com/rui314/mold/issues/1566

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
2026-04-18 11:28:03 +09:00
Rui Ueyama 1b2306a71b Fix test failure on SPARC
We need to ignore SPARC-specific symbol with STT_REGISTER.

Fixes https://github.com/rui314/mold/issues/1564
2026-04-14 20:50:28 +09:00
Rui Ueyama a91381b1b1 Bump mold version to 2.41.0 2026-04-13 18:31:07 +09:00
Rui Ueyama 5b8c7dc023 Merge pull request #1547 from sicherha/fix_textrel2_test_on_Fedora_44
Fix `textrel2` test on Fedora 44
2026-04-07 16:46:48 +09:00
Rui Ueyama a1ae8da0c4 Fix --omagic to pack all sections into a single PT_LOAD segment
Previously, --omagic only flipped segment permissions to RWX but
still page-aligned PT_LOAD segments and emitted PT_GNU_RELRO, so
text and data ended up in separate page-padded segments. This
mismatched the documented behavior ("makes the first data segment
not aligned to a page boundary") and GNU ld's --omagic, which also
disables shared linking.

Make --omagic imply --Bstatic, disable RELRO, and stop forcing
PT_LOAD's p_align to the page size. Also gate chunk->is_relro on
z_relro so a disabled RELRO no longer leaks into address layout.

Fixes https://github.com/rui314/mold/issues/1562
2026-04-07 16:41:35 +09:00
Rui UeyamaandClaude Opus 4.6 632a0eead1 Fix spurious "duplicate symbol" errors with LTO
When linking with LTO, archive members extracted pre-LTO to satisfy
references from IR objects could cause false duplicate symbol errors.
This happened in two ways:

1. The pre-LTO check_duplicate_symbols flagged conflicts between LTO IR
   objects and regular objects. These conflicts are handled by the LTO
   backend and should not be reported.

2. After LTO compilation, archive members that were extracted pre-LTO
   retained stale is_reachable flags even when the LTO output superseded
   all their symbols. The post-LTO duplicate check then saw both the
   archive member and the LTO output defining the same symbol.

Fix both issues:
- In check_duplicate_symbols, skip errors where one side is an LTO IR
  object and the other is not. Only IR-vs-IR duplicates need catching.
- In do_lto, reset reachability for archive members before the post-LTO
  resolve_symbols so that only genuinely needed members are re-extracted.

Fixes https://github.com/rui314/mold/issues/1421

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
2026-04-05 19:55:33 +09:00
Rui Ueyama 04430ade4b Merge pull request #1546 from jwakely/linker-script-group-as-needed
Fix getting output type from linker scripts that use AS_NEEDED
2026-04-05 14:35:21 +09:00
Rui UeyamaandClaude Opus 4.6 91a5f3c4bf Skip nightly build-all workflow when there are no new commits
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
2026-04-05 14:28:23 +09:00
Rui Ueyama e804c8caff Fix a compiler warning 2026-04-05 13:47:07 +09:00
Rui Ueyama f378b18369 Support R_RISCV_GOT32_PCREL
This change should unbreak our riscv64 buildbot by fixing
test/relative-vtables.sh test.
2026-04-05 13:47:07 +09:00
Rui Ueyama ff83f60fbc Merge pull request #1557 from akarle/ak/fix-separate-debug-file-corruption
Fix separate-debug-file strtab corruption
2026-04-04 15:58:17 +09:00
Rui Ueyama f8b49ee83e Fix FreeBSD buildbot 2026-04-04 15:54:06 +09:00
Rui Ueyama 18ea70c696 Merge pull request #1554 from karesansui-u/fix/redo-main-abort-message
Replace abort() with diagnostic message in redo_main()
2026-04-04 15:48:29 +09:00
Rui Ueyama fd95adda95 "#undef ERROR" before use for MinGW compatibility
https://github.com/rui314/mold/pul/1558
2026-04-04 15:01:32 +09:00
Rui Ueyama 4e58828670 Simplify and add tests 2026-04-04 13:55:53 +09:00
Rui Ueyama 027111315e Merge pull request #1559 from shivendra02467/fix-shlib-undefined-visibility
[ELF] Fix --no-allow-shlib-undefined for hidden static providers and LTO
2026-04-04 13:45:12 +09:00
Rui Ueyama d4d5eae43d Do not report an error on LTO unclaimed files in archives
Previously, mold eagerly report an "not claimed by the LTO plugin" error
if it finds an LTO object file that is not claimed by an LTO plugin.
2026-04-04 11:34:42 +09:00
Rui Ueyama 274a46b9f7 Revert "defer incompatible archive LTO member errors until selected"
This reverts commit 6c4701955c.
2026-04-04 11:09:05 +09:00
Rui Ueyama 906af24d25 Merge pull request #1560 from tobim/push-rpuotyoounvk
Various LTO compatibility fixes
2026-04-04 11:07:20 +09:00
Rui UeyamaandClaude Opus 4.6 be28db1241 Support R_AARCH64_GOTPCREL32 relocation for relative vtables
This relocation is used by clang's -fexperimental-relative-c++-abi-vtables
to emit 32-bit GOT-relative PC-relative offsets in vtable entries (e.g. for
RTTI pointers). It resolves to G + GOT + A - P as a 32-bit value.

Fixes https://github.com/rui314/mold/issues/1561

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
2026-04-03 19:46:19 +09:00
Tobias Mayer 6c4701955c defer incompatible archive LTO member errors until selected 2026-03-21 14:21:00 +01:00
Tobias Mayer ce0ee76550 Add regression test for mixed LLVM/GCC slim LTO archive link 2026-03-21 14:18:31 +01:00
Tobias Mayer 2a1e5c6e8d Fall back to native code for GCC fat LTO objects with LLVM plugin 2026-03-21 14:18:31 +01:00
Tobias Mayer 487b83e581 Add a test for mixed toolchain fatLTO
LTO links where the main driver is LLVM but some objects
are GCC fatLTO should succeed.
2026-03-21 14:18:31 +01:00
Tobias Mayer a660037f95 Preserve non-lto comdat members when section is shared with LTO objects 2026-03-21 10:24:17 +01:00
Tobias Mayer 10342f7c2a Add a regression test for mixed lto and assembly comdat 2026-03-21 10:02:05 +01:00
Shivendra Sharma f6bb1175eb [ELF] Fix --no-allow-shlib-undefined for hidden static providers and LTO
This patch fixes two distinct blind spots in how `mold` handles
--no-allow-shlib-undefined checks during the final link phase:

1. The Hidden Static Provider Bug (False Negative)
Previously, if a shared library requested a symbol that was provided by a
static object file with STV_HIDDEN, `mold` would silently overwrite the
global symbol visibility to STV_DEFAULT and rubber-stamp the binary,
resulting in a guaranteed `symbol lookup error` at runtime.
Fix: We now interrogate the raw `elf_syms` array of the static object
provider. If the original visibility is hidden/internal, we flag it as an
invalid provider and throw the error, matching GNU ld/lld behavior.

2. The LTO Shadowing Bug (False Positive)
When LTO is heavily utilized, GCC's plugin may optimize away symbols that
are theoretically provided by static objects if they aren't used internally.
If a shared library still needs that symbol, `mold`'s static state
registered the provider as NULL and threw an error, ignoring the fact
that a loaded DSO (like libc.so) offered a valid public fallback.
Fix: Introduced `has_public_dso_fallback()`. Before throwing an undefined
symbol error, `mold` performs a rapid scan of `ctx.dsos`. If a public
fallback exists dynamically, the error is safely suppressed.
2026-03-20 00:07:44 +05:30
Alex Karle 4cd4d5693e Fix separate-debug-file strtab corruption
Originally discussed in #1535, the debug data generated by
--separate-debug-file is being corrupted. In the best case scenario,
there's many blank/nameless undefined symbols in the symtab, but in
most production scenario I've encountered, the file is corrupt / gdb
refuses to load it:

    $ gdb main
    Reading symbols from $PWD/main.dbg...
    BFD: /$PWD/main.dbg: invalid string offset 4130582 >= 1345 for section `.strtab'
    BFD: /$PWD/main.dbg: invalid string offset 16777228 >= 1345 for section `.strtab'

    $ readelf -Ws main.dbg
    Symbol table '.symtab' contains 139 entries:
    [...]
    38: 0000000000000000     0 SECTION LOCAL  DEFAULT   38 .strtab
    39: 0000000000000000     0 SECTION LOCAL  DEFAULT   39 .symtab
    40: 0018a80200010000 0xb10000003f071500 NOTYPE  LOCAL  DEFAULT bad section index[3232] <corrupt>
    41: 0000003f07140000 0xfd02000100000cc2 NOTYPE  LOCAL  HIDDEN  bad section index[11500] <corrupt>
    42: 02000100000cd300 0x3f0712000008a3 <unknown>: 7 LOCAL  PROTECTED [<other>: 3c]   UND <corrupt>
    43: 3f07110000056202 0x100000cf5000000 NOTYPE  LOCAL  DEFAULT    1 <corrupt>

This appears to be happening because the strtab size is set to be larger
than its true value, causing garbage data to be placed in it, which
breaks the symtab.

This regression occurred in 5b71029, which fixed a different form of
corruption (the PHDR symbol escaping into the section headers, shifting
the section offsets), but created this strtab corruption. The root cause
is that `compute_section_headers` was moved prior to the wiping of
irrelevant chunks. `compute_section_headers` computes the strtab size by
adding up individual chunk strtab contributions. It needs to occur _after_
the wiping so that the strtab doesn't account for the data before the
wipe.

The core of this patch is really just a partial revert of 5b71029--the
compute_section_headers call appears to have been in the correct place
prior to this change; I've added a test that catches the issue by
intentionally bloating the size of the `.plt` section with many external
symbols. This section is wiped in the dbg data, so by bloating it, the
test can inspect for corruption.

Fixes #1535

Signed-off-by: Alex Karle <[email protected]>
2026-03-16 12:13:09 -04:00
karesansui 93e1aeaec6 Replace abort() with diagnostic message in redo_main()
When mold auto-detects an unsupported target from ELF headers,
redo_main() previously called abort() with no context. This replaces
it with a descriptive error message matching the style used by the
-m flag path in cmdline.cc.

Fixes #1553
2026-03-12 02:24:00 +09:00
Rui Ueyama fc96c1b9ef Do not call fallocate on tmpfs
Interestingly, calling fallocate on tmpfs is very slow. It took
almost a second for a fresh ~5 GiB file, even though intuititvely
it should finish very quickly.
2026-03-05 08:40:16 +09:00
Rui Ueyama 0c0c42b31d Add an entry for fedora-asahi-remix 2026-03-01 13:23:17 +09:00
Jonathan Wakely e0a6f8e1d3 Fix getting output type from linker scripts that use AS_NEEDED
Make Script<E>::get_script_output_type support AS_NEEDED inside an INPUT
or GROUP group, so that the libatomic_asneeded.so linker script used by
GCC 16 works.

Fixes #1545

Signed-off-by: Jonathan Wakely <[email protected]>
2026-02-28 09:08:49 +00:00
Rui Ueyama f8da34575f Unbreak Windows buildbot 2026-02-27 16:10:33 +09:00
Rui Ueyama a4db2382a5 Improve --gdb-index performance
This change eliminates the cost of zero-initialization on vector
resizing by using malloc/realloc instead. We also parallelize the
loops to write adddress areas and types to .gdb_index.
2026-02-27 15:45:51 +09:00
Rui Ueyama 6d6a128bf9 Handle -C and --chroot early
We read version script files in cmdline.cc, so we need to handle -C
and --chroot early. Otherwise, a version script passed by command line
can cause a "file not found" error.
2026-02-27 15:45:51 +09:00
Rui Ueyama aeb1041f1a Refactor 2026-02-27 15:45:51 +09:00
Christoph Erhardt 6e40a87aad Fix textrel2 test on Fedora 44
Tell the GNU linker to not report dynamic relocations in read-only
sections as an error.
2026-01-30 22:09:29 +01:00
Rui Ueyama 17956fdfa1 Merge pull request #1541 from mati865/push-qlyqwkunnpmx
Fix typo in test name
2025-12-12 09:43:18 +09:00
Rui Ueyama 97b43aaf0d Fix CI 2025-12-10 13:57:55 +09:00
Rui Ueyama c3bf8fd803 Attempt to fix CI 2025-12-10 12:59:41 +09:00
Rui Ueyama 4e51cfaa67 Update mold.1 (automated commit) 2025-12-10 03:29:09 +00:00
Rui Ueyama 87873cad57 Change the man page to reflect reality 2025-12-10 12:15:40 +09:00
Rui Ueyama 9f463a62ac Align the end of RELRO to a page boundary even if -z separate-loadable-segment is given
Otherwise the last page of a relro segment is not protected by the
runtime, as the runtime aligns down the end address of a RELRO segment.
2025-12-10 12:11:45 +09:00
Rui Ueyama 5b710298fc d1;10;0cAttempt to fix an issue with --separate-debug-file
https://github.com/rui314/mold/issues/1535
2025-11-17 16:01:14 +09:00
Rui Ueyama 4798f43358 Do not emit a bogus "PHDR" section for --separate-debug-file
Previously, we emit a bogus section with name "PHDR" for a separate
debug file.

Fixes https://github.com/rui314/mold/issues/1535
2025-11-15 12:42:53 +09:00
Rui Ueyama fcd5391981 Adapt to a recent readelf output format change
Fixes https://github.com/rui314/mold/issues/1533
2025-11-11 22:39:36 +09:00
Rui Ueyama ad08999d03 Refactor 2025-11-10 19:55:51 +09:00
Rui Ueyama 016bb8af6e Refactor 2025-11-05 20:13:14 +09:00
Rui Ueyama 3a5985ca14 Refactor 2025-11-04 22:06:18 +09:00
Rui Ueyama e99551c9f0 Refactor 2025-11-04 16:38:39 +09:00
Rui Ueyama 56f8eb3b92 Refactor 2025-11-04 15:54:44 +09:00
Rui Ueyama e459ed59c6 [SPARC64] Do not assume the dest reg of R_SPARC_GOTDATA_OP is always %g1
https://github.com/rui314/mold/issues/1529
2025-11-04 15:25:33 +09:00
Rui Ueyama 4d94410ad4 [SPARC64] Preserve %rd when rewriting R_SPARC_TLS_GD_ADD
https://github.com/rui314/mold/issues/1529
2025-11-04 15:21:58 +09:00
Rui Ueyama 069f4da96a [SPARC64] Change how we swap instructions for TLS_GD_CALL
https://github.com/rui314/mold/issues/1529
2025-11-04 15:21:52 +09:00
Rui Ueyama 01923e4b4b Refactor 2025-11-02 17:33:40 +09:00
Rui Ueyama 5cac744adf Merge pull request #1528 from koachan/main
[SPARC64] Preserve registers used during optimization transforms
2025-11-02 17:29:08 +09:00
Koakuma a88a2163b2 [SPARC64] Decouple rs1 and rd in R_SPARC_TLS_GD_LO10 transform
The compiler might place the two values on different registers,
so don't modify them during transforms.
2025-11-02 15:18:15 +07:00
Koakuma ca0b5973a2 [SPARC64] Preserve rd of TLS *_ADD relocations during optimization
GCC might decide to put the result of the operation in another register,
so remove hardcodings of %o0 as the destination register (`rd` field).
2025-11-02 00:40:20 +07:00
Rui Ueyama 5fb6d8aa63 Fix CI 2025-10-31 16:28:42 +09:00
Rui Ueyama 3ad34d4ec3 Do not create a PDE IFUNC entry if the ifunc is not referenced within the exe
Fixes https://github.com/rui314/mold/issues/1527
2025-10-31 15:47:06 +09:00
Rui Ueyama dce9ac1e5c Allow building mold for a specific set of targets
By popular request, this commit adds a new cmake variable that allows
building mold with a limited set of supported targets, which can help
reduce build time.

The use of this flag is discouraged unless you build mold frequently
for personal use. Otherwise, build mold with the default settings for
simplicity and maximum compatibility. In particular, distributions
should not build mold with a limited set of supported targets.

Fixes https://github.com/rui314/mold/issues/1364
2025-10-31 12:20:47 +09:00
Rui Ueyama 7cc59cc71a Fix a compiler warning 2025-10-30 15:39:21 +09:00
Rui Ueyama 98d7691c21 Refactor 2025-10-25 11:24:50 +09:00
Rui Ueyama 92adbb1d61 [SPARC64] Fix TLS GD with GCC -O2
Fixes https://github.com/rui314/mold/issues/1525
2025-10-25 11:13:21 +09:00
Rui Ueyama 8634570e56 Refactor 2025-10-23 21:05:50 +09:00
Rui Ueyama fce89c24e5 Respect --no-threads with --compress-debug-sections
Fixes https://github.com/rui314/mold/issues/1524
2025-10-23 12:28:39 +09:00
Rui Ueyama 2b36f8a982 Merge pull request #1523 from koachan/main
[SPARC64] Fix R_SPARC_TLS_GD_ADD handling
2025-10-23 10:04:45 +09:00
Rui Ueyama 7f8c50bfa7 [SPARC] Honor --no-apply-dnamic-relocs for R_SPARC_UA64
Related to: https://github.com/rui314/mold/pull/1523
2025-10-23 10:03:17 +09:00
Koakuma a16bb13a01 [SPARC64] Fix R_SPARC_TLS_GD_ADD handling
R_SPARC_TLS_GD_ADD can use any register for the base address (ELF Handling
For Thread-Local Storage, pg. 21), so remove the hardcoded %l7 usage.
2025-10-22 20:16:41 +07:00
Rui Ueyama f55ad918d4 [SPARC64] Emit dynamic relocations for R_SPARC_UA64
Related to: https://github.com/rui314/mold/pull/1523
2025-10-22 18:08:22 +09:00
Rui Ueyama 5164da8ad2 Fix CI 2025-10-09 17:20:06 +09:00
Rui Ueyama 7cc21c7657 Refactor 2025-10-09 16:30:37 +09:00
Rui Ueyama f8089ca121 Merge pull request #1522 from bmilanich/pr/eh_icf
Consider .gcc_except_table* sections for ICF.
2025-10-09 12:44:27 +09:00
Rui Ueyama 04a99003c2 Add missing newlines 2025-10-09 11:45:21 +09:00
Basil Milanich fd8659027b Consider .gcc_except_table* sections for ICF. 2025-10-08 14:12:07 -05:00
Rui Ueyama 0b20faa0cd [PPC32] Support R_PPC_DTPREL32 in non-alloc sections
Fixes https://github.com/rui314/mold/issues/1521
2025-10-03 10:47:27 +09:00
Rui Ueyama 7c82a6a00e Fix typo 2025-09-21 11:45:04 +09:00
Rui Ueyama 5148754c14 Refactor 2025-09-20 17:29:38 +09:00
Rui Ueyama 91439beea5 --section-order: Report an error if address goes backward
https://github.com/rui314/mold/issues/1514
2025-09-20 12:16:25 +09:00
Rui Ueyama 311da914d3 Refactor 2025-09-20 11:09:43 +09:00
Rui Ueyama dfb18afd69 Fix CI 2025-09-19 19:19:23 +09:00
Rui Ueyama 8dee52318b Run MemorySanitizer tests on GitHub Actions 2025-09-19 19:08:04 +09:00
Rui Ueyama 9d7f20b34e Avoid unnecessary zero-initializations 2025-09-19 19:08:04 +09:00
Rui Ueyama aa80487536 Merge pull request #1517 from etwoo/feature/msan-build
support MemorySanitizer w/ instrumented C++ stdlib
2025-09-19 15:16:32 +09:00
Eric Woo 13cec07aa3 fix msan error in compress.cc
==70801==WARNING: MemorySanitizer: use-of-uninitialized-value
0) 0x56218bab14b3 in mold::zlib_compress(std::__1::span<unsigned char, 18446744073709551615ul>) /home/ewoo/mold/lib/compress.cc:89:13
1) 0x56218bab14b3 in mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0::operator()(long) const /home/ewoo/mold/lib/compress.cc:106:17
2) 0x56218bab14b3 in decltype(std::declval<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0 const&>()(std::declval<long&>())) std::__1::__invoke[abi:nn220000]<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0 const&, long&>(mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0 const&, long&) /home/ewoo/llvm-project/build/include/c++/v1/__type_traits/invoke.h:251:25
3) 0x56218bab14b3 in std::__1::__invoke_result<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0 const&, long&>::type std::__1::invoke[abi:nn220000]<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0 const&, long&>(mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0 const&, long&) /home/ewoo/llvm-project/build/include/c++/v1/__functional/invoke.h:29:10
4) 0x56218bab14b3 in std::__1::__invoke_result<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0 const&, long&>::type tbb::detail::d0::invoke<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0 const&, long&>(mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0 const&, long&) /usr/include/tbb/../oneapi/tbb/detail/_utils.h:356:12
5) 0x56218bab14b3 in tbb::detail::d1::parallel_for_body_wrapper<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0, long>::operator()(tbb::detail::d1::blocked_range<long> const&) const /usr/include/tbb/../oneapi/tbb/parallel_for.h:206:13
6) 0x56218bab14b3 in decltype(std::declval<tbb::detail::d1::parallel_for_body_wrapper<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0, long> const&>()(std::declval<tbb::detail::d1::blocked_range<long>&>())) std::__1::__invoke[abi:nn220000]<tbb::detail::d1::parallel_for_body_wrapper<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0, long> const&, tbb::detail::d1::blocked_range<long>&>(tbb::detail::d1::parallel_for_body_wrapper<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0, long> const&, tbb::detail::d1::blocked_range<long>&) /home/ewoo/llvm-project/build/include/c++/v1/__type_traits/invoke.h:251:25
7) 0x56218bab14b3 in std::__1::__invoke_result<tbb::detail::d1::parallel_for_body_wrapper<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0, long> const&, tbb::detail::d1::blocked_range<long>&>::type std::__1::invoke[abi:nn220000]<tbb::detail::d1::parallel_for_body_wrapper<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0, long> const&, tbb::detail::d1::blocked_range<long>&>(tbb::detail::d1::parallel_for_body_wrapper<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0, long> const&, tbb::detail::d1::blocked_range<long>&) /home/ewoo/llvm-project/build/include/c++/v1/__functional/invoke.h:29:10
8) 0x56218bab14b3 in std::__1::__invoke_result<tbb::detail::d1::parallel_for_body_wrapper<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0, long> const&, tbb::detail::d1::blocked_range<long>&>::type tbb::detail::d0::invoke<tbb::detail::d1::parallel_for_body_wrapper<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0, long> const&, tbb::detail::d1::blocked_range<long>&>(tbb::detail::d1::parallel_for_body_wrapper<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0, long> const&, tbb::detail::d1::blocked_range<long>&) /usr/include/tbb/../oneapi/tbb/detail/_utils.h:356:12
9) 0x56218bab14b3 in tbb::detail::d1::start_for<tbb::detail::d1::blocked_range<long>, tbb::detail::d1::parallel_for_body_wrapper<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0, long>, tbb::detail::d1::auto_partitioner const>::run_body(tbb::detail::d1::blocked_range<long>&) /usr/include/tbb/../oneapi/tbb/parallel_for.h:117:9

Uninitialized value was created by an allocation of 'nbits.i.i.i.i.i.i.i.i.i' in the stack frame
0) 0x56218bab11e6 in mold::zlib_compress(std::__1::span<unsigned char, 18446744073709551615ul>) /home/ewoo/mold/lib/compress.cc:87:3
1) 0x56218bab11e6 in mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0::operator()(long) const /home/ewoo/mold/lib/compress.cc:106:17
2) 0x56218bab11e6 in decltype(std::declval<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0 const&>()(std::declval<long&>())) std::__1::__invoke[abi:nn220000]<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0 const&, long&>(mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0 const&, long&) /home/ewoo/llvm-project/build/include/c++/v1/__type_traits/invoke.h:251:25
3) 0x56218bab11e6 in std::__1::__invoke_result<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0 const&, long&>::type std::__1::invoke[abi:nn220000]<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0 const&, long&>(mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0 const&, long&) /home/ewoo/llvm-project/build/include/c++/v1/__functional/invoke.h:29:10
4) 0x56218bab11e6 in std::__1::__invoke_result<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0 const&, long&>::type tbb::detail::d0::invoke<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0 const&, long&>(mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0 const&, long&) /usr/include/tbb/../oneapi/tbb/detail/_utils.h:356:12
5) 0x56218bab11e6 in tbb::detail::d1::parallel_for_body_wrapper<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0, long>::operator()(tbb::detail::d1::blocked_range<long> const&) const /usr/include/tbb/../oneapi/tbb/parallel_for.h:206:13
6) 0x56218bab11e6 in decltype(std::declval<tbb::detail::d1::parallel_for_body_wrapper<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0, long> const&>()(std::declval<tbb::detail::d1::blocked_range<long>&>())) std::__1::__invoke[abi:nn220000]<tbb::detail::d1::parallel_for_body_wrapper<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0, long> const&, tbb::detail::d1::blocked_range<long>&>(tbb::detail::d1::parallel_for_body_wrapper<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0, long> const&, tbb::detail::d1::blocked_range<long>&) /home/ewoo/llvm-project/build/include/c++/v1/__type_traits/invoke.h:251:25
7) 0x56218bab11e6 in std::__1::__invoke_result<tbb::detail::d1::parallel_for_body_wrapper<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0, long> const&, tbb::detail::d1::blocked_range<long>&>::type std::__1::invoke[abi:nn220000]<tbb::detail::d1::parallel_for_body_wrapper<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0, long> const&, tbb::detail::d1::blocked_range<long>&>(tbb::detail::d1::parallel_for_body_wrapper<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0, long> const&, tbb::detail::d1::blocked_range<long>&) /home/ewoo/llvm-project/build/include/c++/v1/__functional/invoke.h:29:10
8) 0x56218bab11e6 in std::__1::__invoke_result<tbb::detail::d1::parallel_for_body_wrapper<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0, long> const&, tbb::detail::d1::blocked_range<long>&>::type tbb::detail::d0::invoke<tbb::detail::d1::parallel_for_body_wrapper<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0, long> const&, tbb::detail::d1::blocked_range<long>&>(tbb::detail::d1::parallel_for_body_wrapper<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0, long> const&, tbb::detail::d1::blocked_range<long>&) /usr/include/tbb/../oneapi/tbb/detail/_utils.h:356:12
9) 0x56218bab11e6 in tbb::detail::d1::start_for<tbb::detail::d1::blocked_range<long>, tbb::detail::d1::parallel_for_body_wrapper<mold::ZlibCompressor::ZlibCompressor(unsigned char*, long)::$_0, long>, tbb::detail::d1::auto_partitioner const>::run_body(tbb::detail::d1::blocked_range<long>&) /usr/include/tbb/../oneapi/tbb/parallel_for.h:117:9

SUMMARY: MemorySanitizer: use-of-uninitialized-value /home/ewoo/mold/lib/compress.cc:89:13 in mold::zlib_compress(std::__1::span<unsigned char, 18446744073709551615ul>)
2025-09-18 04:05:30 +00:00
Eric Woo 921649fc7d fix msan error in passes.cc
Uninitialized bytes in fwrite at offset 880 inside [0x724000002000, 7968)
==60359==WARNING: MemorySanitizer: use-of-uninitialized-value
0) 0x561279ddfd59 in mold::MallocOutputFile<mold::X86_64>::close(mold::Context<mold::X86_64>&) /home/ewoo/mold/src/mold.h:1587:5

Uninitialized value was stored to memory at
0) 0x56127521426e in __msan_memcpy.part.0 msan_interceptors.cpp.o
1) 0x56127701a499 in void mold::write_vector<unsigned char>(void*, std::__1::vector<unsigned char, std::__1::allocator<unsigned char>> const&) /home/ewoo/mold/src/../lib/lib.h:246:5
2) 0x56127701a499 in mold::BuildIdSection<mold::X86_64>::copy_buf(mold::Context<mold::X86_64>&) /home/ewoo/mold/src/output-chunks.cc:2745:3

Uninitialized value was stored to memory at
0) 0x56127521412e in __msan_memmove.part.0 msan_interceptors.cpp.o
1) 0x561277df5cca in unsigned char* std::__1::__constexpr_memmove[abi:nn220000]<unsigned char, unsigned char>(unsigned char*, unsigned char*, std::__1::__element_count) /home/ewoo/llvm-project/build/include/c++/v1/__string/constexpr_c_functions.h:228:5
2) 0x561277df5cca in std::__1::pair<unsigned char*, unsigned char*> std::__1::__copy_trivial_impl[abi:nn220000]<unsigned char, unsigned char>(unsigned char*, unsigned char*, unsigned char*) /home/ewoo/llvm-project/build/include/c++/v1/__algorithm/copy_move_common.h:64:3
3) 0x561277df5cca in std::__1::pair<unsigned char*, unsigned char*> std::__1::__copy_impl::operator()[abi:nn220000]<unsigned char, unsigned char, 0>(unsigned char*, unsigned char*, unsigned char*) const /home/ewoo/llvm-project/build/include/c++/v1/__algorithm/copy.h:234:12
4) 0x561277df5cca in std::__1::pair<unsigned char*, unsigned char*> std::__1::__copy_move_unwrap_iters[abi:nn220000]<std::__1::__copy_impl, unsigned char*, unsigned char*, unsigned char*, 0>(unsigned char*, unsigned char*, unsigned char*) /home/ewoo/llvm-project/build/include/c++/v1/__algorithm/copy_move_common.h:94:19
5) 0x561277df5cca in std::__1::pair<unsigned char*, unsigned char*> std::__1::__copy[abi:nn220000]<unsigned char*, unsigned char*, unsigned char*>(unsigned char*, unsigned char*, unsigned char*) /home/ewoo/llvm-project/build/include/c++/v1/__algorithm/copy.h:241:10
6) 0x561277df5cca in unsigned char* std::__1::copy[abi:nn220000]<unsigned char*, unsigned char*>(unsigned char*, unsigned char*, unsigned char*) /home/ewoo/llvm-project/build/include/c++/v1/__algorithm/copy.h:247:10
7) 0x561277df5cca in unsigned char* std::__1::__uninitialized_allocator_copy_impl[abi:nn220000]<std::__1::allocator<unsigned char>, unsigned char, unsigned char, 0>(std::__1::allocator<unsigned char>&, unsigned char*, unsigned char*, unsigned char*) /home/ewoo/llvm-project/build/include/c++/v1/__memory/uninitialized_algorithms.h:574:12
8) 0x561277df5cca in unsigned char* std::__1::__uninitialized_allocator_copy[abi:nn220000]<std::__1::allocator<unsigned char>, unsigned char*, unsigned char*, unsigned char*>(std::__1::allocator<unsigned char>&, unsigned char*, unsigned char*, unsigned char*) /home/ewoo/llvm-project/build/include/c++/v1/__memory/uninitialized_algorithms.h:582:28
9) 0x561277df5cca in void std::__1::vector<unsigned char, std::__1::allocator<unsigned char>>::__construct_at_end<unsigned char*, unsigned char*>(unsigned char*, unsigned char*, unsigned long) /home/ewoo/llvm-project/build/include/c++/v1/__vector/vector.h:954:17
10) 0x561277df5cca in void std::__1::vector<unsigned char, std::__1::allocator<unsigned char>>::__init_with_size[abi:nn220000]<unsigned char*, unsigned char*>(unsigned char*, unsigned char*, unsigned long) /home/ewoo/llvm-project/build/include/c++/v1/__vector/vector.h:602:7
11) 0x561277df5cca in std::__1::vector<unsigned char, std::__1::allocator<unsigned char>>::vector[abi:nn220000]<unsigned char*, 0>(unsigned char*, unsigned char*) /home/ewoo/llvm-project/build/include/c++/v1/__vector/vector.h:214:5
12) 0x561277df5cca in void mold::write_build_id<mold::X86_64>(mold::Context<mold::X86_64>&) /home/ewoo/mold/src/passes.cc:3270:29

Uninitialized value was created by an allocation of 'buf' in the stack frame
0) 0x561277df597e in void mold::write_build_id<mold::X86_64>(mold::Context<mold::X86_64>&) /home/ewoo/mold/src/passes.cc:3266:5

SUMMARY: MemorySanitizer: use-of-uninitialized-value /home/ewoo/mold/src/mold.h:1587:5 in mold::MallocOutputFile<mold::X86_64>::close(mold::Context<mold::X86_64>&)
2025-09-18 04:04:25 +00:00
Eric Woo 33545031d7 fix msan error in input-sections.cc
==45321==WARNING: MemorySanitizer: use-of-uninitialized-value
0) 0x56205a24a8f8 in char const* std::__1::__constexpr_memchr[abi:nn220000]<char const, char>(char const*, char, unsigned long) /home/ewoo/llvm-project/build/include/c++/v1/__string/constexpr_c_functions.h:149:30
1) 0x56205a24a8f8 in std::__1::char_traits<char>::find[abi:nn220000](char const*, unsigned long, char const&) /home/ewoo/llvm-project/build/include/c++/v1/__string/char_traits.h:135:12
2) 0x56205a24a8f8 in unsigned long std::__1::__str_find[abi:nn220000]<char, unsigned long, std::__1::char_traits<char>, 18446744073709551615ul>(char const*, unsigned long, char, unsigned long) /home/ewoo/llvm-project/build/include/c++/v1/__string/char_traits.h:353:23
3) 0x56205a24a8f8 in std::__1::basic_string_view<char, std::__1::char_traits<char>>::find[abi:nn220000](char, unsigned long) const /home/ewoo/llvm-project/build/include/c++/v1/string_view:509:12
4) 0x56205a24a8f8 in mold::find_null(std::__1::basic_string_view<char, std::__1::char_traits<char>>, long, long) /home/ewoo/mold/src/input-sections.cc:363:17
5) 0x56205a24a8f8 in mold::MergeableSection<mold::X86_64>::split_contents(mold::Context<mold::X86_64>&) /home/ewoo/mold/src/input-sections.cc:401:20

Uninitialized value was created by a heap allocation
0) 0x562058af198b in operator new(unsigned long) (/home/ewoo/mold/build/mold+0x23a98b) (BuildId: 29d9e90237b4b68ceff9639a3d9599d70f774d75)
1) 0x56205a2430fa in mold::InputSection<mold::X86_64>::uncompress(mold::Context<mold::X86_64>&) /home/ewoo/mold/src/input-sections.cc:65:13

SUMMARY: MemorySanitizer: use-of-uninitialized-value /home/ewoo/mold/src/input-sections.cc:363:17 in mold::find_null(std::__1::basic_string_view<char, std::__1::char_traits<char>>, long, long)
2025-09-18 04:03:09 +00:00
Eric Woo f800f5226c support MemorySanitizer w/ instrumented C++ stdlib 2025-09-18 04:02:39 +00:00
Rui Ueyama 8cf0b59e8f Update --help message 2025-09-15 08:04:28 +09:00
Rui Ueyama 729ceabd8c Update mold.1 (automated commit) 2025-09-14 14:34:10 +00:00
Rui Ueyama 131b5a9e89 Update man page 2025-09-14 23:32:00 +09:00
Rui Ueyama 056286d741 Partially revert 7dd7db0559
so that the output from --print-gc-sections and --print-icc-sections
remains the same as they were.

https://github.com/rui314/mold/pull/1507
2025-09-14 23:12:42 +09:00
Rui Ueyama 79ad0d2b4a Refactor 2025-09-14 19:13:35 +09:00
Rui Ueyama 6454b6c85a Merge pull request #1507 from pedronavf/print_sections_to_file
--print-gc-sections and --print-icf-sections can now take =FILE to s…
2025-09-14 19:11:30 +09:00
Rui Ueyama 49f9c5964a Do not convert read-only data to bss with --zero-to-data
Read-only data sections containing uninitialized variables are essentially
useless because reading from them always returns 0 at any time, so if they
exist, they are there for a reason. We do not want to automatically
convert it to a BSS.

https://github.com/rui314/mold/issues/1514
2025-09-14 10:36:03 +09:00
Rui Ueyama 3fb153e2ee Fix CI 2025-09-12 18:13:05 +09:00
Rui Ueyama 847341d208 Add --zero-to-bss
https://github.com/rui314/mold/pull/1515
2025-09-12 15:26:31 +09:00
Rui Ueyama 34c39e39f0 Refactor 2025-08-30 10:18:46 +09:00
Rui Ueyama 05827319da Fix test for system that doesn't support -static 2025-08-27 09:11:47 +09:00
Rui Ueyama ffbcfdb880 Merge pull request #1508 from PowellNGL/patch-1
Fix GCC package name for RHEL-based distros in install-build-deps.sh
2025-08-27 09:11:32 +09:00
Nick Powell 415176f530 Use correct GCC C++ package name for RHEL-based distros 2025-08-26 19:04:19 +02:00
Pedro Navarro 7dd7db0559 --print-gc-sections and --print-icf-sections can now take =FILE to save their output to a file 2025-08-26 02:26:41 -07:00
Rui Ueyama f5658e8bb4 Refactor 2025-08-26 11:24:12 +09:00
Rui Ueyama 8825ed7004 Merge pull request #1506 from minhbq-99/fix-pop-state
Correctly track --static value inside --push-state/--pop-state
2025-08-26 10:54:45 +09:00
Bui Quang Minh 001f718647 Correctly track --static value inside --push-state/--pop-state
Currently, when using --static inside --push-state and --pop-state, we
incorrectly mark the global state as static. This commit changes to the
correct behavior where global state is marked as static only when
--static is used at the outer most (outside of any --push-state,
--pop-state).
2025-08-25 23:02:40 +08:00
Rui Ueyama 4aa1a32d72 Bump required clang version from 12 to 16
Closes https://github.com/rui314/mold/issues/1505
2025-08-21 11:05:15 +09:00
Rui Ueyama 083901f42d Bump mold version to 2.40.4 2025-08-17 10:32:35 +09:00
Rui Ueyama 0a36282062 Revert "Choose the last file if we have more than one -l for the same DSO"
This reverts commit 2783e82436.

I don't want to take too much risk for 2.40.4, so I'll revert this change
for now.

https://github.com/rui314/mold/issues/1494
2025-08-16 12:21:41 +09:00
Rui Ueyama 754949f118 Revert "Upgrade bundled mimalloc to 3.0.8"
This reverts commit 1b76a11af2.

Closes https://github.com/rui314/mold/issues/1502
2025-08-15 11:48:32 +09:00
Rui Ueyama 05f5cdcf29 Add Alt Linux 2025-08-15 07:29:03 +09:00
Rui Ueyama 0ef640c95d Add Oracle Linux
Fixes https://github.com/rui314/mold/issues/1501
2025-08-15 06:58:13 +09:00
Rui Ueyama 7e0b728378 [ppc64v2] Support R_PPC64_GOT16
Fixes https://github.com/rui314/mold/issues/1497
2025-08-11 11:32:55 +09:00
Rui Ueyama d9b20a1580 [PPC64v2] Support R_PPC64_REL14
Fixes https://github.com/rui314/mold/issues/1496
2025-08-09 22:48:53 +09:00
Rui Ueyama 2783e82436 Choose the last file if we have more than one -l for the same DSO
Fixes https://github.com/rui314/mold/issues/1494

Based on https://github.com/rui314/mold/issues/1494
2025-08-04 20:33:44 +09:00
Rui Ueyama 9f5b7064dc Handle .data.rel.ro.{hot,unlikely} RELRO sections 2025-07-31 10:44:13 +09:00
Rui Ueyama f83747d6f6 Change the filename of Windows release binary 2025-07-31 06:33:29 +09:00
Rui Ueyama 282ca44e90 Bump mold version to 2.40.3 2025-07-30 13:42:59 +09:00
Rui Ueyama 201bc71b42 Revert f0b933820d
Emitting multiple zlib streams in a single .debug_info section broke
a few tools.

Fixes https://github.com/rui314/mold/issues/1491
2025-07-29 14:57:39 +09:00
Rui Ueyama de3a25f3ca [sh4] Use invalid instruction to fill gaps in executable sections
Quote from SH-4 CPU Core Architecture manual:

  The opcode 0xFFFD is guaranteed to be defined in any SH-4
  architecture revision. Other unused opcodes may be treated as
  reserved in any particular SH-4 implementation.

  The only undefined opcode which the architecture guarantees to cause
  a General Illegal Instruction Exception is 0xFFFD.
2025-07-28 10:06:57 +09:00
Rui Ueyama 5aa8809932 Refactor 2025-07-28 10:06:57 +09:00
Rui Ueyama ce04287237 [s390x] Fill gaps in executable sections with invalid insns instead of NOPs 2025-07-28 10:06:57 +09:00
Rui Ueyama 5485fdec1b [PPC64LE] Support R_PPC64_GOT16* relocations
Fixes https://github.com/rui314/mold/issues/1490
2025-07-27 18:33:25 +09:00
Rui Ueyama 69fee4966f Make --sysroot work with --chroot 2025-07-24 11:20:29 +09:00
Mateusz Mikuła afca89faaa Fix typo in test name 2025-07-23 22:07:16 +02:00
Rui Ueyama 1b76a11af2 Upgrade bundled mimalloc to 3.0.8 2025-07-22 14:40:02 +09:00
Rui Ueyama a531ce43b7 Build daily Windows archives 2025-07-19 16:41:13 +09:00
Rui Ueyama 6fb9564b4a Build a Zip file for Windows binary distribution 2025-07-18 09:44:16 +09:00
Rui Ueyama 4e44823cc0 Refactor 2025-07-15 15:33:51 +09:00
Rui Ueyama dd44e4768d Add a comment 2025-07-15 15:29:43 +09:00
Rui Ueyama 00efcf3d36 Refactor 2025-07-15 11:55:52 +09:00
Rui Ueyama 01358f680d Remove an issue template 2025-07-15 11:43:18 +09:00
Rui Ueyama 6e8852ea2a Allow partially-quoted tokens in response files
Fixes https://github.com/rui314/mold/issues/1485
2025-07-15 10:04:16 +09:00
Rui Ueyama 12e701cf32 Fix test
Closes https://github.com/rui314/mold/issues/1483
2025-07-14 21:44:44 +09:00
Rui Ueyama 5a144e161e Merge pull request #1482 from sicherha/fix_mimalloc_on_raspberry_pi
Build bundled mimalloc library without architecture-specific optimisations
2025-07-14 14:19:52 +09:00
Christoph Erhardt 278750593f Build bundled mimalloc library without architecture-specific optimisations
On 64-bit ARM, mimalloc is built with the compiler flag
`-march=armv8.1-a` by default. This flag advises GCC to make use of
ARMv8.1-A-specific instructions such as `ldaddal`. Machines based on the
older ARMv8-A instruction set - for example the Raspberry Pi 4 -
experience 'Illegal instruction (core dumped)' crashes as a result.

To ensure compatibility with machines that do not support ARMv8.1-A,
disable architecture-specific optimisations by setting the CMake option
`MI_NO_OPT_ARCH` to `ON`.

As of mimalloc 2.2.3, 64-bit ARM is the only architecture affected by
the `MI_NO_OPT_ARCH` option.
2025-07-13 18:26:57 +02:00
Rui Ueyama b862729608 Merge pull request #1481 from Ni-g-3l/add-zorin-distrib
Add Zorin Distribution to install deps script
2025-07-12 15:30:22 +09:00
Nig3l 6a842c7182 Add Zorin Distribution to install deps script 2025-07-12 07:14:58 +02:00
Rui Ueyama e61093dfd6 Bump mold version to 2.40.2 2025-07-12 10:36:11 +09:00
Rui Ueyama a01599cb4d Refactor 2025-07-08 09:38:53 +09:00
Rui Ueyama 87e4c7f552 Update toolchains 2025-07-02 13:10:41 +09:00
Rui Ueyama fa74740703 Refactor 2025-06-30 16:10:42 +09:00
Rui Ueyama 121c0ed23d Accept DSO-only linking
Fixes https://github.com/rui314/mold/issues/1478
2025-06-29 09:34:44 +09:00
Rui Ueyama ed7ccccf54 Add comments 2025-06-27 21:54:48 +09:00
Rui Ueyama 23789a7936 Add an overflow check for R_LARCH_32_PCREL 2025-06-27 09:58:38 +09:00
Rui Ueyama df91548a62 Add comments 2025-06-26 22:39:46 +09:00
Rui Ueyama bc7e4af733 Refactor 2025-06-26 10:25:40 +09:00
Rui Ueyama 2f6cf922e9 Add comments 2025-06-25 14:17:14 +09:00
Rui Ueyama 949e72307f Check certificates when downloading source archives
Pointed out at https://github.com/rui314/mold/discussions/1477
2025-06-25 10:35:29 +09:00
Rui Ueyama b3778207fc Refactor 2025-06-24 14:18:24 +09:00
Rui Ueyama 8eafdfbd0a Add a timer 2025-06-24 13:18:47 +09:00
Rui Ueyama 72c0340ca5 Ignore -fuse-ld
GCC passes the flag to the linker for some reason.
2025-06-24 13:07:49 +09:00
Rui Ueyama 934ca5bb5b Refactor 2025-06-24 10:54:05 +09:00
Rui Ueyama c9fa7111a2 Ensure that our Integer types are compiled to single load/store instructions
It turns out that at least clang-18 fails to compile our code to load
a 32-bit native-endian value from memory using shift + bitwise OR into
a single instruction when compiled with `-march=native`. It instead
compiles the code to complicated vector instructions.

It seems that the good old bswap and memcpy are the best option to
prevent the compiler from overthinking.
2025-06-23 15:09:50 +09:00
Rui Ueyama a3682ddbfd Refactor 2025-06-23 11:20:23 +09:00
Rui Ueyama 75772401cc Make loongarch64 build reproducible 2025-06-20 14:06:24 +09:00
Rui Ueyama 36f8ead511 Refactor 2025-06-20 14:06:24 +09:00
Rui Ueyama 30303d716a Fix test 2025-06-19 10:19:46 +09:00
Rui Ueyama 281d78caea Add a test 2025-06-19 09:53:58 +09:00
Rui Ueyama e9afa21adc Merge pull request #1475 from andreas-schwab/main
[RISC-V] Fix range checks for HI20/LO12 reloc addends
2025-06-19 09:18:15 +09:00
Andreas Schwab 52c698d625 [RISC-V] Fix range checks for HI20/LO12 reloc addends
Fixes #1474

Signed-off-by: Andreas Schwab <[email protected]>
2025-06-18 15:46:46 +02:00
Rui Ueyama 7eedb949b7 [RISC-V] Add additional relocation overflow checks
Fixes https://github.com/rui314/mold/issues/1474
2025-06-18 21:00:19 +09:00
Rui Ueyama 85408b4976 Refactor 2025-06-18 16:22:58 +09:00
Rui Ueyama 4cb46531d0 Refactor 2025-06-17 15:31:17 +09:00
Rui Ueyama ea98b0cb71 Refactor 2025-06-17 11:13:50 +09:00
Rui Ueyama 27edd50815 Fix CI 2025-06-16 14:11:09 +09:00
Rui Ueyama 18ecf979b8 Make the size of the thunk entry vary based on the distance to the target 2025-06-16 14:02:58 +09:00
Rui Ueyama 47f6c2839c Long range extension thunk for ARM64 2025-06-14 10:22:29 +09:00
Rui Ueyama da144e9d23 Refactor 2025-06-12 14:34:46 +09:00
Rui Ueyama f556d2b4ae Extend CI timeout 2025-06-11 22:02:41 +09:00
Rui Ueyama f2a62f70ef Set timeout to CI 2025-06-11 21:13:20 +09:00
Rui Ueyama 1956ae6ac8 Close fd when doing LTO
Fixes https://github.com/rui314/mold/issues/1362
2025-06-11 10:21:59 +09:00
Rui Ueyama 96c798d7be Bump mold version to 2.40.1 2025-06-08 10:06:35 +09:00
Rui Ueyama 6214f7be34 Refactor 2025-06-07 14:06:13 +09:00
Rui Ueyama 9b8b666da8 Update mold.1 (automated commit) 2025-06-07 04:03:43 +00:00
Rui Ueyama 6465232f0c Update a man page
Fixes https://github.com/rui314/mold/issues/1471
2025-06-07 13:02:20 +09:00
Rui Ueyama cc32a6d559 Reduce memory usage of Aho-Corasick search 2025-06-07 12:43:54 +09:00
Rui Ueyama 6214e132f8 Add a comment 2025-06-06 15:03:25 +09:00
Rui Ueyama 6ca242b8f0 Refactor 2025-06-06 14:24:13 +09:00
Rui Ueyama 3721c52d34 Refactor 2025-06-06 10:11:28 +09:00
Rui Ueyama 341c88e426 Refactor 2025-06-05 23:01:55 +09:00
Rui Ueyama 8f3f71663e Refactor 2025-06-05 18:54:39 +09:00
Rui Ueyama 1012a4804c Refactor 2025-06-05 18:13:47 +09:00
Rui Ueyama 5962014fd8 Fix CI 2025-06-05 17:40:58 +09:00
Rui Ueyama dac20fa243 Improve multi-glob pattern matcher so that it's linear-time
I asked ChatGPT how to match multiple glob patterns simultaneously with a
given input string, while avoiding the textbook-style NFA-to-DFA conversion.
Then it suggested that I implement a bitvector-based NFA simulation
algorithm that I wasn't aware. I don't think I could have come up with it
myself easily. This is impressive. ChatGPT is so good at programming and
sometimes much better than me! It may not be oto long before AI writes all
the code for me.
2025-06-05 17:14:34 +09:00
Rui Ueyama 8fa6fc86d3 Optimize glob matcher 2025-06-05 10:32:09 +09:00
Rui Ueyama 1c64d40ab8 Do not use recursion for glob pattern matching
Our glob pattern matcher was an exponential-time implementation.
This can easily be improved by avoiding recursion as explained at
https://research.swtch.com/glob.
2025-06-05 09:12:57 +09:00
Rui Ueyama 58cf042386 Refactor 2025-06-04 14:46:21 +09:00
Rui Ueyama d2f8bffdba Rename lib/common.h -> lib/lib.h 2025-06-04 14:23:43 +09:00
Rui Ueyama 60a9e013d3 Move jobs-{unix,win32}.cc from lib/ to src/ 2025-06-04 13:30:40 +09:00
Rui Ueyama a8893494ae Revert "Use std::syncostream"
This reverts commit 2479b81f8a because
it cannot be built with clang-16.
2025-06-04 12:49:52 +09:00
Rui Ueyama 2479b81f8a Use std::syncostream 2025-06-04 10:45:04 +09:00
Rui Ueyama 4dd133a31e Move {mapped-file,signal}-{unix,win32}.cc from lib/ to src/
The distinction between lib and src no longer makes much sense since
we no longer support Mach-O.
2025-06-04 10:44:54 +09:00
Rui Ueyama 02678c8af0 Refactor 2025-06-03 19:41:31 +09:00
Rui Ueyama 29e8b83886 Refactor 2025-06-03 16:56:23 +09:00
Rui Ueyama f0b933820d Emit multiple zlib streams to compressed sections
I learned that tools supporting compressed sections generally support
concatenated zlib streams within a single compressed debug section.
Examples include GNU/LLVM binutils and libbacktrace. So let's emit
compressed data that way, because it's much easier to do.

It is actually desirable to emit multiple small compressed blocks for
extremely large debug sections, because zlib doesn't support compressed
streams larger than 4 GiB on machines where the long type is 32 bits.

Context: https://sourceware.org/pipermail/binutils/2025-June/141514.html
2025-06-03 13:53:13 +09:00
Rui Ueyama 9a2f31164b Refactor 2025-06-03 08:44:29 +09:00
Rui Ueyama 8ef93b14d6 Refactor 2025-06-02 12:03:19 +09:00
Rui Ueyama 8d57a4419d Refactor 2025-06-02 11:35:21 +09:00
Rui Ueyama 0ef77ebd98 Do not use std::vector<u8> to avoid zero-initialization 2025-06-02 09:31:15 +09:00
Rui Ueyama 00d45d317f Merge pull request #1470 from a5ehren/cachyos 2025-06-02 06:57:32 +09:00
Ehren Bendler cdb63bb2a1 Update install-build-deps.sh to know what cachyos is
It's just a popular arch fork, so put it in the arch block
2025-06-01 11:52:56 -04:00
Rui Ueyama bcd77395d4 Use & instead of \0 in sed
\0 is not a portable way to represent the entire match; & is.
2025-06-01 10:52:55 +09:00
Rui Ueyama 4074707891 Fix CI 2025-06-01 10:52:55 +09:00
Rui Ueyama 079f914898 Use all available cores to compress debug sections 2025-06-01 10:03:05 +09:00
Rui Ueyama d59c55959c Avoid zero-initializing a very large vector
Chunk<E>::uncompressed_data can be very large as it contains an entire
output section contents. std::vector<u8>::resize() zero-initializes the
contents. We want to avoid that cost.

On my AMD Threadripper 7980X machine, zero-initializing 1 GiB of
memory takes ~200 ms. I can confirm that this change shaves off ~1.2s
for an executable with 5 GiB of debug info sections in uncompressed
form.
2025-06-01 10:03:05 +09:00
Rui Ueyama 5e19601a17 Fix debug section prefix 2025-05-31 11:32:50 +09:00
Rui Ueyama 3de3dcc001 Remove unused #include 2025-05-31 09:01:12 +09:00
Rui Ueyama 0d8334e5a2 Report an error if .dynsym refers to a section whose index is >65280
Unlike .symtab, .dynsym cannot have an extended symbol table, so a
section index that is larger than 65279 is not representable in an
output.

Fixes https://github.com/rui314/mold/issues/1468
2025-05-30 11:48:19 +09:00
Rui Ueyama a731b4c58c Build distribution binaries using GitHub Actions 2025-05-29 21:01:43 +09:00
Rui Ueyama 971317f3a9 Fix CI 2025-05-29 15:00:25 +09:00
Rui Ueyama 0a55058042 Do not link with the bundled TBB if BLAKE3_USE_TBB is ON
Recent versions of BLAKE3 library can be built with OneTBB for parallelization.
If that is enabled, we don't want to build and link our bundled OneTBB to avoid
conflict.

Fixes https://github.com/rui314/mold/issues/1466
2025-05-29 11:52:28 +09:00
Rui Ueyama 32746ebbb3 Upgrade bundled blake3 to v1.8.2 2025-05-29 11:51:01 +09:00
Rui Ueyama 1f44438b63 Add Manjaro Linux 2025-05-29 11:35:10 +09:00
Rui Ueyama b53197300b Bump mold version to 2.40.0 2025-05-26 11:30:00 +09:00
Rui Ueyama 3e9321b9a0 Fix test flakiness 2025-05-26 11:30:00 +09:00
Rui Ueyama 142eeaba86 Fix CI 2025-05-25 11:42:00 +09:00
Rui Ueyama 15f6b9f966 Refactor 2025-05-24 13:43:17 +09:00
Rui Ueyama a310cd27d4 Refactor 2025-05-23 18:33:04 +09:00
Rui Ueyama 2d178eb1ed Fix ASAN failure on an invalid downcast 2025-05-22 20:35:04 +09:00
Rui Ueyama 0fdffad57f Fix an assertion failure when a word-size weak abs reloc is promoted to dynamic
Fixes https://github.com/rui314/mold/issues/1462
2025-05-22 19:53:19 +09:00
Rui Ueyama d05272c4ee Fix CI 2025-05-22 12:11:22 +09:00
Rui Ueyama 1683826f2f Fix tests
Closes https://github.com/rui314/mold/issues/1461
2025-05-22 10:34:29 +09:00
Rui Ueyama 9ddec785b1 Do not sort debug info sections unless we have a mix of DWARF32 and DWARF64 2025-05-21 21:38:55 +09:00
Rui Ueyama e4f867ad77 Stabilize test 2025-05-21 21:38:55 +09:00
Rui Ueyama b7ce902e52 Add a test 2025-05-20 22:23:10 +09:00
Rui Ueyama 133613a493 Refactor 2025-05-20 21:29:37 +09:00
Rui Ueyama b026d7119b Sort debug sections in a separate debug file 2025-05-20 18:27:56 +09:00
Rui Ueyama fc0dd85d87 Add a comment 2025-05-20 18:01:27 +09:00
Rui Ueyama 6577232458 Rename a variable 2025-05-20 16:22:19 +09:00
Rui Ueyama b504cacf25 Fix CI 2025-05-20 15:45:20 +09:00
Rui Ueyama 159ce3ba67 Place strings referenced by DWARF32 at the beginning of .debug_str 2025-05-20 15:29:50 +09:00
Rui Ueyama 4c2dae0a2d Fix CI 2025-05-20 14:11:42 +09:00
Rui Ueyama 275c53d615 Merge pull request #1459 from el-remph/static-ldso
Fix linked executable segfaulting if -I and -static are both passed
2025-05-20 11:43:36 +09:00
Rui Ueyama fdd959880a Sort not only .debug_info but also other debug info sections
I believe it is unlikely that sections such as .debug_line grow beyond
4 GiB to cause relocation overflows, but doing some extra work is
trivial, so just do it.
2025-05-20 11:35:07 +09:00
Rui Ueyama 26d827d6e7 Read not only one CU but all CUs in .debug_info 2025-05-19 23:37:58 +09:00
Rui Ueyama 858fd8c495 Refactor 2025-05-19 23:35:06 +09:00
remph c13ecc99eb Fix linked executable segfaulting if -I and -static are both passed 2025-05-19 15:27:06 +01:00
Rui Ueyama 122268d147 Add a test 2025-05-19 16:22:07 +09:00
Rui Ueyama b990467c16 Upgrade bundled OneTBB to 2022.1.0
Specifically, we need https://github.com/uxlfoundation/oneTBB/commit/3c85ad77664f6ec1d50d9aebbd2d2f21adae0e21
to fix our ASAN bot.
2025-05-19 14:58:05 +09:00
Rui Ueyama 3d866bea61 Fix CI 2025-05-19 14:26:09 +09:00
Rui Ueyama 19a1bc651f Layout DWARF32 before DWARF64 to mitigate relocation overflow issues
Previously, if the combined size of DWARF32 and DWARF64 debug
information exceeded 2^32 bytes, a relocation overflow could occur.

Since not all static libraries are built with -gdwarf64, this overflow
issue prevented some very large programs from using static libraries
built without -gdwarf64. Recompiling everything with -gdwarf64 is the
"right" way to fix it, but that's not always feasible for various
reasons.

This commit mitigates the issue.

By sorting the input .debug_info sections so that DWARF32 debug info
comes before DWARF64, a relocation overflow can occur only if the size
of DWARF32 debug info alone exceeds 2^32 bytes.
2025-05-19 14:12:39 +09:00
Rui Ueyama 18c813a21c Make all tests pass on FreeBSD/ARM64 2025-05-16 18:06:21 +09:00
Rui Ueyama af36625a02 Fix bug where --as-needed did not work if --allow-shlib-undefined is given 2025-05-16 15:40:21 +09:00
Rui Ueyama 53c1758503 Align TLS segment correctly
This effectively reverts 5249edc57f.
2025-05-16 11:42:26 +09:00
Rui Ueyama 67ceb41b79 Refactor 2025-05-15 16:08:50 +09:00
Rui Ueyama 7d99824c3d Bump mold version to 2.39.1 2025-05-12 11:26:07 +09:00
Rui Ueyama 344dcff8e8 Change an error mesasge 2025-05-12 10:51:51 +09:00
Rui Ueyama d0dffd5985 Attempt to fix a use-after-free
https://github.com/rui314/mold/issues/1456
2025-05-11 11:12:57 +09:00
Rui Ueyama 251041e371 Fix comment 2025-05-06 11:13:42 +09:00
Rui Ueyama 4dd872ee8d Do not make a call to an unresolved weak symbol into an infinite loop
We don't do that other than RISC-V, so let's stop doing it entirely.
2025-05-06 10:41:39 +09:00
Rui Ueyama bb1b9dda64 Refactor 2025-05-06 10:26:15 +09:00
Rui Ueyama 78ec306cf4 Merge pull request #1453 from ziyao233/riscv/relax-undef-weak-sym
shrink-sections: Relax weak undef symbols if promotion is possible
2025-05-06 08:41:41 +09:00
Yao Zi 163c62f491 shrink-sections: Relax weak undef symbols if promotion is possible
A call to weak undefined symbol could be relaxed as long as the symbol
could be promoted to a dynamic symbol, where the relaxation is done
against the known PLT address and the address of the symbol itself
doesn't really matter.

Signed-off-by: Yao Zi <[email protected]>
2025-05-05 07:01:21 -04:00
Rui Ueyama 026fb54b9d Bump mold version to 2.39.0 2025-05-05 12:27:19 +09:00
Rui Ueyama cd162ba9f6 Remove an unnecessary constraint 2025-05-05 11:47:36 +09:00
Rui Ueyama e08e7f6aa4 Do not turn a call to a resolved weak undefined symbol into an infinite loop
Fixes https://github.com/rui314/mold/issues/1451
2025-05-05 11:35:03 +09:00
Rui Ueyama 6be2e676d2 Refactor 2025-05-04 20:07:24 +09:00
Rui Ueyama b6565025a3 Merge pull request #1450 from pcc/cargo
Update instructions to enable mold in Cargo on all architectures
2025-05-04 18:01:12 +09:00
Peter Collingbourne 118dc5de8e Update instructions to enable mold in Cargo on all architectures
Make it so that the Cargo instructions enable mold for Linux on all
architectures not just x86_64.
2025-05-04 01:21:20 -07:00
Rui Ueyama f3475d9f09 Refactor 2025-05-04 16:04:33 +09:00
Rui Ueyama 299904e56f Give the lowest priority to gentoo-test script 2025-05-04 14:21:55 +09:00
Rui Ueyama fded2d8a77 Remove redundant thunks after computing section addresses
Previously, we might mistakenly remove necessary symbols from thunks,
causing the linker to crash.

This fixes https://github.com/rui314/mold/issues/1449
2025-05-04 13:36:23 +09:00
Rui Ueyama f0e726024e Remove an unused function 2025-05-03 16:27:00 +09:00
Rui Ueyama 3d29f1678d Refactor 2025-05-03 11:44:50 +09:00
Rui Ueyama 559f23ff81 Give the lowest priority to gentoo-test script 2025-05-03 11:43:53 +09:00
Rui Ueyama 3274bcbdf0 Fix spurious --no-allow-shlib-undefined error
Fixes https://github.com/rui314/mold/issues/1446
2025-05-02 21:34:01 +09:00
Rui Ueyama 83dd353979 Prevent oscillation when computing the size of the phdr
Fixes https://github.com/rui314/mold/issues/1447
2025-05-01 09:34:27 +09:00
Rui Ueyama 8bae43b842 Make --default-symver work with version scripts
Previously, symbols specified with the `global:` label were unversioned
even if --default-symver was given. Now, such symbols are versioned with
the output file's soname.

Fixes https://github.com/rui314/mold/issues/1448
2025-04-30 20:45:09 +09:00
Rui Ueyama 4038cfd64d Refactor 2025-04-30 16:20:36 +09:00
Rui Ueyama 41079c64f5 [arm32be] Report error if --be8 is missing 2025-04-30 08:24:49 +09:00
Rui Ueyama 02e410133b Refactor 2025-04-29 14:34:25 +09:00
Rui Ueyama e70236a45a Refactor 2025-04-29 13:03:28 +09:00
Rui Ueyama 285c5bb509 Refactor 2025-04-29 09:12:43 +09:00
Rui Ueyama 975da12333 Fix CI 2025-04-28 22:12:05 +09:00
Rui Ueyama 157b16adb5 Support big-endian ARM32 target 2025-04-28 21:09:59 +09:00
Rui Ueyama 564757a0f5 [RV32] Do not report error on R_RISCV_32 2025-04-28 21:09:59 +09:00
Rui Ueyama 216b4a2325 Refactor 2025-04-28 15:01:56 +09:00
Rui Ueyama 954107e29c Bump mold version to 2.38.1 2025-04-28 11:10:38 +09:00
Rui Ueyama 21e20e01da Make --no-allow-shlib-undefined not report an error on circular dependencies
Fixes https://github.com/rui314/mold/issues/1445
2025-04-28 10:50:48 +09:00
Rui Ueyama 161cb6c99c Fix CI 2025-04-26 11:40:07 +09:00
Rui Ueyama 21cfee556a Transitively test library dependencies for --no-allow-shlib-undefined
This change is needed to build sci-libs/spqr-2.0.9-r4::gentoo
2025-04-26 11:30:38 +09:00
Rui Ueyama 06203c4a72 Make dist.sh reproducible for ARM32 on ARM64 2025-04-23 15:36:06 +09:00
Rui Ueyama 232067634f Simplify 2025-04-23 13:25:15 +09:00
Rui Ueyama 3f9aab7066 Build with -Wl,--gdb-index only for Debug build 2025-04-23 12:42:27 +09:00
Rui Ueyama 8b88595f26 Make dist.sh's output deterministic
It looks like `podman build` command does not push the newly created
image with the `latest` tag to ghcr.io, so the following `podman run`
pulled an older image. That caused an issue that the scirpt created
a different tarball than the one on my local machine.

I don't know what is the "proper" fix, but it looks like adding
`--pull=never` to `podman run` forbids it from pulling an older image
from ghcr.io, solving the issue.
2025-04-23 12:31:28 +09:00
Rui Ueyama fa09c562c9 Simplify 2025-04-23 11:53:24 +09:00
Rui Ueyama 9457d993c4 Upgrade Clang 2025-04-22 18:10:19 +09:00
Rui Ueyama 43810dfecd Make --no-allow-shlib-undefined on by default for executables
Fixes https://github.com/rui314/mold/issues/1443
2025-04-22 13:20:13 +09:00
Rui Ueyama 2d6061a233 Change how we resolve versioned symbols with default version
If a versioned symbol is a default one, it can be referred to with two
names, e.g. `foo` and `foo@VERSION`. Previously, we forward
`foo@VERSION` to `foo` if it is undefined, but it didn't always work
correctly. Now, we resolve both `foo` and `foo@VERSION` as usual and
then forward `foo@VERSION` to `foo` afterwards.

Fixes https://github.com/rui314/mold/issues/1169
2025-04-22 12:21:50 +09:00
Rui Ueyama 3d6aad65b0 Simplify 2025-04-21 13:55:31 +09:00
Rui Ueyama f7b1920f84 Make dist.sh reproducible again
mimalloc 2.2.2 started using __DATE__ and __TIME__ macros which make
the output non-reproducible. We can set the SOURCE_DATE_EPOCH environment
variable to fix the "current" timestamp.
2025-04-16 16:17:28 +09:00
Rui Ueyama b42f978212 Bump mold version to 2.38.0 2025-04-16 11:57:48 +09:00
1947 changed files with 110974 additions and 49612 deletions

No files matched your search

-10
View File
@@ -1,10 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
Delete this message and describe the bug here. Note that we only fix bugs in the most recent release of mold, so please make sure that you are using the most recent version.
+47 -5
View File
@@ -3,12 +3,27 @@ name: Build all tarballs
on:
schedule:
- cron: '0 0 * * *'
release:
types: [ published ]
workflow_dispatch:
jobs:
build-tarballs:
check-changes:
runs-on: ubuntu-24.04
outputs:
has_changes: ${{ github.event_name == 'workflow_dispatch' || steps.check.outputs.has_changes }}
steps:
- name: Check for new commits since last successful run
id: check
env:
GH_TOKEN: ${{ github.token }}
run: |
last=$(gh run list -R ${{ github.repository }} -w build-all.yml -b main -s success -L1 --json createdAt -q '.[0].createdAt')
if [ -z "$last" ] || [ "$last" = "null" ] || [ "$(gh api repos/${{ github.repository }}/commits?sha=main\&since=$last\&per_page=1 | jq length)" -gt 0 ]; then
echo "has_changes=true" >> "$GITHUB_OUTPUT"
fi
build-linux:
needs: check-changes
if: needs.check-changes.outputs.has_changes == 'true'
strategy:
matrix:
include:
@@ -28,7 +43,7 @@ jobs:
steps:
- name: Checkout Repository
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Install Podman
run: sudo apt-get update && sudo apt-get install -y podman qemu-user-static
@@ -44,8 +59,35 @@ jobs:
run: ./dist.sh ${{ matrix.target }}
- name: Upload artifact
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: ${{ matrix.target }}
path: dist/mold-*.tar.gz
compression-level: 0
build-windows:
needs: check-changes
if: needs.check-changes.outputs.has_changes == 'true'
runs-on: windows-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v6
- name: Build and Archive
shell: pwsh
run: |
mkdir build
cd build
cmake -T clangcl ..
cmake --build . --config Release -j $Env:NUMBER_OF_PROCESSORS
cmake --install . --config Release --prefix ../mold-install
cd ..
New-Item -ItemType Directory -Force dist | Out-Null
$version = $Env:TAG -replace '^v', ''
Compress-Archive -Path mold-install\* -DestinationPath dist\mold-$version-windows-x86_64.zip
- name: Upload artifact
uses: actions/upload-artifact@v7
with:
name: win-x86_64
path: dist/mold-*.*
+2 -2
View File
@@ -21,7 +21,7 @@ jobs:
steps:
- name: Checkout Repository
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Install Podman
run: sudo apt-get update && sudo apt-get install -y podman qemu-user-static
@@ -37,7 +37,7 @@ jobs:
run: ./dist.sh ${{ matrix.target }}
- name: Upload artifact
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: ${{ matrix.target }}
path: dist/mold-*.tar.gz
+36 -13
View File
@@ -5,7 +5,7 @@ on:
env:
UBSAN_OPTIONS: print_stacktrace=1:halt_on_error=1
jobs:
build-sanitizers:
build-asan-tsan:
strategy:
matrix:
target:
@@ -13,8 +13,9 @@ jobs:
- '-DMOLD_USE_ASAN=On'
- '-DMOLD_USE_TSAN=On'
runs-on: ubuntu-24.04
timeout-minutes: 60
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- uses: rui314/setup-mold@staging
- run: sudo ./install-build-deps.sh
- name: build
@@ -26,7 +27,7 @@ jobs:
cmake --build . -j$(nproc)
- run: cd build; ctest --output-on-failure -j$(nproc)
- name: archive test results
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
if: failure()
with:
name: test-results-clang
@@ -34,10 +35,23 @@ jobs:
build
!build/CMakeFiles
build-msan:
runs-on: ubuntu-24.04
timeout-minutes: 60
steps:
- uses: actions/checkout@v6
- uses: redhat-actions/podman-login@v1
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- run: .github/workflows/run-msan.sh
build-multi-archs:
runs-on: ubuntu-24.04
timeout-minutes: 60
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: install-build-deps
run: |
sudo ./install-build-deps.sh
@@ -51,7 +65,7 @@ jobs:
cmake --build . -j$(nproc)
- run: cd build; ctest --output-on-failure -j$(nproc)
- name: archive test results
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
if: failure()
with:
name: test-results-multi-archs
@@ -65,16 +79,17 @@ jobs:
distro:
- alpine
- archlinux
- debian:11 # GCC 10 and CMake 3.18 - the minimum supported by mold
- debian:12
- fedora
- gentoo/stage3
- opensuse/tumbleweed
- ubuntu:22.04
- ubuntu:25.04
runs-on: ubuntu-latest
timeout-minutes: 60
container: ${{ matrix.distro }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- run: ./install-build-deps.sh
- name: build
run: |
@@ -83,19 +98,24 @@ jobs:
cmake ..
cmake --build . -j$(nproc)
- run: cd build; ctest --output-on-failure -j$(nproc)
- name: sanitize distro name
if: failure()
id: sanitize
run: echo "name=$(echo '${{ matrix.distro }}' | tr '/:' '--')" >> "$GITHUB_OUTPUT"
- name: archive test results
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
if: failure()
with:
name: test-results-${{ matrix.distro }}
name: test-results-${{ steps.sanitize.outputs.name }}
path: |
build
!build/CMakeFiles
build-macos:
runs-on: macos-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: build
run: |
mkdir build
@@ -105,8 +125,9 @@ jobs:
build-windows:
runs-on: windows-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: build
run: |
mkdir build
@@ -116,8 +137,9 @@ jobs:
build-msys:
runs-on: windows-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Setup MSYS2
uses: msys2/setup-msys2@v2
with:
@@ -134,8 +156,9 @@ jobs:
build-freebsd:
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Build and test
uses: vmactions/freebsd-vm@v1
with:
+24 -10
View File
@@ -5,7 +5,7 @@ apt-get install -y wget xz-utils
# Install a 32-bit RISC-V toolchain
mkdir /rv32
wget -O- -q https://github.com/riscv-collab/riscv-gnu-toolchain/releases/download/2025.01.20/riscv32-glibc-ubuntu-22.04-gcc-nightly-2025.01.20-nightly.tar.xz | tar -C /rv32 --strip-components=1 --xz -xf -
wget -O- --progress=dot:mega https://github.com/riscv-collab/riscv-gnu-toolchain/releases/download/2025.06.13/riscv32-glibc-ubuntu-24.04-gcc-nightly-2025.06.13-nightly.tar.xz | tar -C /rv32 --strip-components=1 --xz -xf -
ln -sf /rv32/sysroot /usr/riscv32-linux-gnu
echo '/rv32/bin/riscv32-unknown-linux-gnu-gcc -L/usr/riscv32-linux-gnu "$@"' > /usr/bin/riscv32-linux-gnu-gcc
@@ -18,7 +18,7 @@ done
# Install a LoongArch toolchain
mkdir /larch
wget -O- -q https://github.com/loongson/build-tools/releases/download/2025.02.21/x86_64-cross-tools-loongarch64-binutils_2.44-gcc_14.2.0-glibc_2.41.tar.xz | tar -C /larch --strip-components=1 --xz -xf -
wget -O- --progress=dot:mega https://github.com/loongson/build-tools/releases/download/2025.06.06/x86_64-cross-tools-loongarch64-binutils_2.44-gcc_15.1.0-glibc_2.41.tar.xz | tar -C /larch --strip-components=1 --xz -xf -
cp -r /larch/loongarch64-unknown-linux-gnu/lib/* /larch/target/lib64
ln -sf /larch/target /usr/loongarch64-linux-gnu
@@ -27,25 +27,39 @@ for i in gcc g++ objdump objcopy strip; do
ln -sf /larch/bin/loongarch64-unknown-linux-gnu-$i /usr/bin/loongarch64-linux-gnu-$i
done
wget -O /usr/local/bin/qemu-loongarch64 -q https://github.com/loongson/build-tools/releases/download/2024.11.01/qemu-loongarch64
wget -O /usr/local/bin/qemu-loongarch64 --progress=dot:mega https://github.com/loongson/build-tools/releases/download/2025.06.06/qemu-loongarch64
chmod 755 /usr/local/bin/qemu-loongarch64
# Install ARM32 big-endian toolchain
mkdir /armeb
wget -O- --progress=dot:mega https://toolchains.bootlin.com/downloads/releases/toolchains/armebv7-eabihf/tarballs/armebv7-eabihf--glibc--stable-2024.05-1.tar.xz | tar -C /armeb --strip-components=1 --xz -xf -
ln -sf /armeb/armeb-buildroot-linux-gnueabihf/sysroot /usr/armeb-linux-gnueabihf
echo '/armeb/bin/armeb-linux-gcc -L/usr/armeb-linux-gnu "$@"' > /usr/bin/armeb-linux-gnueabihf-gcc
echo '/armeb/bin/armeb-linux-g++ -L/usr/armeb-linux-gnu "$@"' > /usr/bin/armeb-linux-gnueabihf-g++
chmod 755 /usr/bin/armeb-linux-gnueabihf-{gcc,g++}
for i in objdump objcopy strip; do
ln -sf /armeb/bin/armeb-linux-$i /usr/bin/armeb-linux-gnueabihf-$i
done
# Install ARM64 big-endian toolchain
mkdir /aarch64be
wget -O- -q https://sources.buildroot.net/toolchain-external-arm-aarch64-be/arm-gnu-toolchain-13.3.rel1-x86_64-aarch64_be-none-linux-gnu.tar.xz | tar -C /aarch64be --strip-components=1 --xz -xf -
wget -O- --progress=dot:mega https://toolchains.bootlin.com/downloads/releases/toolchains/aarch64be/tarballs/aarch64be--glibc--stable-2024.05-1.tar.xz | tar -C /aarch64be --strip-components=1 --xz -xf -
ln -sf /aarch64be/aarch64_be-none-linux-gnu/libc /usr/aarch64_be-linux-gnu
echo '/aarch64be/bin/aarch64_be-none-linux-gnu-gcc -L/usr/aarch64_be-linux-gnu "$@"' > /usr/bin/aarch64_be-linux-gnu-gcc
echo '/aarch64be/bin/aarch64_be-none-linux-gnu-g++ -L/usr/aarch64_be-linux-gnu "$@"' > /usr/bin/aarch64_be-linux-gnu-g++
ln -sf /aarch64be/aarch64_be-buildroot-linux-gnu/sysroot /usr/aarch64_be-linux-gnu
echo '/aarch64be/bin/aarch64_be-linux-gcc -L/usr/aarch64_be-linux-gnu "$@"' > /usr/bin/aarch64_be-linux-gnu-gcc
echo '/aarch64be/bin/aarch64_be-linux-g++ -L/usr/aarch64_be-linux-gnu "$@"' > /usr/bin/aarch64_be-linux-gnu-g++
chmod 755 /usr/bin/aarch64_be-linux-gnu-{gcc,g++}
for i in objdump objcopy strip; do
ln -sf /aarch64be/bin/aarch64_be-none-linux-gnu-$i /usr/bin/aarch64_be-linux-gnu-$i
ln -sf /aarch64be/bin/aarch64_be-linux-$i /usr/bin/aarch64_be-linux-gnu-$i
done
# Install SH4 big-endian toolchain
mkdir /sh4aeb
wget -O- -q https://toolchains.bootlin.com/downloads/releases/toolchains/sh-sh4aeb/tarballs/sh-sh4aeb--glibc--stable-2024.05-1.tar.xz | tar -C /sh4aeb --strip-components=1 --xz -xf -
wget -O- --progress=dot:mega https://toolchains.bootlin.com/downloads/releases/toolchains/sh-sh4aeb/tarballs/sh-sh4aeb--glibc--stable-2024.05-1.tar.xz | tar -C /sh4aeb --strip-components=1 --xz -xf -
ln -sf /sh4aeb/sh4aeb-buildroot-linux-gnu/sysroot /usr/sh4aeb-linux-gnu
echo '/sh4aeb/bin/sh4aeb-linux-gcc -L/usr/sh4aeb-linux-gnu "$@"' > /usr/bin/sh4aeb-linux-gnu-gcc
echo '/sh4aeb/bin/sh4aeb-linux-g++ -L/usr/sh4aeb-linux-gnu "$@"' > /usr/bin/sh4aeb-linux-gnu-g++
@@ -57,5 +71,5 @@ done
# Install Intel SDE CPU emulator for CET-related tests
mkdir /sde
wget -O- -q https://downloadmirror.intel.com/843185/sde-external-9.48.0-2024-11-25-lin.tar.xz | tar -C /sde --strip-components=1 --xz -xf -
wget -O- --progress=dot:mega https://downloadmirror.intel.com/850782/sde-external-9.53.0-2025-03-16-lin.tar.xz | tar -C /sde --strip-components=1 --xz -xf -
ln -s /sde/sde64 /usr/bin
+104
View File
@@ -0,0 +1,104 @@
name: Build & attach tarballs on tag push
on:
push:
tags: ['v[0-9]*']
permissions:
contents: write
packages: write
id-token: write
env:
TAG: ${{ github.ref_name }}
jobs:
build-linux:
strategy:
matrix:
include:
- { target: x86_64, os: ubuntu-24.04 }
- { target: aarch64, os: ubuntu-24.04-arm }
- { target: arm, os: ubuntu-24.04-arm }
- { target: riscv64, os: ubuntu-24.04 }
- { target: ppc64le, os: ubuntu-24.04 }
- { target: s390x, os: ubuntu-24.04 }
- { target: loongarch64, os: ubuntu-24.04 }
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v6
- name: Install Podman
run: sudo apt-get update && sudo apt-get install -y podman qemu-user-static
- name: Login to GitHub Container Registry
uses: redhat-actions/podman-login@v1
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- run: ./dist.sh ${{ matrix.target }}
- uses: actions/upload-artifact@v7
with:
name: ${{ matrix.target }}
path: dist/mold-*.tar.gz
build-windows:
runs-on: windows-latest
steps:
- uses: actions/checkout@v6
- name: Build and Archive
shell: pwsh
run: |
mkdir build
cd build
cmake -T clangcl ..
cmake --build . --config Release -j $Env:NUMBER_OF_PROCESSORS
cmake --install . --config Release --prefix ../mold-install
cd ..
New-Item -ItemType Directory -Force dist | Out-Null
$version = $Env:TAG -replace '^v', ''
Compress-Archive -Path mold-install\* -DestinationPath dist\mold-$version-x86_64-windows.zip
- uses: actions/upload-artifact@v7
with:
name: win-x86_64
path: dist/mold-*.*
publish:
needs: [build-linux, build-windows]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/download-artifact@v8
with:
path: dist
pattern: '*'
merge-multiple: true
- name: Ensure release exists (create if missing)
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -e
if gh release view "$TAG" >& /dev/null; then
echo "Existing release found for $TAG"
else
echo "Creating draft release for $TAG"
gh release create "$TAG" --draft \
--title "$TAG" \
--notes "Automated draft – fill the changelog later."
fi
- name: Upload tarballs
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release upload "$TAG" dist/* --clobber
gh release view "$TAG" --json assets --jq '.assets[].name'
+37
View File
@@ -0,0 +1,37 @@
#!/bin/bash
set -e -x
cd "$(dirname $0)"/../..
if [ "$GITHUB_REPOSITORY" = '' ]; then
image=mold-msan
image_build="podman build -t $image -"
else
# If this script is running on GitHub Actions, we want to cache
# the created container image in GitHub's container repostiory.
image=ghcr.io/$GITHUB_REPOSITORY/mold-msan
image_build="podman build -t $image --output=type=registry --layers --cache-to $image --cache-from $image -"
fi
cat <<EOF | $image_build
FROM mirror.gcr.io/library/ubuntu:24.04
RUN apt-get update && \
apt-get install -y --no-install-recommends build-essential gcc g++ wget ca-certificates make cmake ninja-build python3 clang-20 llvm-20-dev libclang-20-dev libclang-rt-20-dev libtbb-dev mold && \
rm -rf /var/lib/apt/lists
RUN mkdir /llvm && \
cd /llvm && \
wget -O- --progress=dot:mega https://github.com/llvm/llvm-project/archive/refs/tags/llvmorg-21.1.1.tar.gz | tar xzf - --strip-components=1 && \
cmake -S ./runtimes -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_RUNTIMES='libcxx;libcxxabi;libunwind' -DCMAKE_C_COMPILER=clang-20 -DCMAKE_CXX_COMPILER=clang++-20 -DLLVM_USE_SANITIZER=MemoryWithOrigins && \
cmake --build build -j\$(nproc) && \
cmake --install build --prefix /msan && \
rm -rf /llvm
EOF
# Build mold and run its tests
podman run -it --rm --pull=never -v "$(pwd):/mold:ro" --security-opt seccomp=unconfined $image bash -c "
set -e
mkdir /build
cd /build
cmake -GNinja -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_COMPILER=clang-20 -DCMAKE_CXX_COMPILER=clang++-20 -DMOLD_USE_MSAN=ON -DMOLD_STDLIB_PREFIX=/msan -DMOLD_USE_MIMALLOC=0 -DMOLD_USE_SYSTEM_TBB=1 -DMOLD_USE_MOLD=1 /mold
cmake --build . -j\$(nproc)
setarch -R ctest --output-on-failure -j\$(nproc)
"
+1 -1
View File
@@ -14,7 +14,7 @@ jobs:
steps:
- name: Check out repository
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Install ronn
run: sudo apt-get update && sudo apt-get install -y ronn
+101 -54
View File
@@ -44,7 +44,7 @@
# features and behave exactly the same.
cmake_minimum_required(VERSION 3.14)
project(mold VERSION 2.37.1)
project(mold VERSION 2.42.1)
include(CMakeDependentOption)
include(CheckSymbolExists)
@@ -62,7 +62,11 @@ endif()
# Build mold itself using mold if -DMOLD_USE_MOLD=ON
option(MOLD_USE_MOLD "Use mold to build mold" OFF)
if(MOLD_USE_MOLD)
target_link_options(mold PRIVATE -fuse-ld=mold -Wl,--gdb-index)
target_link_options(mold PRIVATE -fuse-ld=mold)
if(CMAKE_BUILD_TYPE MATCHES "Deb")
target_link_options(mold PRIVATE -Wl,--gdb-index)
endif()
if(CMAKE_BUILD_TYPE MATCHES "^Rel")
target_link_options(mold PRIVATE -Wl,--gc-sections,--icf=safe)
@@ -113,6 +117,22 @@ if(MOLD_USE_TSAN)
target_link_options(mold PRIVATE -fsanitize=thread)
endif()
# Enable MemorySanitizer if -DMOLD_USE_MSAN=ON
option(MOLD_USE_MSAN "Build mold with MemorySanitizer" OFF)
if(MOLD_USE_MSAN)
set(MOLD_STDLIB_PREFIX /usr CACHE FILEPATH "prefix of instrumented stdlib")
target_compile_options(mold PRIVATE
-g -Og -fsanitize=memory -fsanitize-memory-track-origins
-nostdinc++ -isystem ${MOLD_STDLIB_PREFIX}/include/c++/v1
-DENABLE_MSAN_UNPOISON
)
target_link_options(mold PRIVATE
-fsanitize=memory
-nostdlib++ -L ${MOLD_STDLIB_PREFIX}/lib -lc++
-Wl,-rpath,${MOLD_STDLIB_PREFIX}/lib
)
endif()
# Statically-link libstdc++ if -DMOLD_MOSTLY_STATIC=ON.
#
# This option is intended to be used by `./dist.sh` script to create a
@@ -129,10 +149,9 @@ find_package(ZLIB QUIET)
if(ZLIB_FOUND AND NOT MOLD_MOSTLY_STATIC)
target_link_libraries(mold PRIVATE ZLIB::ZLIB)
else()
set(ZLIB_BUILD_EXAMPLES OFF CACHE INTERNAL "")
set(ZLIB_BUILD_SHARED OFF CACHE INTERNAL "")
set(ZLIB_BUILD_TESTING OFF CACHE INTERNAL "")
add_subdirectory(third-party/zlib EXCLUDE_FROM_ALL)
target_include_directories(zlibstatic INTERFACE third-party/zlib
$<TARGET_PROPERTY:zlibstatic,BINARY_DIR>)
target_link_libraries(mold PRIVATE zlibstatic)
endif()
@@ -160,13 +179,13 @@ check_include_file(zstd.h HAVE_ZSTD_H)
if(HAVE_ZSTD_H AND NOT MOLD_MOSTLY_STATIC)
target_link_libraries(mold PRIVATE zstd)
else()
set(ZSTD_BUILD_PROGRAMS OFF)
set(ZSTD_BUILD_CONTRIB OFF)
set(ZSTD_BUILD_TESTS OFF)
set(ZSTD_MULTITHREAD_SUPPORT OFF)
set(ZSTD_BUILD_SHARED OFF)
set(ZSTD_BUILD_STATIC ON)
add_subdirectory(third-party/zstd/build/cmake EXCLUDE_FROM_ALL)
target_compile_definitions(libzstd_static PRIVATE
ZSTD_BUILD_STATIC=1
ZSTD_BUILD_SHARED=0
ZSTD_BUILD_PROGRAMS=0
ZSTD_MULTITHREAD_SUPPORT=0
ZSTD_BUILD_TESTS=0)
target_include_directories(mold PUBLIC third-party/zstd/lib)
target_link_libraries(mold PRIVATE libzstd_static)
endif()
@@ -189,14 +208,19 @@ cmake_dependent_option(
# By default, we build a bundled mimalloc and statically-link it to
# mold. If you want to dynamically link to the system's
# libmimalloc.so, pass -DMOLD_USE_SYSTEM_MIMALLOC=ON.
#
# We require mimalloc v3 or newer. mimalloc v2 doesn't use transparent
# huge pages for the allocation path that serves mold's largest data
# structures, which makes linking large programs significantly slower.
if(MOLD_USE_MIMALLOC)
if(MOLD_USE_SYSTEM_MIMALLOC)
find_package(mimalloc REQUIRED)
find_package(mimalloc 3 REQUIRED)
target_link_libraries(mold PRIVATE mimalloc)
else()
function(mold_add_mimalloc)
set(MI_BUILD_STATIC ON CACHE INTERNAL "")
set(MI_BUILD_TESTS OFF CACHE INTERNAL "")
set(MI_NO_OPT_ARCH ON CACHE INTERNAL "")
add_subdirectory(third-party/mimalloc EXCLUDE_FROM_ALL)
target_compile_definitions(mimalloc-static PRIVATE MI_USE_ENVIRON=0)
target_link_libraries(mold PRIVATE mimalloc-static)
@@ -213,7 +237,7 @@ endif()
# to mold. If you want to link to the system's libtbb2.so, pass
# -DMOLD_USE_SYSTEM_TBB=ON.
option(MOLD_USE_SYSTEM_TBB "Use system or vendored TBB" OFF)
if(MOLD_USE_SYSTEM_TBB)
if(MOLD_USE_SYSTEM_TBB OR BLAKE3_USE_TBB)
find_package(TBB REQUIRED)
target_link_libraries(mold PRIVATE TBB::tbb)
else()
@@ -285,22 +309,25 @@ endif()
check_symbol_exists(madvise sys/mman.h HAVE_MADVISE)
check_symbol_exists(uname sys/utsname.h HAVE_UNAME)
# glibc declares fallocate only under _GNU_SOURCE. C++ compilers
# predefine it on Linux, but this check runs the C compiler, which
# does not.
set(CMAKE_REQUIRED_DEFINITIONS -D_GNU_SOURCE)
check_symbol_exists(fallocate fcntl.h HAVE_FALLOCATE)
unset(CMAKE_REQUIRED_DEFINITIONS)
# Create a .cc file containing the current git hash for `mold --version`.
add_custom_target(git_hash
COMMAND ${CMAKE_COMMAND}
-DSOURCE_DIR=${CMAKE_SOURCE_DIR}
-DOUTPUT_FILE=${CMAKE_BINARY_DIR}/git-hash.cc
-DOUTPUT_FILE=${CMAKE_BINARY_DIR}/mold-git-hash.h
-P ${CMAKE_SOURCE_DIR}/lib/update-git-hash.cmake
DEPENDS lib/update-git-hash.cmake
BYPRODUCTS git-hash.cc
BYPRODUCTS mold-git-hash.h
VERBATIM)
add_dependencies(mold git_hash)
# Create config.h file
configure_file(lib/config.h.in config.h)
include_directories(${CMAKE_CURRENT_BINARY_DIR})
# Almost all functions are template in mold which take a target type
# (e.g. X86_64) as its type parameter. Since we suport more than 10
# targets, compiling a single source file for all the targets is very
@@ -309,16 +336,38 @@ include_directories(${CMAKE_CURRENT_BINARY_DIR})
# As a workaround, we create a .cc file for each target and spawn many
# compiler instances. This is hacky but greatly reduces compile time
# on a multicore machine.
list(APPEND MOLD_ELF_TARGETS
X86_64 I386 ARM64LE ARM64BE ARM32 RV32LE RV32BE RV64LE RV64BE PPC32
PPC64V1 PPC64V2 S390X SPARC64 M68K SH4LE SH4BE LOONGARCH32 LOONGARCH64)
#
# You can build mold for a specific set of targets by passing, e.g.,
# -DMOLD_TARGETS='X86_64;ARM64LE', though this is strongly discouraged
# for build reproducibility. Use this option only if you build mold
# frequently for your personal use; otherwise, always build mold with all
# targets enabled. We provide this flag in the trust that you will not
# abuse it, so please don’t betray that trust.
set(MOLD_TARGETS
X86_64 I386 ARM64LE ARM64BE ARM32LE ARM32BE RV32LE RV32BE RV64LE
RV64BE PPC32 PPC64V1 PPC64V2 S390X SPARC64 M68K SH4LE SH4BE
LOONGARCH32 LOONGARCH64
CACHE STRING "List of supported targets")
list(APPEND MOLD_ELF_TEMPLATE_FILES
list(GET MOLD_TARGETS 0 MOLD_FIRST_TARGET)
list(APPEND MOLD_TEMPLATE_FILES
src/arch-arm32.cc
src/arch-arm64.cc
src/arch-i386.cc
src/arch-loongarch.cc
src/arch-m68k.cc
src/arch-ppc32.cc
src/arch-ppc64v1.cc
src/arch-ppc64v2.cc
src/arch-riscv.cc
src/arch-s390x.cc
src/arch-sh4.cc
src/arch-sparc64.cc
src/arch-x86-64.cc
src/archive-file.cc
src/cmdline.cc
src/error.cc
src/filetype.cc
src/gc-sections.cc
src/gdb-index.cc
@@ -337,18 +386,18 @@ list(APPEND MOLD_ELF_TEMPLATE_FILES
)
if(WIN32 AND NOT MINGW)
list(APPEND MOLD_ELF_TEMPLATE_FILES src/lto-win32.cc)
list(APPEND MOLD_TEMPLATE_FILES src/lto-win32.cc)
else()
list(APPEND MOLD_ELF_TEMPLATE_FILES src/lto-unix.cc)
list(APPEND MOLD_TEMPLATE_FILES src/lto-unix.cc)
endif()
if(WIN32)
list(APPEND MOLD_ELF_TEMPLATE_FILES
list(APPEND MOLD_TEMPLATE_FILES
src/output-file-win32.cc
src/subprocess-win32.cc
)
else()
list(APPEND MOLD_ELF_TEMPLATE_FILES
list(APPEND MOLD_TEMPLATE_FILES
src/output-file-unix.cc
src/subprocess-unix.cc
)
@@ -365,54 +414,50 @@ function(mold_instantiate_templates SOURCE TARGET)
target_sources(mold PRIVATE ${PATH})
endfunction()
foreach (SOURCE IN LISTS MOLD_ELF_TEMPLATE_FILES)
foreach(TARGET IN LISTS MOLD_ELF_TARGETS)
foreach (SOURCE IN LISTS MOLD_TEMPLATE_FILES)
foreach(TARGET IN LISTS MOLD_TARGETS)
mold_instantiate_templates(${SOURCE} ${TARGET})
set(HAVE_TARGET_${TARGET} 1)
endforeach()
endforeach()
# Add other non-template source files.
target_sources(mold PRIVATE
git-hash.cc
lib/aho-corasick.cc
lib/arena.cc
lib/compress.cc
lib/crc32.cc
lib/demangle.cc
lib/filepath.cc
lib/glob.cc
lib/hyperloglog.cc
lib/mimalloc.cc
lib/multi-glob.cc
lib/perf.cc
lib/random.cc
lib/tar.cc
src/arch-arm32.cc
src/arch-i386.cc
src/arch-m68k.cc
src/arch-ppc32.cc
src/arch-ppc64v1.cc
src/arch-ppc64v2.cc
src/arch-s390x.cc
src/arch-sparc64.cc
src/arch-x86-64.cc
src/config.cc
src/elf.cc
src/entry.cc
third-party/rust-demangle/rust-demangle.c
)
if(WIN32)
target_sources(mold PRIVATE
lib/jobs-win32.cc
lib/mapped-file-win32.cc
lib/signal-win32.cc
src/jobs-win32.cc
src/mapped-file-win32.cc
src/signal-win32.cc
)
else()
target_sources(mold PRIVATE
lib/jobs-unix.cc
lib/mapped-file-unix.cc
lib/signal-unix.cc
src/jobs-unix.cc
src/mapped-file-unix.cc
src/signal-unix.cc
)
endif()
# Create config.h file
configure_file(lib/config.h.in config.h)
include_directories(${CMAKE_CURRENT_BINARY_DIR})
# Test configs
include(CTest)
if(BUILD_TESTING)
@@ -448,10 +493,12 @@ if(NOT CMAKE_SKIP_INSTALL_RULES)
file(CREATE_LINK \${OLD_REL} \$ENV{DESTDIR}\${NEW_ABS} SYMBOLIC)")
endfunction()
mold_install_relative_symlink(${CMAKE_INSTALL_BINDIR}/mold${CMAKE_EXECUTABLE_SUFFIX}
${CMAKE_INSTALL_LIBEXECDIR}/mold/ld${CMAKE_EXECUTABLE_SUFFIX})
mold_install_relative_symlink(${CMAKE_INSTALL_BINDIR}/mold${CMAKE_EXECUTABLE_SUFFIX}
${CMAKE_INSTALL_BINDIR}/ld.mold${CMAKE_EXECUTABLE_SUFFIX})
mold_install_relative_symlink(${CMAKE_INSTALL_MANDIR}/man1/mold.1
${CMAKE_INSTALL_MANDIR}/man1/ld.mold.1)
if(NOT WIN32)
mold_install_relative_symlink(${CMAKE_INSTALL_BINDIR}/mold${CMAKE_EXECUTABLE_SUFFIX}
${CMAKE_INSTALL_LIBEXECDIR}/mold/ld${CMAKE_EXECUTABLE_SUFFIX})
mold_install_relative_symlink(${CMAKE_INSTALL_BINDIR}/mold${CMAKE_EXECUTABLE_SUFFIX}
${CMAKE_INSTALL_BINDIR}/ld.mold${CMAKE_EXECUTABLE_SUFFIX})
mold_install_relative_symlink(${CMAKE_INSTALL_MANDIR}/man1/mold.1
${CMAKE_INSTALL_MANDIR}/man1/ld.mold.1)
endif()
endif()
+128 -56
View File
@@ -1,30 +1,21 @@
# mold: A Modern Linker
mold is a faster drop-in replacement for existing Unix linkers. It is several
times quicker than the LLVM lld linker, the second-fastest open-source linker,
which I initially developed a few years ago. mold aims to enhance developer
productivity by minimizing build time, particularly in rapid
debug-edit-rebuild cycles.
mold is a high-performance drop-in replacement for existing Unix linkers,
designed to speed up builds. In our August 2026 benchmarks, it links 4.9x
faster than [LLVM lld](https://lld.llvm.org) and 1.9x faster than
[wild](https://github.com/wild-linker/wild) at the median; see
[Benchmark](#benchmark) for the full results.
Here is a performance comparison of GNU ld, GNU gold, LLVM lld, and
mold when linking final debuginfo-enabled executables for major large
programs on a simulated 16-core, 32-thread machine.
mold is written by the original developer of LLVM lld, the linker that
Android, Chrome, FreeBSD, PlayStation, Nintendo Switch, and other production
systems are built with. mold started as an effort to build an even faster
linker from scratch, free of the architectural limits its author had run into
while optimizing lld. It has been in production use since 2021, and today it
is the default linker of many large open-source projects and is used
internally by many companies.
![Link speed comparison](docs/chart.svg)
| Program (linker output size) | GNU ld | GNU gold | LLVM lld | mold
|-------------------------------|--------|----------|----------|------
| MySQL 8.3 (0.47 GiB) | 10.84s | 7.47s | 1.64s | 0.46s
| Clang 19 (1.56 GiB) | 42.07s | 33.13s | 5.20s | 1.35s
| Chromium 124 (1.35 GiB) | N/A | 27.40s | 6.10s | 1.52s
mold is so fast that it is only 2x _slower_ than the `cp` command on the same
machine. If you find that mold is not faster than other linkers, feel
free to [file a bug report](https://github.com/rui314/mold/issues).
mold supports x86-64, i386, ARM64, ARM32, 64-bit/32-bit little/big-endian
RISC-V, 32-bit PowerPC, 64-bit big-endian PowerPC ELFv1, 64-bit little-endian
PowerPC ELFv2, s390x, 64-bit/32-bit LoongArch, SPARC64, m68k, and SH-4.
mold supports x86-64, i386, ARM 32/64, RISC-V 32/64, PowerPC 32/64, s390x,
LoongArch 32/64, SPARC64, m68k, and SH-4.
## Why does linking speed matter?
@@ -38,30 +29,124 @@ speed up this process, saving you time and preventing distractions while
waiting for a lengthy build to finish. The difference is most noticeable
during rapid debug-edit-rebuild cycles.
## Benchmark
Here is a performance comparison of lld, wild, and mold when linking nine
large programs on two machines:
- AMD Ryzen Threadripper 7980X (64 cores) running Ubuntu 24.04
- Apple M1 Ultra (16 performance cores and 4 efficiency cores) running Fedora
Asahi Remix 42; the benchmark is restricted to the performance cores
The Threadripper represents many-core workstation and server processors, and
the M1 Ultra represents high-performance desktop processors.
All three linkers were built from source in release configuration as of
2026-08-28 and ran with their default options. Times are wall-clock times as
seen by the caller, the median of three runs after a warm-up. The ARM64 rows
are the ARM64 builds of the same programs. The benchmark suite, including all
linker inputs, is available on [Zenodo](https://zenodo.org/records/21882261).
**AMD Ryzen Threadripper 7980X, debug builds**
| Program (output size) | lld | wild | mold | wild/mold | lld/mold
|-----------------------------------|--------|-------|-------|-----------|---------
| Blender 5.2 (2.46 GiB) | 4.72s | 1.98s | 0.86s | 2.3x | 5.5x
| Chromium 145 (4.51 GiB) | 16.64s | 3.98s | 1.65s | 2.4x | 10.1x
| Chromium 145 ARM64 (4.76 GiB) | 20.91s | N/A | 1.83s | N/A | 11.4x
| Clang 21 (4.19 GiB) | 6.19s | 3.71s | 1.34s | 2.8x | 4.6x
| ClickHouse 26.1 (5.58 GiB) | 6.62s | 4.40s | 0.98s | 4.5x | 6.7x
| Firefox 149 (2.37 GiB) | 5.11s | N/A | 0.78s | N/A | 6.5x
| Firefox 149 ARM64 (2.43 GiB) | 6.40s | N/A | 0.93s | N/A | 6.9x
| Godot 4.6 (1.01 GiB) | 1.77s | 1.08s | 0.46s | 2.3x | 3.8x
| LibreOffice 26.2 (0.98 GiB) | 3.46s | 1.41s | 0.44s | 3.2x | 7.9x
| PyTorch 2.9 (3.51 GiB) | 4.35s | 2.48s | 0.80s | 3.1x | 5.4x
| TensorFlow 2.21 (9.55 GiB) | 50.73s | N/A | 3.15s | N/A | 16.1x
**AMD Ryzen Threadripper 7980X, release builds**
| Program (output size) | lld | wild | mold | wild/mold | lld/mold
|-----------------------------------|-------|-------|-------|-----------|---------
| Blender 5.2 (0.24 GiB) | 0.85s | 0.30s | 0.20s | 1.5x | 4.2x
| Chromium 145 (0.58 GiB) | 6.48s | N/A | 0.64s | N/A | 10.2x
| Chromium 145 ARM64 (0.60 GiB) | 7.91s | N/A | 0.73s | N/A | 10.8x
| Clang 21 (0.21 GiB) | 0.53s | 0.23s | 0.11s | 2.2x | 5.0x
| ClickHouse 26.1 (1.23 GiB) | 3.18s | 2.07s | 0.41s | 5.0x | 7.7x
| Firefox 149 (0.22 GiB) | 1.01s | 0.41s | 0.21s | 2.0x | 4.9x
| Godot 4.6 (0.15 GiB) | 0.44s | 0.21s | 0.08s | 2.7x | 5.8x
| LibreOffice 26.2 (0.19 GiB) | 1.13s | 0.57s | 0.19s | 3.0x | 6.0x
| PyTorch 2.9 (0.31 GiB) | 0.68s | 0.32s | 0.15s | 2.2x | 4.7x
| TensorFlow 2.21 (0.73 GiB) | 9.62s | N/A | 0.70s | N/A | 13.7x
**Apple M1 Ultra, debug builds**
| Program (output size) | lld | wild | mold | wild/mold | lld/mold
|-----------------------------------|--------|-------|-------|-----------|---------
| Blender 5.2 (2.46 GiB) | 3.21s | 1.81s | 1.56s | 1.2x | 2.1x
| Chromium 145 (4.51 GiB) | 9.54s | 3.49s | 2.22s | 1.6x | 4.3x
| Chromium 145 ARM64 (4.76 GiB) | 12.67s | N/A | 2.31s | N/A | 5.5x
| Clang 21 (4.19 GiB) | 4.40s | 2.78s | 2.96s | 0.9x | 1.5x
| ClickHouse 26.1 (5.58 GiB) | 4.92s | 3.50s | 1.71s | 2.1x | 2.9x
| Firefox 149 (2.37 GiB) | 3.21s | N/A | 1.12s | N/A | 2.9x
| Firefox 149 ARM64 (2.43 GiB) | 4.13s | N/A | 1.12s | N/A | 3.7x
| Godot 4.6 (1.01 GiB) | 1.12s | 0.81s | 0.62s | 1.3x | 1.8x
| LibreOffice 26.2 (0.98 GiB) | 2.08s | 0.94s | 0.63s | 1.5x | 3.3x
| PyTorch 2.9 (3.51 GiB) | 3.07s | 2.10s | 1.45s | 1.4x | 2.1x
| TensorFlow 2.21 (9.55 GiB) | 43.68s | N/A | 4.43s | N/A | 9.9x
**Apple M1 Ultra, release builds**
| Program (output size) | lld | wild | mold | wild/mold | lld/mold
|-----------------------------------|-------|-------|-------|-----------|---------
| Blender 5.2 (0.24 GiB) | 0.56s | 0.20s | 0.25s | 0.8x | 2.3x
| Chromium 145 (0.58 GiB) | 4.25s | N/A | 0.78s | N/A | 5.5x
| Chromium 145 ARM64 (0.60 GiB) | 5.17s | N/A | 0.91s | N/A | 5.7x
| Clang 21 (0.21 GiB) | 0.30s | 0.15s | 0.14s | 1.0x | 2.1x
| ClickHouse 26.1 (1.23 GiB) | 1.94s | 0.95s | 0.55s | 1.7x | 3.5x
| Firefox 149 (0.22 GiB) | 0.56s | 0.23s | 0.20s | 1.1x | 2.7x
| Godot 4.6 (0.15 GiB) | 0.26s | 0.11s | 0.11s | 1.0x | 2.3x
| LibreOffice 26.2 (0.19 GiB) | 0.62s | 0.30s | 0.23s | 1.3x | 2.7x
| PyTorch 2.9 (0.31 GiB) | 0.43s | 0.22s | 0.17s | 1.3x | 2.5x
| TensorFlow 2.21 (0.73 GiB) | 8.07s | N/A | 0.68s | N/A | 11.9x
N/A indicates that the linker cannot link that program. wild's Chromium
release links are also marked N/A because wild does not implement `--icf=all`
and links without identical code folding.
## Why is mold so fast?
mold owes its speed to pervasive parallelism and to efficient data structures
and algorithms. For details, read our paper "mold: A Massively Parallel
Linker" (ASPLOS 2027), available at https://arxiv.org/abs/2608.23228.
## Installation
Binary packages for the following systems are currently available:
[![Packaging status](https://repology.org/badge/vertical-allrepos/mold.svg)](https://repology.org/project/mold/versions)
Prebuilt binaries for Linux on x86-64, ARM64, ARM32, RISC-V, PPC64LE, s390x,
and LoongArch are also attached to each
[GitHub release](https://github.com/rui314/mold/releases).
## How to Build
mold is written in C++20, so if you build mold yourself, you will need a
recent version of a C++ compiler and a C++ standard library. We recommend GCC
10.2 or Clang 12.0.0 (or later) and libstdc++ 10 or libc++ 7 (or later).
10.2 or Clang 16.0.0 (or later) and libstdc++ 10 or libc++ 7 (or later).
### Install Dependencies
To install build dependencies, run `./install-build-deps.sh` in this
directory. It will detect your Linux distribution and attempt to install the
necessary packages. You may need to run it as root.
necessary packages.
### Compile mold
```shell
git clone --branch stable https://github.com/rui314/mold.git
cd mold
./install-build-deps.sh
sudo ./install-build-deps.sh
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=c++ -B build
cmake --build build -j$(nproc)
sudo cmake --build build --target install
@@ -120,7 +205,7 @@ accept an absolute path as an argument for `-fuse-ld`.
Create `.cargo/config.toml` in your project directory with the following:
```toml
[target.x86_64-unknown-linux-gnu]
[target.'cfg(target_os = "linux")']
linker = "clang"
rustflags = ["-C", "link-arg=-fuse-ld=/path/to/mold"]
```
@@ -131,7 +216,7 @@ example above, we use `clang` as a linker driver since it always accepts the
may be able to remove the `linker = "clang"` line.
```toml
[target.x86_64-unknown-linux-gnu]
[target.'cfg(target_os = "linux")']
rustflags = ["-C", "link-arg=-fuse-ld=mold"]
```
@@ -219,39 +304,26 @@ is available online. You can read the same manual by running `man mold`.
</details>
## Why is mold so fast?
## Stability
One reason is that it utilizes faster algorithms and more efficient data
structures compared to other linkers. Another reason is that mold is highly
parallelized.
Here is a side-by-side comparison of per-core CPU usage for lld (left) and
mold (right), linking the same program, a Chromium executable.
![CPU usage comparison in htop animation](docs/htop.gif)
As you can see, mold uses all available cores throughout its execution and
finishes quickly. In contrast, lld fails to utilize available cores most of
the time. In this demo, the maximum parallelism is artificially capped at 16,
so that the bars fit in the GIF.
For details, please see the [design notes](docs/design.md).
mold has been developed in the open since 2020 and has more than 140
contributors. Its test suite, which covers every linker feature, runs in CI for
all supported target CPU architectures, natively or under QEMU, and under ASAN
and TSAN. Before each release, we try to build all of Gentoo Linux's roughly
19,000 packages with mold, using GNU ld as a control, to find regressions before
they reach a release.
## Sponsors
It is taken for granted nowadays that compiler toolchains can be easily
installed and used for free, and people may not think too much about the
individuals behind these "free tools". mold supports many projects, but it
is essentially a one-person project. This situation is similar to the one
depicted in the following xkcd illustration.
mold is free to use, but keeping it maintained is continuous work: supporting
new architectures and toolchain features, keeping up with the projects that
depend on it, and making it faster. That work is funded by sponsors. If mold
saves you or your company time, please consider becoming a
[GitHub sponsor](https://github.com/sponsors/rui314).
[![xkcd 2347](https://imgs.xkcd.com/comics/dependency.png)](https://xkcd.com/2347)
If you think that the "Nebraska guy" should be rewarded, please consider
becoming our [GitHub sponsor](https://github.com/sponsors/rui314)!
We thank everyone who sponsors our project. In particular, we'd like to acknowledge
the following people and organizations who have sponsored $128/month or more:
We thank everyone who sponsors the project. In particular, we'd like to
acknowledge the following people and organizations who have sponsored
$128/month or more:
### Corporate sponsors
+100 -53
View File
@@ -1,40 +1,45 @@
#!/bin/bash
#
# This script creates a mold binary distribution. The output is written in
# `dist` directory as `mold-$version-$arch-linux.tar.gz` (e.g.,
# `mold-1.0.3-x86_64-linux.tar.gz`).
# This script creates a mold binary distribution. The output is written to
# the `dist` directory as `mold-$version-$arch-linux.tar.gz` (e.g.
# `mold-2.40.0-x86_64-linux.tar.gz`).
#
# The mold executable created by this script is statically linked to
# libstdc++ but dynamically linked to libc, libm, libz, and librt, as
# these libraries almost always exist on any Linux system. We can't
# statically link libc because doing so would disable dlopen(), which is
# necessary to open the LTO linker plugin.
#
# This script aims to produce reproducible outputs. That means if you run
# the script twice on the same git commit, it should produce bit-by-bit
# identical binary files. This property is crucial as a countermeasure
# against supply chain attacks. With this, you can verify that the binary
# files distributed on the GitHub release pages are created from the
# commit with release tags by rebuilding the binaries yourself.
# This script aims to produce reproducible outputs. That means each time
# it's run on the same git commit, it generates a bit-for-bit identical
# binary file regardless of when or where it's executed. This property
# serves as a strong safeguard against supply chain attacks. With a
# reproducible build, anyone can independently verify that the binary
# files published on our GitHub release page were built from the git
# commit tagged for release by rebuilding the binaries themselves.
#
# Debian provides snapshot.debian.org to host all historical binary
# packages. We use it to construct Podman images pinned to a
# particular timestamp.
# packages. We use it to construct a container image pinned to a
# particular timestamp. snapshot.debian.org is known to be very slow,
# but that shouldn't be a big problem for us because we only need that
# site the first time.
#
# We aim to use a reasonably old Debian version because we'll dynamically
# link glibc to mold, and a binary linked against a newer version of glibc
# won't work on a system with an older version of glibc.
# The mold executable created by this script is statically linked to
# libstdc++, but dynamically linked to glibc, libm and a few other
# libraries, as these libraries are almost always available on any Linux
# system. We can't statically link glibc because doing so would disable
# dlopen(), which is required to load the LTO linker plugin.
#
# We want to build mold with Clang rather than GCC because mold's
# We use a reasonably old Debian version for the build environment because
# a binary dynamically linked against a newer version of glibc won't work
# on a system with an older version of glibc.
#
# We prefer to build mold with Clang rather than GCC because mold's
# Identical Code Folding works best with the LLVM address significance
# table (.llvm_addrsig). Building a release binary with GCC yields a
# slightly larger binary than Clang's.
# table (.llvm_addrsig). Building a release binary with GCC produces a
# slightly larger binary than with Clang.
#
# We need a recent version of Clang to build mold. If it's not available
# via apt-get, we'll build it ourselves.
#
# You may need to install qemu-user-static package to build non-native
# binaries.
# This script can be used to create non-native binaries (e.g., building
# aarch64 binary on x86-64) because Podman automatically runs everything
# under QEMU if the container image is not native. To use this script for
# non-native builds, you may need to install the qemu-user-static package.
set -e -x
cd "$(dirname $0)"
@@ -74,21 +79,34 @@ fi
case $arch in
x86_64)
# Debian 9 (Stretch) released in June 2017.
# We use a Google-provided mirror (gcr.io) of the official Docker hub
# (docker.io) because docker.io has a strict rate limit policy.
#
# We use a Google-provided mirror (gcr.io) instead of the official Docker
# Hub (docker.io) because docker.io has a strict rate limit policy.
#
# The toolchain in Debian 9 is too old to build mold, so we rebuild it
# from source. We download source archives from official sites and build
# them locally, rather than downloading pre-built binaries from somewhere
# else, to avoid relying on unverifiable third-party binary blobs. Each
# archive is checked against the SHA-256 hash recorded below before it is
# unpacked, so this git commit alone determines every input of the build.
# A compromised network or mirror can only make the build fail, never
# change its output. Podman caches the result of each RUN command, so
# rebuilding is done only once per host.
cat <<EOF | $image_build
FROM mirror.gcr.io/library/debian:stretch@sha256:c5c5200ff1e9c73ffbf188b4a67eb1c91531b644856b4aefe86a58d2f0cb05be
ENV DEBIAN_FRONTEND=noninteractive TZ=UTC
RUN sed -i -e '/^deb/d' -e 's/^# deb /deb [trusted=yes] /g' /etc/apt/sources.list && \
RUN sed -i -e '/^deb/d' -e 's/^# deb /deb /g' /etc/apt/sources.list && \
echo 'Acquire::Retries "10"; Acquire::http::timeout "10"; Acquire::Check-Valid-Until "false";' > /etc/apt/apt.conf.d/80-retries && \
apt-get update && \
apt-get install -y --no-install-recommends wget file make gcc g++ zlib1g-dev libssl-dev && \
apt-get install -y --no-install-recommends wget xz-utils file make gcc g++ git zlib1g-dev libssl-dev ca-certificates && \
rm -rf /var/lib/apt/lists
# Build CMake 3.27
RUN mkdir /build && \
cd /build && \
wget -O- --no-check-certificate --progress=dot:mega https://cmake.org/files/v3.27/cmake-3.27.7.tar.gz | tar xzf - --strip-components=1 && \
wget --progress=dot:mega https://cmake.org/files/v3.27/cmake-3.27.7.tar.gz && \
echo '08f71a106036bf051f692760ef9558c0577c42ac39e96ba097e7662bd4158d8e cmake-3.27.7.tar.gz' | sha256sum -c && \
tar xf cmake-3.27.7.tar.gz --strip-components=1 && \
./bootstrap --parallel=\$(nproc) && \
make -j\$(nproc) && \
make install && \
@@ -97,11 +115,19 @@ RUN mkdir /build && \
# Build GCC 14
RUN mkdir /build && \
cd /build && \
wget -O- --no-check-certificate --progress=dot:mega https://ftpmirror.gnu.org/gcc/gcc-14.2.0/gcc-14.2.0.tar.gz | tar xzf - --strip-components=1 && \
wget --progress=dot:mega https://ftp.gnu.org/gnu/gcc/gcc-14.2.0/gcc-14.2.0.tar.xz && \
echo 'a7b39bc69cbf9e25826c5a60ab26477001f7c08d85cec04bc0e29cabed6f3cc9 gcc-14.2.0.tar.xz' | sha256sum -c && \
tar xf gcc-14.2.0.tar.xz --strip-components=1 && \
mkdir gmp mpc mpfr && \
wget -O- --no-check-certificate --progress=dot:mega https://ftpmirror.gnu.org/gmp/gmp-6.3.0.tar.gz | tar xzf - --strip-components=1 -C gmp && \
wget -O- --no-check-certificate --progress=dot:mega https://ftpmirror.gnu.org/mpc/mpc-1.3.1.tar.gz | tar xzf - --strip-components=1 -C mpc && \
wget -O- --no-check-certificate --progress=dot:mega https://ftpmirror.gnu.org/mpfr/mpfr-4.2.1.tar.gz | tar xzf - --strip-components=1 -C mpfr && \
wget --progress=dot:mega https://ftp.gnu.org/gnu/gmp/gmp-6.3.0.tar.xz && \
echo 'a3c2b80201b89e68616f4ad30bc66aee4927c3ce50e33929ca819d5c43538898 gmp-6.3.0.tar.xz' | sha256sum -c && \
tar xf gmp-6.3.0.tar.xz --strip-components=1 -C gmp && \
wget --progress=dot:mega https://ftp.gnu.org/gnu/mpc/mpc-1.3.1.tar.gz && \
echo 'ab642492f5cf882b74aa0cb730cd410a81edcdbec895183ce930e706c1c759b8 mpc-1.3.1.tar.gz' | sha256sum -c && \
tar xf mpc-1.3.1.tar.gz --strip-components=1 -C mpc && \
wget --progress=dot:mega https://ftp.gnu.org/gnu/mpfr/mpfr-4.2.1.tar.xz && \
echo '277807353a6726978996945af13e52829e3abd7a9a5b7fb2793894e18f1fcbb2 mpfr-4.2.1.tar.xz' | sha256sum -c && \
tar xf mpfr-4.2.1.tar.xz --strip-components=1 -C mpfr && \
./configure --prefix=/usr --enable-languages=c,c++ --disable-bootstrap --disable-multilib && \
make -j\$(nproc) && \
make install && \
@@ -111,7 +137,9 @@ RUN mkdir /build && \
# Build GNU binutils 2.43
RUN mkdir /build && \
cd /build && \
wget -O- --no-check-certificate --progress=dot:mega https://ftpmirror.gnu.org/binutils/binutils-2.43.tar.gz | tar xzf - --strip-components=1 && \
wget --progress=dot:mega https://ftp.gnu.org/gnu/binutils/binutils-2.43.tar.xz && \
echo 'b53606f443ac8f01d1d5fc9c39497f2af322d99e14cea5c0b4b124d630379365 binutils-2.43.tar.xz' | sha256sum -c && \
tar xf binutils-2.43.tar.xz --strip-components=1 && \
./configure --prefix=/usr && \
make -j\$(nproc) && \
make install && \
@@ -120,16 +148,20 @@ RUN mkdir /build && \
# Build Python 3.12.7
RUN mkdir /build && \
cd /build && \
wget -O- --no-check-certificate --progress=dot:mega https://www.python.org/ftp/python/3.12.7/Python-3.12.7.tgz | tar xzf - --strip-components=1 && \
wget --progress=dot:mega https://www.python.org/ftp/python/3.12.7/Python-3.12.7.tar.xz && \
echo '24887b92e2afd4a2ac602419ad4b596372f67ac9b077190f459aba390faf5550 Python-3.12.7.tar.xz' | sha256sum -c && \
tar xf Python-3.12.7.tar.xz --strip-components=1 && \
./configure && \
make -j\$(nproc) && \
make install && \
rm -rf /build
# Build LLVM 19
# Build LLVM 20
RUN mkdir /build && \
cd /build && \
wget -O- --no-check-certificate --progress=dot:mega https://github.com/llvm/llvm-project/archive/refs/tags/llvmorg-19.1.7.tar.gz | tar xzf - --strip-components=1 && \
wget --progress=dot:mega https://github.com/llvm/llvm-project/releases/download/llvmorg-20.1.3/llvm-project-20.1.3.src.tar.xz && \
echo 'b6183c41281ee3f23da7fda790c6d4f5877aed103d1e759763b1008bdd0e2c50 llvm-project-20.1.3.src.tar.xz' | sha256sum -c && \
tar xf llvm-project-20.1.3.src.tar.xz --strip-components=1 && \
mkdir b && \
cd b && \
cmake -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_PROJECTS=clang ../llvm && \
@@ -141,16 +173,16 @@ EOF
aarch64 | arm | ppc64le | s390x)
# Debian 11 (Bullseye) released in August 2021
#
# We don't want to build Clang for these targets with Qemu becuase
# that'd take extremely long time. Also I believe old build machines
# are usually x86-64.
# We don't want to build Clang for these targets on QEMU becuase it
# would take an extremely long time. Also, I believe old Linux boxes
# are typically x86-64.
cat <<EOF | $image_build
FROM mirror.gcr.io/library/debian:bullseye-20240904@sha256:8ccc486c29a3ad02ad5af7f1156e2152dff3ba5634eec9be375269ef123457d8
ENV DEBIAN_FRONTEND=noninteractive TZ=UTC
RUN sed -i -e '/^deb/d' -e 's/^# deb /deb [trusted=yes] /g' /etc/apt/sources.list && \
RUN sed -i -e '/^deb/d' -e 's/^# deb /deb /g' /etc/apt/sources.list && \
echo 'Acquire::Retries "10"; Acquire::http::timeout "10"; Acquire::Check-Valid-Until "false";' > /etc/apt/apt.conf.d/80-retries && \
apt-get update && \
apt-get install -y --no-install-recommends build-essential gcc-10 g++-10 clang-16 cmake && \
apt-get install -y --no-install-recommends build-essential gcc-10 g++-10 clang-16 cmake git && \
ln -sf /usr/bin/clang-16 /usr/bin/clang && \
ln -sf /usr/bin/clang++-16 /usr/bin/clang++ && \
rm -rf /var/lib/apt/lists
@@ -163,19 +195,20 @@ ENV DEBIAN_FRONTEND=noninteractive TZ=UTC
RUN sed -i -e '/^URIs/d' -e 's/^# http/URIs: http/' /etc/apt/sources.list.d/debian.sources && \
echo 'Acquire::Retries "10"; Acquire::http::timeout "10"; Acquire::Check-Valid-Until "false";' > /etc/apt/apt.conf.d/80-retries && \
apt-get update && \
apt-get install -y --no-install-recommends build-essential gcc-14 g++-14 clang-18 cmake && \
apt-get install -y --no-install-recommends build-essential gcc-14 g++-14 clang-18 cmake git && \
ln -sf /usr/bin/clang-18 /usr/bin/clang && \
ln -sf /usr/bin/clang++-18 /usr/bin/clang++ && \
rm -rf /var/lib/apt/lists
EOF
;;
loongarch64)
# LoongArch build is not reproducible yet
cat <<EOF | $image_build
FROM mirror.gcr.io/loongarch64/debian:sid
FROM mirror.gcr.io/loongarch64/debian:sid@sha256:0356df4e494bbb86bb469377a00789a5b42bbf67d5ff649a3f9721b745cbef77
ENV DEBIAN_FRONTEND=noninteractive TZ=UTC
RUN apt-get update && \
apt-get install -y --no-install-recommends build-essential gcc-14 g++-14 clang-19 cmake && \
RUN echo 'deb http://deb.debian.org/debian sid main' > /etc/apt/sources.list && \
echo 'Acquire::Retries "10"; Acquire::http::timeout "10"; Acquire::Check-Valid-Until "false";' > /etc/apt/apt.conf.d/80-retries && \
apt-get update && \
apt-get install -y --no-install-recommends build-essential gcc-14 g++-14 clang-19 cmake git && \
ln -sf /usr/bin/clang-19 /usr/bin/clang && \
ln -sf /usr/bin/clang++-19 /usr/bin/clang++ && \
rm -rf /var/lib/apt/lists
@@ -189,20 +222,34 @@ esac
version=$(sed -n 's/^project(mold VERSION \(.*\))/\1/p' CMakeLists.txt)
dest=mold-$version-$arch-linux
# Source tarballs available on GitHub don't contain .git history.
# Source tarballs available on GitHub don't contain .git directory.
# Clone the repo if missing.
[ -d .git ] || git clone --branch v$version --depth 1 --bare https://github.com/rui314/mold .git
# We use the timestamp of the last Git commit as the file timestamp
# for build artifacts.
timestamp="$(git log -1 --format=%ci)"
timestamp=$(git log -1 --format=%ct)
# `uname -m` in an ARM32 container running on an ARM64 host reports it
# not as ARM32 but as ARM64. That confuses BLAKE3's cmake script and
# erroneously enables NEON SIMD instructions. `setarch` can be used to
# change the output of `uname -m`.
setarch=
[ $arch = arm ] && setarch='setarch linux32'
# Build mold in a container.
mkdir -p dist
# Build mold in a container.
#
# SOURCE_DATE_EPOCH is a standardized environment variable that allows
# build artifacts to appear as if they were built at a specific time.
# We use it to control how the compiler expands the C/C++ __DATE__ and
# __TIME__ macros.
podman run --arch $arch -it --rm --userns=host --pids-limit=-1 --network=none \
-v "$(pwd):/mold:ro" -v "$(pwd)/dist:/dist" $image bash -c "
--pull=never -v "$(pwd):/mold:ro" -v "$(pwd)/dist:/dist" $image \
$setarch bash -c "
set -e
export SOURCE_DATE_EPOCH=$timestamp
mkdir /build
cd /build
cmake -DCMAKE_BUILD_TYPE=Release -DMOLD_MOSTLY_STATIC=1 -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ /mold
@@ -212,7 +259,7 @@ cmake -DMOLD_USE_MOLD=1 .
cmake --build . -j\$(nproc)
ctest --output-on-failure -j\$(nproc)
cmake --install . --prefix $dest --strip
find $dest -print | xargs touch --no-dereference --date='$timestamp'
find $dest -print | xargs touch --no-dereference --date=@$timestamp
find $dest -print | sort | tar -cf - --no-recursion --files-from=- | gzip -9nc > /dist/$dest.tar.gz
cp mold /dist
sha256sum /dist/$dest.tar.gz
-1
View File
@@ -1 +0,0 @@
<svg version="1.1" viewBox="0.0 0.0 600.0 371.0" fill="none" stroke="none" stroke-linecap="square" stroke-miterlimit="10" width="600" height="371" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg"><path fill="#ffffff" d="M0 0L600.0 0L600.0 371.0L0 371.0L0 0Z" fill-rule="nonzero"/><path stroke="#333333" stroke-width="1.0" stroke-linecap="butt" d="M82.5 275.5L581.5 275.5" fill-rule="nonzero"/><path stroke="#cccccc" stroke-width="1.0" stroke-linecap="butt" d="M82.5 231.5L581.5 231.5" fill-rule="nonzero"/><path stroke="#cccccc" stroke-width="1.0" stroke-linecap="butt" d="M82.5 186.5L581.5 186.5" fill-rule="nonzero"/><path stroke="#cccccc" stroke-width="1.0" stroke-linecap="butt" d="M82.5 142.5L581.5 142.5" fill-rule="nonzero"/><path stroke="#cccccc" stroke-width="1.0" stroke-linecap="butt" d="M82.5 98.5L581.5 98.5" fill-rule="nonzero"/><path stroke="#cccccc" stroke-width="1.0" stroke-linecap="butt" d="M82.5 53.5L581.5 53.5" fill-rule="nonzero"/><clipPath id="id_0"><path d="M82.55 53.916668L581.45 53.916668L581.45 275.45L82.55 275.45L82.55 53.916668Z" clip-rule="nonzero"/></clipPath><path stroke="#000000" stroke-width="2.0" stroke-linecap="butt" stroke-opacity="0.0" clip-path="url(#id_0)" d="M158.0 275.0L136.0 275.0L136.0 229.0C136.0 227.89543 136.89543 227.0 138.0 227.0L156.0 227.0C157.10457 227.0 158.0 227.89543 158.0 229.0Z" fill-rule="nonzero"/><path fill="#4285f4" clip-path="url(#id_0)" d="M158.0 275.0L136.0 275.0L136.0 229.0C136.0 227.89543 136.89543 227.0 138.0 227.0L156.0 227.0C157.10457 227.0 158.0 227.89543 158.0 229.0Z" fill-rule="nonzero"/><path stroke="#000000" stroke-width="2.0" stroke-linecap="butt" stroke-opacity="0.0" clip-path="url(#id_0)" d="M307.0 275.0L285.0 275.0L285.0 91.0C285.0 89.89543 285.89542 89.0 287.0 89.0L305.0 89.0C306.10458 89.0 307.0 89.89543 307.0 91.0Z" fill-rule="nonzero"/><path fill="#4285f4" clip-path="url(#id_0)" d="M307.0 275.0L285.0 275.0L285.0 91.0C285.0 89.89543 285.89542 89.0 287.0 89.0L305.0 89.0C306.10458 89.0 307.0 89.89543 307.0 91.0Z" fill-rule="nonzero"/><path stroke="#000000" stroke-width="2.0" stroke-linecap="butt" stroke-opacity="0.0" clip-path="url(#id_0)" d="M182.0 275.0L160.0 275.0L160.0 244.0C160.0 242.89543 160.89543 242.0 162.0 242.0L180.0 242.0C181.10457 242.0 182.0 242.89543 182.0 244.0Z" fill-rule="nonzero"/><path fill="#ea4335" clip-path="url(#id_0)" d="M182.0 275.0L160.0 275.0L160.0 244.0C160.0 242.89543 160.89543 242.0 162.0 242.0L180.0 242.0C181.10457 242.0 182.0 242.89543 182.0 244.0Z" fill-rule="nonzero"/><path stroke="#000000" stroke-width="2.0" stroke-linecap="butt" stroke-opacity="0.0" clip-path="url(#id_0)" d="M331.0 275.0L309.0 275.0L309.0 131.0C309.0 129.89543 309.89542 129.0 311.0 129.0L329.0 129.0C330.10458 129.0 331.0 129.89543 331.0 131.0Z" fill-rule="nonzero"/><path fill="#ea4335" clip-path="url(#id_0)" d="M331.0 275.0L309.0 275.0L309.0 131.0C309.0 129.89543 309.89542 129.0 311.0 129.0L329.0 129.0C330.10458 129.0 331.0 129.89543 331.0 131.0Z" fill-rule="nonzero"/><path stroke="#000000" stroke-width="2.0" stroke-linecap="butt" stroke-opacity="0.0" clip-path="url(#id_0)" d="M480.0 275.0L458.0 275.0L458.0 156.0C458.0 154.89543 458.89542 154.0 460.0 154.0L478.0 154.0C479.10458 154.0 480.0 154.89543 480.0 156.0Z" fill-rule="nonzero"/><path fill="#ea4335" clip-path="url(#id_0)" d="M480.0 275.0L458.0 275.0L458.0 156.0C458.0 154.89543 458.89542 154.0 460.0 154.0L478.0 154.0C479.10458 154.0 480.0 154.89543 480.0 156.0Z" fill-rule="nonzero"/><path stroke="#000000" stroke-width="2.0" stroke-linecap="butt" stroke-opacity="0.0" clip-path="url(#id_0)" d="M206.0 275.0L184.0 275.0L184.0 270.0C184.0 268.89542 184.89543 268.0 186.0 268.0L204.0 268.0C205.10457 268.0 206.0 268.89542 206.0 270.0Z" fill-rule="nonzero"/><path fill="#fbbc04" clip-path="url(#id_0)" d="M206.0 275.0L184.0 275.0L184.0 270.0C184.0 268.89542 184.89543 268.0 186.0 268.0L204.0 268.0C205.10457 268.0 206.0 268.89542 206.0 270.0Z" fill-rule="nonzero"/><path stroke="#000000" stroke-width="2.0" stroke-linecap="butt" stroke-opacity="0.0" clip-path="url(#id_0)" d="M355.0 275.0L333.0 275.0L333.0 254.0C333.0 252.89543 333.89542 252.0 335.0 252.0L353.0 252.0C354.10458 252.0 355.0 252.89543 355.0 254.0Z" fill-rule="nonzero"/><path fill="#fbbc04" clip-path="url(#id_0)" d="M355.0 275.0L333.0 275.0L333.0 254.0C333.0 252.89543 333.89542 252.0 335.0 252.0L353.0 252.0C354.10458 252.0 355.0 252.89543 355.0 254.0Z" fill-rule="nonzero"/><path stroke="#000000" stroke-width="2.0" stroke-linecap="butt" stroke-opacity="0.0" clip-path="url(#id_0)" d="M504.0 275.0L482.0 275.0L482.0 250.0C482.0 248.89543 482.89542 248.0 484.0 248.0L502.0 248.0C503.10458 248.0 504.0 248.89543 504.0 250.0Z" fill-rule="nonzero"/><path fill="#fbbc04" clip-path="url(#id_0)" d="M504.0 275.0L482.0 275.0L482.0 250.0C482.0 248.89543 482.89542 248.0 484.0 248.0L502.0 248.0C503.10458 248.0 504.0 248.89543 504.0 250.0Z" fill-rule="nonzero"/><path stroke="#000000" stroke-width="2.0" stroke-linecap="butt" stroLine truncated

Before

Width:  |  Height:  |  Size: 99 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 596 KiB

+179
View File
@@ -0,0 +1,179 @@
# Instrumenting mold with MemorySanitizer
## Introduction
Per <https://github.com/google/sanitizers/wiki/MemorySanitizerLibcxxHowTo>:
> If you want MemorySanitizer to work properly and not produce any false
> positives, you must ensure that all the code in your program and in
> libraries it uses is instrumented (i.e. built with `-fsanitize=memory`).
> In particular, you would need to link against an MSan-instrumented C++
> standard library. We recommend to use [libc++](https://libcxx.llvm.org/)
> for that purpose.
## Building instrumented libc++
Build an MSan-instrumented libc++ from source:
```sh
cd ~
git clone https://github.com/llvm/llvm-project
cd llvm-project
cmake -S ./runtimes -B build-libcxx -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DLLVM_ENABLE_RUNTIMES="libcxx;libcxxabi" \
-DCMAKE_C_COMPILER=clang \
-DCMAKE_CXX_COMPILER=clang++ \
-DLLVM_USE_SANITIZER=MemoryWithOrigins
cmake --build build-libcxx -- cxx cxxabi
export LIBCXX="$HOME/llvm-project/build-libcxx" # for subsequent build steps
```
Upon success, `./build-libcxx/{include,lib}` will contain the resulting
headers and shared libraries.
## Linking mold against instrumented libc++
Use the `MOLD_USE_MSAN` and `MOLD_STDLIB_PREFIX` cmake variables to link
mold against the instrumented build of libc++:
```sh
cd ~
git clone https://github.com/rui314/mold.git
cd mold
cmake -B build -G Ninja \
-DCMAKE_BUILD_TYPE=Debug \
-DCMAKE_C_COMPILER=clang \
-DCMAKE_CXX_COMPILER=clang++ \
-DMOLD_USE_MSAN=ON \
-DMOLD_STDLIB_PREFIX="$LIBCXX" \
-DMOLD_USE_MIMALLOC=OFF \
-DMOLD_USE_SYSTEM_TBB=ON
cmake --build build
```
Most of mold's tests (except those for `-flto`) should work at this
point. Run them like normal:
```sh
ctest --test-dir build
```
Any resulting MemorySanitizer errors should be visible in
`./build/Temporary/Testing/LastTest.log`.
## Building instrumented LTO plugin (experimental)
Exercising `-flto` with MemorySanitizer (and without false positives)
requires instrumenting the transitive dependencies of `lto-unix.cc`:
- `libiberty.a`
- `liblto_plugin.so`
- `LLVMgold.so`
> [!NOTE]
> This is more involved and time-consuming than building only libc++ from
> source. The cost-benefit ratio of this additional instrumentation and
> test coverage may be unfavorable in many cases.
> [!IMPORTANT]
> The following steps are experimental and unlikely to work exactly as-is
> under CI runners and individual developer environments. Consider the
> following a starting point rather than a complete HOWTO.
Build an MSan-instrumented GNU libiberty:
```sh
cd ~
git clone [email protected]:gcc-mirror/gcc.git
cd gcc/libiberty
export CC=clang
export CFLAGS="-g -Og -fsanitize=memory -fsanitize-memory-track-origins"
export LDFLAGS="-fsanitize=memory"
./configure
make -j$(nproc)
unset CC CFLAGS LDFLAGS
```
Install the resulting `./libiberty.a` into the build toolchain being
tested with mold. If the system image is ephemeral or disposable (e.g. a
short-lived VM or container), a quick-and-dirty install could look like:
```sh
sudo cp /usr/lib/libiberty.a{,.bak}
sudo cp ./libiberty.a /usr/lib/
```
Build an MSan-instrumented GCC LTO plugin library:
```sh
cd ~/gcc/lto-plugin # use already-cloned repo from earlier step
export CC=clang
export CFLAGS="-g -Og -fsanitize=memory -fsanitize-memory-track-origins"
export LDFLAGS="-fsanitize=memory"
./configure --with-libiberty=/usr/lib
make -j$(nproc)
unset CC CFLAGS LDFLAGS
```
Install the resulting `liblto_plugin.so`. With the same caveats discussed
above, a simple install into an ephemeral environment could look like:
```sh
export GCC_VERSION="$(gcc -dumpversion)"
sudo cp /usr/lib/gcc/x86_64-pc-linux-gnu/$GCC_VERSION/liblto_plugin.so{,.bak}
sudo cp .libs/liblto_plugin.so /usr/lib/gcc/x86_64-pc-linux-gnu/$GCC_VERSION/
```
Build an MSan-instrumented LLVM LTO plugin library:
```sh
cd ~/llvm-project # use already-cloned repo from earlier step
CF="-nostdinc++ -isystem $LIBCXX/include/c++/v1"
INCDIR="$(find /usr -name plugin-api.h -type f | head -1 | xargs dirname)"
function configure
{
cmake -S ./llvm -B build-plugin -G Ninja \
-DLLVM_ENABLE_PROJECTS=clang \
-DLLVM_TARGETS_TO_BUILD=X86 \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_C_COMPILER=clang \
-DCMAKE_CXX_COMPILER=clang++ \
-DCMAKE_C_FLAGS="$CF" \
-DCMAKE_CXX_FLAGS="$CF" \
-DCMAKE_EXE_LINKER_FLAGS="$1" \
-DCMAKE_POSITION_INDEPENDENT_CODE=ON \
-DLLVM_BINUTILS_INCDIR="$INCDIR" \
-DLLVM_USE_SANITIZER=MemoryWithOrigins
}
# workaround for linker issues: configure twice with different LDFLAGS
configure "-nostdlib++ -L $LIBCXX/lib -Wl,--rpath=$LIBCXX/lib"
configure "-nostdlib++ -L $LIBCXX/lib -lc++ -Wl,--rpath=$LIBCXX/lib"
cmake --build build-plugin -- LLVMgold.so
```
Install the resulting `LLVMgold.so`. With the same caveats discussed
above, a simple install into an ephemeral environment could look like:
```sh
sudo cp /usr/lib/LLVMgold.so{,.bak}
sudo cp ./build-plugin/lib/LLVMgold.so /usr/lib/
```
mold's tests for `-flto` should now (mostly) work, though there do seem
to be issues around `gcc` inferring `-flto` when not explicitly specified.
One workaround is to use `clang` as `TEST_CC`:
```sh
cd ~/mold # use already-cloned and built repo from earlier step
TEST_CC=clang TEST_CXX=clang++ ctest --test-dir build
```
## References
- <https://github.com/google/sanitizers/wiki/MemorySanitizerBootstrappingClang>
- <https://llvm.org/docs/GoldPlugin.html#lto-how-to-build>
+39 -9
View File
@@ -1,6 +1,6 @@
.\" generated with Ronn-NG/v0.9.1
.\" http://github.com/apjanke/ronn-ng/tree/0.9.1
.TH "MOLD" "1" "February 2025" ""
.TH "MOLD" "1" "August 2026" ""
.SH "NAME"
\fBmold\fR \- a modern linker
.SH "SYNOPSIS"
@@ -70,6 +70,8 @@ Permit or do not permit mold to create a debug info file in the background\.
.TP
\fB\-\-fork\fR, \fB\-\-no\-fork\fR
Spawn a child process and let it do the actual linking\. When linking a large program, the OS kernel can take a few hundred milliseconds to terminate a \fBmold\fR process\. \fB\-\-fork\fR hides that latency\. By default, it does fork\.
.IP
Note that \fB\-\-fork\fR also hides the resource usage statistics reported by time(2), since it doesn't call waitpid(2) on the child process\. If you need those statistics, pass \fB\-\-no\-fork\fR\.
.TP
\fB\-\-perf\fR
Print performance statistics\.
@@ -130,6 +132,17 @@ Use multiple threads\. By default, \fBmold\fR uses as many threads as the number
\fB\-\-quick\-exit\fR, \fB\-\-no\-quick\-exit\fR
Use or do not use \fBquick_exit\fR to exit\.
.TP
\fB\-\-zero\-to\-bss\fR
Convert all\-zero data sections into BSS\.
.IP
When this option is enabled, \fBmold\fR scans input data sections that are not of type \fBSHT_NOBITS\fR and checks whether their contents consist solely of zero bytes\. Such sections are then converted into BSS (\fBSHT_NOBITS\fR) sections\. This reduces the size of the output file, since BSS sections occupy no space in the file image\.
.IP
This behavior is especially useful for user\-defined sections created with \fB__attribute__((section("\.sectname")))\fR that contain uninitialized global variables\. GCC and Clang do not automatically mark such sections as BSS even if their contents are entirely zero, and instead emit them as regular data sections\.
.IP
For example, consider \fB__attribute__((section("\.sectname"))) int vec[256];\fR\.
.IP
By default, this results in a \fB\.sectname\fR section of type \fBSHT_PROGBITS\fR filled with zeros\. With \fB\-\-zero\-to\-bss\fR, the linker will recognize it as empty data and convert it to a \fBSHT_NOBITS\fR section, reducing the output file size without changing runtime semantics\.
.TP
\fB\-z rewrite\-endbr\fR, \fB\-z norewrite\-endbr\fR
As a security measure, some CPU instruction sets have recently gained a feature to protect control flow integrity by disallowing indirect branches by default\. If the feature is enabled, the instruction that is executed immediately after an indirect branch must be an branch target marker instruction, or a CPU\-level fault will raise\. The marker instruction is also known as "landing pad" instruction, to which indirect branches can land\. This feature makes ROP attacks harder to conduct\.
.IP
@@ -139,7 +152,9 @@ By default, the compiler always emits a landing pad at the beginning of each glo
.IP
If \fB\-\-rewrite\-endbr\fR is given, mold conducts a whole program analysis to identify functions whose addresses are actually taken and rewrites landing pads with no\-ops for non\-address\-taken functions, reducing the attack surface\.
.IP
This feature is currently available only on x86\-64\.
This feature is currently available only on x86\-64 and arm64\.
.IP
On arm64, the landing pad is \fBbti c\fR\. Note that code compiled with \fB\-mbranch\-protection=standard\fR (BTI + pointer authentication) starts functions with \fBpaciasp\fR, which doubles as a landing pad, so there is no separate \fBbti c\fR to rewrite and this option has no effect on such code\.
.SH "GNU\-COMPATIBLE OPTIONS"
.TP
\fB\-\-help\fR
@@ -180,7 +195,10 @@ Omit \fB\.debug_*\fR sections from the output file\.
Read linker script from \fIfile\fR\.
.TP
\fB\-X\fR, \fB\-\-discard\-locals\fR
Discard temporary local symbols to reduce the sizes of the symbol table and the string table\. Temporary local symbols are local symbols starting with \fB\.L\fR\. Compilers usually generate such symbols for unnamed program elements such as string literals or floating\-point literals\.
Discard temporary local symbols to reduce the sizes of the symbol table and the string table\. Temporary local symbols are local symbols starting with \fB\.L\fR\. Compilers usually generate such symbols for unnamed program elements such as string literals or floating\-point literals\. This is the default; use \fB\-\-discard\-none\fR to keep temporary local symbols\.
.TP
\fB\-\-discard\-none\fR
Keep all local symbols in the symbol table, including temporary ones starting with \fB\.L\fR, except ones in mergeable sections\. This restores the default behavior of GNU ld\. \fBmold\fR and \fBld\.lld\fR discard temporary local symbols by default\.
.TP
\fB\-e\fR \fIsymbol\fR, \fB\-\-entry\fR=\fIsymbol\fR:
@@ -245,6 +263,9 @@ Alias for \fB\-\-section\-start=\.data=\fR\fIaddress\fR\.
\fB\-\-Ttext\fR=\fIaddress\fR
Alias for \fB\-\-section\-start=\.text=\fR\fIaddress\fR\.
.TP
\fB\-\-Ttext\-segment\fR=\fIaddress\fR
Set the address of the first byte of the text segment\.
.TP
\fB\-\-allow\-multiple\-definition\fR
Normally, the linker reports an error if there are more than one definition of a symbol\. This option changes the default behavior so that it doesn't report an error for duplicate definitions and instead use the first definition\.
.TP
@@ -269,8 +290,8 @@ Synonym for \fB\-\-build\-id=sha256\fR\.
\fB\-\-no\-build\-id\fR
Synonym for \fB\-\-build\-id=none\fR\.
.TP
\fB\-\-compress\-debug\-sections\fR=[ \fBzlib\fR | \fBzlib\-gabi\fR | \fBzstd\fR | \fBnone\fR ]
Compress DWARF debug info (\fB\.debug_*\fR sections) using the zlib or zstd compression algorithm\. \fBzlib\-gabi\fR is an alias for \fBzlib\fR\.
\fB\-\-compress\-debug\-sections\fR=[ \fBzlib\fR | \fBzlib\-gabi\fR | \fBzlib:\fR\fIN\fR | \fBzstd\fR | \fBzstd:\fR\fIN\fR | \fBnone\fR ]
Compress DWARF debug info (\fB\.debug_*\fR sections) using the zlib or zstd compression algorithm\. \fBzlib\-gabi\fR is an alias for \fBzlib\fR\. \fBzlib:\fR\fIN\fR selects the zlib compression level, where \fIN\fR is between 0 and 9 (0 means no compression), and \fBzstd:\fR\fIN\fR selects the zstd compression level, where \fIN\fR is between 1 and 22\. Higher levels achieve better compression ratios but are slower\. \fBzlib\fR is equivalent to \fBzlib:1\fR and \fBzstd\fR is equivalent to \fBzstd:3\fR\.
.TP
\fB\-\-defsym\fR=\fIsymbol\fR=\fIvalue\fR
Define \fIsymbol\fR as an alias for \fIvalue\fR\.
@@ -358,10 +379,16 @@ Embed a specified string into the \fB\.note\.package\fR section\. This option is
.IP
An argument to this option is treated as percent\-encoded and decoded before being inserted into the section, allowing you to avoid the use of the comma (\fB,\fR) character in the argument\. This is useful because the compiler replaces all occurrences of commas in \fB\-Wl,\fR with spaces before forwarding them to the linker\. Note that \fBmold\fR always interprets the argument as percent\-encoded, so you also need to escape all occurrences of \fB%\fR as \fB%25\fR\.
.TP
\fB\-\-pack\-dyn\-relocs\fR=[ \fBrelr\fR | \fBnone\fR ]
\fB\-\-pack\-dyn\-relocs\fR=[ \fBrelr\fR | \fBandroid\fR | \fBandroid+relr\fR | \fBnone\fR ]
Compress dynamic relocations in the output file\.
.IP
If \fBrelr\fR is specified, all \fBR_*_RELATIVE\fR relocations are put into \fB\.relr\.dyn\fR section instead of \fB\.rel\.dyn\fR or \fB\.rela\.dyn\fR section\. Since \fB\.relr\.dyn\fR section uses a space\-efficient encoding scheme, specifying this flag can reduce the size of the output\. This is typically most effective for position\-independent executable\.
.IP
Note that a runtime loader has to support \fB\.relr\.dyn\fR to run executables or shared libraries linked with \fB\-\-pack\-dyn\-relocs=relr\fR\. As of 2022, only ChromeOS, Android and Fuchsia support it\.
If \fBandroid\fR is specified, all dynamic relocations are encoded in Android's packed relocation format (APS2) and stored in the \fB\.rel\.dyn\fR or \fB\.rela\.dyn\fR section, whose section type is changed to \fBSHT_ANDROID_REL\fR or \fBSHT_ANDROID_RELA\fR\. The dynamic table is updated to use \fBDT_ANDROID_REL\fR / \fBDT_ANDROID_RELA\fR and their \fBSZ\fR variants in place of the standard tags\.
.IP
\fBandroid+relr\fR combines both: \fBR_*_RELATIVE\fR relocations go to \fB\.relr\.dyn\fR and the rest are APS2\-encoded\. This is the configuration used by most Android system binaries\.
.IP
Note that a runtime loader has to support \fB\.relr\.dyn\fR to run executables or shared libraries linked with \fB\-\-pack\-dyn\-relocs=relr\fR\. As of 2026, glibc (since 2\.36), musl (since 1\.2\.4), Android, ChromeOS, and Fuchsia all support it\. The APS2 format, on the other hand, is only handled by Android's bionic loader; the encoder itself is target\-agnostic, but the produced binary can only be loaded on platforms where bionic (or a compatible loader) handles \fBDT_ANDROID_REL(A)\fR\.
.TP
\fB\-\-pie\fR, \fB\-\-pic\-executable\fR, \fB\-\-no\-pie\fR, \fB\-\-no\-pic\-executable\fR
Create a position\-independent executable\.
@@ -427,6 +454,9 @@ How to handle undefined symbols\.
\fB\-\-version\-script\fR=\fIfile\fR
Read version script from \fIfile\fR\. If \fIfile\fR does not exist in the current directory, it is searched from library search paths for the sake of compatibility with GNU ld\.
.TP
\fB\-w\fR, \fB\-\-no\-warnings\fR
Suppress warnings and cancel \fB\-\-fatal\-warnings\fR\.
.TP
\fB\-\-warn\-common\fR, \fB\-\-no\-warn\-common\fR
Warn about common symbols\.
.TP
@@ -483,9 +513,9 @@ By default, \fBmold\fR generates a relro segment\. \fB\-z norelro\fR disables th
\fB\-z separate\-loadable\-segments\fR, \fB\-z separate\-code\fR, \fB\-z noseparate\-code\fR
If one memory page contains multiple segments, the page protection bits are set in such a way that the needed attributes (writable or executable) are satisfied for all segments\. This usually happens at a boundary of two segments with two different attributes\.
.IP
\fBseparate\-loadable\-segments\fR adds paddings between segments with different attributes so that they do not share the same page\. This is the default\.
\fBseparate\-loadable\-segments\fR adds paddings between segments with different attributes so that they do not share the same page\.
.IP
\fBseparate\-code\fR adds paddings only between executable and non\-executable segments\.
\fBseparate\-code\fR adds paddings only between executable and non\-executable segments\. This is the default\.
.IP
\fBnoseparate\-code\fR does not add any paddings between segments\.
.TP
+74 -9
View File
@@ -159,6 +159,10 @@ but as `-o magic`.
program, the OS kernel can take a few hundred milliseconds to terminate a
`mold` process. `--fork` hides that latency. By default, it does fork.
Note that `--fork` also hides the resource usage statistics reported by
time(2), since it doesn't call waitpid(2) on the child process. If you
need those statistics, pass `--no-fork`.
* `--perf`:
Print performance statistics.
@@ -267,6 +271,28 @@ but as `-o magic`.
* `--quick-exit`, `--no-quick-exit`:
Use or do not use `quick_exit` to exit.
* `--zero-to-bss`:
Convert all-zero data sections into BSS.
When this option is enabled, `mold` scans input data sections that are not
of type `SHT_NOBITS` and checks whether their contents consist solely of
zero bytes. Such sections are then converted into BSS (`SHT_NOBITS`)
sections. This reduces the size of the output file, since BSS sections
occupy no space in the file image.
This behavior is especially useful for user-defined sections created with
`__attribute__((section(".sectname")))` that contain uninitialized global
variables. GCC and Clang do not automatically mark such sections as BSS
even if their contents are entirely zero, and instead emit them as regular
data sections.
For example, consider `__attribute__((section(".sectname"))) int vec[256];`.
By default, this results in a `.sectname` section of type `SHT_PROGBITS`
filled with zeros. With `--zero-to-bss`, the linker will recognize it as
empty data and convert it to a `SHT_NOBITS` section, reducing the output
file size without changing runtime semantics.
* `-z rewrite-endbr`, `-z norewrite-endbr`:
As a security measure, some CPU instruction sets have recently gained a
feature to protect control flow integrity by disallowing indirect branches
@@ -292,7 +318,12 @@ but as `-o magic`.
landing pads with no-ops for non-address-taken functions, reducing the
attack surface.
This feature is currently available only on x86-64.
This feature is currently available only on x86-64 and arm64.
On arm64, the landing pad is `bti c`. Note that code compiled with
`-mbranch-protection=standard` (BTI + pointer authentication) starts
functions with `paciasp`, which doubles as a landing pad, so there is no
separate `bti c` to rewrite and this option has no effect on such code.
## GNU-COMPATIBLE OPTIONS
@@ -349,7 +380,14 @@ but as `-o magic`.
Discard temporary local symbols to reduce the sizes of the symbol table and
the string table. Temporary local symbols are local symbols starting with
`.L`. Compilers usually generate such symbols for unnamed program elements
such as string literals or floating-point literals.
such as string literals or floating-point literals. This is the default;
use `--discard-none` to keep temporary local symbols.
* `--discard-none`:
Keep all local symbols in the symbol table, including temporary ones
starting with `.L`, except ones in mergeable sections. This restores the
default behavior of GNU ld. `mold` and `ld.lld` discard temporary local
symbols by default.
* `-e` _symbol_, `--entry`=_symbol_:
@@ -432,6 +470,9 @@ but as `-o magic`.
* `--Ttext`=_address_:
Alias for `--section-start=.text=`_address_.
* `--Ttext-segment`=_address_:
Set the address of the first byte of the text segment.
* `--allow-multiple-definition`:
Normally, the linker reports an error if there are more than one definition
of a symbol. This option changes the default behavior so that it doesn't
@@ -480,9 +521,14 @@ but as `-o magic`.
* `--no-build-id`:
Synonym for `--build-id=none`.
* `--compress-debug-sections`=[ `zlib` | `zlib-gabi` | `zstd` | `none` ]:
* `--compress-debug-sections`=[ `zlib` | `zlib-gabi` | `zlib:`_N_ | `zstd` | `zstd:`_N_ | `none` ]:
Compress DWARF debug info (`.debug_*` sections) using the zlib or zstd
compression algorithm. `zlib-gabi` is an alias for `zlib`.
compression algorithm. `zlib-gabi` is an alias for `zlib`. `zlib:`_N_
selects the zlib compression level, where _N_ is between 0 and 9 (0
means no compression), and `zstd:`_N_ selects the zstd compression
level, where _N_ is between 1 and 22. Higher levels achieve better
compression ratios but are slower. `zlib` is equivalent to `zlib:1`
and `zstd` is equivalent to `zstd:3`.
* `--defsym`=_symbol_=_value_:
Define _symbol_ as an alias for _value_.
@@ -632,16 +678,32 @@ but as `-o magic`.
argument as percent-encoded, so you also need to escape all occurrences
of `%` as `%25`.
* `--pack-dyn-relocs`=[ `relr` | `none` ]:
* `--pack-dyn-relocs`=[ `relr` | `android` | `android+relr` | `none` ]:
Compress dynamic relocations in the output file.
If `relr` is specified, all `R_*_RELATIVE` relocations are put into
`.relr.dyn` section instead of `.rel.dyn` or `.rela.dyn` section. Since
`.relr.dyn` section uses a space-efficient encoding scheme, specifying this
flag can reduce the size of the output. This is typically most effective for
position-independent executable.
If `android` is specified, all dynamic relocations are encoded in Android's
packed relocation format (APS2) and stored in the `.rel.dyn` or `.rela.dyn`
section, whose section type is changed to `SHT_ANDROID_REL` or
`SHT_ANDROID_RELA`. The dynamic table is updated to use `DT_ANDROID_REL`
/ `DT_ANDROID_RELA` and their `SZ` variants in place of the standard tags.
`android+relr` combines both: `R_*_RELATIVE` relocations go to `.relr.dyn`
and the rest are APS2-encoded. This is the configuration used by most
Android system binaries.
Note that a runtime loader has to support `.relr.dyn` to run executables or
shared libraries linked with `--pack-dyn-relocs=relr`. As of 2022, only
ChromeOS, Android and Fuchsia support it.
shared libraries linked with `--pack-dyn-relocs=relr`. As of 2026, glibc
(since 2.36), musl (since 1.2.4), Android, ChromeOS, and Fuchsia all
support it. The APS2 format, on the other hand, is only handled by
Android's bionic loader; the encoder itself is target-agnostic, but the
produced binary can only be loaded on platforms where bionic (or a
compatible loader) handles `DT_ANDROID_REL(A)`.
* `--pie`, `--pic-executable`, `--no-pie`, `--no-pic-executable`:
Create a position-independent executable.
@@ -731,6 +793,9 @@ but as `-o magic`.
directory, it is searched from library search paths for the sake of
compatibility with GNU ld.
* `-w`, `--no-warnings`:
Suppress warnings and cancel `--fatal-warnings`.
* `--warn-common`, `--no-warn-common`:
Warn about common symbols.
@@ -828,10 +893,10 @@ but as `-o magic`.
segments with two different attributes.
`separate-loadable-segments` adds paddings between segments with different
attributes so that they do not share the same page. This is the default.
attributes so that they do not share the same page.
`separate-code` adds paddings only between executable and non-executable
segments.
segments. This is the default.
`noseparate-code` does not add any paddings between segments.
+11 -7
View File
@@ -8,24 +8,24 @@ set -e
set -x
case "$ID" in
ubuntu | pop | linuxmint | debian | raspbian | neon)
ubuntu | pop | linuxmint | debian | raspbian | neon | zorin)
apt-get update
apt-get install -y cmake gcc g++ clang gdb
;;
fedora | amzn | rhel | centos)
dnf install -y gcc-g++ cmake glibc-static libstdc++-static diffutils util-linux tar
fedora | fedora-* | amzn | rhel | centos)
dnf install -y gcc-c++ cmake glibc-static libstdc++-static diffutils util-linux tar
;;
rocky)
dnf install -y gcc-g++ cmake diffutils util-linux
rocky | ol)
dnf install -y gcc-c++ cmake diffutils util-linux
;;
opensuse-*)
zypper install -y make cmake gcc-c++ glibc-devel-static tar diffutils util-linux
zypper install -y make cmake gcc-c++ glibc-devel-static tar diffutils util-linux gawk
;;
gentoo)
emerge-webrsync
FEATURES='getbinpkg binpkg-request-signature' emerge dev-build/cmake
;;
arch | archarm | artix | endeavouros)
arch | archarm | artix | endeavouros | manjaro | cachyos)
pacman -Sy --needed --noconfirm base-devel cmake util-linux
;;
void)
@@ -42,6 +42,10 @@ clear-linux-os)
almalinux)
dnf install -y gcc-toolset-13-gcc-c++ gcc-toolset-13-libstdc++-devel cmake diffutils
;;
altlinux)
apt-get update
apt-get install -y gcc-c++ make cmake ctest diffutils util-linux
;;
freebsd)
pkg update
pkg install -y cmake bash binutils gcc
+146
View File
@@ -0,0 +1,146 @@
// This file implements the Aho-Corasick algorithm to search multiple
// strings within an input string simultaneously. It is essentially a
// trie with additional links. For details, see
// https://en.wikipedia.org/wiki/Aho-Corasick_algorithm.
//
// We use it for simple glob patterns in version scripts or dynamic
// list files. Here are some examples of glob patterns:
//
// qt_private_api_tag*
// *16QAccessibleCache*
// *32QAbstractFileIconProviderPrivate*
// *17QPixmapIconEngine*
//
// Aho-Corasick can do only substring search, so it cannot handle
// complex glob patterns such as `*foo*bar*`. We handle such patterns
// with the Glob class.
#include "lib.h"
#include <queue>
namespace mold {
bool AhoCorasick::can_handle(std::string_view pat) {
if (pat.starts_with('*'))
pat.remove_prefix(1);
if (pat.ends_with('*'))
pat.remove_suffix(1);
return !pat.empty() && pat.find_first_of("*?[\\") == pat.npos;
}
i32 AhoCorasick::find_child(i32 node, u8 ch) const {
if (node == 0)
return root_children[ch];
for (i32 i = nodes[node].first_child; i != -1; i = nodes[i].next_sibling)
if (nodes[i].ch == ch)
return i;
return -1;
}
i32 AhoCorasick::add_child(i32 node, u8 ch) {
if (i32 child = find_child(node, ch); child != -1)
return child;
i32 child = nodes.size();
i32 sibling = nodes[node].first_child;
nodes.emplace_back();
nodes[child].next_sibling = sibling;
nodes[child].ch = ch;
nodes[node].first_child = child;
if (node == 0)
root_children[ch] = child;
return child;
}
i64 AhoCorasick::find(std::string_view str) {
if (nodes.empty())
return -1;
i32 idx = 0;
i64 val = -1;
auto walk = [&](u8 c) {
for (i32 j = idx; j != -1; j = nodes[j].suffix_link) {
if (i32 child = find_child(j, c); child != -1) {
idx = child;
val = std::max(val, nodes[child].value);
return;
}
}
idx = 0;
};
walk('\0');
for (u8 c : str) {
walk(c);
if (val == max_value)
return val;
}
walk('\0');
return val;
}
bool AhoCorasick::add(std::string_view pat, i64 val) {
assert(can_handle(pat));
max_value = std::max(max_value, val);
if (nodes.empty()) {
root_children.fill(-1);
nodes.emplace_back();
}
i32 idx = 0;
auto walk = [&](u8 c) { idx = add_child(idx, c); };
// We handle "foo" as if "\0foo\0", "*foo" as if "foo\0", "foo*" as
// if "\0foo", and "*foo*" as if "foo". Aho-Corasick can do only
// substring matching, so we use \0 as a beginning/end-of-string
// markers.
if (!pat.starts_with('*'))
walk('\0');
for (u8 c : pat)
if (c != '*')
walk(c);
if (!pat.ends_with('*'))
walk('\0');
nodes[idx].value = std::max(nodes[idx].value, val);
return true;
}
void AhoCorasick::compile() {
if (nodes.empty())
return;
// A failure link may refer to any node at the previous depth, so failure
// links must be constructed breadth-first.
std::queue<i32> queue;
for (i32 child = nodes[0].first_child; child != -1;
child = nodes[child].next_sibling) {
nodes[child].suffix_link = 0;
queue.push(child);
}
while (!queue.empty()) {
i32 idx = queue.front();
queue.pop();
for (i32 child = nodes[idx].first_child; child != -1;
child = nodes[child].next_sibling) {
i32 suffix = nodes[idx].suffix_link;
while (suffix != 0 && find_child(suffix, nodes[child].ch) == -1)
suffix = nodes[suffix].suffix_link;
if (i32 next = find_child(suffix, nodes[child].ch); next != -1)
suffix = next;
nodes[child].suffix_link = suffix;
nodes[child].value = std::max(nodes[child].value, nodes[suffix].value);
queue.push(child);
}
}
}
} // namespace mold
+134
View File
@@ -0,0 +1,134 @@
#include "lib.h"
#ifdef _WIN32
# include <windows.h>
#endif
namespace mold {
static constexpr u32 BLOCK_SIZE = 1024 * 1024;
static constexpr u32 BLOCK_ALIGNMENT = 64;
static constexpr u32 MAX_LOCAL_ALLOC = BLOCK_SIZE / 4;
static constexpr u32 NUM_ARENA_SLOTS = 10;
// Reserved address space is made accessible in chunks of this size.
static constexpr u64 COMMIT_CHUNK = 64 * 1024 * 1024;
static std::atomic<u32> next_arena_slot;
static thread_local u32 positions[NUM_ARENA_SLOTS];
static thread_local u32 ends[NUM_ARENA_SLOTS];
[[noreturn]]
static void memory_error(std::string_view action, u64 size) {
std::cerr << "mold: cannot " << action << ' ' << size
<< " bytes of virtual memory\n";
std::exit(1);
}
ArenaResource::ArenaResource() : arena_slot(next_arena_slot++) {
assert(arena_slot < NUM_ARENA_SLOTS);
// Reserve address space without making it accessible. An inaccessible
// mapping is not charged against the system's commit limit even where
// overcommit is disabled, and fork() does not charge it either. Pages
// are made accessible by commit() as the arena grows.
#ifdef _WIN32
data = (u8 *)VirtualAlloc(nullptr, (SIZE_T)SIZE, MEM_RESERVE, PAGE_NOACCESS);
if (!data)
memory_error("reserve", SIZE);
#else
data = (u8 *)mmap(nullptr, SIZE, PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
if (data == MAP_FAILED)
memory_error("reserve", SIZE);
#endif
#ifdef MADV_HUGEPAGE
// Large links fill the beginning of the arena densely. Transparent huge
// pages reduce address-translation overhead without populating unused pages.
madvise(data, SIZE, MADV_HUGEPAGE);
#endif
}
ArenaResource::~ArenaResource() {
#ifdef _WIN32
VirtualFree(data, 0, MEM_RELEASE);
#else
munmap(data, SIZE);
#endif
}
// Makes the reserved range up to `end` accessible. Memory is committed in
// whole chunks so that the arena grows with few system calls.
void ArenaResource::commit(u64 end) {
std::scoped_lock lock(commit_mu);
u64 cur = committed.load(std::memory_order_relaxed);
if (end <= cur)
return;
u64 next = align_to(end, COMMIT_CHUNK);
#ifdef _WIN32
if (!VirtualAlloc(data + cur, (SIZE_T)(next - cur), MEM_COMMIT, PAGE_READWRITE))
memory_error("commit", next - cur);
#else
if (mprotect(data + cur, next - cur, PROT_READ | PROT_WRITE) == -1)
memory_error("commit", next - cur);
#endif
committed.store(next, std::memory_order_release);
}
void *ArenaResource::allocate(u64 size, u64 alignment) {
assert(std::has_single_bit(alignment));
// ArenaPtr and base-relative indices both encode offsets in four-byte units.
alignment = std::max<u64>(alignment, 4);
// Standard containers make many small allocations. Reserve them from a
// thread-local block to avoid contending on the global bump pointer.
if (size <= MAX_LOCAL_ALLOC && alignment <= BLOCK_ALIGNMENT) {
// Offsets are from the beginning of the arena in four-byte units.
u32 &pos = positions[arena_slot];
u32 &end = ends[arena_slot];
u32 size4 = align_to(size, 4) / 4;
if (pos) {
u32 begin = align_to(pos, alignment / 4);
if (begin <= end && size4 <= end - begin) {
pos = begin + size4;
return data + (u64)begin * 4;
}
}
u8 *begin = (u8 *)allocate_global(BLOCK_SIZE, BLOCK_ALIGNMENT);
u32 begin4 = (begin - data) / 4;
pos = begin4 + size4;
end = begin4 + BLOCK_SIZE / 4;
return begin;
}
return allocate_global(size, alignment);
}
void *ArenaResource::allocate_global(u64 size, u64 alignment) {
u64 old = offset.load(std::memory_order_relaxed);
u64 begin, end;
for (;;) {
u64 padding = -old & (alignment - 1);
if (old > SIZE || padding > SIZE - old || size > SIZE - old - padding) {
std::cerr << "mold: cannot allocate more than " << SIZE
<< " bytes for linker data structures on this host\n";
std::exit(1);
}
begin = old + padding;
end = begin + size;
if (offset.compare_exchange_weak(old, end, std::memory_order_relaxed))
break;
}
if (end > committed.load(std::memory_order_acquire))
commit(end);
return data + begin;
}
} // namespace mold
+64
View File
@@ -0,0 +1,64 @@
// This is the same as std::atomic except that the default memory
// order is relaxed instead of sequential consistency.
#pragma once
#include <atomic>
namespace mold {
template <typename T>
struct Atomic : std::atomic<T> {
static constexpr std::memory_order relaxed = std::memory_order_relaxed;
using std::atomic<T>::atomic;
Atomic(const Atomic<T> &other) : std::atomic<T>(other.load()) {}
Atomic<T> &operator=(const Atomic<T> &other) {
store(other.load());
return *this;
}
void operator=(T val) { store(val); }
operator T() const { return load(); }
void store(T val, std::memory_order order = relaxed) {
std::atomic<T>::store(val, order);
}
T load(std::memory_order order = relaxed) const {
return std::atomic<T>::load(order);
}
T exchange(T val) { return std::atomic<T>::exchange(val, relaxed); }
T fetch_or(T val, std::memory_order order = relaxed) {
return std::atomic<T>::fetch_or(val, order);
}
T fetch_and(T val, std::memory_order order = relaxed) {
return std::atomic<T>::fetch_and(val, order);
}
T operator|=(T val) {
// Avoid exclusive cache-line ownership if all requested bits are set.
T old = load();
return (old & val) == val ? old : fetch_or(val);
}
T operator++() { return std::atomic<T>::fetch_add(1, relaxed) + 1; }
T operator--() { return std::atomic<T>::fetch_sub(1, relaxed) - 1; }
T operator++(int) { return std::atomic<T>::fetch_add(1, relaxed); }
T operator--(int) { return std::atomic<T>::fetch_sub(1, relaxed); }
bool test_and_set() {
// A relaxed load + branch (assuming miss) takes only around 20 cycles,
// while an atomic RMW can easily take hundreds on x86. We note that it's
// common that another thread beat us in marking, so doing an optimistic
// early test tends to improve performance in the ~20% ballpark.
return load() || exchange(true);
}
};
} // namespace mold
-979
View File
@@ -1,979 +0,0 @@
#pragma once
#include "config.h"
#include "integers.h"
#include <array>
#include <atomic>
#include <bit>
#include <bitset>
#include <cassert>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <fcntl.h>
#include <filesystem>
#include <iostream>
#include <mutex>
#include <optional>
#include <span>
#include <sstream>
#include <string>
#include <string_view>
#include <sys/stat.h>
#include <sys/types.h>
#include <tbb/concurrent_vector.h>
#include <tbb/enumerable_thread_specific.h>
#include <tbb/parallel_for.h>
#include <vector>
#ifdef _WIN32
# include <io.h>
#else
# include <sys/mman.h>
# include <unistd.h>
#endif
#define XXH_INLINE_ALL 1
#include "../third-party/xxhash/xxhash.h"
#ifdef NDEBUG
# define unreachable() __builtin_unreachable()
#else
# define unreachable() assert(0 && "unreachable")
#endif
inline uint64_t hash_string(std::string_view str) {
return XXH3_64bits(str.data(), str.size());
}
class HashCmp {
public:
static size_t hash(const std::string_view &k) {
return hash_string(k);
}
static bool equal(const std::string_view &k1, const std::string_view &k2) {
return k1 == k2;
}
};
namespace mold {
namespace ranges = std::ranges;
using namespace std::literals::string_literals;
using namespace std::literals::string_view_literals;
inline char *output_tmpfile;
inline u8 *output_buffer_start = nullptr;
inline u8 *output_buffer_end = nullptr;
extern std::string mold_git_hash;
std::string errno_string();
void cleanup();
void install_signal_handler();
inline u64 combine_hash(u64 a, u64 b) {
return a ^ (b + 0x9e3779b9 + (a << 6) + (a >> 2));
}
//
// Error output
//
// Some C++ stdlibs don't support std::osyncstream even though
// it's is in the C++20 standard. So we implement it ourselves.
class SyncStream {
public:
SyncStream(std::ostream &out) : out(out) {}
~SyncStream() {
emit();
}
template <typename T> SyncStream &operator<<(T &&val) {
ss << std::forward<T>(val);
return *this;
}
void emit() {
if (emitted)
return;
std::scoped_lock lock(mu);
out << ss.str() << '\n';
emitted = true;
}
private:
std::ostream &out;
std::stringstream ss;
bool emitted = false;
static inline std::mutex mu;
};
template <typename Context>
class Out {
public:
Out(Context &ctx) {}
template <typename T> Out &operator<<(T &&val) {
out << std::forward<T>(val);
return *this;
}
private:
SyncStream out{std::cout};
};
static std::string_view fatal_mono = "mold: fatal: ";
static std::string_view fatal_color = "mold: \033[0;1;31mfatal:\033[0m ";
static std::string_view error_mono = "mold: error: ";
static std::string_view error_color = "mold: \033[0;1;31merror:\033[0m ";
static std::string_view warning_mono = "mold: warning: ";
static std::string_view warning_color = "mold: \033[0;1;35mwarning:\033[0m ";
template <typename Context>
class Fatal {
public:
Fatal(Context &ctx) {
out << (ctx.arg.color_diagnostics ? fatal_color : fatal_mono);
}
[[noreturn]] ~Fatal() {
out.emit();
cleanup();
_exit(1);
}
template <typename T> Fatal &operator<<(T &&val) {
out << std::forward<T>(val);
return *this;
}
private:
SyncStream out{std::cerr};
};
template <typename Context>
class Error {
public:
Error(Context &ctx) {
if (ctx.arg.noinhibit_exec) {
out << (ctx.arg.color_diagnostics ? warning_color : warning_mono);
} else {
out << (ctx.arg.color_diagnostics ? error_color : error_mono);
ctx.has_error = true;
}
}
template <typename T> Error &operator<<(T &&val) {
out << std::forward<T>(val);
return *this;
}
private:
SyncStream out{std::cerr};
};
template <typename Context>
class Warn {
public:
Warn(Context &ctx) {
if (ctx.arg.suppress_warnings)
return;
out.emplace(std::cerr);
if (ctx.arg.fatal_warnings) {
*out << (ctx.arg.color_diagnostics ? error_color : error_mono);
ctx.has_error = true;
} else {
*out << (ctx.arg.color_diagnostics ? warning_color : warning_mono);
}
}
template <typename T> Warn &operator<<(T &&val) {
if (out)
*out << std::forward<T>(val);
return *this;
}
private:
std::optional<SyncStream> out;
};
//
// Atomics
//
// This is the same as std::atomic except that the default memory
// order is relaxed instead of sequential consistency.
template <typename T>
struct Atomic : std::atomic<T> {
static constexpr std::memory_order relaxed = std::memory_order_relaxed;
using std::atomic<T>::atomic;
Atomic(const Atomic<T> &other) : std::atomic<T>(other.load()) {}
Atomic<T> &operator=(const Atomic<T> &other) {
store(other.load());
return *this;
}
void operator=(T val) { store(val); }
operator T() const { return load(); }
void store(T val, std::memory_order order = relaxed) {
std::atomic<T>::store(val, order);
}
T load(std::memory_order order = relaxed) const {
return std::atomic<T>::load(order);
}
T exchange(T val) { return std::atomic<T>::exchange(val, relaxed); }
T operator|=(T val) { return std::atomic<T>::fetch_or(val, relaxed); }
T operator++() { return std::atomic<T>::fetch_add(1, relaxed) + 1; }
T operator--() { return std::atomic<T>::fetch_sub(1, relaxed) - 1; }
T operator++(int) { return std::atomic<T>::fetch_add(1, relaxed); }
T operator--(int) { return std::atomic<T>::fetch_sub(1, relaxed); }
bool test_and_set() {
// A relaxed load + branch (assuming miss) takes only around 20 cycles,
// while an atomic RMW can easily take hundreds on x86. We note that it's
// common that another thread beat us in marking, so doing an optimistic
// early test tends to improve performance in the ~20% ballpark.
return load() || exchange(true);
}
};
//
// mimalloc.cc
//
void set_mimalloc_options();
//
// perf.cc
//
// Counter is used to collect statistics numbers.
class Counter {
public:
Counter(std::string_view name, i64 value = 0) : name(name), values(value) {
static std::mutex mu;
std::scoped_lock lock(mu);
instances.push_back(this);
}
Counter &operator++(int) {
if (enabled) [[unlikely]]
values.local()++;
return *this;
}
Counter &operator+=(int delta) {
if (enabled) [[unlikely]]
values.local() += delta;
return *this;
}
static void print();
static inline bool enabled = false;
private:
i64 get_value();
std::string_view name;
tbb::enumerable_thread_specific<i64> values;
static inline std::vector<Counter *> instances;
};
// Timer and TimeRecord records elapsed time (wall clock time)
// used by each pass of the linker.
struct TimerRecord {
TimerRecord(std::string name, TimerRecord *parent = nullptr);
void stop();
std::string name;
TimerRecord *parent;
tbb::concurrent_vector<TimerRecord *> children;
i64 start;
i64 end;
i64 user;
i64 sys;
bool stopped = false;
};
void
print_timer_records(tbb::concurrent_vector<std::unique_ptr<TimerRecord>> &);
template <typename Context>
class Timer {
public:
Timer(Context &ctx, std::string name, Timer *parent = nullptr) {
record = new TimerRecord(name, parent ? parent->record : nullptr);
ctx.timer_records.emplace_back(record);
}
Timer(const Timer &) = delete;
~Timer() {
record->stop();
}
void stop() {
record->stop();
}
private:
TimerRecord *record;
};
//
// Utility functions
//
// Some C++ libraries haven't implemented std::has_single_bit yet.
inline bool has_single_bit(u64 val) {
return std::popcount(val) == 1;
}
// Some C++ libraries haven't implemented std::bit_ceil yet.
inline u64 bit_ceil(u64 val) {
if (has_single_bit(val))
return val;
return 1LL << (64 - std::countl_zero(val));
}
inline u64 align_to(u64 val, u64 align) {
if (align == 0)
return val;
assert(has_single_bit(align));
return (val + align - 1) & ~(align - 1);
}
inline u64 align_down(u64 val, u64 align) {
assert(has_single_bit(align));
return val & ~(align - 1);
}
inline u64 bit(u64 val, i64 pos) {
return (val >> pos) & 1;
};
// Returns [hi:lo] bits of val.
inline u64 bits(u64 val, u64 hi, u64 lo) {
return (val >> lo) & ((1LL << (hi - lo + 1)) - 1);
}
// Cast val to a signed N bit integer.
// For example, sign_extend(x, 32) == (i32)x for any integer x.
inline i64 sign_extend(u64 val, i64 n) {
return (i64)(val << (64 - n)) >> (64 - n);
}
inline bool is_int(u64 val, i64 n) {
return sign_extend(val, n) == val;
}
template <typename T, typename Compare = std::less<T>>
void update_minimum(std::atomic<T> &atomic, u64 new_val, Compare cmp = {}) {
T old_val = atomic.load(std::memory_order_relaxed);
while (cmp(new_val, old_val) &&
!atomic.compare_exchange_weak(old_val, new_val,
std::memory_order_relaxed));
}
template <typename T, typename Compare = std::less<T>>
void update_maximum(std::atomic<T> &atomic, u64 new_val, Compare cmp = {}) {
T old_val = atomic.load(std::memory_order_relaxed);
while (cmp(old_val, new_val) &&
!atomic.compare_exchange_weak(old_val, new_val,
std::memory_order_relaxed));
}
template <typename T>
inline void append(std::vector<T> &x, const auto &y) {
x.insert(x.end(), y.begin(), y.end());
}
template <typename T>
inline std::vector<T> flatten(std::vector<std::vector<T>> &vec) {
i64 size = 0;
for (std::vector<T> &v : vec)
size += v.size();
std::vector<T> ret;
ret.reserve(size);
for (std::vector<T> &v : vec)
append(ret, v);
return ret;
}
template <typename T>
inline void remove_duplicates(std::vector<T> &vec) {
vec.erase(std::unique(vec.begin(), vec.end()), vec.end());
}
inline i64 write_string(void *buf, std::string_view str) {
memcpy(buf, str.data(), str.size());
*((u8 *)buf + str.size()) = '\0';
return str.size() + 1;
}
template <typename T>
inline void write_vector(void *buf, const std::vector<T> &vec) {
if (!vec.empty())
memcpy(buf, vec.data(), vec.size() * sizeof(T));
}
inline void encode_uleb(std::vector<u8> &vec, u64 val) {
do {
u8 byte = val & 0x7f;
val >>= 7;
vec.push_back(val ? (byte | 0x80) : byte);
} while (val);
}
inline void encode_sleb(std::vector<u8> &vec, i64 val) {
for (;;) {
u8 byte = val & 0x7f;
val >>= 7;
bool neg = (byte & 0x40);
if ((val == 0 && !neg) || (val == -1 && neg)) {
vec.push_back(byte);
break;
}
vec.push_back(byte | 0x80);
}
}
inline i64 write_uleb(u8 *buf, u64 val) {
i64 i = 0;
do {
u8 byte = val & 0x7f;
val >>= 7;
buf[i++] = val ? (byte | 0x80) : byte;
} while (val);
return i;
}
inline u64 read_uleb(u8 **buf) {
u64 val = 0;
u8 shift = 0;
u8 byte;
do {
byte = *(*buf)++;
val |= (byte & 0x7f) << shift;
shift += 7;
} while (byte & 0x80);
return val;
}
inline u64 read_uleb(u8 *buf) {
u8 *tmp = buf;
return read_uleb(&tmp);
}
inline i64 read_sleb(u8 **buf) {
u64 val = 0;
u8 shift = 0;
u8 byte;
do {
byte = *(*buf)++;
val |= (byte & 0x7f) << shift;
shift += 7;
} while (byte & 0x80);
return sign_extend(val, shift);
}
inline i64 read_sleb(u8 *buf) {
u8 *tmp = buf;
return read_sleb(&tmp);
}
inline u64 read_uleb(std::string_view *str) {
u8 *start = (u8 *)str->data();
u8 *ptr = start;
u64 val = read_uleb(&ptr);
*str = str->substr(ptr - start);
return val;
}
inline u64 read_uleb(std::string_view str) {
std::string_view tmp = str;
return read_uleb(&tmp);
}
inline i64 uleb_size(u64 val) {
for (int i = 1; i < 9; i++)
if (val < (1LL << (7 * i)))
return i;
return 9;
}
inline void overwrite_uleb(u8 *loc, u64 val) {
while (*loc & 0b1000'0000) {
*loc++ = 0b1000'0000 | (val & 0b0111'1111);
val >>= 7;
}
*loc = val & 0b0111'1111;
}
static inline void pause() {
#if defined(__x86_64__)
asm volatile("pause");
#elif defined(__aarch64__)
asm volatile("yield");
#elif defined(__ARM_ARCH_7A__) || defined(__ARM_ARCH_8A__)
asm volatile("yield");
#endif
}
//
// Concurrent Map
//
// This is an implementation of a fast concurrent hash map. Unlike
// ordinary hash tables, this impl just aborts if it becomes full.
// So you need to give a correct estimation of the final size before
// using it. We use this hash map to uniquify pieces of data in
// mergeable sections.
//
// We've implemented this ourselves because the performance of
// conrurent hash map is critical for our linker.
template <typename T>
class ConcurrentMap {
public:
ConcurrentMap() = default;
ConcurrentMap(i64 nbuckets) {
resize(nbuckets);
}
~ConcurrentMap() {
if (entries) {
#ifdef _WIN32
_aligned_free(entries);
#else
munmap(entries, sizeof(Entry) * nbuckets);
#endif
}
}
// In order to avoid unnecessary cache-line false sharing, we want
// to make this object to be aligned to a reasonably large
// power-of-two address.
struct alignas(32) Entry {
Atomic<const char *> key;
u32 keylen;
T value;
};
void resize(i64 nbuckets) {
assert(!entries);
this->nbuckets = std::max<i64>(MIN_NBUCKETS, bit_ceil(nbuckets));
i64 bufsize = sizeof(Entry) * this->nbuckets;
// Allocate a zero-initialized buffer. We use mmap() if available
// because it's faster than malloc() and memset().
#ifdef _WIN32
entries = (Entry *)_aligned_malloc(bufsize, alignof(Entry));
memset((void *)entries, 0, bufsize);
#else
entries = (Entry *)mmap(nullptr, bufsize, PROT_READ | PROT_WRITE,
MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
#endif
}
std::pair<T *, bool> insert(std::string_view key, u64 hash, const T &val) {
assert(has_single_bit(nbuckets));
u64 begin = hash & (nbuckets - 1);
u64 mask = nbuckets / NUM_SHARDS - 1;
for (i64 i = 0; i < MAX_RETRY; i++) {
u64 idx = (begin & ~mask) | ((begin + i) & mask);
Entry &ent = entries[idx];
// It seems avoiding compare-and-swap is faster overall at least
// on my Zen4 machine, so do it.
if (const char *ptr = ent.key.load(std::memory_order_acquire);
ptr != nullptr && ptr != (char *)-1) {
if (key == std::string_view(ptr, ent.keylen))
return {&ent.value, false};
continue;
}
// Otherwise, use CAS to atomically claim the ownership of the slot.
const char *ptr = nullptr;
bool claimed = ent.key.compare_exchange_strong(ptr, (char *)-1,
std::memory_order_acquire);
// If we successfully claimed the ownership of the slot,
// copy values to it.
if (claimed) {
new (&ent.value) T(val);
ent.keylen = key.size();
ent.key.store(key.data(), std::memory_order_release);
return {&ent.value, true};
}
// If someone is copying values to the slot, do busy wait.
while (ptr == (char *)-1) {
pause();
ptr = ent.key.load(std::memory_order_acquire);
}
// If the same key is already present, this is the slot we are
// looking for.
if (key == std::string_view(ptr, ent.keylen))
return {&ent.value, false};
}
std::cerr << "ConcurrentMap is full\n";
abort();
}
i64 get_idx(T *value) const {
uintptr_t addr = (uintptr_t)value - (uintptr_t)value % sizeof(Entry);
return (Entry *)addr - entries;
}
// Return a list of map entries sorted in a deterministic order.
std::vector<Entry *> get_sorted_entries(i64 shard_idx) {
if (nbuckets == 0)
return {};
i64 shard_size = nbuckets / NUM_SHARDS;
i64 begin = shard_idx * shard_size;
i64 end = begin + shard_size;
i64 sz = 0;
for (i64 i = begin; i < end; i++)
if (entries[i].key)
sz++;
std::vector<Entry *> vec;
vec.reserve(sz);
// Since the shard is circular, we need to handle the last entries
// as if they were next to the first entries.
while (entries[end - 1].key)
vec.push_back(entries + --end);
// Find entries contiguous in the buckets and sort them.
i64 last = 0;
for (i64 i = begin; i < end;) {
while (i < end && entries[i].key)
vec.push_back(entries + i++);
std::sort(vec.begin() + last, vec.end(), [](Entry *a, Entry *b) {
if (a->keylen != b->keylen)
return a->keylen < b->keylen;
return memcmp(a->key, b->key, a->keylen) < 0;
});
last = vec.size();
while (i < end && !entries[i].key)
i++;
}
return vec;
}
std::vector<Entry *> get_sorted_entries_all() {
std::vector<std::vector<Entry *>> vec(NUM_SHARDS);
tbb::parallel_for((i64)0, NUM_SHARDS, [&](i64 i) {
vec[i] = get_sorted_entries(i);
});
return flatten(vec);
}
static constexpr i64 MIN_NBUCKETS = 4096;
static constexpr i64 NUM_SHARDS = 16;
static constexpr i64 MAX_RETRY = 256;
Entry *entries = nullptr;
u64 nbuckets = 0;
};
//
// random.cc
//
void get_random_bytes(u8 *buf, i64 size);
//
// hyperloglog.cc
//
class HyperLogLog {
public:
void insert(u64 hash) {
update_maximum(buckets[hash & (NBUCKETS - 1)], std::countl_zero(hash) + 1);
}
i64 get_cardinality() const;
void merge(const HyperLogLog &other) {
for (i64 i = 0; i < NBUCKETS; i++)
update_maximum(buckets[i], other.buckets[i]);
}
private:
static constexpr i64 NBUCKETS = 2048;
static constexpr double ALPHA = 0.79402;
Atomic<u8> buckets[NBUCKETS];
};
//
// glob.cc
//
class Glob {
typedef enum { STRING, STAR, QUESTION, BRACKET } Kind;
struct Element {
Element(Kind k) : kind(k) {}
Kind kind;
std::string str;
std::bitset<256> bitset;
};
public:
static std::optional<Glob> compile(std::string_view pat);
bool match(std::string_view str);
private:
Glob(std::vector<Element> &&vec) : elements(vec) {}
static bool do_match(std::string_view str, std::span<Element> elements);
std::vector<Element> elements;
};
//
// multi-glob.cc
//
class MultiGlob {
public:
bool add(std::string_view pat, i64 val);
bool empty() const { return strings.empty(); }
std::optional<i64> find(std::string_view str);
private:
struct TrieNode {
i64 value = -1;
TrieNode *suffix_link = nullptr;
std::unique_ptr<TrieNode> children[256];
};
void compile();
void fix_suffix_links(TrieNode &node);
void fix_values();
i64 find_aho_corasick(std::string_view str);
std::vector<std::string> strings;
std::unique_ptr<TrieNode> root;
std::vector<std::pair<Glob, i64>> globs;
std::once_flag once;
bool is_compiled = false;
bool prefix_match = false;
};
//
// filepath.cc
//
inline std::filesystem::path path_dirname(std::string_view path) {
return std::filesystem::path(path).parent_path();
}
inline std::string path_filename(std::string_view path) {
return std::filesystem::path(path).filename().string();
}
inline std::string path_clean(std::string_view path) {
return std::filesystem::path(path).lexically_normal().string();
}
std::string get_self_path();
//
// demangle.cc
//
std::optional<std::string_view> demangle_cpp(std::string_view name);
std::optional<std::string_view> demangle_rust(std::string_view name);
//
// jbos.cc
//
void acquire_global_lock();
void release_global_lock();
//
// crc32.cc
//
u32 compute_crc32(u32 crc, u8 *buf, i64 len);
std::vector<u8> crc32_solve(u32 current, u32 desired);
//
// compress.cc
//
class Compressor {
public:
virtual void write_to(u8 *buf) = 0;
virtual ~Compressor() {}
i64 compressed_size = 0;
};
class ZlibCompressor : public Compressor {
public:
ZlibCompressor(u8 *buf, i64 size);
void write_to(u8 *buf) override;
private:
std::vector<std::vector<u8>> shards;
u64 checksum = 0;
};
class ZstdCompressor : public Compressor {
public:
ZstdCompressor(u8 *buf, i64 size);
void write_to(u8 *buf) override;
private:
std::vector<std::vector<u8>> shards;
};
//
// tar.cc
//
// TarFile is a class to create a tar file.
//
// If you pass `--repro` to mold, mold collects all input files and
// put them into `<output-file-path>.repro.tar`, so that it is easy to
// run the same command with the same command line arguments.
class TarWriter {
public:
static std::unique_ptr<TarWriter>
open(std::string output_path, std::string basedir);
~TarWriter();
void append(std::string path, std::string_view data);
private:
TarWriter(FILE *out, std::string basedir) : out(out), basedir(basedir) {}
FILE *out = nullptr;
std::string basedir;
};
//
// Memory-mapped file
//
// MappedFile represents an mmap'ed input file.
// mold uses mmap-IO only.
class MappedFile {
public:
~MappedFile() { unmap(); }
void unmap();
void close_fd();
void reopen_fd(const std::string &path);
template <typename Context>
MappedFile *slice(Context &ctx, std::string name, u64 start, u64 size) {
MappedFile *mf = new MappedFile;
mf->name = name;
mf->data = data + start;
mf->size = size;
mf->parent = this;
ctx.mf_pool.emplace_back(mf);
return mf;
}
std::string_view get_contents() {
return std::string_view((char *)data, size);
}
i64 get_offset() const {
return parent ? (data - parent->data + parent->get_offset()) : 0;
}
// Returns a string that uniquely identify a file that is possibly
// in an archive.
std::string get_identifier() const {
if (parent) {
// We use the file offset within an archive as an identifier
// because archive members may have the same name.
return parent->name + ":" + std::to_string(get_offset());
}
if (thin_parent) {
// If this is a thin archive member, the filename part is
// guaranteed to be unique.
return thin_parent->name + ":" + name;
}
return name;
}
std::string name;
u8 *data = nullptr;
i64 size = 0;
bool given_fullpath = true;
MappedFile *parent = nullptr;
MappedFile *thin_parent = nullptr;
// For --dependency-file
bool is_dependency = true;
#ifdef _WIN32
HANDLE fd = INVALID_HANDLE_VALUE;
#else
int fd = -1;
#endif
};
MappedFile *open_file_impl(const std::string &path, std::string &error);
template <typename Context>
MappedFile *open_file(Context &ctx, std::string path) {
if (path.starts_with('/') && !ctx.arg.chroot.empty())
path = ctx.arg.chroot + "/" + path_clean(path);
std::string error;
MappedFile *mf = open_file_impl(path, error);
if (!error.empty())
Fatal(ctx) << error;
if (mf)
ctx.mf_pool.emplace_back(mf);
return mf;
}
template <typename Context>
MappedFile *must_open_file(Context &ctx, std::string path) {
MappedFile *mf = open_file(ctx, path);
if (!mf)
Fatal(ctx) << "cannot open " << path << ": " << errno_string();
return mf;
}
} // namespace mold
+42 -53
View File
@@ -14,7 +14,7 @@
// a little bit. However, if a shard size is large enough, that loss
// is negligible in practice.
#include "common.h"
#include "lib.h"
#include <tbb/parallel_for_each.h>
#include <zlib.h>
@@ -30,42 +30,41 @@ namespace mold {
static constexpr i64 SHARD_SIZE = 1024 * 1024;
static std::vector<std::string_view> split(std::string_view input) {
std::vector<std::string_view> shards;
while (input.size() >= SHARD_SIZE) {
shards.push_back(input.substr(0, SHARD_SIZE));
input = input.substr(SHARD_SIZE);
}
if (!input.empty())
shards.push_back(input);
return shards;
Compressor::~Compressor() {
for (std::span<u8> shard : shards)
delete[] shard.data();
}
static std::vector<u8> zlib_compress(std::string_view input) {
// Initialize zlib stream. Since debug info is generally compressed
// pretty well with lower compression levels, we chose compression
// level 1.
z_stream strm;
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
strm.opaque = Z_NULL;
static std::vector<std::span<u8>> split(std::span<u8> input) {
std::vector<std::span<u8>> vec;
while (!input.empty()) {
i64 sz = std::min<i64>(SHARD_SIZE, input.size());
vec.push_back(input.subspan(0, sz));
input = input.subspan(sz);
}
return vec;
}
CHECK(deflateInit2(&strm, 1, Z_DEFLATED, -15, 8, Z_DEFAULT_STRATEGY));
static std::span<u8> zlib_compress(std::span<u8> input, int level) {
// Initialize zlib stream. Since debug info is generally compressed
// pretty well with lower compression levels, the default level is 1.
z_stream strm = {};
CHECK(deflateInit2(&strm, level, Z_DEFLATED, -15, 8, Z_DEFAULT_STRATEGY));
// Set an input buffer
strm.avail_in = input.size();
strm.next_in = (u8 *)input.data();
strm.next_in = input.data();
// Set an output buffer. deflateBound() returns an upper bound
// on the compression size. +16 for Z_SYNC_FLUSH.
std::vector<u8> buf(deflateBound(&strm, strm.avail_in) + 16);
i64 bufsize = deflateBound(&strm, strm.avail_in) + 16;
u8 *buf = new u8[bufsize];
// Compress data. It writes all compressed bytes except the last
// partial byte, so up to 7 bits can be held to be written to the
// buffer.
strm.avail_out = buf.size();
strm.next_out = buf.data();
strm.avail_out = bufsize;
strm.next_out = buf;
CHECK(deflate(&strm, Z_BLOCK));
// This is a workaround for libbacktrace before 2022-04-06.
@@ -84,29 +83,26 @@ static std::vector<u8> zlib_compress(std::string_view input) {
// that the bit position is 7 in the next byte.
//
// https://github.com/ianlancetaylor/libbacktrace/pull/87
int nbits;
int nbits = 0;
deflatePending(&strm, Z_NULL, &nbits);
if (nbits == 5)
CHECK(deflatePrime(&strm, 10, 2));
CHECK(deflate(&strm, Z_SYNC_FLUSH));
assert(strm.avail_out > 0);
buf.resize(buf.size() - strm.avail_out);
buf.shrink_to_fit();
deflateEnd(&strm);
return buf;
return {buf, (size_t)(bufsize - strm.avail_out)};
}
ZlibCompressor::ZlibCompressor(u8 *buf, i64 size) {
std::string_view input{(char *)buf, (size_t)size};
std::vector<std::string_view> inputs = split(input);
std::vector<u64> adlers(inputs.size());
ZlibCompressor::ZlibCompressor(u8 *buf, i64 size, i64 level) {
std::vector<std::span<u8>> inputs = split(std::span(buf, size));
std::vector<u32> adlers(inputs.size());
shards.resize(inputs.size());
// Compress each shard
tbb::parallel_for((i64)0, (i64)inputs.size(), [&](i64 i) {
adlers[i] = adler32(1, (u8 *)inputs[i].data(), inputs[i].size());
shards[i] = zlib_compress(inputs[i]);
std::span<u8> in = inputs[i];
adlers[i] = adler32(1, in.data(), in.size());
shards[i] = zlib_compress(in, level);
});
// Combine checksums
@@ -116,7 +112,7 @@ ZlibCompressor::ZlibCompressor(u8 *buf, i64 size) {
// Comput the total size
compressed_size = 8; // the header and the trailer
for (std::vector<u8> &shard : shards)
for (std::span<u8> &shard : shards)
compressed_size += shard.size();
}
@@ -127,7 +123,7 @@ void ZlibCompressor::write_to(u8 *buf) {
// Copy compressed data
std::vector<i64> offsets(shards.size());
offsets[0] = 2; // +2 for header
offsets[0] = 2; // +2 for the header
for (i64 i = 1; i < shards.size(); i++)
offsets[i] = offsets[i - 1] + shards[i - 1].size();
@@ -139,35 +135,28 @@ void ZlibCompressor::write_to(u8 *buf) {
u8 *end = buf + compressed_size;
end[-6] = 3;
end[-5] = 0;
// Write a checksum
*(ub32 *)(end - 4) = checksum;
}
static std::vector<u8> zstd_compress(std::string_view input) {
std::vector<u8> buf(ZSTD_COMPRESSBOUND(input.size()));
constexpr int LEVEL = 3; // compression level; must be between 1 to 22
size_t sz = ZSTD_compress(buf.data(), buf.size(), input.data(), input.size(),
LEVEL);
static std::span<u8> zstd_compress(std::span<u8> input, int level) {
i64 bufsize = ZSTD_COMPRESSBOUND(input.size());
u8 *buf = new u8[bufsize];
size_t sz = ZSTD_compress(buf, bufsize, input.data(), input.size(), level);
assert(!ZSTD_isError(sz));
buf.resize(sz);
buf.shrink_to_fit();
return buf;
return {buf, sz};
}
ZstdCompressor::ZstdCompressor(u8 *buf, i64 size) {
std::string_view input{(char *)buf, (size_t)size};
std::vector<std::string_view> inputs = split(input);
ZstdCompressor::ZstdCompressor(u8 *buf, i64 size, i64 level) {
std::vector<std::span<u8>> inputs = split(std::span(buf, size));
shards.resize(inputs.size());
// Compress each shard
tbb::parallel_for((i64)0, (i64)inputs.size(), [&](i64 i) {
shards[i] = zstd_compress(inputs[i]);
shards[i] = zstd_compress(inputs[i], level);
});
compressed_size = 0;
for (std::vector<u8> &shard : shards)
for (std::span<u8> &shard : shards)
compressed_size += shard.size();
}
+27 -4
View File
@@ -1,7 +1,30 @@
#define MOLD_VERSION "@mold_VERSION@"
#define MOLD_LIBDIR "@CMAKE_INSTALL_FULL_LIBDIR@"
#define MOLD_FIRST_TARGET @MOLD_FIRST_TARGET@
#cmakedefine HAVE_MADVISE 1
#cmakedefine HAVE_UNAME 1
#cmakedefine MOLD_USE_MIMALLOC 1
#cmakedefine MOLD_USE_SYSTEM_MIMALLOC 1
#cmakedefine01 HAVE_FALLOCATE
#cmakedefine01 HAVE_MADVISE
#cmakedefine01 HAVE_UNAME
#cmakedefine01 MOLD_USE_MIMALLOC
#cmakedefine01 MOLD_USE_SYSTEM_MIMALLOC
#cmakedefine01 HAVE_TARGET_X86_64
#cmakedefine01 HAVE_TARGET_I386
#cmakedefine01 HAVE_TARGET_ARM64LE
#cmakedefine01 HAVE_TARGET_ARM64BE
#cmakedefine01 HAVE_TARGET_ARM32LE
#cmakedefine01 HAVE_TARGET_ARM32BE
#cmakedefine01 HAVE_TARGET_RV32LE
#cmakedefine01 HAVE_TARGET_RV32BE
#cmakedefine01 HAVE_TARGET_RV64LE
#cmakedefine01 HAVE_TARGET_RV64BE
#cmakedefine01 HAVE_TARGET_PPC32
#cmakedefine01 HAVE_TARGET_PPC64V1
#cmakedefine01 HAVE_TARGET_PPC64V2
#cmakedefine01 HAVE_TARGET_S390X
#cmakedefine01 HAVE_TARGET_SPARC64
#cmakedefine01 HAVE_TARGET_M68K
#cmakedefine01 HAVE_TARGET_SH4LE
#cmakedefine01 HAVE_TARGET_SH4BE
#cmakedefine01 HAVE_TARGET_LOONGARCH64
#cmakedefine01 HAVE_TARGET_LOONGARCH32
+1 -1
View File
@@ -1,4 +1,4 @@
#include "common.h"
#include "lib.h"
#include <tbb/parallel_for_each.h>
#include <zlib.h>
+1 -1
View File
@@ -1,4 +1,4 @@
#include "common.h"
#include "lib.h"
#include <cstdlib>
+1 -1
View File
@@ -1,4 +1,4 @@
#include "common.h"
#include "lib.h"
#include <filesystem>
#include <sys/stat.h>
+50
View File
@@ -0,0 +1,50 @@
#!/usr/bin/env python3
# Work around a deadlock in portage's binary package extraction.
#
# tar_stream_reader.close() joins its stdin-feeder thread and then waits
# for the external decompressor to exit without draining the
# decompressor's remaining stdout. tarfile stops reading at the tar
# end-of-archive marker, so trailing archive padding is left in the
# pipe. If that residue exceeds the pipe buffer, the decompressor
# blocks writing forever and close() never returns, freezing the whole
# emerge. Small pipe buffers make this reachable in practice: the
# kernel shrinks new pipes to one page once a user exceeds
# fs.pipe-user-pages-soft, which our 80 parallel containers do.
#
# The fix is to drain the decompressor's stdout to EOF before joining
# and waiting. This script patches the installed gpkg.py at container
# image build time and fails loudly if the code no longer matches, so
# a portage upgrade that changes (or fixes) this code is noticed.
import sys
import portage.gpkg
path = portage.gpkg.__file__
src = open(path).read()
old = """ if self.proc is not None:
self.thread.join()
"""
new = """ if self.proc is not None:
# Drain remaining decompressor output before joining and
# waiting; otherwise the decompressor can block forever on
# a full stdout pipe. Injected by mold's lib/gentoo-test.sh.
try:
while self.read_io.read(65536):
pass
except Exception:
pass
self.thread.join()
"""
if src.count(old) != 1:
sys.exit(f"gpkg workaround: expected pattern not found in {path}; "
"portage changed, update lib/gentoo-gpkg-workaround.py")
with open(path, "w") as f:
f.write(src.replace(old, new))
import py_compile
py_compile.compile(path, doraise=True)
print(f"gpkg deadlock workaround applied to {path}")
+145 -28
View File
@@ -2,10 +2,25 @@
#
# This test script takes a Gentoo package name and tries to build it
# using mold in a Podman environment. We chose Gentoo Linux as a test
# target, because its source-based package allows us to build programs
# target, because its source-based packages allow us to build programs
# locally and run their test suites without any hassle.
#
# You can get a complete list of Gentoo packages availalbe for testing
# We first build a package with mold acting as /usr/bin/ld. Only if
# that fails, we build the same package again with GNU ld to tell
# whether the failure is mold's fault. The package's own test suite
# runs in both cases; FEATURES=test is scoped to just the package
# under test via package.env, because applying it to the entire
# dependency graph makes a large fraction of the tree unresolvable.
# Results are classified into three directories:
#
# success: the package built fine with mold (or, for a control
# build, with GNU ld)
# failure: the package built with GNU ld but not with mold, which
# means it hit a mold bug
# broken: the package didn't build even with GNU ld, so the
# failure is not mold's fault
#
# You can get a complete list of Gentoo packages available for testing
# with the following command:
#
# podman run --rm mold-gentoo emerge --color n -s '' | \
@@ -20,50 +35,152 @@ fi
set -x
# Create a Podman image
if ! podman image ls mold-gentoo | grep -q mold-gentoo; then
set -e
cat <<EOF | podman build -t mold-gentoo -
FROM docker.io/gentoo/stage3
RUN emerge-webrsync
RUN echo 'USE="X ssl elogind -systemd corefonts truetype jpeg jpeg2k tiff zstd static-libs binary -perl"' >> /etc/portage/make.conf && \
echo 'ACCEPT_KEYWORDS="~amd64"' >> /etc/portage/make.conf && \
echo 'ACCEPT_LICENSE="* -@EULA"' >> /etc/portage/make.conf && \
echo 'FEATURES="\${FEATURE} noclean nostrip ccache -ipc-sandbox -network-sandbox -pid-sandbox -sandbox"' >> /etc/portage/make.conf && \
echo 'CCACHE_DIR="/ccache"' >> /etc/portage/make.conf && \
FEATURES='getbinpkg binpkg-request-signature' emerge gdb lld llvm-core/clang vim emacs strace ccache xeyes dev-build/cmake dev-vcs/git && \
rm -rf /var/tmp/portage
EOF
set +e
# Test results are keyed by the commit hash embedded in the mold
# binary, so refuse to run with a binary that doesn't have one.
git_hash=$(./dist/mold --version | perl -ne '/\(([0-9a-f]{40})\b/ and print $1;')
if [ "$git_hash" = "" ]; then
echo "Cannot get a git hash from ./dist/mold --version. Run this script at the mold source root, with dist/mold built from a git checkout."
exit 1
fi
git_hash=$(./dist/mold --version | perl -ne '/\((\w+)/; print $1;')
cache_dir=/var/cache/mold-gentoo
# Create a Podman image. The lock prevents parallel invocations of
# this script from racing to build the same image.
#
# A few notes on the container configuration:
#
# - We accept ~amd64 (testing) packages to test the newest version of
# everything. The stage3 image is built for the stable profile, so
# we update the whole system to ~amd64 once here; without that,
# half-upgraded packages (e.g. perl and its virtuals) cause
# unsolvable dependency conflicts in every container.
#
# - EMERGE_DEFAULT_OPTS makes emerge automatically apply the USE flag
# adjustments it would otherwise just print and give up on. An
# ephemeral container has no /etc/portage/package.use accumulated by
# an administrator, so without this, thousands of packages are
# unbuildable out of the box.
#
# - freetype and harfbuzz mutually depend on each other, so we
# pre-install them with the documented two-step bootstrap. During
# that step, pillow is built without truetype support to break
# another dependency cycle (docutils -> pillow -> harfbuzz -> glib
# -> docutils). The override is removed afterwards; once the cycle
# participants are installed, they satisfy buildtime dependencies
# and the cycle can no longer form.
#
# - qtbase's test suite requires icu (REQUIRED_USE), which autounmask
# doesn't resolve on its own, hence the package.use entry.
#
# - gcc doesn't auto-select a newly installed major version, so we
# switch to the newest slot explicitly after the world update.
#
# - lib/gentoo-gpkg-workaround.py patches a deadlock in portage's
# binary package extraction. See that file for details.
#
# - static-libs is not in USE because it contradicts REQUIRED_USE
# constraints in some packages (e.g. lvm2 forbids static-libs with
# udev), and building static archives doesn't exercise the linker
# anyway. Where a dependency genuinely needs it, emerge re-enables
# it per package via autounmask.
#
# - /etc/portage/env/test.conf allows marking a single package for
# FEATURES=test through package.env, which is how we scope test
# suites to just the package under test.
#
# - The image build compiles a couple hundred packages from source,
# because the Gentoo binhost only carries stable versions built with
# default USE flags, which rarely match our ~amd64/USE combination.
# To pay that cost only once, the build publishes what it compiles
# to the GNU ld binary package pool and reuses it, so a rebuilt
# image only compiles packages whose version has changed since the
# last build. The toolchain step still uses the binhost, where our
# configuration does match (e.g. clang). Since it verifies binhost
# package signatures and our own pool is unsigned, it runs with a
# private PKGDIR so the two never mix.
(
flock 9
if ! podman image exists mold-gentoo; then
cat <<EOF | podman build -v $cache_dir/distfiles:/var/cache/distfiles -v $cache_dir/ccache:/ccache -v $cache_dir/binpkgs-ld:/var/cache/binpkgs -v `pwd`/lib:/mold-lib:ro -t mold-gentoo - || exit 1
FROM docker.io/gentoo/stage3
RUN emerge-webrsync
RUN echo 'USE="X ssl elogind -systemd truetype jpeg jpeg2k tiff zstd -perl udev lcms alsa"' >> /etc/portage/make.conf && \
echo 'ACCEPT_KEYWORDS="~amd64"' >> /etc/portage/make.conf && \
echo 'ACCEPT_LICENSE="* -@EULA"' >> /etc/portage/make.conf && \
echo 'FEATURES="nostrip ccache binpkg-multi-instance -ipc-sandbox -network-sandbox -pid-sandbox -sandbox"' >> /etc/portage/make.conf && \
echo 'CCACHE_DIR="/ccache"' >> /etc/portage/make.conf && \
echo 'MAKEOPTS="-j$(nproc) --load-average=100"' >> /etc/portage/make.conf && \
echo 'EMERGE_DEFAULT_OPTS="--autounmask-continue=y --autounmask-license=n --autounmask-backtrack=y --backtrack=100 --jobs=16 --load-average=100"' >> /etc/portage/make.conf && \
mkdir -p /etc/portage/env /etc/portage/package.use && \
echo 'FEATURES="test"' > /etc/portage/env/test.conf && \
printf 'media-libs/freetype harfbuzz\ndev-qt/qtbase icu\n' > /etc/portage/package.use/mold && \
echo 'dev-python/pillow -truetype' > /etc/portage/package.use/cycle-break && \
FEATURES=buildpkg emerge --usepkg --update --deep --newuse @world && \
gcc-config \$(gcc-config -l | tail -n 1 | awk '{print \$2}') && env-update && \
USE=-harfbuzz FEATURES=buildpkg emerge --oneshot media-libs/freetype && \
FEATURES=buildpkg emerge --usepkg --oneshot media-libs/freetype media-libs/harfbuzz && \
rm /etc/portage/package.use/cycle-break && \
PKGDIR=/var/tmp/binpkgs FEATURES='getbinpkg binpkg-request-signature' emerge gdb lld llvm-core/clang vim emacs strace ccache dev-build/cmake dev-vcs/git && \
rm -rf /var/tmp/portage /var/tmp/binpkgs
RUN python3 /mold-lib/gentoo-gpkg-workaround.py
EOF
fi
) 9> /tmp/mold-gentoo-image.lock || exit 1
if [ "$package" = dev-libs/concurrencykit ]; then
echo "Skipping known broken package: $package"
exit 0
fi
# Build a given package in Podman
# Build a given package in Podman.
#
# Build artifacts are shared between containers through host
# directories bind-mounted into every container:
#
# /var/cache/mold-gentoo/ccache: compiler cache
# /var/cache/mold-gentoo/distfiles: source tarballs, so that each
# distfile is downloaded only once across all containers and runs
# /var/cache/mold-gentoo/binpkgs-{mold,ld}: binary packages, so that each
# dependency is built only once instead of once per container that
# needs it
#
# Every emerge runs with FEATURES=buildpkg to publish what it built to
# the binary package pool, and dependencies are installed with
# --usepkg from the pool when an exact version and USE match exists.
# The package under test itself is always built from source. In the
# mold run, dependencies are still linked with mold and their binaries
# are still exercised by the test suites of everything built on top of
# them; the pool just memoizes that work across containers. The mold
# and GNU ld runs use separate pools so that a control build can never
# install mold-linked binaries.
cmd1='(cd /usr/bin; ln -sf /mold/dist/mold $(realpath ld))'
cmd2="MAKEOPTS=-'j$(nproc) --load-average=100' emerge --onlydeps $package"
cmd3="MAKEOPTS='-j$(nproc) --load-average=100' FEATURES=test emerge $package"
cmd2="echo '$package test.conf' > /etc/portage/package.env"
cmd3="FEATURES=buildpkg emerge --usepkg --onlydeps $package"
cmd4="FEATURES=buildpkg emerge $package"
filename=`echo "$package" | sed 's!/!_!g'`
podman="podman run --rm --pids-limit=-1 --cap-add=SYS_PTRACE -v `pwd`:/mold:ro -v /var/cache/ccache-gentoo:/ccache mold-gentoo timeout -v -k 15s 3h"
podman="podman run --rm --pids-limit=-1 --cap-add=SYS_PTRACE -v `pwd`:/mold:ro -v $cache_dir/ccache:/ccache -v $cache_dir/distfiles:/var/cache/distfiles"
run="mold-gentoo timeout -v -k 15s 3h chrt --idle 0 nice -n 19 bash -c"
dir=gentoo/$git_hash
mkdir -p "$dir"/success "$dir"/failure
mkdir -p "$dir"/success "$dir"/failure "$dir"/broken
$podman nice -n 19 bash -c "$cmd1 && $cmd2 && $cmd3" >& "$dir"/"$filename".mold
# Build the package with mold. If this succeeds, we don't need a
# control build.
$podman -v $cache_dir/binpkgs-mold:/var/cache/binpkgs $run "$cmd1 && $cmd2 && $cmd3 && $cmd4" >& "$dir"/"$filename".mold
if [ $? = 0 ]; then
mv "$dir"/"$filename".mold "$dir"/success
else
mv "$dir"/"$filename".mold "$dir"/failure
exit 0
fi
$podman nice -n 19 bash -c "$cmd2 && $cmd3" >& "$dir"/"$filename".ld
# Build the package again with GNU ld to tell whether the failure is
# mold's fault.
$podman -v $cache_dir/binpkgs-ld:/var/cache/binpkgs $run "$cmd2 && $cmd3 && $cmd4" >& "$dir"/"$filename".ld
if [ $? = 0 ]; then
mv "$dir"/"$filename".mold "$dir"/failure
mv "$dir"/"$filename".ld "$dir"/success
else
mv "$dir"/"$filename".ld "$dir"/failure
mv "$dir"/"$filename".mold "$dir"/broken
mv "$dir"/"$filename".ld "$dir"/broken
fi
+286 -75
View File
@@ -1,15 +1,21 @@
#include "common.h"
// This file implements the glob matcher used for symbol name patterns.
// Exact, prefix and suffix patterns are matched directly, and simple
// substring patterns are combined into an Aho-Corasick matcher. The
// remaining patterns are matched with the non-recursive algorithm described
// at https://research.swtch.com/glob. If there are many such patterns, a
// bit-parallel NFA matches them together.
#include <cstring>
#include "lib.h"
namespace mold {
std::optional<Glob> Glob::compile(std::string_view pat) {
std::vector<Element> vec;
std::optional<Glob::Pattern> Glob::Pattern::compile(std::string_view pat,
i64 value) {
std::vector<Token> tokens;
while (!pat.empty()) {
u8 c = pat[0];
pat = pat.substr(1);
pat.remove_prefix(1);
switch (c) {
case '[': {
@@ -18,27 +24,30 @@ std::optional<Glob> Glob::compile(std::string_view pat) {
// [abc]: a, b or c
// [$\]!]: $, ] or !
// [a-czg-i]: a, b, c, z, g, h, or i
// [^a-z]: Any character except lowercase letters
vec.emplace_back(BRACKET);
std::bitset<256> &bitset = vec.back().bitset;
// [!a-z]: Any character except lowercase letters
//
// Both `!` and `^` are accepted as negation markers. `!` is the
// POSIX/shell convention used by other linkers. `^` was mold's
// original syntax and is kept for backward compatibility.
tokens.emplace_back(BRACKET);
std::bitset<256> &chars = tokens.back().chars;
bool negate = false;
if (!pat.empty() && pat[0] == '^') {
negate = true;
pat = pat.substr(1);
}
bool closed = false;
if (!pat.empty() && (pat[0] == '!' || pat[0] == '^')) {
negate = true;
pat.remove_prefix(1);
}
while (!pat.empty()) {
if (pat[0] == ']') {
pat = pat.substr(1);
pat.remove_prefix(1);
closed = true;
break;
}
if (pat[0] == '\\') {
pat = pat.substr(1);
pat.remove_prefix(1);
if (pat.empty())
return {};
}
@@ -46,23 +55,23 @@ std::optional<Glob> Glob::compile(std::string_view pat) {
if (pat.size() >= 3 && pat[1] == '-') {
u8 start = pat[0];
u8 end = pat[2];
pat = pat.substr(3);
pat.remove_prefix(3);
if (end == '\\') {
if (pat.empty())
return {};
end = pat[0];
pat = pat.substr(1);
pat.remove_prefix(1);
}
if (end < start)
return {};
for (i64 i = start; i <= end; i++)
bitset[i] = true;
chars[i] = true;
} else {
bitset[(u8)pat[0]] = true;
pat = pat.substr(1);
chars[(u8)pat[0]] = true;
pat.remove_prefix(1);
}
}
@@ -70,87 +79,289 @@ std::optional<Glob> Glob::compile(std::string_view pat) {
return {};
if (negate)
bitset.flip();
chars.flip();
break;
}
case '?':
vec.emplace_back(QUESTION);
tokens.emplace_back(QUESTION);
break;
case '*':
vec.emplace_back(STAR);
if (tokens.empty() || tokens.back().kind != STAR)
tokens.emplace_back(STAR);
break;
case '\\':
if (pat.empty())
return {};
if (vec.empty() || vec.back().kind != STRING)
vec.emplace_back(STRING);
vec.back().str += pat[0];
pat = pat.substr(1);
if (tokens.empty() || tokens.back().kind != STRING)
tokens.emplace_back(STRING);
tokens.back().str += pat[0];
pat.remove_prefix(1);
break;
default:
if (vec.empty() || vec.back().kind != STRING)
vec.emplace_back(STRING);
vec.back().str += c;
if (tokens.empty() || tokens.back().kind != STRING)
tokens.emplace_back(STRING);
tokens.back().str += c;
break;
}
}
return {Glob{std::move(vec)}};
return Pattern(std::move(tokens), value);
}
bool Glob::match(std::string_view str) {
return do_match(str, elements);
}
bool Glob::Pattern::match(std::string_view str) const {
if (!tokens.empty() && tokens.back().kind == STRING &&
!str.ends_with(tokens.back().str))
return false;
bool Glob::do_match(std::string_view str, std::span<Element> elements) {
while (!elements.empty()) {
Element &e = elements[0];
elements = elements.subspan(1);
i64 x = 0;
i64 y = 0;
i64 next_x = -1;
i64 next_y = -1;
switch (e.kind) {
case STRING:
if (!str.starts_with(e.str))
return false;
str = str.substr(e.str.size());
break;
case STAR:
if (elements.empty())
return true;
while (x < str.size() || y < tokens.size()) {
if (y < tokens.size()) {
const Token &tok = tokens[y];
// Patterns like "*foo*bar*" should be much more common than more
// complex ones like "*foo*[abc]*" or "*foo**?bar*", so we optimize
// the former case here.
if (elements[0].kind == STRING) {
for (;;) {
size_t pos = str.find(elements[0].str);
switch (tok.kind) {
case STRING:
if (str.substr(x).starts_with(tok.str)) {
x += tok.str.size();
y++;
continue;
}
break;
case STAR:
next_y = y++;
next_x = x + 1;
if (y < tokens.size() && tokens[y].kind == STRING) {
const std::string &s = tokens[y].str;
i64 pos = str.find(s, x);
if (pos == str.npos)
return false;
if (do_match(str.substr(pos + elements[0].str.size()),
elements.subspan(1)))
return true;
str = str.substr(pos + 1);
next_x = pos + 1;
x = pos + s.size();
y++;
}
continue;
case QUESTION:
if (x < str.size()) {
x++;
y++;
continue;
}
break;
case BRACKET:
if (x < str.size() && tok.chars[(u8)str[x]]) {
x++;
y++;
continue;
}
break;
}
}
// Other cases are handled here.
for (i64 j = 0; j < str.size(); j++)
if (do_match(str.substr(j), elements))
return true;
return false;
case QUESTION:
if (str.empty())
return false;
str = str.substr(1);
break;
case BRACKET:
if (str.empty() || !e.bitset[str[0]])
return false;
str = str.substr(1);
break;
// Retry the last star after assigning one more input byte to it.
if (next_x != -1 && next_x <= str.size()) {
x = next_x;
y = next_y;
continue;
}
return false;
}
return true;
}
void Glob::Nfa::compile(std::span<const Pattern> patterns) {
i64 num_states = 0;
for (const Pattern &pattern : patterns) {
num_states++;
for (const Pattern::Token &tok : pattern.tokens) {
if (tok.kind == Pattern::STRING)
num_states += tok.str.size();
else if (tok.kind != Pattern::STAR)
num_states++;
}
}
return str.empty();
i64 num_words = (num_states + 63) / 64;
initial_states.resize(num_words);
star_states.resize(num_words);
accept_states.resize(num_words);
char_masks.resize(256 * num_words);
values.resize(num_states, -1);
auto set_bit = [](std::vector<u64> &vec, i64 pos) {
vec[pos / 64] |= 1ULL << (pos % 64);
};
i64 state = 0;
for (const Pattern &pattern : patterns) {
set_bit(initial_states, state);
for (const Pattern::Token &tok : pattern.tokens) {
switch (tok.kind) {
case Pattern::STRING:
for (u8 c : tok.str) {
state++;
char_masks[c * num_words + state / 64] |= 1ULL << (state % 64);
}
break;
case Pattern::STAR:
set_bit(star_states, state);
break;
case Pattern::QUESTION:
state++;
for (i64 c = 0; c < 256; c++)
char_masks[c * num_words + state / 64] |= 1ULL << (state % 64);
break;
case Pattern::BRACKET:
state++;
for (i64 c = 0; c < 256; c++)
if (tok.chars[c])
char_masks[c * num_words + state / 64] |= 1ULL << (state % 64);
break;
}
}
set_bit(accept_states, state);
values[state] = pattern.value;
state++;
}
}
i64 Glob::Nfa::match(std::string_view str) const {
static thread_local std::vector<u64> states;
states.assign(initial_states.begin(), initial_states.end());
i64 num_words = states.size();
for (u8 c : str) {
const u64 *mask = char_masks.data() + c * num_words;
u64 carry = 0;
for (i64 i = 0; i < num_words; i++) {
u64 old = states[i];
u64 next = (old << 1) | carry;
states[i] = (old & star_states[i]) | (next & mask[i]);
carry = old >> 63;
}
}
i64 value = -1;
for (i64 i = 0; i < num_words; i++) {
u64 word = states[i] & accept_states[i];
while (word) {
i64 bit = std::countr_zero(word);
value = std::max(value, values[i * 64 + bit]);
word &= word - 1;
}
}
return value;
}
static bool is_literal(std::string_view pat) {
return pat.find_first_of("*?[\\") == pat.npos;
}
bool Glob::add(std::string_view pat, i64 val) {
assert(val >= 0);
assert(!is_compiled);
is_empty = false;
max_value = std::max(max_value, val);
// Match-all, exact, prefix and suffix patterns are handled with
// plain string comparisons instead of the matchers below, which
// have to scan the entire input string on every query.
if (pat == "*") {
match_all = std::max(match_all, val);
return true;
}
if (is_literal(pat)) {
exacts.push_back({std::string(pat), val});
return true;
}
if (pat.ends_with('*') && is_literal(pat.substr(0, pat.size() - 1))) {
prefixes.push_back({std::string(pat.substr(0, pat.size() - 1)), val});
return true;
}
if (pat.starts_with('*') && is_literal(pat.substr(1))) {
suffixes.push_back({std::string(pat.substr(1)), val});
return true;
}
// If the pattern requires only a single substring search, the
// Aho-Corasick algorithm is even faster than our glob matcher.
if (aho_corasick.can_handle(pat))
return aho_corasick.add(pat, val);
if (std::optional<Pattern> pattern = Pattern::compile(pat, val)) {
patterns.push_back(std::move(*pattern));
return true;
}
return false;
}
i64 Glob::find(std::string_view str) {
std::call_once(once, [&] {
// If the same name was added more than once, keep only the entry
// with the largest value, as find() returns the largest match.
// Sorting by (name, negated value) places that entry first in
// each run of duplicates, which is the one unique() keeps.
ranges::sort(exacts, {}, [](const LiteralPattern &p) {
return std::pair<std::string_view, i64>(p.pat, -p.value);
});
auto dup = ranges::unique(exacts, {}, &LiteralPattern::pat);
exacts.erase(dup.begin(), dup.end());
if (patterns.size() >= 64) {
nfa.compile(patterns);
patterns.clear();
}
aho_corasick.compile();
is_compiled = true;
});
i64 val = match_all;
if (val == max_value)
return val;
auto it = ranges::lower_bound(exacts, str, {}, &LiteralPattern::pat);
if (it != exacts.end() && it->pat == str)
val = std::max(val, it->value);
for (const LiteralPattern &p : prefixes)
if (val < p.value && str.starts_with(p.pat))
val = p.value;
for (const LiteralPattern &p : suffixes)
if (val < p.value && str.ends_with(p.pat))
val = p.value;
if (val == max_value)
return val;
val = std::max(val, aho_corasick.find(str));
if (val == max_value)
return val;
if (!nfa.empty())
val = std::max(val, nfa.match(str));
for (const Pattern &p : patterns)
if (val < p.value && p.match(str))
val = p.value;
return val;
}
} // namespace mold
+6 -1
View File
@@ -4,13 +4,18 @@
// For more info, read
// https://engineering.fb.com/2018/12/13/data-infrastructure/hyperloglog
#include "common.h"
#include "lib.h"
#include <cmath>
namespace mold {
i64 HyperLogLog::get_cardinality() const {
u8 buckets[NBUCKETS] = {};
for (const Sketch &sketch : sketches)
for (i64 i = 0; i < NBUCKETS; i++)
buckets[i] = std::max(buckets[i], sketch.buckets[i]);
double z = 0;
for (i64 val : buckets)
z += std::ldexp(1.0, -val);
+59 -76
View File
@@ -5,24 +5,30 @@
//
// 1. mold is always a cross linker and should not depend on what host it
// is running on. For example, users should be able to run mold on a
// big-endian SPARC machine to create a little-endian RV64 binary.
// little-endian x86 machine to create a big-endian s390x binary.
//
// 2. Even though data members in all ELF data strucutres are naturally
// aligned, they are not guaranteed to be aligned on memory because of
// archive files. Archive files (.a files) align each member only to a
// archive files. Archive files (.a files) align each file only to a
// 2 byte boundary, so anything larger than 2 bytes may be misaligned
// in an mmap'ed memory. Misaligned access is an undefined behavior in
// C/C++, so we shouldn't cast an arbitrary pointer to a uint32_t, for
// example, to read a 32 bit value.
//
// The data types defined in this file don't depend on host byte order and
// don't do unaligned access. Note that modern compilers are smart enough
// to recognize shift and bitwise OR patterns and compile them into a
// single load instruction.
// The data types defined in this file are independent of the host byte
// order and are designed to avoid unaligned access.
//
// Note that in C/C++, memcpy is a portable and efficient way to access
// unaligned data, as it is typically treated as an intrinsic. Compilers
// can easily optimize memcpy calls in this file into a single load or
// store instruction.
#pragma once
#include <bit>
#include <cstdint>
#include <cstring>
#include <type_traits>
#if !defined(__LITTLE_ENDIAN__) && !defined(__BIG_ENDIAN__)
# if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
@@ -36,93 +42,70 @@
namespace mold {
typedef uint8_t u8;
typedef uint16_t u16;
typedef uint32_t u32;
typedef uint64_t u64;
typedef int8_t i8;
typedef int16_t i16;
typedef int32_t i32;
typedef int64_t i64;
template <typename T, bool is_le, int size = sizeof(T)>
class Integer {
public:
constexpr Integer() = default;
constexpr Integer(T x) requires (is_le && size == 2)
: buf{(u8)x, (u8)(x >> 8)} {}
constexpr Integer(T x) requires (is_le && size == 3)
: buf{(u8)x, (u8)(x >> 8), (u8)(x >> 16)} {}
constexpr Integer(T x) requires (is_le && size == 4)
: buf{(u8)x, (u8)(x >> 8), (u8)(x >> 16), (u8)(x >> 24)} {}
constexpr Integer(T x) requires (is_le && size == 8)
: buf{(u8)x, (u8)(x >> 8), (u8)(x >> 16), (u8)(x >> 24),
(u8)(x >> 32), (u8)(x >> 40), (u8)(x >> 48), (u8)(x >> 56)} {}
constexpr Integer(T x) requires (!is_le && size == 2)
: buf{(u8)(x >> 8), (u8)x} {}
constexpr Integer(T x) requires (!is_le && size == 3)
: buf{(u8)(x >> 16), (u8)(x >> 8), (u8)x} {}
constexpr Integer(T x) requires (!is_le && size == 4)
: buf{(u8)(x >> 24), (u8)(x >> 16), (u8)(x >> 8), (u8)x} {}
constexpr Integer(T x) requires (!is_le && size == 8)
: buf{(u8)(x >> 56), (u8)(x >> 48), (u8)(x >> 40), (u8)(x >> 32),
(u8)(x >> 24), (u8)(x >> 16), (u8)(x >> 8), (u8)x} {}
operator T() const {
if constexpr (is_le) {
if constexpr (size == 2)
return buf[1] << 8 | buf[0];
else if constexpr (size == 3)
return buf[2] << 16 | buf[1] << 8 | buf[0];
else if constexpr (size == 4)
return buf[3] << 24 | buf[2] << 16 | buf[1] << 8 | buf[0];
else
return (u64)buf[7] << 56 | (u64)buf[6] << 48 |
(u64)buf[5] << 40 | (u64)buf[4] << 32 |
(u64)buf[3] << 24 | (u64)buf[2] << 16 |
(u64)buf[1] << 8 | (u64)buf[0];
constexpr Integer(T v) {
if (std::is_constant_evaluated() || size == 3) {
for (int i = 0; i < size; i++)
buf[is_le ? i : (size - i - 1)] = v >> (i * 8);
} else {
if constexpr (size == 2)
return buf[0] << 8 | buf[1];
else if constexpr (size == 3)
return buf[0] << 16 | buf[1] << 8 | buf[2];
else if constexpr (size == 4)
return buf[0] << 24 | buf[1] << 16 | buf[2] << 8 | buf[3];
else
return (u64)buf[0] << 56 | (u64)buf[1] << 48 |
(u64)buf[2] << 40 | (u64)buf[3] << 32 |
(u64)buf[4] << 24 | (u64)buf[5] << 16 |
(u64)buf[6] << 8 | (u64)buf[7];
if (!is_native)
v = bswap(v);
memcpy(buf, &v, size);
}
}
Integer &operator=(T x) {
new (this) Integer(x);
return *this;
operator T() const {
if constexpr (size == 3) {
if constexpr (is_le)
return buf[2] << 16 | buf[1] << 8 | buf[0];
return buf[0] << 16 | buf[1] << 8 | buf[2];
} else {
T v;
memcpy(&v, buf, size);
return is_native ? v : bswap(v);
}
}
Integer &operator=(T v) { new (this) Integer(v); return *this; }
Integer &operator++() { return *this = *this + 1; }
Integer operator++(int) { return ++*this - 1; }
Integer operator++(int) { auto x = *this; ++*this; return x; }
Integer &operator--() { return *this = *this - 1; }
Integer operator--(int) { return --*this + 1; }
Integer &operator+=(T x) { return *this = *this + x; }
Integer &operator-=(T x) { return *this = *this - x; }
Integer &operator&=(T x) { return *this = *this & x; }
Integer &operator|=(T x) { return *this = *this | x; }
Integer operator--(int) { auto x = *this; --*this; return x; }
Integer &operator+=(T v) { return *this = *this + v; }
Integer &operator-=(T v) { return *this = *this - v; }
Integer &operator&=(T v) { return *this = *this & v; }
Integer &operator|=(T v) { return *this = *this | v; }
private:
u8 buf[size];
static constexpr bool is_native =
(std::endian::native == (is_le ? std::endian::little : std::endian::big));
static T bswap(T v) {
switch (size) {
case 2: return __builtin_bswap16(v);
case 4: return __builtin_bswap32(v);
case 8: return __builtin_bswap64(v);
default: __builtin_unreachable();
}
}
uint8_t buf[size];
};
using i8 = int8_t;
using i16 = int16_t;
using i32 = int32_t;
using i64 = int64_t;
using u8 = uint8_t;
using u16 = uint16_t;
using u32 = uint32_t;
using u64 = uint64_t;
using il16 = Integer<i16, true>;
using il32 = Integer<i32, true>;
using il64 = Integer<i64, true>;
+1288
View File
File diff suppressed because it is too large. Load diff
-51
View File
@@ -1,51 +0,0 @@
#include "common.h"
namespace mold {
MappedFile *open_file_impl(const std::string &path, std::string &error) {
i64 fd = ::open(path.c_str(), O_RDONLY);
if (fd == -1) {
if (errno != ENOENT)
error = "opening " + path + " failed: " + errno_string();
return nullptr;
}
struct stat st;
if (fstat(fd, &st) == -1)
error = path + ": fstat failed: " + errno_string();
MappedFile *mf = new MappedFile;
mf->name = path;
mf->size = st.st_size;
if (st.st_size > 0) {
mf->data = (u8 *)mmap(nullptr, st.st_size, PROT_READ | PROT_WRITE,
MAP_PRIVATE, fd, 0);
if (mf->data == MAP_FAILED)
error = path + ": mmap failed: " + errno_string();
}
close(fd);
return mf;
}
void MappedFile::unmap() {
if (size == 0 || parent || !data)
return;
munmap(data, size);
data = nullptr;
}
void MappedFile::close_fd() {
if (fd == -1)
return;
close(fd);
fd = -1;
}
void MappedFile::reopen_fd(const std::string &path) {
if (fd == -1)
fd = open(path.c_str(), O_RDONLY);
}
} // namespace mold
-24
View File
@@ -1,24 +0,0 @@
#include "common.h"
// Including mimalloc-new-delete.h overrides new/delete operators.
// We need it only when we are using mimalloc as a dynamic library.
#if MOLD_USE_SYSTEM_MIMALLOC
# include <mimalloc-new-delete.h>
#endif
// Silence mimalloc warning messages that users can just ignore.
#if MOLD_USE_MIMALLOC
# include <mimalloc.h>
namespace mold {
void set_mimalloc_options() {
mi_option_disable(mi_option_verbose);
mi_option_disable(mi_option_show_errors);
}
}
#else
namespace mold {
void set_mimalloc_options() {}
}
#endif
-186
View File
@@ -1,186 +0,0 @@
// This file implements the Aho-Corasick algorithm to match multiple
// glob patterns to symbol strings as quickly as possible.
//
// Here are some examples of glob patterns:
//
// qt_private_api_tag*
// *16QAccessibleCache*
// *32QAbstractFileIconProviderPrivate*
// *17QPixmapIconEngine*
//
// `*` is a wildcard that matches any substring. We sometimes have
// hundreds of glob patterns and have to match them against millions
// of symbol strings.
//
// Aho-Corasick cannot handle complex patterns such as `*foo*bar*`.
// We handle such patterns with the Glob class. Glob is relatively
// slow, but complex patterns are rare in practice, so it should be
// OK.
#include "common.h"
#include <queue>
#include <regex>
namespace mold {
std::optional<i64> MultiGlob::find(std::string_view str) {
std::call_once(once, [&] { compile(); });
i64 val = -1;
// Match against simple glob patterns
if (root)
val = find_aho_corasick(str);
// Match against complex glob patterns
for (std::pair<Glob, i64> &glob : globs)
if (glob.first.match(str))
val = std::max(val, glob.second);
if (val == -1)
return {};
return val;
}
i64 MultiGlob::find_aho_corasick(std::string_view str) {
TrieNode *node = root.get();
i64 val = -1;
auto walk = [&](u8 c) {
for (;;) {
if (node->children[c]) {
node = node->children[c].get();
val = std::max(val, node->value);
return;
}
if (!node->suffix_link)
return;
node = node->suffix_link;
}
};
walk('\0');
for (u8 c : str) {
if (prefix_match && node == root.get())
return val;
walk(c);
}
walk('\0');
return val;
}
static bool is_simple_pattern(std::string_view pat) {
static std::regex re(R"(\*?[^*[?]+\*?)", std::regex_constants::optimize);
return std::regex_match(pat.begin(), pat.end(), re);
}
static std::string handle_stars(std::string_view pat) {
std::string str(pat);
// Convert "foo" -> "\0foo\0", "*foo" -> "foo\0", "foo*" -> "\0foo"
// and "*foo*" -> "foo". Aho-Corasick can do only substring matching,
// so we use \0 as beginning/end-of-string markers.
if (str.starts_with('*') && str.ends_with('*'))
return str.substr(1, str.size() - 2);
if (str.starts_with('*'))
return str.substr(1) + "\0"s;
if (str.ends_with('*'))
return "\0"s + str.substr(0, str.size() - 1);
return "\0"s + str + "\0"s;
}
bool MultiGlob::add(std::string_view pat, i64 val) {
assert(!is_compiled);
assert(!pat.empty());
strings.emplace_back(pat);
// Complex glob pattern
if (!is_simple_pattern(pat)) {
if (std::optional<Glob> glob = Glob::compile(pat)) {
globs.emplace_back(std::move(*glob), val);
return true;
}
return false;
}
// Simple glob pattern
if (!root)
root.reset(new TrieNode);
TrieNode *node = root.get();
for (u8 c : handle_stars(pat)) {
if (!node->children[c])
node->children[c].reset(new TrieNode);
node = node->children[c].get();
}
node->value = std::max(node->value, val);
return true;
}
void MultiGlob::compile() {
is_compiled = true;
if (root) {
fix_suffix_links(*root);
fix_values();
// If no pattern starts with '*', set prefix_match to true.
// We'll use this flag for optimization.
prefix_match = true;
for (i64 i = 1; i < 256; i++) {
if (root->children[i]) {
prefix_match = false;
break;
}
}
}
}
void MultiGlob::fix_suffix_links(TrieNode &node) {
for (i64 i = 0; i < 256; i++) {
if (!node.children[i])
continue;
TrieNode &child = *node.children[i];
TrieNode *cur = node.suffix_link;
for (;;) {
if (!cur) {
child.suffix_link = root.get();
break;
}
if (cur->children[i]) {
child.suffix_link = cur->children[i].get();
break;
}
cur = cur->suffix_link;
}
fix_suffix_links(child);
}
}
void MultiGlob::fix_values() {
std::queue<TrieNode *> queue;
queue.push(root.get());
do {
TrieNode *node = queue.front();
queue.pop();
for (std::unique_ptr<TrieNode> &child : node->children) {
if (!child)
continue;
child->value = std::max(child->value, child->suffix_link->value);
queue.push(child.get());
}
} while (!queue.empty());
}
} // namespace mold
+4 -2
View File
@@ -1,11 +1,13 @@
#include "common.h"
#include "lib.h"
#include <functional>
#include <iomanip>
#include <ios>
#include <tbb/concurrent_vector.h>
#ifndef _WIN32
#ifdef _WIN32
#include <windows.h>
#else
#include <sys/resource.h>
#include <sys/time.h>
#endif
+1 -1
View File
@@ -1,4 +1,4 @@
#include "common.h"
#include "lib.h"
#include <random>
+6 -1
View File
@@ -1,6 +1,6 @@
// This file contains functions to create a tar file.
#include "common.h"
#include "lib.h"
#ifdef _WIN32
# define ftruncate _chsize_s
@@ -86,6 +86,9 @@ void TarWriter::append(std::string path, std::string_view data) {
UstarHeader pax = {};
std::string attr = encode_path(basedir, path);
if (attr.size() >= 0100'000'000'000)
unreachable();
snprintf(pax.size, sizeof(pax.size), "%011zo", attr.size());
pax.name[0] = '/';
pax.typeflag[0] = 'x';
@@ -99,6 +102,8 @@ void TarWriter::append(std::string path, std::string_view data) {
// Write Ustar header
UstarHeader ustar = {};
memcpy(ustar.mode, "0000664", 8);
if (data.size() >= 0100'000'000'000)
unreachable();
snprintf(ustar.size, sizeof(ustar.size), "%011zo", data.size());
finalize(ustar);
fwrite(&ustar, sizeof(ustar), 1, out);
+18 -19
View File
@@ -1,27 +1,26 @@
# Get a git hash value. We do not want to use git command here
# because we don't want to make git a build-time dependency.
if(EXISTS "${SOURCE_DIR}/.git/HEAD")
file(READ "${SOURCE_DIR}/.git/HEAD" HASH)
string(STRIP "${HASH}" HASH)
# Embed the current git commit hash into the mold executable. We ask git
# instead of parsing files under .git because the repository format varies
# (e.g. packed refs, reftable). When building from a source tarball, there's
# no .git, and the hash is simply omitted.
if(EXISTS "${SOURCE_DIR}/.git")
execute_process(
COMMAND git -C "${SOURCE_DIR}" rev-parse HEAD
OUTPUT_VARIABLE HASH
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_QUIET
RESULT_VARIABLE EXIT_CODE)
if(HASH MATCHES "^ref: (.*)")
set(HEAD "${CMAKE_MATCH_1}")
if(EXISTS "${SOURCE_DIR}/.git/${HEAD}")
file(READ "${SOURCE_DIR}/.git/${HEAD}" HASH)
string(STRIP "${HASH}" HASH)
else()
file(READ "${SOURCE_DIR}/.git/packed-refs" PACKED_REFS)
string(REGEX REPLACE ".*\n([0-9a-f]+) ${HEAD}\n.*" "\\1" HASH "\n${PACKED_REFS}")
endif()
if(NOT EXIT_CODE EQUAL 0)
set(HASH "")
endif()
endif()
# Create new file contents and update a given file if necessary.
set(NEW_CONTENTS "#include <string>
namespace mold {
std::string mold_git_hash = \"${HASH}\";
}
")
if("${HASH}" STREQUAL "")
set(NEW_CONTENTS "")
else()
set(NEW_CONTENTS "#define MOLD_GIT_HASH \"${HASH}\"\n")
endif()
if(EXISTS "${OUTPUT_FILE}")
file(READ "${OUTPUT_FILE}" OLD_CONTENTS)
+198 -130
View File
@@ -31,18 +31,21 @@
//
// https://github.com/ARM-software/abi-aa/blob/main/aaelf32/aaelf32.rst
#if MOLD_ARM32LE || MOLD_ARM32BE
#include "mold.h"
#include <tbb/parallel_for.h>
#include <tbb/parallel_for_each.h>
namespace mold {
using E = ARM32;
using E = MOLD_TARGET;
template <>
i64 get_addend(u8 *loc, const ElfRel<E> &rel) {
ul32 *arm = (ul32 *)loc;
ul16 *thm = (ul16 *)loc;
U32<E> *arm = (U32<E> *)loc;
U16<E> *thm = (U16<E> *)loc;
switch (rel.r_type) {
case R_ARM_ABS32:
@@ -59,7 +62,7 @@ i64 get_addend(u8 *loc, const ElfRel<E> &rel) {
case R_ARM_TLS_GOTDESC:
case R_ARM_TARGET1:
case R_ARM_TARGET2:
return (il32)*arm;
return (I32<E>)*arm;
case R_ARM_THM_JUMP8:
return sign_extend(thm[0], 8) << 1;
case R_ARM_THM_JUMP11:
@@ -125,7 +128,7 @@ i64 get_addend(u8 *loc, const ElfRel<E> &rel) {
static void write_arm_mov(u8 *loc, u32 val) {
u32 imm12 = bits(val, 11, 0);
u32 imm4 = bits(val, 15, 12);
*(ul32 *)loc = (*(ul32 *)loc & 0xfff0'f000) | (imm4 << 16) | imm12;
*(U32<E> *)loc = (*(U32<E> *)loc & 0xfff0'f000) | (imm4 << 16) | imm12;
}
static void write_thm_b21(u8 *loc, u32 val) {
@@ -135,7 +138,7 @@ static void write_thm_b21(u8 *loc, u32 val) {
u32 imm6 = bits(val, 17, 12);
u32 imm11 = bits(val, 11, 1);
ul16 *buf = (ul16 *)loc;
U16<E> *buf = (U16<E> *)loc;
buf[0] = (buf[0] & 0b1111'1011'1100'0000) | (S << 10) | imm6;
buf[1] = (buf[1] & 0b1101'0000'0000'0000) | (J1 << 13) | (J2 << 11) | imm11;
}
@@ -149,7 +152,7 @@ static void write_thm_b25(u8 *loc, u32 val) {
u32 imm10 = bits(val, 21, 12);
u32 imm11 = bits(val, 11, 1);
ul16 *buf = (ul16 *)loc;
U16<E> *buf = (U16<E> *)loc;
buf[0] = (buf[0] & 0b1111'1000'0000'0000) | (S << 10) | imm10;
buf[1] = (buf[1] & 0b1101'0000'0000'0000) | (J1 << 13) | (J2 << 11) | imm11;
}
@@ -160,7 +163,7 @@ static void write_thm_mov(u8 *loc, u32 val) {
u32 imm3 = bits(val, 10, 8);
u32 imm8 = bits(val, 7, 0);
ul16 *buf = (ul16 *)loc;
U16<E> *buf = (U16<E> *)loc;
buf[0] = (buf[0] & 0b1111'1011'1111'0000) | (i << 10) | imm4;
buf[1] = (buf[1] & 0b1000'1111'0000'0000) | (imm3 << 12) | imm8;
}
@@ -184,13 +187,13 @@ void write_addend(u8 *loc, i64 val, const ElfRel<E> &rel) {
case R_ARM_TLS_GOTDESC:
case R_ARM_TARGET1:
case R_ARM_TARGET2:
*(ul32 *)loc = val;
*(U32<E> *)loc = val;
break;
case R_ARM_THM_JUMP8:
*(ul16 *)loc = (*(ul16 *)loc & 0xff00) | bits(val, 8, 1);
*(U16<E> *)loc = (*(U16<E> *)loc & 0xff00) | bits(val, 8, 1);
break;
case R_ARM_THM_JUMP11:
*(ul16 *)loc = (*(ul16 *)loc & 0xf800) | bits(val, 11, 1);
*(U16<E> *)loc = (*(U16<E> *)loc & 0xf800) | bits(val, 11, 1);
break;
case R_ARM_THM_CALL:
case R_ARM_THM_JUMP24:
@@ -200,7 +203,7 @@ void write_addend(u8 *loc, i64 val, const ElfRel<E> &rel) {
case R_ARM_CALL:
case R_ARM_JUMP24:
case R_ARM_PLT32:
*(ul32 *)loc = (*(ul32 *)loc & 0xff00'0000) | bits(val, 25, 2);
*(U32<E> *)loc = (*(U32<E> *)loc & 0xff00'0000) | bits(val, 25, 2);
break;
case R_ARM_MOVW_PREL_NC:
case R_ARM_MOVW_ABS_NC:
@@ -209,7 +212,7 @@ void write_addend(u8 *loc, i64 val, const ElfRel<E> &rel) {
write_arm_mov(loc, val);
break;
case R_ARM_PREL31:
*(ul32 *)loc = (*(ul32 *)loc & 0x8000'0000) | (val & 0x7fff'ffff);
*(U32<E> *)loc = (*(U32<E> *)loc & 0x8000'0000) | (val & 0x7fff'ffff);
break;
case R_ARM_THM_MOVW_PREL_NC:
case R_ARM_THM_MOVW_ABS_NC:
@@ -236,7 +239,7 @@ void write_plt_header(Context<E> &ctx, u8 *buf) {
};
memcpy(buf, insn, sizeof(insn));
*(ul32 *)(buf + 16) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr - 16;
*(U32<E> *)(buf + 16) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr - 16;
}
static constexpr ul32 plt_entry[] = {
@@ -249,42 +252,34 @@ static constexpr ul32 plt_entry[] = {
template <>
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
memcpy(buf, plt_entry, sizeof(plt_entry));
*(ul32 *)(buf + 12) = sym.get_gotplt_addr(ctx) - sym.get_plt_addr(ctx) - 12;
*(U32<E> *)(buf + 12) = sym.get_gotplt_addr(ctx) - sym.get_plt_addr(ctx) - 12;
}
template <>
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
memcpy(buf, plt_entry, sizeof(plt_entry));
*(ul32 *)(buf + 12) = sym.get_got_pltgot_addr(ctx) - sym.get_plt_addr(ctx) - 12;
*(U32<E> *)(buf + 12) = sym.get_got_pltgot_addr(ctx) - sym.get_plt_addr(ctx) - 12;
}
template <>
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
u64 offset, u64 val) {
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, InputSection<E> &isec,
const ElfRel<E> &rel, u64 offset, u64 val) {
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
switch (rel.r_type) {
case R_NONE:
break;
case R_ARM_ABS32:
*(ul32 *)loc = val;
*(U32<E> *)loc = val;
break;
case R_ARM_REL32:
*(ul32 *)loc = val - this->shdr.sh_addr - offset;
*(U32<E> *)loc = val - this->shdr.sh_addr - offset;
break;
default:
Fatal(ctx) << "unsupported relocation in .eh_frame: " << rel;
}
}
static Thunk<E> &get_reachable_thunk(OutputSection<E> &osec, u64 addr) {
auto it = std::upper_bound(osec.thunks.begin(), osec.thunks.end(), addr,
[](u64 addr, std::unique_ptr<Thunk<E>> &thunk) {
return addr < thunk->get_addr();
});
return **it;
}
template <>
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
std::span<const ElfRel<E>> rels = get_rels(ctx);
@@ -294,13 +289,16 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
if (rel.r_type == R_NONE || rel.r_type == R_ARM_V4BX)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
Symbol<E> &sym = *file->symbols[rel.r_sym];
if (sym.get_type() == STT_TLS && sym.is_remaining_undef_weak())
continue;
u8 *loc = base + rel.r_offset;
u64 S = sym.get_addr(ctx);
u64 A = get_addend(*this, rel);
u64 P = get_addr() + rel.r_offset;
u64 T = S & 1;
u64 T = is_thumb_func(ctx, sym);
u64 G = sym.get_got_idx(ctx) * sizeof(Word<E>);
u64 GOT = ctx.got->shdr.sh_addr;
@@ -312,7 +310,11 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
auto get_arm_thunk_addr = [&] { return sym.get_thunk_addr(ctx, P) + 4; };
auto get_tlsdesc_trampoline_addr = [&] {
return get_reachable_thunk(*output_section, P).get_addr();
auto it = ranges::upper_bound(output_section->thunks, P, {},
[](std::unique_ptr<Thunk<E>> &thunk) {
return thunk->get_addr();
});
return (*it)->get_addr();
};
switch (rel.r_type) {
@@ -320,78 +322,79 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
case R_ARM_TARGET1:
break;
case R_ARM_REL32:
*(ul32 *)loc = S + A - P;
*(U32<E> *)loc = S + A - P;
break;
case R_ARM_THM_CALL: {
if (sym.is_remaining_undef_weak()) {
// On ARM, calling an weak undefined symbol jumps to the
// next instruction.
*(ul32 *)loc = 0x8000'f3af; // NOP.W
*(U32<E> *)loc = 0x8000'f3af; // NOP.W
break;
}
// THM_CALL relocation refers to either BL or BLX instruction.
// They are different in only one bit. We need to use BL if
// the jump target is Thumb. Otherwise, use BLX.
// They are different in only one bit. We need to use BLX if the
// jump target is an ARM function. Otherwise, use BL.
i64 val1 = S + A - P;
i64 val2 = align_to(S + A - P, 4);
bool arm = is_arm_func(ctx, sym);
if (T && is_int(val1, 25)) {
*(ul16 *)(loc + 2) |= 0x1000; // BL
if (!arm && is_int(val1, 25)) {
*(U16<E> *)(loc + 2) |= 0x1000; // BL
write_thm_b25(loc, val1);
} else if (!T && is_int(val2, 25)) {
*(ul16 *)(loc + 2) &= ~0x1000; // BLX
} else if (arm && is_int(val2, 25)) {
*(U16<E> *)(loc + 2) &= ~0x1000; // BLX
write_thm_b25(loc, val2);
} else {
*(ul16 *)(loc + 2) |= 0x1000; // BL
*(U16<E> *)(loc + 2) |= 0x1000; // BL
write_thm_b25(loc, get_thumb_thunk_addr() + A - P);
}
break;
}
case R_ARM_BASE_PREL:
*(ul32 *)loc = GOT + A - P;
*(U32<E> *)loc = GOT + A - P;
break;
case R_ARM_GOTOFF32:
*(ul32 *)loc = ((S + A) | T) - GOT;
*(U32<E> *)loc = ((S + A) | T) - GOT;
break;
case R_ARM_GOT_PREL:
case R_ARM_TARGET2:
*(ul32 *)loc = GOT + G + A - P;
*(U32<E> *)loc = GOT + G + A - P;
break;
case R_ARM_GOT_BREL:
*(ul32 *)loc = G + A;
*(U32<E> *)loc = G + A;
break;
case R_ARM_CALL: {
if (sym.is_remaining_undef_weak()) {
*(ul32 *)loc = 0xe320'f000; // NOP
*(U32<E> *)loc = 0xe320'f000; // NOP
break;
}
// Just like THM_CALL, ARM_CALL relocation refers to either BL or
// BLX instruction. We may need to rewrite BL → BLX or BLX → BL.
bool is_bl = ((*(ul32 *)loc & 0xff00'0000) == 0xeb00'0000);
bool is_blx = ((*(ul32 *)loc & 0xfe00'0000) == 0xfa00'0000);
bool is_bl = ((*(U32<E> *)loc & 0xff00'0000) == 0xeb00'0000);
bool is_blx = ((*(U32<E> *)loc & 0xfe00'0000) == 0xfa00'0000);
if (!is_bl && !is_blx)
Fatal(ctx) << *this << ": R_ARM_CALL refers to neither BL nor BLX";
i64 val = S + A - P;
if (is_int(val, 26)) {
if (T) {
*(ul32 *)loc = 0xfa00'0000; // BLX
*(ul32 *)loc |= (bit(val, 1) << 24) | bits(val, 25, 2);
*(U32<E> *)loc = 0xfa00'0000; // BLX
*(U32<E> *)loc |= (bit(val, 1) << 24) | bits(val, 25, 2);
} else {
*(ul32 *)loc = 0xeb00'0000; // BL
*(ul32 *)loc |= bits(val, 25, 2);
*(U32<E> *)loc = 0xeb00'0000; // BL
*(U32<E> *)loc |= bits(val, 25, 2);
}
} else {
*(ul32 *)loc = 0xeb00'0000; // BL
*(ul32 *)loc |= bits(get_arm_thunk_addr() + A - P, 25, 2);
*(U32<E> *)loc = 0xeb00'0000; // BL
*(U32<E> *)loc |= bits(get_arm_thunk_addr() + A - P, 25, 2);
}
break;
}
case R_ARM_JUMP24: {
if (sym.is_remaining_undef_weak()) {
*(ul32 *)loc = 0xe320'f000; // NOP
*(U32<E> *)loc = 0xe320'f000; // NOP
break;
}
@@ -404,26 +407,26 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
i64 val = S + A - P;
if (T || !is_int(val, 26))
val = get_arm_thunk_addr() + A - P;
*(ul32 *)loc = (*(ul32 *)loc & 0xff00'0000) | bits(val, 25, 2);
*(U32<E> *)loc = (*(U32<E> *)loc & 0xff00'0000) | bits(val, 25, 2);
break;
}
case R_ARM_PLT32:
if (sym.is_remaining_undef_weak()) {
*(ul32 *)loc = 0xe320'f000; // NOP
*(U32<E> *)loc = 0xe320'f000; // NOP
} else {
u64 val = (T ? get_arm_thunk_addr() : S) + A - P;
*(ul32 *)loc = (*(ul32 *)loc & 0xff00'0000) | bits(val, 25, 2);
*(U32<E> *)loc = (*(U32<E> *)loc & 0xff00'0000) | bits(val, 25, 2);
}
break;
case R_ARM_THM_JUMP8:
check(S + A - P, -(1 << 8), 1 << 8);
*(ul16 *)loc &= 0xff00;
*(ul16 *)loc |= bits(S + A - P, 8, 1);
*(U16<E> *)loc &= 0xff00;
*(U16<E> *)loc |= bits(S + A - P, 8, 1);
break;
case R_ARM_THM_JUMP11:
check(S + A - P, -(1 << 11), 1 << 11);
*(ul16 *)loc &= 0xf800;
*(ul16 *)loc |= bits(S + A - P, 11, 1);
*(U16<E> *)loc &= 0xf800;
*(U16<E> *)loc |= bits(S + A - P, 11, 1);
break;
case R_ARM_THM_JUMP19:
check(S + A - P, -(1 << 20), 1 << 20);
@@ -431,14 +434,14 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
break;
case R_ARM_THM_JUMP24: {
if (sym.is_remaining_undef_weak()) {
*(ul32 *)loc = 0x8000'f3af; // NOP
*(U32<E> *)loc = 0x8000'f3af; // NOP
break;
}
// Just like R_ARM_JUMP24, we need to jump to a thunk if we need to
// switch processor mode.
i64 val = S + A - P;
if (!T || !is_int(val, 25))
if (is_arm_func(ctx, sym) || !is_int(val, 25))
val = get_thumb_thunk_addr() + A - P;
write_thm_b25(loc, val);
break;
@@ -454,8 +457,8 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
break;
case R_ARM_PREL31:
check(S + A - P, -(1LL << 30), 1LL << 30);
*(ul32 *)loc &= 0x8000'0000;
*(ul32 *)loc |= (S + A - P) & 0x7fff'ffff;
*(U32<E> *)loc &= 0x8000'0000;
*(U32<E> *)loc |= (S + A - P) & 0x7fff'ffff;
break;
case R_ARM_THM_MOVW_ABS_NC:
write_thm_mov(loc, (S + A) | T);
@@ -473,19 +476,19 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
write_thm_mov(loc, (S + A) >> 16);
break;
case R_ARM_TLS_GD32:
*(ul32 *)loc = sym.get_tlsgd_addr(ctx) + A - P;
*(U32<E> *)loc = sym.get_tlsgd_addr(ctx) + A - P;
break;
case R_ARM_TLS_LDM32:
*(ul32 *)loc = ctx.got->get_tlsld_addr(ctx) + A - P;
*(U32<E> *)loc = ctx.got->get_tlsld_addr(ctx) + A - P;
break;
case R_ARM_TLS_LDO32:
*(ul32 *)loc = S + A - ctx.dtp_addr;
*(U32<E> *)loc = S + A - ctx.dtp_addr;
break;
case R_ARM_TLS_IE32:
*(ul32 *)loc = sym.get_gottp_addr(ctx) + A - P;
*(U32<E> *)loc = sym.get_gottp_addr(ctx) + A - P;
break;
case R_ARM_TLS_LE32:
*(ul32 *)loc = S + A - ctx.tp_addr;
*(U32<E> *)loc = S + A - ctx.tp_addr;
break;
case R_ARM_TLS_GOTDESC:
// ARM32 TLSDESC uses the following code sequence to materialize
@@ -514,35 +517,35 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
// .L2: .word foo(gottpoff) + . - .L1
if (sym.has_tlsdesc(ctx)) {
// A is odd if the corresponding TLS_CALL is Thumb.
*(ul32 *)loc = sym.get_tlsdesc_addr(ctx) - P + A - ((A & 1) ? 6 : 4);
*(U32<E> *)loc = sym.get_tlsdesc_addr(ctx) - P + A - ((A & 1) ? 6 : 4);
} else if (sym.has_gottp(ctx)) {
*(ul32 *)loc = sym.get_gottp_addr(ctx) - P + A - ((A & 1) ? 5 : 8);
*(U32<E> *)loc = sym.get_gottp_addr(ctx) - P + A - ((A & 1) ? 5 : 8);
} else {
*(ul32 *)loc = S - ctx.tp_addr;
*(U32<E> *)loc = S - ctx.tp_addr;
}
break;
case R_ARM_TLS_CALL:
if (sym.has_tlsdesc(ctx)) {
*(ul32 *)loc = 0xeb00'0000; // bl 0
*(ul32 *)loc |= bits(get_tlsdesc_trampoline_addr() - P - 8, 25, 2);
*(U32<E> *)loc = 0xeb00'0000; // bl 0
*(U32<E> *)loc |= bits(get_tlsdesc_trampoline_addr() - P - 8, 25, 2);
} else if (sym.has_gottp(ctx)) {
*(ul32 *)loc = 0xe79f'0000; // ldr r0, [pc, r0]
*(U32<E> *)loc = 0xe79f'0000; // ldr r0, [pc, r0]
} else {
*(ul32 *)loc = 0xe320'f000; // nop
*(U32<E> *)loc = 0xe320'f000; // nop
}
break;
case R_ARM_THM_TLS_CALL:
if (sym.has_tlsdesc(ctx)) {
u64 val = align_to(get_tlsdesc_trampoline_addr() - P - 4, 4);
write_thm_b25(loc, val);
*(ul16 *)(loc + 2) &= ~0x1000; // rewrite BL with BLX
*(U16<E> *)(loc + 2) &= ~0x1000; // rewrite BL with BLX
} else if (sym.has_gottp(ctx)) {
// Since `ldr r0, [pc, r0]` is not representable in Thumb,
// we use two instructions instead.
*(ul16 *)loc = 0x4478; // add r0, pc
*(ul16 *)(loc + 2) = 0x6800; // ldr r0, [r0]
*(U16<E> *)loc = 0x4478; // add r0, pc
*(U16<E> *)(loc + 2) = 0x6800; // ldr r0, [r0]
} else {
*(ul32 *)loc = 0x8000'f3af; // nop.w
*(U32<E> *)loc = 0x8000'f3af; // nop.w
}
break;
default:
@@ -553,14 +556,11 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
template <>
void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
std::span<const ElfRel<E>> rels = get_rels(ctx);
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
for_each_reloc(ctx, [&](const ElfRel<E> &rel, i64) ALWAYS_INLINE {
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
continue;
return;
Symbol<E> &sym = *file.symbols[rel.r_sym];
Symbol<E> &sym = *file->symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
SectionFragment<E> *frag;
@@ -573,22 +573,22 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
switch (rel.r_type) {
case R_ARM_ABS32:
if (std::optional<u64> val = get_tombstone(sym, frag))
*(ul32 *)loc = *val;
*(U32<E> *)loc = *val;
else
*(ul32 *)loc = S + A;
*(U32<E> *)loc = S + A;
break;
case R_ARM_TLS_LDO32:
if (std::optional<u64> val = get_tombstone(sym, frag))
*(ul32 *)loc = *val;
*(U32<E> *)loc = *val;
else
*(ul32 *)loc = S + A - ctx.dtp_addr;
*(U32<E> *)loc = S + A - ctx.dtp_addr;
break;
default:
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
<< rel;
break;
}
}
});
}
template <>
@@ -602,7 +602,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
Symbol<E> &sym = *file->symbols[rel.r_sym];
if (sym.is_ifunc())
sym.flags |= NEEDS_GOT | NEEDS_PLT;
@@ -695,27 +695,28 @@ void Thunk<E>::copy_buf(Context<E> &ctx) {
static_assert(E::thunk_hdr_size == sizeof(hdr));
static_assert(E::thunk_size == sizeof(entry));
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
memcpy(buf, hdr, sizeof(hdr));
buf += sizeof(hdr);
u8 *base = ctx.buf + output_section.shdr.sh_offset + offset;
memcpy(base, hdr, sizeof(hdr));
u64 P = output_section.shdr.sh_addr + offset + sizeof(hdr);
for (Symbol<E> *sym : symbols) {
for (i64 i = 0; i < symbols.size(); i++) {
u64 S = symbols[i]->get_addr(ctx);
u64 P = get_addr() + offsets[i];
u8 *buf = base + offsets[i];
memcpy(buf, entry, sizeof(entry));
*(ul32 *)(buf + 12) = sym->get_addr(ctx) - P - 16;
buf += sizeof(entry);
P += sizeof(entry);
*(U32<E> *)(buf + 12) = S - P - 16;
}
}
template <>
u64 get_eflags(Context<E> &ctx) {
return EF_ARM_EABI_VER5;
if constexpr (E::is_le)
return EF_ARM_EABI_VER5;
else
return EF_ARM_EABI_VER5 | EF_ARM_BE8;
}
void create_arm_exidx_section(Context<E> &ctx) {
template <>
void create_arm_exidx_section<E>(Context<E> &ctx) {
for (i64 i = 0; i < ctx.chunks.size(); i++) {
OutputSection<E> *osec = ctx.chunks[i]->to_osec();
@@ -726,34 +727,26 @@ void create_arm_exidx_section(Context<E> &ctx) {
ctx.chunk_pool.emplace_back(sec);
for (InputSection<E> *isec : osec->members)
isec->is_alive = false;
isec->kill();
break;
}
}
}
void Arm32ExidxSection::compute_section_size(Context<E> &ctx) {
template <>
void Arm32ExidxSection<E>::compute_section_size(Context<E> &ctx) {
output_section.compute_section_size(ctx);
this->shdr.sh_size = output_section.shdr.sh_size + 8; // +8 for sentinel
}
void Arm32ExidxSection::update_shdr(Context<E> &ctx) {
template <>
void Arm32ExidxSection<E>::update_shdr(Context<E> &ctx) {
// .ARM.exidx's sh_link should be set to the .text section index.
// Runtime doesn't care about it, but the binutils's strip command does.
if (Chunk<E> *chunk = find_chunk(ctx, ".text"))
this->shdr.sh_link = chunk->shndx;
}
void Arm32ExidxSection::remove_duplicate_entries(Context<E> &ctx) {
this->shdr.sh_size = get_contents(ctx).size();
}
void Arm32ExidxSection::copy_buf(Context<E> &ctx) {
std::vector<u8> contents = get_contents(ctx);
assert(this->shdr.sh_size == contents.size());
write_vector(ctx.buf + this->shdr.sh_offset, contents);
}
// Returns the end of the text segment
static u64 get_text_end(Context<E> &ctx) {
u64 ret = 0;
@@ -772,7 +765,8 @@ static u64 get_text_end(Context<E> &ctx) {
// likely that it's due to some historical reason.
//
// This function returns contents of .ARM.exidx.
std::vector<u8> Arm32ExidxSection::get_contents(Context<E> &ctx) {
template <>
std::vector<u8> Arm32ExidxSection<E>::get_contents(Context<E> &ctx) {
// .ARM.exidx records consists of a signed 31-bit relative address
// and a 32-bit value. The relative address indicates the start
// address of a function that the record covers. The value is one of
@@ -788,8 +782,8 @@ std::vector<u8> Arm32ExidxSection::get_contents(Context<E> &ctx) {
const u32 CANTUNWIND = 1;
struct Entry {
ul32 addr;
ul32 val;
U32<E> addr;
U32<E> val;
};
// We reserve one extra slot for the sentinel
@@ -820,19 +814,13 @@ std::vector<u8> Arm32ExidxSection::get_contents(Context<E> &ctx) {
ent[i].val = 0x7fff'ffff & (ent[i].val + offset);
});
std::sort(ent, ent + num_entries, [](const Entry &a, const Entry &b) {
return a.addr < b.addr;
});
ranges::sort(ent, ent + num_entries, {}, &Entry::addr);
// Remove duplicate adjacent entries. That is, if two adjacent functions
// have the same compact unwind info or are both CANTUNWIND, we can
// merge them into a single address range.
auto it = std::unique(ent, ent + num_entries,
[](const Entry &a, const Entry &b) {
return a.val == b.val;
});
num_entries = it - ent;
auto tail = ranges::unique(ent, ent + num_entries, {}, &Entry::val);
num_entries -= tail.size();
buf.resize(num_entries * sizeof(Entry));
// Make addresses relative to themselves.
@@ -846,4 +834,84 @@ std::vector<u8> Arm32ExidxSection::get_contents(Context<E> &ctx) {
return buf;
}
template <>
void Arm32ExidxSection<E>::remove_duplicate_entries(Context<E> &ctx) {
this->shdr.sh_size = get_contents(ctx).size();
}
template <>
void Arm32ExidxSection<E>::copy_buf(Context<E> &ctx) {
std::vector<u8> contents = get_contents(ctx);
assert(this->shdr.sh_size == contents.size());
write_vector(ctx.buf + this->shdr.sh_offset, contents);
}
// Even though using ARM32 in big-endian mode is very rare, the processor
// technically supports both little- and big-endian modes. There are two
// variants of big-endian mode: BE32 and BE8. In BE32, instructions and
// data are encoded in big-endian. In BE8, instructions are encoded in
// little-endian, and only data is in big-endian. BE8 is the de facto
// standard for ARMv6 or later. We support only BE8.
//
// A tricky thing is that instructions in an object file are always
// big-endian if the file is compiled for big-endian mode. In other words,
// the compiler always emit code in BE32 if -mbig-endian is specified. It
// is the linker's responsibility to rewrite instructions from big-endian
// to little-endian for an BE8 output. This function does that.
//
// The text section may contain a mix of 32-bit ARM instructions, 16-bit
// Thumb instructions, and data. We need to distinguish them to swap 4
// bytes, 2 bytes, or not swap bytes, respectively. The beginning of ARM
// code, Thumb code, and data is labeled with a mapping symbol of $a, $t,
// and $d, respectively. We use mapping symbols to determine what to do
// with the text section.
//
// This function is called after we copy the input section contents to the
// output file. We rewrite instructions in the output buffer in place.
#if MOLD_ARM32BE
void arm32be_swap_bytes(Context<E> &ctx) {
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
// Collect mapping symbols
std::vector<Symbol<E> *> syms;
for (Symbol<E> *sym : file->get_local_syms())
if (InputSection<E> *isec = sym->get_input_section())
if (isec->is_alive() && (isec->shdr().sh_flags & SHF_EXECINSTR))
if (std::string_view x = sym->name();
x == "$a" || x.starts_with("$a.") ||
x == "$t" || x.starts_with("$t.") ||
x == "$d" || x.starts_with("$d."))
syms.push_back(sym);
// Group mapping symbols by input section and sort by address
ranges::stable_sort(syms, {}, [](const Symbol<E> *sym) {
return std::tuple{(uintptr_t)sym->get_input_section(), sym->value};
});
// Swap bytes
for (i64 i = 0; i < syms.size(); i++) {
Symbol<E> &sym = *syms[i];
if (sym.name().starts_with("$d"))
continue;
InputSection<E> &isec = *sym.get_input_section();
u8 *base = ctx.buf + isec.output_section->shdr.sh_offset + isec.offset;
u8 *start = base + sym.value;
u8 *end;
if (i + 1 < syms.size() && syms[i + 1]->get_input_section() == &isec)
end = base + syms[i + 1]->value;
else
end = base + isec.sh_size;
i64 sz = sym.name().starts_with("$a") ? 4 : 2;
for (u8 *p = start; p < end; p += sz)
std::reverse(p, p + sz);
}
});
}
#endif
} // namespace mold
#endif
+148 -36
View File
@@ -104,9 +104,14 @@ void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
}
template <>
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
u64 offset, u64 val) {
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, InputSection<E> &isec,
const ElfRel<E> &rel, u64 offset, u64 val) {
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
u64 P = this->shdr.sh_addr + offset;
auto check = [&](i64 val, i64 lo, i64 hi) {
check_range(ctx, isec, rel, val, lo, hi);
};
switch (rel.r_type) {
case R_NONE:
@@ -115,10 +120,11 @@ void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
*(U64<E> *)loc = val;
break;
case R_AARCH64_PREL32:
*(U32<E> *)loc = val - this->shdr.sh_addr - offset;
check(val - P, -(1LL << 31), 1LL << 31);
*(U32<E> *)loc = val - P;
break;
case R_AARCH64_PREL64:
*(U64<E> *)loc = val - this->shdr.sh_addr - offset;
*(U64<E> *)loc = val - P;
break;
default:
Fatal(ctx) << "unsupported relocation in .eh_frame: " << rel;
@@ -145,14 +151,17 @@ static bool is_add(u8 *loc) {
template <>
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
std::span<const ElfRel<E>> rels = get_rels(ctx);
std::span<ElfRel<E>> rels = get_rels(ctx);
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
Symbol<E> &sym = *file->symbols[rel.r_sym];
if (sym.get_type() == STT_TLS && sym.is_remaining_undef_weak())
continue;
u8 *loc = base + rel.r_offset;
u64 S = sym.get_addr(ctx);
@@ -165,6 +174,11 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
check_range(ctx, i, val, lo, hi);
};
auto rewrite = [&](i64 idx, u32 ty) {
if (ctx.arg.emit_relocs)
rels[idx].r_type = ty;
};
switch (rel.r_type) {
case R_AARCH64_ABS64:
break;
@@ -208,6 +222,30 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
case R_AARCH64_MOVW_UABS_G3:
*(ul32 *)loc |= bits(S + A, 63, 48) << 5;
break;
case R_AARCH64_MOVW_PREL_G0:
check(S + A - P, -(1 << 15), 1 << 15);
write_movn_movz(loc, S + A - P);
break;
case R_AARCH64_MOVW_PREL_G0_NC:
*(ul32 *)loc |= bits(S + A - P, 15, 0) << 5;
break;
case R_AARCH64_MOVW_PREL_G1:
check(S + A - P, -(1LL << 31), 1LL << 31);
write_movn_movz(loc, (i64)(S + A - P) >> 16);
break;
case R_AARCH64_MOVW_PREL_G1_NC:
*(ul32 *)loc |= bits(S + A - P, 31, 16) << 5;
break;
case R_AARCH64_MOVW_PREL_G2:
check(S + A - P, -(1LL << 47), 1LL << 47);
write_movn_movz(loc, (i64)(S + A - P) >> 32);
break;
case R_AARCH64_MOVW_PREL_G2_NC:
*(ul32 *)loc |= bits(S + A - P, 47, 32) << 5;
break;
case R_AARCH64_MOVW_PREL_G3:
*(ul32 *)loc |= bits(S + A - P, 63, 48) << 5;
break;
case R_AARCH64_ADR_GOT_PAGE:
if (sym.has_got(ctx)) {
i64 val = page(G + GOT + A) - page(P);
@@ -222,6 +260,9 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
u32 reg = bits(*(ul32 *)loc, 4, 0);
*(ul32 *)(loc + 4) = 0x9100'0000 | (reg << 5) | reg; // ADD
*(ul32 *)(loc + 4) |= bits(S + A, 11, 0) << 10;
rewrite(i, R_AARCH64_ADR_PREL_PG_HI21);
rewrite(i + 1, R_AARCH64_ADD_ABS_LO12_NC);
i++;
}
break;
@@ -248,6 +289,8 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
*(ul32 *)loc = 0xd503'201f; // nop
*(ul32 *)(loc + 4) = 0x1000'0000 | reg1; // adr
write_adr(loc + 4, val);
rewrite(i, R_NONE);
rewrite(i + 1, R_AARCH64_ADR_PREL_LO21);
i++;
break;
}
@@ -283,6 +326,10 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
check(S + A - P, -(1LL << 31), 1LL << 31);
*(U32<E> *)loc = S + A - P;
break;
case R_AARCH64_GOTPCREL32:
check(G + GOT + A - P, -(1LL << 31), 1LL << 31);
*(U32<E> *)loc = G + GOT + A - P;
break;
case R_AARCH64_CONDBR19:
case R_AARCH64_LD_PREL_LO19:
check(S + A - P, -(1LL << 20), 1LL << 20);
@@ -397,13 +444,16 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
write_adrp(loc, val);
} else {
*(ul32 *)loc = 0xd503'201f; // nop
rewrite(i, R_NONE);
}
break;
case R_AARCH64_TLSDESC_LD64_LO12:
if (sym.has_tlsdesc(ctx))
if (sym.has_tlsdesc(ctx)) {
*(ul32 *)loc |= bits(sym.get_tlsdesc_addr(ctx) + A, 11, 3) << 10;
else
} else {
*(ul32 *)loc = 0xd503'201f; // nop
rewrite(i, R_NONE);
}
break;
case R_AARCH64_TLSDESC_ADD_LO12:
if (sym.has_tlsdesc(ctx)) {
@@ -411,9 +461,11 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
} else if (sym.has_gottp(ctx)) {
*(ul32 *)loc = 0x9000'0000; // adrp x0, 0
write_adrp(loc, page(sym.get_gottp_addr(ctx) + A) - page(P));
rewrite(i, R_AARCH64_TLSIE_ADR_GOTTPREL_PAGE21);
} else {
*(ul32 *)loc = 0xd2a0'0000; // movz x0, 0, lsl #16
*(ul32 *)loc |= bits(S + A - ctx.tp_addr, 32, 16) << 5;
rewrite(i, R_AARCH64_TLSLE_MOVW_TPREL_G1);
}
break;
case R_AARCH64_TLSDESC_CALL:
@@ -422,9 +474,11 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
} else if (sym.has_gottp(ctx)) {
*(ul32 *)loc = 0xf940'0000; // ldr x0, [x0, 0]
*(ul32 *)loc |= bits(sym.get_gottp_addr(ctx) + A, 11, 3) << 10;
rewrite(i, R_AARCH64_TLSIE_LD64_GOTTPREL_LO12_NC);
} else {
*(ul32 *)loc = 0xf280'0000; // movk x0, 0
*(ul32 *)loc |= bits(S + A - ctx.tp_addr, 15, 0) << 5;
rewrite(i, R_AARCH64_TLSLE_MOVW_TPREL_G0_NC);
}
break;
default:
@@ -435,14 +489,11 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
template <>
void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
std::span<const ElfRel<E>> rels = get_rels(ctx);
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
for_each_reloc(ctx, [&](const ElfRel<E> &rel, i64 i) ALWAYS_INLINE {
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
continue;
return;
Symbol<E> &sym = *file.symbols[rel.r_sym];
Symbol<E> &sym = *file->symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
SectionFragment<E> *frag;
@@ -453,7 +504,7 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
u64 A = frag ? frag_addend : (i64)rel.r_addend;
auto check = [&](i64 val, i64 lo, i64 hi) {
check_range(ctx, val, i, lo, hi);
check_range(ctx, i, val, lo, hi);
};
switch (rel.r_type) {
@@ -467,12 +518,18 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
check(S + A, 0, 1LL << 32);
*(U32<E> *)loc = S + A;
break;
case R_AARCH64_TLS_DTPREL64:
if (std::optional<u64> val = get_tombstone(sym, frag))
*(U64<E> *)loc = *val;
else
*(U64<E> *)loc = S + A - ctx.dtp_addr;
break;
default:
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
<< rel;
break;
}
}
});
}
template <>
@@ -486,8 +543,8 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = (u8 *)(contents.data() + rel.r_offset);
Symbol<E> &sym = *file->symbols[rel.r_sym];
u8 *loc = contents + rel.r_offset;
if (sym.is_ifunc())
sym.flags |= NEEDS_GOT | NEEDS_PLT;
@@ -525,6 +582,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
break;
case R_AARCH64_LD64_GOT_LO12_NC:
case R_AARCH64_LD64_GOTPAGE_LO15:
case R_AARCH64_GOTPCREL32:
sym.flags |= NEEDS_GOT;
break;
case R_AARCH64_CALL26:
@@ -568,6 +626,13 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
case R_AARCH64_MOVW_UABS_G1_NC:
case R_AARCH64_MOVW_UABS_G2:
case R_AARCH64_MOVW_UABS_G2_NC:
case R_AARCH64_MOVW_PREL_G0:
case R_AARCH64_MOVW_PREL_G0_NC:
case R_AARCH64_MOVW_PREL_G1:
case R_AARCH64_MOVW_PREL_G1_NC:
case R_AARCH64_MOVW_PREL_G2:
case R_AARCH64_MOVW_PREL_G2_NC:
case R_AARCH64_MOVW_PREL_G3:
case R_AARCH64_PREL16:
case R_AARCH64_PREL32:
case R_AARCH64_PREL64:
@@ -587,31 +652,78 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
}
}
// The size of a thunk entry varies on ARM64 depending on the distance to
// the branch target. This function computes the size of each thunk entry.
template <>
void Thunk<E>::copy_buf(Context<E> &ctx) {
constexpr ul32 insn[] = {
0x9000'0010, // adrp x16, 0 # R_AARCH64_ADR_PREL_PG_HI21
0x9100'0210, // add x16, x16 # R_AARCH64_ADD_ABS_LO12_NC
0xd61f'0200, // br x16
0xd420'7d00, // brk
void Thunk<E>::shrink_size(Context<E> &ctx) {
offsets.clear();
offsets.push_back(0);
i64 off = 0;
// The distance between S and P is only reduced by shrink_size(), but
// page(S) – page(P) may still increase by one page due to address
// changes, so we add a safety margin.
//
// For example, page(0x1200) – page(0x1000) is 0, whereas
// page(0x1100) – page(0xfff) is 0x1000, even though the latter
// distance is shorter than the former.
auto is_small = [](i64 prel) {
return is_int(prel + 0x1000, 33) && is_int(prel - 0x1000, 33);
};
static_assert(E::thunk_size == sizeof(insn));
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
u64 P = output_section.shdr.sh_addr + offset;
for (Symbol<E> *sym : symbols) {
u64 S = sym->get_addr(ctx);
memcpy(buf, insn, E::thunk_size);
write_adrp(buf, page(S) - page(P));
*(ul32 *)(buf + 4) |= bits(S, 11, 0) << 10;
buf += E::thunk_size;
P += E::thunk_size;
u64 P = get_addr() + off;
i64 prel = page(S) - page(P);
off += is_small(prel) ? 12 : 24;
offsets.push_back(off);
}
}
template <>
void Thunk<E>::copy_buf(Context<E> &ctx) {
// Short thunk with a 33 bit displacement
constexpr ul32 insn1[] = {
0x9000'0010, // adrp x16, 0
0x9100'0210, // add x16, x16
0xd61f'0200, // br x16
};
// Long thunk with a 64 bit displacement
constexpr ul32 insn2[] = {
0x1000'0010, // adr x16, 0
0xd2a0'0011, // movz x17, 0, lsl #16
0xf2c0'0011, // movk x17, 0, lsl #32
0xf2e0'0011, // movk x17, 0, lsl #48
0x8b11'0210, // add x16, x16, x17
0xd61f'0200, // br x16
};
u8 *base = ctx.buf + output_section.shdr.sh_offset + offset;
for (i64 i = 0; i < symbols.size(); i++) {
u64 S = symbols[i]->get_addr(ctx);
u64 P = get_addr() + offsets[i];
u8 *buf = base + offsets[i];
if (offsets[i + 1] - offsets[i] == sizeof(insn1)) {
i64 prel = page(S) - page(P);
assert(is_int(prel, 33));
memcpy(buf, insn1, sizeof(insn1));
write_adrp(buf, prel);
*(ul32 *)(buf + 4) |= bits(S, 11, 0) << 10;
} else {
memcpy(buf, insn2, sizeof(insn2));
write_adr(buf, bits(S - P, 15, 0));
*(ul32 *)(buf + 4) |= bits(S - P, 31, 16) << 5;
*(ul32 *)(buf + 8) |= bits(S - P, 47, 32) << 5;
*(ul32 *)(buf + 12) |= bits(S - P, 63, 48) << 5;
}
}
}
template class Thunk<E>;
} // namespace mold
#endif
+24 -18
View File
@@ -33,6 +33,8 @@
//
// https://github.com/rui314/psabi/blob/main/i386.pdf
#if MOLD_I386
#include "mold.h"
namespace mold {
@@ -44,10 +46,10 @@ i64 get_addend(u8 *loc, const ElfRel<E> &rel) {
switch (rel.r_type) {
case R_386_8:
case R_386_PC8:
return *loc;
return *(i8 *)loc;
case R_386_16:
case R_386_PC16:
return *(ul16 *)loc;
return *(il16 *)loc;
case R_386_32:
case R_386_PC32:
case R_386_GOT32:
@@ -63,7 +65,7 @@ i64 get_addend(u8 *loc, const ElfRel<E> &rel) {
case R_386_TLS_LDO_32:
case R_386_SIZE32:
case R_386_TLS_GOTDESC:
return *(ul32 *)loc;
return *(il32 *)loc;
default:
return 0;
}
@@ -172,8 +174,8 @@ void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
}
template <>
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
u64 offset, u64 val) {
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, InputSection<E> &isec,
const ElfRel<E> &rel, u64 offset, u64 val) {
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
switch (rel.r_type) {
@@ -278,14 +280,17 @@ static u32 relax_tlsdesc_to_le(u8 *loc) {
template <>
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
std::span<const ElfRel<E>> rels = get_rels(ctx);
std::span<ElfRel<E>> rels = get_rels(ctx);
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
Symbol<E> &sym = *file->symbols[rel.r_sym];
if (sym.get_type() == STT_TLS && sym.is_remaining_undef_weak())
continue;
u8 *loc = base + rel.r_offset;
u64 S = sym.get_addr(ctx);
@@ -333,6 +338,8 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
loc[-2] = insn >> 8;
loc[-1] = insn;
*(ul32 *)loc = S + A - GOT;
if (ctx.arg.emit_relocs)
rel.r_type = R_386_GOTOFF;
}
break;
case R_386_GOTOFF:
@@ -434,14 +441,11 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
template <>
void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
std::span<const ElfRel<E>> rels = get_rels(ctx);
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
for_each_reloc(ctx, [&](const ElfRel<E> &rel, i64 i) ALWAYS_INLINE {
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
continue;
return;
Symbol<E> &sym = *file.symbols[rel.r_sym];
Symbol<E> &sym = *file->symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
SectionFragment<E> *frag;
@@ -453,7 +457,7 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
u64 GOT = ctx.got->shdr.sh_addr;
auto check = [&](i64 val, i64 lo, i64 hi) {
check_range(ctx, val, i, lo, hi);
check_range(ctx, i, val, lo, hi);
};
switch (rel.r_type) {
@@ -500,7 +504,7 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
default:
unreachable();
}
}
});
}
template <>
@@ -514,8 +518,8 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = (u8 *)(contents.data() + rel.r_offset);
Symbol<E> &sym = *file->symbols[rel.r_sym];
u8 *loc = contents + rel.r_offset;
if (sym.is_ifunc())
sym.flags |= NEEDS_GOT | NEEDS_PLT;
@@ -596,3 +600,5 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
}
} // namespace mold
#endif
+95 -38
View File
@@ -66,7 +66,7 @@ static u64 higher(u64 val, u64 pc) {
// LU52I.D simply set bits to [51:31] and to [63:53], respectively.
//
// Compensating all the sign-extensions is a bit complicated. The
// psABI gave the following formula.
// psABI gives the following formula.
val = val + 0x8000'0000 + ((val & 0x800) ? (0x1000 - 0x1'0000'0000) : 0);
return page(val) - page(pc - 8);
}
@@ -132,8 +132,8 @@ static void set_rj(u8 *loc, u32 rj) {
// ld.d $t0, $t0, 0 # R_LARCH_GOT_PC_LO12, R_LARCH_RELAX
static bool is_relaxable_got_load(Context<E> &ctx, InputSection<E> &isec, i64 i) {
std::span<const ElfRel<E>> rels = isec.get_rels(ctx);
Symbol<E> &sym = *isec.file.symbols[rels[i].r_sym];
u8 *buf = (u8 *)isec.contents.data();
Symbol<E> &sym = *isec.file->symbols[rels[i].r_sym];
u8 *buf = isec.contents;
if (ctx.arg.relax &&
sym.is_pcrel_linktime_const(ctx) &&
@@ -219,9 +219,14 @@ void write_pltgot_entry<E>(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
}
template <>
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
u64 offset, u64 val) {
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, InputSection<E> &isec,
const ElfRel<E> &rel, u64 offset, u64 val) {
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
u64 P = this->shdr.sh_addr + offset;
auto check = [&](i64 val, i64 lo, i64 hi) {
check_range(ctx, isec, rel, val, lo, hi);
};
switch (rel.r_type) {
case R_NONE:
@@ -257,7 +262,8 @@ void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
*(ul64 *)loc -= val;
break;
case R_LARCH_32_PCREL:
*(ul32 *)loc = val - this->shdr.sh_addr - offset;
check(val - P, -(1LL << 31), 1LL << 31);
*(ul32 *)loc = val - P;
break;
case R_LARCH_64_PCREL:
*(ul64 *)loc = val - this->shdr.sh_addr - offset;
@@ -269,13 +275,13 @@ void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
template <>
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
std::span<const ElfRel<E>> rels = get_rels(ctx);
std::span<ElfRel<E>> rels = get_rels(ctx);
std::span<RelocDelta> deltas = extra.r_deltas;
i64 k = 0;
u8 *buf = (u8 *)contents.data();
u8 *buf = contents;
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE || rel.r_type == R_LARCH_RELAX ||
rel.r_type == R_LARCH_MARK_LA || rel.r_type == R_LARCH_MARK_PCREL ||
@@ -294,7 +300,10 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
r_delta = deltas[k - 1].delta;
}
Symbol<E> &sym = *file.symbols[rel.r_sym];
Symbol<E> &sym = *file->symbols[rel.r_sym];
if (sym.get_type() == STT_TLS && sym.is_remaining_undef_weak())
continue;
i64 r_offset = rel.r_offset - r_delta;
u8 *loc = base + r_offset;
@@ -332,6 +341,11 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
<< " for relocation " << rel;
};
auto rewrite = [&](i64 idx, u32 ty) {
if (ctx.arg.emit_relocs)
rels[idx].r_type = ty;
};
switch (rel.r_type) {
case R_LARCH_32:
assert(E::is_64);
@@ -346,7 +360,8 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
write_d5k16(loc, (S + A - P) >> 2);
break;
case R_LARCH_B26:
check_branch(S + A - P, -(1 << 27), 1 << 27);
if (!sym.is_remaining_undef_weak())
check_branch(S + A - P, -(1 << 27), 1 << 27);
write_d10k16(loc, (S + A - P) >> 2);
break;
case R_LARCH_ABS_LO12:
@@ -375,10 +390,13 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
if (removed_bytes == 0) {
write_j20(loc, hi20(S + A, P));
} else {
// Rewrite pcalau12i + addi.d with pcaddi
// Rewrite pcalau12i + addi.d with pcaddi. The high part vanishes and
// the low part becomes the pcaddi's PC-relative relocation.
assert(removed_bytes == 4);
*(ul32 *)loc = 0x1800'0000 | get_rd(*(ul32 *)loc); // pcaddi
write_j20(loc, (S + A - P) >> 2);
rewrite(i, R_NONE);
rewrite(i + 2, R_LARCH_PCREL20_S2);
i += 3;
}
break;
@@ -417,10 +435,13 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
}
write_j20(loc, hi20(GOT + G + A, P));
} else {
// Rewrite pcalau12i + ld.d with pcaddi
// Rewrite pcalau12i + ld.d with pcaddi. The high part vanishes and the
// low part becomes the pcaddi's PC-relative relocation.
assert(removed_bytes == 4);
*(ul32 *)loc = 0x1800'0000 | get_rd(*(ul32 *)loc); // pcaddi
write_j20(loc, (S + A - P) >> 2);
rewrite(i, R_NONE);
rewrite(i + 2, R_LARCH_PCREL20_S2);
i += 3;
}
break;
@@ -518,6 +539,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
*(ul64 *)loc -= S + A;
break;
case R_LARCH_32_PCREL:
check(S + A - P, -(1LL << 31), 1LL << 31);
*(ul32 *)loc = S + A - P;
break;
case R_LARCH_64_PCREL:
@@ -526,7 +548,8 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
case R_LARCH_CALL36:
if (removed_bytes == 0) {
i64 val = S + A - P;
check_branch(val, -(1LL << 37) - 0x20000, (1LL << 37) - 0x20000);
if (!sym.is_remaining_undef_weak())
check_branch(val, -(1LL << 37) - 0x20000, (1LL << 37) - 0x20000);
write_j20(loc, (val + 0x20000) >> 18);
write_k16(loc + 4, val >> 2);
} else {
@@ -537,6 +560,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
else
*(ul32 *)loc = 0x5400'0000; // BL
write_d10k16(loc, (S + A - P) >> 2);
rewrite(i, R_LARCH_B26);
}
break;
case R_LARCH_ADD_ULEB128:
@@ -590,30 +614,49 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
// jirl $ra, $ra, 0
if (sym.has_tlsdesc(ctx) && removed_bytes == 0)
write_j20(loc, hi20(sym.get_tlsdesc_addr(ctx) + A, P));
// pcalau12i + addi.d => pcaddi when TLSDESC is kept; both deleted when it
// is relaxed to IE/LE. Either way the high part loses its relocation.
// The folded pcaddi's relocation is emitted from the LO12 slot below.
if (!sym.has_tlsdesc(ctx) || removed_bytes)
rewrite(i, R_NONE);
break;
case R_LARCH_TLS_DESC_PC_LO12:
if (sym.has_tlsdesc(ctx) && removed_bytes == 0) {
i64 dist = sym.get_tlsdesc_addr(ctx) + A - P;
if (is_int(dist, 22)) {
*(ul32 *)loc = 0x1800'0000 | get_rd(*(ul32 *)loc); // pcaddi
write_j20(loc, dist >> 2);
} else {
write_k12(loc, sym.get_tlsdesc_addr(ctx) + A);
if (sym.has_tlsdesc(ctx)) {
if (removed_bytes == 0) {
i64 dist = sym.get_tlsdesc_addr(ctx) + A - P;
if (is_int(dist, 22)) {
*(ul32 *)loc = 0x1800'0000 | get_rd(*(ul32 *)loc); // pcaddi
write_j20(loc, dist >> 2);
rewrite(i, R_LARCH_TLS_DESC_PCREL20_S2);
} else {
write_k12(loc, sym.get_tlsdesc_addr(ctx) + A);
}
}
} else {
rewrite(i, R_NONE);
}
break;
case R_LARCH_TLS_DESC_LD:
if (sym.has_tlsdesc(ctx) || removed_bytes == 4) {
// Do nothing
// TLSDESC is relaxed to IE or LE. The ld.d slot holds the first
// instruction of the resulting sequence.
if (sym.has_tlsdesc(ctx)) {
// Do nothing (TLSDESC kept)
} else if (removed_bytes == 4) {
// Small TP offset: the instruction was deleted.
rewrite(i, R_NONE);
} else if (sym.has_gottp(ctx)) {
*(ul32 *)loc = 0x1a00'0004; // pcalau12i $a0, 0
write_j20(loc, hi20(sym.get_gottp_addr(ctx) + A, P));
rewrite(i, R_LARCH_TLS_IE_PC_HI20);
} else {
*(ul32 *)loc = 0x1400'0004; // lu12i.w $a0, 0
write_j20(loc, (S + A + 0x800 - ctx.tp_addr) >> 12);
rewrite(i, R_LARCH_TLS_LE_HI20);
}
break;
case R_LARCH_TLS_DESC_CALL:
// The jirl slot holds the second instruction of the IE or LE sequence.
if (sym.has_tlsdesc(ctx)) {
// Do nothing
} else if (sym.has_gottp(ctx)) {
@@ -622,6 +665,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
else
*(ul32 *)loc = 0x2880'0084; // ld.w $a0, $a0, 0
write_k12(loc, sym.get_gottp_addr(ctx) + A);
rewrite(i, R_LARCH_TLS_IE_PC_LO12);
} else {
i64 val = S + A - ctx.tp_addr;
if (0 <= val && val < 0x1000)
@@ -629,11 +673,16 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
else
*(ul32 *)loc = 0x0280'0084; // addi.w $a0, $a0, 0
write_k12(loc, val);
rewrite(i, R_LARCH_TLS_LE_LO12);
}
break;
case R_LARCH_TLS_LE_HI20_R:
// lu12i.w + add.d + addi.d => addi.d when the variable is within 2 KiB
// of TP, in which case the lu12i.w loses its relocation.
if (removed_bytes == 0)
write_j20(loc, (S + A + 0x800 - ctx.tp_addr) >> 12);
else
rewrite(i, R_NONE);
break;
case R_LARCH_TLS_LE_LO12_R: {
i64 val = S + A - ctx.tp_addr;
@@ -645,8 +694,13 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
set_rj(loc, 2);
break;
}
case R_LARCH_64:
case R_LARCH_TLS_LE_ADD_R:
// add.d that materializes TP + offset; removed together with the
// lu12i.w when the variable is within 2 KiB of TP.
if (removed_bytes)
rewrite(i, R_NONE);
break;
case R_LARCH_64:
break;
default:
unreachable();
@@ -656,19 +710,16 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
template <>
void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
std::span<const ElfRel<E>> rels = get_rels(ctx);
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
for_each_reloc(ctx, [&](const ElfRel<E> &rel, i64) ALWAYS_INLINE {
if (rel.r_type == R_NONE)
continue;
return;
Symbol<E> &sym = *file.symbols[rel.r_sym];
Symbol<E> &sym = *file->symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
if (!sym.file) {
if (!sym.file && &sym != discarded_comdat_sym<E>) {
record_undef_error(ctx, rel);
continue;
return;
}
SectionFragment<E> *frag;
@@ -741,7 +792,7 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
<< rel;
break;
}
}
});
}
template <>
@@ -761,7 +812,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
if (record_undef_error(ctx, rel))
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
Symbol<E> &sym = *file->symbols[rel.r_sym];
if (sym.is_ifunc())
sym.flags |= NEEDS_GOT | NEEDS_PLT;
@@ -854,13 +905,13 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
template <>
void shrink_section(Context<E> &ctx, InputSection<E> &isec) {
std::span<const ElfRel<E>> rels = isec.get_rels(ctx);
std::vector<RelocDelta> &deltas = isec.extra.r_deltas;
std::vector<RelocDelta> deltas;
i64 r_delta = 0;
u8 *buf = (u8 *)isec.contents.data();
u8 *buf = isec.contents;
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &r = rels[i];
Symbol<E> &sym = *isec.file.symbols[r.r_sym];
Symbol<E> &sym = *isec.file->symbols[r.r_sym];
auto remove = [&](i64 d) {
r_delta += d;
@@ -980,7 +1031,7 @@ void shrink_section(Context<E> &ctx, InputSection<E> &isec) {
// pcaddi $t0, <offset>
if (is_relaxable_got_load(ctx, isec, i))
if (i64 dist = compute_distance(ctx, sym, isec, r);
is_int(dist, 22))
is_int(dist, 22) && (dist & 3) == 0)
remove(4);
break;
case R_LARCH_TLS_DESC_PC_HI20:
@@ -1006,6 +1057,12 @@ void shrink_section(Context<E> &ctx, InputSection<E> &isec) {
}
}
if (deltas.empty())
return;
RelocDelta *p = ctx.arena.template allocate<RelocDelta>(deltas.size());
ranges::copy(deltas, p);
isec.extra.r_deltas = {p, deltas.size()};
isec.sh_size -= r_delta;
}
+21 -11
View File
@@ -14,6 +14,8 @@
//
// https://github.com/rui314/psabi/blob/main/m68k.pdf
#if MOLD_M68K
#include "mold.h"
namespace mold {
@@ -56,8 +58,8 @@ void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
}
template <>
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
u64 offset, u64 val) {
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, InputSection<E> &isec,
const ElfRel<E> &rel, u64 offset, u64 val) {
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
switch (rel.r_type) {
@@ -83,7 +85,10 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
if (rel.r_type == R_NONE)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
Symbol<E> &sym = *file->symbols[rel.r_sym];
if (sym.get_type() == STT_TLS && sym.is_remaining_undef_weak())
continue;
u8 *loc = base + rel.r_offset;
u64 S = sym.get_addr(ctx);
@@ -208,14 +213,11 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
template <>
void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
std::span<const ElfRel<E>> rels = get_rels(ctx);
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
for_each_reloc(ctx, [&](const ElfRel<E> &rel, i64) ALWAYS_INLINE {
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
continue;
return;
Symbol<E> &sym = *file.symbols[rel.r_sym];
Symbol<E> &sym = *file->symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
SectionFragment<E> *frag;
@@ -232,11 +234,17 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
else
*(ub32 *)loc = S + A;
break;
case R_68K_TLS_LDO32:
if (std::optional<u64> val = get_tombstone(sym, frag))
*(ub32 *)loc = *val;
else
*(ub32 *)loc = S + A - ctx.dtp_addr;
break;
default:
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
<< rel;
}
}
});
}
template <>
@@ -249,7 +257,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
Symbol<E> &sym = *file->symbols[rel.r_sym];
if (sym.is_ifunc())
Error(ctx) << sym << ": GNU ifunc symbol is not supported on m68k";
@@ -310,3 +318,5 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
}
} // namespace mold
#endif
+31 -22
View File
@@ -40,6 +40,8 @@
//
// https://github.com/rui314/psabi/blob/main/ppc32.pdf
#if MOLD_PPC32
#include "mold.h"
namespace mold {
@@ -126,8 +128,8 @@ void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
}
template <>
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
u64 offset, u64 val) {
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, InputSection<E> &isec,
const ElfRel<E> &rel, u64 offset, u64 val) {
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
switch (rel.r_type) {
@@ -148,14 +150,17 @@ template <>
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
std::span<const ElfRel<E>> rels = get_rels(ctx);
u64 GOT2 = file.extra.got2 ? file.extra.got2->get_addr() : 0;
u64 GOT2 = file->extra.got2 ? file->extra.got2->get_addr() : 0;
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
Symbol<E> &sym = *file->symbols[rel.r_sym];
if (sym.get_type() == STT_TLS && sym.is_remaining_undef_weak())
continue;
u8 *loc = base + rel.r_offset;
u64 S = sym.get_addr(ctx);
@@ -282,14 +287,11 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
template <>
void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
std::span<const ElfRel<E>> rels = get_rels(ctx);
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
for_each_reloc(ctx, [&](const ElfRel<E> &rel, i64) ALWAYS_INLINE {
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
continue;
return;
Symbol<E> &sym = *file.symbols[rel.r_sym];
Symbol<E> &sym = *file->symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
SectionFragment<E> *frag;
@@ -306,11 +308,17 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
else
*(ub32 *)loc = S + A;
break;
case R_PPC_DTPREL32:
if (std::optional<u64> val = get_tombstone(sym, frag))
*(ub32 *)loc = *val;
else
*(ub32 *)loc = S + A - ctx.dtp_addr;
break;
default:
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
<< rel;
}
}
});
}
template <>
@@ -324,7 +332,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
Symbol<E> &sym = *file->symbols[rel.r_sym];
if (sym.is_ifunc())
sym.flags |= NEEDS_GOT | NEEDS_PLT;
@@ -416,27 +424,28 @@ void Thunk<E>::copy_buf(Context<E> &ctx) {
static_assert(E::thunk_size == sizeof(plt_entry));
static_assert(E::thunk_size == sizeof(local_thunk));
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
u64 P = output_section.shdr.sh_addr + offset;
u8 *base = ctx.buf + output_section.shdr.sh_offset + offset;
for (Symbol<E> *sym : symbols) {
if (sym->has_plt(ctx)) {
u64 got =
sym->has_got(ctx) ? sym->get_got_addr(ctx) : sym->get_gotplt_addr(ctx);
for (i64 i = 0; i < symbols.size(); i++) {
Symbol<E> &sym = *symbols[i];
u64 P = get_addr() + offsets[i];
u8 *buf = base + offsets[i];
if (sym.has_plt(ctx)) {
u64 got = sym.has_got(ctx) ? sym.get_got_addr(ctx) : sym.get_gotplt_addr(ctx);
i64 val = got - P - 8;
memcpy(buf, plt_entry, sizeof(plt_entry));
*(ub32 *)(buf + 16) |= higha(val);
*(ub32 *)(buf + 20) |= lo(val);
} else {
i64 val = sym->get_addr(ctx) - P - 8;
i64 val = sym.get_addr(ctx) - P - 8;
memcpy(buf, local_thunk, sizeof(local_thunk));
*(ub32 *)(buf + 16) |= higha(val);
*(ub32 *)(buf + 20) |= lo(val);
}
buf += E::thunk_size;
P += E::thunk_size;
}
}
} // namespace mold
#endif
+85 -60
View File
@@ -45,6 +45,8 @@
//
// https://github.com/rui314/psabi/blob/main/ppc64v1.pdf
#if MOLD_PPC64V1
#include "mold.h"
#include <algorithm>
@@ -129,9 +131,14 @@ template <>
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {}
template <>
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
u64 offset, u64 val) {
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, InputSection<E> &isec,
const ElfRel<E> &rel, u64 offset, u64 val) {
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
u64 P = this->shdr.sh_addr + offset;
auto check = [&](i64 val, i64 lo, i64 hi) {
check_range(ctx, isec, rel, val, lo, hi);
};
switch (rel.r_type) {
case R_NONE:
@@ -140,10 +147,11 @@ void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
*(ub64 *)loc = val;
break;
case R_PPC64_REL32:
*(ub32 *)loc = val - this->shdr.sh_addr - offset;
check(val - P, -(1LL << 31), 1LL << 31);
*(ub32 *)loc = val - P;
break;
case R_PPC64_REL64:
*(ub64 *)loc = val - this->shdr.sh_addr - offset;
*(ub64 *)loc = val - P;
break;
default:
Fatal(ctx) << "unsupported relocation in .eh_frame: " << rel;
@@ -159,7 +167,10 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
if (rel.r_type == R_NONE)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
Symbol<E> &sym = *file->symbols[rel.r_sym];
if (sym.get_type() == STT_TLS && sym.is_remaining_undef_weak())
continue;
u8 *loc = base + rel.r_offset;
u64 S = sym.get_addr(ctx);
@@ -217,6 +228,9 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
case R_PPC64_REL16_LO:
*(ub16 *)loc = lo(S + A - P);
break;
case R_PPC64_GOT16:
*(ub16 *)loc = G - TOC;
break;
case R_PPC64_PLT16_HA:
*(ub16 *)loc = ha(G + GOT - TOC);
break;
@@ -280,14 +294,11 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
template <>
void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
std::span<const ElfRel<E>> rels = get_rels(ctx);
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
for_each_reloc(ctx, [&](const ElfRel<E> &rel, i64 i) ALWAYS_INLINE {
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
continue;
return;
Symbol<E> &sym = *file.symbols[rel.r_sym];
Symbol<E> &sym = *file->symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
SectionFragment<E> *frag;
@@ -298,7 +309,7 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
u64 A = frag ? frag_addend : (i64)rel.r_addend;
auto check = [&](i64 val, i64 lo, i64 hi) {
check_range(ctx, val, i, lo, hi);
check_range(ctx, i, val, lo, hi);
};
switch (rel.r_type) {
@@ -319,7 +330,7 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
<< rel;
}
}
});
}
template <>
@@ -333,7 +344,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
Symbol<E> &sym = *file->symbols[rel.r_sym];
if (sym.is_ifunc())
sym.flags |= NEEDS_GOT | NEEDS_PLT | NEEDS_PPC_OPD;
@@ -351,6 +362,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
if (sym.is_imported)
sym.flags |= NEEDS_PLT;
break;
case R_PPC64_GOT16:
case R_PPC64_PLT16_HA:
sym.flags |= NEEDS_GOT;
break;
@@ -452,49 +464,46 @@ void Thunk<E>::copy_buf(Context<E> &ctx) {
static_assert(E::thunk_size == sizeof(plt_thunk));
static_assert(E::thunk_size == sizeof(local_thunk));
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
u8 *base = ctx.buf + output_section.shdr.sh_offset + offset;
u64 TOC = ctx.extra.TOC->value;
for (Symbol<E> *sym : symbols) {
if (sym->has_got(ctx)) {
i64 val = sym->get_got_addr(ctx) - ctx.extra.TOC->value;
for (i64 i = 0; i < symbols.size(); i++) {
Symbol<E> &sym = *symbols[i];
u8 *buf = base + offsets[i];
if (sym.has_got(ctx)) {
memcpy(buf, pltgot_thunk, sizeof(pltgot_thunk));
i64 val = sym.get_got_addr(ctx) - TOC;
*(ub32 *)(buf + 4) |= higha(val);
*(ub32 *)(buf + 8) |= lo(val);
} else if(sym->has_plt(ctx)) {
i64 val = sym->get_gotplt_addr(ctx) - ctx.extra.TOC->value;
} else if(sym.has_plt(ctx)) {
memcpy(buf, plt_thunk, sizeof(plt_thunk));
i64 val = sym.get_gotplt_addr(ctx) - TOC;
*(ub32 *)(buf + 4) |= higha(val);
*(ub32 *)(buf + 8) |= lo(val);
} else {
i64 val = sym->get_addr(ctx, NO_OPD) - ctx.extra.TOC->value;
memcpy(buf, local_thunk, sizeof(local_thunk));
i64 val = sym.get_addr(ctx, NO_OPD) - TOC;
*(ub32 *)buf |= higha(val);
*(ub32 *)(buf + 4) |= lo(val);
}
buf += E::thunk_size;
}
}
static InputSection<E> *get_opd_section(ObjectFile<E> &file) {
for (std::unique_ptr<InputSection<E>> &isec : file.sections)
for (InputSection<E> *isec : file.sections)
if (isec && isec->name() == ".opd")
return isec.get();
return isec;
return nullptr;
}
static ElfRel<E> *
get_relocation_at(Context<E> &ctx, InputSection<E> &isec, i64 offset) {
get_relocation_at(Context<E> &ctx, InputSection<E> &isec, u64 offset) {
std::span<ElfRel<E>> rels = isec.get_rels(ctx);
auto it = std::lower_bound(rels.begin(), rels.end(), offset,
[](const ElfRel<E> &r, i64 offset) {
return r.r_offset < offset;
auto it = ranges::lower_bound(rels, offset, {}, [](const ElfRel<E> &r) {
return (u64)r.r_offset;
});
if (it == rels.end())
return nullptr;
if (it->r_offset != offset)
if (it == rels.end() || it->r_offset != offset)
return nullptr;
return &*it;
}
@@ -509,9 +518,7 @@ struct OpdSymbol {
static Symbol<E> *
get_opd_sym_at(std::span<OpdSymbol> syms, u64 offset) {
auto it = ranges::lower_bound(syms, offset, {}, &OpdSymbol::r_offset);
if (it == syms.end())
return nullptr;
if (it->r_offset != offset)
if (it == syms.end() || it->r_offset != offset)
return nullptr;
return it->sym;
}
@@ -562,7 +569,7 @@ void ppc64v1_rewrite_opd(Context<E> &ctx) {
InputSection<E> *opd = get_opd_section(*file);
if (!opd)
return;
opd->is_alive = false;
opd->kill();
// Move symbols from .opd to .text.
std::vector<OpdSymbol> opd_syms;
@@ -593,8 +600,8 @@ void ppc64v1_rewrite_opd(Context<E> &ctx) {
ranges::stable_sort(opd_syms, {}, &OpdSymbol::r_offset);
// Rewrite relocations so that they directly refer to .opd.
for (std::unique_ptr<InputSection<E>> &isec : file->sections) {
if (!isec || !isec->is_alive || isec.get() == opd)
for (InputSection<E> *isec : file->sections) {
if (!isec || !isec->is_alive() || isec == opd)
continue;
for (ElfRel<E> &r : isec->get_rels(ctx)) {
@@ -618,11 +625,10 @@ void ppc64v1_rewrite_opd(Context<E> &ctx) {
// refers to the function's .opd entry. This function marks such symbols
// with NEEDS_PPC_OPD.
void ppc64v1_scan_symbols(Context<E> &ctx) {
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
for (Symbol<E> *sym : file->symbols)
if (sym->file == file && sym->is_exported)
if (u32 ty = sym->get_type(); ty == STT_FUNC || ty == STT_GNU_IFUNC)
sym->flags |= NEEDS_PPC_OPD;
ctx.symbol_map.parallel_for_each([](Symbol<E> &sym) {
if (sym.file && !sym.file->is_dso && sym.is_exported)
if (u32 ty = sym.get_type(); ty == STT_FUNC || ty == STT_GNU_IFUNC)
sym.flags |= NEEDS_PPC_OPD;
});
// Functions referenced by the ELF header also have to have .opd entries.
@@ -637,36 +643,55 @@ void ppc64v1_scan_symbols(Context<E> &ctx) {
}
void PPC64OpdSection::add_symbol(Context<E> &ctx, Symbol<E> *sym) {
sym->set_opd_idx(ctx, symbols.size());
sym->aux->opd_idx = symbols.size();
symbols.push_back(sym);
this->shdr.sh_size += ENTRY_SIZE;
}
i64 PPC64OpdSection::get_reldyn_size(Context<E> &ctx) const {
if (ctx.arg.pic)
return symbols.size() * 2;
return 0;
i64 PPC64OpdSection::get_num_dynrels(Context<E> &ctx) const {
return ctx.arg.pic ? symbols.size() * 2 : 0;
}
std::vector<u64> PPC64OpdSection::get_relr_offsets(Context<E> &ctx) {
if (!ctx.arg.pic)
return {};
std::vector<u64> offsets;
offsets.reserve(symbols.size() * 2);
for (Symbol<E> *sym : symbols) {
u64 loc = sym->get_opd_addr(ctx) - this->shdr.sh_addr;
offsets.push_back(loc);
offsets.push_back(loc + 8);
}
return offsets;
}
void PPC64OpdSection::write_dynrels(Context<E> &ctx, ElfRel<E> *buf) const {
if (!ctx.arg.pic)
return;
for (Symbol<E> *sym : symbols) {
u64 loc = sym->get_opd_addr(ctx);
if (!ctx.arg.pack_dyn_relocs_relr || !this->num_relrs ||
loc % sizeof(Word<E>) != 0)
*buf++ = ElfRel<E>(loc, E::R_RELATIVE, 0, sym->get_addr(ctx, NO_PLT | NO_OPD));
if (!ctx.arg.pack_dyn_relocs_relr || !this->num_relrs ||
(loc + 8) % sizeof(Word<E>) != 0)
*buf++ = ElfRel<E>(loc + 8, E::R_RELATIVE, 0, ctx.extra.TOC->value);
}
}
void PPC64OpdSection::copy_buf(Context<E> &ctx) {
ub64 *buf = (ub64 *)(ctx.buf + this->shdr.sh_offset);
ElfRel<E> *rel = nullptr;
if (ctx.arg.pic)
rel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset + reldyn_offset);
for (Symbol<E> *sym : symbols) {
u64 addr = sym->get_addr(ctx, NO_PLT | NO_OPD);
*buf++ = addr;
*buf++ = ctx.extra.TOC->value;
*buf++ = 0;
if (ctx.arg.pic) {
u64 loc = sym->get_opd_addr(ctx);
*rel++ = ElfRel<E>(loc, E::R_RELATIVE, 0, addr);
*rel++ = ElfRel<E>(loc + 8, E::R_RELATIVE, 0, ctx.extra.TOC->value);
}
}
}
} // namespace mold
#endif
+163 -27
View File
@@ -80,6 +80,8 @@
//
// https://github.com/rui314/psabi/blob/main/ppc64v2.pdf
#if MOLD_PPC64V2
#include "mold.h"
namespace mold {
@@ -150,9 +152,14 @@ template <>
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {}
template <>
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
u64 offset, u64 val) {
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, InputSection<E> &isec,
const ElfRel<E> &rel, u64 offset, u64 val) {
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
u64 P = this->shdr.sh_addr + offset;
auto check = [&](i64 val, i64 lo, i64 hi) {
check_range(ctx, isec, rel, val, lo, hi);
};
switch (rel.r_type) {
case R_NONE:
@@ -161,10 +168,11 @@ void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
*(ul64 *)loc = val;
break;
case R_PPC64_REL32:
*(ul32 *)loc = val - this->shdr.sh_addr - offset;
check(val - P, -(1LL << 31), 1LL << 31);
*(ul32 *)loc = val - P;
break;
case R_PPC64_REL64:
*(ul64 *)loc = val - this->shdr.sh_addr - offset;
*(ul64 *)loc = val - P;
break;
default:
Fatal(ctx) << "unsupported relocation in .eh_frame: " << rel;
@@ -191,7 +199,10 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
if (rel.r_type == R_NONE)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
Symbol<E> &sym = *file->symbols[rel.r_sym];
if (sym.get_type() == STT_TLS && sym.is_remaining_undef_weak())
continue;
u8 *loc = base + rel.r_offset;
u64 S = sym.get_addr(ctx);
@@ -204,6 +215,10 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
auto r2save_thunk_addr = [&] { return sym.get_thunk_addr(ctx, P); };
auto no_r2save_thunk_addr = [&] { return sym.get_thunk_addr(ctx, P) + 8; };
auto check = [&](i64 val, i64 lo, i64 hi) {
check_range(ctx, i, val, lo, hi);
};
switch (rel.r_type) {
case R_PPC64_TOC16_HA:
*(ul16 *)loc = ha(S + A - TOC);
@@ -212,6 +227,9 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
*(ul16 *)loc = lo(S + A - TOC);
break;
case R_PPC64_TOC16_DS:
check(S + A - TOC, -(1 << 15), 1 << 15);
*(ul16 *)loc |= (S + A - TOC) & 0xfffc;
break;
case R_PPC64_TOC16_LO_DS:
*(ul16 *)loc |= (S + A - TOC) & 0xfffc;
break;
@@ -243,6 +261,9 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
*(ul32 *)loc |= bits(val, 25, 2) << 2;
}
break;
case R_PPC64_REL14:
*(ul32 *)loc |= bits(S + A - P, 15, 2) << 2;
break;
case R_PPC64_REL32:
*(ul32 *)loc = S + A - P;
break;
@@ -255,6 +276,18 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
case R_PPC64_REL16_LO:
*(ul16 *)loc = lo(S + A - P);
break;
case R_PPC64_GOT16:
*(ul16 *)loc = G - TOC;
break;
case R_PPC64_GOT16_LO:
*(ul16 *)loc = lo(G - TOC);
break;
case R_PPC64_GOT16_HI:
*(ul16 *)loc = hi(G - TOC);
break;
case R_PPC64_GOT16_HA:
*(ul16 *)loc = ha(G - TOC);
break;
case R_PPC64_PLT16_HA:
*(ul16 *)loc = ha(G + GOT - TOC);
break;
@@ -344,14 +377,11 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
template <>
void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
std::span<const ElfRel<E>> rels = get_rels(ctx);
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
for_each_reloc(ctx, [&](const ElfRel<E> &rel, i64 i) ALWAYS_INLINE {
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
continue;
return;
Symbol<E> &sym = *file.symbols[rel.r_sym];
Symbol<E> &sym = *file->symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
SectionFragment<E> *frag;
@@ -383,7 +413,7 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
<< rel;
}
}
});
}
template <>
@@ -397,7 +427,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
Symbol<E> &sym = *file->symbols[rel.r_sym];
if (sym.is_ifunc())
sym.flags |= NEEDS_GOT | NEEDS_PLT;
@@ -416,6 +446,10 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
sym.flags |= NEEDS_PLT;
ctx.extra.is_power10 = true;
break;
case R_PPC64_GOT16:
case R_PPC64_GOT16_LO:
case R_PPC64_GOT16_HI:
case R_PPC64_GOT16_HA:
case R_PPC64_PLT16_HA:
case R_PPC64_PLT_PCREL34:
case R_PPC64_PLT_PCREL34_NOTOC:
@@ -431,12 +465,11 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
ctx.needs_tlsld = true;
break;
case R_PPC64_TPREL16_HA:
case R_PPC64_TPREL16_LO:
case R_PPC64_TPREL16_LO_DS:
case R_PPC64_TPREL34:
check_tlsle(ctx, sym, rel);
break;
case R_PPC64_ADDR64:
case R_PPC64_REL14:
case R_PPC64_REL32:
case R_PPC64_REL64:
case R_PPC64_TOC16_HA:
@@ -456,6 +489,8 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
case R_PPC64_GOT_TPREL16_LO_DS:
case R_PPC64_GOT_TLSGD16_LO:
case R_PPC64_GOT_TLSLD16_LO:
case R_PPC64_TPREL16_LO:
case R_PPC64_TPREL16_LO_DS:
case R_PPC64_TLS:
case R_PPC64_TLSGD:
case R_PPC64_TLSLD:
@@ -518,15 +553,16 @@ void Thunk<E>::copy_buf(Context<E> &ctx) {
static_assert(E::thunk_size == sizeof(local_thunk));
static_assert(E::thunk_size == sizeof(local_thunk_power10));
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
u64 P = output_section.shdr.sh_addr + offset;
u8 *base = ctx.buf + output_section.shdr.sh_offset + offset;
u64 TOC = ctx.extra.TOC->value;
for (Symbol<E> *sym : symbols) {
if (sym->has_plt(ctx)) {
u64 got =
sym->has_got(ctx) ? sym->get_got_addr(ctx) : sym->get_gotplt_addr(ctx);
for (i64 i = 0; i < symbols.size(); i++) {
Symbol<E> &sym = *symbols[i];
u64 P = get_addr() + offsets[i];
u8 *buf = base + offsets[i];
if (sym.has_plt(ctx)) {
u64 got = sym.has_got(ctx) ? sym.get_got_addr(ctx) : sym.get_gotplt_addr(ctx);
if (ctx.extra.is_power10) {
memcpy(buf, plt_thunk_power10, E::thunk_size);
write34(buf + 8, got - P - 8);
@@ -536,7 +572,7 @@ void Thunk<E>::copy_buf(Context<E> &ctx) {
*(ul32 *)(buf + 12) |= lo(got - TOC);
}
} else {
u64 S = sym->get_addr(ctx);
u64 S = sym.get_addr(ctx);
if (ctx.extra.is_power10) {
memcpy(buf, local_thunk_power10, E::thunk_size);
write34(buf + 8, S - P - 8);
@@ -546,15 +582,13 @@ void Thunk<E>::copy_buf(Context<E> &ctx) {
*(ul32 *)(buf + 12) |= lo(S - TOC);
}
}
buf += E::thunk_size;
P += E::thunk_size;
}
}
// GCC may emit references to the following functions in function prologue
// and epiilogue if -Os is specified. For some reason, these functions are
// not in libgcc.a and expected to be synthesized by the linker.
// and epilogue if -Os is specified. For some reason, these functions are
// not in libgcc.a and expected to be synthesized by the linker. There are
// variants for general-purpose, floating-point and vector registers.
const std::vector<std::pair<std::string_view, u32>>
ppc64_save_restore_insns = {
{ "_savegpr0_14", 0xf9c1ff70 }, // std r14,-144(r1)
@@ -644,6 +678,106 @@ ppc64_save_restore_insns = {
{ "_restgpr1_30", 0xebccfff0 }, // ld r30,-16(r12)
{ "_restgpr1_31", 0xebecfff8 }, // ld r31,-8(r12)
{ "", 0x4e800020 }, // blr
{ "_savefpr_14", 0xd9c1ff70 }, // stfd f14,-144(r1)
{ "_savefpr_15", 0xd9e1ff78 }, // stfd f15,-136(r1)
{ "_savefpr_16", 0xda01ff80 }, // stfd f16,-128(r1)
{ "_savefpr_17", 0xda21ff88 }, // stfd f17,-120(r1)
{ "_savefpr_18", 0xda41ff90 }, // stfd f18,-112(r1)
{ "_savefpr_19", 0xda61ff98 }, // stfd f19,-104(r1)
{ "_savefpr_20", 0xda81ffa0 }, // stfd f20,-96(r1)
{ "_savefpr_21", 0xdaa1ffa8 }, // stfd f21,-88(r1)
{ "_savefpr_22", 0xdac1ffb0 }, // stfd f22,-80(r1)
{ "_savefpr_23", 0xdae1ffb8 }, // stfd f23,-72(r1)
{ "_savefpr_24", 0xdb01ffc0 }, // stfd f24,-64(r1)
{ "_savefpr_25", 0xdb21ffc8 }, // stfd f25,-56(r1)
{ "_savefpr_26", 0xdb41ffd0 }, // stfd f26,-48(r1)
{ "_savefpr_27", 0xdb61ffd8 }, // stfd f27,-40(r1)
{ "_savefpr_28", 0xdb81ffe0 }, // stfd f28,-32(r1)
{ "_savefpr_29", 0xdba1ffe8 }, // stfd f29,-24(r1)
{ "_savefpr_30", 0xdbc1fff0 }, // stfd f30,-16(r1)
{ "_savefpr_31", 0xdbe1fff8 }, // stfd f31,-8(r1)
{ "", 0xf8010010 }, // std r0,16(r1)
{ "", 0x4e800020 }, // blr
{ "_restfpr_14", 0xc9c1ff70 }, // lfd f14,-144(r1)
{ "_restfpr_15", 0xc9e1ff78 }, // lfd f15,-136(r1)
{ "_restfpr_16", 0xca01ff80 }, // lfd f16,-128(r1)
{ "_restfpr_17", 0xca21ff88 }, // lfd f17,-120(r1)
{ "_restfpr_18", 0xca41ff90 }, // lfd f18,-112(r1)
{ "_restfpr_19", 0xca61ff98 }, // lfd f19,-104(r1)
{ "_restfpr_20", 0xca81ffa0 }, // lfd f20,-96(r1)
{ "_restfpr_21", 0xcaa1ffa8 }, // lfd f21,-88(r1)
{ "_restfpr_22", 0xcac1ffb0 }, // lfd f22,-80(r1)
{ "_restfpr_23", 0xcae1ffb8 }, // lfd f23,-72(r1)
{ "_restfpr_24", 0xcb01ffc0 }, // lfd f24,-64(r1)
{ "_restfpr_25", 0xcb21ffc8 }, // lfd f25,-56(r1)
{ "_restfpr_26", 0xcb41ffd0 }, // lfd f26,-48(r1)
{ "_restfpr_27", 0xcb61ffd8 }, // lfd f27,-40(r1)
{ "_restfpr_28", 0xcb81ffe0 }, // lfd f28,-32(r1)
{ "_restfpr_29", 0xe8010010 }, // ld r0,16(r1)
{ "", 0xcba1ffe8 }, // lfd f29,-24(r1)
{ "", 0x7c0803a6 }, // mtlr r0
{ "", 0xcbc1fff0 }, // lfd f30,-16(r1)
{ "", 0xcbe1fff8 }, // lfd f31,-8(r1)
{ "", 0x4e800020 }, // blr
{ "_restfpr_30", 0xcbc1fff0 }, // lfd f30,-16(r1)
{ "_restfpr_31", 0xe8010010 }, // ld r0,16(r1)
{ "", 0xcbe1fff8 }, // lfd f31,-8(r1)
{ "", 0x7c0803a6 }, // mtlr r0
{ "", 0x4e800020 }, // blr
{ "_savevr_20", 0x3980ff40 }, // li r12,-192
{ "", 0x7e8c01ce }, // stvx v20,r12,r0
{ "_savevr_21", 0x3980ff50 }, // li r12,-176
{ "", 0x7eac01ce }, // stvx v21,r12,r0
{ "_savevr_22", 0x3980ff60 }, // li r12,-160
{ "", 0x7ecc01ce }, // stvx v22,r12,r0
{ "_savevr_23", 0x3980ff70 }, // li r12,-144
{ "", 0x7eec01ce }, // stvx v23,r12,r0
{ "_savevr_24", 0x3980ff80 }, // li r12,-128
{ "", 0x7f0c01ce }, // stvx v24,r12,r0
{ "_savevr_25", 0x3980ff90 }, // li r12,-112
{ "", 0x7f2c01ce }, // stvx v25,r12,r0
{ "_savevr_26", 0x3980ffa0 }, // li r12,-96
{ "", 0x7f4c01ce }, // stvx v26,r12,r0
{ "_savevr_27", 0x3980ffb0 }, // li r12,-80
{ "", 0x7f6c01ce }, // stvx v27,r12,r0
{ "_savevr_28", 0x3980ffc0 }, // li r12,-64
{ "", 0x7f8c01ce }, // stvx v28,r12,r0
{ "_savevr_29", 0x3980ffd0 }, // li r12,-48
{ "", 0x7fac01ce }, // stvx v29,r12,r0
{ "_savevr_30", 0x3980ffe0 }, // li r12,-32
{ "", 0x7fcc01ce }, // stvx v30,r12,r0
{ "_savevr_31", 0x3980fff0 }, // li r12,-16
{ "", 0x7fec01ce }, // stvx v31,r12,r0
{ "", 0x4e800020 }, // blr
{ "_restvr_20", 0x3980ff40 }, // li r12,-192
{ "", 0x7e8c00ce }, // lvx v20,r12,r0
{ "_restvr_21", 0x3980ff50 }, // li r12,-176
{ "", 0x7eac00ce }, // lvx v21,r12,r0
{ "_restvr_22", 0x3980ff60 }, // li r12,-160
{ "", 0x7ecc00ce }, // lvx v22,r12,r0
{ "_restvr_23", 0x3980ff70 }, // li r12,-144
{ "", 0x7eec00ce }, // lvx v23,r12,r0
{ "_restvr_24", 0x3980ff80 }, // li r12,-128
{ "", 0x7f0c00ce }, // lvx v24,r12,r0
{ "_restvr_25", 0x3980ff90 }, // li r12,-112
{ "", 0x7f2c00ce }, // lvx v25,r12,r0
{ "_restvr_26", 0x3980ffa0 }, // li r12,-96
{ "", 0x7f4c00ce }, // lvx v26,r12,r0
{ "_restvr_27", 0x3980ffb0 }, // li r12,-80
{ "", 0x7f6c00ce }, // lvx v27,r12,r0
{ "_restvr_28", 0x3980ffc0 }, // li r12,-64
{ "", 0x7f8c00ce }, // lvx v28,r12,r0
{ "_restvr_29", 0x3980ffd0 }, // li r12,-48
{ "", 0x7fac00ce }, // lvx v29,r12,r0
{ "_restvr_30", 0x3980ffe0 }, // li r12,-32
{ "", 0x7fcc00ce }, // lvx v30,r12,r0
{ "_restvr_31", 0x3980fff0 }, // li r12,-16
{ "", 0x7fec00ce }, // lvx v31,r12,r0
{ "", 0x4e800020 }, // blr
};
void PPC64SaveRestoreSection::copy_buf(Context<E> &ctx) {
@@ -658,3 +792,5 @@ u64 get_eflags(Context<E> &ctx) {
}
} // namespace mold
#endif
+118 -55
View File
@@ -158,9 +158,14 @@ void write_pltgot_entry<E>(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
}
template <>
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
u64 offset, u64 val) {
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, InputSection<E> &isec,
const ElfRel<E> &rel, u64 offset, u64 val) {
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
u64 P = this->shdr.sh_addr + offset;
auto check = [&](i64 val, i64 lo, i64 hi) {
check_range(ctx, isec, rel, val, lo, hi);
};
switch (rel.r_type) {
case R_NONE:
@@ -193,7 +198,8 @@ void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
*(U32<E> *)loc = val;
break;
case R_RISCV_32_PCREL:
*(U32<E> *)loc = val - this->shdr.sh_addr - offset;
check(val - P, -(1LL << 31), 1LL << 31);
*(U32<E> *)loc = val - P;
break;
default:
Fatal(ctx) << "unsupported relocation in .eh_frame: " << rel;
@@ -252,26 +258,26 @@ find_paired_reloc(Context<E> &ctx, InputSection<E> &isec,
// ld t0, 0(t0) # R_RISCV_PCREL_LO12_I(.L0), R_RISCV_RELAX
static bool is_got_load_pair(Context<E> &ctx, InputSection<E> &isec,
std::span<const ElfRel<E>> rels, i64 i) {
u8 *buf = (u8 *)isec.contents.data();
u8 *buf = isec.contents;
return i + 3 < rels.size() &&
rels[i].r_type == R_RISCV_GOT_HI20 &&
rels[i + 1].r_type == R_RISCV_RELAX &&
rels[i + 2].r_type == R_RISCV_PCREL_LO12_I &&
rels[i + 3].r_type == R_RISCV_RELAX &&
rels[i].r_offset == rels[i + 2].r_offset - 4 &&
rels[i].r_offset == isec.file.symbols[rels[i + 2].r_sym]->value &&
rels[i].r_offset == isec.file->symbols[rels[i + 2].r_sym]->value &&
get_rd(buf + rels[i].r_offset) == get_rd(buf + rels[i + 2].r_offset);
}
template <>
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
std::span<const ElfRel<E>> rels = get_rels(ctx);
std::span<ElfRel<E>> rels = get_rels(ctx);
std::span<RelocDelta> deltas = extra.r_deltas;
i64 k = 0;
u8 *buf = (u8 *)contents.data();
u8 *buf = contents;
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE || rel.r_type == R_RISCV_RELAX)
continue;
@@ -287,7 +293,10 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
r_delta = deltas[k - 1].delta;
}
Symbol<E> &sym = *file.symbols[rel.r_sym];
Symbol<E> &sym = *file->symbols[rel.r_sym];
if (sym.get_type() == STT_TLS && sym.is_remaining_undef_weak())
continue;
i64 r_offset = rel.r_offset - r_delta;
u8 *loc = base + r_offset;
@@ -301,10 +310,20 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
check_range(ctx, i, val, lo, hi);
};
auto utype = [&](i64 val) {
check(val, -(1LL << 31) - 0x800, (1LL << 31) - 0x800);
write_utype(loc, val);
};
auto rewrite = [&](i64 idx, u32 ty) {
if (ctx.arg.emit_relocs)
rels[idx].r_type = ty;
};
switch (rel.r_type) {
case R_RISCV_32:
assert(E::is_64);
*(U32<E> *)loc = S + A;
if (E::is_64)
*(U32<E> *)loc = S + A;
break;
case R_RISCV_64:
break;
@@ -321,28 +340,26 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
i64 val = S + A - P;
i64 rd = get_rd(buf + rel.r_offset + 4);
// Calling an undefined weak symbol does not make sense.
// We make such call into an infinite loop. This should
// help debugging of a faulty program.
if (sym.esym().is_undef_weak())
val = 0;
if (removed_bytes == 4) {
// auipc + jalr -> jal
*(ul32 *)loc = (rd << 7) | 0b1101111;
write_jtype(loc, val);
rewrite(i, R_RISCV_JAL);
} else if (removed_bytes == 6 && rd == 0) {
// auipc + jalr -> c.j
*(ul16 *)loc = 0b101'00000000000'01;
write_cjtype(loc, val);
rewrite(i, R_RISCV_RVC_JUMP);
} else if (removed_bytes == 6 && rd == 1) {
// auipc + jalr -> c.jal
assert(!E::is_64);
*(ul16 *)loc = 0b001'00000000000'01;
write_cjtype(loc, val);
rewrite(i, R_RISCV_RVC_JUMP);
} else {
assert(removed_bytes == 0);
check(val, -(1LL << 31), 1LL << 31);
if (!sym.is_remaining_undef_weak())
check(val, -(1LL << 31) - 0x800, (1LL << 31) - 0x800);
write_utype(loc, val);
write_itype(loc + 4, val);
}
@@ -359,11 +376,17 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
// c.li <rd>, val
*(ul16 *)loc = 0b010'0'00000'00000'01 | (rd << 7);
write_citype(loc, sym.get_addr(ctx));
// The value is materialized directly, so neither this nor the paired
// PCREL_LO12 (the load) needs a relocation anymore.
rewrite(i, R_NONE);
rewrite(i + 2, R_NONE);
i += 3;
} else if (removed_bytes == 4) {
// addi <rd>, zero, val
*(ul32 *)loc = 0b0010011 | (rd << 7);
write_itype(loc, sym.get_addr(ctx));
rewrite(i, R_NONE);
rewrite(i + 2, R_NONE);
i += 3;
} else {
assert(removed_bytes == 0);
@@ -372,31 +395,31 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
if (ctx.arg.relax && sym.is_pcrel_linktime_const(ctx) &&
is_got_load_pair(ctx, *this, rels, i) && is_int(val, 32)) {
// auipc <rd>, %hi20(val)
write_utype(loc, val);
utype(val);
// addi <rd>, <rd>, %lo12(val)
*(ul32 *)(loc + 4) = 0b0010011 | (rd << 15) | (rd << 7);
write_itype(loc + 4, val);
i += 3;
} else {
write_utype(loc, G + GOT + A - P);
utype(G + GOT + A - P);
}
}
break;
}
case R_RISCV_TLS_GOT_HI20:
write_utype(loc, sym.get_gottp_addr(ctx) + A - P);
utype(sym.get_gottp_addr(ctx) + A - P);
break;
case R_RISCV_TLS_GD_HI20:
write_utype(loc, sym.get_tlsgd_addr(ctx) + A - P);
utype(sym.get_tlsgd_addr(ctx) + A - P);
break;
case R_RISCV_PCREL_HI20:
write_utype(loc, S + A - P);
utype(S + A - P);
break;
case R_RISCV_PCREL_LO12_I:
case R_RISCV_PCREL_LO12_S: {
const ElfRel<E> &rel2 = find_paired_reloc(ctx, *this, rels, sym, i);
Symbol<E> &sym2 = *file.symbols[rel2.r_sym];
Symbol<E> &sym2 = *file->symbols[rel2.r_sym];
auto write =
(rel.r_type == R_RISCV_PCREL_LO12_I) ? write_itype : write_stype;
@@ -423,15 +446,19 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
break;
}
case R_RISCV_HI20:
// lui (+ addi) => an instruction holding a link-time constant. The lui
// may be compressed to c.lui (removed 2 bytes) or deleted outright
// (removed 4 bytes); either way it no longer needs a relocation.
if (removed_bytes == 2) {
// Rewrite LUI with C.LUI
i64 rd = get_rd(buf + rel.r_offset);
*(ul16 *)loc = 0b011'0'00000'00000'01 | (rd << 7);
write_citype(loc, (S + A + 0x800) >> 12);
} else if (removed_bytes == 0) {
check(S + A, -(1LL << 31), 1LL << 31);
write_utype(loc, S + A);
utype(S + A);
}
if (removed_bytes)
rewrite(i, R_NONE);
break;
case R_RISCV_LO12_I:
case R_RISCV_LO12_S:
@@ -448,14 +475,19 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
break;
case R_RISCV_TPREL_HI20:
assert(removed_bytes == 0 || removed_bytes == 4);
// lui + add => deleted; the variable is accessed relative to tp directly.
if (removed_bytes == 0)
write_utype(loc, S + A - ctx.tp_addr);
utype(S + A - ctx.tp_addr);
else
rewrite(i, R_NONE);
break;
case R_RISCV_TPREL_ADD:
// This relocation just annotates an ADD instruction that can be
// removed when a TPREL is relaxed. No value is needed to be
// written.
assert(removed_bytes == 0 || removed_bytes == 4);
if (removed_bytes)
rewrite(i, R_NONE);
break;
case R_RISCV_TPREL_LO12_I:
case R_RISCV_TPREL_LO12_S: {
@@ -473,7 +505,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
}
case R_RISCV_TLSDESC_HI20:
// RISC-V TLSDESC uses the following code sequence to materialize
// a TP-relative address in x0.
// a TP-relative address in a0.
//
// .L0:
// auipc tX, 0
@@ -510,18 +542,25 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
// If the code-shrinking relaxation is disabled, we may leave
// original useless instructions instead of deleting them, but we
// accept that because relaxations are enabled by default.
if (sym.has_tlsdesc(ctx) && removed_bytes == 0)
write_utype(loc, sym.get_tlsdesc_addr(ctx) + A - P);
if (sym.has_tlsdesc(ctx)) {
if (removed_bytes == 0)
utype(sym.get_tlsdesc_addr(ctx) + A - P);
} else {
rewrite(i, R_NONE);
}
break;
case R_RISCV_TLSDESC_LOAD_LO12:
case R_RISCV_TLSDESC_ADD_LO12:
case R_RISCV_TLSDESC_CALL: {
const ElfRel<E> &rel2 = find_paired_reloc(ctx, *this, rels, sym, i);
Symbol<E> &sym2 = *file->symbols[rel2.r_sym];
if (!sym2.has_tlsdesc(ctx))
rewrite(i, R_NONE);
if (removed_bytes == 4)
break;
const ElfRel<E> &rel2 = find_paired_reloc(ctx, *this, rels, sym, i);
Symbol<E> &sym2 = *file.symbols[rel2.r_sym];
u64 S = sym2.get_addr(ctx);
u64 A = rel2.r_addend;
u64 P = get_addr() + rel2.r_offset - get_r_delta(*this, rel2.r_offset);
@@ -538,10 +577,10 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
write_itype(loc, sym2.get_tlsdesc_addr(ctx) + A - P);
} else if (sym2.has_gottp(ctx)) {
*(ul32 *)loc = 0x517; // auipc a0,<hi20>
write_utype(loc, sym2.get_gottp_addr(ctx) + A - P);
utype(sym2.get_gottp_addr(ctx) + A - P);
} else {
*(ul32 *)loc = 0x537; // lui a0,<hi20>
write_utype(loc, S + A - ctx.tp_addr);
utype(S + A - ctx.tp_addr);
}
break;
case R_RISCV_TLSDESC_CALL:
@@ -564,7 +603,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
break;
}
case R_RISCV_ADD8:
loc += S + A;
*loc += S + A;
break;
case R_RISCV_ADD16:
*(U16<E> *)loc += S + A;
@@ -576,7 +615,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
*(U64<E> *)loc += S + A;
break;
case R_RISCV_SUB8:
loc -= S + A;
*loc -= S + A;
break;
case R_RISCV_SUB16:
*(U16<E> *)loc -= S + A;
@@ -632,6 +671,9 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
case R_RISCV_32_PCREL:
*(U32<E> *)loc = S + A - P;
break;
case R_RISCV_GOT32_PCREL:
*(U32<E> *)loc = G + GOT + A - P;
break;
case R_RISCV_SET_ULEB128:
overwrite_uleb(loc, S + A);
break;
@@ -646,14 +688,11 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
template <>
void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
std::span<const ElfRel<E>> rels = get_rels(ctx);
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
for_each_reloc(ctx, [&](const ElfRel<E> &rel, i64) ALWAYS_INLINE {
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
continue;
return;
Symbol<E> &sym = *file.symbols[rel.r_sym];
Symbol<E> &sym = *file->symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
SectionFragment<E> *frag;
@@ -673,6 +712,18 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
else
*(U64<E> *)loc = S + A;
break;
case R_RISCV_TLS_DTPREL32:
if (std::optional<u64> val = get_tombstone(sym, frag))
*(U32<E> *)loc = *val;
else
*(U32<E> *)loc = S + A - ctx.dtp_addr;
break;
case R_RISCV_TLS_DTPREL64:
if (std::optional<u64> val = get_tombstone(sym, frag))
*(U64<E> *)loc = *val;
else
*(U64<E> *)loc = S + A - ctx.dtp_addr;
break;
case R_RISCV_ADD8:
*loc += S + A;
break;
@@ -723,7 +774,7 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
<< rel;
break;
}
}
});
}
template <>
@@ -737,7 +788,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
Symbol<E> &sym = *file->symbols[rel.r_sym];
if (sym.is_ifunc())
sym.flags |= NEEDS_GOT | NEEDS_PLT;
@@ -757,6 +808,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
sym.flags |= NEEDS_PLT;
break;
case R_RISCV_GOT_HI20:
case R_RISCV_GOT32_PCREL:
sym.flags |= NEEDS_GOT;
break;
case R_RISCV_TLS_GOT_HI20:
@@ -773,9 +825,6 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
scan_pcrel(ctx, sym, rel);
break;
case R_RISCV_TPREL_HI20:
case R_RISCV_TPREL_LO12_I:
case R_RISCV_TPREL_LO12_S:
case R_RISCV_TPREL_ADD:
check_tlsle(ctx, sym, rel);
break;
case R_RISCV_64:
@@ -785,6 +834,9 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
case R_RISCV_PCREL_LO12_S:
case R_RISCV_LO12_I:
case R_RISCV_LO12_S:
case R_RISCV_TPREL_LO12_I:
case R_RISCV_TPREL_LO12_S:
case R_RISCV_TPREL_ADD:
case R_RISCV_TLSDESC_LOAD_LO12:
case R_RISCV_TLSDESC_ADD_LO12:
case R_RISCV_TLSDESC_CALL:
@@ -843,17 +895,17 @@ u64 get_eflags(Context<E> &ctx) {
template <>
void shrink_section(Context<E> &ctx, InputSection<E> &isec) {
std::span<const ElfRel<E>> rels = isec.get_rels(ctx);
std::vector<RelocDelta> &deltas = isec.extra.r_deltas;
std::vector<RelocDelta> deltas;
i64 r_delta = 0;
u8 *buf = (u8 *)isec.contents.data();
u8 *buf = isec.contents;
// True if we can use 2-byte instructions. This is usually true on
// Unix because RV64GC is generally considered the baseline hardware.
bool use_rvc = isec.file.get_eflags() & EF_RISCV_RVC;
bool use_rvc = isec.file->get_eflags() & EF_RISCV_RVC;
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &r = rels[i];
Symbol<E> &sym = *isec.file.symbols[r.r_sym];
Symbol<E> &sym = *isec.file->symbols[r.r_sym];
auto remove = [&](i64 d) {
r_delta += d;
@@ -867,9 +919,14 @@ void shrink_section(Context<E> &ctx, InputSection<E> &isec) {
// follows the NOPs is aligned to a specified alignment boundary.
if (r.r_type == R_RISCV_ALIGN) {
// The total bytes of NOPs is stored to r_addend, so the next
// instruction is r_addend away.
// instruction is r_addend away. The alignment itself is not recorded
// anywhere; it is the smallest power of two greater than r_addend,
// because the assembler emits as many NOP bytes as the worst case
// requires, which is the alignment minus the minimum instruction
// size. For example, `.balign 4` yields r_addend 2 in RVC code and
// `.balign 8` yields 4 in non-RVC code.
u64 P = isec.get_addr() + r.r_offset - r_delta;
u64 desired = align_to(P, bit_ceil(r.r_addend));
u64 desired = align_to(P, bit_ceil(r.r_addend + 1));
u64 actual = P + r.r_addend;
if (desired != actual)
remove(actual - desired);
@@ -979,7 +1036,7 @@ void shrink_section(Context<E> &ctx, InputSection<E> &isec) {
case R_RISCV_TLSDESC_LOAD_LO12:
case R_RISCV_TLSDESC_ADD_LO12: {
const ElfRel<E> &rel2 = find_paired_reloc(ctx, isec, rels, sym, i);
Symbol<E> &sym2 = *isec.file.symbols[rel2.r_sym];
Symbol<E> &sym2 = *isec.file->symbols[rel2.r_sym];
if (r.r_type == R_RISCV_TLSDESC_LOAD_LO12) {
if (!sym2.has_tlsdesc(ctx))
@@ -996,6 +1053,12 @@ void shrink_section(Context<E> &ctx, InputSection<E> &isec) {
}
}
if (deltas.empty())
return;
RelocDelta *p = ctx.arena.template allocate<RelocDelta>(deltas.size());
ranges::copy(deltas, p);
isec.extra.r_deltas = {p, deltas.size()};
isec.sh_size -= r_delta;
}
+46 -24
View File
@@ -35,6 +35,8 @@
//
// https://github.com/IBM/s390x-abi/releases/download/v1.6.1/lzsabi_s390x.pdf
#if MOLD_S390X
#include "mold.h"
namespace mold {
@@ -48,18 +50,24 @@ static void write_mid20(u8 *loc, u64 val) {
template <>
void write_plt_header(Context<E> &ctx, u8 *buf) {
static u8 insn[] = {
// Compute PLT_INDEX
// Compute the offset into .rela.plt. This is equivalent to
// (%r0 - %r1 - 48 - 14) * 3/2 where %r0 is the PLT entry address
// plus 14, %r1 is the start address of .plt, and 48 is the size
// of this PLT header. We multiply by 3/2 because each PLT entry
// is 16 bytes, whereas each .rela.plt entry is 24 bytes.
0xb9, 0x09, 0x00, 0x01, // sgr %r0, %r1
0xa7, 0x0b, 0xff, 0xc2, // aghi %r0, -62
0xeb, 0x10, 0x00, 0x01, 0x00, 0x0c, // srlg %r1, %r0, 1
0xb9, 0x08, 0x00, 0x01, // agr %r0, %r1
// Store the computed value to 56(%r15) and .got.plt[1] to 48(%15)
// where %r15 is the stack pointer.
0xe3, 0x00, 0xf0, 0x38, 0x00, 0x24, // stg %r0, 56(%r15)
// Branch to _dl_runtime_resolve
0xc0, 0x10, 0, 0, 0, 0, // larl %r1, GOTPLT_OFFSET
0xd2, 0x07, 0xf0, 0x30, 0x10, 0x08, // mvc 48(8, %r15), 8(%r1)
// Branch to _dl_runtime_resolve
0xe3, 0x10, 0x10, 0x10, 0x00, 0x04, // lg %r1, 16(%r1)
0x07, 0xf1, // br %r1
0x07, 0x00, 0x07, 0x00, // nopr; nopr
0x00, 0x00, 0x00, 0x00, // (filler)
};
memcpy(buf, insn, sizeof(insn));
@@ -73,7 +81,7 @@ void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
0xc0, 0x10, 0, 0, 0, 0, // larl %r1, GOTPLT_ENTRY_OFFSET
0xe3, 0x10, 0x10, 0x00, 0x00, 0x04, // lg %r1, (%r1)
0x0d, 0x01, // basr %r0, %r1
0x07, 0x00, // nopr
0x00, 0x00, // (filler)
};
memcpy(buf, insn, sizeof(insn));
@@ -86,7 +94,7 @@ void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
0xc0, 0x10, 0, 0, 0, 0, // larl %r1, GOT_ENTRY_OFFSET
0xe3, 0x10, 0x10, 0x00, 0x00, 0x04, // lg %r1, (%r1)
0x07, 0xf1, // br %r1
0x07, 0x00, // nopr
0x00, 0x00, // (filler)
};
memcpy(buf, insn, sizeof(insn));
@@ -94,15 +102,21 @@ void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
}
template <>
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
u64 offset, u64 val) {
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, InputSection<E> &isec,
const ElfRel<E> &rel, u64 offset, u64 val) {
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
u64 P = this->shdr.sh_addr + offset;
auto check = [&](i64 val, i64 lo, i64 hi) {
check_range(ctx, isec, rel, val, lo, hi);
};
switch (rel.r_type) {
case R_NONE:
break;
case R_390_PC32:
*(ub32 *)loc = val - this->shdr.sh_addr - offset;
check(val - P, -(1LL << 31), 1LL << 31);
*(ub32 *)loc = val - P;
break;
case R_390_64:
*(ub64 *)loc = val;
@@ -114,14 +128,17 @@ void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
template <>
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
std::span<const ElfRel<E>> rels = get_rels(ctx);
std::span<ElfRel<E>> rels = get_rels(ctx);
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
Symbol<E> &sym = *file->symbols[rel.r_sym];
if (sym.get_type() == STT_TLS && sym.is_remaining_undef_weak())
continue;
u8 *loc = base + rel.r_offset;
u64 S = sym.get_addr(ctx);
@@ -151,7 +168,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
break;
case R_390_12:
check(S + A, 0, 1 << 12);
*(ul16 *)loc |= bits(S + A, 11, 0);
*(ub16 *)loc |= bits(S + A, 11, 0);
break;
case R_390_16:
check(S + A, 0, 1 << 16);
@@ -169,7 +186,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
case R_390_PC12DBL:
case R_390_PLT12DBL:
check_dbl(S + A - P, -(1 << 12), 1 << 12);
*(ul16 *)loc |= bits(S + A - P, 12, 1);
*(ub16 *)loc |= bits(S + A - P, 12, 1);
break;
case R_390_PC16:
check(S + A - P, -(1 << 15), 1 << 15);
@@ -194,14 +211,18 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
*(ub32 *)loc |= bits(S + A - P, 24, 1);
break;
case R_390_PC32DBL:
case R_390_PLT32DBL:
check_dbl(S + A - P, -(1LL << 32), 1LL << 32);
*(ub32 *)loc = (S + A - P) >> 1;
break;
case R_390_PLT32DBL:
if (!sym.is_remaining_undef_weak())
check_dbl(S + A - P, -(1LL << 32), 1LL << 32);
*(ub32 *)loc = (S + A - P) >> 1;
break;
case R_390_GOT12:
case R_390_GOTPLT12:
check(G + A, 0, 1 << 12);
*(ul16 *)loc |= bits(G + A, 11, 0);
*(ub16 *)loc |= bits(G + A, 11, 0);
break;
case R_390_GOT16:
case R_390_GOTPLT16:
@@ -254,6 +275,8 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
is_int(val, 33)) {
*(ub16 *)(loc - 2) = 0xc000 | (op & 0x00f0);
*(ub32 *)loc = val >> 1;
if (ctx.arg.emit_relocs)
rel.r_type = R_390_PC32DBL;
break;
}
}
@@ -334,14 +357,11 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
template <>
void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
std::span<const ElfRel<E>> rels = get_rels(ctx);
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
for_each_reloc(ctx, [&](const ElfRel<E> &rel, i64 i) ALWAYS_INLINE {
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
continue;
return;
Symbol<E> &sym = *file.symbols[rel.r_sym];
Symbol<E> &sym = *file->symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
SectionFragment<E> *frag;
@@ -352,7 +372,7 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
u64 A = frag ? frag_addend : (i64)rel.r_addend;
auto check = [&](i64 val, i64 lo, i64 hi) {
check_range(ctx, val, i, lo, hi);
check_range(ctx, i, val, lo, hi);
};
switch (rel.r_type) {
@@ -375,7 +395,7 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
default:
Fatal(ctx) << *this << ": apply_reloc_nonalloc: " << rel;
}
}
});
}
template <>
@@ -389,7 +409,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
Symbol<E> &sym = *file->symbols[rel.r_sym];
if (sym.is_ifunc())
sym.flags |= NEEDS_GOT | NEEDS_PLT;
@@ -483,3 +503,5 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
}
} // namespace mold
#endif
+36 -25
View File
@@ -47,9 +47,10 @@
// with a piece of code to store the address of .got to %r12.
// We can use the register in our PLT for position-independent output.
//
// - Even though it uses the RELA-type relocations, relocation addends
// are stored not to the r_addend field but to the relocated section
// contents for some reason. Therefore, it's effectively REL.
// - Even though it uses RELA-type relocations, object files store
// addends not in the r_addend field but in the relocated section
// contents. Dynamic relocations, however, follow the usual RELA
// convention.
//
// - It looks like the ecosystem has bit-rotted. Some tests, especially
// one using C++ exceptions, don't pass even with GNU ld.
@@ -66,8 +67,8 @@ namespace mold {
using E = MOLD_TARGET;
// Even though SH-4 uses RELA-type relocations, addends are stored to
// relocated places for some reason.
// Even though SH-4 uses RELA-type relocations, addends are stored in
// the relocated places for some reason.
template <>
i64 get_addend(u8 *loc, const ElfRel<E> &rel) {
switch (rel.r_type) {
@@ -137,7 +138,7 @@ void write_plt_header(Context<E> &ctx, u8 *buf) {
0x5221, // mov.l @(4, r2), r2
0x402b, // jmp @r0
0xe000, // mov #0, r0
0x0009, // nop
0xfffd, // (illegal)
0, 0, // 1: .long GOTPLT
};
@@ -151,32 +152,36 @@ template <>
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
if (ctx.arg.pic) {
constexpr U16<E> insn[] = {
0xd001, // mov.l 1f, r0
0xd002, // mov.l 1f, r0
0x00ce, // mov.l @(r0, r12), r0
0xd102, // mov.l 2f, r1
0x402b, // jmp @r0
0xd101, // mov.l 2f, r1
0x0009, // nop
0x0009, // nop
0, 0, // 1: .long GOTPLT_ENTRY
0, 0, // 2: .long INDEX_IN_RELPLT
};
static_assert(sizeof(insn) == E::plt_size);
memcpy(buf, insn, sizeof(insn));
*(U32<E> *)(buf + 8) = sym.get_gotplt_addr(ctx) - ctx.got->shdr.sh_addr;
*(U32<E> *)(buf + 12) = sym.get_plt_idx(ctx) * sizeof(ElfRel<E>);
*(U32<E> *)(buf + 12) = sym.get_gotplt_addr(ctx) - ctx.got->shdr.sh_addr;
*(U32<E> *)(buf + 16) = sym.get_plt_idx(ctx) * sizeof(ElfRel<E>);
} else {
constexpr U16<E> insn[] = {
0xd001, // mov.l 1f, r0
0xd002, // mov.l 1f, r0
0x6002, // mov.l @r0, r0
0xd102, // mov.l 2f, r1
0x402b, // jmp @r0
0xd101, // mov.l 2f, r1
0x0009, // nop
0x0009, // nop
0, 0, // 1: .long GOTPLT_ENTRY
0, 0, // 2: .long INDEX_IN_RELPLT
};
static_assert(sizeof(insn) == E::plt_size);
memcpy(buf, insn, sizeof(insn));
*(U32<E> *)(buf + 8) = sym.get_gotplt_addr(ctx);
*(U32<E> *)(buf + 12) = sym.get_plt_idx(ctx) * sizeof(ElfRel<E>);
*(U32<E> *)(buf + 12) = sym.get_gotplt_addr(ctx);
*(U32<E> *)(buf + 16) = sym.get_plt_idx(ctx) * sizeof(ElfRel<E>);
}
}
@@ -210,8 +215,8 @@ void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
}
template <>
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
u64 offset, u64 val) {
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, InputSection<E> &isec,
const ElfRel<E> &rel, u64 offset, u64 val) {
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
switch (rel.r_type) {
@@ -237,7 +242,10 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
if (rel.r_type == R_NONE)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
Symbol<E> &sym = *file->symbols[rel.r_sym];
if (sym.get_type() == STT_TLS && sym.is_remaining_undef_weak())
continue;
u8 *loc = base + rel.r_offset;
u64 S = sym.get_addr(ctx);
@@ -285,14 +293,11 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
template <>
void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
std::span<const ElfRel<E>> rels = get_rels(ctx);
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
for_each_reloc(ctx, [&](const ElfRel<E> &rel, i64) ALWAYS_INLINE {
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
continue;
return;
Symbol<E> &sym = *file.symbols[rel.r_sym];
Symbol<E> &sym = *file->symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
SectionFragment<E> *frag;
@@ -309,11 +314,17 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
else
*(U32<E> *)loc = S + A;
break;
case R_SH_TLS_LDO_32:
if (std::optional<u64> val = get_tombstone(sym, frag))
*(U32<E> *)loc = *val;
else
*(U32<E> *)loc = S + A - ctx.dtp_addr;
break;
default:
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
<< rel;
}
}
});
}
template <>
@@ -326,7 +337,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
Symbol<E> &sym = *file->symbols[rel.r_sym];
if (sym.is_ifunc())
Error(ctx) << sym << ": GNU ifunc symbol is not supported on sh4";
+137 -61
View File
@@ -53,6 +53,8 @@
//
// https://github.com/rui314/psabi/blob/main/sparc.pdf
#if MOLD_SPARC64
#include "mold.h"
namespace mold {
@@ -76,26 +78,77 @@ void write_plt_header(Context<E> &ctx, u8 *buf) {
memset(buf, 0, E::plt_hdr_size);
}
// Returns the byte offset within .plt of the data pointer for a large SPARC
// PLT entry. See write_plt_entry below for the block layout this assumes.
i64 sparc_plt_ptr_offset(Context<E> &ctx, i64 pltidx) {
i64 i = pltidx - sparc_num_small_plt;
i64 block = i / 160;
i64 nlarge = (i64)ctx.plt->symbols.size() - sparc_num_small_plt;
i64 nstub = std::min<i64>(160, nlarge - block * 160);
return 0x100000 + block * 5120 + nstub * 24 + (i % 160) * 8;
}
template <>
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
static ub32 insn[] = {
0x0300'0000, // sethi (. - .PLT0), %g1
0x3068'0000, // ba,a %xcc, .PLT1
0x0100'0000, // nop
0x0100'0000, // nop
0x0100'0000, // nop
0x0100'0000, // nop
0x0100'0000, // nop
0x0100'0000, // nop
};
// SPARC uses two PLT entry formats. A "small" entry branches directly to
// the resolver stub (.PLT1) with a BPcc instruction whose reach is only
// ±1 MiB. Once the PLT grows past that (0x100000 bytes), we switch to a
// "large" format: rather than branching to the resolver, a large entry
// loads a 64-bit value from a nearby data pointer and jumps to (that value
// + its own address).
//
// Large entries are grouped into blocks of 160: each block is 160 code
// stubs followed by 160 8-byte data pointers, one per stub. The stubs must
// sit on a fixed grid, 24 bytes apart, because that is how the loader finds
// them: on a lazy bind the stub jumps to the resolver (.PLT0) with only its
// own address, and the loader derives which symbol to resolve from that
// address. Nothing may sit between two stubs, so each stub's pointer lives
// in the block's pointer region, which it reaches with a signed 13-bit ldx
// offset (see to_plt_offset). This layout is dictated by the loader; we
// cannot rearrange or simplify it.
i64 idx = sym.get_plt_idx(ctx);
u64 plt0 = ctx.plt->shdr.sh_addr;
u64 plt1 = ctx.plt->shdr.sh_addr + E::plt_size;
u64 entry = sym.get_plt_addr(ctx);
if (idx < sparc_num_small_plt) {
static ub32 insn[] = {
0x0300'0000, // sethi (. - .PLT0), %g1
0x3068'0000, // ba,a %xcc, .PLT1
0x0100'0000, // nop
0x0100'0000, // nop
0x0100'0000, // nop
0x0100'0000, // nop
0x0100'0000, // nop
0x0100'0000, // nop
};
memcpy(buf, insn, sizeof(insn));
*(ub32 *)buf |= bits(entry - plt0, 21, 0);
*(ub32 *)(buf + 4) |= bits(plt1 - entry - 4, 20, 2);
u64 plt0 = ctx.plt->shdr.sh_addr;
u64 plt1 = ctx.plt->shdr.sh_addr + E::plt_size;
u64 entry = sym.get_plt_addr(ctx);
memcpy(buf, insn, sizeof(insn));
*(ub32 *)buf |= bits(entry - plt0, 21, 0);
*(ub32 *)(buf + 4) |= bits(plt1 - entry - 4, 20, 2);
} else {
static ub32 insn[] = {
0x8a10'000f, // mov %o7, %g5
0x4000'0002, // call . + 8
0x0100'0000, // nop
0xc25b'e000, // ldx [ %o7 + .ptr ], %g1
0x83c3'c001, // jmpl %o7 + %g1, %g1
0x9e10'0005, // mov %g5, %o7
};
u64 plt = ctx.plt->shdr.sh_addr;
u64 call = sym.get_plt_addr(ctx) + 4;
i64 ptroff = sparc_plt_ptr_offset(ctx, idx);
memcpy(buf, insn, sizeof(insn));
*(ub32 *)(buf + 12) |= bits(plt + ptroff - call, 12, 0);
// The data pointer initially holds (.PLT0 - call) so that the first call
// jumps to .PLT0, where the loader's lazy resolver lives. The resolver
// later overwrites it with (target - call).
*(ub64 *)(ctx.buf + ctx.plt->shdr.sh_offset + ptroff) = plt - call;
}
}
template <>
@@ -116,9 +169,14 @@ void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
}
template <>
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
u64 offset, u64 val) {
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, InputSection<E> &isec,
const ElfRel<E> &rel, u64 offset, u64 val) {
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
u64 P = this->shdr.sh_addr + offset;
auto check = [&](i64 val, i64 lo, i64 hi) {
check_range(ctx, isec, rel, val, lo, hi);
};
switch (rel.r_type) {
case R_NONE:
@@ -128,7 +186,8 @@ void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
*(ub64 *)loc = val;
break;
case R_SPARC_DISP32:
*(ub32 *)loc = val - this->shdr.sh_addr - offset;
check(val - P, -(1LL << 31), 1LL << 31);
*(ub32 *)loc = val - P;
break;
default:
Fatal(ctx) << "unsupported relocation in .eh_frame: " << rel;
@@ -139,12 +198,17 @@ template <>
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
std::span<const ElfRel<E>> rels = get_rels(ctx);
for (i64 i = 0; i < rels.size(); i++) {
// We iterate over relocations in reverse order so that it is easy
// to swap instructions for R_SPARC_TLS_GD_CALL.
for (i64 i = (i64)rels.size() - 1; i >= 0; i--) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
Symbol<E> &sym = *file->symbols[rel.r_sym];
if (sym.get_type() == STT_TLS && sym.is_remaining_undef_weak())
continue;
u8 *loc = base + rel.r_offset;
u64 S = sym.get_addr(ctx);
@@ -157,6 +221,10 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
check_range(ctx, i, val, lo, hi);
};
auto rs1 = [&] { return *(ub32 *)loc & (0b11111 << 14); };
auto rs2 = [&] { return *(ub32 *)loc & 0b11111; };
auto rd = [&] { return *(ub32 *)loc & (0b11111 << 25); };
switch (rel.r_type) {
case R_SPARC_5:
check(S + A, 0, 1 << 5);
@@ -172,7 +240,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
break;
case R_SPARC_8:
check(S + A, 0, 1 << 8);
*(u8 *)loc = S + A;
*loc = S + A;
break;
case R_SPARC_10:
check(S + A, 0, 1 << 10);
@@ -206,13 +274,12 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
*(ub32 *)loc = S + A;
break;
case R_SPARC_PLT64:
case R_SPARC_UA64:
case R_SPARC_REGISTER:
*(ub64 *)loc = S + A;
break;
case R_SPARC_DISP8:
check(S + A - P, -(1 << 7), 1 << 7);
*(u8 *)loc = S + A - P;
*loc = S + A - P;
break;
case R_SPARC_DISP16:
check(S + A - P, -(1 << 15), 1 << 15);
@@ -242,7 +309,8 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
break;
case R_SPARC_WDISP30:
case R_SPARC_WPLT30:
check(S + A - P, -(1LL << 31), 1LL << 31);
if (!sym.is_remaining_undef_weak())
check(S + A - P, -(1LL << 31), 1LL << 31);
*(ub32 *)loc |= bits(S + A - P, 31, 2);
break;
case R_SPARC_HI22:
@@ -297,12 +365,11 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
break;
case R_SPARC_GOTDATA_OP:
if (sym.is_absolute()) {
// ldx [ %g2 + %g1 ], %g1 → nop
*(ub32 *)loc = 0x0100'0000;
// ldx [ %rs1 + %rs2 ], %rd → mov %rs2, %rd
*(ub32 *)loc = 0x8010'0000 | rs2() | rd();
} else if (sym.is_pcrel_linktime_const(ctx)) {
// ldx [ %g2 + %g1 ], %g1 → add %g2, %g1, %g1
*(ub32 *)loc &= 0b00'11111'000000'11111'1'11111111'11111;
*(ub32 *)loc |= 0b10'00000'000000'00000'0'00000000'00000;
// ldx [ %rs1 + %rs2 ], %rd → add %rs1, %rs2, %rd
*(ub32 *)loc = 0x8000'0000 | rs1() | rs2() | rd();
}
break;
case R_SPARC_PC10:
@@ -357,12 +424,12 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
if (sym.has_tlsgd(ctx)) {
*(ub32 *)loc |= bits(sym.get_tlsgd_addr(ctx) + A - GOT, 9, 0);
} else if (sym.has_gottp(ctx)) {
u32 rd = bits(*(ub32 *)loc, 29, 25);
*(ub32 *)loc = 0x8010'2000 | (rd << 25) | (rd << 14); // or %reg, $0, %reg
// add %rs1, %rs2, %rd → or %rs1, $imm, %rd
*(ub32 *)loc = 0x8010'2000 | rs1() | rd();
*(ub32 *)loc |= bits(sym.get_gottp_addr(ctx) + A - GOT, 9, 0);
} else {
u32 rd = bits(*(ub32 *)loc, 29, 25);
*(ub32 *)loc = 0x8018'2000 | (rd << 25) | (rd << 14); // xor %reg, $0, %reg
// add %rs1, %rs2, %rd → xor %rs1, $imm, %rd
*(ub32 *)loc = 0x8018'2000 | rs1() | rd();
*(ub32 *)loc |= bits(S + A - ctx.tp_addr, 9, 0) | 0b1'1100'0000'0000;
}
break;
@@ -370,11 +437,11 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
if (sym.has_tlsgd(ctx)) {
// do nothing
} else if (sym.has_gottp(ctx)) {
u32 rs2 = bits(*(ub32 *)loc, 4, 0);
*(ub32 *)loc = 0xd05d'c000 | rs2; // ldx [ %l7 + %reg ], %o0
// add %rs1, %rs2, %rd → ldx [ %rs1 + %rs2 ], %rd
*(ub32 *)loc = 0xc058'0000 | rs1() | rs2() | rd();
} else {
u32 rs2 = bits(*(ub32 *)loc, 4, 0);
*(ub32 *)loc = 0x9001'c000 | rs2; // add %g7, %reg, %o0
// add %rs1, %rs2, %rd → add %g7, %rs2, %rd
*(ub32 *)loc = 0x8001'c000 | rs2() | rd();
}
break;
case R_SPARC_TLS_GD_CALL:
@@ -382,9 +449,17 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
u64 addr = ctx.extra.tls_get_addr->get_addr(ctx);
*(ub32 *)loc |= bits(addr + A - P, 31, 2);
} else if (sym.has_gottp(ctx)) {
*(ub32 *)loc = 0x9001'c008; // add %g7, %o0, %o0
// When we rewrite a branch instruction with a non-branch one,
// we need to swap the instruction and the following one so that
// the original execution order, which is inverted due to the
// branch delay slot, is preserved.
//
// Since we apply relocations from the end to the beginning,
// the instruction at loc + 4 is already complete.
memcpy(loc, loc + 4, 4);
*(ub32 *)(loc + 4) = 0x9001'c008; // add %g7, %o0, %o0
} else {
*(ub32 *)loc = 0x0100'0000; // nop
*(ub32 *)loc = 0x0100'0000; // call → nop
}
break;
case R_SPARC_TLS_LDM_HI22:
@@ -400,12 +475,8 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
*(ub32 *)loc |= bits(ctx.tp_addr - ctx.tls_begin, 9, 0);
break;
case R_SPARC_TLS_LDM_ADD:
if (ctx.got->has_tlsld(ctx)) {
// do nothing
} else {
u32 rs2 = bits(*(ub32 *)loc, 4, 0);
*(ub32 *)loc = 0x9021'c000 | rs2; // sub %g7, %reg, %o0
}
if (!ctx.got->has_tlsld(ctx))
*(ub32 *)loc = 0x8021'c000 | rs2() | rd(); // sub %g7, %rs2, %rd
break;
case R_SPARC_TLS_LDM_CALL:
if (ctx.got->has_tlsld(ctx)) {
@@ -437,6 +508,7 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
*(ub32 *)loc = sym.esym().st_size + A;
break;
case R_SPARC_64:
case R_SPARC_UA64:
case R_SPARC_TLS_LDO_ADD:
case R_SPARC_TLS_IE_LD:
case R_SPARC_TLS_IE_LDX:
@@ -450,14 +522,11 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
template <>
void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
std::span<const ElfRel<E>> rels = get_rels(ctx);
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
for_each_reloc(ctx, [&](const ElfRel<E> &rel, i64 i) ALWAYS_INLINE {
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
continue;
return;
Symbol<E> &sym = *file.symbols[rel.r_sym];
Symbol<E> &sym = *file->symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
SectionFragment<E> *frag;
@@ -468,7 +537,7 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
u64 A = frag ? frag_addend : (i64)rel.r_addend;
auto check = [&](i64 val, i64 lo, i64 hi) {
check_range(ctx, val, i, lo, hi);
check_range(ctx, i, val, lo, hi);
};
switch (rel.r_type) {
@@ -493,13 +562,14 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
default:
Fatal(ctx) << *this << ": apply_reloc_nonalloc: " << rel;
}
}
});
}
template <>
void InputSection<E>::scan_relocations(Context<E> &ctx) {
assert(shdr().sh_flags & SHF_ALLOC);
std::span<const ElfRel<E>> rels = get_rels(ctx);
bool needs_tlsgd = false;
// Scan relocations
for (i64 i = 0; i < rels.size(); i++) {
@@ -507,7 +577,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
Symbol<E> &sym = *file->symbols[rel.r_sym];
if (sym.is_ifunc())
sym.flags |= NEEDS_GOT | NEEDS_PLT;
@@ -526,7 +596,6 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
case R_SPARC_REGISTER:
case R_SPARC_UA16:
case R_SPARC_UA32:
case R_SPARC_UA64:
case R_SPARC_PC_HM10:
case R_SPARC_OLO10:
case R_SPARC_LOX10:
@@ -584,6 +653,7 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
sym.flags |= NEEDS_GOTTP;
} else {
sym.flags |= NEEDS_TLSGD;
needs_tlsgd = true;
}
break;
case R_SPARC_TLS_LDM_HI22:
@@ -597,23 +667,21 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
case R_SPARC_TLS_IE_HI22:
sym.flags |= NEEDS_GOTTP;
break;
case R_SPARC_TLS_GD_CALL:
case R_SPARC_TLS_LDM_CALL:
if (Symbol<E> *sym = ctx.extra.tls_get_addr; sym->is_imported)
sym->flags |= NEEDS_PLT;
break;
case R_SPARC_TLS_LE_HIX22:
case R_SPARC_TLS_LE_LOX10:
check_tlsle(ctx, sym, rel);
break;
case R_SPARC_64:
case R_SPARC_UA64:
case R_SPARC_GOTDATA_OP_LOX10:
case R_SPARC_GOTDATA_OP:
case R_SPARC_GOTDATA_LOX10:
case R_SPARC_TLS_GD_LO10:
case R_SPARC_TLS_GD_ADD:
case R_SPARC_TLS_GD_CALL:
case R_SPARC_TLS_LDM_LO10:
case R_SPARC_TLS_LDM_ADD:
case R_SPARC_TLS_LDM_CALL:
case R_SPARC_TLS_LDO_HIX22:
case R_SPARC_TLS_LDO_LOX10:
case R_SPARC_TLS_LDO_ADD:
@@ -627,6 +695,14 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
Error(ctx) << *this << ": unknown relocation: " << rel;
}
}
// TLS_GD_CALL and TLS_LDM_CALL relocations implicitly refer to
// __tls_get_addr, which may be dynamically linked from libc.so.
Symbol<E> &sym = *ctx.extra.tls_get_addr;
if (sym.is_imported && (needs_tlsgd || ctx.needs_tlsld))
sym.flags |= NEEDS_PLT;
}
} // namespace mold
#endif
+34 -109
View File
@@ -25,8 +25,9 @@
//
// https://gitlab.com/x86-psABIs/x86-64-ABI
#include "mold.h"
#if MOLD_X86_64
#include "mold.h"
#include <tbb/parallel_for_each.h>
namespace mold {
@@ -103,24 +104,31 @@ void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
}
template <>
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, const ElfRel<E> &rel,
u64 offset, u64 val) {
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, InputSection<E> &isec,
const ElfRel<E> &rel, u64 offset, u64 val) {
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
u64 P = this->shdr.sh_addr + offset;
auto check = [&](i64 val, i64 lo, i64 hi) {
check_range(ctx, isec, rel, val, lo, hi);
};
switch (rel.r_type) {
case R_NONE:
break;
case R_X86_64_32:
check(val, 0, 1LL << 32);
*(ul32 *)loc = val;
break;
case R_X86_64_64:
*(ul64 *)loc = val;
break;
case R_X86_64_PC32:
*(ul32 *)loc = val - this->shdr.sh_addr - offset;
check(val - P, -(1LL << 31), 1LL << 31);
*(ul32 *)loc = val - P;
break;
case R_X86_64_PC64:
*(ul64 *)loc = val - this->shdr.sh_addr - offset;
*(ul64 *)loc = val - P;
break;
default:
Fatal(ctx) << "unsupported relocation in .eh_frame: " << rel;
@@ -432,14 +440,17 @@ static void relax_ld_to_le(u8 *loc, const ElfRel<E> &rel, i64 tls_size) {
// scan_relocations().
template <>
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
std::span<const ElfRel<E>> rels = get_rels(ctx);
std::span<ElfRel<E>> rels = get_rels(ctx);
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
Symbol<E> &sym = *file->symbols[rel.r_sym];
if (sym.get_type() == STT_TLS && sym.is_remaining_undef_weak())
continue;
u8 *loc = base + rel.r_offset;
u64 S = sym.get_addr(ctx);
@@ -488,9 +499,13 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
*(ul16 *)loc = S + A - P;
break;
case R_X86_64_PC32:
case R_X86_64_PLT32:
write32s(S + A - P);
break;
case R_X86_64_PLT32:
if (!sym.is_remaining_undef_weak())
check(S + A - P, -(1LL << 31), 1LL << 31);
*(ul32 *)loc = S + A - P;
break;
case R_X86_64_PC64:
*(ul64 *)loc = S + A - P;
break;
@@ -527,6 +542,8 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
loc[-2] = insn >> 8;
loc[-1] = insn;
*(ul32 *)loc = S + A - P;
if (ctx.arg.emit_relocs)
rel.r_type = R_X86_64_PC32;
break;
}
}
@@ -656,14 +673,11 @@ void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
// scan_relocations.
template <>
void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
std::span<const ElfRel<E>> rels = get_rels(ctx);
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
for_each_reloc(ctx, [&](const ElfRel<E> &rel, i64 i) ALWAYS_INLINE {
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
continue;
return;
Symbol<E> &sym = *file.symbols[rel.r_sym];
Symbol<E> &sym = *file->symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
SectionFragment<E> *frag;
@@ -740,7 +754,7 @@ void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
<< rel;
break;
}
}
});
}
// Linker has to create data structures in an output file to apply
@@ -759,8 +773,8 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = (u8 *)(contents.data() + rel.r_offset);
Symbol<E> &sym = *file->symbols[rel.r_sym];
u8 *loc = contents + rel.r_offset;
if (sym.is_ifunc())
sym.flags |= NEEDS_GOT | NEEDS_PLT;
@@ -858,95 +872,6 @@ void InputSection<E>::scan_relocations(Context<E> &ctx) {
}
}
// Intel CET is a relatively new CPU feature to enhance security by
// protecting control flow integrity. If the feature is enabled, indirect
// branches (i.e. branch instructions that take a register instead of an
// immediate) must land on a "landing pad" instruction, or a CPU-level fault
// will raise. That prevents an attacker to branch to a middle of a random
// function, making ROP or JOP much harder to conduct.
//
// On x86-64, the landing pad instruction is ENDBR64. That is actually a
// repurposed NOP instruction to provide binary compatibility with older
// hardware that doesn't support CET.
//
// The problem here is that the compiler always emits a landing pad at the
// beginning fo a global function because it doesn't know whether or not the
// function's address is taken in other translation units. As a result, the
// resulting binary contains more landing pads than necessary.
//
// This function rewrites a landing pad with a nop if the function's address
// was not actually taken. We can do what the compiler cannot because we
// know about all translation units.
void rewrite_endbr(Context<E> &ctx) {
Timer t(ctx, "rewrite_endbr");
constexpr u8 endbr64[] = {0xf3, 0x0f, 0x1e, 0xfa};
constexpr u8 nop[] = {0x0f, 0x1f, 0x40, 0x00};
// Rewrite all endbr64 instructions referred to by function symbols with
// NOPs. We handle only global symbols because the compiler doesn't emit
// an endbr64 for a file-scoped function in the first place if its address
// is not taken within the file.
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
for (Symbol<E> *sym : file->get_global_syms()) {
if (sym->file == file && sym->esym().st_type == STT_FUNC) {
if (InputSection<E> *isec = sym->get_input_section();
isec && (isec->shdr().sh_flags & SHF_EXECINSTR)) {
if (OutputSection<E> *osec = isec->output_section) {
u8 *buf = ctx.buf + osec->shdr.sh_offset + isec->offset + sym->value;
if (memcmp(buf, endbr64, 4) == 0)
memcpy(buf, nop, 4);
}
}
}
}
});
auto write_back = [&](InputSection<E> *isec, i64 offset) {
// If isec has an endbr64 at a given offset, copy that instruction to
// the output buffer, possibly overwriting a nop written in the above
// loop.
if (isec && isec->output_section &&
(isec->shdr().sh_flags & SHF_EXECINSTR) &&
0 <= offset && offset <= isec->contents.size() - 4 &&
memcmp(isec->contents.data() + offset, endbr64, 4) == 0)
memcpy(ctx.buf + isec->output_section->shdr.sh_offset + isec->offset + offset,
endbr64, 4);
};
// Write back endbr64 instructions if they are referred to by address-taking
// relocations.
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
for (std::unique_ptr<InputSection<E>> &isec : file->sections) {
if (isec && isec->is_alive && (isec->shdr().sh_flags & SHF_ALLOC)) {
for (const ElfRel<E> &rel : isec->get_rels(ctx)) {
if (!is_func_call_rel(rel)) {
Symbol<E> *sym = file->symbols[rel.r_sym];
if (sym->esym().st_type == STT_SECTION)
write_back(sym->get_input_section(), rel.r_addend);
else
write_back(sym->get_input_section(), sym->value);
}
}
}
}
});
// We record addresses of some symbols in the ELF header, .dynamic or in
// .dynsym. We need to retain endbr64s for such symbols.
auto keep = [&](Symbol<E> *sym) {
if (sym)
write_back(sym->get_input_section(), sym->value);
};
keep(ctx.arg.entry);
keep(ctx.arg.init);
keep(ctx.arg.fini);
if (ctx.dynsym)
for (Symbol<E> *sym : ctx.dynsym->symbols)
if (sym && sym->is_exported)
keep(sym);
}
} // namespace mold
#endif
+49 -16
View File
@@ -23,12 +23,11 @@
// see the contents of libc.a by running `ar t
// /usr/lib/x86_64-linux-gnu/libc.a`.
#pragma once
#include "common.h"
#include "mold.h"
namespace mold {
namespace {
struct ArHdr {
char ar_name[16];
char ar_date[12];
@@ -74,13 +73,16 @@ struct ArHdr {
return {ar_name, sizeof(ar_name)};
}
};
}
template <typename Context>
std::vector<MappedFile *>
read_thin_archive_members(Context &ctx, MappedFile *mf) {
// Returns the paths of the members of a thin archive, which are stored
// outside of the archive file, without opening them.
template <typename E>
std::vector<std::string>
get_thin_archive_member_paths(Context<E> &ctx, MappedFile *mf) {
u8 *begin = mf->data;
u8 *data = begin + 8;
std::vector<MappedFile *> vec;
std::vector<std::string> vec;
std::string_view strtab;
while (data < begin + mf->size) {
@@ -111,20 +113,34 @@ read_thin_archive_members(Context &ctx, MappedFile *mf) {
std::string name = hdr.read_name(strtab, body);
// Skip if symbol table
if (name == "__.SYMDEF" || name == "__.SYMDEF SORTED")
if (name == "__.SYMDEF" || name == "__.SYMDEF SORTED") {
data = body + size;
continue;
}
std::string path = name.starts_with('/') ?
name : (path_dirname(mf->name) / name).string();
vec.push_back(must_open_file(ctx, path));
vec.back()->thin_parent = mf;
if (name.starts_with('/'))
vec.push_back(name);
else
vec.push_back((path_dirname(mf->name) / name).string());
data = body;
}
return vec;
}
template <typename Context>
std::vector<MappedFile *> read_fat_archive_members(Context &ctx, MappedFile *mf) {
template <typename E>
std::vector<MappedFile *>
read_thin_archive_members(Context<E> &ctx, MappedFile *mf) {
std::vector<MappedFile *> vec;
for (std::string &path : get_thin_archive_member_paths(ctx, mf)) {
vec.push_back(must_open_file(ctx, path));
vec.back()->thin_parent = mf;
}
return vec;
}
template <typename E>
std::vector<MappedFile *>
read_fat_archive_members(Context<E> &ctx, MappedFile *mf) {
u8 *begin = mf->data;
u8 *data = begin + 8;
std::vector<MappedFile *> vec;
@@ -161,13 +177,30 @@ std::vector<MappedFile *> read_fat_archive_members(Context &ctx, MappedFile *mf)
return vec;
}
template <typename Context>
std::vector<MappedFile *> read_archive_members(Context &ctx, MappedFile *mf) {
template <typename E>
std::vector<MappedFile *>
read_archive_members(Context<E> &ctx, MappedFile *mf) {
std::string_view str = mf->get_contents();
if (str.starts_with("!<arch>\n"))
return read_fat_archive_members(ctx, mf);
assert(str.starts_with("!<thin>\n"));
return read_thin_archive_members(ctx, mf);
}
using E = MOLD_TARGET;
template std::vector<std::string>
get_thin_archive_member_paths(Context<E> &, MappedFile *);
template std::vector<MappedFile *>
read_thin_archive_members(Context<E> &, MappedFile *);
template std::vector<MappedFile *>
read_fat_archive_members(Context<E> &, MappedFile *);
template std::vector<MappedFile *>
read_archive_members(Context<E> &, MappedFile *);
} // namespace mold
+377 -204
View File
File diff suppressed because it is too large. Load diff
-12
View File
@@ -1,12 +0,0 @@
#include "mold.h"
namespace mold {
std::string get_mold_version() {
if (mold_git_hash.empty())
return "mold "s + MOLD_VERSION + " (compatible with GNU ld)";
return "mold "s + MOLD_VERSION + " (" + mold_git_hash +
"; compatible with GNU ld)";
}
} // namespace mold
+15 -4
View File
@@ -1,11 +1,12 @@
#include "elf.h"
#include <sstream>
namespace mold {
static std::string unknown_type(u32 r_type) {
char buf[50];
snprintf(buf, sizeof(buf), "unknown (0x%x)", r_type);
return buf;
std::stringstream ss;
ss << "unknown (0x" << std::hex << r_type << ")";
return ss.str();
}
#define CASE(x) case x: return #x
@@ -160,6 +161,7 @@ std::string rel_to_string<ARM64LE>(u32 r_type) {
CASE(R_AARCH64_LD64_GOT_LO12_NC);
CASE(R_AARCH64_LD64_GOTPAGE_LO15);
CASE(R_AARCH64_PLT32);
CASE(R_AARCH64_GOTPCREL32);
CASE(R_AARCH64_TLSGD_ADR_PREL21);
CASE(R_AARCH64_TLSGD_ADR_PAGE21);
CASE(R_AARCH64_TLSGD_ADD_LO12_NC);
@@ -232,7 +234,7 @@ std::string rel_to_string<ARM64BE>(u32 r_type) {
}
template <>
std::string rel_to_string<ARM32>(u32 r_type) {
std::string rel_to_string<ARM32LE>(u32 r_type) {
switch (r_type) {
CASE(R_ARM_NONE);
CASE(R_ARM_PC24);
@@ -373,6 +375,11 @@ std::string rel_to_string<ARM32>(u32 r_type) {
return unknown_type(r_type);
}
template <>
std::string rel_to_string<ARM32BE>(u32 r_type) {
return rel_to_string<ARM32LE>(r_type);
}
template <>
std::string rel_to_string<RV64LE>(u32 r_type) {
switch (r_type) {
@@ -388,6 +395,7 @@ std::string rel_to_string<RV64LE>(u32 r_type) {
CASE(R_RISCV_TLS_DTPREL64);
CASE(R_RISCV_TLS_TPREL32);
CASE(R_RISCV_TLS_TPREL64);
CASE(R_RISCV_TLSDESC);
CASE(R_RISCV_BRANCH);
CASE(R_RISCV_JAL);
CASE(R_RISCV_CALL);
@@ -422,6 +430,7 @@ std::string rel_to_string<RV64LE>(u32 r_type) {
CASE(R_RISCV_SET8);
CASE(R_RISCV_SET16);
CASE(R_RISCV_SET32);
CASE(R_RISCV_GOT32_PCREL);
CASE(R_RISCV_32_PCREL);
CASE(R_RISCV_IRELATIVE);
CASE(R_RISCV_PLT32);
@@ -744,6 +753,8 @@ std::string rel_to_string<SPARC64>(u32 r_type) {
CASE(R_SPARC_GOTDATA_OP_HIX22);
CASE(R_SPARC_GOTDATA_OP_LOX10);
CASE(R_SPARC_GOTDATA_OP);
CASE(R_SPARC_SIZE32);
CASE(R_SPARC_JMP_IREL);
CASE(R_SPARC_IRELATIVE);
}
return unknown_type(r_type);
+109 -56
View File
@@ -13,7 +13,8 @@ struct X86_64;
struct I386;
struct ARM64LE;
struct ARM64BE;
struct ARM32;
struct ARM32LE;
struct ARM32BE;
struct RV64LE;
struct RV64BE;
struct RV32LE;
@@ -80,7 +81,11 @@ enum : u32 {
SHT_RELR = 19,
SHT_CREL = 0x40000014,
SHT_LOOS = 0x60000000,
SHT_ANDROID_REL = 0x60000001,
SHT_ANDROID_RELA = 0x60000002,
SHT_LLVM_ADDRSIG = 0x6fff4c03,
SHT_ANDROID_RELR = 0x6fffff00,
SHT_GNU_SFRAME = 0x6ffffff4,
SHT_GNU_HASH = 0x6ffffff6,
SHT_GNU_VERDEF = 0x6ffffffd,
SHT_GNU_VERNEED = 0x6ffffffe,
@@ -165,6 +170,7 @@ enum : u32 {
VER_NDX_GLOBAL = 1,
VER_NDX_LAST_RESERVED = 1,
VER_NDX_UNSPECIFIED = 0xffff,
VERSYM_HIDDEN = 0x8000,
};
enum : u32 {
@@ -173,10 +179,6 @@ enum : u32 {
VER_FLG_INFO = 4,
};
enum : u32 {
VERSYM_HIDDEN = 0x8000,
};
enum : u32 {
PT_NULL = 0,
PT_LOAD = 1,
@@ -190,6 +192,7 @@ enum : u32 {
PT_GNU_STACK = 0x6474e551,
PT_GNU_RELRO = 0x6474e552,
PT_GNU_PROPERTY = 0x6474e553,
PT_GNU_SFRAME = 0x6474e554,
PT_OPENBSD_RANDOMIZE = 0x65a3dbe6,
PT_ARM_EXIDX = 0x70000001,
PT_RISCV_ATTRIBUTES = 0x70000003,
@@ -284,6 +287,13 @@ enum : u32 {
DT_RELRSZ = 35,
DT_RELR = 36,
DT_RELRENT = 37,
DT_ANDROID_REL = 0x6000000f,
DT_ANDROID_RELSZ = 0x60000010,
DT_ANDROID_RELA = 0x60000011,
DT_ANDROID_RELASZ = 0x60000012,
DT_ANDROID_RELR = 0x6fffe000,
DT_ANDROID_RELRSZ = 0x6fffe001,
DT_ANDROID_RELRENT = 0x6fffe003,
DT_GNU_HASH = 0x6ffffef5,
DT_DEPAUDIT = 0x6ffffefb,
DT_AUDIT = 0x6ffffefc,
@@ -352,14 +362,20 @@ enum : u32 {
};
enum : u32 {
ELFCOMPRESS_NONE = 0,
ELFCOMPRESS_ZLIB = 1,
ELFCOMPRESS_ZSTD = 2,
};
enum : u32 {
EF_ARM_ABI_FLOAT_SOFT = 0x00000200,
EF_ARM_ABI_FLOAT_HARD = 0x00000400,
EF_ARM_EABI_VER5 = 0x05000000,
EF_ARM_ABI_FLOAT_SOFT = 0x200,
EF_ARM_ABI_FLOAT_HARD = 0x400,
EF_ARM_BE8 = 0x800000,
EF_ARM_EABI_VER5 = 0x5000000,
};
enum : u32 {
EF_PPC64_ABI = 3,
};
enum : u32 {
@@ -554,6 +570,7 @@ enum : u32 {
R_AARCH64_LD64_GOT_LO12_NC = 0x138,
R_AARCH64_LD64_GOTPAGE_LO15 = 0x139,
R_AARCH64_PLT32 = 0x13a,
R_AARCH64_GOTPCREL32 = 0x13b,
R_AARCH64_TLSGD_ADR_PREL21 = 0x200,
R_AARCH64_TLSGD_ADR_PAGE21 = 0x201,
R_AARCH64_TLSGD_ADD_LO12_NC = 0x202,
@@ -804,6 +821,7 @@ enum : u32 {
R_RISCV_SET8 = 54,
R_RISCV_SET16 = 55,
R_RISCV_SET32 = 56,
R_RISCV_GOT32_PCREL = 41,
R_RISCV_32_PCREL = 57,
R_RISCV_IRELATIVE = 58,
R_RISCV_PLT32 = 59,
@@ -1411,9 +1429,11 @@ enum : u32 {
enum : u32 {
DW_UT_compile = 0x01,
DW_UT_type = 0x02,
DW_UT_partial = 0x03,
DW_UT_skeleton = 0x04,
DW_UT_split_compile = 0x05,
DW_UT_split_type = 0x06,
};
enum : u32 {
@@ -1488,6 +1508,52 @@ template <typename E> using U64 = std::conditional_t<E::is_le, ul64, ub64>;
template <typename E> using Word = std::conditional_t<E::is_64, U64<E>, U32<E>>;
template <typename E> using SWord = std::conditional_t<E::is_64, I64<E>, I32<E>>;
//
// SFrame is a simple unwind information format used as a lightweight
// alternative to .eh_frame. A .sframe section consists of a header, an
// array of Function Descriptor Entries (FDEs) sorted by PC, and a blob
// of Frame Row Entries (FREs). mold understands SFrame Version 3.
//
// https://sourceware.org/binutils/docs/sframe-spec.html
//
static constexpr u16 SFRAME_MAGIC = 0xdee2;
static constexpr u8 SFRAME_F_FDE_SORTED = 0x1;
static constexpr u8 SFRAME_F_FRAME_POINTER = 0x2;
static constexpr u8 SFRAME_F_FDE_FUNC_START_PCREL = 0x4;
static constexpr u8 SFRAME_ABI_AARCH64_ENDIAN_BIG = 1;
static constexpr u8 SFRAME_ABI_AARCH64_ENDIAN_LITTLE = 2;
static constexpr u8 SFRAME_ABI_AMD64_ENDIAN_LITTLE = 3;
static constexpr u8 SFRAME_ABI_S390X_ENDIAN_BIG = 4;
template <typename E>
struct SFrameHeader {
U16<E> magic;
u8 version;
u8 flags;
u8 abi_arch;
i8 cfa_fixed_fp_offset;
i8 cfa_fixed_ra_offset;
u8 auxhdr_len;
U32<E> num_fdes;
U32<E> num_fres;
U32<E> fre_len;
U32<E> fdeoff;
U32<E> freoff;
};
// The index part of an SFrame Version 3 FDE. The func_start_offset field
// is PC-relative (relative to its own address) when the section flag
// SFRAME_F_FDE_FUNC_START_PCREL is set, which is how mold always emits it.
template <typename E>
struct SFrameFdeIdx {
I64<E> func_start_offset;
U32<E> func_size;
U32<E> func_start_fre_off;
};
template <typename E> requires E::is_64
struct ElfSym<E> {
bool is_undef() const { return st_shndx == SHN_UNDEF; }
@@ -1770,47 +1836,21 @@ struct ElfRel<SPARC64> {
ib64 r_addend;
};
template <>
struct ElfRel<SH4LE> {
ElfRel() = default;
// Addend is ignored except for base relocations because even though
// SH4 is RELA, r_addend is ignored in most cases and works as if it
// were REL.
ElfRel(u64 offset, u32 type, u32 sym, i64 addend)
: r_offset(offset), r_type(type), r_sym(sym), r_addend(sym ? 0 : addend) {}
ul32 r_offset;
u8 r_type;
ul24 r_sym;
il32 r_addend;
};
template <>
struct ElfRel<SH4BE> {
ElfRel() = default;
ElfRel(u64 offset, u32 type, u32 sym, i64 addend)
: r_offset(offset), r_sym(sym), r_type(type), r_addend(sym ? 0 : addend) {}
ub32 r_offset;
ub24 r_sym;
u8 r_type;
ib32 r_addend;
};
//
// Machine descriptions
//
template <typename E> concept supports_ifunc = requires { E::R_IRELATIVE; };
template <typename E> concept supports_tlsdesc = requires { E::R_TLSDESC; };
template <typename E> concept supports_sframe = requires { E::sframe_abi; };
template <typename E> concept needs_thunk = requires { E::thunk_size; };
template <typename E> concept is_x86_64 = std::same_as<E, X86_64>;
template <typename E> concept is_i386 = std::same_as<E, I386>;
template <typename E> concept is_arm64le = std::same_as<E, ARM64LE>;
template <typename E> concept is_arm64be = std::same_as<E, ARM64BE>;
template <typename E> concept is_arm32 = std::same_as<E, ARM32>;
template <typename E> concept is_arm32le = std::same_as<E, ARM32LE>;
template <typename E> concept is_arm32be = std::same_as<E, ARM32BE>;
template <typename E> concept is_rv64le = std::same_as<E, RV64LE>;
template <typename E> concept is_rv64be = std::same_as<E, RV64BE>;
template <typename E> concept is_rv32le = std::same_as<E, RV32LE>;
@@ -1827,6 +1867,7 @@ template <typename E> concept is_loongarch64 = std::same_as<E, LOONGARCH64>;
template <typename E> concept is_loongarch32 = std::same_as<E, LOONGARCH32>;
template <typename E> concept is_x86 = is_x86_64<E> || is_i386<E>;
template <typename E> concept is_arm32 = is_arm32le<E> || is_arm32be<E>;
template <typename E> concept is_arm64 = is_arm64le<E> || is_arm64be<E>;
template <typename E> concept is_arm = is_arm64<E> || is_arm32<E>;
template <typename E> concept is_rv64 = is_rv64le<E> || is_rv64be<E>;
@@ -1845,10 +1886,11 @@ struct X86_64 {
static constexpr bool is_rela = true;
static constexpr u32 page_size = 4096;
static constexpr u32 e_machine = EM_X86_64;
static constexpr u8 sframe_abi = SFRAME_ABI_AMD64_ENDIAN_LITTLE;
static constexpr u32 plt_hdr_size = 32;
static constexpr u32 plt_size = 16;
static constexpr u32 pltgot_size = 8;
static constexpr u8 filler[] = { 0xcc }; // int3
static constexpr u8 trap[] = { 0xcc }; // int3
static constexpr u32 R_COPY = R_X86_64_COPY;
static constexpr u32 R_GLOB_DAT = R_X86_64_GLOB_DAT;
@@ -1860,6 +1902,7 @@ struct X86_64 {
static constexpr u32 R_TPOFF = R_X86_64_TPOFF64;
static constexpr u32 R_DTPMOD = R_X86_64_DTPMOD64;
static constexpr u32 R_TLSDESC = R_X86_64_TLSDESC;
static constexpr u32 R_SFRAME = R_X86_64_PC64;
static constexpr u32 R_FUNCALL[] = { R_X86_64_PLT32, R_X86_64_PLTOFF64 };
};
@@ -1873,7 +1916,7 @@ struct I386 {
static constexpr u32 plt_hdr_size = 16;
static constexpr u32 plt_size = 16;
static constexpr u32 pltgot_size = 8;
static constexpr u8 filler[] = { 0xcc }; // int3
static constexpr u8 trap[] = { 0xcc }; // int3
static constexpr u32 R_COPY = R_386_COPY;
static constexpr u32 R_GLOB_DAT = R_386_GLOB_DAT;
@@ -1895,12 +1938,13 @@ struct ARM64LE {
static constexpr bool is_rela = true;
static constexpr u32 page_size = 65536;
static constexpr u32 e_machine = EM_AARCH64;
static constexpr u8 sframe_abi = SFRAME_ABI_AARCH64_ENDIAN_LITTLE;
static constexpr u32 plt_hdr_size = 32;
static constexpr u32 plt_size = 16;
static constexpr u32 pltgot_size = 16;
static constexpr u32 thunk_hdr_size = 0;
static constexpr u32 thunk_size = 16;
static constexpr u8 filler[] = { 0x00, 0x7d, 0x20, 0xd4 }; // brk
static constexpr u32 thunk_size = 24;
static constexpr u8 trap[] = { 0x00, 0x7d, 0x20, 0xd4 }; // brk
static constexpr u32 R_COPY = R_AARCH64_COPY;
static constexpr u32 R_GLOB_DAT = R_AARCH64_GLOB_DAT;
@@ -1912,15 +1956,17 @@ struct ARM64LE {
static constexpr u32 R_TPOFF = R_AARCH64_TLS_TPREL64;
static constexpr u32 R_DTPMOD = R_AARCH64_TLS_DTPMOD64;
static constexpr u32 R_TLSDESC = R_AARCH64_TLSDESC;
static constexpr u32 R_SFRAME = R_AARCH64_PREL64;
static constexpr u32 R_FUNCALL[] = { R_AARCH64_JUMP26, R_AARCH64_CALL26 };
};
struct ARM64BE : ARM64LE {
static constexpr std::string_view name = "arm64be";
static constexpr bool is_le = false;
static constexpr u8 sframe_abi = SFRAME_ABI_AARCH64_ENDIAN_BIG;
};
struct ARM32 {
struct ARM32LE {
static constexpr std::string_view name = "arm32";
static constexpr bool is_64 = false;
static constexpr bool is_le = true;
@@ -1932,7 +1978,7 @@ struct ARM32 {
static constexpr u32 pltgot_size = 16;
static constexpr u32 thunk_hdr_size = 16;
static constexpr u32 thunk_size = 16;
static constexpr u8 filler[] = { 0xff, 0xde }; // udf
static constexpr u8 trap[] = { 0xff, 0xde }; // udf
static constexpr u32 R_COPY = R_ARM_COPY;
static constexpr u32 R_GLOB_DAT = R_ARM_GLOB_DAT;
@@ -1950,6 +1996,11 @@ struct ARM32 {
};
};
struct ARM32BE : ARM32LE {
static constexpr std::string_view name = "arm32be";
static constexpr bool is_le = false;
};
struct RV64LE {
static constexpr std::string_view name = "riscv64";
static constexpr bool is_64 = true;
@@ -1960,7 +2011,7 @@ struct RV64LE {
static constexpr u32 plt_hdr_size = 32;
static constexpr u32 plt_size = 16;
static constexpr u32 pltgot_size = 16;
static constexpr u8 filler[] = { 0x02, 0x90 }; // c.ebreak
static constexpr u8 trap[] = { 0x02, 0x90 }; // c.ebreak
static constexpr u32 R_COPY = R_RISCV_COPY;
static constexpr u32 R_GLOB_DAT = R_RISCV_64;
@@ -1990,7 +2041,7 @@ struct RV32LE {
static constexpr u32 plt_hdr_size = 32;
static constexpr u32 plt_size = 16;
static constexpr u32 pltgot_size = 16;
static constexpr u8 filler[] = { 0x02, 0x90 }; // c.ebreak
static constexpr u8 trap[] = { 0x02, 0x90 }; // c.ebreak
static constexpr u32 R_COPY = R_RISCV_COPY;
static constexpr u32 R_GLOB_DAT = R_RISCV_32;
@@ -2022,7 +2073,7 @@ struct PPC32 {
static constexpr u32 pltgot_size = 36;
static constexpr u32 thunk_hdr_size = 0;
static constexpr u32 thunk_size = 36;
static constexpr u8 filler[] = { 0x7f, 0xe0, 0x00, 0x08 }; // trap
static constexpr u8 trap[] = { 0x7f, 0xe0, 0x00, 0x08 }; // trap
static constexpr u32 R_COPY = R_PPC_COPY;
static constexpr u32 R_GLOB_DAT = R_PPC_GLOB_DAT;
@@ -2064,7 +2115,7 @@ struct PPC64V1 : PPC64 {
static constexpr u32 pltgot_size = 0;
static constexpr u32 thunk_hdr_size = 0;
static constexpr u32 thunk_size = 28;
static constexpr u8 filler[] = { 0x7f, 0xe0, 0x00, 0x08 }; // trap
static constexpr u8 trap[] = { 0x7f, 0xe0, 0x00, 0x08 }; // trap
};
struct PPC64V2 : PPC64 {
@@ -2075,7 +2126,7 @@ struct PPC64V2 : PPC64 {
static constexpr u32 pltgot_size = 0;
static constexpr u32 thunk_hdr_size = 0;
static constexpr u32 thunk_size = 24;
static constexpr u8 filler[] = { 0x08, 0x00, 0xe0, 0x7f }; // trap
static constexpr u8 trap[] = { 0x08, 0x00, 0xe0, 0x7f }; // trap
};
struct S390X {
@@ -2085,10 +2136,11 @@ struct S390X {
static constexpr bool is_rela = true;
static constexpr u32 page_size = 4096;
static constexpr u32 e_machine = EM_S390X;
static constexpr u8 sframe_abi = SFRAME_ABI_S390X_ENDIAN_BIG;
static constexpr u32 plt_hdr_size = 48;
static constexpr u32 plt_size = 16;
static constexpr u32 pltgot_size = 16;
static constexpr u8 filler[] = { 0x07, 0x00 }; // nop
static constexpr u8 trap[] = { 0x00, 0x00 }; // invalid
static constexpr u32 R_COPY = R_390_COPY;
static constexpr u32 R_GLOB_DAT = R_390_GLOB_DAT;
@@ -2099,6 +2151,7 @@ struct S390X {
static constexpr u32 R_DTPOFF = R_390_TLS_DTPOFF;
static constexpr u32 R_TPOFF = R_390_TLS_TPOFF;
static constexpr u32 R_DTPMOD = R_390_TLS_DTPMOD;
static constexpr u32 R_SFRAME = R_390_PC64;
static constexpr u32 R_FUNCALL[] = { R_390_PLT32DBL };
};
@@ -2112,7 +2165,7 @@ struct SPARC64 {
static constexpr u32 plt_hdr_size = 128;
static constexpr u32 plt_size = 32;
static constexpr u32 pltgot_size = 32;
static constexpr u8 filler[] = { 0x91, 0xd0, 0x20, 0x05 }; // ta 5
static constexpr u8 trap[] = { 0x91, 0xd0, 0x20, 0x05 }; // ta 5
static constexpr u32 R_COPY = R_SPARC_COPY;
static constexpr u32 R_GLOB_DAT = R_SPARC_GLOB_DAT;
@@ -2136,7 +2189,7 @@ struct M68K {
static constexpr u32 plt_hdr_size = 18;
static constexpr u32 plt_size = 14;
static constexpr u32 pltgot_size = 8;
static constexpr u8 filler[] = { 0x4a, 0xfc }; // illegal
static constexpr u8 trap[] = { 0x4a, 0xfc }; // illegal
static constexpr u32 R_COPY = R_68K_COPY;
static constexpr u32 R_GLOB_DAT = R_68K_GLOB_DAT;
@@ -2157,9 +2210,9 @@ struct SH4LE {
static constexpr u32 page_size = 4096;
static constexpr u32 e_machine = EM_SH;
static constexpr u32 plt_hdr_size = 16;
static constexpr u32 plt_size = 16;
static constexpr u32 plt_size = 20;
static constexpr u32 pltgot_size = 12;
static constexpr u8 filler[] = { 0x00, 0x90 }; // nop
static constexpr u8 trap[] = { 0xfd, 0xff }; // illegal
static constexpr u32 R_COPY = R_SH_COPY;
static constexpr u32 R_GLOB_DAT = R_SH_GLOB_DAT;
@@ -2175,7 +2228,7 @@ struct SH4LE {
struct SH4BE : SH4LE {
static constexpr std::string_view name = "sh4be";
static constexpr bool is_le = false;
static constexpr u8 filler[] = { 0x90, 0x00 }; // nop
static constexpr u8 trap[] = { 0xff, 0xfd }; // illegal
};
struct LOONGARCH64 {
@@ -2188,7 +2241,7 @@ struct LOONGARCH64 {
static constexpr u32 plt_hdr_size = 32;
static constexpr u32 plt_size = 16;
static constexpr u32 pltgot_size = 16;
static constexpr u8 filler[] = { 0x00, 0x00, 0x2a, 0x00 }; // break 0
static constexpr u8 trap[] = { 0x00, 0x00, 0x2a, 0x00 }; // break 0
static constexpr u32 R_COPY = R_LARCH_COPY;
static constexpr u32 R_GLOB_DAT = R_LARCH_64;
@@ -2213,7 +2266,7 @@ struct LOONGARCH32 {
static constexpr u32 plt_hdr_size = 32;
static constexpr u32 plt_size = 16;
static constexpr u32 pltgot_size = 16;
static constexpr u8 filler[] = { 0x00, 0x00, 0x2a, 0x00 }; // break 0
static constexpr u8 trap[] = { 0x00, 0x00, 0x2a, 0x00 }; // break 0
static constexpr u32 R_COPY = R_LARCH_COPY;
static constexpr u32 R_GLOB_DAT = R_LARCH_32;
+43
View File
@@ -0,0 +1,43 @@
#include "config.h"
#include "mold.h"
#include "mold-git-hash.h"
#if MOLD_USE_MIMALLOC
# include <mimalloc.h>
#endif
#if MOLD_USE_SYSTEM_MIMALLOC
// Including mimalloc-new-delete.h overrides the new/delete operators.
// We need it only when using mimalloc as a dynamic library.
// This header should be included in only one source file, so we do
// it in this file.
# include <mimalloc-new-delete.h>
#endif
#ifdef __linux__
# include <sys/prctl.h>
#endif
std::string mold::mold_version =
#ifdef MOLD_GIT_HASH
"mold " MOLD_VERSION " (" MOLD_GIT_HASH "; compatible with GNU ld)";
#else
"mold " MOLD_VERSION " (compatible with GNU ld)";
#endif
int main(int argc, char **argv) {
#if MOLD_USE_MIMALLOC
// Silence mimalloc warnings that users can ignore
mi_option_disable(mi_option_verbose);
mi_option_disable(mi_option_show_errors);
#endif
#if defined(__linux__) && defined(PR_SET_THP_DISABLE)
// A parent process may have disabled transparent huge pages, and the
// flag is inherited. Huge pages make mold considerably faster on
// large links, so re-enable them for this process.
prctl(PR_SET_THP_DISABLE, 0, 0, 0, 0);
#endif
return mold::mold_main<mold::MOLD_FIRST_TARGET>(argc, argv);
}
+55
View File
@@ -0,0 +1,55 @@
#include "mold.h"
namespace mold {
static std::string_view fatal_mono = "mold: fatal: ";
static std::string_view fatal_color = "mold: \033[0;1;31mfatal:\033[0m ";
static std::string_view error_mono = "mold: error: ";
static std::string_view error_color = "mold: \033[0;1;31merror:\033[0m ";
static std::string_view warning_mono = "mold: warning: ";
static std::string_view warning_color = "mold: \033[0;1;35mwarning:\033[0m ";
template <typename E>
Fatal<E>::Fatal(Context<E> &ctx) {
out << (ctx.arg.color_diagnostics ? fatal_color : fatal_mono);
}
template <typename E>
[[noreturn]] Fatal<E>::~Fatal() {
out.emit();
cleanup();
_exit(1);
}
template <typename E>
Error<E>::Error(Context<E> &ctx) {
if (ctx.arg.noinhibit_exec) {
out << (ctx.arg.color_diagnostics ? warning_color : warning_mono);
} else {
out << (ctx.arg.color_diagnostics ? error_color : error_mono);
ctx.has_error = true;
}
}
template <typename E>
Warn<E>::Warn(Context<E> &ctx) {
if (ctx.arg.suppress_warnings)
return;
out.emplace(std::cerr);
if (ctx.arg.fatal_warnings) {
*out << (ctx.arg.color_diagnostics ? error_color : error_mono);
ctx.has_error = true;
} else {
*out << (ctx.arg.color_diagnostics ? warning_color : warning_mono);
}
}
using E = MOLD_TARGET;
template class Fatal<E>;
template class Error<E>;
template class Warn<E>;
} // namespace mold
+34 -15
View File
@@ -1,5 +1,4 @@
#include "mold.h"
#include "../lib/archive-file.h"
namespace mold {
@@ -14,11 +13,16 @@ static bool is_text_file(MappedFile *mf) {
}
template <typename E>
static bool is_gcc_lto_obj(MappedFile *mf, bool has_plugin) {
static bool is_gcc_lto_obj(MappedFile *mf, bool has_gcc_plugin) {
const char *data = mf->get_contents().data();
ElfEhdr<E> &ehdr = *(ElfEhdr<E> *)data;
ElfShdr<E> *sh_begin = (ElfShdr<E> *)(data + ehdr.e_shoff);
std::span<ElfShdr<E>> shdrs{(ElfShdr<E> *)(data + ehdr.e_shoff), ehdr.e_shnum};
// e_shnum is a 16-bit field. If an object file contains more than 65279
// sections, e_shnum is zero and the actual number is stored to the first
// section header's sh_size field.
i64 num_sections = (ehdr.e_shnum == 0) ? sh_begin->sh_size : ehdr.e_shnum;
std::span<ElfShdr<E>> shdrs{sh_begin, (size_t)num_sections};
// e_shstrndx is a 16-bit field. If .shstrtab's section index is
// too large, the actual number is stored to sh_link field.
@@ -31,7 +35,7 @@ static bool is_gcc_lto_obj(MappedFile *mf, bool has_plugin) {
// the LTO linker plugin is available and falls back as regular
// objects otherwise. GCC FAT LTO object can be identified by the
// presence of `.gcc.lto_.symtab` section.
if (has_plugin) {
if (has_gcc_plugin) {
std::string_view name = data + shdrs[shstrtab_idx].sh_offset + sec.sh_name;
if (name.starts_with(".gnu.lto_.symtab."))
return true;
@@ -69,11 +73,15 @@ static bool is_gcc_lto_obj(MappedFile *mf, bool has_plugin) {
template <typename E>
FileType get_file_type(Context<E> &ctx, MappedFile *mf) {
std::string_view data = mf->get_contents();
bool has_plugin = !ctx.arg.plugin.empty();
if (data.empty())
return FileType::EMPTY;
// GCC FAT LTO objects can be linked as regular ELF objects. If the active
// plugin is LLVM's, treat them as regular objects so that we can fall back
// to native code instead of routing them through GCC LTO handling.
bool has_gcc_plugin = !ctx.arg.plugin.empty() &&
ctx.arg.plugin.find("LLVMgold.") == std::string_view::npos;
if (data.starts_with("\177ELF")) {
u8 byte_order = ((ElfEhdr<I386> *)data.data())->e_ident[EI_DATA];
@@ -82,10 +90,10 @@ FileType get_file_type(Context<E> &ctx, MappedFile *mf) {
if (ehdr.e_type == ET_REL) {
if (ehdr.e_ident[EI_CLASS] == ELFCLASS32) {
if (is_gcc_lto_obj<I386>(mf, has_plugin))
if (is_gcc_lto_obj<I386>(mf, has_gcc_plugin))
return FileType::GCC_LTO_OBJ;
} else {
if (is_gcc_lto_obj<X86_64>(mf, has_plugin))
if (is_gcc_lto_obj<X86_64>(mf, has_gcc_plugin))
return FileType::GCC_LTO_OBJ;
}
return FileType::ELF_OBJ;
@@ -98,10 +106,10 @@ FileType get_file_type(Context<E> &ctx, MappedFile *mf) {
if (ehdr.e_type == ET_REL) {
if (ehdr.e_ident[EI_CLASS] == ELFCLASS32) {
if (is_gcc_lto_obj<M68K>(mf, has_plugin))
if (is_gcc_lto_obj<M68K>(mf, has_gcc_plugin))
return FileType::GCC_LTO_OBJ;
} else {
if (is_gcc_lto_obj<SPARC64>(mf, has_plugin))
if (is_gcc_lto_obj<SPARC64>(mf, has_gcc_plugin))
return FileType::GCC_LTO_OBJ;
}
return FileType::ELF_OBJ;
@@ -130,16 +138,16 @@ static std::string_view get_elf_type(u8 *buf) {
bool is_le = (buf[EI_DATA] == ELFDATA2LSB);
bool is_64 = (buf[EI_CLASS] == ELFCLASS64);
auto *ehdr_le = (ElfEhdr<I386> *)buf;
auto *ehdr_be = (ElfEhdr<M68K> *)buf;
auto *ehdr64_le = (ElfEhdr<ARM64LE> *)buf;
auto *ehdr64_be = (ElfEhdr<ARM64BE> *)buf;
switch (is_le ? ehdr_le->e_machine : ehdr_be->e_machine) {
switch (is_le ? ehdr64_le->e_machine : ehdr64_be->e_machine) {
case EM_386:
return I386::name;
case EM_X86_64:
return X86_64::name;
case EM_ARM:
return ARM32::name;
return is_le ? ARM32LE::name : ARM32BE::name;
case EM_AARCH64:
return is_le ? ARM64LE::name : ARM64BE::name;
case EM_RISCV:
@@ -149,7 +157,18 @@ static std::string_view get_elf_type(u8 *buf) {
case EM_PPC:
return PPC32::name;
case EM_PPC64:
return is_le ? PPC64V2::name : PPC64V1::name;
// ELFv1 is big-endian and ELFv2 is little-endian by convention, but
// the correspondence is not a rule; musl for example uses ELFv2 on
// big-endian too. We support only the usual combinations, so treat
// the others as unrecognizable rather than silently linking them
// against the wrong ABI.
if (!is_le)
if (u32 abi = (ehdr64_be->e_flags & EF_PPC64_ABI); abi == 0 || abi == 1)
return PPC64V1::name;
if (is_le)
if (u32 abi = (ehdr64_le->e_flags & EF_PPC64_ABI); abi == 0 || abi == 2)
return PPC64V2::name;
return "";
case EM_S390X:
return S390X::name;
case EM_SPARC64:
+139 -34
View File
@@ -4,6 +4,8 @@
#include "mold.h"
#include <fstream>
#include <tbb/concurrent_unordered_map.h>
#include <tbb/concurrent_vector.h>
#include <tbb/parallel_for_each.h>
@@ -27,24 +29,46 @@ static bool should_keep(const InputSection<E> &isec) {
name.starts_with(".ctors") ||
name.starts_with(".dtors") ||
name.starts_with(".init") ||
name.starts_with(".fini") ||
is_c_identifier(name);
name.starts_with(".fini");
}
// Sections whose names are valid C identifiers can be referenced via
// __start_<name>/__stop_<name> symbols, which the linker synthesizes.
// Such sections must be kept alive only if such a marker symbol is
// referenced from a live section. This map lets us find all sections
// of a given name when we encounter such a reference during marking.
template <typename E>
using StartStopMap =
tbb::concurrent_unordered_map<std::string_view,
tbb::concurrent_vector<InputSection<E> *>>;
template <typename E>
static StartStopMap<E> build_start_stop_map(Context<E> &ctx) {
StartStopMap<E> map;
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
for (InputSection<E> *isec : file->sections)
if (isec && isec->is_alive() && (isec->shdr().sh_flags & SHF_ALLOC))
if (std::string_view name = isec->name();
is_c_identifier(name))
map[name].push_back(isec);
});
return map;
}
template <typename E>
static bool mark_section(InputSection<E> *isec) {
return isec && isec->is_alive && !isec->is_visited.test_and_set();
return isec && isec->is_alive() && isec->visit();
}
template <typename E>
static tbb::concurrent_vector<InputSection<E> *>
static std::vector<InputSection<E> *>
collect_root_set(Context<E> &ctx) {
Timer t(ctx, "collect_root_set");
tbb::concurrent_vector<InputSection<E> *> rootset;
tbb::enumerable_thread_specific<std::vector<InputSection<E> *>> rootset;
auto enqueue_section = [&](InputSection<E> *isec) {
if (mark_section(isec))
rootset.push_back(isec);
rootset.local().push_back(isec);
};
auto enqueue_symbol = [&](Symbol<E> *sym) {
@@ -58,20 +82,21 @@ collect_root_set(Context<E> &ctx) {
// Add sections that are not subject to garbage collection.
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
for (std::unique_ptr<InputSection<E>> &isec : file->sections) {
if (!isec || !isec->is_alive)
for (InputSection<E> *isec : file->sections) {
if (!isec || !isec->is_alive())
continue;
// --gc-sections discards only SHF_ALLOC sections. If you want to
// reduce the amount of non-memory-mapped segments, you should
// use `strip` command, compile without debug info or use
// --strip-all linker option.
u32 flags = isec->shdr().sh_flags;
if (!(flags & SHF_ALLOC))
isec->is_visited = true;
if (!(isec->shdr().sh_flags & SHF_ALLOC)) {
isec->set_visited();
continue;
}
if (should_keep(*isec))
enqueue_section(isec.get());
enqueue_section(isec);
}
});
@@ -91,20 +116,32 @@ collect_root_set(Context<E> &ctx) {
enqueue_symbol(file->symbols[rel.r_sym]);
});
return rootset;
std::vector<std::vector<InputSection<E> *>> vec;
for (std::vector<InputSection<E> *> &v : rootset)
vec.push_back(std::move(v));
return flatten(vec);
}
static std::string_view start_stop_name(std::string_view sym) {
if (sym.starts_with("__start_"))
return sym.substr(8);
if (sym.starts_with("__stop_"))
return sym.substr(7);
return "";
}
template <typename E>
static void visit(Context<E> &ctx, InputSection<E> *isec,
tbb::feeder<InputSection<E> *> &feeder, i64 depth) {
assert(isec->is_visited);
static void visit_section(Context<E> &ctx, InputSection<E> *isec,
tbb::feeder<InputSection<E> *> &feeder, i64 depth,
const StartStopMap<E> &start_stop_map) {
assert(isec->is_visited());
// Mark a section alive. For better performacne, we don't call
// `feeder.add` too often.
auto mark = [&](InputSection<E> *sec) {
if (mark_section(sec)) {
if (depth < 3)
visit(ctx, sec, feeder, depth + 1);
visit_section(ctx, sec, feeder, depth + 1, start_stop_map);
else
feeder.add(sec);
}
@@ -114,17 +151,45 @@ static void visit(Context<E> &ctx, InputSection<E> *isec,
// describing how to handle exceptions for that function.
// We want to keep associated .eh_frame records.
for (FdeRecord<E> &fde : isec->get_fdes())
for (const ElfRel<E> &rel : fde.get_rels(isec->file).subspan(1))
if (Symbol<E> *sym = isec->file.symbols[rel.r_sym])
for (const ElfRel<E> &rel : fde.get_rels(*isec->file).subspan(1))
if (Symbol<E> *sym = isec->file->symbols[rel.r_sym])
mark(sym->get_input_section());
for (const ElfRel<E> &rel : isec->get_rels(ctx)) {
// Symbol can refer to either a section fragment or an input section.
Symbol<E> &sym = *isec->file.symbols[rel.r_sym];
if (SectionFragment<E> *frag = sym.get_frag())
Symbol<E> &sym = *isec->file->symbols[rel.r_sym];
if (sym.file && sym.file->is_dso) {
sym.file->is_reachable.test_and_set();
continue;
}
if (SectionFragment<E> *frag = sym.get_frag()) {
frag->is_alive = true;
else
mark(sym.get_input_section());
continue;
}
mark(sym.get_input_section());
// A reference to __start_<name> or __stop_<name> keeps every
// section named <name> alive, mirroring how those symbols are
// defined. A single such reference can keep an enormous number of
// sections alive, so we spread the fanout over threads instead
// of marking the sections one by one.
if (std::string_view name = start_stop_name(sym.name());
!name.empty()) {
if (auto it = start_stop_map.find(name);
it != start_stop_map.end()) {
// Mark and visit the sections with a nested parallel loop.
// As in mark() below, a section added to a feeder has
// already been marked, so a feeder's loop body must visit
// it unconditionally.
tbb::parallel_for_each(it->second, [&](InputSection<E> *isec) {
if (mark_section(isec))
visit_section(ctx, isec, feeder, 0, start_stop_map);
});
}
}
}
if constexpr (is_arm32<E>)
@@ -134,12 +199,13 @@ static void visit(Context<E> &ctx, InputSection<E> *isec,
// Mark all reachable sections
template <typename E>
static void mark(Context<E> &ctx,
tbb::concurrent_vector<InputSection<E> *> &rootset) {
std::vector<InputSection<E> *> &rootset,
const StartStopMap<E> &start_stop_map) {
Timer t(ctx, "mark");
tbb::parallel_for_each(rootset, [&](InputSection<E> *isec,
tbb::feeder<InputSection<E> *> &feeder) {
visit(ctx, isec, feeder, 0);
visit_section(ctx, isec, feeder, 0, start_stop_map);
});
}
@@ -147,26 +213,65 @@ static void mark(Context<E> &ctx,
template <typename E>
static void sweep(Context<E> &ctx) {
Timer t(ctx, "sweep");
static Counter counter("garbage_sections");
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
for (std::unique_ptr<InputSection<E>> &isec : file->sections) {
if (isec && isec->is_alive && !isec->is_visited) {
if (ctx.arg.print_gc_sections)
Out(ctx) << "removing unused section " << *isec;
std::vector<std::vector<InputSection<E> *>> sections(ctx.objs.size());
tbb::parallel_for((i64)0, (i64)ctx.objs.size(), [&](i64 i) {
ObjectFile<E> &file = *ctx.objs[i];
for (InputSection<E> *isec : file.sections) {
if (isec && isec->is_alive() && !isec->is_visited()) {
isec->kill();
counter++;
sections[i].push_back(isec);
}
}
});
std::string &path = ctx.arg.print_gc_sections;
if (!path.empty()) {
std::ostream *out = &std::cout;
std::ofstream file;
if (path != "-") {
file.open(path);
if (file.fail())
Fatal(ctx) << "--print-gc-sections: cannot open " << path << ": "
<< errno_string();
out = &file;
}
i64 saved_bytes = 0;
for (std::span<InputSection<E> *> vec : sections)
for (InputSection<E> *isec : vec)
*out << "removing unused section " << *isec << '\n';
*out << "GC saved " << saved_bytes << " bytes\n";
}
}
template <typename E>
void gc_sections(Context<E> &ctx) {
Timer t(ctx, "gc");
tbb::concurrent_vector<InputSection<E> *> rootset = collect_root_set(ctx);
mark(ctx, rootset);
for (SharedFile<E> *file : ctx.dsos)
if (file->as_needed)
file->is_reachable = false;
for (Symbol<E> *sym : ctx.arg.undefined)
if (sym->file && sym->file->is_dso)
sym->file->is_reachable = true;
for (Symbol<E> *sym : ctx.arg.require_defined)
if (sym->file && sym->file->is_dso)
sym->file->is_reachable = true;
std::vector<InputSection<E> *> rootset = collect_root_set(ctx);
StartStopMap<E> start_stop_map = build_start_stop_map(ctx);
mark(ctx, rootset, start_stop_map);
sweep(ctx);
std::erase_if(ctx.dsos, [](SharedFile<E> *file) { return !file->is_reachable; });
}
using E = MOLD_TARGET;
+696 -308
View File
File diff suppressed because it is too large. Load diff
+325 -323
View File
@@ -67,8 +67,7 @@
#include "mold.h"
#include "../lib/siphash.h"
#include <array>
#include <cstdio>
#include <fstream>
#include <tbb/concurrent_unordered_map.h>
#include <tbb/concurrent_vector.h>
#include <tbb/enumerable_thread_specific.h>
@@ -76,24 +75,14 @@
#include <tbb/parallel_for_each.h>
#include <tbb/parallel_sort.h>
static constexpr int64_t HASH_SIZE = 16;
using Digest = std::array<uint8_t, HASH_SIZE>;
namespace std {
template <> struct hash<Digest> {
size_t operator()(const Digest &k) const {
static_assert(sizeof(size_t) <= HASH_SIZE);
size_t val;
memcpy(&val, k.data(), sizeof(size_t));
return val;
}
};
}
namespace mold {
static u8 hmac_key[16];
struct Digest {
u64 hi;
u64 lo;
};
static u8 siphash_key[16];
template <typename E>
static void uniquify_cies(Context<E> &ctx) {
@@ -129,112 +118,25 @@ static bool is_eligible(Context<E> &ctx, InputSection<E> &isec) {
return false;
if (shdr.sh_flags & SHF_EXECINSTR)
return (ctx.arg.icf_all || !isec.address_taken) &&
return (ctx.arg.icf_all || !isec.is_address_taken()) &&
name != ".init" && name != ".fini";
// .gcc_except_table contains a compiler-generated table. Pointer
// equality for the section is not significant because only the C++
// exception handling code will use the table at runtime.
if (name == ".gcc_except_table" ||
name.starts_with(".gcc_except_table."))
return true;
bool is_readonly = !(shdr.sh_flags & SHF_WRITE);
bool is_relro = isec.name().starts_with(".data.rel.ro");
return (ctx.arg.ignore_data_address_equality || !isec.address_taken) &&
bool is_relro = name.starts_with(".data.rel.ro");
return (ctx.arg.ignore_data_address_equality || !isec.is_address_taken()) &&
(is_readonly || is_relro);
}
template <typename E>
static bool is_leaf(Context<E> &ctx, InputSection<E> &isec) {
if (!isec.get_rels(ctx).empty())
return false;
for (FdeRecord<E> &fde : isec.get_fdes())
if (fde.get_rels(isec.file).size() > 1)
return false;
return true;
}
template <typename E>
struct LeafHasher {
size_t operator()(InputSection<E> *isec) const {
u64 h = hash_string(isec->contents);
for (FdeRecord<E> &fde : isec->get_fdes()) {
u64 h2 = hash_string(fde.get_contents(isec->file).substr(8));
h = combine_hash(h, h2);
}
return h;
}
};
template <typename E>
struct LeafEq {
bool operator()(InputSection<E> *a, InputSection<E> *b) const {
if (a->contents != b->contents)
return false;
std::span<FdeRecord<E>> x = a->get_fdes();
std::span<FdeRecord<E>> y = b->get_fdes();
if (x.size() != y.size())
return false;
for (i64 i = 0; i < x.size(); i++)
if (x[i].get_contents(a->file).substr(8) !=
y[i].get_contents(b->file).substr(8))
return false;
return true;
}
};
// Early merge of leaf nodes, which can be processed without constructing the
// entire graph. This reduces the vertex count and improves memory efficiency.
template <typename E>
static void merge_leaf_nodes(Context<E> &ctx) {
Timer t(ctx, "merge_leaf_nodes");
static Counter eligible("icf_eligibles");
static Counter non_eligible("icf_non_eligibles");
static Counter leaf("icf_leaf_nodes");
tbb::concurrent_unordered_map<InputSection<E> *, Atomic<InputSection<E> *>,
LeafHasher<E>, LeafEq<E>> map;
tbb::parallel_for((i64)0, (i64)ctx.objs.size(), [&](i64 i) {
for (std::unique_ptr<InputSection<E>> &isec : ctx.objs[i]->sections) {
if (!isec || !isec->is_alive)
continue;
if (!is_eligible(ctx, *isec)) {
non_eligible++;
continue;
}
if (is_leaf(ctx, *isec)) {
leaf++;
isec->icf_leaf = true;
auto [it, inserted] = map.insert({isec.get(), isec.get()});
if (!inserted) {
InputSection<E> *isec2 = it->second.load();
while (isec->get_priority() < isec2->get_priority() &&
!it->second.compare_exchange_strong(isec2, isec.get()));
}
} else {
eligible++;
isec->icf_eligible = true;
}
}
});
tbb::parallel_for((i64)0, (i64)ctx.objs.size(), [&](i64 i) {
for (std::unique_ptr<InputSection<E>> &isec : ctx.objs[i]->sections) {
if (isec && isec->is_alive && isec->icf_leaf) {
auto it = map.find(isec.get());
assert(it != map.end());
isec->leader = it->second;
}
}
});
}
template <typename E>
static Digest compute_digest(Context<E> &ctx, InputSection<E> &isec) {
SipHash13_128 hasher(hmac_key);
SipHash13_128 hasher(siphash_key);
auto hash = [&](auto val) {
hasher.update((u8 *)&val, sizeof(val));
@@ -248,7 +150,7 @@ static Digest compute_digest(Context<E> &ctx, InputSection<E> &isec) {
auto hash_symbol = [&](Symbol<E> &sym) {
InputSection<E> *isec = sym.get_input_section();
if (!sym.file) {
if (!sym.file || sym.is_imported) {
hash('1');
hash((u64)&sym);
} else if (SectionFragment<E> *frag = sym.get_frag()) {
@@ -256,83 +158,207 @@ static Digest compute_digest(Context<E> &ctx, InputSection<E> &isec) {
hash((u64)frag);
} else if (!isec) {
hash('3');
} else if (isec->leader) {
} else if (isec->icf_idx != -1) {
hash('4');
hash((u64)isec->leader);
} else if (isec->icf_eligible) {
hash('5');
} else {
hash('6');
hash('5');
hash((u64)isec);
}
hash(sym.value);
};
hash_string(isec.contents);
hash_string(isec.get_contents());
hash(isec.shdr().sh_flags);
hash(isec.get_fdes().size());
std::span<FdeRecord<E>> fdes = isec.get_fdes();
hash(fdes.size());
hash(isec.get_rels(ctx).size());
for (FdeRecord<E> &fde : isec.get_fdes()) {
hash(isec.file.cies[fde.cie_idx].icf_idx);
for (FdeRecord<E> &fde : fdes) {
hash(isec.file->cies[fde.cie_idx].icf_idx);
// Bytes 0 to 4 contain the length of this record, and
// bytes 4 to 8 contain an offset to CIE.
hash_string(fde.get_contents(isec.file).substr(8));
hash_string(fde.get_contents(*isec.file).substr(8));
hash(fde.get_rels(isec.file).size());
hash(fde.get_rels(*isec.file).size());
for (const ElfRel<E> &rel : fde.get_rels(isec.file).subspan(1)) {
hash_symbol(*isec.file.symbols[rel.r_sym]);
for (const ElfRel<E> &rel : fde.get_rels(*isec.file).subspan(1)) {
hash_symbol(*isec.file->symbols[rel.r_sym]);
hash(rel.r_type);
hash(rel.r_offset - fde.input_offset);
hash(get_addend(isec.file.cies[fde.cie_idx].input_section, rel));
hash(get_addend(isec.file->cies[fde.cie_idx].input_section, rel));
}
}
for (i64 i = 0; i < isec.get_rels(ctx).size(); i++) {
const ElfRel<E> &rel = isec.get_rels(ctx)[i];
for (const ElfRel<E> &rel : isec.get_rels(ctx)) {
hash(rel.r_offset);
hash(rel.r_type);
hash(get_addend(isec, rel));
hash_symbol(*isec.file.symbols[rel.r_sym]);
hash_symbol(*isec.file->symbols[rel.r_sym]);
}
Digest digest;
hasher.finish(digest.data());
hasher.finish(&digest);
return digest;
}
// A concurrent hash map from digest to section. We use it to count the
// number of distinct digests and to elect the leader section of each
// digest's equivalence class.
//
// The number of distinct digests cannot exceed the number of digests,
// so we can allocate a large enough table upfront and never need to
// grow it.
//
// The map is reused across propagation rounds. Instead of clearing the
// table at the start of each round, we stamp each slot with the round
// number in which it was written; a slot stamped with an earlier round
// is treated as vacant.
//
// Each slot consists of two 64-bit words and a section pointer. The
// first word packs the round number, a busy bit, and 48 bits of the
// digest; the second word holds another 64 bits. An inserter claims a
// vacant slot by installing the first word with compare-and-swap with
// the busy bit set, writes the second word and the pointer, and then
// rewrites the first word with the busy bit cleared to publish the
// slot. Since the slot index is derived from digest bits that the
// first word doesn't contain, a successful match effectively compares
// an entire 128-bit digest, so the map is exact under the same
// hash-collision assumption the surrounding algorithm is built on.
//
// Of all sections inserted with the same digest in the same round, the
// slot ends up pointing to the one with the lowest priority, which ICF
// uses as the leader of the digest's equivalence class.
template <typename E>
class DigestMap {
public:
DigestMap(i64 n) :
mask(bit_ceil(n * 2) - 1),
slots(mask + 1) {}
void next_round() {
if (++round == 1 << 15) {
// The round number wrapped around, making stale slot stamps
// ambiguous, so reset the table. In practice, ICF converges long
// before this point.
for (Slot &slot : slots)
slot.hi = 0;
round = 1;
}
}
// Returns true if the digest was not in the table.
bool insert(const Digest &digest, InputSection<E> *isec) {
constexpr u64 busy_bit = 1LL << 48;
u64 tag = digest.hi >> 16;
u64 value = (round << 49) | tag;
for (i64 i = digest.hi & mask;; i = (i + 1) & mask) {
Slot &slot = slots[i];
u64 x = slot.hi.load(std::memory_order_acquire);
// If the slot was last written in an earlier round, it's vacant;
// try to claim it.
while ((x >> 49) != round) {
if (slot.hi.compare_exchange_weak(x, value | busy_bit,
std::memory_order_acquire)) {
slot.lo = digest.lo;
slot.leader = isec;
slot.hi.store(value, std::memory_order_release);
return true;
}
}
// The slot is occupied. If it holds a different digest, try the
// next slot.
if ((x & 0xffff'ffff'ffff) != tag)
continue;
// The tags match; compare the digest bits in the second word,
// waiting for the writer to publish them if the slot is still
// being claimed.
while (x & busy_bit)
x = slot.hi.load(std::memory_order_acquire);
if (slot.lo != digest.lo)
continue;
// The digest is already in the table; keep the slot pointing to
// the lowest-priority section.
InputSection<E> *cur = slot.leader;
while (isec->get_priority() < cur->get_priority() &&
!slot.leader.compare_exchange_strong(cur, isec));
return false;
}
}
// Returns the section associated with a digest. The digest must have
// been inserted in the current round, and no insertion may be running
// concurrently.
InputSection<E> *find(const Digest &digest) {
u64 value = (round << 49) | (digest.hi >> 16);
for (i64 i = digest.hi & mask;; i = (i + 1) & mask)
if (slots[i].hi == value && slots[i].lo == digest.lo)
return slots[i].leader;
}
private:
struct Slot {
Atomic<u64> hi;
Atomic<u64> lo;
Atomic<InputSection<E> *> leader;
};
// Time begins in round 1 so that all-zero slots, the initial state
// of the table, read as vacant.
u64 round = 1;
u64 mask;
std::vector<Slot> slots;
};
template <typename E>
static std::vector<InputSection<E> *> gather_sections(Context<E> &ctx) {
Timer t(ctx, "gather_sections");
// Count the number of input sections for each input file.
std::vector<i64> num_sections(ctx.objs.size());
static Counter eligible("icf_eligibles");
static Counter non_eligible("icf_non_eligibles");
// Count the number of eligible input sections for each input file
// and turn the counts into starting indices with a prefix sum.
std::vector<i64> indices(ctx.objs.size() + 1);
tbb::parallel_for((i64)0, (i64)ctx.objs.size(), [&](i64 i) {
for (std::unique_ptr<InputSection<E>> &isec : ctx.objs[i]->sections)
if (isec && isec->is_alive && isec->icf_eligible)
num_sections[i]++;
for (InputSection<E> *isec : ctx.objs[i]->sections) {
if (!isec)
continue;
assert(isec->icf_idx == -1);
if (!isec->is_alive())
continue;
if (is_eligible(ctx, *isec)) {
eligible++;
isec->icf_idx = 0;
indices[i + 1]++;
} else {
non_eligible++;
}
}
});
std::vector<i64> section_indices(ctx.objs.size());
for (i64 i = 0; i < ctx.objs.size() - 1; i++)
section_indices[i + 1] = section_indices[i] + num_sections[i];
for (i64 i = 1; i < indices.size(); i++)
indices[i] += indices[i - 1];
std::vector<InputSection<E> *> sections(
section_indices.back() + num_sections.back());
std::vector<InputSection<E> *> sections(indices.back());
// Fill `sections` contents.
tbb::parallel_for((i64)0, (i64)ctx.objs.size(), [&](i64 i) {
i64 idx = section_indices[i];
for (std::unique_ptr<InputSection<E>> &isec : ctx.objs[i]->sections)
if (isec && isec->is_alive && isec->icf_eligible)
sections[idx++] = isec.get();
});
tbb::parallel_for((i64)0, (i64)sections.size(), [&](i64 i) {
sections[i]->icf_idx = i;
i64 idx = indices[i];
for (InputSection<E> *isec : ctx.objs[i]->sections) {
if (isec && isec->icf_idx != -1) {
isec->icf_idx = idx;
sections[idx++] = isec;
}
}
});
return sections;
@@ -353,6 +379,13 @@ compute_digests(Context<E> &ctx, std::span<InputSection<E> *> sections) {
// Build a graph, treating every function as a vertex and every function call
// as an edge. See the description at the top for a more detailed formulation.
// We use u32 indices here to improve cache locality.
//
// Relocations in a section's FDEs are edges too, because compute_digest
// hashes eligible relocation targets without identity, and every such
// target must be represented as an edge to remain distinguishable. In
// particular, an FDE's reference to an LSDA is an edge; without it, two
// identical functions whose exception tables catch different types would
// be folded into one.
template <typename E>
static void gather_edges(Context<E> &ctx,
std::span<InputSection<E> *> sections,
@@ -360,136 +393,134 @@ static void gather_edges(Context<E> &ctx,
std::vector<u32> &edge_indices) {
Timer t(ctx, "gather_edges");
if (sections.empty())
return;
std::vector<i64> num_edges(sections.size());
edge_indices.resize(sections.size());
// Count the number of outgoing edges for each vertex and turn the
// counts into starting indices with a prefix sum. The extra entry at
// the end makes edge_indices[i + 1] valid for every vertex, so that
// vertex i's edges are edge_indices[i] to edge_indices[i + 1].
edge_indices.resize(sections.size() + 1);
tbb::parallel_for((i64)0, (i64)sections.size(), [&](i64 i) {
InputSection<E> &isec = *sections[i];
assert(isec.icf_eligible);
assert(isec.icf_idx != -1);
for (i64 j = 0; j < isec.get_rels(ctx).size(); j++) {
const ElfRel<E> &rel = isec.get_rels(ctx)[j];
Symbol<E> &sym = *isec.file.symbols[rel.r_sym];
if (!sym.get_frag())
if (InputSection<E> *isec = sym.get_input_section())
if (isec->icf_eligible)
num_edges[i]++;
}
for (FdeRecord<E> &fde : isec.get_fdes())
for (const ElfRel<E> &rel : fde.get_rels(*isec.file).subspan(1))
if (Symbol<E> &sym = *isec.file->symbols[rel.r_sym];
InputSection<E> *isec = sym.get_input_section())
if (isec->icf_idx != -1)
edge_indices[i + 1]++;
for (const ElfRel<E> &rel : isec.get_rels(ctx))
if (Symbol<E> &sym = *isec.file->symbols[rel.r_sym];
InputSection<E> *isec = sym.get_input_section())
if (isec->icf_idx != -1)
edge_indices[i + 1]++;
});
for (i64 i = 0; i < num_edges.size() - 1; i++)
edge_indices[i + 1] = edge_indices[i] + num_edges[i];
for (i64 i = 1; i < edge_indices.size(); i++)
edge_indices[i] += edge_indices[i - 1];
edges.resize(edge_indices.back() + num_edges.back());
edges.resize(edge_indices.back());
tbb::parallel_for((i64)0, (i64)num_edges.size(), [&](i64 i) {
tbb::parallel_for((i64)0, (i64)sections.size(), [&](i64 i) {
InputSection<E> &isec = *sections[i];
i64 idx = edge_indices[i];
for (ElfRel<E> &rel : isec.get_rels(ctx)) {
Symbol<E> &sym = *isec.file.symbols[rel.r_sym];
if (InputSection<E> *isec = sym.get_input_section())
if (isec->icf_eligible)
for (FdeRecord<E> &fde : isec.get_fdes())
for (const ElfRel<E> &rel : fde.get_rels(*isec.file).subspan(1))
if (Symbol<E> &sym = *isec.file->symbols[rel.r_sym];
InputSection<E> *isec = sym.get_input_section())
if (isec->icf_idx != -1)
edges[idx++] = isec->icf_idx;
for (const ElfRel<E> &rel : isec.get_rels(ctx))
if (Symbol<E> &sym = *isec.file->symbols[rel.r_sym];
InputSection<E> *isec = sym.get_input_section())
if (isec->icf_idx != -1)
edges[idx++] = isec->icf_idx;
}
});
}
template <typename E>
static i64 propagate(std::span<std::vector<Digest>> digests,
std::span<u32> edges, std::span<u32> edge_indices,
bool &slot, std::span<u8> converged,
tbb::affinity_partitioner &ap) {
static Counter round("icf_round");
round++;
i64 num_digests = digests[0].size();
tbb::enumerable_thread_specific<i64> changed;
tbb::parallel_for((i64)0, num_digests, [&](i64 i) {
if (converged[i])
return;
SipHash13_128 hasher(hmac_key);
hasher.update(digests[2][i].data(), HASH_SIZE);
// Compute the next-round digest of each vertex by hashing its current
// digest and the current digests of the vertices it refers to. A
// vertex's digest after the nth round is therefore a hash of its
// unfolding into a tree of depth n.
static void propagate(std::vector<Digest> &cur, std::vector<Digest> &next,
std::span<u32> edges, std::span<u32> edge_indices) {
tbb::parallel_for((i64)0, (i64)cur.size(), [&](i64 i) {
SipHash13_128 hasher(siphash_key);
hasher.update(&cur[i], sizeof(Digest));
i64 begin = edge_indices[i];
i64 end = (i + 1 == num_digests) ? edges.size() : edge_indices[i + 1];
i64 end = edge_indices[i + 1];
for (i64 j : edges.subspan(begin, end - begin))
hasher.update(digests[slot][j].data(), HASH_SIZE);
hasher.finish(digests[!slot][i].data());
if (digests[slot][i] == digests[!slot][i]) {
// This node has converged. Skip further iterations as it will
// yield the same hash.
converged[i] = true;
} else {
changed.local()++;
}
}, ap);
slot = !slot;
return changed.combine(std::plus());
hasher.update(&cur[j], sizeof(Digest));
hasher.finish(&next[i]);
});
std::swap(cur, next);
}
template <typename E>
static i64 count_num_classes(std::span<Digest> digests,
tbb::affinity_partitioner &ap) {
std::vector<Digest> vec(digests.begin(), digests.end());
tbb::parallel_sort(vec);
std::span<InputSection<E> *> sections,
DigestMap<E> &map) {
map.next_round();
tbb::enumerable_thread_specific<i64> num_classes;
tbb::parallel_for((i64)0, (i64)vec.size() - 1, [&](i64 i) {
if (vec[i] != vec[i + 1])
tbb::parallel_for((i64)0, (i64)digests.size(), [&](i64 i) {
if (map.insert(digests[i], sections[i]))
num_classes.local()++;
}, ap);
});
static Counter counter("icf_round");
counter++;
return num_classes.combine(std::plus());
}
template <typename E>
static void print_icf_sections(Context<E> &ctx) {
tbb::concurrent_vector<InputSection<E> *> leaders;
static void
print_icf_sections(Context<E> &ctx, std::span<InputSection<E> *> sections) {
tbb::concurrent_vector<InputSection<E> *> leader_sections;
tbb::concurrent_unordered_multimap<InputSection<E> *, InputSection<E> *> map;
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
for (std::unique_ptr<InputSection<E>> &isec : file->sections) {
if (isec && isec->is_alive && isec->leader) {
if (isec.get() == isec->leader)
leaders.push_back(isec.get());
else
map.insert({isec->leader, isec.get()});
}
}
tbb::parallel_for_each(sections, [&](InputSection<E> *isec) {
if (isec == isec->icf_leader)
leader_sections.push_back(isec);
else
map.insert({isec->icf_leader, isec});
});
tbb::parallel_sort(leaders.begin(), leaders.end(),
tbb::parallel_sort(leader_sections.begin(), leader_sections.end(),
[](InputSection<E> *a, InputSection<E> *b) {
return a->get_priority() < b->get_priority();
});
i64 saved_bytes = 0;
std::ostream *out = &std::cout;
std::ofstream file;
for (InputSection<E> *leader : leaders) {
auto [begin, end] = map.equal_range(leader);
if (begin == end)
continue;
Out(ctx) << "selected section " << *leader;
i64 n = 0;
for (auto it = begin; it != end; it++) {
Out(ctx) << " removing identical section " << *it->second;
n++;
}
saved_bytes += leader->contents.size() * n;
std::string &path = ctx.arg.print_icf_sections;
if (path != "-") {
file.open(path);
if (file.fail())
Fatal(ctx) << "--print-icf-sections: cannot open " << path << ": "
<< errno_string();
out = &file;
}
Out(ctx) << "ICF saved " << saved_bytes << " bytes";
i64 saved_bytes = 0;
for (InputSection<E> *leader : leader_sections) {
auto [begin, end] = map.equal_range(leader);
if (begin != end) {
*out << "selected section " << *leader << '\n';
for (auto it = begin; it != end; it++) {
*out << " removing identical section " << *it->second << '\n';
saved_bytes += leader->get_contents().size();
}
}
}
*out << "ICF saved " << saved_bytes << " bytes\n";
}
template <typename E>
@@ -498,106 +529,76 @@ void icf_sections(Context<E> &ctx) {
if (ctx.objs.empty())
return;
get_random_bytes(hmac_key, sizeof(hmac_key));
get_random_bytes(siphash_key, sizeof(siphash_key));
uniquify_cies(ctx);
merge_leaf_nodes(ctx);
// Prepare for the propagation rounds.
std::vector<InputSection<E> *> sections = gather_sections(ctx);
// We allocate 3 arrays to store hashes for each vertex.
//
// Index 0 and 1 are used for tree hashes from the previous
// iteration and the current iteration. They switch roles every
// iteration. See `slot` below.
//
// Index 2 stores the initial, single-vertex hash. This is combined
// with hashes from the connected vertices to form the tree hash
// described above.
std::vector<std::vector<Digest>> digests(3);
digests[0] = compute_digests<E>(ctx, sections);
digests[1].resize(digests[0].size());
digests[2] = digests[0];
// `digests` holds the current digest of each vertex
std::vector<Digest> digests = compute_digests<E>(ctx, sections);
std::vector<u32> edges;
std::vector<u32> edge_indices;
gather_edges<E>(ctx, sections, edges, edge_indices);
std::vector<u8> converged(digests[0].size());
bool slot = 0;
// The digest map is used to count the number of distinct digests in
// the loop below. As a side effect, it records the lowest-priority
// section for each digest, which the grouping step after the loop
// uses as the leader of each equivalence class.
DigestMap<E> map(digests.size());
// Execute the propagation rounds until convergence is obtained.
//
// The number of distinct digests can only monotonically increase as
// the rounds hash ever-deeper trees, so once two consecutive rounds
// yield the same count, the partition of sections into equivalence
// classes has stopped changing and will remain unchanged for further
// iterations (proof omitted for brevity). Note that individual
// digests may well still be changing at that point; sections that
// have a cycle in downstream (i.e. recursive functions and functions
// that call them) never settle on a digest. That doesn't matter
// because sections in the same class change their digests in
// lockstep, keeping the partition intact.
{
Timer t(ctx, "propagate");
tbb::affinity_partitioner ap;
// A cheap test that the graph hasn't converged yet.
// The loop after this one uses a strict condition, but it's expensive
// as it requires sorting the entire hash collection.
//
// For nodes that have a cycle in downstream (i.e. recursive
// functions and functions that calls recursive functions) will always
// change with the iterations. Nodes that doesn't (i.e. non-recursive
// functions) will stop changing as soon as the propagation depth reaches
// the call tree depth.
// Here, we test whether we have reached sufficient depth for the latter,
// which is a necessary (but not sufficient) condition for convergence.
i64 num_changed = -1;
for (;;) {
i64 n = propagate<E>(digests, edges, edge_indices, slot, converged, ap);
if (n == num_changed)
break;
num_changed = n;
}
// Run the pass until the unique number of hashes stop increasing, at which
// point we have achieved convergence (proof omitted for brevity).
i64 num_classes = -1;
for (;;) {
// count_num_classes requires sorting which is O(n log n), so do a little
// more work beforehand to amortize that log factor.
for (i64 i = 0; i < 10; i++)
propagate<E>(digests, edges, edge_indices, slot, converged, ap);
std::vector<Digest> scratch(digests.size());
i64 n = count_num_classes<E>(digests[slot], ap);
if (n == num_classes)
for (;;) {
// count_num_classes is as expensive as propagate, so we propagate
// a few times before counting the number of distinct groups.
propagate(digests, scratch, edges, edge_indices);
propagate(digests, scratch, edges, edge_indices);
propagate(digests, scratch, edges, edge_indices);
i64 m = count_num_classes<E>(digests, sections, map);
if (m == num_classes)
break;
num_classes = n;
num_classes = m;
}
}
// Group sections by hash values.
// Group sections by digest. The final counting round has already
// elected a leader for each digest; look it up.
{
Timer t(ctx, "group");
auto *map =
new tbb::concurrent_unordered_map<Digest, Atomic<InputSection<E> *>>;
std::span<Digest> digest = digests[slot];
tbb::parallel_for((i64)0, (i64)sections.size(), [&](i64 i) {
InputSection<E> *isec = sections[i];
auto [it, inserted] = map->insert({digest[i], isec});
if (!inserted) {
InputSection<E> *isec2 = it->second.load();
while (isec->get_priority() < isec2->get_priority() &&
!it->second.compare_exchange_strong(isec2, isec));
}
sections[i]->icf_leader = map.find(digests[i]);
});
tbb::parallel_for((i64)0, (i64)sections.size(), [&](i64 i) {
auto it = map->find(digest[i]);
assert(it != map->end());
sections[i]->leader = it->second;
});
// Since free'ing the map is slow, postpone it.
ctx.on_exit.push_back([=] { delete map; });
}
if (ctx.arg.print_icf_sections)
print_icf_sections(ctx);
if (!ctx.arg.print_icf_sections.empty())
print_icf_sections<E>(ctx, sections);
// Update alignment of leaders.
{
Timer t(ctx, "update_alignment");
tbb::parallel_for_each(sections, [&](InputSection<E> *isec) {
if (isec != isec->icf_leader)
update_maximum(isec->icf_leader->p2align, isec->p2align);
});
}
// Eliminate duplicate sections.
// Symbols pointing to eliminated sections will be redirected on the fly when
@@ -605,12 +606,13 @@ void icf_sections(Context<E> &ctx) {
{
Timer t(ctx, "sweep");
static Counter eliminated("icf_eliminated");
tbb::parallel_for_each(ctx.objs, [](ObjectFile<E> *file) {
for (std::unique_ptr<InputSection<E>> &isec : file->sections) {
if (isec && isec->is_alive && isec->icf_removed()) {
isec->kill();
eliminated++;
}
tbb::parallel_for_each(sections, [&](InputSection<E> *isec) {
if (isec != isec->icf_leader) {
isec->set_icf_removed();
isec->kill();
eliminated++;
} else {
isec->offset = -1;
}
});
}
+670 -277
View File
File diff suppressed because it is too large. Load diff
+154 -95
View File
@@ -31,13 +31,20 @@ bool cie_equals(const CieRecord<E> &a, const CieRecord<E> &b) {
}
template <typename E>
InputSection<E>::InputSection(Context<E> &ctx, ObjectFile<E> &file, i64 shndx)
: file(file), shndx(shndx) {
if (shndx < file.elf_sections.size())
contents = {(char *)file.mf->data + shdr().sh_offset, (size_t)shdr().sh_size};
InputSection<E>::InputSection(Context<E> &ctx, ObjectFile<E> &file, i64 shndx,
std::string_view section_name)
: file(&file), shndx(shndx) {
if (shndx < file.elf_sections.size()) {
std::string_view name = section_name;
if (name.empty())
name = file.shstrtab.data() + file.elf_sections[shndx].sh_name;
namelen = std::min<i64>(name.size(), UINT16_MAX);
contents = file.mf->data + shdr().sh_offset;
}
if (shdr().sh_flags & SHF_COMPRESSED) {
ElfChdr<E> &chdr = *(ElfChdr<E> *)&contents[0];
ElfChdr<E> &chdr = *(ElfChdr<E> *)contents;
sh_size = chdr.ch_size;
p2align = to_p2align(chdr.ch_addralign);
} else {
@@ -59,48 +66,73 @@ InputSection<E>::InputSection(Context<E> &ctx, ObjectFile<E> &file, i64 shndx)
template <typename E>
void InputSection<E>::uncompress(Context<E> &ctx) {
if (!(shdr().sh_flags & SHF_COMPRESSED) || uncompressed)
if (!(shdr().sh_flags & SHF_COMPRESSED) || is_uncompressed())
return;
u8 *buf = new u8[sh_size];
copy_contents(ctx, buf);
contents = std::string_view((char *)buf, sh_size);
copy_contents_to(ctx, buf, sh_size);
contents = buf;
ctx.string_pool.emplace_back(buf);
uncompressed = true;
set_uncompressed();
}
template <typename E>
void InputSection<E>::copy_contents(Context<E> &ctx, u8 *buf) {
if (!(shdr().sh_flags & SHF_COMPRESSED) || uncompressed) {
memcpy(buf, contents.data(), contents.size());
void InputSection<E>::copy_contents_to(Context<E> &ctx, u8 *buf, i64 sz) {
if (!(shdr().sh_flags & SHF_COMPRESSED) || is_uncompressed()) {
memcpy(buf, contents, sz);
return;
}
if (contents.size() < sizeof(ElfChdr<E>))
std::string_view view = get_contents();
if (view.size() < sizeof(ElfChdr<E>))
Fatal(ctx) << *this << ": corrupted compressed section";
ElfChdr<E> &hdr = *(ElfChdr<E> *)&contents[0];
std::string_view data = contents.substr(sizeof(ElfChdr<E>));
ElfChdr<E> &hdr = *(ElfChdr<E> *)contents;
std::string_view data = view.substr(sizeof(ElfChdr<E>));
switch (hdr.ch_type) {
case ELFCOMPRESS_ZLIB: {
unsigned long size = sh_size;
if (::uncompress(buf, &size, (u8 *)data.data(), data.size()) != Z_OK)
Fatal(ctx) << *this << ": uncompress failed";
assert(size == sh_size);
z_stream s = {};
inflateInit(&s);
s.next_in = (u8 *)data.data();
s.avail_in = data.size();
s.next_out = buf;
s.avail_out = sz;
int r;
while (s.total_out < sz && (r = inflate(&s, Z_NO_FLUSH)) == Z_OK);
if (s.total_out < sz && r != Z_STREAM_END)
Fatal(ctx) << *this << ": uncompress failed: " << s.msg;
inflateEnd(&s);
msan_unpoison(buf, sz);
break;
}
case ELFCOMPRESS_ZSTD:
if (ZSTD_decompress(buf, sh_size, (u8 *)data.data(), data.size()) != sh_size)
Fatal(ctx) << *this << ": ZSTD_decompress failed";
case ELFCOMPRESS_ZSTD: {
ZSTD_DCtx *dctx = ZSTD_createDCtx();
ZSTD_inBuffer in = { data.data(), data.size() };
ZSTD_outBuffer out = { buf, (size_t)sz };
while (out.pos < out.size) {
size_t r = ZSTD_decompressStream(dctx, &out, &in);
if (ZSTD_isError(r))
Fatal(ctx) << *this << ": uncompress failed: " << ZSTD_getErrorName(r);
if (r == 0 && out.pos < out.size)
Fatal(ctx) << *this << ": uncompress failed: premature end of input";
}
ZSTD_freeDCtx(dctx);
msan_unpoison(buf, sz);
break;
}
default:
Fatal(ctx) << *this << ": unsupported compression type: 0x"
<< std::hex << hdr.ch_type;
}
}
typedef enum : u8 { NONE, ERROR, COPYREL, PLT, CPLT } Action;
// wingdi.h defines ERROR as a macro, so undefine it before use
#undef ERROR
typedef enum : u8 { NONE, ERROR, CANONICAL, PLT } Action;
template <typename E>
static void do_action(Context<E> &ctx, Action action, InputSection<E> &isec,
@@ -113,23 +145,13 @@ static void do_action(Context<E> &ctx, Action action, InputSection<E> &isec,
<< std::hex << rel.r_offset << " against symbol `"
<< sym << "' can not be used; recompile with -fPIC";
break;
case COPYREL:
// Create a copy relocation
if (!sym.file->is_dso) {
assert(sym.esym().is_undef_weak());
Error(ctx) << isec << ": cannot create a copy relocation for "
<< sym <<"; recompile with -fPIE or -fPIC";
}
sym.flags |= NEEDS_COPYREL;
case CANONICAL:
sym.flags |= NEEDS_CANONICAL;
break;
case PLT:
// Create a PLT entry
sym.flags |= NEEDS_PLT;
break;
case CPLT:
// Create a canonical PLT entry
sym.flags |= NEEDS_CPLT;
break;
}
}
@@ -161,9 +183,9 @@ void InputSection<E>::scan_pcrel(Context<E> &ctx, Symbol<E> &sym,
// linker generally does not support PC-relative relocations.
static Action table[][4] = {
// Absolute Local Imported data Imported code
{ ERROR, NONE, ERROR, PLT }, // Shared object
{ ERROR, NONE, COPYREL, CPLT }, // Position-independent exec
{ NONE, NONE, COPYREL, CPLT }, // Position-dependent exec
{ ERROR, NONE, ERROR, PLT }, // Shared object
{ ERROR, NONE, CANONICAL, CANONICAL }, // Position-independent exec
{ NONE, NONE, CANONICAL, CANONICAL }, // Position-dependent exec
};
Action action = table[get_output_type(ctx)][get_sym_type(sym)];
@@ -180,9 +202,9 @@ void InputSection<E>::scan_absrel(Context<E> &ctx, Symbol<E> &sym,
// resolved at link-time.
static Action table[][4] = {
// Absolute Local Imported data Imported code
{ NONE, ERROR, ERROR, ERROR }, // Shared object
{ NONE, ERROR, ERROR, ERROR }, // Position-independent exec
{ NONE, NONE, COPYREL, CPLT }, // Position-dependent exec
{ NONE, ERROR, ERROR, ERROR }, // Shared object
{ NONE, ERROR, ERROR, ERROR }, // Position-independent exec
{ NONE, NONE, CANONICAL, CANONICAL }, // Position-dependent exec
};
Action action = table[get_output_type(ctx)][get_sym_type(sym)];
@@ -234,23 +256,26 @@ void InputSection<E>::write_to(Context<E> &ctx, u8 *buf) {
if (deltas.empty()) {
// If a section is not relaxed, we can copy it as a one big chunk.
copy_contents(ctx, buf);
copy_contents_to(ctx, buf, sh_size);
} else {
// A relaxed section is copied piece-wise.
memcpy(buf, contents.data(), deltas[0].offset);
memcpy(buf, contents, deltas[0].offset);
i64 input_size = get_contents().size();
for (i64 i = 0; i < deltas.size(); i++) {
i64 offset = deltas[i].offset;
i64 delta = deltas[i].delta;
i64 end = (i + 1 == deltas.size()) ? contents.size() : deltas[i + 1].offset;
i64 end = input_size;
if (i + 1 < deltas.size())
end = deltas[i + 1].offset;
i64 removed_bytes = get_removed_bytes(deltas, i);
memcpy(buf + offset + removed_bytes - delta,
contents.data() + offset + removed_bytes,
contents + offset + removed_bytes,
end - offset - removed_bytes);
}
}
} else {
copy_contents(ctx, buf);
copy_contents_to(ctx, buf, sh_size);
}
// Apply relocations
@@ -266,49 +291,72 @@ void InputSection<E>::write_to(Context<E> &ctx, u8 *buf) {
template <typename E>
std::string_view
InputSection<E>::get_func_name(Context<E> &ctx, i64 offset) const {
for (Symbol<E> *sym : file.symbols) {
if (sym->file == &file) {
const ElfSym<E> &esym = sym->esym();
if (esym.st_shndx == shndx && esym.st_type == STT_FUNC &&
esym.st_value <= offset && offset < esym.st_value + esym.st_size) {
if (ctx.arg.demangle)
return demangle(*sym);
return sym->name();
}
}
}
for (Symbol<E> *sym : file->symbols)
if (sym->file == file)
if (const ElfSym<E> &esym = sym->esym();
esym.st_shndx == shndx && esym.st_type == STT_FUNC &&
esym.st_value <= offset && offset < esym.st_value + esym.st_size)
return ctx.arg.demangle ? demangle(*sym) : sym->name();
return "";
}
// Find the prevailing group having the same signature as the discarded group
// that contains esym. This is called only on an error path, so a linear scan is
// sufficient.
template <typename E>
static ObjectFile<E> *
find_comdat_owner(Context<E> &ctx, ObjectFile<E> &file, const ElfSym<E> &esym) {
if (esym.is_undef() || esym.is_abs() || esym.is_common())
return nullptr;
i64 shndx = file.get_shndx(esym);
for (ComdatGroupRef<E> &ref : file.comdat_groups) {
if (ref.is_owner ||
!ranges::any_of(ref.members(file), [&](u32 x) { return x == shndx; }))
continue;
Symbol<E> *sig = ref.signature(ctx);
for (ObjectFile<E> *file : ctx.objs)
for (ComdatGroupRef<E> &ref : file->comdat_groups)
if (ref.is_owner && ref.signature(ctx) == sig)
return file;
}
return nullptr;
}
// Test if the symbol a given relocation refers to has already been resolved.
// If not, record that error and returns true.
template <typename E>
bool InputSection<E>::record_undef_error(Context<E> &ctx, const ElfRel<E> &rel) {
// If a relocation refers to a linker-synthesized symbol for a
// section fragment, it's always been resolved.
if (file.elf_syms.size() <= rel.r_sym)
if (file->elf_syms.size() <= rel.r_sym)
return false;
Symbol<E> &sym = *file.symbols[rel.r_sym];
const ElfSym<E> &esym = file.elf_syms[rel.r_sym];
Symbol<E> &sym = *file->symbols[rel.r_sym];
const ElfSym<E> &esym = file->elf_syms[rel.r_sym];
// If a symbol is defined in a comdat group, and the comdat group is
// discarded, the symbol may not have an owner. It is technically an
// violation of the One Definition Rule, so it is a programmer's fault.
if (!sym.file) {
Error(ctx) << *this << ": " << sym << " refers to a discarded COMDAT section"
<< " probably due to an ODR violation";
// A global symbol in a discarded COMDAT group should resolve to the
// corresponding symbol in the prevailing group. If it does not, the
// object files violate the One Definition Rule.
if (!sym.file && &sym != discarded_comdat_sym<E>) {
std::stringstream ss;
ss << *this << ": " << sym << " refers to a discarded COMDAT section"
<< " probably due to an ODR violation";
if (ObjectFile<E> *owner = find_comdat_owner(ctx, *file, esym))
ss << '\n' << ">>> prevailing definition is in " << *owner;
Error(ctx) << ss.str();
return true;
}
auto record = [&] {
std::stringstream ss;
if (std::string_view source = file.get_source_name(); !source.empty())
if (std::string_view source = file->get_source_name(); !source.empty())
ss << ">>> referenced by " << source << "\n";
else
ss << ">>> referenced by " << *this << "\n";
ss << ">>> " << file;
ss << ">>> " << *file;
if (std::string_view func = get_func_name(ctx, rel.r_offset); !func.empty())
ss << ":(" << func << ")";
ss << '\n';
@@ -342,12 +390,9 @@ bool InputSection<E>::record_undef_error(Context<E> &ctx, const ElfRel<E> &rel)
template <typename E>
MergeableSection<E>::MergeableSection(Context<E> &ctx, MergedSection<E> &parent,
std::unique_ptr<InputSection<E>> &isec)
: parent(parent), p2align(isec->p2align), section(std::move(isec)) {
section->uncompress(ctx);
std::scoped_lock lock(parent.mu);
parent.members.push_back(this);
InputSection<E> *isec)
: parent(parent), p2align(isec->p2align), input_section(isec) {
input_section->uncompress(ctx);
}
static size_t find_null(std::string_view data, i64 pos, i64 entsize) {
@@ -380,42 +425,40 @@ static size_t find_null(std::string_view data, i64 pos, i64 entsize) {
// We do not support mergeable sections that have relocations.
template <typename E>
void MergeableSection<E>::split_contents(Context<E> &ctx) {
std::string_view data = section->contents;
std::string_view data = input_section->get_contents();
if (data.size() > UINT32_MAX)
Fatal(ctx) << *section << ": mergeable section too large";
Fatal(ctx) << *input_section << ": mergeable section too large";
i64 entsize = parent.shdr.sh_entsize;
HyperLogLog::Sketch &sketch = parent.estimator.local();
// Split sections
auto add_fragment = [&](i64 pos, i64 size) {
frag_offsets.push_back(pos);
u64 hash = hash_string(data.substr(pos, size));
hashes.push_back(hash);
sketch.insert(hash);
};
// Hash each fragment while its contents are still in the cache.
if (parent.shdr.sh_flags & SHF_STRINGS) {
for (i64 pos = 0; pos < data.size();) {
frag_offsets.push_back(pos);
size_t end = find_null(data, pos, entsize);
if (end == data.npos)
Fatal(ctx) << *section << ": string is not null terminated";
Fatal(ctx) << *input_section << ": string is not null terminated";
add_fragment(pos, end + entsize - pos);
pos = end + entsize;
}
} else {
if (data.size() % entsize)
Fatal(ctx) << *section << ": section size is not multiple of sh_entsize";
Fatal(ctx) << *input_section
<< ": section size is not multiple of sh_entsize";
frag_offsets.reserve(data.size() / entsize);
hashes.reserve(data.size() / entsize);
for (i64 pos = 0; pos < data.size(); pos += entsize)
frag_offsets.push_back(pos);
add_fragment(pos, entsize);
}
// Compute hashes for section pieces
HyperLogLog estimator;
hashes.reserve(frag_offsets.size());
for (i64 i = 0; i < frag_offsets.size(); i++) {
u64 hash = hash_string(get_contents(i));
hashes.push_back(hash);
estimator.insert(hash);
}
parent.estimator.merge(estimator);
static Counter counter("string_fragments");
counter += frag_offsets.size();
}
@@ -423,8 +466,20 @@ void MergeableSection<E>::split_contents(Context<E> &ctx) {
template <typename E>
void MergeableSection<E>::resolve_contents(Context<E> &ctx) {
fragments.reserve(frag_offsets.size());
for (i64 i = 0; i < frag_offsets.size(); i++)
fragments.push_back(parent.insert(ctx, get_contents(i), hashes[i], p2align));
// The hash table is typically much larger than the cache, so each
// insertion stalls on a cache miss for its first probe. We know all
// hashes upfront, so prefetch the bucket a few insertions ahead.
constexpr i64 lookahead = 8;
for (i64 i = 0; i < frag_offsets.size(); i++) {
if (i + lookahead < frag_offsets.size())
parent.map.prefetch(hashes[i + lookahead]);
SectionFragment<E> *frag =
parent.insert(ctx, get_contents(i), hashes[i], p2align);
fragments.push_back(parent.map.get_idx(frag));
}
// Reclaim memory as we'll never use this vector again
hashes.clear();
@@ -433,6 +488,10 @@ void MergeableSection<E>::resolve_contents(Context<E> &ctx) {
using E = MOLD_TARGET;
// InputSections are allocated from the arena, which does not run
// destructors, so the class must stay trivially destructible.
static_assert(std::is_trivially_destructible_v<InputSection<E>>);
template bool cie_equals(const CieRecord<E> &, const CieRecord<E> &);
template class InputSection<E>;
template class MergeableSection<E>;
+1 -1
View File
@@ -9,7 +9,7 @@
// mold processes to just 1 for each user. It is intended to be used as
// `MOLD_JOBS=1 ninja` or `MOLD_JOBS=1 make -j$(nproc)`.
#include "common.h"
#include "mold.h"
#include <cstdlib>
#include <fcntl.h>
File renamed without changes.
+44 -8
View File
@@ -111,6 +111,27 @@ static std::string_view unquote(std::string_view s) {
return s;
}
// Version scripts and dynamic lists take a quoted name literally, while
// an unquoted one is a glob pattern. We escape glob metacharacters in a
// quoted name so that the pattern matcher treats them as literals.
template <typename E>
std::string_view Script<E>::unquote_pattern(std::string_view tok) {
if (!tok.starts_with('"'))
return tok;
std::string_view str = unquote(tok);
if (str.find_first_of("*?[\\") == str.npos)
return str;
std::string out;
for (char c : str) {
if (c == '*' || c == '?' || c == '[' || c == '\\')
out += '\\';
out += c;
}
return save_string(ctx, out);
}
template <typename E>
std::span<std::string_view>
Script<E>::read_output_format(std::span<std::string_view> tok) {
@@ -124,7 +145,11 @@ Script<E>::read_output_format(std::span<std::string_view> tok) {
template <typename E>
static bool is_in_sysroot(Context<E> &ctx, std::string path) {
std::string rel = std::filesystem::relative(path, ctx.arg.sysroot).string();
std::string sysroot = ctx.arg.sysroot;
if (sysroot.starts_with('/') && !ctx.arg.chroot.empty())
sysroot = ctx.arg.chroot + "/" + path_clean(sysroot);
std::string rel = std::filesystem::relative(path, sysroot).string();
return rel != "." && !rel.starts_with("../");
}
@@ -196,7 +221,8 @@ Script<E>::read_group(std::span<std::string_view> tok) {
}
MappedFile *mf = resolve_path(tok[0], true);
read_file(ctx, rctx, mf);
ReaderContext rctx2 = rctx.next_child();
read_file(ctx, rctx2, mf);
tok = tok.subspan(1);
}
@@ -245,9 +271,12 @@ std::string_view Script<E>::get_script_output_type() {
}
if (tok.size() >= 3 && (tok[0] == "INPUT" || tok[0] == "GROUP") &&
tok[1] == "(")
tok[1] == "(") {
if (tok.size() >= 5 && tok[2] == "AS_NEEDED" && tok[3] == "(")
tok = tok.subspan(2);
if (MappedFile *mf = resolve_path(tok[2], false))
return get_machine_type(ctx, rctx, mf);
}
return "";
}
@@ -261,6 +290,13 @@ static bool read_label(std::span<std::string_view> &tok, std::string label) {
tok = tok.subspan(2);
return true;
}
// The tokenizer keeps a colon in a token because of the C++ scope
// operator, so `local:*` is a single token. Split the pattern off.
if (tok.size() >= 1 && tok[0].starts_with(label + ":")) {
tok[0] = tok[0].substr(label.size() + 1);
return true;
}
return false;
}
@@ -301,11 +337,11 @@ Script<E>::read_version_script_commands(std::span<std::string_view> tok,
if (tok[0] == "*") {
ctx.default_version = (is_global ? ver_idx : (u32)VER_NDX_LOCAL);
} else if (is_global) {
ctx.version_patterns.push_back({unquote(tok[0]), mf->name, ver_str,
ver_idx, is_cpp});
ctx.version_patterns.push_back({unquote_pattern(tok[0]), mf->name,
ver_str, ver_idx, is_cpp});
} else {
ctx.version_patterns.push_back({unquote(tok[0]), mf->name, ver_str,
VER_NDX_LOCAL, is_cpp});
ctx.version_patterns.push_back({unquote_pattern(tok[0]), mf->name,
ver_str, VER_NDX_LOCAL, is_cpp});
}
tok = tok.subspan(1);
@@ -379,7 +415,7 @@ Script<E>::read_dynamic_list_commands(std::span<std::string_view> tok,
continue;
}
result.push_back({unquote(tok[0]), "", is_cpp});
result.push_back({unquote_pattern(tok[0]), "", is_cpp});
tok = skip(tok.subspan(1), ";");
}
return tok;
+102 -103
View File
@@ -29,8 +29,8 @@
//
// 4. Continue the rest of the linking process as usual.
//
// When gcc or clang inovkes ld, they pass `-plugin linker-plugin.so` to
// the linker. The given .so file provides a way to call the compiler
// 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
@@ -179,13 +179,18 @@ static PluginStatus add_input_file(const char *path) {
MappedFile *mf = must_open_file(ctx, path);
mf->is_dependency = false;
ObjectFile<E> *file = new ObjectFile<E>(ctx, mf, "");
ObjectFile<E> *file = ctx.arena.template make<ObjectFile<E>>(ctx, mf, "");
ctx.obj_pool.emplace_back(file);
lto_objects<E>.push_back(file);
file->priority = file_priority++;
file->is_lto_output = true;
file->is_reachable = true;
file->parse(ctx);
file->parse_symbols(ctx);
// parse_symbols() only registers global symbols. Create their shared
// Symbol objects and fill in the file's pointers before resolving.
gather_symbols(ctx);
file->resolve_symbols(ctx);
return LDPS_OK;
}
@@ -280,7 +285,7 @@ template <typename E>
static PluginStatus
get_symbols(const void *handle, int nsyms, PluginSymbol *psyms, bool is_v2) {
ObjectFile<E> &file = *(ObjectFile<E> *)handle;
assert(file.is_lto_obj);
assert(file.is_lto_input);
// If file is an archive member which was not chose to be included in
// to the final result, we need to make the plugin to ignore all
@@ -307,7 +312,7 @@ get_symbols(const void *handle, int nsyms, PluginSymbol *psyms, bool is_v2) {
if (sym.file->is_dso)
return LDPR_RESOLVED_DYN;
if (((ObjectFile<E> *)sym.file)->is_lto_obj && !sym.is_wrapped)
if (sym.file->to_obj()->is_lto_input && !sym.is_wrapped)
return esym.is_undef() ? LDPR_RESOLVED_IR : LDPR_PREEMPTED_IR;
return esym.is_undef() ? LDPR_RESOLVED_EXEC : LDPR_PREEMPTED_REG;
};
@@ -342,8 +347,8 @@ static void restart_process(Context<E> &ctx) {
for (std::string_view arg : ctx.cmdline_args)
args.push_back(strdup(std::string(arg).c_str()));
for (std::unique_ptr<ObjectFile<E>> &file : ctx.obj_pool)
if (file->is_lto_obj && !file->is_reachable)
for (ArenaObjectPtr<ObjectFile<E>> &file : ctx.obj_pool)
if (file->is_lto_input && !file->is_reachable)
args.push_back(strdup(("--:ignore-ir-file=" +
file->mf->get_identifier()).c_str()));
@@ -425,7 +430,8 @@ get_api_version(const char *plugin_identifier,
if (LAPI_V1 < minimal_api_supported)
Fatal(*gctx<E>) << "LTO plugin does not support V0 or V1 API";
std::string version = get_mold_version() + "\0"s;
// The plugin reads the string after this function has returned
static std::string version = mold_version + "\0"s;
*linker_identifier = "mold";
*linker_version = version.data();
@@ -440,74 +446,71 @@ get_api_version(const char *plugin_identifier,
// dlopen the linker plugin file
template <typename E>
static void load_lto_plugin(Context<E> &ctx) {
static std::once_flag flag;
if (phase != 0)
return;
phase = 1;
gctx<E> = &ctx;
std::call_once(flag, [&] {
assert(phase == 0);
phase = 1;
gctx<E> = &ctx;
void *handle = dlopen(ctx.arg.plugin.c_str(), RTLD_NOW | RTLD_LOCAL);
if (!handle)
Fatal(ctx) << "could not open plugin file: " << dlerror();
void *handle = dlopen(ctx.arg.plugin.c_str(), RTLD_NOW | RTLD_LOCAL);
if (!handle)
Fatal(ctx) << "could not open plugin file: " << dlerror();
OnloadFn *onload = (OnloadFn *)dlsym(handle, "onload");
if (!onload)
Fatal(ctx) << "failed to load plugin " << ctx.arg.plugin << ": "
<< dlerror();
OnloadFn *onload = (OnloadFn *)dlsym(handle, "onload");
if (!onload)
Fatal(ctx) << "failed to load plugin " << ctx.arg.plugin << ": "
<< dlerror();
auto save = [&](std::string_view str) {
return save_string(ctx, std::string(str).c_str()).data();
};
auto save = [&](std::string_view str) {
return save_string(ctx, std::string(str).c_str()).data();
};
std::vector<PluginTagValue> tv;
tv.emplace_back(LDPT_MESSAGE, message<E>);
std::vector<PluginTagValue> tv;
tv.emplace_back(LDPT_MESSAGE, message<E>);
if (ctx.arg.shared)
tv.emplace_back(LDPT_LINKER_OUTPUT, LDPO_DYN);
else if (ctx.arg.pie)
tv.emplace_back(LDPT_LINKER_OUTPUT, LDPO_PIE);
else
tv.emplace_back(LDPT_LINKER_OUTPUT, LDPO_EXEC);
if (ctx.arg.shared)
tv.emplace_back(LDPT_LINKER_OUTPUT, LDPO_DYN);
else if (ctx.arg.pie)
tv.emplace_back(LDPT_LINKER_OUTPUT, LDPO_PIE);
else
tv.emplace_back(LDPT_LINKER_OUTPUT, LDPO_EXEC);
for (std::string_view opt : ctx.arg.plugin_opt)
tv.emplace_back(LDPT_OPTION, save(opt));
for (std::string_view opt : ctx.arg.plugin_opt)
tv.emplace_back(LDPT_OPTION, save(opt));
tv.emplace_back(LDPT_REGISTER_CLAIM_FILE_HOOK, register_claim_file_hook<E>);
tv.emplace_back(LDPT_REGISTER_ALL_SYMBOLS_READ_HOOK,
register_all_symbols_read_hook<E>);
tv.emplace_back(LDPT_REGISTER_CLEANUP_HOOK, register_cleanup_hook<E>);
tv.emplace_back(LDPT_ADD_SYMBOLS, add_symbols);
tv.emplace_back(LDPT_GET_SYMBOLS, get_symbols_v1);
tv.emplace_back(LDPT_ADD_INPUT_FILE, add_input_file<E>);
tv.emplace_back(LDPT_GET_INPUT_FILE, get_input_file);
tv.emplace_back(LDPT_RELEASE_INPUT_FILE, release_input_file<E>);
tv.emplace_back(LDPT_ADD_INPUT_LIBRARY, add_input_library);
tv.emplace_back(LDPT_OUTPUT_NAME, save(ctx.arg.output));
tv.emplace_back(LDPT_SET_EXTRA_LIBRARY_PATH, set_extra_library_path);
tv.emplace_back(LDPT_GET_VIEW, get_view<E>);
tv.emplace_back(LDPT_GET_INPUT_SECTION_COUNT, get_input_section_count);
tv.emplace_back(LDPT_GET_INPUT_SECTION_TYPE, get_input_section_type);
tv.emplace_back(LDPT_GET_INPUT_SECTION_NAME, get_input_section_name);
tv.emplace_back(LDPT_GET_INPUT_SECTION_CONTENTS, get_input_section_contents);
tv.emplace_back(LDPT_UPDATE_SECTION_ORDER, update_section_order);
tv.emplace_back(LDPT_ALLOW_SECTION_ORDERING, allow_section_ordering);
tv.emplace_back(LDPT_ADD_SYMBOLS_V2, add_symbols);
tv.emplace_back(LDPT_GET_SYMBOLS_V2, get_symbols_v2<E>);
tv.emplace_back(LDPT_ALLOW_UNIQUE_SEGMENT_FOR_SECTIONS,
allow_unique_segment_for_sections);
tv.emplace_back(LDPT_UNIQUE_SEGMENT_FOR_SECTIONS, unique_segment_for_sections);
tv.emplace_back(LDPT_GET_SYMBOLS_V3, get_symbols_v3<E>);
tv.emplace_back(LDPT_GET_INPUT_SECTION_ALIGNMENT, get_input_section_alignment);
tv.emplace_back(LDPT_GET_INPUT_SECTION_SIZE, get_input_section_size);
tv.emplace_back(LDPT_REGISTER_NEW_INPUT_HOOK, register_new_input_hook<E>);
tv.emplace_back(LDPT_GET_WRAP_SYMBOLS, get_wrap_symbols);
tv.emplace_back(LDPT_GET_API_VERSION, get_api_version<E>);
tv.emplace_back(LDPT_NULL, 0);
tv.emplace_back(LDPT_REGISTER_CLAIM_FILE_HOOK, register_claim_file_hook<E>);
tv.emplace_back(LDPT_REGISTER_ALL_SYMBOLS_READ_HOOK,
register_all_symbols_read_hook<E>);
tv.emplace_back(LDPT_REGISTER_CLEANUP_HOOK, register_cleanup_hook<E>);
tv.emplace_back(LDPT_ADD_SYMBOLS, add_symbols);
tv.emplace_back(LDPT_GET_SYMBOLS, get_symbols_v1);
tv.emplace_back(LDPT_ADD_INPUT_FILE, add_input_file<E>);
tv.emplace_back(LDPT_GET_INPUT_FILE, get_input_file);
tv.emplace_back(LDPT_RELEASE_INPUT_FILE, release_input_file<E>);
tv.emplace_back(LDPT_ADD_INPUT_LIBRARY, add_input_library);
tv.emplace_back(LDPT_OUTPUT_NAME, save(ctx.arg.output));
tv.emplace_back(LDPT_SET_EXTRA_LIBRARY_PATH, set_extra_library_path);
tv.emplace_back(LDPT_GET_VIEW, get_view<E>);
tv.emplace_back(LDPT_GET_INPUT_SECTION_COUNT, get_input_section_count);
tv.emplace_back(LDPT_GET_INPUT_SECTION_TYPE, get_input_section_type);
tv.emplace_back(LDPT_GET_INPUT_SECTION_NAME, get_input_section_name);
tv.emplace_back(LDPT_GET_INPUT_SECTION_CONTENTS, get_input_section_contents);
tv.emplace_back(LDPT_UPDATE_SECTION_ORDER, update_section_order);
tv.emplace_back(LDPT_ALLOW_SECTION_ORDERING, allow_section_ordering);
tv.emplace_back(LDPT_ADD_SYMBOLS_V2, add_symbols);
tv.emplace_back(LDPT_GET_SYMBOLS_V2, get_symbols_v2<E>);
tv.emplace_back(LDPT_ALLOW_UNIQUE_SEGMENT_FOR_SECTIONS,
allow_unique_segment_for_sections);
tv.emplace_back(LDPT_UNIQUE_SEGMENT_FOR_SECTIONS, unique_segment_for_sections);
tv.emplace_back(LDPT_GET_SYMBOLS_V3, get_symbols_v3<E>);
tv.emplace_back(LDPT_GET_INPUT_SECTION_ALIGNMENT, get_input_section_alignment);
tv.emplace_back(LDPT_GET_INPUT_SECTION_SIZE, get_input_section_size);
tv.emplace_back(LDPT_REGISTER_NEW_INPUT_HOOK, register_new_input_hook<E>);
tv.emplace_back(LDPT_GET_WRAP_SYMBOLS, get_wrap_symbols);
tv.emplace_back(LDPT_GET_API_VERSION, get_api_version<E>);
tv.emplace_back(LDPT_NULL, 0);
[[maybe_unused]] PluginStatus status = onload(tv.data());
assert(status == LDPS_OK);
});
[[maybe_unused]] PluginStatus status = onload(tv.data());
assert(status == LDPS_OK);
}
template <typename E>
@@ -536,8 +539,6 @@ static ElfSym<E> to_elf_sym(PluginSymbol &psym) {
}
switch (psym.symbol_type) {
case LDST_UNKNOWN:
break;
case LDST_FUNCTION:
esym.st_type = STT_FUNC;
break;
@@ -547,8 +548,6 @@ static ElfSym<E> to_elf_sym(PluginSymbol &psym) {
};
switch (psym.visibility) {
case LDPV_DEFAULT:
break;
case LDPV_PROTECTED:
esym.st_visibility = STV_PROTECTED;
break;
@@ -600,27 +599,23 @@ create_plugin_input_file(Context<E> &ctx, MappedFile *mf) {
template <typename E>
ObjectFile<E> *read_lto_object(Context<E> &ctx, MappedFile *mf) {
if (ctx.arg.plugin.empty())
Fatal(ctx) << mf->name << ": don't know how to handle this LTO object file "
<< "because no -plugin option was given. Please make sure you "
<< "added -flto not only for creating object files but also for "
<< "creating the final executable.";
Fatal(ctx) << mf->name << ": unable to handle this LTO object file because "
<< "the -plugin option was not provided. Please make sure you "
<< "added -flto not only when creating object files but also "
<< "when linking the final executable.";
load_lto_plugin(ctx);
// V0 API's claim_file is not thread-safe.
static std::mutex mu;
std::unique_lock lock(mu, std::defer_lock);
if (!is_gcc_linker_api_v1)
lock.lock();
// Create mold's object instance
ObjectFile<E> *obj = new ObjectFile<E>;
ObjectFile<E> *obj =
ctx.arena.template make<ObjectFile<E>>(ctx);
ctx.obj_pool.emplace_back(obj);
obj->filename = mf->name;
obj->symbols.push_back(new Symbol<E>);
Symbol<E> *dummy = ctx.arena.template make<Symbol<E>>();
obj->symbols.emplace_back(dummy);
obj->first_global = 1;
obj->is_lto_obj = true;
obj->is_lto_input = true;
obj->mf = mf;
obj->archive_name = mf->parent ? mf->parent->name : "";
@@ -633,17 +628,18 @@ ObjectFile<E> *read_lto_object(Context<E> &ctx, MappedFile *mf) {
// claim_file_hook() calls add_symbols() which initializes `plugin_symbols`
int claimed = false;
claim_file_hook(&file, &claimed);
if (!claimed)
Fatal(ctx) << mf->name << ": not claimed by the LTO plugin;"
<< " please make sure you are using the same compiler of the"
<< " same version for all object files";
// It looks like GCC doesn't need fd after claim_file_hook() while
// LLVM needs it and takes the ownership of fd. To prevent "too many
// open files" issue, we close fd only for GCC. This is ugly, though.
if (!is_llvm(ctx)) {
MappedFile *mf2 = mf->parent ? mf->parent : mf;
mf2->close_fd();
if (mf->parent)
mf->parent->close_fd();
else
mf->close_fd();
if (!claimed) {
if (!mf->parent && !mf->thin_parent)
Fatal(ctx) << mf->name << ": not claimed by the LTO plugin;"
<< " please make sure you are using the same compiler of the"
<< " same version for all object files";
return nullptr;
}
// Create a symbol strtab
@@ -654,7 +650,8 @@ ObjectFile<E> *read_lto_object(Context<E> &ctx, MappedFile *mf) {
// Initialize esyms
obj->lto_elf_syms.resize(plugin_symbols.size() + 1);
obj->lto_comdat_groups.resize(plugin_symbols.size() + 1);
obj->lto_comdat_signatures.resize(plugin_symbols.size() + 1);
obj->lto_comdat_discarded.resize(plugin_symbols.size() + 1);
i64 strtab_offset = 1;
for (i64 i = 0; i < plugin_symbols.size(); i++) {
@@ -670,13 +667,15 @@ ObjectFile<E> *read_lto_object(Context<E> &ctx, MappedFile *mf) {
// section. We handle such symbols differently than comdat symbols in
// a regular file because, unlike regular object files, IR files don't
// have input sections.
if (psym.comdat_key)
obj->lto_comdat_groups[i + 1] = insert_comdat_group(ctx, psym.comdat_key);
if (psym.comdat_key) {
std::string_view key = save_string(ctx, psym.comdat_key);
obj->lto_comdat_signatures[i + 1] = get_symbol(ctx, key);
}
}
obj->symbol_strtab = save_string(ctx, strtab);
obj->elf_syms = obj->lto_elf_syms;
obj->initialize_symbols(ctx);
obj->register_global_symbols(ctx);
plugin_symbols.clear();
return obj;
}
@@ -695,10 +694,10 @@ std::vector<ObjectFile<E> *> run_lto_plugin(Context<E> &ctx) {
// Set `referenced_by_regular_obj` bit.
tbb::parallel_for_each(ctx.objs, [](ObjectFile<E> *file) {
if (!file->is_lto_obj) {
if (!file->is_lto_input) {
for (Symbol<E> *sym : file->get_global_syms()) {
if (sym->file && !sym->file->is_dso &&
((ObjectFile<E> *)sym->file)->is_lto_obj) {
sym->file->to_obj()->is_lto_input) {
std::scoped_lock lock(sym->mu);
sym->referenced_by_regular_obj = true;
}
@@ -726,7 +725,7 @@ std::vector<ObjectFile<E> *> run_lto_plugin(Context<E> &ctx) {
// given to the LTO backend. Such sections contains code and data for
// peripherails (typically GPUs).
for (ObjectFile<E> *file : ctx.objs) {
if (file->is_reachable && !file->is_lto_obj && file->is_gcc_offload_obj) {
if (file->is_reachable && !file->is_lto_input && file->is_gcc_offload_obj) {
PluginInputFile pfile = create_plugin_input_file(ctx, file->mf);
int claimed = false;
claim_file_hook(&pfile, &claimed);
+373 -177
View File
@@ -1,122 +1,141 @@
#include "mold.h"
#include "../lib/archive-file.h"
#include "config.h"
#include <cstring>
#include <filesystem>
#include <functional>
#include <sys/stat.h>
#include <sys/types.h>
#include <tbb/global_control.h>
#include <tbb/parallel_for_each.h>
#include <tbb/parallel_sort.h>
#include <unordered_set>
#ifdef _WIN32
# include <direct.h>
# define chdir _chdir
#else
# include <unistd.h>
#endif
#ifdef MOLD_X86_64
int main(int argc, char **argv) {
mold::set_mimalloc_options();
return mold::mold_main<mold::X86_64>(argc, argv);
}
#endif
namespace mold {
template <typename E>
static void
check_file_compatibility(Context<E> &ctx, ReaderContext &rctx, MappedFile *mf) {
std::string_view target = get_machine_type(ctx, rctx, mf);
if (target.empty())
Fatal(ctx) << mf->name << ": unknown machine type";
if (target != ctx.arg.emulation)
Fatal(ctx) << mf->name << ": incompatible file type: "
<< ctx.arg.emulation << " is expected but got " << target;
}
template <typename E>
static ObjectFile<E> *new_object_file(Context<E> &ctx, ReaderContext &rctx,
MappedFile *mf, std::string archive_name) {
static void new_object_file(Context<E> &ctx, ReaderContext &rctx,
MappedFile *mf, std::string archive_name) {
static Counter count("parsed_objs");
count++;
check_file_compatibility(ctx, rctx, mf);
ObjectFile<E> *file = new ObjectFile<E>(ctx, mf, archive_name);
ObjectFile<E> *file =
ctx.arena.template make<ObjectFile<E>>(ctx, mf, archive_name);
ctx.obj_pool.emplace_back(file);
file->priority = ctx.file_priority++;
file->is_reachable =
!rctx.in_lib && (archive_name.empty() || rctx.whole_archive);
file->as_needed =
rctx.in_lib || (!archive_name.empty() && !rctx.whole_archive);
file->is_reachable = !file->as_needed;
rctx.tg->run([file, &ctx] { file->parse(ctx); });
if (ctx.arg.trace)
Out(ctx) << "trace: " << *file;
return file;
file->parse_symbols(ctx);
ctx.unsorted_input_files.push_back({rctx.pos, file});
}
// IR files for LTO are not read in place. We only record them here,
// and read_input_files() hands them to the LTO plugin once all input
// files have been found. We do this because LTO object file reading
// is order-dependent.
template <typename E>
static void defer_lto_obj(Context<E> &ctx, ReaderContext &rctx,
MappedFile *mf, std::string archive_name) {
ReaderJob job;
job.rctx = rctx;
job.mf = mf;
job.archive_name = archive_name;
ctx.lto_jobs.push_back(std::move(job));
}
template <typename E>
static ObjectFile<E> *new_lto_obj(Context<E> &ctx, ReaderContext &rctx,
MappedFile *mf, std::string archive_name) {
static void new_lto_obj(Context<E> &ctx, ReaderContext &rctx,
MappedFile *mf, std::string archive_name) {
static Counter count("parsed_lto_objs");
count++;
if (ctx.arg.ignore_ir_file.count(mf->get_identifier()))
return nullptr;
return;
ObjectFile<E> *file = read_lto_object(ctx, mf);
file->priority = ctx.file_priority++;
file->archive_name = archive_name;
file->is_reachable =
!rctx.in_lib && (archive_name.empty() || rctx.whole_archive);
if (!file)
return;
if (ctx.arg.trace)
Out(ctx) << "trace: " << *file;
return file;
file->archive_name = archive_name;
file->as_needed =
rctx.in_lib || (!archive_name.empty() && !rctx.whole_archive);
file->is_reachable = !file->as_needed;
ctx.unsorted_input_files.push_back({rctx.pos, file});
}
template <typename E>
static SharedFile<E> *
new_shared_file(Context<E> &ctx, ReaderContext &rctx, MappedFile *mf) {
static void new_shared_file(Context<E> &ctx, ReaderContext &rctx,
MappedFile *mf) {
if (rctx.static_)
Fatal(ctx) << mf->name << ": attempted static link of a dynamic object";
check_file_compatibility(ctx, rctx, mf);
SharedFile<E> *file = new SharedFile<E>(ctx, mf);
SharedFile<E> *file = ctx.arena.template make<SharedFile<E>>(ctx, mf);
ctx.dso_pool.emplace_back(file);
file->priority = ctx.file_priority++;
file->is_reachable = !rctx.as_needed;
file->as_needed = rctx.as_needed;
file->is_reachable = !file->as_needed;
rctx.tg->run([file, &ctx] { file->parse(ctx); });
if (ctx.arg.trace)
Out(ctx) << "trace: " << *file;
return file;
file->parse(ctx);
ctx.unsorted_input_files.push_back({rctx.pos, file});
}
// Reads a file inside an archive.
template <typename E>
static void read_archive_member(Context<E> &ctx, ReaderContext &rctx,
MappedFile *mf, std::string archive_name) {
switch (get_file_type(ctx, mf)) {
case FileType::ELF_OBJ:
new_object_file(ctx, rctx, mf, archive_name);
break;
case FileType::GCC_LTO_OBJ:
case FileType::LLVM_BITCODE:
defer_lto_obj(ctx, rctx, mf, archive_name);
break;
case FileType::ELF_DSO:
Warn(ctx) << archive_name << "(" << mf->name
<< "): shared object file in an archive is ignored";
break;
default:
break;
}
}
// Reads the given file, which is located at rctx.pos in the command
// line. If the file is a container, i.e. an archive file or a linker
// script, the files in it are read in place, recursively. IR files
// for LTO are only recorded; see defer_lto_obj().
//
// read_input_files() reads top-level files with this function too but
// overrides the container cases to read archive members in parallel.
template <typename E>
void read_file(Context<E> &ctx, ReaderContext &rctx, MappedFile *mf) {
switch (get_file_type(ctx, mf)) {
case FileType::ELF_OBJ:
ctx.objs.push_back(new_object_file(ctx, rctx, mf, ""));
new_object_file(ctx, rctx, mf, "");
return;
case FileType::ELF_DSO:
ctx.dsos.push_back(new_shared_file(ctx, rctx, mf));
new_shared_file(ctx, rctx, mf);
return;
case FileType::AR:
case FileType::THIN_AR:
for (MappedFile *child : read_archive_members(ctx, mf)) {
switch (get_file_type(ctx, child)) {
case FileType::ELF_OBJ:
ctx.objs.push_back(new_object_file(ctx, rctx, child, mf->name));
break;
case FileType::GCC_LTO_OBJ:
case FileType::LLVM_BITCODE:
if (ObjectFile<E> *file = new_lto_obj(ctx, rctx, child, mf->name))
ctx.objs.push_back(file);
break;
case FileType::ELF_DSO:
Warn(ctx) << mf->name << "(" << child->name
<< "): shared object file in an archive is ignored";
break;
default:
break;
}
ReaderContext rctx2 = rctx.next_child();
read_archive_member(ctx, rctx2, child, mf->name);
}
return;
case FileType::TEXT:
@@ -124,8 +143,7 @@ void read_file(Context<E> &ctx, ReaderContext &rctx, MappedFile *mf) {
return;
case FileType::GCC_LTO_OBJ:
case FileType::LLVM_BITCODE:
if (ObjectFile<E> *file = new_lto_obj(ctx, rctx, mf, ""))
ctx.objs.push_back(file);
defer_lto_obj(ctx, rctx, mf, "");
return;
default:
Fatal(ctx) << mf->name << ": unknown file type";
@@ -134,35 +152,23 @@ void read_file(Context<E> &ctx, ReaderContext &rctx, MappedFile *mf) {
template <typename E>
static std::string_view
detect_machine_type(Context<E> &ctx, std::vector<std::string> args) {
for (ReaderContext rctx; const std::string &arg : args) {
if (arg == "--Bstatic") {
rctx.static_ = true;
} else if (arg == "--Bdynamic") {
rctx.static_ = false;
} else if (!arg.starts_with('-')) {
if (MappedFile *mf = open_file(ctx, arg))
detect_machine_type(Context<E> &ctx, std::span<ReaderJob> jobs) {
for (ReaderJob &job : jobs)
if (!job.is_lib)
if (MappedFile *mf = open_file(ctx, job.name))
if (get_file_type(ctx, mf) != FileType::TEXT)
if (std::string_view target = get_machine_type(ctx, rctx, mf);
if (std::string_view target = get_machine_type(ctx, job.rctx, mf);
!target.empty())
return target;
}
}
for (ReaderContext rctx; const std::string &arg : args) {
if (arg == "--Bstatic") {
rctx.static_ = true;
} else if (arg == "--Bdynamic") {
rctx.static_ = false;
} else if (!arg.starts_with('-')) {
if (MappedFile *mf = open_file(ctx, arg))
for (ReaderJob &job : jobs)
if (!job.is_lib)
if (MappedFile *mf = open_file(ctx, job.name))
if (get_file_type(ctx, mf) == FileType::TEXT)
if (std::string_view target =
Script(ctx, rctx, mf).get_script_output_type();
Script(ctx, job.rctx, mf).get_script_output_type();
!target.empty())
return target;
}
}
Fatal(ctx) << "-m option is missing";
}
@@ -204,72 +210,169 @@ MappedFile *find_library(Context<E> &ctx, ReaderContext &rctx, std::string name)
Fatal(ctx) << "library not found: " << name;
}
// Reads all input files.
//
// Reading input files is I/O- and CPU-intensive, and a large program
// can easily consist of tens of thousands of them, so we want to read
// files in parallel. The command line, on the other hand, is
// inherently sequential: options such as --as-needed or
// --whole-archive apply to the files after them, and a file's
// priority for symbol resolution is its position in the command line.
//
// We reconcile the two as follows: the command line parser has
// already recorded the reader state and the position for each file
// argument in its ReaderJob. We open and read files in parallel and
// then sort the files we've found back into the command line order to
// assign priorities.
template <typename E>
static void read_input_files(Context<E> &ctx, std::span<std::string> args) {
static void read_input_files(Context<E> &ctx, std::vector<ReaderJob> &jobs) {
Timer t(ctx, "read_input_files");
ReaderContext rctx;
std::vector<ReaderContext> stack;
std::unordered_set<std::string_view> visited;
// Open and read files in parallel. Archive files are expanded into
// one job per member so that members are read in parallel too.
//
// Linker scripts are one exception to the parallelism: they can
// modify the context, e.g. by defining symbol versions, so we only
// collect them here and parse them after this loop, one at a time
// and in the command line order, to keep their effects
// deterministic. Scripts given as input files are rare and small,
// such as the GROUP file that glibc installs as libc.so, so the
// lost parallelism doesn't matter.
//
// IR files for LTO are the other exception. This loop only records
// them, and we hand them to the LTO plugin after the scripts have
// been parsed, also in the command line order.
//
// Parsing scripts late assumes that no script directive affects how
// command line arguments after the script are read. That holds for
// the directives we currently support: a script can only add input
// files, whose positions order them correctly, and define symbol
// versions or symbols, which are not used until after this
// function. If we add a directive that doesn't satisfy this, such
// as SEARCH_DIR, which affects how -l arguments after it are
// resolved, this scheme needs to be revisited.
tbb::concurrent_vector<ReaderJob> scripts;
tbb::task_group tg;
rctx.tg = &tg;
tbb::parallel_for_each(jobs, [&](ReaderJob &job,
tbb::feeder<ReaderJob> &feeder) {
ReaderContext &rctx = job.rctx;
while (!args.empty()) {
std::string_view arg = args[0];
args = args.subspan(1);
if (arg == "--as-needed") {
rctx.as_needed = true;
} else if (arg == "--no-as-needed") {
rctx.as_needed = false;
} else if (arg == "--whole-archive") {
rctx.whole_archive = true;
} else if (arg == "--no-whole-archive") {
rctx.whole_archive = false;
} else if (arg == "--Bstatic") {
rctx.static_ = true;
} else if (arg == "--Bdynamic") {
rctx.static_ = false;
} else if (arg == "--start-lib") {
rctx.in_lib = true;
} else if (arg == "--end-lib") {
rctx.in_lib = false;
} else if (arg == "--push-state") {
stack.push_back(rctx);
} else if (arg == "--pop-state") {
if (stack.empty())
Fatal(ctx) << "no state pushed before popping";
rctx = stack.back();
stack.pop_back();
} else if (arg.starts_with("-l")) {
arg = arg.substr(2);
if (visited.contains(arg))
continue;
visited.insert(arg);
MappedFile *mf = find_library(ctx, rctx, std::string(arg));
mf->given_fullpath = false;
read_file(ctx, rctx, mf);
} else {
read_file(ctx, rctx, must_open_file(ctx, std::string(arg)));
// An archive member is enqueued by the job that read its archive
// file. A thin archive's members are opened here rather than by
// that job, so that the files of a large archive are opened in
// parallel.
if (!job.archive_name.empty()) {
MappedFile *mf = job.mf;
if (!mf) {
mf = must_open_file(ctx, job.name);
mf->thin_parent = job.thin_parent;
}
read_archive_member(ctx, rctx, mf, job.archive_name);
return;
}
// Everything else is a command line argument that we need to open.
MappedFile *mf;
if (job.is_lib) {
mf = find_library(ctx, rctx, job.name);
mf->given_fullpath = false;
} else {
mf = must_open_file(ctx, job.name);
}
switch (get_file_type(ctx, mf)) {
case FileType::AR:
for (MappedFile *child : read_fat_archive_members(ctx, mf)) {
ReaderJob job2;
job2.rctx = rctx.next_child();
job2.mf = child;
job2.archive_name = mf->name;
feeder.add(std::move(job2));
}
break;
case FileType::THIN_AR:
for (std::string &path : get_thin_archive_member_paths(ctx, mf)) {
ReaderJob job2;
job2.rctx = rctx.next_child();
job2.name = std::move(path);
job2.archive_name = mf->name;
job2.thin_parent = mf;
feeder.add(std::move(job2));
}
break;
case FileType::TEXT:
job.mf = mf;
scripts.push_back(std::move(job));
break;
default:
read_file(ctx, rctx, mf);
}
});
// Parse linker scripts and read the files they name.
ranges::sort(scripts, {}, [](const ReaderJob &job) {
return job.rctx.pos;
});
for (ReaderJob &job : scripts)
Script(ctx, job.rctx, job.mf).parse_linker_script();
// Hand IR files to the LTO plugin, in the command line order. The
// plugin keeps global state, so claims can't run in parallel anyway,
// and LLVM's LTO assumes that the module holding the prevailing copy
// of a COMDAT group arrives before the modules holding the other
// copies, as it does with a serial linker. Otherwise the losing
// copies' internal symbols become undefined references in the LTO
// result.
ranges::sort(ctx.lto_jobs, {}, [](const ReaderJob &job) {
return job.rctx.pos;
});
for (ReaderJob &job : ctx.lto_jobs)
new_lto_obj(ctx, job.rctx, job.mf, job.archive_name);
// Sort the files into the command line order and assign priorities.
tbb::parallel_sort(ctx.unsorted_input_files, [](auto &a, auto &b) {
return a.first < b.first;
});
for (auto &pair : ctx.unsorted_input_files) {
InputFile<E> *file = pair.second;
file->priority = ctx.file_priority++;
if (ctx.arg.trace)
Out(ctx) << "trace: " << *file;
if (file->is_dso)
ctx.dsos.push_back(file->to_dso());
else
ctx.objs.push_back(file->to_obj());
}
if (ctx.objs.empty())
Fatal(ctx) << "no input files";
ctx.unsorted_input_files.clear();
tg.wait();
if (ctx.objs.empty() && ctx.dsos.empty())
Fatal(ctx) << "no input files";
}
template <typename E>
static bool has_lto_obj(Context<E> &ctx) {
for (ObjectFile<E> *file : ctx.objs)
if (file->is_reachable && (file->is_lto_obj || file->is_gcc_offload_obj))
if (file->is_reachable && (file->is_lto_input || file->is_gcc_offload_obj))
return true;
return false;
}
template <typename E>
static i64 get_thread_count(Context<E> &ctx) {
if (ctx.arg.thread_count.has_value())
return *ctx.arg.thread_count;
// mold doesn't scale well with too many threads, so limit it to 32.
int n = tbb::global_control::active_value(
tbb::global_control::max_allowed_parallelism);
return std::min(n, 32);
}
template <typename E>
int mold_main(int argc, char **argv) {
Context<E> ctx;
@@ -278,36 +381,39 @@ int mold_main(int argc, char **argv) {
if (argc >= 2 && (argv[1] == "-run"sv || argv[1] == "--run"sv))
process_run_subcommand(ctx, argc, argv);
// parse_nonpositional_args() may chdir(2) for -C. If we end up
// restarting in redo_main(), we need to re-enter from the original
// directory so relative paths (e.g. response files) still resolve.
std::error_code ec;
std::filesystem::path orig_cwd = std::filesystem::current_path(ec);
// Parse non-positional command line options
ctx.cmdline_args = expand_response_files(ctx, argv);
std::vector<std::string> file_args = parse_nonpositional_args(ctx);
std::vector<ReaderJob> jobs = parse_nonpositional_args(ctx);
// If no -m option is given, deduce it from input files.
if (ctx.arg.emulation.empty())
ctx.arg.emulation = detect_machine_type(ctx, file_args);
ctx.arg.emulation = detect_machine_type(ctx, jobs);
// Redo if -m is not x86-64.
if constexpr (is_x86_64<E>)
if (ctx.arg.emulation != X86_64::name)
return redo_main(ctx, argc, argv);
// Redo if -m does not match with our speculation.
if (ctx.arg.emulation != E::name) {
if (!ec)
std::filesystem::current_path(orig_cwd, ec);
return redo_main<E>(ctx.arg.emulation, argc, argv);
}
Timer t_all(ctx, "all");
install_signal_handler();
if (!ctx.arg.directory.empty())
if (chdir(ctx.arg.directory.c_str()) == -1)
Fatal(ctx) << "chdir failed: " << ctx.arg.directory
<< ": " << errno_string();
// Fork a subprocess unless --no-fork is given.
if (ctx.arg.fork)
fork_child();
fork_child<E>();
acquire_global_lock();
tbb::global_control tbb_cont(tbb::global_control::max_allowed_parallelism,
ctx.arg.thread_count);
ctx.global_limit.emplace(tbb::global_control::max_allowed_parallelism,
get_thread_count(ctx));
// Handle --wrap options if any.
for (std::string_view name : ctx.arg.wrap)
@@ -322,7 +428,7 @@ int mold_main(int argc, char **argv) {
get_symbol(ctx, arg)->is_traced = true;
// Parse input files
read_input_files(ctx, file_args);
read_input_files(ctx, jobs);
// Uniquify shared object files by soname
{
@@ -360,9 +466,25 @@ int mold_main(int argc, char **argv) {
std::erase_if(ctx.objs, [](InputFile<E> *file) { return !file->is_reachable; });
std::erase_if(ctx.dsos, [](InputFile<E> *file) { return !file->is_reachable; });
// Building .gdb_index is split into three stages because the required data
// becomes available at different points in the link. Compilation units and
// public names depend only on input sections, so read them now in a
// low-priority arena while foreground passes continue.
bool create_gdb_index = ctx.arg.gdb_index && !ctx.arg.relocatable;
tbb::task_arena gdb_input_arena(tbb::task_arena::automatic, 1,
tbb::task_arena::priority::low);
tbb::task_group gdb_task;
if (create_gdb_index)
gdb_input_arena.execute([&] {
gdb_task.run([&] { read_gdb_index_inputs(ctx); });
});
// Parse .eh_frame section contents.
parse_eh_frame_sections(ctx);
// Parse .sframe section contents.
parse_sframe_sections(ctx);
// Split mergeable section contents into section pieces.
create_merged_sections(ctx);
@@ -374,6 +496,21 @@ int mold_main(int argc, char **argv) {
return 0;
}
// Non-allocated merged sections are independent of symbol and relocation
// processing. Resolve them while the remaining foreground passes run.
tbb::task_arena merge_arena(tbb::task_arena::automatic, 1,
tbb::task_arena::priority::low);
tbb::task_group merge_task;
merge_arena.execute([&] {
merge_task.run([&] {
tbb::parallel_for_each(ctx.merged_sections,
[&](ArenaObjectPtr<MergedSection<E>> &sec) {
if (!(sec->shdr.sh_flags & SHF_ALLOC))
sec->resolve(ctx);
});
});
});
// Create .bss sections for common symbols.
convert_common_symbols(ctx);
@@ -390,6 +527,11 @@ int mold_main(int argc, char **argv) {
if (!ctx.arg.allow_multiple_definition)
check_duplicate_symbols(ctx);
// Handle --zero-to-bss, which converts data sections containing only
// zeros into BSS.
if (ctx.arg.zero_to_bss)
convert_zero_to_bss(ctx);
// Set "address-taken" bits for input sections.
if (ctx.arg.icf)
compute_address_significance(ctx);
@@ -489,31 +631,55 @@ int mold_main(int argc, char **argv) {
// .got.plt, .dynsym, .dynstr, etc.
scan_relocations(ctx);
// Compute the is_weak bit for each imported symbol.
compute_imported_symbol_weakness(ctx);
// Now that we know all exported symbols, make sure that no versioned
// name is defined twice.
check_symbol_version_conflicts(ctx);
merge_arena.execute([&] { merge_task.wait(); });
// Sort sections by section attributes so that we'll have to
// create as few segments as possible.
sort_output_sections(ctx);
if (!ctx.arg.separate_debug_file.empty())
// Handle --separate-debug-file.
if (ctx.gnu_debuglink)
separate_debug_sections(ctx);
// Compute sizes of output sections while assigning offsets
// within an output section to input sections.
compute_section_sizes(ctx);
// If --packed_dyn_relocs=relr was given, base relocations are stored
// to a .relr.dyn section in a compressed form. Construct a compressed
// relocations now so that we can fix section sizes and file layout.
// RELR is encoded independently for each output chunk using offsets
// relative to that chunk.
if (ctx.arg.pack_dyn_relocs_relr)
construct_relr(ctx);
ctx.reldyn->construct_relr(ctx);
// Reserve a space for dynamic symbol strings in .dynstr and sort
// .dynsym contents if necessary. Beyond this point, no symbol will
// be added to .dynsym.
sort_dynsyms(ctx);
// sort_debug_info_sections may uncompress the same .debug_info sections.
if (create_gdb_index)
gdb_input_arena.execute([&] { gdb_task.wait(); });
// Sort .debug_info contents so that DWARF32 debug info precedes that of
// DWARF64. This is to mitigate the possibility of a relocation overflow.
sort_debug_info_sections(ctx);
// Type vectors identify compilation units by their order in the output
// .debug_info section. That order is now fixed, so build the table while the
// remaining layout passes continue. This stage stops scaling after about 12
// workers, so limit its arena to avoid competing with foreground work.
i64 gdb_table_workers =
std::clamp<i64>(get_thread_count(ctx) * 3 / 8, 1, 12);
tbb::task_arena gdb_table_arena(gdb_table_workers, 1,
tbb::task_arena::priority::low);
if (ctx.gdb_index && !ctx.gnu_debuglink)
gdb_table_arena.execute([&] {
gdb_task.run([&] { build_gdb_index_tables(ctx); });
});
// Print reports about undefined symbols, if needed.
if (ctx.arg.unresolved_symbols == UNRESOLVED_ERROR)
report_undef_errors(ctx);
@@ -531,18 +697,16 @@ int mold_main(int argc, char **argv) {
// Here, we construct output .eh_frame contents.
ctx.eh_frame->construct(ctx);
// .sframe is likewise parsed and reconstructed by the linker. Build
// the merged, PC-sorted output .sframe.
if constexpr (supports_sframe<E>)
ctx.sframe->construct(ctx);
// If --emit-relocs is given, we'll copy relocation sections from input
// files to an output file.
if (ctx.arg.emit_relocs)
create_reloc_sections(ctx);
// We've created range extension thunks with a pessimistive assumption
// that all out-of-section references are out of range. Now that we are
// able to assign addresses to all SHF_ALLOC output sections, we can
// eliminate excessive thunks.
if constexpr (needs_thunk<E>)
remove_redundant_thunks(ctx);
// Compute .symtab and .strtab sizes for each file.
if (!ctx.arg.strip_all)
create_output_symtab(ctx);
@@ -562,6 +726,14 @@ int mold_main(int argc, char **argv) {
filesize = set_osec_offsets(ctx);
}
// We've created range extension thunks with a pessimistive assumption
// that all out-of-section references are out of range. Now that we know
// the addresses of all sections,, we can eliminate excessive thunks.
if constexpr (needs_thunk<E>) {
remove_redundant_thunks(ctx);
filesize = set_osec_offsets(ctx);
}
if constexpr (is_arm32<E>) {
if (ctx.extra.exidx) {
ctx.extra.exidx->remove_duplicate_entries(ctx);
@@ -578,18 +750,29 @@ int mold_main(int argc, char **argv) {
// GOT or PLT addresses have a correct final value.
// If --compress-debug-sections is given, compress .debug_* sections
// using zlib.
if (ctx.arg.compress_debug_sections != COMPRESS_NONE) {
// using zlib or zstd.
if (ctx.arg.compress_debug_sections != ELFCOMPRESS_NONE) {
compress_debug_sections(ctx);
filesize = set_osec_offsets(ctx);
}
// At this point, both memory and file layouts are fixed.
// Gather thunk symbols and attach them to themselves.
if constexpr (needs_thunk<E>)
gather_thunk_addresses(ctx);
// Re-finalize layout. fix_synthetic_symbols above may have changed
// addends for dynamic relocations referencing synthetic symbols, which
// can shift the encoded size of .rela.dyn under --pack-dyn-relocs=android
// because Android's packed format encodes addends in variable-length
// SLEB128. Other modes, including ordinary RELR, encode nothing whose
// size depends on addends, so they do not need this pass.
if (ctx.arg.pack_dyn_relocs_android)
filesize = set_osec_offsets(ctx);
// At this point, both memory and file layouts are fixed.
ctx.reldyn->update_shdr(ctx);
t_before_copy.stop();
// Create an output file
@@ -601,7 +784,10 @@ int mold_main(int argc, char **argv) {
// Copy input sections to the output file and apply relocations.
copy_chunks(ctx);
if constexpr (is_x86_64<E>)
if constexpr (is_arm32be<E>)
arm32be_swap_bytes(ctx);
if constexpr (is_x86_64<E> || is_arm64<E>)
if (ctx.arg.z_rewrite_endbr)
rewrite_endbr(ctx);
@@ -609,11 +795,12 @@ int mold_main(int argc, char **argv) {
// so we sort them.
sort_reldyn(ctx);
// .gdb_index's contents cannot be constructed before applying
// relocations to other debug sections. We have relocated debug
// sections now, so write the .gdb_index section.
if (ctx.gdb_index && ctx.arg.separate_debug_file.empty())
// The final stage reads address ranges, which requires relocated debug
// sections. We have applied the relocations now, so finish the index.
if (ctx.gdb_index && !ctx.gnu_debuglink) {
gdb_table_arena.execute([&] { gdb_task.wait(); });
write_gdb_index(ctx);
}
// .note.gnu.build-id section contains a cryptographic hash of the
// entire output file. Now that we wrote everything except build-id,
@@ -621,7 +808,7 @@ int mold_main(int argc, char **argv) {
if (ctx.buildid)
write_build_id(ctx);
if (!ctx.arg.separate_debug_file.empty())
if (ctx.gnu_debuglink)
write_gnu_debuglink(ctx);
t_copy.stop();
@@ -642,7 +829,7 @@ int mold_main(int argc, char **argv) {
if (ctx.arg.print_map)
print_map(ctx);
if (!ctx.arg.separate_debug_file.empty())
if (ctx.gnu_debuglink)
write_separate_debug_file(ctx);
// Show stats numbers
@@ -655,14 +842,22 @@ int mold_main(int argc, char **argv) {
std::cout << std::flush;
std::cerr << std::flush;
notify_parent();
notify_parent<E>();
release_global_lock();
#if HAVE_MADVISE
// Dropping page table entries here in parallel makes process exit
// faster, as the kernel otherwise reclaims them in a single thread
// on exit. File contents stay in the page cache.
tbb::parallel_for_each(ctx.mf_pool, [](std::unique_ptr<MappedFile> &mf) {
if (mf->is_mmapped && !mf->parent && mf->data && mf->size)
madvise(mf->data, mf->size, MADV_DONTNEED);
});
#endif
if (ctx.arg.quick_exit)
_exit(0);
for (std::function<void()> &fn : ctx.on_exit)
fn();
ctx.checkpoint();
return 0;
}
@@ -670,5 +865,6 @@ int mold_main(int argc, char **argv) {
using E = MOLD_TARGET;
template int mold_main<E>(int, char **);
template void read_file(Context<E> &, ReaderContext &, MappedFile *);
} // namespace mold
+11 -12
View File
@@ -19,14 +19,12 @@ static Map<E> get_map(Context<E> &ctx) {
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
for (Symbol<E> *sym : file->symbols) {
if (sym->file != file || sym->get_type() == STT_SECTION)
continue;
if (InputSection<E> *isec = sym->get_input_section()) {
assert(file == &isec->file);
typename Map<E>::accessor acc;
map.insert(acc, {isec, {}});
acc->second.push_back(sym);
if (sym->file == file && sym->get_type() != STT_SECTION) {
if (InputSection<E> *isec = sym->get_input_section()) {
typename Map<E>::accessor acc;
map.insert(acc, {isec, {}});
acc->second.push_back(sym);
}
}
}
});
@@ -48,10 +46,11 @@ void print_map(Context<E> &ctx) {
std::ostream *out = &std::cout;
std::ofstream file;
if (!ctx.arg.Map.empty()) {
if (!ctx.arg.Map.empty() && ctx.arg.Map != "-") {
file.open(ctx.arg.Map);
if (!file.is_open())
Fatal(ctx) << "cannot open " << ctx.arg.Map << ": " << errno_string();
if (file.fail())
Fatal(ctx) << "--print-map: cannot open " << ctx.arg.Map << ": "
<< errno_string();
out = &file;
}
@@ -72,7 +71,7 @@ void print_map(Context<E> &ctx) {
if (!osec)
continue;
std::span<InputSection<E> *> members = osec->members;
std::span<ArenaPtr<InputSection<E>>> members = osec->members;
std::vector<std::string> bufs(members.size());
tbb::parallel_for((i64)0, (i64)members.size(), [&](i64 i) {
+83
View File
@@ -0,0 +1,83 @@
#include "mold.h"
namespace mold {
// Files up to this size are read into malloc'ed memory rather than
// mmap'ed. mmap(2) takes the process's address space lock, so with tens
// of thousands of input files, the calls serialize at a few microseconds
// each no matter how many threads make them. read(2) takes no such lock,
// but copying costs memory bandwidth in proportion to the file size, so
// large files are still mmap'ed. On a Chromium link, files below this
// threshold are 64% of the inputs by count but 16% by size.
//
// The buffers add up to hundreds of megabytes on such a link, so they
// need to be backed by huge pages; with 4 KiB pages, faulting them in
// costs more than the mmap calls did. mimalloc does that by default.
static constexpr i64 READ_THRESHOLD = 32 * 1024;
MappedFile *open_file_impl(const std::string &path, std::string &error) {
i64 fd = ::open(path.c_str(), O_RDONLY);
if (fd == -1) {
if (errno != ENOENT)
error = "opening " + path + " failed: " + errno_string();
return nullptr;
}
struct stat st;
if (fstat(fd, &st) == -1)
error = path + ": fstat failed: " + errno_string();
MappedFile *mf = new MappedFile;
mf->name = path;
mf->size = st.st_size;
if (st.st_size > 0 && st.st_size <= READ_THRESHOLD && S_ISREG(st.st_mode)) {
u8 *buf = (u8 *)malloc(st.st_size);
for (i64 pos = 0; pos < st.st_size;) {
i64 n = pread(fd, buf + pos, st.st_size - pos, pos);
if (n > 0) {
pos += n;
} else if (n == 0) {
error = path + ": file is shorter than its reported size";
break;
} else if (errno != EINTR) {
error = path + ": read failed: " + errno_string();
break;
}
}
mf->data = buf;
} else if (st.st_size > 0) {
mf->data = (u8 *)mmap(nullptr, st.st_size, PROT_READ | PROT_WRITE,
MAP_PRIVATE, fd, 0);
if (mf->data == MAP_FAILED)
error = path + ": mmap failed: " + errno_string();
mf->is_mmapped = true;
}
close(fd);
return mf;
}
void MappedFile::unmap() {
if (size == 0 || parent || !data)
return;
if (is_mmapped)
munmap(data, size);
else
free(data);
data = nullptr;
}
void MappedFile::close_fd() {
if (fd == -1)
return;
close(fd);
fd = -1;
}
void MappedFile::reopen_fd(const std::string &path) {
if (fd == -1)
fd = open(path.c_str(), O_RDONLY);
}
} // namespace mold
@@ -1,4 +1,4 @@
#include "common.h"
#include "mold.h"
namespace mold {
@@ -48,6 +48,7 @@ MappedFile *open_file_impl(const std::string &path, std::string &error) {
error = path + ": MapViewOfFile failed: " + errno_string();
return nullptr;
}
mf->is_mmapped = true;
}
return mf;
+1412 -424
View File
File diff suppressed because it is too large. Load diff
+961 -350
View File
File diff suppressed because it is too large. Load diff
+117 -36
View File
@@ -1,3 +1,4 @@
#include "config.h"
#include "mold.h"
#include <fcntl.h>
@@ -6,6 +7,11 @@
#include <sys/mman.h>
#include <system_error>
#ifdef __linux__
# include <sys/vfs.h>
# include <linux/magic.h>
#endif
namespace mold {
static u32 get_umask() {
@@ -40,8 +46,7 @@ public:
MemoryMappedOutputFile(Context<E> &ctx, std::string path, i64 filesize, int perm)
: OutputFile<E>(path, filesize, true) {
std::string pid = std::to_string(getpid());
std::string tmpfile =
path_dirname(path) / ("." + path_filename(path) + "." + pid);
std::string tmpfile = path_dirname(path) / ("."s + path_filename(path) + "." + pid);
this->fd = open_or_create_file(ctx, path, tmpfile, perm);
@@ -53,14 +58,34 @@ public:
output_tmpfile = (char *)save_string(ctx, tmpfile).data();
#ifdef __linux__
fallocate(this->fd, 0, 0, filesize);
#if HAVE_FALLOCATE
// Calling fallocate speeds up later linking passes on ext4 by
// taking disk block allocation out of the page-fault handler.
// On tmpfs, it instead makes things much slower: the kernel
// allocates and zeroes every page of the range inside the
// syscall, on one thread, which takes ~1.8 s for a 5 GiB file.
if (struct statfs fs;
fstatfs(this->fd, &fs) || fs.f_type != TMPFS_MAGIC)
fallocate(this->fd, 0, 0, filesize);
#endif
this->buf = (u8 *)mmap(nullptr, filesize, PROT_READ | PROT_WRITE,
// We map the file with twice as much address space as its size, so
// that extend() can grow the file into the mapping in place.
// Touching the mapping beyond the end of the file is not allowed,
// but growing the file with ftruncate makes the tail of the
// mapping accessible without any further mmap call.
vasize = filesize * 2;
this->buf = (u8 *)mmap(nullptr, vasize, PROT_READ | PROT_WRITE,
MAP_SHARED, this->fd, 0);
if (this->buf == MAP_FAILED)
Fatal(ctx) << path << ": mmap failed: " << errno_string();
if (this->buf == MAP_FAILED) {
// If the address space is too tight, map just the file.
vasize = filesize;
this->buf = (u8 *)mmap(nullptr, filesize, PROT_READ | PROT_WRITE,
MAP_SHARED, this->fd, 0);
if (this->buf == MAP_FAILED)
Fatal(ctx) << path << ": mmap failed: " << errno_string();
}
mold::output_buffer_start = this->buf;
mold::output_buffer_end = this->buf + filesize;
@@ -71,20 +96,51 @@ public:
::close(fd2);
}
// Extend the file so that the caller can fill the appended data
// through the tail of the mapping. This is called at most once per
// output file.
u8 *extend(Context<E> &ctx, i64 size) override {
i64 mapsize = this->filesize;
if (ftruncate(this->fd, mapsize + size) == -1)
Fatal(ctx) << "ftruncate failed: " << errno_string();
#if HAVE_FALLOCATE
if (struct statfs fs;
fstatfs(this->fd, &fs) || fs.f_type != TMPFS_MAGIC)
fallocate(this->fd, 0, mapsize, size);
#endif
if (mapsize + size > vasize) {
// The appended data does not fit in the mapping. Map the grown
// file again, moving the buffer.
munmap(this->buf, vasize);
vasize = mapsize + size;
this->buf = (u8 *)mmap(nullptr, vasize, PROT_READ | PROT_WRITE,
MAP_SHARED, this->fd, 0);
if (this->buf == MAP_FAILED)
Fatal(ctx) << this->path << ": mmap failed: " << errno_string();
ctx.buf = this->buf;
mold::output_buffer_start = this->buf;
}
this->filesize += size;
mold::output_buffer_end = this->buf + mapsize + size;
#ifdef MADV_HUGEPAGE
madvise(this->buf, mapsize + size, MADV_HUGEPAGE);
#endif
return this->buf + mapsize;
}
void close(Context<E> &ctx) override {
Timer t(ctx, "close_file");
if (!this->is_unmapped)
munmap(this->buf, this->filesize);
if (this->buf2.empty()) {
::close(this->fd);
} else {
FILE *out = fdopen(this->fd, "w");
fseek(out, 0, SEEK_END);
fwrite(&this->buf2[0], this->buf2.size(), 1, out);
fclose(out);
}
munmap(this->buf, vasize);
::close(this->fd);
// If an output file already exists, open a file and then remove it.
// This is the fastest way to unlink a file, as it does not make the
@@ -100,6 +156,9 @@ public:
private:
int fd2 = -1;
// Size of the file mapping, which may extend past the end of the file.
i64 vasize = 0;
};
template <typename E>
@@ -122,14 +181,7 @@ OutputFile<E>::open(Context<E> &ctx, std::string path, i64 filesize, int perm) {
#ifdef MADV_HUGEPAGE
// Enable transparent huge page for an output memory-mapped file.
// On Linux, it has an effect only on tmpfs mounted with `huge=advise`,
// but it can make the linker ~10% faster. You can try it by creating
// a tmpfs with the following commands
//
// $ mkdir tmp
// $ sudo mount -t tmpfs -o size=2G,huge=advise none tmp
//
// and then specifying a path under the directory as an output file.
// Linking a Chromium debug build is ~20% faster with this madvise call.
madvise(file->buf, filesize, MADV_HUGEPAGE);
#endif
@@ -161,10 +213,20 @@ void LockingOutputFile<E>::resize(Context<E> &ctx, i64 filesize) {
if (ftruncate(this->fd, filesize) == -1)
Fatal(ctx) << "ftruncate failed: " << errno_string();
this->buf = (u8 *)mmap(nullptr, filesize, PROT_READ | PROT_WRITE,
// As in MemoryMappedOutputFile, we map the file with twice as much
// address space as its size so that extend() can grow the file into
// the mapping in place.
vasize = filesize * 2;
this->buf = (u8 *)mmap(nullptr, vasize, PROT_READ | PROT_WRITE,
MAP_SHARED, this->fd, 0);
if (this->buf == MAP_FAILED)
Fatal(ctx) << this->path << ": mmap failed: " << errno_string();
if (this->buf == MAP_FAILED) {
vasize = filesize;
this->buf = (u8 *)mmap(nullptr, filesize, PROT_READ | PROT_WRITE,
MAP_SHARED, this->fd, 0);
if (this->buf == MAP_FAILED)
Fatal(ctx) << this->path << ": mmap failed: " << errno_string();
}
this->filesize = filesize;
mold::output_buffer_start = this->buf;
@@ -172,17 +234,36 @@ void LockingOutputFile<E>::resize(Context<E> &ctx, i64 filesize) {
}
template <typename E>
void LockingOutputFile<E>::close(Context<E> &ctx) {
if (!this->is_unmapped)
munmap(this->buf, this->filesize);
u8 *LockingOutputFile<E>::extend(Context<E> &ctx, i64 size) {
i64 mapsize = this->filesize;
if (!this->buf2.empty()) {
FILE *out = fdopen(this->fd, "w");
fseek(out, 0, SEEK_END);
fwrite(&this->buf2[0], this->buf2.size(), 1, out);
fclose(out);
if (ftruncate(this->fd, mapsize + size) == -1)
Fatal(ctx) << "ftruncate failed: " << errno_string();
if (mapsize + size > vasize) {
// The appended data does not fit in the mapping. Map the grown
// file again, moving the buffer.
munmap(this->buf, vasize);
vasize = mapsize + size;
this->buf = (u8 *)mmap(nullptr, vasize, PROT_READ | PROT_WRITE,
MAP_SHARED, this->fd, 0);
if (this->buf == MAP_FAILED)
Fatal(ctx) << this->path << ": mmap failed: " << errno_string();
ctx.buf = this->buf;
mold::output_buffer_start = this->buf;
}
this->filesize += size;
mold::output_buffer_end = this->buf + mapsize + size;
return this->buf + mapsize;
}
template <typename E>
void LockingOutputFile<E>::close(Context<E> &ctx) {
if (!this->is_unmapped)
munmap(this->buf, vasize);
::close(this->fd);
}
+31 -12
View File
@@ -39,22 +39,36 @@ public:
CloseHandle(handle);
}
u8 *extend(Context<E> &ctx, i64 size) override {
UnmapViewOfFile(this->buf);
HANDLE map = CreateFileMapping(handle, nullptr, PAGE_READWRITE, 0,
this->filesize + size, nullptr);
if (!map)
Fatal(ctx) << this->path << ": CreateFileMapping failed: "
<< GetLastError();
this->buf = (u8 *)MapViewOfFile(map, FILE_MAP_WRITE, 0, 0,
this->filesize + size);
if (!this->buf)
Fatal(ctx) << this->path << ": MapViewOfFile failed: " << GetLastError();
CloseHandle(map);
u8 *space = this->buf + this->filesize;
memset(space, 0, size);
this->filesize += size;
ctx.buf = this->buf;
mold::output_buffer_start = this->buf;
mold::output_buffer_end = this->buf + this->filesize;
return space;
}
void close(Context<E> &ctx) override {
Timer t(ctx, "close_file");
UnmapViewOfFile(this->buf);
if (!this->buf2.empty()) {
if (SetFilePointer(handle, 0, nullptr, FILE_END) == INVALID_SET_FILE_POINTER)
Fatal(ctx) << this->path << ": SetFilePointer failed: "
<< GetLastError();
DWORD written;
if (!WriteFile(handle, this->buf2.data(), this->buf2.size(), &written,
nullptr))
Fatal(ctx) << this->path << ": WriteFile failed: " << GetLastError();
}
CloseHandle(handle);
handle = INVALID_HANDLE_VALUE;
}
@@ -106,6 +120,11 @@ LockingOutputFile<E>::LockingOutputFile(Context<E> &ctx, std::string path,
template <typename E>
void LockingOutputFile<E>::resize(Context<E> &ctx, i64 filesize) {}
template <typename E>
u8 *LockingOutputFile<E>::extend(Context<E> &ctx, i64 size) {
return nullptr;
}
template <typename E>
void LockingOutputFile<E>::close(Context<E> &ctx) {}
+1210 -556
View File
File diff suppressed because it is too large. Load diff
+10 -4
View File
@@ -50,6 +50,8 @@ static void r_create_synthetic_sections(Context<E> &ctx) {
ctx.shdr = push(new OutputShdr<E>);
ctx.eh_frame = push(new EhFrameSection<E>);
ctx.eh_frame_reloc = push(new EhFrameRelocSection<E>);
ctx.sframe = push(new SFrameSection<E>);
ctx.sframe_reloc = push(new SFrameRelocSection<E>);
ctx.strtab = push(new StrtabSection<E>);
ctx.symtab = push(new SymtabSection<E>);
ctx.shstrtab = push(new ShstrtabSection<E>);
@@ -74,16 +76,17 @@ static void create_comdat_group_sections(Context<E> &ctx) {
ObjectFile<E> &file = *ctx.objs[i];
for (ComdatGroupRef<E> &ref : file.comdat_groups) {
if (ref.group->owner != file.priority)
if (!ref.is_owner)
continue;
Symbol<E> *sym = file.symbols[file.elf_sections[ref.sect_idx].sh_info];
assert(sym);
std::vector<Chunk<E> *> members;
for (u32 j : ref.members) {
for (u32 j : ref.members(file)) {
const ElfShdr<E> &shdr = file.elf_sections[j];
if (shdr.sh_type == (E::is_rela ? SHT_RELA : SHT_REL)) {
if (shdr.sh_type == (E::is_rela ? SHT_RELA : SHT_REL) ||
shdr.sh_type == SHT_CREL) {
InputSection<E> &isec = *file.sections[shdr.sh_info];
members.push_back(isec.output_section->reloc_sec.get());
} else {
@@ -125,7 +128,7 @@ static void r_claim_unresolved_symbols(Context<E> &ctx) {
continue;
sym.file = file;
sym.origin = 0;
sym.origin = nullptr;
sym.value = 0;
sym.sym_idx = i;
}
@@ -161,6 +164,9 @@ void combine_objects(Context<E> &ctx) {
ctx.eh_frame->construct(ctx);
if constexpr (supports_sframe<E>)
ctx.sframe->construct(ctx);
create_reloc_sections(ctx);
create_comdat_group_sections(ctx);
+2 -6
View File
@@ -89,8 +89,8 @@ void shrink_sections(Context<E> &ctx) {
// only ~0.04% larger than that of GNU ld), so we don't bother to handle
// them. We scan relocations only once here.
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
for (std::unique_ptr<InputSection<E>> &isec : file->sections)
if (isec && isec->is_alive && (isec->shdr().sh_flags & SHF_EXECINSTR))
for (InputSection<E> *isec : file->sections)
if (isec && isec->is_alive() && (isec->shdr().sh_flags & SHF_EXECINSTR))
shrink_section(ctx, *isec);
});
@@ -122,10 +122,6 @@ i64 compute_distance(Context<E> &ctx, Symbol<E> &sym,
if (sym.is_absolute())
return INT64_MAX;
// Likewise, relocations against weak undefined symbols won't be relaxed.
if (sym.esym().is_undef_weak())
return INT64_MAX;
// Compute a distance between the relocated place and the symbol.
i64 S = sym.get_addr(ctx);
i64 A = rel.r_addend;
+3 -4
View File
@@ -1,4 +1,4 @@
#include "common.h"
#include "mold.h"
#include <csignal>
#include <tbb/version.h>
@@ -28,8 +28,7 @@ void cleanup() {
// for the first time.
//
// If a disk becomes full as a result of a write to an mmap'ed memory
// region, the failure of the write is reported as a SIGBUS or structured
// exeption with code EXCEPTION_IN_PAGE_ERROR on Windows. This
// region, the failure of the write is reported as a SIGBUS. This
// signal handler catches that signal and prints out a user-friendly
// error message. Without this, it is very hard to realize that the
// disk might be full.
@@ -74,7 +73,7 @@ void install_signal_handler() {
sigaction(SIGBUS, &action, NULL);
// OneTBB 2021.9.0 has the interface version 12090.
if (TBB_runtime_interface_version() < 12090) {
if (TBB_runtime_interface_version() <= 12090) {
sigabrt_msg = "mold: aborted\n"
"mold: mold with libtbb version 2021.9.0 or older is known to be unstable "
"under heavy load. Your libtbb version is " +
+1 -1
View File
@@ -1,4 +1,4 @@
#include "common.h"
#include "mold.h"
#include <windows.h>
+6 -3
View File
@@ -1,3 +1,4 @@
#include "config.h"
#include "mold.h"
#include <csignal>
@@ -10,12 +11,12 @@
namespace mold {
#ifdef MOLD_X86_64
static int pipe_write_fd = -1;
// Exiting from a program with large memory usage is slow --
// it may take a few hundred milliseconds. To hide the latency,
// we fork a child and let it do the actual linking work.
template <typename E>
void fork_child() {
int pipefd[2];
if (pipe(pipefd) == -1) {
@@ -52,6 +53,7 @@ void fork_child() {
pipe_write_fd = pipefd[1];
}
template <typename E>
void notify_parent() {
if (pipe_write_fd == -1)
return;
@@ -61,7 +63,6 @@ void notify_parent() {
assert(n == 1);
pipe_write_fd = -1;
}
#endif
template <typename E>
static std::string find_dso(Context<E> &ctx, std::filesystem::path self) {
@@ -104,7 +105,7 @@ void process_run_subcommand(Context<E> &ctx, int argc, char **argv) {
setenv("LD_PRELOAD", dso_path.c_str(), 1);
setenv("MOLD_PATH", self.c_str(), 1);
// If ld, ld.lld or ld.gold is specified, run mold itself
// If ld, ld.lld or ld.gold is specified, run mold instead
if (std::string cmd = path_filename(argv[2]);
cmd == "ld" || cmd == "ld.lld" || cmd == "ld.gold") {
std::vector<char *> args;
@@ -123,6 +124,8 @@ void process_run_subcommand(Context<E> &ctx, int argc, char **argv) {
using E = MOLD_TARGET;
template void fork_child<E>();
template void notify_parent<E>();
template void process_run_subcommand(Context<E> &, int, char **);
} // namespace mold
+5 -2
View File
@@ -2,10 +2,11 @@
namespace mold {
#ifdef MOLD_X86_64
template <typename E>
void fork_child() {}
template <typename E>
void notify_parent() {}
#endif
template <typename E>
[[noreturn]]
@@ -15,6 +16,8 @@ void process_run_subcommand(Context<E> &ctx, int argc, char **argv) {
using E = MOLD_TARGET;
template void fork_child<E>();
template void notify_parent<E>();
template void process_run_subcommand(Context<E> &, int, char **);
} // namespace mold
+164 -106
View File
@@ -11,17 +11,12 @@
//
// The function in this file creates thunks.
//
// Note that although thunks play an important role in an executable, they
// don't take up too much space in it. For example, among the clang-16's
// text segment whose size is ~300 MiB on ARM64, thunks in total occupy
// only ~30 KiB or 0.01%. Of course the number depends on an ISA; we would
// need more thunks on ARM32 whose branch range is shorter than ARM64.
// That said, the total size of thunks still isn't that much. Therefore,
// we don't need to try too hard to reduce thunk size to the absolute
// minimum.
// Thunk size varies widely across programs. In an ARM64 build of Clang 16,
// thunks occupy about 30 KiB (0.01%) of a ~300 MiB text section, compared
// with about 12.5 MiB (2.5%) of a ~500 MiB text section in TensorFlow.
#if MOLD_ARM32 || MOLD_ARM64LE || MOLD_ARM64BE || MOLD_PPC32 || \
MOLD_PPC64V1 || MOLD_PPC64V2
#if MOLD_ARM32LE || MOLD_ARM32BE || MOLD_ARM64LE || MOLD_ARM64BE || \
MOLD_PPC32 || MOLD_PPC64V1 || MOLD_PPC64V2
#include "mold.h"
@@ -32,97 +27,104 @@ namespace mold {
using E = MOLD_TARGET;
// We create thunks for each 25.6/3.2/6.4 MiB code block for
// We create thunks for each 32/8/16 MiB code block for
// ARM64/ARM32/PPC, respectively.
static constexpr i64 batch_size = branch_distance<E> / 5;
static constexpr i64 batch_size =
(is_arm64<E> ? 32 : is_arm32<E> ? 8 : 16) * 1024 * 1024;
// We assume that a single thunk group is smaller than 1 MiB.
static constexpr i64 max_thunk_size = 1024 * 1024;
// We assume that a single thunk group is smaller than 16/4/8 MiB
// for ARM64/ARM32/PPC, respectively.
static constexpr i64 max_thunk_size = batch_size / 2;
// We align thunks to 16 byte boundaries because many processor vendors
// recommend we align branch targets to 16 byte boundaries for performance
// reasons.
static constexpr i64 thunk_align = 16;
// Power10 prefixed instructions must not cross a 64-byte boundary.
// Aligning each thunk group to a 8-byte boundary guarantees that.
static constexpr i64 thunk_align = 8;
template <typename E>
static bool is_reachable(Context<E> &ctx, bool first_pass, InputSection<E> &isec,
Symbol<E> &sym, const ElfRel<E> &rel) {
// If the target section is in the same output section but
// hasn't got any address yet, that's unreacahble.
InputSection<E> *isec2 = sym.get_input_section();
if (isec2 && isec.output_section == isec2->output_section &&
isec2->offset == -1)
static bool
requires_thunk(Context<E> &ctx, InputSection<E> &isec, const ElfRel<E> &rel,
bool first_pass) {
if (!is_func_call_rel(rel))
return false;
Symbol<E> &sym = *isec.file->symbols[rel.r_sym];
// We don't know about the final file layout on the first pass, so
// we assume pessimistically that all out-of-section relocations are
// out-of-range. Excessive thunks will be removed on the second pass.
if (first_pass) {
// On the first pass, we pessimistically assume that all out-of-section
// relocations are out of range.
InputSection<E> *isec2 = sym.get_input_section();
if (!isec2 || isec.output_section != isec2->output_section)
return false;
return true;
// If the target section is in the same output section but
// hasn't got any address yet, that's unreacahble.
if (isec2->offset == -1)
return true;
// Even if the target is the same section, we branch to its PLT
// if it has one. So a symbol with a PLT is also considered an
// out-of-section reference.
if (sym.has_plt(ctx))
return false;
return true;
}
// Thumb and ARM B instructions cannot be converted to BX, so we
// always have to make them jump to a thunk to switch processor mode
// even if their destinations are reachable.
if constexpr (is_arm32<E>)
if ((rel.r_type == R_ARM_JUMP24 && is_thumb_func(ctx, sym)) ||
(rel.r_type == R_ARM_PLT32 && is_thumb_func(ctx, sym)) ||
(rel.r_type == R_ARM_THM_JUMP24 && is_arm_func(ctx, sym)))
return true;
// On PowerPC, all PLT calls go through range extension thunks.
if constexpr (is_ppc<E>)
if (sym.has_plt(ctx))
return true;
// PowerPC before Power9 lacks PC-relative load/store instructions.
// Functions compiled for Power9 or earlier assume that r2 points to
// GOT+0x8000, while those for Power10 uses r2 as a scratch register.
// We need a thunk to recompute r2 for interworking.
if constexpr (is_ppc64v2<E>)
if ((rel.r_type == R_PPC64_REL24 && !sym.esym().ppc64_preserves_r2()) ||
(rel.r_type == R_PPC64_REL24_NOTOC && sym.esym().ppc64_uses_toc()))
return true;
// Compute a distance between the relocated place and the symbol
// and check if they are within reach.
i64 S = sym.get_addr(ctx, NO_OPD);
i64 A = get_addend(isec, rel);
i64 P = isec.get_addr() + rel.r_offset;
i64 val = S + A - P;
return -branch_distance<E> <= val && val < branch_distance<E>;
}
template <typename E>
static bool needs_shim(Context<E> &ctx, Symbol<E> &sym, const ElfRel<E> &rel) {
// Thumb and ARM B instructions cannot be converted to BX, so we
// always have to make them jump to a thunk to switch processor mode
// even if their destinations are within their ranges.
if constexpr (is_arm32<E>) {
bool is_thumb = sym.get_addr(ctx) & 1;
return (rel.r_type == R_ARM_THM_JUMP24 && !is_thumb) ||
(rel.r_type == R_ARM_JUMP24 && is_thumb) ||
(rel.r_type == R_ARM_PLT32 && is_thumb);
}
// On PowerPC, all PLT calls go through range extension thunks.
if constexpr (is_ppc32<E> || is_ppc64v1<E>)
return sym.has_plt(ctx);
// PowerPC before Power9 lacks PC-relative load/store instructions.
// Functions compiled for Power9 or earlier assume that r2 points to
// GOT+0x8000, while those for Power10 uses r2 as a scratch register.
// We need to a thunk to recompute r2 for interworking.
if constexpr (is_ppc64v2<E>)
return sym.has_plt(ctx) ||
(rel.r_type == R_PPC64_REL24 && !sym.esym().ppc64_preserves_r2()) ||
(rel.r_type == R_PPC64_REL24_NOTOC && sym.esym().ppc64_uses_toc());
return false;
return val < -branch_distance<E> || branch_distance<E> <= val;
}
template <>
void OutputSection<E>::create_range_extension_thunks(Context<E> &ctx,
bool first_pass) {
std::span<InputSection<E> *> m = members;
void Thunk<E>::compute_size() {
offsets.clear();
for (i64 i = 0; i <= symbols.size(); i++)
offsets.push_back(E::thunk_hdr_size + i * E::thunk_size);
}
template <>
void OutputSection<E>::create_range_extension_thunks(Context<E> &ctx) {
std::span<ArenaPtr<InputSection<E>>> m = members;
if (m.empty())
return;
// Initialize input sections with a dummy offset so that we can
// distinguish sections that have got an address with the one who
// haven't.
for (InputSection<E> *isec : m)
// distinguish sections whose addresses have been assigned from those
// whose addresses have not.
tbb::parallel_for_each(m, [](InputSection<E> *isec) {
isec->offset = -1;
});
thunks.clear();
// We create thunks from the beginning of the section to the end.
// We manage progress using four offsets which increase monotonically.
// The locations they point to are always A <= B <= C <= D.
//
// Input sections between B and C are in the current batch.
// Input sections between B and C are the current batch.
//
// A is the input section with the smallest address than can reach
// from the current batch.
@@ -161,17 +163,21 @@ void OutputSection<E>::create_range_extension_thunks(Context<E> &ctx,
d++;
}
// Move C forward so that C is apart from B by BATCH_SIZE. We want
// to make sure that there's at least one section between B and C
// to ensure progress.
c = b + 1;
while (c < d && m[c]->offset + m[c]->sh_size < m[b]->offset + batch_size)
c++;
// Find the end of the current batch. Section end addresses are sorted,
// so use binary search. Starting from B + 1 guarantees progress.
std::span<ArenaPtr<InputSection<E>>> range = m.subspan(b + 1, d - b - 1);
c = ranges::lower_bound(range, m[b]->offset + batch_size, {},
[](InputSection<E> *isec) {
return isec->offset + isec->sh_size;
}) - m.begin();
// Move A forward so that A is reachable from C.
// Find the first section that is within branch range of C.
i64 c_offset = (c == d) ? offset : m[c]->offset;
while (a < b && m[a]->offset + branch_distance<E> < c_offset)
a++;
range = m.subspan(a, b - a);
a = ranges::lower_bound(range, c_offset - branch_distance<E>, {},
[](InputSection<E> *isec) {
return isec->offset;
}) - m.begin();
// Erase references to out-of-range thunks.
for (; t < thunks.size() && thunks[t]->offset < m[a]->offset; t++)
@@ -181,44 +187,28 @@ void OutputSection<E>::create_range_extension_thunks(Context<E> &ctx,
// Create a new thunk and place it at D.
offset = align_to(offset, thunk_align);
thunks.emplace_back(std::make_unique<Thunk<E>>(*this, offset));
Thunk<E> &thunk = *thunks.back();
tbb::enumerable_thread_specific<std::vector<Symbol<E> *>> symbols;
// Scan relocations between B and C to collect symbols that need
// entries in the new thunk.
std::mutex mu;
tbb::parallel_for(b, c, [&](i64 i) {
InputSection<E> &isec = *m[i];
for (const ElfRel<E> &rel : isec.get_rels(ctx)) {
if (!is_func_call_rel(rel))
continue;
// Skip if the symbol is undefined. apply_reloc() will report an error.
Symbol<E> &sym = *isec.file.symbols[rel.r_sym];
if (!sym.file)
continue;
// Skip if we can directly branch to the destination.
if (is_reachable(ctx, first_pass, isec, sym, rel) &&
!needs_shim(ctx, sym, rel))
continue;
// Add the symbol to the current thunk if it's not added already.
if (!sym.flags.test_and_set()) {
std::scoped_lock lock(mu);
thunk.symbols.push_back(&sym);
}
}
for (const ElfRel<E> &rel : isec.get_rels(ctx))
if (requires_thunk(ctx, isec, rel, true))
if (Symbol<E> &sym = *isec.file->symbols[rel.r_sym];
!sym.flags.test_and_set())
symbols.local().push_back(&sym);
});
// Sort symbols added to the thunk to make the output deterministic.
ranges::sort(thunk.symbols, {}, [](Symbol<E> *x) {
return std::tuple{x->file->priority, x->sym_idx};
});
// Add symbols to the thunk
for (std::vector<Symbol<E> *> &vec : symbols)
append(thunk.symbols, vec);
// Now that we know the number of symbols in the thunk, we can compute
// the thunk's size.
thunk.compute_size();
assert(thunk.size() < max_thunk_size);
offset += thunk.size();
@@ -226,22 +216,89 @@ void OutputSection<E>::create_range_extension_thunks(Context<E> &ctx,
b = c;
}
// Clear marks for thunks that are still reachable from the last batch.
for (; t < thunks.size(); t++)
for (Symbol<E> *sym : thunks[t]->symbols)
sym->flags = 0;
// Sort symbols for deterministic output.
tbb::parallel_for_each(thunks, [](std::unique_ptr<Thunk<E>> &thunk) {
ranges::sort(thunk->symbols, {}, [](Symbol<E> *sym) {
return std::tuple{sym->file->priority, sym->sym_idx};
});
});
this->shdr.sh_size = offset;
}
// create_range_extension_thunks() creates thunks with a pessimistic
// assumption that all out-of-section references are out of range.
// After computing output section addresses, we revisit all thunks to
// remove unneeded entries from them.
//
// We create more thunks than necessary and then eliminate some of
// them later, instead of just creating thunks at this stage. This is
// because we can safely shrink sections after assigning addresses to
// them without worrying about making existing references to thunks go
// out of range. On the other hand, if we insert thunks after
// assigning addresses to sections, references to thunks could become
// out of range due to the new extra gaps for thunks. Thus, the
// creation of thunks is a two-pass process.
template <>
void remove_redundant_thunks(Context<E> &ctx) {
Timer t(ctx, "remove_redundant_thunks");
set_osec_offsets(ctx);
// Gather output executable sections
std::vector<OutputSection<E> *> sections;
for (Chunk<E> *chunk : ctx.chunks)
if (OutputSection<E> *osec = chunk->to_osec())
if (osec->shdr.sh_flags & SHF_EXECINSTR)
osec->create_range_extension_thunks(ctx, false);
sections.push_back(osec);
// Mark all symbols that actually need range extension thunks
for (OutputSection<E> *osec : sections) {
tbb::parallel_for_each(osec->members, [&](InputSection<E> *isec) {
for (const ElfRel<E> &rel : isec->get_rels(ctx))
if (is_func_call_rel(rel))
if (Symbol<E> *sym = isec->file->symbols[rel.r_sym];
!sym->flags &&
requires_thunk(ctx, *isec, rel, false))
sym->flags.test_and_set();
});
}
// Remove symbols from thunks if they don't actually need range
// extension thunks
for (OutputSection<E> *osec : sections) {
tbb::parallel_for_each(osec->thunks,
[&](std::unique_ptr<Thunk<E>> &thunk) {
std::erase_if(thunk->symbols,
[&](Symbol<E> *sym) { return !sym->flags; });
thunk->shrink_size(ctx);
});
}
// Recompute section sizes
tbb::parallel_for_each(sections, [&](OutputSection<E> *osec) {
std::span<ArenaPtr<InputSection<E>>> m = osec->members;
std::span<std::unique_ptr<Thunk<E>>> t = osec->thunks;
i64 offset = 0;
while (!m.empty() || !t.empty()) {
if (!m.empty() && (t.empty() || m[0]->offset < t[0]->offset)) {
offset = align_to(offset, 1 << m[0]->p2align);
m[0]->offset = offset;
offset += m[0]->sh_size;
m = m.subspan(1);
} else {
offset = align_to(offset, thunk_align);
t[0]->offset = offset;
offset += t[0]->size();
t = t.subspan(1);
}
}
osec->shdr.sh_size = offset;
});
}
// When applying relocations, we want to know the address in a reachable
@@ -259,7 +316,8 @@ void gather_thunk_addresses(Context<E> &ctx) {
std::vector<OutputSection<E> *> sections;
for (Chunk<E> *chunk : ctx.chunks)
if (OutputSection<E> *osec = chunk->to_osec())
sections.push_back(osec);
if (osec->shdr.sh_flags & SHF_EXECINSTR)
sections.push_back(osec);
ranges::stable_sort(sections, {}, [](OutputSection<E> *x) {
return x->shdr.sh_addr;
@@ -269,8 +327,8 @@ void gather_thunk_addresses(Context<E> &ctx) {
for (std::unique_ptr<Thunk<E>> &thunk : osec->thunks) {
for (i64 i = 0; i < thunk->symbols.size(); i++) {
Symbol<E> &sym = *thunk->symbols[i];
sym.add_aux(ctx);
ctx.symbol_aux[sym.aux_idx].thunk_addrs.push_back(thunk->get_addr(i));
u64 addr = thunk->get_addr() + thunk->offsets[i];
sym.add_thunk_addr(ctx, addr);
}
}
}
+8 -3
View File
@@ -30,8 +30,8 @@ endif()
if(MOLD_ENABLE_QEMU_TESTS)
list(APPEND QEMU_ARCHS
x86_64 i386 arm aarch64 aarch64_be ppc ppc64 ppc64le sparc64 sh4 sh4eb
s390x riscv64 riscv32 m68k loongarch64)
x86_64 i386 arm armeb aarch64 aarch64_be ppc ppc64 ppc64le sparc64
sh4 sh4eb s390x riscv64 riscv32 m68k loongarch64)
LIST(APPEND TRIPLES
x86_64-linux-gnu
@@ -39,6 +39,7 @@ if(MOLD_ENABLE_QEMU_TESTS)
aarch64-linux-gnu
aarch64_be-linux-gnu
arm-linux-gnueabihf
armeb-linux-gnueabihf
riscv64-linux-gnu
powerpc-linux-gnu
powerpc64-linux-gnu
@@ -85,7 +86,7 @@ function(add_target ARCH TRIPLE)
endif()
add_test(NAME ${TESTNAME}
COMMAND bash -x ${CMAKE_CURRENT_LIST_DIR}/${TEST}
COMMAND ${CMAKE_CURRENT_LIST_DIR}/${TEST}
WORKING_DIRECTORY ${mold_BINARY_DIR})
set_property(TEST ${TESTNAME} APPEND PROPERTY ENVIRONMENT
@@ -121,6 +122,10 @@ if(${MACHINE} STREQUAL "arm" OR (HAS_qemu-arm AND HAS_arm-linux-gnueabihf-gcc))
add_target(arm arm-linux-gnueabihf)
endif()
if(${MACHINE} STREQUAL "armeb" OR (HAS_qemu-armeb AND HAS_armeb-linux-gnueabihf-gcc))
add_target(armeb armeb-linux-gnueabihf)
endif()
if(${MACHINE} STREQUAL "riscv64" OR (HAS_qemu-riscv64 AND HAS_riscv64-linux-gnu-gcc))
add_target(riscv64 riscv64-linux-gnu)
endif()
+1 -1
View File
@@ -1,4 +1,4 @@
#!/bin/bash
#!/usr/bin/env bash
. $(dirname $0)/common.inc
[ $MACHINE = aarch64 ] && skip
+1 -1
View File
@@ -1,4 +1,4 @@
#!/bin/bash
#!/usr/bin/env bash
. $(dirname $0)/common.inc
# This test crashes only on qemu-sparc64 running on GitHub Actions,
+1 -1
View File
@@ -1,4 +1,4 @@
#!/bin/bash
#!/usr/bin/env bash
. $(dirname $0)/common.inc
echo 'int main() { return 0; }' | $CC -c -o $t/a.o -xc -
+1 -1
View File
@@ -1,4 +1,4 @@
#!/bin/bash
#!/usr/bin/env bash
. $(dirname $0)/common.inc
cat <<EOF | $CC -o $t/a.o -c -xc -
+74
View File
@@ -0,0 +1,74 @@
#!/usr/bin/env bash
. $(dirname $0)/common.inc
# When mold relaxes an instruction sequence, the relocations attached to the
# original instructions no longer match the relaxed code. With --emit-relocs,
# mold must rewrite the emitted relocations so that their types describe the
# relaxed instructions.
cat <<'EOF' | $CC -o $t/a.o -c -xassembler -
.text
.globl _start
_start:
# GOT-load relaxation: ADRP+LDR => ADRP+ADD
# R_AARCH64_ADR_GOT_PAGE => R_AARCH64_ADR_PREL_PG_HI21
# R_AARCH64_LD64_GOT_LO12_NC => R_AARCH64_ADD_ABS_LO12_NC
adrp x0, :got:gvar
ldr x0, [x0, #:got_lo12:gvar]
# ADRP+ADD => NOP+ADR (gvar is within +-1 MiB)
# R_AARCH64_ADR_PREL_PG_HI21 => R_AARCH64_NONE
# R_AARCH64_ADD_ABS_LO12_NC => R_AARCH64_ADR_PREL_LO21
adrp x1, gvar
add x1, x1, #:lo12:gvar
# Both sequences must materialize the same address.
cmp x0, x1
bne 1f
# ... and it must point at gvar, whose value is 42.
ldr w2, [x0]
cmp w2, #42
bne 1f
mov x0, #0
b 2f
1:
mov x0, #1
2:
mov x8, #93 // __NR_exit
svc #0
.data
.hidden gvar
gvar:
.word 42
EOF
$CC -B. -o $t/exe $t/a.o --static -nostdlib -Wl,-e,_start,--emit-relocs,--relax
$QEMU $t/exe
$OBJDUMP -dr $t/exe > $t/exe.objdump
# Relaxation took place.
grep -Fw nop $t/exe.objdump
grep -Fw adr $t/exe.objdump
grep -Fw add $t/exe.objdump
# The relocations were rewritten to match the relaxed instructions.
grep -Fw R_AARCH64_ADR_PREL_PG_HI21 $t/exe.objdump
grep -Fw R_AARCH64_ADD_ABS_LO12_NC $t/exe.objdump
grep -Fw R_AARCH64_ADR_PREL_LO21 $t/exe.objdump
# The stale pre-relaxation relocations are gone.
not grep -Fw R_AARCH64_ADR_GOT_PAGE $t/exe.objdump
not grep -Fw R_AARCH64_LD64_GOT_LO12_NC $t/exe.objdump
# Each rewritten relocation must sit on the relaxed instruction it describes.
awk '
$3 == "adrp" || $3 == "add" || $3 == "adr" || $3 == "nop" { insn[$1] = $3 }
$2 == "R_AARCH64_ADR_PREL_PG_HI21" { if (insn[$1] != "adrp") { bad=1; print "bad offset: " $0 } }
$2 == "R_AARCH64_ADD_ABS_LO12_NC" { if (insn[$1] != "add") { bad=1; print "bad offset: " $0 } }
$2 == "R_AARCH64_ADR_PREL_LO21" { if (insn[$1] != "adr") { bad=1; print "bad offset: " $0 } }
END { exit bad+0 }
' $t/exe.objdump
+90
View File
@@ -0,0 +1,90 @@
#!/usr/bin/env bash
. $(dirname $0)/common.inc
# Depending on the symbol, mold relaxes a TLSDESC sequence into a local-exec
# (LE) or initial-exec (IE) sequence. The four-instruction sequence is rewritten
# in place: the ADRP and LDR become NOPs, and the ADD and BLR become the two
# instructions that materialize the address. With --emit-relocs, the emitted
# relocations must be rewritten to match whichever relaxation was applied.
cat <<'EOF' | $CC -o $t/a.o -c -xc - -fPIC
_Thread_local char foo[4] = "foo";
_Thread_local char bar[4] = "bar";
EOF
cat <<'EOF' | $CC -o $t/b.o -c -xc - -fPIC -mtls-dialect=desc -O2
extern _Thread_local char foo[4];
extern _Thread_local char bar[4];
char *get_foo() { return foo; }
char *get_bar() { return bar; }
EOF
cat <<EOF | $CC -o $t/c.o -c -xc - -mtls-dialect=desc
#include <stdio.h>
char *get_foo();
char *get_bar();
int main() { printf("%s %s\n", get_foo(), get_bar()); }
EOF
#
# Local TLS in an executable: TLSDESC => LE
#
$CC -B. -o $t/exe1 $t/a.o $t/b.o $t/c.o -Wl,--emit-relocs,--relax
$QEMU $t/exe1 | grep -F 'foo bar'
$OBJDUMP -dr $t/exe1 > $t/exe1.objdump
# The ADD/BLR became MOVZ/MOVK that build the TP-relative offset.
grep -Fw R_AARCH64_TLSLE_MOVW_TPREL_G1 $t/exe1.objdump
grep -Fw R_AARCH64_TLSLE_MOVW_TPREL_G0_NC $t/exe1.objdump
# No TLSDESC relocation survives.
not grep -Fw R_AARCH64_TLSDESC_ADR_PAGE21 $t/exe1.objdump
not grep -Fw R_AARCH64_TLSDESC_LD64_LO12 $t/exe1.objdump
not grep -Fw R_AARCH64_TLSDESC_ADD_LO12 $t/exe1.objdump
not grep -Fw R_AARCH64_TLSDESC_CALL $t/exe1.objdump
# The G1 relocation sits on a movz and the G0_NC on a movk, not on the leading
# adrp/ldr (which are now nops).
awk '
$3 == "movz" || $3 == "movk" || $3 == "nop" { insn[$1] = $3 }
$2 == "R_AARCH64_TLSLE_MOVW_TPREL_G1" { if (insn[$1] != "movz") { bad=1; print "bad offset: " $0 } }
$2 == "R_AARCH64_TLSLE_MOVW_TPREL_G0_NC" { if (insn[$1] != "movk") { bad=1; print "bad offset: " $0 } }
END { exit bad+0 }
' $t/exe1.objdump
#
# TLS defined in a shared library: TLSDESC => IE
#
$CC -B. -shared -o $t/d.so $t/a.o -Wl,--relax
$CC -B. -o $t/exe2 $t/b.o $t/c.o $t/d.so -Wl,--emit-relocs,--relax -Wl,-rpath=$t
$QEMU $t/exe2 | grep -F 'foo bar'
$OBJDUMP -dr $t/exe2 > $t/exe2.objdump
# The ADD/BLR became ADRP/LDR that load the address from the GOT.
grep -Fw R_AARCH64_TLSIE_ADR_GOTTPREL_PAGE21 $t/exe2.objdump
grep -Fw R_AARCH64_TLSIE_LD64_GOTTPREL_LO12_NC $t/exe2.objdump
not grep -Fw R_AARCH64_TLSDESC_ADD_LO12 $t/exe2.objdump
not grep -Fw R_AARCH64_TLSDESC_CALL $t/exe2.objdump
awk '
$3 == "adrp" || $3 == "ldr" || $3 == "nop" { insn[$1] = $3 }
$2 == "R_AARCH64_TLSIE_ADR_GOTTPREL_PAGE21" { if (insn[$1] != "adrp") { bad=1; print "bad offset: " $0 } }
$2 == "R_AARCH64_TLSIE_LD64_GOTTPREL_LO12_NC" { if (insn[$1] != "ldr") { bad=1; print "bad offset: " $0 } }
END { exit bad+0 }
' $t/exe2.objdump
#
# Relocatable output: no relaxation happens and TLSDESC is not downgraded, so
# every relocation must be passed through unchanged rather than rewritten as if
# it had been relaxed to LE/IE.
#
$CC -B. -o $t/reloc.o $t/b.o -r
$OBJDUMP -r $t/reloc.o > $t/reloc.objdump
grep -Fw R_AARCH64_TLSDESC_ADR_PAGE21 $t/reloc.objdump
grep -Fw R_AARCH64_TLSDESC_LD64_LO12 $t/reloc.objdump
grep -Fw R_AARCH64_TLSDESC_ADD_LO12 $t/reloc.objdump
grep -Fw R_AARCH64_TLSDESC_CALL $t/reloc.objdump
not grep -Fw R_AARCH64_TLSLE_MOVW_TPREL_G1 $t/reloc.objdump
not grep -Fw R_AARCH64_TLSIE_ADR_GOTTPREL_PAGE21 $t/reloc.objdump
+58
View File
@@ -0,0 +1,58 @@
#!/usr/bin/env bash
. $(dirname $0)/common.inc
cat <<EOF > $t/a.c
#include <stdio.h>
void fn3();
void fn4();
__attribute__((section(SEC1))) void fn1() { printf(" fn1"); fn3(); }
__attribute__((section(SEC1))) void fn2() { printf(" fn2"); fn4(); }
int main() {
printf(" main");
fn1();
printf("\n");
}
EOF
cat <<EOF > $t/b.c
#include <stdio.h>
void fn1();
void fn2();
__attribute__((section(SEC2))) void fn3() { printf(" fn3"); fn2(); }
__attribute__((section(SEC2))) void fn4() { printf(" fn4"); }
EOF
# .high is placed billions of bytes away from the rest of the program.
# Functions there cannot have unwind tables because an FDE refers to its
# function with a 32-bit PC-relative relocation. We put fn1 and fn2 in
# either section to test calls in both directions.
flags="-fno-asynchronous-unwind-tables -fno-unwind-tables"
starts="-Wl,--section-start=.low=0x10000000,--section-start=.high=0x100000000"
$CC -c -o $t/c.o $t/a.c $flags -DSEC1='".low"' -DSEC2='".high"'
$CC -c -o $t/d.o $t/b.c $flags -DSEC1='".low"' -DSEC2='".high"'
$CC -B. -o $t/exe1 $t/c.o $t/d.o $starts
$QEMU $t/exe1 | grep 'main fn1 fn3 fn2 fn4'
$CC -c -o $t/e.o $t/a.c $flags -DSEC1='".high"' -DSEC2='".low"'
$CC -c -o $t/f.o $t/b.c $flags -DSEC1='".high"' -DSEC2='".low"'
$CC -B. -o $t/exe2 $t/e.o $t/f.o $starts
$QEMU $t/exe2 | grep 'main fn1 fn3 fn2 fn4'
flags="$flags -fno-PIC -mcmodel=large"
starts="-Wl,--section-start=.low=0x10000000,--section-start=.high=0x400000000"
$CC -c -o $t/g.o $t/a.c $flags -DSEC1='".low"' -DSEC2='".high"'
$CC -c -o $t/h.o $t/b.c $flags -DSEC1='".low"' -DSEC2='".high"'
$CC -B. -o $t/exe3 $t/g.o $t/h.o -pie $starts
$QEMU $t/exe3 | grep 'main fn1 fn3 fn2 fn4'
$CC -c -o $t/i.o $t/a.c $flags -DSEC1='".high"' -DSEC2='".low"'
$CC -c -o $t/j.o $t/b.c $flags -DSEC1='".high"' -DSEC2='".low"'
$CC -B. -o $t/exe4 $t/i.o $t/j.o -pie $starts
$QEMU $t/exe4 | grep 'main fn1 fn3 fn2 fn4'
+36
View File
@@ -0,0 +1,36 @@
#!/usr/bin/env bash
. $(dirname $0)/common.inc
[ $MACHINE = aarch64 ] || skip
command -v timeout >/dev/null || skip
cat <<EOF | $CC -c -o $t/a.o -fPIC -xassembler - 2> /dev/null || skip
.section .custom_rodata_a, "a", %progbits
.hidden A
.global A
A:
.space 8
.section .custom_rodata_b_huge, "a", %progbits
.balign 16384
.hidden B
.global B
B:
.space 8
.section .data, "aw", %progbits
.rept 7439
.quad A
.endr
.global P_1
P_1:
.quad A
.global P_2
P_2:
.quad B
EOF
timeout 5s ./mold -m aarch64elf -shared -o $t/b.so $t/a.o \
--pack-dyn-relocs=android -z max-page-size=16384
@@ -1,4 +1,4 @@
#!/bin/bash
#!/usr/bin/env bash
. $(dirname $0)/common.inc
cat <<EOF | $CC -c -o $t/a.o -fPIC -xc -
+1 -1
View File
@@ -1,4 +1,4 @@
#!/bin/bash
#!/usr/bin/env bash
. $(dirname $0)/common.inc
cat <<EOF | $CC -c -o $t/a.o -fPIC -xassembler - 2> /dev/null || skip
+1 -1
View File
@@ -1,4 +1,4 @@
#!/bin/bash
#!/usr/bin/env bash
. $(dirname $0)/common.inc
cat <<EOF | $CC -fPIC -c -o $t/a.o -xassembler - -mthumb
+1 -1
View File
@@ -1,4 +1,4 @@
#!/bin/bash
#!/usr/bin/env bash
. $(dirname $0)/common.inc
cat <<EOF | $CXX -o $t/a.o -c -xc++ - -fno-PIC
@@ -1,4 +1,4 @@
#!/bin/bash
#!/usr/bin/env bash
. $(dirname $0)/common.inc
cat <<EOF | $CC -c -o $t/a.o -fPIC -xc -
+1 -1
View File
@@ -1,4 +1,4 @@
#!/bin/bash
#!/usr/bin/env bash
. $(dirname $0)/common.inc
test_cflags -mthumb || skip
Loaded 100 of 1947 files, more files were not shown because too many files have changed in this diff. Show more