Compare commits

..
19 Commits
Author SHA1 Message Date
Rui UeyamaandClaude Opus 4.8 5e95e7a88a Remove orphaned comments and tidy ScriptExpr/ScriptCmd enums
Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-13 14:40:42 +09:00
Rui UeyamaandClaude Fable 5 7e68606f78 Make the reader state optional for the Script class
Script needs the command line reader's state only for commands that
read other input files, such as GROUP or INPUT. Version scripts and
dynamic lists share the class just for the lexer, and their callers
had to fabricate a meaningless ReaderContext to satisfy the
constructor. The state is now passed as a nullable pointer and
fetched through an accessor that fails with a clear message if a
script uses a file-reading command where none is available.

Also assert that the task group is present where parse tasks are
spawned; a fabricated reader state reaching that path used to be a
null pointer dereference.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-06-12 12:49:56 +09:00
Rui UeyamaandClaude Fable 5 ffc170652f Update stale comments
The script engine has long outgrown "evaluates only a subset".

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-06-12 12:21:30 +09:00
Rui UeyamaandClaude Fable 5 8366016955 Merge mergeable sections under linker scripts
String merging used to be disabled when a linker script described the
layout, trading away deduplication. Now mergeable sections are merged
exactly as usual and the script lays out the result.

Input sections are matched against the script's patterns before they
are split into section pieces, since the patterns refer to the
original per-file sections. Sections matched by different output
section descriptions merge separately, which also gives each output
section its own deduplication domain, as in GNU ld and LLD. A merged
section that belongs to a script-described output section is then
laid out and written as a part of it — inside its boundary symbols
and segment assignments — rather than as a chunk of its own, and
symbols in it get the containing section's index.

Moving the matching earlier also means /DISCARD/ now applies to
mergeable sections, which used to be skipped because they were
already split by the time discards ran.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-06-11 14:03:04 +09:00
Rui UeyamaandClaude Fable 5 ebd2865fea Remove --section-order and --physical-image-base
These options were mold-specific substitutes for linker scripts,
added back when supporting the real language was off the table. Now
that SECTIONS, PHDRS and MEMORY are supported, a script expresses
everything they could and more, so they can go, along with the
third layout mode and the __phys_start_/__phys_stop_ symbols that
existed only for them.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-06-11 13:28:28 +09:00
Rui UeyamaandClaude Fable 5 cce86d402a Remove ctx.arg.entry_given
The entry symbol now starts out null and is filled in by -e, by the
first ENTRY command in a linker script, or by the _start default once
all input files have been read. This makes the precedence fall out of
the order in which the sources are consulted instead of a side flag,
and the gc-root and undefined-list registration that was duplicated
between the option and the script now happens in one place.

As a side effect, the first ENTRY command now wins over later ones,
as in LLD; it used to be the last.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-06-11 12:13:07 +09:00
Rui UeyamaandClaude Fable 5 187583f489 Support MEMORY regions
A MEMORY command defines named memory regions, the address spaces of
a microcontroller's flash and RAM. Each region has a single cursor:
a section placed with >region is allocated at it, and a load image
directed with AT>region consumes it too, which is how a RAM section's
initialization image is stacked after the code in flash. ORIGIN and
LENGTH are usable in expressions, REGION_ALIAS gives a region another
name, and a section that names no region and no address — including
an orphan — goes to the first region whose attributes accept it. A
section that doesn't fit its region is an error, reported only if it
persists once the layout has converged.

Since a script can give sections addresses in any order, the layout
no longer assumes monotonically increasing addresses: the ELF and
program headers are placed in front of the lowest-addressed section
rather than the first-laid one, and after convergence the engine
verifies that no two sections overlap, in virtual or in load address
space. (File offsets and program headers already handled arbitrary
address order.) The overlap check immediately caught orphans
colliding with region-placed sections, hence the orphan rule above.

With this, the stock STM32 vendor script links: vectors and code in
flash, data in RAM with its load address in flash, and the classic
_estack/_sidata symbols all come out as the startup code expects.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-06-11 11:32:19 +09:00
Rui UeyamaandClaude Fable 5 9ffcd8453f Remove --dump-script
It was a debug aid for developing the linker script parser and has
outlived its purpose. It was also the source of a flaky test failure:
the flag called exit(0) while input files were still being parsed by
other threads, which occasionally crashed in static destructors and
produced an empty dump.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-06-11 11:32:19 +09:00
Rui UeyamaandClaude Fable 5 ba77281c46 Support SORT_NONE
SORT_NONE(pattern) simply keeps the input order. GNU ld's own default
script uses it for .init and .fini, so any script derived from
`ld --verbose` output contains it.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-06-11 11:32:19 +09:00
Rui UeyamaandClaude Fable 5 53cbf48020 Make script-laid userland executables actually run, with tests
A new test that links an ordinary C program with a linker script and
executes it exposed a series of gaps that only freestanding programs
like the Linux kernel could tolerate:

- The ELF and program headers were never mapped, so AT_PHDR pointed
  at unmapped memory and glibc crashed in _dl_aux_init. They are now
  mapped if they fit in front of the first section, as in LLD; with
  an explicit PHDRS command, FILEHDR/PHDRS keywords decide instead.

- Orphan sections were anchored by an ad-hoc attribute-similarity
  score, which tore .tdata and .tbss apart (producing two PT_TLS
  segments) and placed .plt.got between data sections (creating an
  executable island in a page shared with data). An orphan is now
  inserted where the default section order would put it: after the
  last script section that the default order ranks no higher, with a
  preference for one of exactly the same class. Orphans sharing an
  insertion point are ordered among themselves by the default order.

- Laying out an orphan now first executes the script commands up to
  its insertion point, so orphans respect preceding location counter
  assignments.

- A TLS NOBITS section consumes no address space in script layout,
  matching the regular layout.

- A fill pattern now starts anew at each gap it fills, as in LLD,
  consistently between member gaps and data command gaps.

