mirror of
https://github.com/rui314/mold.git
synced 2026-11-09 04:49:33 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6695c49379 | ||
|
|
dd116fb3a7 | ||
|
|
6c2df40850 | ||
|
|
09f01b4377 | ||
|
|
683bae0ec5 | ||
|
|
19fd8fc80c | ||
|
|
165f74e43c | ||
|
|
8de8ab49ca | ||
|
|
14078d38ab | ||
|
|
87204bf7bf | ||
|
|
e526945f5d | ||
|
|
6a5626e3a7 | ||
|
|
e2c0d36c3e | ||
|
|
16c23858fd | ||
|
|
4565d27c14 | ||
|
|
c611fb05c0 | ||
|
|
1191627942 | ||
|
|
776f9ee6e0 |
No files matched your search
@@ -1,21 +0,0 @@
|
||||
# rustc's LoongArch targets use the medium code model, but the cc crate
|
||||
# compiles C code with the C compiler's default normal code model, whose
|
||||
# direct calls reach only ±128 MiB, unless -Ccode-model is given
|
||||
# explicitly. A debug build of mold has more text than that, so we pass
|
||||
# the model explicitly to make the cc crate pass -mcmodel=medium.
|
||||
[target.'cfg(target_arch = "loongarch64")']
|
||||
rustflags = ["-Ccode-model=medium"]
|
||||
|
||||
# LLVM's address-significance table tells the linker which symbols'
|
||||
# addresses the program uses. Without it, Identical Code Folding (see
|
||||
# cli/build.rs) has to assume that every piece of read-only data has a
|
||||
# significant address, so it can't merge two functions that refer to
|
||||
# identical constants, such as the panic locations that each crate
|
||||
# instantiating the linker has its own copies of.
|
||||
[target.'cfg(all())']
|
||||
rustflags = ["-Cllvm-args=-addrsig"]
|
||||
|
||||
# Rust links musl statically by default, but a static executable can't
|
||||
# dlopen() the LTO plugin.
|
||||
[target.'cfg(target_env = "musl")']
|
||||
rustflags = ["-Ctarget-feature=-crt-static"]
|
||||
@@ -1,13 +0,0 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: [rui314] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
|
||||
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
||||
@@ -1,83 +0,0 @@
|
||||
name: Build all tarballs
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 0 * * *'
|
||||
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'
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- { target: x86_64, os: ubuntu-24.04 }
|
||||
- { target: aarch64, os: ubuntu-24.04-arm }
|
||||
- { target: arm, os: ubuntu-24.04-arm }
|
||||
- { target: riscv64, os: ubuntu-24.04 }
|
||||
- { target: ppc64le, os: ubuntu-24.04 }
|
||||
- { target: s390x, os: ubuntu-24.04 }
|
||||
- { target: loongarch64, os: ubuntu-24.04 }
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: Install Podman
|
||||
run: sudo apt-get update && sudo apt-get install -y podman qemu-user-static
|
||||
|
||||
- name: Build a tarball
|
||||
run: ./dist.sh ${{ matrix.target }}
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: ${{ matrix.target }}
|
||||
path: dist/mold-*.tar.gz
|
||||
compression-level: 0
|
||||
|
||||
build-windows:
|
||||
needs: check-changes
|
||||
if: needs.check-changes.outputs.has_changes == 'true'
|
||||
runs-on: windows-latest
|
||||
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-*.*
|
||||
@@ -1,36 +0,0 @@
|
||||
name: Build native tarballs
|
||||
|
||||
on:
|
||||
push:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build-tarballs:
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- { target: x86_64, os: ubuntu-24.04 }
|
||||
- { target: aarch64, os: ubuntu-24.04-arm }
|
||||
- { target: arm, os: ubuntu-24.04-arm }
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: Install Podman
|
||||
run: sudo apt-get update && sudo apt-get install -y podman qemu-user-static
|
||||
|
||||
- name: Build a tarball
|
||||
run: ./dist.sh ${{ matrix.target }}
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: ${{ matrix.target }}
|
||||
path: dist/mold-*.tar.gz
|
||||
compression-level: 0
|
||||
@@ -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
|
||||
+11
-186
@@ -1,192 +1,17 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
pull_request:
|
||||
|
||||
branches: [ main ]
|
||||
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
|
||||
build:
|
||||
runs-on: ubuntu-20.04
|
||||
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:
|
||||
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
|
||||
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 }}
|
||||
- name: archive test results
|
||||
uses: actions/upload-artifact@v7
|
||||
if: failure()
|
||||
with:
|
||||
name: test-results-${{ matrix.sanitizer }}
|
||||
path: target/${{ matrix.sanitizer }}/x86_64-unknown-linux-gnu/debug/mold-test/out/test
|
||||
|
||||
build-msan:
|
||||
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
|
||||
- name: archive test results
|
||||
uses: actions/upload-artifact@v7
|
||||
if: failure()
|
||||
with:
|
||||
name: test-results-msan
|
||||
path: target/msan/x86_64-unknown-linux-gnu/debug/mold-test/out/test
|
||||
|
||||
test-distros:
|
||||
strategy:
|
||||
matrix:
|
||||
distro:
|
||||
- alpine
|
||||
- archlinux
|
||||
- debian:13
|
||||
- fedora
|
||||
- gentoo/stage3
|
||||
- opensuse/tumbleweed
|
||||
- ubuntu:22.04
|
||||
- ubuntu:26.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
|
||||
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"
|
||||
- name: archive test results
|
||||
uses: actions/upload-artifact@v7
|
||||
if: failure()
|
||||
with:
|
||||
name: test-results-${{ steps.sanitize.outputs.name }}
|
||||
path: target/debug/mold-test/out/test
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
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
|
||||
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
|
||||
- uses: actions/checkout@v2
|
||||
- name: apt-get
|
||||
run: sudo dpkg --add-architecture i386 && sudo apt-get update && sudo apt-get install build-essential libstdc++-10-dev cmake clang libssl-dev zlib1g-dev libxxhash-dev libtbb-dev git dwarfdump libc6-dev:i386 lib32gcc-10-dev gcc-multilib
|
||||
- name: make
|
||||
run: make -j$(nproc)
|
||||
- name: make test
|
||||
run: make test
|
||||
@@ -1,75 +0,0 @@
|
||||
#!/bin/bash -x
|
||||
|
||||
apt-get update
|
||||
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 -
|
||||
|
||||
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
|
||||
echo '/rv32/bin/riscv32-unknown-linux-gnu-g++ -L/usr/riscv32-linux-gnu "$@"' > /usr/bin/riscv32-linux-gnu-g++
|
||||
chmod 755 /usr/bin/riscv32-linux-gnu-{gcc,g++}
|
||||
|
||||
for i in objdump objcopy strip; do
|
||||
ln -sf /rv32/bin/riscv32-unknown-linux-gnu-$i /usr/bin/riscv32-linux-gnu-$i
|
||||
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 -
|
||||
|
||||
cp -r /larch/loongarch64-unknown-linux-gnu/lib/* /larch/target/lib64
|
||||
ln -sf /larch/target /usr/loongarch64-linux-gnu
|
||||
|
||||
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
|
||||
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 -
|
||||
|
||||
ln -sf /aarch64be/aarch64_be-buildroot-linux-gnu/sysroot /usr/aarch64_be-linux-gnu
|
||||
echo '/aarch64be/bin/aarch64_be-linux-gcc -L/usr/aarch64_be-linux-gnu "$@"' > /usr/bin/aarch64_be-linux-gnu-gcc
|
||||
echo '/aarch64be/bin/aarch64_be-linux-g++ -L/usr/aarch64_be-linux-gnu "$@"' > /usr/bin/aarch64_be-linux-gnu-g++
|
||||
chmod 755 /usr/bin/aarch64_be-linux-gnu-{gcc,g++}
|
||||
|
||||
for i in objdump objcopy strip; do
|
||||
ln -sf /aarch64be/bin/aarch64_be-linux-$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 -
|
||||
|
||||
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++
|
||||
chmod 755 /usr/bin/sh4aeb-linux-gnu-{gcc,g++}
|
||||
|
||||
for i in objdump objcopy strip; do
|
||||
ln -sf /sh4aeb/bin/sh4aeb-linux-$i /usr/bin/sh4aeb-linux-gnu-$i
|
||||
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 -
|
||||
ln -s /sde/sde64 /usr/bin
|
||||
@@ -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
|
||||
@@ -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'
|
||||
@@ -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,36 +0,0 @@
|
||||
name: Update manpage
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'docs/mold.md'
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
update-manpage:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: Install ronn
|
||||
run: sudo apt-get update && sudo apt-get install -y ronn
|
||||
|
||||
- name: Generate mold.1 from mold.md
|
||||
run: ronn --roff docs/mold.md
|
||||
|
||||
- name: Configure Git
|
||||
run: |
|
||||
git config --global user.name 'Rui Ueyama'
|
||||
git config --global user.email '[email protected]'
|
||||
|
||||
- name: Commit and push if mold.1 is updated
|
||||
run: |
|
||||
git add docs/mold.1
|
||||
git diff --staged --quiet || (git commit -m "Update mold.1 (automated commit)" && git push)
|
||||
+9
-7
@@ -1,8 +1,10 @@
|
||||
/target
|
||||
/out
|
||||
/dist
|
||||
/bench
|
||||
/.claude
|
||||
/.codex
|
||||
*.o
|
||||
*.so
|
||||
*~
|
||||
__pycache__/
|
||||
/mold
|
||||
/test/tmp
|
||||
options.inc
|
||||
core
|
||||
ld
|
||||
oneTBB
|
||||
gentoo
|
||||
Whitespace-only changes.
@@ -0,0 +1,54 @@
|
||||
# Contributing to mold
|
||||
|
||||
Summary: When you send a patch, you must agree that the patch is
|
||||
released under the dual license of the GNU AGPLv3 and the MIT license.
|
||||
As an indication of agreement, please add a `Signed-off-by` line to
|
||||
your commit message using `git commit -s`.
|
||||
|
||||
Here is the long official version:
|
||||
|
||||
mold is currently licensed under the GNU AGPLv3, but I want to relicense
|
||||
it under a more liberal license in the future. To make this possible,
|
||||
I want all patches to be submitted under the dual license of the GNU
|
||||
AGPLv3 and the MIT license (the "Dual License") from the beginning.
|
||||
|
||||
To make it clear that contributors have agreed to release their
|
||||
patches under the Dual License, I introduced a sign-off procedure just
|
||||
like the Linux kernel project did. The sign-off is a simple line at
|
||||
the end of the git commit message, which certifies that you wrote it
|
||||
or otherwise have the right to pass it on as an open-source patch. The
|
||||
rules are pretty simple: if you can certify the below:
|
||||
|
||||
> ## Developer's Certificate of Origin
|
||||
>
|
||||
> By making a contribution to this project, I certify that:
|
||||
>
|
||||
> 1. The contribution was created in whole or in part by me and I
|
||||
> have the right to submit it under the Dual License; or
|
||||
>
|
||||
> 2. The contribution is based upon previous work that, to the best
|
||||
> of my knowledge, is covered under the the Dual License and I
|
||||
> have the right under the Dual License to submit that work with
|
||||
> modifications, whether created in whole or in part by me, under
|
||||
> the Dual License (unless I am permitted to submit under
|
||||
> different licenses), as indicated in the file; or
|
||||
>
|
||||
> 3. The contribution was provided directly to me by some other
|
||||
> person who certified (1), (2) or (3) and I have not modified it.
|
||||
>
|
||||
> 4. I understand and agree that this project and the contribution
|
||||
> are public and that a record of the contribution (including all
|
||||
> personal information I submit with it, including my sign-off) is
|
||||
> maintained indefinitely and may be redistributed consistent with
|
||||
> this project or the open source license(s) involved.
|
||||
|
||||
then you just add a line saying:
|
||||
|
||||
```
|
||||
Signed-off-by: Random J Developer <[email protected]>
|
||||
```
|
||||
|
||||
using your real name (sorry, no pseudonyms or anonymous contributions.)
|
||||
This will be done for you automatically if you use ``git commit -s``.
|
||||
Reverts should also include "Signed-off-by". ``git revert -s`` does that
|
||||
for you.
|
||||
Generated
-744
@@ -1,744 +0,0 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "allocator-api2"
|
||||
version = "0.2.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
|
||||
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
version = "0.7.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56"
|
||||
|
||||
[[package]]
|
||||
name = "blake3"
|
||||
version = "1.8.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d9e454fc11f76977dc803893aff6304ed33d6a26efae8696573bea74baa27ae"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"constant_time_eq",
|
||||
"cpufeatures",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa"
|
||||
dependencies = [
|
||||
"hybrid-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bstr"
|
||||
version = "1.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6bb31b46c14244e20ee9984b11bf5c992b91fb6939fea616e3512c8baecdbe5f"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ad534f4357a5264cce5019c989cf66a4f0dc4e0d1b1d15f8aacec0ff7360273"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"jobserver",
|
||||
"libc",
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||
|
||||
[[package]]
|
||||
name = "const-oid"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c"
|
||||
|
||||
[[package]]
|
||||
name = "constant_time_eq"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b"
|
||||
|
||||
[[package]]
|
||||
name = "cpp_demangle"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0667304c32ea56cb4cd6d2d7c0cfe9a2f8041229db8c033af7f8d69492429def"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8498c871161e1742aaa9d52551b2d6ebdd4c3d45a3be423e3728f33b955be550"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-deque"
|
||||
version = "0.8.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5181e0de7b61eb03a81e347d6dd8797bae9da5146707b51077e2d71a54ec0ceb"
|
||||
dependencies = [
|
||||
"crossbeam-epoch",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-epoch"
|
||||
version = "0.9.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17"
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453"
|
||||
dependencies = [
|
||||
"hybrid-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"const-oid",
|
||||
"crypto-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34"
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||
|
||||
[[package]]
|
||||
name = "find-msvc-tools"
|
||||
version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890"
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e634e2e0ebac1ee034020da1ca582e17ffe4e0f5e985823721e168928136dcb"
|
||||
dependencies = [
|
||||
"zlib-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "foldhash"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"r-efi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.17.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
|
||||
dependencies = [
|
||||
"allocator-api2",
|
||||
"equivalent",
|
||||
"foldhash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hybrid-array"
|
||||
version = "0.4.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "27f864f10dfb56725ce5ce5472bc52252c8f93a4ab86327122cebf62c5f59a17"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
||||
|
||||
[[package]]
|
||||
name = "jobserver"
|
||||
version = "0.1.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.189"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
|
||||
|
||||
[[package]]
|
||||
name = "libz-rs-sys"
|
||||
version = "0.6.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "434324ee61a8e4a05dd3fdf03d51c073ea7fdaf5a35b2ca33c4ca9f3f37def4a"
|
||||
dependencies = [
|
||||
"zlib-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
|
||||
|
||||
[[package]]
|
||||
name = "memmap2"
|
||||
version = "0.9.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d1219ed1b7f229ee7104d281dd01d6802fe28bb6e95d292942c4daacdeb798c0"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold"
|
||||
version = "3.0.0"
|
||||
dependencies = [
|
||||
"mold-elf",
|
||||
"mold-elf-arm32",
|
||||
"mold-elf-arm32be",
|
||||
"mold-elf-arm64",
|
||||
"mold-elf-arm64be",
|
||||
"mold-elf-i386",
|
||||
"mold-elf-loongarch32",
|
||||
"mold-elf-loongarch64",
|
||||
"mold-elf-m68k",
|
||||
"mold-elf-ppc32",
|
||||
"mold-elf-ppc64v1",
|
||||
"mold-elf-ppc64v2",
|
||||
"mold-elf-riscv32",
|
||||
"mold-elf-riscv32be",
|
||||
"mold-elf-riscv64",
|
||||
"mold-elf-riscv64be",
|
||||
"mold-elf-s390x",
|
||||
"mold-elf-sh4",
|
||||
"mold-elf-sh4be",
|
||||
"mold-elf-sparc64",
|
||||
"mold-elf-x86_64",
|
||||
"mold-macho",
|
||||
"mold-macho-arm64",
|
||||
"mold-macho-x86_64",
|
||||
"mold-mimalloc",
|
||||
"mold-tests",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-common"
|
||||
version = "3.0.0"
|
||||
dependencies = [
|
||||
"bstr",
|
||||
"cpp_demangle",
|
||||
"flate2",
|
||||
"libc",
|
||||
"libz-rs-sys",
|
||||
"memchr",
|
||||
"memmap2",
|
||||
"portable-atomic",
|
||||
"rayon",
|
||||
"rustc-demangle",
|
||||
"xxhash-rust",
|
||||
"zlib-rs",
|
||||
"zstd",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-elf"
|
||||
version = "3.0.0"
|
||||
dependencies = [
|
||||
"blake3",
|
||||
"bstr",
|
||||
"cc",
|
||||
"crc32fast",
|
||||
"getrandom",
|
||||
"hashbrown",
|
||||
"libc",
|
||||
"memchr",
|
||||
"mold-common",
|
||||
"portable-atomic",
|
||||
"rayon",
|
||||
"uuid",
|
||||
"xxhash-rust",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-elf-arm32"
|
||||
version = "3.0.0"
|
||||
dependencies = [
|
||||
"mold-elf",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-elf-arm32be"
|
||||
version = "3.0.0"
|
||||
dependencies = [
|
||||
"mold-elf",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-elf-arm64"
|
||||
version = "3.0.0"
|
||||
dependencies = [
|
||||
"mold-elf",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-elf-arm64be"
|
||||
version = "3.0.0"
|
||||
dependencies = [
|
||||
"mold-elf",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-elf-i386"
|
||||
version = "3.0.0"
|
||||
dependencies = [
|
||||
"mold-elf",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-elf-loongarch32"
|
||||
version = "3.0.0"
|
||||
dependencies = [
|
||||
"mold-elf",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-elf-loongarch64"
|
||||
version = "3.0.0"
|
||||
dependencies = [
|
||||
"mold-elf",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-elf-m68k"
|
||||
version = "3.0.0"
|
||||
dependencies = [
|
||||
"mold-elf",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-elf-ppc32"
|
||||
version = "3.0.0"
|
||||
dependencies = [
|
||||
"mold-elf",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-elf-ppc64v1"
|
||||
version = "3.0.0"
|
||||
dependencies = [
|
||||
"mold-elf",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-elf-ppc64v2"
|
||||
version = "3.0.0"
|
||||
dependencies = [
|
||||
"mold-elf",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-elf-riscv32"
|
||||
version = "3.0.0"
|
||||
dependencies = [
|
||||
"mold-elf",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-elf-riscv32be"
|
||||
version = "3.0.0"
|
||||
dependencies = [
|
||||
"mold-elf",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-elf-riscv64"
|
||||
version = "3.0.0"
|
||||
dependencies = [
|
||||
"mold-elf",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-elf-riscv64be"
|
||||
version = "3.0.0"
|
||||
dependencies = [
|
||||
"mold-elf",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-elf-s390x"
|
||||
version = "3.0.0"
|
||||
dependencies = [
|
||||
"mold-elf",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-elf-sh4"
|
||||
version = "3.0.0"
|
||||
dependencies = [
|
||||
"mold-elf",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-elf-sh4be"
|
||||
version = "3.0.0"
|
||||
dependencies = [
|
||||
"mold-elf",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-elf-sparc64"
|
||||
version = "3.0.0"
|
||||
dependencies = [
|
||||
"mold-elf",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-elf-x86_64"
|
||||
version = "3.0.0"
|
||||
dependencies = [
|
||||
"mold-elf",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-macho"
|
||||
version = "3.0.0"
|
||||
dependencies = [
|
||||
"hashbrown",
|
||||
"libc",
|
||||
"memchr",
|
||||
"memmap2",
|
||||
"mold-common",
|
||||
"portable-atomic",
|
||||
"rayon",
|
||||
"serde_json",
|
||||
"sha1",
|
||||
"sha2",
|
||||
"uuid",
|
||||
"xxhash-rust",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-macho-arm64"
|
||||
version = "3.0.0"
|
||||
dependencies = [
|
||||
"mold-macho",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-macho-x86_64"
|
||||
version = "3.0.0"
|
||||
dependencies = [
|
||||
"mold-macho",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-mimalloc"
|
||||
version = "0.1.52"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b7efd1b6c74e7a09c1d296af4875b79118a5ea32d70736601a8323ddcbcb4c2"
|
||||
dependencies = [
|
||||
"mold-mimalloc-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-mimalloc-sys"
|
||||
version = "0.1.49"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "66a79c372cdbf45bf4548fd4aa40a0b2805ef2ee8e25e5185e11bf2b5c5de366"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-tests"
|
||||
version = "3.0.0"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548"
|
||||
|
||||
[[package]]
|
||||
name = "portable-atomic"
|
||||
version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.107"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.47"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "r-efi"
|
||||
version = "6.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
|
||||
|
||||
[[package]]
|
||||
name = "rayon"
|
||||
version = "1.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d"
|
||||
dependencies = [
|
||||
"either",
|
||||
"rayon-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rayon-core"
|
||||
version = "1.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91"
|
||||
dependencies = [
|
||||
"crossbeam-deque",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2"
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b74b56ffa8bb2830709a538c2cbcae9aa062db0d2a42563bfb09bdaae44020eb"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.229"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_core"
|
||||
version = "1.0.229"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.229"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.151"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
"serde",
|
||||
"serde_core",
|
||||
"zmij",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha1"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "3.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6275cddf4610d1775e6d1fe9469b2e77d0f39fd98fb7450901b821e0c53649f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.20.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.27.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97277d36b9c3ace13e58fa6e753f8b0bbbf302a18dd193240d70f9e29681059a"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xxhash-rust"
|
||||
version = "0.8.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aee1b19627c7c60102ab80d3a9cbe18de90bfe03bfa6c3715447681f0e8c8af6"
|
||||
|
||||
[[package]]
|
||||
name = "zlib-rs"
|
||||
version = "0.6.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b268e58e7c693d7c271f93ffc4ba3b380412554231c85bf61ca7af91042a4112"
|
||||
|
||||
[[package]]
|
||||
name = "zmij"
|
||||
version = "1.0.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
|
||||
|
||||
[[package]]
|
||||
name = "zstd"
|
||||
version = "0.13.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a"
|
||||
dependencies = [
|
||||
"zstd-safe",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zstd-safe"
|
||||
version = "7.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d"
|
||||
dependencies = [
|
||||
"zstd-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zstd-sys"
|
||||
version = "2.0.16+zstd.1.5.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"pkg-config",
|
||||
]
|
||||
-51
@@ -1,51 +0,0 @@
|
||||
# The ELF linker in elf/ is generic over the target. Instantiating it for
|
||||
# all twenty targets in one crate keeps the compiler on a single core for
|
||||
# minutes, so each target is instantiated in a crate of its own under
|
||||
# arch/, and the executable in cli/ dispatches to them.
|
||||
[workspace]
|
||||
resolver = "3"
|
||||
members = ["cli", "common", "elf", "macho", "arch/*", "tests"]
|
||||
|
||||
[workspace.package]
|
||||
version = "3.0.0"
|
||||
edition = "2024"
|
||||
rust-version = "1.95"
|
||||
license = "MIT"
|
||||
repository = "https://github.com/rui314/mold"
|
||||
|
||||
# The workspace crates are published together and depend on each other's
|
||||
# internals, so each one requires the exact version of the others. Keep
|
||||
# these versions equal to the workspace version above.
|
||||
[workspace.dependencies]
|
||||
libc = "0.2"
|
||||
mold-common = { path = "common", version = "=3.0.0" }
|
||||
mold-elf = { path = "elf", version = "=3.0.0" }
|
||||
mold-elf-x86_64 = { path = "arch/elf-x86_64", version = "=3.0.0" }
|
||||
mold-elf-i386 = { path = "arch/elf-i386", version = "=3.0.0" }
|
||||
mold-elf-arm32 = { path = "arch/elf-arm32", version = "=3.0.0" }
|
||||
mold-elf-arm32be = { path = "arch/elf-arm32be", version = "=3.0.0" }
|
||||
mold-elf-arm64 = { path = "arch/elf-arm64", version = "=3.0.0" }
|
||||
mold-elf-arm64be = { path = "arch/elf-arm64be", version = "=3.0.0" }
|
||||
mold-elf-riscv64 = { path = "arch/elf-riscv64", version = "=3.0.0" }
|
||||
mold-elf-riscv64be = { path = "arch/elf-riscv64be", version = "=3.0.0" }
|
||||
mold-elf-riscv32 = { path = "arch/elf-riscv32", version = "=3.0.0" }
|
||||
mold-elf-riscv32be = { path = "arch/elf-riscv32be", version = "=3.0.0" }
|
||||
mold-elf-ppc64v2 = { path = "arch/elf-ppc64v2", version = "=3.0.0" }
|
||||
mold-elf-ppc32 = { path = "arch/elf-ppc32", version = "=3.0.0" }
|
||||
mold-elf-ppc64v1 = { path = "arch/elf-ppc64v1", version = "=3.0.0" }
|
||||
mold-elf-s390x = { path = "arch/elf-s390x", version = "=3.0.0" }
|
||||
mold-elf-sparc64 = { path = "arch/elf-sparc64", version = "=3.0.0" }
|
||||
mold-elf-m68k = { path = "arch/elf-m68k", version = "=3.0.0" }
|
||||
mold-elf-sh4 = { path = "arch/elf-sh4", version = "=3.0.0" }
|
||||
mold-elf-sh4be = { path = "arch/elf-sh4be", version = "=3.0.0" }
|
||||
mold-elf-loongarch64 = { path = "arch/elf-loongarch64", version = "=3.0.0" }
|
||||
mold-elf-loongarch32 = { path = "arch/elf-loongarch32", version = "=3.0.0" }
|
||||
mold-macho = { path = "macho", version = "=3.0.0" }
|
||||
mold-macho-arm64 = { path = "arch/macho-arm64", version = "=3.0.0" }
|
||||
mold-macho-x86_64 = { path = "arch/macho-x86_64", version = "=3.0.0" }
|
||||
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
# Copying generic and inline functions once per crate instead of once per
|
||||
# codegen unit makes the executable about a quarter smaller.
|
||||
codegen-units = 1
|
||||
@@ -1,21 +1,671 @@
|
||||
MIT License
|
||||
Note: I'm looking for a sponsor who wants to purchase the copyright
|
||||
of this work and relicense it under a more liberal license such as
|
||||
the MIT license. For now, mold is released under the GNU AGPL v3.
|
||||
|
||||
Copyright (c) 2023 Rui Ueyama
|
||||
If you just want to use mold but do not want to use AGPL software
|
||||
in your organization, a commercial license is available. Please
|
||||
contact [email protected].
|
||||
|
||||
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.
|
||||
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
Version 3, 19 November 2007
|
||||
|
||||
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.
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU Affero General Public License is a free, copyleft license for
|
||||
software and other kinds of works, specifically designed to ensure
|
||||
cooperation with the community in the case of network server software.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
our General Public Licenses are intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
Developers that use our General Public Licenses protect your rights
|
||||
with two steps: (1) assert copyright on the software, and (2) offer
|
||||
you this License which gives you legal permission to copy, distribute
|
||||
and/or modify the software.
|
||||
|
||||
A secondary benefit of defending all users' freedom is that
|
||||
improvements made in alternate versions of the program, if they
|
||||
receive widespread use, become available for other developers to
|
||||
incorporate. Many developers of free software are heartened and
|
||||
encouraged by the resulting cooperation. However, in the case of
|
||||
software used on network servers, this result may fail to come about.
|
||||
The GNU General Public License permits making a modified version and
|
||||
letting the public access it on a server without ever releasing its
|
||||
source code to the public.
|
||||
|
||||
The GNU Affero General Public License is designed specifically to
|
||||
ensure that, in such cases, the modified source code becomes available
|
||||
to the community. It requires the operator of a network server to
|
||||
provide the source code of the modified version running there to the
|
||||
users of that server. Therefore, public use of a modified version, on
|
||||
a publicly accessible server, gives the public access to the source
|
||||
code of the modified version.
|
||||
|
||||
An older license, called the Affero General Public License and
|
||||
published by Affero, was designed to accomplish similar goals. This is
|
||||
a different license, not a version of the Affero GPL, but Affero has
|
||||
released a new version of the Affero GPL which permits relicensing under
|
||||
this license.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU Affero General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Remote Network Interaction; Use with the GNU General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, if you modify the
|
||||
Program, your modified version must prominently offer all users
|
||||
interacting with it remotely through a computer network (if your version
|
||||
supports such interaction) an opportunity to receive the Corresponding
|
||||
Source of your version by providing access to the Corresponding Source
|
||||
from a network server at no charge, through some standard or customary
|
||||
means of facilitating copying of software. This Corresponding Source
|
||||
shall include the Corresponding Source for any work covered by version 3
|
||||
of the GNU General Public License that is incorporated pursuant to the
|
||||
following paragraph.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the work with which it is combined will remain governed by version
|
||||
3 of the GNU General Public License.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU Affero General Public License from time to time. Such new versions
|
||||
will be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU Affero General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU Affero General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU Affero General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If your software can interact with users remotely through a computer
|
||||
network, you should also make sure that it provides a way for users to
|
||||
get its source. For example, if your program is a web application, its
|
||||
interface could display a "Source" link that leads users to an archive
|
||||
of the code. There are many ways you could offer source, and different
|
||||
solutions will be better for different programs; see section 13 for the
|
||||
specific requirements.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU AGPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
@@ -0,0 +1,96 @@
|
||||
CC = clang
|
||||
CXX = clang++
|
||||
|
||||
GIT_HASH ?= $(shell [ -d .git ] && git rev-parse HEAD)
|
||||
|
||||
CPPFLAGS = -g -Imimalloc/include -pthread -std=c++20 \
|
||||
-Wno-deprecated-volatile \
|
||||
-DMOLD_VERSION=\"0.9.2\" \
|
||||
-DGIT_HASH=\"$(GIT_HASH)\" \
|
||||
$(EXTRA_CPPFLAGS)
|
||||
LDFLAGS += $(EXTRA_LDFLAGS)
|
||||
LIBS = -Wl,-as-needed -lcrypto -pthread -ltbb -lz -lxxhash -ldl
|
||||
OBJS = main.o object_file.o input_sections.o output_chunks.o \
|
||||
mapfile.o perf.o linker_script.o archive_file.o output_file.o \
|
||||
subprocess.o gc_sections.o icf.o symbols.o cmdline.o filepath.o \
|
||||
passes.o tar.o compress.o memory_mapped_file.o relocatable.o \
|
||||
arch_x86_64.o arch_i386.o
|
||||
|
||||
PREFIX ?= /usr
|
||||
DEBUG ?= 0
|
||||
LTO ?= 0
|
||||
ASAN ?= 0
|
||||
TSAN ?= 0
|
||||
|
||||
ifeq ($(DEBUG), 1)
|
||||
CPPFLAGS += -O0
|
||||
else
|
||||
CPPFLAGS += -O2
|
||||
endif
|
||||
|
||||
ifeq ($(LTO), 1)
|
||||
CPPFLAGS += -flto -O3
|
||||
LDFLAGS += -flto
|
||||
endif
|
||||
|
||||
ifeq ($(ASAN), 1)
|
||||
CPPFLAGS += -fsanitize=address
|
||||
LDFLAGS += -fsanitize=address
|
||||
else
|
||||
# By default, we want to use mimalloc as a memory allocator.
|
||||
# Since replacing the standard malloc is not compatible with ASAN,
|
||||
# we do that only when ASAN is not enabled.
|
||||
ifdef SYSTEM_MIMALLOC
|
||||
LIBS += -lmimalloc
|
||||
else
|
||||
MIMALLOC_LIB = mimalloc/out/release/libmimalloc.a
|
||||
LIBS += -Wl,-whole-archive $(MIMALLOC_LIB) -Wl,-no-whole-archive
|
||||
endif
|
||||
endif
|
||||
|
||||
ifeq ($(TSAN), 1)
|
||||
CPPFLAGS += -fsanitize=thread
|
||||
LDFLAGS += -fsanitize=thread
|
||||
endif
|
||||
|
||||
all: mold mold-wrapper.so
|
||||
|
||||
mold: $(OBJS) $(MIMALLOC_LIB)
|
||||
$(CXX) $(CXXFLAGS) $(OBJS) -o $@ $(LDFLAGS) $(LIBS)
|
||||
|
||||
mold-wrapper.so: mold-wrapper.c Makefile
|
||||
$(CC) -fPIC -shared -o $@ $< -ldl
|
||||
|
||||
$(OBJS): mold.h elf.h Makefile
|
||||
|
||||
$(MIMALLOC_LIB):
|
||||
mkdir -p mimalloc/out/release
|
||||
(cd mimalloc/out/release; CFLAGS=-DMI_USE_ENVIRON=0 cmake ../..)
|
||||
$(MAKE) -C mimalloc/out/release
|
||||
|
||||
test tests check: all
|
||||
$(MAKE) -C test --output-sync --no-print-directory
|
||||
|
||||
install: all
|
||||
install -m 755 -d $(DESTDIR)$(PREFIX)/bin
|
||||
install -m 755 mold $(DESTDIR)$(PREFIX)/bin
|
||||
strip $(DESTDIR)$(PREFIX)/bin/mold
|
||||
|
||||
install -m 755 -d $(DESTDIR)$(PREFIX)/lib/mold
|
||||
install -m 644 mold-wrapper.so $(DESTDIR)$(PREFIX)/lib/mold
|
||||
strip $(DESTDIR)$(PREFIX)/lib/mold/mold-wrapper.so
|
||||
|
||||
install -m 755 -d $(DESTDIR)$(PREFIX)/share/man/man1
|
||||
install -m 644 docs/mold.1 $(DESTDIR)$(PREFIX)/share/man/man1
|
||||
rm -f $(DESTDIR)$(PREFIX)/share/man/man1/mold.1.gz
|
||||
gzip -9 $(DESTDIR)$(PREFIX)/share/man/man1/mold.1
|
||||
|
||||
uninstall:
|
||||
rm -f $(DESTDIR)$(PREFIX)/bin/mold
|
||||
rm -f $(DESTDIR)$(PREFIX)/share/man/man1/mold.1.gz
|
||||
rm -rf $(DESTDIR)$(PREFIX)/lib/mold
|
||||
|
||||
clean:
|
||||
rm -rf *.o *~ mold mold-wrapper.so test/tmp
|
||||
|
||||
.PHONY: all test tests check clean $(TESTS)
|
||||
@@ -1,276 +1,115 @@
|
||||
# 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 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.
|
||||
mold is a high performance drop-in replacement for existing Unix
|
||||
linkers. It is several times faster than LLVM lld linker, the (then-)
|
||||
fastest open-source linker which I originally created a few years ago.
|
||||
Here is a performance comparison of GNU gold, LLVM lld and mold for
|
||||
linking final executables of major large programs.
|
||||
|
||||
mold supports x86-64, i386, ARM 32/64, RISC-V 32/64, PowerPC 32/64, s390x,
|
||||
LoongArch 32/64, SPARC64, m68k, and SH-4.
|
||||
| Program (linker output size) | GNU gold | LLVM lld | mold | mold w/ preloading
|
||||
|-------------------------------|----------|----------|-------|-------------------
|
||||
| Firefox 87 (1.6 GiB) | 29.2s | 6.16s | 1.69s | 0.79s
|
||||
| Chrome 86 (1.9 GiB) | 54.5s | 11.7s | 1.85s | 0.97s
|
||||
| Clang 13 (3.1 GiB) | 59.4s | 5.68s | 2.76s | 0.86s
|
||||
|
||||
## Why does linking speed matter?
|
||||
(These nubmers are measured on an AMD Threadripper 3990X 64-core
|
||||
machine with 32 threads enabled. All programs are built with debug
|
||||
info enabled.)
|
||||
|
||||
If you are using a compiled language such as C, C++, or Rust, a build consists
|
||||
of two phases. In the first phase, a compiler compiles source files into
|
||||
object files (`.o` files). In the second phase, a linker takes all object
|
||||
files and combines them into a single executable or shared library file.
|
||||
Let me explain the "w/ preloading" column. mold supports the file
|
||||
preloading feature. That is, if you run mold with `-preload` flag
|
||||
along with other command line flags, it becomes a daemon and halts
|
||||
after parsing input files. Then, if you invoke mold with the same
|
||||
command line options (except `-preload` flag), it tells the daemon to
|
||||
reload only updated files and proceed. With this feature enabled, and
|
||||
if most of the input files haven't been updated, mold achieve a
|
||||
near-`cp` performance or even exceeds it, as the throughput of file
|
||||
copy using the `cp` command is about 2 GiB/s on my machine.
|
||||
|
||||
The second phase can be time-consuming if your build output is large. mold can
|
||||
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.
|
||||
So, mold is extremely fast per-se and even faster with a bit of cheating.
|
||||
|
||||
## Benchmark
|
||||
Why is mold so fast? One reason is because it simply uses faster
|
||||
algorithms and efficient data structures than other linkers do.
|
||||
The other reason is that the new linker is highly parallelized.
|
||||
|
||||
Here is a performance comparison of lld, wild, and mold when linking nine
|
||||
large programs on two machines:
|
||||
Here is a side-by-side comparison of per-core CPU usage of lld (left)
|
||||
and mold (right). They are linking the same program, Chromium
|
||||
executable.
|
||||
|
||||
- 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.
|
||||
As you can see, mold uses all available cores throughout its execution
|
||||
and finishes quickly. On the other hand, lld failed to use available
|
||||
cores most of the time. On this demo, the maximum parallelism is
|
||||
artificially capped to 16 so that the bars fit in the GIF.
|
||||
|
||||
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).
|
||||
Currently, mold is being developed with Linux/x86-64 as the primary
|
||||
target platform. mold can link many user-land programs including large
|
||||
ones such as web browsers for that target. It also has preliminary
|
||||
Linux/i386 support. Supporting other OSes and ISAs are planned after
|
||||
Linux/x86-64 support is complete.
|
||||
|
||||
**AMD Ryzen Threadripper 7980X, debug builds**
|
||||
## How to build
|
||||
|
||||
| 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
|
||||
mold is written in C++20, so you need a very recent version of GCC or
|
||||
Clang. I'm using Ubuntu 20.04 as a development platform. In that
|
||||
environment, you can build mold by the following commands.
|
||||
|
||||
**AMD Ryzen Threadripper 7980X, release builds**
|
||||
|
||||
| Program (output size) | lld | wild | mold | wild/mold | lld/mold
|
||||
|-----------------------------------|-------|-------|-------|-----------|---------
|
||||
| Blender 5.2 (0.24 GiB) | 0.85s | 0.30s | 0.20s | 1.5x | 4.2x
|
||||
| Chromium 145 (0.58 GiB) | 6.48s | N/A | 0.64s | N/A | 10.2x
|
||||
| Chromium 145 ARM64 (0.60 GiB) | 7.91s | N/A | 0.73s | N/A | 10.8x
|
||||
| Clang 21 (0.21 GiB) | 0.53s | 0.23s | 0.11s | 2.2x | 5.0x
|
||||
| ClickHouse 26.1 (1.23 GiB) | 3.18s | 2.07s | 0.41s | 5.0x | 7.7x
|
||||
| Firefox 149 (0.22 GiB) | 1.01s | 0.41s | 0.21s | 2.0x | 4.9x
|
||||
| Godot 4.6 (0.15 GiB) | 0.44s | 0.21s | 0.08s | 2.7x | 5.8x
|
||||
| LibreOffice 26.2 (0.19 GiB) | 1.13s | 0.57s | 0.19s | 3.0x | 6.0x
|
||||
| PyTorch 2.9 (0.31 GiB) | 0.68s | 0.32s | 0.15s | 2.2x | 4.7x
|
||||
| TensorFlow 2.21 (0.73 GiB) | 9.62s | N/A | 0.70s | N/A | 13.7x
|
||||
|
||||
**Apple M1 Ultra, debug builds**
|
||||
|
||||
| Program (output size) | lld | wild | mold | wild/mold | lld/mold
|
||||
|-----------------------------------|--------|-------|-------|-----------|---------
|
||||
| Blender 5.2 (2.46 GiB) | 3.21s | 1.81s | 1.56s | 1.2x | 2.1x
|
||||
| Chromium 145 (4.51 GiB) | 9.54s | 3.49s | 2.22s | 1.6x | 4.3x
|
||||
| Chromium 145 ARM64 (4.76 GiB) | 12.67s | N/A | 2.31s | N/A | 5.5x
|
||||
| Clang 21 (4.19 GiB) | 4.40s | 2.78s | 2.96s | 0.9x | 1.5x
|
||||
| ClickHouse 26.1 (5.58 GiB) | 4.92s | 3.50s | 1.71s | 2.1x | 2.9x
|
||||
| Firefox 149 (2.37 GiB) | 3.21s | N/A | 1.12s | N/A | 2.9x
|
||||
| Firefox 149 ARM64 (2.43 GiB) | 4.13s | N/A | 1.12s | N/A | 3.7x
|
||||
| Godot 4.6 (1.01 GiB) | 1.12s | 0.81s | 0.62s | 1.3x | 1.8x
|
||||
| LibreOffice 26.2 (0.98 GiB) | 2.08s | 0.94s | 0.63s | 1.5x | 3.3x
|
||||
| PyTorch 2.9 (3.51 GiB) | 3.07s | 2.10s | 1.45s | 1.4x | 2.1x
|
||||
| TensorFlow 2.21 (9.55 GiB) | 43.68s | N/A | 4.43s | N/A | 9.9x
|
||||
|
||||
**Apple M1 Ultra, release builds**
|
||||
|
||||
| Program (output size) | lld | wild | mold | wild/mold | lld/mold
|
||||
|-----------------------------------|-------|-------|-------|-----------|---------
|
||||
| Blender 5.2 (0.24 GiB) | 0.56s | 0.20s | 0.25s | 0.8x | 2.3x
|
||||
| Chromium 145 (0.58 GiB) | 4.25s | N/A | 0.78s | N/A | 5.5x
|
||||
| Chromium 145 ARM64 (0.60 GiB) | 5.17s | N/A | 0.91s | N/A | 5.7x
|
||||
| Clang 21 (0.21 GiB) | 0.30s | 0.15s | 0.14s | 1.0x | 2.1x
|
||||
| ClickHouse 26.1 (1.23 GiB) | 1.94s | 0.95s | 0.55s | 1.7x | 3.5x
|
||||
| Firefox 149 (0.22 GiB) | 0.56s | 0.23s | 0.20s | 1.1x | 2.7x
|
||||
| Godot 4.6 (0.15 GiB) | 0.26s | 0.11s | 0.11s | 1.0x | 2.3x
|
||||
| LibreOffice 26.2 (0.19 GiB) | 0.62s | 0.30s | 0.23s | 1.3x | 2.7x
|
||||
| PyTorch 2.9 (0.31 GiB) | 0.43s | 0.22s | 0.17s | 1.3x | 2.5x
|
||||
| TensorFlow 2.21 (0.73 GiB) | 8.07s | N/A | 0.68s | N/A | 11.9x
|
||||
|
||||
N/A indicates that the linker cannot link that program. wild's Chromium
|
||||
release links are also marked N/A because wild does not implement `--icf=all`
|
||||
and links without identical code folding.
|
||||
|
||||
## Why is mold so fast?
|
||||
|
||||
mold owes its speed to pervasive parallelism and to efficient data structures
|
||||
and algorithms. For details, read our paper "mold: A Massively Parallel
|
||||
Linker" (ASPLOS 2027), available at https://arxiv.org/abs/2608.23228.
|
||||
|
||||
## Installation
|
||||
|
||||
Binary packages for the following systems are currently available:
|
||||
|
||||
[](https://repology.org/project/mold/versions)
|
||||
|
||||
Prebuilt binaries for Linux on x86-64, ARM64, ARM32, RISC-V, PPC64LE, s390x,
|
||||
and LoongArch are also attached to each
|
||||
[GitHub release](https://github.com/rui314/mold/releases).
|
||||
|
||||
## How to Build
|
||||
|
||||
mold is written in Rust and built with Cargo. It requires Rust 1.95 or later;
|
||||
if your system's Rust is older, install the stable toolchain with
|
||||
[rustup](https://rustup.rs/). You also need Git and a C compiler such as GCC or
|
||||
Clang, which you can install with your system's package manager.
|
||||
|
||||
### Compile mold
|
||||
|
||||
```shell
|
||||
git clone https://github.com/rui314/mold.git
|
||||
cd mold
|
||||
cargo build --release
|
||||
```
|
||||
$ sudo apt-get install build-essential libstdc++-10-dev cmake clang libssl-dev zlib1g-dev libxxhash-dev libtbb-dev git
|
||||
$ git clone https://github.com/rui314/mold.git
|
||||
$ cd mold
|
||||
$ make
|
||||
```
|
||||
|
||||
The executable is `target/release/mold`.
|
||||
The last `make` command creates `mold` executable.
|
||||
|
||||
### Install mold
|
||||
If you don't have Ubuntu 20.04, or if for any reason `make` in the
|
||||
above commands doesn't work for you, you can use Docker to build it in
|
||||
a Docker environment. To do so, just run `./build-static.sh` in this
|
||||
directory. The script creates a Ubuntu 20.04 Docker image, install
|
||||
necessary tools and libraries to it and build mold as a static binary.
|
||||
|
||||
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`.
|
||||
`make test` depends on a few more packages. To install, run the following commands:
|
||||
|
||||
You can also run `target/release/mold` directly without installing it.
|
||||
```
|
||||
$ sudo dpkg --add-architecture i386
|
||||
$ sudo apt-get install dwarfdump libc6-dev:i386 lib32gcc-10-dev gcc-multilib
|
||||
```
|
||||
|
||||
## How to use
|
||||
|
||||
<details><summary>A classic way to use mold</summary>
|
||||
On Unix, the linker command (which is usually `/usr/bin/ld`) is
|
||||
invoked indirectly by `cc` (or `gcc` or `clang`), which is typically
|
||||
in turn indirectly invoked by `make` or some other build system
|
||||
command. It is sometimes very hard to pass an appropriate command line
|
||||
option to `cc` to specify an alternative linker.
|
||||
|
||||
On Unix, the linker command (usually `/usr/bin/ld`) is indirectly invoked by
|
||||
the compiler driver (typically `cc`, `gcc`, or `clang`), which is in turn
|
||||
indirectly invoked by `make` or other build system commands.
|
||||
To deal with the situation, mold has a feature to intercept all
|
||||
invocations of `/usr/bin/ld`, `/usr/bin/ld.lld` or `/usr/bin/ld.gold`
|
||||
and redirect it to itself. To use the feature, run `make` (or other
|
||||
build command) as a subcommand of mold as follows:
|
||||
|
||||
If you can specify an additional command line option for your compiler driver
|
||||
by modifying the build system's config files, add one of the following flags
|
||||
to use mold instead of `/usr/bin/ld`:
|
||||
|
||||
- For Clang: pass `-fuse-ld=mold`
|
||||
|
||||
- For GCC 12.1.0 or later: pass `-fuse-ld=mold`
|
||||
|
||||
- For GCC before 12.1.0: the `-fuse-ld` option does not accept `mold` as a
|
||||
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 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
|
||||
accept an absolute path as an argument for `-fuse-ld`.
|
||||
|
||||
</details>
|
||||
|
||||
<details><summary>If you are using Rust</summary>
|
||||
|
||||
Create `.cargo/config.toml` in your project directory with the following:
|
||||
|
||||
```toml
|
||||
[target.'cfg(target_os = "linux")']
|
||||
linker = "clang"
|
||||
rustflags = ["-C", "link-arg=-fuse-ld=/path/to/mold"]
|
||||
```
|
||||
$ path/to/mold -run make <make-options-if-any>
|
||||
```
|
||||
|
||||
where `/path/to/mold` is an absolute path to the mold executable. In the
|
||||
example above, we use `clang` as a linker driver since it always accepts the
|
||||
`-fuse-ld` option. If your GCC is recent enough to recognize the option, you
|
||||
may be able to remove the `linker = "clang"` line.
|
||||
Internally, mold invokes a given command with `LD_PRELOAD` environment
|
||||
variable set to its companion shared object file. The shared object
|
||||
file intercepts all function calls to exec-family functions to replace
|
||||
`argv[0]` with `mold` if it is `/usr/bin/ld`, `/usr/bin/ld.gold` or
|
||||
`/usr/bin/ld.lld`.
|
||||
|
||||
```toml
|
||||
[target.'cfg(target_os = "linux")']
|
||||
rustflags = ["-C", "link-arg=-fuse-ld=mold"]
|
||||
```
|
||||
Alternatively, you can pass `-fuse-ld=<absolute-path-to-mold-executable>`
|
||||
to a linker command line. Since GCC doesn't support that option,
|
||||
I recommend using clang.
|
||||
|
||||
If you want to use mold for all projects, add the above snippet to
|
||||
`~/.cargo/config.toml`.
|
||||
|
||||
</details>
|
||||
|
||||
<details><summary>If you are using Nim</summary>
|
||||
|
||||
Create `config.nims` in your project directory with the following:
|
||||
|
||||
```nim
|
||||
when findExe("mold").len > 0 and defined(linux):
|
||||
switch("passL", "-fuse-ld=mold")
|
||||
```
|
||||
|
||||
where `mold` must be included in the `PATH` environment variable. In this
|
||||
example, `gcc` is used as the linker driver. Use the `-fuse-ld` option if your
|
||||
GCC is recent enough to recognize this option.
|
||||
|
||||
If you want to use mold for all projects, add the above snippet to
|
||||
`~/.config/config.nims`.
|
||||
|
||||
</details>
|
||||
|
||||
<details><summary>If you are using Conan package manager</summary>
|
||||
|
||||
You can configure [Conan](https://github.com/conan-io) to download the latest
|
||||
version of `mold` and use it as the linker when building your dependencies and
|
||||
projects from source. Please see the instructions [here](https://conan.io/center/recipes/mold).
|
||||
|
||||
</details>
|
||||
|
||||
<details><summary>mold -run</summary>
|
||||
|
||||
It is sometimes very hard to pass an appropriate command line option to `cc`
|
||||
to specify an alternative linker. To address this situation, mold has a
|
||||
feature to intercept all invocations of `ld`, `ld.bfd`, `ld.lld`, or `ld.gold`
|
||||
and redirect them to itself. To use this feature, run `make` (or another build
|
||||
command) as a subcommand of mold as follows:
|
||||
|
||||
```shell
|
||||
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
|
||||
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.
|
||||
|
||||
</details>
|
||||
|
||||
<details><summary>GitHub Actions</summary>
|
||||
|
||||
You can use our [setup-mold](https://github.com/rui314/setup-mold) GitHub
|
||||
Action to speed up GitHub-hosted continuous builds. Although GitHub Actions
|
||||
run on a 4 core machine, mold is still significantly faster than the default
|
||||
GNU linker, especially when linking large programs.
|
||||
|
||||
</details>
|
||||
|
||||
<details><summary>Verify that you are using mold</summary>
|
||||
|
||||
mold leaves its identification string in the `.comment` section of an output
|
||||
mold leaves its identification string in `.comment` section in an output
|
||||
file. You can print it out to verify that you are actually using mold.
|
||||
|
||||
```shell
|
||||
```
|
||||
$ readelf -p .comment <executable-file>
|
||||
|
||||
String dump of section '.comment':
|
||||
@@ -278,56 +117,505 @@ String dump of section '.comment':
|
||||
[ 2b] mold 9a1679b47d9b22012ec7dfbda97c8983956716f7
|
||||
```
|
||||
|
||||
If `mold` is present in the `.comment` section, the file was created by mold.
|
||||
If `mold` is in `.comment`, the file is created by mold.
|
||||
|
||||
</details>
|
||||
# Design and implementation of mold
|
||||
|
||||
<details><summary>Online manual</summary>
|
||||
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.
|
||||
|
||||
Since mold is a drop-in replacement, you should be able to use it without
|
||||
reading its manual. However, if you need it, [mold's man page](docs/mold.md)
|
||||
is available online. You can read the same manual by running `man mold`.
|
||||
## Motivation
|
||||
|
||||
</details>
|
||||
Here is why I'm writing a new linker:
|
||||
|
||||
## Stability
|
||||
- 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.
|
||||
|
||||
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.
|
||||
- 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.
|
||||
|
||||
## Sponsors
|
||||
- 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.
|
||||
|
||||
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).
|
||||
## Basic design
|
||||
|
||||
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:
|
||||
- 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.
|
||||
|
||||
### Corporate sponsors
|
||||
- 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.
|
||||
|
||||
<a href="https://mercury.com"><img src="docs/mercury-logo.png" align=center height=120 width=400 alt=Mercury></a>
|
||||
- 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.
|
||||
|
||||
<a href="https://cybozu-global.com"><img src="docs/cyboze-logo.png" align=center height=120 width=133 alt=Cybozu></a>
|
||||
- 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).
|
||||
|
||||
<a href="https://www.emergetools.com"><img src="docs/emerge-tools-logo.png" align=center height=120 width=240 alt="Emerge Tools"></a><br>
|
||||
- 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.
|
||||
|
||||
- [G-Research](https://www.gresearch.co.uk)
|
||||
- [Signal Slot Inc.](https://github.com/signal-slot)
|
||||
- [GlareDB](https://github.com/GlareDB)
|
||||
- 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.
|
||||
|
||||
### Individual sponsors
|
||||
- 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.
|
||||
|
||||
- [Wei Wu](https://github.com/lazyparser)
|
||||
- [kyle-elliott](https://github.com/kyle-elliott)
|
||||
- [Bryant Biggs](https://github.com/bryantbiggs)
|
||||
- [kraptor23](https://github.com/kraptor23)
|
||||
- [Jinkyu Yi](https://github.com/jincreator)
|
||||
- [Pedro Navarro](https://github.com/pedronavf)
|
||||
## Compatibility
|
||||
|
||||
- GNU ld, GNU gold and LLVM lld support essentially the same set of
|
||||
command line options and features. mold doesn't have to be
|
||||
completely compatible with them. As long as it can be used for
|
||||
linking large user-land programs, I'm fine with that. It is OK to
|
||||
leave some command line options unimplemented; if mold is blazingly
|
||||
fast, other projects would still be happy to adopt it by modifying
|
||||
their projects' build files.
|
||||
|
||||
- mold emits Linux executables and runs only on Linux. I won't avoid
|
||||
Unix-ism when writing code. I don't want to think about portability
|
||||
until mold becomes a thing that's worth to be ported.
|
||||
|
||||
## 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
|
||||
langauge. But at the same time, we want to satisfy the user needs that
|
||||
are currently satisfied with the linker script langauge. So, what
|
||||
should we do? Here is my observation:
|
||||
|
||||
- Linker script allows to do 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 truely 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 langauge:
|
||||
|
||||
- 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 adddresses.
|
||||
|
||||
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 "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 [Markle
|
||||
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 rate. 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 a 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 stiches them together into a single
|
||||
executable or a shared library image.
|
||||
|
||||
In reality, modern linkers for Unix-like systems are much more
|
||||
compilcated 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 why a particular feature has been added to a linker in the
|
||||
first place.
|
||||
|
||||
1. Original Unix didn't support shared library, and a program was
|
||||
always loaded to a fixed address. An executable was something like
|
||||
a memory dump which 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 other 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 leave a 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 was 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 a 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 achive 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 an output by default.
|
||||
An archive works as supplements 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 '80s, Sun Microsystems, a leading commercial Unix vendor at the
|
||||
time, added a 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 piece 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 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 into 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 to 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 to 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 advise, 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 workaround 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 of improving 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 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.
|
||||
@@ -1,13 +0,0 @@
|
||||
# Architecture crates
|
||||
|
||||
Each subdirectory is a small crate that instantiates the generic ELF or
|
||||
Mach-O linker for one target architecture. Separate crates let Cargo compile
|
||||
the architectures in parallel. Instantiating all targets in one crate leaves
|
||||
a long serial compilation step. A directory is named after its crate without
|
||||
the `mold-` prefix, e.g. `elf-x86_64` holds `mold-elf-x86_64`.
|
||||
|
||||
The ELF linker lives in [`elf/`](../elf/), with target-specific code in
|
||||
[`elf/arch/`](../elf/arch/), and the Mach-O linker in [`macho/`](../macho/),
|
||||
with target-specific code in [`macho/arm64.rs`](../macho/arm64.rs) and
|
||||
[`macho/x86_64.rs`](../macho/x86_64.rs). These crates provide entry points
|
||||
that the [`cli/`](../cli/) executable selects for the input files' target.
|
||||
@@ -1,14 +0,0 @@
|
||||
[package]
|
||||
name = "mold-elf-arm32"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
description = "An internal crate of the mold linker, which instantiates its ELF linker for arm32"
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
mold-elf.workspace = true
|
||||
@@ -1,3 +0,0 @@
|
||||
pub fn link(cmdline: mold_elf::driver::Cmdline) -> Option<&'static str> {
|
||||
mold_elf::driver::link::<mold_elf::arch::Arm32>(cmdline)
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
[package]
|
||||
name = "mold-elf-arm32be"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
description = "An internal crate of the mold linker, which instantiates its ELF linker for arm32be"
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
mold-elf.workspace = true
|
||||
@@ -1,3 +0,0 @@
|
||||
pub fn link(cmdline: mold_elf::driver::Cmdline) -> Option<&'static str> {
|
||||
mold_elf::driver::link::<mold_elf::arch::Arm32Be>(cmdline)
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
[package]
|
||||
name = "mold-elf-arm64"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
description = "An internal crate of the mold linker, which instantiates its ELF linker for arm64"
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
mold-elf.workspace = true
|
||||
@@ -1,3 +0,0 @@
|
||||
pub fn link(cmdline: mold_elf::driver::Cmdline) -> Option<&'static str> {
|
||||
mold_elf::driver::link::<mold_elf::arch::Arm64>(cmdline)
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
[package]
|
||||
name = "mold-elf-arm64be"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
description = "An internal crate of the mold linker, which instantiates its ELF linker for arm64be"
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
mold-elf.workspace = true
|
||||
@@ -1,3 +0,0 @@
|
||||
pub fn link(cmdline: mold_elf::driver::Cmdline) -> Option<&'static str> {
|
||||
mold_elf::driver::link::<mold_elf::arch::Arm64Be>(cmdline)
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
[package]
|
||||
name = "mold-elf-i386"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
description = "An internal crate of the mold linker, which instantiates its ELF linker for i386"
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
mold-elf.workspace = true
|
||||
@@ -1,3 +0,0 @@
|
||||
pub fn link(cmdline: mold_elf::driver::Cmdline) -> Option<&'static str> {
|
||||
mold_elf::driver::link::<mold_elf::arch::I386>(cmdline)
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
[package]
|
||||
name = "mold-elf-loongarch32"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
description = "An internal crate of the mold linker, which instantiates its ELF linker for loongarch32"
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
mold-elf.workspace = true
|
||||
@@ -1,3 +0,0 @@
|
||||
pub fn link(cmdline: mold_elf::driver::Cmdline) -> Option<&'static str> {
|
||||
mold_elf::driver::link::<mold_elf::arch::LoongArch32>(cmdline)
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
[package]
|
||||
name = "mold-elf-loongarch64"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
description = "An internal crate of the mold linker, which instantiates its ELF linker for loongarch64"
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
mold-elf.workspace = true
|
||||
@@ -1,3 +0,0 @@
|
||||
pub fn link(cmdline: mold_elf::driver::Cmdline) -> Option<&'static str> {
|
||||
mold_elf::driver::link::<mold_elf::arch::LoongArch64>(cmdline)
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
[package]
|
||||
name = "mold-elf-m68k"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
description = "An internal crate of the mold linker, which instantiates its ELF linker for m68k"
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
mold-elf.workspace = true
|
||||
@@ -1,3 +0,0 @@
|
||||
pub fn link(cmdline: mold_elf::driver::Cmdline) -> Option<&'static str> {
|
||||
mold_elf::driver::link::<mold_elf::arch::M68k>(cmdline)
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
[package]
|
||||
name = "mold-elf-ppc32"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
description = "An internal crate of the mold linker, which instantiates its ELF linker for ppc32"
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
mold-elf.workspace = true
|
||||
@@ -1,3 +0,0 @@
|
||||
pub fn link(cmdline: mold_elf::driver::Cmdline) -> Option<&'static str> {
|
||||
mold_elf::driver::link::<mold_elf::arch::Ppc32>(cmdline)
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
[package]
|
||||
name = "mold-elf-ppc64v1"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
description = "An internal crate of the mold linker, which instantiates its ELF linker for ppc64v1"
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
mold-elf.workspace = true
|
||||
@@ -1,3 +0,0 @@
|
||||
pub fn link(cmdline: mold_elf::driver::Cmdline) -> Option<&'static str> {
|
||||
mold_elf::driver::link::<mold_elf::arch::Ppc64V1>(cmdline)
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
[package]
|
||||
name = "mold-elf-ppc64v2"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
description = "An internal crate of the mold linker, which instantiates its ELF linker for ppc64v2"
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
mold-elf.workspace = true
|
||||
@@ -1,3 +0,0 @@
|
||||
pub fn link(cmdline: mold_elf::driver::Cmdline) -> Option<&'static str> {
|
||||
mold_elf::driver::link::<mold_elf::arch::Ppc64V2>(cmdline)
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
[package]
|
||||
name = "mold-elf-riscv32"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
description = "An internal crate of the mold linker, which instantiates its ELF linker for riscv32"
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
mold-elf.workspace = true
|
||||
@@ -1,3 +0,0 @@
|
||||
pub fn link(cmdline: mold_elf::driver::Cmdline) -> Option<&'static str> {
|
||||
mold_elf::driver::link::<mold_elf::arch::Riscv32>(cmdline)
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
[package]
|
||||
name = "mold-elf-riscv32be"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
description = "An internal crate of the mold linker, which instantiates its ELF linker for riscv32be"
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
mold-elf.workspace = true
|
||||
@@ -1,3 +0,0 @@
|
||||
pub fn link(cmdline: mold_elf::driver::Cmdline) -> Option<&'static str> {
|
||||
mold_elf::driver::link::<mold_elf::arch::Riscv32Be>(cmdline)
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
[package]
|
||||
name = "mold-elf-riscv64"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
description = "An internal crate of the mold linker, which instantiates its ELF linker for riscv64"
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
mold-elf.workspace = true
|
||||
@@ -1,3 +0,0 @@
|
||||
pub fn link(cmdline: mold_elf::driver::Cmdline) -> Option<&'static str> {
|
||||
mold_elf::driver::link::<mold_elf::arch::Riscv64>(cmdline)
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
[package]
|
||||
name = "mold-elf-riscv64be"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
description = "An internal crate of the mold linker, which instantiates its ELF linker for riscv64be"
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
mold-elf.workspace = true
|
||||
@@ -1,3 +0,0 @@
|
||||
pub fn link(cmdline: mold_elf::driver::Cmdline) -> Option<&'static str> {
|
||||
mold_elf::driver::link::<mold_elf::arch::Riscv64Be>(cmdline)
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
[package]
|
||||
name = "mold-elf-s390x"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
description = "An internal crate of the mold linker, which instantiates its ELF linker for s390x"
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
mold-elf.workspace = true
|
||||
@@ -1,3 +0,0 @@
|
||||
pub fn link(cmdline: mold_elf::driver::Cmdline) -> Option<&'static str> {
|
||||
mold_elf::driver::link::<mold_elf::arch::S390x>(cmdline)
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
[package]
|
||||
name = "mold-elf-sh4"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
description = "An internal crate of the mold linker, which instantiates its ELF linker for sh4"
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
mold-elf.workspace = true
|
||||
@@ -1,3 +0,0 @@
|
||||
pub fn link(cmdline: mold_elf::driver::Cmdline) -> Option<&'static str> {
|
||||
mold_elf::driver::link::<mold_elf::arch::Sh4>(cmdline)
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
[package]
|
||||
name = "mold-elf-sh4be"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
description = "An internal crate of the mold linker, which instantiates its ELF linker for sh4be"
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
mold-elf.workspace = true
|
||||
@@ -1,3 +0,0 @@
|
||||
pub fn link(cmdline: mold_elf::driver::Cmdline) -> Option<&'static str> {
|
||||
mold_elf::driver::link::<mold_elf::arch::Sh4Be>(cmdline)
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
[package]
|
||||
name = "mold-elf-sparc64"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
description = "An internal crate of the mold linker, which instantiates its ELF linker for sparc64"
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
mold-elf.workspace = true
|
||||
@@ -1,3 +0,0 @@
|
||||
pub fn link(cmdline: mold_elf::driver::Cmdline) -> Option<&'static str> {
|
||||
mold_elf::driver::link::<mold_elf::arch::Sparc64>(cmdline)
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
[package]
|
||||
name = "mold-elf-x86_64"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
description = "An internal crate of the mold linker, which instantiates its ELF linker for x86_64"
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
mold-elf.workspace = true
|
||||
@@ -1,3 +0,0 @@
|
||||
pub fn link(cmdline: mold_elf::driver::Cmdline) -> Option<&'static str> {
|
||||
mold_elf::driver::link::<mold_elf::arch::X86_64>(cmdline)
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
[package]
|
||||
name = "mold-macho-arm64"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
description = "An internal crate of the mold linker, which instantiates its Mach-O linker for arm64"
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
mold-macho.workspace = true
|
||||
@@ -1,7 +0,0 @@
|
||||
//! The linker instantiated for arm64. Every target has a crate like this
|
||||
//! one, so that the compiler can build the targets in parallel.
|
||||
|
||||
/// Links for this target.
|
||||
pub fn link(parsed: mold_macho::driver::ParsedArgs) {
|
||||
mold_macho::driver::link::<mold_macho::arch::Arm64>(parsed)
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
[package]
|
||||
name = "mold-macho-x86_64"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
description = "An internal crate of the mold linker, which instantiates its Mach-O linker for x86_64"
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
mold-macho.workspace = true
|
||||
@@ -1,7 +0,0 @@
|
||||
//! The linker instantiated for x86_64. Every target has a crate like this
|
||||
//! one, so that the compiler can build the targets in parallel.
|
||||
|
||||
/// Links for this target.
|
||||
pub fn link(parsed: mold_macho::driver::ParsedArgs) {
|
||||
mold_macho::driver::link::<mold_macho::arch::X86_64>(parsed)
|
||||
}
|
||||
+450
@@ -0,0 +1,450 @@
|
||||
#include "mold.h"
|
||||
|
||||
enum {
|
||||
R_TLS_GOTIE = R_END + 1,
|
||||
R_TLS_LE,
|
||||
R_TLS_IE,
|
||||
R_TLS_GD,
|
||||
R_TLS_LD,
|
||||
R_TLS_GOTDESC,
|
||||
R_TLS_GOTDESC_RELAX,
|
||||
R_TLS_DESC_CALL_RELAX,
|
||||
R_TPOFF,
|
||||
};
|
||||
|
||||
template <>
|
||||
void PltSection<I386>::copy_buf(Context<I386> &ctx) {
|
||||
u8 *buf = ctx.buf + this->shdr.sh_offset;
|
||||
|
||||
// Write PLT header
|
||||
if (ctx.arg.pic) {
|
||||
static const u8 plt0[] = {
|
||||
0xff, 0xb3, 0x04, 0, 0, 0, // pushl 4(%ebx)
|
||||
0xff, 0xa3, 0x08, 0, 0, 0, // jmp *8(%ebx)
|
||||
0x90, 0x90, 0x90, 0x90, // nop
|
||||
};
|
||||
memcpy(buf, plt0, sizeof(plt0));
|
||||
} else {
|
||||
static const u8 plt0[] = {
|
||||
0xff, 0x35, 0, 0, 0, 0, // pushl (GOTPLT+4)
|
||||
0xff, 0x25, 0, 0, 0, 0, // jmp *(GOTPLT+8)
|
||||
0x90, 0x90, 0x90, 0x90, // nop
|
||||
};
|
||||
memcpy(buf, plt0, sizeof(plt0));
|
||||
*(u32 *)(buf + 2) = ctx.gotplt->shdr.sh_addr + 4;
|
||||
*(u32 *)(buf + 8) = ctx.gotplt->shdr.sh_addr + 8;
|
||||
}
|
||||
|
||||
// Write PLT entries
|
||||
i64 relplt_idx = 0;
|
||||
|
||||
for (Symbol<I386> *sym : symbols) {
|
||||
u8 *ent = buf + sym->get_plt_idx(ctx) * I386::plt_size;
|
||||
|
||||
if (ctx.arg.pic) {
|
||||
static const u8 data[] = {
|
||||
0xff, 0xa3, 0, 0, 0, 0, // jmp *foo@GOT(%ebx)
|
||||
0x68, 0, 0, 0, 0, // pushl $reloc_offset
|
||||
0xe9, 0, 0, 0, 0, // jmp .PLT0@PC
|
||||
};
|
||||
memcpy(ent, data, sizeof(data));
|
||||
*(u32 *)(ent + 2) = sym->get_gotplt_addr(ctx) - ctx.gotplt->shdr.sh_addr;
|
||||
} else {
|
||||
static const u8 data[] = {
|
||||
0xff, 0x25, 0, 0, 0, 0, // jmp *foo@GOT
|
||||
0x68, 0, 0, 0, 0, // pushl $reloc_offset
|
||||
0xe9, 0, 0, 0, 0, // jmp .PLT0@PC
|
||||
};
|
||||
memcpy(ent, data, sizeof(data));
|
||||
*(u32 *)(ent + 2) = sym->get_gotplt_addr(ctx);
|
||||
}
|
||||
|
||||
*(u32 *)(ent + 7) = relplt_idx++ * sizeof(ElfRel<I386>);
|
||||
*(u32 *)(ent + 12) = this->shdr.sh_addr - sym->get_plt_addr(ctx) - 16;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void PltGotSection<I386>::copy_buf(Context<I386> &ctx) {
|
||||
u8 *buf = ctx.buf + this->shdr.sh_offset;
|
||||
|
||||
if (ctx.arg.pic) {
|
||||
static const u8 data[] = {
|
||||
0xff, 0xa3, 0, 0, 0, 0, // jmp *foo@GOT(%ebx)
|
||||
0x66, 0x90, // nop
|
||||
};
|
||||
|
||||
for (i64 i = 0; i < symbols.size(); i++) {
|
||||
u8 *ent = buf + i * sizeof(data);
|
||||
memcpy(ent, data, sizeof(data));
|
||||
*(u32 *)(ent + 2) = symbols[i]->get_got_addr(ctx) - ctx.gotplt->shdr.sh_addr;
|
||||
}
|
||||
} else {
|
||||
static const u8 data[] = {
|
||||
0xff, 0x25, 0, 0, 0, 0, // jmp *foo@GOT
|
||||
0x66, 0x90, // nop
|
||||
};
|
||||
|
||||
for (i64 i = 0; i < symbols.size(); i++) {
|
||||
u8 *ent = buf + i * sizeof(data);
|
||||
memcpy(ent, data, sizeof(data));
|
||||
*(u32 *)(ent + 2) = symbols[i]->get_got_addr(ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void EhFrameSection<I386>::apply_reloc(Context<I386> &ctx,
|
||||
ElfRel<I386> &rel,
|
||||
u64 loc, u64 val) {
|
||||
u8 *base = ctx.buf + this->shdr.sh_offset;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_386_NONE:
|
||||
return;
|
||||
case R_386_32:
|
||||
*(u32 *)(base + loc) = val;
|
||||
return;
|
||||
case R_386_PC32:
|
||||
*(u32 *)(base + loc) = val - this->shdr.sh_addr - loc;
|
||||
return;
|
||||
}
|
||||
unreachable(ctx);
|
||||
}
|
||||
|
||||
static void write_val(Context<I386> &ctx, u64 r_type, u8 *loc, u64 val) {
|
||||
switch (r_type) {
|
||||
case R_386_NONE:
|
||||
return;
|
||||
case R_386_8:
|
||||
case R_386_PC8:
|
||||
*loc += val;
|
||||
return;
|
||||
case R_386_16:
|
||||
case R_386_PC16:
|
||||
*(u16 *)loc += val;
|
||||
return;
|
||||
case R_386_32:
|
||||
case R_386_PC32:
|
||||
case R_386_GOT32:
|
||||
case R_386_GOT32X:
|
||||
case R_386_PLT32:
|
||||
case R_386_GOTOFF:
|
||||
case R_386_GOTPC:
|
||||
case R_386_TLS_LDM:
|
||||
case R_386_TLS_GOTIE:
|
||||
case R_386_TLS_LE:
|
||||
case R_386_TLS_IE:
|
||||
case R_386_TLS_GD:
|
||||
case R_386_TLS_LDO_32:
|
||||
case R_386_SIZE32:
|
||||
case R_386_TLS_GOTDESC:
|
||||
*(u32 *)loc += val;
|
||||
return;
|
||||
}
|
||||
unreachable(ctx);
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<I386>::apply_reloc_alloc(Context<I386> &ctx, u8 *base) {
|
||||
ElfRel<I386> *dynrel = nullptr;
|
||||
std::span<ElfRel<I386>> rels = get_rels(ctx);
|
||||
i64 frag_idx = 0;
|
||||
|
||||
if (ctx.reldyn)
|
||||
dynrel = (ElfRel<I386> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
|
||||
file.reldyn_offset + this->reldyn_offset);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<I386> &rel = rels[i];
|
||||
Symbol<I386> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
const SectionFragmentRef<I386> *ref = nullptr;
|
||||
if (rel_fragments && rel_fragments[frag_idx].idx == i)
|
||||
ref = &rel_fragments[frag_idx++];
|
||||
|
||||
auto write = [&](u64 val) {
|
||||
write_val(ctx, rel.r_type, loc, val);
|
||||
};
|
||||
|
||||
#define S (ref ? ref->frag->get_addr(ctx) : sym.get_addr(ctx))
|
||||
#define A (ref ? ref->addend : 0)
|
||||
#define P (output_section->shdr.sh_addr + offset + rel.r_offset)
|
||||
#define G (sym.get_got_addr(ctx) - ctx.got->shdr.sh_addr)
|
||||
#define GOTPLT ctx.gotplt->shdr.sh_addr
|
||||
|
||||
switch (rel_types[i]) {
|
||||
case R_NONE:
|
||||
break;
|
||||
case R_ABS:
|
||||
write(S + A);
|
||||
break;
|
||||
case R_BASEREL:
|
||||
*dynrel++ = {P, R_386_RELATIVE, 0};
|
||||
*(u32 *)loc += S + A;
|
||||
break;
|
||||
case R_DYN:
|
||||
*dynrel++ = {P, R_386_32, (u32)sym.get_dynsym_idx(ctx)};
|
||||
*(u32 *)loc += A;
|
||||
break;
|
||||
case R_PC:
|
||||
write(S + A - P);
|
||||
break;
|
||||
case R_GOT:
|
||||
write(sym.get_got_addr(ctx) + A - GOTPLT);
|
||||
break;
|
||||
case R_GOTOFF:
|
||||
write(S + A - GOTPLT);
|
||||
break;
|
||||
case R_GOTPC:
|
||||
write(GOTPLT + A - P);
|
||||
break;
|
||||
case R_GOTPCREL:
|
||||
write(G + GOTPLT + A - P);
|
||||
break;
|
||||
case R_TLS_GOTIE:
|
||||
write(sym.get_gottp_addr(ctx) + A - GOTPLT);
|
||||
break;
|
||||
case R_TLS_LE:
|
||||
write(S + A - ctx.tls_end);
|
||||
break;
|
||||
case R_TLS_IE:
|
||||
write(sym.get_gottp_addr(ctx) + A);
|
||||
break;
|
||||
case R_TLS_GD:
|
||||
write(sym.get_tlsgd_addr(ctx) + A - GOTPLT);
|
||||
break;
|
||||
case R_TLS_LD:
|
||||
write(ctx.got->get_tlsld_addr(ctx) + A - GOTPLT);
|
||||
break;
|
||||
case R_TPOFF:
|
||||
write(S + A - ctx.tls_begin);
|
||||
break;
|
||||
case R_TLS_GOTDESC:
|
||||
write(sym.get_tlsdesc_addr(ctx) + A - GOTPLT);
|
||||
break;
|
||||
case R_TLS_GOTDESC_RELAX: {
|
||||
static const u8 insn[] = {
|
||||
0x8d, 0x05, 0, 0, 0, 0, // lea 0, %eax
|
||||
};
|
||||
memcpy(loc - 2, insn, sizeof(insn));
|
||||
write(S + A - ctx.tls_end);
|
||||
break;
|
||||
}
|
||||
case R_TLS_DESC_CALL_RELAX:
|
||||
// call *(%rax) -> nop
|
||||
loc[0] = 0x66;
|
||||
loc[1] = 0x90;
|
||||
break;
|
||||
case R_SIZE:
|
||||
write(sym.esym().st_size + A);
|
||||
break;
|
||||
default:
|
||||
unreachable(ctx);
|
||||
}
|
||||
|
||||
#undef S
|
||||
#undef A
|
||||
#undef P
|
||||
#undef G
|
||||
#undef GOTPLT
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<I386>::apply_reloc_nonalloc(Context<I386> &ctx, u8 *base) {
|
||||
std::span<ElfRel<I386>> rels = get_rels(ctx);
|
||||
i64 frag_idx = 0;
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<I386> &rel = rels[i];
|
||||
if (rel.r_type == R_386_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<I386> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
if (!sym.file) {
|
||||
report_undef(ctx, sym);
|
||||
continue;
|
||||
}
|
||||
|
||||
const SectionFragmentRef<I386> *ref = nullptr;
|
||||
if (rel_fragments && rel_fragments[frag_idx].idx == i)
|
||||
ref = &rel_fragments[frag_idx++];
|
||||
|
||||
auto write = [&](u64 val) {
|
||||
write_val(ctx, rel.r_type, loc, val);
|
||||
};
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_386_8:
|
||||
case R_386_16:
|
||||
case R_386_32:
|
||||
case R_386_PC8:
|
||||
case R_386_PC16:
|
||||
case R_386_PC32:
|
||||
case R_386_GOTPC:
|
||||
if (ref)
|
||||
write(ref->frag->get_addr(ctx) + ref->addend);
|
||||
else
|
||||
write(sym.get_addr(ctx));
|
||||
break;
|
||||
case R_386_GOTOFF:
|
||||
write(sym.get_addr(ctx) - ctx.got->shdr.sh_addr);
|
||||
break;
|
||||
case R_386_TLS_LDO_32:
|
||||
write(sym.get_addr(ctx) - ctx.tls_begin);
|
||||
break;
|
||||
case R_386_SIZE32:
|
||||
write(sym.esym().st_size);
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
|
||||
<< rel_to_string<I386>(rel.r_type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<I386>::scan_relocations(Context<I386> &ctx) {
|
||||
assert(shdr.sh_flags & SHF_ALLOC);
|
||||
|
||||
this->reldyn_offset = file.num_dynrel * sizeof(ElfRel<I386>);
|
||||
std::span<ElfRel<I386>> rels = get_rels(ctx);
|
||||
bool is_writable = (shdr.sh_flags & SHF_WRITE);
|
||||
|
||||
// Scan relocations
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<I386> &rel = rels[i];
|
||||
|
||||
if (rel.r_type == R_386_NONE) {
|
||||
rel_types[i] = R_NONE;
|
||||
break;
|
||||
}
|
||||
|
||||
Symbol<I386> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = (u8 *)(contents.data() + rel.r_offset);
|
||||
|
||||
if (!sym.file) {
|
||||
report_undef(ctx, sym);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sym.get_type() == STT_GNU_IFUNC) {
|
||||
sym.flags |= NEEDS_GOT;
|
||||
sym.flags |= NEEDS_PLT;
|
||||
}
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_386_8:
|
||||
case R_386_16: {
|
||||
Action table[][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ NONE, ERROR, ERROR, ERROR }, // DSO
|
||||
{ NONE, ERROR, ERROR, ERROR }, // PIE
|
||||
{ NONE, NONE, COPYREL, PLT }, // PDE
|
||||
};
|
||||
|
||||
dispatch(ctx, table, R_ABS, i);
|
||||
break;
|
||||
}
|
||||
case R_386_32: {
|
||||
Action table[][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ NONE, BASEREL, DYNREL, DYNREL }, // DSO
|
||||
{ NONE, BASEREL, DYNREL, DYNREL }, // PIE
|
||||
{ NONE, NONE, DYNREL, PLT }, // PDE
|
||||
};
|
||||
|
||||
dispatch(ctx, table, R_ABS, i);
|
||||
break;
|
||||
}
|
||||
case R_386_PC8:
|
||||
case R_386_PC16: {
|
||||
Action table[][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ ERROR, NONE, ERROR, ERROR }, // DSO
|
||||
{ ERROR, NONE, COPYREL, PLT }, // PIE
|
||||
{ NONE, NONE, COPYREL, PLT }, // PDE
|
||||
};
|
||||
|
||||
dispatch(ctx, table, R_PC, i);
|
||||
break;
|
||||
}
|
||||
case R_386_PC32: {
|
||||
Action table[][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ BASEREL, NONE, ERROR, ERROR }, // DSO
|
||||
{ BASEREL, NONE, COPYREL, PLT }, // PIE
|
||||
{ NONE, NONE, COPYREL, PLT }, // PDE
|
||||
};
|
||||
|
||||
dispatch(ctx, table, R_PC, i);
|
||||
break;
|
||||
}
|
||||
case R_386_GOT32:
|
||||
case R_386_GOT32X:
|
||||
sym.flags |= NEEDS_GOT;
|
||||
rel_types[i] = R_GOT;
|
||||
break;
|
||||
case R_386_PLT32:
|
||||
if (sym.is_imported)
|
||||
sym.flags |= NEEDS_PLT;
|
||||
rel_types[i] = R_PC;
|
||||
break;
|
||||
case R_386_GOTOFF:
|
||||
rel_types[i] = R_GOTOFF;
|
||||
break;
|
||||
case R_386_GOTPC:
|
||||
sym.flags |= NEEDS_GOT;
|
||||
rel_types[i] = R_GOTPC;
|
||||
break;
|
||||
case R_386_TLS_GOTIE:
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
rel_types[i] = R_TLS_GOTIE;
|
||||
break;
|
||||
case R_386_TLS_LE:
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
rel_types[i] = R_TLS_LE;
|
||||
break;
|
||||
case R_386_TLS_IE:
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
rel_types[i] = R_TLS_IE;
|
||||
break;
|
||||
case R_386_TLS_GD:
|
||||
sym.flags |= NEEDS_TLSGD;
|
||||
rel_types[i] = R_TLS_GD;
|
||||
break;
|
||||
case R_386_TLS_LDM:
|
||||
sym.flags |= NEEDS_TLSLD;
|
||||
rel_types[i] = R_TLS_LD;
|
||||
break;
|
||||
case R_386_TLS_LDO_32:
|
||||
rel_types[i] = R_TPOFF;
|
||||
break;
|
||||
case R_386_SIZE32:
|
||||
rel_types[i] = R_SIZE;
|
||||
break;
|
||||
case R_386_TLS_GOTDESC:
|
||||
if (ctx.arg.relax && !ctx.arg.shared) {
|
||||
rel_types[i] = R_TLS_GOTDESC_RELAX;
|
||||
} else {
|
||||
sym.flags |= NEEDS_TLSDESC;
|
||||
rel_types[i] = R_TLS_GOTDESC;
|
||||
}
|
||||
break;
|
||||
case R_386_TLS_DESC_CALL:
|
||||
if (ctx.arg.relax && !ctx.arg.shared)
|
||||
rel_types[i] = R_TLS_DESC_CALL_RELAX;
|
||||
else
|
||||
rel_types[i] = R_NONE;
|
||||
break;
|
||||
default:
|
||||
Error(ctx) << *this << ": unknown relocation: "
|
||||
<< rel_to_string<I386>(rel.r_type);
|
||||
}
|
||||
}
|
||||
}
|
||||
+687
@@ -0,0 +1,687 @@
|
||||
#include "mold.h"
|
||||
|
||||
enum {
|
||||
R_GOTPCRELX_RELAX = R_END + 1,
|
||||
R_REX_GOTPCRELX_RELAX,
|
||||
R_TLSGD,
|
||||
R_TLSGD_RELAX_LE,
|
||||
R_TLSLD,
|
||||
R_TLSLD_RELAX_LE,
|
||||
R_DTPOFF,
|
||||
R_DTPOFF_RELAX,
|
||||
R_TPOFF,
|
||||
R_GOTTPOFF,
|
||||
R_GOTTPOFF_RELAX,
|
||||
R_GOTPC_TLSDESC,
|
||||
R_GOTPC_TLSDESC_RELAX_LE,
|
||||
R_TLSDESC_CALL_RELAX,
|
||||
};
|
||||
|
||||
template <>
|
||||
void PltSection<X86_64>::copy_buf(Context<X86_64> &ctx) {
|
||||
u8 *buf = ctx.buf + this->shdr.sh_offset;
|
||||
|
||||
// Write PLT header
|
||||
static const u8 plt0[] = {
|
||||
0xff, 0x35, 0, 0, 0, 0, // pushq GOTPLT+8(%rip)
|
||||
0xff, 0x25, 0, 0, 0, 0, // jmp *GOTPLT+16(%rip)
|
||||
0x0f, 0x1f, 0x40, 0x00, // nop
|
||||
};
|
||||
|
||||
memcpy(buf, plt0, sizeof(plt0));
|
||||
*(u32 *)(buf + 2) = ctx.gotplt->shdr.sh_addr - this->shdr.sh_addr + 2;
|
||||
*(u32 *)(buf + 8) = ctx.gotplt->shdr.sh_addr - this->shdr.sh_addr + 4;
|
||||
|
||||
// Write PLT entries
|
||||
i64 relplt_idx = 0;
|
||||
|
||||
static const u8 data[] = {
|
||||
0xff, 0x25, 0, 0, 0, 0, // jmp *foo@GOTPLT
|
||||
0x68, 0, 0, 0, 0, // push $index_in_relplt
|
||||
0xe9, 0, 0, 0, 0, // jmp PLT[0]
|
||||
};
|
||||
|
||||
for (Symbol<X86_64> *sym : symbols) {
|
||||
u8 *ent = buf + sym->get_plt_idx(ctx) * X86_64::plt_size;
|
||||
memcpy(ent, data, sizeof(data));
|
||||
*(u32 *)(ent + 2) = sym->get_gotplt_addr(ctx) - sym->get_plt_addr(ctx) - 6;
|
||||
*(u32 *)(ent + 7) = relplt_idx++;
|
||||
*(u32 *)(ent + 12) = this->shdr.sh_addr - sym->get_plt_addr(ctx) - 16;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void PltGotSection<X86_64>::copy_buf(Context<X86_64> &ctx) {
|
||||
u8 *buf = ctx.buf + this->shdr.sh_offset;
|
||||
|
||||
static const u8 data[] = {
|
||||
0xff, 0x25, 0, 0, 0, 0, // jmp *foo@GOT
|
||||
0x66, 0x90, // nop
|
||||
};
|
||||
|
||||
for (Symbol<X86_64> *sym : symbols) {
|
||||
u8 *ent = buf + sym->get_pltgot_idx(ctx) * X86_64::pltgot_size;
|
||||
memcpy(ent, data, sizeof(data));
|
||||
*(u32 *)(ent + 2) = sym->get_got_addr(ctx) - sym->get_plt_addr(ctx) - 6;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void EhFrameSection<X86_64>::apply_reloc(Context<X86_64> &ctx,
|
||||
ElfRel<X86_64> &rel,
|
||||
u64 loc, u64 val) {
|
||||
u8 *base = ctx.buf + this->shdr.sh_offset;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_X86_64_NONE:
|
||||
return;
|
||||
case R_X86_64_32:
|
||||
*(u32 *)(base + loc) = val;
|
||||
return;
|
||||
case R_X86_64_64:
|
||||
*(u64 *)(base + loc) = val;
|
||||
return;
|
||||
case R_X86_64_PC32:
|
||||
*(u32 *)(base + loc) = val - this->shdr.sh_addr - loc;
|
||||
return;
|
||||
case R_X86_64_PC64:
|
||||
*(u64 *)(base + loc) = val - this->shdr.sh_addr - loc;
|
||||
return;
|
||||
}
|
||||
unreachable(ctx);
|
||||
}
|
||||
|
||||
static void overflow_check(Context<X86_64> &ctx, InputSection<X86_64> *sec,
|
||||
Symbol<X86_64> &sym, u64 r_type, u64 val) {
|
||||
switch (r_type) {
|
||||
case R_X86_64_8:
|
||||
if (val != (u8)val)
|
||||
Error(ctx) << *sec << ": relocation R_X86_64_8 against " << sym
|
||||
<< " out of range: " << val << " is not in [0, 255]";
|
||||
return;
|
||||
case R_X86_64_PC8:
|
||||
if (val != (i8)val)
|
||||
Error(ctx) << *sec << ": relocation R_X86_64_PC8 against " << sym
|
||||
<< " out of range: " << (i64)val << " is not in [-128, 127]";
|
||||
return;
|
||||
case R_X86_64_16:
|
||||
if (val != (u16)val)
|
||||
Error(ctx) << *sec << ": relocation R_X86_64_16 against " << sym
|
||||
<< " out of range: " << val << " is not in [0, 65535]";
|
||||
return;
|
||||
case R_X86_64_PC16:
|
||||
if (val != (i16)val)
|
||||
Error(ctx) << *sec << ": relocation R_X86_64_PC16 against " << sym
|
||||
<< " out of range: " << (i64)val
|
||||
<< " is not in [-32768, 32767]";
|
||||
return;
|
||||
case R_X86_64_32:
|
||||
if (val != (u32)val)
|
||||
Error(ctx) << *sec << ": relocation R_X86_64_32 against " << sym
|
||||
<< " out of range: " << val << " is not in [0, 4294967296]";
|
||||
return;
|
||||
case R_X86_64_32S:
|
||||
case R_X86_64_PC32:
|
||||
case R_X86_64_GOT32:
|
||||
case R_X86_64_GOTPC32:
|
||||
case R_X86_64_GOTPCREL:
|
||||
case R_X86_64_GOTPCRELX:
|
||||
case R_X86_64_REX_GOTPCRELX:
|
||||
case R_X86_64_PLT32:
|
||||
case R_X86_64_TLSGD:
|
||||
case R_X86_64_TLSLD:
|
||||
case R_X86_64_TPOFF32:
|
||||
case R_X86_64_DTPOFF32:
|
||||
case R_X86_64_GOTTPOFF:
|
||||
case R_X86_64_GOTPC32_TLSDESC:
|
||||
case R_X86_64_SIZE32:
|
||||
case R_X86_64_TLSDESC_CALL:
|
||||
if (val != (i32)val)
|
||||
Error(ctx) << *sec << ": relocation " << rel_to_string<X86_64>(r_type)
|
||||
<< " against " << sym << " out of range: " << (i64)val
|
||||
<< " is not in [-2147483648, 2147483647]";
|
||||
return;
|
||||
case R_X86_64_NONE:
|
||||
case R_X86_64_64:
|
||||
case R_X86_64_PC64:
|
||||
case R_X86_64_TPOFF64:
|
||||
case R_X86_64_DTPOFF64:
|
||||
case R_X86_64_GOT64:
|
||||
case R_X86_64_GOTPCREL64:
|
||||
case R_X86_64_GOTPC64:
|
||||
case R_X86_64_SIZE64:
|
||||
return;
|
||||
}
|
||||
unreachable(ctx);
|
||||
}
|
||||
|
||||
static void write_val(Context<X86_64> &ctx, u64 r_type, u8 *loc, u64 val) {
|
||||
switch (r_type) {
|
||||
case R_X86_64_NONE:
|
||||
return;
|
||||
case R_X86_64_8:
|
||||
case R_X86_64_PC8:
|
||||
*loc = val;
|
||||
return;
|
||||
case R_X86_64_16:
|
||||
case R_X86_64_PC16:
|
||||
*(u16 *)loc = val;
|
||||
return;
|
||||
case R_X86_64_32:
|
||||
case R_X86_64_32S:
|
||||
case R_X86_64_PC32:
|
||||
case R_X86_64_GOT32:
|
||||
case R_X86_64_GOTPC32:
|
||||
case R_X86_64_GOTPCREL:
|
||||
case R_X86_64_GOTPCRELX:
|
||||
case R_X86_64_REX_GOTPCRELX:
|
||||
case R_X86_64_PLT32:
|
||||
case R_X86_64_TLSGD:
|
||||
case R_X86_64_TLSLD:
|
||||
case R_X86_64_TPOFF32:
|
||||
case R_X86_64_DTPOFF32:
|
||||
case R_X86_64_GOTTPOFF:
|
||||
case R_X86_64_GOTPC32_TLSDESC:
|
||||
case R_X86_64_SIZE32:
|
||||
case R_X86_64_TLSDESC_CALL:
|
||||
*(u32 *)loc = val;
|
||||
return;
|
||||
case R_X86_64_64:
|
||||
case R_X86_64_PC64:
|
||||
case R_X86_64_TPOFF64:
|
||||
case R_X86_64_DTPOFF64:
|
||||
case R_X86_64_GOT64:
|
||||
case R_X86_64_GOTPCREL64:
|
||||
case R_X86_64_GOTPC64:
|
||||
case R_X86_64_SIZE64:
|
||||
*(u64 *)loc = val;
|
||||
return;
|
||||
}
|
||||
unreachable(ctx);
|
||||
}
|
||||
|
||||
static u32 relax_gotpcrelx(u8 *loc) {
|
||||
switch ((loc[0] << 8) | loc[1]) {
|
||||
case 0xff15: return 0x90e8; // call *0(%rip) -> call 0
|
||||
case 0xff25: return 0x90e9; // jmp *0(%rip) -> jmp 0
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static u32 relax_rex_gotpcrelx(u8 *loc) {
|
||||
switch ((loc[0] << 16) | (loc[1] << 8) | loc[2]) {
|
||||
case 0x488b05: return 0x488d05; // mov 0(%rip), %rax -> lea 0(%rip), %rax
|
||||
case 0x488b0d: return 0x488d0d; // mov 0(%rip), %rcx -> lea 0(%rip), %rcx
|
||||
case 0x488b15: return 0x488d15; // mov 0(%rip), %rdx -> lea 0(%rip), %rdx
|
||||
case 0x488b1d: return 0x488d1d; // mov 0(%rip), %rbx -> lea 0(%rip), %rbx
|
||||
case 0x488b25: return 0x488d25; // mov 0(%rip), %rsp -> lea 0(%rip), %rsp
|
||||
case 0x488b2d: return 0x488d2d; // mov 0(%rip), %rbp -> lea 0(%rip), %rbp
|
||||
case 0x488b35: return 0x488d35; // mov 0(%rip), %rsi -> lea 0(%rip), %rsi
|
||||
case 0x488b3d: return 0x488d3d; // mov 0(%rip), %rdi -> lea 0(%rip), %rdi
|
||||
case 0x4c8b05: return 0x4c8d05; // mov 0(%rip), %r8 -> lea 0(%rip), %r8
|
||||
case 0x4c8b0d: return 0x4c8d0d; // mov 0(%rip), %r9 -> lea 0(%rip), %r9
|
||||
case 0x4c8b15: return 0x4c8d15; // mov 0(%rip), %r10 -> lea 0(%rip), %r10
|
||||
case 0x4c8b1d: return 0x4c8d1d; // mov 0(%rip), %r11 -> lea 0(%rip), %r11
|
||||
case 0x4c8b25: return 0x4c8d25; // mov 0(%rip), %r12 -> lea 0(%rip), %r12
|
||||
case 0x4c8b2d: return 0x4c8d2d; // mov 0(%rip), %r13 -> lea 0(%rip), %r13
|
||||
case 0x4c8b35: return 0x4c8d35; // mov 0(%rip), %r14 -> lea 0(%rip), %r14
|
||||
case 0x4c8b3d: return 0x4c8d3d; // mov 0(%rip), %r15 -> lea 0(%rip), %r15
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static u32 relax_gottpoff(u8 *loc) {
|
||||
switch ((loc[0] << 16) | (loc[1] << 8) | loc[2]) {
|
||||
case 0x488b05: return 0x48c7c0; // mov 0(%rip), %rax -> mov $0, %rax
|
||||
case 0x488b0d: return 0x48c7c1; // mov 0(%rip), %rcx -> mov $0, %rcx
|
||||
case 0x488b15: return 0x48c7c2; // mov 0(%rip), %rdx -> mov $0, %rdx
|
||||
case 0x488b1d: return 0x48c7c3; // mov 0(%rip), %rbx -> mov $0, %rbx
|
||||
case 0x488b25: return 0x48c7c4; // mov 0(%rip), %rsp -> mov $0, %rsp
|
||||
case 0x488b2d: return 0x48c7c5; // mov 0(%rip), %rbp -> mov $0, %rbp
|
||||
case 0x488b35: return 0x48c7c6; // mov 0(%rip), %rsi -> mov $0, %rsi
|
||||
case 0x488b3d: return 0x48c7c7; // mov 0(%rip), %rdi -> mov $0, %rdi
|
||||
case 0x4c8b05: return 0x49c7c0; // mov 0(%rip), %r8 -> mov $0, %r8
|
||||
case 0x4c8b0d: return 0x49c7c1; // mov 0(%rip), %r9 -> mov $0, %r9
|
||||
case 0x4c8b15: return 0x49c7c2; // mov 0(%rip), %r10 -> mov $0, %r10
|
||||
case 0x4c8b1d: return 0x49c7c3; // mov 0(%rip), %r11 -> mov $0, %r11
|
||||
case 0x4c8b25: return 0x49c7c4; // mov 0(%rip), %r12 -> mov $0, %r12
|
||||
case 0x4c8b2d: return 0x49c7c5; // mov 0(%rip), %r13 -> mov $0, %r13
|
||||
case 0x4c8b35: return 0x49c7c6; // mov 0(%rip), %r14 -> mov $0, %r14
|
||||
case 0x4c8b3d: return 0x49c7c7; // mov 0(%rip), %r15 -> mov $0, %r15
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Apply relocations to SHF_ALLOC sections (i.e. sections that are
|
||||
// mapped to memory at runtime) based on the result of
|
||||
// scan_relocations().
|
||||
template <>
|
||||
void InputSection<X86_64>::apply_reloc_alloc(Context<X86_64> &ctx, u8 *base) {
|
||||
ElfRel<X86_64> *dynrel = nullptr;
|
||||
std::span<ElfRel<X86_64>> rels = get_rels(ctx);
|
||||
i64 frag_idx = 0;
|
||||
|
||||
if (ctx.reldyn)
|
||||
dynrel = (ElfRel<X86_64> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
|
||||
file.reldyn_offset + this->reldyn_offset);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<X86_64> &rel = rels[i];
|
||||
Symbol<X86_64> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
const SectionFragmentRef<X86_64> *ref = nullptr;
|
||||
if (rel_fragments && rel_fragments[frag_idx].idx == i)
|
||||
ref = &rel_fragments[frag_idx++];
|
||||
|
||||
auto write = [&](u64 val) {
|
||||
overflow_check(ctx, this, sym, rel.r_type, val);
|
||||
write_val(ctx, rel.r_type, loc, val);
|
||||
};
|
||||
|
||||
#define S (ref ? ref->frag->get_addr(ctx) : sym.get_addr(ctx))
|
||||
#define A (ref ? ref->addend : rel.r_addend)
|
||||
#define P (output_section->shdr.sh_addr + offset + rel.r_offset)
|
||||
#define G (sym.get_got_addr(ctx) - ctx.got->shdr.sh_addr)
|
||||
#define GOT ctx.got->shdr.sh_addr
|
||||
|
||||
switch (rel_types[i]) {
|
||||
case R_NONE:
|
||||
break;
|
||||
case R_ABS:
|
||||
write(S + A);
|
||||
break;
|
||||
case R_BASEREL:
|
||||
*dynrel++ = {P, R_X86_64_RELATIVE, 0, (i64)(S + A)};
|
||||
*(u64 *)loc = S + A;
|
||||
break;
|
||||
case R_DYN:
|
||||
*dynrel++ = {P, R_X86_64_64, (u32)sym.get_dynsym_idx(ctx), A};
|
||||
*(u64 *)loc = A;
|
||||
break;
|
||||
case R_PC:
|
||||
write(S + A - P);
|
||||
break;
|
||||
case R_GOT:
|
||||
write(G + A);
|
||||
break;
|
||||
case R_GOTPC:
|
||||
write(GOT + A - P);
|
||||
break;
|
||||
case R_GOTPCREL:
|
||||
write(G + GOT + A - P);
|
||||
break;
|
||||
case R_GOTPCRELX_RELAX: {
|
||||
u32 insn = relax_gotpcrelx(loc - 2);
|
||||
loc[-2] = insn >> 8;
|
||||
loc[-1] = insn;
|
||||
write(S + A - P);
|
||||
break;
|
||||
}
|
||||
case R_REX_GOTPCRELX_RELAX: {
|
||||
u32 insn = relax_rex_gotpcrelx(loc - 3);
|
||||
loc[-3] = insn >> 16;
|
||||
loc[-2] = insn >> 8;
|
||||
loc[-1] = insn;
|
||||
write(S + A - P);
|
||||
break;
|
||||
}
|
||||
case R_TLSGD:
|
||||
write(sym.get_tlsgd_addr(ctx) + A - P);
|
||||
break;
|
||||
case R_TLSGD_RELAX_LE: {
|
||||
// Relax GD to LE
|
||||
static const u8 insn[] = {
|
||||
0x64, 0x48, 0x8b, 0x04, 0x25, 0, 0, 0, 0, // mov %fs:0, %rax
|
||||
0x48, 0x8d, 0x80, 0, 0, 0, 0, // lea 0(%rax), %rax
|
||||
};
|
||||
memcpy(loc - 4, insn, sizeof(insn));
|
||||
*(u32 *)(loc + 8) = S - ctx.tls_end + A + 4;
|
||||
i++;
|
||||
break;
|
||||
}
|
||||
case R_TLSLD:
|
||||
write(ctx.got->get_tlsld_addr(ctx) + A - P);
|
||||
break;
|
||||
case R_TLSLD_RELAX_LE: {
|
||||
// Relax LD to LE
|
||||
static const u8 insn[] = {
|
||||
0x66, 0x66, 0x66, // (padding)
|
||||
0x64, 0x48, 0x8b, 0x04, 0x25, 0, 0, 0, 0, // mov %fs:0, %rax
|
||||
};
|
||||
memcpy(loc - 3, insn, sizeof(insn));
|
||||
i++;
|
||||
break;
|
||||
}
|
||||
case R_DTPOFF:
|
||||
write(S + A - ctx.tls_begin);
|
||||
break;
|
||||
case R_DTPOFF_RELAX:
|
||||
write(S + A - ctx.tls_end);
|
||||
break;
|
||||
case R_TPOFF:
|
||||
write(S + A - ctx.tls_end);
|
||||
break;
|
||||
case R_GOTTPOFF:
|
||||
write(sym.get_gottp_addr(ctx) + A - P);
|
||||
break;
|
||||
case R_GOTTPOFF_RELAX: {
|
||||
u32 insn = relax_gottpoff(loc - 3);
|
||||
loc[-3] = insn >> 16;
|
||||
loc[-2] = insn >> 8;
|
||||
loc[-1] = insn;
|
||||
write(S + A - ctx.tls_end + 4);
|
||||
break;
|
||||
}
|
||||
case R_GOTPC_TLSDESC:
|
||||
write(sym.get_tlsdesc_addr(ctx) + A - P);
|
||||
break;
|
||||
case R_GOTPC_TLSDESC_RELAX_LE: {
|
||||
static const u8 insn[] = {
|
||||
0x48, 0xc7, 0xc0, 0, 0, 0, 0, // mov $0, %rax
|
||||
};
|
||||
memcpy(loc - 3, insn, sizeof(insn));
|
||||
write(S + A - ctx.tls_end + 4);
|
||||
break;
|
||||
}
|
||||
case R_SIZE:
|
||||
write(sym.esym().st_size + A);
|
||||
break;
|
||||
case R_TLSDESC_CALL_RELAX:
|
||||
// call *(%rax) -> nop
|
||||
loc[0] = 0x66;
|
||||
loc[1] = 0x90;
|
||||
break;
|
||||
default:
|
||||
unreachable(ctx);
|
||||
}
|
||||
|
||||
#undef S
|
||||
#undef A
|
||||
#undef P
|
||||
#undef G
|
||||
#undef GOT
|
||||
}
|
||||
}
|
||||
|
||||
// This function is responsible for applying relocations against
|
||||
// non-SHF_ALLOC sections (i.e. sections that are not mapped to memory
|
||||
// at runtime).
|
||||
//
|
||||
// Relocations against non-SHF_ALLOC sections are much easier to
|
||||
// handle than that against SHF_ALLOC sections. It is because, since
|
||||
// they are not mapped to memory, they don't contain any variable or
|
||||
// function and never need PLT or GOT. Non-SHF_ALLOC sections are
|
||||
// mostly debug info sections.
|
||||
//
|
||||
// Relocations against non-SHF_ALLOC sections are not scanned by
|
||||
// scan_relocations.
|
||||
template <>
|
||||
void InputSection<X86_64>::apply_reloc_nonalloc(Context<X86_64> &ctx, u8 *base) {
|
||||
std::span<ElfRel<X86_64>> rels = get_rels(ctx);
|
||||
i64 frag_idx = 0;
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<X86_64> &rel = rels[i];
|
||||
if (rel.r_type == R_X86_64_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<X86_64> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
if (!sym.file) {
|
||||
report_undef(ctx, sym);
|
||||
continue;
|
||||
}
|
||||
|
||||
const SectionFragmentRef<X86_64> *ref = nullptr;
|
||||
if (rel_fragments && rel_fragments[frag_idx].idx == i)
|
||||
ref = &rel_fragments[frag_idx++];
|
||||
|
||||
auto write = [&](u64 val) {
|
||||
overflow_check(ctx, this, sym, rel.r_type, val);
|
||||
write_val(ctx, rel.r_type, loc, val);
|
||||
};
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_X86_64_8:
|
||||
case R_X86_64_16:
|
||||
case R_X86_64_32:
|
||||
case R_X86_64_32S:
|
||||
case R_X86_64_64:
|
||||
if (ref)
|
||||
write(ref->frag->get_addr(ctx) + ref->addend);
|
||||
else
|
||||
write(sym.get_addr(ctx) + rel.r_addend);
|
||||
break;
|
||||
case R_X86_64_DTPOFF32:
|
||||
case R_X86_64_DTPOFF64:
|
||||
write(sym.get_addr(ctx) + rel.r_addend - ctx.tls_begin);
|
||||
break;
|
||||
case R_X86_64_SIZE32:
|
||||
case R_X86_64_SIZE64:
|
||||
write(sym.esym().st_size + rel.r_addend);
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
|
||||
<< rel_to_string<X86_64>(rel.r_type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Linker has to create data structures in an output file to apply
|
||||
// some type of relocations. For example, if a relocation refers a GOT
|
||||
// or a PLT entry of a symbol, linker has to create an entry in .got
|
||||
// or in .plt for that symbol. In order to fix the file layout, we
|
||||
// need to scan relocations.
|
||||
template <>
|
||||
void InputSection<X86_64>::scan_relocations(Context<X86_64> &ctx) {
|
||||
assert(shdr.sh_flags & SHF_ALLOC);
|
||||
|
||||
this->reldyn_offset = file.num_dynrel * sizeof(ElfRel<X86_64>);
|
||||
std::span<ElfRel<X86_64>> rels = get_rels(ctx);
|
||||
bool is_writable = (shdr.sh_flags & SHF_WRITE);
|
||||
|
||||
// Scan relocations
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<X86_64> &rel = rels[i];
|
||||
|
||||
if (rel.r_type == R_X86_64_NONE) {
|
||||
rel_types[i] = R_NONE;
|
||||
continue;
|
||||
}
|
||||
|
||||
Symbol<X86_64> &sym = *file.symbols[rel.r_sym];
|
||||
u8 *loc = (u8 *)(contents.data() + rel.r_offset);
|
||||
|
||||
if (!sym.file) {
|
||||
report_undef(ctx, sym);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sym.get_type() == STT_GNU_IFUNC) {
|
||||
sym.flags |= NEEDS_GOT;
|
||||
sym.flags |= NEEDS_PLT;
|
||||
}
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_X86_64_8:
|
||||
case R_X86_64_16:
|
||||
case R_X86_64_32:
|
||||
case R_X86_64_32S: {
|
||||
// Dynamic linker does not support 8, 16 or 32-bit dynamic
|
||||
// relocations for these types of relocations. We report an
|
||||
// error if we cannot relocate them even at load-time.
|
||||
Action table[][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ NONE, ERROR, ERROR, ERROR }, // DSO
|
||||
{ NONE, ERROR, ERROR, ERROR }, // PIE
|
||||
{ NONE, NONE, COPYREL, PLT }, // PDE
|
||||
};
|
||||
|
||||
dispatch(ctx, table, R_ABS, i);
|
||||
break;
|
||||
}
|
||||
case R_X86_64_64: {
|
||||
// Unlike the above, we can use R_X86_64_RELATIVE and R_86_64_64
|
||||
// relocations.
|
||||
Action table[][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ NONE, BASEREL, DYNREL, DYNREL }, // DSO
|
||||
{ NONE, BASEREL, DYNREL, DYNREL }, // PIE
|
||||
{ NONE, NONE, DYNREL, PLT }, // PDE
|
||||
};
|
||||
|
||||
dispatch(ctx, table, R_ABS, i);
|
||||
break;
|
||||
}
|
||||
case R_X86_64_PC8:
|
||||
case R_X86_64_PC16:
|
||||
case R_X86_64_PC32: {
|
||||
Action table[][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ ERROR, NONE, ERROR, ERROR }, // DSO
|
||||
{ ERROR, NONE, COPYREL, PLT }, // PIE
|
||||
{ NONE, NONE, COPYREL, PLT }, // PDE
|
||||
};
|
||||
|
||||
dispatch(ctx, table, R_PC, i);
|
||||
break;
|
||||
}
|
||||
case R_X86_64_PC64: {
|
||||
Action table[][4] = {
|
||||
// Absolute Local Imported data Imported code
|
||||
{ BASEREL, NONE, ERROR, ERROR }, // DSO
|
||||
{ BASEREL, NONE, COPYREL, PLT }, // PIE
|
||||
{ NONE, NONE, COPYREL, PLT }, // PDE
|
||||
};
|
||||
|
||||
dispatch(ctx, table, R_PC, i);
|
||||
break;
|
||||
}
|
||||
case R_X86_64_GOT32:
|
||||
case R_X86_64_GOT64:
|
||||
sym.flags |= NEEDS_GOT;
|
||||
rel_types[i] = R_GOT;
|
||||
break;
|
||||
case R_X86_64_GOTPC32:
|
||||
case R_X86_64_GOTPC64:
|
||||
sym.flags |= NEEDS_GOT;
|
||||
rel_types[i] = R_GOTPC;
|
||||
break;
|
||||
case R_X86_64_GOTPCREL:
|
||||
case R_X86_64_GOTPCREL64:
|
||||
sym.flags |= NEEDS_GOT;
|
||||
rel_types[i] = R_GOTPCREL;
|
||||
break;
|
||||
case R_X86_64_GOTPCRELX: {
|
||||
if (rel.r_addend != -4)
|
||||
Fatal(ctx) << *this << ": bad r_addend for R_X86_64_GOTPCRELX";
|
||||
|
||||
if (ctx.arg.relax && !sym.is_imported && sym.is_relative(ctx) &&
|
||||
relax_gotpcrelx(loc - 2)) {
|
||||
rel_types[i] = R_GOTPCRELX_RELAX;
|
||||
} else {
|
||||
sym.flags |= NEEDS_GOT;
|
||||
rel_types[i] = R_GOTPCREL;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case R_X86_64_REX_GOTPCRELX:
|
||||
if (rel.r_addend != -4)
|
||||
Fatal(ctx) << *this << ": bad r_addend for R_X86_64_REX_GOTPCRELX";
|
||||
|
||||
if (ctx.arg.relax && !sym.is_imported && sym.is_relative(ctx) &&
|
||||
relax_rex_gotpcrelx(loc - 3)) {
|
||||
rel_types[i] = R_REX_GOTPCRELX_RELAX;
|
||||
} else {
|
||||
sym.flags |= NEEDS_GOT;
|
||||
rel_types[i] = R_GOTPCREL;
|
||||
}
|
||||
break;
|
||||
case R_X86_64_PLT32:
|
||||
if (sym.is_imported)
|
||||
sym.flags |= NEEDS_PLT;
|
||||
rel_types[i] = R_PC;
|
||||
break;
|
||||
case R_X86_64_TLSGD: {
|
||||
if (i + 1 == rels.size())
|
||||
Fatal(ctx) << *this
|
||||
<< ": TLSGD reloc must be followed by PLT32 or GOTPCREL";
|
||||
|
||||
if (ctx.arg.relax && !ctx.arg.shared && !sym.is_imported) {
|
||||
rel_types[i++] = R_TLSGD_RELAX_LE;
|
||||
} else {
|
||||
sym.flags |= NEEDS_TLSGD;
|
||||
rel_types[i] = R_TLSGD;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case R_X86_64_TLSLD:
|
||||
if (i + 1 == rels.size())
|
||||
Fatal(ctx) << *this
|
||||
<< ": TLSGD reloc must be followed by PLT32 or GOTPCREL";
|
||||
if (sym.is_imported)
|
||||
Fatal(ctx) << *this << ": TLSLD reloc refers external symbol " << sym;
|
||||
|
||||
if (ctx.arg.relax && !ctx.arg.shared) {
|
||||
rel_types[i++] = R_TLSLD_RELAX_LE;
|
||||
} else {
|
||||
sym.flags |= NEEDS_TLSLD;
|
||||
rel_types[i] = R_TLSLD;
|
||||
}
|
||||
break;
|
||||
case R_X86_64_DTPOFF32:
|
||||
case R_X86_64_DTPOFF64:
|
||||
if (sym.is_imported)
|
||||
Fatal(ctx) << *this << ": DTPOFF reloc refers external symbol " << sym;
|
||||
|
||||
if (ctx.arg.relax && !ctx.arg.shared)
|
||||
rel_types[i] = R_DTPOFF_RELAX;
|
||||
else
|
||||
rel_types[i] = R_DTPOFF;
|
||||
break;
|
||||
case R_X86_64_TPOFF32:
|
||||
case R_X86_64_TPOFF64:
|
||||
rel_types[i] = R_TPOFF;
|
||||
break;
|
||||
case R_X86_64_GOTTPOFF:
|
||||
ctx.has_gottp_rel = true;
|
||||
|
||||
if (ctx.arg.relax && !ctx.arg.shared && !sym.is_imported &&
|
||||
relax_gottpoff(loc - 3)) {
|
||||
rel_types[i] = R_GOTTPOFF_RELAX;
|
||||
} else {
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
rel_types[i] = R_GOTTPOFF;
|
||||
}
|
||||
break;
|
||||
case R_X86_64_GOTPC32_TLSDESC:
|
||||
if (memcmp(loc - 3, "\x48\x8d\x05", 3))
|
||||
Fatal(ctx) << *this << ": GOTPC32_TLSDESC relocation is used"
|
||||
<< " against an invalid code sequence";
|
||||
|
||||
if (ctx.arg.relax && !ctx.arg.shared) {
|
||||
rel_types[i] = R_GOTPC_TLSDESC_RELAX_LE;
|
||||
} else {
|
||||
sym.flags |= NEEDS_TLSDESC;
|
||||
rel_types[i] = R_GOTPC_TLSDESC;
|
||||
}
|
||||
break;
|
||||
case R_X86_64_SIZE32:
|
||||
case R_X86_64_SIZE64:
|
||||
rel_types[i] = R_SIZE;
|
||||
break;
|
||||
case R_X86_64_TLSDESC_CALL:
|
||||
if (ctx.arg.relax && !ctx.arg.shared)
|
||||
rel_types[i] = R_TLSDESC_CALL_RELAX;
|
||||
else
|
||||
rel_types[i] = R_NONE;
|
||||
break;
|
||||
default:
|
||||
Error(ctx) << *this << ": unknown relocation: "
|
||||
<< rel_to_string<X86_64>(rel.r_type);
|
||||
}
|
||||
}
|
||||
}
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
#include "mold.h"
|
||||
|
||||
struct ArHdr {
|
||||
char ar_name[16];
|
||||
char ar_date[12];
|
||||
char ar_uid[6];
|
||||
char ar_gid[6];
|
||||
char ar_mode[8];
|
||||
char ar_size[10];
|
||||
char ar_fmag[2];
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
std::vector<MemoryMappedFile<E> *>
|
||||
read_thin_archive_members(Context<E> &ctx, MemoryMappedFile<E> *mb) {
|
||||
u8 *begin = mb->data(ctx);
|
||||
u8 *data = begin + 8;
|
||||
std::vector<MemoryMappedFile<E> *> vec;
|
||||
std::string_view strtab;
|
||||
|
||||
while (data < begin + mb->size()) {
|
||||
// Each header is aligned to a 2 byte boundary.
|
||||
if ((begin - data) % 2)
|
||||
data++;
|
||||
|
||||
ArHdr &hdr = *(ArHdr *)data;
|
||||
u8 *body = data + sizeof(hdr);
|
||||
u64 size = atol(hdr.ar_size);
|
||||
|
||||
// Read a string table.
|
||||
if (!memcmp(hdr.ar_name, "// ", 3)) {
|
||||
strtab = {(char *)body, size};
|
||||
data = body + size;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip a symbol table.
|
||||
if (!memcmp(hdr.ar_name, "/ ", 2)) {
|
||||
data = body + size;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (hdr.ar_name[0] != '/')
|
||||
Fatal(ctx) << mb->name << ": filename is not stored as a long filename";
|
||||
|
||||
const char *start = strtab.data() + atoi(hdr.ar_name + 1);
|
||||
std::string name(start, (const char *)strstr(start, "/\n"));
|
||||
std::string path = name.starts_with('/') ?
|
||||
name : std::string(path_dirname(mb->name)) + "/" + name;
|
||||
vec.push_back(MemoryMappedFile<E>::must_open(ctx, path));
|
||||
data = body;
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
std::vector<MemoryMappedFile<E> *>
|
||||
read_fat_archive_members(Context<E> &ctx, MemoryMappedFile<E> *mb) {
|
||||
u8 *begin = mb->data(ctx);
|
||||
u8 *data = begin + 8;
|
||||
std::vector<MemoryMappedFile<E> *> vec;
|
||||
std::string_view strtab;
|
||||
|
||||
while (begin + mb->size() - data >= 2) {
|
||||
if ((begin - data) % 2)
|
||||
data++;
|
||||
|
||||
ArHdr &hdr = *(ArHdr *)data;
|
||||
u8 *body = data + sizeof(hdr);
|
||||
u64 size = atol(hdr.ar_size);
|
||||
data = body + size;
|
||||
|
||||
if (!memcmp(hdr.ar_name, "// ", 3)) {
|
||||
strtab = {(char *)body, size};
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!memcmp(hdr.ar_name, "/ ", 2) ||
|
||||
!memcmp(hdr.ar_name, "__.SYMDEF/", 10))
|
||||
continue;
|
||||
|
||||
std::string name;
|
||||
|
||||
if (hdr.ar_name[0] == '/') {
|
||||
const char *start = strtab.data() + atoi(hdr.ar_name + 1);
|
||||
name = {start, (const char *)strstr(start, "/\n")};
|
||||
} else {
|
||||
name = {hdr.ar_name, strchr(hdr.ar_name, '/')};
|
||||
}
|
||||
|
||||
vec.push_back(mb->slice(ctx, name, body - begin, size));
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
std::vector<MemoryMappedFile<E> *>
|
||||
read_archive_members(Context<E> &ctx, MemoryMappedFile<E> *mb) {
|
||||
switch (get_file_type(ctx, mb)) {
|
||||
case FileType::AR:
|
||||
return read_fat_archive_members(ctx, mb);
|
||||
case FileType::THIN_AR:
|
||||
return read_thin_archive_members(ctx, mb);
|
||||
default:
|
||||
unreachable(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
#define INSTANTIATE(E) \
|
||||
template std::vector<MemoryMappedFile<E> *> \
|
||||
read_fat_archive_members(Context<E> &, MemoryMappedFile<E> *); \
|
||||
template std::vector<MemoryMappedFile<E> *> \
|
||||
read_thin_archive_members(Context<E> &, MemoryMappedFile<E> *); \
|
||||
template std::vector<MemoryMappedFile<E> *> \
|
||||
read_archive_members(Context<E> &, MemoryMappedFile<E> *);
|
||||
|
||||
INSTANTIATE(X86_64);
|
||||
INSTANTIATE(I386);
|
||||
Executable
+40
@@ -0,0 +1,40 @@
|
||||
#!/bin/bash -x
|
||||
# This is a shell script to build a statically-linked mold
|
||||
# executable using Docker, so that it is easy to build mold
|
||||
# on non-Ubuntu 20.04 machines.
|
||||
#
|
||||
# A docker image used for building mold is persistent.
|
||||
# Run `docker image rm mold-build-ubuntu20` to remove the image
|
||||
# from disk.
|
||||
|
||||
set -e
|
||||
|
||||
# If the existing file is not statically-linked, remove it.
|
||||
[ -f mold ] && ! ldd mold 2>&1 | grep -q 'not a dynamic executable' && \
|
||||
rm mold
|
||||
|
||||
cat <<EOF | docker build -t mold-build-ubuntu20 -
|
||||
FROM ubuntu:20.04
|
||||
RUN apt-get update && \
|
||||
TZ=Europe/London apt-get install -y tzdata && \
|
||||
apt-get install -y build-essential git clang lld cmake \
|
||||
libstdc++-10-dev libxxhash-dev zlib1g-dev libssl-dev && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
EOF
|
||||
|
||||
if ! [ -d oneTBB ]; then
|
||||
git clone https://github.com/oneapi-src/oneTBB.git
|
||||
(cd oneTBB; git checkout --quiet v2020.3)
|
||||
fi
|
||||
|
||||
docker run -it --rm -v `pwd`/oneTBB:/oneTBB -u $(id -u):$(id -g) \
|
||||
mold-build-ubuntu20 \
|
||||
make -C /oneTBB -j$(nproc) extra_inc=big_iron.inc
|
||||
|
||||
tbb_bindir=$(ls -d oneTBB/build/linux_intel64_*_release)
|
||||
|
||||
docker run -it --rm -v `pwd`:/mold -u $(id -u):$(id -g) \
|
||||
mold-build-ubuntu20 \
|
||||
make -C /mold -j$(nproc) \
|
||||
EXTRA_CPPFLAGS=-IoneTBB/include \
|
||||
EXTRA_LDFLAGS="-fuse-ld=lld -static -L$tbb_bindir"
|
||||
@@ -1,82 +0,0 @@
|
||||
[package]
|
||||
name = "mold"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
description = "A high-performance ELF linker, rewritten in Rust"
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
readme = "../README.md"
|
||||
keywords = ["linker", "elf"]
|
||||
categories = ["command-line-utilities", "development-tools::build-utils"]
|
||||
|
||||
[[bin]]
|
||||
name = "mold"
|
||||
path = "main.rs"
|
||||
|
||||
[[test]]
|
||||
name = "integration"
|
||||
path = "integration.rs"
|
||||
harness = false
|
||||
|
||||
[dependencies]
|
||||
mimalloc = { package = "mold-mimalloc", version = "0.1.52" }
|
||||
mold-elf.workspace = true
|
||||
mold-elf-x86_64 = { workspace = true, optional = true }
|
||||
mold-elf-i386 = { workspace = true, optional = true }
|
||||
mold-elf-arm32 = { workspace = true, optional = true }
|
||||
mold-elf-arm32be = { workspace = true, optional = true }
|
||||
mold-elf-arm64 = { workspace = true, optional = true }
|
||||
mold-elf-arm64be = { workspace = true, optional = true }
|
||||
mold-elf-riscv64 = { workspace = true, optional = true }
|
||||
mold-elf-riscv64be = { workspace = true, optional = true }
|
||||
mold-elf-riscv32 = { workspace = true, optional = true }
|
||||
mold-elf-riscv32be = { workspace = true, optional = true }
|
||||
mold-elf-ppc64v2 = { workspace = true, optional = true }
|
||||
mold-elf-ppc32 = { workspace = true, optional = true }
|
||||
mold-elf-ppc64v1 = { workspace = true, optional = true }
|
||||
mold-elf-s390x = { workspace = true, optional = true }
|
||||
mold-elf-sparc64 = { workspace = true, optional = true }
|
||||
mold-elf-m68k = { workspace = true, optional = true }
|
||||
mold-elf-sh4 = { workspace = true, optional = true }
|
||||
mold-elf-sh4be = { workspace = true, optional = true }
|
||||
mold-elf-loongarch64 = { workspace = true, optional = true }
|
||||
mold-elf-loongarch32 = { workspace = true, optional = true }
|
||||
mold-macho.workspace = true
|
||||
mold-macho-arm64 = { workspace = true, optional = true }
|
||||
mold-macho-x86_64 = { workspace = true, optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
mold-tests = { path = "../tests" }
|
||||
|
||||
# One feature per target. All are built by default; a build for one
|
||||
# target alone (`--no-default-features --features x86_64`) is much faster.
|
||||
[features]
|
||||
default = [
|
||||
"x86_64", "i386", "arm32", "arm32be", "arm64", "arm64be", "riscv64", "riscv64be", "riscv32", "riscv32be",
|
||||
"ppc64v2", "ppc32", "ppc64v1", "s390x", "sparc64", "m68k", "sh4", "sh4be", "loongarch64", "loongarch32",
|
||||
"macho-arm64", "macho-x86_64",
|
||||
]
|
||||
system-allocator = []
|
||||
x86_64 = ["dep:mold-elf-x86_64"]
|
||||
i386 = ["dep:mold-elf-i386"]
|
||||
arm32 = ["dep:mold-elf-arm32"]
|
||||
arm32be = ["dep:mold-elf-arm32be"]
|
||||
arm64 = ["dep:mold-elf-arm64"]
|
||||
arm64be = ["dep:mold-elf-arm64be"]
|
||||
riscv64 = ["dep:mold-elf-riscv64"]
|
||||
riscv64be = ["dep:mold-elf-riscv64be"]
|
||||
riscv32 = ["dep:mold-elf-riscv32"]
|
||||
riscv32be = ["dep:mold-elf-riscv32be"]
|
||||
ppc64v2 = ["dep:mold-elf-ppc64v2"]
|
||||
ppc32 = ["dep:mold-elf-ppc32"]
|
||||
ppc64v1 = ["dep:mold-elf-ppc64v1"]
|
||||
s390x = ["dep:mold-elf-s390x"]
|
||||
sparc64 = ["dep:mold-elf-sparc64"]
|
||||
m68k = ["dep:mold-elf-m68k"]
|
||||
sh4 = ["dep:mold-elf-sh4"]
|
||||
sh4be = ["dep:mold-elf-sh4be"]
|
||||
loongarch64 = ["dep:mold-elf-loongarch64"]
|
||||
loongarch32 = ["dep:mold-elf-loongarch32"]
|
||||
macho-arm64 = ["dep:mold-macho-arm64"]
|
||||
macho-x86_64 = ["dep:mold-macho-x86_64"]
|
||||
@@ -1,55 +0,0 @@
|
||||
//! Passes host-dependent options to the linker for the mold executable.
|
||||
|
||||
use std::env;
|
||||
use std::ffi::OsString;
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
fn main() {
|
||||
println!("cargo:rerun-if-changed=build.rs");
|
||||
|
||||
// mimalloc uses 64-bit atomics, which C compilers implement as calls
|
||||
// into libatomic on hosts without them, such as 32-bit PowerPC.
|
||||
let widths = env::var("CARGO_CFG_TARGET_HAS_ATOMIC").unwrap_or_default();
|
||||
if !widths.split(',').any(|w| w == "64") {
|
||||
// rustc places libraries named by this crate before its dependencies
|
||||
// on the linker command line, where --as-needed would drop libatomic.
|
||||
// Link arguments come after all libraries.
|
||||
println!("cargo:rustc-link-arg-bins=-latomic");
|
||||
}
|
||||
|
||||
// The linker is instantiated for each target, and many functions compile
|
||||
// to the same machine code for several targets. Identical Code Folding
|
||||
// merges them, which roughly halves the text of a release build. GNU ld
|
||||
// doesn't support it, so we pass the option only if the linker takes it.
|
||||
if env::var("PROFILE").unwrap() == "release" && link_succeeds("-Wl,--icf=all") {
|
||||
println!("cargo:rustc-link-arg-bins=-Wl,--icf=all");
|
||||
}
|
||||
}
|
||||
|
||||
/// Links an empty program with the given linker argument, using the same
|
||||
/// compiler, linker and flags as the mold executable.
|
||||
fn link_succeeds(arg: &str) -> bool {
|
||||
let out_dir = env::var_os("OUT_DIR").unwrap();
|
||||
let mut cmd = Command::new(env::var_os("RUSTC").unwrap());
|
||||
cmd.arg("--target").arg(env::var("TARGET").unwrap());
|
||||
cmd.arg("-o").arg(Path::new(&out_dir).join("link-probe"));
|
||||
cmd.arg(format!("-Clink-arg={arg}"));
|
||||
if let Some(linker) = env::var_os("RUSTC_LINKER") {
|
||||
let mut opt = OsString::from("-Clinker=");
|
||||
opt.push(linker);
|
||||
cmd.arg(opt);
|
||||
}
|
||||
// Flags are separated by 0x1f.
|
||||
let flags = env::var("CARGO_ENCODED_RUSTFLAGS").unwrap_or_default();
|
||||
cmd.args(flags.split('\x1f').filter(|flag| !flag.is_empty()));
|
||||
cmd.arg("-");
|
||||
cmd.stdin(Stdio::piped()).stdout(Stdio::null()).stderr(Stdio::null());
|
||||
|
||||
let Ok(mut child) = cmd.spawn() else {
|
||||
return false;
|
||||
};
|
||||
child.stdin.take().unwrap().write_all(b"fn main() {}").unwrap();
|
||||
child.wait().is_ok_and(|status| status.success())
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
//! Runs the shell tests of the format the host builds programs for. The
|
||||
//! Mach-O tests drive Apple's toolchain, which only macOS has, and the ELF
|
||||
//! tests need a toolchain that makes ELF programs, which macOS lacks.
|
||||
|
||||
use std::path::Path;
|
||||
use std::process::ExitCode;
|
||||
|
||||
fn main() -> ExitCode {
|
||||
#[cfg(target_os = "macos")]
|
||||
let (cases, run) = ("tests/macho", mold_tests::macho::run);
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
let (cases, run) = ("tests/elf", mold_tests::elf::run);
|
||||
|
||||
let root = Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap();
|
||||
run(&root.join(cases), Path::new(env!("CARGO_BIN_EXE_mold")))
|
||||
}
|
||||
-115
@@ -1,115 +0,0 @@
|
||||
//! The `mold` executable: runs the ELF linker, or the Mach-O linker if
|
||||
//! invoked as `ld64.mold`, instantiated for the target the inputs are for.
|
||||
//! Each target is instantiated in a crate of its own so that the compiler
|
||||
//! can build them in parallel, and a feature per target decides which of
|
||||
//! them are built in.
|
||||
|
||||
// rustc emits asynchronous unwind tables, which compact unwind can't
|
||||
// encode, so the unwind info of most functions goes to __eh_frame. In a
|
||||
// debug build, that section exceeds the 16 MiB that compact unwind can
|
||||
// refer to, and Apple's ld warns about that. The warning concerns only
|
||||
// the speed of unwinding, so we keep rustc from reporting it.
|
||||
#![cfg_attr(all(target_vendor = "apple", debug_assertions), allow(linker_messages))]
|
||||
|
||||
use mold_elf::driver::Cmdline;
|
||||
use std::ffi::OsStr;
|
||||
use std::path::Path;
|
||||
|
||||
// A Rust executable can define only one global allocator, so select mimalloc
|
||||
// here rather than in the linker library.
|
||||
#[cfg(not(feature = "system-allocator"))]
|
||||
#[global_allocator]
|
||||
static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
|
||||
|
||||
type LinkFn = fn(Cmdline) -> Option<&'static str>;
|
||||
type MachoLinkFn = fn(mold_macho::driver::ParsedArgs);
|
||||
|
||||
// Each target has its own monomorphized link function. The ELF linker
|
||||
// starts with the first enabled target and switches to the matching
|
||||
// function if the inputs are for another target. The Mach-O linker finds
|
||||
// the target as it parses the options, and calls its function.
|
||||
const ELF_TARGETS: &[(&str, LinkFn)] = &[
|
||||
#[cfg(feature = "x86_64")]
|
||||
("x86_64", mold_elf_x86_64::link),
|
||||
#[cfg(feature = "i386")]
|
||||
("i386", mold_elf_i386::link),
|
||||
#[cfg(feature = "arm32")]
|
||||
("arm32", mold_elf_arm32::link),
|
||||
#[cfg(feature = "arm32be")]
|
||||
("arm32be", mold_elf_arm32be::link),
|
||||
#[cfg(feature = "arm64")]
|
||||
("arm64", mold_elf_arm64::link),
|
||||
#[cfg(feature = "arm64be")]
|
||||
("arm64be", mold_elf_arm64be::link),
|
||||
#[cfg(feature = "riscv64")]
|
||||
("riscv64", mold_elf_riscv64::link),
|
||||
#[cfg(feature = "riscv64be")]
|
||||
("riscv64be", mold_elf_riscv64be::link),
|
||||
#[cfg(feature = "riscv32")]
|
||||
("riscv32", mold_elf_riscv32::link),
|
||||
#[cfg(feature = "riscv32be")]
|
||||
("riscv32be", mold_elf_riscv32be::link),
|
||||
#[cfg(feature = "ppc64v2")]
|
||||
("ppc64v2", mold_elf_ppc64v2::link),
|
||||
#[cfg(feature = "ppc32")]
|
||||
("ppc32", mold_elf_ppc32::link),
|
||||
#[cfg(feature = "ppc64v1")]
|
||||
("ppc64v1", mold_elf_ppc64v1::link),
|
||||
#[cfg(feature = "s390x")]
|
||||
("s390x", mold_elf_s390x::link),
|
||||
#[cfg(feature = "sparc64")]
|
||||
("sparc64", mold_elf_sparc64::link),
|
||||
#[cfg(feature = "m68k")]
|
||||
("m68k", mold_elf_m68k::link),
|
||||
#[cfg(feature = "sh4")]
|
||||
("sh4", mold_elf_sh4::link),
|
||||
#[cfg(feature = "sh4be")]
|
||||
("sh4be", mold_elf_sh4be::link),
|
||||
#[cfg(feature = "loongarch64")]
|
||||
("loongarch64", mold_elf_loongarch64::link),
|
||||
#[cfg(feature = "loongarch32")]
|
||||
("loongarch32", mold_elf_loongarch32::link),
|
||||
];
|
||||
|
||||
const MACHO_TARGETS: &[(&str, MachoLinkFn)] = &[
|
||||
#[cfg(feature = "macho-arm64")]
|
||||
("arm64", mold_macho_arm64::link),
|
||||
#[cfg(feature = "macho-x86_64")]
|
||||
("x86_64", mold_macho_x86_64::link),
|
||||
];
|
||||
|
||||
fn link_fn<F: Copy>(targets: &[(&str, F)], target: &str) -> F {
|
||||
for &(name, link) in targets {
|
||||
if name == target {
|
||||
return link;
|
||||
}
|
||||
}
|
||||
eprintln!(
|
||||
"mold: unsupported target: {target}; rebuild mold with the appropriate target support"
|
||||
);
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
// As with lld's ld64.lld, the name tells mold to act as Apple's linker,
|
||||
// ld64. clang on macOS looks for ld64.mold when given -fuse-ld=mold.
|
||||
fn is_ld64(argv0: &OsStr) -> bool {
|
||||
let name = Path::new(argv0).file_name().unwrap_or_default().as_encoded_bytes();
|
||||
name == b"ld64" || name.starts_with(b"ld64.")
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let args: Vec<_> = std::env::args_os().collect();
|
||||
if args.first().is_some_and(|argv0| is_ld64(argv0)) {
|
||||
let link = |target: &str, parsed| link_fn(MACHO_TARGETS, target)(parsed);
|
||||
mold_macho::driver::main(args, link);
|
||||
std::process::exit(0);
|
||||
}
|
||||
|
||||
let Some(&(initial_target, _)) = ELF_TARGETS.first() else {
|
||||
eprintln!("mold: no targets enabled; rebuild mold with the appropriate target support");
|
||||
std::process::exit(1);
|
||||
};
|
||||
let link = |target: &str, cmdline| link_fn(ELF_TARGETS, target)(cmdline);
|
||||
mold_elf::driver::main(args, initial_target, link);
|
||||
std::process::exit(0);
|
||||
}
|
||||
+850
@@ -0,0 +1,850 @@
|
||||
#include "mold.h"
|
||||
|
||||
#include <sstream>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <tbb/global_control.h>
|
||||
#include <unistd.h>
|
||||
#include <unordered_set>
|
||||
|
||||
static const char helpmsg[] = R"(
|
||||
Options:
|
||||
--help Report usage information
|
||||
-v, --version Report version information
|
||||
-V Report version and target information
|
||||
-(, --start-group Ignored
|
||||
-), --end-group Ignored
|
||||
-C DIR, --directory DIR Change to DIR before doing anything
|
||||
-E, --export-dynamic Put symbols in the dynamic symbol table
|
||||
--no-export-dynamic
|
||||
-F LIBNAME, --filter LIBNAME
|
||||
Set DT_FILTER to the specified value
|
||||
-I FILE, --dynamic-linker FILE
|
||||
Set dynamic linker path
|
||||
--no-dynamic-linker
|
||||
-L DIR, --library-path DIR Add DIR to library search path
|
||||
-M, --print-map Write map file to stdout
|
||||
-N, --omagic Do not page align data, do not make text readonly
|
||||
--no-omagic
|
||||
-O NUMBER Ignored
|
||||
-S, --strip-debug Strip .debug_* sections
|
||||
-T FILE, --script FILE Read linker script
|
||||
-X, --discard-locals Discard temporary local symbols
|
||||
-e SYMBOL, --entry SYMBOL Set program entry point
|
||||
-f SHLIB, --auxiliary SHLIB Set DT_AUXILIARY to the specified value
|
||||
-h LIBNAME, --soname LIBNAME
|
||||
Set shared library name
|
||||
-l LIBNAME Search for a given library
|
||||
-m EMULATION Ignored
|
||||
-o FILE, --output FILE Set output filename
|
||||
-r, --relocatable Generate relocatable output
|
||||
-s, --strip-all Strip .symtab section
|
||||
-u SYMBOL, --undefined SYMBOL
|
||||
Force to resolve SYMBOL
|
||||
--Bdynamic Link against shared libraries (default)
|
||||
--Bstatic Do not link against shared libraries
|
||||
--Bsymbolic Bind global symbols locally
|
||||
--Bsymbolic-functions Bind global functions locally
|
||||
--Map FILE Write map file to a given file
|
||||
--allow-multiple-definition Allow multiple definitions
|
||||
--as-needed Only set DT_NEEDED if used
|
||||
--no-as-needed
|
||||
--build-id [none,md5,sha1,sha256,uuid,HEXSTRING]
|
||||
Generate build ID
|
||||
--no-build-id
|
||||
--chroot DIR Set a given path to root directory
|
||||
--color-diagnostics Ignored
|
||||
--compress-debug-sections [none,zlib,zlib-gabi,zlib-gnu]
|
||||
Compress .debug_* sections
|
||||
--demangle Demangle C++ symbols in log messages (default)
|
||||
--no-demangle
|
||||
--disable-new-dtags Ignored
|
||||
--dynamic-list Read a list of dynamic symbols
|
||||
--eh-frame-hdr Create .eh_frame_hdr section
|
||||
--no-eh-frame-hdr
|
||||
--enable-new-dtags Ignored
|
||||
--exclude-libs LIB,LIB,.. Mark all symbols in given libraries hidden
|
||||
--fatal-warnings Ignored
|
||||
--no-fatal-warnings Ignored
|
||||
--fini SYMBOL Call SYMBOL at unload-time
|
||||
--fork Spawn a child process (default)
|
||||
--no-fork
|
||||
--gc-sections Remove unreferenced sections
|
||||
--no-gc-sections
|
||||
--gdb-index Ignored
|
||||
--hash-style [sysv,gnu,both]
|
||||
Set hash style
|
||||
--icf Fold identical code
|
||||
--no-icf
|
||||
--image-base ADDR Set the base address to a given value
|
||||
--init SYMBOL Call SYMBOl at load-time
|
||||
--no-undefined Report undefined symbols (even with --shared)
|
||||
--perf Print performance statistics
|
||||
--pie, --pic-executable Create a position independent executable
|
||||
--no-pie, --no-pic-executable
|
||||
--plugin Ignored
|
||||
--plugin-opt Ignored
|
||||
--pop-state Pop state of flags governing input file handling
|
||||
--preload
|
||||
--print-gc-sections Print removed unreferenced sections
|
||||
--no-print-gc-sections
|
||||
--print-icf-sections Print folded identical sections
|
||||
--no-print-icf-sections
|
||||
--push-state Pop state of flags governing input file handling
|
||||
--quick-exit Use quick_exit to exit (default)
|
||||
--no-quick-exit
|
||||
--relax Optimize instructions (default)
|
||||
--no-relax
|
||||
--repro Embed input files to .repro section
|
||||
--retain-symbols-file FILE Keep only symbols listed in FILE
|
||||
--rpath DIR Add DIR to runtime search path
|
||||
--rpath-link DIR Ignored
|
||||
--run COMMAND ARG... Run COMMAND with mold as /usr/bin/ld
|
||||
--shared, --Bshareable Create a share library
|
||||
--sort-common Ignored
|
||||
--sort-section Ignored
|
||||
--spare-dynamic-tags NUMBER Reserve give number of tags in .dynamic section
|
||||
--static Do not link against shared libraries
|
||||
--stats Print input statistics
|
||||
--sysroot DIR Set target system root directory
|
||||
--thread-count COUNT Use COUNT number of threads
|
||||
--threads Use multiple threads (default)
|
||||
--no-threads
|
||||
--trace Print name of each input file
|
||||
--unique PATTERN Don't merge input sections that match a given pattern
|
||||
--unresolved-symbols [report-all,ignore-all,ignore-in-object-files,ignore-in-shared-libs]
|
||||
How to handle unresolved symbols
|
||||
--version-script FILE Read version script
|
||||
--warn-common Warn about common symbols
|
||||
--no-warn-common
|
||||
--warn-unresolved-symbols Report unresolved symbols as warnings
|
||||
--error-unresolved-symbols
|
||||
Report unresolved symbols as errors (default)
|
||||
--whole-archive Include all objects from static archives
|
||||
--no-whole-archive
|
||||
--wrap SYMBOL Use wrapper function for a given symbol
|
||||
-z defs Report undefined symbols (even with --shared)
|
||||
-z nodefs
|
||||
-z execstack Require executable stack
|
||||
-z noexecstack
|
||||
-z initfirst Mark DSO to be initialized first at runtime
|
||||
-z interpose Mark object to interpose all DSOs but executable
|
||||
-z keep-text-section-prefix Keep .text.{hot,unknown,unlikely,startup,exit} as separate sections in the final binary
|
||||
-z nokeep-text-section-prefix
|
||||
-z lazy Enable lazy function resolution (default)
|
||||
-z nocopyreloc Do not create copy relocations
|
||||
-z nodelete Mark DSO non-deletable at runtime
|
||||
-z nodlopen Mark DSO not available to dlopen
|
||||
-z nodump Mark DSO not available to dldump
|
||||
-z now Disable lazy function resolution
|
||||
-z origin Mark object requiring immediate $ORIGIN processing at runtime
|
||||
-z relro Make some sections read-only after relocation (default)
|
||||
-z norelro
|
||||
-z text Report error if DT_TEXTREL is set
|
||||
-z notext
|
||||
-z textoff
|
||||
|
||||
mold: supported targets: elf32-i386 elf64-x86-64
|
||||
mold: supported emulations: elf_i386 elf_x86_64)";
|
||||
|
||||
template <typename E>
|
||||
static std::vector<std::string_view>
|
||||
read_response_file(Context<E> &ctx, std::string_view path) {
|
||||
std::vector<std::string_view> vec;
|
||||
MemoryMappedFile<E> *mb =
|
||||
MemoryMappedFile<E>::must_open(ctx, std::string(path));
|
||||
u8 *data = mb->data(ctx);
|
||||
|
||||
auto read_quoted = [&](i64 i, char quote) {
|
||||
std::string buf;
|
||||
while (i < mb->size() && data[i] != quote) {
|
||||
if (data[i] == '\\') {
|
||||
buf.append(1, data[i + 1]);
|
||||
i += 2;
|
||||
} else {
|
||||
buf.append(1, data[i++]);
|
||||
}
|
||||
}
|
||||
if (i >= mb->size())
|
||||
Fatal(ctx) << path << ": premature end of input";
|
||||
vec.push_back(save_string(ctx, buf));
|
||||
return i + 1;
|
||||
};
|
||||
|
||||
auto read_unquoted = [&](i64 i) {
|
||||
std::string buf;
|
||||
while (i < mb->size() && !isspace(data[i]))
|
||||
buf.append(1, data[i++]);
|
||||
vec.push_back(save_string(ctx, buf));
|
||||
return i;
|
||||
};
|
||||
|
||||
for (i64 i = 0; i < mb->size();) {
|
||||
if (isspace(data[i]))
|
||||
i++;
|
||||
else if (data[i] == '\'')
|
||||
i = read_quoted(i + 1, '\'');
|
||||
else if (data[i] == '\"')
|
||||
i = read_quoted(i + 1, '\"');
|
||||
else
|
||||
i = read_unquoted(i);
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
std::vector<std::string_view>
|
||||
expand_response_files(Context<E> &ctx, char **argv) {
|
||||
std::vector<std::string_view> vec;
|
||||
|
||||
for (i64 i = 0; argv[i]; i++) {
|
||||
if (argv[i][0] == '@')
|
||||
append(vec, read_response_file(ctx, argv[i] + 1));
|
||||
else
|
||||
vec.push_back(argv[i]);
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
static std::vector<std::string> add_dashes(std::string name) {
|
||||
// Multi-letter linker options can be preceded by either a single
|
||||
// dash or double dashes except ones starting with "o", which must
|
||||
// be preceded by double dashes. For example, "-omagic" is
|
||||
// interpreted as "-o magic". If you really want to specify the
|
||||
// "omagic" option, you have to pass "--omagic".
|
||||
if (name[0] == 'o')
|
||||
return {"--" + name};
|
||||
return {"-" + name, "--" + name};
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
bool read_arg(Context<E> &ctx, std::span<std::string_view> &args,
|
||||
std::string_view &arg, std::string name) {
|
||||
if (name.size() == 1) {
|
||||
if (args[0] == "-" + name) {
|
||||
if (args.size() == 1)
|
||||
Fatal(ctx) << "option -" << name << ": argument missing";
|
||||
arg = args[1];
|
||||
args = args.subspan(2);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (args[0].starts_with("-" + name)) {
|
||||
arg = args[0].substr(name.size() + 1);
|
||||
args = args.subspan(1);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
for (std::string opt : add_dashes(name)) {
|
||||
if (args[0] == opt) {
|
||||
if (args.size() == 1)
|
||||
Fatal(ctx) << "option -" << name << ": argument missing";
|
||||
arg = args[1];
|
||||
args = args.subspan(2);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (args[0].starts_with(opt + "=")) {
|
||||
arg = args[0].substr(opt.size() + 1);
|
||||
args = args.subspan(1);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool read_flag(std::span<std::string_view> &args, std::string name) {
|
||||
for (std::string opt : add_dashes(name)) {
|
||||
if (args[0] == opt) {
|
||||
args = args.subspan(1);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool read_z_flag(std::span<std::string_view> &args, std::string name) {
|
||||
if (args.size() >= 2 && args[0] == "-z" && args[1] == name) {
|
||||
args = args.subspan(2);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!args.empty() && args[0] == "-z" + name) {
|
||||
args = args.subspan(1);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
std::string create_response_file(Context<E> &ctx) {
|
||||
std::string buf;
|
||||
std::stringstream out;
|
||||
|
||||
std::string cwd = get_current_dir();
|
||||
out << "-C " << cwd.substr(1) << "\n";
|
||||
|
||||
if (cwd != "/") {
|
||||
out << "--chroot ..";
|
||||
i64 depth = std::count(cwd.begin(), cwd.end(), '/');
|
||||
for (i64 i = 1; i < depth; i++)
|
||||
out << "/..";
|
||||
out << "\n";
|
||||
}
|
||||
|
||||
for (i64 i = 1; i < ctx.cmdline_args.size(); i++) {
|
||||
std::string_view arg = ctx.cmdline_args[i];
|
||||
|
||||
if (arg == "-repro" || arg == "--repro") {
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
out << arg << "\n";
|
||||
}
|
||||
|
||||
return out.str();
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static i64 parse_hex(Context<E> &ctx, std::string opt, std::string_view value) {
|
||||
if (!value.starts_with("0x") && !value.starts_with("0X"))
|
||||
Fatal(ctx) << "option -" << opt << ": not a hexadecimal number";
|
||||
value = value.substr(2);
|
||||
if (value.find_first_not_of("0123456789abcdefABCDEF") != value.npos)
|
||||
Fatal(ctx) << "option -" << opt << ": not a hexadecimal number";
|
||||
return std::stol(std::string(value), nullptr, 16);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static i64 parse_number(Context<E> &ctx, std::string opt,
|
||||
std::string_view value) {
|
||||
size_t nread;
|
||||
i64 ret = std::stol(std::string(value), &nread, 0);
|
||||
if (value.size() != nread)
|
||||
Fatal(ctx) << "option -" << opt << ": not a number: " << value;
|
||||
return ret;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static std::vector<u8> parse_hex_build_id(Context<E> &ctx,
|
||||
std::string_view arg) {
|
||||
assert(arg.starts_with("0x") || arg.starts_with("0X"));
|
||||
|
||||
if (arg.size() % 2)
|
||||
Fatal(ctx) << "invalid build-id: " << arg;
|
||||
if (arg.substr(2).find_first_not_of("0123456789abcdefABCDEF") != arg.npos)
|
||||
Fatal(ctx) << "invalid build-id: " << arg;
|
||||
|
||||
arg = arg.substr(2);
|
||||
|
||||
auto fn = [](char c) {
|
||||
if ('0' <= c && c <= '9')
|
||||
return c - '0';
|
||||
if ('a' <= c && c <= 'f')
|
||||
return c - 'a' + 10;
|
||||
assert('A' <= c && c <= 'F');
|
||||
return c - 'A' + 10;
|
||||
};
|
||||
|
||||
std::vector<u8> vec(arg.size() / 2);
|
||||
for (i64 i = 0; i < vec.size(); i++)
|
||||
vec[i] = (fn(arg[i * 2]) << 4) | fn(arg[i * 2 + 1]);
|
||||
return vec;
|
||||
}
|
||||
|
||||
static std::vector<std::string_view>
|
||||
split_by_comma_or_colon(std::string_view str) {
|
||||
std::vector<std::string_view> vec;
|
||||
|
||||
for (;;) {
|
||||
i64 pos = str.find_first_of(",:");
|
||||
if (pos == str.npos) {
|
||||
vec.push_back(str);
|
||||
break;
|
||||
}
|
||||
vec.push_back(str.substr(0, pos));
|
||||
str = str.substr(pos);
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
static i64 get_default_thread_count() {
|
||||
// mold doesn't scale above 32 threads.
|
||||
int n = tbb::global_control::active_value(
|
||||
tbb::global_control::max_allowed_parallelism);
|
||||
return std::min(n, 32);
|
||||
}
|
||||
|
||||
static std::string_view trim(std::string_view str) {
|
||||
size_t pos = str.find_first_not_of(" \t");
|
||||
if (pos == str.npos)
|
||||
return "";
|
||||
str = str.substr(pos);
|
||||
|
||||
pos = str.find_last_not_of(" \t");
|
||||
if (pos == str.npos)
|
||||
return str;
|
||||
return str.substr(0, pos + 1);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
static void read_retain_symbols_file(Context<E> &ctx, std::string_view path) {
|
||||
MemoryMappedFile<E> *mb =
|
||||
MemoryMappedFile<E>::must_open(ctx, std::string(path));
|
||||
std::string_view data((char *)mb->data(ctx), mb->size());
|
||||
|
||||
ctx.arg.retain_symbols_file.reset(new std::unordered_set<std::string_view>);
|
||||
|
||||
while (!data.empty()) {
|
||||
size_t pos = data.find('\n');
|
||||
std::string_view name;
|
||||
|
||||
if (pos == data.npos) {
|
||||
name = data;
|
||||
data = "";
|
||||
} else {
|
||||
name = data.substr(0, pos);
|
||||
data = data.substr(pos + 1);
|
||||
}
|
||||
|
||||
name = trim(name);
|
||||
if (!name.empty())
|
||||
ctx.arg.retain_symbols_file->insert(name);
|
||||
}
|
||||
}
|
||||
|
||||
static bool is_file(std::string_view path) {
|
||||
struct stat st;
|
||||
return stat(std::string(path).c_str(), &st) == 0 &&
|
||||
(st.st_mode & S_IFMT) != S_IFDIR;
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void parse_nonpositional_args(Context<E> &ctx,
|
||||
std::vector<std::string_view> &remaining) {
|
||||
std::span<std::string_view> args = ctx.cmdline_args;
|
||||
args = args.subspan(1);
|
||||
|
||||
ctx.arg.thread_count = get_default_thread_count();
|
||||
bool version_shown = false;
|
||||
|
||||
while (!args.empty()) {
|
||||
std::string_view arg;
|
||||
|
||||
if (read_flag(args, "help")) {
|
||||
SyncOut(ctx) << "Usage: " << ctx.cmdline_args[0]
|
||||
<< " [options] file...\n" << helpmsg;
|
||||
exit(0);
|
||||
}
|
||||
|
||||
if (read_arg(ctx, args, arg, "o") || read_arg(ctx, args, arg, "output")) {
|
||||
ctx.arg.output = arg;
|
||||
} else if (read_arg(ctx, args, arg, "dynamic-linker") ||
|
||||
read_arg(ctx, args, arg, "I")) {
|
||||
ctx.arg.dynamic_linker = arg;
|
||||
} else if (read_arg(ctx, args, arg, "no-dynamic-linker")) {
|
||||
ctx.arg.dynamic_linker = "";
|
||||
} else if (read_flag(args, "v")) {
|
||||
SyncOut(ctx) << get_version_string();
|
||||
version_shown = true;
|
||||
} else if (read_flag(args, "version")) {
|
||||
SyncOut(ctx) << get_version_string();
|
||||
exit(0);
|
||||
} else if (read_flag(args, "V")) {
|
||||
SyncOut(ctx) << get_version_string()
|
||||
<< "\n Supported emulations:\n elf_x86_64\n elf_i386";
|
||||
version_shown = true;
|
||||
} else if (read_flag(args, "export-dynamic") || read_flag(args, "E")) {
|
||||
ctx.arg.export_dynamic = true;
|
||||
} else if (read_flag(args, "no-export-dynamic")) {
|
||||
ctx.arg.export_dynamic = false;
|
||||
} else if (read_flag(args, "Bsymbolic")) {
|
||||
ctx.arg.Bsymbolic = true;
|
||||
} else if (read_flag(args, "Bsymbolic-functions")) {
|
||||
ctx.arg.Bsymbolic_functions = true;
|
||||
} else if (read_arg(ctx, args, arg, "exclude-libs")) {
|
||||
append(ctx.arg.exclude_libs, split_by_comma_or_colon(arg));
|
||||
} else if (read_arg(ctx, args, arg, "e") ||
|
||||
read_arg(ctx, args, arg, "entry")) {
|
||||
ctx.arg.entry = arg;
|
||||
} else if (read_arg(ctx, args, arg, "Map")) {
|
||||
ctx.arg.Map = arg;
|
||||
ctx.arg.print_map = true;
|
||||
} else if (read_flag(args, "print-map") || read_flag(args, "M")) {
|
||||
ctx.arg.print_map = true;
|
||||
} else if (read_flag(args, "static") || read_flag(args, "Bstatic")) {
|
||||
ctx.arg.is_static = true;
|
||||
remaining.push_back("-Bstatic");
|
||||
} else if (read_flag(args, "Bdynamic")) {
|
||||
ctx.arg.is_static = false;
|
||||
remaining.push_back("-Bdynamic");
|
||||
} else if (read_flag(args, "shared") || read_flag(args, "Bshareable")) {
|
||||
ctx.arg.shared = true;
|
||||
} else if (read_arg(ctx, args, arg, "spare-dynamic-tags")) {
|
||||
ctx.arg.spare_dynamic_tags = parse_number(ctx, "spare-dynamic-tags", arg);
|
||||
} else if (read_flag(args, "demangle")) {
|
||||
ctx.arg.demangle = true;
|
||||
} else if (read_flag(args, "no-demangle")) {
|
||||
ctx.arg.demangle = false;
|
||||
} else if (read_arg(ctx, args, arg, "y") ||
|
||||
read_arg(ctx, args, arg, "trace-symbol")) {
|
||||
ctx.arg.trace_symbol.push_back(arg);
|
||||
} else if (read_arg(ctx, args, arg, "filler")) {
|
||||
ctx.arg.filler = parse_hex(ctx, "filler", arg);
|
||||
} else if (read_arg(ctx, args, arg, "L") ||
|
||||
read_arg(ctx, args, arg, "library-path")) {
|
||||
ctx.arg.library_paths.push_back(arg);
|
||||
} else if (read_arg(ctx, args, arg, "sysroot")) {
|
||||
ctx.arg.sysroot = arg;
|
||||
} else if (read_arg(ctx, args, arg, "unique")) {
|
||||
ctx.arg.unique.reset(new std::regex(glob_to_regex(arg)));
|
||||
} else if (read_arg(ctx, args, arg, "unresolved-symbols")) {
|
||||
if (arg == "report-all" || arg == "ignore-in-shared-libs")
|
||||
ctx.arg.unresolved_symbols = UnresolvedKind::ERROR;
|
||||
else if (arg == "ignore-all" || arg == "ignore-in-object-files")
|
||||
ctx.arg.unresolved_symbols = UnresolvedKind::IGNORE;
|
||||
else
|
||||
Fatal(ctx) << "unknown --unresolved-symbols argument: " << arg;
|
||||
} else if (read_arg(ctx, args, arg, "u") ||
|
||||
read_arg(ctx, args, arg, "undefined")) {
|
||||
ctx.arg.undefined.push_back(arg);
|
||||
} else if (read_arg(ctx, args, arg, "init")) {
|
||||
ctx.arg.init = arg;
|
||||
} else if (read_arg(ctx, args, arg, "fini")) {
|
||||
ctx.arg.fini = arg;
|
||||
} else if (read_arg(ctx, args, arg, "hash-style")) {
|
||||
if (arg == "sysv") {
|
||||
ctx.arg.hash_style_sysv = true;
|
||||
ctx.arg.hash_style_gnu = false;
|
||||
} else if (arg == "gnu") {
|
||||
ctx.arg.hash_style_sysv = false;
|
||||
ctx.arg.hash_style_gnu = true;
|
||||
} else if (arg == "both") {
|
||||
ctx.arg.hash_style_sysv = true;
|
||||
ctx.arg.hash_style_gnu = true;
|
||||
} else {
|
||||
Fatal(ctx) << "invalid --hash-style argument: " << arg;
|
||||
}
|
||||
} else if (read_arg(ctx, args, arg, "soname") ||
|
||||
read_arg(ctx, args, arg, "h")) {
|
||||
ctx.arg.soname = arg;
|
||||
} else if (read_flag(args, "allow-multiple-definition")) {
|
||||
ctx.arg.allow_multiple_definition = true;
|
||||
} else if (read_flag(args, "trace")) {
|
||||
ctx.arg.trace = true;
|
||||
} else if (read_flag(args, "eh-frame-hdr")) {
|
||||
ctx.arg.eh_frame_hdr = true;
|
||||
} else if (read_flag(args, "no-eh-frame-hdr")) {
|
||||
ctx.arg.eh_frame_hdr = false;
|
||||
} else if (read_flag(args, "pie") || read_flag(args, "pic-executable")) {
|
||||
ctx.arg.pic = true;
|
||||
ctx.arg.pie = true;
|
||||
} else if (read_flag(args, "no-pie") ||
|
||||
read_flag(args, "no-pic-executable")) {
|
||||
ctx.arg.pic = false;
|
||||
ctx.arg.pie = false;
|
||||
} else if (read_flag(args, "relax")) {
|
||||
ctx.arg.relax = true;
|
||||
} else if (read_flag(args, "no-relax")) {
|
||||
ctx.arg.relax = false;
|
||||
} else if (read_flag(args, "r") || read_flag(args, "relocatable")) {
|
||||
ctx.arg.relocatable = true;
|
||||
} else if (read_flag(args, "perf")) {
|
||||
ctx.arg.perf = true;
|
||||
} else if (read_flag(args, "stats")) {
|
||||
ctx.arg.stats = true;
|
||||
Counter::enabled = true;
|
||||
} else if (read_arg(ctx, args, arg, "C") ||
|
||||
read_arg(ctx, args, arg, "directory")) {
|
||||
ctx.arg.directory = arg;
|
||||
} else if (read_arg(ctx, args, arg, "chroot")) {
|
||||
ctx.arg.chroot = arg;
|
||||
} else if (read_flag(args, "warn-common")) {
|
||||
ctx.arg.warn_common = true;
|
||||
} else if (read_flag(args, "no-warn-common")) {
|
||||
ctx.arg.warn_common = false;
|
||||
} else if (read_arg(ctx, args, arg, "compress-debug-sections")) {
|
||||
if (arg == "zlib" || arg == "zlib-gabi")
|
||||
ctx.arg.compress_debug_sections = COMPRESS_GABI;
|
||||
else if (arg == "zlib-gnu")
|
||||
ctx.arg.compress_debug_sections = COMPRESS_GNU;
|
||||
else if (arg == "none")
|
||||
ctx.arg.compress_debug_sections = COMPRESS_NONE;
|
||||
else
|
||||
Fatal(ctx) << "invalid --compress-debug-sections argument: " << arg;
|
||||
} else if (read_arg(ctx, args, arg, "wrap")) {
|
||||
ctx.arg.wrap.insert(arg);
|
||||
} else if (read_flag(args, "omagic") || read_flag(args, "N")) {
|
||||
ctx.arg.omagic = true;
|
||||
ctx.arg.is_static = true;
|
||||
} else if (read_flag(args, "no-omagic")) {
|
||||
ctx.arg.omagic = false;
|
||||
} else if (read_arg(ctx, args, arg, "retain-symbols-file")) {
|
||||
read_retain_symbols_file(ctx, arg);
|
||||
} else if (read_flag(args, "repro")) {
|
||||
ctx.arg.repro = true;
|
||||
} else if (read_z_flag(args, "now")) {
|
||||
ctx.arg.z_now = true;
|
||||
} else if (read_z_flag(args, "lazy")) {
|
||||
ctx.arg.z_now = false;
|
||||
} else if (read_z_flag(args, "execstack")) {
|
||||
ctx.arg.z_execstack = true;
|
||||
} else if (read_z_flag(args, "noexecstack")) {
|
||||
ctx.arg.z_execstack = false;
|
||||
} else if (read_z_flag(args, "relro")) {
|
||||
ctx.arg.z_relro = true;
|
||||
} else if (read_z_flag(args, "norelro")) {
|
||||
ctx.arg.z_relro = false;
|
||||
} else if (read_z_flag(args, "defs")) {
|
||||
ctx.arg.z_defs = true;
|
||||
} else if (read_z_flag(args, "nodefs")) {
|
||||
ctx.arg.z_defs = false;
|
||||
} else if (read_z_flag(args, "nodlopen")) {
|
||||
ctx.arg.z_dlopen = false;
|
||||
} else if (read_z_flag(args, "nodelete")) {
|
||||
ctx.arg.z_delete = false;
|
||||
} else if (read_z_flag(args, "nocopyreloc")) {
|
||||
ctx.arg.z_copyreloc = false;
|
||||
} else if (read_z_flag(args, "nodump")) {
|
||||
ctx.arg.z_dump = false;
|
||||
} else if (read_z_flag(args, "initfirst")) {
|
||||
ctx.arg.z_initfirst = true;
|
||||
} else if (read_z_flag(args, "interpose")) {
|
||||
ctx.arg.z_interpose = true;
|
||||
} else if (read_z_flag(args, "muldefs")) {
|
||||
ctx.arg.allow_multiple_definition = true;
|
||||
} else if (read_z_flag(args, "keep-text-section-prefix")) {
|
||||
ctx.arg.z_keep_text_section_prefix = true;
|
||||
} else if (read_z_flag(args, "nokeep-text-section-prefix")) {
|
||||
ctx.arg.z_keep_text_section_prefix = false;
|
||||
} else if (read_z_flag(args, "text")) {
|
||||
ctx.arg.z_text = true;
|
||||
} else if (read_z_flag(args, "notext") || read_z_flag(args, "textoff")) {
|
||||
ctx.arg.z_text = false;
|
||||
} else if (read_z_flag(args, "origin")) {
|
||||
ctx.arg.z_origin = true;
|
||||
} else if (read_flag(args, "no-undefined")) {
|
||||
ctx.arg.z_defs = true;
|
||||
} else if (read_flag(args, "fatal-warnings")) {
|
||||
ctx.arg.fatal_warnings = true;
|
||||
} else if (read_flag(args, "no-fatal-warnings")) {
|
||||
ctx.arg.fatal_warnings = false;
|
||||
} else if (read_flag(args, "fork")) {
|
||||
ctx.arg.fork = true;
|
||||
} else if (read_flag(args, "no-fork")) {
|
||||
ctx.arg.fork = false;
|
||||
} else if (read_flag(args, "gc-sections")) {
|
||||
ctx.arg.gc_sections = true;
|
||||
} else if (read_flag(args, "no-gc-sections")) {
|
||||
ctx.arg.gc_sections = false;
|
||||
} else if (read_flag(args, "print-gc-sections")) {
|
||||
ctx.arg.print_gc_sections = true;
|
||||
} else if (read_flag(args, "no-print-gc-sections")) {
|
||||
ctx.arg.print_gc_sections = false;
|
||||
} else if (read_arg(ctx, args, arg, "icf")) {
|
||||
if (arg == "all")
|
||||
ctx.arg.icf = true;
|
||||
else if (arg == "none")
|
||||
ctx.arg.icf = false;
|
||||
else
|
||||
Fatal(ctx) << "unknown --icf argument: " << arg;
|
||||
} else if (read_flag(args, "no-icf")) {
|
||||
ctx.arg.icf = false;
|
||||
} else if (read_arg(ctx, args, arg, "image-base")) {
|
||||
ctx.arg.image_base = parse_number(ctx, "image-base", arg);
|
||||
} else if (read_flag(args, "quick-exit")) {
|
||||
ctx.arg.quick_exit = true;
|
||||
} else if (read_flag(args, "no-quick-exit")) {
|
||||
ctx.arg.quick_exit = false;
|
||||
} else if (read_flag(args, "print-icf-sections")) {
|
||||
ctx.arg.print_icf_sections = true;
|
||||
} else if (read_flag(args, "no-print-icf-sections")) {
|
||||
ctx.arg.print_icf_sections = false;
|
||||
} else if (read_flag(args, "quick-exit")) {
|
||||
ctx.arg.quick_exit = true;
|
||||
} else if (read_flag(args, "no-quick-exit")) {
|
||||
ctx.arg.quick_exit = false;
|
||||
} else if (read_arg(ctx, args, arg, "thread-count")) {
|
||||
ctx.arg.thread_count = parse_number(ctx, "thread-count", arg);
|
||||
} else if (read_flag(args, "threads")) {
|
||||
ctx.arg.thread_count = get_default_thread_count();
|
||||
} else if (read_flag(args, "no-threads")) {
|
||||
ctx.arg.thread_count = 1;
|
||||
} else if (read_flag(args, "discard-all") || read_flag(args, "x")) {
|
||||
ctx.arg.discard_all = true;
|
||||
} else if (read_flag(args, "discard-locals") || read_flag(args, "X")) {
|
||||
ctx.arg.discard_locals = true;
|
||||
} else if (read_flag(args, "strip-all") || read_flag(args, "s")) {
|
||||
ctx.arg.strip_all = true;
|
||||
} else if (read_flag(args, "strip-debug") || read_flag(args, "S")) {
|
||||
ctx.arg.strip_all = true;
|
||||
} else if (read_flag(args, "warn-unresolved-symbols")) {
|
||||
ctx.arg.unresolved_symbols = UnresolvedKind::WARN;
|
||||
} else if (read_flag(args, "error-unresolved-symbols")) {
|
||||
ctx.arg.unresolved_symbols = UnresolvedKind::ERROR;
|
||||
} else if (read_arg(ctx, args, arg, "rpath")) {
|
||||
if (!ctx.arg.rpaths.empty())
|
||||
ctx.arg.rpaths += ":";
|
||||
ctx.arg.rpaths += arg;
|
||||
} else if (read_arg(ctx, args, arg, "R")) {
|
||||
if (is_file(arg))
|
||||
Fatal(ctx) << "-R" << arg
|
||||
<< ": -R as an alias for --just-symbols is not supported";
|
||||
|
||||
if (!ctx.arg.rpaths.empty())
|
||||
ctx.arg.rpaths += ":";
|
||||
ctx.arg.rpaths += arg;
|
||||
} else if (read_flag(args, "build-id")) {
|
||||
ctx.arg.build_id.kind = BuildId::HASH;
|
||||
ctx.arg.build_id.hash_size = 20;
|
||||
} else if (read_arg(ctx, args, arg, "build-id")) {
|
||||
if (arg == "none") {
|
||||
ctx.arg.build_id.kind = BuildId::NONE;
|
||||
} else if (arg == "uuid") {
|
||||
ctx.arg.build_id.kind = BuildId::UUID;
|
||||
} else if (arg == "md5") {
|
||||
ctx.arg.build_id.kind = BuildId::HASH;
|
||||
ctx.arg.build_id.hash_size = 16;
|
||||
} else if (arg == "sha1") {
|
||||
ctx.arg.build_id.kind = BuildId::HASH;
|
||||
ctx.arg.build_id.hash_size = 20;
|
||||
} else if (arg == "sha256") {
|
||||
ctx.arg.build_id.kind = BuildId::HASH;
|
||||
ctx.arg.build_id.hash_size = 32;
|
||||
} else if (arg.starts_with("0x") || arg.starts_with("0X")) {
|
||||
ctx.arg.build_id.kind = BuildId::HEX;
|
||||
ctx.arg.build_id.value = parse_hex_build_id(ctx, arg);
|
||||
} else {
|
||||
Fatal(ctx) << "invalid --build-id argument: " << arg;
|
||||
}
|
||||
} else if (read_flag(args, "no-build-id")) {
|
||||
ctx.arg.build_id.kind = BuildId::NONE;
|
||||
} else if (read_arg(ctx, args, arg, "auxiliary") ||
|
||||
read_arg(ctx, args, arg, "f")) {
|
||||
ctx.arg.auxiliary.push_back(arg);
|
||||
} else if (read_arg(ctx, args, arg, "filter") ||
|
||||
read_arg(ctx, args, arg, "F")) {
|
||||
ctx.arg.filter.push_back(arg);
|
||||
} else if (read_flag(args, "preload")) {
|
||||
ctx.arg.preload = true;
|
||||
} else if (read_arg(ctx, args, arg, "O")) {
|
||||
} else if (read_flag(args, "O0")) {
|
||||
} else if (read_flag(args, "O1")) {
|
||||
} else if (read_flag(args, "O2")) {
|
||||
} else if (read_flag(args, "verbose")) {
|
||||
} else if (read_arg(ctx, args, arg, "plugin")) {
|
||||
} else if (read_arg(ctx, args, arg, "plugin-opt")) {
|
||||
} else if (read_flag(args, "color-diagnostics")) {
|
||||
} else if (read_flag(args, "gdb-index")) {
|
||||
} else if (read_arg(ctx, args, arg, "m")) {
|
||||
} else if (read_flag(args, "eh-frame-hdr")) {
|
||||
} else if (read_flag(args, "start-group")) {
|
||||
} else if (read_flag(args, "end-group")) {
|
||||
} else if (read_flag(args, "(")) {
|
||||
} else if (read_flag(args, ")")) {
|
||||
} else if (read_flag(args, "fatal-warnings")) {
|
||||
} else if (read_flag(args, "enable-new-dtags")) {
|
||||
} else if (read_flag(args, "disable-new-dtags")) {
|
||||
} else if (read_flag(args, "nostdlib")) {
|
||||
} else if (read_flag(args, "allow-shlib-undefined")) {
|
||||
} else if (read_flag(args, "no-allow-shlib-undefined")) {
|
||||
} else if (read_flag(args, "no-add-needed")) {
|
||||
} else if (read_flag(args, "no-copy-dt-needed-entries")) {
|
||||
} else if (read_flag(args, "no-undefined-version")) {
|
||||
} else if (read_arg(ctx, args, arg, "sort-section")) {
|
||||
} else if (read_flag(args, "sort-common")) {
|
||||
} else if (read_flag(args, "warn-once")) {
|
||||
} else if (read_flag(args, "nodefaultlibs")) {
|
||||
} else if (read_flag(args, "warn-constructors")) {
|
||||
} else if (read_arg(ctx, args, arg, "rpath-link")) {
|
||||
} else if (read_z_flag(args, "combreloc")) {
|
||||
} else if (read_z_flag(args, "nocombreloc")) {
|
||||
} else if (read_arg(ctx, args, arg, "version-script")) {
|
||||
remaining.push_back("--version-script");
|
||||
remaining.push_back(arg);
|
||||
} else if (read_arg(ctx, args, arg, "dynamic-list")) {
|
||||
remaining.push_back("--dynamic-list");
|
||||
remaining.push_back(arg);
|
||||
} else if (read_flag(args, "as-needed")) {
|
||||
remaining.push_back("-as-needed");
|
||||
} else if (read_flag(args, "no-as-needed")) {
|
||||
remaining.push_back("-no-as-needed");
|
||||
} else if (read_flag(args, "whole-archive")) {
|
||||
remaining.push_back("-whole-archive");
|
||||
} else if (read_flag(args, "no-whole-archive")) {
|
||||
remaining.push_back("-no-whole-archive");
|
||||
} else if (read_arg(ctx, args, arg, "l")) {
|
||||
remaining.push_back("-l");
|
||||
remaining.push_back(arg);
|
||||
} else if (read_arg(ctx, args, arg, "script") ||
|
||||
read_arg(ctx, args, arg, "T")) {
|
||||
remaining.push_back(arg);
|
||||
} else if (read_flag(args, "push-state")) {
|
||||
remaining.push_back("-push-state");
|
||||
} else if (read_flag(args, "pop-state")) {
|
||||
remaining.push_back("-pop-state");
|
||||
} else {
|
||||
if (args[0] == "-z" && args.size() >= 2)
|
||||
Fatal(ctx) << "mold: unknown command line option: -z " << args[1];
|
||||
if (args[0][0] == '-')
|
||||
Fatal(ctx) << "mold: unknown command line option: " << args[0];
|
||||
remaining.push_back(args[0]);
|
||||
args = args.subspan(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx.arg.shared) {
|
||||
ctx.arg.pic = true;
|
||||
ctx.arg.dynamic_linker = "";
|
||||
}
|
||||
|
||||
if (ctx.arg.pic)
|
||||
ctx.arg.image_base = 0;
|
||||
|
||||
if (ctx.arg.retain_symbols_file) {
|
||||
ctx.arg.strip_all = false;
|
||||
ctx.arg.discard_all = false;
|
||||
}
|
||||
|
||||
if (ctx.arg.relocatable)
|
||||
ctx.arg.is_static = true;
|
||||
|
||||
if (!ctx.arg.shared) {
|
||||
if (!ctx.arg.filter.empty())
|
||||
Fatal(ctx) << "-filter may not be used without -shared";
|
||||
if (!ctx.arg.auxiliary.empty())
|
||||
Fatal(ctx) << "-auxiliary may not be used without -shared";
|
||||
}
|
||||
|
||||
if (char *env = getenv("MOLD_REPRO"); env && env[0])
|
||||
ctx.arg.repro = true;
|
||||
|
||||
if (ctx.arg.output.empty())
|
||||
ctx.arg.output = "a.out";
|
||||
|
||||
if (version_shown && remaining.empty())
|
||||
exit(0);
|
||||
}
|
||||
|
||||
#define INSTANTIATE(E) \
|
||||
template \
|
||||
std::vector<std::string_view> \
|
||||
expand_response_files(Context<E> &ctx, char **argv); \
|
||||
\
|
||||
template \
|
||||
bool read_arg(Context<E> &ctx, std::span<std::string_view> &args, \
|
||||
std::string_view &arg, \
|
||||
std::string name); \
|
||||
\
|
||||
template std::string create_response_file(Context<E> &ctx); \
|
||||
\
|
||||
template \
|
||||
void parse_nonpositional_args(Context<E> &ctx, \
|
||||
std::vector<std::string_view> &remaining)
|
||||
|
||||
INSTANTIATE(X86_64);
|
||||
INSTANTIATE(I386);
|
||||
@@ -1,27 +0,0 @@
|
||||
[package]
|
||||
name = "mold-common"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
description = "An internal crate of the mold linker, which holds the code its linkers share"
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
bstr = "1"
|
||||
cpp_demangle = "0.5.1"
|
||||
flate2 = { version = "1", default-features = false, features = ["zlib-rs"] }
|
||||
libc.workspace = true
|
||||
libz-rs-sys = "0.6.8"
|
||||
memchr = "2.8.3"
|
||||
memmap2 = "0.9"
|
||||
# std has no 64-bit atomics on hosts such as 32-bit PowerPC.
|
||||
portable-atomic = "1"
|
||||
rayon = "1"
|
||||
rustc-demangle = "0.1"
|
||||
xxhash-rust = { version = "0.8", features = ["xxh3"] }
|
||||
zlib-rs = { version = "0.6.8", default-features = false }
|
||||
zstd = "0.13"
|
||||
@@ -1,206 +0,0 @@
|
||||
//! This file contains functions to read an archive file (.a file).
|
||||
//! An archive file is just a bundle of object files. It's similar to
|
||||
//! tar or zip, but the contents are not compressed.
|
||||
//!
|
||||
//! An archive file is either "regular" or "thin". A regular archive
|
||||
//! contains object files directly, while a thin archive contains only
|
||||
//! pathnames. In the latter case, actual file contents have to be read
|
||||
//! from given pathnames. A regular archive is sometimes called a "fat"
|
||||
//! archive as opposed to "thin".
|
||||
//!
|
||||
//! If an archive file is given to the linker, the linker pulls out
|
||||
//! object files that are needed to resolve undefined symbols. So,
|
||||
//! bundling object files as an archive and giving that archive to the
|
||||
//! linker has a different meaning than directly giving the same set of
|
||||
//! object files to the linker. The former links only needed object
|
||||
//! files, while the latter links all the given object files.
|
||||
//!
|
||||
//! Therefore, if you link libc.a for example, not all the libc
|
||||
//! functions are linked to your binary. Instead, only object files
|
||||
//! that provide functions and variables used in your program get
|
||||
//! linked. To make this efficient, static library functions are
|
||||
//! usually placed in separate object files in an archive file. You can
|
||||
//! see the contents of libc.a by running `ar t
|
||||
//! /usr/lib/x86_64-linux-gnu/libc.a`.
|
||||
//!
|
||||
//! GNU ar stores long member names in a string table member. BSD ar,
|
||||
//! which macOS uses, stores a long name right after the member header
|
||||
//! instead, and puts a symbol table in a member named __.SYMDEF. Both
|
||||
//! forms are read here.
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use crate::bytes::{cstr_at, os_str};
|
||||
use crate::fatal;
|
||||
use crate::mapped_file::{MappedFile, apply_chroot};
|
||||
|
||||
const HEADER_SIZE: usize = 60;
|
||||
|
||||
/// A parsed archive member header.
|
||||
struct ArHeader<'a> {
|
||||
name: &'a [u8],
|
||||
date: u64,
|
||||
size: usize,
|
||||
}
|
||||
|
||||
impl<'a> ArHeader<'a> {
|
||||
fn parse(bytes: &'a [u8]) -> Option<Self> {
|
||||
let bytes = bytes.get(..HEADER_SIZE)?;
|
||||
let date = parse_decimal(&bytes[16..28]) as u64;
|
||||
let size = parse_decimal(&bytes[48..58]);
|
||||
Some(ArHeader { name: &bytes[..16], date, size })
|
||||
}
|
||||
|
||||
fn is_strtab(&self) -> bool {
|
||||
self.name.starts_with(b"// ")
|
||||
}
|
||||
|
||||
fn is_symtab(&self) -> bool {
|
||||
self.name.starts_with(b"/ ") || self.name.starts_with(b"/SYM64/ ")
|
||||
}
|
||||
|
||||
/// Returns the member's file name. A BSD-style long name is stored
|
||||
/// right after the header, so `body` is advanced past it.
|
||||
fn read_name(&self, strtab: &'a [u8], body: &mut &'a [u8]) -> &'a [u8] {
|
||||
// BSD-style long filename
|
||||
if let Some(rest) = self.name.strip_prefix(b"#1/") {
|
||||
let len = parse_decimal(rest);
|
||||
let (name, remaining) = body.split_at(len.min(body.len()));
|
||||
*body = remaining;
|
||||
return cstr_at(name, 0);
|
||||
}
|
||||
|
||||
// SysV-style long filename
|
||||
if let Some(rest) = self.name.strip_prefix(b"/") {
|
||||
let offset = parse_decimal(rest);
|
||||
let start = strtab.get(offset..).unwrap_or(&[]);
|
||||
let end = memchr::memmem::find(start, b"/\n").unwrap_or(start.len());
|
||||
return &start[..end];
|
||||
}
|
||||
|
||||
// Short filename, space-padded and, in the SysV form, slash-terminated
|
||||
let end = memchr::memchr(b'/', self.name).unwrap_or(self.name.len());
|
||||
self.name[..end].trim_ascii_end()
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses the leading decimal digits of a field, like `atoi`.
|
||||
fn parse_decimal(bytes: &[u8]) -> usize {
|
||||
bytes
|
||||
.iter()
|
||||
.skip_while(|b| b.is_ascii_whitespace())
|
||||
.take_while(|b| b.is_ascii_digit())
|
||||
.fold(0, |acc, &b| acc * 10 + (b - b'0') as usize)
|
||||
}
|
||||
|
||||
/// An archive member. A thin archive's member has no contents in the
|
||||
/// archive; its name is the path of the file that has them.
|
||||
pub struct Member {
|
||||
pub name: &'static [u8],
|
||||
pub data: &'static [u8],
|
||||
/// The modification time in the member header.
|
||||
pub date: u64,
|
||||
}
|
||||
|
||||
/// Iterates over the members of the archive `data`, regular or thin,
|
||||
/// skipping the symbol tables and the string table. `path` is the
|
||||
/// archive's name for error messages.
|
||||
pub fn members<'a>(path: &'a Path, data: &'static [u8]) -> impl Iterator<Item = Member> + 'a {
|
||||
let thin = data.starts_with(b"!<thin>\n");
|
||||
debug_assert!(thin || data.starts_with(b"!<arch>\n"));
|
||||
let mut pos = 8;
|
||||
let mut strtab: &'static [u8] = &[];
|
||||
|
||||
std::iter::from_fn(move || {
|
||||
loop {
|
||||
if data.len() - pos < 2 {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Each header is aligned to a 2 byte boundary.
|
||||
if pos % 2 != 0 {
|
||||
pos += 1;
|
||||
}
|
||||
|
||||
let hdr = ArHeader::parse(&data[pos..])?;
|
||||
let body_start = pos + HEADER_SIZE;
|
||||
let body_end = (body_start + hdr.size).min(data.len());
|
||||
let mut body = &data[body_start..body_end];
|
||||
|
||||
// Read a string table.
|
||||
if hdr.is_strtab() {
|
||||
strtab = body;
|
||||
pos = body_end;
|
||||
continue;
|
||||
}
|
||||
// Skip a symbol table.
|
||||
if hdr.is_symtab() {
|
||||
pos = body_end;
|
||||
continue;
|
||||
}
|
||||
|
||||
if thin && !hdr.name.starts_with(b"#1/") && !hdr.name.starts_with(b"/") {
|
||||
fatal!("{}: filename is not stored as a long filename", path.display());
|
||||
}
|
||||
|
||||
// Read the name field
|
||||
let name = hdr.read_name(strtab, &mut body);
|
||||
|
||||
if thin {
|
||||
// A thin archive member's contents live elsewhere; only a
|
||||
// BSD-style long name occupies space after the header.
|
||||
pos = body_start + (body_end - body_start - body.len());
|
||||
body = &[];
|
||||
} else {
|
||||
pos = body_end;
|
||||
}
|
||||
|
||||
// Skip BSD archive symbol tables (__.SYMDEF, __.SYMDEF SORTED,
|
||||
// __.SYMDEF_64, ...).
|
||||
if name.starts_with(b"__.SYMDEF") {
|
||||
pos = body_end;
|
||||
continue;
|
||||
}
|
||||
|
||||
return Some(Member { name, data: body, date: hdr.date });
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the paths of the members of a thin archive, which are stored
|
||||
/// outside of the archive file, without opening them.
|
||||
pub fn get_thin_archive_member_paths<'a>(
|
||||
chroot: &'a Path,
|
||||
mf: &'static MappedFile,
|
||||
) -> impl Iterator<Item = PathBuf> + 'a {
|
||||
members(&mf.name, mf.data()).map(move |member| member_path(chroot, mf, member.name))
|
||||
}
|
||||
|
||||
// An absolute member name is looked up in the --chroot directory. A relative
|
||||
// one is relative to the archive, whose path is already in that directory.
|
||||
fn member_path(chroot: &Path, mf: &MappedFile, name: &[u8]) -> PathBuf {
|
||||
let name = Path::new(os_str(name));
|
||||
if name.is_absolute() {
|
||||
apply_chroot(chroot, name).into_owned()
|
||||
} else {
|
||||
mf.name.parent().unwrap_or(Path::new(".")).join(name)
|
||||
}
|
||||
}
|
||||
|
||||
/// Opens members as they are consumed. Parallel readers can instead schedule
|
||||
/// thin-member paths on workers using get_thin_archive_member_paths().
|
||||
pub fn read_archive_members<'a>(
|
||||
chroot: &'a Path,
|
||||
mf: &'static MappedFile,
|
||||
) -> impl Iterator<Item = &'static MappedFile> + 'a {
|
||||
let thin = mf.data().starts_with(b"!<thin>\n");
|
||||
let base = mf.data().as_ptr() as usize;
|
||||
members(&mf.name, mf.data()).map(move |member| {
|
||||
if thin {
|
||||
mf.open_thin_member(&member_path(chroot, mf, member.name))
|
||||
} else {
|
||||
let name = PathBuf::from(os_str(member.name));
|
||||
mf.slice(name, member.data.as_ptr() as usize - base, member.data.len())
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
//! Integer and bit manipulation: alignment, bit fields and sign extension.
|
||||
|
||||
/// Rounds `value` up to a multiple of `align`, which must be zero or a power
|
||||
/// of two. Zero means "no alignment".
|
||||
#[inline]
|
||||
pub fn align_to(value: u64, align: u64) -> u64 {
|
||||
if align == 0 {
|
||||
return value;
|
||||
}
|
||||
debug_assert!(align.is_power_of_two());
|
||||
(value + align - 1) & !(align - 1)
|
||||
}
|
||||
|
||||
/// Rounds `val` up to the next value congruent to `modulus` modulo
|
||||
/// `align`: the smallest x >= val with x % align == modulus. ld64 places
|
||||
/// every subsection this way, keeping the offset it had within its
|
||||
/// section modulo the section's alignment, not merely rounding up to the
|
||||
/// section's alignment.
|
||||
pub fn align_to_mod(val: u64, align: u64, modulus: u64) -> u64 {
|
||||
debug_assert!(align.is_power_of_two() && modulus < align);
|
||||
if val <= modulus { modulus } else { align_to(val - modulus, align) + modulus }
|
||||
}
|
||||
|
||||
/// Rounds `value` down to a multiple of `align`, which must be a power of two.
|
||||
pub fn align_down(value: u64, align: u64) -> u64 {
|
||||
debug_assert!(align.is_power_of_two());
|
||||
value & !(align - 1)
|
||||
}
|
||||
|
||||
/// Returns bit `pos` of `value`.
|
||||
pub fn bit(value: u64, pos: u32) -> u64 {
|
||||
(value >> pos) & 1
|
||||
}
|
||||
|
||||
// Returns bits [hi:lo] of `value`.
|
||||
#[inline]
|
||||
pub fn bits(value: u64, hi: u32, lo: u32) -> u64 {
|
||||
(value >> lo) & ((1u64 << (hi - lo + 1)) - 1)
|
||||
}
|
||||
|
||||
// Casts `value` to a signed `n`-bit integer.
|
||||
// For example, sign_extend(x, 32) == x as i32 as i64 for any x.
|
||||
pub fn sign_extend(value: u64, n: u32) -> i64 {
|
||||
((value << (64 - n)) as i64) >> (64 - n)
|
||||
}
|
||||
|
||||
/// Whether `value` is representable as a signed `n`-bit integer.
|
||||
pub fn is_int(value: i64, n: u32) -> bool {
|
||||
sign_extend(value as u64, n) == value
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn sign_extension() {
|
||||
assert_eq!(sign_extend(0xff, 8), -1);
|
||||
assert_eq!(sign_extend(0x7f, 8), 127);
|
||||
assert!(is_int(-128, 8));
|
||||
assert!(!is_int(128, 8));
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
//! Finds the git commit that mold is built from, for the linkers' version
|
||||
//! strings.
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
|
||||
// Embed the current git commit hash into the mold executable. We ask git
|
||||
// instead of parsing files under .git because the repository format varies
|
||||
// (e.g. packed refs, reftable). When building from a source tarball, there's
|
||||
// no .git, and the hash is simply omitted.
|
||||
fn git_hash(source_dir: &Path) -> Option<String> {
|
||||
let dot_git = source_dir.join(".git");
|
||||
if !dot_git.exists() {
|
||||
// Source archives use the package version. If Git metadata is added
|
||||
// later, a clean rebuild is needed to embed the new commit hash.
|
||||
return None;
|
||||
}
|
||||
if dot_git.is_file() {
|
||||
println!("cargo:rerun-if-changed={}", dot_git.display());
|
||||
}
|
||||
|
||||
let git_path = |name: &str| -> Option<PathBuf> {
|
||||
let output = Command::new("git")
|
||||
.arg("-C")
|
||||
.arg(source_dir)
|
||||
.args(["rev-parse", "--git-path", name])
|
||||
.output()
|
||||
.ok()?;
|
||||
if !output.status.success() {
|
||||
return None;
|
||||
}
|
||||
let path = PathBuf::from(String::from_utf8(output.stdout).ok()?.trim());
|
||||
Some(if path.is_absolute() { path } else { source_dir.join(path) })
|
||||
};
|
||||
|
||||
let reftable = git_path("reftable/tables.list").filter(|p| p.exists());
|
||||
if let Some(head) = git_path("HEAD").filter(|p| p.exists()) {
|
||||
println!("cargo:rerun-if-changed={}", head.display());
|
||||
if let Ok(contents) = std::fs::read_to_string(&head)
|
||||
&& let Some(reference) =
|
||||
contents.strip_prefix("ref: ").map(str::trim).filter(|_| reftable.is_none())
|
||||
&& let Some(path) = git_path(reference)
|
||||
{
|
||||
// A packed reference may acquire a loose file later.
|
||||
// Watch its nearest existing directory until that happens.
|
||||
if let Some(path) = path.ancestors().find(|p| p.exists()) {
|
||||
println!("cargo:rerun-if-changed={}", path.display());
|
||||
}
|
||||
}
|
||||
}
|
||||
for path in git_path("packed-refs").into_iter().chain(reftable).filter(|p| p.exists()) {
|
||||
println!("cargo:rerun-if-changed={}", path.display());
|
||||
}
|
||||
|
||||
let output =
|
||||
Command::new("git").arg("-C").arg(source_dir).args(["rev-parse", "HEAD"]).output().ok()?;
|
||||
output.status.success().then(|| String::from_utf8_lossy(&output.stdout).trim().to_string())
|
||||
}
|
||||
|
||||
fn main() {
|
||||
println!("cargo:rerun-if-changed=build.rs");
|
||||
|
||||
// This crate is in common/ of the source tree.
|
||||
let manifest_dir = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap());
|
||||
if let Some(hash) = git_hash(manifest_dir.parent().unwrap()) {
|
||||
println!("cargo:rustc-env=MOLD_GIT_HASH={hash}");
|
||||
}
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
//! Byte strings: names that need not be UTF-8, and the text of response
|
||||
//! files, linker scripts and symbol lists.
|
||||
|
||||
/// Whether a byte is white space as isspace() takes it in the C locale,
|
||||
/// without the function call that a tokenizer would otherwise make for
|
||||
/// every byte of a response file.
|
||||
pub fn is_space(c: u8) -> bool {
|
||||
matches!(c, b' ' | b'\t' | b'\n' | 0x0b | 0x0c | b'\r')
|
||||
}
|
||||
|
||||
/// A byte string without the white space (see is_space) it starts and
|
||||
/// ends with.
|
||||
pub fn trim_space(mut bytes: &[u8]) -> &[u8] {
|
||||
while let [first, rest @ ..] = bytes
|
||||
&& is_space(*first)
|
||||
{
|
||||
bytes = rest;
|
||||
}
|
||||
while let [rest @ .., last] = bytes
|
||||
&& is_space(*last)
|
||||
{
|
||||
bytes = rest;
|
||||
}
|
||||
bytes
|
||||
}
|
||||
|
||||
/// The lines of a text file, as str::lines splits a string: at each
|
||||
/// '\n', a '\r' before it dropped, the last line ended or not. Lists
|
||||
/// of names (symbols, files) are bytes, whatever their encoding.
|
||||
pub fn lines(text: &[u8]) -> impl Iterator<Item = &[u8]> {
|
||||
let n = if text.is_empty() { 0 } else { usize::MAX };
|
||||
let text = text.strip_suffix(b"\n").unwrap_or(text);
|
||||
let lines = text.split(|&c| c == b'\n').take(n);
|
||||
lines.map(|line| line.strip_suffix(b"\r").unwrap_or(line))
|
||||
}
|
||||
|
||||
/// Splits a byte string at the first `sep`, as str::split_once does a
|
||||
/// string.
|
||||
pub fn split_once(bytes: &[u8], sep: u8) -> Option<(&[u8], &[u8])> {
|
||||
let i = memchr::memchr(sep, bytes)?;
|
||||
Some((&bytes[..i], &bytes[i + 1..]))
|
||||
}
|
||||
|
||||
/// Writes a NUL-terminated string and returns the number of bytes written.
|
||||
pub fn write_cstr(buf: &mut [u8], s: &[u8]) -> usize {
|
||||
buf[..s.len()].copy_from_slice(s);
|
||||
buf[s.len()] = 0;
|
||||
s.len() + 1
|
||||
}
|
||||
|
||||
/// Returns the NUL-terminated string starting at `offset` in a string table.
|
||||
/// The result excludes the terminator. A missing terminator yields the rest
|
||||
/// of the table.
|
||||
#[inline]
|
||||
pub fn cstr_at(table: &[u8], offset: usize) -> &[u8] {
|
||||
let rest = table.get(offset..).unwrap_or(&[]);
|
||||
let end = memchr::memchr(0, rest).unwrap_or(rest.len());
|
||||
&rest[..end]
|
||||
}
|
||||
|
||||
/// Converts bytes from a response file or linker script to an OS string.
|
||||
/// Unix paths can contain arbitrary non-NUL bytes.
|
||||
pub fn os_str(bytes: &[u8]) -> &std::ffi::OsStr {
|
||||
use bstr::ByteSlice;
|
||||
bytes.to_os_str().unwrap_or_else(|_| crate::fatal!("invalid OS string: {}", display(bytes)))
|
||||
}
|
||||
|
||||
/// Formats a byte string for diagnostics, replacing invalid UTF-8.
|
||||
pub fn display(bytes: &[u8]) -> std::borrow::Cow<'_, str> {
|
||||
String::from_utf8_lossy(bytes)
|
||||
}
|
||||
@@ -1,211 +0,0 @@
|
||||
//! CityHash64, as libc++'s std::hash of a string computes it: a
|
||||
//! Mach-O mergeable record hashes its names with that.
|
||||
//!
|
||||
//! This implementation is based on CityHash v1.0.3 at
|
||||
//! https://github.com/google/cityhash. libc++ differs from it in two
|
||||
//! places, marked "libc++:" below.
|
||||
|
||||
// The license of https://github.com/google/cityhash, from its COPYING:
|
||||
//
|
||||
// Copyright (c) 2011 Google, Inc.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
// Some primes between 2^63 and 2^64 for various uses.
|
||||
const K0: u64 = 0xc3a5_c85c_97cb_3127;
|
||||
const K1: u64 = 0xb492_b66f_be98_f273;
|
||||
const K2: u64 = 0x9ae1_6a3b_2f90_404f;
|
||||
const K3: u64 = 0xc949_d7c7_509e_6557;
|
||||
|
||||
fn fetch64(s: &[u8], at: usize) -> u64 {
|
||||
u64::from_le_bytes(s[at..at + 8].try_into().unwrap())
|
||||
}
|
||||
|
||||
fn fetch32(s: &[u8], at: usize) -> u32 {
|
||||
u32::from_le_bytes(s[at..at + 4].try_into().unwrap())
|
||||
}
|
||||
|
||||
fn shift_mix(val: u64) -> u64 {
|
||||
val ^ (val >> 47)
|
||||
}
|
||||
|
||||
/// Hash128to64() of the 128-bit value with `u` as its low half.
|
||||
fn hash_len_16(u: u64, v: u64) -> u64 {
|
||||
// Murmur-inspired hashing.
|
||||
const MUL: u64 = 0x9ddf_ea08_eb38_2d69;
|
||||
let mut a = (u ^ v).wrapping_mul(MUL);
|
||||
a ^= a >> 47;
|
||||
let mut b = (v ^ a).wrapping_mul(MUL);
|
||||
b ^= b >> 47;
|
||||
b.wrapping_mul(MUL)
|
||||
}
|
||||
|
||||
fn hash_len_0_to_16(s: &[u8]) -> u64 {
|
||||
let len = s.len();
|
||||
if len > 8 {
|
||||
let a = fetch64(s, 0);
|
||||
let b = fetch64(s, len - 8);
|
||||
return hash_len_16(a, b.wrapping_add(len as u64).rotate_right(len as u32)) ^ b;
|
||||
}
|
||||
if len >= 4 {
|
||||
// libc++: `a << 3` is in 32 bits, not 64.
|
||||
let a = fetch32(s, 0);
|
||||
return hash_len_16((len as u64).wrapping_add((a << 3) as u64), fetch32(s, len - 4) as u64);
|
||||
}
|
||||
if len > 0 {
|
||||
let (a, b, c) = (s[0] as u32, s[len >> 1] as u32, s[len - 1] as u32);
|
||||
let y = a.wrapping_add(b << 8);
|
||||
let z = (len as u32).wrapping_add(c << 2);
|
||||
return shift_mix((y as u64).wrapping_mul(K2) ^ (z as u64).wrapping_mul(K3))
|
||||
.wrapping_mul(K2);
|
||||
}
|
||||
K2
|
||||
}
|
||||
|
||||
// This probably works well for 16-byte strings as well, but it may be overkill
|
||||
// in that case.
|
||||
fn hash_len_17_to_32(s: &[u8]) -> u64 {
|
||||
let len = s.len();
|
||||
let a = fetch64(s, 0).wrapping_mul(K1);
|
||||
let b = fetch64(s, 8);
|
||||
let c = fetch64(s, len - 8).wrapping_mul(K2);
|
||||
let d = fetch64(s, len - 16).wrapping_mul(K0);
|
||||
hash_len_16(
|
||||
a.wrapping_sub(b).rotate_right(43).wrapping_add(c.rotate_right(30)).wrapping_add(d),
|
||||
a.wrapping_add((b ^ K3).rotate_right(20)).wrapping_sub(c).wrapping_add(len as u64),
|
||||
)
|
||||
}
|
||||
|
||||
/// A 16-byte hash for s[0] ... s[31], `a` and `b`. Quick and dirty.
|
||||
/// Callers do best to use "random-looking" values for `a` and `b`.
|
||||
fn weak_hash_len_32_with_seeds(s: &[u8], a: u64, b: u64) -> (u64, u64) {
|
||||
let (w, x, y, z) = (fetch64(s, 0), fetch64(s, 8), fetch64(s, 16), fetch64(s, 24));
|
||||
let mut a = a.wrapping_add(w);
|
||||
let mut b = b.wrapping_add(a).wrapping_add(z).rotate_right(21);
|
||||
let c = a;
|
||||
a = a.wrapping_add(x).wrapping_add(y);
|
||||
b = b.wrapping_add(a.rotate_right(44));
|
||||
(a.wrapping_add(z), b.wrapping_add(c))
|
||||
}
|
||||
|
||||
/// An 8-byte hash for 33 to 64 bytes.
|
||||
fn hash_len_33_to_64(s: &[u8]) -> u64 {
|
||||
let len = s.len();
|
||||
let mut z = fetch64(s, 24);
|
||||
let mut a = fetch64(s, 0)
|
||||
.wrapping_add((len as u64).wrapping_add(fetch64(s, len - 16)).wrapping_mul(K0));
|
||||
let mut b = a.wrapping_add(z).rotate_right(52);
|
||||
let mut c = a.rotate_right(37);
|
||||
a = a.wrapping_add(fetch64(s, 8));
|
||||
c = c.wrapping_add(a.rotate_right(7));
|
||||
a = a.wrapping_add(fetch64(s, 16));
|
||||
let vf = a.wrapping_add(z);
|
||||
let vs = b.wrapping_add(a.rotate_right(31)).wrapping_add(c);
|
||||
a = fetch64(s, 16).wrapping_add(fetch64(s, len - 32));
|
||||
// libc++: z is added to, not set.
|
||||
z = z.wrapping_add(fetch64(s, len - 8));
|
||||
b = a.wrapping_add(z).rotate_right(52);
|
||||
c = a.rotate_right(37);
|
||||
a = a.wrapping_add(fetch64(s, len - 24));
|
||||
c = c.wrapping_add(a.rotate_right(7));
|
||||
a = a.wrapping_add(fetch64(s, len - 16));
|
||||
let wf = a.wrapping_add(z);
|
||||
let ws = b.wrapping_add(a.rotate_right(31)).wrapping_add(c);
|
||||
let r = shift_mix(
|
||||
vf.wrapping_add(ws).wrapping_mul(K2).wrapping_add(wf.wrapping_add(vs).wrapping_mul(K0)),
|
||||
);
|
||||
shift_mix(r.wrapping_mul(K0).wrapping_add(vs)).wrapping_mul(K2)
|
||||
}
|
||||
|
||||
pub fn hash(s: &[u8]) -> u64 {
|
||||
let len = s.len();
|
||||
if len <= 16 {
|
||||
return hash_len_0_to_16(s);
|
||||
}
|
||||
if len <= 32 {
|
||||
return hash_len_17_to_32(s);
|
||||
}
|
||||
if len <= 64 {
|
||||
return hash_len_33_to_64(s);
|
||||
}
|
||||
|
||||
// For strings over 64 bytes we hash the end first, and then as we
|
||||
// loop we keep 56 bytes of state: v, w, x, y, and z.
|
||||
let n = len as u64;
|
||||
let mut x = fetch64(s, len - 40);
|
||||
let mut y = fetch64(s, len - 16).wrapping_add(fetch64(s, len - 56));
|
||||
let mut z = hash_len_16(fetch64(s, len - 48).wrapping_add(n), fetch64(s, len - 24));
|
||||
let mut v = weak_hash_len_32_with_seeds(&s[len - 64..], n, z);
|
||||
let mut w = weak_hash_len_32_with_seeds(&s[len - 32..], y.wrapping_add(K1), x);
|
||||
x = x.wrapping_mul(K1).wrapping_add(fetch64(s, 0));
|
||||
|
||||
// Decrease len to the nearest multiple of 64, and operate on 64-byte chunks.
|
||||
for c in s[..(len - 1) & !63].as_chunks::<64>().0 {
|
||||
x = (x.wrapping_add(y).wrapping_add(v.0).wrapping_add(fetch64(c, 8)))
|
||||
.rotate_right(37)
|
||||
.wrapping_mul(K1);
|
||||
y = (y.wrapping_add(v.1).wrapping_add(fetch64(c, 48))).rotate_right(42).wrapping_mul(K1);
|
||||
x ^= w.1;
|
||||
y = y.wrapping_add(v.0).wrapping_add(fetch64(c, 40));
|
||||
z = z.wrapping_add(w.0).rotate_right(33).wrapping_mul(K1);
|
||||
v = weak_hash_len_32_with_seeds(c, v.1.wrapping_mul(K1), x.wrapping_add(w.0));
|
||||
w = weak_hash_len_32_with_seeds(
|
||||
&c[32..],
|
||||
z.wrapping_add(w.1),
|
||||
y.wrapping_add(fetch64(c, 16)),
|
||||
);
|
||||
std::mem::swap(&mut z, &mut x);
|
||||
}
|
||||
hash_len_16(
|
||||
hash_len_16(v.0, w.0).wrapping_add(shift_mix(y).wrapping_mul(K1)).wrapping_add(z),
|
||||
hash_len_16(v.1, w.1).wrapping_add(x),
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
// std::hash<std::string_view> from Xcode's libc++, of each length's
|
||||
// prefix of `data`. Lengths 4 to 8 and 33 to 64 are where libc++
|
||||
// differs from CityHash v1.0.3.
|
||||
#[test]
|
||||
fn matches_libcxx() {
|
||||
let data: Vec<u8> =
|
||||
(0..400).map(|i: usize| (i * 131 + (i >> 3) * 7 + 0x5a) as u8).collect();
|
||||
for (len, expected) in [
|
||||
(0, 0x9ae1_6a3b_2f90_404f),
|
||||
(3, 0x51d1_83ca_b178_69e7),
|
||||
(5, 0x06ab_c9b6_c811_7ca8),
|
||||
(8, 0xed1f_6592_0960_3ce9),
|
||||
(12, 0x48b7_91b0_fe9b_e040),
|
||||
(17, 0x027a_8eab_ea05_2a3f),
|
||||
(32, 0x1c21_a4ef_fa7b_3f54),
|
||||
(33, 0x6583_3651_60f0_a2e0),
|
||||
(50, 0x1e81_10d2_800d_5748),
|
||||
(64, 0xd021_ebba_7723_3653),
|
||||
(65, 0x73c3_2c4f_c042_52a2),
|
||||
(128, 0xe173_4283_984b_218f),
|
||||
(129, 0xd017_1b7c_8ed1_4630),
|
||||
(400, 0x3683_4df1_0afa_73c8),
|
||||
] {
|
||||
assert_eq!(hash(&data[..len]), expected, "len={len}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,258 +0,0 @@
|
||||
//! This file implements a multi-threaded zlib and zstd compression
|
||||
//! routine.
|
||||
//!
|
||||
//! zlib-compressed data can be merged just by concatenation as long as
|
||||
//! each piece of data is flushed with Z_SYNC_FLUSH. In this file, we
|
||||
//! split input data into multiple shards, compress them individually
|
||||
//! and concatenate them. We then append a header, a trailer and a
|
||||
//! checksum so that the concatenated data is valid zlib-format data.
|
||||
//!
|
||||
//! zstd-compressed data can be merged in the same way.
|
||||
//!
|
||||
//! Using threads to compress data has a downside. Since the dictionary
|
||||
//! is reset at shard boundaries, the compression ratio is sacrificed a
|
||||
//! little bit. However, if the shard size is large enough, that loss is
|
||||
//! negligible in practice.
|
||||
|
||||
use std::io::{ErrorKind, Read};
|
||||
|
||||
use flate2::FlushDecompress;
|
||||
use rayon::prelude::*;
|
||||
use zlib_rs::adler32::{adler32, adler32_combine};
|
||||
|
||||
use crate::worker_local::WorkerLocal;
|
||||
|
||||
const SHARD_SIZE: usize = 1024 * 1024;
|
||||
|
||||
/// Compresses data with zlib or zstd at a fixed level, using all threads.
|
||||
pub struct Compressor(Method);
|
||||
|
||||
enum Method {
|
||||
Zlib(i32),
|
||||
Zstd(i32, ZstdContexts),
|
||||
}
|
||||
|
||||
// Creating a zstd context costs nearly as much as compressing a shard at
|
||||
// higher levels, so each Rayon worker reuses one for all shards.
|
||||
type ZstdContexts = WorkerLocal<Option<zstd::bulk::Compressor<'static>>>;
|
||||
|
||||
/// Compressed data, kept as shards until it is written to the output.
|
||||
pub enum CompressedData {
|
||||
Zlib { shards: Vec<Vec<u8>>, checksum: u32 },
|
||||
Zstd { shards: Vec<Vec<u8>> },
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for CompressedData {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "CompressedData({} bytes)", self.compressed_size())
|
||||
}
|
||||
}
|
||||
|
||||
/// Compresses a shard as a raw deflate stream ending with a sync flush,
|
||||
/// so that the stream ends on a byte boundary and can be concatenated.
|
||||
fn zlib_compress_shard(input: &[u8], level: i32) -> Vec<u8> {
|
||||
// Initialize a zlib stream. Since debug info is generally compressed
|
||||
// pretty well with lower compression levels, the default level is 1.
|
||||
let mut stream = libz_rs_sys::z_stream::default();
|
||||
// SAFETY: stream is a valid z_stream with the default allocator. It
|
||||
// stays at the same address until deflateEnd.
|
||||
let status = unsafe {
|
||||
libz_rs_sys::deflateInit2_(
|
||||
&mut stream,
|
||||
level,
|
||||
libz_rs_sys::Z_DEFLATED,
|
||||
-15,
|
||||
8,
|
||||
libz_rs_sys::Z_DEFAULT_STRATEGY,
|
||||
libz_rs_sys::zlibVersion(),
|
||||
size_of::<libz_rs_sys::z_stream>() as i32,
|
||||
)
|
||||
};
|
||||
assert_eq!(status, libz_rs_sys::Z_OK);
|
||||
|
||||
// Set an input buffer
|
||||
stream.avail_in = input.len() as u32;
|
||||
stream.next_in = input.as_ptr();
|
||||
|
||||
// Set an output buffer. deflateBound() returns an upper bound
|
||||
// on the compression size. +16 for Z_SYNC_FLUSH.
|
||||
// SAFETY: stream is initialized and remains valid through deflateEnd.
|
||||
let bound = unsafe { libz_rs_sys::deflateBound(&mut stream, stream.avail_in.into()) } as usize;
|
||||
let mut out = vec![0; bound + 16];
|
||||
|
||||
// Compress data. It writes all compressed bytes except the last
|
||||
// partial byte, so up to 7 bits may be left pending without being
|
||||
// written to the buffer.
|
||||
stream.avail_out = out.len() as u32;
|
||||
stream.next_out = out.as_mut_ptr();
|
||||
// SAFETY: the input and output buffers remain alive and the output has
|
||||
// deflateBound() + 16 bytes of space.
|
||||
let status = unsafe { libz_rs_sys::deflate(&mut stream, libz_rs_sys::Z_BLOCK) };
|
||||
assert_eq!(status, libz_rs_sys::Z_OK);
|
||||
|
||||
// This is a workaround for libbacktrace before 2022-04-06.
|
||||
//
|
||||
// Zlib is a bit stream, and what Z_SYNC_FLUSH does is to write a
|
||||
// three-bit value indicating the start of an uncompressed data
|
||||
// block followed by four bytes 00 00 ff ff which indicate that
|
||||
// the length of the block is zero. libbacktrace uses its own zlib
|
||||
// inflate routine, and it had a bug where, if that particular
|
||||
// three-bit value happened to end at a byte boundary, it accidentally
|
||||
// skipped the next byte.
|
||||
//
|
||||
// In order to avoid triggering that bug, we should avoid calling
|
||||
// deflate() with Z_SYNC_FLUSH if the current bit position is 5.
|
||||
// If it's 5, we insert an empty block consisting of 10 bits so
|
||||
// that the bit position is 7 in the next byte.
|
||||
//
|
||||
// https://github.com/ianlancetaylor/libbacktrace/pull/87
|
||||
let mut nbits = 0;
|
||||
// SAFETY: stream is initialized and nbits is a valid output pointer.
|
||||
let status =
|
||||
unsafe { libz_rs_sys::deflatePending(&mut stream, std::ptr::null_mut(), &raw mut nbits) };
|
||||
assert_eq!(status, libz_rs_sys::Z_OK);
|
||||
if nbits == 5 {
|
||||
// SAFETY: stream is initialized and has enough pending-buffer space.
|
||||
let status = unsafe { libz_rs_sys::deflatePrime(&mut stream, 10, 2) };
|
||||
assert_eq!(status, libz_rs_sys::Z_OK);
|
||||
}
|
||||
// SAFETY: stream and its input and output buffers remain valid.
|
||||
let status = unsafe { libz_rs_sys::deflate(&mut stream, libz_rs_sys::Z_SYNC_FLUSH) };
|
||||
assert_eq!(status, libz_rs_sys::Z_OK);
|
||||
|
||||
let len = out.len() - stream.avail_out as usize;
|
||||
// SAFETY: stream was initialized successfully and is no longer used.
|
||||
unsafe { libz_rs_sys::deflateEnd(&mut stream) };
|
||||
out.truncate(len);
|
||||
out
|
||||
}
|
||||
|
||||
impl Compressor {
|
||||
pub fn zlib(level: i32) -> Self {
|
||||
Self(Method::Zlib(level))
|
||||
}
|
||||
|
||||
pub fn zstd(level: i32) -> Self {
|
||||
Self(Method::Zstd(level, WorkerLocal::new(|| None)))
|
||||
}
|
||||
|
||||
pub fn compress(&self, input: &[u8]) -> CompressedData {
|
||||
match &self.0 {
|
||||
Method::Zlib(level) => zlib_compress(input, *level),
|
||||
Method::Zstd(level, contexts) => zstd_compress(input, *level, contexts),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn zlib_compress(input: &[u8], level: i32) -> CompressedData {
|
||||
// Compress each shard
|
||||
let (shards, adlers): (Vec<Vec<u8>>, Vec<u32>) = input
|
||||
.par_chunks(SHARD_SIZE)
|
||||
.map(|shard| (zlib_compress_shard(shard, level), adler32(1, shard)))
|
||||
.unzip();
|
||||
|
||||
// Combine checksums
|
||||
let mut checksum = adlers.first().copied().unwrap_or(1);
|
||||
for (adler, shard) in adlers.iter().zip(input.chunks(SHARD_SIZE)).skip(1) {
|
||||
checksum = adler32_combine(checksum, *adler, shard.len() as u64);
|
||||
}
|
||||
CompressedData::Zlib { shards, checksum }
|
||||
}
|
||||
|
||||
fn zstd_compress(input: &[u8], level: i32, contexts: &ZstdContexts) -> CompressedData {
|
||||
// Compress each shard
|
||||
let shards = input
|
||||
.par_chunks(SHARD_SIZE)
|
||||
.map(|shard| {
|
||||
let mut slot = contexts.get();
|
||||
let cctx = slot.get_or_insert_with(|| {
|
||||
zstd::bulk::Compressor::new(level).expect("cannot create a zstd context")
|
||||
});
|
||||
cctx.compress(shard).expect("zstd compression failed")
|
||||
})
|
||||
.collect();
|
||||
CompressedData::Zstd { shards }
|
||||
}
|
||||
|
||||
impl CompressedData {
|
||||
pub fn compressed_size(&self) -> usize {
|
||||
// Compute the total size
|
||||
match self {
|
||||
// the header and the trailer
|
||||
Self::Zlib { shards, .. } => 8 + shards.iter().map(Vec::len).sum::<usize>(),
|
||||
Self::Zstd { shards } => shards.iter().map(Vec::len).sum(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_to(&self, buf: &mut [u8]) {
|
||||
match self {
|
||||
Self::Zlib { shards, checksum } => {
|
||||
// Write a zlib-format header
|
||||
buf[0] = 0x78;
|
||||
buf[1] = 0x9c;
|
||||
|
||||
// Copy compressed data
|
||||
// +2 for the header
|
||||
let mut pos = 2;
|
||||
for shard in shards {
|
||||
buf[pos..pos + shard.len()].copy_from_slice(shard);
|
||||
pos += shard.len();
|
||||
}
|
||||
// Write a trailer
|
||||
// An empty final block, then the Adler-32 checksum.
|
||||
buf[pos] = 3;
|
||||
buf[pos + 1] = 0;
|
||||
buf[pos + 2..pos + 6].copy_from_slice(&checksum.to_be_bytes());
|
||||
}
|
||||
Self::Zstd { shards } => {
|
||||
// Copy compressed data
|
||||
let mut pos = 0;
|
||||
for shard in shards {
|
||||
buf[pos..pos + shard.len()].copy_from_slice(shard);
|
||||
pos += shard.len();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Decompresses a zlib stream into a buffer of known size.
|
||||
pub fn zlib_decompress(input: &[u8], out: &mut [u8]) -> Result<(), String> {
|
||||
let mut decompress = flate2::Decompress::new(true);
|
||||
loop {
|
||||
let in_pos = decompress.total_in() as usize;
|
||||
let out_pos = decompress.total_out() as usize;
|
||||
if out_pos >= out.len() {
|
||||
return Ok(());
|
||||
}
|
||||
let status = decompress
|
||||
.decompress(
|
||||
&input[in_pos.min(input.len())..],
|
||||
&mut out[out_pos..],
|
||||
FlushDecompress::None,
|
||||
)
|
||||
.map_err(|e| e.to_string())?;
|
||||
match status {
|
||||
flate2::Status::StreamEnd => break,
|
||||
flate2::Status::BufError if decompress.total_in() as usize >= input.len() => break,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
if (decompress.total_out() as usize) < out.len() {
|
||||
return Err("premature end of input".to_string());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Decompresses a zstd stream, stopping when the requested output is full.
|
||||
/// DWARF classification uses this to read just the unit header.
|
||||
pub fn zstd_decompress(input: &[u8], out: &mut [u8]) -> Result<(), String> {
|
||||
let mut decoder = zstd::stream::read::Decoder::with_buffer(input).map_err(|e| e.to_string())?;
|
||||
decoder.read_exact(out).map_err(|e| {
|
||||
if e.kind() == ErrorKind::UnexpectedEof {
|
||||
"premature end of input".to_string()
|
||||
} else {
|
||||
e.to_string()
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1,475 +0,0 @@
|
||||
//! A fast concurrent hash map.
|
||||
//!
|
||||
//! Concurrent Map
|
||||
//!
|
||||
//! This is an implementation of a fast concurrent hash map. Unlike
|
||||
//! ordinary hash tables, this impl just aborts if it becomes full.
|
||||
//! So you need to give a correct estimate of the final size before
|
||||
//! using it. We use this hash map to uniquify pieces of data in
|
||||
//! mergeable sections.
|
||||
//!
|
||||
//! We've implemented this ourselves because the performance of
|
||||
//! a concurrent hash map is critical for our linker.
|
||||
//!
|
||||
//! The map is an open-addressing table. Insertion is lock-free: a thread
|
||||
//! claims an empty bucket with a compare-and-swap on its key pointer,
|
||||
//! initializes the value, and then publishes the key; a thread that finds
|
||||
//! a claimed bucket spins until the key appears. Probing stays within a
|
||||
//! shard of the table, so the entries of a shard can be laid out
|
||||
//! independently of the others. Keys are byte strings that live for the
|
||||
//! whole link, such as string literals inside input files, hashed once by
|
||||
//! the caller.
|
||||
|
||||
use rayon::prelude::*;
|
||||
#[cfg(windows)]
|
||||
use std::alloc::{Layout, alloc_zeroed, dealloc};
|
||||
use std::cell::UnsafeCell;
|
||||
use std::mem::MaybeUninit;
|
||||
use std::ptr;
|
||||
use std::sync::atomic::{AtomicPtr, AtomicU32, Ordering};
|
||||
|
||||
pub const NUM_SHARDS: usize = 64;
|
||||
|
||||
// MIN_NBUCKETS is chosen so that even the smallest map has
|
||||
// MAX_RETRY buckets per shard; probing is confined to a shard, and
|
||||
// a probe that visits MAX_RETRY distinct occupied slots aborts.
|
||||
const MIN_NBUCKETS: usize = 16384;
|
||||
const MAX_RETRY: usize = 256;
|
||||
const _: () = assert!(MIN_NBUCKETS / NUM_SHARDS >= MAX_RETRY);
|
||||
|
||||
/// The key pointer of a bucket that a thread has claimed but not yet
|
||||
/// published: -1 marks the slot as claimed until its value has been
|
||||
/// initialized.
|
||||
const CLAIMED: *mut u8 = usize::MAX as *mut u8;
|
||||
|
||||
// In order to avoid unnecessary cache-line false sharing, we want
|
||||
// to make this object aligned to a reasonably large
|
||||
// power-of-two address.
|
||||
#[repr(C, align(32))]
|
||||
struct Entry<T> {
|
||||
key: AtomicPtr<u8>,
|
||||
// Published by the release store of `key` and read after the acquire
|
||||
// load that observes it, so relaxed accesses suffice.
|
||||
keylen: AtomicU32,
|
||||
value: UnsafeCell<MaybeUninit<T>>,
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
fn allocate_entries<T>(bufsize: usize) -> *mut Entry<T> {
|
||||
// SAFETY: this creates fresh private anonymous storage.
|
||||
let entries = unsafe {
|
||||
libc::mmap(
|
||||
ptr::null_mut(),
|
||||
bufsize,
|
||||
libc::PROT_READ | libc::PROT_WRITE,
|
||||
libc::MAP_ANONYMOUS | libc::MAP_PRIVATE,
|
||||
-1,
|
||||
0,
|
||||
)
|
||||
};
|
||||
if entries == libc::MAP_FAILED {
|
||||
let err = std::io::Error::last_os_error();
|
||||
panic!("mmap of {bufsize} bytes failed: {}", crate::error::strerror(&err));
|
||||
}
|
||||
entries.cast()
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn allocate_entries<T>(bufsize: usize) -> *mut Entry<T> {
|
||||
let layout = Layout::from_size_align(bufsize, align_of::<Entry<T>>())
|
||||
.expect("invalid concurrent-map layout");
|
||||
// SAFETY: the layout has nonzero size.
|
||||
let entries = unsafe { alloc_zeroed(layout).cast::<Entry<T>>() };
|
||||
if entries.is_null() {
|
||||
panic!("cannot allocate {bufsize} bytes for concurrent map");
|
||||
}
|
||||
entries
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
unsafe fn deallocate_entries<T>(entries: *mut Entry<T>, bufsize: usize) {
|
||||
// SAFETY: `entries` was mapped by `allocate_entries` with this size.
|
||||
let _ = unsafe { libc::munmap(entries.cast(), bufsize) };
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
unsafe fn deallocate_entries<T>(entries: *mut Entry<T>, bufsize: usize) {
|
||||
let layout = Layout::from_size_align(bufsize, align_of::<Entry<T>>())
|
||||
.expect("invalid concurrent-map layout");
|
||||
// SAFETY: `entries` was allocated by `allocate_entries` with this layout.
|
||||
unsafe { dealloc(entries.cast(), layout) };
|
||||
}
|
||||
|
||||
/// The index of a bucket, which identifies an entry for the life of
|
||||
/// the map.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub struct EntryId(u32);
|
||||
|
||||
impl EntryId {
|
||||
pub fn raw(self) -> u32 {
|
||||
self.0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn from_raw(raw: u32) -> Self {
|
||||
Self(raw)
|
||||
}
|
||||
}
|
||||
|
||||
/// A map under construction. Once concurrent insertion is complete, freeze
|
||||
/// it to look up and traverse its entries.
|
||||
pub struct ConcurrentMap<T> {
|
||||
entries: *mut Entry<T>,
|
||||
nbuckets: usize,
|
||||
}
|
||||
|
||||
// SAFETY: entries are shared between threads only through atomics and
|
||||
// values that are initialized before they become reachable.
|
||||
unsafe impl<T: Send + Sync> Send for ConcurrentMap<T> {}
|
||||
unsafe impl<T: Send + Sync> Sync for ConcurrentMap<T> {}
|
||||
|
||||
impl<T> Default for ConcurrentMap<T> {
|
||||
/// A map without buckets, to be replaced before use.
|
||||
fn default() -> Self {
|
||||
Self { entries: ptr::null_mut(), nbuckets: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ConcurrentMap<T> {
|
||||
/// A map with room for about `nkeys` distinct keys.
|
||||
pub fn with_capacity(nkeys: usize) -> Self {
|
||||
let nbuckets = nkeys.next_power_of_two().max(MIN_NBUCKETS);
|
||||
let bufsize = Self::bufsize(nbuckets);
|
||||
// mmap is faster than malloc + memset on Unix; the platform allocator
|
||||
// returns equivalently zeroed storage on Windows.
|
||||
let entries = allocate_entries(bufsize);
|
||||
// SAFETY: the range is the fresh allocation; the advice is only a
|
||||
// hint on targets that support it.
|
||||
unsafe { crate::mem::madvise_hugepage(entries.cast(), bufsize) };
|
||||
Self { entries, nbuckets }
|
||||
}
|
||||
|
||||
fn bufsize(nbuckets: usize) -> usize {
|
||||
size_of::<Entry<T>>().checked_mul(nbuckets).expect("table size overflow")
|
||||
}
|
||||
|
||||
/// Counts the number of entries.
|
||||
fn len(&self) -> usize {
|
||||
(0..self.nbuckets).filter(|&idx| self.is_occupied(idx)).count()
|
||||
}
|
||||
|
||||
fn entry(&self, idx: usize) -> &Entry<T> {
|
||||
debug_assert!(idx < self.nbuckets);
|
||||
// SAFETY: idx is within the allocation.
|
||||
unsafe { &*self.entries.add(idx) }
|
||||
}
|
||||
|
||||
fn is_occupied(&self, idx: usize) -> bool {
|
||||
let key = self.entry(idx).key.load(Ordering::Acquire);
|
||||
!key.is_null() && key != CLAIMED
|
||||
}
|
||||
|
||||
fn value_at(&self, idx: usize) -> &T {
|
||||
// SAFETY: callers only ask for values of published entries, which
|
||||
// were initialized before publication.
|
||||
unsafe { (*self.entry(idx).value.get()).assume_init_ref() }
|
||||
}
|
||||
|
||||
/// The probe sequence for a hash: the buckets of one shard, starting
|
||||
/// from the hash's home bucket and wrapping around within the shard.
|
||||
fn probe(&self, hash: u64) -> impl Iterator<Item = usize> {
|
||||
let begin = hash as usize & (self.nbuckets - 1);
|
||||
let mask = self.nbuckets / NUM_SHARDS - 1;
|
||||
(0..MAX_RETRY).map(move |i| (begin & !mask) | ((begin + i) & mask))
|
||||
}
|
||||
|
||||
/// Inserts `key` unless it is present, initializing the value with
|
||||
/// `init`. Returns the entry, its value and whether it was inserted.
|
||||
pub fn insert_with(
|
||||
&self,
|
||||
key: &'static [u8],
|
||||
hash: u64,
|
||||
init: impl FnOnce() -> T,
|
||||
) -> (EntryId, &T, bool) {
|
||||
self.insert_entry(
|
||||
key.as_ptr(),
|
||||
hash,
|
||||
|ptr, len| {
|
||||
// SAFETY: published keys remain live for the link.
|
||||
unsafe { std::slice::from_raw_parts(ptr, len as usize) == key }
|
||||
},
|
||||
|| (u32::try_from(key.len()).expect("key length fits in 32 bits"), init()),
|
||||
)
|
||||
}
|
||||
|
||||
/// Inserts a NUL-terminated key without storing its length at the call
|
||||
/// site. `initialize` computes the length and value only if this call
|
||||
/// claims a new entry.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// `key` must point to a NUL-terminated byte string that remains live for
|
||||
/// the lifetime of the map. Its length must fit in u32.
|
||||
pub unsafe fn insert_cstr_with(
|
||||
&self,
|
||||
key: *const u8,
|
||||
hash: u64,
|
||||
initialize: impl FnOnce(*const u8) -> (u32, T),
|
||||
) -> (EntryId, &T, bool) {
|
||||
// This variant avoids storing a length alongside every caller-side key.
|
||||
// Only a newly inserted key needs its length computed by `initialize`.
|
||||
self.insert_entry(
|
||||
key,
|
||||
hash,
|
||||
|ptr, _| {
|
||||
// SAFETY: guaranteed for `key` by the caller and for `ptr` by
|
||||
// the entry publication invariant.
|
||||
unsafe { libc::strcmp(key.cast(), ptr.cast()) == 0 }
|
||||
},
|
||||
|| initialize(key),
|
||||
)
|
||||
}
|
||||
|
||||
fn insert_entry(
|
||||
&self,
|
||||
key: *const u8,
|
||||
hash: u64,
|
||||
equals: impl Fn(*const u8, u32) -> bool,
|
||||
initialize: impl FnOnce() -> (u32, T),
|
||||
) -> (EntryId, &T, bool) {
|
||||
assert!(self.nbuckets > 0, "the map hasn't been sized");
|
||||
let mut initialize = Some(initialize);
|
||||
|
||||
for idx in self.probe(hash) {
|
||||
let ent = self.entry(idx);
|
||||
|
||||
// Avoid an atomic update when the slot is already occupied.
|
||||
let mut ptr = ent.key.load(Ordering::Acquire);
|
||||
if !ptr.is_null() && ptr != CLAIMED {
|
||||
if equals(ptr, ent.keylen.load(Ordering::Relaxed)) {
|
||||
return (EntryId(idx as u32), self.value_at(idx), false);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if ptr.is_null() {
|
||||
match ent.key.compare_exchange(
|
||||
ptr::null_mut(),
|
||||
CLAIMED,
|
||||
Ordering::Acquire,
|
||||
Ordering::Acquire,
|
||||
) {
|
||||
Ok(_) => {
|
||||
let (keylen, value) = initialize.take().expect("initialized once")();
|
||||
// SAFETY: the claim gives this thread exclusive
|
||||
// access to the bucket until the key is published.
|
||||
unsafe { (*ent.value.get()).write(value) };
|
||||
ent.keylen.store(keylen, Ordering::Relaxed);
|
||||
ent.key.store(key.cast_mut(), Ordering::Release);
|
||||
return (EntryId(idx as u32), self.value_at(idx), true);
|
||||
}
|
||||
Err(current) => ptr = current,
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for the thread that claimed the slot to publish its key.
|
||||
while ptr == CLAIMED {
|
||||
std::hint::spin_loop();
|
||||
ptr = ent.key.load(Ordering::Acquire);
|
||||
}
|
||||
if equals(ptr, ent.keylen.load(Ordering::Relaxed)) {
|
||||
return (EntryId(idx as u32), self.value_at(idx), false);
|
||||
}
|
||||
}
|
||||
panic!("the concurrent map is full");
|
||||
}
|
||||
|
||||
// Prefetch the bucket where a key with the given hash would be
|
||||
// probed first. Useful when a caller knows the hashes of upcoming
|
||||
// insertions, as probes into a large table miss the cache almost
|
||||
// every time.
|
||||
#[inline]
|
||||
pub fn prefetch(&self, hash: u64) {
|
||||
if self.nbuckets > 0 {
|
||||
let idx = hash as usize & (self.nbuckets - 1);
|
||||
// SAFETY: the masked index is within the allocated table.
|
||||
let ptr = unsafe { self.entries.add(idx) };
|
||||
crate::prefetch(ptr.cast());
|
||||
}
|
||||
}
|
||||
|
||||
/// Finishes insertion and returns the map for lookup and traversal.
|
||||
pub fn freeze(self) -> FrozenMap<T> {
|
||||
FrozenMap(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Drop for ConcurrentMap<T> {
|
||||
fn drop(&mut self) {
|
||||
if self.entries.is_null() {
|
||||
return;
|
||||
}
|
||||
if std::mem::needs_drop::<T>() {
|
||||
for idx in 0..self.nbuckets {
|
||||
if self.is_occupied(idx) {
|
||||
// SAFETY: the value of a published entry is initialized
|
||||
// and dropped exactly once, here.
|
||||
unsafe { (*self.entry(idx).value.get()).assume_init_drop() };
|
||||
}
|
||||
}
|
||||
}
|
||||
// SAFETY: allocated in with_capacity and not yet released.
|
||||
unsafe { deallocate_entries(self.entries, Self::bufsize(self.nbuckets)) };
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> std::fmt::Debug for ConcurrentMap<T> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "ConcurrentMap({} entries)", self.len())
|
||||
}
|
||||
}
|
||||
|
||||
/// A map after all insertions, for lookup and traversal. Values are
|
||||
/// reachable only through shared references.
|
||||
#[derive(Debug)]
|
||||
pub struct FrozenMap<T>(ConcurrentMap<T>);
|
||||
|
||||
impl<T> Default for FrozenMap<T> {
|
||||
fn default() -> Self {
|
||||
Self(ConcurrentMap::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> FrozenMap<T> {
|
||||
/// The published key of a bucket, if any.
|
||||
fn key_at(&self, idx: usize) -> Option<&'static [u8]> {
|
||||
let ent = self.0.entry(idx);
|
||||
let key = ent.key.load(Ordering::Acquire);
|
||||
if key.is_null() || key == CLAIMED {
|
||||
return None;
|
||||
}
|
||||
// SAFETY: a published key is a live 'static slice whose length was
|
||||
// written before the key pointer.
|
||||
Some(unsafe {
|
||||
std::slice::from_raw_parts(key, ent.keylen.load(Ordering::Relaxed) as usize)
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns a published entry identified by an ID obtained from this map.
|
||||
/// Linker callers only retain IDs returned by insertion or traversal of
|
||||
/// the owning map; they never probe a missing key or pass another map's
|
||||
/// ID here. That invariant makes the unchecked, initialized-value lookup
|
||||
/// below valid without a second occupancy check on this hot path.
|
||||
pub fn get(&self, id: EntryId) -> &T {
|
||||
self.0.value_at(id.0 as usize)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn prefetch(&self, id: EntryId) {
|
||||
crate::prefetch(std::ptr::from_ref(self.0.entry(id.0 as usize)).cast());
|
||||
}
|
||||
|
||||
pub fn key(&self, id: EntryId) -> &'static [u8] {
|
||||
self.key_at(id.0 as usize).expect("an occupied bucket")
|
||||
}
|
||||
|
||||
/// Returns the entries in the given shard in a deterministic order.
|
||||
///
|
||||
/// Linear probing fills the same set of buckets whatever the order
|
||||
/// keys were inserted in, but which of two colliding keys got the
|
||||
/// earlier bucket depends on it, so each run of adjacent occupied
|
||||
/// buckets is sorted by key.
|
||||
pub fn sorted_entries(&self, shard: usize) -> Vec<EntryId> {
|
||||
if self.0.nbuckets == 0 {
|
||||
return Vec::new();
|
||||
}
|
||||
let shard_size = self.0.nbuckets / NUM_SHARDS;
|
||||
let begin = shard * shard_size;
|
||||
let mut end = begin + shard_size;
|
||||
let occupied = |idx: usize| self.0.is_occupied(idx);
|
||||
|
||||
let size = (begin..end).filter(|&idx| occupied(idx)).count();
|
||||
let mut vec: Vec<EntryId> = Vec::with_capacity(size);
|
||||
|
||||
// Since the shard is circular, we need to handle the last entries
|
||||
// as if they were next to the first entries.
|
||||
while begin < end && occupied(end - 1) {
|
||||
end -= 1;
|
||||
vec.push(EntryId(end as u32));
|
||||
}
|
||||
|
||||
let sort_run = |run: &mut [EntryId]| {
|
||||
run.sort_unstable_by(|&a, &b| {
|
||||
let (ka, kb) = (self.key(a), self.key(b));
|
||||
ka.len().cmp(&kb.len()).then_with(|| ka.cmp(kb))
|
||||
});
|
||||
};
|
||||
|
||||
// Find entries contiguous in the buckets and sort them.
|
||||
let mut last = 0;
|
||||
let mut i = begin;
|
||||
while i < end {
|
||||
while i < end && occupied(i) {
|
||||
vec.push(EntryId(i as u32));
|
||||
i += 1;
|
||||
}
|
||||
sort_run(&mut vec[last..]);
|
||||
last = vec.len();
|
||||
while i < end && !occupied(i) {
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
vec
|
||||
}
|
||||
|
||||
/// Returns all map entries in deterministic order.
|
||||
pub fn sorted_entries_all(&self) -> Vec<EntryId>
|
||||
where
|
||||
T: Send + Sync,
|
||||
{
|
||||
(0..NUM_SHARDS).into_par_iter().flat_map_iter(|shard| self.sorted_entries(shard)).collect()
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.0.len()
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len() == 0
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn insert_and_lookup() {
|
||||
let map: ConcurrentMap<u32> = ConcurrentMap::with_capacity(100);
|
||||
let keys: Vec<&'static [u8]> = (0..1000)
|
||||
.map(|i| &*Box::leak(format!("key{i}").into_bytes().into_boxed_slice()))
|
||||
.collect();
|
||||
let hash = |k: &[u8]| xxhash_rust::xxh3::xxh3_64(k);
|
||||
let mut ids = Vec::with_capacity(keys.len());
|
||||
for (i, &k) in keys.iter().enumerate() {
|
||||
let (id, v, inserted) = map.insert_with(k, hash(k), || i as u32);
|
||||
assert!(inserted);
|
||||
assert_eq!(*v, i as u32);
|
||||
ids.push(id);
|
||||
}
|
||||
for (i, &k) in keys.iter().enumerate() {
|
||||
let (id, v, inserted) = map.insert_with(k, hash(k), || 99999);
|
||||
assert!(!inserted);
|
||||
assert_eq!(id, ids[i]);
|
||||
assert_eq!(*v, i as u32);
|
||||
}
|
||||
let map = map.freeze();
|
||||
for (i, &id) in ids.iter().enumerate() {
|
||||
assert_eq!(map.key(id), keys[i]);
|
||||
assert_eq!(*map.get(id), i as u32);
|
||||
}
|
||||
assert_eq!(map.len(), 1000);
|
||||
let sorted: usize = (0..NUM_SHARDS).map(|s| map.sorted_entries(s).len()).sum();
|
||||
assert_eq!(sorted, 1000);
|
||||
}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
//! Symbol name demangling for diagnostics.
|
||||
|
||||
/// Demangles an Itanium C++ ABI symbol name, if `name` is one the
|
||||
/// demangler reads: its identifiers are ASCII, so a name with other
|
||||
/// bytes stays as it is.
|
||||
pub fn demangle_cpp(name: &[u8]) -> Option<String> {
|
||||
if !name.starts_with(b"_Z") {
|
||||
return None;
|
||||
}
|
||||
cpp_demangle::Symbol::new(name).ok()?.demangle().ok()
|
||||
}
|
||||
|
||||
/// Demangles a Rust symbol name in either the legacy or the v0 scheme.
|
||||
/// Crate disambiguator hashes are omitted, as rustc's own tools do.
|
||||
pub fn demangle_rust(name: &[u8]) -> Option<String> {
|
||||
let name = std::str::from_utf8(name).ok()?;
|
||||
if let Ok(sym) = rustc_demangle::try_demangle(name) {
|
||||
return Some(format!("{sym:#}"));
|
||||
}
|
||||
|
||||
// A legacy name may be followed by characters that aren't part of the
|
||||
// mangling, as in `_ZN2ns7versionEv`, which is `ns::version` and a
|
||||
// trailing `v`. Demangle the path and keep the rest as it is.
|
||||
let end = legacy_path_end(name)?;
|
||||
let sym = rustc_demangle::try_demangle(&name[..end]).ok()?;
|
||||
Some(format!("{sym:#}{}", &name[end..]))
|
||||
}
|
||||
|
||||
/// The end of the legacy-mangled path `_ZN<len><ident>...E` at the start
|
||||
/// of `name`, if there is one.
|
||||
fn legacy_path_end(name: &str) -> Option<usize> {
|
||||
let mut rest = name.strip_prefix("_ZN")?;
|
||||
loop {
|
||||
if rest.starts_with('E') {
|
||||
return Some(name.len() - rest.len() + 1);
|
||||
}
|
||||
let digits = rest.bytes().take_while(u8::is_ascii_digit).count();
|
||||
let len: usize = rest[..digits].parse().ok()?;
|
||||
if len == 0 {
|
||||
return None;
|
||||
}
|
||||
rest = rest.get(digits.checked_add(len)?..)?;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn rust_legacy_with_suffix() {
|
||||
assert_eq!(demangle_rust(b"_ZN2ns7versionEv").as_deref(), Some("ns::versionv"));
|
||||
assert_eq!(demangle_rust(b"_ZN3foo3barE").as_deref(), Some("foo::bar"));
|
||||
assert_eq!(demangle_rust(b"_ZN3foo").as_deref(), None);
|
||||
assert_eq!(demangle_rust(b"main"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rust_legacy_length_overflow() {
|
||||
// Adding the digit count to this length wraps to zero, so an
|
||||
// unchecked parser would keep visiting the same identifier.
|
||||
let len = usize::MAX - usize::MAX.ilog10() as usize;
|
||||
let name = format!("_ZN{len}E");
|
||||
assert_eq!(demangle_rust(name.as_bytes()), None);
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
//! Fixed-byte-order integer I/O on byte slices.
|
||||
//!
|
||||
//! These functions read and write integers of a known byte order, for code
|
||||
//! that patches section contents of a known target. Target-generic code
|
||||
//! uses the methods on the linker's target type, which pick the byte order
|
||||
//! from the target.
|
||||
|
||||
macro_rules! endian_io {
|
||||
($int:ty, $readb:ident, $readl:ident, $writeb:ident, $writel:ident) => {
|
||||
#[inline]
|
||||
pub fn $readb(bytes: &[u8]) -> $int {
|
||||
let bytes = bytes[..size_of::<$int>()].try_into().unwrap();
|
||||
<$int>::from_be_bytes(bytes)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn $readl(bytes: &[u8]) -> $int {
|
||||
let bytes = bytes[..size_of::<$int>()].try_into().unwrap();
|
||||
<$int>::from_le_bytes(bytes)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn $writeb(bytes: &mut [u8], value: $int) {
|
||||
bytes[..size_of::<$int>()].copy_from_slice(&value.to_be_bytes());
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn $writel(bytes: &mut [u8], value: $int) {
|
||||
bytes[..size_of::<$int>()].copy_from_slice(&value.to_le_bytes());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
endian_io!(u16, read_ub16, read_ul16, write_ub16, write_ul16);
|
||||
endian_io!(u32, read_ub32, read_ul32, write_ub32, write_ul32);
|
||||
endian_io!(u64, read_ub64, read_ul64, write_ub64, write_ul64);
|
||||
endian_io!(i16, read_ib16, read_il16, write_ib16, write_il16);
|
||||
endian_io!(i32, read_ib32, read_il32, write_ib32, write_il32);
|
||||
endian_io!(i64, read_ib64, read_il64, write_ib64, write_il64);
|
||||
-190
@@ -1,190 +0,0 @@
|
||||
//! Error, warning and fatal-error reporting.
|
||||
//!
|
||||
//! Errors don't abort the link immediately: the linker keeps going so that
|
||||
//! all problems are reported in one run, then exits before writing the
|
||||
//! output. Fatal errors are for conditions the linker can't continue past.
|
||||
|
||||
use std::fmt;
|
||||
use std::io::{self, Write};
|
||||
use std::sync::Mutex;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
/// Whether to demangle symbol names in diagnostics. This is process-wide
|
||||
/// state so that `Display` implementations, which have no access to the
|
||||
/// linker context, can consult it.
|
||||
static DEMANGLE: AtomicBool = AtomicBool::new(true);
|
||||
|
||||
pub fn set_demangle(enabled: bool) {
|
||||
DEMANGLE.store(enabled, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
pub fn demangle_enabled() -> bool {
|
||||
DEMANGLE.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
// Diagnostic settings, error state and output serialization are process-wide.
|
||||
static COLOR: AtomicBool = AtomicBool::new(false);
|
||||
static FATAL_WARNINGS: AtomicBool = AtomicBool::new(false);
|
||||
static SUPPRESS_WARNINGS: AtomicBool = AtomicBool::new(false);
|
||||
static NOINHIBIT_EXEC: AtomicBool = AtomicBool::new(false);
|
||||
static HAS_ERROR: AtomicBool = AtomicBool::new(false);
|
||||
static OUTPUT_LOCK: Mutex<()> = Mutex::new(());
|
||||
|
||||
pub fn set_color(on: bool) {
|
||||
COLOR.store(on, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
pub fn set_fatal_warnings(on: bool) {
|
||||
FATAL_WARNINGS.store(on, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
pub fn set_suppress_warnings(on: bool) {
|
||||
SUPPRESS_WARNINGS.store(on, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
pub fn set_noinhibit_exec(on: bool) {
|
||||
NOINHIBIT_EXEC.store(on, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
pub fn noinhibit_exec() -> bool {
|
||||
NOINHIBIT_EXEC.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
// Format each message before taking the lock so diagnostics from different
|
||||
// threads cannot interleave.
|
||||
fn emit(prefix_mono: &str, prefix_color: &str, msg: fmt::Arguments) {
|
||||
let prefix = if COLOR.load(Ordering::Relaxed) { prefix_color } else { prefix_mono };
|
||||
let text = format!("{prefix}{msg}\n");
|
||||
let _guard = OUTPUT_LOCK.lock().unwrap_or_else(|e| e.into_inner());
|
||||
let _ = io::stderr().write_all(text.as_bytes());
|
||||
}
|
||||
|
||||
/// Reports an unrecoverable error and exits.
|
||||
pub fn fatal(msg: fmt::Arguments) -> ! {
|
||||
emit("mold: fatal: ", "mold: \x1b[0;1;31mfatal:\x1b[0m ", msg);
|
||||
exit_after_cleanup(1);
|
||||
}
|
||||
|
||||
/// Reports an error. With `--noinhibit-exec` it is downgraded to a warning.
|
||||
pub fn error(msg: fmt::Arguments) {
|
||||
if NOINHIBIT_EXEC.load(Ordering::Relaxed) {
|
||||
emit("mold: warning: ", "mold: \x1b[0;1;35mwarning:\x1b[0m ", msg);
|
||||
} else {
|
||||
emit("mold: error: ", "mold: \x1b[0;1;31merror:\x1b[0m ", msg);
|
||||
HAS_ERROR.store(true, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
/// Reports a warning. With `--fatal-warnings` it is promoted to an error.
|
||||
pub fn warn(msg: fmt::Arguments) {
|
||||
if SUPPRESS_WARNINGS.load(Ordering::Relaxed) {
|
||||
return;
|
||||
}
|
||||
if FATAL_WARNINGS.load(Ordering::Relaxed) {
|
||||
emit("mold: error: ", "mold: \x1b[0;1;31merror:\x1b[0m ", msg);
|
||||
HAS_ERROR.store(true, Ordering::Relaxed);
|
||||
} else {
|
||||
emit("mold: warning: ", "mold: \x1b[0;1;35mwarning:\x1b[0m ", msg);
|
||||
}
|
||||
}
|
||||
|
||||
/// Prints a message with no prefix to stderr: some reports of what the
|
||||
/// link did (the Mach-O linker's -why_live, -why_load and text
|
||||
/// relocations) come in lines of their own.
|
||||
pub fn notice(msg: fmt::Arguments) {
|
||||
emit("", "", msg);
|
||||
}
|
||||
|
||||
/// Prints an informational message to stdout.
|
||||
pub fn out(msg: fmt::Arguments) {
|
||||
let text = format!("{msg}\n");
|
||||
let _guard = OUTPUT_LOCK.lock().unwrap_or_else(|e| e.into_inner());
|
||||
let _ = io::stdout().write_all(text.as_bytes());
|
||||
}
|
||||
|
||||
/// Returns the text that C's strerror gives for an I/O error. Unlike
|
||||
/// `io::Error`'s `Display`, it doesn't append " (os error N)". An error that
|
||||
/// doesn't come from the OS is formatted as usual.
|
||||
pub fn strerror(err: &io::Error) -> String {
|
||||
#[cfg(not(windows))]
|
||||
if let Some(errno) = err.raw_os_error() {
|
||||
let mut buf = [0u8; 256];
|
||||
// SAFETY: strerror_r writes at most `buf.len()` bytes to `buf`.
|
||||
let ret = unsafe { libc::strerror_r(errno, buf.as_mut_ptr().cast(), buf.len()) };
|
||||
if ret == 0
|
||||
&& let Ok(msg) = std::ffi::CStr::from_bytes_until_nul(&buf)
|
||||
{
|
||||
return msg.to_string_lossy().into_owned();
|
||||
}
|
||||
}
|
||||
err.to_string()
|
||||
}
|
||||
|
||||
/// Exits with a failure status if any error has been reported.
|
||||
pub fn checkpoint() {
|
||||
if HAS_ERROR.load(Ordering::Relaxed) {
|
||||
exit_after_cleanup(1);
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes a partially-written output file, then terminates the process
|
||||
/// without running destructors. Input files are mapped for the process's
|
||||
/// lifetime, so there is nothing else to release.
|
||||
pub fn exit_after_cleanup(status: i32) -> ! {
|
||||
crate::output_file::cleanup();
|
||||
let _ = io::stdout().flush();
|
||||
let _ = io::stderr().flush();
|
||||
// SAFETY: `_exit` only terminates the process.
|
||||
#[cfg(not(windows))]
|
||||
unsafe {
|
||||
libc::_exit(status)
|
||||
}
|
||||
#[cfg(windows)]
|
||||
std::process::exit(status)
|
||||
}
|
||||
|
||||
/// Makes a panic remove a partially-written output file, as a fatal error
|
||||
/// does, before the default hook reports the panic. Nothing recovers from
|
||||
/// a panic, so it always terminates the process.
|
||||
pub fn install_panic_hook() {
|
||||
let default_hook = std::panic::take_hook();
|
||||
std::panic::set_hook(Box::new(move |info| {
|
||||
crate::output_file::cleanup();
|
||||
default_hook(info);
|
||||
}));
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! fatal {
|
||||
($($arg:tt)*) => {
|
||||
$crate::error::fatal(format_args!($($arg)*))
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! error {
|
||||
($($arg:tt)*) => {
|
||||
$crate::error::error(format_args!($($arg)*))
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! warn {
|
||||
($($arg:tt)*) => {
|
||||
$crate::error::warn(format_args!($($arg)*))
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! notice {
|
||||
($($arg:tt)*) => {
|
||||
$crate::error::notice(format_args!($($arg)*))
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! out {
|
||||
($($arg:tt)*) => {
|
||||
$crate::error::out(format_args!($($arg)*))
|
||||
};
|
||||
}
|
||||
-826
@@ -1,826 +0,0 @@
|
||||
//! The glob matcher used for symbol name patterns in version scripts,
|
||||
//! dynamic list files and Mach-O symbol lists. Exact, prefix and suffix
|
||||
//! patterns are matched directly. Simple substring patterns such as these
|
||||
//!
|
||||
//! *16QAccessibleCache*
|
||||
//! *32QAbstractFileIconProviderPrivate*
|
||||
//! *17QPixmapIconEngine*
|
||||
//!
|
||||
//! are combined into an Aho-Corasick matcher, essentially a trie with
|
||||
//! additional links that searches for all of them in one pass over the
|
||||
//! input (https://en.wikipedia.org/wiki/Aho-Corasick_algorithm). It can only
|
||||
//! search substrings, so complex patterns such as `*foo*bar*` are matched
|
||||
//! with the non-recursive algorithm described at
|
||||
//! https://research.swtch.com/glob, and if there are many of them, a
|
||||
//! bit-parallel NFA matches them together.
|
||||
|
||||
use std::collections::VecDeque;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
enum Token {
|
||||
Str(Vec<u8>),
|
||||
Star,
|
||||
Question,
|
||||
Bracket(Box<[bool; 256]>),
|
||||
}
|
||||
|
||||
/// How a pattern spells bracket expressions and escapes: as fnmatch(3)
|
||||
/// does, which is how the ELF linkers read version scripts, or as
|
||||
/// ld-prime reads a Mach-O symbol list (see ld_prime_bracket).
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
enum Syntax {
|
||||
Fnmatch,
|
||||
LdPrime,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct Pattern {
|
||||
tokens: Vec<Token>,
|
||||
// Searchers for the literals that follow stars, indexed like `tokens`.
|
||||
// Building a searcher can take longer than searching a symbol name, so
|
||||
// we build them once.
|
||||
finders: Vec<Option<memchr::memmem::Finder<'static>>>,
|
||||
value: i64,
|
||||
}
|
||||
|
||||
impl Pattern {
|
||||
fn compile(mut pat: &[u8], value: i64, syntax: Syntax) -> Option<Self> {
|
||||
let mut tokens: Vec<Token> = Vec::new();
|
||||
|
||||
while let Some((&c, rest)) = pat.split_first() {
|
||||
pat = rest;
|
||||
match c {
|
||||
b'[' if syntax == Syntax::LdPrime => {
|
||||
let end = pat.iter().position(|&c| c == b']')?;
|
||||
tokens.push(Token::Bracket(ld_prime_bracket(&pat[..end])?));
|
||||
pat = &pat[end + 1..];
|
||||
}
|
||||
b'[' => {
|
||||
// Here are a few bracket pattern examples:
|
||||
//
|
||||
// [abc]: a, b or c
|
||||
// [$\]!]: $, ] or !
|
||||
// [a-czg-i]: a, b, c, z, g, h, or i
|
||||
// [!a-z]: Any character except lowercase letters
|
||||
//
|
||||
// Both `!` and `^` are accepted as negation markers. `!` is the
|
||||
// POSIX/shell convention used by other linkers. `^` was mold's
|
||||
// original syntax and is kept for backward compatibility.
|
||||
let mut chars = Box::new([false; 256]);
|
||||
let mut negate = false;
|
||||
let mut closed = false;
|
||||
|
||||
if let Some((&b'!' | &b'^', rest)) = pat.split_first() {
|
||||
negate = true;
|
||||
pat = rest;
|
||||
}
|
||||
|
||||
while let Some((&c, rest)) = pat.split_first() {
|
||||
if c == b']' {
|
||||
pat = rest;
|
||||
closed = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if c == b'\\' {
|
||||
pat = rest;
|
||||
if pat.is_empty() {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
if pat.len() >= 3 && pat[1] == b'-' {
|
||||
let start = pat[0];
|
||||
let mut end = pat[2];
|
||||
pat = &pat[3..];
|
||||
if end == b'\\' {
|
||||
end = *pat.first()?;
|
||||
pat = &pat[1..];
|
||||
}
|
||||
if end < start {
|
||||
return None;
|
||||
}
|
||||
chars[start as usize..=end as usize].fill(true);
|
||||
} else {
|
||||
chars[pat[0] as usize] = true;
|
||||
pat = &pat[1..];
|
||||
}
|
||||
}
|
||||
|
||||
if !closed {
|
||||
return None;
|
||||
}
|
||||
if negate {
|
||||
for flag in chars.iter_mut() {
|
||||
*flag = !*flag;
|
||||
}
|
||||
}
|
||||
tokens.push(Token::Bracket(chars));
|
||||
}
|
||||
b'?' => tokens.push(Token::Question),
|
||||
b'*' => {
|
||||
if !matches!(tokens.last(), Some(Token::Star)) {
|
||||
tokens.push(Token::Star);
|
||||
}
|
||||
}
|
||||
// A backslash takes the next character as itself. ld-prime
|
||||
// takes one at the end as itself too.
|
||||
b'\\' => match pat.split_first() {
|
||||
Some((&escaped, rest)) => {
|
||||
pat = rest;
|
||||
push_char(&mut tokens, escaped);
|
||||
}
|
||||
None if syntax == Syntax::LdPrime => push_char(&mut tokens, c),
|
||||
None => return None,
|
||||
},
|
||||
_ => push_char(&mut tokens, c),
|
||||
}
|
||||
}
|
||||
|
||||
let finders = (0..tokens.len())
|
||||
.map(|i| match &tokens[i] {
|
||||
Token::Str(literal) if i > 0 && matches!(tokens[i - 1], Token::Star) => {
|
||||
Some(memchr::memmem::Finder::new(literal).into_owned())
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
Some(Self { tokens, finders, value })
|
||||
}
|
||||
|
||||
fn matches(&self, s: &[u8]) -> bool {
|
||||
if let Some(Token::Str(suffix)) = self.tokens.last()
|
||||
&& !s.ends_with(suffix)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
let mut x = 0;
|
||||
let mut y = 0;
|
||||
let mut next: Option<(usize, usize)> = None;
|
||||
|
||||
while x < s.len() || y < self.tokens.len() {
|
||||
if y < self.tokens.len() {
|
||||
let tok = &self.tokens[y];
|
||||
match tok {
|
||||
Token::Str(literal) => {
|
||||
if s[x..].starts_with(literal) {
|
||||
x += literal.len();
|
||||
y += 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
Token::Star => {
|
||||
next = Some((x + 1, y));
|
||||
y += 1;
|
||||
if let Some(Some(finder)) = self.finders.get(y) {
|
||||
let Some(pos) = finder.find(&s[x..]) else {
|
||||
return false;
|
||||
};
|
||||
let pos = x + pos;
|
||||
next = Some((pos + 1, y - 1));
|
||||
x = pos + finder.needle().len();
|
||||
y += 1;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
Token::Question => {
|
||||
if x < s.len() {
|
||||
x += 1;
|
||||
y += 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
Token::Bracket(chars) => {
|
||||
if x < s.len() && chars[s[x] as usize] {
|
||||
x += 1;
|
||||
y += 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Retry the last star after assigning one more input byte to it.
|
||||
match next {
|
||||
Some((nx, ny)) if nx <= s.len() => {
|
||||
x = nx;
|
||||
y = ny;
|
||||
}
|
||||
_ => return false,
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/// The characters a bracket expression matches, given what is between
|
||||
/// its brackets, as ld-prime reads one: it ends at the first `]` (so
|
||||
/// `[]` matches nothing) and knows no negation or escapes - `[!a]`
|
||||
/// matches `!` or `a`, `[\a]` `\` or `a`. A `-` between two characters
|
||||
/// makes a range from the one before it, which after a range is that
|
||||
/// `-` itself (`[a-b-d]` is `a`-`b` and `-`-`d`), to the one after it;
|
||||
/// a reversed range is empty. A `-` first or last makes the expression,
|
||||
/// and so its pattern, malformed (None): it matches nothing.
|
||||
fn ld_prime_bracket(class: &[u8]) -> Option<Box<[bool; 256]>> {
|
||||
let mut chars = Box::new([false; 256]);
|
||||
let mut prev = None;
|
||||
let mut i = 0;
|
||||
while i < class.len() {
|
||||
let c = class[i];
|
||||
if c == b'-' {
|
||||
let (Some(start), Some(&end)) = (prev, class.get(i + 1)) else {
|
||||
return None;
|
||||
};
|
||||
for x in start..=end {
|
||||
chars[x as usize] = true;
|
||||
}
|
||||
i += 1;
|
||||
} else {
|
||||
chars[c as usize] = true;
|
||||
}
|
||||
prev = Some(c);
|
||||
i += 1;
|
||||
}
|
||||
Some(chars)
|
||||
}
|
||||
|
||||
fn push_char(tokens: &mut Vec<Token>, c: u8) {
|
||||
if let Some(Token::Str(literal)) = tokens.last_mut() {
|
||||
literal.push(c);
|
||||
} else {
|
||||
tokens.push(Token::Str(vec![c]));
|
||||
}
|
||||
}
|
||||
|
||||
// Nfa matches many glob patterns in parallel by representing each state
|
||||
// with one bit. It is used only for large pattern sets; matching individual
|
||||
// patterns is faster when there are only a few of them.
|
||||
#[derive(Debug, Default)]
|
||||
struct Nfa {
|
||||
initial_states: Vec<u64>,
|
||||
star_states: Vec<u64>,
|
||||
accept_states: Vec<u64>,
|
||||
char_masks: Vec<u64>,
|
||||
values: Vec<i64>,
|
||||
}
|
||||
|
||||
impl Nfa {
|
||||
fn compile(patterns: &[Pattern]) -> Self {
|
||||
let mut num_states = 0;
|
||||
for pattern in patterns {
|
||||
num_states += 1;
|
||||
for tok in &pattern.tokens {
|
||||
match tok {
|
||||
Token::Str(literal) => num_states += literal.len(),
|
||||
Token::Star => {}
|
||||
_ => num_states += 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let num_words = num_states.div_ceil(64);
|
||||
let mut nfa = Self {
|
||||
initial_states: vec![0; num_words],
|
||||
star_states: vec![0; num_words],
|
||||
accept_states: vec![0; num_words],
|
||||
char_masks: vec![0; 256 * num_words],
|
||||
values: vec![-1; num_states],
|
||||
};
|
||||
|
||||
let set_bit = |vec: &mut [u64], pos: usize| vec[pos / 64] |= 1 << (pos % 64);
|
||||
let mut state = 0;
|
||||
|
||||
for pattern in patterns {
|
||||
set_bit(&mut nfa.initial_states, state);
|
||||
for tok in &pattern.tokens {
|
||||
match tok {
|
||||
Token::Str(literal) => {
|
||||
for &c in literal {
|
||||
state += 1;
|
||||
nfa.char_masks[c as usize * num_words + state / 64] |=
|
||||
1 << (state % 64);
|
||||
}
|
||||
}
|
||||
Token::Star => set_bit(&mut nfa.star_states, state),
|
||||
Token::Question => {
|
||||
state += 1;
|
||||
for c in 0..256 {
|
||||
nfa.char_masks[c * num_words + state / 64] |= 1 << (state % 64);
|
||||
}
|
||||
}
|
||||
Token::Bracket(chars) => {
|
||||
state += 1;
|
||||
for c in 0..256 {
|
||||
if chars[c] {
|
||||
nfa.char_masks[c * num_words + state / 64] |= 1 << (state % 64);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
set_bit(&mut nfa.accept_states, state);
|
||||
nfa.values[state] = pattern.value;
|
||||
state += 1;
|
||||
}
|
||||
nfa
|
||||
}
|
||||
|
||||
fn is_empty(&self) -> bool {
|
||||
self.initial_states.is_empty()
|
||||
}
|
||||
|
||||
fn matches(&self, s: &[u8]) -> i64 {
|
||||
let num_words = self.initial_states.len();
|
||||
let mut states = self.initial_states.clone();
|
||||
|
||||
for &c in s {
|
||||
let mask = &self.char_masks[c as usize * num_words..][..num_words];
|
||||
let mut carry = 0;
|
||||
for i in 0..num_words {
|
||||
let old = states[i];
|
||||
let next = (old << 1) | carry;
|
||||
states[i] = (old & self.star_states[i]) | (next & mask[i]);
|
||||
carry = old >> 63;
|
||||
}
|
||||
}
|
||||
|
||||
let mut value = -1;
|
||||
for (i, &state) in states.iter().enumerate() {
|
||||
let mut word = state & self.accept_states[i];
|
||||
while word != 0 {
|
||||
let bit = word.trailing_zeros() as usize;
|
||||
value = value.max(self.values[i * 64 + bit]);
|
||||
word &= word - 1;
|
||||
}
|
||||
}
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
/// An Aho-Corasick automaton for patterns that are plain substring
|
||||
/// searches, such as `*foo*`, `foo*` or `*foo`. Anchors are represented by
|
||||
/// a NUL byte at the beginning or end of the pattern.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
struct AhoCorasick {
|
||||
// Most trie nodes have only one child. The root uses a dense table because
|
||||
// it is visited for almost every input byte; other edges are stored sparsely.
|
||||
root_children: Vec<i32>,
|
||||
nodes: Vec<TrieNode>,
|
||||
max_value: i64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct TrieNode {
|
||||
value: i64,
|
||||
suffix_link: i32,
|
||||
first_child: i32,
|
||||
next_sibling: i32,
|
||||
ch: u8,
|
||||
}
|
||||
|
||||
impl Default for TrieNode {
|
||||
fn default() -> Self {
|
||||
Self { value: -1, suffix_link: -1, first_child: -1, next_sibling: -1, ch: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
impl AhoCorasick {
|
||||
fn can_handle(pat: &[u8]) -> bool {
|
||||
let pat = pat.strip_prefix(b"*").unwrap_or(pat);
|
||||
let pat = pat.strip_suffix(b"*").unwrap_or(pat);
|
||||
!pat.is_empty() && !pat.iter().any(|&c| matches!(c, b'*' | b'?' | b'[' | b'\\'))
|
||||
}
|
||||
|
||||
fn find_child(&self, node: i32, ch: u8) -> i32 {
|
||||
if node == 0 {
|
||||
return self.root_children[ch as usize];
|
||||
}
|
||||
let mut i = self.nodes[node as usize].first_child;
|
||||
while i != -1 {
|
||||
if self.nodes[i as usize].ch == ch {
|
||||
return i;
|
||||
}
|
||||
i = self.nodes[i as usize].next_sibling;
|
||||
}
|
||||
-1
|
||||
}
|
||||
|
||||
fn add_child(&mut self, node: i32, ch: u8) -> i32 {
|
||||
let child = self.find_child(node, ch);
|
||||
if child != -1 {
|
||||
return child;
|
||||
}
|
||||
let child = self.nodes.len() as i32;
|
||||
let sibling = self.nodes[node as usize].first_child;
|
||||
self.nodes.push(TrieNode { next_sibling: sibling, ch, ..TrieNode::default() });
|
||||
self.nodes[node as usize].first_child = child;
|
||||
if node == 0 {
|
||||
self.root_children[ch as usize] = child;
|
||||
}
|
||||
child
|
||||
}
|
||||
|
||||
fn add(&mut self, pat: &[u8], value: i64) {
|
||||
debug_assert!(Self::can_handle(pat));
|
||||
self.max_value = self.max_value.max(value);
|
||||
if self.nodes.is_empty() {
|
||||
self.root_children = vec![-1; 256];
|
||||
self.nodes.push(TrieNode::default());
|
||||
}
|
||||
|
||||
// We handle "foo" as if "\0foo\0", "*foo" as if "foo\0", "foo*" as
|
||||
// if "\0foo", and "*foo*" as if "foo". Aho-Corasick can do only
|
||||
// substring matching, so we use \0 as a beginning/end-of-string
|
||||
// marker.
|
||||
let mut idx = 0;
|
||||
if !pat.starts_with(b"*") {
|
||||
idx = self.add_child(idx, 0);
|
||||
}
|
||||
for &c in pat {
|
||||
if c != b'*' {
|
||||
idx = self.add_child(idx, c);
|
||||
}
|
||||
}
|
||||
if !pat.ends_with(b"*") {
|
||||
idx = self.add_child(idx, 0);
|
||||
}
|
||||
let node = &mut self.nodes[idx as usize];
|
||||
node.value = node.value.max(value);
|
||||
}
|
||||
|
||||
fn compile(&mut self) {
|
||||
if self.nodes.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
// A failure link may refer to any shallower node, so failure links
|
||||
// must be constructed breadth-first.
|
||||
let mut queue = VecDeque::new();
|
||||
let mut child = self.nodes[0].first_child;
|
||||
while child != -1 {
|
||||
self.nodes[child as usize].suffix_link = 0;
|
||||
queue.push_back(child);
|
||||
child = self.nodes[child as usize].next_sibling;
|
||||
}
|
||||
|
||||
while let Some(idx) = queue.pop_front() {
|
||||
let mut child = self.nodes[idx as usize].first_child;
|
||||
while child != -1 {
|
||||
let ch = self.nodes[child as usize].ch;
|
||||
let mut suffix = self.nodes[idx as usize].suffix_link;
|
||||
while suffix != 0 && self.find_child(suffix, ch) == -1 {
|
||||
suffix = self.nodes[suffix as usize].suffix_link;
|
||||
}
|
||||
let next = self.find_child(suffix, ch);
|
||||
if next != -1 {
|
||||
suffix = next;
|
||||
}
|
||||
self.nodes[child as usize].suffix_link = suffix;
|
||||
let suffix_value = self.nodes[suffix as usize].value;
|
||||
let node = &mut self.nodes[child as usize];
|
||||
node.value = node.value.max(suffix_value);
|
||||
queue.push_back(child);
|
||||
child = self.nodes[child as usize].next_sibling;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This runs once per byte while matching symbol names. Keep the state
|
||||
// in the caller's registers instead of outlining the loop body.
|
||||
#[inline(always)]
|
||||
fn walk(&self, c: u8, idx: &mut i32, value: &mut i64) -> i64 {
|
||||
let mut j = *idx;
|
||||
while j != -1 {
|
||||
let child = self.find_child(j, c);
|
||||
if child != -1 {
|
||||
*idx = child;
|
||||
*value = (*value).max(self.nodes[child as usize].value);
|
||||
return *value;
|
||||
}
|
||||
j = self.nodes[j as usize].suffix_link;
|
||||
}
|
||||
*idx = 0;
|
||||
*value
|
||||
}
|
||||
|
||||
fn find(&self, s: &[u8]) -> i64 {
|
||||
if self.nodes.is_empty() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
let mut idx = 0;
|
||||
let mut value = -1;
|
||||
self.walk(0, &mut idx, &mut value);
|
||||
for &c in s {
|
||||
if self.walk(c, &mut idx, &mut value) == self.max_value {
|
||||
return self.max_value;
|
||||
}
|
||||
}
|
||||
self.walk(0, &mut idx, &mut value)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct Literal {
|
||||
pat: Vec<u8>,
|
||||
value: i64,
|
||||
}
|
||||
|
||||
/// A set of glob patterns, each associated with a value. Looking up a
|
||||
/// string returns the largest value among the matching patterns, or -1.
|
||||
#[derive(Debug)]
|
||||
pub struct Glob {
|
||||
// Patterns that need only a literal string comparison are kept out
|
||||
// of the automaton-based matchers below, which scan the entire input
|
||||
// string per query. Real version scripts consist almost entirely of
|
||||
// such patterns (e.g. `local: *;` or `v8dbg_*;`), and we match them
|
||||
// against every defined symbol name.
|
||||
match_all: i64, // "*"
|
||||
max_value: i64,
|
||||
exacts: Vec<Literal>,
|
||||
prefixes: Vec<Literal>,
|
||||
suffixes: Vec<Literal>,
|
||||
patterns: Vec<Pattern>, // "foo*bar"
|
||||
aho_corasick: AhoCorasick,
|
||||
nfa: Nfa,
|
||||
}
|
||||
|
||||
/// Collects patterns before compiling an immutable matcher.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct GlobBuilder {
|
||||
glob: Glob,
|
||||
}
|
||||
|
||||
fn is_literal(pat: &[u8]) -> bool {
|
||||
!pat.iter().any(|&c| matches!(c, b'*' | b'?' | b'[' | b'\\'))
|
||||
}
|
||||
|
||||
impl Default for Glob {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
match_all: -1,
|
||||
max_value: -1,
|
||||
exacts: Vec::new(),
|
||||
prefixes: Vec::new(),
|
||||
suffixes: Vec::new(),
|
||||
patterns: Vec::new(),
|
||||
aho_corasick: AhoCorasick::default(),
|
||||
nfa: Nfa::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GlobBuilder {
|
||||
/// Adds a pattern. Returns false if the pattern is malformed.
|
||||
pub fn add(&mut self, pat: &[u8], value: i64) -> bool {
|
||||
self.add_pattern(pat, value, Syntax::Fnmatch)
|
||||
}
|
||||
|
||||
/// Adds a pattern of a Mach-O symbol list, whose bracket expressions
|
||||
/// and escapes are read as ld-prime reads them (see ld_prime_bracket).
|
||||
/// Returns false if the pattern is malformed.
|
||||
pub fn add_ld_prime(&mut self, pat: &[u8], value: i64) -> bool {
|
||||
self.add_pattern(pat, value, Syntax::LdPrime)
|
||||
}
|
||||
|
||||
/// Adds a string that matches itself alone, wildcards and all.
|
||||
pub fn add_literal(&mut self, s: &[u8], value: i64) {
|
||||
debug_assert!(value >= 0);
|
||||
self.glob.max_value = self.glob.max_value.max(value);
|
||||
self.glob.exacts.push(Literal { pat: s.to_vec(), value });
|
||||
}
|
||||
|
||||
fn add_pattern(&mut self, pat: &[u8], value: i64, syntax: Syntax) -> bool {
|
||||
debug_assert!(value >= 0);
|
||||
self.glob.max_value = self.glob.max_value.max(value);
|
||||
|
||||
// Match-all, exact, prefix and suffix patterns are handled with
|
||||
// plain string comparisons instead of the matchers below, which
|
||||
// have to scan the entire input string on every query.
|
||||
if pat == b"*" {
|
||||
self.glob.match_all = self.glob.match_all.max(value);
|
||||
return true;
|
||||
}
|
||||
if is_literal(pat) {
|
||||
self.glob.exacts.push(Literal { pat: pat.to_vec(), value });
|
||||
return true;
|
||||
}
|
||||
if let Some(prefix) = pat.strip_suffix(b"*").filter(|p| is_literal(p)) {
|
||||
self.glob.prefixes.push(Literal { pat: prefix.to_vec(), value });
|
||||
return true;
|
||||
}
|
||||
if let Some(suffix) = pat.strip_prefix(b"*").filter(|p| is_literal(p)) {
|
||||
self.glob.suffixes.push(Literal { pat: suffix.to_vec(), value });
|
||||
return true;
|
||||
}
|
||||
// If the pattern requires only a single substring search, the
|
||||
// Aho-Corasick algorithm is even faster than our glob matcher.
|
||||
if AhoCorasick::can_handle(pat) {
|
||||
self.glob.aho_corasick.add(pat, value);
|
||||
return true;
|
||||
}
|
||||
match Pattern::compile(pat, value, syntax) {
|
||||
Some(pattern) => {
|
||||
self.glob.patterns.push(pattern);
|
||||
true
|
||||
}
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Consumes the construction state. The result supports parallel queries.
|
||||
pub fn build(self) -> Glob {
|
||||
let mut glob = self.glob;
|
||||
if glob.match_all == glob.max_value {
|
||||
return glob;
|
||||
}
|
||||
|
||||
// For duplicate names, retain the largest value.
|
||||
glob.exacts.sort_unstable_by(|a, b| a.pat.cmp(&b.pat).then(b.value.cmp(&a.value)));
|
||||
glob.exacts.dedup_by(|a, b| a.pat == b.pat);
|
||||
if glob.patterns.len() >= 64 {
|
||||
glob.nfa = Nfa::compile(&glob.patterns);
|
||||
glob.patterns = Vec::new();
|
||||
}
|
||||
glob.aho_corasick.compile();
|
||||
glob
|
||||
}
|
||||
}
|
||||
|
||||
impl Glob {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.max_value < 0
|
||||
}
|
||||
|
||||
/// Returns the largest value of a matching pattern, or -1 if none match.
|
||||
pub fn find(&self, s: &[u8]) -> i64 {
|
||||
let mut value = self.match_all;
|
||||
if value == self.max_value {
|
||||
return value;
|
||||
}
|
||||
|
||||
if let Ok(i) = self.exacts.binary_search_by(|l| l.pat.as_slice().cmp(s)) {
|
||||
value = value.max(self.exacts[i].value);
|
||||
}
|
||||
for p in &self.prefixes {
|
||||
if value < p.value && s.starts_with(&p.pat) {
|
||||
value = p.value;
|
||||
}
|
||||
}
|
||||
for p in &self.suffixes {
|
||||
if value < p.value && s.ends_with(&p.pat) {
|
||||
value = p.value;
|
||||
}
|
||||
}
|
||||
if value == self.max_value {
|
||||
return value;
|
||||
}
|
||||
value = value.max(self.aho_corasick.find(s));
|
||||
if value == self.max_value {
|
||||
return value;
|
||||
}
|
||||
if !self.nfa.is_empty() {
|
||||
value = value.max(self.nfa.matches(s));
|
||||
}
|
||||
for p in &self.patterns {
|
||||
if value < p.value && p.matches(s) {
|
||||
value = p.value;
|
||||
}
|
||||
}
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn glob(pats: &[&str]) -> Glob {
|
||||
let mut g = GlobBuilder::default();
|
||||
for (i, p) in pats.iter().enumerate() {
|
||||
assert!(g.add(p.as_bytes(), i as i64));
|
||||
}
|
||||
g.build()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_has_no_matches() {
|
||||
let g = Glob::default();
|
||||
assert!(g.is_empty());
|
||||
assert_eq!(g.find(b"missing"), -1);
|
||||
|
||||
let mut g = GlobBuilder::default();
|
||||
assert!(g.add(b"present", 7));
|
||||
let g = g.build();
|
||||
assert!(!g.is_empty());
|
||||
assert_eq!(g.find(b"present"), 7);
|
||||
assert_eq!(g.find(b"missing"), -1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shared_priorities_preserve_highest_match() {
|
||||
let mut g = GlobBuilder::default();
|
||||
for (pattern, priority) in
|
||||
[("*", 0), ("*inner*", 2), ("prefix*", 2), ("*suffix", 2), ("exact", 3), ("p?efix*", 4)]
|
||||
{
|
||||
assert!(g.add(pattern.as_bytes(), priority));
|
||||
}
|
||||
let g = g.build();
|
||||
for (name, expected) in
|
||||
[("none", 0), ("hasinnersuffix", 2), ("suffix", 2), ("exact", 3), ("prefixsuffix", 4)]
|
||||
{
|
||||
assert_eq!(g.find(name.as_bytes()), expected);
|
||||
}
|
||||
let mut g = GlobBuilder::default();
|
||||
assert!(g.add(b"*inner*", 0));
|
||||
assert!(g.add(b"*suffix", 0));
|
||||
let g = g.build();
|
||||
assert_eq!(g.find(b"innersuffix"), 0);
|
||||
assert_eq!(g.find(b"none"), -1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn literals() {
|
||||
let g = glob(&["foo", "bar*", "*baz", "*"]);
|
||||
assert_eq!(g.find(b"foo"), 3);
|
||||
assert_eq!(g.find(b"barx"), 3);
|
||||
assert_eq!(g.find(b"xbaz"), 3);
|
||||
let g = glob(&["foo", "bar*", "*baz"]);
|
||||
assert_eq!(g.find(b"foo"), 0);
|
||||
assert_eq!(g.find(b"barx"), 1);
|
||||
assert_eq!(g.find(b"xbaz"), 2);
|
||||
assert_eq!(g.find(b"nothing"), -1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn complex() {
|
||||
let g = glob(&["a*b*c", "x?z", "[a-c]d", "*mid*", "[!x]y"]);
|
||||
assert_eq!(g.find(b"axxbyyc"), 0);
|
||||
assert_eq!(g.find(b"xyz"), 1);
|
||||
assert_eq!(g.find(b"bd"), 2);
|
||||
assert_eq!(g.find(b"dd"), -1);
|
||||
assert_eq!(g.find(b"leftmidright"), 3);
|
||||
assert_eq!(g.find(b"ay"), 4);
|
||||
assert_eq!(g.find(b"xy"), -1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ld_prime_brackets() {
|
||||
let mut g = GlobBuilder::default();
|
||||
for (i, pat) in
|
||||
["a[a-b-d]", "b[c-a]", "c[\\a]", "d[]x", "e\\*", "f\\", "g[!x]y"].iter().enumerate()
|
||||
{
|
||||
assert!(g.add_ld_prime(pat.as_bytes(), i as i64));
|
||||
}
|
||||
let g = g.build();
|
||||
for (name, expected) in [
|
||||
("ab", 0),
|
||||
("a-", 0),
|
||||
("aA", 0),
|
||||
("ac", 0),
|
||||
("a,", -1),
|
||||
("bc", 1),
|
||||
("bb", -1),
|
||||
("c\\", 2),
|
||||
("ca", 2),
|
||||
("d]x", -1),
|
||||
("dx", -1),
|
||||
("e*", 4),
|
||||
("ex", -1),
|
||||
("f\\", 5),
|
||||
("g!y", 6),
|
||||
("gxy", 6),
|
||||
("gay", -1),
|
||||
] {
|
||||
assert_eq!(g.find(name.as_bytes()), expected, "{name}");
|
||||
}
|
||||
let mut g = GlobBuilder::default();
|
||||
for pat in ["x[", "y[a-]", "z[-a]"] {
|
||||
assert!(!g.add_ld_prime(pat.as_bytes(), 0));
|
||||
}
|
||||
assert!(!g.add(b"f\\", 0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn literal_matches_only_itself() {
|
||||
let mut g = GlobBuilder::default();
|
||||
g.add_literal(b"a*b", 0);
|
||||
let g = g.build();
|
||||
assert_eq!(g.find(b"a*b"), 0);
|
||||
assert_eq!(g.find(b"axb"), -1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn many_patterns_use_nfa() {
|
||||
let pats: Vec<String> = (0..100).map(|i| format!("p{i}*q?r")).collect();
|
||||
let refs: Vec<&str> = pats.iter().map(String::as_str).collect();
|
||||
let g = glob(&refs);
|
||||
assert_eq!(g.find(b"p42xxqyr"), 42);
|
||||
assert_eq!(g.find(b"p42xxqr"), -1);
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
//! This file implements the HyperLogLog algorithm, which estimates
|
||||
//! the number of unique items in a given multiset.
|
||||
//!
|
||||
//! For more info, read
|
||||
//! https://engineering.fb.com/2018/12/13/data-infrastructure/hyperloglog
|
||||
|
||||
/// Estimates the number of distinct values in a stream of hashes.
|
||||
///
|
||||
/// Each hash selects a register by its low bits; the register keeps the
|
||||
/// longest run of leading zeros seen, plus one. The estimate is a scaled
|
||||
/// harmonic mean of the registers.
|
||||
#[derive(Clone)]
|
||||
pub struct HyperLogLog {
|
||||
registers: [u8; Self::NUM_REGISTERS],
|
||||
}
|
||||
|
||||
impl HyperLogLog {
|
||||
const NUM_REGISTERS: usize = 2048;
|
||||
const ALPHA: f64 = 0.79402;
|
||||
|
||||
#[inline]
|
||||
pub fn insert(&mut self, hash: u64) {
|
||||
let register = &mut self.registers[hash as usize & (Self::NUM_REGISTERS - 1)];
|
||||
*register = (*register).max(hash.leading_zeros() as u8 + 1);
|
||||
}
|
||||
|
||||
/// Combines two estimators, as if every value had been inserted into
|
||||
/// one of them.
|
||||
#[inline]
|
||||
pub fn merged(mut self, other: &Self) -> Self {
|
||||
for (a, b) in self.registers.iter_mut().zip(&other.registers) {
|
||||
*a = (*a).max(*b);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn cardinality(&self) -> u64 {
|
||||
let z: f64 = self.registers.iter().map(|&r| 2f64.powi(-i32::from(r))).sum();
|
||||
(Self::ALPHA * (Self::NUM_REGISTERS * Self::NUM_REGISTERS) as f64 / z) as u64
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for HyperLogLog {
|
||||
fn default() -> Self {
|
||||
Self { registers: [0; Self::NUM_REGISTERS] }
|
||||
}
|
||||
}
|
||||
|
||||
impl Extend<u64> for HyperLogLog {
|
||||
fn extend<I: IntoIterator<Item = u64>>(&mut self, hashes: I) {
|
||||
for hash in hashes {
|
||||
self.insert(hash);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn estimates_within_a_few_percent() {
|
||||
let mut hll = HyperLogLog::default();
|
||||
hll.extend((0..100_000u64).map(|i| xxhash_rust::xxh3::xxh3_64(&i.to_le_bytes())));
|
||||
let n = hll.cardinality();
|
||||
// The scale constant follows mold, whose estimates run about 10% high.
|
||||
assert!((95_000..125_000).contains(&n), "estimate {n}");
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
//! Limits the number of concurrent mold processes.
|
||||
|
||||
// Many build systems attempt to invoke as many linker processes as there
|
||||
// are cores, based on the assumption that the linker is single-threaded.
|
||||
// However, since mold is multi-threaded, such build systems' behavior is
|
||||
// not beneficial and just increases the overall peak memory usage.
|
||||
// On machines with limited memory, this could lead to an out-of-memory
|
||||
// error.
|
||||
//
|
||||
// This file implements a feature that limits the number of concurrent
|
||||
// mold processes to just 1 for each user. It is intended to be used as
|
||||
// `MOLD_JOBS=1 ninja` or `MOLD_JOBS=1 make -j$(nproc)`.
|
||||
|
||||
#[cfg(not(windows))]
|
||||
use std::ffi::CStr;
|
||||
#[cfg(not(windows))]
|
||||
use std::fs::{File, OpenOptions};
|
||||
#[cfg(not(windows))]
|
||||
use std::os::unix::{fs::OpenOptionsExt, io::AsRawFd};
|
||||
#[cfg(not(windows))]
|
||||
use std::sync::Mutex;
|
||||
|
||||
#[cfg(not(windows))]
|
||||
static LOCK_FILE: Mutex<Option<File>> = Mutex::new(None);
|
||||
|
||||
#[cfg(not(windows))]
|
||||
pub fn acquire_global_lock() {
|
||||
if std::env::var_os("MOLD_JOBS").as_deref() != Some(std::ffi::OsStr::new("1")) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Like other XDG variables, an empty XDG_RUNTIME_DIR counts as unset.
|
||||
let path = if let Some(dir) = std::env::var_os("XDG_RUNTIME_DIR").filter(|d| !d.is_empty()) {
|
||||
std::path::PathBuf::from(dir).join("mold-lock")
|
||||
} else {
|
||||
// SAFETY: getpwuid returns either null or a pointer to process-global
|
||||
// storage. We copy the name before making another libc call.
|
||||
let name = unsafe {
|
||||
let pwd = libc::getpwuid(libc::getuid());
|
||||
if pwd.is_null() || (*pwd).pw_name.is_null() {
|
||||
return;
|
||||
}
|
||||
CStr::from_ptr((*pwd).pw_name).to_bytes().to_vec()
|
||||
};
|
||||
let name = String::from_utf8_lossy(&name);
|
||||
std::path::PathBuf::from(format!("/tmp/mold-lock-{name}"))
|
||||
};
|
||||
let Ok(file) =
|
||||
OpenOptions::new().write(true).create(true).truncate(false).mode(0o600).open(path)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
// SAFETY: file owns a valid descriptor. Keep lockf for compatibility with
|
||||
// other mold processes; flock uses a different locking protocol.
|
||||
if unsafe { libc::lockf(file.as_raw_fd(), libc::F_LOCK, 0) } == -1 {
|
||||
return;
|
||||
}
|
||||
*LOCK_FILE.lock().unwrap() = Some(file);
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
pub fn acquire_global_lock() {}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
pub fn release_global_lock() {
|
||||
drop(LOCK_FILE.lock().unwrap().take());
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
pub fn release_global_lock() {}
|
||||
@@ -1,132 +0,0 @@
|
||||
//! LEB128, the variable-length integer encoding of DWARF and of other
|
||||
//! tables in object files.
|
||||
|
||||
use crate::bits::sign_extend;
|
||||
|
||||
/// Appends `value` in unsigned LEB128 encoding.
|
||||
pub fn encode_uleb(out: &mut Vec<u8>, mut value: u64) {
|
||||
loop {
|
||||
let byte = (value & 0x7f) as u8;
|
||||
value >>= 7;
|
||||
if value == 0 {
|
||||
out.push(byte);
|
||||
return;
|
||||
}
|
||||
out.push(byte | 0x80);
|
||||
}
|
||||
}
|
||||
|
||||
/// Appends `value` in signed LEB128 encoding.
|
||||
pub fn encode_sleb(out: &mut Vec<u8>, mut value: i64) {
|
||||
loop {
|
||||
let byte = (value & 0x7f) as u8;
|
||||
value >>= 7;
|
||||
let negative = byte & 0x40 != 0;
|
||||
if (value == 0 && !negative) || (value == -1 && negative) {
|
||||
out.push(byte);
|
||||
return;
|
||||
}
|
||||
out.push(byte | 0x80);
|
||||
}
|
||||
}
|
||||
|
||||
/// Writes `value` in unsigned LEB128 encoding at the start of `buf`,
|
||||
/// returning its length.
|
||||
pub fn write_uleb(buf: &mut [u8], mut value: u64) -> usize {
|
||||
let mut i = 0;
|
||||
loop {
|
||||
let mut byte = (value & 0x7f) as u8;
|
||||
value >>= 7;
|
||||
if value != 0 {
|
||||
byte |= 0x80;
|
||||
}
|
||||
buf[i] = byte;
|
||||
i += 1;
|
||||
if value == 0 {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The length of `value` in unsigned LEB128 encoding.
|
||||
pub fn uleb_size(mut value: u64) -> usize {
|
||||
let mut len = 1;
|
||||
while value >= 0x80 {
|
||||
value >>= 7;
|
||||
len += 1;
|
||||
}
|
||||
len
|
||||
}
|
||||
|
||||
/// Overwrites an existing unsigned LEB128 value in place, keeping its length.
|
||||
pub fn overwrite_uleb(buf: &mut [u8], mut value: u64) {
|
||||
let mut i = 0;
|
||||
while buf[i] & 0x80 != 0 {
|
||||
buf[i] = 0x80 | (value & 0x7f) as u8;
|
||||
value >>= 7;
|
||||
i += 1;
|
||||
}
|
||||
buf[i] = (value & 0x7f) as u8;
|
||||
}
|
||||
|
||||
/// Reads an unsigned LEB128 value, advancing `bytes` past it.
|
||||
#[inline]
|
||||
pub fn read_uleb(bytes: &mut &[u8]) -> u64 {
|
||||
let mut value = 0;
|
||||
let mut shift = 0;
|
||||
loop {
|
||||
let (&byte, rest) = bytes.split_first().expect("truncated LEB128");
|
||||
*bytes = rest;
|
||||
if shift < 64 {
|
||||
value |= ((byte & 0x7f) as u64) << shift;
|
||||
}
|
||||
shift += 7;
|
||||
if byte & 0x80 == 0 {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Reads a signed LEB128 value, advancing `bytes` past it.
|
||||
#[inline]
|
||||
pub fn read_sleb(bytes: &mut &[u8]) -> i64 {
|
||||
let mut value = 0u64;
|
||||
let mut shift = 0;
|
||||
loop {
|
||||
let (&byte, rest) = bytes.split_first().expect("truncated LEB128");
|
||||
*bytes = rest;
|
||||
if shift < 64 {
|
||||
value |= ((byte & 0x7f) as u64) << shift;
|
||||
}
|
||||
shift += 7;
|
||||
if byte & 0x80 == 0 {
|
||||
return if shift < 64 { sign_extend(value, shift) } else { value as i64 };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn leb128_roundtrip() {
|
||||
for &value in &[0u64, 1, 127, 128, 300, u32::MAX as u64, u64::MAX] {
|
||||
let mut buf = Vec::new();
|
||||
encode_uleb(&mut buf, value);
|
||||
assert_eq!(uleb_size(value), buf.len());
|
||||
let mut written = [0xff; 10];
|
||||
assert_eq!(write_uleb(&mut written, value), buf.len());
|
||||
assert_eq!(&written[..buf.len()], buf);
|
||||
let mut slice = buf.as_slice();
|
||||
assert_eq!(read_uleb(&mut slice), value);
|
||||
assert!(slice.is_empty());
|
||||
}
|
||||
for &value in &[0i64, -1, 63, 64, -64, -65, i64::MIN, i64::MAX] {
|
||||
let mut buf = Vec::new();
|
||||
encode_sleb(&mut buf, value);
|
||||
let mut slice = buf.as_slice();
|
||||
assert_eq!(read_sleb(&mut slice), value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
//! Code that mold's linkers share. Nothing here depends on the format of
|
||||
//! object files.
|
||||
|
||||
pub mod archive_file;
|
||||
pub mod bits;
|
||||
pub mod bytes;
|
||||
pub mod cityhash;
|
||||
pub mod compress;
|
||||
pub mod concurrent_map;
|
||||
pub mod demangle;
|
||||
pub mod endian;
|
||||
pub mod error;
|
||||
pub mod glob;
|
||||
pub mod hyperloglog;
|
||||
pub mod jobs;
|
||||
pub mod leb128;
|
||||
pub mod mapped_file;
|
||||
pub mod mem;
|
||||
pub mod output_file;
|
||||
pub mod parallel;
|
||||
pub mod path;
|
||||
pub mod perf;
|
||||
mod prefetch;
|
||||
pub mod record;
|
||||
pub mod response_file;
|
||||
pub mod siphash;
|
||||
pub mod subprocess;
|
||||
pub mod tar;
|
||||
pub mod worker_local;
|
||||
|
||||
pub use prefetch::prefetch;
|
||||
|
||||
/// The git commit that mold is built from, or None if the source tree has
|
||||
/// no git metadata, as in a source tarball.
|
||||
pub const GIT_HASH: Option<&str> = option_env!("MOLD_GIT_HASH");
|
||||
@@ -1,308 +0,0 @@
|
||||
//! Input file access.
|
||||
//!
|
||||
//! Every input file is read once and kept in memory for the whole link,
|
||||
//! because sections, symbol names and relocation tables of object files
|
||||
//! are used until the output is written. Files are therefore leaked with a
|
||||
//! `'static` lifetime rather than tracked with reference counts, which
|
||||
//! keeps lifetimes out of every data structure that refers to file
|
||||
//! contents.
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::fs::File;
|
||||
use std::io::{self, Read};
|
||||
use std::ops::Range;
|
||||
#[cfg(not(windows))]
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Mutex;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
#[cfg(not(windows))]
|
||||
use rayon::prelude::*;
|
||||
|
||||
use crate::error::strerror;
|
||||
use crate::fatal;
|
||||
use crate::mem::leak;
|
||||
use crate::parallel::SyncUnsafeCell;
|
||||
use crate::path::clean_path;
|
||||
|
||||
/// All files opened during this link.
|
||||
static FILE_POOL: Mutex<Vec<&'static MappedFile>> = Mutex::new(Vec::new());
|
||||
|
||||
/// The files that are memory-mapped, for [`drop_mappings`].
|
||||
static MMAPPED_FILES: Mutex<Vec<&'static MappedFile>> = Mutex::new(Vec::new());
|
||||
|
||||
/// The device and inode numbers of the memory-mapped files, for
|
||||
/// [`is_mmapped`].
|
||||
#[cfg(not(windows))]
|
||||
static MMAPPED_INODES: Mutex<Vec<(u64, u64)>> = Mutex::new(Vec::new());
|
||||
|
||||
/// Returns all files opened during this link, sorted by name. Input
|
||||
/// files are opened in parallel, so the order in which they were opened
|
||||
/// changes from run to run.
|
||||
pub fn file_pool() -> Vec<&'static MappedFile> {
|
||||
let mut files = FILE_POOL.lock().unwrap().clone();
|
||||
files.sort_by(|a, b| a.name.cmp(&b.name));
|
||||
files
|
||||
}
|
||||
|
||||
/// Drops the page table entries of the mapped input files, in parallel.
|
||||
/// This makes process exit faster, as the kernel otherwise reclaims them
|
||||
/// in a single thread on exit. The files' contents stay in the page cache,
|
||||
/// but the relocation records rewritten in place through these private
|
||||
/// mappings are discarded, so this must be the last use of the input data.
|
||||
pub fn drop_mappings() {
|
||||
let files = std::mem::take(&mut *MMAPPED_FILES.lock().unwrap());
|
||||
#[cfg(windows)]
|
||||
let _ = files;
|
||||
#[cfg(not(windows))]
|
||||
files.par_iter().for_each(|mf| {
|
||||
// SAFETY: the range is a whole mapping of the file that lives for
|
||||
// the rest of the process; MADV_DONTNEED only discards the pages,
|
||||
// which are faulted in again if they are ever read.
|
||||
unsafe {
|
||||
libc::madvise(
|
||||
mf.data().as_ptr() as *mut libc::c_void,
|
||||
mf.data().len(),
|
||||
libc::MADV_DONTNEED,
|
||||
)
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/// Returns true if the file at `path` is memory-mapped as an input. Such a
|
||||
/// file must not be written in place, because a private mapping reflects
|
||||
/// changes to the file in the pages it has not copied yet.
|
||||
#[cfg(not(windows))]
|
||||
pub fn is_mmapped(path: &Path) -> bool {
|
||||
std::fs::metadata(path)
|
||||
.is_ok_and(|m| MMAPPED_INODES.lock().unwrap().contains(&(m.dev(), m.ino())))
|
||||
}
|
||||
|
||||
/// Output files are never written in place on Windows.
|
||||
#[cfg(windows)]
|
||||
pub fn is_mmapped(_path: &Path) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
// Files up to this size are read into malloc'ed memory rather than
|
||||
// mmap'ed. mmap(2) takes the process's address space lock, so with tens
|
||||
// of thousands of input files, the calls serialize at a few microseconds
|
||||
// each no matter how many threads make them. read(2) takes no such lock,
|
||||
// but copying costs memory bandwidth in proportion to the file size, so
|
||||
// large files are still mmap'ed. On a Chromium link, files below this
|
||||
// threshold are 64% of the inputs by count but 16% by size.
|
||||
//
|
||||
// The buffers add up to hundreds of megabytes on such a link, so they
|
||||
// need to be backed by huge pages; with 4 KiB pages, faulting them in
|
||||
// costs more than the mmap calls did. mimalloc does that by default.
|
||||
const READ_THRESHOLD: u64 = 32 * 1024;
|
||||
|
||||
// MappedFile represents an input file that is either mmap'ed or read into
|
||||
// memory. Either way, its contents are accessible through `data`.
|
||||
#[derive(Debug)]
|
||||
pub struct MappedFile {
|
||||
/// The path this file was opened by, which already has --chroot
|
||||
/// applied, or the member name for a slice of an archive.
|
||||
pub name: PathBuf,
|
||||
/// The contents, in an allocation that is deliberately leaked with the
|
||||
/// input file. The bytes are cells because the linker modifies private
|
||||
/// relocation records in place, as C++ mold does.
|
||||
pub data: &'static [SyncUnsafeCell<u8>],
|
||||
|
||||
/// False if the file was found by searching library paths (`-l`),
|
||||
/// which affects how a shared library's soname defaults.
|
||||
pub given_fullpath: bool,
|
||||
|
||||
/// The archive this file is a member of.
|
||||
pub parent: Option<&'static Self>,
|
||||
|
||||
/// The thin archive this file is a member of.
|
||||
pub thin_parent: Option<&'static Self>,
|
||||
|
||||
// For --dependency-file
|
||||
pub is_dependency: AtomicBool,
|
||||
}
|
||||
|
||||
impl MappedFile {
|
||||
fn open_impl(path: &Path) -> io::Result<&'static Self> {
|
||||
let file = File::open(path)?;
|
||||
|
||||
let display = path.display();
|
||||
let metadata =
|
||||
file.metadata().unwrap_or_else(|e| fatal!("{display}: fstat failed: {}", strerror(&e)));
|
||||
let size = metadata.len();
|
||||
|
||||
// True if `data` is a memory mapping of the file rather than a copy of
|
||||
// its contents in anonymous memory.
|
||||
let mut is_mmapped = false;
|
||||
let data: &'static [SyncUnsafeCell<u8>] = if size == 0 {
|
||||
&[]
|
||||
} else if size <= READ_THRESHOLD && metadata.is_file() {
|
||||
let mut buf = Vec::with_capacity(size as usize);
|
||||
(&file)
|
||||
.take(size)
|
||||
.read_to_end(&mut buf)
|
||||
.unwrap_or_else(|e| fatal!("{display}: read failed: {}", strerror(&e)));
|
||||
if buf.len() as u64 != size {
|
||||
fatal!("{display}: file is shorter than its reported size");
|
||||
}
|
||||
SyncUnsafeCell::from_mut(Vec::leak(buf))
|
||||
} else {
|
||||
// C++ mold maps inputs MAP_PRIVATE with write permission so it
|
||||
// can redirect ordinary relocation records in place without
|
||||
// changing the input file.
|
||||
//
|
||||
// SAFETY: the mapping is private and lives for the rest of the
|
||||
// process. Input files are not expected to change while the
|
||||
// linker runs.
|
||||
let map_len = usize::try_from(size)
|
||||
.unwrap_or_else(|_| fatal!("{display}: file is too large to map"));
|
||||
let map = unsafe { memmap2::MmapOptions::new().len(map_len).map_copy(&file) }
|
||||
.unwrap_or_else(|e| fatal!("{display}: mmap failed: {}", strerror(&e)));
|
||||
is_mmapped = true;
|
||||
SyncUnsafeCell::from_mut(Box::leak(Box::new(map)).as_mut())
|
||||
};
|
||||
|
||||
let mf = leak(Self {
|
||||
name: path.to_path_buf(),
|
||||
data,
|
||||
given_fullpath: true,
|
||||
parent: None,
|
||||
thin_parent: None,
|
||||
is_dependency: AtomicBool::new(true),
|
||||
});
|
||||
FILE_POOL.lock().unwrap().push(mf);
|
||||
if is_mmapped {
|
||||
MMAPPED_FILES.lock().unwrap().push(mf);
|
||||
#[cfg(not(windows))]
|
||||
MMAPPED_INODES.lock().unwrap().push((metadata.dev(), metadata.ino()));
|
||||
}
|
||||
Ok(mf)
|
||||
}
|
||||
|
||||
/// Opens a file, returning `None` if it doesn't exist.
|
||||
pub fn open(path: impl AsRef<Path>) -> Option<&'static Self> {
|
||||
let path = path.as_ref();
|
||||
match Self::open_impl(path) {
|
||||
Ok(mf) => Some(mf),
|
||||
Err(e) if e.kind() == io::ErrorKind::NotFound => None,
|
||||
Err(e) => fatal!("opening {} failed: {}", path.display(), strerror(&e)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Opens a file that must exist.
|
||||
pub fn must_open(path: impl AsRef<Path>) -> &'static Self {
|
||||
let path = path.as_ref();
|
||||
Self::open_impl(path)
|
||||
.unwrap_or_else(|e| fatal!("cannot open {}: {}", path.display(), strerror(&e)))
|
||||
}
|
||||
|
||||
/// Returns a view of a member of this archive.
|
||||
pub fn slice(&'static self, name: PathBuf, start: usize, size: usize) -> &'static Self {
|
||||
let mf = leak(Self {
|
||||
name,
|
||||
data: &self.data[start..start + size],
|
||||
given_fullpath: true,
|
||||
parent: Some(self),
|
||||
thin_parent: None,
|
||||
is_dependency: AtomicBool::new(true),
|
||||
});
|
||||
FILE_POOL.lock().unwrap().push(mf);
|
||||
mf
|
||||
}
|
||||
|
||||
/// Opens a member whose bytes are stored outside this thin archive.
|
||||
pub fn open_thin_member(&'static self, path: &Path) -> &'static Self {
|
||||
let member = Self::must_open(path);
|
||||
leak(Self {
|
||||
name: member.name.clone(),
|
||||
data: member.data,
|
||||
given_fullpath: true,
|
||||
parent: None,
|
||||
thin_parent: Some(self),
|
||||
is_dependency: AtomicBool::new(true),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn size(&self) -> usize {
|
||||
self.data.len()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn data(&self) -> &[u8] {
|
||||
// SAFETY: the only writes are to relocation ranges during exclusive
|
||||
// linker phases, which use no shared view of those ranges.
|
||||
unsafe { SyncUnsafeCell::as_slice(self.data) }
|
||||
}
|
||||
|
||||
/// Returns a relocation range for modification.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The caller must have exclusive access to `range` while the slice is
|
||||
/// alive, and no shared reference to an overlapping range may be used
|
||||
/// during that time.
|
||||
#[allow(clippy::mut_from_ref)]
|
||||
pub unsafe fn data_mut(&self, range: Range<usize>) -> &mut [u8] {
|
||||
// SAFETY: guaranteed by the caller.
|
||||
unsafe { SyncUnsafeCell::as_mut_slice(&self.data[range]) }
|
||||
}
|
||||
|
||||
/// The offset of this slice within its top-level archive file.
|
||||
pub fn offset(&self) -> usize {
|
||||
match self.parent {
|
||||
Some(parent) => {
|
||||
self.data.as_ptr() as usize - parent.data.as_ptr() as usize + parent.offset()
|
||||
}
|
||||
None => 0,
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a string that uniquely identifies a file that is possibly
|
||||
// in an archive.
|
||||
pub fn identifier(&self) -> std::ffi::OsString {
|
||||
if let Some(parent) = self.parent {
|
||||
// Archive members may have the same name, so use the file offset.
|
||||
let mut name = parent.name.as_os_str().to_os_string();
|
||||
name.push(format!(":{}", self.offset()));
|
||||
return name;
|
||||
}
|
||||
if let Some(parent) = self.thin_parent {
|
||||
// Thin archive members have unique filenames.
|
||||
let mut name = parent.name.as_os_str().to_os_string();
|
||||
name.push(":");
|
||||
name.push(&self.name);
|
||||
return name;
|
||||
}
|
||||
self.name.as_os_str().to_os_string()
|
||||
}
|
||||
|
||||
pub fn is_dependency(&self) -> bool {
|
||||
self.is_dependency.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
pub fn set_dependency(&self, value: bool) {
|
||||
self.is_dependency.store(value, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn apply_chroot<'a>(chroot: &Path, path: &'a Path) -> Cow<'a, Path> {
|
||||
if path.is_absolute() && !chroot.as_os_str().is_empty() {
|
||||
Cow::Owned(chroot.join(clean_path(path).strip_prefix("/").unwrap_or(path)))
|
||||
} else {
|
||||
Cow::Borrowed(path)
|
||||
}
|
||||
}
|
||||
|
||||
/// Opens an input file, applying `--chroot` to absolute paths.
|
||||
pub fn open_file(chroot: &Path, path: impl AsRef<Path>) -> Option<&'static MappedFile> {
|
||||
MappedFile::open(apply_chroot(chroot, path.as_ref()))
|
||||
}
|
||||
|
||||
/// Opens an input file that must exist, applying `--chroot` to absolute paths.
|
||||
pub fn must_open_file(chroot: &Path, path: impl AsRef<Path>) -> &'static MappedFile {
|
||||
let path = path.as_ref();
|
||||
MappedFile::open_impl(&apply_chroot(chroot, path))
|
||||
.unwrap_or_else(|e| fatal!("cannot open {}: {}", path.display(), strerror(&e)))
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
//! Memory: transparent huge pages for large tables, and allocations that
|
||||
//! are never freed.
|
||||
|
||||
/// Requests transparent huge pages for a mapped byte range on Linux-based
|
||||
/// targets.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// `data..data + size` must describe a live mapping.
|
||||
#[cfg(any(target_os = "android", target_os = "linux"))]
|
||||
pub(crate) unsafe fn madvise_hugepage(data: *mut u8, size: usize) {
|
||||
// SAFETY: guaranteed by the caller; MADV_HUGEPAGE is only a kernel hint.
|
||||
let _ = unsafe { libc::madvise(data.cast(), size, libc::MADV_HUGEPAGE) };
|
||||
}
|
||||
|
||||
/// No-op on targets that do not support Linux's `MADV_HUGEPAGE` advice.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Kept identical to the supported-target signature.
|
||||
#[cfg(not(any(target_os = "android", target_os = "linux")))]
|
||||
pub(crate) unsafe fn madvise_hugepage(_data: *mut u8, _size: usize) {}
|
||||
|
||||
/// Requests transparent huge pages for the whole pages strictly inside an
|
||||
/// allocation, leaving possible allocator metadata in its boundary pages
|
||||
/// untouched.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// `data..data + size` must describe a live allocation.
|
||||
#[cfg(any(target_os = "android", target_os = "linux"))]
|
||||
pub unsafe fn madvise_hugepage_interior(data: *const u8, size: usize) {
|
||||
let page_size = unsafe { libc::sysconf(libc::_SC_PAGESIZE) };
|
||||
if size == 0 || page_size <= 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
let page_size = page_size as usize;
|
||||
let start = data.addr();
|
||||
let Some(end) = start.checked_add(size) else {
|
||||
return;
|
||||
};
|
||||
let Some(rounded) = start.checked_add(page_size - 1) else {
|
||||
return;
|
||||
};
|
||||
let first_page = rounded / page_size * page_size;
|
||||
let last_page = end / page_size * page_size;
|
||||
if first_page < last_page {
|
||||
// SAFETY: these complete pages lie strictly inside the caller's
|
||||
// allocation.
|
||||
unsafe { madvise_hugepage(data.with_addr(first_page).cast_mut(), last_page - first_page) };
|
||||
}
|
||||
}
|
||||
|
||||
/// No-op on targets that do not support Linux's `MADV_HUGEPAGE` advice.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Kept identical to the supported-target signature.
|
||||
#[cfg(not(any(target_os = "android", target_os = "linux")))]
|
||||
pub unsafe fn madvise_hugepage_interior(_data: *const u8, _size: usize) {}
|
||||
|
||||
/// Leaks a value for the rest of the process's lifetime.
|
||||
///
|
||||
/// Input files, symbol names and a few other objects must outlive every
|
||||
/// data structure of a link, and the process exits as soon as the link is
|
||||
/// done, so never freeing them is both simplest and cheapest.
|
||||
pub fn leak<T>(value: T) -> &'static T {
|
||||
Box::leak(Box::new(value))
|
||||
}
|
||||
|
||||
/// Leaks a byte string for the rest of the process's lifetime.
|
||||
pub fn leak_bytes(bytes: Vec<u8>) -> &'static [u8] {
|
||||
Vec::leak(bytes)
|
||||
}
|
||||
@@ -1,574 +0,0 @@
|
||||
//! The output file and helpers for writing to it from many threads.
|
||||
|
||||
use std::fs::{File, OpenOptions};
|
||||
use std::io::{self, Write};
|
||||
use std::ops::Range;
|
||||
#[cfg(not(windows))]
|
||||
use std::os::unix::fs::{OpenOptionsExt, PermissionsExt};
|
||||
#[cfg(not(windows))]
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use std::path::{Path, PathBuf};
|
||||
#[cfg(windows)]
|
||||
use std::sync::Mutex;
|
||||
#[cfg(not(windows))]
|
||||
use std::sync::atomic::{AtomicPtr, AtomicUsize, Ordering};
|
||||
|
||||
use memmap2::MmapMut;
|
||||
#[cfg(not(windows))]
|
||||
use memmap2::MmapOptions;
|
||||
|
||||
use crate::error::strerror;
|
||||
use crate::fatal;
|
||||
|
||||
/// The temporary file being written, removed on a fatal error.
|
||||
#[cfg(windows)]
|
||||
static TMPFILE: Mutex<Option<PathBuf>> = Mutex::new(None);
|
||||
#[cfg(not(windows))]
|
||||
static TMPFILE: AtomicPtr<libc::c_char> = AtomicPtr::new(std::ptr::null_mut());
|
||||
|
||||
/// Registers the file that cleanup() removes. The Mach-O linker doesn't
|
||||
/// use OutputFile but registers its output, which it writes in place.
|
||||
pub fn set_tmpfile(path: Option<&Path>) {
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
// Published paths live until process exit: a signal handler on another
|
||||
// thread may still be using the old pointer when this registration
|
||||
// changes.
|
||||
let ptr = path.map_or(std::ptr::null_mut(), |path| {
|
||||
std::ffi::CString::new(path.as_os_str().as_encoded_bytes())
|
||||
.expect("temporary path contains NUL")
|
||||
.into_raw()
|
||||
});
|
||||
TMPFILE.store(ptr, Ordering::Release);
|
||||
}
|
||||
#[cfg(windows)]
|
||||
{
|
||||
*TMPFILE.lock().unwrap() = path.map(Path::to_path_buf);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
static OUTPUT_BUFFER_START: AtomicUsize = AtomicUsize::new(0);
|
||||
#[cfg(not(windows))]
|
||||
static OUTPUT_BUFFER_END: AtomicUsize = AtomicUsize::new(0);
|
||||
|
||||
#[cfg(not(windows))]
|
||||
fn set_output_buffer_range(start: usize, len: usize) {
|
||||
OUTPUT_BUFFER_END.store(0, Ordering::SeqCst);
|
||||
OUTPUT_BUFFER_START.store(start, Ordering::SeqCst);
|
||||
OUTPUT_BUFFER_END.store(start + len, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
pub fn output_buffer_contains(addr: usize) -> bool {
|
||||
let start = OUTPUT_BUFFER_START.load(Ordering::SeqCst);
|
||||
let end = OUTPUT_BUFFER_END.load(Ordering::SeqCst);
|
||||
start != 0 && start <= addr && addr < end
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
fn open_options(mode: u32) -> OpenOptions {
|
||||
let mut options = OpenOptions::new();
|
||||
options.mode(mode);
|
||||
options
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn open_options(_mode: u32) -> OpenOptions {
|
||||
OpenOptions::new()
|
||||
}
|
||||
|
||||
fn set_permissions(file: &File, perm: u32) -> io::Result<()> {
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
file.set_permissions(std::fs::Permissions::from_mode(perm & !umask()))
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
{
|
||||
let mut permissions = file.metadata()?.permissions();
|
||||
permissions.set_readonly(perm & 0o200 == 0);
|
||||
file.set_permissions(permissions)
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes a partially written output file.
|
||||
pub fn cleanup() {
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
let path = TMPFILE.swap(std::ptr::null_mut(), Ordering::AcqRel);
|
||||
if !path.is_null() {
|
||||
// SAFETY: path is a published, NUL-terminated string that is never
|
||||
// freed. This function is also called from a signal handler, so it
|
||||
// must not lock, allocate or drop owned storage. unlink is
|
||||
// signal-safe.
|
||||
unsafe { libc::unlink(path) };
|
||||
}
|
||||
}
|
||||
#[cfg(windows)]
|
||||
if let Ok(mut guard) = TMPFILE.lock() {
|
||||
if let Some(path) = guard.take() {
|
||||
let _ = std::fs::remove_file(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum Storage {
|
||||
File {
|
||||
file: File,
|
||||
// Empty files and a locked file awaiting resize have no mapping.
|
||||
// A mapping may reserve more address space than the file's `len`.
|
||||
map: Option<MmapMut>,
|
||||
len: usize,
|
||||
},
|
||||
Memory(Vec<u8>),
|
||||
}
|
||||
|
||||
/// An output file, either memory-mapped or buffered in memory.
|
||||
pub struct OutputFile {
|
||||
path: PathBuf,
|
||||
tmp_path: Option<PathBuf>,
|
||||
storage: Storage,
|
||||
perm: u32,
|
||||
}
|
||||
|
||||
/// Calling fallocate speeds up later linking passes on ext4 by taking disk
|
||||
/// block allocation out of the page-fault handler. On tmpfs, it instead
|
||||
/// makes things much slower: the kernel allocates and zeroes every page of
|
||||
/// the range inside the syscall, on one thread, which takes ~1.8 s for a
|
||||
/// 5 GiB file.
|
||||
#[cfg(any(target_os = "android", target_os = "linux"))]
|
||||
fn preallocate(file: &File, offset: u64, size: u64) {
|
||||
// SAFETY: fstatfs and fallocate only inspect and act on a valid open
|
||||
// descriptor; the statfs buffer is fully written before it is read.
|
||||
unsafe {
|
||||
let mut fs: libc::statfs = std::mem::zeroed();
|
||||
if libc::fstatfs(file.as_raw_fd(), &raw mut fs) != 0 || fs.f_type != libc::TMPFS_MAGIC as _
|
||||
{
|
||||
libc::fallocate(file.as_raw_fd(), 0, offset as libc::off_t, size as libc::off_t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "linux")))]
|
||||
fn preallocate(_file: &File, _offset: u64, _size: u64) {}
|
||||
|
||||
/// Returns the length of the buffer that holds a file of the given size.
|
||||
/// No Rust object can be larger than isize::MAX bytes, which limits the
|
||||
/// output to 2 GiB on 32-bit hosts.
|
||||
fn buffer_len(path: &Path, size: u64) -> usize {
|
||||
if isize::try_from(size).is_err() {
|
||||
fatal!("{}: output file is too large for this host: {size} bytes", path.display());
|
||||
}
|
||||
size as usize
|
||||
}
|
||||
|
||||
fn map_file(file: &File, len: usize) -> io::Result<Option<MmapMut>> {
|
||||
if len == 0 {
|
||||
return Ok(None);
|
||||
}
|
||||
// We map the file with twice as much address space as its size, so
|
||||
// that extend() can grow the file into the mapping in place. Touching
|
||||
// the mapping beyond the end of the file is not allowed, but growing
|
||||
// the file with ftruncate makes the tail of the mapping accessible
|
||||
// without any further mmap call.
|
||||
//
|
||||
// SAFETY: the file is private to this process until it's closed.
|
||||
#[cfg(not(windows))]
|
||||
let map = unsafe { MmapOptions::new().len(len * 2).map_mut(file) }
|
||||
// If the address space is too tight, map just the file.
|
||||
.or_else(|_| unsafe { MmapMut::map_mut(file) });
|
||||
#[cfg(windows)]
|
||||
let map = unsafe { MmapMut::map_mut(file) };
|
||||
let mut map = map?;
|
||||
// Enable transparent huge pages for a memory-mapped output file
|
||||
// when the target provides the required advice.
|
||||
// Linking a Chromium debug build is ~20% faster with this madvise call.
|
||||
//
|
||||
// Without this, every 4 KiB page of the output takes its own
|
||||
// page fault when it is first written, and the faults of the
|
||||
// many copying threads serialize on the file's page cache. With
|
||||
// it, the kernel backs the mapping with large folios and the
|
||||
// number of faults drops by an order of magnitude.
|
||||
// SAFETY: the range is the mapping; the advice is only a hint.
|
||||
unsafe { crate::mem::madvise_hugepage(map.as_mut_ptr(), map.len()) };
|
||||
Ok(Some(map))
|
||||
}
|
||||
|
||||
impl OutputFile {
|
||||
#[cfg(not(windows))]
|
||||
fn publish_output_buffer(&self) {
|
||||
match &self.storage {
|
||||
Storage::File { map: Some(map), len, .. } => {
|
||||
set_output_buffer_range(map.as_ptr() as usize, *len);
|
||||
}
|
||||
_ => set_output_buffer_range(0, 0),
|
||||
}
|
||||
}
|
||||
|
||||
/// Opens an output file of the given size.
|
||||
///
|
||||
/// A regular file is written through a memory mapping of a temporary
|
||||
/// file that is renamed into place on close, so that a failed link
|
||||
/// doesn't leave a truncated output behind and a running executable
|
||||
/// isn't modified underneath the kernel. Anything else, such as a
|
||||
/// device, a pipe or standard output, is assembled in memory and
|
||||
/// written out at the end. If `mmap` is false, a regular file is
|
||||
/// assembled in memory too and then written out to the temporary file.
|
||||
pub fn open(path: &Path, size: u64, perm: u32, overwrite_in_place: bool, mmap: bool) -> Self {
|
||||
let len = buffer_len(path, size);
|
||||
let is_special =
|
||||
path == Path::new("-") || std::fs::metadata(path).is_ok_and(|m| !m.is_file());
|
||||
if is_special {
|
||||
return Self {
|
||||
path: path.to_path_buf(),
|
||||
tmp_path: None,
|
||||
storage: Storage::Memory(vec![0; len]),
|
||||
perm,
|
||||
};
|
||||
}
|
||||
|
||||
let dir = Path::new(path)
|
||||
.parent()
|
||||
.filter(|p| !p.as_os_str().is_empty())
|
||||
.unwrap_or(Path::new("."));
|
||||
let mut name = std::ffi::OsString::from(".");
|
||||
name.push(path.file_name().unwrap_or_default());
|
||||
name.push(format!(".{}", std::process::id()));
|
||||
let tmp = dir.join(name);
|
||||
|
||||
if !mmap {
|
||||
return Self {
|
||||
path: path.to_path_buf(),
|
||||
tmp_path: Some(tmp),
|
||||
storage: Storage::Memory(vec![0; len]),
|
||||
perm,
|
||||
};
|
||||
}
|
||||
|
||||
// Reuse an existing file if it exists and is writable because on
|
||||
// Linux, writing to an existing file is much faster than creating a
|
||||
// fresh file and writing to it. A file mapped as an input is not reused, as
|
||||
// the linker is still reading it.
|
||||
let reuse_existing = || -> Option<File> {
|
||||
if !overwrite_in_place
|
||||
|| crate::mapped_file::is_mmapped(path)
|
||||
|| std::fs::rename(path, &tmp).is_err()
|
||||
{
|
||||
return None;
|
||||
}
|
||||
match open_options(perm).read(true).write(true).create(true).open(&tmp) {
|
||||
Ok(file) => Some(file),
|
||||
Err(_) => {
|
||||
let _ = std::fs::remove_file(&tmp);
|
||||
None
|
||||
}
|
||||
}
|
||||
};
|
||||
let file = reuse_existing().unwrap_or_else(|| {
|
||||
open_options(perm)
|
||||
.read(true)
|
||||
.write(true)
|
||||
.create(true)
|
||||
.truncate(true)
|
||||
.open(&tmp)
|
||||
.unwrap_or_else(|e| fatal!("cannot open {}: {}", tmp.display(), strerror(&e)))
|
||||
});
|
||||
set_tmpfile(Some(&tmp));
|
||||
|
||||
set_permissions(&file, perm)
|
||||
.unwrap_or_else(|e| fatal!("{}: fchmod failed: {}", tmp.display(), strerror(&e)));
|
||||
file.set_len(size)
|
||||
.unwrap_or_else(|e| fatal!("{}: ftruncate failed: {}", tmp.display(), strerror(&e)));
|
||||
preallocate(&file, 0, size);
|
||||
|
||||
let map = map_file(&file, len)
|
||||
.unwrap_or_else(|e| fatal!("{}: mmap failed: {}", path.display(), strerror(&e)));
|
||||
let output = Self {
|
||||
path: path.to_path_buf(),
|
||||
tmp_path: Some(tmp),
|
||||
storage: Storage::File { file, map, len },
|
||||
perm,
|
||||
};
|
||||
#[cfg(not(windows))]
|
||||
output.publish_output_buffer();
|
||||
output
|
||||
}
|
||||
|
||||
/// Opens a file that is written in place rather than renamed and holds an
|
||||
/// exclusive `flock`. This is used for a separate debug file that a
|
||||
/// debugger may wait on. The file is made unusable right away so that a
|
||||
/// stale one is not picked up by accident; [`Self::resize`] gives it its
|
||||
/// size.
|
||||
#[cfg(not(windows))]
|
||||
pub fn open_locked(path: &Path, perm: u32) -> Self {
|
||||
let mut file = open_options(perm)
|
||||
.read(true)
|
||||
.write(true)
|
||||
.create(true)
|
||||
.open(path)
|
||||
.unwrap_or_else(|e| fatal!("cannot open {}: {}", path.display(), strerror(&e)));
|
||||
// SAFETY: flock on a valid descriptor.
|
||||
unsafe {
|
||||
libc::flock(file.as_raw_fd(), libc::LOCK_EX);
|
||||
}
|
||||
// We may be overwriting an existing debug info file. We want to
|
||||
// make the file unusable so that gdb won't use it by accident until
|
||||
// it's ready.
|
||||
file.write_all(&[0; 256])
|
||||
.unwrap_or_else(|e| fatal!("{}: write failed: {}", path.display(), strerror(&e)));
|
||||
Self {
|
||||
path: path.to_path_buf(),
|
||||
tmp_path: None,
|
||||
storage: Storage::File { file, map: None, len: 0 },
|
||||
perm,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
pub fn open_locked(_path: &Path, _perm: u32) -> OutputFile {
|
||||
fatal!("LockingOutputFile is not supported on Windows");
|
||||
}
|
||||
|
||||
/// Sets the size of a file opened with [`Self::open_locked`].
|
||||
pub fn resize(&mut self, size: u64) {
|
||||
let new_len = buffer_len(&self.path, size);
|
||||
let Storage::File { file, map, len } = &mut self.storage else {
|
||||
panic!("resizing an output file that isn't a file");
|
||||
};
|
||||
file.set_len(size).unwrap_or_else(|e| {
|
||||
fatal!("{}: ftruncate failed: {}", self.path.display(), strerror(&e))
|
||||
});
|
||||
*map = map_file(file, new_len)
|
||||
.unwrap_or_else(|e| fatal!("{}: mmap failed: {}", self.path.display(), strerror(&e)));
|
||||
*len = new_len;
|
||||
#[cfg(not(windows))]
|
||||
self.publish_output_buffer();
|
||||
}
|
||||
|
||||
/// Whether the buffer is a mapping of the file rather than memory.
|
||||
pub fn is_mmapped(&self) -> bool {
|
||||
matches!(self.storage, Storage::File { map: Some(_), .. })
|
||||
}
|
||||
|
||||
pub fn buf(&mut self) -> &mut [u8] {
|
||||
match &mut self.storage {
|
||||
Storage::File { map: Some(map), len, .. } => &mut map[..*len],
|
||||
Storage::File { map: None, .. } => &mut [],
|
||||
Storage::Memory(vec) => &mut vec[..],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
match &self.storage {
|
||||
Storage::File { len, .. } => *len,
|
||||
Storage::Memory(vec) => vec.len(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len() == 0
|
||||
}
|
||||
|
||||
// Extend the file so the caller can fill the appended data through the tail
|
||||
// of the mapping. This is called at most once per output file, for
|
||||
// .gdb_index, whose size is not known until the other sections have been
|
||||
// written. The new space is zero-initialized, and the output buffer may
|
||||
// move.
|
||||
pub fn extend(&mut self, size: usize) {
|
||||
let new_len = buffer_len(&self.path, self.len() as u64 + size as u64);
|
||||
match &mut self.storage {
|
||||
Storage::Memory(vec) => vec.resize(new_len, 0),
|
||||
Storage::File { file, map, len } => {
|
||||
file.set_len(new_len as u64).unwrap_or_else(|e| {
|
||||
fatal!("{}: ftruncate failed: {}", self.path.display(), strerror(&e))
|
||||
});
|
||||
// Allocate only the appended range. Reallocating the already
|
||||
// written prefix can flush dirty extents on filesystems such
|
||||
// as btrfs and serialize a large part of .gdb_index output.
|
||||
preallocate(file, *len as u64, size as u64);
|
||||
if map.as_ref().is_none_or(|map| new_len > map.len()) {
|
||||
// The appended data does not fit in the existing mapping, so map
|
||||
// the grown file again. Release the old mapping first, as the
|
||||
// address space may be too tight for both. The data written
|
||||
// through it stays in the file.
|
||||
*map = None;
|
||||
*map = map_file(file, new_len).unwrap_or_else(|e| {
|
||||
fatal!("{}: mmap failed: {}", self.path.display(), strerror(&e))
|
||||
});
|
||||
}
|
||||
*len = new_len;
|
||||
}
|
||||
}
|
||||
#[cfg(not(windows))]
|
||||
self.publish_output_buffer();
|
||||
}
|
||||
|
||||
/// Finishes writing and moves the file into place. The mapping is
|
||||
/// released without waiting for the data to reach the disk.
|
||||
pub fn close(self) {
|
||||
#[cfg(not(windows))]
|
||||
set_output_buffer_range(0, 0);
|
||||
// Hold the file (and any lock) through publication of the output.
|
||||
let _file = match self.storage {
|
||||
Storage::File { file, map, .. } => {
|
||||
drop(map);
|
||||
Some(file)
|
||||
}
|
||||
Storage::Memory(vec) => {
|
||||
if self.path == Path::new("-") {
|
||||
let mut stdout = std::io::stdout().lock();
|
||||
stdout
|
||||
.write_all(&vec)
|
||||
.and_then(|()| stdout.flush())
|
||||
.unwrap_or_else(|e| fatal!("write failed: {}", strerror(&e)));
|
||||
// Close the descriptor too: the parent process may
|
||||
// already have exited, and a program that then runs
|
||||
// the output would fail with ETXTBSY while this
|
||||
// process still holds it open for writing.
|
||||
// SAFETY: nothing else writes to stdout after this.
|
||||
#[cfg(not(windows))]
|
||||
unsafe {
|
||||
libc::close(libc::STDOUT_FILENO);
|
||||
}
|
||||
return;
|
||||
}
|
||||
// A regular file is written to the temporary file, which is
|
||||
// renamed below.
|
||||
let dest = self.tmp_path.as_deref().unwrap_or(&self.path);
|
||||
set_tmpfile(self.tmp_path.as_deref());
|
||||
let mut file = open_options(self.perm)
|
||||
.write(true)
|
||||
.create(true)
|
||||
.truncate(true)
|
||||
.open(dest)
|
||||
.unwrap_or_else(|e| fatal!("cannot open {}: {}", dest.display(), strerror(&e)));
|
||||
file.write_all(&vec).unwrap_or_else(|e| {
|
||||
fatal!("{}: write failed: {}", dest.display(), strerror(&e))
|
||||
});
|
||||
None
|
||||
}
|
||||
};
|
||||
if let Some(tmp) = self.tmp_path {
|
||||
// If an output file already exists, open it and then remove it.
|
||||
// This is the fastest way to unlink a file, as it does not make the
|
||||
// system immediately release disk blocks occupied by the file.
|
||||
// The descriptor is kept until the process exits.
|
||||
if let Ok(old) = File::open(&self.path) {
|
||||
let _ = std::fs::remove_file(&self.path);
|
||||
std::mem::forget(old);
|
||||
}
|
||||
std::fs::rename(&tmp, &self.path).unwrap_or_else(|e| {
|
||||
fatal!(
|
||||
"cannot rename {} to {}: {}",
|
||||
tmp.display(),
|
||||
self.path.display(),
|
||||
strerror(&e)
|
||||
)
|
||||
});
|
||||
set_tmpfile(None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
fn umask() -> u32 {
|
||||
// SAFETY: umask is thread-safe in the sense that it just swaps a
|
||||
// process-wide value; restoring it immediately keeps it unchanged.
|
||||
unsafe {
|
||||
let mask = libc::umask(0);
|
||||
libc::umask(mask);
|
||||
mask as u32
|
||||
}
|
||||
}
|
||||
|
||||
/// Splits a buffer into consecutive slices at the given ascending
|
||||
/// offsets. The first slice starts at `offsets[0]`; bytes before it are
|
||||
/// not returned. The last slice extends to the end of the buffer.
|
||||
pub fn split_at_offsets<'a, T>(buf: &'a mut [T], offsets: &[u64]) -> Vec<&'a mut [T]> {
|
||||
let mut slices = Vec::with_capacity(offsets.len());
|
||||
let mut rest = buf;
|
||||
let mut pos = 0usize;
|
||||
for (i, &start) in offsets.iter().enumerate() {
|
||||
let start = start as usize;
|
||||
let end = offsets.get(i + 1).map_or(rest.len() + pos, |&e| e as usize);
|
||||
debug_assert!(pos <= start && start <= end);
|
||||
rest.split_off_mut(..start - pos).unwrap();
|
||||
let slice = rest.split_off_mut(..end - start).unwrap();
|
||||
slices.push(slice);
|
||||
|
||||
pos = end;
|
||||
}
|
||||
slices
|
||||
}
|
||||
|
||||
/// Borrows several disjoint ranges of a buffer mutably at once.
|
||||
pub fn split_ranges<'a>(buf: &'a mut [u8], ranges: &[Range<u64>]) -> Vec<&'a mut [u8]> {
|
||||
let mut order: Vec<usize> = (0..ranges.len()).collect();
|
||||
order.sort_by_key(|&i| ranges[i].start);
|
||||
|
||||
let mut result: Vec<Option<&'a mut [u8]>> = (0..ranges.len()).map(|_| None).collect();
|
||||
let mut rest = buf;
|
||||
let mut pos = 0u64;
|
||||
for i in order {
|
||||
let r = &ranges[i];
|
||||
assert!(r.start >= pos, "overlapping output ranges");
|
||||
rest.split_off_mut(..(r.start - pos) as usize).unwrap();
|
||||
let slice = rest.split_off_mut(..(r.end - r.start) as usize).unwrap();
|
||||
result[i] = Some(slice);
|
||||
|
||||
pos = r.end;
|
||||
}
|
||||
result.into_iter().map(|s| s.unwrap()).collect()
|
||||
}
|
||||
|
||||
#[cfg(all(test, not(windows)))]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn tracks_output_buffer_addresses() {
|
||||
set_output_buffer_range(0x1000, 0x100);
|
||||
assert!(!output_buffer_contains(0x0fff));
|
||||
assert!(output_buffer_contains(0x1000));
|
||||
assert!(output_buffer_contains(0x10ff));
|
||||
assert!(!output_buffer_contains(0x1100));
|
||||
set_output_buffer_range(0, 0);
|
||||
|
||||
let path = std::env::temp_dir().join(format!("mold-output-{}", std::process::id()));
|
||||
let mut file = OutputFile::open(&path, 8192, 0o600, true, true);
|
||||
let start = file.buf().as_ptr() as usize;
|
||||
assert!(output_buffer_contains(start));
|
||||
assert!(output_buffer_contains(start + 8191));
|
||||
assert!(!output_buffer_contains(start + 8192));
|
||||
file.close();
|
||||
assert!(!output_buffer_contains(start));
|
||||
|
||||
// Cover empty regular files, remapping on growth, buffering in
|
||||
// memory, and the locked staging state used by separate debug
|
||||
// output. Keep this in the same test because output publication
|
||||
// uses process-global state.
|
||||
for mmap in [true, false] {
|
||||
for initial in [0, 8] {
|
||||
let mut file = OutputFile::open(&path, initial, 0o600, true, mmap);
|
||||
file.buf().fill(7);
|
||||
file.extend(65536);
|
||||
assert!(file.buf()[..initial as usize].iter().all(|&b| b == 7));
|
||||
assert!(file.buf()[initial as usize..].iter().all(|&b| b == 0));
|
||||
file.close();
|
||||
let contents = std::fs::read(&path).unwrap();
|
||||
assert_eq!(contents.len() as u64, initial + 65536);
|
||||
assert!(contents[..initial as usize].iter().all(|&b| b == 7));
|
||||
}
|
||||
}
|
||||
let mut file = OutputFile::open_locked(&path, 0o600);
|
||||
assert!(file.buf().is_empty());
|
||||
file.resize(0);
|
||||
assert!(!file.is_mmapped());
|
||||
file.resize(4);
|
||||
file.buf().copy_from_slice(b"test");
|
||||
file.close();
|
||||
assert_eq!(std::fs::read(&path).unwrap(), b"test");
|
||||
std::fs::remove_file(path).unwrap();
|
||||
}
|
||||
}
|
||||
@@ -1,190 +0,0 @@
|
||||
use rayon::prelude::*;
|
||||
|
||||
/// An owned Rayon job whose result is needed by a later linker pass.
|
||||
pub struct Background<T> {
|
||||
receiver: std::sync::mpsc::Receiver<T>,
|
||||
name: &'static str,
|
||||
}
|
||||
|
||||
impl<T: Send + 'static> Background<T> {
|
||||
pub fn spawn(name: &'static str, run: impl FnOnce() -> T + Send + 'static) -> Self {
|
||||
let (sender, receiver) = std::sync::mpsc::sync_channel(1);
|
||||
rayon::spawn(move || {
|
||||
let _ = sender.send(run());
|
||||
});
|
||||
Self { receiver, name }
|
||||
}
|
||||
|
||||
pub fn join(self) -> T {
|
||||
use std::sync::mpsc::TryRecvError;
|
||||
loop {
|
||||
match self.receiver.try_recv() {
|
||||
Ok(value) => return value,
|
||||
Err(TryRecvError::Disconnected) => panic!("{} task failed", self.name),
|
||||
Err(TryRecvError::Empty) => {
|
||||
// A blocking receive would deadlock a one-worker pool.
|
||||
if !matches!(rayon::yield_now(), Some(rayon::Yield::Executed)) {
|
||||
std::thread::yield_now();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Stably partitions a slice, returning the number of matching elements.
|
||||
/// Each block scatters into disjoint ranges computed from its match count.
|
||||
pub fn stable_partition<T: Copy + Send + Sync>(
|
||||
values: &mut [T],
|
||||
pred: impl Fn(&T) -> bool + Sync,
|
||||
) -> usize {
|
||||
const BLOCK: usize = 16384;
|
||||
let matches: Vec<bool> = if values.len() <= BLOCK {
|
||||
values.iter().map(&pred).collect()
|
||||
} else {
|
||||
values.par_iter().map(&pred).collect()
|
||||
};
|
||||
let counts: Vec<usize> =
|
||||
matches.par_chunks(BLOCK).map(|chunk| chunk.iter().filter(|&&v| v).count()).collect();
|
||||
let num_matches = counts.iter().sum();
|
||||
if num_matches == 0 || num_matches == values.len() {
|
||||
return num_matches;
|
||||
}
|
||||
|
||||
let mut output = values.to_vec();
|
||||
let (mut yes, mut no) = output.split_at_mut(num_matches);
|
||||
let parts: Vec<_> = counts
|
||||
.iter()
|
||||
.zip(matches.chunks(BLOCK))
|
||||
.map(|(&count, flags)| {
|
||||
let matched = yes.split_off_mut(..count).unwrap();
|
||||
|
||||
let unmatched = no.split_off_mut(..flags.len() - count).unwrap();
|
||||
|
||||
(matched, unmatched)
|
||||
})
|
||||
.collect();
|
||||
parts.into_par_iter().zip(values.par_chunks(BLOCK)).zip(matches.par_chunks(BLOCK)).for_each(
|
||||
|(((yes, no), input), flags)| {
|
||||
let (mut y, mut n) = (0, 0);
|
||||
for (&value, &matched) in input.iter().zip(flags) {
|
||||
if matched {
|
||||
yes[y] = value;
|
||||
y += 1;
|
||||
} else {
|
||||
no[n] = value;
|
||||
n += 1;
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
values
|
||||
.par_chunks_mut(BLOCK)
|
||||
.zip(output.par_chunks(BLOCK))
|
||||
.for_each(|(dst, src)| dst.copy_from_slice(src));
|
||||
num_matches
|
||||
}
|
||||
|
||||
/// An `UnsafeCell` that may be shared between threads, like std's unstable
|
||||
/// type of the same name. A parallel pass views a slice as cells so that
|
||||
/// each task can write the elements it owns through a shared reference,
|
||||
/// with the slice's bounds checks still applied to every access.
|
||||
#[repr(transparent)]
|
||||
pub struct SyncUnsafeCell<T>(std::cell::UnsafeCell<T>);
|
||||
|
||||
// SAFETY: the unsafe methods below require callers to keep concurrent
|
||||
// accesses to one cell disjoint, which is what `Sync` promises.
|
||||
unsafe impl<T: Send + Sync> Sync for SyncUnsafeCell<T> {}
|
||||
|
||||
impl<T> SyncUnsafeCell<T> {
|
||||
pub fn new(value: T) -> Self {
|
||||
Self(std::cell::UnsafeCell::new(value))
|
||||
}
|
||||
|
||||
/// Views an exclusively borrowed slice as shared cells.
|
||||
pub fn from_mut(slice: &mut [T]) -> &[Self] {
|
||||
// SAFETY: a cell has the same layout as its content, and the
|
||||
// exclusive borrow of the slice is given up for the shared view.
|
||||
unsafe { &*(std::ptr::from_mut(slice) as *const [Self]) }
|
||||
}
|
||||
|
||||
/// A raw pointer to the content.
|
||||
pub fn get(&self) -> *mut T {
|
||||
self.0.get()
|
||||
}
|
||||
|
||||
pub fn get_mut(&mut self) -> &mut T {
|
||||
self.0.get_mut()
|
||||
}
|
||||
|
||||
/// Views cells as their contents.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// No cell in `cells` may be written while the returned slice is alive.
|
||||
pub(crate) unsafe fn as_slice(cells: &[Self]) -> &[T] {
|
||||
// SAFETY: a cell has the same layout as its content; the caller
|
||||
// guarantees that the contents do not change.
|
||||
unsafe { &*(std::ptr::from_ref(cells) as *const [T]) }
|
||||
}
|
||||
|
||||
/// Views cells as their exclusively borrowed contents.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// No cell in `cells` may be accessed otherwise while the returned slice
|
||||
/// is alive.
|
||||
#[allow(clippy::mut_from_ref)]
|
||||
pub unsafe fn as_mut_slice(cells: &[Self]) -> &mut [T] {
|
||||
let ptr = std::cell::UnsafeCell::raw_get(cells.as_ptr().cast());
|
||||
// SAFETY: a cell has the same layout as its content; the caller
|
||||
// guarantees exclusive access.
|
||||
unsafe { std::slice::from_raw_parts_mut(ptr, cells.len()) }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> std::fmt::Debug for SyncUnsafeCell<T> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("SyncUnsafeCell").finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets flag bits without taking exclusive ownership of a cache line when
|
||||
/// all requested bits are already set. Callers needing the previous value
|
||||
/// must use `fetch_or` directly.
|
||||
#[inline]
|
||||
pub fn atomic_or(atomic: &std::sync::atomic::AtomicU8, bits: u8) {
|
||||
use std::sync::atomic::Ordering::Relaxed;
|
||||
if atomic.load(Relaxed) & bits != bits {
|
||||
atomic.fetch_or(bits, Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn background_job_joins_on_one_worker() {
|
||||
rayon::ThreadPoolBuilder::new().num_threads(1).build().unwrap().install(|| {
|
||||
let job = Background::spawn("test", || (0..100usize).into_par_iter().sum::<usize>());
|
||||
assert_eq!(job.join(), 4950);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn preserves_both_groups_across_blocks() {
|
||||
for len in [0, 1, 16383, 16384, 16385, 65539] {
|
||||
for divisor in [1, 2, 7, 100000] {
|
||||
let mut values: Vec<_> = (0..len).collect();
|
||||
let (yes, no): (Vec<_>, Vec<_>) =
|
||||
values.iter().copied().partition(|v| v % divisor == 0);
|
||||
let count = stable_partition(&mut values, |v| v % divisor == 0);
|
||||
assert_eq!(count, yes.len());
|
||||
assert_eq!(&values[..count], yes);
|
||||
assert_eq!(&values[count..], no);
|
||||
assert_eq!(stable_partition(&mut values, |_| false), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
//! Lexical path manipulation, which doesn't consult the file system.
|
||||
|
||||
/// Normalizes a path lexically, resolving `.` and `..` components without
|
||||
/// consulting the file system.
|
||||
pub fn path_clean(path: &str) -> String {
|
||||
clean_path(std::path::Path::new(path)).to_string_lossy().into_owned()
|
||||
}
|
||||
|
||||
/// The bytes of a path, as the file system and Mach-O load commands
|
||||
/// hold them.
|
||||
pub fn path_bytes(path: &std::path::Path) -> &[u8] {
|
||||
path.as_os_str().as_encoded_bytes()
|
||||
}
|
||||
|
||||
/// Normalizes an OS path without resolving symlinks.
|
||||
pub fn clean_path(path: &std::path::Path) -> std::path::PathBuf {
|
||||
use std::path::{Component, PathBuf};
|
||||
let mut out = PathBuf::new();
|
||||
for component in path.components() {
|
||||
match component {
|
||||
Component::CurDir => {}
|
||||
Component::ParentDir => match out.components().next_back() {
|
||||
Some(Component::RootDir) => {}
|
||||
None | Some(Component::ParentDir) => out.push(".."),
|
||||
_ => {
|
||||
out.pop();
|
||||
}
|
||||
},
|
||||
other => out.push(other),
|
||||
}
|
||||
}
|
||||
if out.as_os_str().is_empty() { PathBuf::from(".") } else { out }
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn clean_paths() {
|
||||
assert_eq!(path_clean("a/./b/../c"), "a/c");
|
||||
assert_eq!(path_clean("/a/../.."), "/");
|
||||
assert_eq!(path_clean("../a"), "../a");
|
||||
assert_eq!(path_clean("../../a/b"), "../../a/b");
|
||||
assert_eq!(path_clean("a/../../b"), "../b");
|
||||
assert_eq!(path_clean("a/b/../../../c"), "../c");
|
||||
assert_eq!(path_clean(".."), "..");
|
||||
assert_eq!(path_clean("/.."), "/");
|
||||
}
|
||||
}
|
||||
-347
@@ -1,347 +0,0 @@
|
||||
//! Statistics counters and wall-clock and CPU time accounting.
|
||||
|
||||
// Counter is used to collect statistics.
|
||||
|
||||
use std::io::Write;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::{Arc, Mutex, Once};
|
||||
use std::time::Instant;
|
||||
|
||||
use portable_atomic::AtomicI64;
|
||||
|
||||
use crate::out;
|
||||
|
||||
static COUNTERS_ENABLED: AtomicBool = AtomicBool::new(false);
|
||||
static COUNTERS: Mutex<Vec<&'static Counter>> = Mutex::new(Vec::new());
|
||||
|
||||
pub struct Counter {
|
||||
name: &'static str,
|
||||
value: AtomicI64,
|
||||
registered: Once,
|
||||
}
|
||||
|
||||
impl Counter {
|
||||
pub const fn new(name: &'static str) -> Self {
|
||||
Self { name, value: AtomicI64::new(0), registered: Once::new() }
|
||||
}
|
||||
|
||||
pub fn enable() {
|
||||
COUNTERS_ENABLED.store(true, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn increment(&'static self) {
|
||||
self.add(1);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn add(&'static self, delta: i64) {
|
||||
if !COUNTERS_ENABLED.load(Ordering::Relaxed) {
|
||||
return;
|
||||
}
|
||||
self.registered.call_once(|| {
|
||||
COUNTERS.lock().unwrap().push(self);
|
||||
});
|
||||
self.value.fetch_add(delta, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
pub fn print(stats: impl IntoIterator<Item = (&'static str, i64)>) {
|
||||
let mut counters: Vec<_> = COUNTERS
|
||||
.lock()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|counter| (counter.name, counter.value.load(Ordering::Relaxed)))
|
||||
.collect();
|
||||
counters.extend(stats);
|
||||
counters.sort_by_key(|&(_, value)| value);
|
||||
for (name, value) in counters {
|
||||
out!("{:>20}={}", name, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Record {
|
||||
name: String,
|
||||
start: Instant,
|
||||
end: Option<Instant>,
|
||||
user_start: f64,
|
||||
sys_start: f64,
|
||||
user: f64,
|
||||
sys: f64,
|
||||
parent: Option<usize>,
|
||||
children: Vec<usize>,
|
||||
}
|
||||
|
||||
// Infer implicit parents from complete intervals, as C++ mold does. Overlapping
|
||||
// timers need not be nested, and explicitly named parents take precedence.
|
||||
fn nest_records(records: &mut [Record]) {
|
||||
for record in records.iter_mut() {
|
||||
record.children.clear();
|
||||
}
|
||||
for i in 0..records.len() {
|
||||
if records[i].parent.is_none() {
|
||||
let inner = &records[i];
|
||||
records[i].parent = records[..i].iter().rposition(|outer| {
|
||||
outer.start <= inner.start && inner.end.unwrap() <= outer.end.unwrap()
|
||||
});
|
||||
}
|
||||
if let Some(parent) = records[i].parent {
|
||||
records[parent].children.push(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Collects wall-clock and CPU timing records for the passes of a link.
|
||||
/// Cloning shares the underlying records.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Timers {
|
||||
records: Option<Arc<Mutex<Vec<Record>>>>,
|
||||
}
|
||||
|
||||
/// A running timer, stopped when dropped.
|
||||
#[must_use = "a timer records its interval when dropped"]
|
||||
pub struct Timer {
|
||||
timers: Timers,
|
||||
index: usize,
|
||||
stopped: bool,
|
||||
}
|
||||
|
||||
/// A clonable handle for starting timers beneath a running timer.
|
||||
#[derive(Clone)]
|
||||
pub struct TimerHandle {
|
||||
timers: Timers,
|
||||
parent: usize,
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
#[repr(C)]
|
||||
struct FileTime {
|
||||
low: u32,
|
||||
high: u32,
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
#[link(name = "kernel32")]
|
||||
unsafe extern "system" {
|
||||
fn GetCurrentProcess() -> *mut std::ffi::c_void;
|
||||
fn GetProcessTimes(
|
||||
process: *mut std::ffi::c_void,
|
||||
creation: *mut FileTime,
|
||||
exit: *mut FileTime,
|
||||
kernel: *mut FileTime,
|
||||
user: *mut FileTime,
|
||||
) -> i32;
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
fn rusage() -> (f64, f64) {
|
||||
// SAFETY: `usage` is a valid, writable rusage struct.
|
||||
let mut usage: libc::rusage = unsafe { std::mem::zeroed() };
|
||||
unsafe { libc::getrusage(libc::RUSAGE_SELF, &raw mut usage) };
|
||||
let to_secs = |t: libc::timeval| t.tv_sec as f64 + t.tv_usec as f64 / 1_000_000.0;
|
||||
(to_secs(usage.ru_utime), to_secs(usage.ru_stime))
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn rusage() -> (f64, f64) {
|
||||
let mut creation = FileTime { low: 0, high: 0 };
|
||||
let mut exit = FileTime { low: 0, high: 0 };
|
||||
let mut kernel = FileTime { low: 0, high: 0 };
|
||||
let mut user = FileTime { low: 0, high: 0 };
|
||||
// SAFETY: all FILETIME pointers are valid outputs and the pseudo-handle
|
||||
// returned by GetCurrentProcess is always valid in this process.
|
||||
unsafe {
|
||||
GetProcessTimes(GetCurrentProcess(), &mut creation, &mut exit, &mut kernel, &mut user);
|
||||
}
|
||||
let to_secs = |time: FileTime| {
|
||||
let ticks = (u64::from(time.high) << 32) | u64::from(time.low);
|
||||
ticks as f64 / 10_000_000.0
|
||||
};
|
||||
(to_secs(user), to_secs(kernel))
|
||||
}
|
||||
|
||||
impl Default for Timers {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Timers {
|
||||
pub fn new() -> Self {
|
||||
Self { records: Some(Arc::new(Mutex::new(Vec::new()))) }
|
||||
}
|
||||
|
||||
/// Skips clock reads, system calls and shared recording when --perf (or
|
||||
/// the Mach-O linker's -print_statistics) is off.
|
||||
pub fn disabled() -> Self {
|
||||
Self { records: None }
|
||||
}
|
||||
|
||||
fn inactive(&self) -> Timer {
|
||||
Timer { timers: self.clone(), index: 0, stopped: true }
|
||||
}
|
||||
|
||||
/// Starts a timer whose nesting is inferred from completed time intervals.
|
||||
pub fn start(&self, name: &str) -> Timer {
|
||||
self.start_child(name, None)
|
||||
}
|
||||
|
||||
fn start_child(&self, name: impl std::fmt::Display, parent: Option<usize>) -> Timer {
|
||||
let Some(records) = &self.records else {
|
||||
return self.inactive();
|
||||
};
|
||||
let (user, sys) = rusage();
|
||||
let mut records = records.lock().unwrap();
|
||||
let index = records.len();
|
||||
records.push(Record {
|
||||
name: name.to_string(),
|
||||
start: Instant::now(),
|
||||
end: None,
|
||||
user_start: user,
|
||||
sys_start: sys,
|
||||
user: 0.0,
|
||||
sys: 0.0,
|
||||
parent,
|
||||
children: Vec::new(),
|
||||
});
|
||||
Timer { timers: self.clone(), index, stopped: false }
|
||||
}
|
||||
|
||||
fn stop(&self, index: usize) {
|
||||
let (user, sys) = rusage();
|
||||
let mut records = self.records.as_ref().unwrap().lock().unwrap();
|
||||
let record = &mut records[index];
|
||||
if record.end.is_none() {
|
||||
record.end = Some(Instant::now());
|
||||
record.user = user - record.user_start;
|
||||
record.sys = sys - record.sys_start;
|
||||
}
|
||||
}
|
||||
|
||||
/// Prints the timer tree to `out`: stdout for the ELF linker, as C++
|
||||
/// mold does, and stderr for the Mach-O linker, where ld64 reports its
|
||||
/// -print_statistics figures.
|
||||
pub fn print(&self, out: &mut dyn Write) {
|
||||
let Some(records) = &self.records else {
|
||||
return;
|
||||
};
|
||||
let mut records = records.lock().unwrap();
|
||||
let now = Instant::now();
|
||||
for r in records.iter_mut() {
|
||||
if r.end.is_none() {
|
||||
r.end = Some(now);
|
||||
}
|
||||
}
|
||||
nest_records(&mut records);
|
||||
|
||||
fn print_rec(out: &mut dyn Write, records: &[Record], i: usize, indent: usize) {
|
||||
let r = &records[i];
|
||||
let real = r.end.unwrap().duration_since(r.start).as_secs_f64();
|
||||
let _ = writeln!(
|
||||
out,
|
||||
" {:8.3} {:8.3} {:8.3} {}{}",
|
||||
r.user,
|
||||
r.sys,
|
||||
real,
|
||||
" ".repeat(indent * 2),
|
||||
r.name
|
||||
);
|
||||
// nest_records adds children in their recorded start order.
|
||||
for &child in &r.children {
|
||||
print_rec(out, records, child, indent + 1);
|
||||
}
|
||||
}
|
||||
|
||||
let _ = writeln!(out, " User System Real Name");
|
||||
for i in 0..records.len() {
|
||||
if records[i].parent.is_none() {
|
||||
print_rec(out, &records, i, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Timer {
|
||||
/// Starts a timer nested in this one. Timers started from parallel
|
||||
/// tasks name their parent this way, because in parallel code, the
|
||||
/// timer that started last is not necessarily the enclosing one.
|
||||
pub fn child(&self, name: impl std::fmt::Display) -> Self {
|
||||
self.timers.start_child(name, Some(self.index))
|
||||
}
|
||||
|
||||
pub fn handle(&self) -> TimerHandle {
|
||||
TimerHandle { timers: self.timers.clone(), parent: self.index }
|
||||
}
|
||||
|
||||
pub fn stop(&mut self) {
|
||||
if !self.stopped {
|
||||
self.stopped = true;
|
||||
self.timers.stop(self.index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TimerHandle {
|
||||
pub fn child(&self, name: impl std::fmt::Display) -> Timer {
|
||||
self.timers.start_child(name, Some(self.parent))
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Timer {
|
||||
fn drop(&mut self) {
|
||||
self.stop();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn implicit_timer_parents_follow_completed_intervals() {
|
||||
let timers = Timers::new();
|
||||
let root = timers.start("root");
|
||||
let _running = [
|
||||
timers.start("foreground"),
|
||||
timers.start("nested"),
|
||||
timers.start("background"),
|
||||
timers.start("next foreground"),
|
||||
root.child("explicit child"),
|
||||
];
|
||||
let mut records = timers.records.as_ref().unwrap().lock().unwrap();
|
||||
let start = records[0].start;
|
||||
for (record, (begin, end)) in
|
||||
records.iter_mut().zip([(0, 100), (10, 40), (15, 25), (20, 70), (50, 80), (60, 65)])
|
||||
{
|
||||
record.start = start + std::time::Duration::from_millis(begin);
|
||||
record.end = Some(start + std::time::Duration::from_millis(end));
|
||||
}
|
||||
nest_records(&mut records);
|
||||
let children: Vec<_> = records.iter().map(|r| r.children.clone()).collect();
|
||||
drop(records);
|
||||
assert_eq!(children, [vec![1, 3, 4, 5], vec![2], vec![], vec![], vec![], vec![]]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn optional_recording_includes_nested_and_background_timers() {
|
||||
for timers in [Timers::new(), Timers::disabled()] {
|
||||
let mut root = timers.start("root");
|
||||
let child = root.child("child");
|
||||
let handle = root.handle();
|
||||
std::thread::spawn(move || drop(handle.child("background"))).join().unwrap();
|
||||
drop(child);
|
||||
root.stop();
|
||||
root.stop();
|
||||
if let Some(records) = &timers.records {
|
||||
let mut records = records.lock().unwrap();
|
||||
nest_records(&mut records);
|
||||
assert_eq!(records.len(), 3);
|
||||
assert!(records.iter().all(|record| record.end.is_some()));
|
||||
assert_eq!(records[0].children, [1, 2]);
|
||||
} else {
|
||||
assert!(root.timers.records.is_none());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
/// Prefetches a cache line for a future read, preferring the L1 cache.
|
||||
/// Targets without a supported prefetch instruction ignore the hint.
|
||||
#[inline]
|
||||
pub fn prefetch(_ptr: *const u8) {
|
||||
#[cfg(any(all(target_arch = "x86", target_feature = "sse"), target_arch = "x86_64"))]
|
||||
// SAFETY: PREFETCHT0 is an SSE instruction, which x86-64 always has and
|
||||
// the cfg requires on 32-bit x86. It is a non-faulting hint and changes
|
||||
// no registers or memory.
|
||||
unsafe {
|
||||
std::arch::asm!(
|
||||
"prefetcht0 [{ptr}]",
|
||||
ptr = in(reg) _ptr,
|
||||
options(readonly, nostack, preserves_flags),
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(any(target_arch = "aarch64", target_arch = "arm64ec"))]
|
||||
// SAFETY: PRFM is a non-faulting hint and changes no registers or memory.
|
||||
unsafe {
|
||||
std::arch::asm!(
|
||||
"prfm pldl1keep, [{ptr}]",
|
||||
ptr = in(reg) _ptr,
|
||||
options(readonly, nostack, preserves_flags),
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "arm")]
|
||||
// SAFETY: PLD is a non-faulting hint and changes no registers or memory.
|
||||
unsafe {
|
||||
std::arch::asm!(
|
||||
"pld [{ptr}]",
|
||||
ptr = in(reg) _ptr,
|
||||
options(readonly, nostack, preserves_flags),
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))]
|
||||
// SAFETY: PREFETCH.R is an ORI hint, which is a no-op without Zicbop.
|
||||
// Spell it as ORI so the assembler does not require that extension.
|
||||
unsafe {
|
||||
std::arch::asm!(
|
||||
"ori zero, {ptr}, 1",
|
||||
ptr = in(reg) _ptr,
|
||||
options(readonly, nostack, preserves_flags),
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(any(target_arch = "powerpc", target_arch = "powerpc64"))]
|
||||
// SAFETY: DCBT is a non-faulting hint and changes no registers or memory.
|
||||
unsafe {
|
||||
std::arch::asm!(
|
||||
"dcbt 0, {ptr}",
|
||||
ptr = in(reg) _ptr,
|
||||
options(readonly, nostack, preserves_flags),
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "s390x")]
|
||||
// SAFETY: PFD is a non-faulting hint and changes no registers or memory.
|
||||
unsafe {
|
||||
std::arch::asm!(
|
||||
"pfd 1, 0({ptr})",
|
||||
ptr = in(reg_addr) _ptr,
|
||||
options(readonly, nostack, preserves_flags),
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "loongarch64")]
|
||||
// SAFETY: PRELD is a non-faulting hint and changes no registers or memory.
|
||||
unsafe {
|
||||
std::arch::asm!(
|
||||
"preld 0, {ptr}, 0",
|
||||
ptr = in(reg) _ptr,
|
||||
options(readonly, nostack, preserves_flags),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
//! Records of object files, read, written and viewed in their file
|
||||
//! representation. A record's fields are bytes, or integers backed by
|
||||
//! bytes that read and write in the file's byte order, so a record has
|
||||
//! alignment one: one in a possibly unaligned archive member is read in
|
||||
//! place, without depending on the host's byte order.
|
||||
|
||||
/// A record stored in its file representation.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Implementations must have alignment one, contain no padding or references,
|
||||
/// and accept every bit pattern. These requirements let records in possibly
|
||||
/// unaligned archive members be read and written without host dependencies.
|
||||
pub unsafe trait FileRecord: Clone + Copy + Default + Send + Sync + 'static {
|
||||
fn parse(bytes: &[u8]) -> Self {
|
||||
const { assert!(align_of::<Self>() == 1) };
|
||||
assert!(bytes.len() >= size_of::<Self>());
|
||||
// SAFETY: the trait guarantees that every bit pattern is valid, and
|
||||
// the length check proves that a complete record is available.
|
||||
unsafe { bytes.as_ptr().cast::<Self>().read_unaligned() }
|
||||
}
|
||||
|
||||
fn write(&self, buf: &mut [u8]) {
|
||||
assert!(buf.len() >= size_of::<Self>());
|
||||
// SAFETY: `buf` has room for the complete record, and copying bytes
|
||||
// does not depend on its alignment.
|
||||
unsafe {
|
||||
std::ptr::copy_nonoverlapping(
|
||||
std::ptr::from_ref(self).cast::<u8>(),
|
||||
buf.as_mut_ptr(),
|
||||
size_of::<Self>(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// The record's bytes, as a file holds them.
|
||||
fn as_bytes(&self) -> &[u8] {
|
||||
// SAFETY: a record has no padding, so all of its bytes are
|
||||
// initialized.
|
||||
unsafe {
|
||||
std::slice::from_raw_parts(std::ptr::from_ref(self).cast::<u8>(), size_of::<Self>())
|
||||
}
|
||||
}
|
||||
|
||||
fn write_all(records: &[Self], buf: &mut [u8]) {
|
||||
for (record, slot) in records.iter().zip(buf.chunks_exact_mut(size_of::<Self>())) {
|
||||
record.write(slot);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Views one record directly in its file representation.
|
||||
pub fn record_from_bytes<R: FileRecord>(data: &[u8]) -> &R {
|
||||
const { assert!(align_of::<R>() == 1) };
|
||||
assert!(data.len() >= size_of::<R>());
|
||||
// SAFETY: FileRecord requires alignment one and every bit pattern to be
|
||||
// valid. The length check proves that one complete record is present.
|
||||
unsafe { &*data.as_ptr().cast() }
|
||||
}
|
||||
|
||||
/// Views records directly in their file representation.
|
||||
pub fn records_from_bytes<R: FileRecord>(data: &[u8]) -> &[R] {
|
||||
const { assert!(align_of::<R>() == 1 && size_of::<R>() != 0) };
|
||||
let size = size_of::<R>();
|
||||
assert!(data.len().is_multiple_of(size));
|
||||
// SAFETY: FileRecord requires alignment one and every bit pattern to be
|
||||
// valid. The resulting slice covers exactly `data`.
|
||||
unsafe { std::slice::from_raw_parts(data.as_ptr().cast(), data.len() / size) }
|
||||
}
|
||||
|
||||
/// Mutably views records directly in their file representation.
|
||||
pub fn records_from_bytes_mut<R: FileRecord>(data: &mut [u8]) -> &mut [R] {
|
||||
const { assert!(align_of::<R>() == 1 && size_of::<R>() != 0) };
|
||||
let size = size_of::<R>();
|
||||
assert!(data.len().is_multiple_of(size));
|
||||
// SAFETY: FileRecord requires alignment one and every bit pattern to be
|
||||
// valid. `data` is exclusively borrowed for the returned slice.
|
||||
unsafe { std::slice::from_raw_parts_mut(data.as_mut_ptr().cast(), data.len() / size) }
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
//! Response files. If a command line argument is in the form of
|
||||
//! `@path/to/some/file` (i.e. it starts with an at sign), the linker reads
|
||||
//! the given file and interprets its contents as a list of command line
|
||||
//! arguments. A file containing command line arguments is called a
|
||||
//! "response file".
|
||||
//!
|
||||
//! A response file is often used to pass a very large number of arguments
|
||||
//! to the linker without exceeding the kernel's command line length limit.
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::ffi::{OsStr, OsString};
|
||||
use std::path::Path;
|
||||
|
||||
use bstr::ByteVec;
|
||||
|
||||
use crate::bytes::{display, is_space, os_str};
|
||||
use crate::fatal;
|
||||
use crate::mapped_file::MappedFile;
|
||||
|
||||
/// Replaces each "@path/to/some/text/file" argument with the arguments
|
||||
/// the file holds. For the Mach-O linker (`macho`), an argument starting
|
||||
/// with "@rpath", "@loader_path" or "@executable_path" is a dylib path,
|
||||
/// not a response file, wherever it is, even an option's argument.
|
||||
pub fn expand_response_files(argv: Vec<OsString>, macho: bool) -> Vec<Cow<'static, OsStr>> {
|
||||
let mut args = Vec::new();
|
||||
for arg in argv {
|
||||
if let Some(path) = response_file(arg.as_encoded_bytes(), macho) {
|
||||
args.extend(read_response_file(path, 1, macho));
|
||||
} else {
|
||||
args.push(Cow::Owned(arg));
|
||||
}
|
||||
}
|
||||
args
|
||||
}
|
||||
|
||||
/// The response file an argument names, if it names one (see
|
||||
/// expand_response_files).
|
||||
fn response_file(arg: &[u8], macho: bool) -> Option<&Path> {
|
||||
const DYLIB_PATHS: [&[u8]; 3] = [b"@rpath", b"@loader_path", b"@executable_path"];
|
||||
if macho && DYLIB_PATHS.iter().any(|prefix| arg.starts_with(prefix)) {
|
||||
return None;
|
||||
}
|
||||
arg.strip_prefix(b"@").map(|path| Path::new(os_str(path)))
|
||||
}
|
||||
|
||||
/// Opens a response file, tokenizes its contents, and returns the tokens.
|
||||
fn read_response_file(path: &Path, depth: usize, macho: bool) -> Vec<Cow<'static, OsStr>> {
|
||||
if depth > 10 {
|
||||
fatal!("{}: response file nesting too deep", path.display());
|
||||
}
|
||||
|
||||
let mf = MappedFile::must_open(path);
|
||||
mf.set_dependency(false);
|
||||
let data = mf.data();
|
||||
|
||||
// Arguments are passed on as C strings, e.g. to the LTO plugin, so they
|
||||
// must not contain a NUL byte. Arguments given by the OS never do.
|
||||
if data.contains(&0) {
|
||||
fatal!("{}: response file contains a NUL byte", path.display());
|
||||
}
|
||||
|
||||
let mut expanded = Vec::new();
|
||||
let mut i = 0;
|
||||
|
||||
while i < data.len() {
|
||||
if is_space(data[i]) {
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Plain tokens can borrow the mapping, which lives for the complete
|
||||
// link. Copy only when removing quotes or backslashes.
|
||||
let start = i;
|
||||
while i < data.len() && !is_space(data[i]) && !matches!(data[i], b'\\' | b'\'' | b'"') {
|
||||
i += 1;
|
||||
}
|
||||
let mut tok = Cow::Borrowed(&data[start..i]);
|
||||
let mut quote = None;
|
||||
while i < data.len() {
|
||||
let c = data[i];
|
||||
if c == b'\\' {
|
||||
if i + 1 == data.len() {
|
||||
fatal!("{}: premature end of input", path.display());
|
||||
}
|
||||
tok.to_mut().push(data[i + 1]);
|
||||
i += 2;
|
||||
} else if let Some(q) = quote {
|
||||
if c == q {
|
||||
quote = None;
|
||||
} else {
|
||||
tok.to_mut().push(c);
|
||||
}
|
||||
i += 1;
|
||||
} else if c == b'\'' || c == b'"' {
|
||||
quote = Some(c);
|
||||
i += 1;
|
||||
} else if is_space(c) {
|
||||
break;
|
||||
} else {
|
||||
tok.to_mut().push(c);
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
if quote.is_some() {
|
||||
fatal!("{}: premature end of input", path.display());
|
||||
}
|
||||
if let Some(nested) = response_file(&tok, macho) {
|
||||
expanded.extend(read_response_file(nested, depth + 1, macho));
|
||||
} else {
|
||||
expanded.push(match tok {
|
||||
Cow::Borrowed(bytes) => Cow::Borrowed(os_str(bytes)),
|
||||
Cow::Owned(bytes) => Cow::Owned(
|
||||
bytes
|
||||
.into_os_string()
|
||||
.unwrap_or_else(|e| fatal!("invalid OS string: {}", display(e.as_bytes()))),
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
expanded
|
||||
}
|
||||
@@ -1,166 +0,0 @@
|
||||
//! A configurable implementation of SipHash.
|
||||
//!
|
||||
//! The compression rounds, finalization rounds and 64- or 128-bit output are
|
||||
//! selected with const generic parameters. This implementation is based on
|
||||
//! the reference implementation at https://github.com/rui314/siphash.
|
||||
|
||||
pub struct SipHashTmpl<const C_ROUNDS: usize, const D_ROUNDS: usize, const OUTLEN: usize> {
|
||||
v0: u64,
|
||||
v1: u64,
|
||||
v2: u64,
|
||||
v3: u64,
|
||||
buf: [u8; 8],
|
||||
buflen: u8,
|
||||
sum: u8,
|
||||
}
|
||||
|
||||
impl<const C_ROUNDS: usize, const D_ROUNDS: usize, const OUTLEN: usize>
|
||||
SipHashTmpl<C_ROUNDS, D_ROUNDS, OUTLEN>
|
||||
{
|
||||
#[inline]
|
||||
pub fn new(key: &[u8; 16]) -> Self {
|
||||
const { assert!(OUTLEN == 64 || OUTLEN == 128) };
|
||||
|
||||
let k0 = u64::from_le_bytes(key[..8].try_into().unwrap());
|
||||
let k1 = u64::from_le_bytes(key[8..].try_into().unwrap());
|
||||
let mut hasher = Self {
|
||||
v0: 0x736f_6d65_7073_6575 ^ k0,
|
||||
v1: 0x646f_7261_6e64_6f6d ^ k1,
|
||||
v2: 0x6c79_6765_6e65_7261 ^ k0,
|
||||
v3: 0x7465_6462_7974_6573 ^ k1,
|
||||
buf: [0; 8],
|
||||
buflen: 0,
|
||||
sum: 0,
|
||||
};
|
||||
|
||||
if OUTLEN == 128 {
|
||||
hasher.v1 ^= 0xee;
|
||||
}
|
||||
hasher
|
||||
}
|
||||
|
||||
// ICF repeatedly hashes fixed-size digests. Inlining lets the optimizer
|
||||
// remove the buffering paths for these word-aligned updates.
|
||||
#[inline(always)]
|
||||
pub fn update(&mut self, mut msg: &[u8]) {
|
||||
self.sum = self.sum.wrapping_add(msg.len() as u8);
|
||||
|
||||
if self.buflen != 0 {
|
||||
let buflen = self.buflen as usize;
|
||||
if buflen + msg.len() < 8 {
|
||||
self.buf[buflen..buflen + msg.len()].copy_from_slice(msg);
|
||||
self.buflen += msg.len() as u8;
|
||||
return;
|
||||
}
|
||||
|
||||
let n = 8 - buflen;
|
||||
self.buf[buflen..].copy_from_slice(&msg[..n]);
|
||||
self.compress(u64::from_le_bytes(self.buf));
|
||||
msg = &msg[n..];
|
||||
self.buflen = 0;
|
||||
}
|
||||
|
||||
let (words, msg) = msg.as_chunks::<8>();
|
||||
for &word in words {
|
||||
self.compress(u64::from_le_bytes(word));
|
||||
}
|
||||
|
||||
self.buf[..msg.len()].copy_from_slice(msg);
|
||||
self.buflen = msg.len() as u8;
|
||||
}
|
||||
|
||||
/// Hashes a word in little-endian byte order. The common aligned case
|
||||
/// bypasses the byte buffer, including in loops of fixed-size updates.
|
||||
#[inline(always)]
|
||||
pub fn update_u64(&mut self, word: u64) {
|
||||
if self.buflen != 0 {
|
||||
self.update(&word.to_le_bytes());
|
||||
return;
|
||||
}
|
||||
self.sum = self.sum.wrapping_add(8);
|
||||
self.compress(word);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn finish(mut self, out: &mut [u8]) {
|
||||
assert_eq!(out.len() * 8, OUTLEN);
|
||||
|
||||
self.buf[self.buflen as usize..].fill(0);
|
||||
self.compress((u64::from(self.sum) << 56) | u64::from_le_bytes(self.buf));
|
||||
|
||||
self.v2 ^= if OUTLEN == 128 { 0xee } else { 0xff };
|
||||
self.finalize();
|
||||
out[..8].copy_from_slice(&(self.v0 ^ self.v1 ^ self.v2 ^ self.v3).to_le_bytes());
|
||||
|
||||
if OUTLEN == 128 {
|
||||
self.v1 ^= 0xdd;
|
||||
self.finalize();
|
||||
out[8..].copy_from_slice(&(self.v0 ^ self.v1 ^ self.v2 ^ self.v3).to_le_bytes());
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn round(&mut self) {
|
||||
self.v0 = self.v0.wrapping_add(self.v1);
|
||||
self.v1 = self.v1.rotate_left(13);
|
||||
self.v1 ^= self.v0;
|
||||
self.v0 = self.v0.rotate_left(32);
|
||||
self.v2 = self.v2.wrapping_add(self.v3);
|
||||
self.v3 = self.v3.rotate_left(16);
|
||||
self.v3 ^= self.v2;
|
||||
self.v0 = self.v0.wrapping_add(self.v3);
|
||||
self.v3 = self.v3.rotate_left(21);
|
||||
self.v3 ^= self.v0;
|
||||
self.v2 = self.v2.wrapping_add(self.v1);
|
||||
self.v1 = self.v1.rotate_left(17);
|
||||
self.v1 ^= self.v2;
|
||||
self.v2 = self.v2.rotate_left(32);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn compress(&mut self, m: u64) {
|
||||
self.v3 ^= m;
|
||||
for _ in 0..C_ROUNDS {
|
||||
self.round();
|
||||
}
|
||||
self.v0 ^= m;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn finalize(&mut self) {
|
||||
for _ in 0..D_ROUNDS {
|
||||
self.round();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type SipHash13_128 = SipHashTmpl<1, 3, 128>;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn word_updates_match_bytes_with_partial_buffers_and_length_wraparound() {
|
||||
let key = std::array::from_fn(|i| i as u8);
|
||||
let data: Vec<u8> = (0..1040).map(|i| (i * 97) as u8).collect();
|
||||
for prefix in 0..8 {
|
||||
for words in 0..128 {
|
||||
let end = prefix + words * 8;
|
||||
let mut bytes = SipHash13_128::new(&key);
|
||||
bytes.update(&data[..end + 3]);
|
||||
|
||||
let mut mixed = SipHash13_128::new(&key);
|
||||
mixed.update(&data[..prefix]);
|
||||
for &word in data[prefix..end].as_chunks::<8>().0 {
|
||||
mixed.update_u64(u64::from_le_bytes(word));
|
||||
}
|
||||
mixed.update(&data[end..end + 3]);
|
||||
let (mut expected, mut actual) = ([0; 16], [0; 16]);
|
||||
bytes.finish(&mut expected);
|
||||
mixed.finish(&mut actual);
|
||||
assert_eq!(actual, expected, "prefix={prefix}, words={words}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,140 +0,0 @@
|
||||
//! Process management: forking a child to hide exit latency, and signal
|
||||
//! handling for disk-full errors.
|
||||
|
||||
#[cfg(not(windows))]
|
||||
use std::fs::File;
|
||||
#[cfg(not(windows))]
|
||||
use std::io::{Read, Write};
|
||||
#[cfg(not(windows))]
|
||||
use std::os::unix::io::{FromRawFd, OwnedFd};
|
||||
#[cfg(not(windows))]
|
||||
use std::sync::Mutex;
|
||||
|
||||
#[cfg(not(windows))]
|
||||
static PIPE_WRITER: Mutex<Option<OwnedFd>> = Mutex::new(None);
|
||||
|
||||
// Exiting from a program with large memory usage is slow --
|
||||
// it may take a few hundred milliseconds. To hide the latency,
|
||||
// we fork a child and let it do the actual linking work.
|
||||
#[cfg(not(windows))]
|
||||
pub fn fork_child() {
|
||||
let mut pipefd = [0i32; 2];
|
||||
// Keep the pipe's descriptors inheritable across the LTO restart.
|
||||
// SAFETY: pipe initializes both descriptors on success; each then has
|
||||
// exactly one owner in this process.
|
||||
let (reader, writer) = unsafe {
|
||||
if libc::pipe(pipefd.as_mut_ptr()) == -1 {
|
||||
eprintln!("mold: pipe failed");
|
||||
std::process::exit(1);
|
||||
}
|
||||
(OwnedFd::from_raw_fd(pipefd[0]), OwnedFd::from_raw_fd(pipefd[1]))
|
||||
};
|
||||
// SAFETY: this runs before the linker starts its worker threads. The
|
||||
// parent only waits for completion and exits; the child continues linking.
|
||||
unsafe {
|
||||
let pid = libc::fork();
|
||||
if pid == -1 {
|
||||
eprintln!("mold: fork failed");
|
||||
std::process::exit(1);
|
||||
}
|
||||
if pid > 0 {
|
||||
// Parent
|
||||
drop(writer);
|
||||
if File::from(reader).read_exact(&mut [0u8]).is_ok() {
|
||||
libc::_exit(0);
|
||||
}
|
||||
|
||||
// If SIGCHLD is ignored, which is inherited across exec, the
|
||||
// child is reaped automatically and waitpid fails. Its exit
|
||||
// status is then lost, so report a failure.
|
||||
let mut status = 0;
|
||||
if libc::waitpid(pid, &raw mut status, 0) == -1 {
|
||||
libc::_exit(1);
|
||||
}
|
||||
if libc::WIFEXITED(status) {
|
||||
libc::_exit(libc::WEXITSTATUS(status));
|
||||
}
|
||||
if libc::WIFSIGNALED(status) {
|
||||
libc::raise(libc::WTERMSIG(status));
|
||||
}
|
||||
libc::_exit(1);
|
||||
}
|
||||
}
|
||||
// Child
|
||||
drop(reader);
|
||||
*PIPE_WRITER.lock().unwrap() = Some(writer);
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
pub fn fork_child() {}
|
||||
|
||||
/// Tells the parent that the output is complete.
|
||||
#[cfg(not(windows))]
|
||||
pub fn notify_parent() {
|
||||
let Some(writer) = PIPE_WRITER.lock().unwrap().take() else {
|
||||
return;
|
||||
};
|
||||
let _ = File::from(writer).write_all(&[1]);
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
pub fn notify_parent() {}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
extern "C" fn on_signal(
|
||||
signo: libc::c_int,
|
||||
info: *mut libc::siginfo_t,
|
||||
_context: *mut libc::c_void,
|
||||
) {
|
||||
// mold mmap's an output file, and the mmap succeeds even if there's
|
||||
// not enough space left on the filesystem. The actual disk blocks are
|
||||
// not allocated on the mmap call but when the program writes to it
|
||||
// for the first time.
|
||||
//
|
||||
// If a disk becomes full as a result of a write to an mmap'ed memory
|
||||
// region, the failure of the write is reported as a SIGBUS. This
|
||||
// signal handler catches that signal and prints out a user-friendly
|
||||
// error message. Without this, it is very hard to realize that the
|
||||
// disk might be full.
|
||||
let addr = if info.is_null() {
|
||||
0
|
||||
} else {
|
||||
// SAFETY: SA_SIGINFO gives the handler a valid siginfo_t pointer.
|
||||
unsafe { (*info).si_addr() as usize }
|
||||
};
|
||||
if (signo == libc::SIGSEGV || signo == libc::SIGBUS)
|
||||
&& crate::output_file::output_buffer_contains(addr)
|
||||
{
|
||||
// Handle disk full error
|
||||
let msg = b"mold: failed to write to an output file. Disk full?\n";
|
||||
// SAFETY: write is async-signal-safe.
|
||||
unsafe {
|
||||
libc::write(libc::STDERR_FILENO, msg.as_ptr().cast(), msg.len());
|
||||
}
|
||||
}
|
||||
crate::output_file::cleanup();
|
||||
// Re-throw the signal
|
||||
// SAFETY: restoring the default handlers and re-raising.
|
||||
unsafe {
|
||||
libc::signal(libc::SIGSEGV, libc::SIG_DFL);
|
||||
libc::signal(libc::SIGBUS, libc::SIG_DFL);
|
||||
libc::raise(signo);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
pub fn install_signal_handler() {
|
||||
// SAFETY: installing a signal handler with the three-argument SA_SIGINFO
|
||||
// calling convention.
|
||||
unsafe {
|
||||
let mut action: libc::sigaction = std::mem::zeroed();
|
||||
action.sa_sigaction = on_signal as *const () as libc::sighandler_t;
|
||||
libc::sigemptyset(&raw mut action.sa_mask);
|
||||
action.sa_flags = libc::SA_SIGINFO;
|
||||
libc::sigaction(libc::SIGSEGV, &raw const action, std::ptr::null_mut());
|
||||
libc::sigaction(libc::SIGBUS, &raw const action, std::ptr::null_mut());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
pub fn install_signal_handler() {}
|
||||
Loaded 100 of 2026 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user