mirror of
https://github.com/rui314/mold.git
synced 2026-11-09 17:52:02 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bda68f94a4 | ||
|
|
601b9e691c |
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']
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
Delete this message and describe the bug here. Note that we only fix bugs in the most recent release of mold, so please make sure that you are using the most recent version.
|
||||
@@ -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
|
||||
+45
-181
@@ -1,192 +1,56 @@
|
||||
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-clang:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- name: install test dependencies
|
||||
- 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 bsdmainutils dwarfdump libc6-dev:i386 lib32gcc-10-dev libstdc++-10-dev-arm64-cross gcc-aarch64-linux-gnu g++-aarch64-linux-gnu qemu-user
|
||||
- name: ccache
|
||||
uses: hendrikmuhs/ccache-action@v1
|
||||
- name: make
|
||||
run: |
|
||||
sudo ./install-test-deps.sh
|
||||
sudo .github/workflows/install-extras.sh
|
||||
- run: cargo build --locked
|
||||
- name: check the build for 32-bit hosts
|
||||
export PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH"
|
||||
make CC=clang CXX=clang++ DEBUG=1 -j$(nproc)
|
||||
- name: make test
|
||||
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
|
||||
export PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH"
|
||||
make test -j$(nproc)
|
||||
build-gcc11:
|
||||
runs-on: ubuntu-20.04
|
||||
container: gcc:11.1.0
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: apt-get
|
||||
run: dpkg --add-architecture i386 && apt-get update && apt-get install -y sudo libssl-dev zlib1g-dev libxxhash-dev cmake clang dwarfdump bsdextrautils libgcc-9-dev gcc-aarch64-linux-gnu g++-aarch64-linux-gnu libc6-dev:i386 lib32gcc-10-dev qemu-user
|
||||
- name: ccache
|
||||
uses: hendrikmuhs/ccache-action@v1
|
||||
- name: make
|
||||
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
|
||||
|
||||
export PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH"
|
||||
make CC=gcc CXX=g++ DEBUG=1 -j$(nproc)
|
||||
- name: make test
|
||||
run: |
|
||||
export PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH"
|
||||
make test -j$(nproc)
|
||||
build-macos:
|
||||
runs-on: macos-latest
|
||||
timeout-minutes: 60
|
||||
runs-on: macos-11
|
||||
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: brew
|
||||
run: brew install xxhash
|
||||
- name: ccache
|
||||
uses: hendrikmuhs/ccache-action@v1
|
||||
- name: make
|
||||
run: |
|
||||
export PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH"
|
||||
make DEBUG=1 -j$(sysctl -n hw.logicalcpu)
|
||||
- name: make test
|
||||
run: |
|
||||
export PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH"
|
||||
make test -j$(sysctl -n hw.logicalcpu)
|
||||
@@ -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)
|
||||
+12
-7
@@ -1,8 +1,13 @@
|
||||
/target
|
||||
/out
|
||||
/dist
|
||||
/bench
|
||||
/.claude
|
||||
/.codex
|
||||
*.o
|
||||
*.so
|
||||
*~
|
||||
__pycache__/
|
||||
.cache
|
||||
compile_commands.json
|
||||
/mold
|
||||
/test/tmp
|
||||
options.inc
|
||||
core
|
||||
ld
|
||||
ld64.mold
|
||||
gentoo
|
||||
out
|
||||
@@ -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,197 @@
|
||||
PREFIX = /usr/local
|
||||
BINDIR = $(PREFIX)/bin
|
||||
LIBDIR = $(PREFIX)/lib
|
||||
LIBEXECDIR = $(PREFIX)/libexec
|
||||
MANDIR = $(PREFIX)/share/man
|
||||
|
||||
INSTALL = install
|
||||
INSTALL_PROGRAM = $(INSTALL)
|
||||
INSTALL_DATA = $(INSTALL) -m 644
|
||||
|
||||
D = $(DESTDIR)
|
||||
|
||||
# CXX defaults to `g++`. Rewrite it with a vendor-neutral compiler
|
||||
# name `c++`.
|
||||
ifeq ($(origin CXX), default)
|
||||
CXX = c++
|
||||
endif
|
||||
|
||||
# If you want to keep symbols in the installed binary, run make with
|
||||
# `STRIP=true` to run /bin/true instead of the strip command.
|
||||
STRIP = strip
|
||||
|
||||
SRCS=$(wildcard *.cc elf/*.cc macho/*.cc)
|
||||
HEADERS=$(wildcard *.h elf/*.h macho/*.h)
|
||||
OBJS=$(SRCS:%.cc=out/%.o)
|
||||
|
||||
OS = $(shell uname -s)
|
||||
ARCH = $(shell uname -m)
|
||||
|
||||
IS_ANDROID = 0
|
||||
ifneq ($(findstring -android,$(shell $(CC) -dumpmachine)),)
|
||||
IS_ANDROID = 1
|
||||
endif
|
||||
|
||||
# If you want to compile mold for debugging, invoke make as
|
||||
# `make CXXFLAGS=-g`.
|
||||
CFLAGS = -O2
|
||||
CXXFLAGS = -O2
|
||||
|
||||
MOLD_CXXFLAGS = -std=c++20 -fno-exceptions -fno-unwind-tables \
|
||||
-fno-asynchronous-unwind-tables -DMOLD_VERSION=\"1.0.3\" \
|
||||
-DLIBDIR="\"$(LIBDIR)\""
|
||||
|
||||
MOLD_LDFLAGS = -pthread -lz -lm
|
||||
|
||||
GIT_HASH = $(shell [ -d .git ] && git rev-parse HEAD)
|
||||
ifneq ($(GIT_HASH),)
|
||||
MOLD_CXXFLAGS += -DGIT_HASH=\"$(GIT_HASH)\"
|
||||
endif
|
||||
|
||||
LTO = 0
|
||||
|
||||
ifeq ($(LTO), 1)
|
||||
CXXFLAGS += -flto -O3
|
||||
LDFLAGS += -flto
|
||||
endif
|
||||
|
||||
# By default, we want to use mimalloc as a memory allocator. mimalloc
|
||||
# is disabled on macOS and Android because it didn't work on those hosts.
|
||||
USE_MIMALLOC = 1
|
||||
ifeq ($(OS), Darwin)
|
||||
USE_MIMALLOC = 0
|
||||
else ifeq ($(IS_ANDROID), 1)
|
||||
USE_MIMALLOC = 0
|
||||
endif
|
||||
|
||||
ifeq ($(USE_MIMALLOC), 1)
|
||||
ifdef SYSTEM_MIMALLOC
|
||||
MOLD_LDFLAGS += -lmimalloc
|
||||
else
|
||||
MIMALLOC_LIB = out/mimalloc/libmimalloc.a
|
||||
MOLD_CXXFLAGS += -Ithird-party/mimalloc/include
|
||||
MOLD_LDFLAGS += -Wl,-whole-archive $(MIMALLOC_LIB) -Wl,-no-whole-archive
|
||||
endif
|
||||
endif
|
||||
|
||||
ifdef SYSTEM_TBB
|
||||
MOLD_LDFLAGS += -ltbb
|
||||
else
|
||||
TBB_LIB = out/tbb/libs/libtbb.a
|
||||
MOLD_LDFLAGS += $(TBB_LIB)
|
||||
MOLD_CXXFLAGS += -Ithird-party/tbb/include
|
||||
endif
|
||||
|
||||
ifdef SYSTEM_XXHASH
|
||||
MOLD_LDFLAGS += -lxxhash
|
||||
else
|
||||
XXHASH_LIB = third-party/xxhash/libxxhash.a
|
||||
MOLD_LDFLAGS += $(XXHASH_LIB)
|
||||
MOLD_CXXFLAGS += -Ithird-party/xxhash
|
||||
endif
|
||||
|
||||
ifeq ($(OS), Linux)
|
||||
ifeq ($(IS_ANDROID), 0)
|
||||
# glibc before 2.17 need librt for clock_gettime
|
||||
MOLD_LDFLAGS += -lrt
|
||||
endif
|
||||
endif
|
||||
|
||||
# Use pkg-config to know where libcrypto resides.
|
||||
ifneq ($(OS), Darwin)
|
||||
MOLD_CXXFLAGS += $(shell pkg-config --cflags-only-I openssl)
|
||||
MOLD_LDFLAGS += $(shell pkg-config --libs-only-L openssl) -lcrypto
|
||||
endif
|
||||
|
||||
# '-latomic' flag is needed building on riscv64 system
|
||||
# RV32 system doesn't tested yet
|
||||
# seems like '-atomic' would be better but not working.
|
||||
ifeq ($(ARCH), riscv64)
|
||||
MOLD_LDFLAGS += -latomic
|
||||
endif
|
||||
|
||||
# -Wc++11-narrowing is a fatal error on Android, so disable it.
|
||||
ifeq ($(IS_ANDROID), 1)
|
||||
MOLD_CXXFLAGS += -Wno-c++11-narrowing
|
||||
endif
|
||||
|
||||
ifeq ($(OS), Linux)
|
||||
MOLD_WRAPPER_LDFLAGS = -Wl,-push-state -Wl,-no-as-needed -ldl -Wl,-pop-state
|
||||
endif
|
||||
|
||||
all: mold mold-wrapper.so
|
||||
|
||||
mold: $(OBJS) $(MIMALLOC_LIB) $(TBB_LIB) $(XXHASH_LIB)
|
||||
$(CXX) $(OBJS) -o $@ $(MOLD_LDFLAGS) $(LDFLAGS)
|
||||
ln -sf mold ld
|
||||
ln -sf mold ld64.mold
|
||||
|
||||
mold-wrapper.so: elf/mold-wrapper.c Makefile
|
||||
$(CC) $(CFLAGS) -fPIC -shared -o $@ $< $(MOLD_WRAPPER_LDFLAGS) $(LDFLAGS)
|
||||
|
||||
out/%.o: %.cc $(HEADERS) Makefile out/elf/.keep out/macho/.keep
|
||||
$(CXX) $(MOLD_CXXFLAGS) $(CXXFLAGS) -c -o $@ $<
|
||||
|
||||
out/elf/.keep:
|
||||
mkdir -p out/elf
|
||||
touch $@
|
||||
|
||||
out/macho/.keep:
|
||||
mkdir -p out/macho
|
||||
touch $@
|
||||
|
||||
$(MIMALLOC_LIB):
|
||||
mkdir -p out/mimalloc
|
||||
(cd out/mimalloc; CFLAGS=-DMI_USE_ENVIRON=0 cmake -G'Unix Makefiles' ../../third-party/mimalloc)
|
||||
$(MAKE) -C out/mimalloc mimalloc-static
|
||||
|
||||
$(TBB_LIB):
|
||||
mkdir -p out/tbb
|
||||
(cd out/tbb; cmake -G'Unix Makefiles' -DBUILD_SHARED_LIBS=OFF -DTBB_TEST=OFF -DCMAKE_CXX_FLAGS=-D__TBB_DYNAMIC_LOAD_ENABLED=0 -DTBB_STRICT=OFF ../../third-party/tbb)
|
||||
$(MAKE) -C out/tbb tbb
|
||||
(cd out/tbb; ln -sf *_relwithdebinfo libs)
|
||||
|
||||
$(XXHASH_LIB):
|
||||
$(MAKE) -C third-party/xxhash libxxhash.a
|
||||
|
||||
test tests check: all
|
||||
ifeq ($(OS), Darwin)
|
||||
$(MAKE) -C test -f Makefile.darwin --no-print-directory
|
||||
else
|
||||
$(MAKE) -C test -f Makefile.linux --no-print-directory --output-sync
|
||||
endif
|
||||
|
||||
@if test -t 1; then \
|
||||
printf '\e[32mPassed all tests\e[0m\n'; \
|
||||
else \
|
||||
echo 'Passed all tests'; \
|
||||
fi
|
||||
|
||||
install: all
|
||||
$(INSTALL) -d $D$(BINDIR)
|
||||
$(INSTALL_PROGRAM) mold $D$(BINDIR)
|
||||
$(STRIP) $D$(BINDIR)/mold
|
||||
|
||||
$(INSTALL) -d $D$(LIBDIR)/mold
|
||||
$(INSTALL_DATA) mold-wrapper.so $D$(LIBDIR)/mold
|
||||
$(STRIP) $D$(LIBDIR)/mold/mold-wrapper.so
|
||||
|
||||
$(INSTALL) -d $D$(LIBEXECDIR)/mold
|
||||
ln -sf $(BINDIR)/mold $D$(LIBEXECDIR)/mold/ld
|
||||
|
||||
$(INSTALL) -d $D$(MANDIR)/man1
|
||||
$(INSTALL_DATA) docs/mold.1 $D$(MANDIR)/man1
|
||||
|
||||
ln -sf mold $D$(BINDIR)/ld.mold
|
||||
ln -sf mold $D$(BINDIR)/ld64.mold
|
||||
|
||||
uninstall:
|
||||
rm -f $D$(BINDIR)/mold $D$(BINDIR)/ld.mold $D$(BINDIR)/ld64.mold
|
||||
rm -f $D$(MANDIR)/man1/mold.1
|
||||
rm -rf $D$(LIBDIR)/mold
|
||||
|
||||
clean:
|
||||
rm -rf *~ mold mold-wrapper.so out ld ld64.mold
|
||||
$(MAKE) -C third-party/xxhash clean
|
||||
|
||||
.PHONY: all test tests check clean
|
||||
@@ -1,187 +1,133 @@
|
||||
# mold: A Modern Linker
|
||||
|
||||
mold is a high-performance drop-in replacement for existing Unix linkers,
|
||||
designed to speed up builds. In our August 2026 benchmarks, it links 4.9x
|
||||
faster than [LLVM lld](https://lld.llvm.org) and 1.9x faster than
|
||||
[wild](https://github.com/wild-linker/wild) at the median; see
|
||||
[Benchmark](#benchmark) for the full results.
|
||||
mold is a faster drop-in replacement for existing Unix linkers.
|
||||
It is several times faster than LLVM lld linker, the second-fastest
|
||||
open-source linker which I originally created a few years ago.
|
||||
mold is created for increasing developer productivity by reducing
|
||||
build time especially in rapid debug-edit-rebuild cycles.
|
||||
|
||||
mold is written by the original developer of LLVM lld, the linker that
|
||||
Android, Chrome, FreeBSD, PlayStation, Nintendo Switch, and other production
|
||||
systems are built with. mold started as an effort to build an even faster
|
||||
linker from scratch, free of the architectural limits its author had run into
|
||||
while optimizing lld. It has been in production use since 2021, and today it
|
||||
is the default linker of many large open-source projects and is used
|
||||
internally by many companies.
|
||||
Here is a performance comparison of GNU gold, LLVM lld, and mold for
|
||||
linking final debuginfo-enabled executables of major large programs
|
||||
on a simulated 8-core 16-threads machine.
|
||||
|
||||
mold supports x86-64, i386, ARM 32/64, RISC-V 32/64, PowerPC 32/64, s390x,
|
||||
LoongArch 32/64, SPARC64, m68k, and SH-4.
|
||||

|
||||
|
||||
## Why does linking speed matter?
|
||||
| Program (linker output size) | GNU gold | LLVM lld | mold
|
||||
|-------------------------------|----------|----------|--------
|
||||
| Chrome 96 (1.89 GiB) | 53.86s | 11.74s | 2.21s
|
||||
| Clang 13 (3.18 GiB) | 64.12s | 5.82s | 2.90s
|
||||
| Firefox 89 libxul (1.64 GiB) | 32.95s | 6.80s | 1.42s
|
||||
|
||||
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.
|
||||
mold is so fast that it is only 2x _slower_ than `cp` on the same
|
||||
machine. Feel free to [file a bug](https://github.com/rui314/mold/issues)
|
||||
if you find mold is not faster than other linkers.
|
||||
|
||||
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.
|
||||
mold currently supports x86-64, i386 and ARM64.
|
||||
|
||||
## Benchmark
|
||||
## Why does the speed of linking matter?
|
||||
|
||||
Here is a performance comparison of lld, wild, and mold when linking nine
|
||||
large programs on two machines:
|
||||
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 a
|
||||
source file into an object file (`.o` files). In the second phase,
|
||||
a linker takes all object files to combine them into a single executable
|
||||
or a shared library file.
|
||||
|
||||
- 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 second phase takes a long time if your build output is large.
|
||||
mold can make it faster, saving your time and keeping you from being
|
||||
distracted while waiting for a long build to finish. The difference is
|
||||
most noticeable when you are in rapid debug-edit-rebuild cycles.
|
||||
|
||||
The Threadripper represents many-core workstation and server processors, and
|
||||
the M1 Ultra represents high-performance desktop processors.
|
||||
## Install
|
||||
|
||||
All three linkers were built from source in release configuration as of
|
||||
2026-08-28 and ran with their default options. Times are wall-clock times as
|
||||
seen by the caller, the median of three runs after a warm-up. The ARM64 rows
|
||||
are the ARM64 builds of the same programs. The benchmark suite, including all
|
||||
linker inputs, is available on [Zenodo](https://zenodo.org/records/21882261).
|
||||
|
||||
**AMD Ryzen Threadripper 7980X, debug builds**
|
||||
|
||||
| Program (output size) | lld | wild | mold | wild/mold | lld/mold
|
||||
|-----------------------------------|--------|-------|-------|-----------|---------
|
||||
| Blender 5.2 (2.46 GiB) | 4.72s | 1.98s | 0.86s | 2.3x | 5.5x
|
||||
| Chromium 145 (4.51 GiB) | 16.64s | 3.98s | 1.65s | 2.4x | 10.1x
|
||||
| Chromium 145 ARM64 (4.76 GiB) | 20.91s | N/A | 1.83s | N/A | 11.4x
|
||||
| Clang 21 (4.19 GiB) | 6.19s | 3.71s | 1.34s | 2.8x | 4.6x
|
||||
| ClickHouse 26.1 (5.58 GiB) | 6.62s | 4.40s | 0.98s | 4.5x | 6.7x
|
||||
| Firefox 149 (2.37 GiB) | 5.11s | N/A | 0.78s | N/A | 6.5x
|
||||
| Firefox 149 ARM64 (2.43 GiB) | 6.40s | N/A | 0.93s | N/A | 6.9x
|
||||
| Godot 4.6 (1.01 GiB) | 1.77s | 1.08s | 0.46s | 2.3x | 3.8x
|
||||
| LibreOffice 26.2 (0.98 GiB) | 3.46s | 1.41s | 0.44s | 3.2x | 7.9x
|
||||
| PyTorch 2.9 (3.51 GiB) | 4.35s | 2.48s | 0.80s | 3.1x | 5.4x
|
||||
| TensorFlow 2.21 (9.55 GiB) | 50.73s | N/A | 3.15s | N/A | 16.1x
|
||||
|
||||
**AMD Ryzen Threadripper 7980X, release builds**
|
||||
|
||||
| Program (output size) | lld | wild | mold | wild/mold | lld/mold
|
||||
|-----------------------------------|-------|-------|-------|-----------|---------
|
||||
| Blender 5.2 (0.24 GiB) | 0.85s | 0.30s | 0.20s | 1.5x | 4.2x
|
||||
| Chromium 145 (0.58 GiB) | 6.48s | N/A | 0.64s | N/A | 10.2x
|
||||
| Chromium 145 ARM64 (0.60 GiB) | 7.91s | N/A | 0.73s | N/A | 10.8x
|
||||
| Clang 21 (0.21 GiB) | 0.53s | 0.23s | 0.11s | 2.2x | 5.0x
|
||||
| ClickHouse 26.1 (1.23 GiB) | 3.18s | 2.07s | 0.41s | 5.0x | 7.7x
|
||||
| Firefox 149 (0.22 GiB) | 1.01s | 0.41s | 0.21s | 2.0x | 4.9x
|
||||
| Godot 4.6 (0.15 GiB) | 0.44s | 0.21s | 0.08s | 2.7x | 5.8x
|
||||
| LibreOffice 26.2 (0.19 GiB) | 1.13s | 0.57s | 0.19s | 3.0x | 6.0x
|
||||
| PyTorch 2.9 (0.31 GiB) | 0.68s | 0.32s | 0.15s | 2.2x | 4.7x
|
||||
| TensorFlow 2.21 (0.73 GiB) | 9.62s | N/A | 0.70s | N/A | 13.7x
|
||||
|
||||
**Apple M1 Ultra, debug builds**
|
||||
|
||||
| Program (output size) | lld | wild | mold | wild/mold | lld/mold
|
||||
|-----------------------------------|--------|-------|-------|-----------|---------
|
||||
| Blender 5.2 (2.46 GiB) | 3.21s | 1.81s | 1.56s | 1.2x | 2.1x
|
||||
| Chromium 145 (4.51 GiB) | 9.54s | 3.49s | 2.22s | 1.6x | 4.3x
|
||||
| Chromium 145 ARM64 (4.76 GiB) | 12.67s | N/A | 2.31s | N/A | 5.5x
|
||||
| Clang 21 (4.19 GiB) | 4.40s | 2.78s | 2.96s | 0.9x | 1.5x
|
||||
| ClickHouse 26.1 (5.58 GiB) | 4.92s | 3.50s | 1.71s | 2.1x | 2.9x
|
||||
| Firefox 149 (2.37 GiB) | 3.21s | N/A | 1.12s | N/A | 2.9x
|
||||
| Firefox 149 ARM64 (2.43 GiB) | 4.13s | N/A | 1.12s | N/A | 3.7x
|
||||
| Godot 4.6 (1.01 GiB) | 1.12s | 0.81s | 0.62s | 1.3x | 1.8x
|
||||
| LibreOffice 26.2 (0.98 GiB) | 2.08s | 0.94s | 0.63s | 1.5x | 3.3x
|
||||
| PyTorch 2.9 (3.51 GiB) | 3.07s | 2.10s | 1.45s | 1.4x | 2.1x
|
||||
| TensorFlow 2.21 (9.55 GiB) | 43.68s | N/A | 4.43s | N/A | 9.9x
|
||||
|
||||
**Apple M1 Ultra, release builds**
|
||||
|
||||
| Program (output size) | lld | wild | mold | wild/mold | lld/mold
|
||||
|-----------------------------------|-------|-------|-------|-----------|---------
|
||||
| Blender 5.2 (0.24 GiB) | 0.56s | 0.20s | 0.25s | 0.8x | 2.3x
|
||||
| Chromium 145 (0.58 GiB) | 4.25s | N/A | 0.78s | N/A | 5.5x
|
||||
| Chromium 145 ARM64 (0.60 GiB) | 5.17s | N/A | 0.91s | N/A | 5.7x
|
||||
| Clang 21 (0.21 GiB) | 0.30s | 0.15s | 0.14s | 1.0x | 2.1x
|
||||
| ClickHouse 26.1 (1.23 GiB) | 1.94s | 0.95s | 0.55s | 1.7x | 3.5x
|
||||
| Firefox 149 (0.22 GiB) | 0.56s | 0.23s | 0.20s | 1.1x | 2.7x
|
||||
| Godot 4.6 (0.15 GiB) | 0.26s | 0.11s | 0.11s | 1.0x | 2.3x
|
||||
| LibreOffice 26.2 (0.19 GiB) | 0.62s | 0.30s | 0.23s | 1.3x | 2.7x
|
||||
| PyTorch 2.9 (0.31 GiB) | 0.43s | 0.22s | 0.17s | 1.3x | 2.5x
|
||||
| TensorFlow 2.21 (0.73 GiB) | 8.07s | N/A | 0.68s | N/A | 11.9x
|
||||
|
||||
N/A indicates that the linker cannot link that program. wild's Chromium
|
||||
release links are also marked N/A because wild does not implement `--icf=all`
|
||||
and links without identical code folding.
|
||||
|
||||
## Why is mold so fast?
|
||||
|
||||
mold owes its speed to pervasive parallelism and to efficient data structures
|
||||
and algorithms. For details, read our paper "mold: A Massively Parallel
|
||||
Linker" (ASPLOS 2027), available at https://arxiv.org/abs/2608.23228.
|
||||
|
||||
## Installation
|
||||
|
||||
Binary packages for the following systems are currently available:
|
||||
Binary packages for the following distros 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
|
||||
|
||||
## How to Build
|
||||
mold is written in C++20, so if you build mold yourself, you need a
|
||||
recent version of a C++ compiler and a C++ standard library. GCC 10.2
|
||||
or Clang 12.0.0 as well as libstdc++ 10 or libc++ 7 are recommended.
|
||||
|
||||
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.
|
||||
I'm using Ubuntu 20.04 as a development platform. In that environment,
|
||||
you can build mold by the following commands.
|
||||
|
||||
### Install dependencies
|
||||
|
||||
#### Ubuntu 20.04 and later / Debian 11 and later
|
||||
|
||||
```shell
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y build-essential git clang cmake libstdc++-10-dev libssl-dev libxxhash-dev zlib1g-dev pkg-config
|
||||
```
|
||||
|
||||
#### Fedora 34 and later
|
||||
|
||||
```shell
|
||||
sudo dnf install -y git clang cmake openssl-devel xxhash-devel zlib-devel libstdc++-devel
|
||||
```
|
||||
|
||||
### Compile mold
|
||||
|
||||
```shell
|
||||
git clone https://github.com/rui314/mold.git
|
||||
cd mold
|
||||
cargo build --release
|
||||
git checkout v1.0.3
|
||||
make -j$(nproc) CXX=clang++
|
||||
sudo make install
|
||||
```
|
||||
|
||||
The executable is `target/release/mold`.
|
||||
You may need to pass a C++20 compiler command name to `make`.
|
||||
In the above case, `clang++` is passed. If it doesn't work for you,
|
||||
try a specific version of a compiler such as `g++-10` or `clang++-12`.
|
||||
|
||||
### Install mold
|
||||
By default, `mold` is installed to `/usr/local/bin`.
|
||||
|
||||
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`.
|
||||
If you don't use a recent enough Linux distribution, 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 instead of running `make -j$(nproc)`. The shell script creates a
|
||||
Ubuntu 20.04 Docker image, installs necessary tools and libraries to it,
|
||||
and builds mold as a statically-linked executable.
|
||||
|
||||
You can also run `target/release/mold` directly without installing it.
|
||||
`make test` depends on a few more packages. To install, run the following commands:
|
||||
|
||||
```shell
|
||||
sudo dpkg --add-architecture i386
|
||||
sudo apt update
|
||||
sudo apt-get install bsdmainutils dwarfdump libc6-dev:i386 lib32gcc-10-dev libstdc++-10-dev-arm64-cross gcc-10-aarch64-linux-gnu g++-10-aarch64-linux-gnu
|
||||
```
|
||||
|
||||
## How to use
|
||||
|
||||
<details><summary>A classic way to use mold</summary>
|
||||
|
||||
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.
|
||||
On Unix, the linker command (which is usually `/usr/bin/ld`) is
|
||||
invoked indirectly by the compiler driver (which is usually `cc`,
|
||||
`gcc` or `clang`), which is typically in turn indirectly invoked by
|
||||
`make` or some other build system command.
|
||||
|
||||
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`:
|
||||
If you can specify an additional command line option to your compiler
|
||||
driver by modifying 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`
|
||||
- Clang: pass `-fuse-ld=mold`
|
||||
|
||||
- For GCC 12.1.0 or later: pass `-fuse-ld=mold`
|
||||
- GCC 12.1.0 (upcoming version) 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`.
|
||||
- GCC before 12.1.0: `-fuse-ld` does not accept `mold` as a valid
|
||||
argument, so you need to use `-B` option instead. `-B` is an option
|
||||
to tell GCC where to look for external commands such as `ld`.
|
||||
|
||||
If you installed mold using the script above, there is a directory named
|
||||
`/usr/local/libexec/mold`, and the `ld` command there is a symlink to mold.
|
||||
Pass `-B/usr/local/libexec/mold` to GCC. If you installed under another
|
||||
prefix, use that prefix instead.
|
||||
If you have installed mold with `make install`, there should be a
|
||||
directory named `/usr/libexec/mold` (or `/usr/local/libexec/mold`,
|
||||
depending on your $PREFIX), and `ld` command should be there. The
|
||||
`ld` is actually a symlink to `mold`. So, all you need is to pass
|
||||
`-B/usr/libexec/mold` (or `-B/usr/local/libexec/mold`) to GCC.
|
||||
|
||||
If you haven't installed `ld.mold` to any `$PATH`, you can still pass
|
||||
`-fuse-ld=/absolute/path/to/mold` to clang to use mold. However, GCC does not
|
||||
accept an absolute path as an argument for `-fuse-ld`.
|
||||
If you haven't installed `mold` to any `$PATH`, you can still pass
|
||||
`-fuse-ld=/absolute/path/to/mold` to clang to use mold. GCC does not
|
||||
take an absolute path as an argument for `-fuse-ld` though.
|
||||
|
||||
</details>
|
||||
|
||||
@@ -189,145 +135,77 @@ accept an absolute path as an argument for `-fuse-ld`.
|
||||
|
||||
Create `.cargo/config.toml` in your project directory with the following:
|
||||
|
||||
```toml
|
||||
[target.'cfg(target_os = "linux")']
|
||||
```
|
||||
[target.x86_64-unknown-linux-gnu]
|
||||
linker = "clang"
|
||||
rustflags = ["-C", "link-arg=-fuse-ld=/path/to/mold"]
|
||||
```
|
||||
|
||||
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.
|
||||
where `/path/to/mold` is an absolute path to `mold` exectuable.
|
||||
|
||||
```toml
|
||||
[target.'cfg(target_os = "linux")']
|
||||
rustflags = ["-C", "link-arg=-fuse-ld=mold"]
|
||||
```
|
||||
|
||||
If you want to use mold for all projects, add the above snippet to
|
||||
If you want to use mold for all projects, put 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:
|
||||
It is sometimes very hard to pass an appropriate command line option
|
||||
to `cc` to specify an alternative linker. To deal with the situation,
|
||||
mold has a feature to intercept all invocations of `ld`, `ld.lld` or
|
||||
`ld.gold` and redirect it to itself. To use the 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.
|
||||
Here's an example showing how to link Rust code when using the
|
||||
cargo package manager:
|
||||
|
||||
```shell
|
||||
mold -run cargo build
|
||||
```
|
||||
|
||||
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(3)`-family functions to
|
||||
replace `argv[0]` with `mold` if it is `ld`, `ld.gold` or `ld.lld`.
|
||||
|
||||
</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>
|
||||
readelf -p .comment <executable-file>
|
||||
|
||||
String dump of section '.comment':
|
||||
[ 0] GCC: (Ubuntu 10.2.0-5ubuntu1~20.04) 10.2.0
|
||||
[ 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>
|
||||
## Why is mold so fast?
|
||||
|
||||
<details><summary>Online manual</summary>
|
||||
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.
|
||||
|
||||
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`.
|
||||
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.
|
||||
|
||||
</details>
|
||||

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