New tests: a vDSO-shaped shared library whose script places the
synthesized dynamic sections with FILEHDR/PHDRS segments; SIZEOF/ADDR
of declared-but-empty sections (the kernel's GOT assertions); a C
program laid out by a script, executed under qemu, both static and
dynamic; and fill patterns in location counter gaps.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-06-11 11:32:19 +09:00
Rui UeyamaandClaude Fable 5 5f87604373 Evaluate symbol assignments during the address walk
The script engine used to have three separate evaluation timelines:
parse-time --defsym conversion, layout-time location counter bindings
recorded in three side fields of ScriptAssignment, and a post-layout
evaluation pass. Section bodies were additionally sized by a second,
restricted expression evaluator that worked in offset space and kept
its own map of body symbols. The deferred bindings also required a
heuristic to decide which section a boundary address belongs to.

Symbol assignments within SECTIONS are now executed by the address
walk itself, in script order, with the live location counter and the
section it lies in. This kills the offset-space evaluator, the
binding side fields and the boundary heuristic: a symbol defined
between two sections is naturally associated with the section just
laid out, which is also what GNU ld and LLD do. Since an expression
may refer to an address or a symbol that is computed later, the walk
runs repeatedly until the layout converges, with a bound to reject
divergent scripts like `. = sym;` where sym depends on `.`.

Assignments outside SECTIONS are still evaluated after the layout is
fixed, now also iterating to handle chains like
`PROVIDE(a = b); PROVIDE(b = 0x1000);` in either order.

SECTIONS is now rejected in relocatable links (it used to crash) and
in combination with --section-order (it used to silently win).

New tests cover bodies of sections that never materialize, transitive
PROVIDE chains, the `. = ASSERT(...)` idiom and KEEP(*(SORT(...))).

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-06-11 11:32:19 +09:00
Rui UeyamaandClaude Fable 5 d49fd79114 Add tests for script-driven layout and fix three bugs they found
New tests cover PHDRS (segment creation, FLAGS, :phdr references and
their inheritance), AT() load addresses and LOADADDR, data commands
with fill patterns, KEEP under --gc-sections, SORT_BY_NAME and
SORT_BY_INIT_PRIORITY, location counter arithmetic in section bodies,
(NOLOAD) and (INFO), orphan placement and --orphan-handling, the
association of a boundary symbol with the preceding section, and
per-file input section patterns.

They caught three bugs:

- AT() was honored only when a PHDRS command was present. The default
  program header logic now takes a section's load address from the
  script and starts a new PT_LOAD when the distance between virtual
  and load addresses changes.

- An output section materialized only for its data commands (e.g. the
  Linux kernel setup's .signature) skipped the finalization step, so
  its fill pattern was dropped.

- On architectures with range extension thunks, an executable section
  described by a script was laid out twice: once by the thunk-creation
  pass and once by the script engine. The stale thunks were then
  written at offsets that no longer existed, corrupting neighboring
  sections. Script-described sections now skip thunk creation; a
  branch out of range is reported as a relocation error. Proper thunk
  support within script layout is left for future work.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-06-11 11:32:19 +09:00
Rui UeyamaandClaude Fable 5 2376604619 Fix the first-match-wins rule for linker script section patterns
MultiGlob::find returns the match with the largest value because
version script matching, its original use, gives the highest priority
to the last pattern. The script section matcher assumed the opposite,
so when two patterns matched the same input section, the later one
won. In the Linux kernel's compressed boot stub, the 1 MiB
.rodata..compressed payload was claimed by `*(.rodata.*)` instead of
its own output section, which shifted the stub's layout and made the
kernel triple-fault during decompression.

Ranks are now stored negated relative to INT32_MAX so that the
earliest pattern in the script has the largest value.

With this fix, a mold-linked Linux kernel boots: QEMU runs it to a
working PID 1, and the kernel banner duly credits `mold 2.41.0` as
the linker.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-06-11 11:32:19 +09:00
Rui UeyamaandClaude Fable 5 73770696cd Lay out output sections as described by SECTIONS and PHDRS
mold can now execute the core of the linker script language: output
sections are formed by first-match-wins wildcard matching with KEEP,
EXCLUDE_FILE and nested SORT specifiers; bodies run their location
counter arithmetic, symbol assignments and BYTE/SHORT/LONG/QUAD data
commands; addresses follow SECTIONS order with AT() load addresses
tracked per chunk; orphan sections are placed after the most similar
script section; and a PHDRS command, including FILEHDR/PHDRS and
:phdr references with inheritance, takes over program header creation.

The engine lives almost entirely in the linker's serial passes. Input
section matching is a pure function applied in parallel, reusing the
MultiGlob machinery whose insertion order gives both the first-match-
wins rule and member ordering. Address assignment is a single forward
walk over the SECTIONS commands with explicit location counter and
LMA states; it re-runs whenever set_osec_offsets() does, so it
composes with section-size convergence. An output section body is
re-laid out once its address is known, which makes in-body ALIGN
exact without inflating the section alignment.

Some semantic points worth noting:

- PROVIDE now defines a symbol only if it is referenced by an input
  file or, transitively, by an evaluated script expression; an
  unreferenced PROVIDE's value expression is not evaluated at all.
- A symbol defined between two sections is associated with the
  preceding section, as in GNU ld. The Linux kernel depends on this:
  `_end` must belong to .brk, or objcopy --remove-section=.modinfo
  would strip it.
- SIZEOF()/ADDR() of a script-declared section that didn't
  materialize evaluate to zero, and our always-created placeholder
  .got/.got.plt are not placed by script descriptions, so assertions
  like the kernel's ASSERT(SIZEOF(.got) == 0) behave as with GNU ld.
- Global symbols now keep their binding in the output symbol table as
  in GNU ld and LLD, instead of being demoted to local when not
  exported. The kernel build reads nm output of a previous link, so
  the bindings are load-bearing. Hidden visibility and a version
  script's `local:` still demote.
- A mergeable section's size is no longer padded up to its alignment
  if that would make it indivisible by sh_entsize; libelf-based tools
  such as objtool reject such sections.
- SHF_MERGE sections are not deduplicated when a script drives the
  layout, so that the script's patterns place their contents.
- --orphan-handling=place|warn|error is supported, and the
  --[no-]warn-rwx-segments flags are accepted.

With this change, mold builds a booting Linux kernel (x86-64
defconfig, QEMU-tested) with the kernel's unmodified linker scripts:
vmlinux, the vDSO, the compressed boot stub and the 16-bit setup code
all link with mold.

Constructs we cannot faithfully execute yet, such as MEMORY region
placement and OVERLAY, are still rejected with an error.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-06-11 11:32:19 +09:00
Rui UeyamaandClaude Fable 5 abf35a55c0 Support /DISCARD/ in linker scripts
A SECTIONS command whose output section descriptions consist only of
/DISCARD/ can now be executed: input sections matching the discard
patterns are removed from the output before garbage collection. File
patterns and EXCLUDE_FILE are honored.

This introduces the first piece of the script section-matching engine.
All patterns are compiled into a MultiGlob whose insertion order gives
the first-match-wins semantics, the same machinery version scripts
use. Sections are matched in parallel; the per-candidate file
constraint check happens only for the rare patterns that have one.

Discarding a mergeable section is rejected for now because its
contents have already been split into section pieces by the time
scripts are evaluated.

SECTIONS commands that lay out output sections are still rejected.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-06-11 11:31:52 +09:00
Rui UeyamaandClaude Fable 5 103348b4a5 Evaluate ENTRY, symbol assignments, PROVIDE and ASSERT in linker scripts
This change implements the linker script commands that do not require
script-driven section layout:

ENTRY(sym) sets the entry point. The -e option takes precedence.

Symbol assignments may now use arbitrary expressions, e.g.
`text_end = ADDR(.text) + SIZEOF(.text);`. Such expressions can only
be computed once the output file layout is fixed, so they are
evaluated at the end of fix_synthetic_symbols(), piggybacking on the
existing --defsym machinery for symbol creation. An expression value
tracks which output section it is relative to, so a symbol defined
this way remains correct in position-independent executables.

PROVIDE and PROVIDE_HIDDEN define a symbol only if no input file
defines it. They are handled by a new pass that runs between symbol
resolution and compute_import_export(), so version scripts and symbol
export treat them exactly like any other defined symbol.

ASSERT conditions are checked after the layout is finalized.

The expression evaluator supports the C operators with their usual
precedence and ABSOLUTE, ADDR, ALIGN, ALIGNOF, ASSERT, BLOCK,
CONSTANT(MAXPAGESIZE), DEFINED, LOG2CEIL, MAX, MIN, SEGMENT_START,
SIZEOF and SIZEOF_HEADERS. The ternary operator and && and || are
lazily evaluated to support idioms like `DEFINED(foo) ? foo : 0x1000`.

Commands that do require script-driven layout, such as SECTIONS and
MEMORY, are still rejected with an error.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-06-11 11:31:52 +09:00
Rui UeyamaandClaude Fable 5 5d2c0306f0 Parse the entire linker script language
This change makes mold's parser understand the GNU linker script
language in its entirety: SECTIONS with all output section attributes
and input section patterns, MEMORY, PHDRS, OVERLAY, INSERT,
REGION_ALIAS, ASSERT, data commands, INCLUDE and the full expression
grammar. Scripts are parsed into ScriptCmd/ScriptExpr trees, which are
plain data structures that can be printed with a new hidden
--dump-script flag for debugging.

Parsing and evaluation are now separate. We still evaluate only the
commands we have always supported (INPUT, GROUP, VERSION and defsym-
style assignments) plus a few new trivial ones (EXTERN, SEARCH_DIR,
INCLUDE). Everything else is a hard error pointing at the offending
command. The principle is that mold must never silently create an
output file whose layout differs from what a script demands; an
unsupported command is reported loudly instead of being ignored.
Evaluation of SECTIONS and friends will be built on top of this parser
in subsequent changes.

Since the language's lexical rule is context-dependent (`*` is a
wildcard in a pattern but an operator in an expression), the parser
switches the lexer between three modes. If lookahead tokens exist when
the mode changes, they are simply un-lexed and re-lexed in the new
mode, which is possible because tokens are string_views into the
mapped file.

As a bonus, `foo = 0x1000;` in a script now defines an absolute symbol
as --defsym does; it previously defined foo as an alias for a symbol
literally named "0x1000".

GNU ld's own 250-line internal default script (`ld --verbose` output)
parses and round-trips through --dump-script.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-06-11 11:31:52 +09:00
Rui UeyamaandClaude Fable 5 748f35f4f2 Make the linker script lexer lazy
We used to tokenize an entire linker script upfront. That doesn't work
for the full linker script grammar because the language is context-
dependent; for example, `*` is a wildcard in an input section pattern
but a multiplication operator in an expression, so `foo*bar` must be
lexed as one token in the former context and three in the latter.

GNU ld and LLD handle this with a lexer whose behavior is switched by
the parser. As a first step toward that, this change makes the lexer
produce tokens on demand as the parser consumes them. No behavior
change.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-06-11 11:31:52 +09:00
Rui UeyamaandClaude Fable 5 d7df43a38e Refactor the linker script parser to use a cursor
The linker script parser used to thread a span of remaining tokens
through every function, with each function returning what it didn't
consume. That style worked for the tiny grammar we currently support,
but it doesn't scale to the full SECTIONS/MEMORY/PHDRS grammar with
an expression language, which needs deeply recursive descent.

This change converts the parser to a conventional stateful cursor
with peek()/next()/consume()/skip() primitives. No behavior change.

This is a groundwork commit for full linker script support.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-06-11 11:31:52 +09:00
3243 changed files with 762298 additions and 53289 deletions

No files matched your search

-7
View File
@@ -1,7 +0,0 @@
# rustc's LoongArch targets use the medium code model, but the cc crate
# compiles C code with the C compiler's default normal code model, whose
# direct calls reach only ±128 MiB, unless -Ccode-model is given
# explicitly. A debug build of mold has more text than that, so we pass
# the model explicitly to make the cc crate pass -mcmodel=medium.
[target.'cfg(target_arch = "loongarch64")']
rustflags = ["-Ccode-model=medium"]
+13 -6
View File
@@ -43,13 +43,13 @@ jobs:
steps:
- name: Checkout Repository
uses: actions/checkout@v7
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@v2
uses: redhat-actions/podman-login@v1
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
@@ -71,13 +71,20 @@ jobs:
runs-on: windows-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
uses: actions/checkout@v6
- name: Build and Archive
shell: pwsh
run: ./.github/workflows/build-windows.ps1
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
+2 -2
View File
@@ -21,13 +21,13 @@ jobs:
steps:
- name: Checkout Repository
uses: actions/checkout@v7
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@v2
uses: redhat-actions/podman-login@v1
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
-16
View File
@@ -1,16 +0,0 @@
$ErrorActionPreference = 'Stop'
Set-Location "$PSScriptRoot/../.."
cargo build --release --locked --package mold-cli
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
$metadata = cargo metadata --locked --no-deps --format-version 1 | ConvertFrom-Json
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
$version = ($metadata.packages | Where-Object name -eq 'mold-cli').version
$stage = 'mold-install'
New-Item -ItemType Directory -Force "$stage/bin", "$stage/share/man/man1", "$stage/share/doc/mold", dist | Out-Null
Copy-Item target/release/mold.exe "$stage/bin/"
Copy-Item docs/mold.1 "$stage/share/man/man1/"
Copy-Item LICENSE "$stage/share/doc/mold/"
Compress-Archive -Path "$stage/*" -DestinationPath "dist/mold-$version-x86_64-windows.zip" -Force
+91 -111
View File
@@ -1,116 +1,85 @@
name: CI
on:
push:
pull_request:
env:
UBSAN_OPTIONS: print_stacktrace=1:halt_on_error=1
jobs:
test-all-targets:
runs-on: ubuntu-24.04
timeout-minutes: 60
env:
# Incremental artifacts and mold's own debug info are unused in CI.
CARGO_INCREMENTAL: 0
CARGO_PROFILE_DEV_DEBUG: 0
steps:
- uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@stable
- name: install test dependencies
run: |
sudo ./install-test-deps.sh
sudo .github/workflows/install-extras.sh
- run: cargo build --locked
- name: check the build for 32-bit hosts
run: |
rustup target add armv7-unknown-linux-gnueabihf i686-unknown-linux-gnu \
powerpc-unknown-linux-gnu
cargo check --locked --workspace --all-targets --target armv7-unknown-linux-gnueabihf
# i386 aligns 64-bit integers to 4 bytes
cargo check --locked --workspace --all-targets --target i686-unknown-linux-gnu
# 32-bit PowerPC has no 64-bit atomics
cargo check --locked --workspace --all-targets --target powerpc-unknown-linux-gnu
- name: verify all target toolchains are available
run: |
cargo test --locked -p mold-cli --test integration -- --list |
tee "$RUNNER_TEMP/test-inventory.txt"
! grep -q '^unavailable:' "$RUNNER_TEMP/test-inventory.txt"
# All workspace crates were built above. Avoid linking empty test binaries
# for the target crates and command.
- run: cargo test --locked -p mold -p mold-tests -p mold-cli --lib --test integration
- name: archive test results
uses: actions/upload-artifact@v7
if: failure()
with:
name: test-results-all-targets
path: target/debug/mold-test/out/test
test-sanitizers:
build-asan-tsan:
strategy:
matrix:
include:
- sanitizer: address
# Input files intentionally remain allocated until process exit.
runtime-options: detect_leaks=0:halt_on_error=1
test-prefix: ''
test-timeout: 30
- sanitizer: thread
runtime-options: halt_on_error=1
# TSan cannot model the atomic-fence synchronization in Rayon's
# work-stealing queue. Two CPUs still exercise parallel linker code.
test-prefix: taskset -c 0,1
test-timeout: 60
target:
- ''
- '-DMOLD_USE_ASAN=On'
- '-DMOLD_USE_TSAN=On'
runs-on: ubuntu-24.04
timeout-minutes: 60
env:
CARGO_TARGET_DIR: target/${{ matrix.sanitizer }}
RUSTFLAGS: -Zsanitizer=${{ matrix.sanitizer }}
RUSTDOCFLAGS: -Zsanitizer=${{ matrix.sanitizer }}
steps:
- uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@nightly
with:
components: rust-src
- run: sudo ./install-test-deps.sh
- run: cargo +nightly build --locked -Zbuild-std --target x86_64-unknown-linux-gnu -p mold-cli --no-default-features --features system-allocator,x86_64
- run: |
if [ '${{ matrix.sanitizer }}' = address ]; then
export ASAN_OPTIONS='${{ matrix.runtime-options }}'
else
export TSAN_OPTIONS='${{ matrix.runtime-options }}'
fi
${{ matrix.test-prefix }} cargo +nightly test --locked -Zbuild-std --target x86_64-unknown-linux-gnu -p mold-cli --no-default-features --features system-allocator,x86_64 --test integration -- --native --test-threads 1 --timeout ${{ matrix.test-timeout }}
- uses: rui314/setup-mold@staging
- run: sudo ./install-build-deps.sh
- name: build
run: |
sudo apt-get install -y clang-18 clang gcc-multilib gdb dwarfdump zstd
mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_COMPILER=clang-18 -DCMAKE_CXX_COMPILER=clang++-18 ${{ matrix.target }} ..
cmake --build . -j$(nproc)
- run: cd build; ctest --output-on-failure -j$(nproc)
- name: archive test results
uses: actions/upload-artifact@v7
if: failure()
with:
name: test-results-${{ matrix.sanitizer }}
path: target/${{ matrix.sanitizer }}/x86_64-unknown-linux-gnu/debug/mold-test/out/test
name: test-results-clang
path: |
build
!build/CMakeFiles
build-msan:
runs-on: ubuntu-24.04
timeout-minutes: 60
steps:
- uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@nightly
- uses: redhat-actions/podman-login@v1
with:
components: rust-src
- run: sudo ./install-test-deps.sh
- run: sudo apt-get install -y clang llvm
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@v6
- name: install-build-deps
run: |
sudo ./install-build-deps.sh
sudo ./install-cross-tools.sh
sudo .github/workflows/install-extras.sh
- name: build
run: |
mkdir build
cd build
cmake ..
cmake --build . -j$(nproc)
- run: cd build; ctest --output-on-failure -j$(nproc)
- name: archive test results
uses: actions/upload-artifact@v7
if: failure()
with:
name: test-results-msan
path: target/msan/x86_64-unknown-linux-gnu/debug/mold-test/out/test
name: test-results-multi-archs
path: |
build
!build/CMakeFiles
test-distros:
build-distros:
strategy:
matrix:
distro:
- alpine
- archlinux
- debian:12
- debian:11 # GCC 10 and CMake 3.18 - the minimum supported by mold
- fedora
- gentoo/stage3
- opensuse/tumbleweed
@@ -118,17 +87,17 @@ jobs:
- ubuntu:25.04
runs-on: ubuntu-latest
timeout-minutes: 60
# Tumbleweed does not declare PATH in its container configuration.
container:
image: ${{ matrix.distro }}
env:
PATH: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
container: ${{ matrix.distro }}
steps:
- uses: actions/checkout@v6
- run: ./install-test-deps.sh
- uses: dtolnay/rust-toolchain@stable
- run: cargo build --locked -p mold-cli --no-default-features --features x86_64
- run: cargo test --locked -p mold-cli --no-default-features --features x86_64 --test integration -- --native
- run: ./install-build-deps.sh
- name: build
run: |
mkdir build
cd build
cmake ..
cmake --build . -j$(nproc)
- run: cd build; ctest --output-on-failure -j$(nproc)
- name: sanitize distro name
if: failure()
id: sanitize
@@ -138,55 +107,66 @@ jobs:
if: failure()
with:
name: test-results-${{ steps.sanitize.outputs.name }}
path: target/debug/mold-test/out/test
path: |
build
!build/CMakeFiles
build-macos:
runs-on: macos-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@stable
- run: cargo build --locked -p mold-cli --no-default-features --features x86_64
- name: build
run: |
mkdir build
cd build
cmake ..
cmake --build . -j$(sysctl -n hw.physicalcpu)
build-windows:
runs-on: windows-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@stable
- run: cargo build --locked -p mold-cli --no-default-features --features x86_64
- name: build
run: |
mkdir build
cd build
cmake -T clangcl ..
cmake --build . -j $Env:NUMBER_OF_PROCESSORS
build-msys:
runs-on: windows-latest
timeout-minutes: 60
env:
CARGO_NET_GIT_FETCH_WITH_CLI: true
steps:
- uses: actions/checkout@v6
- uses: msys2/setup-msys2@v2
- name: Setup MSYS2
uses: msys2/setup-msys2@v2
with:
msystem: UCRT64
update: true
install: git
pacboy: rust:p cc:p dlfcn:p
- shell: msys2 {0}
run: cargo build --locked -p mold-cli --no-default-features --features x86_64
pacboy: gcc-libs:p libwinpthread-git:p tbb:p zlib:p zstd:p dlfcn:p cc:p cmake:p ninja:p
- name: build
shell: msys2 {0}
run: |
mkdir build
cd build
cmake -GNinja -DMOLD_USE_MIMALLOC=OFF -DMOLD_USE_SYSTEM_TBB=ON ..
cmake --build . -j $(nproc)
build-freebsd:
runs-on: ubuntu-latest
timeout-minutes: 60
env:
# The VM has little disk space, so omit unused build artifacts.
CARGO_INCREMENTAL: 0
CARGO_PROFILE_DEV_DEBUG: 0
CARGO_PROFILE_TEST_DEBUG: 0
steps:
- uses: actions/checkout@v6
- uses: vmactions/freebsd-vm@v1
- name: Build and test
uses: vmactions/freebsd-vm@v1
with:
usesh: true
run: |
pkg install -y rust
./install-test-deps.sh
cargo build --locked -p mold-cli --no-default-features --features x86_64
cargo test --locked -p mold-cli --no-default-features --features x86_64 --test integration -- --native
./install-build-deps.sh
mkdir build
cd build
cmake ..
cmake --build . -j$(nproc)
ctest --output-on-failure -j$(nproc)
+14 -7
View File
@@ -28,13 +28,13 @@ jobs:
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v7
- 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@v2
uses: redhat-actions/podman-login@v1
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
@@ -50,13 +50,20 @@ jobs:
build-windows:
runs-on: windows-latest
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
- uses: actions/checkout@v6
- name: Build and Archive
shell: pwsh
run: ./.github/workflows/build-windows.ps1
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:
@@ -67,7 +74,7 @@ jobs:
needs: [build-linux, build-windows]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- uses: actions/download-artifact@v8
with:
+33 -30
View File
@@ -1,34 +1,37 @@
#!/bin/bash
set -euo pipefail
cd "$(dirname "$0")/../.."
set -e -x
cd "$(dirname $0)"/../..
# MSan requires instrumented Rust, std, and native dependencies:
# https://doc.rust-lang.org/unstable-book/compiler-flags/sanitizer.html#memorysanitizer
export CARGO_TARGET_DIR="${CARGO_TARGET_DIR:-target/msan}"
export RUSTFLAGS='-Zsanitizer=memory -Zsanitizer-memory-track-origins=2'
export RUSTDOCFLAGS="$RUSTFLAGS"
export CC_x86_64_unknown_linux_gnu=clang
export CFLAGS_x86_64_unknown_linux_gnu='-fsanitize=memory -fsanitize-memory-track-origins=2 -fno-omit-frame-pointer'
export LIBZ_SYS_STATIC=1
export MSAN_OPTIONS=halt_on_error=1
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
# The instrumented linker is too slow unoptimized for the heaviest tests
# to finish within the per-test timeout.
export CARGO_PROFILE_DEV_OPT_LEVEL=1
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
# Use instrumentable implementations instead of BLAKE3/Zstd assembly, and
# select mold as well as mold-cli so Cargo enables its dependency features.
args=(
--locked -Zbuild-std --target x86_64-unknown-linux-gnu
--package mold --package mold-cli --no-default-features
--features mold-cli/system-allocator,mold-cli/x86_64,blake3/pure,zstd/no_asm
--test integration
)
cargo +nightly test "${args[@]}" --no-run
# The preload wrapper runs inside uninstrumented compiler processes, not
# inside mold. Rebuild it without MSan so those processes can load it.
clang -shared -fPIC -o "$CARGO_TARGET_DIR/x86_64-unknown-linux-gnu/debug/mold-wrapper.so" \
c/mold-wrapper.c -ldl
cargo +nightly test "${args[@]}" -- --native --test-threads 1 --timeout 60 "$@"
# 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 -4
View File
@@ -8,16 +8,13 @@ on:
- main
workflow_dispatch:
permissions:
contents: write
jobs:
update-manpage:
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v7
uses: actions/checkout@v6
- name: Install ronn
run: sudo apt-get update && sudo apt-get install -y ronn
+10 -5
View File
@@ -1,6 +1,11 @@
/target
/dist
/bench
/.claude
/.codex
*.o
*.so
*~
**/.DS_Store
.cache
core
gentoo
/mold-*.tar.gz
/build*
/mold
/dist
+493
View File
@@ -0,0 +1,493 @@
# You can customize a build by specifying CMake options. An option may be
# given in the -Dvariable=value form. For a boolean variable, `ON` or `1`
# means true, while `OFF` or `0` means false.
#
# Here are a couple of common cmake options:
#
# -DCMAKE_C_COMPILER=<command-name>
#
# Specifies the C compiler name to use. The default value is `cc`.
#
# -DCMAKE_CXX_COMPILER=<command-name>
#
# Specifies the C++ compiler name to use. The default value is `c++`.
#
# -DCMAKE_INSTALL_PREFIX=<directory>
#
# Specifies the install target directory. The default value is `/usr/local`.
#
# -DCMAKE_BUILD_TYPE=[Debug | Release | RelWithDebInfo | MinSizeRel]
#
# Specifies the build type. The default is `Release`, which is the right
# option unless you are debugging mold.
#
# An example of a cmake command line is shown below:
#
# $ cmake -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_CXX_COMPILER=g++-12 ..
#
# where `..` refers to this directory.
#
# With cmake, you may run `cmake --install .` instead of `make install` to
# install build artifacts to system directories. If you want to install
# artifacts to a temporary target directory, run `cmake --install . --prefix
# <dir>`.
#
# You can see the current cmake variables and their values by running
# `cmake -N -L .` in a build directory.
#
# Note that in this file, we provide various dials and knobs to configure
# how to build mold. However, as a policy, we do not provide a way to
# enable/disable any individual mold's feature. In other words, we do not
# provide options like `--enable-foo` or `--disable-foo`. The motivation
# behind this is build reproducibility. We want to guarantee that all builds
# of the mold linker of the same version will have the exact same set of
# features and behave exactly the same.
cmake_minimum_required(VERSION 3.14)
project(mold VERSION 2.41.0)
include(CMakeDependentOption)
include(CheckSymbolExists)
include(GNUInstallDirs)
add_executable(mold)
target_compile_features(mold PRIVATE cxx_std_20)
if(MINGW)
target_link_libraries(mold PRIVATE dl)
else()
target_link_libraries(mold PRIVATE ${CMAKE_DL_LIBS})
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)
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)
endif()
endif()
if(NOT "${CMAKE_CXX_COMPILER_FRONTEND_VARIANT}" STREQUAL "MSVC")
target_compile_options(mold PRIVATE
-fno-exceptions
-fno-unwind-tables
-fno-asynchronous-unwind-tables
-ffunction-sections
-fdata-sections
-Wall
-Wextra
-Wno-sign-compare
-Wno-unused-function
-Wno-unused-parameter
-Wno-missing-field-initializers
-ggnu-pubnames)
endif()
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
target_compile_options(mold PRIVATE -D_GLIBCXX_ASSERTIONS)
endif()
if(CMAKE_SYSTEM_NAME STREQUAL "OpenBSD")
set(OPENBSD ON)
endif()
# Build mold with -flto if -DMOLD_LTO=ON
option(MOLD_LTO "Build mold with link-time optimization enabled")
if(MOLD_LTO)
set_property(TARGET mold PROPERTY INTERPROCEDURAL_OPTIMIZATION ON)
endif()
# Enable AddressSanitizer if -DMOLD_USE_ASAN=ON
option(MOLD_USE_ASAN "Build mold with AddressSanitizer" OFF)
if(MOLD_USE_ASAN)
target_compile_options(mold PRIVATE -fsanitize=address -fsanitize=undefined)
target_link_options(mold PRIVATE -fsanitize=address -fsanitize=undefined)
endif()
# Enabled ThreadSanitizer if -DMOLD_USE_TSAN=ON
option(MOLD_USE_TSAN "Build mold with ThreadSanitizer" OFF)
if(MOLD_USE_TSAN)
target_compile_options(mold PRIVATE -fsanitize=thread)
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
# mold binary that works on various Linux distros. You probably don't
# need nor want to set this to ON.
option(MOLD_MOSTLY_STATIC "Statically link libstdc++ and some other libraries" OFF)
if(MOLD_MOSTLY_STATIC)
target_link_options(mold PRIVATE -static-libstdc++)
endif()
# Find zlib. If libz.so is not found, we compile a bundled one and
# statically-link it to mold.
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 "")
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()
# Find BLAKE3 cryptographic hash library. Just like zlib, if libblkae3.so
# is not found, we compile a bundled one and statically-link it to mold.
find_package(BLAKE3 QUIET)
if(BLAKE3_FOUND AND NOT MOLD_MOSTLY_STATIC)
target_link_libraries(mold PRIVATE BLAKE3::blake3)
else()
function(mold_add_blake3)
set(BUILD_SHARED_LIBS OFF)
add_subdirectory(third-party/blake3/c EXCLUDE_FROM_ALL)
target_link_libraries(mold PRIVATE blake3)
target_include_directories(mold PUBLIC third-party/blake3/c)
endfunction()
mold_add_blake3()
endif()
# Find zstd compression library. If zstd.h is not found, we compile a
# bundled one and statically-link it to mold.
include(CheckIncludeFile)
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_include_directories(mold PUBLIC third-party/zstd/lib)
target_link_libraries(mold PRIVATE libzstd_static)
endif()
# Find mimalloc. mimalloc is an alternative malloc implementation
# optimized for multi-threaded applications.
#
# If you want to use the usual libc's malloc, pass -DMOLD_USE_MIMALLOC=OFF.
#
# We enable mimalloc by default for 64-bit targets. It doesn't seem to
# be stable on 32-bit targets.
cmake_dependent_option(
MOLD_USE_MIMALLOC "Use mimalloc" ON
"CMAKE_SIZEOF_VOID_P EQUAL 8; NOT APPLE; NOT ANDROID; NOT OPENBSD; NOT MOLD_USE_ASAN; NOT MOLD_USE_TSAN" OFF)
cmake_dependent_option(
MOLD_USE_SYSTEM_MIMALLOC "Use system or vendored mimalloc" OFF
MOLD_USE_MIMALLOC OFF)
# 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.
if(MOLD_USE_MIMALLOC)
if(MOLD_USE_SYSTEM_MIMALLOC)
find_package(mimalloc 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)
endfunction()
mold_add_mimalloc()
endif()
endif()
# Find TBB. TBB (OneTBB or Intel TBB) is a high-level threading library.
# Use of this library is mandatory.
#
# By default, we build a bundled one and statically-link the library
# 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 OR BLAKE3_USE_TBB)
find_package(TBB REQUIRED)
target_link_libraries(mold PRIVATE TBB::tbb)
else()
function(mold_add_tbb)
set(BUILD_SHARED_LIBS OFF)
set(TBB_TEST OFF CACHE INTERNAL "")
set(TBB_STRICT OFF CACHE INTERNAL "")
add_subdirectory(third-party/tbb EXCLUDE_FROM_ALL)
target_compile_definitions(tbb PRIVATE __TBB_DYNAMIC_LOAD_ENABLED=0)
target_link_libraries(mold PRIVATE TBB::tbb)
endfunction()
mold_add_tbb()
endif()
# We always use Clang to build mold on Windows. MSVC can't compile mold.
if(WIN32)
if(MSVC AND NOT CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
message(FATAL_ERROR
"Your compiler is not supported; install Clang from Visual Studio Installer and re-run cmake with '-T clangcl'")
endif()
target_compile_definitions(mold PRIVATE NOGDI NOMINMAX)
if(MINGW)
target_compile_definitions(mold PRIVATE _WIN32_WINNT=0xA00)
target_link_libraries(mold PRIVATE bcrypt)
endif()
else()
include(CheckLibraryExists)
check_library_exists(m pow "" LIBM_FOUND)
if(LIBM_FOUND)
target_link_libraries(mold PRIVATE m)
endif()
endif()
# Build mold-wrapper.so
if(NOT APPLE AND NOT WIN32)
add_library(mold-wrapper SHARED)
install(TARGETS mold-wrapper DESTINATION ${CMAKE_INSTALL_LIBDIR}/mold)
# Remove the default `lib` prefix
set_target_properties(mold-wrapper PROPERTIES PREFIX "")
target_link_libraries(mold-wrapper PRIVATE ${CMAKE_DL_LIBS})
target_sources(mold-wrapper PRIVATE src/mold-wrapper.c)
endif()
# If atomics doesn't work by default, add -latomic.
# We need the flag on riscv, armv6 and m68k.
include(CheckCXXSourceCompiles)
check_cxx_source_compiles("#include <atomic>
int main() {
std::atomic_uint8_t a;
std::atomic_uint16_t b;
std::atomic_uint32_t c;
std::atomic_uint64_t d;
return ++a + ++b + ++c + ++d;
}" HAVE_FULL_ATOMIC_SUPPORT)
if(NOT HAVE_FULL_ATOMIC_SUPPORT)
target_link_libraries(mold PRIVATE atomic)
endif()
# Add -pthread
if(NOT APPLE AND NOT MSVC)
target_compile_options(mold PRIVATE -pthread)
target_link_options(mold PRIVATE -pthread)
endif()
check_symbol_exists(madvise sys/mman.h HAVE_MADVISE)
check_symbol_exists(uname sys/utsname.h HAVE_UNAME)
# 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}/mold-git-hash.h
-P ${CMAKE_SOURCE_DIR}/lib/update-git-hash.cmake
DEPENDS lib/update-git-hash.cmake
BYPRODUCTS mold-git-hash.h
VERBATIM)
add_dependencies(mold git_hash)
# 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
# slow.
#
# 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.
#
# 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(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
src/icf.cc
src/input-files.cc
src/input-sections.cc
src/linker-script.cc
src/main.cc
src/mapfile.cc
src/output-chunks.cc
src/passes.cc
src/relocatable.cc
src/shrink-sections.cc
src/thunks.cc
src/tls.cc
)
if(WIN32 AND NOT MINGW)
list(APPEND MOLD_TEMPLATE_FILES src/lto-win32.cc)
else()
list(APPEND MOLD_TEMPLATE_FILES src/lto-unix.cc)
endif()
if(WIN32)
list(APPEND MOLD_TEMPLATE_FILES
src/output-file-win32.cc
src/subprocess-win32.cc
)
else()
list(APPEND MOLD_TEMPLATE_FILES
src/output-file-unix.cc
src/subprocess-unix.cc
)
endif()
function(mold_instantiate_templates SOURCE TARGET)
set(PATH ${CMAKE_BINARY_DIR}/${SOURCE}.${TARGET}.cc)
if(NOT EXISTS ${PATH})
file(WRITE ${PATH} "#define MOLD_${TARGET} 1
#define MOLD_TARGET ${TARGET}
#include \"${CMAKE_SOURCE_DIR}/${SOURCE}\"
")
endif()
target_sources(mold PRIVATE ${PATH})
endfunction()
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
lib/aho-corasick.cc
lib/compress.cc
lib/crc32.cc
lib/demangle.cc
lib/filepath.cc
lib/glob.cc
lib/hyperloglog.cc
lib/perf.cc
lib/random.cc
lib/tar.cc
src/elf.cc
src/entry.cc
third-party/rust-demangle/rust-demangle.c
)
if(WIN32)
target_sources(mold PRIVATE
src/jobs-win32.cc
src/mapped-file-win32.cc
src/signal-win32.cc
)
else()
target_sources(mold PRIVATE
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)
# Create the ld symlinks required for testing
if(NOT WIN32)
add_custom_command(
TARGET mold POST_BUILD
COMMAND ${CMAKE_COMMAND} -E create_symlink mold ld
BYPRODUCTS ld
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
VERBATIM)
endif()
if(${UNIX})
add_subdirectory(test)
endif()
endif()
if(NOT CMAKE_SKIP_INSTALL_RULES)
install(TARGETS mold RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
install(FILES docs/mold.1 DESTINATION ${CMAKE_INSTALL_MANDIR}/man1/)
install(FILES LICENSE DESTINATION ${CMAKE_INSTALL_DOCDIR})
function(mold_install_relative_symlink OLD NEW)
install(CODE "
get_filename_component(PREFIX_ABS \${CMAKE_INSTALL_PREFIX}/ ABSOLUTE)
get_filename_component(OLD_ABS ${OLD} ABSOLUTE BASE_DIR \${PREFIX_ABS})
get_filename_component(NEW_ABS ${NEW} ABSOLUTE BASE_DIR \${PREFIX_ABS})
get_filename_component(NEW_DIR \${NEW_ABS} DIRECTORY)
file(RELATIVE_PATH OLD_REL \${NEW_DIR} \${OLD_ABS})
message(STATUS \"Installing symlink: \$ENV{DESTDIR}\${NEW_ABS} -> \${OLD_REL}\")
file(MAKE_DIRECTORY \$ENV{DESTDIR}\${NEW_DIR})
file(CREATE_LINK \${OLD_REL} \$ENV{DESTDIR}\${NEW_ABS} SYMBOLIC)")
endfunction()
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()
Generated
-590
View File
@@ -1,590 +0,0 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "allocator-api2"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
[[package]]
name = "arrayvec"
version = "0.7.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56"
[[package]]
name = "blake3"
version = "1.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d9e454fc11f76977dc803893aff6304ed33d6a26efae8696573bea74baa27ae"
dependencies = [
"arrayvec",
"cc",
"cfg-if",
"constant_time_eq",
"cpufeatures",
]
[[package]]
name = "bstr"
version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6bb31b46c14244e20ee9984b11bf5c992b91fb6939fea616e3512c8baecdbe5f"
dependencies = [
"memchr",
"regex-automata",
"serde_core",
]
[[package]]
name = "cc"
version = "1.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ad534f4357a5264cce5019c989cf66a4f0dc4e0d1b1d15f8aacec0ff7360273"
dependencies = [
"find-msvc-tools",
"jobserver",
"libc",
"shlex",
]
[[package]]
name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "constant_time_eq"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b"
[[package]]
name = "cpp_demangle"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2bb79cb74d735044c972aae58ed0aaa9a837e85b01106a54c39e42e97f62253"
dependencies = [
"cfg-if",
]
[[package]]
name = "cpufeatures"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566"
dependencies = [
"libc",
]
[[package]]
name = "crc32fast"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8498c871161e1742aaa9d52551b2d6ebdd4c3d45a3be423e3728f33b955be550"
dependencies = [
"cfg-if",
]
[[package]]
name = "crossbeam-deque"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5181e0de7b61eb03a81e347d6dd8797bae9da5146707b51077e2d71a54ec0ceb"
dependencies = [
"crossbeam-epoch",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17"
[[package]]
name = "either"
version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34"
[[package]]
name = "equivalent"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "find-msvc-tools"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890"
[[package]]
name = "flate2"
version = "1.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e634e2e0ebac1ee034020da1ca582e17ffe4e0f5e985823721e168928136dcb"
dependencies = [
"crc32fast",
"libz-sys",
]
[[package]]
name = "foldhash"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
[[package]]
name = "getrandom"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"
dependencies = [
"cfg-if",
"libc",
"r-efi",
]
[[package]]
name = "hashbrown"
version = "0.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
dependencies = [
"allocator-api2",
"equivalent",
"foldhash",
]
[[package]]
name = "jobserver"
version = "0.1.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3"
dependencies = [
"getrandom",
"libc",
]
[[package]]
name = "libc"
version = "0.2.189"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
[[package]]
name = "libmimalloc-sys"
version = "0.1.49"
source = "git+https://github.com/rui314/mimalloc_rust?rev=3979460494f1cd1e7f936cb8e10f41e927c9f698#3979460494f1cd1e7f936cb8e10f41e927c9f698"
dependencies = [
"cc",
]
[[package]]
name = "libz-sys"
version = "1.1.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85bc9657773828b90eeb625adff10eeac83cc21bbfd8e23a03eaa8a33c9e28d9"
dependencies = [
"cc",
"libc",
"pkg-config",
"vcpkg",
]
[[package]]
name = "memchr"
version = "2.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
[[package]]
name = "memmap2"
version = "0.9.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d1219ed1b7f229ee7104d281dd01d6802fe28bb6e95d292942c4daacdeb798c0"
dependencies = [
"libc",
]
[[package]]
name = "mimalloc"
version = "0.1.52"
source = "git+https://github.com/rui314/mimalloc_rust?rev=3979460494f1cd1e7f936cb8e10f41e927c9f698#3979460494f1cd1e7f936cb8e10f41e927c9f698"
dependencies = [
"libmimalloc-sys",
]
[[package]]
name = "mold"
version = "3.0.0"
dependencies = [
"blake3",
"bstr",
"cc",
"cpp_demangle",
"crc32fast",
"flate2",
"getrandom",
"hashbrown",
"libc",
"libz-sys",
"memchr",
"memmap2",
"portable-atomic",
"rayon",
"rustc-demangle",
"uuid",
"xxhash-rust",
"zstd",
]
[[package]]
name = "mold-cli"
version = "3.0.0"
dependencies = [
"mimalloc",
"mold",
"mold-target-arm32",
"mold-target-arm32be",
"mold-target-arm64",
"mold-target-arm64be",
"mold-target-i386",
"mold-target-loongarch32",
"mold-target-loongarch64",
"mold-target-m68k",
"mold-target-ppc32",
"mold-target-ppc64v1",
"mold-target-ppc64v2",
"mold-target-riscv32",
"mold-target-riscv32be",
"mold-target-riscv64",
"mold-target-riscv64be",
"mold-target-s390x",
"mold-target-sh4",
"mold-target-sh4be",
"mold-target-sparc64",
"mold-target-x86_64",
"mold-tests",
]
[[package]]
name = "mold-target-arm32"
version = "3.0.0"
dependencies = [
"mold",
]
[[package]]
name = "mold-target-arm32be"
version = "3.0.0"
dependencies = [
"mold",
]
[[package]]
name = "mold-target-arm64"
version = "3.0.0"
dependencies = [
"mold",
]
[[package]]
name = "mold-target-arm64be"
version = "3.0.0"
dependencies = [
"mold",
]
[[package]]
name = "mold-target-i386"
version = "3.0.0"
dependencies = [
"mold",
]
[[package]]
name = "mold-target-loongarch32"
version = "3.0.0"
dependencies = [
"mold",
]
[[package]]
name = "mold-target-loongarch64"
version = "3.0.0"
dependencies = [
"mold",
]
[[package]]
name = "mold-target-m68k"
version = "3.0.0"
dependencies = [
"mold",
]
[[package]]
name = "mold-target-ppc32"
version = "3.0.0"
dependencies = [
"mold",
]
[[package]]
name = "mold-target-ppc64v1"
version = "3.0.0"
dependencies = [
"mold",
]
[[package]]
name = "mold-target-ppc64v2"
version = "3.0.0"
dependencies = [
"mold",
]
[[package]]
name = "mold-target-riscv32"
version = "3.0.0"
dependencies = [
"mold",
]
[[package]]
name = "mold-target-riscv32be"
version = "3.0.0"
dependencies = [
"mold",
]
[[package]]
name = "mold-target-riscv64"
version = "3.0.0"
dependencies = [
"mold",
]
[[package]]
name = "mold-target-riscv64be"
version = "3.0.0"
dependencies = [
"mold",
]
[[package]]
name = "mold-target-s390x"
version = "3.0.0"
dependencies = [
"mold",
]
[[package]]
name = "mold-target-sh4"
version = "3.0.0"
dependencies = [
"mold",
]
[[package]]
name = "mold-target-sh4be"
version = "3.0.0"
dependencies = [
"mold",
]
[[package]]
name = "mold-target-sparc64"
version = "3.0.0"
dependencies = [
"mold",
]
[[package]]
name = "mold-target-x86_64"
version = "3.0.0"
dependencies = [
"mold",
]
[[package]]
name = "mold-tests"
version = "3.0.0"
dependencies = [
"libc",
]
[[package]]
name = "pkg-config"
version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548"
[[package]]
name = "portable-atomic"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85"
[[package]]
name = "proc-macro2"
version = "1.0.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
dependencies = [
"proc-macro2",
]
[[package]]
name = "r-efi"
version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
[[package]]
name = "rayon"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d"
dependencies = [
"either",
"rayon-core",
]
[[package]]
name = "rayon-core"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91"
dependencies = [
"crossbeam-deque",
"crossbeam-utils",
]
[[package]]
name = "regex-automata"
version = "0.4.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2"
[[package]]
name = "rustc-demangle"
version = "0.1.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b74b56ffa8bb2830709a538c2cbcae9aa062db0d2a42563bfb09bdaae44020eb"
[[package]]
name = "serde_core"
version = "1.0.229"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.229"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "shlex"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
[[package]]
name = "syn"
version = "3.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6275cddf4610d1775e6d1fe9469b2e77d0f39fd98fb7450901b821e0c53649f"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "uuid"
version = "1.27.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97277d36b9c3ace13e58fa6e753f8b0bbbf302a18dd193240d70f9e29681059a"
dependencies = [
"getrandom",
]
[[package]]
name = "vcpkg"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
name = "xxhash-rust"
version = "0.8.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aee1b19627c7c60102ab80d3a9cbe18de90bfe03bfa6c3715447681f0e8c8af6"
[[package]]
name = "zstd"
version = "0.13.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a"
dependencies = [
"zstd-safe",
]
[[package]]
name = "zstd-safe"
version = "7.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d"
dependencies = [
"zstd-sys",
]
[[package]]
name = "zstd-sys"
version = "2.0.16+zstd.1.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748"
dependencies = [
"cc",
"pkg-config",
]
-53
View File
@@ -1,53 +0,0 @@
[package]
name = "mold"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
description = "A high-performance ELF linker, rewritten in Rust"
license.workspace = true
# tests/lib.rs belongs to the mold-tests workspace crate.
autotests = false
# The linker is generic over the target. Instantiating it for all twenty
# targets in one crate keeps the compiler on a single core for minutes, so
# each target is instantiated in a crate of its own under arch/, and the
# executable in cli/ dispatches to them.
[workspace]
members = ["cli", "arch/*", "tests"]
default-members = [".", "cli", "arch/*", "tests"]
[workspace.package]
version = "3.0.0"
edition = "2024"
rust-version = "1.95"
license = "MIT"
[workspace.dependencies]
libc = "0.2"
mold = { path = "." }
[dependencies]
blake3 = "1.8.7"
bstr = "1"
cpp_demangle = "0.4"
crc32fast = "1"
flate2 = { version = "1", default-features = false, features = ["zlib"] }
getrandom = "0.4.3"
hashbrown = "0.17.1"
libc.workspace = true
libz-sys = "1.1.29"
memchr = "2.8.3"
memmap2 = "0.9"
# std has no 64-bit atomics on hosts such as 32-bit PowerPC.
portable-atomic = "1"
rayon = "1"
rustc-demangle = "0.1"
uuid = { version = "1.27", default-features = false, features = ["v4"] }
xxhash-rust = { version = "0.8", features = ["xxh3"] }
zstd = "0.13"
[build-dependencies]
cc = "1"
[profile.release]
panic = "abort"
+84 -145
View File
@@ -1,21 +1,30 @@
# mold: A Modern Linker
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.
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 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.
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 supports x86-64, i386, ARM 32/64, RISC-V 32/64, PowerPC 32/64, s390x,
LoongArch 32/64, SPARC64, m68k, and SH-4.
![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.
## Why does linking speed matter?
@@ -29,134 +38,50 @@ 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 Rust and built with Cargo. It requires Rust 1.95 or later;
if your system's Rust is older, install the stable toolchain with
[rustup](https://rustup.rs/). You also need Git and a C compiler such as GCC or
Clang, which you can install with your system's package manager.
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 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.
### Compile mold
```shell
git clone https://github.com/rui314/mold.git
git clone --branch stable https://github.com/rui314/mold.git
cd mold
cargo build --release
./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
```
The executable and its companion preload library are
`target/release/mold` and `target/release/mold-wrapper.so`.
You might need to pass a C++20 compiler command name to `cmake`. In the
example above, `c++` is passed. If that doesn't work for you, try a specific
version of a compiler, such as `g++-10` or `clang++-12`.
### Install mold
By default, `mold` is installed to `/usr/local/bin`. You can change the
installation location by passing `-DCMAKE_INSTALL_PREFIX=<directory>`.
For other cmake options, see the comments in `CMakeLists.txt`.
Run `sudo ./install-mold.sh` to install mold under `/usr/local`. To install it
under a different prefix, set `PREFIX`, as in
`sudo PREFIX=/usr ./install-mold.sh`. `mold-wrapper.so` is installed to
`$PREFIX/lib/mold`. If your system uses another library directory, such as
`/usr/lib64`, set `MOLD_LIBDIR` to it for both `cargo build` and
`install-mold.sh`.
You can also run `target/release/mold` directly without installing it.
If you are not using a recent enough Linux distribution, or if `cmake` does
not work for you for any reason, you can use Podman to build mold in a
container. To do so, run `./dist.sh` in this directory instead of using
`cmake`. The shell script will pull a container image, build mold and auxiliary
files inside it, and package them into a single tar file named
`dist/mold-$version-$arch-linux.tar.gz`. You can extract the tar file anywhere
and use the mold executable in it.
## How to use
@@ -178,10 +103,11 @@ to use mold instead of `/usr/bin/ld`:
valid argument, so you need to use the `-B` option instead. The `-B` option
tells GCC where to look for external commands like `ld`.
If you installed mold using the script above, there is a directory named
`/usr/local/libexec/mold`, and the `ld` command there is a symlink to mold.
Pass `-B/usr/local/libexec/mold` to GCC. If you installed under another
prefix, use that prefix instead.
If you have installed mold with `make install`, there should be a directory
named `/usr/libexec/mold` (or `/usr/local/libexec/mold`, depending on your
`$PREFIX`), and the `ld` command should be there. The `ld` is actually a
symlink to `mold`. So, all you need is to pass `-B/usr/libexec/mold` (or
`-B/usr/local/libexec/mold`) to GCC.
If you haven't installed `ld.mold` to any `$PATH`, you can still pass
`-fuse-ld=/absolute/path/to/mold` to clang to use mold. However, GCC does not
@@ -255,7 +181,7 @@ mold -run make <make-options-if-any>
Internally, mold invokes a given command with the `LD_PRELOAD` environment
variable set to its companion shared object file. The shared object file
intercepts all function calls to `exec(3)`-family functions to replace
`argv[0]` with `mold` if it is `ld`, `ld.bfd`, `ld.gold`, or `ld.lld`.
`argv[0]` with `mold` if it is `ld`, `ld.bf`, `ld.gold`, or `ld.lld`.
</details>
@@ -293,26 +219,39 @@ is available online. You can read the same manual by running `man mold`.
</details>
## Stability
## Why is mold so fast?
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.
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).
## Sponsors
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).
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.
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:
[![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:
### Corporate sponsors
-11
View File
@@ -1,11 +0,0 @@
# Architecture crates
Each subdirectory is a small crate that instantiates the generic linker for
one target architecture. Separate crates let Cargo compile the architectures
in parallel. Instantiating all targets in one crate leaves a long serial
compilation step.
The shared linker implementation lives in [`src/`](../src/), with
target-specific code in [`src/target/`](../src/target/). These crates provide
entry points that the [`cli/`](../cli/) executable selects for the input
files' target.
-13
View File
@@ -1,13 +0,0 @@
[package]
name = "mold-target-arm32"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
description = "The mold linker instantiated for arm32"
license.workspace = true
[lib]
path = "lib.rs"
[dependencies]
mold.workspace = true
-3
View File
@@ -1,3 +0,0 @@
pub fn link(cmdline: mold::driver::Cmdline) -> mold::driver::LinkResult {
mold::driver::link::<mold::target::Arm32>(cmdline)
}
-13
View File
@@ -1,13 +0,0 @@
[package]
name = "mold-target-arm32be"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
description = "The mold linker instantiated for arm32be"
license.workspace = true
[lib]
path = "lib.rs"
[dependencies]
mold.workspace = true
-3
View File
@@ -1,3 +0,0 @@
pub fn link(cmdline: mold::driver::Cmdline) -> mold::driver::LinkResult {
mold::driver::link::<mold::target::Arm32Be>(cmdline)
}
-13
View File
@@ -1,13 +0,0 @@
[package]
name = "mold-target-arm64"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
description = "The mold linker instantiated for arm64"
license.workspace = true
[lib]
path = "lib.rs"
[dependencies]
mold.workspace = true
-3
View File
@@ -1,3 +0,0 @@
pub fn link(cmdline: mold::driver::Cmdline) -> mold::driver::LinkResult {
mold::driver::link::<mold::target::Arm64>(cmdline)
}
-13
View File
@@ -1,13 +0,0 @@
[package]
name = "mold-target-arm64be"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
description = "The mold linker instantiated for arm64be"
license.workspace = true
[lib]
path = "lib.rs"
[dependencies]
mold.workspace = true
-3
View File
@@ -1,3 +0,0 @@
pub fn link(cmdline: mold::driver::Cmdline) -> mold::driver::LinkResult {
mold::driver::link::<mold::target::Arm64Be>(cmdline)
}
-13
View File
@@ -1,13 +0,0 @@
[package]
name = "mold-target-i386"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
description = "The mold linker instantiated for i386"
license.workspace = true
[lib]
path = "lib.rs"
[dependencies]
mold.workspace = true
-3
View File
@@ -1,3 +0,0 @@
pub fn link(cmdline: mold::driver::Cmdline) -> mold::driver::LinkResult {
mold::driver::link::<mold::target::I386>(cmdline)
}
-13
View File
@@ -1,13 +0,0 @@
[package]
name = "mold-target-loongarch32"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
description = "The mold linker instantiated for loongarch32"
license.workspace = true
[lib]
path = "lib.rs"
[dependencies]
mold.workspace = true
-3
View File
@@ -1,3 +0,0 @@
pub fn link(cmdline: mold::driver::Cmdline) -> mold::driver::LinkResult {
mold::driver::link::<mold::target::LoongArch32>(cmdline)
}
-13
View File
@@ -1,13 +0,0 @@
[package]
name = "mold-target-loongarch64"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
description = "The mold linker instantiated for loongarch64"
license.workspace = true
[lib]
path = "lib.rs"
[dependencies]
mold.workspace = true
-3
View File
@@ -1,3 +0,0 @@
pub fn link(cmdline: mold::driver::Cmdline) -> mold::driver::LinkResult {
mold::driver::link::<mold::target::LoongArch64>(cmdline)
}
-13
View File
@@ -1,13 +0,0 @@
[package]
name = "mold-target-m68k"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
description = "The mold linker instantiated for m68k"
license.workspace = true
[lib]
path = "lib.rs"
[dependencies]
mold.workspace = true
-3
View File
@@ -1,3 +0,0 @@
pub fn link(cmdline: mold::driver::Cmdline) -> mold::driver::LinkResult {
mold::driver::link::<mold::target::M68k>(cmdline)
}
-13
View File
@@ -1,13 +0,0 @@
[package]
name = "mold-target-ppc32"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
description = "The mold linker instantiated for ppc32"
license.workspace = true
[lib]
path = "lib.rs"
[dependencies]
mold.workspace = true
-3
View File
@@ -1,3 +0,0 @@
pub fn link(cmdline: mold::driver::Cmdline) -> mold::driver::LinkResult {
mold::driver::link::<mold::target::Ppc32>(cmdline)
}
-13
View File
@@ -1,13 +0,0 @@
[package]
name = "mold-target-ppc64v1"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
description = "The mold linker instantiated for ppc64v1"
license.workspace = true
[lib]
path = "lib.rs"
[dependencies]
mold.workspace = true
-3
View File
@@ -1,3 +0,0 @@
pub fn link(cmdline: mold::driver::Cmdline) -> mold::driver::LinkResult {
mold::driver::link::<mold::target::Ppc64V1>(cmdline)
}
-13
View File
@@ -1,13 +0,0 @@
[package]
name = "mold-target-ppc64v2"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
description = "The mold linker instantiated for ppc64v2"
license.workspace = true
[lib]
path = "lib.rs"
[dependencies]
mold.workspace = true
-3
View File
@@ -1,3 +0,0 @@
pub fn link(cmdline: mold::driver::Cmdline) -> mold::driver::LinkResult {
mold::driver::link::<mold::target::Ppc64V2>(cmdline)
}
-13
View File
@@ -1,13 +0,0 @@
[package]
name = "mold-target-riscv32"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
description = "The mold linker instantiated for riscv32"
license.workspace = true
[lib]
path = "lib.rs"
[dependencies]
mold.workspace = true
-3
View File
@@ -1,3 +0,0 @@
pub fn link(cmdline: mold::driver::Cmdline) -> mold::driver::LinkResult {
mold::driver::link::<mold::target::Riscv32>(cmdline)
}
-13
View File
@@ -1,13 +0,0 @@
[package]
name = "mold-target-riscv32be"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
description = "The mold linker instantiated for riscv32be"
license.workspace = true
[lib]
path = "lib.rs"
[dependencies]
mold.workspace = true
-3
View File
@@ -1,3 +0,0 @@
pub fn link(cmdline: mold::driver::Cmdline) -> mold::driver::LinkResult {
mold::driver::link::<mold::target::Riscv32Be>(cmdline)
}
-13
View File
@@ -1,13 +0,0 @@
[package]
name = "mold-target-riscv64"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
description = "The mold linker instantiated for riscv64"
license.workspace = true
[lib]
path = "lib.rs"
[dependencies]
mold.workspace = true
-3
View File
@@ -1,3 +0,0 @@
pub fn link(cmdline: mold::driver::Cmdline) -> mold::driver::LinkResult {
mold::driver::link::<mold::target::Riscv64>(cmdline)
}
-13
View File
@@ -1,13 +0,0 @@
[package]
name = "mold-target-riscv64be"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
description = "The mold linker instantiated for riscv64be"
license.workspace = true
[lib]
path = "lib.rs"
[dependencies]
mold.workspace = true
-3
View File
@@ -1,3 +0,0 @@
pub fn link(cmdline: mold::driver::Cmdline) -> mold::driver::LinkResult {
mold::driver::link::<mold::target::Riscv64Be>(cmdline)
}
-13
View File
@@ -1,13 +0,0 @@
[package]
name = "mold-target-s390x"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
description = "The mold linker instantiated for s390x"
license.workspace = true
[lib]
path = "lib.rs"
[dependencies]
mold.workspace = true
-3
View File
@@ -1,3 +0,0 @@
pub fn link(cmdline: mold::driver::Cmdline) -> mold::driver::LinkResult {
mold::driver::link::<mold::target::S390x>(cmdline)
}
-13
View File
@@ -1,13 +0,0 @@
[package]
name = "mold-target-sh4"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
description = "The mold linker instantiated for sh4"
license.workspace = true
[lib]
path = "lib.rs"
[dependencies]
mold.workspace = true
-3
View File
@@ -1,3 +0,0 @@
pub fn link(cmdline: mold::driver::Cmdline) -> mold::driver::LinkResult {
mold::driver::link::<mold::target::Sh4>(cmdline)
}
-13
View File
@@ -1,13 +0,0 @@
[package]
name = "mold-target-sh4be"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
description = "The mold linker instantiated for sh4be"
license.workspace = true
[lib]
path = "lib.rs"
[dependencies]
mold.workspace = true
-3
View File
@@ -1,3 +0,0 @@
pub fn link(cmdline: mold::driver::Cmdline) -> mold::driver::LinkResult {
mold::driver::link::<mold::target::Sh4Be>(cmdline)
}
-13
View File
@@ -1,13 +0,0 @@
[package]
name = "mold-target-sparc64"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
description = "The mold linker instantiated for sparc64"
license.workspace = true
[lib]
path = "lib.rs"
[dependencies]
mold.workspace = true
-3
View File
@@ -1,3 +0,0 @@
pub fn link(cmdline: mold::driver::Cmdline) -> mold::driver::LinkResult {
mold::driver::link::<mold::target::Sparc64>(cmdline)
}
-13
View File
@@ -1,13 +0,0 @@
[package]
name = "mold-target-x86_64"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
description = "The mold linker instantiated for x86_64"
license.workspace = true
[lib]
path = "lib.rs"
[dependencies]
mold.workspace = true
-3
View File
@@ -1,3 +0,0 @@
pub fn link(cmdline: mold::driver::Cmdline) -> mold::driver::LinkResult {
mold::driver::link::<mold::target::X86_64>(cmdline)
}
-115
View File
@@ -1,115 +0,0 @@
//! Builds the two small C parts of mold.
//!
//! `mold-wrapper.so` is the preload library behind `mold -run`. It
//! interposes the exec family, including the C-variadic `execl` functions
//! that stable Rust cannot define, so it is a C file. It is placed next to
//! the `mold` executable, where `-run` looks for it.
//!
//! `lto-message.c` adapts the LTO plugin's printf-like diagnostics
//! callback, which is C-variadic as well, and is linked into mold.
use std::path::{Path, PathBuf};
use std::process::Command;
// 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.
fn git_hash(source_dir: &Path) -> Option<String> {
let dot_git = source_dir.join(".git");
if !dot_git.exists() {
// Source archives use the package version. If Git metadata is added
// later, a clean rebuild is needed to embed the new commit hash.
return None;
}
if dot_git.is_file() {
println!("cargo:rerun-if-changed={}", dot_git.display());
}
let git_path = |name: &str| -> Option<PathBuf> {
let output = Command::new("git")
.arg("-C")
.arg(source_dir)
.args(["rev-parse", "--git-path", name])
.output()
.ok()?;
if !output.status.success() {
return None;
}
let path = PathBuf::from(String::from_utf8(output.stdout).ok()?.trim());
Some(if path.is_absolute() { path } else { source_dir.join(path) })
};
let reftable = git_path("reftable/tables.list").filter(|p| p.exists());
if let Some(head) = git_path("HEAD").filter(|p| p.exists()) {
println!("cargo:rerun-if-changed={}", head.display());
if let Ok(contents) = std::fs::read_to_string(&head)
&& let Some(reference) =
contents.strip_prefix("ref: ").map(str::trim).filter(|_| reftable.is_none())
&& let Some(path) = git_path(reference)
{
// A packed reference may acquire a loose file later.
// Watch its nearest existing directory until that happens.
if let Some(path) = path.ancestors().find(|p| p.exists()) {
println!("cargo:rerun-if-changed={}", path.display());
}
}
}
for path in git_path("packed-refs").into_iter().chain(reftable).filter(|p| p.exists()) {
println!("cargo:rerun-if-changed={}", path.display());
}
let output =
Command::new("git").arg("-C").arg(source_dir).args(["rev-parse", "HEAD"]).output().ok()?;
output.status.success().then(|| String::from_utf8_lossy(&output.stdout).trim().to_string())
}
fn main() {
let source_dir = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap());
let version = std::env::var("CARGO_PKG_VERSION").unwrap();
let version = match git_hash(&source_dir) {
Some(hash) => format!("mold {version} ({hash}; compatible with GNU ld)"),
None => format!("mold {version} (compatible with GNU ld)"),
};
println!("cargo:rustc-env=MOLD_VERSION={version}");
println!("cargo:rerun-if-changed=c/mold-wrapper.c");
println!("cargo:rerun-if-changed=c/lto-message.c");
let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap();
let target_env = std::env::var("CARGO_CFG_TARGET_ENV").unwrap();
if target_os == "windows" && target_env == "msvc" {
return;
}
let out_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap());
let mut build = cc::Build::new();
build.opt_level(2).pic(true);
if let Ok(sanitizer) = std::env::var("CARGO_CFG_SANITIZE") {
build.flag(format!("-fsanitize={sanitizer}"));
}
build.file("c/lto-message.c").compile("ltomessage");
if target_os == "windows" || target_os == "macos" {
return;
}
// The build directory is inside the actual profile directory. PROFILE
// only describes inheritance and need not be that directory's name.
let profile_dir = out_dir
.ancestors()
.find(|path| path.file_name().is_some_and(|name| name == "build"))
.and_then(Path::parent)
.expect("OUT_DIR must be inside Cargo's build directory");
let wrapper = profile_dir.join("mold-wrapper.so");
let mut command = build.get_compiler().to_command();
command.args(["-shared", "-o"]);
command.arg(&wrapper).arg("c/mold-wrapper.c");
if target_os == "android" || target_os == "linux" {
command.arg("-ldl");
}
let status = command.status();
if !matches!(status, Ok(s) if s.success()) {
println!("cargo:warning=could not build mold-wrapper.so; `mold -run` will not work");
}
}
-18
View File
@@ -1,18 +0,0 @@
// The linker plugin reports diagnostics through a printf-like callback.
// C-variadic functions can't be defined in stable Rust, so this adapter
// formats the message and hands it to Rust.
#include <stdarg.h>
#include <stdio.h>
void mold_lto_report(int level, const char *msg);
int mold_lto_message(int level, const char *fmt, ...) {
char buf[1000];
va_list ap;
va_start(ap, fmt);
vsnprintf(buf, sizeof(buf), fmt, ap);
va_end(ap);
mold_lto_report(level, buf);
return 0;
}
-72
View File
@@ -1,72 +0,0 @@
[package]
name = "mold-cli"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
description = "The mold executable"
license.workspace = true
[[bin]]
name = "mold"
path = "main.rs"
[[test]]
name = "integration"
path = "integration.rs"
harness = false
[dependencies]
mimalloc = { git = "https://github.com/rui314/mimalloc_rust", rev = "3979460494f1cd1e7f936cb8e10f41e927c9f698" }
mold.workspace = true
mold-target-x86_64 = { path = "../arch/x86_64", optional = true }
mold-target-i386 = { path = "../arch/i386", optional = true }
mold-target-arm32 = { path = "../arch/arm32", optional = true }
mold-target-arm32be = { path = "../arch/arm32be", optional = true }
mold-target-arm64 = { path = "../arch/arm64", optional = true }
mold-target-arm64be = { path = "../arch/arm64be", optional = true }
mold-target-riscv64 = { path = "../arch/riscv64", optional = true }
mold-target-riscv64be = { path = "../arch/riscv64be", optional = true }
mold-target-riscv32 = { path = "../arch/riscv32", optional = true }
mold-target-riscv32be = { path = "../arch/riscv32be", optional = true }
mold-target-ppc64v2 = { path = "../arch/ppc64v2", optional = true }
mold-target-ppc32 = { path = "../arch/ppc32", optional = true }
mold-target-ppc64v1 = { path = "../arch/ppc64v1", optional = true }
mold-target-s390x = { path = "../arch/s390x", optional = true }
mold-target-sparc64 = { path = "../arch/sparc64", optional = true }
mold-target-m68k = { path = "../arch/m68k", optional = true }
mold-target-sh4 = { path = "../arch/sh4", optional = true }
mold-target-sh4be = { path = "../arch/sh4be", optional = true }
mold-target-loongarch64 = { path = "../arch/loongarch64", optional = true }
mold-target-loongarch32 = { path = "../arch/loongarch32", optional = true }
[dev-dependencies]
mold-tests = { path = "../tests" }
# One feature per target. All are built by default; a build for one
# target alone (`--no-default-features --features x86_64`) is much faster.
[features]
default = [
"x86_64", "i386", "arm32", "arm32be", "arm64", "arm64be", "riscv64", "riscv64be", "riscv32", "riscv32be",
"ppc64v2", "ppc32", "ppc64v1", "s390x", "sparc64", "m68k", "sh4", "sh4be", "loongarch64", "loongarch32",
]
system-allocator = []
x86_64 = ["dep:mold-target-x86_64"]
i386 = ["dep:mold-target-i386"]
arm32 = ["dep:mold-target-arm32"]
arm32be = ["dep:mold-target-arm32be"]
arm64 = ["dep:mold-target-arm64"]
arm64be = ["dep:mold-target-arm64be"]
riscv64 = ["dep:mold-target-riscv64"]
riscv64be = ["dep:mold-target-riscv64be"]
riscv32 = ["dep:mold-target-riscv32"]
riscv32be = ["dep:mold-target-riscv32be"]
ppc64v2 = ["dep:mold-target-ppc64v2"]
ppc32 = ["dep:mold-target-ppc32"]
ppc64v1 = ["dep:mold-target-ppc64v1"]
s390x = ["dep:mold-target-s390x"]
sparc64 = ["dep:mold-target-sparc64"]
m68k = ["dep:mold-target-m68k"]
sh4 = ["dep:mold-target-sh4"]
sh4be = ["dep:mold-target-sh4be"]
loongarch64 = ["dep:mold-target-loongarch64"]
loongarch32 = ["dep:mold-target-loongarch32"]
-15
View File
@@ -1,15 +0,0 @@
//! Links libatomic into mold on hosts without 64-bit atomics, such as
//! 32-bit PowerPC. mimalloc uses 64-bit atomics, which C compilers
//! implement as calls into libatomic on those hosts.
fn main() {
println!("cargo:rerun-if-changed=build.rs");
let widths = std::env::var("CARGO_CFG_TARGET_HAS_ATOMIC").unwrap_or_default();
if !widths.split(',').any(|w| w == "64") {
// rustc places libraries named by this crate before its dependencies
// on the linker command line, where --as-needed would drop libatomic.
// Link arguments come after all libraries.
println!("cargo:rustc-link-arg-bins=-latomic");
}
}
-8
View File
@@ -1,8 +0,0 @@
use std::path::Path;
use std::process::ExitCode;
fn main() -> ExitCode {
let root = Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap();
let cases = [root.join("tests")];
mold_tests::run(&cases, Path::new(env!("CARGO_BIN_EXE_mold")))
}
-79
View File
@@ -1,79 +0,0 @@
//! The `mold` executable: runs the linker instantiated for the target the
//! inputs are for. Each target is instantiated in a crate of its own so
//! that the compiler can build them in parallel, and a feature per target
//! decides which of them are built in.
// A Rust executable can define only one global allocator, so select mimalloc
// here rather than in the linker library.
#[cfg(not(feature = "system-allocator"))]
#[global_allocator]
static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
type LinkFn = fn(mold::driver::Cmdline) -> mold::driver::LinkResult;
// Each target has its own monomorphized link function. Start with the first
// enabled target and switch to the matching function if the inputs differ.
const TARGETS: &[(&str, LinkFn)] = &[
#[cfg(feature = "x86_64")]
("x86_64", mold_target_x86_64::link),
#[cfg(feature = "i386")]
("i386", mold_target_i386::link),
#[cfg(feature = "arm32")]
("arm32", mold_target_arm32::link),
#[cfg(feature = "arm32be")]
("arm32be", mold_target_arm32be::link),
#[cfg(feature = "arm64")]
("arm64", mold_target_arm64::link),
#[cfg(feature = "arm64be")]
("arm64be", mold_target_arm64be::link),
#[cfg(feature = "riscv64")]
("riscv64", mold_target_riscv64::link),
#[cfg(feature = "riscv64be")]
("riscv64be", mold_target_riscv64be::link),
#[cfg(feature = "riscv32")]
("riscv32", mold_target_riscv32::link),
#[cfg(feature = "riscv32be")]
("riscv32be", mold_target_riscv32be::link),
#[cfg(feature = "ppc64v2")]
("ppc64v2", mold_target_ppc64v2::link),
#[cfg(feature = "ppc32")]
("ppc32", mold_target_ppc32::link),
#[cfg(feature = "ppc64v1")]
("ppc64v1", mold_target_ppc64v1::link),
#[cfg(feature = "s390x")]
("s390x", mold_target_s390x::link),
#[cfg(feature = "sparc64")]
("sparc64", mold_target_sparc64::link),
#[cfg(feature = "m68k")]
("m68k", mold_target_m68k::link),
#[cfg(feature = "sh4")]
("sh4", mold_target_sh4::link),
#[cfg(feature = "sh4be")]
("sh4be", mold_target_sh4be::link),
#[cfg(feature = "loongarch64")]
("loongarch64", mold_target_loongarch64::link),
#[cfg(feature = "loongarch32")]
("loongarch32", mold_target_loongarch32::link),
];
fn link_for_target(target: &str, cmdline: mold::driver::Cmdline) -> mold::driver::LinkResult {
for &(name, link) in TARGETS {
if name == target {
return link(cmdline);
}
}
eprintln!(
"mold: unsupported target: {target}; rebuild mold with the appropriate target support"
);
std::process::exit(1);
}
fn main() {
let Some(&(initial_target, _)) = TARGETS.first() else {
eprintln!("mold: no targets enabled; rebuild mold with the appropriate target support");
std::process::exit(1);
};
let args = std::env::args_os().collect();
let status = mold::driver::main(args, initial_target, link_for_target);
std::process::exit(status);
}
+176 -127
View File
@@ -2,33 +2,39 @@
#
# 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.42.0-x86_64-linux.tar.gz`).
# `mold-2.40.0-x86_64-linux.tar.gz`).
#
# This script aims to produce reproducible outputs. The container images,
# Rust toolchain, Cargo dependencies and file timestamps are pinned so that
# the same git commit can produce a bit-for-bit identical binary file. 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.
# 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. Distro package repositories must be pinned as well as the base
# image before a build is fully reproducible. The loongarch64 build still
# uses the live Debian sid repository, so it does not yet have that property.
# 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.
#
# The mold executable created by this script is dynamically linked to the
# system C runtime and other standard system libraries. We can't statically
# link glibc because doing so would disable dlopen(), which is required to
# load the LTO linker plugin.
# 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 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.
#
# The Rust toolchain is downloaded from the official Rust distribution
# site. Its version and SHA-256 hash are recorded below, so the toolchain
# is another pinned build input.
# 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 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.
#
# This script can be used to create non-native binaries (e.g., building
# aarch64 binary on x86-64) because Podman automatically runs everything
@@ -36,16 +42,7 @@
# non-native builds, you may need to install the qemu-user-static package.
set -e -x
cd "$(dirname "$0")"
# The executable embeds the hash of the Git commit it is built from, and
# the commit's timestamp is used as the file timestamp, so this script
# must run in a Git clone. A worktree doesn't work either because its
# `.git` file refers to a directory outside the build container.
if [ ! -d .git ]; then
echo "$0: must be run in a Git clone of mold" >&2
exit 1
fi
cd "$(dirname $0)"
usage() {
echo "Usage: $0 [ x86_64 | aarch64 | arm | riscv64 | ppc64le | s390x | loongarch64 ]"
@@ -55,144 +52,196 @@ usage() {
case $# in
0)
arch=$(uname -m)
if [ "$arch" = arm64 ]; then
if [ $arch = arm64 ]; then
arch=aarch64
elif [[ "$arch" = arm* ]]; then
elif [[ $arch = arm* ]]; then
arch=arm
fi
;;
1)
arch=$1
arch="$1"
;;
*)
usage
;;
esac
rust_version=1.97.1
# Switch to the pinned snapshot.debian.org sources that the Debian images
# ship commented out; the live mirrors no longer carry every architecture.
apt_setup="sed -i -e '/^deb/d' -e 's/^# deb /deb /g' /etc/apt/sources.list"
# Create a Podman image.
if [ "$GITHUB_REPOSITORY" = '' ]; then
image=mold-builder-$arch
image_build="podman build --arch $arch -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-builder-$arch
image_build="podman build --arch $arch -t $image --output=type=registry --layers --cache-to $image --cache-from $image -"
fi
case $arch in
x86_64)
# Debian 9 (Stretch) released in June 2017.
#
# We use a Google-provided mirror (mirror.gcr.io) instead of the official
# Docker Hub (docker.io) because docker.io has a strict rate limit policy.
base_image=mirror.gcr.io/library/debian:stretch@sha256:c5c5200ff1e9c73ffbf188b4a67eb1c91531b644856b4aefe86a58d2f0cb05be
rust_target=x86_64-unknown-linux-gnu
rust_sha256=b4cdbc7cc6b0ee0a2666b1872769fdb2ad8393b28b63952f6493b4b400e4832b
# 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. 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 /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 ca-certificates && \
rm -rf /var/lib/apt/lists
# Build CMake 3.27
RUN mkdir /build && \
cd /build && \
wget -O- --progress=dot:mega https://cmake.org/files/v3.27/cmake-3.27.7.tar.gz | tar xzf - --strip-components=1 && \
./bootstrap --parallel=\$(nproc) && \
make -j\$(nproc) && \
make install && \
rm -rf /build
# Build GCC 14
RUN mkdir /build && \
cd /build && \
wget -O- --progress=dot:mega https://ftpmirror.gnu.org/gcc/gcc-14.2.0/gcc-14.2.0.tar.gz | tar xzf - --strip-components=1 && \
mkdir gmp mpc mpfr && \
wget -O- --progress=dot:mega https://ftpmirror.gnu.org/gmp/gmp-6.3.0.tar.gz | tar xzf - --strip-components=1 -C gmp && \
wget -O- --progress=dot:mega https://ftpmirror.gnu.org/mpc/mpc-1.3.1.tar.gz | tar xzf - --strip-components=1 -C mpc && \
wget -O- --progress=dot:mega https://ftpmirror.gnu.org/mpfr/mpfr-4.2.1.tar.gz | tar xzf - --strip-components=1 -C mpfr && \
./configure --prefix=/usr --enable-languages=c,c++ --disable-bootstrap --disable-multilib && \
make -j\$(nproc) && \
make install && \
ln -sf /usr/lib64/libstdc++.so.6 /usr/lib/x86_64-linux-gnu/libstdc++.so.6 && \
rm -rf /build
# Build GNU binutils 2.43
RUN mkdir /build && \
cd /build && \
wget -O- --progress=dot:mega https://ftpmirror.gnu.org/binutils/binutils-2.43.tar.gz | tar xzf - --strip-components=1 && \
./configure --prefix=/usr && \
make -j\$(nproc) && \
make install && \
rm -fr /build
# Build Python 3.12.7
RUN mkdir /build && \
cd /build && \
wget -O- --progress=dot:mega https://www.python.org/ftp/python/3.12.7/Python-3.12.7.tgz | tar xzf - --strip-components=1 && \
./configure && \
make -j\$(nproc) && \
make install && \
rm -rf /build
# Build LLVM 20
RUN mkdir /build && \
cd /build && \
wget -O- --progress=dot:mega https://github.com/llvm/llvm-project/archive/refs/tags/llvmorg-20.1.3.tar.gz | tar xzf - --strip-components=1 && \
mkdir b && \
cd b && \
cmake -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_PROJECTS=clang ../llvm && \
cmake --build . -j\$(nproc) && \
cmake --install . --strip && \
rm -rf /build
EOF
;;
aarch64)
# Debian 11 (Bullseye) released in August 2021.
base_image=mirror.gcr.io/library/debian:bullseye-20240904@sha256:8ccc486c29a3ad02ad5af7f1156e2152dff3ba5634eec9be375269ef123457d8
rust_target=aarch64-unknown-linux-gnu
rust_sha256=2f2496c70bd336a66a4c8baf2d303ba161f3552f192444c3639ba903c7c1e2c5
;;
arm)
# Debian 11 (Bullseye) released in August 2021.
base_image=mirror.gcr.io/library/debian:bullseye-20240904@sha256:8ccc486c29a3ad02ad5af7f1156e2152dff3ba5634eec9be375269ef123457d8
rust_target=armv7-unknown-linux-gnueabihf
rust_sha256=e89c5e33aaddc6ef56857000c9117875c2997e9a1a500bd7b16277c9874b002f
aarch64 | arm | ppc64le | s390x)
# Debian 11 (Bullseye) released in August 2021
#
# 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 /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 && \
ln -sf /usr/bin/clang-16 /usr/bin/clang && \
ln -sf /usr/bin/clang++-16 /usr/bin/clang++ && \
rm -rf /var/lib/apt/lists
EOF
;;
riscv64)
base_image=mirror.gcr.io/riscv64/debian:unstable-20240926@sha256:25654919c2926f38952cdd14b3300d83d13f2d820715f78c9f4b7a1d9399bf48
apt_setup="sed -i -e '/^URIs/d' -e 's/^# http/URIs: http/' /etc/apt/sources.list.d/debian.sources"
rust_target=riscv64gc-unknown-linux-gnu
rust_sha256=59bec35d8febb2ab918fa41cffbaa5b07146a63bdc33f029ff756d70a3151ece
;;
ppc64le)
# Debian 11 (Bullseye) released in August 2021.
base_image=mirror.gcr.io/library/debian:bullseye-20240904@sha256:8ccc486c29a3ad02ad5af7f1156e2152dff3ba5634eec9be375269ef123457d8
rust_target=powerpc64le-unknown-linux-gnu
rust_sha256=ff524eef5a59d801df09ccad5cdaf9ea1f0a07d75cbed2a7e9f013a9eb76a3c1
;;
s390x)
# Debian 11 (Bullseye) released in August 2021.
base_image=mirror.gcr.io/library/debian:bullseye-20240904@sha256:8ccc486c29a3ad02ad5af7f1156e2152dff3ba5634eec9be375269ef123457d8
rust_target=s390x-unknown-linux-gnu
rust_sha256=808268af9e880d41b8cb32b242e38c9bd3ea7aba6409b02fbffa0fbc5370c538
cat <<EOF | $image_build
FROM mirror.gcr.io/riscv64/debian:unstable-20240926@sha256:25654919c2926f38952cdd14b3300d83d13f2d820715f78c9f4b7a1d9399bf48
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 && \
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)
base_image=mirror.gcr.io/loongarch64/debian:sid@sha256:0356df4e494bbb86bb469377a00789a5b42bbf67d5ff649a3f9721b745cbef77
apt_setup="echo 'deb http://deb.debian.org/debian sid main' > /etc/apt/sources.list"
rust_target=loongarch64-unknown-linux-gnu
rust_sha256=d5a925962854730ae7641420d8337af93988ea4ff47b503a856ec53776c87841
cat <<EOF | $image_build
FROM mirror.gcr.io/loongarch64/debian:sid@sha256:0356df4e494bbb86bb469377a00789a5b42bbf67d5ff649a3f9721b745cbef77
ENV DEBIAN_FRONTEND=noninteractive TZ=UTC
RUN sed -i -e 's!http[^ ]*!http://snapshot.debian.org/archive/debian-ports/20250620T014755Z!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-14 g++-14 clang-19 cmake && \
ln -sf /usr/bin/clang-19 /usr/bin/clang && \
ln -sf /usr/bin/clang++-19 /usr/bin/clang++ && \
rm -rf /var/lib/apt/lists
EOF
;;
*)
usage
;;
esac
# Create a Podman image containing the native C tools and a pinned Rust
# toolchain. The downloaded archive is checked before it is unpacked.
image=mold-rust-builder-$arch
archive=rust-$rust_version-$rust_target.tar.gz
podman build --arch "$arch" -t "$image" - <<EOF
FROM $base_image
ENV DEBIAN_FRONTEND=noninteractive TZ=UTC
RUN $apt_setup && \
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 ca-certificates git wget && \
rm -rf /var/lib/apt/lists
RUN mkdir /tmp/rust && \
cd /tmp/rust && \
wget --progress=dot:mega https://static.rust-lang.org/dist/$archive && \
echo '$rust_sha256 $archive' | sha256sum -c && \
tar xf $archive && \
./${archive%.tar.gz}/install.sh --prefix=/usr/local --disable-ldconfig && \
cd / && \
rm -rf /tmp/rust
EOF
version=$(sed -n 's/^version = "\(.*\)"/\1/p' Cargo.toml)
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 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=%ct)
# `uname -m` in an ARM32 container running on an ARM64 host reports it
# not as ARM32 but as ARM64. Keep the reported machine consistent with
# the ARM32 userspace for any build tool that inspects 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'
[ $arch = arm ] && setarch='setarch linux32'
mkdir -p dist "target/dist-vendor-$arch"
# Cargo verifies registry packages against the checksums in Cargo.lock and
# checks out Git dependencies at the commit recorded there. Vendor them in a
# separate networked step so that the actual build can run without a network.
# Cargo's own cache is a tmpfs because the libgit2 inside a 32-bit cargo can't
# read a bind-mounted ext4 directory (readdir fails with EOVERFLOW under QEMU).
podman run --arch "$arch" -it --rm --userns=host --pids-limit=-1 \
--pull=never --env CARGO_HOME=/cargo --tmpfs /cargo -v "$(pwd):/mold:ro" \
-v "$(pwd)/target/dist-vendor-$arch:/vendor" "$image" $setarch \
bash -c 'cd /mold && cargo vendor --locked /vendor/sources > /vendor/config.toml'
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.
# Fixed source, vendor and target paths keep embedded build paths stable.
podman run --arch "$arch" -it --rm --userns=host --pids-limit=-1 --network=none \
--pull=never --env SOURCE_DATE_EPOCH="$timestamp" --env DEST="$dest" \
-v "$(pwd):/mold:ro" -v "$(pwd)/dist:/dist" \
-v "$(pwd)/target/dist-vendor-$arch:/vendor:ro" "$image" \
$setarch bash -c '
# 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 \
--pull=never -v "$(pwd):/mold:ro" -v "$(pwd)/dist:/dist" $image \
$setarch bash -c "
set -e
export CARGO_TARGET_DIR=/build/target
cd /mold
cargo build --release --frozen --config /vendor/config.toml --package mold-cli
stage=/build/$DEST
DESTDIR=/build PREFIX=/$DEST ./install-mold.sh
strip --strip-unneeded "$stage/bin/mold" "$stage/lib/mold/mold-wrapper.so"
find "$stage" -print | xargs touch --no-dereference --date="@$SOURCE_DATE_EPOCH"
export SOURCE_DATE_EPOCH=$timestamp
mkdir /build
cd /build
find "$DEST" -print | sort | tar -cf - --no-recursion --files-from=- | gzip -9nc > "/dist/$DEST.tar.gz"
sha256sum "/dist/$DEST.tar.gz"
'
cmake -DCMAKE_BUILD_TYPE=Release -DMOLD_MOSTLY_STATIC=1 -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ /mold
cmake --build . -j\$(nproc)
cmake --install .
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 | sort | tar -cf - --no-recursion --files-from=- | gzip -9nc > /dist/$dest.tar.gz
cp mold /dist
sha256sum /dist/$dest.tar.gz
"
+150
View File
@@ -0,0 +1,150 @@
This is a note about interesting bugs that I met during the
development of the mold linker.
## GNU IFUNC
Problem: A statically-linked "hello world" program mysteriously
crashed in `__libc_start_main` function which is called just after
`_start`.
Investigation: I opened up gdb and found that the program reads a
bogus value from some array. It looks like `memcpy` failed to copy
proper data there. After some investigation, I noticed that `memcpy`
did't copy data at all but instead returned the address of
`__memcpy_avx_unaligned` function, which is a real `memcpy` function
optimized for machines with the AVX registers.
This odd issue was caused by the GNU IFUNC mechanism. That is, if a
function symbol has type `STT_GNU_IFUNC`, the function does not do
what its name suggests to do but instead returns a pointer to a
function that does the actual job. In this case, `memcpy` is an IFUNC
function, and it returns an address of `__memcpy_avx_unaligned` which
is a real `memcpy` function.
IFUNC function addresses are stored to `.got` section in an ELF
executable. The dynamic loader executes all IFUNC functions at
startup to replace their GOT entries with their return values. This
mechanism allows programs to choose the best implementation among
variants of the same function at runtime based on the machine info.
If a program is statically-linked, there's no dynamic loader that
rewrites the GOT entries. Therefore, if a program is
statically-linked, a libc's startup routine does that on behalf of the
dynamic loader. Concretely, a startup routine interprets all dynamic
relocations between `__rela_iplt_start` and `__rela_iplt_stop`
symbols. It is linker's responsibility to emit dynamic relocations
for IFUNC symbols even if it is linking a statically-linked program
and mark the beginning and the ending of a `.rela.dyn` section with
the symbols, so that the startup routine can find the relocations.
The bug was my linker didn't define `__rela_iplt_start` and
`__rela_iplt_stop` symbols. Since these symbols are weak, they are
initialized to zero. From the point of the initializer function,
there's no dynamic relocations between `__rela_iplt_start` and
`__rela_iplt_stop` symbols. That left GOT entries for IFUNC symbols
untouched.
The proper fix was to emit dynamic relocations for IFUNC symbols and
define the linker-synthesized symbols. I did that, and the bug was
fixed.
## stdio buffering
Problem: A statically-linked "Hello world" program prints out the
message if executed as `./hello`, but it doesn't output anything if
executed as `./hello | cat`.
Investigation: I knew that the default buffering mode for stdout is
line buffering (buffer is flushed on every '\n'), but if it is not
connected to the terminal (i.e. `isatty(2)` returns 0 on
`STDOUT_FILENO`), it automatically switches to full buffering (buffer
is flushed when it becomes full). So, it looks like libc failed to
flush the stdout on program exit for some reason.
I traced all function calls using gdb and noticed that `__libc_atexit`
was not called. That function seemed to be responsible for buffer
flushing. I don't know how exactly I found the root cause, but after
spending an hour or two, I found that `__start___libc_atexit` and
`__stop___libc_atexit` have value 0 in my linker's output while they
mark a section containing the address of `__libc_atexit` in GNU ld's
output.
So, libc doesn't directly call `__libc_atexit` but instead call all
function pointers between `__start___libc_atexit` and
`__stop___libc_atexit` symbols. libc puts `__libc_atexit` address in
`_libc_atexit` section, expecting that the linker automatically
creates the start and the end marker symbols for the section.
There's an obscure linker feature: if a section name is valid as a C
identifier (e.g. `foo` or `_foo_bar` but not `.foo`), the linker
automatically creates marker symbols by prepending `__start_` and
`__stop_` to the section name. My linker lacked the feature.
I implemented the feature, and the bug was fixed.
## TLS variable initialization
Problem: A statically-linked "hello world" program crashes after
reading a thread-local variable.
Investigation: Thread-local variables are very different from other
types of varaibles because there may be more than one instance of the
same variable in memory. Each thread has its copy of thread-local
varaibles. `%fs` segment register points the end of the variable area
for the current thread, and the variables are accessed as an offset
from `%fs`.
Thread-local variables may be initialized (e.g. `thread_local int x =
5;`). The linker gathers all thread-local variables and put them into
`PT_TLS` segment. At runtime, the contents of the segment is used as
an "initialization image" for new threads. When a new thread is
created, the image is memcpy'ed to the new thread's thread-local
variable area. The initialization image itself is read-only at
runtime.
It took more than a day to find out that memcpy copies the
initialization image to a different place than the thread-local
variables reside. That means, thread-local variables had garbage as
initial values, and the program crashed when using them.
The problem is that I set a very large value (4096) to the alignment
of `PT_TLS` segment. All `PT_LOAD` segments are naturally aligned to
the page boundary, so I use the same value for `PT_TLS`, but that was
a mistake. When a thread initialization routine sets a value to `%fs`,
it first aligns the end of the thread-local variable area address to
`PT_TLS` alignment value. So, if you set a large value to `PT_TLS`
alignment, `%fs` is set to a wrong place.
I fixed `PT_TLS` alignment, and the bug was gone.
## stdio buffering (another issue)
I noticed that a dynamically-linked "hello world" program didn't
flush its stdout buffer on exit. The cause of the problem was that the
executable had more than one DT_NEEDED entry for `libc.so`.
DT_NEEDED entries in `.dynamic` section specify a list of shared
object file names which need to be linked at runtime. I added one
DT_NEEDED entry for each library specified with the `-l` option.
The pitfall is, unlike object files, libraries are allowed to
appear more than once in a command line, and the linker has to
de-duplicate them before processing. Adding more than one DT_NEEDED
entry for the same shared object causes mysterious issues like this.
# Copy relocations and symbol aliases
environ, _environ and __environ point to the same location in libc.so,
so when we create a copy relocation for one of the symbols, we need to
do that for all of them. Otherwise, they'll end up pointing to different
places which causes a very mysterious issue.
# DT_DEBUG and gdb
If you forget to add an entry with type DT_DEBUG to .dynamic, gdb's
`info sharedlibrary` command doesn't print out a list of shared
libraries loaded to memory. The value of the entry doesn't matter, so
it can be just zero. The existence of it is important.
#
__EH_FRAME_BEGIN__ in libgcc/crtstuff.c
+1
View File
@@ -0,0 +1 @@
<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

