mirror of
https://github.com/rui314/mold.git
synced 2026-11-09 04:49:33 -10:00
Compare commits
19
Commits
No files matched your search
@@ -1,21 +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"]
|
||||
|
||||
# LLVM's address-significance table tells the linker which symbols'
|
||||
# addresses the program uses. Without it, Identical Code Folding (see
|
||||
# cli/build.rs) has to assume that every piece of read-only data has a
|
||||
# significant address, so it can't merge two functions that refer to
|
||||
# identical constants, such as the panic locations that each crate
|
||||
# instantiating the linker has its own copies of.
|
||||
[target.'cfg(all())']
|
||||
rustflags = ["-Cllvm-args=-addrsig"]
|
||||
|
||||
# Rust links musl statically by default, but a static executable can't
|
||||
# dlopen() the LTO plugin.
|
||||
[target.'cfg(target_env = "musl")']
|
||||
rustflags = ["-Ctarget-feature=-crt-static"]
|
||||
@@ -8,9 +8,6 @@ on:
|
||||
jobs:
|
||||
check-changes:
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
outputs:
|
||||
has_changes: ${{ github.event_name == 'workflow_dispatch' || steps.check.outputs.has_changes }}
|
||||
steps:
|
||||
@@ -42,14 +39,22 @@ jobs:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
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@v1
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build a tarball
|
||||
run: ./dist.sh ${{ matrix.target }}
|
||||
|
||||
@@ -64,17 +69,22 @@ jobs:
|
||||
needs: check-changes
|
||||
if: needs.check-changes.outputs.has_changes == 'true'
|
||||
runs-on: windows-latest
|
||||
permissions:
|
||||
contents: read
|
||||
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
|
||||
|
||||
@@ -17,14 +17,22 @@ jobs:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
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@v1
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build a tarball
|
||||
run: ./dist.sh ${{ matrix.target }}
|
||||
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
$ErrorActionPreference = 'Stop'
|
||||
Set-Location "$PSScriptRoot/../.."
|
||||
|
||||
cargo build --release --locked --package mold
|
||||
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').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
|
||||
+99
-119
@@ -1,134 +1,103 @@
|
||||
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@v7
|
||||
- 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 --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-common -p mold-elf -p mold-tests -p mold --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@v7
|
||||
- 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 --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 --no-default-features --features system-allocator,x86_64 --test integration -- --native --test-threads 1 --timeout ${{ matrix.test-timeout }}
|
||||
- uses: actions/checkout@v6
|
||||
- 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@v7
|
||||
- uses: dtolnay/rust-toolchain@nightly
|
||||
- uses: actions/checkout@v6
|
||||
- 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:13
|
||||
- debian:11 # GCC 10 and CMake 3.18 - the minimum supported by mold
|
||||
- fedora
|
||||
- gentoo/stage3
|
||||
- opensuse/tumbleweed
|
||||
- ubuntu:22.04
|
||||
- ubuntu:26.04
|
||||
- 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@v7
|
||||
- run: ./install-test-deps.sh
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- run: cargo build --locked -p mold --no-default-features --features x86_64
|
||||
- run: cargo test --locked -p mold --no-default-features --features x86_64 --test integration -- --native
|
||||
- uses: actions/checkout@v6
|
||||
- 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@v7
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- run: cargo build --locked -p mold --no-default-features --features x86_64
|
||||
- uses: actions/checkout@v6
|
||||
- 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@v7
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- run: cargo build --locked -p mold --no-default-features --features x86_64,macho-x86_64
|
||||
- uses: actions/checkout@v6
|
||||
- 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@v7
|
||||
- uses: msys2/setup-msys2@v2
|
||||
- uses: actions/checkout@v6
|
||||
- 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 --no-default-features --features x86_64,macho-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@v7
|
||||
- uses: vmactions/freebsd-vm@v1
|
||||
- uses: actions/checkout@v6
|
||||
- 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 --no-default-features --features x86_64
|
||||
cargo test --locked -p mold --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)
|
||||
@@ -1,56 +0,0 @@
|
||||
name: Mach-O CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
|
||||
# A newer push to the same branch supersedes a run still in progress.
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
test:
|
||||
# The tests drive Apple's toolchain (cc, nm, otool, dyld_info, lldb),
|
||||
# so they need a macOS host. An arm64 runner covers both targets:
|
||||
# the harness runs every test natively and again under Rosetta.
|
||||
runs-on: macos-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
|
||||
- name: Install Rosetta 2
|
||||
run: |
|
||||
if ! arch -x86_64 /usr/bin/true 2>/dev/null; then
|
||||
sudo softwareupdate --install-rosetta --agree-to-license
|
||||
fi
|
||||
|
||||
- name: Update Rust
|
||||
run: rustup update --no-self-update stable && rustup default stable
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
|
||||
# The unit tests, then the shell tests with twice the harness's
|
||||
# default timeout, as mold's CI gives its slow configurations more:
|
||||
# here the simulator's tests are slow, lldb's above all, whose lldb
|
||||
# session alone takes ten seconds on a Mac, and the first of which
|
||||
# ran past a minute on the hosted runners. The executable is built
|
||||
# without the ELF targets, which these tests don't use.
|
||||
- run: cargo test --locked -p mold-macho -p mold-tests --lib
|
||||
- run: |
|
||||
cargo test --locked -p mold --no-default-features --features macho-arm64,macho-x86_64 \
|
||||
--test integration -- --timeout 120
|
||||
|
||||
# The harness reports only which tests failed; each test's log
|
||||
# and its output directory are kept under the target directory.
|
||||
- name: Upload test logs
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: test-logs
|
||||
path: target/debug/mold-test/out/test/
|
||||
if-no-files-found: ignore
|
||||
@@ -4,6 +4,11 @@ on:
|
||||
push:
|
||||
tags: ['v[0-9]*']
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
id-token: write
|
||||
|
||||
env:
|
||||
TAG: ${{ github.ref_name }}
|
||||
|
||||
@@ -19,19 +24,22 @@ jobs:
|
||||
- { target: ppc64le, os: ubuntu-24.04 }
|
||||
- { target: s390x, os: ubuntu-24.04 }
|
||||
- { target: loongarch64, os: ubuntu-24.04 }
|
||||
- { target: x86_64-musl, os: ubuntu-24.04 }
|
||||
- { target: aarch64-musl, os: ubuntu-24.04-arm }
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
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@v1
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- run: ./dist.sh ${{ matrix.target }}
|
||||
|
||||
- uses: actions/upload-artifact@v7
|
||||
@@ -41,16 +49,21 @@ jobs:
|
||||
|
||||
build-windows:
|
||||
runs-on: windows-latest
|
||||
permissions:
|
||||
contents: read
|
||||
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:
|
||||
@@ -60,10 +73,8 @@ jobs:
|
||||
publish:
|
||||
needs: [build-linux, build-windows]
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: actions/download-artifact@v8
|
||||
with:
|
||||
|
||||
@@ -1,28 +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-common and mold-elf as well as mold so Cargo enables their
|
||||
# dependency features.
|
||||
args=(
|
||||
--locked -Zbuild-std --target x86_64-unknown-linux-gnu
|
||||
--package mold-common --package mold-elf --package mold --no-default-features
|
||||
--features mold/system-allocator,mold/x86_64,blake3/pure,zstd/no_asm
|
||||
--test integration
|
||||
)
|
||||
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)
|
||||
"
|
||||
@@ -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
-7
@@ -1,8 +1,11 @@
|
||||
/target
|
||||
/out
|
||||
/dist
|
||||
/bench
|
||||
/.claude
|
||||
/.codex
|
||||
*.o
|
||||
*.so
|
||||
*~
|
||||
__pycache__/
|
||||
**/.DS_Store
|
||||
.cache
|
||||
core
|
||||
gentoo
|
||||
/mold-*.tar.gz
|
||||
/build*
|
||||
/mold
|
||||
/dist
|
||||
+493
@@ -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
-744
@@ -1,744 +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 = "block-buffer"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa"
|
||||
dependencies = [
|
||||
"hybrid-array",
|
||||
]
|
||||
|
||||
[[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 = "const-oid"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c"
|
||||
|
||||
[[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.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0667304c32ea56cb4cd6d2d7c0cfe9a2f8041229db8c033af7f8d69492429def"
|
||||
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 = "crypto-common"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453"
|
||||
dependencies = [
|
||||
"hybrid-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"const-oid",
|
||||
"crypto-common",
|
||||
]
|
||||
|
||||
[[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 = [
|
||||
"zlib-rs",
|
||||
]
|
||||
|
||||
[[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 = "hybrid-array"
|
||||
version = "0.4.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "27f864f10dfb56725ce5ce5472bc52252c8f93a4ab86327122cebf62c5f59a17"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
||||
|
||||
[[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 = "libz-rs-sys"
|
||||
version = "0.6.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "434324ee61a8e4a05dd3fdf03d51c073ea7fdaf5a35b2ca33c4ca9f3f37def4a"
|
||||
dependencies = [
|
||||
"zlib-rs",
|
||||
]
|
||||
|
||||
[[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 = "mold"
|
||||
version = "3.0.0"
|
||||
dependencies = [
|
||||
"mold-elf",
|
||||
"mold-elf-arm32",
|
||||
"mold-elf-arm32be",
|
||||
"mold-elf-arm64",
|
||||
"mold-elf-arm64be",
|
||||
"mold-elf-i386",
|
||||
"mold-elf-loongarch32",
|
||||
"mold-elf-loongarch64",
|
||||
"mold-elf-m68k",
|
||||
"mold-elf-ppc32",
|
||||
"mold-elf-ppc64v1",
|
||||
"mold-elf-ppc64v2",
|
||||
"mold-elf-riscv32",
|
||||
"mold-elf-riscv32be",
|
||||
"mold-elf-riscv64",
|
||||
"mold-elf-riscv64be",
|
||||
"mold-elf-s390x",
|
||||
"mold-elf-sh4",
|
||||
"mold-elf-sh4be",
|
||||
"mold-elf-sparc64",
|
||||
"mold-elf-x86_64",
|
||||
"mold-macho",
|
||||
"mold-macho-arm64",
|
||||
"mold-macho-x86_64",
|
||||
"mold-mimalloc",
|
||||
"mold-tests",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-common"
|
||||
version = "3.0.0"
|
||||
dependencies = [
|
||||
"bstr",
|
||||
"cpp_demangle",
|
||||
"flate2",
|
||||
"libc",
|
||||
"libz-rs-sys",
|
||||
"memchr",
|
||||
"memmap2",
|
||||
"portable-atomic",
|
||||
"rayon",
|
||||
"rustc-demangle",
|
||||
"xxhash-rust",
|
||||
"zlib-rs",
|
||||
"zstd",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-elf"
|
||||
version = "3.0.0"
|
||||
dependencies = [
|
||||
"blake3",
|
||||
"bstr",
|
||||
"cc",
|
||||
"crc32fast",
|
||||
"getrandom",
|
||||
"hashbrown",
|
||||
"libc",
|
||||
"memchr",
|
||||
"mold-common",
|
||||
"portable-atomic",
|
||||
"rayon",
|
||||
"uuid",
|
||||
"xxhash-rust",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-elf-arm32"
|
||||
version = "3.0.0"
|
||||
dependencies = [
|
||||
"mold-elf",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-elf-arm32be"
|
||||
version = "3.0.0"
|
||||
dependencies = [
|
||||
"mold-elf",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-elf-arm64"
|
||||
version = "3.0.0"
|
||||
dependencies = [
|
||||
"mold-elf",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-elf-arm64be"
|
||||
version = "3.0.0"
|
||||
dependencies = [
|
||||
"mold-elf",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-elf-i386"
|
||||
version = "3.0.0"
|
||||
dependencies = [
|
||||
"mold-elf",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-elf-loongarch32"
|
||||
version = "3.0.0"
|
||||
dependencies = [
|
||||
"mold-elf",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-elf-loongarch64"
|
||||
version = "3.0.0"
|
||||
dependencies = [
|
||||
"mold-elf",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-elf-m68k"
|
||||
version = "3.0.0"
|
||||
dependencies = [
|
||||
"mold-elf",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-elf-ppc32"
|
||||
version = "3.0.0"
|
||||
dependencies = [
|
||||
"mold-elf",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-elf-ppc64v1"
|
||||
version = "3.0.0"
|
||||
dependencies = [
|
||||
"mold-elf",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-elf-ppc64v2"
|
||||
version = "3.0.0"
|
||||
dependencies = [
|
||||
"mold-elf",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-elf-riscv32"
|
||||
version = "3.0.0"
|
||||
dependencies = [
|
||||
"mold-elf",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-elf-riscv32be"
|
||||
version = "3.0.0"
|
||||
dependencies = [
|
||||
"mold-elf",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-elf-riscv64"
|
||||
version = "3.0.0"
|
||||
dependencies = [
|
||||
"mold-elf",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-elf-riscv64be"
|
||||
version = "3.0.0"
|
||||
dependencies = [
|
||||
"mold-elf",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-elf-s390x"
|
||||
version = "3.0.0"
|
||||
dependencies = [
|
||||
"mold-elf",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-elf-sh4"
|
||||
version = "3.0.0"
|
||||
dependencies = [
|
||||
"mold-elf",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-elf-sh4be"
|
||||
version = "3.0.0"
|
||||
dependencies = [
|
||||
"mold-elf",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-elf-sparc64"
|
||||
version = "3.0.0"
|
||||
dependencies = [
|
||||
"mold-elf",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-elf-x86_64"
|
||||
version = "3.0.0"
|
||||
dependencies = [
|
||||
"mold-elf",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-macho"
|
||||
version = "3.0.0"
|
||||
dependencies = [
|
||||
"hashbrown",
|
||||
"libc",
|
||||
"memchr",
|
||||
"memmap2",
|
||||
"mold-common",
|
||||
"portable-atomic",
|
||||
"rayon",
|
||||
"serde_json",
|
||||
"sha1",
|
||||
"sha2",
|
||||
"uuid",
|
||||
"xxhash-rust",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-macho-arm64"
|
||||
version = "3.0.0"
|
||||
dependencies = [
|
||||
"mold-macho",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-macho-x86_64"
|
||||
version = "3.0.0"
|
||||
dependencies = [
|
||||
"mold-macho",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-mimalloc"
|
||||
version = "0.1.52"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b7efd1b6c74e7a09c1d296af4875b79118a5ea32d70736601a8323ddcbcb4c2"
|
||||
dependencies = [
|
||||
"mold-mimalloc-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-mimalloc-sys"
|
||||
version = "0.1.49"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "66a79c372cdbf45bf4548fd4aa40a0b2805ef2ee8e25e5185e11bf2b5c5de366"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-tests"
|
||||
version = "3.0.0"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[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"
|
||||
version = "1.0.229"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[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 = "serde_json"
|
||||
version = "1.0.151"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
"serde",
|
||||
"serde_core",
|
||||
"zmij",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha1"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[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 = "typenum"
|
||||
version = "1.20.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"
|
||||
|
||||
[[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 = "xxhash-rust"
|
||||
version = "0.8.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aee1b19627c7c60102ab80d3a9cbe18de90bfe03bfa6c3715447681f0e8c8af6"
|
||||
|
||||
[[package]]
|
||||
name = "zlib-rs"
|
||||
version = "0.6.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b268e58e7c693d7c271f93ffc4ba3b380412554231c85bf61ca7af91042a4112"
|
||||
|
||||
[[package]]
|
||||
name = "zmij"
|
||||
version = "1.0.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
|
||||
|
||||
[[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",
|
||||
]
|
||||
-51
@@ -1,51 +0,0 @@
|
||||
# The ELF linker in elf/ 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]
|
||||
resolver = "3"
|
||||
members = ["cli", "common", "elf", "macho", "arch/*", "tests"]
|
||||
|
||||
[workspace.package]
|
||||
version = "3.0.0"
|
||||
edition = "2024"
|
||||
rust-version = "1.95"
|
||||
license = "MIT"
|
||||
repository = "https://github.com/rui314/mold"
|
||||
|
||||
# The workspace crates are published together and depend on each other's
|
||||
# internals, so each one requires the exact version of the others. Keep
|
||||
# these versions equal to the workspace version above.
|
||||
[workspace.dependencies]
|
||||
libc = "0.2"
|
||||
mold-common = { path = "common", version = "=3.0.0" }
|
||||
mold-elf = { path = "elf", version = "=3.0.0" }
|
||||
mold-elf-x86_64 = { path = "arch/elf-x86_64", version = "=3.0.0" }
|
||||
mold-elf-i386 = { path = "arch/elf-i386", version = "=3.0.0" }
|
||||
mold-elf-arm32 = { path = "arch/elf-arm32", version = "=3.0.0" }
|
||||
mold-elf-arm32be = { path = "arch/elf-arm32be", version = "=3.0.0" }
|
||||
mold-elf-arm64 = { path = "arch/elf-arm64", version = "=3.0.0" }
|
||||
mold-elf-arm64be = { path = "arch/elf-arm64be", version = "=3.0.0" }
|
||||
mold-elf-riscv64 = { path = "arch/elf-riscv64", version = "=3.0.0" }
|
||||
mold-elf-riscv64be = { path = "arch/elf-riscv64be", version = "=3.0.0" }
|
||||
mold-elf-riscv32 = { path = "arch/elf-riscv32", version = "=3.0.0" }
|
||||
mold-elf-riscv32be = { path = "arch/elf-riscv32be", version = "=3.0.0" }
|
||||
mold-elf-ppc64v2 = { path = "arch/elf-ppc64v2", version = "=3.0.0" }
|
||||
mold-elf-ppc32 = { path = "arch/elf-ppc32", version = "=3.0.0" }
|
||||
mold-elf-ppc64v1 = { path = "arch/elf-ppc64v1", version = "=3.0.0" }
|
||||
mold-elf-s390x = { path = "arch/elf-s390x", version = "=3.0.0" }
|
||||
mold-elf-sparc64 = { path = "arch/elf-sparc64", version = "=3.0.0" }
|
||||
mold-elf-m68k = { path = "arch/elf-m68k", version = "=3.0.0" }
|
||||
mold-elf-sh4 = { path = "arch/elf-sh4", version = "=3.0.0" }
|
||||
mold-elf-sh4be = { path = "arch/elf-sh4be", version = "=3.0.0" }
|
||||
mold-elf-loongarch64 = { path = "arch/elf-loongarch64", version = "=3.0.0" }
|
||||
mold-elf-loongarch32 = { path = "arch/elf-loongarch32", version = "=3.0.0" }
|
||||
mold-macho = { path = "macho", version = "=3.0.0" }
|
||||
mold-macho-arm64 = { path = "arch/macho-arm64", version = "=3.0.0" }
|
||||
mold-macho-x86_64 = { path = "arch/macho-x86_64", version = "=3.0.0" }
|
||||
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
# Copying generic and inline functions once per crate instead of once per
|
||||
# codegen unit makes the executable about a quarter smaller.
|
||||
codegen-units = 1
|
||||
@@ -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.
|
||||

|
||||
|
||||
| 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,130 +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:
|
||||
|
||||
[](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 is `target/release/mold`.
|
||||
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`.
|
||||
|
||||
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
|
||||
|
||||
@@ -174,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
|
||||
@@ -249,10 +179,9 @@ mold -run make <make-options-if-any>
|
||||
```
|
||||
|
||||
Internally, mold invokes a given command with the `LD_PRELOAD` environment
|
||||
variable set to a shared object file embedded in mold. The shared object file
|
||||
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`.
|
||||
This feature is available on Linux and FreeBSD.
|
||||
`argv[0]` with `mold` if it is `ld`, `ld.bf`, `ld.gold`, or `ld.lld`.
|
||||
|
||||
</details>
|
||||
|
||||
@@ -290,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.
|
||||
|
||||

|
||||
|
||||
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:
|
||||
[](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
|
||||
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
# Architecture crates
|
||||
|
||||
Each subdirectory is a small crate that instantiates the generic ELF or
|
||||
Mach-O 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. A directory is named after its crate without
|
||||
the `mold-` prefix, e.g. `elf-x86_64` holds `mold-elf-x86_64`.
|
||||
|
||||
The ELF linker lives in [`elf/`](../elf/), with target-specific code in
|
||||
[`elf/arch/`](../elf/arch/), and the Mach-O linker in [`macho/`](../macho/),
|
||||
with target-specific code in [`macho/arm64.rs`](../macho/arm64.rs) and
|
||||
[`macho/x86_64.rs`](../macho/x86_64.rs). These crates provide entry points
|
||||
that the [`cli/`](../cli/) executable selects for the input files' target.
|
||||
@@ -1,14 +0,0 @@
|
||||
[package]
|
||||
name = "mold-elf-arm32"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
description = "An internal crate of the mold linker, which instantiates its ELF linker for arm32"
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
mold-elf.workspace = true
|
||||
@@ -1,3 +0,0 @@
|
||||
pub fn link(cmdline: mold_elf::driver::Cmdline) -> Option<&'static str> {
|
||||
mold_elf::driver::link::<mold_elf::arch::Arm32>(cmdline)
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
[package]
|
||||
name = "mold-elf-arm32be"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
description = "An internal crate of the mold linker, which instantiates its ELF linker for arm32be"
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
mold-elf.workspace = true
|
||||
@@ -1,3 +0,0 @@
|
||||
pub fn link(cmdline: mold_elf::driver::Cmdline) -> Option<&'static str> {
|
||||
mold_elf::driver::link::<mold_elf::arch::Arm32Be>(cmdline)
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
[package]
|
||||
name = "mold-elf-arm64"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
description = "An internal crate of the mold linker, which instantiates its ELF linker for arm64"
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
mold-elf.workspace = true
|
||||
@@ -1,3 +0,0 @@
|
||||
pub fn link(cmdline: mold_elf::driver::Cmdline) -> Option<&'static str> {
|
||||
mold_elf::driver::link::<mold_elf::arch::Arm64>(cmdline)
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
[package]
|
||||
name = "mold-elf-arm64be"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
description = "An internal crate of the mold linker, which instantiates its ELF linker for arm64be"
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
mold-elf.workspace = true
|
||||
@@ -1,3 +0,0 @@
|
||||
pub fn link(cmdline: mold_elf::driver::Cmdline) -> Option<&'static str> {
|
||||
mold_elf::driver::link::<mold_elf::arch::Arm64Be>(cmdline)
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
[package]
|
||||
name = "mold-elf-i386"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
description = "An internal crate of the mold linker, which instantiates its ELF linker for i386"
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
mold-elf.workspace = true
|
||||
@@ -1,3 +0,0 @@
|
||||
pub fn link(cmdline: mold_elf::driver::Cmdline) -> Option<&'static str> {
|
||||
mold_elf::driver::link::<mold_elf::arch::I386>(cmdline)
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
[package]
|
||||
name = "mold-elf-loongarch32"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
description = "An internal crate of the mold linker, which instantiates its ELF linker for loongarch32"
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
mold-elf.workspace = true
|
||||
@@ -1,3 +0,0 @@
|
||||
pub fn link(cmdline: mold_elf::driver::Cmdline) -> Option<&'static str> {
|
||||
mold_elf::driver::link::<mold_elf::arch::LoongArch32>(cmdline)
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
[package]
|
||||
name = "mold-elf-loongarch64"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
description = "An internal crate of the mold linker, which instantiates its ELF linker for loongarch64"
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
mold-elf.workspace = true
|
||||
@@ -1,3 +0,0 @@
|
||||
pub fn link(cmdline: mold_elf::driver::Cmdline) -> Option<&'static str> {
|
||||
mold_elf::driver::link::<mold_elf::arch::LoongArch64>(cmdline)
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
[package]
|
||||
name = "mold-elf-m68k"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
description = "An internal crate of the mold linker, which instantiates its ELF linker for m68k"
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
mold-elf.workspace = true
|
||||
@@ -1,3 +0,0 @@
|
||||
pub fn link(cmdline: mold_elf::driver::Cmdline) -> Option<&'static str> {
|
||||
mold_elf::driver::link::<mold_elf::arch::M68k>(cmdline)
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
[package]
|
||||
name = "mold-elf-ppc32"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
description = "An internal crate of the mold linker, which instantiates its ELF linker for ppc32"
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
mold-elf.workspace = true
|
||||
@@ -1,3 +0,0 @@
|
||||
pub fn link(cmdline: mold_elf::driver::Cmdline) -> Option<&'static str> {
|
||||
mold_elf::driver::link::<mold_elf::arch::Ppc32>(cmdline)
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
[package]
|
||||
name = "mold-elf-ppc64v1"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
description = "An internal crate of the mold linker, which instantiates its ELF linker for ppc64v1"
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
mold-elf.workspace = true
|
||||
@@ -1,3 +0,0 @@
|
||||
pub fn link(cmdline: mold_elf::driver::Cmdline) -> Option<&'static str> {
|
||||
mold_elf::driver::link::<mold_elf::arch::Ppc64V1>(cmdline)
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
[package]
|
||||
name = "mold-elf-ppc64v2"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
description = "An internal crate of the mold linker, which instantiates its ELF linker for ppc64v2"
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
mold-elf.workspace = true
|
||||
@@ -1,3 +0,0 @@
|
||||
pub fn link(cmdline: mold_elf::driver::Cmdline) -> Option<&'static str> {
|
||||
mold_elf::driver::link::<mold_elf::arch::Ppc64V2>(cmdline)
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
[package]
|
||||
name = "mold-elf-riscv32"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
description = "An internal crate of the mold linker, which instantiates its ELF linker for riscv32"
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
mold-elf.workspace = true
|
||||
@@ -1,3 +0,0 @@
|
||||
pub fn link(cmdline: mold_elf::driver::Cmdline) -> Option<&'static str> {
|
||||
mold_elf::driver::link::<mold_elf::arch::Riscv32>(cmdline)
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
[package]
|
||||
name = "mold-elf-riscv32be"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
description = "An internal crate of the mold linker, which instantiates its ELF linker for riscv32be"
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
mold-elf.workspace = true
|
||||
@@ -1,3 +0,0 @@
|
||||
pub fn link(cmdline: mold_elf::driver::Cmdline) -> Option<&'static str> {
|
||||
mold_elf::driver::link::<mold_elf::arch::Riscv32Be>(cmdline)
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
[package]
|
||||
name = "mold-elf-riscv64"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
description = "An internal crate of the mold linker, which instantiates its ELF linker for riscv64"
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
mold-elf.workspace = true
|
||||
@@ -1,3 +0,0 @@
|
||||
pub fn link(cmdline: mold_elf::driver::Cmdline) -> Option<&'static str> {
|
||||
mold_elf::driver::link::<mold_elf::arch::Riscv64>(cmdline)
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
[package]
|
||||
name = "mold-elf-riscv64be"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
description = "An internal crate of the mold linker, which instantiates its ELF linker for riscv64be"
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
mold-elf.workspace = true
|
||||
@@ -1,3 +0,0 @@
|
||||
pub fn link(cmdline: mold_elf::driver::Cmdline) -> Option<&'static str> {
|
||||
mold_elf::driver::link::<mold_elf::arch::Riscv64Be>(cmdline)
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
[package]
|
||||
name = "mold-elf-s390x"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
description = "An internal crate of the mold linker, which instantiates its ELF linker for s390x"
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
mold-elf.workspace = true
|
||||
@@ -1,3 +0,0 @@
|
||||
pub fn link(cmdline: mold_elf::driver::Cmdline) -> Option<&'static str> {
|
||||
mold_elf::driver::link::<mold_elf::arch::S390x>(cmdline)
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
[package]
|
||||
name = "mold-elf-sh4"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
description = "An internal crate of the mold linker, which instantiates its ELF linker for sh4"
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
mold-elf.workspace = true
|
||||
@@ -1,3 +0,0 @@
|
||||
pub fn link(cmdline: mold_elf::driver::Cmdline) -> Option<&'static str> {
|
||||
mold_elf::driver::link::<mold_elf::arch::Sh4>(cmdline)
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
[package]
|
||||
name = "mold-elf-sh4be"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
description = "An internal crate of the mold linker, which instantiates its ELF linker for sh4be"
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
mold-elf.workspace = true
|
||||
@@ -1,3 +0,0 @@
|
||||
pub fn link(cmdline: mold_elf::driver::Cmdline) -> Option<&'static str> {
|
||||
mold_elf::driver::link::<mold_elf::arch::Sh4Be>(cmdline)
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
[package]
|
||||
name = "mold-elf-sparc64"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
description = "An internal crate of the mold linker, which instantiates its ELF linker for sparc64"
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
mold-elf.workspace = true
|
||||
@@ -1,3 +0,0 @@
|
||||
pub fn link(cmdline: mold_elf::driver::Cmdline) -> Option<&'static str> {
|
||||
mold_elf::driver::link::<mold_elf::arch::Sparc64>(cmdline)
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
[package]
|
||||
name = "mold-elf-x86_64"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
description = "An internal crate of the mold linker, which instantiates its ELF linker for x86_64"
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
mold-elf.workspace = true
|
||||
@@ -1,3 +0,0 @@
|
||||
pub fn link(cmdline: mold_elf::driver::Cmdline) -> Option<&'static str> {
|
||||
mold_elf::driver::link::<mold_elf::arch::X86_64>(cmdline)
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
[package]
|
||||
name = "mold-macho-arm64"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
description = "An internal crate of the mold linker, which instantiates its Mach-O linker for arm64"
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
mold-macho.workspace = true
|
||||
@@ -1,7 +0,0 @@
|
||||
//! The linker instantiated for arm64. Every target has a crate like this
|
||||
//! one, so that the compiler can build the targets in parallel.
|
||||
|
||||
/// Links for this target.
|
||||
pub fn link(parsed: mold_macho::driver::ParsedArgs) {
|
||||
mold_macho::driver::link::<mold_macho::arch::Arm64>(parsed)
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
[package]
|
||||
name = "mold-macho-x86_64"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
description = "An internal crate of the mold linker, which instantiates its Mach-O linker for x86_64"
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
mold-macho.workspace = true
|
||||
@@ -1,7 +0,0 @@
|
||||
//! The linker instantiated for x86_64. Every target has a crate like this
|
||||
//! one, so that the compiler can build the targets in parallel.
|
||||
|
||||
/// Links for this target.
|
||||
pub fn link(parsed: mold_macho::driver::ParsedArgs) {
|
||||
mold_macho::driver::link::<mold_macho::arch::X86_64>(parsed)
|
||||
}
|
||||
@@ -1,82 +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
|
||||
repository.workspace = true
|
||||
readme = "../README.md"
|
||||
keywords = ["linker", "elf"]
|
||||
categories = ["command-line-utilities", "development-tools::build-utils"]
|
||||
|
||||
[[bin]]
|
||||
name = "mold"
|
||||
path = "main.rs"
|
||||
|
||||
[[test]]
|
||||
name = "integration"
|
||||
path = "integration.rs"
|
||||
harness = false
|
||||
|
||||
[dependencies]
|
||||
mimalloc = { package = "mold-mimalloc", version = "0.1.52" }
|
||||
mold-elf.workspace = true
|
||||
mold-elf-x86_64 = { workspace = true, optional = true }
|
||||
mold-elf-i386 = { workspace = true, optional = true }
|
||||
mold-elf-arm32 = { workspace = true, optional = true }
|
||||
mold-elf-arm32be = { workspace = true, optional = true }
|
||||
mold-elf-arm64 = { workspace = true, optional = true }
|
||||
mold-elf-arm64be = { workspace = true, optional = true }
|
||||
mold-elf-riscv64 = { workspace = true, optional = true }
|
||||
mold-elf-riscv64be = { workspace = true, optional = true }
|
||||
mold-elf-riscv32 = { workspace = true, optional = true }
|
||||
mold-elf-riscv32be = { workspace = true, optional = true }
|
||||
mold-elf-ppc64v2 = { workspace = true, optional = true }
|
||||
mold-elf-ppc32 = { workspace = true, optional = true }
|
||||
mold-elf-ppc64v1 = { workspace = true, optional = true }
|
||||
mold-elf-s390x = { workspace = true, optional = true }
|
||||
mold-elf-sparc64 = { workspace = true, optional = true }
|
||||
mold-elf-m68k = { workspace = true, optional = true }
|
||||
mold-elf-sh4 = { workspace = true, optional = true }
|
||||
mold-elf-sh4be = { workspace = true, optional = true }
|
||||
mold-elf-loongarch64 = { workspace = true, optional = true }
|
||||
mold-elf-loongarch32 = { workspace = true, optional = true }
|
||||
mold-macho.workspace = true
|
||||
mold-macho-arm64 = { workspace = true, optional = true }
|
||||
mold-macho-x86_64 = { workspace = true, 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",
|
||||
"macho-arm64", "macho-x86_64",
|
||||
]
|
||||
system-allocator = []
|
||||
x86_64 = ["dep:mold-elf-x86_64"]
|
||||
i386 = ["dep:mold-elf-i386"]
|
||||
arm32 = ["dep:mold-elf-arm32"]
|
||||
arm32be = ["dep:mold-elf-arm32be"]
|
||||
arm64 = ["dep:mold-elf-arm64"]
|
||||
arm64be = ["dep:mold-elf-arm64be"]
|
||||
riscv64 = ["dep:mold-elf-riscv64"]
|
||||
riscv64be = ["dep:mold-elf-riscv64be"]
|
||||
riscv32 = ["dep:mold-elf-riscv32"]
|
||||
riscv32be = ["dep:mold-elf-riscv32be"]
|
||||
ppc64v2 = ["dep:mold-elf-ppc64v2"]
|
||||
ppc32 = ["dep:mold-elf-ppc32"]
|
||||
ppc64v1 = ["dep:mold-elf-ppc64v1"]
|
||||
s390x = ["dep:mold-elf-s390x"]
|
||||
sparc64 = ["dep:mold-elf-sparc64"]
|
||||
m68k = ["dep:mold-elf-m68k"]
|
||||
sh4 = ["dep:mold-elf-sh4"]
|
||||
sh4be = ["dep:mold-elf-sh4be"]
|
||||
loongarch64 = ["dep:mold-elf-loongarch64"]
|
||||
loongarch32 = ["dep:mold-elf-loongarch32"]
|
||||
macho-arm64 = ["dep:mold-macho-arm64"]
|
||||
macho-x86_64 = ["dep:mold-macho-x86_64"]
|
||||
@@ -1,55 +0,0 @@
|
||||
//! Passes host-dependent options to the linker for the mold executable.
|
||||
|
||||
use std::env;
|
||||
use std::ffi::OsString;
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
fn main() {
|
||||
println!("cargo:rerun-if-changed=build.rs");
|
||||
|
||||
// mimalloc uses 64-bit atomics, which C compilers implement as calls
|
||||
// into libatomic on hosts without them, such as 32-bit PowerPC.
|
||||
let widths = 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");
|
||||
}
|
||||
|
||||
// The linker is instantiated for each target, and many functions compile
|
||||
// to the same machine code for several targets. Identical Code Folding
|
||||
// merges them, which roughly halves the text of a release build. GNU ld
|
||||
// doesn't support it, so we pass the option only if the linker takes it.
|
||||
if env::var("PROFILE").unwrap() == "release" && link_succeeds("-Wl,--icf=all") {
|
||||
println!("cargo:rustc-link-arg-bins=-Wl,--icf=all");
|
||||
}
|
||||
}
|
||||
|
||||
/// Links an empty program with the given linker argument, using the same
|
||||
/// compiler, linker and flags as the mold executable.
|
||||
fn link_succeeds(arg: &str) -> bool {
|
||||
let out_dir = env::var_os("OUT_DIR").unwrap();
|
||||
let mut cmd = Command::new(env::var_os("RUSTC").unwrap());
|
||||
cmd.arg("--target").arg(env::var("TARGET").unwrap());
|
||||
cmd.arg("-o").arg(Path::new(&out_dir).join("link-probe"));
|
||||
cmd.arg(format!("-Clink-arg={arg}"));
|
||||
if let Some(linker) = env::var_os("RUSTC_LINKER") {
|
||||
let mut opt = OsString::from("-Clinker=");
|
||||
opt.push(linker);
|
||||
cmd.arg(opt);
|
||||
}
|
||||
// Flags are separated by 0x1f.
|
||||
let flags = env::var("CARGO_ENCODED_RUSTFLAGS").unwrap_or_default();
|
||||
cmd.args(flags.split('\x1f').filter(|flag| !flag.is_empty()));
|
||||
cmd.arg("-");
|
||||
cmd.stdin(Stdio::piped()).stdout(Stdio::null()).stderr(Stdio::null());
|
||||
|
||||
let Ok(mut child) = cmd.spawn() else {
|
||||
return false;
|
||||
};
|
||||
child.stdin.take().unwrap().write_all(b"fn main() {}").unwrap();
|
||||
child.wait().is_ok_and(|status| status.success())
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
//! Runs the shell tests of the format the host builds programs for. The
|
||||
//! Mach-O tests drive Apple's toolchain, which only macOS has, and the ELF
|
||||
//! tests need a toolchain that makes ELF programs, which macOS lacks.
|
||||
|
||||
use std::path::Path;
|
||||
use std::process::ExitCode;
|
||||
|
||||
fn main() -> ExitCode {
|
||||
#[cfg(target_os = "macos")]
|
||||
let (cases, run) = ("tests/macho", mold_tests::macho::run);
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
let (cases, run) = ("tests/elf", mold_tests::elf::run);
|
||||
|
||||
let root = Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap();
|
||||
run(&root.join(cases), Path::new(env!("CARGO_BIN_EXE_mold")))
|
||||
}
|
||||
-115
@@ -1,115 +0,0 @@
|
||||
//! The `mold` executable: runs the ELF linker, or the Mach-O linker if
|
||||
//! invoked as `ld64.mold`, 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.
|
||||
|
||||
// rustc emits asynchronous unwind tables, which compact unwind can't
|
||||
// encode, so the unwind info of most functions goes to __eh_frame. In a
|
||||
// debug build, that section exceeds the 16 MiB that compact unwind can
|
||||
// refer to, and Apple's ld warns about that. The warning concerns only
|
||||
// the speed of unwinding, so we keep rustc from reporting it.
|
||||
#![cfg_attr(all(target_vendor = "apple", debug_assertions), allow(linker_messages))]
|
||||
|
||||
use mold_elf::driver::Cmdline;
|
||||
use std::ffi::OsStr;
|
||||
use std::path::Path;
|
||||
|
||||
// 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(Cmdline) -> Option<&'static str>;
|
||||
type MachoLinkFn = fn(mold_macho::driver::ParsedArgs);
|
||||
|
||||
// Each target has its own monomorphized link function. The ELF linker
|
||||
// starts with the first enabled target and switches to the matching
|
||||
// function if the inputs are for another target. The Mach-O linker finds
|
||||
// the target as it parses the options, and calls its function.
|
||||
const ELF_TARGETS: &[(&str, LinkFn)] = &[
|
||||
#[cfg(feature = "x86_64")]
|
||||
("x86_64", mold_elf_x86_64::link),
|
||||
#[cfg(feature = "i386")]
|
||||
("i386", mold_elf_i386::link),
|
||||
#[cfg(feature = "arm32")]
|
||||
("arm32", mold_elf_arm32::link),
|
||||
#[cfg(feature = "arm32be")]
|
||||
("arm32be", mold_elf_arm32be::link),
|
||||
#[cfg(feature = "arm64")]
|
||||
("arm64", mold_elf_arm64::link),
|
||||
#[cfg(feature = "arm64be")]
|
||||
("arm64be", mold_elf_arm64be::link),
|
||||
#[cfg(feature = "riscv64")]
|
||||
("riscv64", mold_elf_riscv64::link),
|
||||
#[cfg(feature = "riscv64be")]
|
||||
("riscv64be", mold_elf_riscv64be::link),
|
||||
#[cfg(feature = "riscv32")]
|
||||
("riscv32", mold_elf_riscv32::link),
|
||||
#[cfg(feature = "riscv32be")]
|
||||
("riscv32be", mold_elf_riscv32be::link),
|
||||
#[cfg(feature = "ppc64v2")]
|
||||
("ppc64v2", mold_elf_ppc64v2::link),
|
||||
#[cfg(feature = "ppc32")]
|
||||
("ppc32", mold_elf_ppc32::link),
|
||||
#[cfg(feature = "ppc64v1")]
|
||||
("ppc64v1", mold_elf_ppc64v1::link),
|
||||
#[cfg(feature = "s390x")]
|
||||
("s390x", mold_elf_s390x::link),
|
||||
#[cfg(feature = "sparc64")]
|
||||
("sparc64", mold_elf_sparc64::link),
|
||||
#[cfg(feature = "m68k")]
|
||||
("m68k", mold_elf_m68k::link),
|
||||
#[cfg(feature = "sh4")]
|
||||
("sh4", mold_elf_sh4::link),
|
||||
#[cfg(feature = "sh4be")]
|
||||
("sh4be", mold_elf_sh4be::link),
|
||||
#[cfg(feature = "loongarch64")]
|
||||
("loongarch64", mold_elf_loongarch64::link),
|
||||
#[cfg(feature = "loongarch32")]
|
||||
("loongarch32", mold_elf_loongarch32::link),
|
||||
];
|
||||
|
||||
const MACHO_TARGETS: &[(&str, MachoLinkFn)] = &[
|
||||
#[cfg(feature = "macho-arm64")]
|
||||
("arm64", mold_macho_arm64::link),
|
||||
#[cfg(feature = "macho-x86_64")]
|
||||
("x86_64", mold_macho_x86_64::link),
|
||||
];
|
||||
|
||||
fn link_fn<F: Copy>(targets: &[(&str, F)], target: &str) -> F {
|
||||
for &(name, link) in targets {
|
||||
if name == target {
|
||||
return link;
|
||||
}
|
||||
}
|
||||
eprintln!(
|
||||
"mold: unsupported target: {target}; rebuild mold with the appropriate target support"
|
||||
);
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
// As with lld's ld64.lld, the name tells mold to act as Apple's linker,
|
||||
// ld64. clang on macOS looks for ld64.mold when given -fuse-ld=mold.
|
||||
fn is_ld64(argv0: &OsStr) -> bool {
|
||||
let name = Path::new(argv0).file_name().unwrap_or_default().as_encoded_bytes();
|
||||
name == b"ld64" || name.starts_with(b"ld64.")
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let args: Vec<_> = std::env::args_os().collect();
|
||||
if args.first().is_some_and(|argv0| is_ld64(argv0)) {
|
||||
let link = |target: &str, parsed| link_fn(MACHO_TARGETS, target)(parsed);
|
||||
mold_macho::driver::main(args, link);
|
||||
std::process::exit(0);
|
||||
}
|
||||
|
||||
let Some(&(initial_target, _)) = ELF_TARGETS.first() else {
|
||||
eprintln!("mold: no targets enabled; rebuild mold with the appropriate target support");
|
||||
std::process::exit(1);
|
||||
};
|
||||
let link = |target: &str, cmdline| link_fn(ELF_TARGETS, target)(cmdline);
|
||||
mold_elf::driver::main(args, initial_target, link);
|
||||
std::process::exit(0);
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
[package]
|
||||
name = "mold-common"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
description = "An internal crate of the mold linker, which holds the code its linkers share"
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
bstr = "1"
|
||||
cpp_demangle = "0.5.1"
|
||||
flate2 = { version = "1", default-features = false, features = ["zlib-rs"] }
|
||||
libc.workspace = true
|
||||
libz-rs-sys = "0.6.8"
|
||||
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"
|
||||
xxhash-rust = { version = "0.8", features = ["xxh3"] }
|
||||
zlib-rs = { version = "0.6.8", default-features = false }
|
||||
zstd = "0.13"
|
||||
@@ -1,206 +0,0 @@
|
||||
//! This file contains functions to read an archive file (.a file).
|
||||
//! An archive file is just a bundle of object files. It's similar to
|
||||
//! tar or zip, but the contents are not compressed.
|
||||
//!
|
||||
//! An archive file is either "regular" or "thin". A regular archive
|
||||
//! contains object files directly, while a thin archive contains only
|
||||
//! pathnames. In the latter case, actual file contents have to be read
|
||||
//! from given pathnames. A regular archive is sometimes called a "fat"
|
||||
//! archive as opposed to "thin".
|
||||
//!
|
||||
//! If an archive file is given to the linker, the linker pulls out
|
||||
//! object files that are needed to resolve undefined symbols. So,
|
||||
//! bundling object files as an archive and giving that archive to the
|
||||
//! linker has a different meaning than directly giving the same set of
|
||||
//! object files to the linker. The former links only needed object
|
||||
//! files, while the latter links all the given object files.
|
||||
//!
|
||||
//! Therefore, if you link libc.a for example, not all the libc
|
||||
//! functions are linked to your binary. Instead, only object files
|
||||
//! that provide functions and variables used in your program get
|
||||
//! linked. To make this efficient, static library functions are
|
||||
//! usually placed in separate object files in an archive file. You can
|
||||
//! see the contents of libc.a by running `ar t
|
||||
//! /usr/lib/x86_64-linux-gnu/libc.a`.
|
||||
//!
|
||||
//! GNU ar stores long member names in a string table member. BSD ar,
|
||||
//! which macOS uses, stores a long name right after the member header
|
||||
//! instead, and puts a symbol table in a member named __.SYMDEF. Both
|
||||
//! forms are read here.
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use crate::bytes::{cstr_at, os_str};
|
||||
use crate::fatal;
|
||||
use crate::mapped_file::{MappedFile, apply_chroot};
|
||||
|
||||
const HEADER_SIZE: usize = 60;
|
||||
|
||||
/// A parsed archive member header.
|
||||
struct ArHeader<'a> {
|
||||
name: &'a [u8],
|
||||
date: u64,
|
||||
size: usize,
|
||||
}
|
||||
|
||||
impl<'a> ArHeader<'a> {
|
||||
fn parse(bytes: &'a [u8]) -> Option<Self> {
|
||||
let bytes = bytes.get(..HEADER_SIZE)?;
|
||||
let date = parse_decimal(&bytes[16..28]) as u64;
|
||||
let size = parse_decimal(&bytes[48..58]);
|
||||
Some(ArHeader { name: &bytes[..16], date, size })
|
||||
}
|
||||
|
||||
fn is_strtab(&self) -> bool {
|
||||
self.name.starts_with(b"// ")
|
||||
}
|
||||
|
||||
fn is_symtab(&self) -> bool {
|
||||
self.name.starts_with(b"/ ") || self.name.starts_with(b"/SYM64/ ")
|
||||
}
|
||||
|
||||
/// Returns the member's file name. A BSD-style long name is stored
|
||||
/// right after the header, so `body` is advanced past it.
|
||||
fn read_name(&self, strtab: &'a [u8], body: &mut &'a [u8]) -> &'a [u8] {
|
||||
// BSD-style long filename
|
||||
if let Some(rest) = self.name.strip_prefix(b"#1/") {
|
||||
let len = parse_decimal(rest);
|
||||
let (name, remaining) = body.split_at(len.min(body.len()));
|
||||
*body = remaining;
|
||||
return cstr_at(name, 0);
|
||||
}
|
||||
|
||||
// SysV-style long filename
|
||||
if let Some(rest) = self.name.strip_prefix(b"/") {
|
||||
let offset = parse_decimal(rest);
|
||||
let start = strtab.get(offset..).unwrap_or(&[]);
|
||||
let end = memchr::memmem::find(start, b"/\n").unwrap_or(start.len());
|
||||
return &start[..end];
|
||||
}
|
||||
|
||||
// Short filename, space-padded and, in the SysV form, slash-terminated
|
||||
let end = memchr::memchr(b'/', self.name).unwrap_or(self.name.len());
|
||||
self.name[..end].trim_ascii_end()
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses the leading decimal digits of a field, like `atoi`.
|
||||
fn parse_decimal(bytes: &[u8]) -> usize {
|
||||
bytes
|
||||
.iter()
|
||||
.skip_while(|b| b.is_ascii_whitespace())
|
||||
.take_while(|b| b.is_ascii_digit())
|
||||
.fold(0, |acc, &b| acc * 10 + (b - b'0') as usize)
|
||||
}
|
||||
|
||||
/// An archive member. A thin archive's member has no contents in the
|
||||
/// archive; its name is the path of the file that has them.
|
||||
pub struct Member {
|
||||
pub name: &'static [u8],
|
||||
pub data: &'static [u8],
|
||||
/// The modification time in the member header.
|
||||
pub date: u64,
|
||||
}
|
||||
|
||||
/// Iterates over the members of the archive `data`, regular or thin,
|
||||
/// skipping the symbol tables and the string table. `path` is the
|
||||
/// archive's name for error messages.
|
||||
pub fn members<'a>(path: &'a Path, data: &'static [u8]) -> impl Iterator<Item = Member> + 'a {
|
||||
let thin = data.starts_with(b"!<thin>\n");
|
||||
debug_assert!(thin || data.starts_with(b"!<arch>\n"));
|
||||
let mut pos = 8;
|
||||
let mut strtab: &'static [u8] = &[];
|
||||
|
||||
std::iter::from_fn(move || {
|
||||
loop {
|
||||
if data.len() - pos < 2 {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Each header is aligned to a 2 byte boundary.
|
||||
if pos % 2 != 0 {
|
||||
pos += 1;
|
||||
}
|
||||
|
||||
let hdr = ArHeader::parse(&data[pos..])?;
|
||||
let body_start = pos + HEADER_SIZE;
|
||||
let body_end = (body_start + hdr.size).min(data.len());
|
||||
let mut body = &data[body_start..body_end];
|
||||
|
||||
// Read a string table.
|
||||
if hdr.is_strtab() {
|
||||
strtab = body;
|
||||
pos = body_end;
|
||||
continue;
|
||||
}
|
||||
// Skip a symbol table.
|
||||
if hdr.is_symtab() {
|
||||
pos = body_end;
|
||||
continue;
|
||||
}
|
||||
|
||||
if thin && !hdr.name.starts_with(b"#1/") && !hdr.name.starts_with(b"/") {
|
||||
fatal!("{}: filename is not stored as a long filename", path.display());
|
||||
}
|
||||
|
||||
// Read the name field
|
||||
let name = hdr.read_name(strtab, &mut body);
|
||||
|
||||
if thin {
|
||||
// A thin archive member's contents live elsewhere; only a
|
||||
// BSD-style long name occupies space after the header.
|
||||
pos = body_start + (body_end - body_start - body.len());
|
||||
body = &[];
|
||||
} else {
|
||||
pos = body_end;
|
||||
}
|
||||
|
||||
// Skip BSD archive symbol tables (__.SYMDEF, __.SYMDEF SORTED,
|
||||
// __.SYMDEF_64, ...).
|
||||
if name.starts_with(b"__.SYMDEF") {
|
||||
pos = body_end;
|
||||
continue;
|
||||
}
|
||||
|
||||
return Some(Member { name, data: body, date: hdr.date });
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the paths of the members of a thin archive, which are stored
|
||||
/// outside of the archive file, without opening them.
|
||||
pub fn get_thin_archive_member_paths<'a>(
|
||||
chroot: &'a Path,
|
||||
mf: &'static MappedFile,
|
||||
) -> impl Iterator<Item = PathBuf> + 'a {
|
||||
members(&mf.name, mf.data()).map(move |member| member_path(chroot, mf, member.name))
|
||||
}
|
||||
|
||||
// An absolute member name is looked up in the --chroot directory. A relative
|
||||
// one is relative to the archive, whose path is already in that directory.
|
||||
fn member_path(chroot: &Path, mf: &MappedFile, name: &[u8]) -> PathBuf {
|
||||
let name = Path::new(os_str(name));
|
||||
if name.is_absolute() {
|
||||
apply_chroot(chroot, name).into_owned()
|
||||
} else {
|
||||
mf.name.parent().unwrap_or(Path::new(".")).join(name)
|
||||
}
|
||||
}
|
||||
|
||||
/// Opens members as they are consumed. Parallel readers can instead schedule
|
||||
/// thin-member paths on workers using get_thin_archive_member_paths().
|
||||
pub fn read_archive_members<'a>(
|
||||
chroot: &'a Path,
|
||||
mf: &'static MappedFile,
|
||||
) -> impl Iterator<Item = &'static MappedFile> + 'a {
|
||||
let thin = mf.data().starts_with(b"!<thin>\n");
|
||||
let base = mf.data().as_ptr() as usize;
|
||||
members(&mf.name, mf.data()).map(move |member| {
|
||||
if thin {
|
||||
mf.open_thin_member(&member_path(chroot, mf, member.name))
|
||||
} else {
|
||||
let name = PathBuf::from(os_str(member.name));
|
||||
mf.slice(name, member.data.as_ptr() as usize - base, member.data.len())
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
//! Integer and bit manipulation: alignment, bit fields and sign extension.
|
||||
|
||||
/// Rounds `value` up to a multiple of `align`, which must be zero or a power
|
||||
/// of two. Zero means "no alignment".
|
||||
#[inline]
|
||||
pub fn align_to(value: u64, align: u64) -> u64 {
|
||||
if align == 0 {
|
||||
return value;
|
||||
}
|
||||
debug_assert!(align.is_power_of_two());
|
||||
(value + align - 1) & !(align - 1)
|
||||
}
|
||||
|
||||
/// Rounds `val` up to the next value congruent to `modulus` modulo
|
||||
/// `align`: the smallest x >= val with x % align == modulus. ld64 places
|
||||
/// every subsection this way, keeping the offset it had within its
|
||||
/// section modulo the section's alignment, not merely rounding up to the
|
||||
/// section's alignment.
|
||||
pub fn align_to_mod(val: u64, align: u64, modulus: u64) -> u64 {
|
||||
debug_assert!(align.is_power_of_two() && modulus < align);
|
||||
if val <= modulus { modulus } else { align_to(val - modulus, align) + modulus }
|
||||
}
|
||||
|
||||
/// Rounds `value` down to a multiple of `align`, which must be a power of two.
|
||||
pub fn align_down(value: u64, align: u64) -> u64 {
|
||||
debug_assert!(align.is_power_of_two());
|
||||
value & !(align - 1)
|
||||
}
|
||||
|
||||
/// Returns bit `pos` of `value`.
|
||||
pub fn bit(value: u64, pos: u32) -> u64 {
|
||||
(value >> pos) & 1
|
||||
}
|
||||
|
||||
// Returns bits [hi:lo] of `value`.
|
||||
#[inline]
|
||||
pub fn bits(value: u64, hi: u32, lo: u32) -> u64 {
|
||||
(value >> lo) & ((1u64 << (hi - lo + 1)) - 1)
|
||||
}
|
||||
|
||||
// Casts `value` to a signed `n`-bit integer.
|
||||
// For example, sign_extend(x, 32) == x as i32 as i64 for any x.
|
||||
pub fn sign_extend(value: u64, n: u32) -> i64 {
|
||||
((value << (64 - n)) as i64) >> (64 - n)
|
||||
}
|
||||
|
||||
/// Whether `value` is representable as a signed `n`-bit integer.
|
||||
pub fn is_int(value: i64, n: u32) -> bool {
|
||||
sign_extend(value as u64, n) == value
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn sign_extension() {
|
||||
assert_eq!(sign_extend(0xff, 8), -1);
|
||||
assert_eq!(sign_extend(0x7f, 8), 127);
|
||||
assert!(is_int(-128, 8));
|
||||
assert!(!is_int(128, 8));
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
//! Finds the git commit that mold is built from, for the linkers' version
|
||||
//! strings.
|
||||
|
||||
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() {
|
||||
println!("cargo:rerun-if-changed=build.rs");
|
||||
|
||||
// This crate is in common/ of the source tree.
|
||||
let manifest_dir = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap());
|
||||
if let Some(hash) = git_hash(manifest_dir.parent().unwrap()) {
|
||||
println!("cargo:rustc-env=MOLD_GIT_HASH={hash}");
|
||||
}
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
//! Byte strings: names that need not be UTF-8, and the text of response
|
||||
//! files, linker scripts and symbol lists.
|
||||
|
||||
/// Whether a byte is white space as isspace() takes it in the C locale,
|
||||
/// without the function call that a tokenizer would otherwise make for
|
||||
/// every byte of a response file.
|
||||
pub fn is_space(c: u8) -> bool {
|
||||
matches!(c, b' ' | b'\t' | b'\n' | 0x0b | 0x0c | b'\r')
|
||||
}
|
||||
|
||||
/// A byte string without the white space (see is_space) it starts and
|
||||
/// ends with.
|
||||
pub fn trim_space(mut bytes: &[u8]) -> &[u8] {
|
||||
while let [first, rest @ ..] = bytes
|
||||
&& is_space(*first)
|
||||
{
|
||||
bytes = rest;
|
||||
}
|
||||
while let [rest @ .., last] = bytes
|
||||
&& is_space(*last)
|
||||
{
|
||||
bytes = rest;
|
||||
}
|
||||
bytes
|
||||
}
|
||||
|
||||
/// The lines of a text file, as str::lines splits a string: at each
|
||||
/// '\n', a '\r' before it dropped, the last line ended or not. Lists
|
||||
/// of names (symbols, files) are bytes, whatever their encoding.
|
||||
pub fn lines(text: &[u8]) -> impl Iterator<Item = &[u8]> {
|
||||
let n = if text.is_empty() { 0 } else { usize::MAX };
|
||||
let text = text.strip_suffix(b"\n").unwrap_or(text);
|
||||
let lines = text.split(|&c| c == b'\n').take(n);
|
||||
lines.map(|line| line.strip_suffix(b"\r").unwrap_or(line))
|
||||
}
|
||||
|
||||
/// Splits a byte string at the first `sep`, as str::split_once does a
|
||||
/// string.
|
||||
pub fn split_once(bytes: &[u8], sep: u8) -> Option<(&[u8], &[u8])> {
|
||||
let i = memchr::memchr(sep, bytes)?;
|
||||
Some((&bytes[..i], &bytes[i + 1..]))
|
||||
}
|
||||
|
||||
/// Writes a NUL-terminated string and returns the number of bytes written.
|
||||
pub fn write_cstr(buf: &mut [u8], s: &[u8]) -> usize {
|
||||
buf[..s.len()].copy_from_slice(s);
|
||||
buf[s.len()] = 0;
|
||||
s.len() + 1
|
||||
}
|
||||
|
||||
/// Returns the NUL-terminated string starting at `offset` in a string table.
|
||||
/// The result excludes the terminator. A missing terminator yields the rest
|
||||
/// of the table.
|
||||
#[inline]
|
||||
pub fn cstr_at(table: &[u8], offset: usize) -> &[u8] {
|
||||
let rest = table.get(offset..).unwrap_or(&[]);
|
||||
let end = memchr::memchr(0, rest).unwrap_or(rest.len());
|
||||
&rest[..end]
|
||||
}
|
||||
|
||||
/// Converts bytes from a response file or linker script to an OS string.
|
||||
/// Unix paths can contain arbitrary non-NUL bytes.
|
||||
pub fn os_str(bytes: &[u8]) -> &std::ffi::OsStr {
|
||||
use bstr::ByteSlice;
|
||||
bytes.to_os_str().unwrap_or_else(|_| crate::fatal!("invalid OS string: {}", display(bytes)))
|
||||
}
|
||||
|
||||
/// Formats a byte string for diagnostics, replacing invalid UTF-8.
|
||||
pub fn display(bytes: &[u8]) -> std::borrow::Cow<'_, str> {
|
||||
String::from_utf8_lossy(bytes)
|
||||
}
|
||||
@@ -1,211 +0,0 @@
|
||||
//! CityHash64, as libc++'s std::hash of a string computes it: a
|
||||
//! Mach-O mergeable record hashes its names with that.
|
||||
//!
|
||||
//! This implementation is based on CityHash v1.0.3 at
|
||||
//! https://github.com/google/cityhash. libc++ differs from it in two
|
||||
//! places, marked "libc++:" below.
|
||||
|
||||
// The license of https://github.com/google/cityhash, from its COPYING:
|
||||
//
|
||||
// Copyright (c) 2011 Google, Inc.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
// Some primes between 2^63 and 2^64 for various uses.
|
||||
const K0: u64 = 0xc3a5_c85c_97cb_3127;
|
||||
const K1: u64 = 0xb492_b66f_be98_f273;
|
||||
const K2: u64 = 0x9ae1_6a3b_2f90_404f;
|
||||
const K3: u64 = 0xc949_d7c7_509e_6557;
|
||||
|
||||
fn fetch64(s: &[u8], at: usize) -> u64 {
|
||||
u64::from_le_bytes(s[at..at + 8].try_into().unwrap())
|
||||
}
|
||||
|
||||
fn fetch32(s: &[u8], at: usize) -> u32 {
|
||||
u32::from_le_bytes(s[at..at + 4].try_into().unwrap())
|
||||
}
|
||||
|
||||
fn shift_mix(val: u64) -> u64 {
|
||||
val ^ (val >> 47)
|
||||
}
|
||||
|
||||
/// Hash128to64() of the 128-bit value with `u` as its low half.
|
||||
fn hash_len_16(u: u64, v: u64) -> u64 {
|
||||
// Murmur-inspired hashing.
|
||||
const MUL: u64 = 0x9ddf_ea08_eb38_2d69;
|
||||
let mut a = (u ^ v).wrapping_mul(MUL);
|
||||
a ^= a >> 47;
|
||||
let mut b = (v ^ a).wrapping_mul(MUL);
|
||||
b ^= b >> 47;
|
||||
b.wrapping_mul(MUL)
|
||||
}
|
||||
|
||||
fn hash_len_0_to_16(s: &[u8]) -> u64 {
|
||||
let len = s.len();
|
||||
if len > 8 {
|
||||
let a = fetch64(s, 0);
|
||||
let b = fetch64(s, len - 8);
|
||||
return hash_len_16(a, b.wrapping_add(len as u64).rotate_right(len as u32)) ^ b;
|
||||
}
|
||||
if len >= 4 {
|
||||
// libc++: `a << 3` is in 32 bits, not 64.
|
||||
let a = fetch32(s, 0);
|
||||
return hash_len_16((len as u64).wrapping_add((a << 3) as u64), fetch32(s, len - 4) as u64);
|
||||
}
|
||||
if len > 0 {
|
||||
let (a, b, c) = (s[0] as u32, s[len >> 1] as u32, s[len - 1] as u32);
|
||||
let y = a.wrapping_add(b << 8);
|
||||
let z = (len as u32).wrapping_add(c << 2);
|
||||
return shift_mix((y as u64).wrapping_mul(K2) ^ (z as u64).wrapping_mul(K3))
|
||||
.wrapping_mul(K2);
|
||||
}
|
||||
K2
|
||||
}
|
||||
|
||||
// This probably works well for 16-byte strings as well, but it may be overkill
|
||||
// in that case.
|
||||
fn hash_len_17_to_32(s: &[u8]) -> u64 {
|
||||
let len = s.len();
|
||||
let a = fetch64(s, 0).wrapping_mul(K1);
|
||||
let b = fetch64(s, 8);
|
||||
let c = fetch64(s, len - 8).wrapping_mul(K2);
|
||||
let d = fetch64(s, len - 16).wrapping_mul(K0);
|
||||
hash_len_16(
|
||||
a.wrapping_sub(b).rotate_right(43).wrapping_add(c.rotate_right(30)).wrapping_add(d),
|
||||
a.wrapping_add((b ^ K3).rotate_right(20)).wrapping_sub(c).wrapping_add(len as u64),
|
||||
)
|
||||
}
|
||||
|
||||
/// A 16-byte hash for s[0] ... s[31], `a` and `b`. Quick and dirty.
|
||||
/// Callers do best to use "random-looking" values for `a` and `b`.
|
||||
fn weak_hash_len_32_with_seeds(s: &[u8], a: u64, b: u64) -> (u64, u64) {
|
||||
let (w, x, y, z) = (fetch64(s, 0), fetch64(s, 8), fetch64(s, 16), fetch64(s, 24));
|
||||
let mut a = a.wrapping_add(w);
|
||||
let mut b = b.wrapping_add(a).wrapping_add(z).rotate_right(21);
|
||||
let c = a;
|
||||
a = a.wrapping_add(x).wrapping_add(y);
|
||||
b = b.wrapping_add(a.rotate_right(44));
|
||||
(a.wrapping_add(z), b.wrapping_add(c))
|
||||
}
|
||||
|
||||
/// An 8-byte hash for 33 to 64 bytes.
|
||||
fn hash_len_33_to_64(s: &[u8]) -> u64 {
|
||||
let len = s.len();
|
||||
let mut z = fetch64(s, 24);
|
||||
let mut a = fetch64(s, 0)
|
||||
.wrapping_add((len as u64).wrapping_add(fetch64(s, len - 16)).wrapping_mul(K0));
|
||||
let mut b = a.wrapping_add(z).rotate_right(52);
|
||||
let mut c = a.rotate_right(37);
|
||||
a = a.wrapping_add(fetch64(s, 8));
|
||||
c = c.wrapping_add(a.rotate_right(7));
|
||||
a = a.wrapping_add(fetch64(s, 16));
|
||||
let vf = a.wrapping_add(z);
|
||||
let vs = b.wrapping_add(a.rotate_right(31)).wrapping_add(c);
|
||||
a = fetch64(s, 16).wrapping_add(fetch64(s, len - 32));
|
||||
// libc++: z is added to, not set.
|
||||
z = z.wrapping_add(fetch64(s, len - 8));
|
||||
b = a.wrapping_add(z).rotate_right(52);
|
||||
c = a.rotate_right(37);
|
||||
a = a.wrapping_add(fetch64(s, len - 24));
|
||||
c = c.wrapping_add(a.rotate_right(7));
|
||||
a = a.wrapping_add(fetch64(s, len - 16));
|
||||
let wf = a.wrapping_add(z);
|
||||
let ws = b.wrapping_add(a.rotate_right(31)).wrapping_add(c);
|
||||
let r = shift_mix(
|
||||
vf.wrapping_add(ws).wrapping_mul(K2).wrapping_add(wf.wrapping_add(vs).wrapping_mul(K0)),
|
||||
);
|
||||
shift_mix(r.wrapping_mul(K0).wrapping_add(vs)).wrapping_mul(K2)
|
||||
}
|
||||
|
||||
pub fn hash(s: &[u8]) -> u64 {
|
||||
let len = s.len();
|
||||
if len <= 16 {
|
||||
return hash_len_0_to_16(s);
|
||||
}
|
||||
if len <= 32 {
|
||||
return hash_len_17_to_32(s);
|
||||
}
|
||||
if len <= 64 {
|
||||
return hash_len_33_to_64(s);
|
||||
}
|
||||
|
||||
// For strings over 64 bytes we hash the end first, and then as we
|
||||
// loop we keep 56 bytes of state: v, w, x, y, and z.
|
||||
let n = len as u64;
|
||||
let mut x = fetch64(s, len - 40);
|
||||
let mut y = fetch64(s, len - 16).wrapping_add(fetch64(s, len - 56));
|
||||
let mut z = hash_len_16(fetch64(s, len - 48).wrapping_add(n), fetch64(s, len - 24));
|
||||
let mut v = weak_hash_len_32_with_seeds(&s[len - 64..], n, z);
|
||||
let mut w = weak_hash_len_32_with_seeds(&s[len - 32..], y.wrapping_add(K1), x);
|
||||
x = x.wrapping_mul(K1).wrapping_add(fetch64(s, 0));
|
||||
|
||||
// Decrease len to the nearest multiple of 64, and operate on 64-byte chunks.
|
||||
for c in s[..(len - 1) & !63].as_chunks::<64>().0 {
|
||||
x = (x.wrapping_add(y).wrapping_add(v.0).wrapping_add(fetch64(c, 8)))
|
||||
.rotate_right(37)
|
||||
.wrapping_mul(K1);
|
||||
y = (y.wrapping_add(v.1).wrapping_add(fetch64(c, 48))).rotate_right(42).wrapping_mul(K1);
|
||||
x ^= w.1;
|
||||
y = y.wrapping_add(v.0).wrapping_add(fetch64(c, 40));
|
||||
z = z.wrapping_add(w.0).rotate_right(33).wrapping_mul(K1);
|
||||
v = weak_hash_len_32_with_seeds(c, v.1.wrapping_mul(K1), x.wrapping_add(w.0));
|
||||
w = weak_hash_len_32_with_seeds(
|
||||
&c[32..],
|
||||
z.wrapping_add(w.1),
|
||||
y.wrapping_add(fetch64(c, 16)),
|
||||
);
|
||||
std::mem::swap(&mut z, &mut x);
|
||||
}
|
||||
hash_len_16(
|
||||
hash_len_16(v.0, w.0).wrapping_add(shift_mix(y).wrapping_mul(K1)).wrapping_add(z),
|
||||
hash_len_16(v.1, w.1).wrapping_add(x),
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
// std::hash<std::string_view> from Xcode's libc++, of each length's
|
||||
// prefix of `data`. Lengths 4 to 8 and 33 to 64 are where libc++
|
||||
// differs from CityHash v1.0.3.
|
||||
#[test]
|
||||
fn matches_libcxx() {
|
||||
let data: Vec<u8> =
|
||||
(0..400).map(|i: usize| (i * 131 + (i >> 3) * 7 + 0x5a) as u8).collect();
|
||||
for (len, expected) in [
|
||||
(0, 0x9ae1_6a3b_2f90_404f),
|
||||
(3, 0x51d1_83ca_b178_69e7),
|
||||
(5, 0x06ab_c9b6_c811_7ca8),
|
||||
(8, 0xed1f_6592_0960_3ce9),
|
||||
(12, 0x48b7_91b0_fe9b_e040),
|
||||
(17, 0x027a_8eab_ea05_2a3f),
|
||||
(32, 0x1c21_a4ef_fa7b_3f54),
|
||||
(33, 0x6583_3651_60f0_a2e0),
|
||||
(50, 0x1e81_10d2_800d_5748),
|
||||
(64, 0xd021_ebba_7723_3653),
|
||||
(65, 0x73c3_2c4f_c042_52a2),
|
||||
(128, 0xe173_4283_984b_218f),
|
||||
(129, 0xd017_1b7c_8ed1_4630),
|
||||
(400, 0x3683_4df1_0afa_73c8),
|
||||
] {
|
||||
assert_eq!(hash(&data[..len]), expected, "len={len}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,258 +0,0 @@
|
||||
//! 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 at shard boundaries, the compression ratio is sacrificed a
|
||||
//! little bit. However, if the shard size is large enough, that loss is
|
||||
//! negligible in practice.
|
||||
|
||||
use std::io::{ErrorKind, Read};
|
||||
|
||||
use flate2::FlushDecompress;
|
||||
use rayon::prelude::*;
|
||||
use zlib_rs::adler32::{adler32, adler32_combine};
|
||||
|
||||
use crate::worker_local::WorkerLocal;
|
||||
|
||||
const SHARD_SIZE: usize = 1024 * 1024;
|
||||
|
||||
/// Compresses data with zlib or zstd at a fixed level, using all threads.
|
||||
pub struct Compressor(Method);
|
||||
|
||||
enum Method {
|
||||
Zlib(i32),
|
||||
Zstd(i32, ZstdContexts),
|
||||
}
|
||||
|
||||
// Creating a zstd context costs nearly as much as compressing a shard at
|
||||
// higher levels, so each Rayon worker reuses one for all shards.
|
||||
type ZstdContexts = WorkerLocal<Option<zstd::bulk::Compressor<'static>>>;
|
||||
|
||||
/// Compressed data, kept as shards until it is written to the output.
|
||||
pub enum CompressedData {
|
||||
Zlib { shards: Vec<Vec<u8>>, checksum: u32 },
|
||||
Zstd { shards: Vec<Vec<u8>> },
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for CompressedData {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "CompressedData({} bytes)", self.compressed_size())
|
||||
}
|
||||
}
|
||||
|
||||
/// Compresses a shard as a raw deflate stream ending with a sync flush,
|
||||
/// so that the stream ends on a byte boundary and can be concatenated.
|
||||
fn zlib_compress_shard(input: &[u8], level: i32) -> Vec<u8> {
|
||||
// Initialize a zlib stream. Since debug info is generally compressed
|
||||
// pretty well with lower compression levels, the default level is 1.
|
||||
let mut stream = libz_rs_sys::z_stream::default();
|
||||
// SAFETY: stream is a valid z_stream with the default allocator. It
|
||||
// stays at the same address until deflateEnd.
|
||||
let status = unsafe {
|
||||
libz_rs_sys::deflateInit2_(
|
||||
&mut stream,
|
||||
level,
|
||||
libz_rs_sys::Z_DEFLATED,
|
||||
-15,
|
||||
8,
|
||||
libz_rs_sys::Z_DEFAULT_STRATEGY,
|
||||
libz_rs_sys::zlibVersion(),
|
||||
size_of::<libz_rs_sys::z_stream>() as i32,
|
||||
)
|
||||
};
|
||||
assert_eq!(status, libz_rs_sys::Z_OK);
|
||||
|
||||
// Set an input buffer
|
||||
stream.avail_in = input.len() as u32;
|
||||
stream.next_in = input.as_ptr();
|
||||
|
||||
// Set an output buffer. deflateBound() returns an upper bound
|
||||
// on the compression size. +16 for Z_SYNC_FLUSH.
|
||||
// SAFETY: stream is initialized and remains valid through deflateEnd.
|
||||
let bound = unsafe { libz_rs_sys::deflateBound(&mut stream, stream.avail_in.into()) } as usize;
|
||||
let mut out = vec![0; bound + 16];
|
||||
|
||||
// Compress data. It writes all compressed bytes except the last
|
||||
// partial byte, so up to 7 bits may be left pending without being
|
||||
// written to the buffer.
|
||||
stream.avail_out = out.len() as u32;
|
||||
stream.next_out = out.as_mut_ptr();
|
||||
// SAFETY: the input and output buffers remain alive and the output has
|
||||
// deflateBound() + 16 bytes of space.
|
||||
let status = unsafe { libz_rs_sys::deflate(&mut stream, libz_rs_sys::Z_BLOCK) };
|
||||
assert_eq!(status, libz_rs_sys::Z_OK);
|
||||
|
||||
// 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 bytes 00 00 ff ff which indicate that
|
||||
// the length of the block is zero. libbacktrace uses its own zlib
|
||||
// inflate routine, and it had a bug where, if that particular
|
||||
// three-bit value happened 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
|
||||
let mut nbits = 0;
|
||||
// SAFETY: stream is initialized and nbits is a valid output pointer.
|
||||
let status =
|
||||
unsafe { libz_rs_sys::deflatePending(&mut stream, std::ptr::null_mut(), &raw mut nbits) };
|
||||
assert_eq!(status, libz_rs_sys::Z_OK);
|
||||
if nbits == 5 {
|
||||
// SAFETY: stream is initialized and has enough pending-buffer space.
|
||||
let status = unsafe { libz_rs_sys::deflatePrime(&mut stream, 10, 2) };
|
||||
assert_eq!(status, libz_rs_sys::Z_OK);
|
||||
}
|
||||
// SAFETY: stream and its input and output buffers remain valid.
|
||||
let status = unsafe { libz_rs_sys::deflate(&mut stream, libz_rs_sys::Z_SYNC_FLUSH) };
|
||||
assert_eq!(status, libz_rs_sys::Z_OK);
|
||||
|
||||
let len = out.len() - stream.avail_out as usize;
|
||||
// SAFETY: stream was initialized successfully and is no longer used.
|
||||
unsafe { libz_rs_sys::deflateEnd(&mut stream) };
|
||||
out.truncate(len);
|
||||
out
|
||||
}
|
||||
|
||||
impl Compressor {
|
||||
pub fn zlib(level: i32) -> Self {
|
||||
Self(Method::Zlib(level))
|
||||
}
|
||||
|
||||
pub fn zstd(level: i32) -> Self {
|
||||
Self(Method::Zstd(level, WorkerLocal::new(|| None)))
|
||||
}
|
||||
|
||||
pub fn compress(&self, input: &[u8]) -> CompressedData {
|
||||
match &self.0 {
|
||||
Method::Zlib(level) => zlib_compress(input, *level),
|
||||
Method::Zstd(level, contexts) => zstd_compress(input, *level, contexts),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn zlib_compress(input: &[u8], level: i32) -> CompressedData {
|
||||
// Compress each shard
|
||||
let (shards, adlers): (Vec<Vec<u8>>, Vec<u32>) = input
|
||||
.par_chunks(SHARD_SIZE)
|
||||
.map(|shard| (zlib_compress_shard(shard, level), adler32(1, shard)))
|
||||
.unzip();
|
||||
|
||||
// Combine checksums
|
||||
let mut checksum = adlers.first().copied().unwrap_or(1);
|
||||
for (adler, shard) in adlers.iter().zip(input.chunks(SHARD_SIZE)).skip(1) {
|
||||
checksum = adler32_combine(checksum, *adler, shard.len() as u64);
|
||||
}
|
||||
CompressedData::Zlib { shards, checksum }
|
||||
}
|
||||
|
||||
fn zstd_compress(input: &[u8], level: i32, contexts: &ZstdContexts) -> CompressedData {
|
||||
// Compress each shard
|
||||
let shards = input
|
||||
.par_chunks(SHARD_SIZE)
|
||||
.map(|shard| {
|
||||
let mut slot = contexts.get();
|
||||
let cctx = slot.get_or_insert_with(|| {
|
||||
zstd::bulk::Compressor::new(level).expect("cannot create a zstd context")
|
||||
});
|
||||
cctx.compress(shard).expect("zstd compression failed")
|
||||
})
|
||||
.collect();
|
||||
CompressedData::Zstd { shards }
|
||||
}
|
||||
|
||||
impl CompressedData {
|
||||
pub fn compressed_size(&self) -> usize {
|
||||
// Compute the total size
|
||||
match self {
|
||||
// the header and the trailer
|
||||
Self::Zlib { shards, .. } => 8 + shards.iter().map(Vec::len).sum::<usize>(),
|
||||
Self::Zstd { shards } => shards.iter().map(Vec::len).sum(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_to(&self, buf: &mut [u8]) {
|
||||
match self {
|
||||
Self::Zlib { shards, checksum } => {
|
||||
// Write a zlib-format header
|
||||
buf[0] = 0x78;
|
||||
buf[1] = 0x9c;
|
||||
|
||||
// Copy compressed data
|
||||
// +2 for the header
|
||||
let mut pos = 2;
|
||||
for shard in shards {
|
||||
buf[pos..pos + shard.len()].copy_from_slice(shard);
|
||||
pos += shard.len();
|
||||
}
|
||||
// Write a trailer
|
||||
// An empty final block, then the Adler-32 checksum.
|
||||
buf[pos] = 3;
|
||||
buf[pos + 1] = 0;
|
||||
buf[pos + 2..pos + 6].copy_from_slice(&checksum.to_be_bytes());
|
||||
}
|
||||
Self::Zstd { shards } => {
|
||||
// Copy compressed data
|
||||
let mut pos = 0;
|
||||
for shard in shards {
|
||||
buf[pos..pos + shard.len()].copy_from_slice(shard);
|
||||
pos += shard.len();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Decompresses a zlib stream into a buffer of known size.
|
||||
pub fn zlib_decompress(input: &[u8], out: &mut [u8]) -> Result<(), String> {
|
||||
let mut decompress = flate2::Decompress::new(true);
|
||||
loop {
|
||||
let in_pos = decompress.total_in() as usize;
|
||||
let out_pos = decompress.total_out() as usize;
|
||||
if out_pos >= out.len() {
|
||||
return Ok(());
|
||||
}
|
||||
let status = decompress
|
||||
.decompress(
|
||||
&input[in_pos.min(input.len())..],
|
||||
&mut out[out_pos..],
|
||||
FlushDecompress::None,
|
||||
)
|
||||
.map_err(|e| e.to_string())?;
|
||||
match status {
|
||||
flate2::Status::StreamEnd => break,
|
||||
flate2::Status::BufError if decompress.total_in() as usize >= input.len() => break,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
if (decompress.total_out() as usize) < out.len() {
|
||||
return Err("premature end of input".to_string());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Decompresses a zstd stream, stopping when the requested output is full.
|
||||
/// DWARF classification uses this to read just the unit header.
|
||||
pub fn zstd_decompress(input: &[u8], out: &mut [u8]) -> Result<(), String> {
|
||||
let mut decoder = zstd::stream::read::Decoder::with_buffer(input).map_err(|e| e.to_string())?;
|
||||
decoder.read_exact(out).map_err(|e| {
|
||||
if e.kind() == ErrorKind::UnexpectedEof {
|
||||
"premature end of input".to_string()
|
||||
} else {
|
||||
e.to_string()
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1,475 +0,0 @@
|
||||
//! A fast concurrent hash map.
|
||||
//!
|
||||
//! 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 estimate 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
|
||||
//! a concurrent hash map is critical for our linker.
|
||||
//!
|
||||
//! The map is an open-addressing table. Insertion is lock-free: a thread
|
||||
//! claims an empty bucket with a compare-and-swap on its key pointer,
|
||||
//! initializes the value, and then publishes the key; a thread that finds
|
||||
//! a claimed bucket spins until the key appears. Probing stays within a
|
||||
//! shard of the table, so the entries of a shard can be laid out
|
||||
//! independently of the others. Keys are byte strings that live for the
|
||||
//! whole link, such as string literals inside input files, hashed once by
|
||||
//! the caller.
|
||||
|
||||
use rayon::prelude::*;
|
||||
#[cfg(windows)]
|
||||
use std::alloc::{Layout, alloc_zeroed, dealloc};
|
||||
use std::cell::UnsafeCell;
|
||||
use std::mem::MaybeUninit;
|
||||
use std::ptr;
|
||||
use std::sync::atomic::{AtomicPtr, AtomicU32, Ordering};
|
||||
|
||||
pub const NUM_SHARDS: usize = 64;
|
||||
|
||||
// MIN_NBUCKETS is chosen so that even the smallest map has
|
||||
// MAX_RETRY buckets per shard; probing is confined to a shard, and
|
||||
// a probe that visits MAX_RETRY distinct occupied slots aborts.
|
||||
const MIN_NBUCKETS: usize = 16384;
|
||||
const MAX_RETRY: usize = 256;
|
||||
const _: () = assert!(MIN_NBUCKETS / NUM_SHARDS >= MAX_RETRY);
|
||||
|
||||
/// The key pointer of a bucket that a thread has claimed but not yet
|
||||
/// published: -1 marks the slot as claimed until its value has been
|
||||
/// initialized.
|
||||
const CLAIMED: *mut u8 = usize::MAX as *mut u8;
|
||||
|
||||
// In order to avoid unnecessary cache-line false sharing, we want
|
||||
// to make this object aligned to a reasonably large
|
||||
// power-of-two address.
|
||||
#[repr(C, align(32))]
|
||||
struct Entry<T> {
|
||||
key: AtomicPtr<u8>,
|
||||
// Published by the release store of `key` and read after the acquire
|
||||
// load that observes it, so relaxed accesses suffice.
|
||||
keylen: AtomicU32,
|
||||
value: UnsafeCell<MaybeUninit<T>>,
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
fn allocate_entries<T>(bufsize: usize) -> *mut Entry<T> {
|
||||
// SAFETY: this creates fresh private anonymous storage.
|
||||
let entries = unsafe {
|
||||
libc::mmap(
|
||||
ptr::null_mut(),
|
||||
bufsize,
|
||||
libc::PROT_READ | libc::PROT_WRITE,
|
||||
libc::MAP_ANONYMOUS | libc::MAP_PRIVATE,
|
||||
-1,
|
||||
0,
|
||||
)
|
||||
};
|
||||
if entries == libc::MAP_FAILED {
|
||||
let err = std::io::Error::last_os_error();
|
||||
panic!("mmap of {bufsize} bytes failed: {}", crate::error::strerror(&err));
|
||||
}
|
||||
entries.cast()
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn allocate_entries<T>(bufsize: usize) -> *mut Entry<T> {
|
||||
let layout = Layout::from_size_align(bufsize, align_of::<Entry<T>>())
|
||||
.expect("invalid concurrent-map layout");
|
||||
// SAFETY: the layout has nonzero size.
|
||||
let entries = unsafe { alloc_zeroed(layout).cast::<Entry<T>>() };
|
||||
if entries.is_null() {
|
||||
panic!("cannot allocate {bufsize} bytes for concurrent map");
|
||||
}
|
||||
entries
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
unsafe fn deallocate_entries<T>(entries: *mut Entry<T>, bufsize: usize) {
|
||||
// SAFETY: `entries` was mapped by `allocate_entries` with this size.
|
||||
let _ = unsafe { libc::munmap(entries.cast(), bufsize) };
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
unsafe fn deallocate_entries<T>(entries: *mut Entry<T>, bufsize: usize) {
|
||||
let layout = Layout::from_size_align(bufsize, align_of::<Entry<T>>())
|
||||
.expect("invalid concurrent-map layout");
|
||||
// SAFETY: `entries` was allocated by `allocate_entries` with this layout.
|
||||
unsafe { dealloc(entries.cast(), layout) };
|
||||
}
|
||||
|
||||
/// The index of a bucket, which identifies an entry for the life of
|
||||
/// the map.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub struct EntryId(u32);
|
||||
|
||||
impl EntryId {
|
||||
pub fn raw(self) -> u32 {
|
||||
self.0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn from_raw(raw: u32) -> Self {
|
||||
Self(raw)
|
||||
}
|
||||
}
|
||||
|
||||
/// A map under construction. Once concurrent insertion is complete, freeze
|
||||
/// it to look up and traverse its entries.
|
||||
pub struct ConcurrentMap<T> {
|
||||
entries: *mut Entry<T>,
|
||||
nbuckets: usize,
|
||||
}
|
||||
|
||||
// SAFETY: entries are shared between threads only through atomics and
|
||||
// values that are initialized before they become reachable.
|
||||
unsafe impl<T: Send + Sync> Send for ConcurrentMap<T> {}
|
||||
unsafe impl<T: Send + Sync> Sync for ConcurrentMap<T> {}
|
||||
|
||||
impl<T> Default for ConcurrentMap<T> {
|
||||
/// A map without buckets, to be replaced before use.
|
||||
fn default() -> Self {
|
||||
Self { entries: ptr::null_mut(), nbuckets: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ConcurrentMap<T> {
|
||||
/// A map with room for about `nkeys` distinct keys.
|
||||
pub fn with_capacity(nkeys: usize) -> Self {
|
||||
let nbuckets = nkeys.next_power_of_two().max(MIN_NBUCKETS);
|
||||
let bufsize = Self::bufsize(nbuckets);
|
||||
// mmap is faster than malloc + memset on Unix; the platform allocator
|
||||
// returns equivalently zeroed storage on Windows.
|
||||
let entries = allocate_entries(bufsize);
|
||||
// SAFETY: the range is the fresh allocation; the advice is only a
|
||||
// hint on targets that support it.
|
||||
unsafe { crate::mem::madvise_hugepage(entries.cast(), bufsize) };
|
||||
Self { entries, nbuckets }
|
||||
}
|
||||
|
||||
fn bufsize(nbuckets: usize) -> usize {
|
||||
size_of::<Entry<T>>().checked_mul(nbuckets).expect("table size overflow")
|
||||
}
|
||||
|
||||
/// Counts the number of entries.
|
||||
fn len(&self) -> usize {
|
||||
(0..self.nbuckets).filter(|&idx| self.is_occupied(idx)).count()
|
||||
}
|
||||
|
||||
fn entry(&self, idx: usize) -> &Entry<T> {
|
||||
debug_assert!(idx < self.nbuckets);
|
||||
// SAFETY: idx is within the allocation.
|
||||
unsafe { &*self.entries.add(idx) }
|
||||
}
|
||||
|
||||
fn is_occupied(&self, idx: usize) -> bool {
|
||||
let key = self.entry(idx).key.load(Ordering::Acquire);
|
||||
!key.is_null() && key != CLAIMED
|
||||
}
|
||||
|
||||
fn value_at(&self, idx: usize) -> &T {
|
||||
// SAFETY: callers only ask for values of published entries, which
|
||||
// were initialized before publication.
|
||||
unsafe { (*self.entry(idx).value.get()).assume_init_ref() }
|
||||
}
|
||||
|
||||
/// The probe sequence for a hash: the buckets of one shard, starting
|
||||
/// from the hash's home bucket and wrapping around within the shard.
|
||||
fn probe(&self, hash: u64) -> impl Iterator<Item = usize> {
|
||||
let begin = hash as usize & (self.nbuckets - 1);
|
||||
let mask = self.nbuckets / NUM_SHARDS - 1;
|
||||
(0..MAX_RETRY).map(move |i| (begin & !mask) | ((begin + i) & mask))
|
||||
}
|
||||
|
||||
/// Inserts `key` unless it is present, initializing the value with
|
||||
/// `init`. Returns the entry, its value and whether it was inserted.
|
||||
pub fn insert_with(
|
||||
&self,
|
||||
key: &'static [u8],
|
||||
hash: u64,
|
||||
init: impl FnOnce() -> T,
|
||||
) -> (EntryId, &T, bool) {
|
||||
self.insert_entry(
|
||||
key.as_ptr(),
|
||||
hash,
|
||||
|ptr, len| {
|
||||
// SAFETY: published keys remain live for the link.
|
||||
unsafe { std::slice::from_raw_parts(ptr, len as usize) == key }
|
||||
},
|
||||
|| (u32::try_from(key.len()).expect("key length fits in 32 bits"), init()),
|
||||
)
|
||||
}
|
||||
|
||||
/// Inserts a NUL-terminated key without storing its length at the call
|
||||
/// site. `initialize` computes the length and value only if this call
|
||||
/// claims a new entry.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// `key` must point to a NUL-terminated byte string that remains live for
|
||||
/// the lifetime of the map. Its length must fit in u32.
|
||||
pub unsafe fn insert_cstr_with(
|
||||
&self,
|
||||
key: *const u8,
|
||||
hash: u64,
|
||||
initialize: impl FnOnce(*const u8) -> (u32, T),
|
||||
) -> (EntryId, &T, bool) {
|
||||
// This variant avoids storing a length alongside every caller-side key.
|
||||
// Only a newly inserted key needs its length computed by `initialize`.
|
||||
self.insert_entry(
|
||||
key,
|
||||
hash,
|
||||
|ptr, _| {
|
||||
// SAFETY: guaranteed for `key` by the caller and for `ptr` by
|
||||
// the entry publication invariant.
|
||||
unsafe { libc::strcmp(key.cast(), ptr.cast()) == 0 }
|
||||
},
|
||||
|| initialize(key),
|
||||
)
|
||||
}
|
||||
|
||||
fn insert_entry(
|
||||
&self,
|
||||
key: *const u8,
|
||||
hash: u64,
|
||||
equals: impl Fn(*const u8, u32) -> bool,
|
||||
initialize: impl FnOnce() -> (u32, T),
|
||||
) -> (EntryId, &T, bool) {
|
||||
assert!(self.nbuckets > 0, "the map hasn't been sized");
|
||||
let mut initialize = Some(initialize);
|
||||
|
||||
for idx in self.probe(hash) {
|
||||
let ent = self.entry(idx);
|
||||
|
||||
// Avoid an atomic update when the slot is already occupied.
|
||||
let mut ptr = ent.key.load(Ordering::Acquire);
|
||||
if !ptr.is_null() && ptr != CLAIMED {
|
||||
if equals(ptr, ent.keylen.load(Ordering::Relaxed)) {
|
||||
return (EntryId(idx as u32), self.value_at(idx), false);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if ptr.is_null() {
|
||||
match ent.key.compare_exchange(
|
||||
ptr::null_mut(),
|
||||
CLAIMED,
|
||||
Ordering::Acquire,
|
||||
Ordering::Acquire,
|
||||
) {
|
||||
Ok(_) => {
|
||||
let (keylen, value) = initialize.take().expect("initialized once")();
|
||||
// SAFETY: the claim gives this thread exclusive
|
||||
// access to the bucket until the key is published.
|
||||
unsafe { (*ent.value.get()).write(value) };
|
||||
ent.keylen.store(keylen, Ordering::Relaxed);
|
||||
ent.key.store(key.cast_mut(), Ordering::Release);
|
||||
return (EntryId(idx as u32), self.value_at(idx), true);
|
||||
}
|
||||
Err(current) => ptr = current,
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for the thread that claimed the slot to publish its key.
|
||||
while ptr == CLAIMED {
|
||||
std::hint::spin_loop();
|
||||
ptr = ent.key.load(Ordering::Acquire);
|
||||
}
|
||||
if equals(ptr, ent.keylen.load(Ordering::Relaxed)) {
|
||||
return (EntryId(idx as u32), self.value_at(idx), false);
|
||||
}
|
||||
}
|
||||
panic!("the concurrent map is full");
|
||||
}
|
||||
|
||||
// Prefetch the bucket where a key with the given hash would be
|
||||
// probed first. Useful when a caller knows the hashes of upcoming
|
||||
// insertions, as probes into a large table miss the cache almost
|
||||
// every time.
|
||||
#[inline]
|
||||
pub fn prefetch(&self, hash: u64) {
|
||||
if self.nbuckets > 0 {
|
||||
let idx = hash as usize & (self.nbuckets - 1);
|
||||
// SAFETY: the masked index is within the allocated table.
|
||||
let ptr = unsafe { self.entries.add(idx) };
|
||||
crate::prefetch(ptr.cast());
|
||||
}
|
||||
}
|
||||
|
||||
/// Finishes insertion and returns the map for lookup and traversal.
|
||||
pub fn freeze(self) -> FrozenMap<T> {
|
||||
FrozenMap(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Drop for ConcurrentMap<T> {
|
||||
fn drop(&mut self) {
|
||||
if self.entries.is_null() {
|
||||
return;
|
||||
}
|
||||
if std::mem::needs_drop::<T>() {
|
||||
for idx in 0..self.nbuckets {
|
||||
if self.is_occupied(idx) {
|
||||
// SAFETY: the value of a published entry is initialized
|
||||
// and dropped exactly once, here.
|
||||
unsafe { (*self.entry(idx).value.get()).assume_init_drop() };
|
||||
}
|
||||
}
|
||||
}
|
||||
// SAFETY: allocated in with_capacity and not yet released.
|
||||
unsafe { deallocate_entries(self.entries, Self::bufsize(self.nbuckets)) };
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> std::fmt::Debug for ConcurrentMap<T> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "ConcurrentMap({} entries)", self.len())
|
||||
}
|
||||
}
|
||||
|
||||
/// A map after all insertions, for lookup and traversal. Values are
|
||||
/// reachable only through shared references.
|
||||
#[derive(Debug)]
|
||||
pub struct FrozenMap<T>(ConcurrentMap<T>);
|
||||
|
||||
impl<T> Default for FrozenMap<T> {
|
||||
fn default() -> Self {
|
||||
Self(ConcurrentMap::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> FrozenMap<T> {
|
||||
/// The published key of a bucket, if any.
|
||||
fn key_at(&self, idx: usize) -> Option<&'static [u8]> {
|
||||
let ent = self.0.entry(idx);
|
||||
let key = ent.key.load(Ordering::Acquire);
|
||||
if key.is_null() || key == CLAIMED {
|
||||
return None;
|
||||
}
|
||||
// SAFETY: a published key is a live 'static slice whose length was
|
||||
// written before the key pointer.
|
||||
Some(unsafe {
|
||||
std::slice::from_raw_parts(key, ent.keylen.load(Ordering::Relaxed) as usize)
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns a published entry identified by an ID obtained from this map.
|
||||
/// Linker callers only retain IDs returned by insertion or traversal of
|
||||
/// the owning map; they never probe a missing key or pass another map's
|
||||
/// ID here. That invariant makes the unchecked, initialized-value lookup
|
||||
/// below valid without a second occupancy check on this hot path.
|
||||
pub fn get(&self, id: EntryId) -> &T {
|
||||
self.0.value_at(id.0 as usize)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn prefetch(&self, id: EntryId) {
|
||||
crate::prefetch(std::ptr::from_ref(self.0.entry(id.0 as usize)).cast());
|
||||
}
|
||||
|
||||
pub fn key(&self, id: EntryId) -> &'static [u8] {
|
||||
self.key_at(id.0 as usize).expect("an occupied bucket")
|
||||
}
|
||||
|
||||
/// Returns the entries in the given shard in a deterministic order.
|
||||
///
|
||||
/// Linear probing fills the same set of buckets whatever the order
|
||||
/// keys were inserted in, but which of two colliding keys got the
|
||||
/// earlier bucket depends on it, so each run of adjacent occupied
|
||||
/// buckets is sorted by key.
|
||||
pub fn sorted_entries(&self, shard: usize) -> Vec<EntryId> {
|
||||
if self.0.nbuckets == 0 {
|
||||
return Vec::new();
|
||||
}
|
||||
let shard_size = self.0.nbuckets / NUM_SHARDS;
|
||||
let begin = shard * shard_size;
|
||||
let mut end = begin + shard_size;
|
||||
let occupied = |idx: usize| self.0.is_occupied(idx);
|
||||
|
||||
let size = (begin..end).filter(|&idx| occupied(idx)).count();
|
||||
let mut vec: Vec<EntryId> = Vec::with_capacity(size);
|
||||
|
||||
// Since the shard is circular, we need to handle the last entries
|
||||
// as if they were next to the first entries.
|
||||
while begin < end && occupied(end - 1) {
|
||||
end -= 1;
|
||||
vec.push(EntryId(end as u32));
|
||||
}
|
||||
|
||||
let sort_run = |run: &mut [EntryId]| {
|
||||
run.sort_unstable_by(|&a, &b| {
|
||||
let (ka, kb) = (self.key(a), self.key(b));
|
||||
ka.len().cmp(&kb.len()).then_with(|| ka.cmp(kb))
|
||||
});
|
||||
};
|
||||
|
||||
// Find entries contiguous in the buckets and sort them.
|
||||
let mut last = 0;
|
||||
let mut i = begin;
|
||||
while i < end {
|
||||
while i < end && occupied(i) {
|
||||
vec.push(EntryId(i as u32));
|
||||
i += 1;
|
||||
}
|
||||
sort_run(&mut vec[last..]);
|
||||
last = vec.len();
|
||||
while i < end && !occupied(i) {
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
vec
|
||||
}
|
||||
|
||||
/// Returns all map entries in deterministic order.
|
||||
pub fn sorted_entries_all(&self) -> Vec<EntryId>
|
||||
where
|
||||
T: Send + Sync,
|
||||
{
|
||||
(0..NUM_SHARDS).into_par_iter().flat_map_iter(|shard| self.sorted_entries(shard)).collect()
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.0.len()
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len() == 0
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn insert_and_lookup() {
|
||||
let map: ConcurrentMap<u32> = ConcurrentMap::with_capacity(100);
|
||||
let keys: Vec<&'static [u8]> = (0..1000)
|
||||
.map(|i| &*Box::leak(format!("key{i}").into_bytes().into_boxed_slice()))
|
||||
.collect();
|
||||
let hash = |k: &[u8]| xxhash_rust::xxh3::xxh3_64(k);
|
||||
let mut ids = Vec::with_capacity(keys.len());
|
||||
for (i, &k) in keys.iter().enumerate() {
|
||||
let (id, v, inserted) = map.insert_with(k, hash(k), || i as u32);
|
||||
assert!(inserted);
|
||||
assert_eq!(*v, i as u32);
|
||||
ids.push(id);
|
||||
}
|
||||
for (i, &k) in keys.iter().enumerate() {
|
||||
let (id, v, inserted) = map.insert_with(k, hash(k), || 99999);
|
||||
assert!(!inserted);
|
||||
assert_eq!(id, ids[i]);
|
||||
assert_eq!(*v, i as u32);
|
||||
}
|
||||
let map = map.freeze();
|
||||
for (i, &id) in ids.iter().enumerate() {
|
||||
assert_eq!(map.key(id), keys[i]);
|
||||
assert_eq!(*map.get(id), i as u32);
|
||||
}
|
||||
assert_eq!(map.len(), 1000);
|
||||
let sorted: usize = (0..NUM_SHARDS).map(|s| map.sorted_entries(s).len()).sum();
|
||||
assert_eq!(sorted, 1000);
|
||||
}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
//! Symbol name demangling for diagnostics.
|
||||
|
||||
/// Demangles an Itanium C++ ABI symbol name, if `name` is one the
|
||||
/// demangler reads: its identifiers are ASCII, so a name with other
|
||||
/// bytes stays as it is.
|
||||
pub fn demangle_cpp(name: &[u8]) -> Option<String> {
|
||||
if !name.starts_with(b"_Z") {
|
||||
return None;
|
||||
}
|
||||
cpp_demangle::Symbol::new(name).ok()?.demangle().ok()
|
||||
}
|
||||
|
||||
/// Demangles a Rust symbol name in either the legacy or the v0 scheme.
|
||||
/// Crate disambiguator hashes are omitted, as rustc's own tools do.
|
||||
pub fn demangle_rust(name: &[u8]) -> Option<String> {
|
||||
let name = std::str::from_utf8(name).ok()?;
|
||||
if let Ok(sym) = rustc_demangle::try_demangle(name) {
|
||||
return Some(format!("{sym:#}"));
|
||||
}
|
||||
|
||||
// A legacy name may be followed by characters that aren't part of the
|
||||
// mangling, as in `_ZN2ns7versionEv`, which is `ns::version` and a
|
||||
// trailing `v`. Demangle the path and keep the rest as it is.
|
||||
let end = legacy_path_end(name)?;
|
||||
let sym = rustc_demangle::try_demangle(&name[..end]).ok()?;
|
||||
Some(format!("{sym:#}{}", &name[end..]))
|
||||
}
|
||||
|
||||
/// The end of the legacy-mangled path `_ZN<len><ident>...E` at the start
|
||||
/// of `name`, if there is one.
|
||||
fn legacy_path_end(name: &str) -> Option<usize> {
|
||||
let mut rest = name.strip_prefix("_ZN")?;
|
||||
loop {
|
||||
if rest.starts_with('E') {
|
||||
return Some(name.len() - rest.len() + 1);
|
||||
}
|
||||
let digits = rest.bytes().take_while(u8::is_ascii_digit).count();
|
||||
let len: usize = rest[..digits].parse().ok()?;
|
||||
if len == 0 {
|
||||
return None;
|
||||
}
|
||||
rest = rest.get(digits.checked_add(len)?..)?;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn rust_legacy_with_suffix() {
|
||||
assert_eq!(demangle_rust(b"_ZN2ns7versionEv").as_deref(), Some("ns::versionv"));
|
||||
assert_eq!(demangle_rust(b"_ZN3foo3barE").as_deref(), Some("foo::bar"));
|
||||
assert_eq!(demangle_rust(b"_ZN3foo").as_deref(), None);
|
||||
assert_eq!(demangle_rust(b"main"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rust_legacy_length_overflow() {
|
||||
// Adding the digit count to this length wraps to zero, so an
|
||||
// unchecked parser would keep visiting the same identifier.
|
||||
let len = usize::MAX - usize::MAX.ilog10() as usize;
|
||||
let name = format!("_ZN{len}E");
|
||||
assert_eq!(demangle_rust(name.as_bytes()), None);
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
//! Fixed-byte-order integer I/O on byte slices.
|
||||
//!
|
||||
//! These functions read and write integers of a known byte order, for code
|
||||
//! that patches section contents of a known target. Target-generic code
|
||||
//! uses the methods on the linker's target type, which pick the byte order
|
||||
//! from the target.
|
||||
|
||||
macro_rules! endian_io {
|
||||
($int:ty, $readb:ident, $readl:ident, $writeb:ident, $writel:ident) => {
|
||||
#[inline]
|
||||
pub fn $readb(bytes: &[u8]) -> $int {
|
||||
let bytes = bytes[..size_of::<$int>()].try_into().unwrap();
|
||||
<$int>::from_be_bytes(bytes)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn $readl(bytes: &[u8]) -> $int {
|
||||
let bytes = bytes[..size_of::<$int>()].try_into().unwrap();
|
||||
<$int>::from_le_bytes(bytes)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn $writeb(bytes: &mut [u8], value: $int) {
|
||||
bytes[..size_of::<$int>()].copy_from_slice(&value.to_be_bytes());
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn $writel(bytes: &mut [u8], value: $int) {
|
||||
bytes[..size_of::<$int>()].copy_from_slice(&value.to_le_bytes());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
endian_io!(u16, read_ub16, read_ul16, write_ub16, write_ul16);
|
||||
endian_io!(u32, read_ub32, read_ul32, write_ub32, write_ul32);
|
||||
endian_io!(u64, read_ub64, read_ul64, write_ub64, write_ul64);
|
||||
endian_io!(i16, read_ib16, read_il16, write_ib16, write_il16);
|
||||
endian_io!(i32, read_ib32, read_il32, write_ib32, write_il32);
|
||||
endian_io!(i64, read_ib64, read_il64, write_ib64, write_il64);
|
||||
-190
@@ -1,190 +0,0 @@
|
||||
//! Error, warning and fatal-error reporting.
|
||||
//!
|
||||
//! Errors don't abort the link immediately: the linker keeps going so that
|
||||
//! all problems are reported in one run, then exits before writing the
|
||||
//! output. Fatal errors are for conditions the linker can't continue past.
|
||||
|
||||
use std::fmt;
|
||||
use std::io::{self, Write};
|
||||
use std::sync::Mutex;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
/// Whether to demangle symbol names in diagnostics. This is process-wide
|
||||
/// state so that `Display` implementations, which have no access to the
|
||||
/// linker context, can consult it.
|
||||
static DEMANGLE: AtomicBool = AtomicBool::new(true);
|
||||
|
||||
pub fn set_demangle(enabled: bool) {
|
||||
DEMANGLE.store(enabled, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
pub fn demangle_enabled() -> bool {
|
||||
DEMANGLE.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
// Diagnostic settings, error state and output serialization are process-wide.
|
||||
static COLOR: AtomicBool = AtomicBool::new(false);
|
||||
static FATAL_WARNINGS: AtomicBool = AtomicBool::new(false);
|
||||
static SUPPRESS_WARNINGS: AtomicBool = AtomicBool::new(false);
|
||||
static NOINHIBIT_EXEC: AtomicBool = AtomicBool::new(false);
|
||||
static HAS_ERROR: AtomicBool = AtomicBool::new(false);
|
||||
static OUTPUT_LOCK: Mutex<()> = Mutex::new(());
|
||||
|
||||
pub fn set_color(on: bool) {
|
||||
COLOR.store(on, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
pub fn set_fatal_warnings(on: bool) {
|
||||
FATAL_WARNINGS.store(on, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
pub fn set_suppress_warnings(on: bool) {
|
||||
SUPPRESS_WARNINGS.store(on, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
pub fn set_noinhibit_exec(on: bool) {
|
||||
NOINHIBIT_EXEC.store(on, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
pub fn noinhibit_exec() -> bool {
|
||||
NOINHIBIT_EXEC.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
// Format each message before taking the lock so diagnostics from different
|
||||
// threads cannot interleave.
|
||||
fn emit(prefix_mono: &str, prefix_color: &str, msg: fmt::Arguments) {
|
||||
let prefix = if COLOR.load(Ordering::Relaxed) { prefix_color } else { prefix_mono };
|
||||
let text = format!("{prefix}{msg}\n");
|
||||
let _guard = OUTPUT_LOCK.lock().unwrap_or_else(|e| e.into_inner());
|
||||
let _ = io::stderr().write_all(text.as_bytes());
|
||||
}
|
||||
|
||||
/// Reports an unrecoverable error and exits.
|
||||
pub fn fatal(msg: fmt::Arguments) -> ! {
|
||||
emit("mold: fatal: ", "mold: \x1b[0;1;31mfatal:\x1b[0m ", msg);
|
||||
exit_after_cleanup(1);
|
||||
}
|
||||
|
||||
/// Reports an error. With `--noinhibit-exec` it is downgraded to a warning.
|
||||
pub fn error(msg: fmt::Arguments) {
|
||||
if NOINHIBIT_EXEC.load(Ordering::Relaxed) {
|
||||
emit("mold: warning: ", "mold: \x1b[0;1;35mwarning:\x1b[0m ", msg);
|
||||
} else {
|
||||
emit("mold: error: ", "mold: \x1b[0;1;31merror:\x1b[0m ", msg);
|
||||
HAS_ERROR.store(true, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
/// Reports a warning. With `--fatal-warnings` it is promoted to an error.
|
||||
pub fn warn(msg: fmt::Arguments) {
|
||||
if SUPPRESS_WARNINGS.load(Ordering::Relaxed) {
|
||||
return;
|
||||
}
|
||||
if FATAL_WARNINGS.load(Ordering::Relaxed) {
|
||||
emit("mold: error: ", "mold: \x1b[0;1;31merror:\x1b[0m ", msg);
|
||||
HAS_ERROR.store(true, Ordering::Relaxed);
|
||||
} else {
|
||||
emit("mold: warning: ", "mold: \x1b[0;1;35mwarning:\x1b[0m ", msg);
|
||||
}
|
||||
}
|
||||
|
||||
/// Prints a message with no prefix to stderr: some reports of what the
|
||||
/// link did (the Mach-O linker's -why_live, -why_load and text
|
||||
/// relocations) come in lines of their own.
|
||||
pub fn notice(msg: fmt::Arguments) {
|
||||
emit("", "", msg);
|
||||
}
|
||||
|
||||
/// Prints an informational message to stdout.
|
||||
pub fn out(msg: fmt::Arguments) {
|
||||
let text = format!("{msg}\n");
|
||||
let _guard = OUTPUT_LOCK.lock().unwrap_or_else(|e| e.into_inner());
|
||||
let _ = io::stdout().write_all(text.as_bytes());
|
||||
}
|
||||
|
||||
/// Returns the text that C's strerror gives for an I/O error. Unlike
|
||||
/// `io::Error`'s `Display`, it doesn't append " (os error N)". An error that
|
||||
/// doesn't come from the OS is formatted as usual.
|
||||
pub fn strerror(err: &io::Error) -> String {
|
||||
#[cfg(not(windows))]
|
||||
if let Some(errno) = err.raw_os_error() {
|
||||
let mut buf = [0u8; 256];
|
||||
// SAFETY: strerror_r writes at most `buf.len()` bytes to `buf`.
|
||||
let ret = unsafe { libc::strerror_r(errno, buf.as_mut_ptr().cast(), buf.len()) };
|
||||
if ret == 0
|
||||
&& let Ok(msg) = std::ffi::CStr::from_bytes_until_nul(&buf)
|
||||
{
|
||||
return msg.to_string_lossy().into_owned();
|
||||
}
|
||||
}
|
||||
err.to_string()
|
||||
}
|
||||
|
||||
/// Exits with a failure status if any error has been reported.
|
||||
pub fn checkpoint() {
|
||||
if HAS_ERROR.load(Ordering::Relaxed) {
|
||||
exit_after_cleanup(1);
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes a partially-written output file, then terminates the process
|
||||
/// without running destructors. Input files are mapped for the process's
|
||||
/// lifetime, so there is nothing else to release.
|
||||
pub fn exit_after_cleanup(status: i32) -> ! {
|
||||
crate::output_file::cleanup();
|
||||
let _ = io::stdout().flush();
|
||||
let _ = io::stderr().flush();
|
||||
// SAFETY: `_exit` only terminates the process.
|
||||
#[cfg(not(windows))]
|
||||
unsafe {
|
||||
libc::_exit(status)
|
||||
}
|
||||
#[cfg(windows)]
|
||||
std::process::exit(status)
|
||||
}
|
||||
|
||||
/// Makes a panic remove a partially-written output file, as a fatal error
|
||||
/// does, before the default hook reports the panic. Nothing recovers from
|
||||
/// a panic, so it always terminates the process.
|
||||
pub fn install_panic_hook() {
|
||||
let default_hook = std::panic::take_hook();
|
||||
std::panic::set_hook(Box::new(move |info| {
|
||||
crate::output_file::cleanup();
|
||||
default_hook(info);
|
||||
}));
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! fatal {
|
||||
($($arg:tt)*) => {
|
||||
$crate::error::fatal(format_args!($($arg)*))
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! error {
|
||||
($($arg:tt)*) => {
|
||||
$crate::error::error(format_args!($($arg)*))
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! warn {
|
||||
($($arg:tt)*) => {
|
||||
$crate::error::warn(format_args!($($arg)*))
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! notice {
|
||||
($($arg:tt)*) => {
|
||||
$crate::error::notice(format_args!($($arg)*))
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! out {
|
||||
($($arg:tt)*) => {
|
||||
$crate::error::out(format_args!($($arg)*))
|
||||
};
|
||||
}
|
||||
-826
@@ -1,826 +0,0 @@
|
||||
//! The glob matcher used for symbol name patterns in version scripts,
|
||||
//! dynamic list files and Mach-O symbol lists. Exact, prefix and suffix
|
||||
//! patterns are matched directly. Simple substring patterns such as these
|
||||
//!
|
||||
//! *16QAccessibleCache*
|
||||
//! *32QAbstractFileIconProviderPrivate*
|
||||
//! *17QPixmapIconEngine*
|
||||
//!
|
||||
//! are combined into an Aho-Corasick matcher, essentially a trie with
|
||||
//! additional links that searches for all of them in one pass over the
|
||||
//! input (https://en.wikipedia.org/wiki/Aho-Corasick_algorithm). It can only
|
||||
//! search substrings, so complex patterns such as `*foo*bar*` are matched
|
||||
//! with the non-recursive algorithm described at
|
||||
//! https://research.swtch.com/glob, and if there are many of them, a
|
||||
//! bit-parallel NFA matches them together.
|
||||
|
||||
use std::collections::VecDeque;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
enum Token {
|
||||
Str(Vec<u8>),
|
||||
Star,
|
||||
Question,
|
||||
Bracket(Box<[bool; 256]>),
|
||||
}
|
||||
|
||||
/// How a pattern spells bracket expressions and escapes: as fnmatch(3)
|
||||
/// does, which is how the ELF linkers read version scripts, or as
|
||||
/// ld-prime reads a Mach-O symbol list (see ld_prime_bracket).
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
enum Syntax {
|
||||
Fnmatch,
|
||||
LdPrime,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct Pattern {
|
||||
tokens: Vec<Token>,
|
||||
// Searchers for the literals that follow stars, indexed like `tokens`.
|
||||
// Building a searcher can take longer than searching a symbol name, so
|
||||
// we build them once.
|
||||
finders: Vec<Option<memchr::memmem::Finder<'static>>>,
|
||||
value: i64,
|
||||
}
|
||||
|
||||
impl Pattern {
|
||||
fn compile(mut pat: &[u8], value: i64, syntax: Syntax) -> Option<Self> {
|
||||
let mut tokens: Vec<Token> = Vec::new();
|
||||
|
||||
while let Some((&c, rest)) = pat.split_first() {
|
||||
pat = rest;
|
||||
match c {
|
||||
b'[' if syntax == Syntax::LdPrime => {
|
||||
let end = pat.iter().position(|&c| c == b']')?;
|
||||
tokens.push(Token::Bracket(ld_prime_bracket(&pat[..end])?));
|
||||
pat = &pat[end + 1..];
|
||||
}
|
||||
b'[' => {
|
||||
// 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.
|
||||
let mut chars = Box::new([false; 256]);
|
||||
let mut negate = false;
|
||||
let mut closed = false;
|
||||
|
||||
if let Some((&b'!' | &b'^', rest)) = pat.split_first() {
|
||||
negate = true;
|
||||
pat = rest;
|
||||
}
|
||||
|
||||
while let Some((&c, rest)) = pat.split_first() {
|
||||
if c == b']' {
|
||||
pat = rest;
|
||||
closed = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if c == b'\\' {
|
||||
pat = rest;
|
||||
if pat.is_empty() {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
if pat.len() >= 3 && pat[1] == b'-' {
|
||||
let start = pat[0];
|
||||
let mut end = pat[2];
|
||||
pat = &pat[3..];
|
||||
if end == b'\\' {
|
||||
end = *pat.first()?;
|
||||
pat = &pat[1..];
|
||||
}
|
||||
if end < start {
|
||||
return None;
|
||||
}
|
||||
chars[start as usize..=end as usize].fill(true);
|
||||
} else {
|
||||
chars[pat[0] as usize] = true;
|
||||
pat = &pat[1..];
|
||||
}
|
||||
}
|
||||
|
||||
if !closed {
|
||||
return None;
|
||||
}
|
||||
if negate {
|
||||
for flag in chars.iter_mut() {
|
||||
*flag = !*flag;
|
||||
}
|
||||
}
|
||||
tokens.push(Token::Bracket(chars));
|
||||
}
|
||||
b'?' => tokens.push(Token::Question),
|
||||
b'*' => {
|
||||
if !matches!(tokens.last(), Some(Token::Star)) {
|
||||
tokens.push(Token::Star);
|
||||
}
|
||||
}
|
||||
// A backslash takes the next character as itself. ld-prime
|
||||
// takes one at the end as itself too.
|
||||
b'\\' => match pat.split_first() {
|
||||
Some((&escaped, rest)) => {
|
||||
pat = rest;
|
||||
push_char(&mut tokens, escaped);
|
||||
}
|
||||
None if syntax == Syntax::LdPrime => push_char(&mut tokens, c),
|
||||
None => return None,
|
||||
},
|
||||
_ => push_char(&mut tokens, c),
|
||||
}
|
||||
}
|
||||
|
||||
let finders = (0..tokens.len())
|
||||
.map(|i| match &tokens[i] {
|
||||
Token::Str(literal) if i > 0 && matches!(tokens[i - 1], Token::Star) => {
|
||||
Some(memchr::memmem::Finder::new(literal).into_owned())
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
Some(Self { tokens, finders, value })
|
||||
}
|
||||
|
||||
fn matches(&self, s: &[u8]) -> bool {
|
||||
if let Some(Token::Str(suffix)) = self.tokens.last()
|
||||
&& !s.ends_with(suffix)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
let mut x = 0;
|
||||
let mut y = 0;
|
||||
let mut next: Option<(usize, usize)> = None;
|
||||
|
||||
while x < s.len() || y < self.tokens.len() {
|
||||
if y < self.tokens.len() {
|
||||
let tok = &self.tokens[y];
|
||||
match tok {
|
||||
Token::Str(literal) => {
|
||||
if s[x..].starts_with(literal) {
|
||||
x += literal.len();
|
||||
y += 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
Token::Star => {
|
||||
next = Some((x + 1, y));
|
||||
y += 1;
|
||||
if let Some(Some(finder)) = self.finders.get(y) {
|
||||
let Some(pos) = finder.find(&s[x..]) else {
|
||||
return false;
|
||||
};
|
||||
let pos = x + pos;
|
||||
next = Some((pos + 1, y - 1));
|
||||
x = pos + finder.needle().len();
|
||||
y += 1;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
Token::Question => {
|
||||
if x < s.len() {
|
||||
x += 1;
|
||||
y += 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
Token::Bracket(chars) => {
|
||||
if x < s.len() && chars[s[x] as usize] {
|
||||
x += 1;
|
||||
y += 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Retry the last star after assigning one more input byte to it.
|
||||
match next {
|
||||
Some((nx, ny)) if nx <= s.len() => {
|
||||
x = nx;
|
||||
y = ny;
|
||||
}
|
||||
_ => return false,
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/// The characters a bracket expression matches, given what is between
|
||||
/// its brackets, as ld-prime reads one: it ends at the first `]` (so
|
||||
/// `[]` matches nothing) and knows no negation or escapes - `[!a]`
|
||||
/// matches `!` or `a`, `[\a]` `\` or `a`. A `-` between two characters
|
||||
/// makes a range from the one before it, which after a range is that
|
||||
/// `-` itself (`[a-b-d]` is `a`-`b` and `-`-`d`), to the one after it;
|
||||
/// a reversed range is empty. A `-` first or last makes the expression,
|
||||
/// and so its pattern, malformed (None): it matches nothing.
|
||||
fn ld_prime_bracket(class: &[u8]) -> Option<Box<[bool; 256]>> {
|
||||
let mut chars = Box::new([false; 256]);
|
||||
let mut prev = None;
|
||||
let mut i = 0;
|
||||
while i < class.len() {
|
||||
let c = class[i];
|
||||
if c == b'-' {
|
||||
let (Some(start), Some(&end)) = (prev, class.get(i + 1)) else {
|
||||
return None;
|
||||
};
|
||||
for x in start..=end {
|
||||
chars[x as usize] = true;
|
||||
}
|
||||
i += 1;
|
||||
} else {
|
||||
chars[c as usize] = true;
|
||||
}
|
||||
prev = Some(c);
|
||||
i += 1;
|
||||
}
|
||||
Some(chars)
|
||||
}
|
||||
|
||||
fn push_char(tokens: &mut Vec<Token>, c: u8) {
|
||||
if let Some(Token::Str(literal)) = tokens.last_mut() {
|
||||
literal.push(c);
|
||||
} else {
|
||||
tokens.push(Token::Str(vec![c]));
|
||||
}
|
||||
}
|
||||
|
||||
// Nfa matches many glob patterns in parallel by representing each state
|
||||
// with one bit. It is used only for large pattern sets; matching individual
|
||||
// patterns is faster when there are only a few of them.
|
||||
#[derive(Debug, Default)]
|
||||
struct Nfa {
|
||||
initial_states: Vec<u64>,
|
||||
star_states: Vec<u64>,
|
||||
accept_states: Vec<u64>,
|
||||
char_masks: Vec<u64>,
|
||||
values: Vec<i64>,
|
||||
}
|
||||
|
||||
impl Nfa {
|
||||
fn compile(patterns: &[Pattern]) -> Self {
|
||||
let mut num_states = 0;
|
||||
for pattern in patterns {
|
||||
num_states += 1;
|
||||
for tok in &pattern.tokens {
|
||||
match tok {
|
||||
Token::Str(literal) => num_states += literal.len(),
|
||||
Token::Star => {}
|
||||
_ => num_states += 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let num_words = num_states.div_ceil(64);
|
||||
let mut nfa = Self {
|
||||
initial_states: vec![0; num_words],
|
||||
star_states: vec![0; num_words],
|
||||
accept_states: vec![0; num_words],
|
||||
char_masks: vec![0; 256 * num_words],
|
||||
values: vec![-1; num_states],
|
||||
};
|
||||
|
||||
let set_bit = |vec: &mut [u64], pos: usize| vec[pos / 64] |= 1 << (pos % 64);
|
||||
let mut state = 0;
|
||||
|
||||
for pattern in patterns {
|
||||
set_bit(&mut nfa.initial_states, state);
|
||||
for tok in &pattern.tokens {
|
||||
match tok {
|
||||
Token::Str(literal) => {
|
||||
for &c in literal {
|
||||
state += 1;
|
||||
nfa.char_masks[c as usize * num_words + state / 64] |=
|
||||
1 << (state % 64);
|
||||
}
|
||||
}
|
||||
Token::Star => set_bit(&mut nfa.star_states, state),
|
||||
Token::Question => {
|
||||
state += 1;
|
||||
for c in 0..256 {
|
||||
nfa.char_masks[c * num_words + state / 64] |= 1 << (state % 64);
|
||||
}
|
||||
}
|
||||
Token::Bracket(chars) => {
|
||||
state += 1;
|
||||
for c in 0..256 {
|
||||
if chars[c] {
|
||||
nfa.char_masks[c * num_words + state / 64] |= 1 << (state % 64);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
set_bit(&mut nfa.accept_states, state);
|
||||
nfa.values[state] = pattern.value;
|
||||
state += 1;
|
||||
}
|
||||
nfa
|
||||
}
|
||||
|
||||
fn is_empty(&self) -> bool {
|
||||
self.initial_states.is_empty()
|
||||
}
|
||||
|
||||
fn matches(&self, s: &[u8]) -> i64 {
|
||||
let num_words = self.initial_states.len();
|
||||
let mut states = self.initial_states.clone();
|
||||
|
||||
for &c in s {
|
||||
let mask = &self.char_masks[c as usize * num_words..][..num_words];
|
||||
let mut carry = 0;
|
||||
for i in 0..num_words {
|
||||
let old = states[i];
|
||||
let next = (old << 1) | carry;
|
||||
states[i] = (old & self.star_states[i]) | (next & mask[i]);
|
||||
carry = old >> 63;
|
||||
}
|
||||
}
|
||||
|
||||
let mut value = -1;
|
||||
for (i, &state) in states.iter().enumerate() {
|
||||
let mut word = state & self.accept_states[i];
|
||||
while word != 0 {
|
||||
let bit = word.trailing_zeros() as usize;
|
||||
value = value.max(self.values[i * 64 + bit]);
|
||||
word &= word - 1;
|
||||
}
|
||||
}
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
/// An Aho-Corasick automaton for patterns that are plain substring
|
||||
/// searches, such as `*foo*`, `foo*` or `*foo`. Anchors are represented by
|
||||
/// a NUL byte at the beginning or end of the pattern.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
struct AhoCorasick {
|
||||
// Most trie nodes have only one child. The root uses a dense table because
|
||||
// it is visited for almost every input byte; other edges are stored sparsely.
|
||||
root_children: Vec<i32>,
|
||||
nodes: Vec<TrieNode>,
|
||||
max_value: i64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct TrieNode {
|
||||
value: i64,
|
||||
suffix_link: i32,
|
||||
first_child: i32,
|
||||
next_sibling: i32,
|
||||
ch: u8,
|
||||
}
|
||||
|
||||
impl Default for TrieNode {
|
||||
fn default() -> Self {
|
||||
Self { value: -1, suffix_link: -1, first_child: -1, next_sibling: -1, ch: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
impl AhoCorasick {
|
||||
fn can_handle(pat: &[u8]) -> bool {
|
||||
let pat = pat.strip_prefix(b"*").unwrap_or(pat);
|
||||
let pat = pat.strip_suffix(b"*").unwrap_or(pat);
|
||||
!pat.is_empty() && !pat.iter().any(|&c| matches!(c, b'*' | b'?' | b'[' | b'\\'))
|
||||
}
|
||||
|
||||
fn find_child(&self, node: i32, ch: u8) -> i32 {
|
||||
if node == 0 {
|
||||
return self.root_children[ch as usize];
|
||||
}
|
||||
let mut i = self.nodes[node as usize].first_child;
|
||||
while i != -1 {
|
||||
if self.nodes[i as usize].ch == ch {
|
||||
return i;
|
||||
}
|
||||
i = self.nodes[i as usize].next_sibling;
|
||||
}
|
||||
-1
|
||||
}
|
||||
|
||||
fn add_child(&mut self, node: i32, ch: u8) -> i32 {
|
||||
let child = self.find_child(node, ch);
|
||||
if child != -1 {
|
||||
return child;
|
||||
}
|
||||
let child = self.nodes.len() as i32;
|
||||
let sibling = self.nodes[node as usize].first_child;
|
||||
self.nodes.push(TrieNode { next_sibling: sibling, ch, ..TrieNode::default() });
|
||||
self.nodes[node as usize].first_child = child;
|
||||
if node == 0 {
|
||||
self.root_children[ch as usize] = child;
|
||||
}
|
||||
child
|
||||
}
|
||||
|
||||
fn add(&mut self, pat: &[u8], value: i64) {
|
||||
debug_assert!(Self::can_handle(pat));
|
||||
self.max_value = self.max_value.max(value);
|
||||
if self.nodes.is_empty() {
|
||||
self.root_children = vec![-1; 256];
|
||||
self.nodes.push(TrieNode::default());
|
||||
}
|
||||
|
||||
// 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
|
||||
// marker.
|
||||
let mut idx = 0;
|
||||
if !pat.starts_with(b"*") {
|
||||
idx = self.add_child(idx, 0);
|
||||
}
|
||||
for &c in pat {
|
||||
if c != b'*' {
|
||||
idx = self.add_child(idx, c);
|
||||
}
|
||||
}
|
||||
if !pat.ends_with(b"*") {
|
||||
idx = self.add_child(idx, 0);
|
||||
}
|
||||
let node = &mut self.nodes[idx as usize];
|
||||
node.value = node.value.max(value);
|
||||
}
|
||||
|
||||
fn compile(&mut self) {
|
||||
if self.nodes.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
// A failure link may refer to any shallower node, so failure links
|
||||
// must be constructed breadth-first.
|
||||
let mut queue = VecDeque::new();
|
||||
let mut child = self.nodes[0].first_child;
|
||||
while child != -1 {
|
||||
self.nodes[child as usize].suffix_link = 0;
|
||||
queue.push_back(child);
|
||||
child = self.nodes[child as usize].next_sibling;
|
||||
}
|
||||
|
||||
while let Some(idx) = queue.pop_front() {
|
||||
let mut child = self.nodes[idx as usize].first_child;
|
||||
while child != -1 {
|
||||
let ch = self.nodes[child as usize].ch;
|
||||
let mut suffix = self.nodes[idx as usize].suffix_link;
|
||||
while suffix != 0 && self.find_child(suffix, ch) == -1 {
|
||||
suffix = self.nodes[suffix as usize].suffix_link;
|
||||
}
|
||||
let next = self.find_child(suffix, ch);
|
||||
if next != -1 {
|
||||
suffix = next;
|
||||
}
|
||||
self.nodes[child as usize].suffix_link = suffix;
|
||||
let suffix_value = self.nodes[suffix as usize].value;
|
||||
let node = &mut self.nodes[child as usize];
|
||||
node.value = node.value.max(suffix_value);
|
||||
queue.push_back(child);
|
||||
child = self.nodes[child as usize].next_sibling;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This runs once per byte while matching symbol names. Keep the state
|
||||
// in the caller's registers instead of outlining the loop body.
|
||||
#[inline(always)]
|
||||
fn walk(&self, c: u8, idx: &mut i32, value: &mut i64) -> i64 {
|
||||
let mut j = *idx;
|
||||
while j != -1 {
|
||||
let child = self.find_child(j, c);
|
||||
if child != -1 {
|
||||
*idx = child;
|
||||
*value = (*value).max(self.nodes[child as usize].value);
|
||||
return *value;
|
||||
}
|
||||
j = self.nodes[j as usize].suffix_link;
|
||||
}
|
||||
*idx = 0;
|
||||
*value
|
||||
}
|
||||
|
||||
fn find(&self, s: &[u8]) -> i64 {
|
||||
if self.nodes.is_empty() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
let mut idx = 0;
|
||||
let mut value = -1;
|
||||
self.walk(0, &mut idx, &mut value);
|
||||
for &c in s {
|
||||
if self.walk(c, &mut idx, &mut value) == self.max_value {
|
||||
return self.max_value;
|
||||
}
|
||||
}
|
||||
self.walk(0, &mut idx, &mut value)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct Literal {
|
||||
pat: Vec<u8>,
|
||||
value: i64,
|
||||
}
|
||||
|
||||
/// A set of glob patterns, each associated with a value. Looking up a
|
||||
/// string returns the largest value among the matching patterns, or -1.
|
||||
#[derive(Debug)]
|
||||
pub struct Glob {
|
||||
// Patterns that need only a literal string comparison are kept out
|
||||
// of the automaton-based matchers below, which scan the entire input
|
||||
// string per query. Real version scripts consist almost entirely of
|
||||
// such patterns (e.g. `local: *;` or `v8dbg_*;`), and we match them
|
||||
// against every defined symbol name.
|
||||
match_all: i64, // "*"
|
||||
max_value: i64,
|
||||
exacts: Vec<Literal>,
|
||||
prefixes: Vec<Literal>,
|
||||
suffixes: Vec<Literal>,
|
||||
patterns: Vec<Pattern>, // "foo*bar"
|
||||
aho_corasick: AhoCorasick,
|
||||
nfa: Nfa,
|
||||
}
|
||||
|
||||
/// Collects patterns before compiling an immutable matcher.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct GlobBuilder {
|
||||
glob: Glob,
|
||||
}
|
||||
|
||||
fn is_literal(pat: &[u8]) -> bool {
|
||||
!pat.iter().any(|&c| matches!(c, b'*' | b'?' | b'[' | b'\\'))
|
||||
}
|
||||
|
||||
impl Default for Glob {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
match_all: -1,
|
||||
max_value: -1,
|
||||
exacts: Vec::new(),
|
||||
prefixes: Vec::new(),
|
||||
suffixes: Vec::new(),
|
||||
patterns: Vec::new(),
|
||||
aho_corasick: AhoCorasick::default(),
|
||||
nfa: Nfa::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GlobBuilder {
|
||||
/// Adds a pattern. Returns false if the pattern is malformed.
|
||||
pub fn add(&mut self, pat: &[u8], value: i64) -> bool {
|
||||
self.add_pattern(pat, value, Syntax::Fnmatch)
|
||||
}
|
||||
|
||||
/// Adds a pattern of a Mach-O symbol list, whose bracket expressions
|
||||
/// and escapes are read as ld-prime reads them (see ld_prime_bracket).
|
||||
/// Returns false if the pattern is malformed.
|
||||
pub fn add_ld_prime(&mut self, pat: &[u8], value: i64) -> bool {
|
||||
self.add_pattern(pat, value, Syntax::LdPrime)
|
||||
}
|
||||
|
||||
/// Adds a string that matches itself alone, wildcards and all.
|
||||
pub fn add_literal(&mut self, s: &[u8], value: i64) {
|
||||
debug_assert!(value >= 0);
|
||||
self.glob.max_value = self.glob.max_value.max(value);
|
||||
self.glob.exacts.push(Literal { pat: s.to_vec(), value });
|
||||
}
|
||||
|
||||
fn add_pattern(&mut self, pat: &[u8], value: i64, syntax: Syntax) -> bool {
|
||||
debug_assert!(value >= 0);
|
||||
self.glob.max_value = self.glob.max_value.max(value);
|
||||
|
||||
// Match-all, exact, prefix and suffix patterns are handled with
|
||||
// plain string comparisons instead of the matchers below, which
|
||||
// have to scan the entire input string on every query.
|
||||
if pat == b"*" {
|
||||
self.glob.match_all = self.glob.match_all.max(value);
|
||||
return true;
|
||||
}
|
||||
if is_literal(pat) {
|
||||
self.glob.exacts.push(Literal { pat: pat.to_vec(), value });
|
||||
return true;
|
||||
}
|
||||
if let Some(prefix) = pat.strip_suffix(b"*").filter(|p| is_literal(p)) {
|
||||
self.glob.prefixes.push(Literal { pat: prefix.to_vec(), value });
|
||||
return true;
|
||||
}
|
||||
if let Some(suffix) = pat.strip_prefix(b"*").filter(|p| is_literal(p)) {
|
||||
self.glob.suffixes.push(Literal { pat: suffix.to_vec(), value });
|
||||
return true;
|
||||
}
|
||||
// If the pattern requires only a single substring search, the
|
||||
// Aho-Corasick algorithm is even faster than our glob matcher.
|
||||
if AhoCorasick::can_handle(pat) {
|
||||
self.glob.aho_corasick.add(pat, value);
|
||||
return true;
|
||||
}
|
||||
match Pattern::compile(pat, value, syntax) {
|
||||
Some(pattern) => {
|
||||
self.glob.patterns.push(pattern);
|
||||
true
|
||||
}
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Consumes the construction state. The result supports parallel queries.
|
||||
pub fn build(self) -> Glob {
|
||||
let mut glob = self.glob;
|
||||
if glob.match_all == glob.max_value {
|
||||
return glob;
|
||||
}
|
||||
|
||||
// For duplicate names, retain the largest value.
|
||||
glob.exacts.sort_unstable_by(|a, b| a.pat.cmp(&b.pat).then(b.value.cmp(&a.value)));
|
||||
glob.exacts.dedup_by(|a, b| a.pat == b.pat);
|
||||
if glob.patterns.len() >= 64 {
|
||||
glob.nfa = Nfa::compile(&glob.patterns);
|
||||
glob.patterns = Vec::new();
|
||||
}
|
||||
glob.aho_corasick.compile();
|
||||
glob
|
||||
}
|
||||
}
|
||||
|
||||
impl Glob {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.max_value < 0
|
||||
}
|
||||
|
||||
/// Returns the largest value of a matching pattern, or -1 if none match.
|
||||
pub fn find(&self, s: &[u8]) -> i64 {
|
||||
let mut value = self.match_all;
|
||||
if value == self.max_value {
|
||||
return value;
|
||||
}
|
||||
|
||||
if let Ok(i) = self.exacts.binary_search_by(|l| l.pat.as_slice().cmp(s)) {
|
||||
value = value.max(self.exacts[i].value);
|
||||
}
|
||||
for p in &self.prefixes {
|
||||
if value < p.value && s.starts_with(&p.pat) {
|
||||
value = p.value;
|
||||
}
|
||||
}
|
||||
for p in &self.suffixes {
|
||||
if value < p.value && s.ends_with(&p.pat) {
|
||||
value = p.value;
|
||||
}
|
||||
}
|
||||
if value == self.max_value {
|
||||
return value;
|
||||
}
|
||||
value = value.max(self.aho_corasick.find(s));
|
||||
if value == self.max_value {
|
||||
return value;
|
||||
}
|
||||
if !self.nfa.is_empty() {
|
||||
value = value.max(self.nfa.matches(s));
|
||||
}
|
||||
for p in &self.patterns {
|
||||
if value < p.value && p.matches(s) {
|
||||
value = p.value;
|
||||
}
|
||||
}
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn glob(pats: &[&str]) -> Glob {
|
||||
let mut g = GlobBuilder::default();
|
||||
for (i, p) in pats.iter().enumerate() {
|
||||
assert!(g.add(p.as_bytes(), i as i64));
|
||||
}
|
||||
g.build()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_has_no_matches() {
|
||||
let g = Glob::default();
|
||||
assert!(g.is_empty());
|
||||
assert_eq!(g.find(b"missing"), -1);
|
||||
|
||||
let mut g = GlobBuilder::default();
|
||||
assert!(g.add(b"present", 7));
|
||||
let g = g.build();
|
||||
assert!(!g.is_empty());
|
||||
assert_eq!(g.find(b"present"), 7);
|
||||
assert_eq!(g.find(b"missing"), -1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shared_priorities_preserve_highest_match() {
|
||||
let mut g = GlobBuilder::default();
|
||||
for (pattern, priority) in
|
||||
[("*", 0), ("*inner*", 2), ("prefix*", 2), ("*suffix", 2), ("exact", 3), ("p?efix*", 4)]
|
||||
{
|
||||
assert!(g.add(pattern.as_bytes(), priority));
|
||||
}
|
||||
let g = g.build();
|
||||
for (name, expected) in
|
||||
[("none", 0), ("hasinnersuffix", 2), ("suffix", 2), ("exact", 3), ("prefixsuffix", 4)]
|
||||
{
|
||||
assert_eq!(g.find(name.as_bytes()), expected);
|
||||
}
|
||||
let mut g = GlobBuilder::default();
|
||||
assert!(g.add(b"*inner*", 0));
|
||||
assert!(g.add(b"*suffix", 0));
|
||||
let g = g.build();
|
||||
assert_eq!(g.find(b"innersuffix"), 0);
|
||||
assert_eq!(g.find(b"none"), -1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn literals() {
|
||||
let g = glob(&["foo", "bar*", "*baz", "*"]);
|
||||
assert_eq!(g.find(b"foo"), 3);
|
||||
assert_eq!(g.find(b"barx"), 3);
|
||||
assert_eq!(g.find(b"xbaz"), 3);
|
||||
let g = glob(&["foo", "bar*", "*baz"]);
|
||||
assert_eq!(g.find(b"foo"), 0);
|
||||
assert_eq!(g.find(b"barx"), 1);
|
||||
assert_eq!(g.find(b"xbaz"), 2);
|
||||
assert_eq!(g.find(b"nothing"), -1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn complex() {
|
||||
let g = glob(&["a*b*c", "x?z", "[a-c]d", "*mid*", "[!x]y"]);
|
||||
assert_eq!(g.find(b"axxbyyc"), 0);
|
||||
assert_eq!(g.find(b"xyz"), 1);
|
||||
assert_eq!(g.find(b"bd"), 2);
|
||||
assert_eq!(g.find(b"dd"), -1);
|
||||
assert_eq!(g.find(b"leftmidright"), 3);
|
||||
assert_eq!(g.find(b"ay"), 4);
|
||||
assert_eq!(g.find(b"xy"), -1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ld_prime_brackets() {
|
||||
let mut g = GlobBuilder::default();
|
||||
for (i, pat) in
|
||||
["a[a-b-d]", "b[c-a]", "c[\\a]", "d[]x", "e\\*", "f\\", "g[!x]y"].iter().enumerate()
|
||||
{
|
||||
assert!(g.add_ld_prime(pat.as_bytes(), i as i64));
|
||||
}
|
||||
let g = g.build();
|
||||
for (name, expected) in [
|
||||
("ab", 0),
|
||||
("a-", 0),
|
||||
("aA", 0),
|
||||
("ac", 0),
|
||||
("a,", -1),
|
||||
("bc", 1),
|
||||
("bb", -1),
|
||||
("c\\", 2),
|
||||
("ca", 2),
|
||||
("d]x", -1),
|
||||
("dx", -1),
|
||||
("e*", 4),
|
||||
("ex", -1),
|
||||
("f\\", 5),
|
||||
("g!y", 6),
|
||||
("gxy", 6),
|
||||
("gay", -1),
|
||||
] {
|
||||
assert_eq!(g.find(name.as_bytes()), expected, "{name}");
|
||||
}
|
||||
let mut g = GlobBuilder::default();
|
||||
for pat in ["x[", "y[a-]", "z[-a]"] {
|
||||
assert!(!g.add_ld_prime(pat.as_bytes(), 0));
|
||||
}
|
||||
assert!(!g.add(b"f\\", 0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn literal_matches_only_itself() {
|
||||
let mut g = GlobBuilder::default();
|
||||
g.add_literal(b"a*b", 0);
|
||||
let g = g.build();
|
||||
assert_eq!(g.find(b"a*b"), 0);
|
||||
assert_eq!(g.find(b"axb"), -1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn many_patterns_use_nfa() {
|
||||
let pats: Vec<String> = (0..100).map(|i| format!("p{i}*q?r")).collect();
|
||||
let refs: Vec<&str> = pats.iter().map(String::as_str).collect();
|
||||
let g = glob(&refs);
|
||||
assert_eq!(g.find(b"p42xxqyr"), 42);
|
||||
assert_eq!(g.find(b"p42xxqr"), -1);
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
//! This file implements the 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
|
||||
|
||||
/// Estimates the number of distinct values in a stream of hashes.
|
||||
///
|
||||
/// Each hash selects a register by its low bits; the register keeps the
|
||||
/// longest run of leading zeros seen, plus one. The estimate is a scaled
|
||||
/// harmonic mean of the registers.
|
||||
#[derive(Clone)]
|
||||
pub struct HyperLogLog {
|
||||
registers: [u8; Self::NUM_REGISTERS],
|
||||
}
|
||||
|
||||
impl HyperLogLog {
|
||||
const NUM_REGISTERS: usize = 2048;
|
||||
const ALPHA: f64 = 0.79402;
|
||||
|
||||
#[inline]
|
||||
pub fn insert(&mut self, hash: u64) {
|
||||
let register = &mut self.registers[hash as usize & (Self::NUM_REGISTERS - 1)];
|
||||
*register = (*register).max(hash.leading_zeros() as u8 + 1);
|
||||
}
|
||||
|
||||
/// Combines two estimators, as if every value had been inserted into
|
||||
/// one of them.
|
||||
#[inline]
|
||||
pub fn merged(mut self, other: &Self) -> Self {
|
||||
for (a, b) in self.registers.iter_mut().zip(&other.registers) {
|
||||
*a = (*a).max(*b);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn cardinality(&self) -> u64 {
|
||||
let z: f64 = self.registers.iter().map(|&r| 2f64.powi(-i32::from(r))).sum();
|
||||
(Self::ALPHA * (Self::NUM_REGISTERS * Self::NUM_REGISTERS) as f64 / z) as u64
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for HyperLogLog {
|
||||
fn default() -> Self {
|
||||
Self { registers: [0; Self::NUM_REGISTERS] }
|
||||
}
|
||||
}
|
||||
|
||||
impl Extend<u64> for HyperLogLog {
|
||||
fn extend<I: IntoIterator<Item = u64>>(&mut self, hashes: I) {
|
||||
for hash in hashes {
|
||||
self.insert(hash);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn estimates_within_a_few_percent() {
|
||||
let mut hll = HyperLogLog::default();
|
||||
hll.extend((0..100_000u64).map(|i| xxhash_rust::xxh3::xxh3_64(&i.to_le_bytes())));
|
||||
let n = hll.cardinality();
|
||||
// The scale constant follows mold, whose estimates run about 10% high.
|
||||
assert!((95_000..125_000).contains(&n), "estimate {n}");
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
//! Limits the number of concurrent mold processes.
|
||||
|
||||
// Many build systems attempt to invoke as many linker processes as there
|
||||
// are cores, based on the assumption that the linker is single-threaded.
|
||||
// However, since mold is multi-threaded, such build systems' behavior is
|
||||
// not beneficial and just increases the overall peak memory usage.
|
||||
// On machines with limited memory, this could lead to an out-of-memory
|
||||
// error.
|
||||
//
|
||||
// This file implements a feature that limits the number of concurrent
|
||||
// mold processes to just 1 for each user. It is intended to be used as
|
||||
// `MOLD_JOBS=1 ninja` or `MOLD_JOBS=1 make -j$(nproc)`.
|
||||
|
||||
#[cfg(not(windows))]
|
||||
use std::ffi::CStr;
|
||||
#[cfg(not(windows))]
|
||||
use std::fs::{File, OpenOptions};
|
||||
#[cfg(not(windows))]
|
||||
use std::os::unix::{fs::OpenOptionsExt, io::AsRawFd};
|
||||
#[cfg(not(windows))]
|
||||
use std::sync::Mutex;
|
||||
|
||||
#[cfg(not(windows))]
|
||||
static LOCK_FILE: Mutex<Option<File>> = Mutex::new(None);
|
||||
|
||||
#[cfg(not(windows))]
|
||||
pub fn acquire_global_lock() {
|
||||
if std::env::var_os("MOLD_JOBS").as_deref() != Some(std::ffi::OsStr::new("1")) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Like other XDG variables, an empty XDG_RUNTIME_DIR counts as unset.
|
||||
let path = if let Some(dir) = std::env::var_os("XDG_RUNTIME_DIR").filter(|d| !d.is_empty()) {
|
||||
std::path::PathBuf::from(dir).join("mold-lock")
|
||||
} else {
|
||||
// SAFETY: getpwuid returns either null or a pointer to process-global
|
||||
// storage. We copy the name before making another libc call.
|
||||
let name = unsafe {
|
||||
let pwd = libc::getpwuid(libc::getuid());
|
||||
if pwd.is_null() || (*pwd).pw_name.is_null() {
|
||||
return;
|
||||
}
|
||||
CStr::from_ptr((*pwd).pw_name).to_bytes().to_vec()
|
||||
};
|
||||
let name = String::from_utf8_lossy(&name);
|
||||
std::path::PathBuf::from(format!("/tmp/mold-lock-{name}"))
|
||||
};
|
||||
let Ok(file) =
|
||||
OpenOptions::new().write(true).create(true).truncate(false).mode(0o600).open(path)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
// SAFETY: file owns a valid descriptor. Keep lockf for compatibility with
|
||||
// other mold processes; flock uses a different locking protocol.
|
||||
if unsafe { libc::lockf(file.as_raw_fd(), libc::F_LOCK, 0) } == -1 {
|
||||
return;
|
||||
}
|
||||
*LOCK_FILE.lock().unwrap() = Some(file);
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
pub fn acquire_global_lock() {}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
pub fn release_global_lock() {
|
||||
drop(LOCK_FILE.lock().unwrap().take());
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
pub fn release_global_lock() {}
|
||||
@@ -1,132 +0,0 @@
|
||||
//! LEB128, the variable-length integer encoding of DWARF and of other
|
||||
//! tables in object files.
|
||||
|
||||
use crate::bits::sign_extend;
|
||||
|
||||
/// Appends `value` in unsigned LEB128 encoding.
|
||||
pub fn encode_uleb(out: &mut Vec<u8>, mut value: u64) {
|
||||
loop {
|
||||
let byte = (value & 0x7f) as u8;
|
||||
value >>= 7;
|
||||
if value == 0 {
|
||||
out.push(byte);
|
||||
return;
|
||||
}
|
||||
out.push(byte | 0x80);
|
||||
}
|
||||
}
|
||||
|
||||
/// Appends `value` in signed LEB128 encoding.
|
||||
pub fn encode_sleb(out: &mut Vec<u8>, mut value: i64) {
|
||||
loop {
|
||||
let byte = (value & 0x7f) as u8;
|
||||
value >>= 7;
|
||||
let negative = byte & 0x40 != 0;
|
||||
if (value == 0 && !negative) || (value == -1 && negative) {
|
||||
out.push(byte);
|
||||
return;
|
||||
}
|
||||
out.push(byte | 0x80);
|
||||
}
|
||||
}
|
||||
|
||||
/// Writes `value` in unsigned LEB128 encoding at the start of `buf`,
|
||||
/// returning its length.
|
||||
pub fn write_uleb(buf: &mut [u8], mut value: u64) -> usize {
|
||||
let mut i = 0;
|
||||
loop {
|
||||
let mut byte = (value & 0x7f) as u8;
|
||||
value >>= 7;
|
||||
if value != 0 {
|
||||
byte |= 0x80;
|
||||
}
|
||||
buf[i] = byte;
|
||||
i += 1;
|
||||
if value == 0 {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The length of `value` in unsigned LEB128 encoding.
|
||||
pub fn uleb_size(mut value: u64) -> usize {
|
||||
let mut len = 1;
|
||||
while value >= 0x80 {
|
||||
value >>= 7;
|
||||
len += 1;
|
||||
}
|
||||
len
|
||||
}
|
||||
|
||||
/// Overwrites an existing unsigned LEB128 value in place, keeping its length.
|
||||
pub fn overwrite_uleb(buf: &mut [u8], mut value: u64) {
|
||||
let mut i = 0;
|
||||
while buf[i] & 0x80 != 0 {
|
||||
buf[i] = 0x80 | (value & 0x7f) as u8;
|
||||
value >>= 7;
|
||||
i += 1;
|
||||
}
|
||||
buf[i] = (value & 0x7f) as u8;
|
||||
}
|
||||
|
||||
/// Reads an unsigned LEB128 value, advancing `bytes` past it.
|
||||
#[inline]
|
||||
pub fn read_uleb(bytes: &mut &[u8]) -> u64 {
|
||||
let mut value = 0;
|
||||
let mut shift = 0;
|
||||
loop {
|
||||
let (&byte, rest) = bytes.split_first().expect("truncated LEB128");
|
||||
*bytes = rest;
|
||||
if shift < 64 {
|
||||
value |= ((byte & 0x7f) as u64) << shift;
|
||||
}
|
||||
shift += 7;
|
||||
if byte & 0x80 == 0 {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Reads a signed LEB128 value, advancing `bytes` past it.
|
||||
#[inline]
|
||||
pub fn read_sleb(bytes: &mut &[u8]) -> i64 {
|
||||
let mut value = 0u64;
|
||||
let mut shift = 0;
|
||||
loop {
|
||||
let (&byte, rest) = bytes.split_first().expect("truncated LEB128");
|
||||
*bytes = rest;
|
||||
if shift < 64 {
|
||||
value |= ((byte & 0x7f) as u64) << shift;
|
||||
}
|
||||
shift += 7;
|
||||
if byte & 0x80 == 0 {
|
||||
return if shift < 64 { sign_extend(value, shift) } else { value as i64 };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn leb128_roundtrip() {
|
||||
for &value in &[0u64, 1, 127, 128, 300, u32::MAX as u64, u64::MAX] {
|
||||
let mut buf = Vec::new();
|
||||
encode_uleb(&mut buf, value);
|
||||
assert_eq!(uleb_size(value), buf.len());
|
||||
let mut written = [0xff; 10];
|
||||
assert_eq!(write_uleb(&mut written, value), buf.len());
|
||||
assert_eq!(&written[..buf.len()], buf);
|
||||
let mut slice = buf.as_slice();
|
||||
assert_eq!(read_uleb(&mut slice), value);
|
||||
assert!(slice.is_empty());
|
||||
}
|
||||
for &value in &[0i64, -1, 63, 64, -64, -65, i64::MIN, i64::MAX] {
|
||||
let mut buf = Vec::new();
|
||||
encode_sleb(&mut buf, value);
|
||||
let mut slice = buf.as_slice();
|
||||
assert_eq!(read_sleb(&mut slice), value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
//! Code that mold's linkers share. Nothing here depends on the format of
|
||||
//! object files.
|
||||
|
||||
pub mod archive_file;
|
||||
pub mod bits;
|
||||
pub mod bytes;
|
||||
pub mod cityhash;
|
||||
pub mod compress;
|
||||
pub mod concurrent_map;
|
||||
pub mod demangle;
|
||||
pub mod endian;
|
||||
pub mod error;
|
||||
pub mod glob;
|
||||
pub mod hyperloglog;
|
||||
pub mod jobs;
|
||||
pub mod leb128;
|
||||
pub mod mapped_file;
|
||||
pub mod mem;
|
||||
pub mod output_file;
|
||||
pub mod parallel;
|
||||
pub mod path;
|
||||
pub mod perf;
|
||||
mod prefetch;
|
||||
pub mod record;
|
||||
pub mod response_file;
|
||||
pub mod siphash;
|
||||
pub mod subprocess;
|
||||
pub mod tar;
|
||||
pub mod worker_local;
|
||||
|
||||
pub use prefetch::prefetch;
|
||||
|
||||
/// The git commit that mold is built from, or None if the source tree has
|
||||
/// no git metadata, as in a source tarball.
|
||||
pub const GIT_HASH: Option<&str> = option_env!("MOLD_GIT_HASH");
|
||||
@@ -1,308 +0,0 @@
|
||||
//! Input file access.
|
||||
//!
|
||||
//! Every input file is read once and kept in memory for the whole link,
|
||||
//! because sections, symbol names and relocation tables of object files
|
||||
//! are used until the output is written. Files are therefore leaked with a
|
||||
//! `'static` lifetime rather than tracked with reference counts, which
|
||||
//! keeps lifetimes out of every data structure that refers to file
|
||||
//! contents.
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::fs::File;
|
||||
use std::io::{self, Read};
|
||||
use std::ops::Range;
|
||||
#[cfg(not(windows))]
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Mutex;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
#[cfg(not(windows))]
|
||||
use rayon::prelude::*;
|
||||
|
||||
use crate::error::strerror;
|
||||
use crate::fatal;
|
||||
use crate::mem::leak;
|
||||
use crate::parallel::SyncUnsafeCell;
|
||||
use crate::path::clean_path;
|
||||
|
||||
/// All files opened during this link.
|
||||
static FILE_POOL: Mutex<Vec<&'static MappedFile>> = Mutex::new(Vec::new());
|
||||
|
||||
/// The files that are memory-mapped, for [`drop_mappings`].
|
||||
static MMAPPED_FILES: Mutex<Vec<&'static MappedFile>> = Mutex::new(Vec::new());
|
||||
|
||||
/// The device and inode numbers of the memory-mapped files, for
|
||||
/// [`is_mmapped`].
|
||||
#[cfg(not(windows))]
|
||||
static MMAPPED_INODES: Mutex<Vec<(u64, u64)>> = Mutex::new(Vec::new());
|
||||
|
||||
/// Returns all files opened during this link, sorted by name. Input
|
||||
/// files are opened in parallel, so the order in which they were opened
|
||||
/// changes from run to run.
|
||||
pub fn file_pool() -> Vec<&'static MappedFile> {
|
||||
let mut files = FILE_POOL.lock().unwrap().clone();
|
||||
files.sort_by(|a, b| a.name.cmp(&b.name));
|
||||
files
|
||||
}
|
||||
|
||||
/// Drops the page table entries of the mapped input files, in parallel.
|
||||
/// This makes process exit faster, as the kernel otherwise reclaims them
|
||||
/// in a single thread on exit. The files' contents stay in the page cache,
|
||||
/// but the relocation records rewritten in place through these private
|
||||
/// mappings are discarded, so this must be the last use of the input data.
|
||||
pub fn drop_mappings() {
|
||||
let files = std::mem::take(&mut *MMAPPED_FILES.lock().unwrap());
|
||||
#[cfg(windows)]
|
||||
let _ = files;
|
||||
#[cfg(not(windows))]
|
||||
files.par_iter().for_each(|mf| {
|
||||
// SAFETY: the range is a whole mapping of the file that lives for
|
||||
// the rest of the process; MADV_DONTNEED only discards the pages,
|
||||
// which are faulted in again if they are ever read.
|
||||
unsafe {
|
||||
libc::madvise(
|
||||
mf.data().as_ptr() as *mut libc::c_void,
|
||||
mf.data().len(),
|
||||
libc::MADV_DONTNEED,
|
||||
)
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/// Returns true if the file at `path` is memory-mapped as an input. Such a
|
||||
/// file must not be written in place, because a private mapping reflects
|
||||
/// changes to the file in the pages it has not copied yet.
|
||||
#[cfg(not(windows))]
|
||||
pub fn is_mmapped(path: &Path) -> bool {
|
||||
std::fs::metadata(path)
|
||||
.is_ok_and(|m| MMAPPED_INODES.lock().unwrap().contains(&(m.dev(), m.ino())))
|
||||
}
|
||||
|
||||
/// Output files are never written in place on Windows.
|
||||
#[cfg(windows)]
|
||||
pub fn is_mmapped(_path: &Path) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
// Files up to this size are read into malloc'ed memory rather than
|
||||
// mmap'ed. mmap(2) takes the process's address space lock, so with tens
|
||||
// of thousands of input files, the calls serialize at a few microseconds
|
||||
// each no matter how many threads make them. read(2) takes no such lock,
|
||||
// but copying costs memory bandwidth in proportion to the file size, so
|
||||
// large files are still mmap'ed. On a Chromium link, files below this
|
||||
// threshold are 64% of the inputs by count but 16% by size.
|
||||
//
|
||||
// The buffers add up to hundreds of megabytes on such a link, so they
|
||||
// need to be backed by huge pages; with 4 KiB pages, faulting them in
|
||||
// costs more than the mmap calls did. mimalloc does that by default.
|
||||
const READ_THRESHOLD: u64 = 32 * 1024;
|
||||
|
||||
// MappedFile represents an input file that is either mmap'ed or read into
|
||||
// memory. Either way, its contents are accessible through `data`.
|
||||
#[derive(Debug)]
|
||||
pub struct MappedFile {
|
||||
/// The path this file was opened by, which already has --chroot
|
||||
/// applied, or the member name for a slice of an archive.
|
||||
pub name: PathBuf,
|
||||
/// The contents, in an allocation that is deliberately leaked with the
|
||||
/// input file. The bytes are cells because the linker modifies private
|
||||
/// relocation records in place, as C++ mold does.
|
||||
pub data: &'static [SyncUnsafeCell<u8>],
|
||||
|
||||
/// False if the file was found by searching library paths (`-l`),
|
||||
/// which affects how a shared library's soname defaults.
|
||||
pub given_fullpath: bool,
|
||||
|
||||
/// The archive this file is a member of.
|
||||
pub parent: Option<&'static Self>,
|
||||
|
||||
/// The thin archive this file is a member of.
|
||||
pub thin_parent: Option<&'static Self>,
|
||||
|
||||
// For --dependency-file
|
||||
pub is_dependency: AtomicBool,
|
||||
}
|
||||
|
||||
impl MappedFile {
|
||||
fn open_impl(path: &Path) -> io::Result<&'static Self> {
|
||||
let file = File::open(path)?;
|
||||
|
||||
let display = path.display();
|
||||
let metadata =
|
||||
file.metadata().unwrap_or_else(|e| fatal!("{display}: fstat failed: {}", strerror(&e)));
|
||||
let size = metadata.len();
|
||||
|
||||
// True if `data` is a memory mapping of the file rather than a copy of
|
||||
// its contents in anonymous memory.
|
||||
let mut is_mmapped = false;
|
||||
let data: &'static [SyncUnsafeCell<u8>] = if size == 0 {
|
||||
&[]
|
||||
} else if size <= READ_THRESHOLD && metadata.is_file() {
|
||||
let mut buf = Vec::with_capacity(size as usize);
|
||||
(&file)
|
||||
.take(size)
|
||||
.read_to_end(&mut buf)
|
||||
.unwrap_or_else(|e| fatal!("{display}: read failed: {}", strerror(&e)));
|
||||
if buf.len() as u64 != size {
|
||||
fatal!("{display}: file is shorter than its reported size");
|
||||
}
|
||||
SyncUnsafeCell::from_mut(Vec::leak(buf))
|
||||
} else {
|
||||
// C++ mold maps inputs MAP_PRIVATE with write permission so it
|
||||
// can redirect ordinary relocation records in place without
|
||||
// changing the input file.
|
||||
//
|
||||
// SAFETY: the mapping is private and lives for the rest of the
|
||||
// process. Input files are not expected to change while the
|
||||
// linker runs.
|
||||
let map_len = usize::try_from(size)
|
||||
.unwrap_or_else(|_| fatal!("{display}: file is too large to map"));
|
||||
let map = unsafe { memmap2::MmapOptions::new().len(map_len).map_copy(&file) }
|
||||
.unwrap_or_else(|e| fatal!("{display}: mmap failed: {}", strerror(&e)));
|
||||
is_mmapped = true;
|
||||
SyncUnsafeCell::from_mut(Box::leak(Box::new(map)).as_mut())
|
||||
};
|
||||
|
||||
let mf = leak(Self {
|
||||
name: path.to_path_buf(),
|
||||
data,
|
||||
given_fullpath: true,
|
||||
parent: None,
|
||||
thin_parent: None,
|
||||
is_dependency: AtomicBool::new(true),
|
||||
});
|
||||
FILE_POOL.lock().unwrap().push(mf);
|
||||
if is_mmapped {
|
||||
MMAPPED_FILES.lock().unwrap().push(mf);
|
||||
#[cfg(not(windows))]
|
||||
MMAPPED_INODES.lock().unwrap().push((metadata.dev(), metadata.ino()));
|
||||
}
|
||||
Ok(mf)
|
||||
}
|
||||
|
||||
/// Opens a file, returning `None` if it doesn't exist.
|
||||
pub fn open(path: impl AsRef<Path>) -> Option<&'static Self> {
|
||||
let path = path.as_ref();
|
||||
match Self::open_impl(path) {
|
||||
Ok(mf) => Some(mf),
|
||||
Err(e) if e.kind() == io::ErrorKind::NotFound => None,
|
||||
Err(e) => fatal!("opening {} failed: {}", path.display(), strerror(&e)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Opens a file that must exist.
|
||||
pub fn must_open(path: impl AsRef<Path>) -> &'static Self {
|
||||
let path = path.as_ref();
|
||||
Self::open_impl(path)
|
||||
.unwrap_or_else(|e| fatal!("cannot open {}: {}", path.display(), strerror(&e)))
|
||||
}
|
||||
|
||||
/// Returns a view of a member of this archive.
|
||||
pub fn slice(&'static self, name: PathBuf, start: usize, size: usize) -> &'static Self {
|
||||
let mf = leak(Self {
|
||||
name,
|
||||
data: &self.data[start..start + size],
|
||||
given_fullpath: true,
|
||||
parent: Some(self),
|
||||
thin_parent: None,
|
||||
is_dependency: AtomicBool::new(true),
|
||||
});
|
||||
FILE_POOL.lock().unwrap().push(mf);
|
||||
mf
|
||||
}
|
||||
|
||||
/// Opens a member whose bytes are stored outside this thin archive.
|
||||
pub fn open_thin_member(&'static self, path: &Path) -> &'static Self {
|
||||
let member = Self::must_open(path);
|
||||
leak(Self {
|
||||
name: member.name.clone(),
|
||||
data: member.data,
|
||||
given_fullpath: true,
|
||||
parent: None,
|
||||
thin_parent: Some(self),
|
||||
is_dependency: AtomicBool::new(true),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn size(&self) -> usize {
|
||||
self.data.len()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn data(&self) -> &[u8] {
|
||||
// SAFETY: the only writes are to relocation ranges during exclusive
|
||||
// linker phases, which use no shared view of those ranges.
|
||||
unsafe { SyncUnsafeCell::as_slice(self.data) }
|
||||
}
|
||||
|
||||
/// Returns a relocation range for modification.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The caller must have exclusive access to `range` while the slice is
|
||||
/// alive, and no shared reference to an overlapping range may be used
|
||||
/// during that time.
|
||||
#[allow(clippy::mut_from_ref)]
|
||||
pub unsafe fn data_mut(&self, range: Range<usize>) -> &mut [u8] {
|
||||
// SAFETY: guaranteed by the caller.
|
||||
unsafe { SyncUnsafeCell::as_mut_slice(&self.data[range]) }
|
||||
}
|
||||
|
||||
/// The offset of this slice within its top-level archive file.
|
||||
pub fn offset(&self) -> usize {
|
||||
match self.parent {
|
||||
Some(parent) => {
|
||||
self.data.as_ptr() as usize - parent.data.as_ptr() as usize + parent.offset()
|
||||
}
|
||||
None => 0,
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a string that uniquely identifies a file that is possibly
|
||||
// in an archive.
|
||||
pub fn identifier(&self) -> std::ffi::OsString {
|
||||
if let Some(parent) = self.parent {
|
||||
// Archive members may have the same name, so use the file offset.
|
||||
let mut name = parent.name.as_os_str().to_os_string();
|
||||
name.push(format!(":{}", self.offset()));
|
||||
return name;
|
||||
}
|
||||
if let Some(parent) = self.thin_parent {
|
||||
// Thin archive members have unique filenames.
|
||||
let mut name = parent.name.as_os_str().to_os_string();
|
||||
name.push(":");
|
||||
name.push(&self.name);
|
||||
return name;
|
||||
}
|
||||
self.name.as_os_str().to_os_string()
|
||||
}
|
||||
|
||||
pub fn is_dependency(&self) -> bool {
|
||||
self.is_dependency.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
pub fn set_dependency(&self, value: bool) {
|
||||
self.is_dependency.store(value, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn apply_chroot<'a>(chroot: &Path, path: &'a Path) -> Cow<'a, Path> {
|
||||
if path.is_absolute() && !chroot.as_os_str().is_empty() {
|
||||
Cow::Owned(chroot.join(clean_path(path).strip_prefix("/").unwrap_or(path)))
|
||||
} else {
|
||||
Cow::Borrowed(path)
|
||||
}
|
||||
}
|
||||
|
||||
/// Opens an input file, applying `--chroot` to absolute paths.
|
||||
pub fn open_file(chroot: &Path, path: impl AsRef<Path>) -> Option<&'static MappedFile> {
|
||||
MappedFile::open(apply_chroot(chroot, path.as_ref()))
|
||||
}
|
||||
|
||||
/// Opens an input file that must exist, applying `--chroot` to absolute paths.
|
||||
pub fn must_open_file(chroot: &Path, path: impl AsRef<Path>) -> &'static MappedFile {
|
||||
let path = path.as_ref();
|
||||
MappedFile::open_impl(&apply_chroot(chroot, path))
|
||||
.unwrap_or_else(|e| fatal!("cannot open {}: {}", path.display(), strerror(&e)))
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
//! Memory: transparent huge pages for large tables, and allocations that
|
||||
//! are never freed.
|
||||
|
||||
/// Requests transparent huge pages for a mapped byte range on Linux-based
|
||||
/// targets.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// `data..data + size` must describe a live mapping.
|
||||
#[cfg(any(target_os = "android", target_os = "linux"))]
|
||||
pub(crate) unsafe fn madvise_hugepage(data: *mut u8, size: usize) {
|
||||
// SAFETY: guaranteed by the caller; MADV_HUGEPAGE is only a kernel hint.
|
||||
let _ = unsafe { libc::madvise(data.cast(), size, libc::MADV_HUGEPAGE) };
|
||||
}
|
||||
|
||||
/// No-op on targets that do not support Linux's `MADV_HUGEPAGE` advice.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Kept identical to the supported-target signature.
|
||||
#[cfg(not(any(target_os = "android", target_os = "linux")))]
|
||||
pub(crate) unsafe fn madvise_hugepage(_data: *mut u8, _size: usize) {}
|
||||
|
||||
/// Requests transparent huge pages for the whole pages strictly inside an
|
||||
/// allocation, leaving possible allocator metadata in its boundary pages
|
||||
/// untouched.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// `data..data + size` must describe a live allocation.
|
||||
#[cfg(any(target_os = "android", target_os = "linux"))]
|
||||
pub unsafe fn madvise_hugepage_interior(data: *const u8, size: usize) {
|
||||
let page_size = unsafe { libc::sysconf(libc::_SC_PAGESIZE) };
|
||||
if size == 0 || page_size <= 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
let page_size = page_size as usize;
|
||||
let start = data.addr();
|
||||
let Some(end) = start.checked_add(size) else {
|
||||
return;
|
||||
};
|
||||
let Some(rounded) = start.checked_add(page_size - 1) else {
|
||||
return;
|
||||
};
|
||||
let first_page = rounded / page_size * page_size;
|
||||
let last_page = end / page_size * page_size;
|
||||
if first_page < last_page {
|
||||
// SAFETY: these complete pages lie strictly inside the caller's
|
||||
// allocation.
|
||||
unsafe { madvise_hugepage(data.with_addr(first_page).cast_mut(), last_page - first_page) };
|
||||
}
|
||||
}
|
||||
|
||||
/// No-op on targets that do not support Linux's `MADV_HUGEPAGE` advice.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Kept identical to the supported-target signature.
|
||||
#[cfg(not(any(target_os = "android", target_os = "linux")))]
|
||||
pub unsafe fn madvise_hugepage_interior(_data: *const u8, _size: usize) {}
|
||||
|
||||
/// Leaks a value for the rest of the process's lifetime.
|
||||
///
|
||||
/// Input files, symbol names and a few other objects must outlive every
|
||||
/// data structure of a link, and the process exits as soon as the link is
|
||||
/// done, so never freeing them is both simplest and cheapest.
|
||||
pub fn leak<T>(value: T) -> &'static T {
|
||||
Box::leak(Box::new(value))
|
||||
}
|
||||
|
||||
/// Leaks a byte string for the rest of the process's lifetime.
|
||||
pub fn leak_bytes(bytes: Vec<u8>) -> &'static [u8] {
|
||||
Vec::leak(bytes)
|
||||
}
|
||||
@@ -1,574 +0,0 @@
|
||||
//! The output file and helpers for writing to it from many threads.
|
||||
|
||||
use std::fs::{File, OpenOptions};
|
||||
use std::io::{self, Write};
|
||||
use std::ops::Range;
|
||||
#[cfg(not(windows))]
|
||||
use std::os::unix::fs::{OpenOptionsExt, PermissionsExt};
|
||||
#[cfg(not(windows))]
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use std::path::{Path, PathBuf};
|
||||
#[cfg(windows)]
|
||||
use std::sync::Mutex;
|
||||
#[cfg(not(windows))]
|
||||
use std::sync::atomic::{AtomicPtr, AtomicUsize, Ordering};
|
||||
|
||||
use memmap2::MmapMut;
|
||||
#[cfg(not(windows))]
|
||||
use memmap2::MmapOptions;
|
||||
|
||||
use crate::error::strerror;
|
||||
use crate::fatal;
|
||||
|
||||
/// The temporary file being written, removed on a fatal error.
|
||||
#[cfg(windows)]
|
||||
static TMPFILE: Mutex<Option<PathBuf>> = Mutex::new(None);
|
||||
#[cfg(not(windows))]
|
||||
static TMPFILE: AtomicPtr<libc::c_char> = AtomicPtr::new(std::ptr::null_mut());
|
||||
|
||||
/// Registers the file that cleanup() removes. The Mach-O linker doesn't
|
||||
/// use OutputFile but registers its output, which it writes in place.
|
||||
pub fn set_tmpfile(path: Option<&Path>) {
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
// Published paths live until process exit: a signal handler on another
|
||||
// thread may still be using the old pointer when this registration
|
||||
// changes.
|
||||
let ptr = path.map_or(std::ptr::null_mut(), |path| {
|
||||
std::ffi::CString::new(path.as_os_str().as_encoded_bytes())
|
||||
.expect("temporary path contains NUL")
|
||||
.into_raw()
|
||||
});
|
||||
TMPFILE.store(ptr, Ordering::Release);
|
||||
}
|
||||
#[cfg(windows)]
|
||||
{
|
||||
*TMPFILE.lock().unwrap() = path.map(Path::to_path_buf);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
static OUTPUT_BUFFER_START: AtomicUsize = AtomicUsize::new(0);
|
||||
#[cfg(not(windows))]
|
||||
static OUTPUT_BUFFER_END: AtomicUsize = AtomicUsize::new(0);
|
||||
|
||||
#[cfg(not(windows))]
|
||||
fn set_output_buffer_range(start: usize, len: usize) {
|
||||
OUTPUT_BUFFER_END.store(0, Ordering::SeqCst);
|
||||
OUTPUT_BUFFER_START.store(start, Ordering::SeqCst);
|
||||
OUTPUT_BUFFER_END.store(start + len, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
pub fn output_buffer_contains(addr: usize) -> bool {
|
||||
let start = OUTPUT_BUFFER_START.load(Ordering::SeqCst);
|
||||
let end = OUTPUT_BUFFER_END.load(Ordering::SeqCst);
|
||||
start != 0 && start <= addr && addr < end
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
fn open_options(mode: u32) -> OpenOptions {
|
||||
let mut options = OpenOptions::new();
|
||||
options.mode(mode);
|
||||
options
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn open_options(_mode: u32) -> OpenOptions {
|
||||
OpenOptions::new()
|
||||
}
|
||||
|
||||
fn set_permissions(file: &File, perm: u32) -> io::Result<()> {
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
file.set_permissions(std::fs::Permissions::from_mode(perm & !umask()))
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
{
|
||||
let mut permissions = file.metadata()?.permissions();
|
||||
permissions.set_readonly(perm & 0o200 == 0);
|
||||
file.set_permissions(permissions)
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes a partially written output file.
|
||||
pub fn cleanup() {
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
let path = TMPFILE.swap(std::ptr::null_mut(), Ordering::AcqRel);
|
||||
if !path.is_null() {
|
||||
// SAFETY: path is a published, NUL-terminated string that is never
|
||||
// freed. This function is also called from a signal handler, so it
|
||||
// must not lock, allocate or drop owned storage. unlink is
|
||||
// signal-safe.
|
||||
unsafe { libc::unlink(path) };
|
||||
}
|
||||
}
|
||||
#[cfg(windows)]
|
||||
if let Ok(mut guard) = TMPFILE.lock() {
|
||||
if let Some(path) = guard.take() {
|
||||
let _ = std::fs::remove_file(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum Storage {
|
||||
File {
|
||||
file: File,
|
||||
// Empty files and a locked file awaiting resize have no mapping.
|
||||
// A mapping may reserve more address space than the file's `len`.
|
||||
map: Option<MmapMut>,
|
||||
len: usize,
|
||||
},
|
||||
Memory(Vec<u8>),
|
||||
}
|
||||
|
||||
/// An output file, either memory-mapped or buffered in memory.
|
||||
pub struct OutputFile {
|
||||
path: PathBuf,
|
||||
tmp_path: Option<PathBuf>,
|
||||
storage: Storage,
|
||||
perm: u32,
|
||||
}
|
||||
|
||||
/// Calling fallocate speeds up later linking passes on ext4 by taking disk
|
||||
/// block allocation out of the page-fault handler. On tmpfs, it instead
|
||||
/// makes things much slower: the kernel allocates and zeroes every page of
|
||||
/// the range inside the syscall, on one thread, which takes ~1.8 s for a
|
||||
/// 5 GiB file.
|
||||
#[cfg(any(target_os = "android", target_os = "linux"))]
|
||||
fn preallocate(file: &File, offset: u64, size: u64) {
|
||||
// SAFETY: fstatfs and fallocate only inspect and act on a valid open
|
||||
// descriptor; the statfs buffer is fully written before it is read.
|
||||
unsafe {
|
||||
let mut fs: libc::statfs = std::mem::zeroed();
|
||||
if libc::fstatfs(file.as_raw_fd(), &raw mut fs) != 0 || fs.f_type != libc::TMPFS_MAGIC as _
|
||||
{
|
||||
libc::fallocate(file.as_raw_fd(), 0, offset as libc::off_t, size as libc::off_t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "linux")))]
|
||||
fn preallocate(_file: &File, _offset: u64, _size: u64) {}
|
||||
|
||||
/// Returns the length of the buffer that holds a file of the given size.
|
||||
/// No Rust object can be larger than isize::MAX bytes, which limits the
|
||||
/// output to 2 GiB on 32-bit hosts.
|
||||
fn buffer_len(path: &Path, size: u64) -> usize {
|
||||
if isize::try_from(size).is_err() {
|
||||
fatal!("{}: output file is too large for this host: {size} bytes", path.display());
|
||||
}
|
||||
size as usize
|
||||
}
|
||||
|
||||
fn map_file(file: &File, len: usize) -> io::Result<Option<MmapMut>> {
|
||||
if len == 0 {
|
||||
return Ok(None);
|
||||
}
|
||||
// We map the file with twice as much address space as its size, so
|
||||
// that extend() can grow the file into the mapping in place. Touching
|
||||
// the mapping beyond the end of the file is not allowed, but growing
|
||||
// the file with ftruncate makes the tail of the mapping accessible
|
||||
// without any further mmap call.
|
||||
//
|
||||
// SAFETY: the file is private to this process until it's closed.
|
||||
#[cfg(not(windows))]
|
||||
let map = unsafe { MmapOptions::new().len(len * 2).map_mut(file) }
|
||||
// If the address space is too tight, map just the file.
|
||||
.or_else(|_| unsafe { MmapMut::map_mut(file) });
|
||||
#[cfg(windows)]
|
||||
let map = unsafe { MmapMut::map_mut(file) };
|
||||
let mut map = map?;
|
||||
// Enable transparent huge pages for a memory-mapped output file
|
||||
// when the target provides the required advice.
|
||||
// Linking a Chromium debug build is ~20% faster with this madvise call.
|
||||
//
|
||||
// Without this, every 4 KiB page of the output takes its own
|
||||
// page fault when it is first written, and the faults of the
|
||||
// many copying threads serialize on the file's page cache. With
|
||||
// it, the kernel backs the mapping with large folios and the
|
||||
// number of faults drops by an order of magnitude.
|
||||
// SAFETY: the range is the mapping; the advice is only a hint.
|
||||
unsafe { crate::mem::madvise_hugepage(map.as_mut_ptr(), map.len()) };
|
||||
Ok(Some(map))
|
||||
}
|
||||
|
||||
impl OutputFile {
|
||||
#[cfg(not(windows))]
|
||||
fn publish_output_buffer(&self) {
|
||||
match &self.storage {
|
||||
Storage::File { map: Some(map), len, .. } => {
|
||||
set_output_buffer_range(map.as_ptr() as usize, *len);
|
||||
}
|
||||
_ => set_output_buffer_range(0, 0),
|
||||
}
|
||||
}
|
||||
|
||||
/// Opens an output file of the given size.
|
||||
///
|
||||
/// A regular file is written through a memory mapping of a temporary
|
||||
/// file that is renamed into place on close, so that a failed link
|
||||
/// doesn't leave a truncated output behind and a running executable
|
||||
/// isn't modified underneath the kernel. Anything else, such as a
|
||||
/// device, a pipe or standard output, is assembled in memory and
|
||||
/// written out at the end. If `mmap` is false, a regular file is
|
||||
/// assembled in memory too and then written out to the temporary file.
|
||||
pub fn open(path: &Path, size: u64, perm: u32, overwrite_in_place: bool, mmap: bool) -> Self {
|
||||
let len = buffer_len(path, size);
|
||||
let is_special =
|
||||
path == Path::new("-") || std::fs::metadata(path).is_ok_and(|m| !m.is_file());
|
||||
if is_special {
|
||||
return Self {
|
||||
path: path.to_path_buf(),
|
||||
tmp_path: None,
|
||||
storage: Storage::Memory(vec![0; len]),
|
||||
perm,
|
||||
};
|
||||
}
|
||||
|
||||
let dir = Path::new(path)
|
||||
.parent()
|
||||
.filter(|p| !p.as_os_str().is_empty())
|
||||
.unwrap_or(Path::new("."));
|
||||
let mut name = std::ffi::OsString::from(".");
|
||||
name.push(path.file_name().unwrap_or_default());
|
||||
name.push(format!(".{}", std::process::id()));
|
||||
let tmp = dir.join(name);
|
||||
|
||||
if !mmap {
|
||||
return Self {
|
||||
path: path.to_path_buf(),
|
||||
tmp_path: Some(tmp),
|
||||
storage: Storage::Memory(vec![0; len]),
|
||||
perm,
|
||||
};
|
||||
}
|
||||
|
||||
// Reuse an existing file if it exists and is writable because on
|
||||
// Linux, writing to an existing file is much faster than creating a
|
||||
// fresh file and writing to it. A file mapped as an input is not reused, as
|
||||
// the linker is still reading it.
|
||||
let reuse_existing = || -> Option<File> {
|
||||
if !overwrite_in_place
|
||||
|| crate::mapped_file::is_mmapped(path)
|
||||
|| std::fs::rename(path, &tmp).is_err()
|
||||
{
|
||||
return None;
|
||||
}
|
||||
match open_options(perm).read(true).write(true).create(true).open(&tmp) {
|
||||
Ok(file) => Some(file),
|
||||
Err(_) => {
|
||||
let _ = std::fs::remove_file(&tmp);
|
||||
None
|
||||
}
|
||||
}
|
||||
};
|
||||
let file = reuse_existing().unwrap_or_else(|| {
|
||||
open_options(perm)
|
||||
.read(true)
|
||||
.write(true)
|
||||
.create(true)
|
||||
.truncate(true)
|
||||
.open(&tmp)
|
||||
.unwrap_or_else(|e| fatal!("cannot open {}: {}", tmp.display(), strerror(&e)))
|
||||
});
|
||||
set_tmpfile(Some(&tmp));
|
||||
|
||||
set_permissions(&file, perm)
|
||||
.unwrap_or_else(|e| fatal!("{}: fchmod failed: {}", tmp.display(), strerror(&e)));
|
||||
file.set_len(size)
|
||||
.unwrap_or_else(|e| fatal!("{}: ftruncate failed: {}", tmp.display(), strerror(&e)));
|
||||
preallocate(&file, 0, size);
|
||||
|
||||
let map = map_file(&file, len)
|
||||
.unwrap_or_else(|e| fatal!("{}: mmap failed: {}", path.display(), strerror(&e)));
|
||||
let output = Self {
|
||||
path: path.to_path_buf(),
|
||||
tmp_path: Some(tmp),
|
||||
storage: Storage::File { file, map, len },
|
||||
perm,
|
||||
};
|
||||
#[cfg(not(windows))]
|
||||
output.publish_output_buffer();
|
||||
output
|
||||
}
|
||||
|
||||
/// Opens a file that is written in place rather than renamed and holds an
|
||||
/// exclusive `flock`. This is used for a separate debug file that a
|
||||
/// debugger may wait on. The file is made unusable right away so that a
|
||||
/// stale one is not picked up by accident; [`Self::resize`] gives it its
|
||||
/// size.
|
||||
#[cfg(not(windows))]
|
||||
pub fn open_locked(path: &Path, perm: u32) -> Self {
|
||||
let mut file = open_options(perm)
|
||||
.read(true)
|
||||
.write(true)
|
||||
.create(true)
|
||||
.open(path)
|
||||
.unwrap_or_else(|e| fatal!("cannot open {}: {}", path.display(), strerror(&e)));
|
||||
// SAFETY: flock on a valid descriptor.
|
||||
unsafe {
|
||||
libc::flock(file.as_raw_fd(), libc::LOCK_EX);
|
||||
}
|
||||
// We may be overwriting an existing debug info file. We want to
|
||||
// make the file unusable so that gdb won't use it by accident until
|
||||
// it's ready.
|
||||
file.write_all(&[0; 256])
|
||||
.unwrap_or_else(|e| fatal!("{}: write failed: {}", path.display(), strerror(&e)));
|
||||
Self {
|
||||
path: path.to_path_buf(),
|
||||
tmp_path: None,
|
||||
storage: Storage::File { file, map: None, len: 0 },
|
||||
perm,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
pub fn open_locked(_path: &Path, _perm: u32) -> OutputFile {
|
||||
fatal!("LockingOutputFile is not supported on Windows");
|
||||
}
|
||||
|
||||
/// Sets the size of a file opened with [`Self::open_locked`].
|
||||
pub fn resize(&mut self, size: u64) {
|
||||
let new_len = buffer_len(&self.path, size);
|
||||
let Storage::File { file, map, len } = &mut self.storage else {
|
||||
panic!("resizing an output file that isn't a file");
|
||||
};
|
||||
file.set_len(size).unwrap_or_else(|e| {
|
||||
fatal!("{}: ftruncate failed: {}", self.path.display(), strerror(&e))
|
||||
});
|
||||
*map = map_file(file, new_len)
|
||||
.unwrap_or_else(|e| fatal!("{}: mmap failed: {}", self.path.display(), strerror(&e)));
|
||||
*len = new_len;
|
||||
#[cfg(not(windows))]
|
||||
self.publish_output_buffer();
|
||||
}
|
||||
|
||||
/// Whether the buffer is a mapping of the file rather than memory.
|
||||
pub fn is_mmapped(&self) -> bool {
|
||||
matches!(self.storage, Storage::File { map: Some(_), .. })
|
||||
}
|
||||
|
||||
pub fn buf(&mut self) -> &mut [u8] {
|
||||
match &mut self.storage {
|
||||
Storage::File { map: Some(map), len, .. } => &mut map[..*len],
|
||||
Storage::File { map: None, .. } => &mut [],
|
||||
Storage::Memory(vec) => &mut vec[..],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
match &self.storage {
|
||||
Storage::File { len, .. } => *len,
|
||||
Storage::Memory(vec) => vec.len(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len() == 0
|
||||
}
|
||||
|
||||
// Extend the file so the caller can fill the appended data through the tail
|
||||
// of the mapping. This is called at most once per output file, for
|
||||
// .gdb_index, whose size is not known until the other sections have been
|
||||
// written. The new space is zero-initialized, and the output buffer may
|
||||
// move.
|
||||
pub fn extend(&mut self, size: usize) {
|
||||
let new_len = buffer_len(&self.path, self.len() as u64 + size as u64);
|
||||
match &mut self.storage {
|
||||
Storage::Memory(vec) => vec.resize(new_len, 0),
|
||||
Storage::File { file, map, len } => {
|
||||
file.set_len(new_len as u64).unwrap_or_else(|e| {
|
||||
fatal!("{}: ftruncate failed: {}", self.path.display(), strerror(&e))
|
||||
});
|
||||
// Allocate only the appended range. Reallocating the already
|
||||
// written prefix can flush dirty extents on filesystems such
|
||||
// as btrfs and serialize a large part of .gdb_index output.
|
||||
preallocate(file, *len as u64, size as u64);
|
||||
if map.as_ref().is_none_or(|map| new_len > map.len()) {
|
||||
// The appended data does not fit in the existing mapping, so map
|
||||
// the grown file again. Release the old mapping first, as the
|
||||
// address space may be too tight for both. The data written
|
||||
// through it stays in the file.
|
||||
*map = None;
|
||||
*map = map_file(file, new_len).unwrap_or_else(|e| {
|
||||
fatal!("{}: mmap failed: {}", self.path.display(), strerror(&e))
|
||||
});
|
||||
}
|
||||
*len = new_len;
|
||||
}
|
||||
}
|
||||
#[cfg(not(windows))]
|
||||
self.publish_output_buffer();
|
||||
}
|
||||
|
||||
/// Finishes writing and moves the file into place. The mapping is
|
||||
/// released without waiting for the data to reach the disk.
|
||||
pub fn close(self) {
|
||||
#[cfg(not(windows))]
|
||||
set_output_buffer_range(0, 0);
|
||||
// Hold the file (and any lock) through publication of the output.
|
||||
let _file = match self.storage {
|
||||
Storage::File { file, map, .. } => {
|
||||
drop(map);
|
||||
Some(file)
|
||||
}
|
||||
Storage::Memory(vec) => {
|
||||
if self.path == Path::new("-") {
|
||||
let mut stdout = std::io::stdout().lock();
|
||||
stdout
|
||||
.write_all(&vec)
|
||||
.and_then(|()| stdout.flush())
|
||||
.unwrap_or_else(|e| fatal!("write failed: {}", strerror(&e)));
|
||||
// Close the descriptor too: the parent process may
|
||||
// already have exited, and a program that then runs
|
||||
// the output would fail with ETXTBSY while this
|
||||
// process still holds it open for writing.
|
||||
// SAFETY: nothing else writes to stdout after this.
|
||||
#[cfg(not(windows))]
|
||||
unsafe {
|
||||
libc::close(libc::STDOUT_FILENO);
|
||||
}
|
||||
return;
|
||||
}
|
||||
// A regular file is written to the temporary file, which is
|
||||
// renamed below.
|
||||
let dest = self.tmp_path.as_deref().unwrap_or(&self.path);
|
||||
set_tmpfile(self.tmp_path.as_deref());
|
||||
let mut file = open_options(self.perm)
|
||||
.write(true)
|
||||
.create(true)
|
||||
.truncate(true)
|
||||
.open(dest)
|
||||
.unwrap_or_else(|e| fatal!("cannot open {}: {}", dest.display(), strerror(&e)));
|
||||
file.write_all(&vec).unwrap_or_else(|e| {
|
||||
fatal!("{}: write failed: {}", dest.display(), strerror(&e))
|
||||
});
|
||||
None
|
||||
}
|
||||
};
|
||||
if let Some(tmp) = self.tmp_path {
|
||||
// If an output file already exists, open it and then remove it.
|
||||
// This is the fastest way to unlink a file, as it does not make the
|
||||
// system immediately release disk blocks occupied by the file.
|
||||
// The descriptor is kept until the process exits.
|
||||
if let Ok(old) = File::open(&self.path) {
|
||||
let _ = std::fs::remove_file(&self.path);
|
||||
std::mem::forget(old);
|
||||
}
|
||||
std::fs::rename(&tmp, &self.path).unwrap_or_else(|e| {
|
||||
fatal!(
|
||||
"cannot rename {} to {}: {}",
|
||||
tmp.display(),
|
||||
self.path.display(),
|
||||
strerror(&e)
|
||||
)
|
||||
});
|
||||
set_tmpfile(None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
fn umask() -> u32 {
|
||||
// SAFETY: umask is thread-safe in the sense that it just swaps a
|
||||
// process-wide value; restoring it immediately keeps it unchanged.
|
||||
unsafe {
|
||||
let mask = libc::umask(0);
|
||||
libc::umask(mask);
|
||||
mask as u32
|
||||
}
|
||||
}
|
||||
|
||||
/// Splits a buffer into consecutive slices at the given ascending
|
||||
/// offsets. The first slice starts at `offsets[0]`; bytes before it are
|
||||
/// not returned. The last slice extends to the end of the buffer.
|
||||
pub fn split_at_offsets<'a, T>(buf: &'a mut [T], offsets: &[u64]) -> Vec<&'a mut [T]> {
|
||||
let mut slices = Vec::with_capacity(offsets.len());
|
||||
let mut rest = buf;
|
||||
let mut pos = 0usize;
|
||||
for (i, &start) in offsets.iter().enumerate() {
|
||||
let start = start as usize;
|
||||
let end = offsets.get(i + 1).map_or(rest.len() + pos, |&e| e as usize);
|
||||
debug_assert!(pos <= start && start <= end);
|
||||
rest.split_off_mut(..start - pos).unwrap();
|
||||
let slice = rest.split_off_mut(..end - start).unwrap();
|
||||
slices.push(slice);
|
||||
|
||||
pos = end;
|
||||
}
|
||||
slices
|
||||
}
|
||||
|
||||
/// Borrows several disjoint ranges of a buffer mutably at once.
|
||||
pub fn split_ranges<'a>(buf: &'a mut [u8], ranges: &[Range<u64>]) -> Vec<&'a mut [u8]> {
|
||||
let mut order: Vec<usize> = (0..ranges.len()).collect();
|
||||
order.sort_by_key(|&i| ranges[i].start);
|
||||
|
||||
let mut result: Vec<Option<&'a mut [u8]>> = (0..ranges.len()).map(|_| None).collect();
|
||||
let mut rest = buf;
|
||||
let mut pos = 0u64;
|
||||
for i in order {
|
||||
let r = &ranges[i];
|
||||
assert!(r.start >= pos, "overlapping output ranges");
|
||||
rest.split_off_mut(..(r.start - pos) as usize).unwrap();
|
||||
let slice = rest.split_off_mut(..(r.end - r.start) as usize).unwrap();
|
||||
result[i] = Some(slice);
|
||||
|
||||
pos = r.end;
|
||||
}
|
||||
result.into_iter().map(|s| s.unwrap()).collect()
|
||||
}
|
||||
|
||||
#[cfg(all(test, not(windows)))]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn tracks_output_buffer_addresses() {
|
||||
set_output_buffer_range(0x1000, 0x100);
|
||||
assert!(!output_buffer_contains(0x0fff));
|
||||
assert!(output_buffer_contains(0x1000));
|
||||
assert!(output_buffer_contains(0x10ff));
|
||||
assert!(!output_buffer_contains(0x1100));
|
||||
set_output_buffer_range(0, 0);
|
||||
|
||||
let path = std::env::temp_dir().join(format!("mold-output-{}", std::process::id()));
|
||||
let mut file = OutputFile::open(&path, 8192, 0o600, true, true);
|
||||
let start = file.buf().as_ptr() as usize;
|
||||
assert!(output_buffer_contains(start));
|
||||
assert!(output_buffer_contains(start + 8191));
|
||||
assert!(!output_buffer_contains(start + 8192));
|
||||
file.close();
|
||||
assert!(!output_buffer_contains(start));
|
||||
|
||||
// Cover empty regular files, remapping on growth, buffering in
|
||||
// memory, and the locked staging state used by separate debug
|
||||
// output. Keep this in the same test because output publication
|
||||
// uses process-global state.
|
||||
for mmap in [true, false] {
|
||||
for initial in [0, 8] {
|
||||
let mut file = OutputFile::open(&path, initial, 0o600, true, mmap);
|
||||
file.buf().fill(7);
|
||||
file.extend(65536);
|
||||
assert!(file.buf()[..initial as usize].iter().all(|&b| b == 7));
|
||||
assert!(file.buf()[initial as usize..].iter().all(|&b| b == 0));
|
||||
file.close();
|
||||
let contents = std::fs::read(&path).unwrap();
|
||||
assert_eq!(contents.len() as u64, initial + 65536);
|
||||
assert!(contents[..initial as usize].iter().all(|&b| b == 7));
|
||||
}
|
||||
}
|
||||
let mut file = OutputFile::open_locked(&path, 0o600);
|
||||
assert!(file.buf().is_empty());
|
||||
file.resize(0);
|
||||
assert!(!file.is_mmapped());
|
||||
file.resize(4);
|
||||
file.buf().copy_from_slice(b"test");
|
||||
file.close();
|
||||
assert_eq!(std::fs::read(&path).unwrap(), b"test");
|
||||
std::fs::remove_file(path).unwrap();
|
||||
}
|
||||
}
|
||||
@@ -1,190 +0,0 @@
|
||||
use rayon::prelude::*;
|
||||
|
||||
/// An owned Rayon job whose result is needed by a later linker pass.
|
||||
pub struct Background<T> {
|
||||
receiver: std::sync::mpsc::Receiver<T>,
|
||||
name: &'static str,
|
||||
}
|
||||
|
||||
impl<T: Send + 'static> Background<T> {
|
||||
pub fn spawn(name: &'static str, run: impl FnOnce() -> T + Send + 'static) -> Self {
|
||||
let (sender, receiver) = std::sync::mpsc::sync_channel(1);
|
||||
rayon::spawn(move || {
|
||||
let _ = sender.send(run());
|
||||
});
|
||||
Self { receiver, name }
|
||||
}
|
||||
|
||||
pub fn join(self) -> T {
|
||||
use std::sync::mpsc::TryRecvError;
|
||||
loop {
|
||||
match self.receiver.try_recv() {
|
||||
Ok(value) => return value,
|
||||
Err(TryRecvError::Disconnected) => panic!("{} task failed", self.name),
|
||||
Err(TryRecvError::Empty) => {
|
||||
// A blocking receive would deadlock a one-worker pool.
|
||||
if !matches!(rayon::yield_now(), Some(rayon::Yield::Executed)) {
|
||||
std::thread::yield_now();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Stably partitions a slice, returning the number of matching elements.
|
||||
/// Each block scatters into disjoint ranges computed from its match count.
|
||||
pub fn stable_partition<T: Copy + Send + Sync>(
|
||||
values: &mut [T],
|
||||
pred: impl Fn(&T) -> bool + Sync,
|
||||
) -> usize {
|
||||
const BLOCK: usize = 16384;
|
||||
let matches: Vec<bool> = if values.len() <= BLOCK {
|
||||
values.iter().map(&pred).collect()
|
||||
} else {
|
||||
values.par_iter().map(&pred).collect()
|
||||
};
|
||||
let counts: Vec<usize> =
|
||||
matches.par_chunks(BLOCK).map(|chunk| chunk.iter().filter(|&&v| v).count()).collect();
|
||||
let num_matches = counts.iter().sum();
|
||||
if num_matches == 0 || num_matches == values.len() {
|
||||
return num_matches;
|
||||
}
|
||||
|
||||
let mut output = values.to_vec();
|
||||
let (mut yes, mut no) = output.split_at_mut(num_matches);
|
||||
let parts: Vec<_> = counts
|
||||
.iter()
|
||||
.zip(matches.chunks(BLOCK))
|
||||
.map(|(&count, flags)| {
|
||||
let matched = yes.split_off_mut(..count).unwrap();
|
||||
|
||||
let unmatched = no.split_off_mut(..flags.len() - count).unwrap();
|
||||
|
||||
(matched, unmatched)
|
||||
})
|
||||
.collect();
|
||||
parts.into_par_iter().zip(values.par_chunks(BLOCK)).zip(matches.par_chunks(BLOCK)).for_each(
|
||||
|(((yes, no), input), flags)| {
|
||||
let (mut y, mut n) = (0, 0);
|
||||
for (&value, &matched) in input.iter().zip(flags) {
|
||||
if matched {
|
||||
yes[y] = value;
|
||||
y += 1;
|
||||
} else {
|
||||
no[n] = value;
|
||||
n += 1;
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
values
|
||||
.par_chunks_mut(BLOCK)
|
||||
.zip(output.par_chunks(BLOCK))
|
||||
.for_each(|(dst, src)| dst.copy_from_slice(src));
|
||||
num_matches
|
||||
}
|
||||
|
||||
/// An `UnsafeCell` that may be shared between threads, like std's unstable
|
||||
/// type of the same name. A parallel pass views a slice as cells so that
|
||||
/// each task can write the elements it owns through a shared reference,
|
||||
/// with the slice's bounds checks still applied to every access.
|
||||
#[repr(transparent)]
|
||||
pub struct SyncUnsafeCell<T>(std::cell::UnsafeCell<T>);
|
||||
|
||||
// SAFETY: the unsafe methods below require callers to keep concurrent
|
||||
// accesses to one cell disjoint, which is what `Sync` promises.
|
||||
unsafe impl<T: Send + Sync> Sync for SyncUnsafeCell<T> {}
|
||||
|
||||
impl<T> SyncUnsafeCell<T> {
|
||||
pub fn new(value: T) -> Self {
|
||||
Self(std::cell::UnsafeCell::new(value))
|
||||
}
|
||||
|
||||
/// Views an exclusively borrowed slice as shared cells.
|
||||
pub fn from_mut(slice: &mut [T]) -> &[Self] {
|
||||
// SAFETY: a cell has the same layout as its content, and the
|
||||
// exclusive borrow of the slice is given up for the shared view.
|
||||
unsafe { &*(std::ptr::from_mut(slice) as *const [Self]) }
|
||||
}
|
||||
|
||||
/// A raw pointer to the content.
|
||||
pub fn get(&self) -> *mut T {
|
||||
self.0.get()
|
||||
}
|
||||
|
||||
pub fn get_mut(&mut self) -> &mut T {
|
||||
self.0.get_mut()
|
||||
}
|
||||
|
||||
/// Views cells as their contents.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// No cell in `cells` may be written while the returned slice is alive.
|
||||
pub(crate) unsafe fn as_slice(cells: &[Self]) -> &[T] {
|
||||
// SAFETY: a cell has the same layout as its content; the caller
|
||||
// guarantees that the contents do not change.
|
||||
unsafe { &*(std::ptr::from_ref(cells) as *const [T]) }
|
||||
}
|
||||
|
||||
/// Views cells as their exclusively borrowed contents.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// No cell in `cells` may be accessed otherwise while the returned slice
|
||||
/// is alive.
|
||||
#[allow(clippy::mut_from_ref)]
|
||||
pub unsafe fn as_mut_slice(cells: &[Self]) -> &mut [T] {
|
||||
let ptr = std::cell::UnsafeCell::raw_get(cells.as_ptr().cast());
|
||||
// SAFETY: a cell has the same layout as its content; the caller
|
||||
// guarantees exclusive access.
|
||||
unsafe { std::slice::from_raw_parts_mut(ptr, cells.len()) }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> std::fmt::Debug for SyncUnsafeCell<T> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("SyncUnsafeCell").finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets flag bits without taking exclusive ownership of a cache line when
|
||||
/// all requested bits are already set. Callers needing the previous value
|
||||
/// must use `fetch_or` directly.
|
||||
#[inline]
|
||||
pub fn atomic_or(atomic: &std::sync::atomic::AtomicU8, bits: u8) {
|
||||
use std::sync::atomic::Ordering::Relaxed;
|
||||
if atomic.load(Relaxed) & bits != bits {
|
||||
atomic.fetch_or(bits, Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn background_job_joins_on_one_worker() {
|
||||
rayon::ThreadPoolBuilder::new().num_threads(1).build().unwrap().install(|| {
|
||||
let job = Background::spawn("test", || (0..100usize).into_par_iter().sum::<usize>());
|
||||
assert_eq!(job.join(), 4950);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn preserves_both_groups_across_blocks() {
|
||||
for len in [0, 1, 16383, 16384, 16385, 65539] {
|
||||
for divisor in [1, 2, 7, 100000] {
|
||||
let mut values: Vec<_> = (0..len).collect();
|
||||
let (yes, no): (Vec<_>, Vec<_>) =
|
||||
values.iter().copied().partition(|v| v % divisor == 0);
|
||||
let count = stable_partition(&mut values, |v| v % divisor == 0);
|
||||
assert_eq!(count, yes.len());
|
||||
assert_eq!(&values[..count], yes);
|
||||
assert_eq!(&values[count..], no);
|
||||
assert_eq!(stable_partition(&mut values, |_| false), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
//! Lexical path manipulation, which doesn't consult the file system.
|
||||
|
||||
/// Normalizes a path lexically, resolving `.` and `..` components without
|
||||
/// consulting the file system.
|
||||
pub fn path_clean(path: &str) -> String {
|
||||
clean_path(std::path::Path::new(path)).to_string_lossy().into_owned()
|
||||
}
|
||||
|
||||
/// The bytes of a path, as the file system and Mach-O load commands
|
||||
/// hold them.
|
||||
pub fn path_bytes(path: &std::path::Path) -> &[u8] {
|
||||
path.as_os_str().as_encoded_bytes()
|
||||
}
|
||||
|
||||
/// Normalizes an OS path without resolving symlinks.
|
||||
pub fn clean_path(path: &std::path::Path) -> std::path::PathBuf {
|
||||
use std::path::{Component, PathBuf};
|
||||
let mut out = PathBuf::new();
|
||||
for component in path.components() {
|
||||
match component {
|
||||
Component::CurDir => {}
|
||||
Component::ParentDir => match out.components().next_back() {
|
||||
Some(Component::RootDir) => {}
|
||||
None | Some(Component::ParentDir) => out.push(".."),
|
||||
_ => {
|
||||
out.pop();
|
||||
}
|
||||
},
|
||||
other => out.push(other),
|
||||
}
|
||||
}
|
||||
if out.as_os_str().is_empty() { PathBuf::from(".") } else { out }
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn clean_paths() {
|
||||
assert_eq!(path_clean("a/./b/../c"), "a/c");
|
||||
assert_eq!(path_clean("/a/../.."), "/");
|
||||
assert_eq!(path_clean("../a"), "../a");
|
||||
assert_eq!(path_clean("../../a/b"), "../../a/b");
|
||||
assert_eq!(path_clean("a/../../b"), "../b");
|
||||
assert_eq!(path_clean("a/b/../../../c"), "../c");
|
||||
assert_eq!(path_clean(".."), "..");
|
||||
assert_eq!(path_clean("/.."), "/");
|
||||
}
|
||||
}
|
||||
-347
@@ -1,347 +0,0 @@
|
||||
//! Statistics counters and wall-clock and CPU time accounting.
|
||||
|
||||
// Counter is used to collect statistics.
|
||||
|
||||
use std::io::Write;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::{Arc, Mutex, Once};
|
||||
use std::time::Instant;
|
||||
|
||||
use portable_atomic::AtomicI64;
|
||||
|
||||
use crate::out;
|
||||
|
||||
static COUNTERS_ENABLED: AtomicBool = AtomicBool::new(false);
|
||||
static COUNTERS: Mutex<Vec<&'static Counter>> = Mutex::new(Vec::new());
|
||||
|
||||
pub struct Counter {
|
||||
name: &'static str,
|
||||
value: AtomicI64,
|
||||
registered: Once,
|
||||
}
|
||||
|
||||
impl Counter {
|
||||
pub const fn new(name: &'static str) -> Self {
|
||||
Self { name, value: AtomicI64::new(0), registered: Once::new() }
|
||||
}
|
||||
|
||||
pub fn enable() {
|
||||
COUNTERS_ENABLED.store(true, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn increment(&'static self) {
|
||||
self.add(1);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn add(&'static self, delta: i64) {
|
||||
if !COUNTERS_ENABLED.load(Ordering::Relaxed) {
|
||||
return;
|
||||
}
|
||||
self.registered.call_once(|| {
|
||||
COUNTERS.lock().unwrap().push(self);
|
||||
});
|
||||
self.value.fetch_add(delta, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
pub fn print(stats: impl IntoIterator<Item = (&'static str, i64)>) {
|
||||
let mut counters: Vec<_> = COUNTERS
|
||||
.lock()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|counter| (counter.name, counter.value.load(Ordering::Relaxed)))
|
||||
.collect();
|
||||
counters.extend(stats);
|
||||
counters.sort_by_key(|&(_, value)| value);
|
||||
for (name, value) in counters {
|
||||
out!("{:>20}={}", name, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Record {
|
||||
name: String,
|
||||
start: Instant,
|
||||
end: Option<Instant>,
|
||||
user_start: f64,
|
||||
sys_start: f64,
|
||||
user: f64,
|
||||
sys: f64,
|
||||
parent: Option<usize>,
|
||||
children: Vec<usize>,
|
||||
}
|
||||
|
||||
// Infer implicit parents from complete intervals, as C++ mold does. Overlapping
|
||||
// timers need not be nested, and explicitly named parents take precedence.
|
||||
fn nest_records(records: &mut [Record]) {
|
||||
for record in records.iter_mut() {
|
||||
record.children.clear();
|
||||
}
|
||||
for i in 0..records.len() {
|
||||
if records[i].parent.is_none() {
|
||||
let inner = &records[i];
|
||||
records[i].parent = records[..i].iter().rposition(|outer| {
|
||||
outer.start <= inner.start && inner.end.unwrap() <= outer.end.unwrap()
|
||||
});
|
||||
}
|
||||
if let Some(parent) = records[i].parent {
|
||||
records[parent].children.push(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Collects wall-clock and CPU timing records for the passes of a link.
|
||||
/// Cloning shares the underlying records.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Timers {
|
||||
records: Option<Arc<Mutex<Vec<Record>>>>,
|
||||
}
|
||||
|
||||
/// A running timer, stopped when dropped.
|
||||
#[must_use = "a timer records its interval when dropped"]
|
||||
pub struct Timer {
|
||||
timers: Timers,
|
||||
index: usize,
|
||||
stopped: bool,
|
||||
}
|
||||
|
||||
/// A clonable handle for starting timers beneath a running timer.
|
||||
#[derive(Clone)]
|
||||
pub struct TimerHandle {
|
||||
timers: Timers,
|
||||
parent: usize,
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
#[repr(C)]
|
||||
struct FileTime {
|
||||
low: u32,
|
||||
high: u32,
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
#[link(name = "kernel32")]
|
||||
unsafe extern "system" {
|
||||
fn GetCurrentProcess() -> *mut std::ffi::c_void;
|
||||
fn GetProcessTimes(
|
||||
process: *mut std::ffi::c_void,
|
||||
creation: *mut FileTime,
|
||||
exit: *mut FileTime,
|
||||
kernel: *mut FileTime,
|
||||
user: *mut FileTime,
|
||||
) -> i32;
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
fn rusage() -> (f64, f64) {
|
||||
// SAFETY: `usage` is a valid, writable rusage struct.
|
||||
let mut usage: libc::rusage = unsafe { std::mem::zeroed() };
|
||||
unsafe { libc::getrusage(libc::RUSAGE_SELF, &raw mut usage) };
|
||||
let to_secs = |t: libc::timeval| t.tv_sec as f64 + t.tv_usec as f64 / 1_000_000.0;
|
||||
(to_secs(usage.ru_utime), to_secs(usage.ru_stime))
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn rusage() -> (f64, f64) {
|
||||
let mut creation = FileTime { low: 0, high: 0 };
|
||||
let mut exit = FileTime { low: 0, high: 0 };
|
||||
let mut kernel = FileTime { low: 0, high: 0 };
|
||||
let mut user = FileTime { low: 0, high: 0 };
|
||||
// SAFETY: all FILETIME pointers are valid outputs and the pseudo-handle
|
||||
// returned by GetCurrentProcess is always valid in this process.
|
||||
unsafe {
|
||||
GetProcessTimes(GetCurrentProcess(), &mut creation, &mut exit, &mut kernel, &mut user);
|
||||
}
|
||||
let to_secs = |time: FileTime| {
|
||||
let ticks = (u64::from(time.high) << 32) | u64::from(time.low);
|
||||
ticks as f64 / 10_000_000.0
|
||||
};
|
||||
(to_secs(user), to_secs(kernel))
|
||||
}
|
||||
|
||||
impl Default for Timers {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Timers {
|
||||
pub fn new() -> Self {
|
||||
Self { records: Some(Arc::new(Mutex::new(Vec::new()))) }
|
||||
}
|
||||
|
||||
/// Skips clock reads, system calls and shared recording when --perf (or
|
||||
/// the Mach-O linker's -print_statistics) is off.
|
||||
pub fn disabled() -> Self {
|
||||
Self { records: None }
|
||||
}
|
||||
|
||||
fn inactive(&self) -> Timer {
|
||||
Timer { timers: self.clone(), index: 0, stopped: true }
|
||||
}
|
||||
|
||||
/// Starts a timer whose nesting is inferred from completed time intervals.
|
||||
pub fn start(&self, name: &str) -> Timer {
|
||||
self.start_child(name, None)
|
||||
}
|
||||
|
||||
fn start_child(&self, name: impl std::fmt::Display, parent: Option<usize>) -> Timer {
|
||||
let Some(records) = &self.records else {
|
||||
return self.inactive();
|
||||
};
|
||||
let (user, sys) = rusage();
|
||||
let mut records = records.lock().unwrap();
|
||||
let index = records.len();
|
||||
records.push(Record {
|
||||
name: name.to_string(),
|
||||
start: Instant::now(),
|
||||
end: None,
|
||||
user_start: user,
|
||||
sys_start: sys,
|
||||
user: 0.0,
|
||||
sys: 0.0,
|
||||
parent,
|
||||
children: Vec::new(),
|
||||
});
|
||||
Timer { timers: self.clone(), index, stopped: false }
|
||||
}
|
||||
|
||||
fn stop(&self, index: usize) {
|
||||
let (user, sys) = rusage();
|
||||
let mut records = self.records.as_ref().unwrap().lock().unwrap();
|
||||
let record = &mut records[index];
|
||||
if record.end.is_none() {
|
||||
record.end = Some(Instant::now());
|
||||
record.user = user - record.user_start;
|
||||
record.sys = sys - record.sys_start;
|
||||
}
|
||||
}
|
||||
|
||||
/// Prints the timer tree to `out`: stdout for the ELF linker, as C++
|
||||
/// mold does, and stderr for the Mach-O linker, where ld64 reports its
|
||||
/// -print_statistics figures.
|
||||
pub fn print(&self, out: &mut dyn Write) {
|
||||
let Some(records) = &self.records else {
|
||||
return;
|
||||
};
|
||||
let mut records = records.lock().unwrap();
|
||||
let now = Instant::now();
|
||||
for r in records.iter_mut() {
|
||||
if r.end.is_none() {
|
||||
r.end = Some(now);
|
||||
}
|
||||
}
|
||||
nest_records(&mut records);
|
||||
|
||||
fn print_rec(out: &mut dyn Write, records: &[Record], i: usize, indent: usize) {
|
||||
let r = &records[i];
|
||||
let real = r.end.unwrap().duration_since(r.start).as_secs_f64();
|
||||
let _ = writeln!(
|
||||
out,
|
||||
" {:8.3} {:8.3} {:8.3} {}{}",
|
||||
r.user,
|
||||
r.sys,
|
||||
real,
|
||||
" ".repeat(indent * 2),
|
||||
r.name
|
||||
);
|
||||
// nest_records adds children in their recorded start order.
|
||||
for &child in &r.children {
|
||||
print_rec(out, records, child, indent + 1);
|
||||
}
|
||||
}
|
||||
|
||||
let _ = writeln!(out, " User System Real Name");
|
||||
for i in 0..records.len() {
|
||||
if records[i].parent.is_none() {
|
||||
print_rec(out, &records, i, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Timer {
|
||||
/// Starts a timer nested in this one. Timers started from parallel
|
||||
/// tasks name their parent this way, because in parallel code, the
|
||||
/// timer that started last is not necessarily the enclosing one.
|
||||
pub fn child(&self, name: impl std::fmt::Display) -> Self {
|
||||
self.timers.start_child(name, Some(self.index))
|
||||
}
|
||||
|
||||
pub fn handle(&self) -> TimerHandle {
|
||||
TimerHandle { timers: self.timers.clone(), parent: self.index }
|
||||
}
|
||||
|
||||
pub fn stop(&mut self) {
|
||||
if !self.stopped {
|
||||
self.stopped = true;
|
||||
self.timers.stop(self.index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TimerHandle {
|
||||
pub fn child(&self, name: impl std::fmt::Display) -> Timer {
|
||||
self.timers.start_child(name, Some(self.parent))
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Timer {
|
||||
fn drop(&mut self) {
|
||||
self.stop();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn implicit_timer_parents_follow_completed_intervals() {
|
||||
let timers = Timers::new();
|
||||
let root = timers.start("root");
|
||||
let _running = [
|
||||
timers.start("foreground"),
|
||||
timers.start("nested"),
|
||||
timers.start("background"),
|
||||
timers.start("next foreground"),
|
||||
root.child("explicit child"),
|
||||
];
|
||||
let mut records = timers.records.as_ref().unwrap().lock().unwrap();
|
||||
let start = records[0].start;
|
||||
for (record, (begin, end)) in
|
||||
records.iter_mut().zip([(0, 100), (10, 40), (15, 25), (20, 70), (50, 80), (60, 65)])
|
||||
{
|
||||
record.start = start + std::time::Duration::from_millis(begin);
|
||||
record.end = Some(start + std::time::Duration::from_millis(end));
|
||||
}
|
||||
nest_records(&mut records);
|
||||
let children: Vec<_> = records.iter().map(|r| r.children.clone()).collect();
|
||||
drop(records);
|
||||
assert_eq!(children, [vec![1, 3, 4, 5], vec![2], vec![], vec![], vec![], vec![]]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn optional_recording_includes_nested_and_background_timers() {
|
||||
for timers in [Timers::new(), Timers::disabled()] {
|
||||
let mut root = timers.start("root");
|
||||
let child = root.child("child");
|
||||
let handle = root.handle();
|
||||
std::thread::spawn(move || drop(handle.child("background"))).join().unwrap();
|
||||
drop(child);
|
||||
root.stop();
|
||||
root.stop();
|
||||
if let Some(records) = &timers.records {
|
||||
let mut records = records.lock().unwrap();
|
||||
nest_records(&mut records);
|
||||
assert_eq!(records.len(), 3);
|
||||
assert!(records.iter().all(|record| record.end.is_some()));
|
||||
assert_eq!(records[0].children, [1, 2]);
|
||||
} else {
|
||||
assert!(root.timers.records.is_none());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
/// Prefetches a cache line for a future read, preferring the L1 cache.
|
||||
/// Targets without a supported prefetch instruction ignore the hint.
|
||||
#[inline]
|
||||
pub fn prefetch(_ptr: *const u8) {
|
||||
#[cfg(any(all(target_arch = "x86", target_feature = "sse"), target_arch = "x86_64"))]
|
||||
// SAFETY: PREFETCHT0 is an SSE instruction, which x86-64 always has and
|
||||
// the cfg requires on 32-bit x86. It is a non-faulting hint and changes
|
||||
// no registers or memory.
|
||||
unsafe {
|
||||
std::arch::asm!(
|
||||
"prefetcht0 [{ptr}]",
|
||||
ptr = in(reg) _ptr,
|
||||
options(readonly, nostack, preserves_flags),
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(any(target_arch = "aarch64", target_arch = "arm64ec"))]
|
||||
// SAFETY: PRFM is a non-faulting hint and changes no registers or memory.
|
||||
unsafe {
|
||||
std::arch::asm!(
|
||||
"prfm pldl1keep, [{ptr}]",
|
||||
ptr = in(reg) _ptr,
|
||||
options(readonly, nostack, preserves_flags),
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "arm")]
|
||||
// SAFETY: PLD is a non-faulting hint and changes no registers or memory.
|
||||
unsafe {
|
||||
std::arch::asm!(
|
||||
"pld [{ptr}]",
|
||||
ptr = in(reg) _ptr,
|
||||
options(readonly, nostack, preserves_flags),
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))]
|
||||
// SAFETY: PREFETCH.R is an ORI hint, which is a no-op without Zicbop.
|
||||
// Spell it as ORI so the assembler does not require that extension.
|
||||
unsafe {
|
||||
std::arch::asm!(
|
||||
"ori zero, {ptr}, 1",
|
||||
ptr = in(reg) _ptr,
|
||||
options(readonly, nostack, preserves_flags),
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(any(target_arch = "powerpc", target_arch = "powerpc64"))]
|
||||
// SAFETY: DCBT is a non-faulting hint and changes no registers or memory.
|
||||
unsafe {
|
||||
std::arch::asm!(
|
||||
"dcbt 0, {ptr}",
|
||||
ptr = in(reg) _ptr,
|
||||
options(readonly, nostack, preserves_flags),
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "s390x")]
|
||||
// SAFETY: PFD is a non-faulting hint and changes no registers or memory.
|
||||
unsafe {
|
||||
std::arch::asm!(
|
||||
"pfd 1, 0({ptr})",
|
||||
ptr = in(reg_addr) _ptr,
|
||||
options(readonly, nostack, preserves_flags),
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "loongarch64")]
|
||||
// SAFETY: PRELD is a non-faulting hint and changes no registers or memory.
|
||||
unsafe {
|
||||
std::arch::asm!(
|
||||
"preld 0, {ptr}, 0",
|
||||
ptr = in(reg) _ptr,
|
||||
options(readonly, nostack, preserves_flags),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
//! Records of object files, read, written and viewed in their file
|
||||
//! representation. A record's fields are bytes, or integers backed by
|
||||
//! bytes that read and write in the file's byte order, so a record has
|
||||
//! alignment one: one in a possibly unaligned archive member is read in
|
||||
//! place, without depending on the host's byte order.
|
||||
|
||||
/// A record stored in its file representation.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Implementations must have alignment one, contain no padding or references,
|
||||
/// and accept every bit pattern. These requirements let records in possibly
|
||||
/// unaligned archive members be read and written without host dependencies.
|
||||
pub unsafe trait FileRecord: Clone + Copy + Default + Send + Sync + 'static {
|
||||
fn parse(bytes: &[u8]) -> Self {
|
||||
const { assert!(align_of::<Self>() == 1) };
|
||||
assert!(bytes.len() >= size_of::<Self>());
|
||||
// SAFETY: the trait guarantees that every bit pattern is valid, and
|
||||
// the length check proves that a complete record is available.
|
||||
unsafe { bytes.as_ptr().cast::<Self>().read_unaligned() }
|
||||
}
|
||||
|
||||
fn write(&self, buf: &mut [u8]) {
|
||||
assert!(buf.len() >= size_of::<Self>());
|
||||
// SAFETY: `buf` has room for the complete record, and copying bytes
|
||||
// does not depend on its alignment.
|
||||
unsafe {
|
||||
std::ptr::copy_nonoverlapping(
|
||||
std::ptr::from_ref(self).cast::<u8>(),
|
||||
buf.as_mut_ptr(),
|
||||
size_of::<Self>(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// The record's bytes, as a file holds them.
|
||||
fn as_bytes(&self) -> &[u8] {
|
||||
// SAFETY: a record has no padding, so all of its bytes are
|
||||
// initialized.
|
||||
unsafe {
|
||||
std::slice::from_raw_parts(std::ptr::from_ref(self).cast::<u8>(), size_of::<Self>())
|
||||
}
|
||||
}
|
||||
|
||||
fn write_all(records: &[Self], buf: &mut [u8]) {
|
||||
for (record, slot) in records.iter().zip(buf.chunks_exact_mut(size_of::<Self>())) {
|
||||
record.write(slot);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Views one record directly in its file representation.
|
||||
pub fn record_from_bytes<R: FileRecord>(data: &[u8]) -> &R {
|
||||
const { assert!(align_of::<R>() == 1) };
|
||||
assert!(data.len() >= size_of::<R>());
|
||||
// SAFETY: FileRecord requires alignment one and every bit pattern to be
|
||||
// valid. The length check proves that one complete record is present.
|
||||
unsafe { &*data.as_ptr().cast() }
|
||||
}
|
||||
|
||||
/// Views records directly in their file representation.
|
||||
pub fn records_from_bytes<R: FileRecord>(data: &[u8]) -> &[R] {
|
||||
const { assert!(align_of::<R>() == 1 && size_of::<R>() != 0) };
|
||||
let size = size_of::<R>();
|
||||
assert!(data.len().is_multiple_of(size));
|
||||
// SAFETY: FileRecord requires alignment one and every bit pattern to be
|
||||
// valid. The resulting slice covers exactly `data`.
|
||||
unsafe { std::slice::from_raw_parts(data.as_ptr().cast(), data.len() / size) }
|
||||
}
|
||||
|
||||
/// Mutably views records directly in their file representation.
|
||||
pub fn records_from_bytes_mut<R: FileRecord>(data: &mut [u8]) -> &mut [R] {
|
||||
const { assert!(align_of::<R>() == 1 && size_of::<R>() != 0) };
|
||||
let size = size_of::<R>();
|
||||
assert!(data.len().is_multiple_of(size));
|
||||
// SAFETY: FileRecord requires alignment one and every bit pattern to be
|
||||
// valid. `data` is exclusively borrowed for the returned slice.
|
||||
unsafe { std::slice::from_raw_parts_mut(data.as_mut_ptr().cast(), data.len() / size) }
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
//! Response files. If a command line argument is in the form of
|
||||
//! `@path/to/some/file` (i.e. it starts with an at sign), the linker reads
|
||||
//! the given file and interprets its contents as a list of command line
|
||||
//! arguments. A file containing command line arguments is called a
|
||||
//! "response file".
|
||||
//!
|
||||
//! A response file is often used to pass a very large number of arguments
|
||||
//! to the linker without exceeding the kernel's command line length limit.
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::ffi::{OsStr, OsString};
|
||||
use std::path::Path;
|
||||
|
||||
use bstr::ByteVec;
|
||||
|
||||
use crate::bytes::{display, is_space, os_str};
|
||||
use crate::fatal;
|
||||
use crate::mapped_file::MappedFile;
|
||||
|
||||
/// Replaces each "@path/to/some/text/file" argument with the arguments
|
||||
/// the file holds. For the Mach-O linker (`macho`), an argument starting
|
||||
/// with "@rpath", "@loader_path" or "@executable_path" is a dylib path,
|
||||
/// not a response file, wherever it is, even an option's argument.
|
||||
pub fn expand_response_files(argv: Vec<OsString>, macho: bool) -> Vec<Cow<'static, OsStr>> {
|
||||
let mut args = Vec::new();
|
||||
for arg in argv {
|
||||
if let Some(path) = response_file(arg.as_encoded_bytes(), macho) {
|
||||
args.extend(read_response_file(path, 1, macho));
|
||||
} else {
|
||||
args.push(Cow::Owned(arg));
|
||||
}
|
||||
}
|
||||
args
|
||||
}
|
||||
|
||||
/// The response file an argument names, if it names one (see
|
||||
/// expand_response_files).
|
||||
fn response_file(arg: &[u8], macho: bool) -> Option<&Path> {
|
||||
const DYLIB_PATHS: [&[u8]; 3] = [b"@rpath", b"@loader_path", b"@executable_path"];
|
||||
if macho && DYLIB_PATHS.iter().any(|prefix| arg.starts_with(prefix)) {
|
||||
return None;
|
||||
}
|
||||
arg.strip_prefix(b"@").map(|path| Path::new(os_str(path)))
|
||||
}
|
||||
|
||||
/// Opens a response file, tokenizes its contents, and returns the tokens.
|
||||
fn read_response_file(path: &Path, depth: usize, macho: bool) -> Vec<Cow<'static, OsStr>> {
|
||||
if depth > 10 {
|
||||
fatal!("{}: response file nesting too deep", path.display());
|
||||
}
|
||||
|
||||
let mf = MappedFile::must_open(path);
|
||||
mf.set_dependency(false);
|
||||
let data = mf.data();
|
||||
|
||||
// Arguments are passed on as C strings, e.g. to the LTO plugin, so they
|
||||
// must not contain a NUL byte. Arguments given by the OS never do.
|
||||
if data.contains(&0) {
|
||||
fatal!("{}: response file contains a NUL byte", path.display());
|
||||
}
|
||||
|
||||
let mut expanded = Vec::new();
|
||||
let mut i = 0;
|
||||
|
||||
while i < data.len() {
|
||||
if is_space(data[i]) {
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Plain tokens can borrow the mapping, which lives for the complete
|
||||
// link. Copy only when removing quotes or backslashes.
|
||||
let start = i;
|
||||
while i < data.len() && !is_space(data[i]) && !matches!(data[i], b'\\' | b'\'' | b'"') {
|
||||
i += 1;
|
||||
}
|
||||
let mut tok = Cow::Borrowed(&data[start..i]);
|
||||
let mut quote = None;
|
||||
while i < data.len() {
|
||||
let c = data[i];
|
||||
if c == b'\\' {
|
||||
if i + 1 == data.len() {
|
||||
fatal!("{}: premature end of input", path.display());
|
||||
}
|
||||
tok.to_mut().push(data[i + 1]);
|
||||
i += 2;
|
||||
} else if let Some(q) = quote {
|
||||
if c == q {
|
||||
quote = None;
|
||||
} else {
|
||||
tok.to_mut().push(c);
|
||||
}
|
||||
i += 1;
|
||||
} else if c == b'\'' || c == b'"' {
|
||||
quote = Some(c);
|
||||
i += 1;
|
||||
} else if is_space(c) {
|
||||
break;
|
||||
} else {
|
||||
tok.to_mut().push(c);
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
if quote.is_some() {
|
||||
fatal!("{}: premature end of input", path.display());
|
||||
}
|
||||
if let Some(nested) = response_file(&tok, macho) {
|
||||
expanded.extend(read_response_file(nested, depth + 1, macho));
|
||||
} else {
|
||||
expanded.push(match tok {
|
||||
Cow::Borrowed(bytes) => Cow::Borrowed(os_str(bytes)),
|
||||
Cow::Owned(bytes) => Cow::Owned(
|
||||
bytes
|
||||
.into_os_string()
|
||||
.unwrap_or_else(|e| fatal!("invalid OS string: {}", display(e.as_bytes()))),
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
expanded
|
||||
}
|
||||
@@ -1,166 +0,0 @@
|
||||
//! A configurable implementation of SipHash.
|
||||
//!
|
||||
//! The compression rounds, finalization rounds and 64- or 128-bit output are
|
||||
//! selected with const generic parameters. This implementation is based on
|
||||
//! the reference implementation at https://github.com/rui314/siphash.
|
||||
|
||||
pub struct SipHashTmpl<const C_ROUNDS: usize, const D_ROUNDS: usize, const OUTLEN: usize> {
|
||||
v0: u64,
|
||||
v1: u64,
|
||||
v2: u64,
|
||||
v3: u64,
|
||||
buf: [u8; 8],
|
||||
buflen: u8,
|
||||
sum: u8,
|
||||
}
|
||||
|
||||
impl<const C_ROUNDS: usize, const D_ROUNDS: usize, const OUTLEN: usize>
|
||||
SipHashTmpl<C_ROUNDS, D_ROUNDS, OUTLEN>
|
||||
{
|
||||
#[inline]
|
||||
pub fn new(key: &[u8; 16]) -> Self {
|
||||
const { assert!(OUTLEN == 64 || OUTLEN == 128) };
|
||||
|
||||
let k0 = u64::from_le_bytes(key[..8].try_into().unwrap());
|
||||
let k1 = u64::from_le_bytes(key[8..].try_into().unwrap());
|
||||
let mut hasher = Self {
|
||||
v0: 0x736f_6d65_7073_6575 ^ k0,
|
||||
v1: 0x646f_7261_6e64_6f6d ^ k1,
|
||||
v2: 0x6c79_6765_6e65_7261 ^ k0,
|
||||
v3: 0x7465_6462_7974_6573 ^ k1,
|
||||
buf: [0; 8],
|
||||
buflen: 0,
|
||||
sum: 0,
|
||||
};
|
||||
|
||||
if OUTLEN == 128 {
|
||||
hasher.v1 ^= 0xee;
|
||||
}
|
||||
hasher
|
||||
}
|
||||
|
||||
// ICF repeatedly hashes fixed-size digests. Inlining lets the optimizer
|
||||
// remove the buffering paths for these word-aligned updates.
|
||||
#[inline(always)]
|
||||
pub fn update(&mut self, mut msg: &[u8]) {
|
||||
self.sum = self.sum.wrapping_add(msg.len() as u8);
|
||||
|
||||
if self.buflen != 0 {
|
||||
let buflen = self.buflen as usize;
|
||||
if buflen + msg.len() < 8 {
|
||||
self.buf[buflen..buflen + msg.len()].copy_from_slice(msg);
|
||||
self.buflen += msg.len() as u8;
|
||||
return;
|
||||
}
|
||||
|
||||
let n = 8 - buflen;
|
||||
self.buf[buflen..].copy_from_slice(&msg[..n]);
|
||||
self.compress(u64::from_le_bytes(self.buf));
|
||||
msg = &msg[n..];
|
||||
self.buflen = 0;
|
||||
}
|
||||
|
||||
let (words, msg) = msg.as_chunks::<8>();
|
||||
for &word in words {
|
||||
self.compress(u64::from_le_bytes(word));
|
||||
}
|
||||
|
||||
self.buf[..msg.len()].copy_from_slice(msg);
|
||||
self.buflen = msg.len() as u8;
|
||||
}
|
||||
|
||||
/// Hashes a word in little-endian byte order. The common aligned case
|
||||
/// bypasses the byte buffer, including in loops of fixed-size updates.
|
||||
#[inline(always)]
|
||||
pub fn update_u64(&mut self, word: u64) {
|
||||
if self.buflen != 0 {
|
||||
self.update(&word.to_le_bytes());
|
||||
return;
|
||||
}
|
||||
self.sum = self.sum.wrapping_add(8);
|
||||
self.compress(word);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn finish(mut self, out: &mut [u8]) {
|
||||
assert_eq!(out.len() * 8, OUTLEN);
|
||||
|
||||
self.buf[self.buflen as usize..].fill(0);
|
||||
self.compress((u64::from(self.sum) << 56) | u64::from_le_bytes(self.buf));
|
||||
|
||||
self.v2 ^= if OUTLEN == 128 { 0xee } else { 0xff };
|
||||
self.finalize();
|
||||
out[..8].copy_from_slice(&(self.v0 ^ self.v1 ^ self.v2 ^ self.v3).to_le_bytes());
|
||||
|
||||
if OUTLEN == 128 {
|
||||
self.v1 ^= 0xdd;
|
||||
self.finalize();
|
||||
out[8..].copy_from_slice(&(self.v0 ^ self.v1 ^ self.v2 ^ self.v3).to_le_bytes());
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn round(&mut self) {
|
||||
self.v0 = self.v0.wrapping_add(self.v1);
|
||||
self.v1 = self.v1.rotate_left(13);
|
||||
self.v1 ^= self.v0;
|
||||
self.v0 = self.v0.rotate_left(32);
|
||||
self.v2 = self.v2.wrapping_add(self.v3);
|
||||
self.v3 = self.v3.rotate_left(16);
|
||||
self.v3 ^= self.v2;
|
||||
self.v0 = self.v0.wrapping_add(self.v3);
|
||||
self.v3 = self.v3.rotate_left(21);
|
||||
self.v3 ^= self.v0;
|
||||
self.v2 = self.v2.wrapping_add(self.v1);
|
||||
self.v1 = self.v1.rotate_left(17);
|
||||
self.v1 ^= self.v2;
|
||||
self.v2 = self.v2.rotate_left(32);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn compress(&mut self, m: u64) {
|
||||
self.v3 ^= m;
|
||||
for _ in 0..C_ROUNDS {
|
||||
self.round();
|
||||
}
|
||||
self.v0 ^= m;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn finalize(&mut self) {
|
||||
for _ in 0..D_ROUNDS {
|
||||
self.round();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type SipHash13_128 = SipHashTmpl<1, 3, 128>;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn word_updates_match_bytes_with_partial_buffers_and_length_wraparound() {
|
||||
let key = std::array::from_fn(|i| i as u8);
|
||||
let data: Vec<u8> = (0..1040).map(|i| (i * 97) as u8).collect();
|
||||
for prefix in 0..8 {
|
||||
for words in 0..128 {
|
||||
let end = prefix + words * 8;
|
||||
let mut bytes = SipHash13_128::new(&key);
|
||||
bytes.update(&data[..end + 3]);
|
||||
|
||||
let mut mixed = SipHash13_128::new(&key);
|
||||
mixed.update(&data[..prefix]);
|
||||
for &word in data[prefix..end].as_chunks::<8>().0 {
|
||||
mixed.update_u64(u64::from_le_bytes(word));
|
||||
}
|
||||
mixed.update(&data[end..end + 3]);
|
||||
let (mut expected, mut actual) = ([0; 16], [0; 16]);
|
||||
bytes.finish(&mut expected);
|
||||
mixed.finish(&mut actual);
|
||||
assert_eq!(actual, expected, "prefix={prefix}, words={words}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,140 +0,0 @@
|
||||
//! Process management: forking a child to hide exit latency, and signal
|
||||
//! handling for disk-full errors.
|
||||
|
||||
#[cfg(not(windows))]
|
||||
use std::fs::File;
|
||||
#[cfg(not(windows))]
|
||||
use std::io::{Read, Write};
|
||||
#[cfg(not(windows))]
|
||||
use std::os::unix::io::{FromRawFd, OwnedFd};
|
||||
#[cfg(not(windows))]
|
||||
use std::sync::Mutex;
|
||||
|
||||
#[cfg(not(windows))]
|
||||
static PIPE_WRITER: Mutex<Option<OwnedFd>> = Mutex::new(None);
|
||||
|
||||
// Exiting from a program with large memory usage is slow --
|
||||
// it may take a few hundred milliseconds. To hide the latency,
|
||||
// we fork a child and let it do the actual linking work.
|
||||
#[cfg(not(windows))]
|
||||
pub fn fork_child() {
|
||||
let mut pipefd = [0i32; 2];
|
||||
// Keep the pipe's descriptors inheritable across the LTO restart.
|
||||
// SAFETY: pipe initializes both descriptors on success; each then has
|
||||
// exactly one owner in this process.
|
||||
let (reader, writer) = unsafe {
|
||||
if libc::pipe(pipefd.as_mut_ptr()) == -1 {
|
||||
eprintln!("mold: pipe failed");
|
||||
std::process::exit(1);
|
||||
}
|
||||
(OwnedFd::from_raw_fd(pipefd[0]), OwnedFd::from_raw_fd(pipefd[1]))
|
||||
};
|
||||
// SAFETY: this runs before the linker starts its worker threads. The
|
||||
// parent only waits for completion and exits; the child continues linking.
|
||||
unsafe {
|
||||
let pid = libc::fork();
|
||||
if pid == -1 {
|
||||
eprintln!("mold: fork failed");
|
||||
std::process::exit(1);
|
||||
}
|
||||
if pid > 0 {
|
||||
// Parent
|
||||
drop(writer);
|
||||
if File::from(reader).read_exact(&mut [0u8]).is_ok() {
|
||||
libc::_exit(0);
|
||||
}
|
||||
|
||||
// If SIGCHLD is ignored, which is inherited across exec, the
|
||||
// child is reaped automatically and waitpid fails. Its exit
|
||||
// status is then lost, so report a failure.
|
||||
let mut status = 0;
|
||||
if libc::waitpid(pid, &raw mut status, 0) == -1 {
|
||||
libc::_exit(1);
|
||||
}
|
||||
if libc::WIFEXITED(status) {
|
||||
libc::_exit(libc::WEXITSTATUS(status));
|
||||
}
|
||||
if libc::WIFSIGNALED(status) {
|
||||
libc::raise(libc::WTERMSIG(status));
|
||||
}
|
||||
libc::_exit(1);
|
||||
}
|
||||
}
|
||||
// Child
|
||||
drop(reader);
|
||||
*PIPE_WRITER.lock().unwrap() = Some(writer);
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
pub fn fork_child() {}
|
||||
|
||||
/// Tells the parent that the output is complete.
|
||||
#[cfg(not(windows))]
|
||||
pub fn notify_parent() {
|
||||
let Some(writer) = PIPE_WRITER.lock().unwrap().take() else {
|
||||
return;
|
||||
};
|
||||
let _ = File::from(writer).write_all(&[1]);
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
pub fn notify_parent() {}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
extern "C" fn on_signal(
|
||||
signo: libc::c_int,
|
||||
info: *mut libc::siginfo_t,
|
||||
_context: *mut libc::c_void,
|
||||
) {
|
||||
// mold mmap's an output file, and the mmap succeeds even if there's
|
||||
// not enough space left on the filesystem. The actual disk blocks are
|
||||
// not allocated on the mmap call but when the program writes to it
|
||||
// for the first time.
|
||||
//
|
||||
// If a disk becomes full as a result of a write to an mmap'ed memory
|
||||
// region, the failure of the write is reported as a SIGBUS. This
|
||||
// signal handler catches that signal and prints out a user-friendly
|
||||
// error message. Without this, it is very hard to realize that the
|
||||
// disk might be full.
|
||||
let addr = if info.is_null() {
|
||||
0
|
||||
} else {
|
||||
// SAFETY: SA_SIGINFO gives the handler a valid siginfo_t pointer.
|
||||
unsafe { (*info).si_addr() as usize }
|
||||
};
|
||||
if (signo == libc::SIGSEGV || signo == libc::SIGBUS)
|
||||
&& crate::output_file::output_buffer_contains(addr)
|
||||
{
|
||||
// Handle disk full error
|
||||
let msg = b"mold: failed to write to an output file. Disk full?\n";
|
||||
// SAFETY: write is async-signal-safe.
|
||||
unsafe {
|
||||
libc::write(libc::STDERR_FILENO, msg.as_ptr().cast(), msg.len());
|
||||
}
|
||||
}
|
||||
crate::output_file::cleanup();
|
||||
// Re-throw the signal
|
||||
// SAFETY: restoring the default handlers and re-raising.
|
||||
unsafe {
|
||||
libc::signal(libc::SIGSEGV, libc::SIG_DFL);
|
||||
libc::signal(libc::SIGBUS, libc::SIG_DFL);
|
||||
libc::raise(signo);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
pub fn install_signal_handler() {
|
||||
// SAFETY: installing a signal handler with the three-argument SA_SIGINFO
|
||||
// calling convention.
|
||||
unsafe {
|
||||
let mut action: libc::sigaction = std::mem::zeroed();
|
||||
action.sa_sigaction = on_signal as *const () as libc::sighandler_t;
|
||||
libc::sigemptyset(&raw mut action.sa_mask);
|
||||
action.sa_flags = libc::SA_SIGINFO;
|
||||
libc::sigaction(libc::SIGSEGV, &raw const action, std::ptr::null_mut());
|
||||
libc::sigaction(libc::SIGBUS, &raw const action, std::ptr::null_mut());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
pub fn install_signal_handler() {}
|
||||
-109
@@ -1,109 +0,0 @@
|
||||
//! This file contains functions to create a tar file.
|
||||
|
||||
use std::fs::File;
|
||||
use std::io::{self, Seek, SeekFrom, Write};
|
||||
|
||||
use std::ffi::{OsStr, OsString};
|
||||
use std::path::Path;
|
||||
|
||||
use crate::bits::align_to;
|
||||
use crate::path::clean_path;
|
||||
|
||||
const BLOCK_SIZE: u64 = 512;
|
||||
|
||||
// TarWriter creates the archive used by --repro.
|
||||
//
|
||||
// If you pass `--repro` to mold, mold collects all input files and puts them
|
||||
// into `<output-file-path>.repro.tar`, making it easy to run the same command
|
||||
// with the same command-line arguments.
|
||||
//
|
||||
/// A tar file consists of one or more Ustar headers 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.
|
||||
pub struct TarWriter {
|
||||
out: File,
|
||||
basedir: OsString,
|
||||
}
|
||||
|
||||
fn ustar_header(name: &[u8], mode: &[u8], size: u64, typeflag: u8) -> [u8; 512] {
|
||||
let mut hdr = [0u8; 512];
|
||||
hdr[..name.len()].copy_from_slice(name);
|
||||
hdr[100..100 + mode.len()].copy_from_slice(mode);
|
||||
let size = format!("{size:011o}");
|
||||
hdr[124..124 + size.len()].copy_from_slice(size.as_bytes());
|
||||
hdr[148..156].fill(b' ');
|
||||
hdr[156] = typeflag;
|
||||
hdr[257..262].copy_from_slice(b"ustar");
|
||||
hdr[263..265].copy_from_slice(b"00");
|
||||
|
||||
// Compute checksum. The field is six octal digits, a NUL byte and a
|
||||
// trailing space.
|
||||
let checksum: u32 = hdr.iter().map(|&b| b as u32).sum();
|
||||
let checksum = format!("{checksum:06o}\0");
|
||||
hdr[148..148 + checksum.len()].copy_from_slice(checksum.as_bytes());
|
||||
hdr
|
||||
}
|
||||
|
||||
/// 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.
|
||||
fn encode_path(basedir: &OsStr, path: &Path) -> Vec<u8> {
|
||||
let mut name = basedir.to_os_string();
|
||||
name.push("/");
|
||||
name.push(path);
|
||||
let path = clean_path(Path::new(&name));
|
||||
let bytes = path.as_os_str().as_encoded_bytes();
|
||||
let len = b" path=\n".len() + bytes.len();
|
||||
let total = len + len.to_string().len();
|
||||
let total = len + total.to_string().len();
|
||||
|
||||
// PAX defines the `path` value as UTF-8. If a filename isn't valid
|
||||
// UTF-8, we have to declare that we are storing raw bytes, or bsdtar
|
||||
// (the tar of FreeBSD and macOS) fails to extract the archive. We
|
||||
// don't declare it for every file because GNU tar doesn't know the
|
||||
// keyword and prints a warning for each occurrence.
|
||||
let mut out = Vec::new();
|
||||
if path.to_str().is_none() {
|
||||
out.extend_from_slice(b"21 hdrcharset=BINARY\n");
|
||||
}
|
||||
out.extend_from_slice(format!("{total} path=").as_bytes());
|
||||
out.extend_from_slice(bytes);
|
||||
out.push(b'\n');
|
||||
out
|
||||
}
|
||||
|
||||
impl TarWriter {
|
||||
pub fn open(output_path: &Path, basedir: &OsStr) -> io::Result<Self> {
|
||||
Ok(Self { out: File::create(output_path)?, basedir: basedir.to_os_string() })
|
||||
}
|
||||
|
||||
pub fn append(&mut self, path: &Path, data: &[u8]) -> io::Result<()> {
|
||||
let attr = encode_path(&self.basedir, path);
|
||||
// Write PAX header
|
||||
self.out.write_all(&ustar_header(b"/", b"", attr.len() as u64, b'x'))?;
|
||||
// Write pathname
|
||||
self.out.write_all(&attr)?;
|
||||
self.pad()?;
|
||||
|
||||
// Write Ustar header
|
||||
self.out.write_all(&ustar_header(b"", b"0000664", data.len() as u64, b'0'))?;
|
||||
// Write file contents
|
||||
self.out.write_all(data)?;
|
||||
self.pad()?;
|
||||
|
||||
// A tar file must end with two empty blocks
|
||||
let pos = self.out.stream_position()?;
|
||||
self.out.set_len(pos + BLOCK_SIZE * 2)
|
||||
}
|
||||
|
||||
fn pad(&mut self) -> io::Result<()> {
|
||||
let pos = self.out.stream_position()?;
|
||||
self.out.seek(SeekFrom::Start(align_to(pos, BLOCK_SIZE)))?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
use std::sync::{Mutex, MutexGuard};
|
||||
|
||||
// Keep independent workers' locks and buffers off the same cache line.
|
||||
#[repr(align(128))]
|
||||
struct Worker<T>(Mutex<T>);
|
||||
|
||||
/// Persistent scratch storage per Rayon worker, plus a slot for callers
|
||||
/// outside the pool. Look up and lock once per file, not once per symbol.
|
||||
pub struct WorkerLocal<T>(Vec<Worker<T>>);
|
||||
|
||||
impl<T> WorkerLocal<T> {
|
||||
pub fn new(init: impl Fn() -> T) -> Self {
|
||||
Self((0..=rayon::current_num_threads()).map(|_| Worker(Mutex::new(init()))).collect())
|
||||
}
|
||||
|
||||
pub fn get(&self) -> MutexGuard<'_, T> {
|
||||
let fallback = self.0.len() - 1;
|
||||
let worker = rayon::current_thread_index().unwrap_or(fallback).min(fallback);
|
||||
self.0[worker].0.lock().unwrap()
|
||||
}
|
||||
|
||||
pub fn into_values(self) -> impl Iterator<Item = T> {
|
||||
self.0.into_iter().map(|worker| worker.0.into_inner().unwrap())
|
||||
}
|
||||
|
||||
pub fn take(&mut self) -> impl Iterator<Item = T> + '_
|
||||
where
|
||||
T: Default,
|
||||
{
|
||||
self.0.iter_mut().map(|worker| std::mem::take(worker.0.get_mut().unwrap()))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use rayon::prelude::*;
|
||||
|
||||
#[test]
|
||||
fn reuses_worker_storage_and_keeps_an_outside_slot() {
|
||||
let pool = rayon::ThreadPoolBuilder::new().num_threads(2).build().unwrap();
|
||||
let mut slots = pool.install(|| {
|
||||
let slots = WorkerLocal::new(Vec::new);
|
||||
for batch in 0..3 {
|
||||
(0..100).into_par_iter().for_each(|i| slots.get().push(batch * 100 + i));
|
||||
}
|
||||
slots
|
||||
});
|
||||
slots.get().push(300);
|
||||
let mut values: Vec<_> = slots.take().flatten().collect();
|
||||
values.sort_unstable();
|
||||
assert_eq!(values, (0..301).collect::<Vec<_>>());
|
||||
assert!(slots.into_values().all(|slot| slot.is_empty()));
|
||||
}
|
||||
}
|
||||
@@ -2,39 +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`). The builds for musl-based systems,
|
||||
# `x86_64-musl` and `aarch64-musl`, are written as
|
||||
# `mold-$version-$arch-linux-musl.tar.gz` instead.
|
||||
# `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.
|
||||
# Neither do the musl builds, which are built on Alpine Linux. Alpine keeps
|
||||
# only the latest version of each package, so its packages can't be pinned.
|
||||
# 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 or musl 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. Likewise, a binary built on a
|
||||
# newer Alpine may use functions that an older musl or libgcc doesn't have,
|
||||
# so the musl builds use the oldest Alpine release that is still supported.
|
||||
# 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
|
||||
@@ -42,195 +42,206 @@
|
||||
# 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 | x86_64-musl | aarch64-musl ]"
|
||||
echo "Usage: $0 [ x86_64 | aarch64 | arm | riscv64 | ppc64le | s390x | loongarch64 ]"
|
||||
exit 1
|
||||
}
|
||||
|
||||
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
|
||||
;;
|
||||
x86_64-musl)
|
||||
# Alpine 3.21 released in December 2024.
|
||||
base_image=mirror.gcr.io/library/alpine:3.21.8@sha256:ce64758a109eb420d874a118f87920e625e12d3634e03b4a5573fd9f6e5d3507
|
||||
rust_target=x86_64-unknown-linux-musl
|
||||
rust_sha256=40dbea28193cf2b488cf3e4a89274ccfb60efa50883f19917a382f84fd05bdc4
|
||||
;;
|
||||
aarch64-musl)
|
||||
# Alpine 3.21 released in December 2024.
|
||||
base_image=mirror.gcr.io/library/alpine:3.21.8@sha256:ce64758a109eb420d874a118f87920e625e12d3634e03b4a5573fd9f6e5d3507
|
||||
rust_target=aarch64-unknown-linux-musl
|
||||
rust_sha256=c5f45b5c6eb7f8fdb277c54c08402b7c931516740fbd4eccc26ba148f7cd5d57
|
||||
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
|
||||
os=linux
|
||||
if [[ $arch = *-musl ]]; then
|
||||
arch=${arch%-musl}
|
||||
os=linux-musl
|
||||
fi
|
||||
version=$(sed -n 's/^project(mold VERSION \(.*\))/\1/p' CMakeLists.txt)
|
||||
dest=mold-$version-$arch-linux
|
||||
|
||||
podman build --arch "$arch" -t "$image" - <<EOF
|
||||
FROM $base_image
|
||||
ENV DEBIAN_FRONTEND=noninteractive TZ=UTC
|
||||
RUN if [ -f /etc/alpine-release ]; then \
|
||||
apk add --no-cache bash build-base coreutils git gzip linux-headers tar wget; \
|
||||
else \
|
||||
$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; \
|
||||
fi
|
||||
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)
|
||||
dest=mold-$version-$arch-$os
|
||||
# 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
|
||||
|
||||
# Link mold again with the mold just built, so that every release binary
|
||||
# gets Identical Code Folding (see cli/build.rs). The default linker for
|
||||
# most targets here is GNU ld, which does not support it. The first mold
|
||||
# runs from a copy because the link replaces the files in the target
|
||||
# directory.
|
||||
mkdir /build/stage1
|
||||
cp $CARGO_TARGET_DIR/release/mold /build/stage1
|
||||
/build/stage1/mold -run cargo rustc --release --frozen --config /vendor/config.toml \
|
||||
--package mold --bin mold -- -Clink-arg=-Wl,--icf=all
|
||||
|
||||
stage=/build/$DEST
|
||||
DESTDIR=/build PREFIX=/$DEST ./install-mold.sh
|
||||
strip --strip-unneeded "$stage/bin/mold"
|
||||
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
@@ -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
|
||||
@@ -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 |
@@ -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
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 596 KiB |
Loaded 100 of 4073 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user