Compare commits

..
1 Commits
Author SHA1 Message Date
Rui Ueyama 57f5c77174 ARC 2025-04-15 16:49:41 +09:00
4027 changed files with 753237 additions and 141528 deletions

No files matched your search

-21
View File
@@ -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"]
+10
View File
@@ -0,0 +1,10 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
Delete this message and describe the bug here. Note that we only fix bugs in the most recent release of mold, so please make sure that you are using the most recent version.
+13 -45
View File
@@ -3,30 +3,12 @@ name: Build all tarballs
on:
schedule:
- cron: '0 0 * * *'
release:
types: [ published ]
workflow_dispatch:
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:
- name: Check for new commits since last successful run
id: check
env:
GH_TOKEN: ${{ github.token }}
run: |
last=$(gh run list -R ${{ github.repository }} -w build-all.yml -b main -s success -L1 --json createdAt -q '.[0].createdAt')
if [ -z "$last" ] || [ "$last" = "null" ] || [ "$(gh api repos/${{ github.repository }}/commits?sha=main\&since=$last\&per_page=1 | jq length)" -gt 0 ]; then
echo "has_changes=true" >> "$GITHUB_OUTPUT"
fi
build-linux:
needs: check-changes
if: needs.check-changes.outputs.has_changes == 'true'
build-tarballs:
strategy:
matrix:
include:
@@ -42,42 +24,28 @@ jobs:
permissions:
contents: read
packages: write
steps:
- name: Checkout Repository
uses: actions/checkout@v7
uses: actions/checkout@v4
- 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 }}
- name: Upload artifact
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.target }}
path: dist/mold-*.tar.gz
compression-level: 0
build-windows:
needs: check-changes
if: needs.check-changes.outputs.has_changes == 'true'
runs-on: windows-latest
permissions:
contents: read
steps:
- name: Checkout Repository
uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
- name: Build and Archive
shell: pwsh
run: ./.github/workflows/build-windows.ps1
- name: Upload artifact
uses: actions/upload-artifact@v7
with:
name: win-x86_64
path: dist/mold-*.*
+10 -2
View File
@@ -17,19 +17,27 @@ jobs:
permissions:
contents: read
packages: write
steps:
- name: Checkout Repository
uses: actions/checkout@v7
uses: actions/checkout@v4
- 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 }}
- name: Upload artifact
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.target }}
path: dist/mold-*.tar.gz
-16
View File
@@ -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
+94 -137
View File
@@ -1,192 +1,149 @@
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-sanitizers:
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@v4
- 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
uses: actions/upload-artifact@v4
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:
build-multi-archs:
runs-on: ubuntu-24.04
timeout-minutes: 60
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@nightly
with:
components: rust-src
- run: sudo ./install-test-deps.sh
- run: sudo apt-get install -y clang llvm
- run: .github/workflows/run-msan.sh
- uses: actions/checkout@v4
- 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
uses: actions/upload-artifact@v4
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
- name: sanitize distro name
if: failure()
id: sanitize
run: echo "name=$(echo '${{ matrix.distro }}' | tr '/:' '--')" >> "$GITHUB_OUTPUT"
- uses: actions/checkout@v4
- 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: archive test results
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@v4
if: failure()
with:
name: test-results-${{ steps.sanitize.outputs.name }}
path: target/debug/mold-test/out/test
name: test-results-${{ matrix.distro }}
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@v4
- 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@v4
- 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@v4
- 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@v4
- 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)
+10 -24
View File
@@ -5,7 +5,7 @@ apt-get install -y wget xz-utils
# Install a 32-bit RISC-V toolchain
mkdir /rv32
wget -O- --progress=dot:mega https://github.com/riscv-collab/riscv-gnu-toolchain/releases/download/2025.06.13/riscv32-glibc-ubuntu-24.04-gcc-nightly-2025.06.13-nightly.tar.xz | tar -C /rv32 --strip-components=1 --xz -xf -
wget -O- -q https://github.com/riscv-collab/riscv-gnu-toolchain/releases/download/2025.01.20/riscv32-glibc-ubuntu-22.04-gcc-nightly-2025.01.20-nightly.tar.xz | tar -C /rv32 --strip-components=1 --xz -xf -
ln -sf /rv32/sysroot /usr/riscv32-linux-gnu
echo '/rv32/bin/riscv32-unknown-linux-gnu-gcc -L/usr/riscv32-linux-gnu "$@"' > /usr/bin/riscv32-linux-gnu-gcc
@@ -18,7 +18,7 @@ done
# Install a LoongArch toolchain
mkdir /larch
wget -O- --progress=dot:mega https://github.com/loongson/build-tools/releases/download/2025.06.06/x86_64-cross-tools-loongarch64-binutils_2.44-gcc_15.1.0-glibc_2.41.tar.xz | tar -C /larch --strip-components=1 --xz -xf -
wget -O- -q https://github.com/loongson/build-tools/releases/download/2025.02.21/x86_64-cross-tools-loongarch64-binutils_2.44-gcc_14.2.0-glibc_2.41.tar.xz | tar -C /larch --strip-components=1 --xz -xf -
cp -r /larch/loongarch64-unknown-linux-gnu/lib/* /larch/target/lib64
ln -sf /larch/target /usr/loongarch64-linux-gnu
@@ -27,39 +27,25 @@ for i in gcc g++ objdump objcopy strip; do
ln -sf /larch/bin/loongarch64-unknown-linux-gnu-$i /usr/bin/loongarch64-linux-gnu-$i
done
wget -O /usr/local/bin/qemu-loongarch64 --progress=dot:mega https://github.com/loongson/build-tools/releases/download/2025.06.06/qemu-loongarch64
wget -O /usr/local/bin/qemu-loongarch64 -q https://github.com/loongson/build-tools/releases/download/2024.11.01/qemu-loongarch64
chmod 755 /usr/local/bin/qemu-loongarch64
# Install ARM32 big-endian toolchain
mkdir /armeb
wget -O- --progress=dot:mega https://toolchains.bootlin.com/downloads/releases/toolchains/armebv7-eabihf/tarballs/armebv7-eabihf--glibc--stable-2024.05-1.tar.xz | tar -C /armeb --strip-components=1 --xz -xf -
ln -sf /armeb/armeb-buildroot-linux-gnueabihf/sysroot /usr/armeb-linux-gnueabihf
echo '/armeb/bin/armeb-linux-gcc -L/usr/armeb-linux-gnu "$@"' > /usr/bin/armeb-linux-gnueabihf-gcc
echo '/armeb/bin/armeb-linux-g++ -L/usr/armeb-linux-gnu "$@"' > /usr/bin/armeb-linux-gnueabihf-g++
chmod 755 /usr/bin/armeb-linux-gnueabihf-{gcc,g++}
for i in objdump objcopy strip; do
ln -sf /armeb/bin/armeb-linux-$i /usr/bin/armeb-linux-gnueabihf-$i
done
# Install ARM64 big-endian toolchain
mkdir /aarch64be
wget -O- --progress=dot:mega https://toolchains.bootlin.com/downloads/releases/toolchains/aarch64be/tarballs/aarch64be--glibc--stable-2024.05-1.tar.xz | tar -C /aarch64be --strip-components=1 --xz -xf -
wget -O- -q https://sources.buildroot.net/toolchain-external-arm-aarch64-be/arm-gnu-toolchain-13.3.rel1-x86_64-aarch64_be-none-linux-gnu.tar.xz | tar -C /aarch64be --strip-components=1 --xz -xf -
ln -sf /aarch64be/aarch64_be-buildroot-linux-gnu/sysroot /usr/aarch64_be-linux-gnu
echo '/aarch64be/bin/aarch64_be-linux-gcc -L/usr/aarch64_be-linux-gnu "$@"' > /usr/bin/aarch64_be-linux-gnu-gcc
echo '/aarch64be/bin/aarch64_be-linux-g++ -L/usr/aarch64_be-linux-gnu "$@"' > /usr/bin/aarch64_be-linux-gnu-g++
ln -sf /aarch64be/aarch64_be-none-linux-gnu/libc /usr/aarch64_be-linux-gnu
echo '/aarch64be/bin/aarch64_be-none-linux-gnu-gcc -L/usr/aarch64_be-linux-gnu "$@"' > /usr/bin/aarch64_be-linux-gnu-gcc
echo '/aarch64be/bin/aarch64_be-none-linux-gnu-g++ -L/usr/aarch64_be-linux-gnu "$@"' > /usr/bin/aarch64_be-linux-gnu-g++
chmod 755 /usr/bin/aarch64_be-linux-gnu-{gcc,g++}
for i in objdump objcopy strip; do
ln -sf /aarch64be/bin/aarch64_be-linux-$i /usr/bin/aarch64_be-linux-gnu-$i
ln -sf /aarch64be/bin/aarch64_be-none-linux-gnu-$i /usr/bin/aarch64_be-linux-gnu-$i
done
# Install SH4 big-endian toolchain
mkdir /sh4aeb
wget -O- --progress=dot:mega https://toolchains.bootlin.com/downloads/releases/toolchains/sh-sh4aeb/tarballs/sh-sh4aeb--glibc--stable-2024.05-1.tar.xz | tar -C /sh4aeb --strip-components=1 --xz -xf -
wget -O- -q https://toolchains.bootlin.com/downloads/releases/toolchains/sh-sh4aeb/tarballs/sh-sh4aeb--glibc--stable-2024.05-1.tar.xz | tar -C /sh4aeb --strip-components=1 --xz -xf -
ln -sf /sh4aeb/sh4aeb-buildroot-linux-gnu/sysroot /usr/sh4aeb-linux-gnu
echo '/sh4aeb/bin/sh4aeb-linux-gcc -L/usr/sh4aeb-linux-gnu "$@"' > /usr/bin/sh4aeb-linux-gnu-gcc
echo '/sh4aeb/bin/sh4aeb-linux-g++ -L/usr/sh4aeb-linux-gnu "$@"' > /usr/bin/sh4aeb-linux-gnu-g++
@@ -71,5 +57,5 @@ done
# Install Intel SDE CPU emulator for CET-related tests
mkdir /sde
wget -O- --progress=dot:mega https://downloadmirror.intel.com/850782/sde-external-9.53.0-2025-03-16-lin.tar.xz | tar -C /sde --strip-components=1 --xz -xf -
wget -O- -q https://downloadmirror.intel.com/843185/sde-external-9.48.0-2024-11-25-lin.tar.xz | tar -C /sde --strip-components=1 --xz -xf -
ln -s /sde/sde64 /usr/bin
-56
View File
@@ -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
-93
View File
@@ -1,93 +0,0 @@
name: Build & attach tarballs on tag push
on:
push:
tags: ['v[0-9]*']
env:
TAG: ${{ github.ref_name }}
jobs:
build-linux:
strategy:
matrix:
include:
- { target: x86_64, os: ubuntu-24.04 }
- { target: aarch64, os: ubuntu-24.04-arm }
- { target: arm, os: ubuntu-24.04-arm }
- { target: riscv64, os: ubuntu-24.04 }
- { target: ppc64le, os: ubuntu-24.04 }
- { target: s390x, os: ubuntu-24.04 }
- { target: loongarch64, os: ubuntu-24.04 }
- { 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
- name: Install Podman
run: sudo apt-get update && sudo apt-get install -y podman qemu-user-static
- run: ./dist.sh ${{ matrix.target }}
- uses: actions/upload-artifact@v7
with:
name: ${{ matrix.target }}
path: dist/mold-*.tar.gz
build-windows:
runs-on: windows-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
- name: Build and Archive
shell: pwsh
run: ./.github/workflows/build-windows.ps1
- uses: actions/upload-artifact@v7
with:
name: win-x86_64
path: dist/mold-*.*
publish:
needs: [build-linux, build-windows]
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v7
- uses: actions/download-artifact@v8
with:
path: dist
pattern: '*'
merge-multiple: true
- name: Ensure release exists (create if missing)
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -e
if gh release view "$TAG" >& /dev/null; then
echo "Existing release found for $TAG"
else
echo "Creating draft release for $TAG"
gh release create "$TAG" --draft \
--title "$TAG" \
--notes "Automated draft – fill the changelog later."
fi
- name: Upload tarballs
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release upload "$TAG" dist/* --clobber
gh release view "$TAG" --json assets --jq '.assets[].name'
-28
View File
@@ -1,28 +0,0 @@
#!/bin/bash
set -euo pipefail
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
# 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
# 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 "$@"
+1 -4
View File
@@ -8,16 +8,13 @@ on:
- main
workflow_dispatch:
permissions:
contents: write
jobs:
update-manpage:
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v7
uses: actions/checkout@v4
- name: Install ronn
run: sudo apt-get update && sudo apt-get install -y ronn
+10 -7
View File
@@ -1,8 +1,11 @@
/target
/out
/dist
/bench
/.claude
/.codex
*.o
*.so
*~
__pycache__/
**/.DS_Store
.cache
core
gentoo
/mold-*.tar.gz
/build*
/mold
/dist
+458
View File
@@ -0,0 +1,458 @@
# 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.37.1)
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 -Wl,--gdb-index)
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()
# 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()
add_subdirectory(third-party/zstd/build/cmake EXCLUDE_FROM_ALL)
target_compile_definitions(libzstd_static PRIVATE
ZSTD_BUILD_STATIC=1
ZSTD_BUILD_SHARED=0
ZSTD_BUILD_PROGRAMS=0
ZSTD_MULTITHREAD_SUPPORT=0
ZSTD_BUILD_TESTS=0)
target_include_directories(mold PUBLIC third-party/zstd/lib)
target_link_libraries(mold PRIVATE libzstd_static)
endif()
# 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 "")
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)
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}/git-hash.cc
-P ${CMAKE_SOURCE_DIR}/lib/update-git-hash.cmake
DEPENDS lib/update-git-hash.cmake
BYPRODUCTS git-hash.cc
VERBATIM)
add_dependencies(mold git_hash)
# Create config.h file
configure_file(lib/config.h.in config.h)
include_directories(${CMAKE_CURRENT_BINARY_DIR})
# Almost all functions are template in mold which take a target type
# (e.g. X86_64) as its type parameter. Since we suport more than 10
# targets, compiling a single source file for all the targets is very
# 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.
list(APPEND MOLD_ELF_TARGETS
X86_64 I386 ARM64LE ARM64BE ARM32 RV32LE RV32BE RV64LE RV64BE PPC32
PPC64V1 PPC64V2 S390X SPARC64 M68K SH4LE SH4BE LOONGARCH32 LOONGARCH64 ARC)
list(APPEND MOLD_ELF_TEMPLATE_FILES
src/arch-arm64.cc
src/arch-loongarch.cc
src/arch-riscv.cc
src/arch-sh4.cc
src/cmdline.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_ELF_TEMPLATE_FILES src/lto-win32.cc)
else()
list(APPEND MOLD_ELF_TEMPLATE_FILES src/lto-unix.cc)
endif()
if(WIN32)
list(APPEND MOLD_ELF_TEMPLATE_FILES
src/output-file-win32.cc
src/subprocess-win32.cc
)
else()
list(APPEND MOLD_ELF_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_ELF_TEMPLATE_FILES)
foreach(TARGET IN LISTS MOLD_ELF_TARGETS)
mold_instantiate_templates(${SOURCE} ${TARGET})
endforeach()
endforeach()
# Add other non-template source files.
target_sources(mold PRIVATE
git-hash.cc
lib/compress.cc
lib/crc32.cc
lib/demangle.cc
lib/filepath.cc
lib/glob.cc
lib/hyperloglog.cc
lib/mimalloc.cc
lib/multi-glob.cc
lib/perf.cc
lib/random.cc
lib/tar.cc
src/arch-arc.cc
src/arch-arm32.cc
src/arch-i386.cc
src/arch-m68k.cc
src/arch-ppc32.cc
src/arch-ppc64v1.cc
src/arch-ppc64v2.cc
src/arch-s390x.cc
src/arch-sparc64.cc
src/arch-x86-64.cc
src/config.cc
src/elf.cc
third-party/rust-demangle/rust-demangle.c
)
if(WIN32)
target_sources(mold PRIVATE
lib/jobs-win32.cc
lib/mapped-file-win32.cc
lib/signal-win32.cc
)
else()
target_sources(mold PRIVATE
lib/jobs-unix.cc
lib/mapped-file-unix.cc
lib/signal-unix.cc
)
endif()
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()
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()
Generated
-744
View File
@@ -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
View File
@@ -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
+87 -145
View File
@@ -1,21 +1,30 @@
# mold: A Modern Linker
mold is a high-performance drop-in replacement for existing Unix linkers,
designed to speed up builds. In our August 2026 benchmarks, it links 4.9x
faster than [LLVM lld](https://lld.llvm.org) and 1.9x faster than
[wild](https://github.com/wild-linker/wild) at the median; see
[Benchmark](#benchmark) for the full results.
mold is a faster drop-in replacement for existing Unix linkers. It is several
times quicker than the LLVM lld linker, the second-fastest open-source linker,
which I initially developed a few years ago. mold aims to enhance developer
productivity by minimizing build time, particularly in rapid
debug-edit-rebuild cycles.
mold is written by the original developer of LLVM lld, the linker that
Android, Chrome, FreeBSD, PlayStation, Nintendo Switch, and other production
systems are built with. mold started as an effort to build an even faster
linker from scratch, free of the architectural limits its author had run into
while optimizing lld. It has been in production use since 2021, and today it
is the default linker of many large open-source projects and is used
internally by many companies.
Here is a performance comparison of GNU ld, GNU gold, LLVM lld, and
mold when linking final debuginfo-enabled executables for major large
programs on a simulated 16-core, 32-thread machine.
mold supports x86-64, i386, ARM 32/64, RISC-V 32/64, PowerPC 32/64, s390x,
LoongArch 32/64, SPARC64, m68k, and SH-4.
![Link speed comparison](docs/chart.svg)
| Program (linker output size) | GNU ld | GNU gold | LLVM lld | mold
|-------------------------------|--------|----------|----------|------
| MySQL 8.3 (0.47 GiB) | 10.84s | 7.47s | 1.64s | 0.46s
| Clang 19 (1.56 GiB) | 42.07s | 33.13s | 5.20s | 1.35s
| Chromium 124 (1.35 GiB) | N/A | 27.40s | 6.10s | 1.52s
mold is so fast that it is only 2x _slower_ than the `cp` command on the same
machine. If you find that mold is not faster than other linkers, feel
free to [file a bug report](https://github.com/rui314/mold/issues).
mold supports x86-64, i386, ARM64, ARM32, 64-bit/32-bit little/big-endian
RISC-V, 32-bit PowerPC, 64-bit big-endian PowerPC ELFv1, 64-bit little-endian
PowerPC ELFv2, s390x, 64-bit/32-bit LoongArch, SPARC64, m68k, and SH-4.
## Why does linking speed matter?
@@ -29,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:
[![Packaging status](https://repology.org/badge/vertical-allrepos/mold.svg)](https://repology.org/project/mold/versions)
Prebuilt binaries for Linux on x86-64, ARM64, ARM32, RISC-V, PPC64LE, s390x,
and LoongArch are also attached to each
[GitHub release](https://github.com/rui314/mold/releases).
## How to Build
mold is written in Rust and built with Cargo. It requires Rust 1.95 or later;
if your system's Rust is older, install the stable toolchain with
[rustup](https://rustup.rs/). You also need Git and a C compiler such as GCC or
Clang, which you can install with your system's package manager.
mold is written in C++20, so if you build mold yourself, you will need a
recent version of a C++ compiler and a C++ standard library. We recommend GCC
10.2 or Clang 12.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
@@ -190,7 +120,7 @@ accept an absolute path as an argument for `-fuse-ld`.
Create `.cargo/config.toml` in your project directory with the following:
```toml
[target.'cfg(target_os = "linux")']
[target.x86_64-unknown-linux-gnu]
linker = "clang"
rustflags = ["-C", "link-arg=-fuse-ld=/path/to/mold"]
```
@@ -201,7 +131,7 @@ example above, we use `clang` as a linker driver since it always accepts the
may be able to remove the `linker = "clang"` line.
```toml
[target.'cfg(target_os = "linux")']
[target.x86_64-unknown-linux-gnu]
rustflags = ["-C", "link-arg=-fuse-ld=mold"]
```
@@ -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.
![CPU usage comparison in htop animation](docs/htop.gif)
As you can see, mold uses all available cores throughout its execution and
finishes quickly. In contrast, lld fails to utilize available cores most of
the time. In this demo, the maximum parallelism is artificially capped at 16,
so that the bars fit in the GIF.
For details, please see the [design notes](docs/design.md).
## Sponsors
mold is free to use, but keeping it maintained is continuous work: supporting
new architectures and toolchain features, keeping up with the projects that
depend on it, and making it faster. That work is funded by sponsors. If mold
saves you or your company time, please consider becoming a
[GitHub sponsor](https://github.com/sponsors/rui314).
It is taken for granted nowadays that compiler toolchains can be easily
installed and used for free, and people may not think too much about the
individuals behind these "free tools". mold supports many projects, but it
is essentially a one-person project. This situation is similar to the one
depicted in the following xkcd illustration.
We thank everyone who sponsors the project. In particular, we'd like to
acknowledge the following people and organizations who have sponsored
$128/month or more:
[![xkcd 2347](https://imgs.xkcd.com/comics/dependency.png)](https://xkcd.com/2347)
If you think that the "Nebraska guy" should be rewarded, please consider
becoming our [GitHub sponsor](https://github.com/sponsors/rui314)!
We thank everyone who sponsors our project. In particular, we'd like to acknowledge
the following people and organizations who have sponsored $128/month or more:
### Corporate sponsors
Executable
+29
View File
@@ -0,0 +1,29 @@
#!/bin/bash -x
set -e
apt-get install -y git bzip2 ninja-build pkg-config libglib2.0-dev python3-distutils-extra libpixman-1-dev
cd /
git clone --depth=1 https://github.com/foss-for-synopsys-dwc-arc-processors/qemu.git
mkdir /qemu/build
cd /qemu/build
../configure --static --extra-cflags=-Wno-error
ninja
ln -s /qemu/build/qemu-arc /qemu/build/qemu-arc64 /usr/local/bin
apt-get install -y libgmp-dev libmpfr-dev texinfo bison yacc flex
cd /
git clone --depth=1 https://github.com/foss-for-synopsys-dwc-arc-processors/binutils-gdb.git
cd /binutils-gdb
mkdir build
cd build
../configure --target=arc-linux
make -j$(nproc)
apt-get install -y mold
mkdir /build
cd /build
cmake -GNinja -DCMAKE_BUILD_TYPE=Debug -DMOLD_USE_MOLD=1 /mold
ninja
TRIPLE=arc-linux-gnu bash -x /mold/test/hello-dynamic.sh
-13
View File
@@ -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.
-14
View File
@@ -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
-3
View File
@@ -1,3 +0,0 @@
pub fn link(cmdline: mold_elf::driver::Cmdline) -> Option<&'static str> {
mold_elf::driver::link::<mold_elf::arch::Arm32>(cmdline)
}
-14
View File
@@ -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
-3
View File
@@ -1,3 +0,0 @@
pub fn link(cmdline: mold_elf::driver::Cmdline) -> Option<&'static str> {
mold_elf::driver::link::<mold_elf::arch::Arm32Be>(cmdline)
}
-14
View File
@@ -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
-3
View File
@@ -1,3 +0,0 @@
pub fn link(cmdline: mold_elf::driver::Cmdline) -> Option<&'static str> {
mold_elf::driver::link::<mold_elf::arch::Arm64>(cmdline)
}
-14
View File
@@ -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
-3
View File
@@ -1,3 +0,0 @@
pub fn link(cmdline: mold_elf::driver::Cmdline) -> Option<&'static str> {
mold_elf::driver::link::<mold_elf::arch::Arm64Be>(cmdline)
}
-14
View File
@@ -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
-3
View File
@@ -1,3 +0,0 @@
pub fn link(cmdline: mold_elf::driver::Cmdline) -> Option<&'static str> {
mold_elf::driver::link::<mold_elf::arch::I386>(cmdline)
}
-14
View File
@@ -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
-3
View File
@@ -1,3 +0,0 @@
pub fn link(cmdline: mold_elf::driver::Cmdline) -> Option<&'static str> {
mold_elf::driver::link::<mold_elf::arch::LoongArch32>(cmdline)
}
-14
View File
@@ -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
-3
View File
@@ -1,3 +0,0 @@
pub fn link(cmdline: mold_elf::driver::Cmdline) -> Option<&'static str> {
mold_elf::driver::link::<mold_elf::arch::LoongArch64>(cmdline)
}
-14
View File
@@ -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
-3
View File
@@ -1,3 +0,0 @@
pub fn link(cmdline: mold_elf::driver::Cmdline) -> Option<&'static str> {
mold_elf::driver::link::<mold_elf::arch::M68k>(cmdline)
}
-14
View File
@@ -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
-3
View File
@@ -1,3 +0,0 @@
pub fn link(cmdline: mold_elf::driver::Cmdline) -> Option<&'static str> {
mold_elf::driver::link::<mold_elf::arch::Ppc32>(cmdline)
}
-14
View File
@@ -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
-3
View File
@@ -1,3 +0,0 @@
pub fn link(cmdline: mold_elf::driver::Cmdline) -> Option<&'static str> {
mold_elf::driver::link::<mold_elf::arch::Ppc64V1>(cmdline)
}
-14
View File
@@ -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
-3
View File
@@ -1,3 +0,0 @@
pub fn link(cmdline: mold_elf::driver::Cmdline) -> Option<&'static str> {
mold_elf::driver::link::<mold_elf::arch::Ppc64V2>(cmdline)
}
-14
View File
@@ -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
-3
View File
@@ -1,3 +0,0 @@
pub fn link(cmdline: mold_elf::driver::Cmdline) -> Option<&'static str> {
mold_elf::driver::link::<mold_elf::arch::Riscv32>(cmdline)
}
-14
View File
@@ -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
-3
View File
@@ -1,3 +0,0 @@
pub fn link(cmdline: mold_elf::driver::Cmdline) -> Option<&'static str> {
mold_elf::driver::link::<mold_elf::arch::Riscv32Be>(cmdline)
}
-14
View File
@@ -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
-3
View File
@@ -1,3 +0,0 @@
pub fn link(cmdline: mold_elf::driver::Cmdline) -> Option<&'static str> {
mold_elf::driver::link::<mold_elf::arch::Riscv64>(cmdline)
}
-14
View File
@@ -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
-3
View File
@@ -1,3 +0,0 @@
pub fn link(cmdline: mold_elf::driver::Cmdline) -> Option<&'static str> {
mold_elf::driver::link::<mold_elf::arch::Riscv64Be>(cmdline)
}
-14
View File
@@ -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
-3
View File
@@ -1,3 +0,0 @@
pub fn link(cmdline: mold_elf::driver::Cmdline) -> Option<&'static str> {
mold_elf::driver::link::<mold_elf::arch::S390x>(cmdline)
}
-14
View File
@@ -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
-3
View File
@@ -1,3 +0,0 @@
pub fn link(cmdline: mold_elf::driver::Cmdline) -> Option<&'static str> {
mold_elf::driver::link::<mold_elf::arch::Sh4>(cmdline)
}
-14
View File
@@ -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
-3
View File
@@ -1,3 +0,0 @@
pub fn link(cmdline: mold_elf::driver::Cmdline) -> Option<&'static str> {
mold_elf::driver::link::<mold_elf::arch::Sh4Be>(cmdline)
}
-14
View File
@@ -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
-3
View File
@@ -1,3 +0,0 @@
pub fn link(cmdline: mold_elf::driver::Cmdline) -> Option<&'static str> {
mold_elf::driver::link::<mold_elf::arch::Sparc64>(cmdline)
}
-14
View File
@@ -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
-3
View File
@@ -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)
}
-14
View File
@@ -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
-7
View File
@@ -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)
}
-14
View File
@@ -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
-7
View File
@@ -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)
}
-82
View File
@@ -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"]
-55
View File
@@ -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())
}
-16
View File
@@ -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
View File
@@ -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);
}
-27
View File
@@ -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"
-206
View File
@@ -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())
}
})
}
-63
View File
@@ -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));
}
}
-68
View File
@@ -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}");
}
}
-71
View File
@@ -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)
}
-211
View File
@@ -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}");
}
}
}
-258
View File
@@ -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()
}
})
}
-475
View File
@@ -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);
}
}
-66
View File
@@ -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);
}
}
-39
View File
@@ -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
View File
@@ -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
View File
@@ -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);
}
}
-70
View File
@@ -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}");
}
}
-70
View File
@@ -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() {}
-132
View File
@@ -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);
}
}
}
-35
View File
@@ -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");
-308
View File
@@ -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)))
}
-75
View File
@@ -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)
}
-574
View File
@@ -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();
}
}
-190
View File
@@ -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);
}
}
}
}
-50
View File
@@ -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
View File
@@ -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());
}
}
}
}
-77
View File
@@ -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),
);
}
}
-79
View File
@@ -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) }
}
-121
View File
@@ -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
}
-166
View File
@@ -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}");
}
}
}
}
-140
View File
@@ -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
View File
@@ -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(())
}
}
-55
View File
@@ -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()));
}
}
+167 -184
View File
@@ -1,236 +1,219 @@
#!/bin/bash
#
# 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.
# This script creates a mold binary distribution. The output is written in
# `dist` directory as `mold-$version-$arch-linux.tar.gz` (e.g.,
# `mold-1.0.3-x86_64-linux.tar.gz`).
#
# This script 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.
# The mold executable created by this script is statically linked to
# libstdc++ but dynamically linked to libc, libm, libz, and librt, as
# these libraries almost always exist on any Linux system. We can't
# statically link libc because doing so would disable dlopen(), which is
# necessary to open the LTO linker plugin.
#
# This script aims to produce reproducible outputs. That means if you run
# the script twice on the same git commit, it should produce bit-by-bit
# identical binary files. This property is crucial as a countermeasure
# against supply chain attacks. With this, you can verify that the binary
# files distributed on the GitHub release pages are created from the
# commit with release tags by rebuilding the binaries yourself.
#
# 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 Podman images pinned to a
# particular timestamp.
#
# 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.
# We aim to use a reasonably old Debian version because we'll dynamically
# link glibc to mold, and a binary linked against a newer version of glibc
# won't work on a system with an older version of glibc.
#
# 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.
# We want to build mold with Clang rather than GCC because mold's
# Identical Code Folding works best with the LLVM address significance
# table (.llvm_addrsig). Building a release binary with GCC yields a
# slightly larger binary than Clang's.
#
# 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 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
# under QEMU if the container image is not native. To use this script for
# non-native builds, you may need to install the qemu-user-static package.
# You may need to install qemu-user-static package to build non-native
# binaries.
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 (gcr.io) of the official Docker hub
# (docker.io) because docker.io has a strict rate limit policy.
cat <<EOF | $image_build
FROM mirror.gcr.io/library/debian:stretch@sha256:c5c5200ff1e9c73ffbf188b4a67eb1c91531b644856b4aefe86a58d2f0cb05be
ENV DEBIAN_FRONTEND=noninteractive TZ=UTC
RUN sed -i -e '/^deb/d' -e 's/^# deb /deb [trusted=yes] /g' /etc/apt/sources.list && \
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 && \
rm -rf /var/lib/apt/lists
# Build CMake 3.27
RUN mkdir /build && \
cd /build && \
wget -O- --no-check-certificate --progress=dot:mega https://cmake.org/files/v3.27/cmake-3.27.7.tar.gz | tar xzf - --strip-components=1 && \
./bootstrap --parallel=\$(nproc) && \
make -j\$(nproc) && \
make install && \
rm -rf /build
# Build GCC 14
RUN mkdir /build && \
cd /build && \
wget -O- --no-check-certificate --progress=dot:mega https://ftpmirror.gnu.org/gcc/gcc-14.2.0/gcc-14.2.0.tar.gz | tar xzf - --strip-components=1 && \
mkdir gmp mpc mpfr && \
wget -O- --no-check-certificate --progress=dot:mega https://ftpmirror.gnu.org/gmp/gmp-6.3.0.tar.gz | tar xzf - --strip-components=1 -C gmp && \
wget -O- --no-check-certificate --progress=dot:mega https://ftpmirror.gnu.org/mpc/mpc-1.3.1.tar.gz | tar xzf - --strip-components=1 -C mpc && \
wget -O- --no-check-certificate --progress=dot:mega https://ftpmirror.gnu.org/mpfr/mpfr-4.2.1.tar.gz | tar xzf - --strip-components=1 -C mpfr && \
./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- --no-check-certificate --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- --no-check-certificate --progress=dot:mega https://www.python.org/ftp/python/3.12.7/Python-3.12.7.tgz | tar xzf - --strip-components=1 && \
./configure && \
make -j\$(nproc) && \
make install && \
rm -rf /build
# Build LLVM 19
RUN mkdir /build && \
cd /build && \
wget -O- --no-check-certificate --progress=dot:mega https://github.com/llvm/llvm-project/archive/refs/tags/llvmorg-19.1.7.tar.gz | tar xzf - --strip-components=1 && \
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 | arm | ppc64le | s390x)
# Debian 11 (Bullseye) released in August 2021
#
# 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
;;
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
# We don't want to build Clang for these targets with Qemu becuase
# that'd take extremely long time. Also I believe old build machines
# are usually x86-64.
cat <<EOF | $image_build
FROM mirror.gcr.io/library/debian:bullseye-20240904@sha256:8ccc486c29a3ad02ad5af7f1156e2152dff3ba5634eec9be375269ef123457d8
ENV DEBIAN_FRONTEND=noninteractive TZ=UTC
RUN sed -i -e '/^deb/d' -e 's/^# deb /deb [trusted=yes] /g' /etc/apt/sources.list && \
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
# LoongArch build is not reproducible yet
cat <<EOF | $image_build
FROM mirror.gcr.io/loongarch64/debian:sid
ENV DEBIAN_FRONTEND=noninteractive TZ=UTC
RUN apt-get update && \
apt-get install -y --no-install-recommends build-essential gcc-14 g++-14 clang-19 cmake && \
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 history.
# 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.
setarch=
[ "$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'
timestamp="$(git log -1 --format=%ci)"
# 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 '
mkdir -p dist
podman run --arch $arch -it --rm --userns=host --pids-limit=-1 --network=none \
-v "$(pwd):/mold:ro" -v "$(pwd)/dist:/dist" $image 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"
mkdir /build
cd /build
find "$DEST" -print | sort | tar -cf - --no-recursion --files-from=- | gzip -9nc > "/dist/$DEST.tar.gz"
sha256sum "/dist/$DEST.tar.gz"
'
cmake -DCMAKE_BUILD_TYPE=Release -DMOLD_MOSTLY_STATIC=1 -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ /mold
cmake --build . -j\$(nproc)
cmake --install .
cmake -DMOLD_USE_MOLD=1 .
cmake --build . -j\$(nproc)
ctest --output-on-failure -j\$(nproc)
cmake --install . --prefix $dest --strip
find $dest -print | xargs touch --no-dereference --date='$timestamp'
find $dest -print | sort | tar -cf - --no-recursion --files-from=- | gzip -9nc > /dist/$dest.tar.gz
cp mold /dist
sha256sum /dist/$dest.tar.gz
"
+150
View File
@@ -0,0 +1,150 @@
This is a note about interesting bugs that I met during the
development of the mold linker.
## GNU IFUNC
Problem: A statically-linked "hello world" program mysteriously
crashed in `__libc_start_main` function which is called just after
`_start`.
Investigation: I opened up gdb and found that the program reads a
bogus value from some array. It looks like `memcpy` failed to copy
proper data there. After some investigation, I noticed that `memcpy`
did't copy data at all but instead returned the address of
`__memcpy_avx_unaligned` function, which is a real `memcpy` function
optimized for machines with the AVX registers.
This odd issue was caused by the GNU IFUNC mechanism. That is, if a
function symbol has type `STT_GNU_IFUNC`, the function does not do
what its name suggests to do but instead returns a pointer to a
function that does the actual job. In this case, `memcpy` is an IFUNC
function, and it returns an address of `__memcpy_avx_unaligned` which
is a real `memcpy` function.
IFUNC function addresses are stored to `.got` section in an ELF
executable. The dynamic loader executes all IFUNC functions at
startup to replace their GOT entries with their return values. This
mechanism allows programs to choose the best implementation among
variants of the same function at runtime based on the machine info.
If a program is statically-linked, there's no dynamic loader that
rewrites the GOT entries. Therefore, if a program is
statically-linked, a libc's startup routine does that on behalf of the
dynamic loader. Concretely, a startup routine interprets all dynamic
relocations between `__rela_iplt_start` and `__rela_iplt_stop`
symbols. It is linker's responsibility to emit dynamic relocations
for IFUNC symbols even if it is linking a statically-linked program
and mark the beginning and the ending of a `.rela.dyn` section with
the symbols, so that the startup routine can find the relocations.
The bug was my linker didn't define `__rela_iplt_start` and
`__rela_iplt_stop` symbols. Since these symbols are weak, they are
initialized to zero. From the point of the initializer function,
there's no dynamic relocations between `__rela_iplt_start` and
`__rela_iplt_stop` symbols. That left GOT entries for IFUNC symbols
untouched.
The proper fix was to emit dynamic relocations for IFUNC symbols and
define the linker-synthesized symbols. I did that, and the bug was
fixed.
## stdio buffering
Problem: A statically-linked "Hello world" program prints out the
message if executed as `./hello`, but it doesn't output anything if
executed as `./hello | cat`.
Investigation: I knew that the default buffering mode for stdout is
line buffering (buffer is flushed on every '\n'), but if it is not
connected to the terminal (i.e. `isatty(2)` returns 0 on
`STDOUT_FILENO`), it automatically switches to full buffering (buffer
is flushed when it becomes full). So, it looks like libc failed to
flush the stdout on program exit for some reason.
I traced all function calls using gdb and noticed that `__libc_atexit`
was not called. That function seemed to be responsible for buffer
flushing. I don't know how exactly I found the root cause, but after
spending an hour or two, I found that `__start___libc_atexit` and
`__stop___libc_atexit` have value 0 in my linker's output while they
mark a section containing the address of `__libc_atexit` in GNU ld's
output.
So, libc doesn't directly call `__libc_atexit` but instead call all
function pointers between `__start___libc_atexit` and
`__stop___libc_atexit` symbols. libc puts `__libc_atexit` address in
`_libc_atexit` section, expecting that the linker automatically
creates the start and the end marker symbols for the section.
There's an obscure linker feature: if a section name is valid as a C
identifier (e.g. `foo` or `_foo_bar` but not `.foo`), the linker
automatically creates marker symbols by prepending `__start_` and
`__stop_` to the section name. My linker lacked the feature.
I implemented the feature, and the bug was fixed.
## TLS variable initialization
Problem: A statically-linked "hello world" program crashes after
reading a thread-local variable.
Investigation: Thread-local variables are very different from other
types of varaibles because there may be more than one instance of the
same variable in memory. Each thread has its copy of thread-local
varaibles. `%fs` segment register points the end of the variable area
for the current thread, and the variables are accessed as an offset
from `%fs`.
Thread-local variables may be initialized (e.g. `thread_local int x =
5;`). The linker gathers all thread-local variables and put them into
`PT_TLS` segment. At runtime, the contents of the segment is used as
an "initialization image" for new threads. When a new thread is
created, the image is memcpy'ed to the new thread's thread-local
variable area. The initialization image itself is read-only at
runtime.
It took more than a day to find out that memcpy copies the
initialization image to a different place than the thread-local
variables reside. That means, thread-local variables had garbage as
initial values, and the program crashed when using them.
The problem is that I set a very large value (4096) to the alignment
of `PT_TLS` segment. All `PT_LOAD` segments are naturally aligned to
the page boundary, so I use the same value for `PT_TLS`, but that was
a mistake. When a thread initialization routine sets a value to `%fs`,
it first aligns the end of the thread-local variable area address to
`PT_TLS` alignment value. So, if you set a large value to `PT_TLS`
alignment, `%fs` is set to a wrong place.
I fixed `PT_TLS` alignment, and the bug was gone.
## stdio buffering (another issue)
I noticed that a dynamically-linked "hello world" program didn't
flush its stdout buffer on exit. The cause of the problem was that the
executable had more than one DT_NEEDED entry for `libc.so`.
DT_NEEDED entries in `.dynamic` section specify a list of shared
object file names which need to be linked at runtime. I added one
DT_NEEDED entry for each library specified with the `-l` option.
The pitfall is, unlike object files, libraries are allowed to
appear more than once in a command line, and the linker has to
de-duplicate them before processing. Adding more than one DT_NEEDED
entry for the same shared object causes mysterious issues like this.
# Copy relocations and symbol aliases
environ, _environ and __environ point to the same location in libc.so,
so when we create a copy relocation for one of the symbols, we need to
do that for all of them. Otherwise, they'll end up pointing to different
places which causes a very mysterious issue.
# DT_DEBUG and gdb
If you forget to add an entry with type DT_DEBUG to .dynamic, gdb's
`info sharedlibrary` command doesn't print out a list of shared
libraries loaded to memory. The value of the entry doesn't matter, so
it can be just zero. The existence of it is important.
#
__EH_FRAME_BEGIN__ in libgcc/crtstuff.c
+1
View File
@@ -0,0 +1 @@
<svg version="1.1" viewBox="0.0 0.0 600.0 371.0" fill="none" stroke="none" stroke-linecap="square" stroke-miterlimit="10" width="600" height="371" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg"><path fill="#ffffff" d="M0 0L600.0 0L600.0 371.0L0 371.0L0 0Z" fill-rule="nonzero"/><path stroke="#333333" stroke-width="1.0" stroke-linecap="butt" d="M82.5 275.5L581.5 275.5" fill-rule="nonzero"/><path stroke="#cccccc" stroke-width="1.0" stroke-linecap="butt" d="M82.5 231.5L581.5 231.5" fill-rule="nonzero"/><path stroke="#cccccc" stroke-width="1.0" stroke-linecap="butt" d="M82.5 186.5L581.5 186.5" fill-rule="nonzero"/><path stroke="#cccccc" stroke-width="1.0" stroke-linecap="butt" d="M82.5 142.5L581.5 142.5" fill-rule="nonzero"/><path stroke="#cccccc" stroke-width="1.0" stroke-linecap="butt" d="M82.5 98.5L581.5 98.5" fill-rule="nonzero"/><path stroke="#cccccc" stroke-width="1.0" stroke-linecap="butt" d="M82.5 53.5L581.5 53.5" fill-rule="nonzero"/><clipPath id="id_0"><path d="M82.55 53.916668L581.45 53.916668L581.45 275.45L82.55 275.45L82.55 53.916668Z" clip-rule="nonzero"/></clipPath><path stroke="#000000" stroke-width="2.0" stroke-linecap="butt" stroke-opacity="0.0" clip-path="url(#id_0)" d="M158.0 275.0L136.0 275.0L136.0 229.0C136.0 227.89543 136.89543 227.0 138.0 227.0L156.0 227.0C157.10457 227.0 158.0 227.89543 158.0 229.0Z" fill-rule="nonzero"/><path fill="#4285f4" clip-path="url(#id_0)" d="M158.0 275.0L136.0 275.0L136.0 229.0C136.0 227.89543 136.89543 227.0 138.0 227.0L156.0 227.0C157.10457 227.0 158.0 227.89543 158.0 229.0Z" fill-rule="nonzero"/><path stroke="#000000" stroke-width="2.0" stroke-linecap="butt" stroke-opacity="0.0" clip-path="url(#id_0)" d="M307.0 275.0L285.0 275.0L285.0 91.0C285.0 89.89543 285.89542 89.0 287.0 89.0L305.0 89.0C306.10458 89.0 307.0 89.89543 307.0 91.0Z" fill-rule="nonzero"/><path fill="#4285f4" clip-path="url(#id_0)" d="M307.0 275.0L285.0 275.0L285.0 91.0C285.0 89.89543 285.89542 89.0 287.0 89.0L305.0 89.0C306.10458 89.0 307.0 89.89543 307.0 91.0Z" fill-rule="nonzero"/><path stroke="#000000" stroke-width="2.0" stroke-linecap="butt" stroke-opacity="0.0" clip-path="url(#id_0)" d="M182.0 275.0L160.0 275.0L160.0 244.0C160.0 242.89543 160.89543 242.0 162.0 242.0L180.0 242.0C181.10457 242.0 182.0 242.89543 182.0 244.0Z" fill-rule="nonzero"/><path fill="#ea4335" clip-path="url(#id_0)" d="M182.0 275.0L160.0 275.0L160.0 244.0C160.0 242.89543 160.89543 242.0 162.0 242.0L180.0 242.0C181.10457 242.0 182.0 242.89543 182.0 244.0Z" fill-rule="nonzero"/><path stroke="#000000" stroke-width="2.0" stroke-linecap="butt" stroke-opacity="0.0" clip-path="url(#id_0)" d="M331.0 275.0L309.0 275.0L309.0 131.0C309.0 129.89543 309.89542 129.0 311.0 129.0L329.0 129.0C330.10458 129.0 331.0 129.89543 331.0 131.0Z" fill-rule="nonzero"/><path fill="#ea4335" clip-path="url(#id_0)" d="M331.0 275.0L309.0 275.0L309.0 131.0C309.0 129.89543 309.89542 129.0 311.0 129.0L329.0 129.0C330.10458 129.0 331.0 129.89543 331.0 131.0Z" fill-rule="nonzero"/><path stroke="#000000" stroke-width="2.0" stroke-linecap="butt" stroke-opacity="0.0" clip-path="url(#id_0)" d="M480.0 275.0L458.0 275.0L458.0 156.0C458.0 154.89543 458.89542 154.0 460.0 154.0L478.0 154.0C479.10458 154.0 480.0 154.89543 480.0 156.0Z" fill-rule="nonzero"/><path fill="#ea4335" clip-path="url(#id_0)" d="M480.0 275.0L458.0 275.0L458.0 156.0C458.0 154.89543 458.89542 154.0 460.0 154.0L478.0 154.0C479.10458 154.0 480.0 154.89543 480.0 156.0Z" fill-rule="nonzero"/><path stroke="#000000" stroke-width="2.0" stroke-linecap="butt" stroke-opacity="0.0" clip-path="url(#id_0)" d="M206.0 275.0L184.0 275.0L184.0 270.0C184.0 268.89542 184.89543 268.0 186.0 268.0L204.0 268.0C205.10457 268.0 206.0 268.89542 206.0 270.0Z" fill-rule="nonzero"/><path fill="#fbbc04" clip-path="url(#id_0)" d="M206.0 275.0L184.0 275.0L184.0 270.0C184.0 268.89542 184.89543 268.0 186.0 268.0L204.0 268.0C205.10457 268.0 206.0 268.89542 206.0 270.0Z" fill-rule="nonzero"/><path stroke="#000000" stroke-width="2.0" stroke-linecap="butt" stroke-opacity="0.0" clip-path="url(#id_0)" d="M355.0 275.0L333.0 275.0L333.0 254.0C333.0 252.89543 333.89542 252.0 335.0 252.0L353.0 252.0C354.10458 252.0 355.0 252.89543 355.0 254.0Z" fill-rule="nonzero"/><path fill="#fbbc04" clip-path="url(#id_0)" d="M355.0 275.0L333.0 275.0L333.0 254.0C333.0 252.89543 333.89542 252.0 335.0 252.0L353.0 252.0C354.10458 252.0 355.0 252.89543 355.0 254.0Z" fill-rule="nonzero"/><path stroke="#000000" stroke-width="2.0" stroke-linecap="butt" stroke-opacity="0.0" clip-path="url(#id_0)" d="M504.0 275.0L482.0 275.0L482.0 250.0C482.0 248.89543 482.89542 248.0 484.0 248.0L502.0 248.0C503.10458 248.0 504.0 248.89543 504.0 250.0Z" fill-rule="nonzero"/><path fill="#fbbc04" clip-path="url(#id_0)" d="M504.0 275.0L482.0 275.0L482.0 250.0C482.0 248.89543 482.89542 248.0 484.0 248.0L502.0 248.0C503.10458 248.0 504.0 248.89543 504.0 250.0Z" fill-rule="nonzero"/><path stroke="#000000" stroke-width="2.0" stroke-linecap="butt" stroLine truncated

After

Width:  |  Height:  |  Size: 99 KiB

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