After

Width:  |  Height:  |  Size: 99 KiB

+46
View File
@@ -0,0 +1,46 @@
# Coding Guidelines
mold is written in C++20, but as is the case with every C++ project,
it has local coding rules. In this document, I'll explain some of them
and try to give justifications for why I chose such rules.
## DOs
- Always use `i64` (which is a type alias for `int64_t` in mold) for
integers unless you have a reason not to. For example, even if you know
that a loop counter won't exceed 100, you should stop thinking about it
and just use `i64`.
Justification: Local variables are usually on CPU registers, so on
64-bit CPUs, there's no performance peanlty on choosing `i64` over
`i32`. Even if a compiler has to spill register values to the stack,
I don't think there's an observable difference between `i32` and
`i64`. Therefore, extra 32 bits are essentially free. On 32-bit CPUs,
they are not free, but that's OK because we are writing mold for modern
computers. mold will still run on 32-bit computers but a bit slowly.
By always using `i64`, we can eliminate the need to think about the
"right" size for each variable. It also reduces the risk of integer
overflow.
Exceptions: If you have to allocate a very large number (e.g. millions)
of the same object, its size matters. In that case, use a smaller type.
## DON'Ts
- Don't use `auto` unless its actual type is obvious in the very narrow
context. Currently, we use `auto` only for lambdas.
Justification: I think `auto` makes code writing easier but code reading
harder, because readers have to make a guess as to what is the actual
type of `auto`. If you are already familiar with the existing codebase,
you may be able to guess it easily, but that's not always the case.
I want to keep the mold codebase friendly to first-time readers.
- Don't over-use inheritance. In mold, most classes don't have parents,
and even if they do, their class hierarchy is very shallow. Currently
its height is just two (i.e. abstract classes and their implementations).
Justification: Designing class hierarchies is fun as it feels like
taxonomy, but I don't think that always help writing code. It looks like
simpler class hierarchy makes its code simpler.
+491
View File
@@ -0,0 +1,491 @@
[This document was written in 2020, and the contents are outdated.
Specifically, we no longer believe that object preloading is a good
idea. That being said, most of the points in this document still hold
even today. Therefore, I'll keep this document as-is.]
## Design and implementation of mold
For the rest of this documentation, I'll explain the design and the
implementation of mold. If you are only interested in using mold, you
don't need to read the below.
### Motivation
Here is why I'm writing a new linker:
- Even though lld has significantly improved the situation, linking is
still one of the slowest steps in a build. It is especially
annoying when I changed one line of code and had to wait for a few
seconds or even more for a linker to complete. It should be
instantaneous. There's a need for a faster linker.
- The number of cores on a PC has increased a lot lately, and this
trend is expected to continue. However, the existing linkers can't
take the advantage of the trend because they don't scale well for more
cores. I have a 64-core/128-thread machine, so my goal is to create
a linker that uses the CPU nicely. mold should be much faster than
other linkers on 4 or 8-core machines too, though.
- It looks to me that the designs of the existing linkers are somewhat
too similar, and I believe there are a lot of drastically different
designs that haven't been explored yet. Developers generally don't
care about linkers as long as they work correctly, and they don't
even think about creating a new one. So there may be lots of low
hanging fruits there in this area.
### Basic design
- In order to achieve a `cp`-like performance, the most important
thing is to fix the layout of an output file as quickly as possible, so
that we can start copying actual data from input object files to an
output file as soon as possible.
- Copying data from input files to an output file is I/O-bounded, so
there should be room for doing computationally-intensive tasks while
copying data from one file to another.
- We should allow the linker to preload object files from disk and
parse them in memory before a complete set of input object files
is ready. To do so, we need
to split the linker into two in such a way that the latter half of
the process finishes as quickly as possible by speculatively parsing
and preprocessing input files in the first half of the process.
- One of the most computationally-intensive stage among linker stages
is symbol resolution. To resolve symbols, we basically have to throw
all symbol strings into a hash table to match undefined symbols with
defined symbols. But this can be done in the preloading stage using
[string interning](https://en.wikipedia.org/wiki/String_interning).
- Object files may contain a special section called a mergeable string
section. The section contains lots of null-terminated strings, and
the linker is expected to gather all mergeable string sections and
merge their contents. So, if two object files contain the same
string literal, for example, the resulting output will contain a
single merged string. This step is computationally intensive, but string
merging can be done in the preloading stage using string interning.
- Static archives (.a files) contain object files, but the static
archive's string table contains only defined symbols of member
object files and lacks other types of symbols. That makes static
archives unsuitable for speculative parsing. Therefore, the linker
should ignore the symbol table of static archive and directly read
static archive members.
- If there's a relocation that uses a GOT of a symbol, then we have to
create a GOT entry for that symbol. Otherwise, we shouldn't. That
means we need to scan all relocation tables to fix the length and
the contents of a .got section. This is computationally intensive,
but this step is parallelizable.
### Linker Script
Linker script is an embedded language for the linker. It is mainly
used to control how input sections are mapped to output sections and
the layout of the output, but it can also do a lot of tricky stuff.
Its feature is useful especially for embedded programming, but it's
also an awfully underdocumented and complex language.
We have to implement a subset of the linker script language anwyay,
because on Linux, /usr/lib/x86_64-linux-gnu/libc.so is (despite its
name) not a shared object file but actually an ASCII file containing
linker script code to load the _actual_ libc.so file. But the feature
set for this purpose is very limited, and it is okay to implement them
to mold.
Besides that, we really don't want to implement the linker script
language. But at the same time, we want to satisfy the user needs that
are currently satisfied with the linker script language. So, what
should we do? Here is my observation:
- Linker script allows doing a lot of tricky stuff, such as specifying
the exact layout of a file, inserting arbitrary bytes between
sections, etc. But most of them can be done with a post-link binary
editing tool (such as `objcopy`).
- It looks like there are two things that truly cannot be done by a
post-link editing tool: (a) mapping input sections to output
sections, and (b) applying relocations.
From the above observation, I believe we need to provide only the
following features instead of the entire linker script language:
- A method to specify how input sections are mapped to output
sections, and
- a method to set addresses to output sections, so that relocations
are applied based on desired addresses.
I believe everything else can be done with a post-link binary editing
tool.
### Details
- As we aim to the 1-second goal for Chromium, every millisecond
counts. We can't ignore the latency of process exit. If we mmap a
lot of files, \_exit(2) is not instantaneous but takes a few hundred
milliseconds because the kernel has to clean up a lot of
resources. As a workaround, we should organize the linker command as
two processes; the first process forks the second process, and the
second process does the actual work. As soon as the second process
writes a result file to a filesystem, it notifies the first process,
and the first process exits. The second process can take time to
exit, because it is not an interactive process.
- At least on Linux, it looks like the filesystem's performance to
allocate new blocks to a new file is the limiting factor when
creating a new large file and filling its contents using mmap.
If you already have a large file in the buffer cache, writing to it is
much faster than creating a new fresh file and writing to it.
Based on this observation, mold overwrites to an existing
executable file if exists. My quick benchmark showed that I could
save 300 milliseconds when creating a 2 GiB output file.
Linux doesn't allow to open an executable for writing if it is
running (you'll get a "text busy" error if you attempt). mold
falls back to the usual way if it fails to open an output file.
- The output from the linker should be deterministic for the sake of
[build reproducibility](https://en.wikipedia.org/wiki/Reproducible_builds)
and ease of debugging. This might add a little bit of overhead to
the linker, but that shouldn't be too much.
- A .build-id, a unique ID embedded to an output file, is usually
computed by applying a cryptographic hash function (e.g. SHA-1) to
an output file. This is a slow step, but we can speed it up by
splitting a file into small chunks, computing SHA-1 for each chunk,
and then computing SHA-1 of the concatenated SHA-1 hashes
(i.e. constructing a [Merkle
Tree](https://en.wikipedia.org/wiki/Merkle_tree) of height 2).
Modern x86 processors have purpose-built instructions for SHA-1 and
can compute SHA-1 pretty quickly at about 2 GiB/s. Using 16
cores, a build-id for a 2 GiB executable can be computed in 60 to 70
milliseconds.
- BFD, gold, and lld support section garbage collection. That is, a
linker runs a mark-sweep garbage collection on an input graph, where
sections are vertices and relocations are edges, to discard all
sections that are not reachable from the entry point symbol
(i.e. `_start`) or a few other root sections. In mold, we are using
multiple threads to mark sections concurrently.
- Similarly, BFD, gold an lld support Identical Comdat Folding (ICF)
as yet another size optimization. ICF merges two or more read-only
sections that happen to have the same contents and relocations.
To do that, we have to find isomorphic subgraphs from larger graphs.
I implemented a new algorithm for mold, which is 5x faster than lld
to do ICF for Chromium (from 5 seconds to 1 second).
- [Intel Threading Building
Blocks](https://github.com/oneapi-src/oneTBB) (TBB) is a good
library for parallel execution and has several concurrent
containers. We are particularly interested in using
`parallel_for_each` and `concurrent_hash_map`.
- TBB provides `tbbmalloc` which works better for multi-threaded
applications than the glib'c malloc, but it looks like
[jemalloc](https://github.com/jemalloc/jemalloc) and
[mimalloc](https://github.com/microsoft/mimalloc) are a little bit
more scalable than `tbbmalloc`.
### Size of the problem
When linking Chrome, a linker reads 3,430,966,844 bytes of data in
total. The data contains the following items:
| Data item | Number
| ------------------------ | ------
| Object files | 30,723
| Public undefined symbols | 1,428,149
| Mergeable strings | 1,579,996
| Comdat groups | 9,914,510
| Regular sections¹ | 10,345,314
| Public defined symbols | 10,512,135
| Symbols | 23,953,607
| Sections | 27,543,225
| Relocations against SHF_ALLOC sections | 39,496,375
| Relocations | 62,024,719
¹ Sections that have to be copied from input object files to an
output file. Sections that contain relocations or symbols are for
example excluded.
### Internals
In this section, I'll explain the internals of mold linker.
#### A brief history of Unix and the Unix linker
Conceptually, what a linker does is pretty simple. A compiler compiles
a fragment of a program (a single source file) into a fragment of
machine code and data (an object file, which typically has the .o
extension), and a linker stitches them together into a single
executable or a shared library image.
In reality, modern linkers for Unix-like systems are much more
complicated than the naive understanding because they have gradually
gained one feature at a time over the 50 years history of Unix, and
they are now something like a bag of lots of miscellaneous features in
which none of the features is more important than the others. It is
very easy to miss the forest for the trees, since for those who don't
know the details of the Unix linker, it is not clear which feature is
essential and which is not.
That being said, one thing is clear that at any point of Unix history,
a Unix linker has a coherent feature set for the Unix of that age. So,
let me entangle the history to see how the operating system, runtime,
and linker have gained features that we see today. That should give
you an idea of why a particular feature has been added to a linker in the
first place.
1. Original Unix didn't support shared libraries, and a program was
always loaded to a fixed address. An executable was something like
a memory dump that was just loaded to a particular address by the
kernel. After loading, the kernel started executing the program by
setting the instruction pointer to a particular address.
The most essential feature for any linker is relocation processing.
The original Unix linker of course supported that. Let me explain
what that is.
Individual object files are inevitably incomplete as a program,
because when a compiler created them, it only see a part of an
entire program. For example, if an object file contains a function
call that refers to another object file, the `call` instruction in the
object cannot be complete, as the compiler has no idea as to what
is the called function's address. To deal with this, the compiler
emits a placeholder value (typically just zero) instead of a real
address and leaves metadata in an object file saying "fix offset X
of this file with an address of Y". That metadata is called
"relocation". Relocations are typically processed by the linker.
It is easy for a linker to apply relocations for the original Unix
because a program is always loaded to a fixed address. It exactly
knows the addresses of all functions and data when linking a
program.
Static library support, which is still an important feature of Unix
linker, also dates back to this early period of Unix history.
To understand what it is, imagine that you are trying to compile
a program for the early Unix. You don't want to waste time to
compile libc functions every time you compile your program (the
computers of the era were incredibly slow), so you have already
placed each libc function into a separate source file and compiled
them individually. That means you have object files for each libc
function, e.g., printf.o, scanf.o, atoi.o, write.o, etc.
Given this configuration, all you have to do to link your program
against libc functions is to pick up the right set of libc object
files and give them to the linker along with the object files of your
program. But, keeping the linker command line in sync with the
libc functions you are using in your program is bothersome. You can
be conservative; you can specify all libc object files to the
command line, but that leads to program bloat because the linker
unconditionally link all object files given to it no matter whether
they are used or not. So, a new feature was added to the linker to
fix the problem. That is the static library, which is also called
the archive file.
An archive file is just a bundle of object files, just like zip
file but in an uncompressed form. An archive file typically has the
.a file extension and named after its contents. For example, the
archive file containing all libc objects is named `libc.a`.
If you pass an archive file along with other object files to the
linker, the linker pulls out an object file from the archive _only
when_ it is referenced by other object files. In other words,
unlike object files directly given to a linker, object files
wrapped in an archive are not linked to the output by default.
An archive works as a supplement to complete your program.
Even today, you can still find a libc archive file. Run `ar t
/usr/lib/x86_64-linux-gnu/libc.a` on Linux should give you a list
of object files in the libc archive.
2. In the '80s, Sun Microsystems, a leading commercial Unix vendor at the
time, added shared library support to their Unix variant, SunOS.
(This section is incomplete.)
### Concurrency strategy
In this section, I'll explain the high-level concurrency strategy of
mold.
In most places, mold adopts data parallelism. That is, we have a huge
number of pieces of data of the same kind, and we process each of them
individually using parallel for-loop. For example, after identifying
the exact set of input object files, we need to scan all relocation
tables to determine the sizes of .got and .plt sections. We do that
using a parallel for-loop. The granularity of parallel processing in
this case is the relocation table.
Data parallelism is very efficient and scalable because there's no
need for threads to communicate with each other while working on each
element of data. In addition to that, data parallelism is easy to
understand, as it is just a for-loop in which multiple iterations may
be executed in parallel. We don't use high-level communication or
synchronization mechanisms such as channels, futures, promises,
latches or something like that in mold.
In some cases, we need to share a little bit of data between threads
while executing a parallel for-loop. For example, the loop to scan
relocations turns on "requires GOT" or "requires PLT" flags in a
symbol. Symbol is a shared resource, and writing to them from multiple
threads without synchronization is unsafe. To deal with it, we made
the flag an atomic variable.
The other common pattern you can find in mold which is build on top of
the parallel for-loop is the map-reduce pattern. That is, we run a
parallel for-loop on a large data set to produce a small data set and
process the small data set with a single thread. Let me take a
build-id computation as an example. Build-id is typically computed by
applying a cryptographic hash function such as SHA-1 on a linker's
output file. To compute it, we first consider an output as a sequence
of 1 MiB blocks and compute a SHA-1 hash for each block in parallel.
Then, we concatenate the SHA-1 hashes and compute a SHA-1 hash on the
hashes to get a final build-id.
Finally, we use concurrent hashmap at a few places in mold. Concurrent
hashmap is a hashmap to which multiple threads can safely insert items
in parallel. We use it in the symbol resolution stage, for example.
To resolve symbols, we basically have to throw in all defined symbols
into a hash table, so that we can find a matching defined symbol for
an undefined symbol by name. We do the hash table insertion from a
parallel for-loop which iterates over a list of input files.
Overall, even though mold is highly scalable, it succeeded to avoid
complexties you often find in complex parallel programs. From high
level, mold just serially executes the linker's internal passes one by
one. Each pass is parallelized using parallel for-loops.
### Rejected ideas
In this section, I'll explain the alternative designs I currently do
not plan to implement and why I turned them down.
- Placing variable-length sections at end of an output file and start
copying file contents before fixing the output file layout
Idea: Fixing the layout of regular sections seems easy, and if we
place them at beginning of a file, we can start copying their
contents from their input files to an output file. While copying
file contents, we can compute the sizes of variable-length sections
such as .got or .plt and place them at end of the file.
Reason for rejection: I did not choose this design because I doubt
if it could actually shorten link time and I think I don't need it
anyway.
The linker has to de-duplicate comdat sections (i.e. inline
functions that are included in multiple object files), so we
cannot compute the layout of regular sections until we resolve all
symbols and de-duplicate comdats. That takes a few hundred
milliseconds. After that, we can compute the sizes of
variable-length sections in less than 100 milliseconds. It's quite
fast, so it doesn't seem to make much sense to proceed without
fixing the final file layout.
The other reason to reject this idea is because there's good a
chance for this idea to have a negative impact on linker's overall
performance. If we copy file contents before fixing the layout, we
can't apply relocations to them while copying because symbol
addresses are not available yet. If we fix the file layout first, we
can apply relocations while copying, which is effectively zero-cost
due to a very good data locality. On the other hand, if we apply
relocations long after we copy file contents, it's pretty expensive
because section contents are very likely to have been evicted from
CPU cache.
- Incremental linking
Idea: Incremental linking is a technique to patch a previous linker's
output file so that only functions or data that are updated from the
previous build are written to it. It is expected to significantly
reduce the amount of data copied from input files to an output file
and thus speed up linking. GNU BFD and gold linkers support it.
Reason for rejection: I turned it down because it (1) is
complicated, (2) doesn't seem to speed it up that much and (3) has
several practical issues. Let me explain each of them.
First, incremental linking for real C/C++ programs is not as easy as
one might think. Let me take malloc as an example. malloc is usually
defined by libc, but you can implement it in your program, and if
that's the case, the symbol `malloc` will be resolved to your
function instead of the one in libc. If you include a library that
defines malloc (such as libjemalloc or libtbbmallc) before libc,
their malloc will override libc's malloc.
Assume that you are using a nonstandard malloc. What if you remove
your malloc from your code, or remove `-ljemalloc` from your
Makefile? The linker has to include a malloc from libc, which may
include more object files to satisfy its dependencies. Such code
change can affect the entire program rather than just replacing one
function. The same is true for adding malloc to your program. Making
a local change doesn't necessarily result in a local change in the
binary level. It can easily have cascading effects.
Some ELF fancy features make incremental linking even harder to
implement. Take the weak symbol as an example. If you define `atoi`
as a weak symbol in your program, and if you are not using `atoi`
at all in your program, that symbol will be resolved to address
0. But if you start using some libc function that indirectly calls
`atoi`, then `atoi` will be included in your program, and your weak
symbol will be resolved to that function. I don't know how to
efficiently fix up a binary for this case.
This is a hard problem, so existing linkers don't try too hard to
solve it. For example, IIRC, gold falls back to full link if any
function is removed from a previous build. If you want to not annoy
users in the fallback case, you need to make full link fast anyway.
Second, incremental linking itself has an overhead. It has to detect
updated files, patch an existing output file and write additional
data to an output file for future incremental linking. GNU gold, for
instance, takes almost 30 seconds on my machine to do a null
incremental link (i.e. no object files are updated from a previous
build) for chrome. It's just too slow.
Third, there are other practical issues in incremental linking. It's
not reproducible, so your binary isn't going to be the same as other
binaries even if you are compiling the same source tree using the
same compiler toolchain. Or, it is complex and there might be a bug
in it. If something doesn't work correctly, "remove --incremental
from your Makefile and try again" could be a piece of advice, but
that isn't ideal.
So, all in all, incremental linking is tricky. I wanted to make full
link as fast as possible, so that we don't have to think about how
to work around the slowness of full link.
- Defining a completely new file format and use it
Idea: Sometimes, the ELF file format itself seems to be a limiting
factor in improving the linker's performance. We might be able to make a
far better one if we create a new file format.
Reason for rejection: I rejected the idea because it apparently has
a practical issue (backward compatibility issue) and also doesn't
seem to improve the performance of linkers that much. As clearly
demonstrated by mold, we can create a fast linker for ELF. I believe
ELF isn't that bad, after all. The semantics of the existing Unix
linkers, such as the name resolution algorithm or the linker script,
have slowed the linkers down, but that's not a problem of the file
format itself.
- Watching object files using inotify(2)
Idea: When mold is running as a daemon for preloading, use
inotify(2) to watch file system updates so that it can reload files
as soon as they are updated.
Reason for rejection: Just like the maximum number of files you can
simultaneously open, the maximum number of files you can watch using
inotify(2) isn't that large. Maybe just a single instance of mold is
fine with inotify(2), but it may fail if you run multiple of it.
The other reason for not doing it is because mold is quite fast
without it anyway. Invoking stat(2) on each file for file update
check takes less than 100 milliseconds for Chrome, and if most of
the input files are not updated, parsing updated files takes almost
no time.
+55
View File
@@ -0,0 +1,55 @@
This page explains the following warning message and how to fix it.
mold emits this message when it sees an object file that may not be
compatible with mold.
```
mold: warning: foo.o: this file may cause a segmentation fault because it requires an executable stack. See https://github.com/rui314/mold/tree/main/docs/execstack.md for more info.
```
# Background
On modern computers, the stack area (to which local variables are
stored) cannot contain executable code. If the control reaches the
stack area, the CPU refuses to execute any code there and the program
is usually terminated due to segmentation fault.
This is a security measure. The stack area used to be executable (old
CPUs generally execute any code as long as it is in a readable memory
region), but that provided an easy attack vector to a malicious user.
They wrote executable code to the stack area using some buffer
overflow bug and jumped there to run arbitrary code in a remote server
process.
To prevent this type of attack, the stack area is no longer executable
since the early 2000s. On Linux, the stack's executable-ness is
controlled by a bit in an executable, and the loader respects that
bit. The bit is set by the linker.
GCC had (and still has) a feature that depends on the executable
stack, so they invented a way to tell the linker to mark the stack
executable. Specifically, if an object file contains a
`.note.GNU-stack` section with the `SHF_EXECSTACK` bit, GNU linker
silently makes the stack of an output file executable.
But the GNU linker's behavior is dangerous. If you accidentally link
an object file that has that marker section, the entire stack area
silently becomes executable, disabling the security mechanism.
Therefore, mold simply ignores that marker section. If you are using
mold, you need to explicitly pass `-z execstack` to the linker to make
the stack executable.
# What caused this issue?
You are likely to use GCC's [Nested
Functions](https://gcc.gnu.org/onlinedocs/gcc/Nested-Functions.html)
feature which still depends on the executable stack.
# How to fix it?
If you know what you are doing, pass `-z execstack` to mold. Beware
that this will significantly weaken your program's security.
If you don't want to pass `-z execstack`, rewrite your code so that
your code does not depend on the executable stack.
+158
View File
@@ -0,0 +1,158 @@
The very concept of linking is simple: a compiler compiles a piece of
source code into an object file (a file containing machine code), and
a linker combines object files into a single executable or a shared
library file. However, the actual implementation of the linker for
modern systems is much more complicated because hardware, operating
system, compiler and linker all have many more features.
In this file, I'll explain random topics in the glossary format that
you need to understand to read mold code.
## DSO
A .so file. Short for Dynamic Shared Object. Often called as a
shared library, a dynamic libray or a shared object as well.
An DSO contains common functions and data that are used by multiple
executables and/or other DSOs. At runtime, a DSO is loaded to a
contiguous region in the virtual address.
## Object file
A .o file. An object file contains machine code and data, but it
cannot be executed because it's not self-contained. For example,
if you compile a C source file containing a call of `printf`,
the actual function code of `printf` is not included in the resulting
object file. You include `stdio.h`, but that teaches the compiler
only about `printf`'s type, and the compiler still don't know what
`printf` actually does. Therefore, it cannot emit code for `printf`.
You need to link an object file with other object file or a shared
library to make it exectuable.
## Virtual address space
A pointer has a value like 0x803020 which is an address of the
pointee. But it doesn't mean that the pointee resides at the
physical memory address 0x803020 on the computer. Modern CPUs
contains so-called Mmeory Management Unit (MMU), and all access to
the memory are first translated by MMU to the physical address.
The address before translation is called the "virtual address".
Unless you are doing the kernel programming, all addresses you
handle are virtual addresses.
The OS kernel controls the MMU so that each process owns the entire
virtual address space. So, even if two process uses the same virtual
address, they don't conflict. They are mapped to different physical
addresses.
The existence of MMU has several implications to the linker. First,
we can link the main executable to a specific address. On process
startup, there's no code or data in the virtual address space, so
the mapping of the main executable always succeed. However, it's not
true to DSOs because they are loaded after the main executable and
possibly other DSOs. Therefore, shared libraries must be linked in a
way that they can be loaded to any address in the virtual address
space.
## Relocation
A piece of information for the linker as to how to link object files
or a dynamic objects.
Object files can refer functions or data in other object files. For
example, if you compile a function which calls a non-local function
`foo`, the resulting code contains something like this:
```
26: e8 00 00 00 00 callq 2b <bar+0xb>
27: R_X86_64_PLT32 foo-0x4
```
The above `callq` is the instruction to call a function at the
machine code level. It's opcode is `0xe8` in x86-64, so the
instruction begins with `0xe8`. The following four bytes are
displacement; that is, the address of the branch target relative to
the end of this `callq` instruction. Notice that the displacement is
0. The compiler couldn't fill the displacement because it has no
idea as to where `foo` will be at runtime. So, the compiler write 0
as a placeholder and instead write a relocation `R_X86_64_PLT32`
with `foo` as its associated symbol. The linker reads this
relocation, computes the offsets between this call instruction and
function `foo` and overwrite the placeholder value 0 with an actual
displacement.
There are many different types of relocations. For example, if you
want to fix up not with a displacement but with an absolute address
of a symbol, you need to use `R_X86_64_ABS64` instead.
## Static library
A .a file. Often called as an archive file or just archive as well.
A static library is a container just like tar or zip. Actually,
there's no technical reason to not use tar or (uncompressed) zip,
but traditionally the .a file format is used by the linker.
A static library contains object files and can be passed to the
linker along with other object files and/or archives.
A linker pulls out object files from an archive only if it is needed
to resolve undefined symbols. In other words, object files in an
archive are not linked by default and used as a complement to supply
missing definitions. This is ideal for a library because you don't
want to link library functions unless you are actually using them.
Contrary to archive files, object files directly given to a linker
are always linked to the output.
To maximize the benefit of archive files, a library often used as a
static library is broken down to small files to separate each
function individually (for example, look at
https://git.musl-libc.org/cgit/musl/tree/src/stdio). By doing this,
you import only used functions.
A static file is created by `ar`, whose command line arguments are
similar to `tar`. A static library contains the symbol table which
offers a quick way to look up an object file for a defined symbol,
but mold does not use the static library's symbol table. mold
doesdn't need a symbol table to exist in an archive, and if exists,
mold just ignores it.
See also: DSO (dynamic library)
## Symbol
A symbol is a label assigned to a specific location in an input file
or an output file. For example, if you define function `foo` and
compile it, the resulting object file contains a symbol `foo`
pointing to the beginning of the machine code for `foo`.
Usually, a symbol name is a function or a variable name. If an
object is anonymous (such the one for a string literal), a compiler
generated a unique symbol, which often starts with `.` to avoid
conflict with user-defined symbols.
For C++, symbol name is a complex "mangled" name. We need to mangle
identifiers because a simple name such as `foo` cannot be uniquely
identify a function or a data in C++, because for example `foo` may
be in a namespace or defined as a static member in some class. If
`foo` is an overloaded function, we need to distinguish different
`foo`s by its type. Therefore, C++ compiler mangles an identifier by
appending nmaepsace names, type information and such so that
different things get different names.
For example, a function `int foo(int)` in a namespace `bar` is
mangled as `_ZN3bar3fooEi`.
A symbol can be either defined or undefined. A defined symbol points
to some location in a file which may contain the function's machine
code or the variable's initial value. An undefined symbol does not
point to anywhere. It needs to be merged with a defined symbol with
the same name at link-time. This merging process is called "name
resolution".
For example, if your program is using `printf`, it usually contains
`printf` as an undefined symbol. You need to link it with `libc.a`
or `libc.so`, which contain a defined symbol of `printf`, to make a
complete program.
BIN
View File
Binary file not shown.

After

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>
+10 -20
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" "October 2026" ""
.TH "MOLD" "1" "May 2026" ""
.SH "NAME"
\fBmold\fR \- a modern linker
.SH "SYNOPSIS"
@@ -48,11 +48,9 @@ Whether good or bad, you should keep these semantics in mind to understand Unix
.P
\fBmold\fR does not have any host\-specific default settings\. This is contrary to the GNU linkers, for which some configurable values, such as system\-dependent library search paths, are hard\-coded\. \fBmold\fR depends only on its command\-line arguments\.
.SH "OPTION NOTATIONS"
Long options are spelled with two dashes\. Some also accept a single dash, but which ones do is somewhat arbitrary for historical reasons\. \fBmold\fR follows the conventions of other linkers for compatibility\. For example, \fB\-\-export\-dynamic\fR can be spelled \fB\-export\-dynamic\fR, while \fB\-\-execute\-only\fR requires two dashes\.
Multi\-letter long options may precede either a single dash or double dashes, except for those starting with the letter "o"\. For historical reasons, long options beginning with "o" must precede double dashes\.
.P
This can cause unexpected behavior when constructing command\-line options in a script\. For example, you might use \fB"\-e$foo"\fR to set the entry point symbol to the value of \fB$foo\fR\. However, if \fB$foo\fR is \fBxport\-dynamic\fR, the argument becomes \fB\-export\-dynamic\fR and is interpreted as \fB\-\-export\-dynamic\fR\.
.P
To avoid such ambiguities, we recommend always using two dashes for long options and separating a single\-letter option from its argument with a space\. For example, use \fB\-e $foo\fR or \fB\-\-entry=$foo\fR to specify the entry point symbol in a script\.
For example, you can spell \fB\-\-as\-needed\fR as \fB\-as\-needed\fR, but \fB\-\-omagic\fR must not be spelled as \fB\-omagic\fR\. \fB\-omagic\fR will be interpreted not as \fB\-\-omagic\fR but as \fB\-o magic\fR\.
.SH "MOLD\-SPECIFIC OPTIONS"
.TP
\fB\-\-chroot\fR=\fIdir\fR
@@ -154,9 +152,7 @@ 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 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\.
This feature is currently available only on x86\-64\.
.SH "GNU\-COMPATIBLE OPTIONS"
.TP
\fB\-\-help\fR
@@ -197,10 +193,7 @@ 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\. 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\.
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\.
.TP
\fB\-e\fR \fIsymbol\fR, \fB\-\-entry\fR=\fIsymbol\fR:
@@ -265,9 +258,6 @@ 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
@@ -517,20 +507,20 @@ If one memory page contains multiple segments, the page protection bits are set
.IP
\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\. This is the default\.
\fBnoseparate\-code\fR does not add any paddings between segments\.
.TP
\fB\-z defs\fR, \fB\-z undefs\fR
\fB\-z defs\fR, \fB\-z nodefs\fR
Report undefined symbols (even with \fB\-\-shared\fR)\.
.TP
\fB\-z shstk\fR
Enforce shadow stack by turning \fBGNU_PROPERTY_X86_FEATURE_1_SHSTK\fR bit in \fB\.note\.gnu\.property\fR output section\. Shadow stack is part of Intel Control\-flow Enforcement Technology (CET), which is available since Tiger Lake (2020)\.
.TP
\fB\-z start\-stop\-visibility\fR=[ \fBhidden\fR | \fBprotected\fR ]
\fB\-z start_stop_visibility\fR=[ \fBhidden\fR | \fBprotected\fR ]
If a section name is valid as a C identifier (i\.e\., it matches \fB/^[_a\-zA\-Z][_a\-zA\-Z0\-9]*$/\fR), mold creates \fB__start_SECNAME\fR and \fB__stop_SECNAME\fR symbols to mark the beginning and end of the section, where \fBSECNAME\fR is the section name\.
.IP
You can make these marker symbols visible from other ELF modules by passing \fB\-z start\-stop\-visibility=protected\fR\. Default is \fBhidden\fR\.
You can make these marker symbols visible from other ELF modules by passing \fB\-z start_stop_visibility=protected\fR\. Default is \fBhidden\fR\.
.TP
\fB\-z text\fR, \fB\-z notext\fR, \fB\-z textoff\fR
\fBmold\fR by default reports an error if dynamic relocations are created in read\-only sections\. If \fB\-z notext\fR or \fB\-z textoff\fR are given, \fBmold\fR creates such dynamic relocations without reporting an error\. \fB\-z text\fR restores the default behavior\.
+13 -37
View File
@@ -127,21 +127,13 @@ arguments.
## OPTION NOTATIONS
Long options are spelled with two dashes. Some also accept a single dash,
but which ones do is somewhat arbitrary for historical reasons. `mold`
follows the conventions of other linkers for compatibility. For example,
`--export-dynamic` can be spelled `-export-dynamic`, while `--execute-only`
requires two dashes.
Multi-letter long options may precede either a single dash or double dashes,
except for those starting with the letter "o". For historical reasons, long
options beginning with "o" must precede double dashes.
This can cause unexpected behavior when constructing command-line options in
a script. For example, you might use `"-e$foo"` to set the entry point symbol
to the value of `$foo`. However, if `$foo` is `xport-dynamic`, the argument
becomes `-export-dynamic` and is interpreted as `--export-dynamic`.
To avoid such ambiguities, we recommend always using two dashes for long
options and separating a single-letter option from its argument with a
space. For example, use `-e $foo` or `--entry=$foo` to specify the entry
point symbol in a script.
For example, you can spell `--as-needed` as `-as-needed`, but `--omagic` must
not be spelled as `-omagic`. `-omagic` will be interpreted not as `--omagic`
but as `-o magic`.
## MOLD-SPECIFIC OPTIONS
@@ -326,12 +318,7 @@ point symbol in a script.
landing pads with no-ops for non-address-taken functions, reducing the
attack surface.
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.
This feature is currently available only on x86-64.
## GNU-COMPATIBLE OPTIONS
@@ -388,14 +375,7 @@ point symbol in a script.
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. 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.
such as string literals or floating-point literals.
* `-e` _symbol_, `--entry`=_symbol_:
@@ -478,9 +458,6 @@ point symbol in a script.
* `--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
@@ -904,12 +881,11 @@ point symbol in a script.
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. This is the
default.
`noseparate-code` does not add any paddings between segments.
* `-z defs`, `-z undefs`:
* `-z defs`, `-z nodefs`:
Report undefined symbols (even with `--shared`).
* `-z shstk`:
@@ -918,14 +894,14 @@ point symbol in a script.
Control-flow Enforcement Technology (CET), which is available since Tiger
Lake (2020).
* `-z start-stop-visibility`=[ `hidden` | `protected` ]:
* `-z start_stop_visibility`=[ `hidden` | `protected` ]:
If a section name is valid as a C identifier (i.e., it matches
`/^[_a-zA-Z][_a-zA-Z0-9]*$/`), mold creates `__start_SECNAME` and
`__stop_SECNAME` symbols to mark the beginning and end of the section,
where `SECNAME` is the section name.
You can make these marker symbols visible from other ELF modules by passing
`-z start-stop-visibility=protected`. Default is `hidden`.
`-z start_stop_visibility=protected`. Default is `hidden`.
* `-z text`, `-z notext`, `-z textoff`:
`mold` by default reports an error if dynamic relocations are created in
+56
View File
@@ -0,0 +1,56 @@
#!/bin/sh
# This script installs binary packages needed to build mold.
# Feel free to send me a PR if your OS is not on this list.
set -e
. /etc/os-release
set -x
case "$ID" in
ubuntu | pop | linuxmint | debian | raspbian | neon | zorin)
apt-get update
apt-get install -y cmake gcc g++ clang gdb
;;
fedora | fedora-* | amzn | rhel | centos)
dnf install -y gcc-c++ cmake glibc-static libstdc++-static diffutils util-linux tar
;;
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 gawk
;;
gentoo)
emerge-webrsync
FEATURES='getbinpkg binpkg-request-signature' emerge dev-build/cmake
;;
arch | archarm | artix | endeavouros | manjaro | cachyos)
pacman -Sy --needed --noconfirm base-devel cmake util-linux
;;
void)
xbps-install -Sy xbps bash make cmake gcc tar diffutils util-linux
;;
alpine)
apk update
apk add bash make linux-headers cmake gcc g++
;;
clear-linux-os)
swupd update
swupd bundle-add c-basic diffutils
;;
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
;;
*)
echo "Error: don't know anything about build dependencies on $ID-$VERSION_ID"
exit 1
esac
+19
View File
@@ -0,0 +1,19 @@
#!/bin/bash
set -e
. /etc/os-release
set -x
# This script install packages for -DMOLD_ENABLE_QEMU_TESTS=1
# to enable cross-target tests.
#
# Feel free to send me a PR if your OS is not on this list.
case "$ID" in
ubuntu | pop | linuxmint | debian | raspbian)
apt-get install -y qemu-user {gcc,g++}-{i686,aarch64,riscv64,powerpc,powerpc64,powerpc64le,s390x,sparc64,m68k,sh4}-linux-gnu {gcc,g++}-arm-linux-gnueabihf
;;
*)
echo "Error: don't know anything about build dependencies on $ID-$VERSION_ID"
exit 1
esac
-31
View File
@@ -1,31 +0,0 @@
#!/bin/sh
set -e
PREFIX=${PREFIX:-/usr/local}
MOLD_LIBDIR=${MOLD_LIBDIR:-$PREFIX/lib}
srcdir=$(CDPATH= cd "$(dirname "$0")" && pwd)
artifact_dir="${CARGO_TARGET_DIR:-$srcdir/target}/release"
if [ ! -x "$artifact_dir/mold" ] ||
[ ! -f "$artifact_dir/mold-wrapper.so" ]; then
echo "install-mold.sh: release artifacts are missing" >&2
echo "Run 'cargo build --release' first." >&2
exit 1
fi
bindir="$DESTDIR$PREFIX/bin"
libdir="$DESTDIR$MOLD_LIBDIR/mold"
libexecdir="$DESTDIR$PREFIX/libexec/mold"
mandir="$DESTDIR$PREFIX/share/man/man1"
docdir="$DESTDIR$PREFIX/share/doc/mold"
install -d "$bindir" "$libdir" "$libexecdir" "$mandir" "$docdir"
install -m 755 "$artifact_dir/mold" "$bindir"
install -m 755 "$artifact_dir/mold-wrapper.so" "$libdir"
install -m 644 "$srcdir/docs/mold.1" "$mandir"
install -m 644 "$srcdir/LICENSE" "$docdir"
ln -sf mold "$bindir/ld.mold"
ln -sf ../../bin/mold "$libexecdir/ld"
ln -sf mold.1 "$mandir/ld.mold.1"
-68
View File
@@ -1,68 +0,0 @@
#!/bin/sh
# This script installs binary packages needed to test mold.
# Feel free to send me a PR if your OS is not on this list.
set -e
. /etc/os-release
set -x
case "$ID" in
ubuntu | pop | linuxmint | debian | raspbian | neon | zorin)
apt-get update
apt-get install -y curl gcc g++ clang gdb
apt-get install -y qemu-user \
gcc-i686-linux-gnu g++-i686-linux-gnu \
gcc-aarch64-linux-gnu g++-aarch64-linux-gnu \
gcc-riscv64-linux-gnu g++-riscv64-linux-gnu \
gcc-powerpc-linux-gnu g++-powerpc-linux-gnu \
gcc-powerpc64-linux-gnu g++-powerpc64-linux-gnu \
gcc-powerpc64le-linux-gnu g++-powerpc64le-linux-gnu \
gcc-s390x-linux-gnu g++-s390x-linux-gnu \
gcc-sparc64-linux-gnu g++-sparc64-linux-gnu \
gcc-m68k-linux-gnu g++-m68k-linux-gnu \
gcc-sh4-linux-gnu g++-sh4-linux-gnu \
gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf
;;
fedora | fedora-* | amzn | rhel | centos)
dnf install -y curl gcc-c++ glibc-static libstdc++-static diffutils util-linux tar
;;
rocky | ol)
dnf install -y curl gcc-c++ diffutils util-linux
;;
opensuse-*)
zypper install -y bash curl gcc-c++ glibc-devel-static tar diffutils util-linux gawk
;;
gentoo)
emerge-webrsync
FEATURES='getbinpkg binpkg-request-signature' emerge net-misc/curl
;;
arch | archarm | artix | endeavouros | manjaro | cachyos)
pacman -Sy --needed --noconfirm base-devel curl util-linux
;;
void)
xbps-install -Sy xbps bash curl gcc tar diffutils util-linux
;;
alpine)
apk update
apk add bash curl linux-headers gcc g++
;;
clear-linux-os)
swupd update
swupd bundle-add c-basic diffutils
;;
almalinux)
dnf install -y curl gcc-toolset-13-gcc-c++ gcc-toolset-13-libstdc++-devel diffutils
;;
altlinux)
apt-get update
apt-get install -y curl gcc-c++ diffutils util-linux
;;
freebsd)
pkg update
pkg install -y bash binutils curl gcc
;;
*)
echo "Error: don't know anything about test dependencies on $ID-$VERSION_ID"
exit 1
esac
+132
View File
@@ -0,0 +1,132 @@
// 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.find_first_of("*?[") == pat.npos;
}
i64 AhoCorasick::find(std::string_view str) {
if (nodes.empty())
return -1;
i64 idx = 0;
i64 val = -1;
auto walk = [&](u8 c) {
for (i64 j = idx; j != -1; j = nodes[j].suffix_link) {
i64 child = nodes[j].children[c];
if (child != -1) {
idx = child;
val = std::max(val, nodes[child].value);
return;
}
}
idx = 0;
};
walk('\0');
for (u8 c : str)
walk(c);
walk('\0');
return val;
}
bool AhoCorasick::add(std::string_view pat, i64 val) {
assert(can_handle(pat));
if (nodes.empty())
nodes.resize(1);
i64 idx = 0;
auto walk = [&](u8 c) {
if (nodes[idx].children[c] == -1) {
nodes[idx].children[c] = nodes.size();
nodes.resize(nodes.size() + 1);
}
idx = nodes[idx].children[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;
fix_suffix_links(0);
fix_values();
}
void AhoCorasick::fix_suffix_links(i64 idx) {
for (i64 i = 0; i < 256; i++) {
i64 child = nodes[idx].children[i];
if (child == -1)
continue;
i64 j = nodes[idx].suffix_link;
for (; j != -1; j = nodes[j].suffix_link) {
if (nodes[j].children[i] != -1) {
nodes[child].suffix_link = j;
break;
}
}
if (j == -1)
nodes[child].suffix_link = 0;
fix_suffix_links(child);
}
}
void AhoCorasick::fix_values() {
std::queue<i64> queue;
queue.push(0);
do {
i64 idx = queue.front();
queue.pop();
for (i64 child : nodes[idx].children) {
if (child != -1) {
i64 suffix = nodes[child].suffix_link;
nodes[child].value = std::max(nodes[child].value, nodes[suffix].value);
queue.push(child);
}
}
} while (!queue.empty());
}
} // namespace mold
+50
View File
@@ -0,0 +1,50 @@
// 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 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);
}
};
} // namespace mold
+78
View File
@@ -0,0 +1,78 @@
#pragma once
#include "integers.h"
#include <cassert>
#include <vector>
namespace mold {
class BitvectorProxy {
public:
BitvectorProxy(u64 &word, size_t pos)
: word(word), mask(1ULL << pos) {}
BitvectorProxy &operator=(bool val) {
if (val)
word |= mask;
else
word &= ~mask;
return *this;
}
BitvectorProxy &operator=(const BitvectorProxy &other) {
return *this = (bool)other;
}
operator bool() const {
return word & mask;
}
private:
u64 &word;
u64 mask;
};
class Bitvector {
public:
Bitvector() = default;
Bitvector(i64 n) : size(n), words((n + 63) / 64) {}
void resize(i64 n) {
words.clear();
words.resize((n + 63) / 64);
size = n;
}
Bitvector &operator|=(const Bitvector &x) {
assert(size == x.size);
for (i64 i = 0; i < words.size(); i++)
words[i] |= x.words[i];
return *this;
}
Bitvector &operator&=(const Bitvector &x) {
assert(size == x.size);
for (i64 i = 0; i < words.size(); i++)
words[i] &= x.words[i];
return *this;
}
Bitvector &operator<<=(size_t n) {
assert(n == 1);
for (i64 i = words.size() - 1; i > 0; i--)
words[i] = (words[i] << 1) | (words[i - 1] >> 63);
words[0] <<= 1;
return *this;
}
BitvectorProxy operator[](size_t pos) {
assert(pos < size);
return {words[pos / 64], pos % 64};
}
i64 size = 0;
std::vector<u64> words;
};
} // namespace mold
+174
View File
@@ -0,0 +1,174 @@
// This file implements a multi-threaded zlib and zstd compression
// routine.
//
// zlib-compressed data can be merged just by concatenation as long as
// each piece of data is flushed with Z_SYNC_FLUSH. In this file, we
// split input data into multiple shards, compress them individually
// and concatenate them. We then append a header, a trailer and a
// checksum so that the concatenated data is valid zlib-format data.
//
// zstd-compressed data can be merged in the same way.
//
// Using threads to compress data has a downside. Since the dictionary
// is reset on boundaries of shards, compression ratio is sacrificed
// a little bit. However, if a shard size is large enough, that loss
// is negligible in practice.
#include "lib.h"
#include <tbb/parallel_for_each.h>
#include <zlib.h>
#include <zstd.h>
#define CHECK(fn) \
do { \
[[maybe_unused]] int r = (fn); \
assert(r == Z_OK); \
} while (0)
namespace mold {
static constexpr i64 SHARD_SIZE = 1024 * 1024;
Compressor::~Compressor() {
for (std::span<u8> shard : shards)
delete[] shard.data();
}
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;
}
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 = input.data();
// Set an output buffer. deflateBound() returns an upper bound
// on the compression size. +16 for Z_SYNC_FLUSH.
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 = bufsize;
strm.next_out = buf;
CHECK(deflate(&strm, Z_BLOCK));
// This is a workaround for libbacktrace before 2022-04-06.
//
// Zlib is a bit stream, and what Z_SYNC_FLUSH does is to write a
// three bit value indicating the start of an uncompressed data
// block followed by four byte data 00 00 ff ff which indicates that
// the length of the block is zero. libbacktrace uses its own zlib
// inflate routine, and it had a bug that if that particular three
// bit value happens to end at a byte boundary, it accidentally
// skipped the next byte.
//
// In order to avoid triggering that bug, we should avoid calling
// deflate() with Z_SYNC_FLUSH if the current bit position is 5.
// If it's 5, we insert an empty block consisting of 10 bits so
// that the bit position is 7 in the next byte.
//
// https://github.com/ianlancetaylor/libbacktrace/pull/87
int nbits = 0;
deflatePending(&strm, Z_NULL, &nbits);
if (nbits == 5)
CHECK(deflatePrime(&strm, 10, 2));
CHECK(deflate(&strm, Z_SYNC_FLUSH));
deflateEnd(&strm);
return {buf, (size_t)(bufsize - strm.avail_out)};
}
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) {
std::span<u8> in = inputs[i];
adlers[i] = adler32(1, in.data(), in.size());
shards[i] = zlib_compress(in, level);
});
// Combine checksums
checksum = adlers[0];
for (i64 i = 1; i < inputs.size(); i++)
checksum = adler32_combine(checksum, adlers[i], inputs[i].size());
// Comput the total size
compressed_size = 8; // the header and the trailer
for (std::span<u8> &shard : shards)
compressed_size += shard.size();
}
void ZlibCompressor::write_to(u8 *buf) {
// Write a zlib-format header
buf[0] = 0x78;
buf[1] = 0x9c;
// Copy compressed data
std::vector<i64> offsets(shards.size());
offsets[0] = 2; // +2 for the header
for (i64 i = 1; i < shards.size(); i++)
offsets[i] = offsets[i - 1] + shards[i - 1].size();
tbb::parallel_for((i64)0, (i64)shards.size(), [&](i64 i) {
memcpy(buf + offsets[i], shards[i].data(), shards[i].size());
});
// Write a trailer
u8 *end = buf + compressed_size;
end[-6] = 3;
end[-5] = 0;
*(ub32 *)(end - 4) = checksum;
}
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));
return {buf, sz};
}
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], level);
});
compressed_size = 0;
for (std::span<u8> &shard : shards)
compressed_size += shard.size();
}
void ZstdCompressor::write_to(u8 *buf) {
// Copy compressed data
std::vector<i64> offsets(shards.size());
for (i64 i = 1; i < shards.size(); i++)
offsets[i] = offsets[i - 1] + shards[i - 1].size();
tbb::parallel_for((i64)0, (i64)shards.size(), [&](i64 i) {
memcpy(buf + offsets[i], shards[i].data(), shards[i].size());
});
}
} // namespace mold
+29
View File
@@ -0,0 +1,29 @@
#define MOLD_VERSION "@mold_VERSION@"
#define MOLD_LIBDIR "@CMAKE_INSTALL_FULL_LIBDIR@"
#define MOLD_FIRST_TARGET @MOLD_FIRST_TARGET@
#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
+60
View File
@@ -0,0 +1,60 @@
#include "lib.h"
#include <tbb/parallel_for_each.h>
#include <zlib.h>
namespace mold {
// This function "forges" a CRC. That is, given the current and a desired
// CRC32 value, crc32_solve() returns a binary blob to add to the end of
// the original data to yield the desired CRC. Trailing garbage is ignored
// by many bianry file formats, so you can create a file with a desired
// CRC using crc32_solve(). We need it for --separate-debug-file.
std::vector<u8> crc32_solve(u32 current, u32 desired) {
constexpr u32 poly = 0xedb88320;
u32 x = ~desired;
// Each iteration computes x = (x * x^-1) mod poly.
for (i64 i = 0; i < 32; i++) {
x = std::rotl(x, 1);
x ^= (x & 1) * (poly << 1);
}
x ^= ~current;
std::vector<u8> out(4);
out[0] = x;
out[1] = x >> 8;
out[2] = x >> 16;
out[3] = x >> 24;
return out;
}
// Compute a CRC for given data in parallel
u32 compute_crc32(u32 crc, u8 *buf, i64 len) {
struct Shard {
u8 *buf;
i64 len;
u32 crc;
};
constexpr i64 shard_size = 1024 * 1024; // 1 MiB
std::vector<Shard> shards;
while (len > 0) {
i64 sz = std::min(len, shard_size);
shards.push_back({buf, sz, 0});
buf += sz;
len -= sz;
}
tbb::parallel_for_each(shards, [](Shard &shard) {
shard.crc = crc32(0, shard.buf, shard.len);
});
for (Shard &shard : shards)
crc = crc32_combine(crc, shard.crc, shard.len);
return crc;
}
} // namespace mold
+42
View File
@@ -0,0 +1,42 @@
#include "lib.h"
#include <cstdlib>
#ifndef _WIN32
#include <cxxabi.h>
#endif
#include "../third-party/rust-demangle/rust-demangle.h"
namespace mold {
std::optional<std::string_view> demangle_cpp(std::string_view name) {
static thread_local char *buf;
static thread_local size_t buflen;
// TODO(cwasser): Actually demangle Symbols on Windows using e.g.
// `UnDecorateSymbolName` from Dbghelp, maybe even Itanium symbols?
#ifndef _WIN32
if (name.starts_with("_Z")) {
int status;
char *p = abi::__cxa_demangle(std::string(name).c_str(), buf, &buflen, &status);
if (status == 0) {
buf = p;
return p;
}
}
#endif
return {};
}
std::optional<std::string_view> demangle_rust(std::string_view name) {
static thread_local char *buf;
free(buf);
buf = rust_demangle(std::string(name).c_str(), 0);
if (buf)
return buf;
return {};
}
} // namespace mold
+42
View File
@@ -0,0 +1,42 @@
#include "lib.h"
#include <filesystem>
#include <sys/stat.h>
#ifdef __APPLE__
# include <mach-o/dyld.h>
#endif
#ifdef __FreeBSD__
# include <sys/sysctl.h>
#endif
namespace mold {
// Returns the path of the mold executable itself
std::string get_self_path() {
#if __APPLE__ || _WIN32
fprintf(stderr, "mold: get_self_path is not supported");
exit(1);
#elif __FreeBSD__
// /proc may not be mounted on FreeBSD. The proper way to get the
// current executable's path is to use sysctl(2).
int mib[4];
mib[0] = CTL_KERN;
mib[1] = KERN_PROC;
mib[2] = KERN_PROC_PATHNAME;
mib[3] = -1;
size_t size;
sysctl(mib, 4, NULL, &size, NULL, 0);
std::string path;
path.resize(size);
sysctl(mib, 4, path.data(), &size, NULL, 0);
return path;
#else
return std::filesystem::read_symlink("/proc/self/exe").string();
#endif
}
} // namespace mold
+69
View File
@@ -0,0 +1,69 @@
#!/bin/bash
#
# 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
# locally and run their test suites without any hassle.
#
# You can get a complete list of Gentoo packages availalbe for testing
# with the following command:
#
# podman run --rm mold-gentoo emerge --color n -s '' | \
# perl -ne 'next unless m!^\*\s+(\S+/\S+)!; print "$1\n"'
package="$1"
if [ "$package" = "" ]; then
echo "Usage: $0 gentoo-package-name"
exit 1
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
fi
git_hash=$(./dist/mold --version | perl -ne '/\((\w+)/; print $1;')
if [ "$package" = dev-libs/concurrencykit ]; then
echo "Skipping known broken package: $package"
exit 0
fi
# Build a given package in Podman
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"
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"
dir=gentoo/$git_hash
mkdir -p "$dir"/success "$dir"/failure
$podman chrt --idle 0 nice -n 19 bash -c "$cmd1 && $cmd2 && $cmd3" >& "$dir"/"$filename".mold
if [ $? = 0 ]; then
mv "$dir"/"$filename".mold "$dir"/success
else
mv "$dir"/"$filename".mold "$dir"/failure
fi
$podman chrt --idle 0 nice -n 19 bash -c "$cmd2 && $cmd3" >& "$dir"/"$filename".ld
if [ $? = 0 ]; then
mv "$dir"/"$filename".ld "$dir"/success
else
mv "$dir"/"$filename".ld "$dir"/failure
fi
+256
View File
@@ -0,0 +1,256 @@
// This file implements a glob matcher that can run multiple glob patterns
// against an input string. mold uses the glob matcher for symbol name
// patterns in a version script or a dynamic list file. Since we may need to
// match hundreds of glob patterns against millions of symbol names, the
// speed of the matcher is very important.
//
// The pattern match implemented in this file is NFA-based, although the
// cost of the function is O(n*m), where n is the number of NFA states and m
// is the length of the input string. We do not use recursion or
// backtracking, unlike a generic NFA-based regular expression matcher. This
// is doable because glob patterns are very limited subsets of regexes.
//
// Here is the explanation of the algorithm. Observe that the only "tricky"
// meta-character in a glob pattern is "*", which matches zero or more
// characters. Other characters and meta-characters always match a single
// input character. So the key of the algorithm is to handle "*" efficiently.
//
// We can represent a glob pattern "a*b*" with three NFA states: q_start, q1
// and q_accept, with the following transition functions:
//
// δ(q_start, "a") = q1
// δ(q1, <any character>) = q1
// δ(q1, "b") = q_accept
// δ(q_accept, <any character>) = q_accept
//
// We can construct such an NFA in a straightforward manner. We maintain NFA
// states as a list, with the initial contents being the start state. Each
// character except for "*" creates a new NFA state, adds a transition from
// the last state in the list to the new one, and appends the new state at
// the end of the list. "*" sets the "is_star" flag on the last NFA state.
// The flag indicates that the state machine can remain in the state for any
// input character.
//
// An NFA constructed this way doesn't have any complicated loops,
// ε-transitions, or anything like that. Each state has only one incoming
// edge. The only loops in the state transition are the self-loops on states
// followed by a "*". Aside from that, the state machine progresses linearly
// from the start state to the accept state.
//
// Each state of an NFA can be represented by a single bit. If a bit is 1,
// the non-deterministic state machine is in that state. Otherwise, it's
// not. Observe that a state with the "is_star" flag will continued to be 1
// once it becomes 1, since the state machine can loop over the state on any
// input character.
//
// With that observation, we can represent an NFA with a bit vector of N
// bits, where N is the number of NFA states. For each input character, bit
// M becomes 1 if
//
// - bit M-1 is 1 and there's a transition from state_{M-1} to state_M
// with the given character, or
// - bit M is 1 and state_M's "is_star" flag is 1.
//
// Initially, the 0th bit is 1 for the start state. At each step, the bits
// propagate from least significant to most significant positions, at most
// one bit at a time. If the most significant bit is 1 after the entire
// input has been processed, the string matches.
//
// This propagation can be implemented with bitwise OR, bitwise AND, and a
// one-bit bit shift on the bit vector. All these operations are very cheap.
//
// We can combine multiple glob matchers into a single matcher by simply
// concatenating the bit vectors of their state machines.
#include "lib.h"
#include <cstring>
namespace mold {
static std::vector<MultiGlob::State> parse_glob(std::string_view pat) {
std::vector<MultiGlob::State> vec(1);
while (!pat.empty()) {
u8 c = pat[0];
pat = pat.substr(1);
std::bitset<256> chars;
switch (c) {
case '*':
vec.back().is_star = true;
continue;
case '?':
chars.set();
break;
case '\\':
if (pat.empty())
return {};
chars[pat[0]] = true;
pat = pat.substr(1);
break;
case '[': {
// Here are a few bracket pattern examples:
//
// [abc]: a, b or c
// [$\]!]: $, ] or !
// [a-czg-i]: a, b, c, z, g, h, or i
// [!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.
bool negate = false;
bool closed = false;
if (!pat.empty() && (pat[0] == '!' || pat[0] == '^')) {
negate = true;
pat = pat.substr(1);
}
while (!pat.empty()) {
if (pat[0] == ']') {
pat = pat.substr(1);
closed = true;
break;
}
if (pat[0] == '\\') {
pat = pat.substr(1);
if (pat.empty())
return {};
}
if (pat.size() >= 3 && pat[1] == '-') {
u8 start = pat[0];
u8 end = pat[2];
pat = pat.substr(3);
if (end == '\\') {
if (pat.empty())
return {};
end = pat[0];
pat = pat.substr(1);
}
if (end < start)
return {};
for (i64 i = start; i <= end; i++)
chars[i] = true;
} else {
chars[pat[0]] = true;
pat = pat.substr(1);
}
}
if (!closed)
return {};
if (negate)
chars.flip();
break;
}
default:
chars[c] = true;
break;
}
vec.push_back({chars, false});
}
return vec;
}
// Instead of returning just a match/no match boolean value, our glob
// matcher returns an integer value associated with each given pattern.
// If multiple patterns match at the same time, the largest associated
// value will be returned by find().
bool MultiGlob::add(std::string_view pat, i64 val) {
std::vector<State> vec = parse_glob(pat);
if (vec.empty())
return false;
patterns.push_back({std::move(vec), val});
return true;
}
void MultiGlob::compile() {
if (patterns.empty())
return;
ranges::stable_sort(patterns, ranges::greater(), &GlobPattern::value);
std::vector<State> states;
for (GlobPattern &p : patterns)
append(states, p.states);
i64 sz = states.size();
start_states.resize(sz);
for (i64 pos = 0; GlobPattern &p : patterns) {
start_states[pos] = true;
pos += p.states.size();
}
star_mask.resize(sz);
for (i64 i = 0; i < sz; i++)
if (states[i].is_star)
star_mask[i] = true;
for (i64 i = 0; i < 256; i++) {
char_mask[i].resize(sz);
for (i64 j = 0; j < sz; j++)
if (states[j].incoming_edge[i])
char_mask[i][j] = true;
}
}
i64 MultiGlob::find(std::string_view str) {
if (patterns.empty())
return -1;
Bitvector bits = start_states;
Bitvector tmp;
for (u8 c : str) {
// This is equivalent to
//
// bits = (bits & star_mask) | ((bits << 1) & char_mask[c])
//
// but we update the existing objects in place to avoid allocating
// temporary objects.
tmp = bits;
tmp &= star_mask;
bits <<= 1;
bits &= char_mask[c];
bits |= tmp;
}
for (i64 pos = 0; GlobPattern &p : patterns) {
pos += p.states.size();
if (bits[pos - 1])
return p.value;
}
return -1;
}
bool Glob::add(std::string_view pat, i64 val) {
assert(val >= 0);
assert(!is_compiled);
// 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);
return multi_glob.add(pat, val);
}
i64 Glob::find(std::string_view str) {
std::call_once(once, [&] {
multi_glob.compile();
aho_corasick.compile();
is_compiled = true;
});
return std::max(multi_glob.find(str), aho_corasick.find(str));
}
} // namespace mold
+20
View File
@@ -0,0 +1,20 @@
// This file implements HyperLogLog algorithm, which estimates
// the number of unique items in a given multiset.
//
// For more info, read
// https://engineering.fb.com/2018/12/13/data-infrastructure/hyperloglog
#include "lib.h"
#include <cmath>
namespace mold {
i64 HyperLogLog::get_cardinality() const {
double z = 0;
for (i64 val : buckets)
z += std::ldexp(1.0, -val);
return ALPHA * NBUCKETS * NBUCKETS / z;
}
} // namespace mold
+127
View File
@@ -0,0 +1,127 @@
// This file defines integral types for file input/output. We need to use
// these types instead of the plain integers (such as uint32_t or int32_t)
// when reading from/writing to an mmap'ed file area for the following
// reasons:
//
// 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
// 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 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 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__
# define __LITTLE_ENDIAN__ 1
# elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
# define __BIG_ENDIAN__ 1
# else
# error "unknown host byte order"
# endif
#endif
namespace mold {
template <typename T, bool is_le, int size = sizeof(T)>
class Integer {
public:
constexpr Integer() = default;
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 (!is_native)
v = bswap(v);
memcpy(buf, &v, size);
}
}
operator T() const {
if (size == 3) {
if (is_le)
return buf[2] << 16 | buf[1] << 8 | buf[0];
return buf[0] << 16 | buf[1] << 8 | buf[2];
}
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) { auto x = *this; ++*this; return x; }
Integer &operator--() { return *this = *this - 1; }
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:
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>;
using ul16 = Integer<u16, true>;
using ul24 = Integer<u32, true, 3>;
using ul32 = Integer<u32, true>;
using ul64 = Integer<u64, true>;
using ib16 = Integer<i16, false>;
using ib32 = Integer<i32, false>;
using ib64 = Integer<i64, false>;
using ub16 = Integer<u16, false>;
using ub24 = Integer<u32, false, 3>;
using ub32 = Integer<u32, false>;
using ub64 = Integer<u64, false>;
} // namespace mold
+709
View File
@@ -0,0 +1,709 @@
#pragma once
#include "atomics.h"
#include "integers.h"
#include "bitvector.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 u64 combine_hash(u64 a, u64 b) {
return a ^ (b + 0x9e3779b9 + (a << 6) + (a >> 2));
}
//
// 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. mmap() is faster than
// malloc()+memset(), and MAP_POPULATE pre-faults pages here to
// avoid CoW faults and TLB shootdowns under concurrent inserts.
#ifdef _WIN32
entries = (Entry *)_aligned_malloc(bufsize, alignof(Entry));
memset((void *)entries, 0, bufsize);
#elif MAP_POPULATE
entries = (Entry *)mmap(nullptr, bufsize, PROT_READ | PROT_WRITE,
MAP_ANONYMOUS | MAP_PRIVATE | MAP_POPULATE, -1, 0);
#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 (begin < end && 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];
};
//
// aho-corasick.cc
//
class AhoCorasick {
public:
bool add(std::string_view pat, i64 val);
bool empty() const { return nodes.empty(); }
void compile();
i64 find(std::string_view str);
static bool can_handle(std::string_view str);
private:
struct TrieNode {
TrieNode() { children.fill(-1); }
i64 value = -1;
i32 suffix_link = -1;
std::array<i32, 256> children;
};
void fix_suffix_links(i64 idx);
void fix_values();
std::vector<TrieNode> nodes;
};
//
// glob.cc
//
class MultiGlob {
public:
bool add(std::string_view pat, i64 val);
bool empty() const { return patterns.empty(); }
void compile();
i64 find(std::string_view str);
struct State {
std::bitset<256> incoming_edge;
bool is_star = false;
};
struct GlobPattern {
std::vector<State> states;
i64 value = -1;
};
private:
std::vector<GlobPattern> patterns;
Bitvector start_states;
Bitvector star_mask;
Bitvector char_mask[256];
};
class Glob {
public:
bool add(std::string_view pat, i64 val);
bool empty() const { return multi_glob.empty() && aho_corasick.empty(); }
i64 find(std::string_view str);
private:
std::once_flag once;
bool is_compiled = false;
MultiGlob multi_glob;
AhoCorasick aho_corasick;
};
//
// 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);
//
// 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;
protected:
std::vector<std::span<u8>> shards;
};
class ZlibCompressor : public Compressor {
public:
ZlibCompressor(u8 *buf, i64 size, i64 level);
void write_to(u8 *buf) override;
private:
u32 checksum = 0;
};
class ZstdCompressor : public Compressor {
public:
ZstdCompressor(u8 *buf, i64 size, i64 level);
void write_to(u8 *buf) override;
};
//
// 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;
};
} // namespace mold
+118
View File
@@ -0,0 +1,118 @@
#include "lib.h"
#include <functional>
#include <iomanip>
#include <ios>
#include <tbb/concurrent_vector.h>
#ifdef _WIN32
#include <windows.h>
#else
#include <sys/resource.h>
#include <sys/time.h>
#endif
namespace mold {
i64 Counter::get_value() {
return values.combine(std::plus());
}
void Counter::print() {
ranges::stable_sort(instances, {}, [](Counter *x) { return x->get_value(); });
for (Counter *c : instances)
std::cout << std::setw(20) << std::right << c->name
<< "=" << c->get_value() << "\n";
}
static i64 now_nsec() {
return (i64)std::chrono::steady_clock::now().time_since_epoch().count();
}
static std::pair<i64, i64> get_usage() {
#ifdef _WIN32
auto to_nsec = [](FILETIME t) -> i64 {
return (((u64)t.dwHighDateTime << 32) + (u64)t.dwLowDateTime) * 100;
};
FILETIME creation, exit, kernel, user;
GetProcessTimes(GetCurrentProcess(), &creation, &exit, &kernel, &user);
return {to_nsec(user), to_nsec(kernel)};
#else
auto to_nsec = [](struct timeval t) -> i64 {
return (i64)t.tv_sec * 1'000'000'000 + t.tv_usec * 1'000;
};
struct rusage ru;
getrusage(RUSAGE_SELF, &ru);
return {to_nsec(ru.ru_utime), to_nsec(ru.ru_stime)};
#endif
}
TimerRecord::TimerRecord(std::string name, TimerRecord *parent)
: name(name), parent(parent) {
start = now_nsec();
std::tie(user, sys) = get_usage();
if (parent)
parent->children.push_back(this);
}
void TimerRecord::stop() {
if (stopped)
return;
stopped = true;
i64 user2;
i64 sys2;
std::tie(user2, sys2) = get_usage();
end = now_nsec();
user = user2 - user;
sys = sys2 - sys;
}
static void print_rec(TimerRecord &rec, i64 indent) {
printf(" % 8.3f % 8.3f % 8.3f %s%s\n",
((double)rec.user / 1'000'000'000),
((double)rec.sys / 1'000'000'000),
(((double)rec.end - rec.start) / 1'000'000'000),
std::string(indent * 2, ' ').c_str(),
rec.name.c_str());
ranges::stable_sort(rec.children, {}, &TimerRecord::start);
for (TimerRecord *child : rec.children)
print_rec(*child, indent + 1);
}
void print_timer_records(
tbb::concurrent_vector<std::unique_ptr<TimerRecord>> &records) {
for (i64 i = records.size() - 1; i >= 0; i--)
records[i]->stop();
for (i64 i = 0; i < records.size(); i++) {
TimerRecord &inner = *records[i];
if (inner.parent)
continue;
for (i64 j = i - 1; j >= 0; j--) {
TimerRecord &outer = *records[j];
if (outer.start <= inner.start && inner.end <= outer.end) {
inner.parent = &outer;
outer.children.push_back(&inner);
break;
}
}
}
std::cout << " User System Real Name\n";
for (std::unique_ptr<TimerRecord> &rec : records)
if (!rec->parent)
print_rec(*rec, 0);
std::cout << std::flush;
}
} // namespace mold
+20
View File
@@ -0,0 +1,20 @@
#include "lib.h"
#include <random>
namespace mold {
void get_random_bytes(u8 *buf, i64 size) {
std::random_device rand;
i64 i = 0;
for (; i < size - 4; i += 4) {
u32 val = rand();
memcpy(buf + i, &val, 4);
}
u32 val = rand();
memcpy(buf + i, &val, size - i);
}
} // namespace mold
+144
View File
@@ -0,0 +1,144 @@
// This is a header-only C++20 implementation of SipHash based on the
// reference implementation. To use, just copy this header file into
// your project and #include it.
//
// https://github.com/rui314/siphash/blob/main/siphash.h
#include <bit>
#include <cstdint>
#include <cstring>
template <int C_ROUNDS, int D_ROUNDS, int OUTLEN>
class SipHashTmpl {
public:
static_assert(OUTLEN == 64 || OUTLEN == 128);
SipHashTmpl(void *key) {
uint64_t k0 = read64(key);
uint64_t k1 = read64((char *)key + 8);
v0 = 0x736f6d6570736575 ^ k0;
v1 = 0x646f72616e646f6d ^ k1;
v2 = 0x6c7967656e657261 ^ k0;
v3 = 0x7465646279746573 ^ k1;
if (OUTLEN == 128)
v1 ^= 0xee;
}
void update(void *msgp, int64_t msglen) {
char *msg = (char *)msgp;
sum += msglen;
if (buflen) {
if (buflen + msglen < 8) {
memcpy(buf + buflen, msg, msglen);
buflen += msglen;
return;
}
int j = 8 - buflen;
memcpy(buf + buflen, msg, j);
compress(read64(buf));
msg += j;
msglen -= j;
buflen = 0;
}
while (msglen >= 8) {
compress(read64(msg));
msg += 8;
msglen -= 8;
}
memcpy(buf, msg, msglen);
buflen = msglen;
}
void finish(void *out) {
memset(buf + buflen, 0, 8 - buflen);
compress(((uint64_t)sum << 56) | read64(buf));
v2 ^= (OUTLEN == 128) ? 0xee : 0xff;
finalize();
write64(out, v0 ^ v1 ^ v2 ^ v3);
if (OUTLEN == 128) {
v1 ^= 0xdd;
finalize();
write64((char *)out + 8, v0 ^ v1 ^ v2 ^ v3);
}
}
static void hash(void *out, void *key, void *in, int inlen) {
SipHashTmpl<C_ROUNDS, D_ROUNDS, OUTLEN> h(key);
h.update(in, inlen);
h.finish(out);
}
private:
uint64_t v0, v1, v2, v3;
uint8_t buf[8];
uint8_t buflen = 0;
uint8_t sum = 0;
uint64_t read64(void *loc) {
uint64_t val;
memcpy(&val, loc, 8);
if (std::endian::native == std::endian::big)
val = bswap(val);
return val;
}
void write64(void *loc, uint64_t val) {
if (std::endian::native == std::endian::big)
val = bswap(val);
memcpy(loc, &val, 8);
}
uint64_t bswap(uint64_t val) {
return ((val << 56) & 0xff00000000000000) |
((val << 40) & 0x00ff000000000000) |
((val << 24) & 0x0000ff0000000000) |
((val << 8) & 0x000000ff00000000) |
((val >> 8) & 0x00000000ff000000) |
((val >> 24) & 0x0000000000ff0000) |
((val >> 40) & 0x000000000000ff00) |
((val >> 56) & 0x00000000000000ff);
}
void round() {
v0 += v1;
v1 = std::rotl(v1, 13);
v1 ^= v0;
v0 = std::rotl(v0, 32);
v2 += v3;
v3 = std::rotl(v3, 16);
v3 ^= v2;
v0 += v3;
v3 = std::rotl(v3, 21);
v3 ^= v0;
v2 += v1;
v1 = std::rotl(v1, 17);
v1 ^= v2;
v2 = std::rotl(v2, 32);
}
void compress(uint64_t m) {
v3 ^= m;
for (int i = 0; i < C_ROUNDS; i++)
round();
v0 ^= m;
}
void finalize() {
for (int i = 0; i < D_ROUNDS; i++)
round();
}
};
using SipHash = SipHashTmpl<2, 4, 64>;
using SipHash128 = SipHashTmpl<2, 4, 128>;
using SipHash13 = SipHashTmpl<1, 3, 64>;
using SipHash13_128 = SipHashTmpl<1, 3, 128>;
+119
View File
@@ -0,0 +1,119 @@
// This file contains functions to create a tar file.
#include "lib.h"
#ifdef _WIN32
# define ftruncate _chsize_s
#endif
namespace mold {
static constexpr i64 BLOCK_SIZE = 512;
// A tar file consists of one or more Ustar header followed by data.
// Each Ustar header represents a single file in an archive.
//
// tar is an old file format, and its `name` field is only 100 bytes long.
// If `name` is longer than 100 bytes, we can emit a PAX header before a
// Ustar header to store a long filename.
//
// For simplicity, we always emit a PAX header even for a short filename.
struct UstarHeader {
char name[100];
char mode[8];
char uid[8];
char gid[8];
char size[12];
char mtime[12];
char checksum[8];
char typeflag[1];
char linkname[100];
char magic[6];
char version[2];
char uname[32];
char gname[32];
char devmajor[8];
char devminor[8];
char prefix[155];
char pad[12];
};
static_assert(sizeof(UstarHeader) == BLOCK_SIZE);
static void finalize(UstarHeader &hdr) {
memset(hdr.checksum, ' ', sizeof(hdr.checksum));
memcpy(hdr.magic, "ustar", 5);
memcpy(hdr.version, "00", 2);
// Compute checksum
int sum = 0;
for (i64 i = 0; i < sizeof(hdr); i++)
sum += ((u8 *)&hdr)[i];
// We need to convince the compiler that sum isn't too big to silence
// -Werror=format-truncation.
if (sum >= 01'000'000)
unreachable();
snprintf(hdr.checksum, sizeof(hdr.checksum), "%06o", sum);
}
static std::string encode_path(std::string basedir, std::string path) {
path = path_clean(basedir + "/" + path);
// Construct a string which contains something like
// "16 path=foo/bar\n" where 16 is the size of the string
// including the size string itself.
i64 len = std::string(" path=\n").size() + path.size();
i64 total = std::to_string(len).size() + len;
total = std::to_string(total).size() + len;
return std::to_string(total) + " path=" + path + "\n";
}
std::unique_ptr<TarWriter>
TarWriter::open(std::string output_path, std::string basedir) {
FILE *out = fopen(output_path.c_str(), "w");
if (!out)
return nullptr;
return std::unique_ptr<TarWriter>(new TarWriter(out, basedir));
}
TarWriter::~TarWriter() {
fclose(out);
}
void TarWriter::append(std::string path, std::string_view data) {
// Write PAX header
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';
finalize(pax);
fwrite(&pax, sizeof(pax), 1, out);
// Write pathname
fwrite(attr.data(), attr.size(), 1, out);
fseek(out, align_to(ftell(out), BLOCK_SIZE), SEEK_SET);
// 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);
// Write file contents
fwrite(data.data(), data.size(), 1, out);
fseek(out, align_to(ftell(out), BLOCK_SIZE), SEEK_SET);
// A tar file must ends with two empty blocks
(void)!ftruncate(fileno(out), ftell(out) + BLOCK_SIZE * 2);
}
} // namespace mold
+33
View File
@@ -0,0 +1,33 @@
# 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)
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()
endif()
endif()
# Create new file contents and update a given file if necessary.
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)
if(NOT "${NEW_CONTENTS}" STREQUAL "${OLD_CONTENTS}")
file(WRITE "${OUTPUT_FILE}" "${NEW_CONTENTS}")
endif()
else()
file(WRITE "${OUTPUT_FILE}" "${NEW_CONTENTS}")
endif()
-2
View File
@@ -1,2 +0,0 @@
[toolchain]
channel = "stable"
-2
View File
@@ -1,2 +0,0 @@
use_small_heuristics = "Max"
use_field_init_shorthand = true
+916
View File
@@ -0,0 +1,916 @@
// ARM32 is a bit special from the linker's viewpoint because ARM
// processors support two different instruction encodings: Thumb and
// ARM (in a narrower sense). Thumb instructions are either 16 bits or
// 32 bits, while ARM instructions are all 32 bits. Feature-wise,
// Thumb is a subset of ARM, so not all ARM instructions are
// representable in Thumb.
//
// ARM processors originally supported only ARM instructions. Thumb
// instructions were later added to increase code density.
//
// ARM processors runs in either ARM mode or Thumb mode. The mode can
// be switched using BX (branch and mode exchange)-family instructions.
// We need to use that instructions to, for example, call a function
// encoded in Thumb from a function encoded in ARM. Sometimes, the
// linker even has to emit interworking thunk code to switch mode.
//
// ARM instructions are aligned to 4 byte boundaries. Thumb are to 2
// byte boundaries. So the least significant bit of a function address
// is always 0.
//
// To distinguish Thumb functions from ARM fucntions, the LSB of a
// function address is repurposed as a boolean flag. If the LSB is 0,
// the function referred to by the address is encoded in ARM;
// otherwise, Thumb.
//
// For example, if a symbol `foo` is of type STT_FUNC and has value
// 0x2001, `foo` is a function using Thumb instructions whose address
// is 0x2000 (not 0x2001, as Thumb instructions are always 2-byte
// aligned). Likewise, if a function pointer has value 0x2001, it
// refers a Thumb function at 0x2000.
//
// 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 = MOLD_TARGET;
template <>
i64 get_addend(u8 *loc, const ElfRel<E> &rel) {
U32<E> *arm = (U32<E> *)loc;
U16<E> *thm = (U16<E> *)loc;
switch (rel.r_type) {
case R_ARM_ABS32:
case R_ARM_REL32:
case R_ARM_BASE_PREL:
case R_ARM_GOTOFF32:
case R_ARM_GOT_PREL:
case R_ARM_GOT_BREL:
case R_ARM_TLS_GD32:
case R_ARM_TLS_LDM32:
case R_ARM_TLS_LDO32:
case R_ARM_TLS_IE32:
case R_ARM_TLS_LE32:
case R_ARM_TLS_GOTDESC:
case R_ARM_TARGET1:
case R_ARM_TARGET2:
return (I32<E>)*arm;
case R_ARM_THM_JUMP8:
return sign_extend(thm[0], 8) << 1;
case R_ARM_THM_JUMP11:
return sign_extend(thm[0], 11) << 1;
case R_ARM_THM_JUMP19: {
// https://developer.arm.com/documentation/ddi0597/2024-12/Base-Instructions/B--Branch-
u32 S = bit(thm[0], 10);
u32 J2 = bit(thm[1], 11);
u32 J1 = bit(thm[1], 13);
u32 imm6 = bits(thm[0], 5, 0);
u32 imm11 = bits(thm[1], 10, 0);
u32 val = (S << 20) | (J2 << 19) | (J1 << 18) | (imm6 << 12) | (imm11 << 1);
return sign_extend(val, 21);
}
case R_ARM_THM_CALL:
case R_ARM_THM_JUMP24:
case R_ARM_THM_TLS_CALL: {
// https://developer.arm.com/documentation/ddi0597/2024-12/Base-Instructions/BL--BLX--immediate---Branch-with-Link-and-optional-Exchange--immediate--
u32 S = bit(thm[0], 10);
u32 J1 = bit(thm[1], 13);
u32 J2 = bit(thm[1], 11);
u32 I1 = !(J1 ^ S);
u32 I2 = !(J2 ^ S);
u32 imm10 = bits(thm[0], 9, 0);
u32 imm11 = bits(thm[1], 10, 0);
u32 val = (S << 24) | (I1 << 23) | (I2 << 22) | (imm10 << 12) | (imm11 << 1);
return sign_extend(val, 25);
}
case R_ARM_CALL:
case R_ARM_JUMP24:
case R_ARM_PLT32:
case R_ARM_TLS_CALL:
return sign_extend(*arm, 24) << 2;
case R_ARM_MOVW_PREL_NC:
case R_ARM_MOVW_ABS_NC:
case R_ARM_MOVT_PREL:
case R_ARM_MOVT_ABS: {
// https://developer.arm.com/documentation/ddi0597/2024-12/Base-Instructions/MOV--MOVS--immediate---Move--immediate--
u32 imm4 = bits(*arm, 19, 16);
u32 imm12 = bits(*arm, 11, 0);
u32 val = (imm4 << 12) | imm12;
return sign_extend(val, 16);
}
case R_ARM_PREL31:
return sign_extend(*arm, 31);
case R_ARM_THM_MOVW_PREL_NC:
case R_ARM_THM_MOVW_ABS_NC:
case R_ARM_THM_MOVT_PREL:
case R_ARM_THM_MOVT_ABS: {
// https://developer.arm.com/documentation/ddi0597/2024-12/Base-Instructions/MOVT--Move-Top-
u32 imm4 = bits(thm[0], 3, 0);
u32 i = bit(thm[0], 10);
u32 imm3 = bits(thm[1], 14, 12);
u32 imm8 = bits(thm[1], 7, 0);
u32 val = (imm4 << 12) | (i << 11) | (imm3 << 8) | imm8;
return sign_extend(val, 16);
}
default:
return 0;
}
}
static void write_arm_mov(u8 *loc, u32 val) {
u32 imm12 = bits(val, 11, 0);
u32 imm4 = bits(val, 15, 12);
*(U32<E> *)loc = (*(U32<E> *)loc & 0xfff0'f000) | (imm4 << 16) | imm12;
}
static void write_thm_b21(u8 *loc, u32 val) {
u32 S = bit(val, 20);
u32 J2 = bit(val, 19);
u32 J1 = bit(val, 18);
u32 imm6 = bits(val, 17, 12);
u32 imm11 = bits(val, 11, 1);
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;
}
static void write_thm_b25(u8 *loc, u32 val) {
u32 S = bit(val, 24);
u32 I1 = bit(val, 23);
u32 I2 = bit(val, 22);
u32 J1 = !I1 ^ S;
u32 J2 = !I2 ^ S;
u32 imm10 = bits(val, 21, 12);
u32 imm11 = bits(val, 11, 1);
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;
}
static void write_thm_mov(u8 *loc, u32 val) {
u32 imm4 = bits(val, 15, 12);
u32 i = bit(val, 11);
u32 imm3 = bits(val, 10, 8);
u32 imm8 = bits(val, 7, 0);
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;
}
template <>
void write_addend(u8 *loc, i64 val, const ElfRel<E> &rel) {
switch (rel.r_type) {
case R_ARM_NONE:
break;
case R_ARM_ABS32:
case R_ARM_REL32:
case R_ARM_BASE_PREL:
case R_ARM_GOTOFF32:
case R_ARM_GOT_PREL:
case R_ARM_GOT_BREL:
case R_ARM_TLS_GD32:
case R_ARM_TLS_LDM32:
case R_ARM_TLS_LDO32:
case R_ARM_TLS_IE32:
case R_ARM_TLS_LE32:
case R_ARM_TLS_GOTDESC:
case R_ARM_TARGET1:
case R_ARM_TARGET2:
*(U32<E> *)loc = val;
break;
case R_ARM_THM_JUMP8:
*(U16<E> *)loc = (*(U16<E> *)loc & 0xff00) | bits(val, 8, 1);
break;
case R_ARM_THM_JUMP11:
*(U16<E> *)loc = (*(U16<E> *)loc & 0xf800) | bits(val, 11, 1);
break;
case R_ARM_THM_CALL:
case R_ARM_THM_JUMP24:
case R_ARM_THM_TLS_CALL:
write_thm_b25(loc, val);
break;
case R_ARM_CALL:
case R_ARM_JUMP24:
case R_ARM_PLT32:
*(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:
case R_ARM_MOVT_PREL:
case R_ARM_MOVT_ABS:
write_arm_mov(loc, val);
break;
case R_ARM_PREL31:
*(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:
case R_ARM_THM_MOVT_PREL:
case R_ARM_THM_MOVT_ABS:
write_thm_mov(loc, val);
break;
default:
unreachable();
}
}
template <>
void write_plt_header(Context<E> &ctx, u8 *buf) {
constexpr ul32 insn[] = {
0xe52d'e004, // push {lr}
0xe59f'e004, // ldr lr, 2f
0xe08f'e00e, // 1: add lr, pc, lr
0xe5be'f008, // ldr pc, [lr, #8]!
0x0000'0000, // 2: .word .got.plt - 1b - 8
0x0000'0000, // (padding)
0x0000'0000, // (padding)
0x0000'0000, // (padding)
};
memcpy(buf, insn, sizeof(insn));
*(U32<E> *)(buf + 16) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr - 16;
}
static constexpr ul32 plt_entry[] = {
0xe59f'c004, // 1: ldr ip, 2f
0xe08c'c00f, // add ip, ip, pc
0xe59c'f000, // ldr pc, [ip]
0x0000'0000, // 2: .word sym@GOT - 1b
};
template <>
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
memcpy(buf, plt_entry, sizeof(plt_entry));
*(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));
*(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) {
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
switch (rel.r_type) {
case R_NONE:
break;
case R_ARM_ABS32:
*(U32<E> *)loc = val;
break;
case R_ARM_REL32:
*(U32<E> *)loc = val - this->shdr.sh_addr - offset;
break;
default:
Fatal(ctx) << "unsupported relocation in .eh_frame: " << rel;
}
}
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++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE || rel.r_type == R_ARM_V4BX)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
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 G = sym.get_got_idx(ctx) * sizeof(Word<E>);
u64 GOT = ctx.got->shdr.sh_addr;
auto check = [&](i64 val, i64 lo, i64 hi) {
check_range(ctx, i, val, lo, hi);
};
auto get_thumb_thunk_addr = [&] { return sym.get_thunk_addr(ctx, P); };
auto get_arm_thunk_addr = [&] { return sym.get_thunk_addr(ctx, P) + 4; };
auto get_tlsdesc_trampoline_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) {
case R_ARM_ABS32:
case R_ARM_TARGET1:
break;
case R_ARM_REL32:
*(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.
*(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.
i64 val1 = S + A - P;
i64 val2 = align_to(S + A - P, 4);
if (T && is_int(val1, 25)) {
*(U16<E> *)(loc + 2) |= 0x1000; // BL
write_thm_b25(loc, val1);
} else if (!T && is_int(val2, 25)) {
*(U16<E> *)(loc + 2) &= ~0x1000; // BLX
write_thm_b25(loc, val2);
} else {
*(U16<E> *)(loc + 2) |= 0x1000; // BL
write_thm_b25(loc, get_thumb_thunk_addr() + A - P);
}
break;
}
case R_ARM_BASE_PREL:
*(U32<E> *)loc = GOT + A - P;
break;
case R_ARM_GOTOFF32:
*(U32<E> *)loc = ((S + A) | T) - GOT;
break;
case R_ARM_GOT_PREL:
case R_ARM_TARGET2:
*(U32<E> *)loc = GOT + G + A - P;
break;
case R_ARM_GOT_BREL:
*(U32<E> *)loc = G + A;
break;
case R_ARM_CALL: {
if (sym.is_remaining_undef_weak()) {
*(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 = ((*(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) {
*(U32<E> *)loc = 0xfa00'0000; // BLX
*(U32<E> *)loc |= (bit(val, 1) << 24) | bits(val, 25, 2);
} else {
*(U32<E> *)loc = 0xeb00'0000; // BL
*(U32<E> *)loc |= bits(val, 25, 2);
}
} else {
*(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()) {
*(U32<E> *)loc = 0xe320'f000; // NOP
break;
}
// These relocs refers to a B (unconditional branch) instruction.
// Unlike BL or BLX, we can't rewrite B to BX in place when the
// processor mode switch is required because BX doesn't takes an
// immediate; it takes only a register. So if mode switch is
// required, we jump to a linker-synthesized thunk which does the
// job with a longer code sequence.
i64 val = S + A - P;
if (T || !is_int(val, 26))
val = get_arm_thunk_addr() + A - P;
*(U32<E> *)loc = (*(U32<E> *)loc & 0xff00'0000) | bits(val, 25, 2);
break;
}
case R_ARM_PLT32:
if (sym.is_remaining_undef_weak()) {
*(U32<E> *)loc = 0xe320'f000; // NOP
} else {
u64 val = (T ? get_arm_thunk_addr() : S) + A - P;
*(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);
*(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);
*(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);
write_thm_b21(loc, S + A - P);
break;
case R_ARM_THM_JUMP24: {
if (sym.is_remaining_undef_weak()) {
*(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))
val = get_thumb_thunk_addr() + A - P;
write_thm_b25(loc, val);
break;
}
case R_ARM_MOVW_PREL_NC:
write_arm_mov(loc, ((S + A) | T) - P);
break;
case R_ARM_MOVW_ABS_NC:
write_arm_mov(loc, (S + A) | T);
break;
case R_ARM_THM_MOVW_PREL_NC:
write_thm_mov(loc, ((S + A) | T) - P);
break;
case R_ARM_PREL31:
check(S + A - P, -(1LL << 30), 1LL << 30);
*(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);
break;
case R_ARM_MOVT_PREL:
write_arm_mov(loc, (S + A - P) >> 16);
break;
case R_ARM_THM_MOVT_PREL:
write_thm_mov(loc, (S + A - P) >> 16);
break;
case R_ARM_MOVT_ABS:
write_arm_mov(loc, (S + A) >> 16);
break;
case R_ARM_THM_MOVT_ABS:
write_thm_mov(loc, (S + A) >> 16);
break;
case R_ARM_TLS_GD32:
*(U32<E> *)loc = sym.get_tlsgd_addr(ctx) + A - P;
break;
case R_ARM_TLS_LDM32:
*(U32<E> *)loc = ctx.got->get_tlsld_addr(ctx) + A - P;
break;
case R_ARM_TLS_LDO32:
*(U32<E> *)loc = S + A - ctx.dtp_addr;
break;
case R_ARM_TLS_IE32:
*(U32<E> *)loc = sym.get_gottp_addr(ctx) + A - P;
break;
case R_ARM_TLS_LE32:
*(U32<E> *)loc = S + A - ctx.tp_addr;
break;
case R_ARM_TLS_GOTDESC:
// ARM32 TLSDESC uses the following code sequence to materialize
// a TP-relative address in r0.
//
// ldr r0, .L2
// .L1: bl foo
// R_ARM_TLS_CALL
// .L2: .word foo + . - .L1
// R_ARM_TLS_GOTDESC
//
// We may relax the instructions to the following if its TP-relative
// address is known at link-time
//
// ldr r0, .L2
// .L1: nop
// ...
// .L2: .word foo(tpoff)
//
// or to the following if the TP-relative address is known at
// process startup time.
//
// ldr r0, .L2
// .L1: ldr r0, [pc, r0]
// ...
// .L2: .word foo(gottpoff) + . - .L1
if (sym.has_tlsdesc(ctx)) {
// A is odd if the corresponding TLS_CALL is Thumb.
*(U32<E> *)loc = sym.get_tlsdesc_addr(ctx) - P + A - ((A & 1) ? 6 : 4);
} else if (sym.has_gottp(ctx)) {
*(U32<E> *)loc = sym.get_gottp_addr(ctx) - P + A - ((A & 1) ? 5 : 8);
} else {
*(U32<E> *)loc = S - ctx.tp_addr;
}
break;
case R_ARM_TLS_CALL:
if (sym.has_tlsdesc(ctx)) {
*(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)) {
*(U32<E> *)loc = 0xe79f'0000; // ldr r0, [pc, r0]
} else {
*(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);
*(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.
*(U16<E> *)loc = 0x4478; // add r0, pc
*(U16<E> *)(loc + 2) = 0x6800; // ldr r0, [r0]
} else {
*(U32<E> *)loc = 0x8000'f3af; // nop.w
}
break;
default:
Error(ctx) << *this << ": unknown relocation: " << rel;
}
}
}
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];
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
SectionFragment<E> *frag;
i64 frag_addend;
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
u64 S = frag ? frag->get_addr(ctx) : sym.get_addr(ctx);
u64 A = frag ? frag_addend : get_addend(*this, rel);
switch (rel.r_type) {
case R_ARM_ABS32:
if (std::optional<u64> val = get_tombstone(sym, frag))
*(U32<E> *)loc = *val;
else
*(U32<E> *)loc = S + A;
break;
case R_ARM_TLS_LDO32:
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;
break;
}
}
}
template <>
void InputSection<E>::scan_relocations(Context<E> &ctx) {
assert(shdr().sh_flags & SHF_ALLOC);
std::span<const ElfRel<E>> rels = get_rels(ctx);
// Scan relocations
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
if (sym.is_ifunc())
sym.flags |= NEEDS_GOT | NEEDS_PLT;
switch (rel.r_type) {
case R_ARM_MOVW_ABS_NC:
case R_ARM_THM_MOVW_ABS_NC:
scan_absrel(ctx, sym, rel);
break;
case R_ARM_THM_CALL:
case R_ARM_CALL:
case R_ARM_JUMP24:
case R_ARM_PLT32:
case R_ARM_THM_JUMP24:
if (sym.is_imported)
sym.flags |= NEEDS_PLT;
break;
case R_ARM_GOT_PREL:
case R_ARM_GOT_BREL:
case R_ARM_TARGET2:
sym.flags |= NEEDS_GOT;
break;
case R_ARM_MOVT_PREL:
case R_ARM_THM_MOVT_PREL:
case R_ARM_PREL31:
scan_pcrel(ctx, sym, rel);
break;
case R_ARM_TLS_GD32:
sym.flags |= NEEDS_TLSGD;
break;
case R_ARM_TLS_LDM32:
ctx.needs_tlsld = true;
break;
case R_ARM_TLS_IE32:
sym.flags |= NEEDS_GOTTP;
break;
case R_ARM_TLS_CALL:
case R_ARM_THM_TLS_CALL:
scan_tlsdesc(ctx, sym);
break;
case R_ARM_TLS_LE32:
check_tlsle(ctx, sym, rel);
break;
case R_ARM_ABS32:
case R_ARM_TARGET1:
case R_ARM_MOVT_ABS:
case R_ARM_THM_MOVT_ABS:
case R_ARM_REL32:
case R_ARM_BASE_PREL:
case R_ARM_GOTOFF32:
case R_ARM_THM_JUMP8:
case R_ARM_THM_JUMP11:
case R_ARM_THM_JUMP19:
case R_ARM_MOVW_PREL_NC:
case R_ARM_THM_MOVW_PREL_NC:
case R_ARM_TLS_LDO32:
case R_ARM_V4BX:
case R_ARM_TLS_GOTDESC:
break;
default:
Error(ctx) << *this << ": unknown relocation: " << rel;
}
}
}
template <>
void Thunk<E>::copy_buf(Context<E> &ctx) {
// TLS trampoline code. ARM32's TLSDESC is designed so that this
// common piece of code is factored out from object files to reduce
// output size. Since no one provide, the linker has to synthesize it.
constexpr ul32 hdr[] = {
0xe08e'0000, // add r0, lr, r0
0xe590'1004, // ldr r1, [r0, #4]
0xe12f'ff11, // bx r1
0xe320'f000, // nop
};
// This is a range extension and mode switch thunk.
// It has two entry points: +0 for Thumb and +4 for ARM.
constexpr u8 entry[] = {
// .thumb
0x78, 0x47, // bx pc # jumps to 1f
0xc0, 0x46, // nop
// .arm
0x00, 0xc0, 0x9f, 0xe5, // 1: ldr ip, 3f
0x0f, 0xf0, 0x8c, 0xe0, // 2: add pc, ip, pc
0x00, 0x00, 0x00, 0x00, // 3: .word sym - 2b
};
static_assert(E::thunk_hdr_size == sizeof(hdr));
static_assert(E::thunk_size == sizeof(entry));
u8 *base = ctx.buf + output_section.shdr.sh_offset + offset;
memcpy(base, hdr, sizeof(hdr));
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));
*(U32<E> *)(buf + 12) = S - P - 16;
}
}
template <>
u64 get_eflags(Context<E> &ctx) {
if constexpr (E::is_le)
return EF_ARM_EABI_VER5;
else
return EF_ARM_EABI_VER5 | EF_ARM_BE8;
}
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();
if (osec && osec->shdr.sh_type == SHT_ARM_EXIDX) {
auto *sec = new Arm32ExidxSection(*osec);
ctx.extra.exidx = sec;
ctx.chunks[i] = sec;
ctx.chunk_pool.emplace_back(sec);
for (InputSection<E> *isec : osec->members)
isec->is_alive = false;
break;
}
}
}
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
}
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;
}
// Returns the end of the text segment
static u64 get_text_end(Context<E> &ctx) {
u64 ret = 0;
for (Chunk<E> *chunk : ctx.chunks)
if (chunk->shdr.sh_flags & SHF_EXECINSTR)
ret = std::max<u64>(ret, chunk->shdr.sh_addr + chunk->shdr.sh_size);
return ret;
}
// ARM executables use an .ARM.exidx section to look up an exception
// handling record for the current instruction pointer. The table needs
// to be sorted by their addresses.
//
// Other target uses .eh_frame_hdr instead for the same purpose.
// I don't know why only ARM uses the different mechanism, but it's
// likely that it's due to some historical reason.
//
// This function returns contents of .ARM.exidx.
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
// the followings:
//
// 1. CANTUNWIND indicating that there's no unwinding info for the function,
// 2. a compact unwinding record encoded into a 32-bit value, or
// 3. a 31-bit relative address which points to a larger record in
// the .ARM.extab section.
//
// CANTUNWIND is value 1. The most significant bit is set in (2) but
// not in (3). So we can distinguished them just by looking at a value.
const u32 CANTUNWIND = 1;
struct Entry {
U32<E> addr;
U32<E> val;
};
// We reserve one extra slot for the sentinel
i64 num_entries = output_section.shdr.sh_size / sizeof(Entry) + 1;
std::vector<u8> buf(num_entries * sizeof(Entry));
Entry *ent = (Entry *)buf.data();
// Write section contents to the buffer
output_section.shdr.sh_addr = this->shdr.sh_addr;
output_section.write_to(ctx, buf.data());
// Fill in sentinel fields
u64 sentinel_addr = this->shdr.sh_addr + sizeof(Entry) * (num_entries - 1);
ent[num_entries - 1].addr = get_text_end(ctx) - sentinel_addr;
ent[num_entries - 1].val = CANTUNWIND;
// Entry's addresses are relative to themselves. In order to sort
// records by address, we first translate them so that the addresses
// are relative to the beginning of the section.
auto is_relative = [](u32 val) {
return val != CANTUNWIND && !(val & 0x8000'0000);
};
tbb::parallel_for((i64)0, num_entries, [&](i64 i) {
i64 offset = sizeof(Entry) * i;
ent[i].addr = sign_extend(ent[i].addr, 31) + offset;
if (is_relative(ent[i].val))
ent[i].val = 0x7fff'ffff & (ent[i].val + offset);
});
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 tail = ranges::unique(ent, ent + num_entries, {}, &Entry::val);
num_entries -= tail.size();
buf.resize(num_entries * sizeof(Entry));
// Make addresses relative to themselves.
tbb::parallel_for((i64)0, num_entries, [&](i64 i) {
i64 offset = sizeof(Entry) * i;
ent[i].addr = 0x7fff'ffff & (ent[i].addr - offset);
if (is_relative(ent[i].val))
ent[i].val = 0x7fff'ffff & (ent[i].val - offset);
});
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
+703
View File
@@ -0,0 +1,703 @@
// This file contains ARM64-specific code. Being new, the ARM64's ELF
// psABI doesn't have anything peculiar. ARM64 is a clean RISC
// instruction set that supports PC-relative load/store instructions.
//
// Unlike ARM32, instructions length doesn't vary. All ARM64
// instructions are 4 bytes long.
//
// Branch instructions used for function call can jump within ±128 MiB.
// We need to create range extension thunks to support binaries whose
// .text is larger than that.
//
// Unlike most other targets, the TLSDESC access model is used by default
// for -fPIC to access thread-local variables instead of the less
// efficient GD model. You can still enable GD but it needs the
// -mtls-dialect=trad flag. Since GD is used rarely, we don't need to
// implement GD → LE relaxation.
//
// https://github.com/ARM-software/abi-aa/blob/main/aaelf64/aaelf64.rst
#if MOLD_ARM64LE || MOLD_ARM64BE
#include "mold.h"
namespace mold {
using E = MOLD_TARGET;
static void write_adrp(u8 *buf, u64 val) {
*(ul32 *)buf |= (bits(val, 13, 12) << 29) | (bits(val, 32, 14) << 5);
}
static void write_adr(u8 *buf, u64 val) {
*(ul32 *)buf |= (bits(val, 1, 0) << 29) | (bits(val, 20, 2) << 5);
}
static void write_movn_movz(u8 *buf, i64 val) {
*(ul32 *)buf &= 0b0000'0000'0110'0000'0000'0000'0001'1111;
if (val >= 0)
*(ul32 *)buf |= 0xd280'0000 | (bits(val, 15, 0) << 5); // rewrite to movz
else
*(ul32 *)buf |= 0x9280'0000 | (bits(~val, 15, 0) << 5); // rewrite to movn
}
static u64 page(u64 val) {
return val & 0xffff'ffff'ffff'f000;
}
template <>
void write_plt_header(Context<E> &ctx, u8 *buf) {
constexpr ul32 insn[] = {
0xa9bf'7bf0, // stp x16, x30, [sp,#-16]!
0x9000'0010, // adrp x16, .got.plt[2]
0xf940'0211, // ldr x17, [x16, .got.plt[2]]
0x9100'0210, // add x16, x16, .got.plt[2]
0xd61f'0220, // br x17
0xd420'7d00, // brk
0xd420'7d00, // brk
0xd420'7d00, // brk
};
u64 gotplt = ctx.gotplt->shdr.sh_addr + 16;
u64 plt = ctx.plt->shdr.sh_addr;
memcpy(buf, insn, sizeof(insn));
write_adrp(buf + 4, page(gotplt) - page(plt + 4));
*(ul32 *)(buf + 8) |= bits(gotplt, 11, 3) << 10;
*(ul32 *)(buf + 12) |= (gotplt & 0xfff) << 10;
}
template <>
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
constexpr ul32 insn[] = {
0x9000'0010, // adrp x16, .got.plt[n]
0xf940'0211, // ldr x17, [x16, .got.plt[n]]
0x9100'0210, // add x16, x16, .got.plt[n]
0xd61f'0220, // br x17
};
u64 gotplt = sym.get_gotplt_addr(ctx);
u64 plt = sym.get_plt_addr(ctx);
memcpy(buf, insn, sizeof(insn));
write_adrp(buf, page(gotplt) - page(plt));
*(ul32 *)(buf + 4) |= bits(gotplt, 11, 3) << 10;
*(ul32 *)(buf + 8) |= (gotplt & 0xfff) << 10;
}
template <>
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
constexpr ul32 insn[] = {
0x9000'0010, // adrp x16, GOT[n]
0xf940'0211, // ldr x17, [x16, GOT[n]]
0xd61f'0220, // br x17
0xd420'7d00, // brk
};
u64 got = sym.get_got_pltgot_addr(ctx);
u64 plt = sym.get_plt_addr(ctx);
memcpy(buf, insn, sizeof(insn));
write_adrp(buf, page(got) - page(plt));
*(ul32 *)(buf + 4) |= bits(got, 11, 3) << 10;
}
template <>
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, 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_AARCH64_ABS64:
*(U64<E> *)loc = val;
break;
case R_AARCH64_PREL32:
*(U32<E> *)loc = val - this->shdr.sh_addr - offset;
break;
case R_AARCH64_PREL64:
*(U64<E> *)loc = val - this->shdr.sh_addr - offset;
break;
default:
Fatal(ctx) << "unsupported relocation in .eh_frame: " << rel;
}
}
static bool is_adrp(u8 *loc) {
// https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/ADRP--Form-PC-relative-address-to-4KB-page-
u32 insn = *(ul32 *)loc;
return (bits(insn, 31, 24) & 0b1001'1111) == 0b1001'0000;
}
static bool is_ldr(u8 *loc) {
// https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/LDR--immediate---Load-Register--immediate--
u32 insn = *(ul32 *)loc;
return (bits(insn, 31, 20) & 0b1111'1111'1100) == 0b1111'1001'0100;
}
static bool is_add(u8 *loc) {
// https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/ADD--immediate---Add--immediate--
u32 insn = *(ul32 *)loc;
return (bits(insn, 31, 20) & 0b1111'1111'1100) == 0b1001'0001'0000;
}
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++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
u64 S = sym.get_addr(ctx);
u64 A = rel.r_addend;
u64 P = get_addr() + rel.r_offset;
u64 G = sym.get_got_idx(ctx) * sizeof(Word<E>);
u64 GOT = ctx.got->shdr.sh_addr;
auto check = [&](i64 val, i64 lo, i64 hi) {
check_range(ctx, i, val, lo, hi);
};
switch (rel.r_type) {
case R_AARCH64_ABS64:
break;
case R_AARCH64_LDST8_ABS_LO12_NC:
case R_AARCH64_ADD_ABS_LO12_NC:
*(ul32 *)loc |= bits(S + A, 11, 0) << 10;
break;
case R_AARCH64_LDST16_ABS_LO12_NC:
*(ul32 *)loc |= bits(S + A, 11, 1) << 10;
break;
case R_AARCH64_LDST32_ABS_LO12_NC:
*(ul32 *)loc |= bits(S + A, 11, 2) << 10;
break;
case R_AARCH64_LDST64_ABS_LO12_NC:
*(ul32 *)loc |= bits(S + A, 11, 3) << 10;
break;
case R_AARCH64_LDST128_ABS_LO12_NC:
*(ul32 *)loc |= bits(S + A, 11, 4) << 10;
break;
case R_AARCH64_MOVW_UABS_G0:
check(S + A, 0, 1 << 16);
*(ul32 *)loc |= bits(S + A, 15, 0) << 5;
break;
case R_AARCH64_MOVW_UABS_G0_NC:
*(ul32 *)loc |= bits(S + A, 15, 0) << 5;
break;
case R_AARCH64_MOVW_UABS_G1:
check(S + A, 0, 1LL << 32);
*(ul32 *)loc |= bits(S + A, 31, 16) << 5;
break;
case R_AARCH64_MOVW_UABS_G1_NC:
*(ul32 *)loc |= bits(S + A, 31, 16) << 5;
break;
case R_AARCH64_MOVW_UABS_G2:
check(S + A, 0, 1LL << 48);
*(ul32 *)loc |= bits(S + A, 47, 32) << 5;
break;
case R_AARCH64_MOVW_UABS_G2_NC:
*(ul32 *)loc |= bits(S + A, 47, 32) << 5;
break;
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);
check(val, -(1LL << 32), 1LL << 32);
write_adrp(loc, val);
} else {
// Relax GOT-loading ADRP+LDR to an immediate ADRP+ADD
i64 val = page(S + A) - page(P);
check(val, -(1LL << 32), 1LL << 32);
write_adrp(loc, val);
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;
i++;
}
break;
case R_AARCH64_ADR_PREL_PG_HI21:
case R_AARCH64_ADR_PREL_PG_HI21_NC: {
// The ARM64 psABI defines that an `ADRP x0, foo` and `ADD x0, x0,
// :lo12: foo` instruction pair to materialize a PC-relative address
// in a register can be relaxed to `NOP` followed by `ADR x0, foo`
// if foo is in PC ± 1 MiB.
if (ctx.arg.relax && sym.is_pcrel_linktime_const(ctx) &&
i + 1 < rels.size()) {
i64 val = S + A - P - 4;
const ElfRel<E> &rel2 = rels[i + 1];
if (is_int(val, 21) &&
rel2.r_type == R_AARCH64_ADD_ABS_LO12_NC &&
rel2.r_sym == rel.r_sym &&
rel2.r_offset == rel.r_offset + 4 &&
rel2.r_addend == rel.r_addend &&
is_adrp(loc) &&
is_add(loc + 4)) {
u32 reg1 = bits(*(ul32 *)loc, 4, 0);
u32 reg2 = bits(*(ul32 *)(loc + 4), 4, 0);
if (reg1 == reg2) {
*(ul32 *)loc = 0xd503'201f; // nop
*(ul32 *)(loc + 4) = 0x1000'0000 | reg1; // adr
write_adr(loc + 4, val);
i++;
break;
}
}
}
i64 val = page(S + A) - page(P);
if (rel.r_type == R_AARCH64_ADR_PREL_PG_HI21)
check(val, -(1LL << 32), 1LL << 32);
write_adrp(loc, val);
break;
}
case R_AARCH64_ADR_PREL_LO21:
check(S + A - P, -(1LL << 20), 1LL << 20);
write_adr(loc, S + A - P);
break;
case R_AARCH64_CALL26:
case R_AARCH64_JUMP26: {
if (sym.is_remaining_undef_weak()) {
// On ARM, calling an weak undefined symbol jumps to the
// next instruction.
*(ul32 *)loc = 0xd503'201f; // nop
break;
}
i64 val = S + A - P;
if (!is_int(val, 28))
val = sym.get_thunk_addr(ctx, P) + A - P;
*(ul32 *)loc |= bits(val, 27, 2);
break;
}
case R_AARCH64_PLT32:
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);
*(ul32 *)loc |= bits(S + A - P, 20, 2) << 5;
break;
case R_AARCH64_PREL16:
check(S + A - P, -(1LL << 15), 1LL << 16);
*(U16<E> *)loc = S + A - P;
break;
case R_AARCH64_PREL32:
check(S + A - P, -(1LL << 31), 1LL << 32);
*(U32<E> *)loc = S + A - P;
break;
case R_AARCH64_PREL64:
*(U64<E> *)loc = S + A - P;
break;
case R_AARCH64_LD64_GOT_LO12_NC:
*(ul32 *)loc |= bits(G + GOT + A, 11, 3) << 10;
break;
case R_AARCH64_LD64_GOTPAGE_LO15: {
i64 val = G + GOT + A - page(GOT);
check(val, 0, 1 << 15);
*(ul32 *)loc |= bits(val, 14, 3) << 10;
break;
}
case R_AARCH64_TLSIE_ADR_GOTTPREL_PAGE21: {
i64 val = page(sym.get_gottp_addr(ctx) + A) - page(P);
check(val, -(1LL << 32), 1LL << 32);
write_adrp(loc, val);
break;
}
case R_AARCH64_TLSIE_LD64_GOTTPREL_LO12_NC:
*(ul32 *)loc |= bits(sym.get_gottp_addr(ctx) + A, 11, 3) << 10;
break;
case R_AARCH64_TLSLE_MOVW_TPREL_G0: {
i64 val = S + A - ctx.tp_addr;
check(val, -(1 << 15), 1 << 15);
write_movn_movz(loc, val);
break;
}
case R_AARCH64_TLSLE_MOVW_TPREL_G0_NC:
*(ul32 *)loc |= bits(S + A - ctx.tp_addr, 15, 0) << 5;
break;
case R_AARCH64_TLSLE_MOVW_TPREL_G1: {
i64 val = S + A - ctx.tp_addr;
check(val, -(1LL << 31), 1LL << 31);
write_movn_movz(loc, val >> 16);
break;
}
case R_AARCH64_TLSLE_MOVW_TPREL_G1_NC:
*(ul32 *)loc |= bits(S + A - ctx.tp_addr, 31, 16) << 5;
break;
case R_AARCH64_TLSLE_MOVW_TPREL_G2: {
i64 val = S + A - ctx.tp_addr;
check(val, -(1LL << 47), 1LL << 47);
write_movn_movz(loc, val >> 32);
break;
}
case R_AARCH64_TLSLE_ADD_TPREL_HI12: {
i64 val = S + A - ctx.tp_addr;
check(val, 0, 1LL << 24);
*(ul32 *)loc |= bits(val, 23, 12) << 10;
break;
}
case R_AARCH64_TLSLE_ADD_TPREL_LO12:
check(S + A - ctx.tp_addr, 0, 1 << 12);
*(ul32 *)loc |= bits(S + A - ctx.tp_addr, 11, 0) << 10;
break;
case R_AARCH64_TLSLE_ADD_TPREL_LO12_NC:
*(ul32 *)loc |= bits(S + A - ctx.tp_addr, 11, 0) << 10;
break;
case R_AARCH64_TLSGD_ADR_PAGE21: {
i64 val = page(sym.get_tlsgd_addr(ctx) + A) - page(P);
check(val, -(1LL << 32), 1LL << 32);
write_adrp(loc, val);
break;
}
case R_AARCH64_TLSGD_ADD_LO12_NC:
*(ul32 *)loc |= bits(sym.get_tlsgd_addr(ctx) + A, 11, 0) << 10;
break;
case R_AARCH64_TLSDESC_ADR_PAGE21:
// ARM64 TLSDESC uses the following code sequence to materialize
// a TP-relative address in x0.
//
// adrp x0, 0
// R_AARCH64_TLSDESC_ADR_PAGE21 foo
// ldr x1, [x0]
// R_AARCH64_TLSDESC_LD64_LO12 foo
// add x0, x0, #0
// R_AARCH64_TLSDESC_ADD_LO12 foo
// blr x1
// R_AARCH64_TLSDESC_CALL foo
//
// We may relax the instructions to the following if its TP-relative
// address is known at link-time
//
// nop
// nop
// movz x0, :tls_offset_hi:foo, lsl #16
// movk x0, :tls_offset_lo:foo
//
// or to the following if the TP-relative address is known at
// process startup time.
//
// nop
// nop
// adrp x0, :gottprel:foo
// ldr x0, [x0, :gottprel_lo12:foo]
if (sym.has_tlsdesc(ctx)) {
i64 val = page(sym.get_tlsdesc_addr(ctx) + A) - page(P);
check(val, -(1LL << 32), 1LL << 32);
write_adrp(loc, val);
} else {
*(ul32 *)loc = 0xd503'201f; // nop
}
break;
case R_AARCH64_TLSDESC_LD64_LO12:
if (sym.has_tlsdesc(ctx))
*(ul32 *)loc |= bits(sym.get_tlsdesc_addr(ctx) + A, 11, 3) << 10;
else
*(ul32 *)loc = 0xd503'201f; // nop
break;
case R_AARCH64_TLSDESC_ADD_LO12:
if (sym.has_tlsdesc(ctx)) {
*(ul32 *)loc |= bits(sym.get_tlsdesc_addr(ctx) + A, 11, 0) << 10;
} 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));
} else {
*(ul32 *)loc = 0xd2a0'0000; // movz x0, 0, lsl #16
*(ul32 *)loc |= bits(S + A - ctx.tp_addr, 32, 16) << 5;
}
break;
case R_AARCH64_TLSDESC_CALL:
if (sym.has_tlsdesc(ctx)) {
// Do nothing
} 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;
} else {
*(ul32 *)loc = 0xf280'0000; // movk x0, 0
*(ul32 *)loc |= bits(S + A - ctx.tp_addr, 15, 0) << 5;
}
break;
default:
unreachable();
}
}
}
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];
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
SectionFragment<E> *frag;
i64 frag_addend;
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
u64 S = frag ? frag->get_addr(ctx) : sym.get_addr(ctx);
u64 A = frag ? frag_addend : (i64)rel.r_addend;
auto check = [&](i64 val, i64 lo, i64 hi) {
check_range(ctx, val, i, lo, hi);
};
switch (rel.r_type) {
case R_AARCH64_ABS64:
if (std::optional<u64> val = get_tombstone(sym, frag))
*(U64<E> *)loc = *val;
else
*(U64<E> *)loc = S + A;
break;
case R_AARCH64_ABS32:
check(S + A, 0, 1LL << 32);
*(U32<E> *)loc = S + A;
break;
default:
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
<< rel;
break;
}
}
}
template <>
void InputSection<E>::scan_relocations(Context<E> &ctx) {
assert(shdr().sh_flags & SHF_ALLOC);
std::span<const ElfRel<E>> rels = get_rels(ctx);
// Scan relocations
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
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);
if (sym.is_ifunc())
sym.flags |= NEEDS_GOT | NEEDS_PLT;
switch (rel.r_type) {
case R_AARCH64_MOVW_UABS_G3:
scan_absrel(ctx, sym, rel);
break;
case R_AARCH64_ADR_GOT_PAGE:
// An ADR_GOT_PAGE and GOT_LO12_NC relocation pair is used to load a
// symbol's address from GOT. If the GOT value is a link-time
// constant, we may be able to rewrite the ADRP+LDR instruction pair
// with an ADRP+ADD, eliminating a GOT memory load.
if (ctx.arg.relax && sym.is_pcrel_linktime_const(ctx) &&
i + 1 < rels.size()) {
// ADRP+LDR must be consecutive and use the same register to relax.
const ElfRel<E> &rel2 = rels[i + 1];
if (rel2.r_type == R_AARCH64_LD64_GOT_LO12_NC &&
rel2.r_offset == rel.r_offset + 4 &&
rel2.r_sym == rel.r_sym &&
rel.r_addend == 0 &&
rel2.r_addend == 0 &&
is_adrp(loc) &&
is_ldr(loc + 4)) {
u32 rd = bits(*(ul32 *)loc, 4, 0);
u32 rn = bits(*(ul32 *)(loc + 4), 9, 5);
u32 rt = bits(*(ul32 *)(loc + 4), 4, 0);
if (rd == rn && rn == rt) {
i++;
break;
}
}
}
sym.flags |= NEEDS_GOT;
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:
case R_AARCH64_JUMP26:
case R_AARCH64_PLT32:
if (sym.is_imported)
sym.flags |= NEEDS_PLT;
break;
case R_AARCH64_TLSIE_ADR_GOTTPREL_PAGE21:
case R_AARCH64_TLSIE_LD64_GOTTPREL_LO12_NC:
sym.flags |= NEEDS_GOTTP;
break;
case R_AARCH64_ADR_PREL_PG_HI21:
case R_AARCH64_ADR_PREL_PG_HI21_NC:
scan_pcrel(ctx, sym, rel);
break;
case R_AARCH64_TLSGD_ADR_PAGE21:
sym.flags |= NEEDS_TLSGD;
break;
case R_AARCH64_TLSDESC_CALL:
scan_tlsdesc(ctx, sym);
break;
case R_AARCH64_TLSLE_MOVW_TPREL_G2:
case R_AARCH64_TLSLE_ADD_TPREL_LO12:
case R_AARCH64_TLSLE_ADD_TPREL_LO12_NC:
check_tlsle(ctx, sym, rel);
break;
case R_AARCH64_ABS64:
case R_AARCH64_ADD_ABS_LO12_NC:
case R_AARCH64_ADR_PREL_LO21:
case R_AARCH64_CONDBR19:
case R_AARCH64_LD_PREL_LO19:
case R_AARCH64_LDST16_ABS_LO12_NC:
case R_AARCH64_LDST32_ABS_LO12_NC:
case R_AARCH64_LDST64_ABS_LO12_NC:
case R_AARCH64_LDST128_ABS_LO12_NC:
case R_AARCH64_LDST8_ABS_LO12_NC:
case R_AARCH64_MOVW_UABS_G0:
case R_AARCH64_MOVW_UABS_G0_NC:
case R_AARCH64_MOVW_UABS_G1:
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:
case R_AARCH64_TLSGD_ADD_LO12_NC:
case R_AARCH64_TLSLE_MOVW_TPREL_G0:
case R_AARCH64_TLSLE_MOVW_TPREL_G0_NC:
case R_AARCH64_TLSLE_MOVW_TPREL_G1:
case R_AARCH64_TLSLE_MOVW_TPREL_G1_NC:
case R_AARCH64_TLSLE_ADD_TPREL_HI12:
case R_AARCH64_TLSDESC_ADR_PAGE21:
case R_AARCH64_TLSDESC_LD64_LO12:
case R_AARCH64_TLSDESC_ADD_LO12:
break;
default:
Error(ctx) << *this << ": unknown relocation: " << rel;
}
}
}
// 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>::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);
};
for (Symbol<E> *sym : symbols) {
u64 S = sym->get_addr(ctx);
u64 P = get_addr() + off;
i64 prel = page(S) - page(P);
off += is_small(prel) ? 16 : 32;
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
0xd420'7d00, // brk
};
// 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
0xd420'7d00, // brk
0xd420'7d00, // brk
};
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] == 16) {
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
+602
View File
@@ -0,0 +1,602 @@
// i386 is similar to x86-64 but lacks PC-relative memory access
// instructions. So it's not straightforward to support position-
// independent code (PIC) on that target.
//
// If an object file is compiled with -fPIC, a function that needs to load
// a value from memory first obtains its own address with the following
// code
//
// call __x86.get_pc_thunk.bx
//
// where __x86.get_pc_thunk.bx is defined as
//
// __x86.get_pc_thunk.bx:
// mov (%esp), %ebx # move the return address to %ebx
// ret
//
// . With the function's own address (or, more precisely, the address
// immediately after the call instruction), the function can compute an
// absolute address of a variable with its address + link-time constant.
//
// Executing call-mov-ret isn't very cheap, and allocating one register to
// store PC isn't cheap too, especially given that i386 has only 8
// general-purpose registers. But that's the cost of PIC on i386. You need
// to pay it when creating a .so and a position-independent executable.
//
// When a position-independent function calls another function, it sets
// %ebx to the address of .got. Position-independent PLT entries use that
// register to load values from .got.plt/.got.
//
// If we are creating a position-dependent executable (PDE), we can't
// assume that %ebx is set to .got. For PDE, we need to create position-
// dependent PLT entries which don't use %ebx.
//
// https://github.com/rui314/psabi/blob/main/i386.pdf
#if MOLD_I386
#include "mold.h"
namespace mold {
using E = I386;
template <>
i64 get_addend(u8 *loc, const ElfRel<E> &rel) {
switch (rel.r_type) {
case R_386_8:
case R_386_PC8:
return *(i8 *)loc;
case R_386_16:
case R_386_PC16:
return *(il16 *)loc;
case R_386_32:
case R_386_PC32:
case R_386_GOT32:
case R_386_GOT32X:
case R_386_PLT32:
case R_386_GOTOFF:
case R_386_GOTPC:
case R_386_TLS_LDM:
case R_386_TLS_GOTIE:
case R_386_TLS_LE:
case R_386_TLS_IE:
case R_386_TLS_GD:
case R_386_TLS_LDO_32:
case R_386_SIZE32:
case R_386_TLS_GOTDESC:
return *(il32 *)loc;
default:
return 0;
}
}
template <>
void write_addend(u8 *loc, i64 val, const ElfRel<E> &rel) {
switch (rel.r_type) {
case R_386_NONE:
break;
case R_386_8:
case R_386_PC8:
*loc = val;
break;
case R_386_16:
case R_386_PC16:
*(ul16 *)loc = val;
break;
case R_386_32:
case R_386_PC32:
case R_386_GOT32:
case R_386_GOT32X:
case R_386_PLT32:
case R_386_GOTOFF:
case R_386_GOTPC:
case R_386_TLS_LDM:
case R_386_TLS_GOTIE:
case R_386_TLS_LE:
case R_386_TLS_IE:
case R_386_TLS_GD:
case R_386_TLS_LDO_32:
case R_386_SIZE32:
case R_386_TLS_GOTDESC:
*(ul32 *)loc = val;
break;
default:
unreachable();
}
}
template <>
void write_plt_header(Context<E> &ctx, u8 *buf) {
if (ctx.arg.pic) {
static const u8 insn[] = {
0x51, // push %ecx
0x8d, 0x8b, 0, 0, 0, 0, // lea GOTPLT+4(%ebx), %ecx
0xff, 0x31, // push (%ecx)
0xff, 0x61, 0x04, // jmp *0x4(%ecx)
0xcc, 0xcc, 0xcc, 0xcc, // (padding)
};
memcpy(buf, insn, sizeof(insn));
*(ul32 *)(buf + 3) = ctx.gotplt->shdr.sh_addr - ctx.got->shdr.sh_addr + 4;
} else {
static const u8 insn[] = {
0x51, // push %ecx
0xb9, 0, 0, 0, 0, // mov GOTPLT+4, %ecx
0xff, 0x31, // push (%ecx)
0xff, 0x61, 0x04, // jmp *0x4(%ecx)
0xcc, 0xcc, 0xcc, 0xcc, 0xcc, // (padding)
};
memcpy(buf, insn, sizeof(insn));
*(ul32 *)(buf + 2) = ctx.gotplt->shdr.sh_addr + 4;
}
}
template <>
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
if (ctx.arg.pic) {
static const u8 insn[] = {
0xb9, 0, 0, 0, 0, // mov $reloc_offset, %ecx
0xff, 0xa3, 0, 0, 0, 0, // jmp *foo@GOT(%ebx)
0xcc, 0xcc, 0xcc, 0xcc, 0xcc, // (padding)
};
memcpy(buf, insn, sizeof(insn));
*(ul32 *)(buf + 1) = sym.get_plt_idx(ctx) * sizeof(ElfRel<E>);
*(ul32 *)(buf + 7) = sym.get_gotplt_addr(ctx) - ctx.got->shdr.sh_addr;
} else {
static const u8 insn[] = {
0xb9, 0, 0, 0, 0, // mov $reloc_offset, %ecx
0xff, 0x25, 0, 0, 0, 0, // jmp *foo@GOT
0xcc, 0xcc, 0xcc, 0xcc, 0xcc, // (padding)
};
memcpy(buf, insn, sizeof(insn));
*(ul32 *)(buf + 1) = sym.get_plt_idx(ctx) * sizeof(ElfRel<E>);
*(ul32 *)(buf + 7) = sym.get_gotplt_addr(ctx);
}
}
template <>
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
if (ctx.arg.pic) {
static const u8 insn[] = {
0xff, 0xa3, 0, 0, 0, 0, // jmp *foo@GOT(%ebx)
0xcc, 0xcc, // (padding)
};
memcpy(buf, insn, sizeof(insn));
*(ul32 *)(buf + 2) = sym.get_got_pltgot_addr(ctx) - ctx.got->shdr.sh_addr;
} else {
static const u8 insn[] = {
0xff, 0x25, 0, 0, 0, 0, // jmp *foo@GOT
0xcc, 0xcc, // (padding)
};
memcpy(buf, insn, sizeof(insn));
*(ul32 *)(buf + 2) = sym.get_got_pltgot_addr(ctx);
}
}
template <>
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, 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_386_32:
*(ul32 *)loc = val;
break;
case R_386_PC32:
*(ul32 *)loc = val - this->shdr.sh_addr - offset;
break;
default:
Fatal(ctx) << "unsupported relocation in .eh_frame: " << rel;
}
}
static u32 relax_got32x(u8 *loc) {
// mov imm(%reg1), %reg2 -> lea imm(%reg1), %reg2
if (loc[0] == 0x8b)
return 0x8d00 | loc[1];
return 0;
}
// Relax GD to LE
static void relax_gd_to_le(u8 *loc, ElfRel<E> rel, u64 val) {
static const u8 insn[] = {
0x65, 0xa1, 0, 0, 0, 0, // mov %gs:0, %eax
0x81, 0xc0, 0, 0, 0, 0, // add $tp_offset, %eax
};
switch (rel.r_type) {
case R_386_PLT32:
case R_386_PC32:
memcpy(loc - 3, insn, sizeof(insn));
*(ul32 *)(loc + 5) = val;
break;
case R_386_GOT32:
case R_386_GOT32X:
memcpy(loc - 2, insn, sizeof(insn));
*(ul32 *)(loc + 6) = val;
break;
default:
unreachable();
}
}
// Relax LD to LE
static void relax_ld_to_le(u8 *loc, ElfRel<E> rel, u64 tls_size) {
switch (rel.r_type) {
case R_386_PLT32:
case R_386_PC32: {
static const u8 insn[] = {
0x65, 0xa1, 0, 0, 0, 0, // mov %gs:0, %eax
0x2d, 0, 0, 0, 0, // sub $tls_size, %eax
};
memcpy(loc - 2, insn, sizeof(insn));
*(ul32 *)(loc + 5) = tls_size;
break;
}
case R_386_GOT32:
case R_386_GOT32X: {
static const u8 insn[] = {
0x65, 0xa1, 0, 0, 0, 0, // mov %gs:0, %eax
0x81, 0xe8, 0, 0, 0, 0, // sub $tls_size, %eax
};
memcpy(loc - 2, insn, sizeof(insn));
*(ul32 *)(loc + 6) = tls_size;
break;
}
default:
unreachable();
}
}
static u32 relax_tlsdesc_to_ie(u8 *loc) {
switch ((loc[0] << 8) | loc[1]) {
case 0x8d83: return 0x8b83; // lea 0(%ebx), %eax -> mov 0(%ebx), %eax
case 0x8d9b: return 0x8b9b; // lea 0(%ebx), %ebx -> mov 0(%ebx), %ebx
case 0x8d8b: return 0x8b8b; // lea 0(%ebx), %ecx -> mov 0(%ebx), %ecx
case 0x8d93: return 0x8b93; // lea 0(%ebx), %edx -> mov 0(%ebx), %edx
case 0x8db3: return 0x8bb3; // lea 0(%ebx), %esi -> mov 0(%ebx), %esi
case 0x8dbb: return 0x8bbb; // lea 0(%ebx), %edi -> mov 0(%ebx), %edi
case 0x8da3: return 0x8ba3; // lea 0(%ebx), %esp -> mov 0(%ebx), %esp
case 0x8dab: return 0x8bab; // lea 0(%ebx), %ebp -> mov 0(%ebx), %ebp
}
return 0;
}
static u32 relax_tlsdesc_to_le(u8 *loc) {
switch ((loc[0] << 8) | loc[1]) {
case 0x8d83: return 0x90b8; // lea 0(%ebx), %eax -> mov $0, %eax
case 0x8d9b: return 0x90bb; // lea 0(%ebx), %ebx -> mov $0, %ebx
case 0x8d8b: return 0x90b9; // lea 0(%ebx), %ecx -> mov $0, %ecx
case 0x8d93: return 0x90ba; // lea 0(%ebx), %edx -> mov $0, %edx
case 0x8db3: return 0x90be; // lea 0(%ebx), %esi -> mov $0, %esi
case 0x8dbb: return 0x90bf; // lea 0(%ebx), %edi -> mov $0, %edi
case 0x8da3: return 0x90bc; // lea 0(%ebx), %esp -> mov $0, %esp
case 0x8dab: return 0x90bd; // lea 0(%ebx), %ebp -> mov $0, %ebp
}
return 0;
}
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++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
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 G = sym.get_got_idx(ctx) * sizeof(Word<E>);
u64 GOT = ctx.got->shdr.sh_addr;
auto check = [&](i64 val, i64 lo, i64 hi) {
check_range(ctx, i, val, lo, hi);
};
switch (rel.r_type) {
case R_386_8:
check(S + A, 0, 1 << 8);
*loc = S + A;
break;
case R_386_16:
check(S + A, 0, 1 << 16);
*(ul16 *)loc = S + A;
break;
case R_386_32:
break;
case R_386_PC8:
check(S + A - P, -(1 << 7), 1 << 7);
*loc = S + A - P;
break;
case R_386_PC16:
check(S + A - P, -(1 << 15), 1 << 15);
*(ul16 *)loc = S + A - P;
break;
case R_386_PC32:
case R_386_PLT32:
*(ul32 *)loc = S + A - P;
break;
case R_386_GOT32:
*(ul32 *)loc = G + A;
break;
case R_386_GOT32X:
if (sym.has_got(ctx)) {
*(ul32 *)loc = G + A;
} else {
u32 insn = relax_got32x(loc - 2);
assert(insn);
loc[-2] = insn >> 8;
loc[-1] = insn;
*(ul32 *)loc = S + A - GOT;
}
break;
case R_386_GOTOFF:
*(ul32 *)loc = S + A - GOT;
break;
case R_386_GOTPC:
*(ul32 *)loc = GOT + A - P;
break;
case R_386_TLS_GOTIE:
*(ul32 *)loc = sym.get_gottp_addr(ctx) + A - GOT;
break;
case R_386_TLS_LE:
*(ul32 *)loc = S + A - ctx.tp_addr;
break;
case R_386_TLS_IE:
*(ul32 *)loc = sym.get_gottp_addr(ctx) + A;
break;
case R_386_TLS_GD:
if (sym.has_tlsgd(ctx))
*(ul32 *)loc = sym.get_tlsgd_addr(ctx) + A - GOT;
else
relax_gd_to_le(loc, rels[++i], S - ctx.tp_addr);
break;
case R_386_TLS_LDM:
if (ctx.got->has_tlsld(ctx))
*(ul32 *)loc = ctx.got->get_tlsld_addr(ctx) + A - GOT;
else
relax_ld_to_le(loc, rels[++i], ctx.tp_addr - ctx.tls_begin);
break;
case R_386_TLS_LDO_32:
*(ul32 *)loc = S + A - ctx.dtp_addr;
break;
case R_386_SIZE32:
*(ul32 *)loc = sym.esym().st_size + A;
break;
case R_386_TLS_GOTDESC:
// i386 TLSDESC uses the following code sequence to materialize
// a TP-relative address in %eax.
//
// lea 0(%ebx), %eax
// R_386_TLS_GOTDESC foo
// call *(%eax)
// R_386_TLS_DESC_CALL foo
//
// We may relax the instructions to the following if its TP-relative
// address is known at link-time
//
// mov $foo@TPOFF, %eax
// nop
//
// or to the following if the TP-relative address is known at
// process startup time.
//
// mov foo@GOTTPOFF(%ebx), %eax
// nop
//
// We allow the following alternative code sequence too because
// LLVM emits such code.
//
// lea 0(%ebx), %reg
// R_386_TLS_GOTDESC foo
// mov %reg, %eax
// call *(%eax)
// R_386_TLS_DESC_CALL foo
//
// Note that the compiler always uses the local-exec TLS model
// for -fno-pic, so TLSDESC code is always PIC (i.e. uses %ebx to
// store the address of GOT.)
if (sym.has_tlsdesc(ctx)) {
*(ul32 *)loc = sym.get_tlsdesc_addr(ctx) + A - GOT;
} else if (sym.has_gottp(ctx)) {
u32 insn = relax_tlsdesc_to_ie(loc - 2);
if (!insn)
Fatal(ctx) << *this << ": illegal instruction sequence for TLSDESC";
loc[-2] = insn >> 8;
loc[-1] = insn;
*(ul32 *)loc = sym.get_gottp_addr(ctx) + A - GOT;
} else {
u32 insn = relax_tlsdesc_to_le(loc - 2);
if (!insn)
Fatal(ctx) << *this << ": illegal instruction sequence for TLSDESC";
loc[-2] = insn >> 8;
loc[-1] = insn;
*(ul32 *)loc = S + A - ctx.tp_addr;
}
break;
case R_386_TLS_DESC_CALL:
if (!sym.has_tlsdesc(ctx)) {
// call *(%eax) -> nop
loc[0] = 0x66;
loc[1] = 0x90;
}
break;
default:
unreachable();
}
}
}
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];
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
SectionFragment<E> *frag;
i64 frag_addend;
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
u64 S = frag ? frag->get_addr(ctx) : sym.get_addr(ctx);
u64 A = frag ? frag_addend : get_addend(*this, rel);
u64 GOT = ctx.got->shdr.sh_addr;
auto check = [&](i64 val, i64 lo, i64 hi) {
check_range(ctx, val, i, lo, hi);
};
switch (rel.r_type) {
case R_386_8:
check(S + A, 0, 1 << 8);
*loc = S + A;
break;
case R_386_16:
check(S + A, 0, 1 << 16);
*(ul16 *)loc = S + A;
break;
case R_386_32:
if (std::optional<u64> val = get_tombstone(sym, frag))
*(ul32 *)loc = *val;
else
*(ul32 *)loc = S + A;
break;
case R_386_PC8:
check(S + A, -(1 << 7), 1 << 7);
*loc = S + A;
break;
case R_386_PC16:
check(S + A, -(1 << 15), 1 << 15);
*(ul16 *)loc = S + A;
break;
case R_386_PC32:
*(ul32 *)loc = S + A;
break;
case R_386_GOTPC:
*(ul32 *)loc = GOT + A;
break;
case R_386_GOTOFF:
*(ul32 *)loc = S + A - GOT;
break;
case R_386_TLS_LDO_32:
if (std::optional<u64> val = get_tombstone(sym, frag))
*(ul32 *)loc = *val;
else
*(ul32 *)loc = S + A - ctx.dtp_addr;
break;
case R_386_SIZE32:
*(ul32 *)loc = sym.esym().st_size + A;
break;
default:
unreachable();
}
}
}
template <>
void InputSection<E>::scan_relocations(Context<E> &ctx) {
assert(shdr().sh_flags & SHF_ALLOC);
std::span<const ElfRel<E>> rels = get_rels(ctx);
// Scan relocations
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
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);
if (sym.is_ifunc())
sym.flags |= NEEDS_GOT | NEEDS_PLT;
if (rel.r_type == R_386_TLS_GD || rel.r_type == R_386_TLS_LDM) {
if (i + 1 == rels.size())
Fatal(ctx) << *this << ": " << rel << " must be followed by PLT or GOT32";
if (u32 ty = rels[i + 1].r_type;
ty != R_386_PLT32 && ty != R_386_PC32 &&
ty != R_386_GOT32 && ty != R_386_GOT32X)
Fatal(ctx) << *this << ": " << rel << " must be followed by PLT or GOT32";
}
switch (rel.r_type) {
case R_386_8:
case R_386_16:
scan_absrel(ctx, sym, rel);
break;
case R_386_PC8:
case R_386_PC16:
case R_386_PC32:
scan_pcrel(ctx, sym, rel);
break;
case R_386_GOT32:
case R_386_GOTPC:
sym.flags |= NEEDS_GOT;
break;
case R_386_GOT32X:
// We always want to relax GOT32X even if --no-relax is given
// because static PIE doesn't work without it.
if (sym.is_pcrel_linktime_const(ctx) && relax_got32x(loc - 2)) {
// Do nothing
} else {
sym.flags |= NEEDS_GOT;
}
break;
case R_386_PLT32:
if (sym.is_imported)
sym.flags |= NEEDS_PLT;
break;
case R_386_TLS_GOTIE:
case R_386_TLS_IE:
sym.flags |= NEEDS_GOTTP;
break;
case R_386_TLS_GD:
// We always relax if -static because libc.a doesn't contain
// __tls_get_addr().
if (ctx.arg.static_ || (ctx.arg.relax && sym.is_tprel_linktime_const(ctx)))
i++;
else
sym.flags |= NEEDS_TLSGD;
break;
case R_386_TLS_LDM:
// We always relax if -static because libc.a doesn't contain
// __tls_get_addr().
if (ctx.arg.static_ || (ctx.arg.relax && !ctx.arg.shared))
i++;
else
ctx.needs_tlsld = true;
break;
case R_386_TLS_GOTDESC:
scan_tlsdesc(ctx, sym);
break;
case R_386_TLS_LE:
check_tlsle(ctx, sym, rel);
break;
case R_386_32:
case R_386_GOTOFF:
case R_386_TLS_LDO_32:
case R_386_SIZE32:
case R_386_TLS_DESC_CALL:
break;
default:
Error(ctx) << *this << ": unknown relocation: " << rel;
}
}
}
} // namespace mold
#endif
File diff suppressed because it is too large. Load diff
+316
View File
@@ -0,0 +1,316 @@
// This file contains code for the Motorola 68000 series microprocessors,
// which is often abbreviated as m68k. Running a Unix-like system on a
// m68k-based machine today is a retro-computing hobby activity, but the
// processor was a popular choice to build Unix computers during '80s.
// Early Sun workstations for example used m68k. Macintosh until 1994 were
// based on m68k as well until they switched to PowerPC (and then to x86
// and to ARM.)
//
// From the linker's point of view, it is not hard to support m68k. It's
// just a 32-bit big-endian CISC ISA. Compared to comtemporary i386,
// m68k's psABI is actually simpler because m68k has PC-relative memory
// access instructions and therefore can support position-independent
// code without too much hassle.
//
// https://github.com/rui314/psabi/blob/main/m68k.pdf
#if MOLD_M68K
#include "mold.h"
namespace mold {
using E = M68K;
template <>
void write_plt_header(Context<E> &ctx, u8 *buf) {
static const u8 insn[] = {
0x2f, 0x00, // move.l %d0, -(%sp)
0x2f, 0x3b, 0x01, 0x70, 0, 0, 0, 0, // move.l (GOTPLT+4, %pc), -(%sp)
0x4e, 0xfb, 0x01, 0x71, 0, 0, 0, 0, // jmp ([GOTPLT+8, %pc])
};
memcpy(buf, insn, sizeof(insn));
*(ub32 *)(buf + 6) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr;
*(ub32 *)(buf + 14) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr - 4;
}
template <>
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
static const u8 insn[] = {
0x20, 0x3c, 0, 0, 0, 0, // move.l PLT_OFFSET, %d0
0x4e, 0xfb, 0x01, 0x71, 0, 0, 0, 0, // jmp ([GOTPLT_ENTRY, %pc])
};
memcpy(buf, insn, sizeof(insn));
*(ub32 *)(buf + 2) = sym.get_plt_idx(ctx) * sizeof(ElfRel<E>);
*(ub32 *)(buf + 10) = sym.get_gotplt_addr(ctx) - sym.get_plt_addr(ctx) - 8;
}
template <>
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
static const u8 insn[] = {
0x4e, 0xfb, 0x01, 0x71, 0, 0, 0, 0, // jmp ([GOT_ENTRY, %pc])
};
memcpy(buf, insn, sizeof(insn));
*(ub32 *)(buf + 4) = sym.get_got_pltgot_addr(ctx) - sym.get_plt_addr(ctx) - 2;
}
template <>
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, 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_68K_32:
*(ub32 *)loc = val;
break;
case R_68K_PC32:
*(ub32 *)loc = val - this->shdr.sh_addr - offset;
break;
default:
Fatal(ctx) << "unsupported relocation in .eh_frame: " << rel;
}
}
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++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
u64 S = sym.get_addr(ctx);
u64 A = rel.r_addend;
u64 P = get_addr() + rel.r_offset;
u64 G = sym.get_got_idx(ctx) * sizeof(Word<E>);
u64 GOT = ctx.got->shdr.sh_addr;
auto check = [&](i64 val, i64 lo, i64 hi) {
check_range(ctx, i, val, lo, hi);
};
auto write16 = [&](u64 val) {
check(val, 0, 1 << 16);
*(ub16 *)loc = val;
};
auto write16s = [&](u64 val) {
check(val, -(1 << 15), 1 << 15);
*(ub16 *)loc = val;
};
auto write8 = [&](u64 val) {
check(val, 0, 1 << 8);
*loc = val;
};
auto write8s = [&](u64 val) {
check(val, -(1 << 7), 1 << 7);
*loc = val;
};
switch (rel.r_type) {
case R_68K_32:
break;
case R_68K_16:
write16(S + A);
break;
case R_68K_8:
write8(S + A);
break;
case R_68K_PC32:
case R_68K_PLT32:
*(ub32 *)loc = S + A - P;
break;
case R_68K_PC16:
case R_68K_PLT16:
write16s(S + A - P);
break;
case R_68K_PC8:
case R_68K_PLT8:
write8s(S + A - P);
break;
case R_68K_GOTPCREL32:
*(ub32 *)loc = GOT + A - P;
break;
case R_68K_GOTPCREL16:
write16s(GOT + A - P);
break;
case R_68K_GOTPCREL8:
write8s(GOT + A - P);
break;
case R_68K_GOTOFF32:
*(ub32 *)loc = G + A;
break;
case R_68K_GOTOFF16:
write16(G + A);
break;
case R_68K_GOTOFF8:
write8(G + A);
break;
case R_68K_TLS_GD32:
*(ub32 *)loc = sym.get_tlsgd_addr(ctx) + A - GOT;
break;
case R_68K_TLS_GD16:
write16(sym.get_tlsgd_addr(ctx) + A - GOT);
break;
case R_68K_TLS_GD8:
write8(sym.get_tlsgd_addr(ctx) + A - GOT);
break;
case R_68K_TLS_LDM32:
*(ub32 *)loc = ctx.got->get_tlsld_addr(ctx) + A - GOT;
break;
case R_68K_TLS_LDM16:
write16(ctx.got->get_tlsld_addr(ctx) + A - GOT);
break;
case R_68K_TLS_LDM8:
write8(ctx.got->get_tlsld_addr(ctx) + A - GOT);
break;
case R_68K_TLS_LDO32:
*(ub32 *)loc = S + A - ctx.dtp_addr;
break;
case R_68K_TLS_LDO16:
write16s(S + A - ctx.dtp_addr);
break;
case R_68K_TLS_LDO8:
write8s(S + A - ctx.dtp_addr);
break;
case R_68K_TLS_IE32:
*(ub32 *)loc = sym.get_gottp_addr(ctx) + A - GOT;
break;
case R_68K_TLS_IE16:
write16(sym.get_gottp_addr(ctx) + A - GOT);
break;
case R_68K_TLS_IE8:
write8(sym.get_gottp_addr(ctx) + A - GOT);
break;
case R_68K_TLS_LE32:
*(ub32 *)loc = S + A - ctx.tp_addr;
break;
case R_68K_TLS_LE16:
write16(S + A - ctx.tp_addr);
break;
case R_68K_TLS_LE8:
write8(S + A - ctx.tp_addr);
break;
default:
unreachable();
}
}
}
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];
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
SectionFragment<E> *frag;
i64 frag_addend;
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
u64 S = frag ? frag->get_addr(ctx) : sym.get_addr(ctx);
u64 A = frag ? frag_addend : (i64)rel.r_addend;
switch (rel.r_type) {
case R_68K_32:
if (std::optional<u64> val = get_tombstone(sym, frag))
*(ub32 *)loc = *val;
else
*(ub32 *)loc = S + A;
break;
default:
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
<< 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);
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
if (sym.is_ifunc())
Error(ctx) << sym << ": GNU ifunc symbol is not supported on m68k";
switch (rel.r_type) {
case R_68K_16:
case R_68K_8:
scan_absrel(ctx, sym, rel);
break;
case R_68K_PC32:
case R_68K_PC16:
case R_68K_PC8:
scan_pcrel(ctx, sym, rel);
break;
case R_68K_GOTPCREL32:
case R_68K_GOTPCREL16:
case R_68K_GOTPCREL8:
case R_68K_GOTOFF32:
case R_68K_GOTOFF16:
case R_68K_GOTOFF8:
sym.flags |= NEEDS_GOT;
break;
case R_68K_PLT32:
case R_68K_PLT16:
case R_68K_PLT8:
if (sym.is_imported)
sym.flags |= NEEDS_PLT;
break;
case R_68K_TLS_GD32:
case R_68K_TLS_GD16:
case R_68K_TLS_GD8:
sym.flags |= NEEDS_TLSGD;
break;
case R_68K_TLS_LDM32:
case R_68K_TLS_LDM16:
case R_68K_TLS_LDM8:
ctx.needs_tlsld = true;
break;
case R_68K_TLS_IE32:
case R_68K_TLS_IE16:
case R_68K_TLS_IE8:
sym.flags |= NEEDS_GOTTP;
break;
case R_68K_TLS_LE32:
case R_68K_TLS_LE16:
case R_68K_TLS_LE8:
check_tlsle(ctx, sym, rel);
break;
case R_68K_32:
case R_68K_TLS_LDO32:
case R_68K_TLS_LDO16:
case R_68K_TLS_LDO8:
break;
default:
Error(ctx) << *this << ": unknown relocation: " << rel;
}
}
}
} // namespace mold
#endif
Loaded 100 of 3243 files, more files were not shown because too many files have changed in this diff. Show more