mirror of
https://github.com/rui314/mold.git
synced 2026-11-09 04:49:33 -10:00
Merge Rust implementation and its history
This commit is contained in:
commit
af97c80062
3641 files changed
+49548
-807171
No files matched your search
@@ -1,13 +0,0 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: [rui314] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
|
||||
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
||||
@@ -1,93 +0,0 @@
|
||||
name: Build all tarballs
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 0 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
check-changes:
|
||||
runs-on: ubuntu-24.04
|
||||
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
|
||||
packages: write
|
||||
|
||||
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: Login to GitHub Container Registry
|
||||
uses: redhat-actions/podman-login@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build a tarball
|
||||
run: ./dist.sh ${{ matrix.target }}
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v7
|
||||
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
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: Build and Archive
|
||||
shell: pwsh
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -T clangcl ..
|
||||
cmake --build . --config Release -j $Env:NUMBER_OF_PROCESSORS
|
||||
cmake --install . --config Release --prefix ../mold-install
|
||||
cd ..
|
||||
New-Item -ItemType Directory -Force dist | Out-Null
|
||||
$version = $Env:TAG -replace '^v', ''
|
||||
Compress-Archive -Path mold-install\* -DestinationPath dist\mold-$version-windows-x86_64.zip
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: win-x86_64
|
||||
path: dist/mold-*.*
|
||||
@@ -1,44 +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
|
||||
packages: write
|
||||
|
||||
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: Login to GitHub Container Registry
|
||||
uses: redhat-actions/podman-login@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build a tarball
|
||||
run: ./dist.sh ${{ matrix.target }}
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: ${{ matrix.target }}
|
||||
path: dist/mold-*.tar.gz
|
||||
compression-level: 0
|
||||
+105
-108
@@ -1,79 +1,87 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
env:
|
||||
UBSAN_OPTIONS: print_stacktrace=1:halt_on_error=1
|
||||
|
||||
jobs:
|
||||
build-asan-tsan:
|
||||
test-all-targets:
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 60
|
||||
env:
|
||||
# Incremental artifacts and mold's own debug info are unused in CI.
|
||||
CARGO_INCREMENTAL: 0
|
||||
CARGO_PROFILE_DEV_DEBUG: 0
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- name: install test dependencies
|
||||
run: |
|
||||
sudo ./install-test-deps.sh
|
||||
sudo .github/workflows/install-extras.sh
|
||||
- run: cargo build --locked
|
||||
- name: check the build for a 32-bit host
|
||||
run: |
|
||||
rustup target add armv7-unknown-linux-gnueabihf
|
||||
cargo check --locked --workspace --all-targets --target armv7-unknown-linux-gnueabihf
|
||||
- name: verify all target toolchains are available
|
||||
run: |
|
||||
cargo test --locked -p mold-cli --test integration -- --list |
|
||||
tee "$RUNNER_TEMP/test-inventory.txt"
|
||||
! grep -q '^unavailable:' "$RUNNER_TEMP/test-inventory.txt"
|
||||
# All workspace crates were built above. Avoid linking empty test binaries
|
||||
# for the target crates and command.
|
||||
- run: cargo test --locked -p mold -p mold-tests -p mold-cli --lib --test integration
|
||||
- name: archive test results
|
||||
uses: actions/upload-artifact@v7
|
||||
if: failure()
|
||||
with:
|
||||
name: test-results-all-targets
|
||||
path: target/debug/mold-test/out/test
|
||||
|
||||
test-sanitizers:
|
||||
strategy:
|
||||
matrix:
|
||||
target:
|
||||
- ''
|
||||
- '-DMOLD_USE_ASAN=On'
|
||||
- '-DMOLD_USE_TSAN=On'
|
||||
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: rui314/setup-mold@staging
|
||||
- run: sudo ./install-build-deps.sh
|
||||
- name: build
|
||||
run: |
|
||||
sudo apt-get install -y clang-18 clang gcc-multilib gdb dwarfdump zstd
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_COMPILER=clang-18 -DCMAKE_CXX_COMPILER=clang++-18 ${{ matrix.target }} ..
|
||||
cmake --build . -j$(nproc)
|
||||
- run: cd build; ctest --output-on-failure -j$(nproc)
|
||||
- uses: actions/checkout@v6
|
||||
- uses: dtolnay/rust-toolchain@nightly
|
||||
with:
|
||||
components: rust-src
|
||||
- run: sudo ./install-test-deps.sh
|
||||
- run: cargo +nightly build --locked -Zbuild-std --target x86_64-unknown-linux-gnu -p mold-cli --no-default-features --features system-allocator,x86_64
|
||||
- run: |
|
||||
if [ '${{ matrix.sanitizer }}' = address ]; then
|
||||
export ASAN_OPTIONS='${{ matrix.runtime-options }}'
|
||||
else
|
||||
export TSAN_OPTIONS='${{ matrix.runtime-options }}'
|
||||
fi
|
||||
${{ matrix.test-prefix }} cargo +nightly test --locked -Zbuild-std --target x86_64-unknown-linux-gnu -p mold-cli --no-default-features --features system-allocator,x86_64 --test integration -- --native --test-threads 1 --timeout ${{ matrix.test-timeout }}
|
||||
- name: archive test results
|
||||
uses: actions/upload-artifact@v7
|
||||
if: failure()
|
||||
with:
|
||||
name: test-results-clang
|
||||
path: |
|
||||
build
|
||||
!build/CMakeFiles
|
||||
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: redhat-actions/podman-login@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- run: .github/workflows/run-msan.sh
|
||||
|
||||
build-multi-archs:
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- name: install-build-deps
|
||||
run: |
|
||||
sudo ./install-build-deps.sh
|
||||
sudo ./install-cross-tools.sh
|
||||
sudo .github/workflows/install-extras.sh
|
||||
- name: build
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake ..
|
||||
cmake --build . -j$(nproc)
|
||||
- run: cd build; ctest --output-on-failure -j$(nproc)
|
||||
- name: archive test results
|
||||
uses: actions/upload-artifact@v7
|
||||
if: failure()
|
||||
with:
|
||||
name: test-results-multi-archs
|
||||
path: |
|
||||
build
|
||||
!build/CMakeFiles
|
||||
|
||||
build-distros:
|
||||
test-distros:
|
||||
strategy:
|
||||
matrix:
|
||||
distro:
|
||||
@@ -87,17 +95,17 @@ jobs:
|
||||
- ubuntu:25.04
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
container: ${{ matrix.distro }}
|
||||
# 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-build-deps.sh
|
||||
- name: build
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake ..
|
||||
cmake --build . -j$(nproc)
|
||||
- run: cd build; ctest --output-on-failure -j$(nproc)
|
||||
- uses: actions/checkout@v6
|
||||
- run: ./install-test-deps.sh
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- run: cargo build --locked -p mold-cli --no-default-features --features x86_64
|
||||
- run: cargo test --locked -p mold-cli --no-default-features --features x86_64 --test integration -- --native
|
||||
- name: sanitize distro name
|
||||
if: failure()
|
||||
id: sanitize
|
||||
@@ -107,66 +115,55 @@ jobs:
|
||||
if: failure()
|
||||
with:
|
||||
name: test-results-${{ steps.sanitize.outputs.name }}
|
||||
path: |
|
||||
build
|
||||
!build/CMakeFiles
|
||||
path: target/debug/mold-test/out/test
|
||||
|
||||
build-macos:
|
||||
runs-on: macos-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- name: build
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake ..
|
||||
cmake --build . -j$(sysctl -n hw.physicalcpu)
|
||||
- uses: actions/checkout@v6
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- run: cargo build --locked -p mold-cli --no-default-features --features x86_64
|
||||
|
||||
build-windows:
|
||||
runs-on: windows-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- name: build
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -T clangcl ..
|
||||
cmake --build . -j $Env:NUMBER_OF_PROCESSORS
|
||||
- uses: actions/checkout@v6
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- run: cargo build --locked -p mold-cli --no-default-features --features x86_64
|
||||
|
||||
build-msys:
|
||||
runs-on: windows-latest
|
||||
timeout-minutes: 60
|
||||
env:
|
||||
CARGO_NET_GIT_FETCH_WITH_CLI: true
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- name: Setup MSYS2
|
||||
uses: msys2/setup-msys2@v2
|
||||
- uses: actions/checkout@v6
|
||||
- uses: msys2/setup-msys2@v2
|
||||
with:
|
||||
msystem: UCRT64
|
||||
update: true
|
||||
pacboy: gcc-libs:p libwinpthread-git:p tbb:p zlib:p zstd:p dlfcn:p cc:p cmake:p ninja:p
|
||||
- name: build
|
||||
shell: msys2 {0}
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -GNinja -DMOLD_USE_MIMALLOC=OFF -DMOLD_USE_SYSTEM_TBB=ON ..
|
||||
cmake --build . -j $(nproc)
|
||||
install: git
|
||||
pacboy: rust:p cc:p dlfcn:p
|
||||
- shell: msys2 {0}
|
||||
run: cargo build --locked -p mold-cli --no-default-features --features x86_64
|
||||
|
||||
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
|
||||
- name: Build and test
|
||||
uses: vmactions/freebsd-vm@v1
|
||||
- uses: actions/checkout@v6
|
||||
- uses: vmactions/freebsd-vm@v1
|
||||
with:
|
||||
usesh: true
|
||||
run: |
|
||||
./install-build-deps.sh
|
||||
mkdir build
|
||||
cd build
|
||||
cmake ..
|
||||
cmake --build . -j$(nproc)
|
||||
ctest --output-on-failure -j$(nproc)
|
||||
pkg install -y rust
|
||||
./install-test-deps.sh
|
||||
cargo build --locked -p mold-cli --no-default-features --features x86_64
|
||||
cargo test --locked -p mold-cli --no-default-features --features x86_64 --test integration -- --native
|
||||
@@ -1,104 +0,0 @@
|
||||
name: Build & attach tarballs on tag push
|
||||
|
||||
on:
|
||||
push:
|
||||
tags: ['v[0-9]*']
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
id-token: write
|
||||
|
||||
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 }
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
|
||||
- name: Install Podman
|
||||
run: sudo apt-get update && sudo apt-get install -y podman qemu-user-static
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: redhat-actions/podman-login@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- 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
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
|
||||
- name: Build and Archive
|
||||
shell: pwsh
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -T clangcl ..
|
||||
cmake --build . --config Release -j $Env:NUMBER_OF_PROCESSORS
|
||||
cmake --install . --config Release --prefix ../mold-install
|
||||
cd ..
|
||||
New-Item -ItemType Directory -Force dist | Out-Null
|
||||
$version = $Env:TAG -replace '^v', ''
|
||||
Compress-Archive -Path mold-install\* -DestinationPath dist\mold-$version-x86_64-windows.zip
|
||||
|
||||
- uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: win-x86_64
|
||||
path: dist/mold-*.*
|
||||
|
||||
publish:
|
||||
needs: [build-linux, build-windows]
|
||||
runs-on: ubuntu-latest
|
||||
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,37 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e -x
|
||||
cd "$(dirname $0)"/../..
|
||||
|
||||
if [ "$GITHUB_REPOSITORY" = '' ]; then
|
||||
image=mold-msan
|
||||
image_build="podman build -t $image -"
|
||||
else
|
||||
# If this script is running on GitHub Actions, we want to cache
|
||||
# the created container image in GitHub's container repostiory.
|
||||
image=ghcr.io/$GITHUB_REPOSITORY/mold-msan
|
||||
image_build="podman build -t $image --output=type=registry --layers --cache-to $image --cache-from $image -"
|
||||
fi
|
||||
|
||||
cat <<EOF | $image_build
|
||||
FROM mirror.gcr.io/library/ubuntu:24.04
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends build-essential gcc g++ wget ca-certificates make cmake ninja-build python3 clang-20 llvm-20-dev libclang-20-dev libclang-rt-20-dev libtbb-dev mold && \
|
||||
rm -rf /var/lib/apt/lists
|
||||
RUN mkdir /llvm && \
|
||||
cd /llvm && \
|
||||
wget -O- --progress=dot:mega https://github.com/llvm/llvm-project/archive/refs/tags/llvmorg-21.1.1.tar.gz | tar xzf - --strip-components=1 && \
|
||||
cmake -S ./runtimes -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_RUNTIMES='libcxx;libcxxabi;libunwind' -DCMAKE_C_COMPILER=clang-20 -DCMAKE_CXX_COMPILER=clang++-20 -DLLVM_USE_SANITIZER=MemoryWithOrigins && \
|
||||
cmake --build build -j\$(nproc) && \
|
||||
cmake --install build --prefix /msan && \
|
||||
rm -rf /llvm
|
||||
EOF
|
||||
|
||||
# Build mold and run its tests
|
||||
podman run -it --rm --pull=never -v "$(pwd):/mold:ro" --security-opt seccomp=unconfined $image bash -c "
|
||||
set -e
|
||||
mkdir /build
|
||||
cd /build
|
||||
cmake -GNinja -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_COMPILER=clang-20 -DCMAKE_CXX_COMPILER=clang++-20 -DMOLD_USE_MSAN=ON -DMOLD_STDLIB_PREFIX=/msan -DMOLD_USE_MIMALLOC=0 -DMOLD_USE_SYSTEM_TBB=1 -DMOLD_USE_MOLD=1 /mold
|
||||
cmake --build . -j\$(nproc)
|
||||
setarch -R ctest --output-on-failure -j\$(nproc)
|
||||
"
|
||||
@@ -1,33 +0,0 @@
|
||||
name: Update manpage
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'docs/mold.md'
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
|
||||
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)
|
||||
+10
-10
@@ -1,11 +1,11 @@
|
||||
*.o
|
||||
*.so
|
||||
*~
|
||||
**/.DS_Store
|
||||
.cache
|
||||
core
|
||||
gentoo
|
||||
/mold-*.tar.gz
|
||||
/build*
|
||||
/mold
|
||||
/target
|
||||
/out
|
||||
/dist
|
||||
/ld
|
||||
/mold-wrapper.so
|
||||
/mold
|
||||
/bench/
|
||||
*~
|
||||
# Byproducts of tests that write into the working directory
|
||||
/exe
|
||||
/sde-cet-checker-out.txt
|
||||
-504
@@ -1,504 +0,0 @@
|
||||
# You can customize a build by specifying CMake options. An option may be
|
||||
# given in the -Dvariable=value form. For a boolean variable, `ON` or `1`
|
||||
# means true, while `OFF` or `0` means false.
|
||||
#
|
||||
# Here are a couple of common cmake options:
|
||||
#
|
||||
# -DCMAKE_C_COMPILER=<command-name>
|
||||
#
|
||||
# Specifies the C compiler name to use. The default value is `cc`.
|
||||
#
|
||||
# -DCMAKE_CXX_COMPILER=<command-name>
|
||||
#
|
||||
# Specifies the C++ compiler name to use. The default value is `c++`.
|
||||
#
|
||||
# -DCMAKE_INSTALL_PREFIX=<directory>
|
||||
#
|
||||
# Specifies the install target directory. The default value is `/usr/local`.
|
||||
#
|
||||
# -DCMAKE_BUILD_TYPE=[Debug | Release | RelWithDebInfo | MinSizeRel]
|
||||
#
|
||||
# Specifies the build type. The default is `Release`, which is the right
|
||||
# option unless you are debugging mold.
|
||||
#
|
||||
# An example of a cmake command line is shown below:
|
||||
#
|
||||
# $ cmake -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_CXX_COMPILER=g++-12 ..
|
||||
#
|
||||
# where `..` refers to this directory.
|
||||
#
|
||||
# With cmake, you may run `cmake --install .` instead of `make install` to
|
||||
# install build artifacts to system directories. If you want to install
|
||||
# artifacts to a temporary target directory, run `cmake --install . --prefix
|
||||
# <dir>`.
|
||||
#
|
||||
# You can see the current cmake variables and their values by running
|
||||
# `cmake -N -L .` in a build directory.
|
||||
#
|
||||
# Note that in this file, we provide various dials and knobs to configure
|
||||
# how to build mold. However, as a policy, we do not provide a way to
|
||||
# enable/disable any individual mold's feature. In other words, we do not
|
||||
# provide options like `--enable-foo` or `--disable-foo`. The motivation
|
||||
# behind this is build reproducibility. We want to guarantee that all builds
|
||||
# of the mold linker of the same version will have the exact same set of
|
||||
# features and behave exactly the same.
|
||||
|
||||
cmake_minimum_required(VERSION 3.14)
|
||||
project(mold VERSION 2.42.1)
|
||||
|
||||
include(CMakeDependentOption)
|
||||
include(CheckSymbolExists)
|
||||
include(GNUInstallDirs)
|
||||
|
||||
add_executable(mold)
|
||||
target_compile_features(mold PRIVATE cxx_std_20)
|
||||
|
||||
if(MINGW)
|
||||
target_link_libraries(mold PRIVATE dl)
|
||||
else()
|
||||
target_link_libraries(mold PRIVATE ${CMAKE_DL_LIBS})
|
||||
endif()
|
||||
|
||||
# Build mold itself using mold if -DMOLD_USE_MOLD=ON
|
||||
option(MOLD_USE_MOLD "Use mold to build mold" OFF)
|
||||
if(MOLD_USE_MOLD)
|
||||
target_link_options(mold PRIVATE -fuse-ld=mold)
|
||||
|
||||
if(CMAKE_BUILD_TYPE MATCHES "Deb")
|
||||
target_link_options(mold PRIVATE -Wl,--gdb-index)
|
||||
endif()
|
||||
|
||||
if(CMAKE_BUILD_TYPE MATCHES "^Rel")
|
||||
target_link_options(mold PRIVATE -Wl,--gc-sections,--icf=safe)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(NOT "${CMAKE_CXX_COMPILER_FRONTEND_VARIANT}" STREQUAL "MSVC")
|
||||
target_compile_options(mold PRIVATE
|
||||
-fno-exceptions
|
||||
-fno-unwind-tables
|
||||
-fno-asynchronous-unwind-tables
|
||||
-ffunction-sections
|
||||
-fdata-sections
|
||||
-Wall
|
||||
-Wextra
|
||||
-Wno-sign-compare
|
||||
-Wno-unused-function
|
||||
-Wno-unused-parameter
|
||||
-Wno-missing-field-initializers
|
||||
-ggnu-pubnames)
|
||||
endif()
|
||||
|
||||
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
target_compile_options(mold PRIVATE -D_GLIBCXX_ASSERTIONS)
|
||||
endif()
|
||||
|
||||
if(CMAKE_SYSTEM_NAME STREQUAL "OpenBSD")
|
||||
set(OPENBSD ON)
|
||||
endif()
|
||||
|
||||
# Build mold with -flto if -DMOLD_LTO=ON
|
||||
option(MOLD_LTO "Build mold with link-time optimization enabled")
|
||||
if(MOLD_LTO)
|
||||
set_property(TARGET mold PROPERTY INTERPROCEDURAL_OPTIMIZATION ON)
|
||||
endif()
|
||||
|
||||
# Enable AddressSanitizer if -DMOLD_USE_ASAN=ON
|
||||
option(MOLD_USE_ASAN "Build mold with AddressSanitizer" OFF)
|
||||
if(MOLD_USE_ASAN)
|
||||
target_compile_options(mold PRIVATE -fsanitize=address -fsanitize=undefined)
|
||||
target_link_options(mold PRIVATE -fsanitize=address -fsanitize=undefined)
|
||||
endif()
|
||||
|
||||
# Enabled ThreadSanitizer if -DMOLD_USE_TSAN=ON
|
||||
option(MOLD_USE_TSAN "Build mold with ThreadSanitizer" OFF)
|
||||
if(MOLD_USE_TSAN)
|
||||
target_compile_options(mold PRIVATE -fsanitize=thread)
|
||||
target_link_options(mold PRIVATE -fsanitize=thread)
|
||||
endif()
|
||||
|
||||
# Enable MemorySanitizer if -DMOLD_USE_MSAN=ON
|
||||
option(MOLD_USE_MSAN "Build mold with MemorySanitizer" OFF)
|
||||
if(MOLD_USE_MSAN)
|
||||
set(MOLD_STDLIB_PREFIX /usr CACHE FILEPATH "prefix of instrumented stdlib")
|
||||
target_compile_options(mold PRIVATE
|
||||
-g -Og -fsanitize=memory -fsanitize-memory-track-origins
|
||||
-nostdinc++ -isystem ${MOLD_STDLIB_PREFIX}/include/c++/v1
|
||||
-DENABLE_MSAN_UNPOISON
|
||||
)
|
||||
target_link_options(mold PRIVATE
|
||||
-fsanitize=memory
|
||||
-nostdlib++ -L ${MOLD_STDLIB_PREFIX}/lib -lc++
|
||||
-Wl,-rpath,${MOLD_STDLIB_PREFIX}/lib
|
||||
)
|
||||
endif()
|
||||
|
||||
# Statically-link libstdc++ if -DMOLD_MOSTLY_STATIC=ON.
|
||||
#
|
||||
# This option is intended to be used by `./dist.sh` script to create a
|
||||
# mold binary that works on various Linux distros. You probably don't
|
||||
# need nor want to set this to ON.
|
||||
option(MOLD_MOSTLY_STATIC "Statically link libstdc++ and some other libraries" OFF)
|
||||
if(MOLD_MOSTLY_STATIC)
|
||||
target_link_options(mold PRIVATE -static-libstdc++)
|
||||
endif()
|
||||
|
||||
# Find zlib. If libz.so is not found, we compile a bundled one and
|
||||
# statically-link it to mold.
|
||||
find_package(ZLIB QUIET)
|
||||
if(ZLIB_FOUND AND NOT MOLD_MOSTLY_STATIC)
|
||||
target_link_libraries(mold PRIVATE ZLIB::ZLIB)
|
||||
else()
|
||||
set(ZLIB_BUILD_SHARED OFF CACHE INTERNAL "")
|
||||
set(ZLIB_BUILD_TESTING OFF CACHE INTERNAL "")
|
||||
add_subdirectory(third-party/zlib EXCLUDE_FROM_ALL)
|
||||
target_link_libraries(mold PRIVATE zlibstatic)
|
||||
endif()
|
||||
|
||||
# Find BLAKE3 cryptographic hash library. Just like zlib, if libblkae3.so
|
||||
# is not found, we compile a bundled one and statically-link it to mold.
|
||||
find_package(BLAKE3 QUIET)
|
||||
if(BLAKE3_FOUND AND NOT MOLD_MOSTLY_STATIC)
|
||||
target_link_libraries(mold PRIVATE BLAKE3::blake3)
|
||||
else()
|
||||
function(mold_add_blake3)
|
||||
set(BUILD_SHARED_LIBS OFF)
|
||||
add_subdirectory(third-party/blake3/c EXCLUDE_FROM_ALL)
|
||||
target_link_libraries(mold PRIVATE blake3)
|
||||
target_include_directories(mold PUBLIC third-party/blake3/c)
|
||||
endfunction()
|
||||
|
||||
mold_add_blake3()
|
||||
endif()
|
||||
|
||||
# Find zstd compression library. If zstd.h is not found, we compile a
|
||||
# bundled one and statically-link it to mold.
|
||||
include(CheckIncludeFile)
|
||||
check_include_file(zstd.h HAVE_ZSTD_H)
|
||||
|
||||
if(HAVE_ZSTD_H AND NOT MOLD_MOSTLY_STATIC)
|
||||
target_link_libraries(mold PRIVATE zstd)
|
||||
else()
|
||||
set(ZSTD_BUILD_PROGRAMS OFF)
|
||||
set(ZSTD_BUILD_CONTRIB OFF)
|
||||
set(ZSTD_BUILD_TESTS OFF)
|
||||
set(ZSTD_MULTITHREAD_SUPPORT OFF)
|
||||
set(ZSTD_BUILD_SHARED OFF)
|
||||
set(ZSTD_BUILD_STATIC ON)
|
||||
add_subdirectory(third-party/zstd/build/cmake EXCLUDE_FROM_ALL)
|
||||
target_include_directories(mold PUBLIC third-party/zstd/lib)
|
||||
target_link_libraries(mold PRIVATE libzstd_static)
|
||||
endif()
|
||||
|
||||
# Find mimalloc. mimalloc is an alternative malloc implementation
|
||||
# optimized for multi-threaded applications.
|
||||
#
|
||||
# If you want to use the usual libc's malloc, pass -DMOLD_USE_MIMALLOC=OFF.
|
||||
#
|
||||
# We enable mimalloc by default for 64-bit targets. It doesn't seem to
|
||||
# be stable on 32-bit targets.
|
||||
cmake_dependent_option(
|
||||
MOLD_USE_MIMALLOC "Use mimalloc" ON
|
||||
"CMAKE_SIZEOF_VOID_P EQUAL 8; NOT APPLE; NOT ANDROID; NOT OPENBSD; NOT MOLD_USE_ASAN; NOT MOLD_USE_TSAN" OFF)
|
||||
|
||||
cmake_dependent_option(
|
||||
MOLD_USE_SYSTEM_MIMALLOC "Use system or vendored mimalloc" OFF
|
||||
MOLD_USE_MIMALLOC OFF)
|
||||
|
||||
# By default, we build a bundled mimalloc and statically-link it to
|
||||
# mold. If you want to dynamically link to the system's
|
||||
# libmimalloc.so, pass -DMOLD_USE_SYSTEM_MIMALLOC=ON.
|
||||
#
|
||||
# We require mimalloc v3 or newer. mimalloc v2 doesn't use transparent
|
||||
# huge pages for the allocation path that serves mold's largest data
|
||||
# structures, which makes linking large programs significantly slower.
|
||||
if(MOLD_USE_MIMALLOC)
|
||||
if(MOLD_USE_SYSTEM_MIMALLOC)
|
||||
find_package(mimalloc 3 REQUIRED)
|
||||
target_link_libraries(mold PRIVATE mimalloc)
|
||||
else()
|
||||
function(mold_add_mimalloc)
|
||||
set(MI_BUILD_STATIC ON CACHE INTERNAL "")
|
||||
set(MI_BUILD_TESTS OFF CACHE INTERNAL "")
|
||||
set(MI_NO_OPT_ARCH ON CACHE INTERNAL "")
|
||||
add_subdirectory(third-party/mimalloc EXCLUDE_FROM_ALL)
|
||||
target_compile_definitions(mimalloc-static PRIVATE MI_USE_ENVIRON=0)
|
||||
target_link_libraries(mold PRIVATE mimalloc-static)
|
||||
endfunction()
|
||||
|
||||
mold_add_mimalloc()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Find TBB. TBB (OneTBB or Intel TBB) is a high-level threading library.
|
||||
# Use of this library is mandatory.
|
||||
#
|
||||
# By default, we build a bundled one and statically-link the library
|
||||
# to mold. If you want to link to the system's libtbb2.so, pass
|
||||
# -DMOLD_USE_SYSTEM_TBB=ON.
|
||||
option(MOLD_USE_SYSTEM_TBB "Use system or vendored TBB" OFF)
|
||||
if(MOLD_USE_SYSTEM_TBB OR BLAKE3_USE_TBB)
|
||||
find_package(TBB REQUIRED)
|
||||
target_link_libraries(mold PRIVATE TBB::tbb)
|
||||
else()
|
||||
function(mold_add_tbb)
|
||||
set(BUILD_SHARED_LIBS OFF)
|
||||
set(TBB_TEST OFF CACHE INTERNAL "")
|
||||
set(TBB_STRICT OFF CACHE INTERNAL "")
|
||||
add_subdirectory(third-party/tbb EXCLUDE_FROM_ALL)
|
||||
target_compile_definitions(tbb PRIVATE __TBB_DYNAMIC_LOAD_ENABLED=0)
|
||||
target_link_libraries(mold PRIVATE TBB::tbb)
|
||||
endfunction()
|
||||
|
||||
mold_add_tbb()
|
||||
endif()
|
||||
|
||||
# We always use Clang to build mold on Windows. MSVC can't compile mold.
|
||||
if(WIN32)
|
||||
if(MSVC AND NOT CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
|
||||
message(FATAL_ERROR
|
||||
"Your compiler is not supported; install Clang from Visual Studio Installer and re-run cmake with '-T clangcl'")
|
||||
endif()
|
||||
|
||||
target_compile_definitions(mold PRIVATE NOGDI NOMINMAX)
|
||||
if(MINGW)
|
||||
target_compile_definitions(mold PRIVATE _WIN32_WINNT=0xA00)
|
||||
target_link_libraries(mold PRIVATE bcrypt)
|
||||
endif()
|
||||
else()
|
||||
include(CheckLibraryExists)
|
||||
check_library_exists(m pow "" LIBM_FOUND)
|
||||
if(LIBM_FOUND)
|
||||
target_link_libraries(mold PRIVATE m)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Build mold-wrapper.so
|
||||
if(NOT APPLE AND NOT WIN32)
|
||||
add_library(mold-wrapper SHARED)
|
||||
install(TARGETS mold-wrapper DESTINATION ${CMAKE_INSTALL_LIBDIR}/mold)
|
||||
|
||||
# Remove the default `lib` prefix
|
||||
set_target_properties(mold-wrapper PROPERTIES PREFIX "")
|
||||
target_link_libraries(mold-wrapper PRIVATE ${CMAKE_DL_LIBS})
|
||||
target_sources(mold-wrapper PRIVATE src/mold-wrapper.c)
|
||||
endif()
|
||||
|
||||
# If atomics doesn't work by default, add -latomic.
|
||||
# We need the flag on riscv, armv6 and m68k.
|
||||
include(CheckCXXSourceCompiles)
|
||||
check_cxx_source_compiles("#include <atomic>
|
||||
int main() {
|
||||
std::atomic_uint8_t a;
|
||||
std::atomic_uint16_t b;
|
||||
std::atomic_uint32_t c;
|
||||
std::atomic_uint64_t d;
|
||||
return ++a + ++b + ++c + ++d;
|
||||
}" HAVE_FULL_ATOMIC_SUPPORT)
|
||||
|
||||
if(NOT HAVE_FULL_ATOMIC_SUPPORT)
|
||||
target_link_libraries(mold PRIVATE atomic)
|
||||
endif()
|
||||
|
||||
# Add -pthread
|
||||
if(NOT APPLE AND NOT MSVC)
|
||||
target_compile_options(mold PRIVATE -pthread)
|
||||
target_link_options(mold PRIVATE -pthread)
|
||||
endif()
|
||||
|
||||
check_symbol_exists(madvise sys/mman.h HAVE_MADVISE)
|
||||
check_symbol_exists(uname sys/utsname.h HAVE_UNAME)
|
||||
|
||||
# glibc declares fallocate only under _GNU_SOURCE. C++ compilers
|
||||
# predefine it on Linux, but this check runs the C compiler, which
|
||||
# does not.
|
||||
set(CMAKE_REQUIRED_DEFINITIONS -D_GNU_SOURCE)
|
||||
check_symbol_exists(fallocate fcntl.h HAVE_FALLOCATE)
|
||||
unset(CMAKE_REQUIRED_DEFINITIONS)
|
||||
|
||||
# Create a .cc file containing the current git hash for `mold --version`.
|
||||
add_custom_target(git_hash
|
||||
COMMAND ${CMAKE_COMMAND}
|
||||
-DSOURCE_DIR=${CMAKE_SOURCE_DIR}
|
||||
-DOUTPUT_FILE=${CMAKE_BINARY_DIR}/mold-git-hash.h
|
||||
-P ${CMAKE_SOURCE_DIR}/lib/update-git-hash.cmake
|
||||
DEPENDS lib/update-git-hash.cmake
|
||||
BYPRODUCTS mold-git-hash.h
|
||||
VERBATIM)
|
||||
|
||||
add_dependencies(mold git_hash)
|
||||
|
||||
# Almost all functions are template in mold which take a target type
|
||||
# (e.g. X86_64) as its type parameter. Since we suport more than 10
|
||||
# targets, compiling a single source file for all the targets is very
|
||||
# slow.
|
||||
#
|
||||
# As a workaround, we create a .cc file for each target and spawn many
|
||||
# compiler instances. This is hacky but greatly reduces compile time
|
||||
# on a multicore machine.
|
||||
#
|
||||
# You can build mold for a specific set of targets by passing, e.g.,
|
||||
# -DMOLD_TARGETS='X86_64;ARM64LE', though this is strongly discouraged
|
||||
# for build reproducibility. Use this option only if you build mold
|
||||
# frequently for your personal use; otherwise, always build mold with all
|
||||
# targets enabled. We provide this flag in the trust that you will not
|
||||
# abuse it, so please don’t betray that trust.
|
||||
set(MOLD_TARGETS
|
||||
X86_64 I386 ARM64LE ARM64BE ARM32LE ARM32BE RV32LE RV32BE RV64LE
|
||||
RV64BE PPC32 PPC64V1 PPC64V2 S390X SPARC64 M68K SH4LE SH4BE
|
||||
LOONGARCH32 LOONGARCH64
|
||||
CACHE STRING "List of supported targets")
|
||||
|
||||
list(GET MOLD_TARGETS 0 MOLD_FIRST_TARGET)
|
||||
|
||||
list(APPEND MOLD_TEMPLATE_FILES
|
||||
src/arch-arm32.cc
|
||||
src/arch-arm64.cc
|
||||
src/arch-i386.cc
|
||||
src/arch-loongarch.cc
|
||||
src/arch-m68k.cc
|
||||
src/arch-ppc32.cc
|
||||
src/arch-ppc64v1.cc
|
||||
src/arch-ppc64v2.cc
|
||||
src/arch-riscv.cc
|
||||
src/arch-s390x.cc
|
||||
src/arch-sh4.cc
|
||||
src/arch-sparc64.cc
|
||||
src/arch-x86-64.cc
|
||||
src/archive-file.cc
|
||||
src/cmdline.cc
|
||||
src/error.cc
|
||||
src/filetype.cc
|
||||
src/gc-sections.cc
|
||||
src/gdb-index.cc
|
||||
src/icf.cc
|
||||
src/input-files.cc
|
||||
src/input-sections.cc
|
||||
src/linker-script.cc
|
||||
src/main.cc
|
||||
src/mapfile.cc
|
||||
src/output-chunks.cc
|
||||
src/passes.cc
|
||||
src/relocatable.cc
|
||||
src/shrink-sections.cc
|
||||
src/thunks.cc
|
||||
src/tls.cc
|
||||
)
|
||||
|
||||
if(WIN32 AND NOT MINGW)
|
||||
list(APPEND MOLD_TEMPLATE_FILES src/lto-win32.cc)
|
||||
else()
|
||||
list(APPEND MOLD_TEMPLATE_FILES src/lto-unix.cc)
|
||||
endif()
|
||||
|
||||
if(WIN32)
|
||||
list(APPEND MOLD_TEMPLATE_FILES
|
||||
src/output-file-win32.cc
|
||||
src/subprocess-win32.cc
|
||||
)
|
||||
else()
|
||||
list(APPEND MOLD_TEMPLATE_FILES
|
||||
src/output-file-unix.cc
|
||||
src/subprocess-unix.cc
|
||||
)
|
||||
endif()
|
||||
|
||||
function(mold_instantiate_templates SOURCE TARGET)
|
||||
set(PATH ${CMAKE_BINARY_DIR}/${SOURCE}.${TARGET}.cc)
|
||||
if(NOT EXISTS ${PATH})
|
||||
file(WRITE ${PATH} "#define MOLD_${TARGET} 1
|
||||
#define MOLD_TARGET ${TARGET}
|
||||
#include \"${CMAKE_SOURCE_DIR}/${SOURCE}\"
|
||||
")
|
||||
endif()
|
||||
target_sources(mold PRIVATE ${PATH})
|
||||
endfunction()
|
||||
|
||||
foreach (SOURCE IN LISTS MOLD_TEMPLATE_FILES)
|
||||
foreach(TARGET IN LISTS MOLD_TARGETS)
|
||||
mold_instantiate_templates(${SOURCE} ${TARGET})
|
||||
set(HAVE_TARGET_${TARGET} 1)
|
||||
endforeach()
|
||||
endforeach()
|
||||
|
||||
# Add other non-template source files.
|
||||
target_sources(mold PRIVATE
|
||||
lib/aho-corasick.cc
|
||||
lib/arena.cc
|
||||
lib/compress.cc
|
||||
lib/crc32.cc
|
||||
lib/demangle.cc
|
||||
lib/filepath.cc
|
||||
lib/glob.cc
|
||||
lib/hyperloglog.cc
|
||||
lib/perf.cc
|
||||
lib/random.cc
|
||||
lib/tar.cc
|
||||
src/elf.cc
|
||||
src/entry.cc
|
||||
third-party/rust-demangle/rust-demangle.c
|
||||
)
|
||||
|
||||
if(WIN32)
|
||||
target_sources(mold PRIVATE
|
||||
src/jobs-win32.cc
|
||||
src/mapped-file-win32.cc
|
||||
src/signal-win32.cc
|
||||
)
|
||||
else()
|
||||
target_sources(mold PRIVATE
|
||||
src/jobs-unix.cc
|
||||
src/mapped-file-unix.cc
|
||||
src/signal-unix.cc
|
||||
)
|
||||
endif()
|
||||
|
||||
# Create config.h file
|
||||
configure_file(lib/config.h.in config.h)
|
||||
include_directories(${CMAKE_CURRENT_BINARY_DIR})
|
||||
|
||||
# Test configs
|
||||
include(CTest)
|
||||
|
||||
if(BUILD_TESTING)
|
||||
# Create the ld symlinks required for testing
|
||||
if(NOT WIN32)
|
||||
add_custom_command(
|
||||
TARGET mold POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E create_symlink mold ld
|
||||
BYPRODUCTS ld
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||
VERBATIM)
|
||||
endif()
|
||||
|
||||
if(${UNIX})
|
||||
add_subdirectory(test)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(NOT CMAKE_SKIP_INSTALL_RULES)
|
||||
install(TARGETS mold RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
install(FILES docs/mold.1 DESTINATION ${CMAKE_INSTALL_MANDIR}/man1/)
|
||||
install(FILES LICENSE DESTINATION ${CMAKE_INSTALL_DOCDIR})
|
||||
|
||||
function(mold_install_relative_symlink OLD NEW)
|
||||
install(CODE "
|
||||
get_filename_component(PREFIX_ABS \${CMAKE_INSTALL_PREFIX}/ ABSOLUTE)
|
||||
get_filename_component(OLD_ABS ${OLD} ABSOLUTE BASE_DIR \${PREFIX_ABS})
|
||||
get_filename_component(NEW_ABS ${NEW} ABSOLUTE BASE_DIR \${PREFIX_ABS})
|
||||
get_filename_component(NEW_DIR \${NEW_ABS} DIRECTORY)
|
||||
file(RELATIVE_PATH OLD_REL \${NEW_DIR} \${OLD_ABS})
|
||||
message(STATUS \"Installing symlink: \$ENV{DESTDIR}\${NEW_ABS} -> \${OLD_REL}\")
|
||||
file(MAKE_DIRECTORY \$ENV{DESTDIR}\${NEW_DIR})
|
||||
file(CREATE_LINK \${OLD_REL} \$ENV{DESTDIR}\${NEW_ABS} SYMBOLIC)")
|
||||
endfunction()
|
||||
|
||||
if(NOT WIN32)
|
||||
mold_install_relative_symlink(${CMAKE_INSTALL_BINDIR}/mold${CMAKE_EXECUTABLE_SUFFIX}
|
||||
${CMAKE_INSTALL_LIBEXECDIR}/mold/ld${CMAKE_EXECUTABLE_SUFFIX})
|
||||
mold_install_relative_symlink(${CMAKE_INSTALL_BINDIR}/mold${CMAKE_EXECUTABLE_SUFFIX}
|
||||
${CMAKE_INSTALL_BINDIR}/ld.mold${CMAKE_EXECUTABLE_SUFFIX})
|
||||
mold_install_relative_symlink(${CMAKE_INSTALL_MANDIR}/man1/mold.1
|
||||
${CMAKE_INSTALL_MANDIR}/man1/ld.mold.1)
|
||||
endif()
|
||||
endif()
|
||||
Generated
+580
@@ -0,0 +1,580 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "allocator-api2"
|
||||
version = "0.2.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
|
||||
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
version = "0.7.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56"
|
||||
|
||||
[[package]]
|
||||
name = "blake3"
|
||||
version = "1.8.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d9e454fc11f76977dc803893aff6304ed33d6a26efae8696573bea74baa27ae"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"constant_time_eq",
|
||||
"cpufeatures",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bstr"
|
||||
version = "1.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6bb31b46c14244e20ee9984b11bf5c992b91fb6939fea616e3512c8baecdbe5f"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ad534f4357a5264cce5019c989cf66a4f0dc4e0d1b1d15f8aacec0ff7360273"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"jobserver",
|
||||
"libc",
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||
|
||||
[[package]]
|
||||
name = "constant_time_eq"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b"
|
||||
|
||||
[[package]]
|
||||
name = "cpp_demangle"
|
||||
version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2bb79cb74d735044c972aae58ed0aaa9a837e85b01106a54c39e42e97f62253"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8498c871161e1742aaa9d52551b2d6ebdd4c3d45a3be423e3728f33b955be550"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-deque"
|
||||
version = "0.8.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5181e0de7b61eb03a81e347d6dd8797bae9da5146707b51077e2d71a54ec0ceb"
|
||||
dependencies = [
|
||||
"crossbeam-epoch",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-epoch"
|
||||
version = "0.9.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17"
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34"
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||
|
||||
[[package]]
|
||||
name = "find-msvc-tools"
|
||||
version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890"
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e634e2e0ebac1ee034020da1ca582e17ffe4e0f5e985823721e168928136dcb"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"libz-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "foldhash"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"r-efi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.17.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
|
||||
dependencies = [
|
||||
"allocator-api2",
|
||||
"equivalent",
|
||||
"foldhash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jobserver"
|
||||
version = "0.1.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.189"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
|
||||
|
||||
[[package]]
|
||||
name = "libmimalloc-sys"
|
||||
version = "0.1.49"
|
||||
source = "git+https://github.com/rui314/mimalloc_rust?rev=cc58b72775b8b5bc89ee37752af61fcb4f7c2909#cc58b72775b8b5bc89ee37752af61fcb4f7c2909"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libz-sys"
|
||||
version = "1.1.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85bc9657773828b90eeb625adff10eeac83cc21bbfd8e23a03eaa8a33c9e28d9"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
|
||||
|
||||
[[package]]
|
||||
name = "memmap2"
|
||||
version = "0.9.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d1219ed1b7f229ee7104d281dd01d6802fe28bb6e95d292942c4daacdeb798c0"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mimalloc"
|
||||
version = "0.1.52"
|
||||
source = "git+https://github.com/rui314/mimalloc_rust?rev=cc58b72775b8b5bc89ee37752af61fcb4f7c2909#cc58b72775b8b5bc89ee37752af61fcb4f7c2909"
|
||||
dependencies = [
|
||||
"libmimalloc-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold"
|
||||
version = "2.42.1"
|
||||
dependencies = [
|
||||
"blake3",
|
||||
"bstr",
|
||||
"cc",
|
||||
"cpp_demangle",
|
||||
"crc32fast",
|
||||
"flate2",
|
||||
"getrandom",
|
||||
"hashbrown",
|
||||
"libc",
|
||||
"libz-sys",
|
||||
"memchr",
|
||||
"memmap2",
|
||||
"rayon",
|
||||
"rustc-demangle",
|
||||
"rustc-hash",
|
||||
"xxhash-rust",
|
||||
"zstd",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-cli"
|
||||
version = "2.42.1"
|
||||
dependencies = [
|
||||
"mimalloc",
|
||||
"mold",
|
||||
"mold-target-arm32",
|
||||
"mold-target-arm32be",
|
||||
"mold-target-arm64",
|
||||
"mold-target-arm64be",
|
||||
"mold-target-i386",
|
||||
"mold-target-loongarch32",
|
||||
"mold-target-loongarch64",
|
||||
"mold-target-m68k",
|
||||
"mold-target-ppc32",
|
||||
"mold-target-ppc64v1",
|
||||
"mold-target-ppc64v2",
|
||||
"mold-target-riscv32",
|
||||
"mold-target-riscv32be",
|
||||
"mold-target-riscv64",
|
||||
"mold-target-riscv64be",
|
||||
"mold-target-s390x",
|
||||
"mold-target-sh4",
|
||||
"mold-target-sh4be",
|
||||
"mold-target-sparc64",
|
||||
"mold-target-x86_64",
|
||||
"mold-tests",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-target-arm32"
|
||||
version = "2.42.1"
|
||||
dependencies = [
|
||||
"mold",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-target-arm32be"
|
||||
version = "2.42.1"
|
||||
dependencies = [
|
||||
"mold",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-target-arm64"
|
||||
version = "2.42.1"
|
||||
dependencies = [
|
||||
"mold",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-target-arm64be"
|
||||
version = "2.42.1"
|
||||
dependencies = [
|
||||
"mold",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-target-i386"
|
||||
version = "2.42.1"
|
||||
dependencies = [
|
||||
"mold",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-target-loongarch32"
|
||||
version = "2.42.1"
|
||||
dependencies = [
|
||||
"mold",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-target-loongarch64"
|
||||
version = "2.42.1"
|
||||
dependencies = [
|
||||
"mold",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-target-m68k"
|
||||
version = "2.42.1"
|
||||
dependencies = [
|
||||
"mold",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-target-ppc32"
|
||||
version = "2.42.1"
|
||||
dependencies = [
|
||||
"mold",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-target-ppc64v1"
|
||||
version = "2.42.1"
|
||||
dependencies = [
|
||||
"mold",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-target-ppc64v2"
|
||||
version = "2.42.1"
|
||||
dependencies = [
|
||||
"mold",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-target-riscv32"
|
||||
version = "2.42.1"
|
||||
dependencies = [
|
||||
"mold",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-target-riscv32be"
|
||||
version = "2.42.1"
|
||||
dependencies = [
|
||||
"mold",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-target-riscv64"
|
||||
version = "2.42.1"
|
||||
dependencies = [
|
||||
"mold",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-target-riscv64be"
|
||||
version = "2.42.1"
|
||||
dependencies = [
|
||||
"mold",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-target-s390x"
|
||||
version = "2.42.1"
|
||||
dependencies = [
|
||||
"mold",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-target-sh4"
|
||||
version = "2.42.1"
|
||||
dependencies = [
|
||||
"mold",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-target-sh4be"
|
||||
version = "2.42.1"
|
||||
dependencies = [
|
||||
"mold",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-target-sparc64"
|
||||
version = "2.42.1"
|
||||
dependencies = [
|
||||
"mold",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-target-x86_64"
|
||||
version = "2.42.1"
|
||||
dependencies = [
|
||||
"mold",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-tests"
|
||||
version = "2.42.1"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548"
|
||||
|
||||
[[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 = "rustc-hash"
|
||||
version = "2.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d"
|
||||
|
||||
[[package]]
|
||||
name = "serde_core"
|
||||
version = "1.0.229"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.229"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "3.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6275cddf4610d1775e6d1fe9469b2e77d0f39fd98fb7450901b821e0c53649f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
||||
|
||||
[[package]]
|
||||
name = "vcpkg"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
||||
|
||||
[[package]]
|
||||
name = "xxhash-rust"
|
||||
version = "0.8.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aee1b19627c7c60102ab80d3a9cbe18de90bfe03bfa6c3715447681f0e8c8af6"
|
||||
|
||||
[[package]]
|
||||
name = "zstd"
|
||||
version = "0.13.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a"
|
||||
dependencies = [
|
||||
"zstd-safe",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zstd-safe"
|
||||
version = "7.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d"
|
||||
dependencies = [
|
||||
"zstd-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zstd-sys"
|
||||
version = "2.0.16+zstd.1.5.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"pkg-config",
|
||||
]
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
[package]
|
||||
name = "mold"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
description = "A high-performance ELF linker, rewritten in Rust"
|
||||
license.workspace = true
|
||||
# tests/lib.rs belongs to the mold-tests workspace crate.
|
||||
autotests = false
|
||||
|
||||
# The linker is generic over the target. Instantiating it for all twenty
|
||||
# targets in one crate keeps the compiler on a single core for minutes, so
|
||||
# each target is instantiated in a crate of its own under arch/, and the
|
||||
# executable in cli/ dispatches to them.
|
||||
[workspace]
|
||||
members = ["cli", "arch/*", "tests"]
|
||||
default-members = [".", "cli", "arch/*", "tests"]
|
||||
|
||||
[workspace.package]
|
||||
version = "2.42.1"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
|
||||
[workspace.dependencies]
|
||||
libc = "0.2"
|
||||
mold = { path = "." }
|
||||
|
||||
[dependencies]
|
||||
blake3 = "1.8.7"
|
||||
bstr = "1"
|
||||
cpp_demangle = "0.4"
|
||||
crc32fast = "1"
|
||||
flate2 = { version = "1", default-features = false, features = ["zlib"] }
|
||||
getrandom = "0.4.3"
|
||||
hashbrown = "0.17.1"
|
||||
libc.workspace = true
|
||||
libz-sys = "1.1.29"
|
||||
memchr = "2.8.3"
|
||||
memmap2 = "0.9"
|
||||
rayon = "1"
|
||||
rustc-demangle = "0.1"
|
||||
rustc-hash = "2"
|
||||
xxhash-rust = { version = "0.8", features = ["xxh3"] }
|
||||
zstd = "0.13"
|
||||
|
||||
[build-dependencies]
|
||||
cc = "1"
|
||||
|
||||
[profile.release]
|
||||
debug = 1
|
||||
codegen-units = 16
|
||||
panic = "abort"
|
||||
|
||||
[profile.dev]
|
||||
opt-level = 1
|
||||
@@ -131,42 +131,28 @@ and LoongArch are also attached to each
|
||||
|
||||
## How to Build
|
||||
|
||||
mold is written in C++20, so if you build mold yourself, you will need a
|
||||
recent version of a C++ compiler and a C++ standard library. We recommend GCC
|
||||
10.2 or Clang 16.0.0 (or later) and libstdc++ 10 or libc++ 7 (or later).
|
||||
|
||||
### Install Dependencies
|
||||
|
||||
To install build dependencies, run `./install-build-deps.sh` in this
|
||||
directory. It will detect your Linux distribution and attempt to install the
|
||||
necessary packages.
|
||||
mold is written in Rust and built with Cargo. Install the stable Rust toolchain
|
||||
with [rustup](https://rustup.rs/). You also need Git and a C compiler such as
|
||||
GCC or Clang, which you can install with your system's package manager.
|
||||
|
||||
### Compile mold
|
||||
|
||||
```shell
|
||||
git clone --branch stable https://github.com/rui314/mold.git
|
||||
cd mold
|
||||
sudo ./install-build-deps.sh
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=c++ -B build
|
||||
cmake --build build -j$(nproc)
|
||||
sudo cmake --build build --target install
|
||||
cargo build --release
|
||||
```
|
||||
|
||||
You might need to pass a C++20 compiler command name to `cmake`. In the
|
||||
example above, `c++` is passed. If that doesn't work for you, try a specific
|
||||
version of a compiler, such as `g++-10` or `clang++-12`.
|
||||
The executable and its companion preload library are
|
||||
`target/release/mold` and `target/release/mold-wrapper.so`.
|
||||
|
||||
By default, `mold` is installed to `/usr/local/bin`. You can change the
|
||||
installation location by passing `-DCMAKE_INSTALL_PREFIX=<directory>`.
|
||||
For other cmake options, see the comments in `CMakeLists.txt`.
|
||||
### Install mold
|
||||
|
||||
If you are not using a recent enough Linux distribution, or if `cmake` does
|
||||
not work for you for any reason, you can use Podman to build mold in a
|
||||
container. To do so, run `./dist.sh` in this directory instead of using
|
||||
`cmake`. The shell script will pull a container image, build mold and auxiliary
|
||||
files inside it, and package them into a single tar file named
|
||||
`dist/mold-$version-$arch-linux.tar.gz`. You can extract the tar file anywhere
|
||||
and use the mold executable in it.
|
||||
Run `sudo ./install-mold.sh` to install mold under `/usr/local`. To install it
|
||||
under a different prefix, set `PREFIX`, as in
|
||||
`sudo PREFIX=/usr ./install-mold.sh`.
|
||||
|
||||
You can also run `target/release/mold` directly without installing it.
|
||||
|
||||
## How to use
|
||||
|
||||
@@ -188,11 +174,10 @@ to use mold instead of `/usr/bin/ld`:
|
||||
valid argument, so you need to use the `-B` option instead. The `-B` option
|
||||
tells GCC where to look for external commands like `ld`.
|
||||
|
||||
If you have installed mold with `make install`, there should be a directory
|
||||
named `/usr/libexec/mold` (or `/usr/local/libexec/mold`, depending on your
|
||||
`$PREFIX`), and the `ld` command should be there. The `ld` is actually a
|
||||
symlink to `mold`. So, all you need is to pass `-B/usr/libexec/mold` (or
|
||||
`-B/usr/local/libexec/mold`) to GCC.
|
||||
If you installed mold using the script above, there is a directory named
|
||||
`/usr/local/libexec/mold`, and the `ld` command there is a symlink to mold.
|
||||
Pass `-B/usr/local/libexec/mold` to GCC. If you installed under another
|
||||
prefix, use that prefix instead.
|
||||
|
||||
If you haven't installed `ld.mold` to any `$PATH`, you can still pass
|
||||
`-fuse-ld=/absolute/path/to/mold` to clang to use mold. However, GCC does not
|
||||
@@ -266,7 +251,7 @@ mold -run make <make-options-if-any>
|
||||
Internally, mold invokes a given command with the `LD_PRELOAD` environment
|
||||
variable set to its companion shared object file. The shared object file
|
||||
intercepts all function calls to `exec(3)`-family functions to replace
|
||||
`argv[0]` with `mold` if it is `ld`, `ld.bf`, `ld.gold`, or `ld.lld`.
|
||||
`argv[0]` with `mold` if it is `ld`, `ld.bfd`, `ld.gold`, or `ld.lld`.
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
# Architecture crates
|
||||
|
||||
Each subdirectory is a small crate that instantiates the generic linker for
|
||||
one target architecture. Separate crates let Cargo compile the architectures
|
||||
in parallel. Instantiating all targets in one crate leaves a long serial
|
||||
compilation step.
|
||||
|
||||
The shared linker implementation lives in [`src/`](../src/), with
|
||||
architecture-specific code in [`src/arch/`](../src/arch/). These crates provide
|
||||
entry points that the [`cli/`](../cli/) executable selects for the input
|
||||
files' target.
|
||||
@@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "mold-target-arm32"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
description = "The mold linker instantiated for arm32"
|
||||
license.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
mold.workspace = true
|
||||
@@ -0,0 +1,12 @@
|
||||
//! The linker instantiated for arm32. Every target has a crate like this
|
||||
//! one, so that the compiler can build the targets in parallel.
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::ffi::OsStr;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Links for this target, or reports the target the inputs are actually
|
||||
/// for.
|
||||
pub fn link(cmdline: Arc<[Cow<'static, OsStr>]>) -> Result<i32, &'static str> {
|
||||
mold::driver::link::<mold::arch::Arm32>(cmdline)
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "mold-target-arm32be"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
description = "The mold linker instantiated for arm32be"
|
||||
license.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
mold.workspace = true
|
||||
@@ -0,0 +1,12 @@
|
||||
//! The linker instantiated for arm32be. Every target has a crate like this
|
||||
//! one, so that the compiler can build the targets in parallel.
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::ffi::OsStr;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Links for this target, or reports the target the inputs are actually
|
||||
/// for.
|
||||
pub fn link(cmdline: Arc<[Cow<'static, OsStr>]>) -> Result<i32, &'static str> {
|
||||
mold::driver::link::<mold::arch::Arm32Be>(cmdline)
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "mold-target-arm64"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
description = "The mold linker instantiated for arm64"
|
||||
license.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
mold.workspace = true
|
||||
@@ -0,0 +1,12 @@
|
||||
//! The linker instantiated for arm64. Every target has a crate like this
|
||||
//! one, so that the compiler can build the targets in parallel.
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::ffi::OsStr;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Links for this target, or reports the target the inputs are actually
|
||||
/// for.
|
||||
pub fn link(cmdline: Arc<[Cow<'static, OsStr>]>) -> Result<i32, &'static str> {
|
||||
mold::driver::link::<mold::arch::Arm64>(cmdline)
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "mold-target-arm64be"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
description = "The mold linker instantiated for arm64be"
|
||||
license.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
mold.workspace = true
|
||||
@@ -0,0 +1,12 @@
|
||||
//! The linker instantiated for arm64be. Every target has a crate like this
|
||||
//! one, so that the compiler can build the targets in parallel.
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::ffi::OsStr;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Links for this target, or reports the target the inputs are actually
|
||||
/// for.
|
||||
pub fn link(cmdline: Arc<[Cow<'static, OsStr>]>) -> Result<i32, &'static str> {
|
||||
mold::driver::link::<mold::arch::Arm64Be>(cmdline)
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "mold-target-i386"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
description = "The mold linker instantiated for i386"
|
||||
license.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
mold.workspace = true
|
||||
@@ -0,0 +1,12 @@
|
||||
//! The linker instantiated for i386. Every target has a crate like this
|
||||
//! one, so that the compiler can build the targets in parallel.
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::ffi::OsStr;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Links for this target, or reports the target the inputs are actually
|
||||
/// for.
|
||||
pub fn link(cmdline: Arc<[Cow<'static, OsStr>]>) -> Result<i32, &'static str> {
|
||||
mold::driver::link::<mold::arch::I386>(cmdline)
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "mold-target-loongarch32"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
description = "The mold linker instantiated for loongarch32"
|
||||
license.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
mold.workspace = true
|
||||
@@ -0,0 +1,12 @@
|
||||
//! The linker instantiated for loongarch32. Every target has a crate like this
|
||||
//! one, so that the compiler can build the targets in parallel.
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::ffi::OsStr;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Links for this target, or reports the target the inputs are actually
|
||||
/// for.
|
||||
pub fn link(cmdline: Arc<[Cow<'static, OsStr>]>) -> Result<i32, &'static str> {
|
||||
mold::driver::link::<mold::arch::LoongArch32>(cmdline)
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "mold-target-loongarch64"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
description = "The mold linker instantiated for loongarch64"
|
||||
license.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
mold.workspace = true
|
||||
@@ -0,0 +1,12 @@
|
||||
//! The linker instantiated for loongarch64. Every target has a crate like this
|
||||
//! one, so that the compiler can build the targets in parallel.
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::ffi::OsStr;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Links for this target, or reports the target the inputs are actually
|
||||
/// for.
|
||||
pub fn link(cmdline: Arc<[Cow<'static, OsStr>]>) -> Result<i32, &'static str> {
|
||||
mold::driver::link::<mold::arch::LoongArch64>(cmdline)
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "mold-target-m68k"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
description = "The mold linker instantiated for m68k"
|
||||
license.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
mold.workspace = true
|
||||
@@ -0,0 +1,12 @@
|
||||
//! The linker instantiated for m68k. Every target has a crate like this
|
||||
//! one, so that the compiler can build the targets in parallel.
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::ffi::OsStr;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Links for this target, or reports the target the inputs are actually
|
||||
/// for.
|
||||
pub fn link(cmdline: Arc<[Cow<'static, OsStr>]>) -> Result<i32, &'static str> {
|
||||
mold::driver::link::<mold::arch::M68k>(cmdline)
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "mold-target-ppc32"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
description = "The mold linker instantiated for ppc32"
|
||||
license.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
mold.workspace = true
|
||||
@@ -0,0 +1,12 @@
|
||||
//! The linker instantiated for ppc32. Every target has a crate like this
|
||||
//! one, so that the compiler can build the targets in parallel.
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::ffi::OsStr;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Links for this target, or reports the target the inputs are actually
|
||||
/// for.
|
||||
pub fn link(cmdline: Arc<[Cow<'static, OsStr>]>) -> Result<i32, &'static str> {
|
||||
mold::driver::link::<mold::arch::Ppc32>(cmdline)
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "mold-target-ppc64v1"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
description = "The mold linker instantiated for ppc64v1"
|
||||
license.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
mold.workspace = true
|
||||
@@ -0,0 +1,12 @@
|
||||
//! The linker instantiated for ppc64v1. Every target has a crate like this
|
||||
//! one, so that the compiler can build the targets in parallel.
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::ffi::OsStr;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Links for this target, or reports the target the inputs are actually
|
||||
/// for.
|
||||
pub fn link(cmdline: Arc<[Cow<'static, OsStr>]>) -> Result<i32, &'static str> {
|
||||
mold::driver::link::<mold::arch::Ppc64V1>(cmdline)
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "mold-target-ppc64v2"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
description = "The mold linker instantiated for ppc64v2"
|
||||
license.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
mold.workspace = true
|
||||
@@ -0,0 +1,12 @@
|
||||
//! The linker instantiated for ppc64v2. Every target has a crate like this
|
||||
//! one, so that the compiler can build the targets in parallel.
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::ffi::OsStr;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Links for this target, or reports the target the inputs are actually
|
||||
/// for.
|
||||
pub fn link(cmdline: Arc<[Cow<'static, OsStr>]>) -> Result<i32, &'static str> {
|
||||
mold::driver::link::<mold::arch::Ppc64V2>(cmdline)
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "mold-target-riscv32"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
description = "The mold linker instantiated for riscv32"
|
||||
license.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
mold.workspace = true
|
||||
@@ -0,0 +1,12 @@
|
||||
//! The linker instantiated for riscv32. Every target has a crate like this
|
||||
//! one, so that the compiler can build the targets in parallel.
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::ffi::OsStr;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Links for this target, or reports the target the inputs are actually
|
||||
/// for.
|
||||
pub fn link(cmdline: Arc<[Cow<'static, OsStr>]>) -> Result<i32, &'static str> {
|
||||
mold::driver::link::<mold::arch::Riscv32>(cmdline)
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "mold-target-riscv32be"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
description = "The mold linker instantiated for riscv32be"
|
||||
license.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
mold.workspace = true
|
||||
@@ -0,0 +1,12 @@
|
||||
//! The linker instantiated for riscv32be. Every target has a crate like this
|
||||
//! one, so that the compiler can build the targets in parallel.
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::ffi::OsStr;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Links for this target, or reports the target the inputs are actually
|
||||
/// for.
|
||||
pub fn link(cmdline: Arc<[Cow<'static, OsStr>]>) -> Result<i32, &'static str> {
|
||||
mold::driver::link::<mold::arch::Riscv32Be>(cmdline)
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "mold-target-riscv64"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
description = "The mold linker instantiated for riscv64"
|
||||
license.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
mold.workspace = true
|
||||
@@ -0,0 +1,12 @@
|
||||
//! The linker instantiated for riscv64. Every target has a crate like this
|
||||
//! one, so that the compiler can build the targets in parallel.
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::ffi::OsStr;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Links for this target, or reports the target the inputs are actually
|
||||
/// for.
|
||||
pub fn link(cmdline: Arc<[Cow<'static, OsStr>]>) -> Result<i32, &'static str> {
|
||||
mold::driver::link::<mold::arch::Riscv64>(cmdline)
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "mold-target-riscv64be"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
description = "The mold linker instantiated for riscv64be"
|
||||
license.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
mold.workspace = true
|
||||
@@ -0,0 +1,12 @@
|
||||
//! The linker instantiated for riscv64be. Every target has a crate like this
|
||||
//! one, so that the compiler can build the targets in parallel.
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::ffi::OsStr;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Links for this target, or reports the target the inputs are actually
|
||||
/// for.
|
||||
pub fn link(cmdline: Arc<[Cow<'static, OsStr>]>) -> Result<i32, &'static str> {
|
||||
mold::driver::link::<mold::arch::Riscv64Be>(cmdline)
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "mold-target-s390x"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
description = "The mold linker instantiated for s390x"
|
||||
license.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
mold.workspace = true
|
||||
@@ -0,0 +1,12 @@
|
||||
//! The linker instantiated for s390x. Every target has a crate like this
|
||||
//! one, so that the compiler can build the targets in parallel.
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::ffi::OsStr;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Links for this target, or reports the target the inputs are actually
|
||||
/// for.
|
||||
pub fn link(cmdline: Arc<[Cow<'static, OsStr>]>) -> Result<i32, &'static str> {
|
||||
mold::driver::link::<mold::arch::S390x>(cmdline)
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "mold-target-sh4"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
description = "The mold linker instantiated for sh4"
|
||||
license.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
mold.workspace = true
|
||||
@@ -0,0 +1,12 @@
|
||||
//! The linker instantiated for sh4. Every target has a crate like this
|
||||
//! one, so that the compiler can build the targets in parallel.
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::ffi::OsStr;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Links for this target, or reports the target the inputs are actually
|
||||
/// for.
|
||||
pub fn link(cmdline: Arc<[Cow<'static, OsStr>]>) -> Result<i32, &'static str> {
|
||||
mold::driver::link::<mold::arch::Sh4>(cmdline)
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "mold-target-sh4be"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
description = "The mold linker instantiated for sh4be"
|
||||
license.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
mold.workspace = true
|
||||
@@ -0,0 +1,12 @@
|
||||
//! The linker instantiated for sh4be. Every target has a crate like this
|
||||
//! one, so that the compiler can build the targets in parallel.
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::ffi::OsStr;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Links for this target, or reports the target the inputs are actually
|
||||
/// for.
|
||||
pub fn link(cmdline: Arc<[Cow<'static, OsStr>]>) -> Result<i32, &'static str> {
|
||||
mold::driver::link::<mold::arch::Sh4Be>(cmdline)
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "mold-target-sparc64"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
description = "The mold linker instantiated for sparc64"
|
||||
license.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
mold.workspace = true
|
||||
@@ -0,0 +1,12 @@
|
||||
//! The linker instantiated for sparc64. Every target has a crate like this
|
||||
//! one, so that the compiler can build the targets in parallel.
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::ffi::OsStr;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Links for this target, or reports the target the inputs are actually
|
||||
/// for.
|
||||
pub fn link(cmdline: Arc<[Cow<'static, OsStr>]>) -> Result<i32, &'static str> {
|
||||
mold::driver::link::<mold::arch::Sparc64>(cmdline)
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "mold-target-x86_64"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
description = "The mold linker instantiated for x86_64"
|
||||
license.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
mold.workspace = true
|
||||
@@ -0,0 +1,12 @@
|
||||
//! The linker instantiated for x86_64. Every target has a crate like this
|
||||
//! one, so that the compiler can build the targets in parallel.
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::ffi::OsStr;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Links for this target, or reports the target the inputs are actually
|
||||
/// for.
|
||||
pub fn link(cmdline: Arc<[Cow<'static, OsStr>]>) -> Result<i32, &'static str> {
|
||||
mold::driver::link::<mold::arch::X86_64>(cmdline)
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
//! Builds the two small C parts of mold.
|
||||
//!
|
||||
//! `mold-wrapper.so` is the preload library behind `mold -run`. It
|
||||
//! interposes the exec family, including the C-variadic `execl` functions
|
||||
//! that stable Rust cannot define, so it is a C file. It is placed next to
|
||||
//! the `mold` executable, where `-run` looks for it.
|
||||
//!
|
||||
//! `lto-message.c` adapts the LTO plugin's printf-like diagnostics
|
||||
//! callback, which is C-variadic as well, and is linked into mold.
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
|
||||
// Embed the current git commit hash into the mold executable. We ask git
|
||||
// instead of parsing files under .git because the repository format varies
|
||||
// (e.g. packed refs, reftable). When building from a source tarball, there's
|
||||
// no .git, and the hash is simply omitted.
|
||||
fn git_hash(source_dir: &Path) -> Option<String> {
|
||||
let dot_git = source_dir.join(".git");
|
||||
if !dot_git.exists() {
|
||||
// Source archives use the package version. If Git metadata is added
|
||||
// later, a clean rebuild is needed to embed the new commit hash.
|
||||
return None;
|
||||
}
|
||||
if dot_git.is_file() {
|
||||
println!("cargo:rerun-if-changed={}", dot_git.display());
|
||||
}
|
||||
|
||||
let git_path = |name: &str| -> Option<PathBuf> {
|
||||
let output = Command::new("git")
|
||||
.arg("-C")
|
||||
.arg(source_dir)
|
||||
.args(["rev-parse", "--git-path", name])
|
||||
.output()
|
||||
.ok()?;
|
||||
if !output.status.success() {
|
||||
return None;
|
||||
}
|
||||
let path = PathBuf::from(String::from_utf8(output.stdout).ok()?.trim());
|
||||
Some(if path.is_absolute() {
|
||||
path
|
||||
} else {
|
||||
source_dir.join(path)
|
||||
})
|
||||
};
|
||||
|
||||
let reftable = git_path("reftable/tables.list").filter(|p| p.exists());
|
||||
if let Some(head) = git_path("HEAD").filter(|p| p.exists()) {
|
||||
println!("cargo:rerun-if-changed={}", head.display());
|
||||
if let Ok(contents) = std::fs::read_to_string(&head) {
|
||||
if let Some(reference) = contents
|
||||
.strip_prefix("ref: ")
|
||||
.map(str::trim)
|
||||
.filter(|_| reftable.is_none())
|
||||
{
|
||||
if let Some(path) = git_path(reference) {
|
||||
// A packed reference may acquire a loose file later.
|
||||
// Watch its nearest existing directory until that happens.
|
||||
if let Some(path) = path.ancestors().find(|p| p.exists()) {
|
||||
println!("cargo:rerun-if-changed={}", path.display());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for path in git_path("packed-refs")
|
||||
.into_iter()
|
||||
.chain(reftable)
|
||||
.filter(|p| p.exists())
|
||||
{
|
||||
println!("cargo:rerun-if-changed={}", path.display());
|
||||
}
|
||||
|
||||
let output = Command::new("git")
|
||||
.arg("-C")
|
||||
.arg(source_dir)
|
||||
.args(["rev-parse", "HEAD"])
|
||||
.output()
|
||||
.ok()?;
|
||||
output
|
||||
.status
|
||||
.success()
|
||||
.then(|| String::from_utf8_lossy(&output.stdout).trim().to_string())
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let source_dir = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap());
|
||||
let version = std::env::var("CARGO_PKG_VERSION").unwrap();
|
||||
let version = match git_hash(&source_dir) {
|
||||
Some(hash) => format!("mold {version} ({hash}; compatible with GNU ld)"),
|
||||
None => format!("mold {version} (compatible with GNU ld)"),
|
||||
};
|
||||
println!("cargo:rustc-env=MOLD_VERSION={version}");
|
||||
|
||||
println!("cargo:rerun-if-changed=c/mold-wrapper.c");
|
||||
println!("cargo:rerun-if-changed=c/lto-message.c");
|
||||
|
||||
let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap();
|
||||
let target_env = std::env::var("CARGO_CFG_TARGET_ENV").unwrap();
|
||||
if target_os == "windows" && target_env == "msvc" {
|
||||
return;
|
||||
}
|
||||
|
||||
let out_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap());
|
||||
let mut build = cc::Build::new();
|
||||
build.opt_level(2).pic(true);
|
||||
if let Ok(sanitizer) = std::env::var("CARGO_CFG_SANITIZE") {
|
||||
build.flag(format!("-fsanitize={sanitizer}"));
|
||||
}
|
||||
build.file("c/lto-message.c").compile("ltomessage");
|
||||
|
||||
if target_os == "windows" || target_os == "macos" {
|
||||
return;
|
||||
}
|
||||
|
||||
// The build directory is inside the actual profile directory. PROFILE
|
||||
// only describes inheritance and need not be that directory's name.
|
||||
let profile_dir = out_dir
|
||||
.ancestors()
|
||||
.find(|path| path.file_name().is_some_and(|name| name == "build"))
|
||||
.and_then(Path::parent)
|
||||
.expect("OUT_DIR must be inside Cargo's build directory");
|
||||
let wrapper = profile_dir.join("mold-wrapper.so");
|
||||
let mut command = build.get_compiler().to_command();
|
||||
command.args(["-shared", "-o"]);
|
||||
command.arg(&wrapper).arg("c/mold-wrapper.c");
|
||||
if target_os == "android" || target_os == "linux" {
|
||||
command.arg("-ldl");
|
||||
}
|
||||
let status = command.status();
|
||||
if !matches!(status, Ok(s) if s.success()) {
|
||||
println!("cargo:warning=could not build mold-wrapper.so; `mold -run` will not work");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// The linker plugin reports diagnostics through a printf-like callback.
|
||||
// C-variadic functions can't be defined in stable Rust, so this adapter
|
||||
// formats the message and hands it to Rust.
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
|
||||
void mold_lto_report(int level, const char *msg);
|
||||
|
||||
int mold_lto_message(int level, const char *fmt, ...) {
|
||||
char buf[1000];
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
vsnprintf(buf, sizeof(buf), fmt, ap);
|
||||
va_end(ap);
|
||||
mold_lto_report(level, buf);
|
||||
return 0;
|
||||
}
|
||||
File renamed without changes.
@@ -0,0 +1,71 @@
|
||||
[package]
|
||||
name = "mold-cli"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
description = "The mold executable"
|
||||
license.workspace = true
|
||||
|
||||
[[bin]]
|
||||
name = "mold"
|
||||
path = "main.rs"
|
||||
|
||||
[[test]]
|
||||
name = "integration"
|
||||
path = "integration.rs"
|
||||
harness = false
|
||||
|
||||
[dependencies]
|
||||
mimalloc = { git = "https://github.com/rui314/mimalloc_rust", rev = "cc58b72775b8b5bc89ee37752af61fcb4f7c2909" }
|
||||
mold.workspace = true
|
||||
mold-target-x86_64 = { path = "../arch/x86_64", optional = true }
|
||||
mold-target-i386 = { path = "../arch/i386", optional = true }
|
||||
mold-target-arm32 = { path = "../arch/arm32", optional = true }
|
||||
mold-target-arm32be = { path = "../arch/arm32be", optional = true }
|
||||
mold-target-arm64 = { path = "../arch/arm64", optional = true }
|
||||
mold-target-arm64be = { path = "../arch/arm64be", optional = true }
|
||||
mold-target-riscv64 = { path = "../arch/riscv64", optional = true }
|
||||
mold-target-riscv64be = { path = "../arch/riscv64be", optional = true }
|
||||
mold-target-riscv32 = { path = "../arch/riscv32", optional = true }
|
||||
mold-target-riscv32be = { path = "../arch/riscv32be", optional = true }
|
||||
mold-target-ppc64v2 = { path = "../arch/ppc64v2", optional = true }
|
||||
mold-target-ppc32 = { path = "../arch/ppc32", optional = true }
|
||||
mold-target-ppc64v1 = { path = "../arch/ppc64v1", optional = true }
|
||||
mold-target-s390x = { path = "../arch/s390x", optional = true }
|
||||
mold-target-sparc64 = { path = "../arch/sparc64", optional = true }
|
||||
mold-target-m68k = { path = "../arch/m68k", optional = true }
|
||||
mold-target-sh4 = { path = "../arch/sh4", optional = true }
|
||||
mold-target-sh4be = { path = "../arch/sh4be", optional = true }
|
||||
mold-target-loongarch64 = { path = "../arch/loongarch64", optional = true }
|
||||
mold-target-loongarch32 = { path = "../arch/loongarch32", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
mold-tests = { path = "../tests" }
|
||||
|
||||
# One feature per target. All are built by default; a build for one
|
||||
# target alone (`--no-default-features --features x86_64`) is much faster.
|
||||
[features]
|
||||
default = [
|
||||
"x86_64", "i386", "arm32", "arm32be", "arm64", "arm64be", "riscv64", "riscv64be", "riscv32", "riscv32be",
|
||||
"ppc64v2", "ppc32", "ppc64v1", "s390x", "sparc64", "m68k", "sh4", "sh4be", "loongarch64", "loongarch32",
|
||||
]
|
||||
system-allocator = []
|
||||
x86_64 = ["dep:mold-target-x86_64"]
|
||||
i386 = ["dep:mold-target-i386"]
|
||||
arm32 = ["dep:mold-target-arm32"]
|
||||
arm32be = ["dep:mold-target-arm32be"]
|
||||
arm64 = ["dep:mold-target-arm64"]
|
||||
arm64be = ["dep:mold-target-arm64be"]
|
||||
riscv64 = ["dep:mold-target-riscv64"]
|
||||
riscv64be = ["dep:mold-target-riscv64be"]
|
||||
riscv32 = ["dep:mold-target-riscv32"]
|
||||
riscv32be = ["dep:mold-target-riscv32be"]
|
||||
ppc64v2 = ["dep:mold-target-ppc64v2"]
|
||||
ppc32 = ["dep:mold-target-ppc32"]
|
||||
ppc64v1 = ["dep:mold-target-ppc64v1"]
|
||||
s390x = ["dep:mold-target-s390x"]
|
||||
sparc64 = ["dep:mold-target-sparc64"]
|
||||
m68k = ["dep:mold-target-m68k"]
|
||||
sh4 = ["dep:mold-target-sh4"]
|
||||
sh4be = ["dep:mold-target-sh4be"]
|
||||
loongarch64 = ["dep:mold-target-loongarch64"]
|
||||
loongarch32 = ["dep:mold-target-loongarch32"]
|
||||
@@ -0,0 +1,10 @@
|
||||
use std::path::Path;
|
||||
use std::process::ExitCode;
|
||||
|
||||
fn main() -> ExitCode {
|
||||
let root = Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||
.parent()
|
||||
.unwrap();
|
||||
let cases = [root.join("tests")];
|
||||
mold_tests::run(&cases, Path::new(env!("CARGO_BIN_EXE_mold")))
|
||||
}
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
//! The `mold` executable: runs the linker instantiated for the target the
|
||||
//! inputs are for. Each target is instantiated in a crate of its own so
|
||||
//! that the compiler can build them in parallel, and a feature per target
|
||||
//! decides which of them are built in.
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::ffi::OsStr;
|
||||
use std::sync::Arc;
|
||||
|
||||
// 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(Arc<[Cow<'static, OsStr>]>) -> Result<i32, &'static str>;
|
||||
|
||||
// Each target has its own monomorphized link function. Start with the first
|
||||
// enabled target and switch to the matching function if the inputs differ.
|
||||
const TARGETS: &[(&str, LinkFn)] = &[
|
||||
#[cfg(feature = "x86_64")]
|
||||
("x86_64", mold_target_x86_64::link),
|
||||
#[cfg(feature = "i386")]
|
||||
("i386", mold_target_i386::link),
|
||||
#[cfg(feature = "arm32")]
|
||||
("arm32", mold_target_arm32::link),
|
||||
#[cfg(feature = "arm32be")]
|
||||
("arm32be", mold_target_arm32be::link),
|
||||
#[cfg(feature = "arm64")]
|
||||
("arm64", mold_target_arm64::link),
|
||||
#[cfg(feature = "arm64be")]
|
||||
("arm64be", mold_target_arm64be::link),
|
||||
#[cfg(feature = "riscv64")]
|
||||
("riscv64", mold_target_riscv64::link),
|
||||
#[cfg(feature = "riscv64be")]
|
||||
("riscv64be", mold_target_riscv64be::link),
|
||||
#[cfg(feature = "riscv32")]
|
||||
("riscv32", mold_target_riscv32::link),
|
||||
#[cfg(feature = "riscv32be")]
|
||||
("riscv32be", mold_target_riscv32be::link),
|
||||
#[cfg(feature = "ppc64v2")]
|
||||
("ppc64v2", mold_target_ppc64v2::link),
|
||||
#[cfg(feature = "ppc32")]
|
||||
("ppc32", mold_target_ppc32::link),
|
||||
#[cfg(feature = "ppc64v1")]
|
||||
("ppc64v1", mold_target_ppc64v1::link),
|
||||
#[cfg(feature = "s390x")]
|
||||
("s390x", mold_target_s390x::link),
|
||||
#[cfg(feature = "sparc64")]
|
||||
("sparc64", mold_target_sparc64::link),
|
||||
#[cfg(feature = "m68k")]
|
||||
("m68k", mold_target_m68k::link),
|
||||
#[cfg(feature = "sh4")]
|
||||
("sh4", mold_target_sh4::link),
|
||||
#[cfg(feature = "sh4be")]
|
||||
("sh4be", mold_target_sh4be::link),
|
||||
#[cfg(feature = "loongarch64")]
|
||||
("loongarch64", mold_target_loongarch64::link),
|
||||
#[cfg(feature = "loongarch32")]
|
||||
("loongarch32", mold_target_loongarch32::link),
|
||||
];
|
||||
|
||||
fn link_for_target(target: &str, cmdline: Arc<[Cow<'static, OsStr>]>) -> Result<i32, &'static str> {
|
||||
for &(name, link) in TARGETS {
|
||||
if name == target {
|
||||
return link(cmdline);
|
||||
}
|
||||
}
|
||||
eprintln!(
|
||||
"mold: unsupported target: {target}; rebuild mold with the appropriate target support"
|
||||
);
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let Some(&(initial_target, _)) = TARGETS.first() else {
|
||||
eprintln!("mold: no targets enabled; rebuild mold with the appropriate target support");
|
||||
std::process::exit(1);
|
||||
};
|
||||
let args = std::env::args_os().collect();
|
||||
let status = mold::driver::main(args, initial_target, link_for_target);
|
||||
std::process::exit(status);
|
||||
}
|
||||
@@ -2,39 +2,33 @@
|
||||
#
|
||||
# This script creates a mold binary distribution. The output is written to
|
||||
# the `dist` directory as `mold-$version-$arch-linux.tar.gz` (e.g.
|
||||
# `mold-2.40.0-x86_64-linux.tar.gz`).
|
||||
# `mold-2.42.0-x86_64-linux.tar.gz`).
|
||||
#
|
||||
# This script aims to produce reproducible outputs. That means each time
|
||||
# it's run on the same git commit, it generates a bit-for-bit identical
|
||||
# binary file regardless of when or where it's executed. This property
|
||||
# serves as a strong safeguard against supply chain attacks. With a
|
||||
# reproducible build, anyone can independently verify that the binary
|
||||
# files published on our GitHub release page were built from the git
|
||||
# commit tagged for release by rebuilding the binaries themselves.
|
||||
# This script aims to produce reproducible outputs. The container images,
|
||||
# Rust toolchain, Cargo dependencies and file timestamps are pinned so that
|
||||
# the same git commit can produce a bit-for-bit identical binary file. This
|
||||
# property serves as a strong safeguard against supply chain attacks. With
|
||||
# a reproducible build, anyone can independently verify that the binary
|
||||
# files published on our GitHub release page were built from the git commit
|
||||
# tagged for release by rebuilding the binaries themselves.
|
||||
#
|
||||
# Debian provides snapshot.debian.org to host all historical binary
|
||||
# packages. We use it to construct a container image pinned to a
|
||||
# particular timestamp. snapshot.debian.org is known to be very slow,
|
||||
# but that shouldn't be a big problem for us because we only need that
|
||||
# site the first time.
|
||||
# packages. Distro package repositories must be pinned as well as the base
|
||||
# image before a build is fully reproducible. The loongarch64 build still
|
||||
# uses the live Debian sid repository, so it does not yet have that property.
|
||||
#
|
||||
# The mold executable created by this script is statically linked to
|
||||
# libstdc++, but dynamically linked to glibc, libm and a few other
|
||||
# libraries, as these libraries are almost always available on any Linux
|
||||
# system. We can't statically link glibc because doing so would disable
|
||||
# dlopen(), which is required to load the LTO linker plugin.
|
||||
# The mold executable created by this script is dynamically linked to the
|
||||
# system C runtime and other standard system libraries. We can't statically
|
||||
# link glibc because doing so would disable dlopen(), which is required to
|
||||
# load the LTO linker plugin.
|
||||
#
|
||||
# We use a reasonably old Debian version for the build environment because
|
||||
# a binary dynamically linked against a newer version of glibc won't work
|
||||
# on a system with an older version of glibc.
|
||||
#
|
||||
# We prefer to build mold with Clang rather than GCC because mold's
|
||||
# Identical Code Folding works best with the LLVM address significance
|
||||
# table (.llvm_addrsig). Building a release binary with GCC produces a
|
||||
# slightly larger binary than with Clang.
|
||||
#
|
||||
# We need a recent version of Clang to build mold. If it's not available
|
||||
# via apt-get, we'll build it ourselves.
|
||||
# The Rust toolchain is downloaded from the official Rust distribution
|
||||
# site. Its version and SHA-256 hash are recorded below, so the toolchain
|
||||
# is another pinned build input.
|
||||
#
|
||||
# This script can be used to create non-native binaries (e.g., building
|
||||
# aarch64 binary on x86-64) because Podman automatically runs everything
|
||||
@@ -42,7 +36,7 @@
|
||||
# non-native builds, you may need to install the qemu-user-static package.
|
||||
|
||||
set -e -x
|
||||
cd "$(dirname $0)"
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
usage() {
|
||||
echo "Usage: $0 [ x86_64 | aarch64 | arm | riscv64 | ppc64le | s390x | loongarch64 ]"
|
||||
@@ -52,174 +46,101 @@ usage() {
|
||||
case $# in
|
||||
0)
|
||||
arch=$(uname -m)
|
||||
if [ $arch = arm64 ]; then
|
||||
if [ "$arch" = arm64 ]; then
|
||||
arch=aarch64
|
||||
elif [[ $arch = arm* ]]; then
|
||||
elif [[ "$arch" = arm* ]]; then
|
||||
arch=arm
|
||||
fi
|
||||
;;
|
||||
1)
|
||||
arch="$1"
|
||||
arch=$1
|
||||
;;
|
||||
*)
|
||||
usage
|
||||
;;
|
||||
esac
|
||||
|
||||
# Create a Podman image.
|
||||
if [ "$GITHUB_REPOSITORY" = '' ]; then
|
||||
image=mold-builder-$arch
|
||||
image_build="podman build --arch $arch -t $image -"
|
||||
else
|
||||
# If this script is running on GitHub Actions, we want to cache
|
||||
# the created container image in GitHub's container repostiory.
|
||||
image=ghcr.io/$GITHUB_REPOSITORY/mold-builder-$arch
|
||||
image_build="podman build --arch $arch -t $image --output=type=registry --layers --cache-to $image --cache-from $image -"
|
||||
fi
|
||||
rust_version=1.97.1
|
||||
|
||||
# Switch to the pinned snapshot.debian.org sources that the Debian images
|
||||
# ship commented out; the live mirrors no longer carry every architecture.
|
||||
apt_setup="sed -i -e '/^deb/d' -e 's/^# deb /deb /g' /etc/apt/sources.list"
|
||||
|
||||
case $arch in
|
||||
x86_64)
|
||||
# Debian 9 (Stretch) released in June 2017.
|
||||
#
|
||||
# We use a Google-provided mirror (gcr.io) instead of the official Docker
|
||||
# Hub (docker.io) because docker.io has a strict rate limit policy.
|
||||
#
|
||||
# The toolchain in Debian 9 is too old to build mold, so we rebuild it
|
||||
# from source. We download source archives from official sites and build
|
||||
# them locally, rather than downloading pre-built binaries from somewhere
|
||||
# else, to avoid relying on unverifiable third-party binary blobs. Each
|
||||
# archive is checked against the SHA-256 hash recorded below before it is
|
||||
# unpacked, so this git commit alone determines every input of the build.
|
||||
# A compromised network or mirror can only make the build fail, never
|
||||
# change its output. Podman caches the result of each RUN command, so
|
||||
# rebuilding is done only once per host.
|
||||
cat <<EOF | $image_build
|
||||
FROM mirror.gcr.io/library/debian:stretch@sha256:c5c5200ff1e9c73ffbf188b4a67eb1c91531b644856b4aefe86a58d2f0cb05be
|
||||
ENV DEBIAN_FRONTEND=noninteractive TZ=UTC
|
||||
RUN sed -i -e '/^deb/d' -e 's/^# deb /deb /g' /etc/apt/sources.list && \
|
||||
echo 'Acquire::Retries "10"; Acquire::http::timeout "10"; Acquire::Check-Valid-Until "false";' > /etc/apt/apt.conf.d/80-retries && \
|
||||
apt-get update && \
|
||||
apt-get install -y --no-install-recommends wget xz-utils file make gcc g++ git zlib1g-dev libssl-dev ca-certificates && \
|
||||
rm -rf /var/lib/apt/lists
|
||||
|
||||
# Build CMake 3.27
|
||||
RUN mkdir /build && \
|
||||
cd /build && \
|
||||
wget --progress=dot:mega https://cmake.org/files/v3.27/cmake-3.27.7.tar.gz && \
|
||||
echo '08f71a106036bf051f692760ef9558c0577c42ac39e96ba097e7662bd4158d8e cmake-3.27.7.tar.gz' | sha256sum -c && \
|
||||
tar xf cmake-3.27.7.tar.gz --strip-components=1 && \
|
||||
./bootstrap --parallel=\$(nproc) && \
|
||||
make -j\$(nproc) && \
|
||||
make install && \
|
||||
rm -rf /build
|
||||
|
||||
# Build GCC 14
|
||||
RUN mkdir /build && \
|
||||
cd /build && \
|
||||
wget --progress=dot:mega https://ftp.gnu.org/gnu/gcc/gcc-14.2.0/gcc-14.2.0.tar.xz && \
|
||||
echo 'a7b39bc69cbf9e25826c5a60ab26477001f7c08d85cec04bc0e29cabed6f3cc9 gcc-14.2.0.tar.xz' | sha256sum -c && \
|
||||
tar xf gcc-14.2.0.tar.xz --strip-components=1 && \
|
||||
mkdir gmp mpc mpfr && \
|
||||
wget --progress=dot:mega https://ftp.gnu.org/gnu/gmp/gmp-6.3.0.tar.xz && \
|
||||
echo 'a3c2b80201b89e68616f4ad30bc66aee4927c3ce50e33929ca819d5c43538898 gmp-6.3.0.tar.xz' | sha256sum -c && \
|
||||
tar xf gmp-6.3.0.tar.xz --strip-components=1 -C gmp && \
|
||||
wget --progress=dot:mega https://ftp.gnu.org/gnu/mpc/mpc-1.3.1.tar.gz && \
|
||||
echo 'ab642492f5cf882b74aa0cb730cd410a81edcdbec895183ce930e706c1c759b8 mpc-1.3.1.tar.gz' | sha256sum -c && \
|
||||
tar xf mpc-1.3.1.tar.gz --strip-components=1 -C mpc && \
|
||||
wget --progress=dot:mega https://ftp.gnu.org/gnu/mpfr/mpfr-4.2.1.tar.xz && \
|
||||
echo '277807353a6726978996945af13e52829e3abd7a9a5b7fb2793894e18f1fcbb2 mpfr-4.2.1.tar.xz' | sha256sum -c && \
|
||||
tar xf mpfr-4.2.1.tar.xz --strip-components=1 -C mpfr && \
|
||||
./configure --prefix=/usr --enable-languages=c,c++ --disable-bootstrap --disable-multilib && \
|
||||
make -j\$(nproc) && \
|
||||
make install && \
|
||||
ln -sf /usr/lib64/libstdc++.so.6 /usr/lib/x86_64-linux-gnu/libstdc++.so.6 && \
|
||||
rm -rf /build
|
||||
|
||||
# Build GNU binutils 2.43
|
||||
RUN mkdir /build && \
|
||||
cd /build && \
|
||||
wget --progress=dot:mega https://ftp.gnu.org/gnu/binutils/binutils-2.43.tar.xz && \
|
||||
echo 'b53606f443ac8f01d1d5fc9c39497f2af322d99e14cea5c0b4b124d630379365 binutils-2.43.tar.xz' | sha256sum -c && \
|
||||
tar xf binutils-2.43.tar.xz --strip-components=1 && \
|
||||
./configure --prefix=/usr && \
|
||||
make -j\$(nproc) && \
|
||||
make install && \
|
||||
rm -fr /build
|
||||
|
||||
# Build Python 3.12.7
|
||||
RUN mkdir /build && \
|
||||
cd /build && \
|
||||
wget --progress=dot:mega https://www.python.org/ftp/python/3.12.7/Python-3.12.7.tar.xz && \
|
||||
echo '24887b92e2afd4a2ac602419ad4b596372f67ac9b077190f459aba390faf5550 Python-3.12.7.tar.xz' | sha256sum -c && \
|
||||
tar xf Python-3.12.7.tar.xz --strip-components=1 && \
|
||||
./configure && \
|
||||
make -j\$(nproc) && \
|
||||
make install && \
|
||||
rm -rf /build
|
||||
|
||||
# Build LLVM 20
|
||||
RUN mkdir /build && \
|
||||
cd /build && \
|
||||
wget --progress=dot:mega https://github.com/llvm/llvm-project/releases/download/llvmorg-20.1.3/llvm-project-20.1.3.src.tar.xz && \
|
||||
echo 'b6183c41281ee3f23da7fda790c6d4f5877aed103d1e759763b1008bdd0e2c50 llvm-project-20.1.3.src.tar.xz' | sha256sum -c && \
|
||||
tar xf llvm-project-20.1.3.src.tar.xz --strip-components=1 && \
|
||||
mkdir b && \
|
||||
cd b && \
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_PROJECTS=clang ../llvm && \
|
||||
cmake --build . -j\$(nproc) && \
|
||||
cmake --install . --strip && \
|
||||
rm -rf /build
|
||||
EOF
|
||||
# We use a Google-provided mirror (mirror.gcr.io) instead of the official
|
||||
# Docker Hub (docker.io) because docker.io has a strict rate limit policy.
|
||||
base_image=mirror.gcr.io/library/debian:stretch@sha256:c5c5200ff1e9c73ffbf188b4a67eb1c91531b644856b4aefe86a58d2f0cb05be
|
||||
rust_target=x86_64-unknown-linux-gnu
|
||||
rust_sha256=b4cdbc7cc6b0ee0a2666b1872769fdb2ad8393b28b63952f6493b4b400e4832b
|
||||
;;
|
||||
aarch64 | arm | ppc64le | s390x)
|
||||
# Debian 11 (Bullseye) released in August 2021
|
||||
#
|
||||
# We don't want to build Clang for these targets on QEMU becuase it
|
||||
# would take an extremely long time. Also, I believe old Linux boxes
|
||||
# are typically x86-64.
|
||||
cat <<EOF | $image_build
|
||||
FROM mirror.gcr.io/library/debian:bullseye-20240904@sha256:8ccc486c29a3ad02ad5af7f1156e2152dff3ba5634eec9be375269ef123457d8
|
||||
ENV DEBIAN_FRONTEND=noninteractive TZ=UTC
|
||||
RUN sed -i -e '/^deb/d' -e 's/^# deb /deb /g' /etc/apt/sources.list && \
|
||||
echo 'Acquire::Retries "10"; Acquire::http::timeout "10"; Acquire::Check-Valid-Until "false";' > /etc/apt/apt.conf.d/80-retries && \
|
||||
apt-get update && \
|
||||
apt-get install -y --no-install-recommends build-essential gcc-10 g++-10 clang-16 cmake git && \
|
||||
ln -sf /usr/bin/clang-16 /usr/bin/clang && \
|
||||
ln -sf /usr/bin/clang++-16 /usr/bin/clang++ && \
|
||||
rm -rf /var/lib/apt/lists
|
||||
EOF
|
||||
aarch64)
|
||||
# Debian 11 (Bullseye) released in August 2021.
|
||||
base_image=mirror.gcr.io/library/debian:bullseye-20240904@sha256:8ccc486c29a3ad02ad5af7f1156e2152dff3ba5634eec9be375269ef123457d8
|
||||
rust_target=aarch64-unknown-linux-gnu
|
||||
rust_sha256=2f2496c70bd336a66a4c8baf2d303ba161f3552f192444c3639ba903c7c1e2c5
|
||||
;;
|
||||
arm)
|
||||
# Debian 11 (Bullseye) released in August 2021.
|
||||
base_image=mirror.gcr.io/library/debian:bullseye-20240904@sha256:8ccc486c29a3ad02ad5af7f1156e2152dff3ba5634eec9be375269ef123457d8
|
||||
rust_target=armv7-unknown-linux-gnueabihf
|
||||
rust_sha256=e89c5e33aaddc6ef56857000c9117875c2997e9a1a500bd7b16277c9874b002f
|
||||
;;
|
||||
riscv64)
|
||||
cat <<EOF | $image_build
|
||||
FROM mirror.gcr.io/riscv64/debian:unstable-20240926@sha256:25654919c2926f38952cdd14b3300d83d13f2d820715f78c9f4b7a1d9399bf48
|
||||
ENV DEBIAN_FRONTEND=noninteractive TZ=UTC
|
||||
RUN sed -i -e '/^URIs/d' -e 's/^# http/URIs: http/' /etc/apt/sources.list.d/debian.sources && \
|
||||
echo 'Acquire::Retries "10"; Acquire::http::timeout "10"; Acquire::Check-Valid-Until "false";' > /etc/apt/apt.conf.d/80-retries && \
|
||||
apt-get update && \
|
||||
apt-get install -y --no-install-recommends build-essential gcc-14 g++-14 clang-18 cmake git && \
|
||||
ln -sf /usr/bin/clang-18 /usr/bin/clang && \
|
||||
ln -sf /usr/bin/clang++-18 /usr/bin/clang++ && \
|
||||
rm -rf /var/lib/apt/lists
|
||||
EOF
|
||||
base_image=mirror.gcr.io/riscv64/debian:unstable-20240926@sha256:25654919c2926f38952cdd14b3300d83d13f2d820715f78c9f4b7a1d9399bf48
|
||||
apt_setup="sed -i -e '/^URIs/d' -e 's/^# http/URIs: http/' /etc/apt/sources.list.d/debian.sources"
|
||||
rust_target=riscv64gc-unknown-linux-gnu
|
||||
rust_sha256=59bec35d8febb2ab918fa41cffbaa5b07146a63bdc33f029ff756d70a3151ece
|
||||
;;
|
||||
ppc64le)
|
||||
# Debian 11 (Bullseye) released in August 2021.
|
||||
base_image=mirror.gcr.io/library/debian:bullseye-20240904@sha256:8ccc486c29a3ad02ad5af7f1156e2152dff3ba5634eec9be375269ef123457d8
|
||||
rust_target=powerpc64le-unknown-linux-gnu
|
||||
rust_sha256=ff524eef5a59d801df09ccad5cdaf9ea1f0a07d75cbed2a7e9f013a9eb76a3c1
|
||||
;;
|
||||
s390x)
|
||||
# Debian 11 (Bullseye) released in August 2021.
|
||||
base_image=mirror.gcr.io/library/debian:bullseye-20240904@sha256:8ccc486c29a3ad02ad5af7f1156e2152dff3ba5634eec9be375269ef123457d8
|
||||
rust_target=s390x-unknown-linux-gnu
|
||||
rust_sha256=808268af9e880d41b8cb32b242e38c9bd3ea7aba6409b02fbffa0fbc5370c538
|
||||
;;
|
||||
loongarch64)
|
||||
cat <<EOF | $image_build
|
||||
FROM mirror.gcr.io/loongarch64/debian:sid@sha256:0356df4e494bbb86bb469377a00789a5b42bbf67d5ff649a3f9721b745cbef77
|
||||
ENV DEBIAN_FRONTEND=noninteractive TZ=UTC
|
||||
RUN echo 'deb http://deb.debian.org/debian sid main' > /etc/apt/sources.list && \
|
||||
echo 'Acquire::Retries "10"; Acquire::http::timeout "10"; Acquire::Check-Valid-Until "false";' > /etc/apt/apt.conf.d/80-retries && \
|
||||
apt-get update && \
|
||||
apt-get install -y --no-install-recommends build-essential gcc-14 g++-14 clang-19 cmake git && \
|
||||
ln -sf /usr/bin/clang-19 /usr/bin/clang && \
|
||||
ln -sf /usr/bin/clang++-19 /usr/bin/clang++ && \
|
||||
rm -rf /var/lib/apt/lists
|
||||
EOF
|
||||
base_image=mirror.gcr.io/loongarch64/debian:sid@sha256:0356df4e494bbb86bb469377a00789a5b42bbf67d5ff649a3f9721b745cbef77
|
||||
apt_setup="echo 'deb http://deb.debian.org/debian sid main' > /etc/apt/sources.list"
|
||||
rust_target=loongarch64-unknown-linux-gnu
|
||||
rust_sha256=d5a925962854730ae7641420d8337af93988ea4ff47b503a856ec53776c87841
|
||||
;;
|
||||
*)
|
||||
usage
|
||||
;;
|
||||
esac
|
||||
|
||||
version=$(sed -n 's/^project(mold VERSION \(.*\))/\1/p' CMakeLists.txt)
|
||||
# Create a Podman image containing the native C tools and a pinned Rust
|
||||
# toolchain. The downloaded archive is checked before it is unpacked.
|
||||
image=mold-rust-builder-$arch
|
||||
archive=rust-$rust_version-$rust_target.tar.gz
|
||||
|
||||
podman build --arch "$arch" -t "$image" - <<EOF
|
||||
FROM $base_image
|
||||
ENV DEBIAN_FRONTEND=noninteractive TZ=UTC
|
||||
RUN $apt_setup && \
|
||||
echo 'Acquire::Retries "10"; Acquire::http::timeout "10"; Acquire::Check-Valid-Until "false";' > /etc/apt/apt.conf.d/80-retries && \
|
||||
apt-get update && \
|
||||
apt-get install -y --no-install-recommends build-essential ca-certificates git wget && \
|
||||
rm -rf /var/lib/apt/lists
|
||||
RUN mkdir /tmp/rust && \
|
||||
cd /tmp/rust && \
|
||||
wget --progress=dot:mega https://static.rust-lang.org/dist/$archive && \
|
||||
echo '$rust_sha256 $archive' | sha256sum -c && \
|
||||
tar xf $archive && \
|
||||
./${archive%.tar.gz}/install.sh --prefix=/usr/local --disable-ldconfig && \
|
||||
cd / && \
|
||||
rm -rf /tmp/rust
|
||||
EOF
|
||||
|
||||
version=$(sed -n 's/^version = "\(.*\)"/\1/p' Cargo.toml)
|
||||
dest=mold-$version-$arch-linux
|
||||
|
||||
# Source tarballs available on GitHub don't contain .git directory.
|
||||
@@ -231,36 +152,42 @@ dest=mold-$version-$arch-linux
|
||||
timestamp=$(git log -1 --format=%ct)
|
||||
|
||||
# `uname -m` in an ARM32 container running on an ARM64 host reports it
|
||||
# not as ARM32 but as ARM64. That confuses BLAKE3's cmake script and
|
||||
# erroneously enables NEON SIMD instructions. `setarch` can be used to
|
||||
# change the output of `uname -m`.
|
||||
# not as ARM32 but as ARM64. Keep the reported machine consistent with
|
||||
# the ARM32 userspace for any build tool that inspects it.
|
||||
setarch=
|
||||
[ $arch = arm ] && setarch='setarch linux32'
|
||||
[ "$arch" = arm ] && setarch='setarch linux32'
|
||||
|
||||
mkdir -p dist
|
||||
mkdir -p dist "target/dist-vendor-$arch"
|
||||
|
||||
# Cargo verifies registry packages against the checksums in Cargo.lock and
|
||||
# checks out Git dependencies at the commit recorded there. Vendor them in a
|
||||
# separate networked step so that the actual build can run without a network.
|
||||
# Cargo's own cache is a tmpfs because the libgit2 inside a 32-bit cargo can't
|
||||
# read a bind-mounted ext4 directory (readdir fails with EOVERFLOW under QEMU).
|
||||
podman run --arch "$arch" -it --rm --userns=host --pids-limit=-1 \
|
||||
--pull=never --env CARGO_HOME=/cargo --tmpfs /cargo -v "$(pwd):/mold:ro" \
|
||||
-v "$(pwd)/target/dist-vendor-$arch:/vendor" "$image" $setarch \
|
||||
bash -c 'cd /mold && cargo vendor --locked /vendor/sources > /vendor/config.toml'
|
||||
|
||||
# Build mold in a container.
|
||||
#
|
||||
# SOURCE_DATE_EPOCH is a standardized environment variable that allows
|
||||
# build artifacts to appear as if they were built at a specific time.
|
||||
# We use it to control how the compiler expands the C/C++ __DATE__ and
|
||||
# __TIME__ macros.
|
||||
podman run --arch $arch -it --rm --userns=host --pids-limit=-1 --network=none \
|
||||
--pull=never -v "$(pwd):/mold:ro" -v "$(pwd)/dist:/dist" $image \
|
||||
$setarch bash -c "
|
||||
# Fixed source, vendor and target paths keep embedded build paths stable.
|
||||
podman run --arch "$arch" -it --rm --userns=host --pids-limit=-1 --network=none \
|
||||
--pull=never --env SOURCE_DATE_EPOCH="$timestamp" --env DEST="$dest" \
|
||||
-v "$(pwd):/mold:ro" -v "$(pwd)/dist:/dist" \
|
||||
-v "$(pwd)/target/dist-vendor-$arch:/vendor:ro" "$image" \
|
||||
$setarch bash -c '
|
||||
set -e
|
||||
export SOURCE_DATE_EPOCH=$timestamp
|
||||
mkdir /build
|
||||
export CARGO_TARGET_DIR=/build/target
|
||||
cd /mold
|
||||
cargo build --release --frozen --config /vendor/config.toml --package mold-cli
|
||||
stage=/build/$DEST
|
||||
DESTDIR=/build PREFIX=/$DEST ./install-mold.sh
|
||||
strip --strip-unneeded "$stage/bin/mold" "$stage/lib/mold/mold-wrapper.so"
|
||||
find "$stage" -print | xargs touch --no-dereference --date="@$SOURCE_DATE_EPOCH"
|
||||
cd /build
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DMOLD_MOSTLY_STATIC=1 -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ /mold
|
||||
cmake --build . -j\$(nproc)
|
||||
cmake --install .
|
||||
cmake -DMOLD_USE_MOLD=1 .
|
||||
cmake --build . -j\$(nproc)
|
||||
ctest --output-on-failure -j\$(nproc)
|
||||
cmake --install . --prefix $dest --strip
|
||||
find $dest -print | xargs touch --no-dereference --date=@$timestamp
|
||||
find $dest -print | sort | tar -cf - --no-recursion --files-from=- | gzip -9nc > /dist/$dest.tar.gz
|
||||
cp mold /dist
|
||||
sha256sum /dist/$dest.tar.gz
|
||||
"
|
||||
find "$DEST" -print | sort | tar -cf - --no-recursion --files-from=- | gzip -9nc > "/dist/$DEST.tar.gz"
|
||||
sha256sum "/dist/$DEST.tar.gz"
|
||||
'
|
||||
-150
@@ -1,150 +0,0 @@
|
||||
This is a note about interesting bugs that I met during the
|
||||
development of the mold linker.
|
||||
|
||||
## GNU IFUNC
|
||||
|
||||
Problem: A statically-linked "hello world" program mysteriously
|
||||
crashed in `__libc_start_main` function which is called just after
|
||||
`_start`.
|
||||
|
||||
Investigation: I opened up gdb and found that the program reads a
|
||||
bogus value from some array. It looks like `memcpy` failed to copy
|
||||
proper data there. After some investigation, I noticed that `memcpy`
|
||||
did't copy data at all but instead returned the address of
|
||||
`__memcpy_avx_unaligned` function, which is a real `memcpy` function
|
||||
optimized for machines with the AVX registers.
|
||||
|
||||
This odd issue was caused by the GNU IFUNC mechanism. That is, if a
|
||||
function symbol has type `STT_GNU_IFUNC`, the function does not do
|
||||
what its name suggests to do but instead returns a pointer to a
|
||||
function that does the actual job. In this case, `memcpy` is an IFUNC
|
||||
function, and it returns an address of `__memcpy_avx_unaligned` which
|
||||
is a real `memcpy` function.
|
||||
|
||||
IFUNC function addresses are stored to `.got` section in an ELF
|
||||
executable. The dynamic loader executes all IFUNC functions at
|
||||
startup to replace their GOT entries with their return values. This
|
||||
mechanism allows programs to choose the best implementation among
|
||||
variants of the same function at runtime based on the machine info.
|
||||
|
||||
If a program is statically-linked, there's no dynamic loader that
|
||||
rewrites the GOT entries. Therefore, if a program is
|
||||
statically-linked, a libc's startup routine does that on behalf of the
|
||||
dynamic loader. Concretely, a startup routine interprets all dynamic
|
||||
relocations between `__rela_iplt_start` and `__rela_iplt_stop`
|
||||
symbols. It is linker's responsibility to emit dynamic relocations
|
||||
for IFUNC symbols even if it is linking a statically-linked program
|
||||
and mark the beginning and the ending of a `.rela.dyn` section with
|
||||
the symbols, so that the startup routine can find the relocations.
|
||||
|
||||
The bug was my linker didn't define `__rela_iplt_start` and
|
||||
`__rela_iplt_stop` symbols. Since these symbols are weak, they are
|
||||
initialized to zero. From the point of the initializer function,
|
||||
there's no dynamic relocations between `__rela_iplt_start` and
|
||||
`__rela_iplt_stop` symbols. That left GOT entries for IFUNC symbols
|
||||
untouched.
|
||||
|
||||
The proper fix was to emit dynamic relocations for IFUNC symbols and
|
||||
define the linker-synthesized symbols. I did that, and the bug was
|
||||
fixed.
|
||||
|
||||
## stdio buffering
|
||||
|
||||
Problem: A statically-linked "Hello world" program prints out the
|
||||
message if executed as `./hello`, but it doesn't output anything if
|
||||
executed as `./hello | cat`.
|
||||
|
||||
Investigation: I knew that the default buffering mode for stdout is
|
||||
line buffering (buffer is flushed on every '\n'), but if it is not
|
||||
connected to the terminal (i.e. `isatty(2)` returns 0 on
|
||||
`STDOUT_FILENO`), it automatically switches to full buffering (buffer
|
||||
is flushed when it becomes full). So, it looks like libc failed to
|
||||
flush the stdout on program exit for some reason.
|
||||
|
||||
I traced all function calls using gdb and noticed that `__libc_atexit`
|
||||
was not called. That function seemed to be responsible for buffer
|
||||
flushing. I don't know how exactly I found the root cause, but after
|
||||
spending an hour or two, I found that `__start___libc_atexit` and
|
||||
`__stop___libc_atexit` have value 0 in my linker's output while they
|
||||
mark a section containing the address of `__libc_atexit` in GNU ld's
|
||||
output.
|
||||
|
||||
So, libc doesn't directly call `__libc_atexit` but instead call all
|
||||
function pointers between `__start___libc_atexit` and
|
||||
`__stop___libc_atexit` symbols. libc puts `__libc_atexit` address in
|
||||
`_libc_atexit` section, expecting that the linker automatically
|
||||
creates the start and the end marker symbols for the section.
|
||||
|
||||
There's an obscure linker feature: if a section name is valid as a C
|
||||
identifier (e.g. `foo` or `_foo_bar` but not `.foo`), the linker
|
||||
automatically creates marker symbols by prepending `__start_` and
|
||||
`__stop_` to the section name. My linker lacked the feature.
|
||||
|
||||
I implemented the feature, and the bug was fixed.
|
||||
|
||||
## TLS variable initialization
|
||||
|
||||
Problem: A statically-linked "hello world" program crashes after
|
||||
reading a thread-local variable.
|
||||
|
||||
Investigation: Thread-local variables are very different from other
|
||||
types of varaibles because there may be more than one instance of the
|
||||
same variable in memory. Each thread has its copy of thread-local
|
||||
varaibles. `%fs` segment register points the end of the variable area
|
||||
for the current thread, and the variables are accessed as an offset
|
||||
from `%fs`.
|
||||
|
||||
Thread-local variables may be initialized (e.g. `thread_local int x =
|
||||
5;`). The linker gathers all thread-local variables and put them into
|
||||
`PT_TLS` segment. At runtime, the contents of the segment is used as
|
||||
an "initialization image" for new threads. When a new thread is
|
||||
created, the image is memcpy'ed to the new thread's thread-local
|
||||
variable area. The initialization image itself is read-only at
|
||||
runtime.
|
||||
|
||||
It took more than a day to find out that memcpy copies the
|
||||
initialization image to a different place than the thread-local
|
||||
variables reside. That means, thread-local variables had garbage as
|
||||
initial values, and the program crashed when using them.
|
||||
|
||||
The problem is that I set a very large value (4096) to the alignment
|
||||
of `PT_TLS` segment. All `PT_LOAD` segments are naturally aligned to
|
||||
the page boundary, so I use the same value for `PT_TLS`, but that was
|
||||
a mistake. When a thread initialization routine sets a value to `%fs`,
|
||||
it first aligns the end of the thread-local variable area address to
|
||||
`PT_TLS` alignment value. So, if you set a large value to `PT_TLS`
|
||||
alignment, `%fs` is set to a wrong place.
|
||||
|
||||
I fixed `PT_TLS` alignment, and the bug was gone.
|
||||
|
||||
## stdio buffering (another issue)
|
||||
|
||||
I noticed that a dynamically-linked "hello world" program didn't
|
||||
flush its stdout buffer on exit. The cause of the problem was that the
|
||||
executable had more than one DT_NEEDED entry for `libc.so`.
|
||||
|
||||
DT_NEEDED entries in `.dynamic` section specify a list of shared
|
||||
object file names which need to be linked at runtime. I added one
|
||||
DT_NEEDED entry for each library specified with the `-l` option.
|
||||
The pitfall is, unlike object files, libraries are allowed to
|
||||
appear more than once in a command line, and the linker has to
|
||||
de-duplicate them before processing. Adding more than one DT_NEEDED
|
||||
entry for the same shared object causes mysterious issues like this.
|
||||
|
||||
# Copy relocations and symbol aliases
|
||||
|
||||
environ, _environ and __environ point to the same location in libc.so,
|
||||
so when we create a copy relocation for one of the symbols, we need to
|
||||
do that for all of them. Otherwise, they'll end up pointing to different
|
||||
places which causes a very mysterious issue.
|
||||
|
||||
# DT_DEBUG and gdb
|
||||
|
||||
If you forget to add an entry with type DT_DEBUG to .dynamic, gdb's
|
||||
`info sharedlibrary` command doesn't print out a list of shared
|
||||
libraries loaded to memory. The value of the entry doesn't matter, so
|
||||
it can be just zero. The existence of it is important.
|
||||
|
||||
#
|
||||
|
||||
__EH_FRAME_BEGIN__ in libgcc/crtstuff.c
|
||||
@@ -1,46 +0,0 @@
|
||||
# Coding Guidelines
|
||||
|
||||
mold is written in C++20, but as is the case with every C++ project,
|
||||
it has local coding rules. In this document, I'll explain some of them
|
||||
and try to give justifications for why I chose such rules.
|
||||
|
||||
## DOs
|
||||
|
||||
- Always use `i64` (which is a type alias for `int64_t` in mold) for
|
||||
integers unless you have a reason not to. For example, even if you know
|
||||
that a loop counter won't exceed 100, you should stop thinking about it
|
||||
and just use `i64`.
|
||||
|
||||
Justification: Local variables are usually on CPU registers, so on
|
||||
64-bit CPUs, there's no performance peanlty on choosing `i64` over
|
||||
`i32`. Even if a compiler has to spill register values to the stack,
|
||||
I don't think there's an observable difference between `i32` and
|
||||
`i64`. Therefore, extra 32 bits are essentially free. On 32-bit CPUs,
|
||||
they are not free, but that's OK because we are writing mold for modern
|
||||
computers. mold will still run on 32-bit computers but a bit slowly.
|
||||
|
||||
By always using `i64`, we can eliminate the need to think about the
|
||||
"right" size for each variable. It also reduces the risk of integer
|
||||
overflow.
|
||||
|
||||
Exceptions: If you have to allocate a very large number (e.g. millions)
|
||||
of the same object, its size matters. In that case, use a smaller type.
|
||||
|
||||
## DON'Ts
|
||||
|
||||
- Don't use `auto` unless its actual type is obvious in the very narrow
|
||||
context. Currently, we use `auto` only for lambdas.
|
||||
|
||||
Justification: I think `auto` makes code writing easier but code reading
|
||||
harder, because readers have to make a guess as to what is the actual
|
||||
type of `auto`. If you are already familiar with the existing codebase,
|
||||
you may be able to guess it easily, but that's not always the case.
|
||||
I want to keep the mold codebase friendly to first-time readers.
|
||||
|
||||
- Don't over-use inheritance. In mold, most classes don't have parents,
|
||||
and even if they do, their class hierarchy is very shallow. Currently
|
||||
its height is just two (i.e. abstract classes and their implementations).
|
||||
|
||||
Justification: Designing class hierarchies is fun as it feels like
|
||||
taxonomy, but I don't think that always help writing code. It looks like
|
||||
simpler class hierarchy makes its code simpler.
|
||||
-491
@@ -1,491 +0,0 @@
|
||||
[This document was written in 2020, and the contents are outdated.
|
||||
Specifically, we no longer believe that object preloading is a good
|
||||
idea. That being said, most of the points in this document still hold
|
||||
even today. Therefore, I'll keep this document as-is.]
|
||||
|
||||
## Design and implementation of mold
|
||||
|
||||
For the rest of this documentation, I'll explain the design and the
|
||||
implementation of mold. If you are only interested in using mold, you
|
||||
don't need to read the below.
|
||||
|
||||
### Motivation
|
||||
|
||||
Here is why I'm writing a new linker:
|
||||
|
||||
- Even though lld has significantly improved the situation, linking is
|
||||
still one of the slowest steps in a build. It is especially
|
||||
annoying when I changed one line of code and had to wait for a few
|
||||
seconds or even more for a linker to complete. It should be
|
||||
instantaneous. There's a need for a faster linker.
|
||||
|
||||
- The number of cores on a PC has increased a lot lately, and this
|
||||
trend is expected to continue. However, the existing linkers can't
|
||||
take the advantage of the trend because they don't scale well for more
|
||||
cores. I have a 64-core/128-thread machine, so my goal is to create
|
||||
a linker that uses the CPU nicely. mold should be much faster than
|
||||
other linkers on 4 or 8-core machines too, though.
|
||||
|
||||
- It looks to me that the designs of the existing linkers are somewhat
|
||||
too similar, and I believe there are a lot of drastically different
|
||||
designs that haven't been explored yet. Developers generally don't
|
||||
care about linkers as long as they work correctly, and they don't
|
||||
even think about creating a new one. So there may be lots of low
|
||||
hanging fruits there in this area.
|
||||
|
||||
### Basic design
|
||||
|
||||
- In order to achieve a `cp`-like performance, the most important
|
||||
thing is to fix the layout of an output file as quickly as possible, so
|
||||
that we can start copying actual data from input object files to an
|
||||
output file as soon as possible.
|
||||
|
||||
- Copying data from input files to an output file is I/O-bounded, so
|
||||
there should be room for doing computationally-intensive tasks while
|
||||
copying data from one file to another.
|
||||
|
||||
- We should allow the linker to preload object files from disk and
|
||||
parse them in memory before a complete set of input object files
|
||||
is ready. To do so, we need
|
||||
to split the linker into two in such a way that the latter half of
|
||||
the process finishes as quickly as possible by speculatively parsing
|
||||
and preprocessing input files in the first half of the process.
|
||||
|
||||
- One of the most computationally-intensive stage among linker stages
|
||||
is symbol resolution. To resolve symbols, we basically have to throw
|
||||
all symbol strings into a hash table to match undefined symbols with
|
||||
defined symbols. But this can be done in the preloading stage using
|
||||
[string interning](https://en.wikipedia.org/wiki/String_interning).
|
||||
|
||||
- Object files may contain a special section called a mergeable string
|
||||
section. The section contains lots of null-terminated strings, and
|
||||
the linker is expected to gather all mergeable string sections and
|
||||
merge their contents. So, if two object files contain the same
|
||||
string literal, for example, the resulting output will contain a
|
||||
single merged string. This step is computationally intensive, but string
|
||||
merging can be done in the preloading stage using string interning.
|
||||
|
||||
- Static archives (.a files) contain object files, but the static
|
||||
archive's string table contains only defined symbols of member
|
||||
object files and lacks other types of symbols. That makes static
|
||||
archives unsuitable for speculative parsing. Therefore, the linker
|
||||
should ignore the symbol table of static archive and directly read
|
||||
static archive members.
|
||||
|
||||
- If there's a relocation that uses a GOT of a symbol, then we have to
|
||||
create a GOT entry for that symbol. Otherwise, we shouldn't. That
|
||||
means we need to scan all relocation tables to fix the length and
|
||||
the contents of a .got section. This is computationally intensive,
|
||||
but this step is parallelizable.
|
||||
|
||||
### Linker Script
|
||||
|
||||
Linker script is an embedded language for the linker. It is mainly
|
||||
used to control how input sections are mapped to output sections and
|
||||
the layout of the output, but it can also do a lot of tricky stuff.
|
||||
Its feature is useful especially for embedded programming, but it's
|
||||
also an awfully underdocumented and complex language.
|
||||
|
||||
We have to implement a subset of the linker script language anwyay,
|
||||
because on Linux, /usr/lib/x86_64-linux-gnu/libc.so is (despite its
|
||||
name) not a shared object file but actually an ASCII file containing
|
||||
linker script code to load the _actual_ libc.so file. But the feature
|
||||
set for this purpose is very limited, and it is okay to implement them
|
||||
to mold.
|
||||
|
||||
Besides that, we really don't want to implement the linker script
|
||||
language. But at the same time, we want to satisfy the user needs that
|
||||
are currently satisfied with the linker script language. So, what
|
||||
should we do? Here is my observation:
|
||||
|
||||
- Linker script allows doing a lot of tricky stuff, such as specifying
|
||||
the exact layout of a file, inserting arbitrary bytes between
|
||||
sections, etc. But most of them can be done with a post-link binary
|
||||
editing tool (such as `objcopy`).
|
||||
|
||||
- It looks like there are two things that truly cannot be done by a
|
||||
post-link editing tool: (a) mapping input sections to output
|
||||
sections, and (b) applying relocations.
|
||||
|
||||
From the above observation, I believe we need to provide only the
|
||||
following features instead of the entire linker script language:
|
||||
|
||||
- A method to specify how input sections are mapped to output
|
||||
sections, and
|
||||
|
||||
- a method to set addresses to output sections, so that relocations
|
||||
are applied based on desired addresses.
|
||||
|
||||
I believe everything else can be done with a post-link binary editing
|
||||
tool.
|
||||
|
||||
### Details
|
||||
|
||||
- As we aim to the 1-second goal for Chromium, every millisecond
|
||||
counts. We can't ignore the latency of process exit. If we mmap a
|
||||
lot of files, \_exit(2) is not instantaneous but takes a few hundred
|
||||
milliseconds because the kernel has to clean up a lot of
|
||||
resources. As a workaround, we should organize the linker command as
|
||||
two processes; the first process forks the second process, and the
|
||||
second process does the actual work. As soon as the second process
|
||||
writes a result file to a filesystem, it notifies the first process,
|
||||
and the first process exits. The second process can take time to
|
||||
exit, because it is not an interactive process.
|
||||
|
||||
- At least on Linux, it looks like the filesystem's performance to
|
||||
allocate new blocks to a new file is the limiting factor when
|
||||
creating a new large file and filling its contents using mmap.
|
||||
If you already have a large file in the buffer cache, writing to it is
|
||||
much faster than creating a new fresh file and writing to it.
|
||||
Based on this observation, mold overwrites to an existing
|
||||
executable file if exists. My quick benchmark showed that I could
|
||||
save 300 milliseconds when creating a 2 GiB output file.
|
||||
Linux doesn't allow to open an executable for writing if it is
|
||||
running (you'll get a "text busy" error if you attempt). mold
|
||||
falls back to the usual way if it fails to open an output file.
|
||||
|
||||
- The output from the linker should be deterministic for the sake of
|
||||
[build reproducibility](https://en.wikipedia.org/wiki/Reproducible_builds)
|
||||
and ease of debugging. This might add a little bit of overhead to
|
||||
the linker, but that shouldn't be too much.
|
||||
|
||||
- A .build-id, a unique ID embedded to an output file, is usually
|
||||
computed by applying a cryptographic hash function (e.g. SHA-1) to
|
||||
an output file. This is a slow step, but we can speed it up by
|
||||
splitting a file into small chunks, computing SHA-1 for each chunk,
|
||||
and then computing SHA-1 of the concatenated SHA-1 hashes
|
||||
(i.e. constructing a [Merkle
|
||||
Tree](https://en.wikipedia.org/wiki/Merkle_tree) of height 2).
|
||||
Modern x86 processors have purpose-built instructions for SHA-1 and
|
||||
can compute SHA-1 pretty quickly at about 2 GiB/s. Using 16
|
||||
cores, a build-id for a 2 GiB executable can be computed in 60 to 70
|
||||
milliseconds.
|
||||
|
||||
- BFD, gold, and lld support section garbage collection. That is, a
|
||||
linker runs a mark-sweep garbage collection on an input graph, where
|
||||
sections are vertices and relocations are edges, to discard all
|
||||
sections that are not reachable from the entry point symbol
|
||||
(i.e. `_start`) or a few other root sections. In mold, we are using
|
||||
multiple threads to mark sections concurrently.
|
||||
|
||||
- Similarly, BFD, gold an lld support Identical Comdat Folding (ICF)
|
||||
as yet another size optimization. ICF merges two or more read-only
|
||||
sections that happen to have the same contents and relocations.
|
||||
To do that, we have to find isomorphic subgraphs from larger graphs.
|
||||
I implemented a new algorithm for mold, which is 5x faster than lld
|
||||
to do ICF for Chromium (from 5 seconds to 1 second).
|
||||
|
||||
- [Intel Threading Building
|
||||
Blocks](https://github.com/oneapi-src/oneTBB) (TBB) is a good
|
||||
library for parallel execution and has several concurrent
|
||||
containers. We are particularly interested in using
|
||||
`parallel_for_each` and `concurrent_hash_map`.
|
||||
|
||||
- TBB provides `tbbmalloc` which works better for multi-threaded
|
||||
applications than the glib'c malloc, but it looks like
|
||||
[jemalloc](https://github.com/jemalloc/jemalloc) and
|
||||
[mimalloc](https://github.com/microsoft/mimalloc) are a little bit
|
||||
more scalable than `tbbmalloc`.
|
||||
|
||||
### Size of the problem
|
||||
|
||||
When linking Chrome, a linker reads 3,430,966,844 bytes of data in
|
||||
total. The data contains the following items:
|
||||
|
||||
| Data item | Number
|
||||
| ------------------------ | ------
|
||||
| Object files | 30,723
|
||||
| Public undefined symbols | 1,428,149
|
||||
| Mergeable strings | 1,579,996
|
||||
| Comdat groups | 9,914,510
|
||||
| Regular sections¹ | 10,345,314
|
||||
| Public defined symbols | 10,512,135
|
||||
| Symbols | 23,953,607
|
||||
| Sections | 27,543,225
|
||||
| Relocations against SHF_ALLOC sections | 39,496,375
|
||||
| Relocations | 62,024,719
|
||||
|
||||
¹ Sections that have to be copied from input object files to an
|
||||
output file. Sections that contain relocations or symbols are for
|
||||
example excluded.
|
||||
|
||||
### Internals
|
||||
|
||||
In this section, I'll explain the internals of mold linker.
|
||||
|
||||
#### A brief history of Unix and the Unix linker
|
||||
|
||||
Conceptually, what a linker does is pretty simple. A compiler compiles
|
||||
a fragment of a program (a single source file) into a fragment of
|
||||
machine code and data (an object file, which typically has the .o
|
||||
extension), and a linker stitches them together into a single
|
||||
executable or a shared library image.
|
||||
|
||||
In reality, modern linkers for Unix-like systems are much more
|
||||
complicated than the naive understanding because they have gradually
|
||||
gained one feature at a time over the 50 years history of Unix, and
|
||||
they are now something like a bag of lots of miscellaneous features in
|
||||
which none of the features is more important than the others. It is
|
||||
very easy to miss the forest for the trees, since for those who don't
|
||||
know the details of the Unix linker, it is not clear which feature is
|
||||
essential and which is not.
|
||||
|
||||
That being said, one thing is clear that at any point of Unix history,
|
||||
a Unix linker has a coherent feature set for the Unix of that age. So,
|
||||
let me entangle the history to see how the operating system, runtime,
|
||||
and linker have gained features that we see today. That should give
|
||||
you an idea of why a particular feature has been added to a linker in the
|
||||
first place.
|
||||
|
||||
1. Original Unix didn't support shared libraries, and a program was
|
||||
always loaded to a fixed address. An executable was something like
|
||||
a memory dump that was just loaded to a particular address by the
|
||||
kernel. After loading, the kernel started executing the program by
|
||||
setting the instruction pointer to a particular address.
|
||||
|
||||
The most essential feature for any linker is relocation processing.
|
||||
The original Unix linker of course supported that. Let me explain
|
||||
what that is.
|
||||
|
||||
Individual object files are inevitably incomplete as a program,
|
||||
because when a compiler created them, it only see a part of an
|
||||
entire program. For example, if an object file contains a function
|
||||
call that refers to another object file, the `call` instruction in the
|
||||
object cannot be complete, as the compiler has no idea as to what
|
||||
is the called function's address. To deal with this, the compiler
|
||||
emits a placeholder value (typically just zero) instead of a real
|
||||
address and leaves metadata in an object file saying "fix offset X
|
||||
of this file with an address of Y". That metadata is called
|
||||
"relocation". Relocations are typically processed by the linker.
|
||||
|
||||
It is easy for a linker to apply relocations for the original Unix
|
||||
because a program is always loaded to a fixed address. It exactly
|
||||
knows the addresses of all functions and data when linking a
|
||||
program.
|
||||
|
||||
Static library support, which is still an important feature of Unix
|
||||
linker, also dates back to this early period of Unix history.
|
||||
To understand what it is, imagine that you are trying to compile
|
||||
a program for the early Unix. You don't want to waste time to
|
||||
compile libc functions every time you compile your program (the
|
||||
computers of the era were incredibly slow), so you have already
|
||||
placed each libc function into a separate source file and compiled
|
||||
them individually. That means you have object files for each libc
|
||||
function, e.g., printf.o, scanf.o, atoi.o, write.o, etc.
|
||||
|
||||
Given this configuration, all you have to do to link your program
|
||||
against libc functions is to pick up the right set of libc object
|
||||
files and give them to the linker along with the object files of your
|
||||
program. But, keeping the linker command line in sync with the
|
||||
libc functions you are using in your program is bothersome. You can
|
||||
be conservative; you can specify all libc object files to the
|
||||
command line, but that leads to program bloat because the linker
|
||||
unconditionally link all object files given to it no matter whether
|
||||
they are used or not. So, a new feature was added to the linker to
|
||||
fix the problem. That is the static library, which is also called
|
||||
the archive file.
|
||||
|
||||
An archive file is just a bundle of object files, just like zip
|
||||
file but in an uncompressed form. An archive file typically has the
|
||||
.a file extension and named after its contents. For example, the
|
||||
archive file containing all libc objects is named `libc.a`.
|
||||
|
||||
If you pass an archive file along with other object files to the
|
||||
linker, the linker pulls out an object file from the archive _only
|
||||
when_ it is referenced by other object files. In other words,
|
||||
unlike object files directly given to a linker, object files
|
||||
wrapped in an archive are not linked to the output by default.
|
||||
An archive works as a supplement to complete your program.
|
||||
|
||||
Even today, you can still find a libc archive file. Run `ar t
|
||||
/usr/lib/x86_64-linux-gnu/libc.a` on Linux should give you a list
|
||||
of object files in the libc archive.
|
||||
|
||||
2. In the '80s, Sun Microsystems, a leading commercial Unix vendor at the
|
||||
time, added shared library support to their Unix variant, SunOS.
|
||||
|
||||
(This section is incomplete.)
|
||||
|
||||
### Concurrency strategy
|
||||
|
||||
In this section, I'll explain the high-level concurrency strategy of
|
||||
mold.
|
||||
|
||||
In most places, mold adopts data parallelism. That is, we have a huge
|
||||
number of pieces of data of the same kind, and we process each of them
|
||||
individually using parallel for-loop. For example, after identifying
|
||||
the exact set of input object files, we need to scan all relocation
|
||||
tables to determine the sizes of .got and .plt sections. We do that
|
||||
using a parallel for-loop. The granularity of parallel processing in
|
||||
this case is the relocation table.
|
||||
|
||||
Data parallelism is very efficient and scalable because there's no
|
||||
need for threads to communicate with each other while working on each
|
||||
element of data. In addition to that, data parallelism is easy to
|
||||
understand, as it is just a for-loop in which multiple iterations may
|
||||
be executed in parallel. We don't use high-level communication or
|
||||
synchronization mechanisms such as channels, futures, promises,
|
||||
latches or something like that in mold.
|
||||
|
||||
In some cases, we need to share a little bit of data between threads
|
||||
while executing a parallel for-loop. For example, the loop to scan
|
||||
relocations turns on "requires GOT" or "requires PLT" flags in a
|
||||
symbol. Symbol is a shared resource, and writing to them from multiple
|
||||
threads without synchronization is unsafe. To deal with it, we made
|
||||
the flag an atomic variable.
|
||||
|
||||
The other common pattern you can find in mold which is build on top of
|
||||
the parallel for-loop is the map-reduce pattern. That is, we run a
|
||||
parallel for-loop on a large data set to produce a small data set and
|
||||
process the small data set with a single thread. Let me take a
|
||||
build-id computation as an example. Build-id is typically computed by
|
||||
applying a cryptographic hash function such as SHA-1 on a linker's
|
||||
output file. To compute it, we first consider an output as a sequence
|
||||
of 1 MiB blocks and compute a SHA-1 hash for each block in parallel.
|
||||
Then, we concatenate the SHA-1 hashes and compute a SHA-1 hash on the
|
||||
hashes to get a final build-id.
|
||||
|
||||
Finally, we use concurrent hashmap at a few places in mold. Concurrent
|
||||
hashmap is a hashmap to which multiple threads can safely insert items
|
||||
in parallel. We use it in the symbol resolution stage, for example.
|
||||
To resolve symbols, we basically have to throw in all defined symbols
|
||||
into a hash table, so that we can find a matching defined symbol for
|
||||
an undefined symbol by name. We do the hash table insertion from a
|
||||
parallel for-loop which iterates over a list of input files.
|
||||
|
||||
Overall, even though mold is highly scalable, it succeeded to avoid
|
||||
complexties you often find in complex parallel programs. From high
|
||||
level, mold just serially executes the linker's internal passes one by
|
||||
one. Each pass is parallelized using parallel for-loops.
|
||||
|
||||
### Rejected ideas
|
||||
|
||||
In this section, I'll explain the alternative designs I currently do
|
||||
not plan to implement and why I turned them down.
|
||||
|
||||
- Placing variable-length sections at end of an output file and start
|
||||
copying file contents before fixing the output file layout
|
||||
|
||||
Idea: Fixing the layout of regular sections seems easy, and if we
|
||||
place them at beginning of a file, we can start copying their
|
||||
contents from their input files to an output file. While copying
|
||||
file contents, we can compute the sizes of variable-length sections
|
||||
such as .got or .plt and place them at end of the file.
|
||||
|
||||
Reason for rejection: I did not choose this design because I doubt
|
||||
if it could actually shorten link time and I think I don't need it
|
||||
anyway.
|
||||
|
||||
The linker has to de-duplicate comdat sections (i.e. inline
|
||||
functions that are included in multiple object files), so we
|
||||
cannot compute the layout of regular sections until we resolve all
|
||||
symbols and de-duplicate comdats. That takes a few hundred
|
||||
milliseconds. After that, we can compute the sizes of
|
||||
variable-length sections in less than 100 milliseconds. It's quite
|
||||
fast, so it doesn't seem to make much sense to proceed without
|
||||
fixing the final file layout.
|
||||
|
||||
The other reason to reject this idea is because there's good a
|
||||
chance for this idea to have a negative impact on linker's overall
|
||||
performance. If we copy file contents before fixing the layout, we
|
||||
can't apply relocations to them while copying because symbol
|
||||
addresses are not available yet. If we fix the file layout first, we
|
||||
can apply relocations while copying, which is effectively zero-cost
|
||||
due to a very good data locality. On the other hand, if we apply
|
||||
relocations long after we copy file contents, it's pretty expensive
|
||||
because section contents are very likely to have been evicted from
|
||||
CPU cache.
|
||||
|
||||
- Incremental linking
|
||||
|
||||
Idea: Incremental linking is a technique to patch a previous linker's
|
||||
output file so that only functions or data that are updated from the
|
||||
previous build are written to it. It is expected to significantly
|
||||
reduce the amount of data copied from input files to an output file
|
||||
and thus speed up linking. GNU BFD and gold linkers support it.
|
||||
|
||||
Reason for rejection: I turned it down because it (1) is
|
||||
complicated, (2) doesn't seem to speed it up that much and (3) has
|
||||
several practical issues. Let me explain each of them.
|
||||
|
||||
First, incremental linking for real C/C++ programs is not as easy as
|
||||
one might think. Let me take malloc as an example. malloc is usually
|
||||
defined by libc, but you can implement it in your program, and if
|
||||
that's the case, the symbol `malloc` will be resolved to your
|
||||
function instead of the one in libc. If you include a library that
|
||||
defines malloc (such as libjemalloc or libtbbmallc) before libc,
|
||||
their malloc will override libc's malloc.
|
||||
|
||||
Assume that you are using a nonstandard malloc. What if you remove
|
||||
your malloc from your code, or remove `-ljemalloc` from your
|
||||
Makefile? The linker has to include a malloc from libc, which may
|
||||
include more object files to satisfy its dependencies. Such code
|
||||
change can affect the entire program rather than just replacing one
|
||||
function. The same is true for adding malloc to your program. Making
|
||||
a local change doesn't necessarily result in a local change in the
|
||||
binary level. It can easily have cascading effects.
|
||||
|
||||
Some ELF fancy features make incremental linking even harder to
|
||||
implement. Take the weak symbol as an example. If you define `atoi`
|
||||
as a weak symbol in your program, and if you are not using `atoi`
|
||||
at all in your program, that symbol will be resolved to address
|
||||
0. But if you start using some libc function that indirectly calls
|
||||
`atoi`, then `atoi` will be included in your program, and your weak
|
||||
symbol will be resolved to that function. I don't know how to
|
||||
efficiently fix up a binary for this case.
|
||||
|
||||
This is a hard problem, so existing linkers don't try too hard to
|
||||
solve it. For example, IIRC, gold falls back to full link if any
|
||||
function is removed from a previous build. If you want to not annoy
|
||||
users in the fallback case, you need to make full link fast anyway.
|
||||
|
||||
Second, incremental linking itself has an overhead. It has to detect
|
||||
updated files, patch an existing output file and write additional
|
||||
data to an output file for future incremental linking. GNU gold, for
|
||||
instance, takes almost 30 seconds on my machine to do a null
|
||||
incremental link (i.e. no object files are updated from a previous
|
||||
build) for chrome. It's just too slow.
|
||||
|
||||
Third, there are other practical issues in incremental linking. It's
|
||||
not reproducible, so your binary isn't going to be the same as other
|
||||
binaries even if you are compiling the same source tree using the
|
||||
same compiler toolchain. Or, it is complex and there might be a bug
|
||||
in it. If something doesn't work correctly, "remove --incremental
|
||||
from your Makefile and try again" could be a piece of advice, but
|
||||
that isn't ideal.
|
||||
|
||||
So, all in all, incremental linking is tricky. I wanted to make full
|
||||
link as fast as possible, so that we don't have to think about how
|
||||
to work around the slowness of full link.
|
||||
|
||||
- Defining a completely new file format and use it
|
||||
|
||||
Idea: Sometimes, the ELF file format itself seems to be a limiting
|
||||
factor in improving the linker's performance. We might be able to make a
|
||||
far better one if we create a new file format.
|
||||
|
||||
Reason for rejection: I rejected the idea because it apparently has
|
||||
a practical issue (backward compatibility issue) and also doesn't
|
||||
seem to improve the performance of linkers that much. As clearly
|
||||
demonstrated by mold, we can create a fast linker for ELF. I believe
|
||||
ELF isn't that bad, after all. The semantics of the existing Unix
|
||||
linkers, such as the name resolution algorithm or the linker script,
|
||||
have slowed the linkers down, but that's not a problem of the file
|
||||
format itself.
|
||||
|
||||
- Watching object files using inotify(2)
|
||||
|
||||
Idea: When mold is running as a daemon for preloading, use
|
||||
inotify(2) to watch file system updates so that it can reload files
|
||||
as soon as they are updated.
|
||||
|
||||
Reason for rejection: Just like the maximum number of files you can
|
||||
simultaneously open, the maximum number of files you can watch using
|
||||
inotify(2) isn't that large. Maybe just a single instance of mold is
|
||||
fine with inotify(2), but it may fail if you run multiple of it.
|
||||
|
||||
The other reason for not doing it is because mold is quite fast
|
||||
without it anyway. Invoking stat(2) on each file for file update
|
||||
check takes less than 100 milliseconds for Chrome, and if most of
|
||||
the input files are not updated, parsing updated files takes almost
|
||||
no time.
|
||||
@@ -1,55 +0,0 @@
|
||||
This page explains the following warning message and how to fix it.
|
||||
mold emits this message when it sees an object file that may not be
|
||||
compatible with mold.
|
||||
|
||||
|
||||
```
|
||||
mold: warning: foo.o: this file may cause a segmentation fault because it requires an executable stack. See https://github.com/rui314/mold/tree/main/docs/execstack.md for more info.
|
||||
```
|
||||
|
||||
# Background
|
||||
|
||||
On modern computers, the stack area (to which local variables are
|
||||
stored) cannot contain executable code. If the control reaches the
|
||||
stack area, the CPU refuses to execute any code there and the program
|
||||
is usually terminated due to segmentation fault.
|
||||
|
||||
This is a security measure. The stack area used to be executable (old
|
||||
CPUs generally execute any code as long as it is in a readable memory
|
||||
region), but that provided an easy attack vector to a malicious user.
|
||||
They wrote executable code to the stack area using some buffer
|
||||
overflow bug and jumped there to run arbitrary code in a remote server
|
||||
process.
|
||||
|
||||
To prevent this type of attack, the stack area is no longer executable
|
||||
since the early 2000s. On Linux, the stack's executable-ness is
|
||||
controlled by a bit in an executable, and the loader respects that
|
||||
bit. The bit is set by the linker.
|
||||
|
||||
GCC had (and still has) a feature that depends on the executable
|
||||
stack, so they invented a way to tell the linker to mark the stack
|
||||
executable. Specifically, if an object file contains a
|
||||
`.note.GNU-stack` section with the `SHF_EXECSTACK` bit, GNU linker
|
||||
silently makes the stack of an output file executable.
|
||||
|
||||
But the GNU linker's behavior is dangerous. If you accidentally link
|
||||
an object file that has that marker section, the entire stack area
|
||||
silently becomes executable, disabling the security mechanism.
|
||||
|
||||
Therefore, mold simply ignores that marker section. If you are using
|
||||
mold, you need to explicitly pass `-z execstack` to the linker to make
|
||||
the stack executable.
|
||||
|
||||
# What caused this issue?
|
||||
|
||||
You are likely to use GCC's [Nested
|
||||
Functions](https://gcc.gnu.org/onlinedocs/gcc/Nested-Functions.html)
|
||||
feature which still depends on the executable stack.
|
||||
|
||||
# How to fix it?
|
||||
|
||||
If you know what you are doing, pass `-z execstack` to mold. Beware
|
||||
that this will significantly weaken your program's security.
|
||||
|
||||
If you don't want to pass `-z execstack`, rewrite your code so that
|
||||
your code does not depend on the executable stack.
|
||||
@@ -1,158 +0,0 @@
|
||||
The very concept of linking is simple: a compiler compiles a piece of
|
||||
source code into an object file (a file containing machine code), and
|
||||
a linker combines object files into a single executable or a shared
|
||||
library file. However, the actual implementation of the linker for
|
||||
modern systems is much more complicated because hardware, operating
|
||||
system, compiler and linker all have many more features.
|
||||
|
||||
In this file, I'll explain random topics in the glossary format that
|
||||
you need to understand to read mold code.
|
||||
|
||||
## DSO
|
||||
|
||||
A .so file. Short for Dynamic Shared Object. Often called as a
|
||||
shared library, a dynamic libray or a shared object as well.
|
||||
|
||||
An DSO contains common functions and data that are used by multiple
|
||||
executables and/or other DSOs. At runtime, a DSO is loaded to a
|
||||
contiguous region in the virtual address.
|
||||
|
||||
## Object file
|
||||
|
||||
A .o file. An object file contains machine code and data, but it
|
||||
cannot be executed because it's not self-contained. For example,
|
||||
if you compile a C source file containing a call of `printf`,
|
||||
the actual function code of `printf` is not included in the resulting
|
||||
object file. You include `stdio.h`, but that teaches the compiler
|
||||
only about `printf`'s type, and the compiler still don't know what
|
||||
`printf` actually does. Therefore, it cannot emit code for `printf`.
|
||||
|
||||
You need to link an object file with other object file or a shared
|
||||
library to make it exectuable.
|
||||
|
||||
## Virtual address space
|
||||
|
||||
A pointer has a value like 0x803020 which is an address of the
|
||||
pointee. But it doesn't mean that the pointee resides at the
|
||||
physical memory address 0x803020 on the computer. Modern CPUs
|
||||
contains so-called Mmeory Management Unit (MMU), and all access to
|
||||
the memory are first translated by MMU to the physical address.
|
||||
The address before translation is called the "virtual address".
|
||||
Unless you are doing the kernel programming, all addresses you
|
||||
handle are virtual addresses.
|
||||
|
||||
The OS kernel controls the MMU so that each process owns the entire
|
||||
virtual address space. So, even if two process uses the same virtual
|
||||
address, they don't conflict. They are mapped to different physical
|
||||
addresses.
|
||||
|
||||
The existence of MMU has several implications to the linker. First,
|
||||
we can link the main executable to a specific address. On process
|
||||
startup, there's no code or data in the virtual address space, so
|
||||
the mapping of the main executable always succeed. However, it's not
|
||||
true to DSOs because they are loaded after the main executable and
|
||||
possibly other DSOs. Therefore, shared libraries must be linked in a
|
||||
way that they can be loaded to any address in the virtual address
|
||||
space.
|
||||
|
||||
## Relocation
|
||||
|
||||
A piece of information for the linker as to how to link object files
|
||||
or a dynamic objects.
|
||||
|
||||
Object files can refer functions or data in other object files. For
|
||||
example, if you compile a function which calls a non-local function
|
||||
`foo`, the resulting code contains something like this:
|
||||
|
||||
```
|
||||
26: e8 00 00 00 00 callq 2b <bar+0xb>
|
||||
27: R_X86_64_PLT32 foo-0x4
|
||||
```
|
||||
|
||||
The above `callq` is the instruction to call a function at the
|
||||
machine code level. It's opcode is `0xe8` in x86-64, so the
|
||||
instruction begins with `0xe8`. The following four bytes are
|
||||
displacement; that is, the address of the branch target relative to
|
||||
the end of this `callq` instruction. Notice that the displacement is
|
||||
0. The compiler couldn't fill the displacement because it has no
|
||||
idea as to where `foo` will be at runtime. So, the compiler write 0
|
||||
as a placeholder and instead write a relocation `R_X86_64_PLT32`
|
||||
with `foo` as its associated symbol. The linker reads this
|
||||
relocation, computes the offsets between this call instruction and
|
||||
function `foo` and overwrite the placeholder value 0 with an actual
|
||||
displacement.
|
||||
|
||||
There are many different types of relocations. For example, if you
|
||||
want to fix up not with a displacement but with an absolute address
|
||||
of a symbol, you need to use `R_X86_64_ABS64` instead.
|
||||
|
||||
## Static library
|
||||
|
||||
A .a file. Often called as an archive file or just archive as well.
|
||||
|
||||
A static library is a container just like tar or zip. Actually,
|
||||
there's no technical reason to not use tar or (uncompressed) zip,
|
||||
but traditionally the .a file format is used by the linker.
|
||||
|
||||
A static library contains object files and can be passed to the
|
||||
linker along with other object files and/or archives.
|
||||
|
||||
A linker pulls out object files from an archive only if it is needed
|
||||
to resolve undefined symbols. In other words, object files in an
|
||||
archive are not linked by default and used as a complement to supply
|
||||
missing definitions. This is ideal for a library because you don't
|
||||
want to link library functions unless you are actually using them.
|
||||
|
||||
Contrary to archive files, object files directly given to a linker
|
||||
are always linked to the output.
|
||||
|
||||
To maximize the benefit of archive files, a library often used as a
|
||||
static library is broken down to small files to separate each
|
||||
function individually (for example, look at
|
||||
https://git.musl-libc.org/cgit/musl/tree/src/stdio). By doing this,
|
||||
you import only used functions.
|
||||
|
||||
A static file is created by `ar`, whose command line arguments are
|
||||
similar to `tar`. A static library contains the symbol table which
|
||||
offers a quick way to look up an object file for a defined symbol,
|
||||
but mold does not use the static library's symbol table. mold
|
||||
doesdn't need a symbol table to exist in an archive, and if exists,
|
||||
mold just ignores it.
|
||||
|
||||
See also: DSO (dynamic library)
|
||||
|
||||
## Symbol
|
||||
|
||||
A symbol is a label assigned to a specific location in an input file
|
||||
or an output file. For example, if you define function `foo` and
|
||||
compile it, the resulting object file contains a symbol `foo`
|
||||
pointing to the beginning of the machine code for `foo`.
|
||||
|
||||
Usually, a symbol name is a function or a variable name. If an
|
||||
object is anonymous (such the one for a string literal), a compiler
|
||||
generated a unique symbol, which often starts with `.` to avoid
|
||||
conflict with user-defined symbols.
|
||||
|
||||
For C++, symbol name is a complex "mangled" name. We need to mangle
|
||||
identifiers because a simple name such as `foo` cannot be uniquely
|
||||
identify a function or a data in C++, because for example `foo` may
|
||||
be in a namespace or defined as a static member in some class. If
|
||||
`foo` is an overloaded function, we need to distinguish different
|
||||
`foo`s by its type. Therefore, C++ compiler mangles an identifier by
|
||||
appending nmaepsace names, type information and such so that
|
||||
different things get different names.
|
||||
|
||||
For example, a function `int foo(int)` in a namespace `bar` is
|
||||
mangled as `_ZN3bar3fooEi`.
|
||||
|
||||
A symbol can be either defined or undefined. A defined symbol points
|
||||
to some location in a file which may contain the function's machine
|
||||
code or the variable's initial value. An undefined symbol does not
|
||||
point to anywhere. It needs to be merged with a defined symbol with
|
||||
the same name at link-time. This merging process is called "name
|
||||
resolution".
|
||||
|
||||
For example, if your program is using `printf`, it usually contains
|
||||
`printf` as an undefined symbol. You need to link it with `libc.a`
|
||||
or `libc.so`, which contain a defined symbol of `printf`, to make a
|
||||
complete program.
|
||||
@@ -1,179 +0,0 @@
|
||||
# Instrumenting mold with MemorySanitizer
|
||||
|
||||
## Introduction
|
||||
|
||||
Per <https://github.com/google/sanitizers/wiki/MemorySanitizerLibcxxHowTo>:
|
||||
|
||||
> If you want MemorySanitizer to work properly and not produce any false
|
||||
> positives, you must ensure that all the code in your program and in
|
||||
> libraries it uses is instrumented (i.e. built with `-fsanitize=memory`).
|
||||
> In particular, you would need to link against an MSan-instrumented C++
|
||||
> standard library. We recommend to use [libc++](https://libcxx.llvm.org/)
|
||||
> for that purpose.
|
||||
|
||||
## Building instrumented libc++
|
||||
|
||||
Build an MSan-instrumented libc++ from source:
|
||||
|
||||
```sh
|
||||
cd ~
|
||||
git clone https://github.com/llvm/llvm-project
|
||||
cd llvm-project
|
||||
cmake -S ./runtimes -B build-libcxx -G Ninja \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DLLVM_ENABLE_RUNTIMES="libcxx;libcxxabi" \
|
||||
-DCMAKE_C_COMPILER=clang \
|
||||
-DCMAKE_CXX_COMPILER=clang++ \
|
||||
-DLLVM_USE_SANITIZER=MemoryWithOrigins
|
||||
cmake --build build-libcxx -- cxx cxxabi
|
||||
export LIBCXX="$HOME/llvm-project/build-libcxx" # for subsequent build steps
|
||||
```
|
||||
|
||||
Upon success, `./build-libcxx/{include,lib}` will contain the resulting
|
||||
headers and shared libraries.
|
||||
|
||||
## Linking mold against instrumented libc++
|
||||
|
||||
Use the `MOLD_USE_MSAN` and `MOLD_STDLIB_PREFIX` cmake variables to link
|
||||
mold against the instrumented build of libc++:
|
||||
|
||||
```sh
|
||||
cd ~
|
||||
git clone https://github.com/rui314/mold.git
|
||||
cd mold
|
||||
cmake -B build -G Ninja \
|
||||
-DCMAKE_BUILD_TYPE=Debug \
|
||||
-DCMAKE_C_COMPILER=clang \
|
||||
-DCMAKE_CXX_COMPILER=clang++ \
|
||||
-DMOLD_USE_MSAN=ON \
|
||||
-DMOLD_STDLIB_PREFIX="$LIBCXX" \
|
||||
-DMOLD_USE_MIMALLOC=OFF \
|
||||
-DMOLD_USE_SYSTEM_TBB=ON
|
||||
cmake --build build
|
||||
```
|
||||
|
||||
Most of mold's tests (except those for `-flto`) should work at this
|
||||
point. Run them like normal:
|
||||
|
||||
```sh
|
||||
ctest --test-dir build
|
||||
```
|
||||
|
||||
Any resulting MemorySanitizer errors should be visible in
|
||||
`./build/Temporary/Testing/LastTest.log`.
|
||||
|
||||
## Building instrumented LTO plugin (experimental)
|
||||
|
||||
Exercising `-flto` with MemorySanitizer (and without false positives)
|
||||
requires instrumenting the transitive dependencies of `lto-unix.cc`:
|
||||
|
||||
- `libiberty.a`
|
||||
- `liblto_plugin.so`
|
||||
- `LLVMgold.so`
|
||||
|
||||
> [!NOTE]
|
||||
> This is more involved and time-consuming than building only libc++ from
|
||||
> source. The cost-benefit ratio of this additional instrumentation and
|
||||
> test coverage may be unfavorable in many cases.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> The following steps are experimental and unlikely to work exactly as-is
|
||||
> under CI runners and individual developer environments. Consider the
|
||||
> following a starting point rather than a complete HOWTO.
|
||||
|
||||
Build an MSan-instrumented GNU libiberty:
|
||||
|
||||
```sh
|
||||
cd ~
|
||||
git clone [email protected]:gcc-mirror/gcc.git
|
||||
cd gcc/libiberty
|
||||
export CC=clang
|
||||
export CFLAGS="-g -Og -fsanitize=memory -fsanitize-memory-track-origins"
|
||||
export LDFLAGS="-fsanitize=memory"
|
||||
./configure
|
||||
make -j$(nproc)
|
||||
unset CC CFLAGS LDFLAGS
|
||||
```
|
||||
|
||||
Install the resulting `./libiberty.a` into the build toolchain being
|
||||
tested with mold. If the system image is ephemeral or disposable (e.g. a
|
||||
short-lived VM or container), a quick-and-dirty install could look like:
|
||||
|
||||
```sh
|
||||
sudo cp /usr/lib/libiberty.a{,.bak}
|
||||
sudo cp ./libiberty.a /usr/lib/
|
||||
```
|
||||
|
||||
Build an MSan-instrumented GCC LTO plugin library:
|
||||
|
||||
```sh
|
||||
cd ~/gcc/lto-plugin # use already-cloned repo from earlier step
|
||||
export CC=clang
|
||||
export CFLAGS="-g -Og -fsanitize=memory -fsanitize-memory-track-origins"
|
||||
export LDFLAGS="-fsanitize=memory"
|
||||
./configure --with-libiberty=/usr/lib
|
||||
make -j$(nproc)
|
||||
unset CC CFLAGS LDFLAGS
|
||||
```
|
||||
|
||||
Install the resulting `liblto_plugin.so`. With the same caveats discussed
|
||||
above, a simple install into an ephemeral environment could look like:
|
||||
|
||||
```sh
|
||||
export GCC_VERSION="$(gcc -dumpversion)"
|
||||
sudo cp /usr/lib/gcc/x86_64-pc-linux-gnu/$GCC_VERSION/liblto_plugin.so{,.bak}
|
||||
sudo cp .libs/liblto_plugin.so /usr/lib/gcc/x86_64-pc-linux-gnu/$GCC_VERSION/
|
||||
```
|
||||
|
||||
Build an MSan-instrumented LLVM LTO plugin library:
|
||||
|
||||
```sh
|
||||
cd ~/llvm-project # use already-cloned repo from earlier step
|
||||
|
||||
CF="-nostdinc++ -isystem $LIBCXX/include/c++/v1"
|
||||
INCDIR="$(find /usr -name plugin-api.h -type f | head -1 | xargs dirname)"
|
||||
|
||||
function configure
|
||||
{
|
||||
cmake -S ./llvm -B build-plugin -G Ninja \
|
||||
-DLLVM_ENABLE_PROJECTS=clang \
|
||||
-DLLVM_TARGETS_TO_BUILD=X86 \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_C_COMPILER=clang \
|
||||
-DCMAKE_CXX_COMPILER=clang++ \
|
||||
-DCMAKE_C_FLAGS="$CF" \
|
||||
-DCMAKE_CXX_FLAGS="$CF" \
|
||||
-DCMAKE_EXE_LINKER_FLAGS="$1" \
|
||||
-DCMAKE_POSITION_INDEPENDENT_CODE=ON \
|
||||
-DLLVM_BINUTILS_INCDIR="$INCDIR" \
|
||||
-DLLVM_USE_SANITIZER=MemoryWithOrigins
|
||||
}
|
||||
|
||||
# workaround for linker issues: configure twice with different LDFLAGS
|
||||
configure "-nostdlib++ -L $LIBCXX/lib -Wl,--rpath=$LIBCXX/lib"
|
||||
configure "-nostdlib++ -L $LIBCXX/lib -lc++ -Wl,--rpath=$LIBCXX/lib"
|
||||
|
||||
cmake --build build-plugin -- LLVMgold.so
|
||||
```
|
||||
|
||||
Install the resulting `LLVMgold.so`. With the same caveats discussed
|
||||
above, a simple install into an ephemeral environment could look like:
|
||||
|
||||
```sh
|
||||
sudo cp /usr/lib/LLVMgold.so{,.bak}
|
||||
sudo cp ./build-plugin/lib/LLVMgold.so /usr/lib/
|
||||
```
|
||||
|
||||
mold's tests for `-flto` should now (mostly) work, though there do seem
|
||||
to be issues around `gcc` inferring `-flto` when not explicitly specified.
|
||||
One workaround is to use `clang` as `TEST_CC`:
|
||||
|
||||
```sh
|
||||
cd ~/mold # use already-cloned and built repo from earlier step
|
||||
TEST_CC=clang TEST_CXX=clang++ ctest --test-dir build
|
||||
```
|
||||
|
||||
## References
|
||||
|
||||
- <https://github.com/google/sanitizers/wiki/MemorySanitizerBootstrappingClang>
|
||||
- <https://llvm.org/docs/GoldPlugin.html#lto-how-to-build>
|
||||
@@ -0,0 +1,89 @@
|
||||
# Simplification audit — September 15, 2026
|
||||
|
||||
This is the original proposal against `47a35e8`. All 28 candidates have since been implemented at the user’s request, with performance measurements deferred until the machine is quiet. See [implementation results](simplification-results.md) for commits and validation. Source references below identify files as they existed at the baseline; some declarations have since been removed.
|
||||
|
||||
Reviewed commit `47a35e8` (`Consolidate shared Cargo workspace settings`). The tree contains 120 tracked Rust files, about 48,000 Rust lines, and 558 shell tests. This audit covers the workspace structure, input pipeline, command-line parser, core linker passes, ELF representations, architecture implementations, output handling, utilities, and test runner. The pre-existing untracked `gentoo/` directory and build artifacts were excluded.
|
||||
|
||||
The best opportunities remove unused bookkeeping or share existing operations while preserving data layout, allocation patterns, and scheduling. There are **28 candidates below: six small deletions, fifteen structural or maintenance changes, and seven experiments that need performance measurements before adoption**. The experiments are possibilities, not claims of acceptable performance. The original audit was a source review; no implementation changes, test runs, or new benchmarks were made during that review.
|
||||
|
||||
I would start with items 1–6, then 7–10 and 15. The unused priority registry is the clearest simplification across several modules. The header-only section types and repeated chunk accessors offer a larger reduction in boilerplate. File collection can then be simplified without changing command-line ordering.
|
||||
|
||||
The first six changes have a direct deletion argument and do not require replacing a performance-sensitive algorithm. Public API removals below assume the workspace's current consumers; an external consumer of the `mold` library could still make them source-incompatible.
|
||||
|
||||
1. **Delete the unused architecture metadata registry.** [TargetInfo and arch::TARGETS](../src/arch/mod.rs) describe twenty architectures in approximately 130 lines, but repository-wide searches find no consumers. Target detection and emulation selection use other code. Delete both declarations. The separate `TARGETS` constants in the CLI and test runner are used and have different purposes. Expected runtime cost: none.
|
||||
|
||||
2. **Delete `Context::file_by_priority` and the DSO keep mask.** The [context field](../src/context.rs) is maintained by the [reader](../src/reader.rs), [DSO deduplication](../src/driver.rs), [internal-object creation](../src/passes.rs), and [LTO output loading](../src/lto.rs). Its stored IDs serve only its own bookkeeping; actual [symbol resolution](../src/passes.rs) builds a file list from `objs` and `dsos`. The reader uses its length to assign priorities, which can instead use `pending.into_iter().enumerate()` and `10000 + i`. Preserve internal priority 0, LTO priorities beginning at 100, and priority gaps after deduplication. Then retain DSOs directly with `dso_sonames.insert(file.soname)`, eliminating the temporary `keep` vector and registry cleanup loop. Keep discarded files in `FileList`'s backing pool so previously recorded symbol pointers remain valid. Expected runtime effect: less allocation and bookkeeping.
|
||||
|
||||
3. **Remove the vestigial script callback from machine detection.** All three callers of [filetype::get_machine_type](../src/filetype.rs) pass `|| None`. The [reader](../src/reader.rs) already handles linker scripts separately. Remove the callback parameter and make the text case return `None`. Preserve detection's native-input-before-script precedence. This removes an abstraction with no current variation and should leave generated work unchanged.
|
||||
|
||||
4. **Delete unused helper methods.** [Symbol::output_chunk](../src/symbol.rs), [Endian::read_i16](../src/util/endian.rs), and [Endian::write_i16](../src/util/endian.rs) have no repository callers. Remove these methods rather than maintain speculative APIs. Check each generated endian helper separately; this finding does not imply all signed helpers are unused. Expected runtime cost: none.
|
||||
|
||||
5. **Keep parser-only settings out of the final `Args`.** The [argument representation](../src/cmdline.rs) retains `be8`, `color_diagnostics`, `demangle`, `fatal_warnings`, `noinhibit_exec`, `suppress_warnings`, and `directory`, although their member accesses are confined to parsing. Use locals for `be8` and `directory`; update diagnostic globals directly for the other settings. Preserve the [initial reset](../src/cmdline.rs) and immediate encounter-order changes, since diagnostics during parsing observe them. This deletes fields, default initializers, and duplicate assignments without changing linking work.
|
||||
|
||||
6. **Remove two redundant pieces of state.** [Context::page_size](../src/context.rs) copies `args.page_size` during construction and is never independently changed; read the argument field directly. [Glob::is_empty](../src/util/glob.rs) can be derived from `max_value < 0`, since the builder starts at -1 and accepts nonnegative values. Retain the method and the builder's nonnegative-value contract, including its behavior when a malformed pattern has been submitted. These remove invariants rather than algorithms.
|
||||
|
||||
The next fifteen changes need implementation review for behavior preservation. Their performance case depends on retaining the existing operations and representations, not on accepting slower replacements.
|
||||
|
||||
7. **Replace nineteen header-only section wrappers with `ChunkHeader<E>`.** Examples are [EhFrameSection](../src/chunks/eh_frame.rs), [InterpSection](../src/chunks/interp.rs), and [OutputEhdr](../src/chunks/mod.rs). The complete set is `EhFrameSection`, `InterpSection`, `RelPltSection`, `SFrameRelocSection`, `DynamicSection`, `NotePackageSection`, `StrtabSection`, `Ppc64SaveRestoreSection`, `HashSection`, `RelroPaddingSection`, `OutputEhdr`, `OutputShdr`, `GdbIndexSection`, `RelrDynSection`, `SymtabSection`, `EhFrameRelocSection`, `ShstrtabSection`, `GotPltSection`, and `SymtabShndxSection`. Each contains only `hdr`. Keep named, section-specific header constructors to preserve flags and alignment, and keep data-bearing section types. Operations mostly already live in module functions. This trades nominal type distinctions for fewer types and `.hdr` layers; it needs no dynamic dispatch or new allocation.
|
||||
|
||||
8. **Declare chunk-header access mappings once.** [chunk_header](../src/context.rs) and [chunk_header_mut](../src/context.rs) repeat essentially the same large match, including optional sections and indexed sections. A small explicit declaration can generate both accessors with static matches. Do this after item 7. Consider sharing related [chunk dispatch](../src/chunks/mod.rs) only where it actually removes repetition; multi-output writers and special cases should stay explicit. Avoid a large macro language or a trait-object registry. The intended result preserves static dispatch and existing field access.
|
||||
|
||||
9. **Generate paired ELF accessor implementations.** The 32/64-bit [PhdrRecord implementations](../src/elf.rs), [SymbolRecord implementations](../src/elf.rs), and [ChdrRecord implementations](../src/elf.rs) repeat field getters and setters. A small macro can emit each pair, including the differing integer types. Keep the actual `repr(C)` ELF structs, field ordering, and layout assertions distinct: the on-disk layouts are not interchangeable. This can reduce mechanical duplication without changing the generated accessors.
|
||||
|
||||
10. **Factor the repeated worker-bin and worker-cache pattern.** [Symbol bins](../src/context.rs), [reader result bins](../src/reader.rs), and caches in [COMDAT processing](../src/passes.rs), [merged-section processing](../src/passes.rs), and [output-section creation](../src/passes.rs) repeat worker indexing, fallback handling, mutex access, and collection. A small worker-storage helper could own these operations. Preserve padding where used, the main-thread fallback, cache reuse per actual worker, and lock frequency. Rayon `map_init` initializes per job, so it is not automatically equivalent. Removing locks through unsafe thread-local access would undermine the simplification.
|
||||
|
||||
11. **Use a small typed helper for background jobs.** The [driver's background wait](../src/driver.rs) and GDB/nonallocated-merge jobs repeat a bounded channel, Rayon spawn, send, and join pattern. A `Background<T>` helper could centralize that lifecycle while keeping timers, ownership, and job labels visible at call sites. Preserve cooperative `rayon::yield_now()` while waiting: a blocking receive can deadlock a one-thread pool. Keep the GDB worker limit and overlap with foreground passes. This should alter plumbing, not scheduling.
|
||||
|
||||
12. **Simplify option parsing incrementally.** [parse_args](../src/cmdline.rs) spans roughly 1,160 lines; its matching macros mutate shared cursor and argument variables. Start with a cursor helper returning borrowed `OsStr` values, a small declaration for plain positive/negative flags, and the repetitive [LTO forwarding cases](../src/cmdline.rs). Keep side-effectful options explicit. Preserve raw filename bytes, single/double-dash matching, attached and optional values, aliases, repeated options, and push/pop state. Do not casually normalize flags into enums: `shared` and `pie` can coexist, and `icf_all` currently has sticky behavior across repeated ICF options. Parsing is a cold path, but compatibility is substantial; a wholesale parser-library migration has a worse risk/reward balance.
|
||||
|
||||
13. **Share nonallocated-relocation preparation across backends.** [PPC64 ELFv1](../src/arch/ppc64v1.rs), [PPC64 ELFv2](../src/arch/ppc64v2.rs), [SPARC64](../src/arch/sparc64.rs), [ARM64](../src/arch/arm64.rs), and several other backends repeat symbol lookup, fragment resolution, addend calculation, location selection, and tombstone handling before their relocation switches. Extract a narrow generic helper or callback for the common preparation. Leave ISA switches, overflow checks, REL/RELA differences, and unusual stored-addend behavior local. Preserve the richer cached path in x86-64. This is hot code: adoption should require inlining/code-generation inspection and relocation-heavy measurements even though the proposed operations are unchanged.
|
||||
|
||||
14. **Share emitted-relocation symbol/addend mapping.** [Ordinary relocations](../src/chunks/reloc.rs), [.eh_frame relocations](../src/chunks/eh_frame_reloc.rs), and [.sframe relocations](../src/chunks/sframe_reloc.rs) repeat the mapping of section symbols to output-section indices and offsets, and ordinary symbols to output-symbol indices. Extract that common mapping. Keep ordinary merged/dead-section handling and `.eh_frame` REL addend patching separate. Scope validation to relocatable output and emitted relocations, including relaxation. A helper should preserve the same arithmetic and lookups.
|
||||
|
||||
15. **Assign input-file IDs once, after sorting owned results.** The reader already has an owned [Loaded enum](../src/reader.rs), but [push_loaded](../src/reader.rs) assigns provisional IDs. Later [final ordering](../src/reader.rs) takes both file lists apart into vectors of optional boxes, sorts `(position, FileId)` records, and rebuilds the lists with final IDs. Keep owned pending results until sorting and assign final IDs/priorities once. Preserve boxed storage, script processing order, and serial LTO claims in command-line order; verify plugin callbacks do not require early list membership. This should remove temporary containers and a state transition. Item 2 makes the change easier.
|
||||
|
||||
16. **Return an iterator for serial recursive archive traversal.** [read_archive_members](../src/archive_file.rs) eagerly collects a vector that its sole caller, [read_file](../src/reader.rs), immediately traverses once. Expose a common iterator over member views instead. Preserve archive positions, dependency registration, and acceptable diagnostic ordering for malformed inputs. The parallel reader deliberately schedules thin-member opening inside workers; leave that separate path parallel. This removes an intermediate allocation without sacrificing that scheduling.
|
||||
|
||||
17. **Represent valid output-backend states directly, if the result is shorter.** [OutputFile](../src/output_file.rs) combines `Storage` with `Option<File>`, forcing repeated cross-field matches and an impossible mapped-without-file branch. An enum that places the file with its backend could remove those invariants. However, [open_locked](../src/output_file.rs) intentionally creates a real file with an empty memory buffer before mapping it, so that staging state must remain representable. Preserve map growth, reservation, permissions, publishing, and locking. This is lower priority: abandon it if a new state machine requires more code than the current representation.
|
||||
|
||||
18. **Separate final statistics from event counters.** [show_stats](../src/passes.rs) creates many static counters and performs zero increments just to register names, even though it computes those quantities at the end from existing state. Build a local statistics snapshot and combine it with counters that genuinely record events during passes. Keep name registration, zero-valued output, and sorting behavior. The normal linking path is unaffected; this reduces machinery in the optional reporting path.
|
||||
|
||||
19. **Reuse the existing report writer for dependency output.** [write_dependency_file](../src/passes.rs) implements its own stdout/file handling and constructs the entire output byte vector. [ReportOutput::with_writer](../src/cmdline.rs) already centralizes buffered output and error handling. Reuse it and stream the two dependency-output passes over the ordered dependency list. Preserve raw path bytes, first-seen order, and stdout/file error semantics; using hash-set iteration would lose ordering. Exact failure-message wording may need adjustment in the helper to retain compatibility. This is an optional-output cleanup, not a reason to add an ordered-set dependency.
|
||||
|
||||
20. **Reduce accidental public API and remove stale explanations.** [lib.rs](../src/lib.rs) exposes almost every implementation module. Workspace entry points are the driver and architecture types; use crate visibility where possible while retaining types required by the public architecture interface. This lets compiler dead-code checks catch more abandoned helpers. Separately, [lto.rs](../src/lto.rs) explains the same LTO sequence twice, [file-removal comments](../src/passes.rs) still claim IDs are renumbered, and [finish_output documentation](../src/arch/mod.rs) mentions an ENDBR operation handled elsewhere in the driver. Correct these locally. Avoid large file moves or documentation deletion that removes the reasoning for optimizations.
|
||||
|
||||
21. **Flatten test-process error handling.** [run_job](../tests/lib.rs) nests log creation, descriptor cloning, spawn, and wait handling. A helper returning `Result<Outcome, ...>` can leave cleanup and status-file writing in one outer path. The descriptor-clone error currently returns early and skips status-file writing, so repairing that is a small intentional error-path behavior change. Preserve process-group timeout cleanup and detection of the shell tests' `skipped` line. Keep the already-simple scoped-thread scheduling and upstream-compatible shell tests. Linker performance is unaffected.
|
||||
|
||||
The remaining seven candidates deliberately trade a specialized implementation for a simpler one. None should be presented as performance-neutral before measurement.
|
||||
|
||||
22. **Replace `CieHandle` pointers with file/record indices.** [CieHandle](../src/chunks/eh_frame.rs) uses two raw pointers and roughly sixty lines of lifetime, aliasing, and mutation plumbing. An `(ObjId, cie_index)` handle could resolve immutable records through the context for comparison, then mutate the indexed record afterward. It may also make the similar leader searches in `.eh_frame` layout and [ICF](../src/icf.rs) shareable. Cost: extra indexed access in deduplication. Measure exception-heavy C++ and ICF links, including many distinct CIEs; verify GC, relocation, and exception behavior.
|
||||
|
||||
23. **Replace the custom mutable Rayon producer with a collected reference list.** [FileList's mutable iterator and producer](../src/input_files.rs) support indexed parallel traversal through live indices over stable boxed storage. Collecting the existing safe mutable iterator into `Vec<&mut T>` and then using its standard parallel iterator could remove much of the custom producer plumbing. Cost: one allocation and approximately eight bytes per live file on a 64-bit host each time the adapter is constructed. Benchmark many-file links and repeated mutable passes at one and many threads. Preserve stable storage and indexed ordering; scanning all dead backing entries is not an equivalent replacement.
|
||||
|
||||
24. **Return owned per-file output-section groups.** [OutputSectionBuilder](../src/passes.rs) allocates a dense array of per-file contributions for each output section and shares it through `Arc` and `UnsafeCell`. Returning each file's groups from the parallel pass, then combining them deterministically, could remove shared mutation and avoid dense empty slots. Cost: possible extra grouping and serial merging; the current flattening is parallel. Preserve persistent lookup caches and parallel bulk copying. Measure many files, many output sections, and Chrome-sized input-section counts before adopting this substantial refactor.
|
||||
|
||||
25. **Use the concurrent map's existing handles for GDB names.** [NameEntryRef](../src/gdb_index.rs) adds a GDB-specific raw-pointer wrapper. The [interning call](../src/gdb_index.rs) already receives an `EntryId` and discards it. Retain that ID and resolve it through the frozen map, or reuse the map's existing reference wrapper if it fits the phase lifetimes. Cost for IDs: additional lookup/address calculation in a path with millions of names. Preserve the compact `NameRecord` layout and deterministic pool ordering. Benchmark GDB indexing separately; this does not justify replacing the compact name-record union with a larger enum.
|
||||
|
||||
26. **Try static bounded chunks for the GDB worker helper.** [limited_parallel_for_mut_init](../src/gdb_index.rs) maintains a mutex-protected queue of roughly twice as many chunks as workers. A bounded number of static `par_chunks_mut` tasks could remove the queue and loop while retaining a maximum parallelism level. Cost: worse balancing for uneven compilation units or name counts, and potentially different scratch-state reuse. Keep the worker cap that protects foreground linking. Compare uneven DWARF workloads and one-thread operation; retain the current helper if imbalance exceeds any simplification benefit.
|
||||
|
||||
27. **Try `memchr` for bounded NUL searches.** [cstr_at](../src/util/mod.rs) selects between unsafe `strlen` and `strnlen`; the [GDB reader](../src/gdb_index.rs) also uses a bounded C scan. The existing `memchr` dependency can express safe searches directly. Preserve each caller's distinct missing-terminator behavior. Cost: potentially different SIMD dispatch and short/long-string behavior in a very hot path. Measure large symbol tables, long names, and debug input; do not assume a simpler Rust expression outperforms libc.
|
||||
|
||||
28. **Use one zlib implementation for compression and decompression.** [Cargo.toml](../Cargo.toml) includes both `flate2` and `libz-sys`. The installed, locked `flate2` 1.1.10 currently enables its Rust backend; an offline feature-tree inspection confirmed active `miniz_oxide`. Configuring `flate2` with `default-features = false, features = ["zlib"]` could reuse the native zlib implementation already used by the [parallel compressor](../src/util/compress.rs), removing the extra backend dependency subtree. Cost: decompression speed and malformed-input error behavior may change. Benchmark compressed debug input and prefix reads. Retain the specialized parallel encoder and its compatibility workaround; switching the decoder backend does not replace them.
|
||||
|
||||
Several tempting changes should stay out of the initial simplification work:
|
||||
|
||||
- **Keep the architecture crates.** [Cargo.toml](../Cargo.toml) explains why all twenty targets are instantiated in separate crates: combining them serializes expensive compilation. Their small wrappers buy build parallelism.
|
||||
- **Keep compact symbols and stable ownership.** The 48-byte symbol representation, dense IDs, lazily allocated auxiliary state, boxed file storage, and process-lifetime input storage serve large input counts. Replacing them with larger general-purpose enums, per-record ownership objects, or pervasive reference counting can cost substantially more memory and cache traffic.
|
||||
- **Keep specialized concurrent tables and worker caches.** The concurrent map, sizing estimates, ICF digest table, and persistent caches avoid contention and repeated work. A single locked standard map or per-task cache is not a low-risk simplification.
|
||||
- **Keep the I/O and scheduling optimizations.** Small-file reads versus mappings, huge-page advice, filesystem-aware preallocation, output-map growth, background GDB/merge work, parallel scatter, and quick exit have explicit performance rationales. Simplifying them requires a separate performance case.
|
||||
- **Keep the fast glob paths unless measurements support deletion.** [Glob](../src/util/glob.rs) has literal, prefix, suffix, automaton, and NFA paths. Although some automaton functionality overlaps the literal paths, its comment explains why common version-script patterns bypass full-string scans. Routing everything through one engine is likely to penalize common inputs; retain item 6's simple state deletion without redesigning matching.
|
||||
- **Do not zero the full GDB output buffer merely to remove uninitialized allocation.** [build_tables](../src/gdb_index.rs) avoids an extra initialization pass over potentially large output. A zeroed buffer would still leave arbitrary-offset parallel scatter to justify, while adding memory traffic. A narrower lifetime-aware scatter wrapper may help, but it does not remove that underlying proof obligation.
|
||||
- **Keep compatibility branches that still support inputs.** Older LTO interfaces, the zlib/libbacktrace workaround, endian-specific layouts, non-UTF-8 paths, and ISA-specific relocation exceptions are supported behavior. Deleting them requires a scope change, not just a refactor.
|
||||
|
||||
For implementation, use small independent patches so any slowdown can be attributed and reverted. Start with deletions, then header representation and dispatch, paired accessors, worker storage, and the reader's owned pending results. Take parser and relocation changes separately. Leave the seven experiments until the easier reductions are complete.
|
||||
|
||||
A practical provisional interpretation of “without impacting performance that much” is no more than a 1–2% geometric-mean slowdown, no repeatable regression above 5% on a representative workload, and no material peak-memory increase. Those are proposed acceptance limits, not measured results or requirements supplied by the user. A result within measurement noise should be reported as inconclusive rather than rounded into a win.
|
||||
|
||||
For performance validation, compare each candidate with a fresh build of the pinned baseline using the same compiler, release profile, and all-target configuration. The existing [performance report](../docs/upstream-sync-performance.md) supplies a useful workload list and protocol, but measures an older revision and cannot validate these proposals. The local `~/bench` response files provide representative Chrome x86-64/ARM64, TensorFlow, Clang, Godot, and other inputs. Use separate output files, warm-ups, rotated baseline/candidate order, several measured rounds, and a quiet host. Compare one and 32 threads for concurrency changes; include small links, large debug/release links, GDB indexing, and compressed input as applicable. Record elapsed time, CPU time, and peak RSS. Use the existing `--no-fork` protocol consistently, with a separate default-mode check when exit behavior matters.
|
||||
|
||||
For correctness validation after implementation, build every architecture instantiation and run the applicable existing unit and shell suites, including available cross-toolchain tests. Reader changes particularly need archive/script ordering, as-needed DSOs, duplicate sonames, LTO claim order, and COMDAT coverage. Parser changes need raw paths, repeated/negated options, and diagnostic ordering. Relocation and CIE changes need `-r`, `--emit-relocs`, GC, ICF, exceptions, and relaxation. GDB and compression changes need the existing determinism, DWARF-format, compressed-debug-input, and compressed-output tests. Add focused tests only for behavior gaps exposed by a refactor; the audit itself does not require new tests.
|
||||
@@ -0,0 +1,108 @@
|
||||
# Simplification implementation — September 15, 2026
|
||||
|
||||
All 28 candidates in the [original audit](simplification-audit.md) have been implemented, each in a separate commit, with one additional relocation review fix. The starting point was `47a35e8`; the implementation ends at `3ccb8e2`. The changes remove a net 726 lines across 63 existing or new source and configuration files, including the added tests. Documentation is excluded from that count.
|
||||
|
||||
**No benchmarks were run.** The user requested implementation of all candidates and deferred measurements because the machine is busy. Correctness checks do not establish acceptable performance. In particular, items 22–28 remain experiments pending measurement.
|
||||
|
||||
## Commits
|
||||
|
||||
| Audit item | Commit | Result |
|
||||
| --- | --- | --- |
|
||||
| 1 | `aa4a728` | Remove unused `TargetInfo` and architecture metadata registry. |
|
||||
| 2 | `1e8dae1` | Remove the priority registry and DSO keep mask; retain stable backing storage. |
|
||||
| 3 | `f7e0ff8` | Remove the unused machine-detection callback. |
|
||||
| 4 | `2308083` | Remove unused symbol and signed-endian helpers. |
|
||||
| 5 | `de4910f` | Keep parser-only settings local and update diagnostic state directly. |
|
||||
| 6 | `7d6d588` | Derive page size and glob emptiness from existing state. |
|
||||
| 7 | `95fef65` | Replace nineteen header-only wrappers with `ChunkHeader`. |
|
||||
| 8 | `d677902` | Generate shared and mutable chunk-header access from one mapping. |
|
||||
| 9 | `2712c5e` | Share paired ELF accessors while preserving explicit on-disk layouts. |
|
||||
| 10 | `4980d26` | Centralize persistent worker bins and caches in `WorkerLocal`. |
|
||||
| 11 | `d6039a2` | Centralize background job ownership and cooperative joins. |
|
||||
| 12 | `67ba746` | Introduce a borrowed argument cursor and declarations for simple switches and LTO forwarding. |
|
||||
| 13 | `1c63947` | Share nonallocated relocation preparation across architecture backends. |
|
||||
| 14 | `284c571` | Share output symbol and addend mapping for emitted relocations. |
|
||||
| 15 | `15b984a` | Sort owned reader results before assigning file IDs and priorities. |
|
||||
| 16 | `9bca3ea` | Traverse archive members lazily through one iterator. |
|
||||
| 17 | `65479de` | Store output files together with their mapping state. |
|
||||
| 18 | `9d9f0e9` | Compute final statistics locally instead of registering global atomic counters. |
|
||||
| 19 | `8e0967d` | Stream dependency reports through the existing buffered writer. |
|
||||
| 20 | `8a56434` | Limit implementation-module visibility and remove stale or repeated explanations. |
|
||||
| 21 | `bb257d5` | Flatten test-process errors and finalize status files on every result path. |
|
||||
| 22 | `c77f241` | Replace raw CIE handles with indices and share CIE deduplication. |
|
||||
| 23 | `19162be` | Use the standard indexed parallel iterator over collected live-file references. |
|
||||
| 24 | `46bbf3d` | Collect owned sparse output-section contributions per file. |
|
||||
| 25 | `ebc789e` | Retain existing concurrent-map entry IDs for GDB names. |
|
||||
| 26 | `e918027` | Use bounded static chunks for GDB background work. |
|
||||
| 27 | `b2effe0` | Use safe, bounded `memchr` searches for NUL-terminated strings. |
|
||||
| 28 | `4658ec4` | Reuse native zlib for decompression and remove the alternate backend dependency subtree. |
|
||||
| Review fix for 14 | `3ccb8e2` | Preserve lazy addend reads for discarded section references and ordinary REL `.eh_frame` symbols. |
|
||||
|
||||
The twenty architecture instantiation crates, compact symbol and GDB record layouts, stable boxed file ownership, specialized concurrent tables, worker caches, output mapping optimizations, parallel compression, fast glob paths, and compatibility branches remain in place. Reducing module visibility may require source changes for external consumers of internal `mold` library modules; workspace consumers compile against the retained public interfaces.
|
||||
|
||||
## Correctness validation
|
||||
|
||||
The workspace check passed for all twenty architecture crates and the CLI without warnings:
|
||||
|
||||
```sh
|
||||
cargo check --locked --offline --workspace -j 4
|
||||
```
|
||||
|
||||
After the relocation review fix, all **34 unit tests** passed: 32 linker tests and two test-runner tests.
|
||||
|
||||
```sh
|
||||
cargo test --locked --offline -p mold -p mold-tests --lib -j 4
|
||||
```
|
||||
|
||||
Focused coverage added during the refactors exercises persistent worker storage, cooperative joining with one worker, GNU argument boundaries and raw path bytes, file-map transitions, live-file ordering and retained backing storage, and skipped implicit-addend reads. Existing unit tests cover the remaining utility behavior.
|
||||
|
||||
Symbol inspection of the complete release executable found no standalone definitions of `resolve_nonalloc` or `output_symidx_addend`, consistent with the intended helper inlining. This is a code-generation check, not a throughput measurement.
|
||||
|
||||
The full release shell suite passed with four concurrent tests and a longer correctness-test timeout for the busy host:
|
||||
|
||||
```sh
|
||||
cargo test --locked --offline --release -p mold-cli --test integration -j 4 -- \
|
||||
--all --test-threads 4 --timeout 180
|
||||
```
|
||||
|
||||
**6,279 shell cases completed: 5,630 passed, 649 skipped, and zero failed.** The suite exercised 14 target configurations: 13 target families plus the PPC64LE POWER10 configuration.
|
||||
|
||||
| Target | Passed | Skipped | Failed |
|
||||
| --- | ---: | ---: | ---: |
|
||||
| aarch64 | 409 | 37 | 0 |
|
||||
| arm | 401 | 49 | 0 |
|
||||
| i686 | 406 | 39 | 0 |
|
||||
| loongarch64 | 394 | 54 | 0 |
|
||||
| m68k | 380 | 60 | 0 |
|
||||
| ppc | 393 | 47 | 0 |
|
||||
| ppc64 | 385 | 56 | 0 |
|
||||
| ppc64le | 393 | 49 | 0 |
|
||||
| ppc64le-power10 | 391 | 51 | 0 |
|
||||
| riscv64 | 409 | 46 | 0 |
|
||||
| s390x | 395 | 47 | 0 |
|
||||
| sh4 | 378 | 62 | 0 |
|
||||
| sparc64 | 393 | 47 | 0 |
|
||||
| x86_64 | 503 | 5 | 0 |
|
||||
| total | 5630 | 649 | 0 |
|
||||
|
||||
The runner reported these unavailable targets because a compiler or QEMU was missing: `aarch64_be`, `armeb`, `riscv32`, and `sh4aeb`. Runtime coverage is limited to the configurations in the table; all twenty architecture crates received build coverage. Per-test logs and status files are in `target/release/mold-test/out/test/results/`.
|
||||
|
||||
The native LTO claim-order and mixed-archive tests, GDB-index determinism and compressed-output tests, compressed-debug-input tests, relocatable exception tests, and emitted C++ relocation tests all passed. The offline dependency tree confirms that `flate2` uses `libz-sys`; the alternate `miniz_oxide` backend is no longer active.
|
||||
|
||||
## Deferred performance checks
|
||||
|
||||
Use `47a35e8` as the overall baseline and the parent of each relevant commit for attribution. Build baseline and candidate with the same compiler, release profile, target features, and allocator. Run measurements only after the user confirms that the host is quiet. The [existing performance report](upstream-sync-performance.md) supplies workload and measurement context; its older results do not validate these changes.
|
||||
|
||||
| Changes | What needs measurement |
|
||||
| --- | --- |
|
||||
| Worker storage, reader ordering, background helper (10, 11, 15) | Small and many-file links at one and many threads; allocation and peak RSS. Worker slots retain locks and are now consistently padded to 128-byte alignment. |
|
||||
| Shared relocation preparation and mapping (13, 14) | Relocation-heavy links, emitted relocations, and `-r`; inspect optimized code for helper inlining. |
|
||||
| Indexed CIE deduplication (22) | Exception-heavy C++ and ICF, including many distinct CIEs. Leaders now carry indices plus the assigned value, with extra indexed access and a larger leader record. |
|
||||
| Standard mutable file iterator (23) | Many-file links and repeated mutable passes. Each invocation allocates a reference vector, approximately eight bytes per live file on a 64-bit host. |
|
||||
| Sparse section contributions (24) | Many files, many output sections, and very large input-section counts. Dense shared cells are gone; per-file grouping and a serial sparse transpose precede the retained parallel flattening and bulk copy. |
|
||||
| GDB entry IDs (25) | Large GDB indexes with millions of names, including indexing time and RSS. Each ID needs resolution through the map. |
|
||||
| Static GDB chunks (26) | Uneven compilation units and name counts, plus one-thread operation. The worker cap remains; static chunks may balance less evenly than the removed work queue. |
|
||||
| Bounded NUL searches (27) | Large symbol tables, short and long names, and debug input. `memchr` and libc may differ in dispatch and throughput. |
|
||||
| Native zlib decoding (28) | Compressed debug input and prefix reads. Decoder throughput and malformed-input error reporting may differ. |
|
||||
|
||||
Use warm-ups, rotated run order, multiple measured rounds, separate output files, and consistent `--no-fork` settings; record elapsed time, CPU time, and peak RSS. Compare representative Chrome, TensorFlow, Clang, and Godot inputs, plus small links and the special cases above. The original audit's proposed 1–2% aggregate and 5% per-workload slowdown limits are discussion points, not measured results or user-specified thresholds. Revise or revert individual experimental commits if their measured cost is unacceptable.
|
||||
@@ -0,0 +1,308 @@
|
||||
project,binary,round,attempt,seconds,cpu,other_cores,status,accepted,warmup
|
||||
clang-debug,baseline,0,1,1.302064555697143,36.457445,1.269180542679893,0,False,True
|
||||
clang-debug,baseline,0,2,1.3635382521897554,36.65199499999999,0.5339087491774338,0,True,True
|
||||
clang-debug,rust,0,1,1.255643641576171,34.508825000000016,0.3433896237268065,0,True,True
|
||||
clang-debug,cpp,0,1,1.3733756728470325,39.34614099999999,0.29406302149127506,0,True,True
|
||||
clang-debug,rust,1,1,1.2287425706163049,34.015029,0.2644744341873846,0,True,False
|
||||
clang-debug,cpp,1,1,1.3434847043827176,39.475082000000015,0.3311671508702054,0,True,False
|
||||
clang-debug,baseline,1,1,1.2507559955120087,35.617377999999974,0.28192709055420856,0,True,False
|
||||
clang-debug,cpp,2,1,1.3299898514524102,39.434488000000044,0.3424928393991593,0,True,False
|
||||
clang-debug,baseline,2,1,1.2805394735187292,35.84775099999996,0.22822334386384088,0,True,False
|
||||
clang-debug,rust,2,1,1.2241095807403326,34.03715399999999,0.49247715243880597,0,True,False
|
||||
clang-debug,baseline,3,1,1.282786862924695,36.14215999999999,0.43486569679111997,0,True,False
|
||||
clang-debug,rust,3,1,1.2395573491230607,34.27005500000007,0.5808081429999392,0,True,False
|
||||
clang-debug,cpp,3,1,1.3450086992233992,38.88715099999996,0.3887328046102838,0,True,False
|
||||
chrome-debug-x86_64,baseline,0,1,1.8528903229162097,48.97778200000005,0.346603352273423,0,True,True
|
||||
chrome-debug-x86_64,rust,0,1,1.8669003788381815,48.966752999999926,3.301326129955485,0,False,True
|
||||
chrome-debug-x86_64,rust,0,2,1.82782475464046,47.62947500000007,0.3285462693552262,0,True,True
|
||||
chrome-debug-x86_64,cpp,0,1,1.7394633879885077,48.23008900000002,0.3793761944936006,0,True,True
|
||||
chrome-debug-x86_64,rust,1,1,1.8606491796672344,48.04337899999996,0.34214993705362207,0,True,False
|
||||
chrome-debug-x86_64,cpp,1,1,1.7588795572519302,48.12182299999995,0.8745209358320227,0,True,False
|
||||
chrome-debug-x86_64,baseline,1,1,1.845357191748917,48.52482900000007,0.2737524222685878,0,True,False
|
||||
chrome-debug-x86_64,cpp,2,1,1.7191041000187397,47.420762999999965,0.37184310268194476,0,True,False
|
||||
chrome-debug-x86_64,baseline,2,1,1.8589701624587178,49.039278999999965,0.290871266565842,0,True,False
|
||||
chrome-debug-x86_64,rust,2,1,1.8301291335374117,47.711004,0.3054407435161601,0,True,False
|
||||
chrome-debug-x86_64,baseline,3,1,1.8484603082761168,48.64538399999992,0.3325015974715141,0,True,False
|
||||
chrome-debug-x86_64,rust,3,1,1.8323230538517237,47.33618800000022,0.4277695481266261,0,True,False
|
||||
chrome-debug-x86_64,cpp,3,1,1.7134374435991049,47.290097999999944,0.30342630650209124,0,True,False
|
||||
chrome-debug-arm64,baseline,0,1,2.01109504327178,52.5978990000001,0.27452755172206705,0,True,True
|
||||
chrome-debug-arm64,rust,0,1,1.9951514089480042,51.35378999999989,0.28379299899189026,0,True,True
|
||||
chrome-debug-arm64,cpp,0,1,1.8408810375258327,51.26968299999976,0.34239963672898655,0,True,True
|
||||
chrome-debug-arm64,rust,1,1,1.993138201534748,51.56305300000031,0.3095354849084768,0,True,False
|
||||
chrome-debug-arm64,cpp,1,1,1.8242124747484922,50.95748600000002,0.3467326379639343,0,True,False
|
||||
chrome-debug-arm64,baseline,1,1,2.017781227827072,52.63038399999982,0.4805357429826344,0,True,False
|
||||
chrome-debug-arm64,cpp,2,1,1.827533544972539,51.10889199999997,0.3781643332192648,0,True,False
|
||||
chrome-debug-arm64,baseline,2,1,2.0347311245277524,53.22964400000001,0.35403007026169836,0,True,False
|
||||
chrome-debug-arm64,rust,2,1,2.002107392065227,51.067821000000095,0.32075152583675864,0,True,False
|
||||
chrome-debug-arm64,baseline,3,1,2.060189605690539,53.302558999999974,0.30455497783743835,0,True,False
|
||||
chrome-debug-arm64,rust,3,1,2.0202610278502107,51.89406299999996,0.39892716429712133,0,True,False
|
||||
chrome-debug-arm64,cpp,3,1,1.857716185040772,51.17670300000009,0.3516667459340752,0,True,False
|
||||
firefox-debug,baseline,0,1,0.8707631807774305,18.87399400000004,0.35142275931870615,0,True,True
|
||||
firefox-debug,rust,0,1,0.7662025578320026,17.631071999999904,0.2726798565741469,0,True,True
|
||||
firefox-debug,cpp,0,1,0.7362016765400767,18.675731999999925,0.4540440596181058,0,True,True
|
||||
firefox-debug,rust,1,1,0.7764734663069248,17.363620999999966,0.2657901515677539,0,True,False
|
||||
firefox-debug,cpp,1,1,0.7399188205599785,18.81537100000014,0.3981909788168844,0,True,False
|
||||
firefox-debug,baseline,1,1,0.8509526969864964,18.73431100000016,0.335728418618112,0,True,False
|
||||
firefox-debug,cpp,2,1,0.7520477306097746,18.628129999999828,0.17534791017464613,0,True,False
|
||||
firefox-debug,baseline,2,1,0.8303549122065306,19.076545000000124,0.30523695583229926,0,True,False
|
||||
firefox-debug,rust,2,1,0.7468512309715152,17.291146000000026,0.19930876651586502,0,True,False
|
||||
firefox-debug,baseline,3,1,0.8485456919297576,18.522374999999784,0.2800379521975399,0,True,False
|
||||
firefox-debug,rust,3,1,0.7704464318230748,17.29722700000002,0.3410658957331743,0,True,False
|
||||
firefox-debug,cpp,3,1,0.7502016928046942,18.801764999999932,0.46418850003929607,0,True,False
|
||||
godot-release,baseline,0,1,0.08993561752140522,1.8734820000001946,0,0,True,True
|
||||
godot-release,rust,0,1,0.09175361320376396,1.7921089999997548,0.8489148497687881,0,True,True
|
||||
godot-release,cpp,0,1,0.08204395696520805,2.110046000000011,0.6088686878534375,0,True,True
|
||||
godot-release,rust,1,1,0.0893305353820324,1.8257830000000013,0,0,True,False
|
||||
godot-release,cpp,1,1,0.08382329251617193,2.096863000000212,1.2304097397455647,0,False,False
|
||||
godot-release,cpp,1,2,0.08365331869572401,2.135050999999976,0.7764066810776238,0,True,False
|
||||
godot-release,baseline,1,1,0.09122956544160843,1.8006059999997888,0.10297103070615488,0,True,False
|
||||
godot-release,cpp,2,1,0.08422635495662689,2.119236000000228,0.24652616875692762,0,True,False
|
||||
godot-release,baseline,2,1,0.09268270432949066,1.8565559999997276,0,0,True,False
|
||||
godot-release,rust,2,1,0.08893565647304058,1.7641150000001744,0,0,True,False
|
||||
godot-release,baseline,3,1,0.0941675528883934,1.9414469999999255,0.727989665885668,0,True,False
|
||||
godot-release,rust,3,1,0.09008325822651386,1.808830000000171,1.567105872492868,0,False,False
|
||||
godot-release,rust,3,2,0.09199639596045017,1.8816750000000866,0,0,True,False
|
||||
godot-release,cpp,3,1,0.08293682336807251,2.1883309999998346,0.6229922968838778,0,True,False
|
||||
blender-debug,baseline,0,1,2.1171876145526767,23.02802399999996,0.1379074760739865,0,True,True
|
||||
blender-debug,rust,0,1,0.8165935063734651,18.935995000000048,0.3355463872237881,0,True,True
|
||||
blender-debug,cpp,0,1,0.8458364196121693,21.79916800000001,0.17832289084785682,0,True,True
|
||||
blender-debug,rust,1,1,0.7891782196238637,18.650581000000102,0.29070619224751487,0,True,False
|
||||
blender-debug,cpp,1,1,0.8555630818009377,22.12849899999992,0.5160355906998179,0,True,False
|
||||
blender-debug,baseline,1,1,0.8551555443555117,19.827981999999793,0.3063980644292683,0,True,False
|
||||
blender-debug,cpp,2,1,0.8384611187502742,21.871599999999944,0.16506430032221242,0,True,False
|
||||
blender-debug,baseline,2,1,0.8320779493078589,19.509327000000212,0.46951490402388657,0,True,False
|
||||
blender-debug,rust,2,1,0.7765179919078946,18.91983100000016,0.4123136849009416,0,True,False
|
||||
blender-debug,baseline,3,1,0.865589995868504,19.665227999999843,0.4214142988404691,0,True,False
|
||||
blender-debug,rust,3,1,0.7764166546985507,19.05161300000009,0.17823805913779045,0,True,False
|
||||
blender-debug,cpp,3,1,0.8100543553009629,21.889526999999816,0.09934271482089903,0,True,False
|
||||
blender-release,baseline,0,1,0.3439562004059553,3.406471000000238,0.7952437165821873,0,True,True
|
||||
blender-release,rust,0,1,0.18424056097865105,3.132713999999851,0.039546126854444016,0,True,True
|
||||
blender-release,cpp,0,1,0.17862512823194265,3.7396899999998823,0.7295166061333918,0,True,True
|
||||
blender-release,rust,1,1,0.18684435170143843,3.151354000000083,0.4209172150834498,0,True,False
|
||||
blender-release,cpp,1,1,0.1817912831902504,3.794995000000199,0.19255597736789026,0,True,False
|
||||
blender-release,baseline,1,1,0.17665290646255016,3.102331999999933,0.3264480476352293,0,True,False
|
||||
blender-release,cpp,2,1,0.18698077369481325,3.909125000000131,0.21860537943230857,0,True,False
|
||||
blender-release,baseline,2,1,0.18932810612022877,3.2079619999999522,0,0,True,False
|
||||
blender-release,rust,2,1,0.1925763115286827,3.150934999999663,0,0,True,False
|
||||
blender-release,baseline,3,1,0.18904330022633076,3.212445000000116,0,0,True,False
|
||||
blender-release,rust,3,1,0.18289270158857107,3.029671000000235,0,0,True,False
|
||||
blender-release,cpp,3,1,0.18079581018537283,3.820694000000003,0,0,True,False
|
||||
chrome-release-x86_64,baseline,0,1,1.1562994131818414,19.523209999999835,0.1010032516360401,0,True,True
|
||||
chrome-release-x86_64,rust,0,1,0.7072577970102429,17.57142900000008,0.1676489083852643,0,True,True
|
||||
chrome-release-x86_64,cpp,0,1,0.6741551300510764,18.57056999999986,0.3403222704143844,0,True,True
|
||||
chrome-release-x86_64,rust,1,1,0.6934982882812619,17.27393099999972,0.32598350107043506,0,True,False
|
||||
chrome-release-x86_64,cpp,1,1,0.6683593858033419,18.598267000000305,0.2719091031741127,0,True,False
|
||||
chrome-release-x86_64,baseline,1,1,0.7294285176321864,18.61605099999997,0.526369604037932,0,True,False
|
||||
chrome-release-x86_64,cpp,2,1,0.6689937002956867,18.661384999999882,0.1773036145778123,0,True,False
|
||||
chrome-release-x86_64,baseline,2,1,0.7405693158507347,18.89608300000009,1.5446454161981544,0,False,False
|
||||
chrome-release-x86_64,baseline,2,2,0.7432413036003709,18.83893900000021,1.4276130690930897,0,False,False
|
||||
chrome-release-x86_64,baseline,2,3,0.7464303085580468,18.966204999999718,1.425176579172987,0,False,False
|
||||
chrome-release-x86_64,baseline,2,4,0.7348534800112247,18.82084200000054,1.3324533715268958,0,False,False
|
||||
chrome-release-x86_64,baseline,2,5,0.7406343771144748,18.899749999999585,1.256017850314411,0,False,False
|
||||
chrome-release-x86_64,baseline,2,6,0.7317181238904595,18.743179000000055,1.4033013018293619,0,False,False
|
||||
chrome-release-x86_64,baseline,2,7,0.7509735366329551,19.136034999999993,1.3901488518259615,0,False,False
|
||||
chrome-release-x86_64,baseline,2,8,0.7349655013531446,18.811993000000257,2.5552314961150686,0,False,False
|
||||
chrome-release-x86_64,baseline,2,9,0.7276110826060176,18.651922999999442,1.412948516424964,0,False,False
|
||||
chrome-release-x86_64,baseline,2,10,0.7351650819182396,18.778921000000537,1.6745613035661198,0,False,False
|
||||
chrome-release-x86_64,baseline,2,11,0.7294868612661958,18.63611899999978,1.0334401352859577,0,False,False
|
||||
chrome-release-x86_64,baseline,2,12,0.737503775395453,18.846215999999913,1.4424115942367919,0,False,False
|
||||
chrome-release-x86_64,baseline,2,13,0.7302921442314982,18.660628000000088,1.532772880802878,0,False,False
|
||||
chrome-release-x86_64,baseline,2,14,0.7414641352370381,18.81252799999993,1.4666548883882313,0,False,False
|
||||
chrome-release-x86_64,baseline,2,15,0.7399630500003695,18.778393999999935,1.0157345034887657,0,False,False
|
||||
chrome-release-x86_64,baseline,2,16,0.7468982618302107,18.83032000000003,1.4723290385055599,0,False,False
|
||||
chrome-release-x86_64,baseline,2,17,0.736293813213706,18.724477000000206,1.419980698621721,0,False,False
|
||||
chrome-release-x86_64,baseline,2,18,0.7458085957914591,18.785351999999875,0.1671313586056876,0,True,False
|
||||
chrome-release-x86_64,rust,2,1,0.7000126726925373,17.568676999999752,0.3304554524432142,0,True,False
|
||||
chrome-release-x86_64,baseline,3,1,0.7410296937450767,18.65463899999986,0.41207659560896903,0,True,False
|
||||
chrome-release-x86_64,rust,3,1,0.6954309800639749,17.321203000000423,1.0623584849725825,0,False,False
|
||||
chrome-release-x86_64,rust,3,2,0.7098307460546494,17.637322999999924,1.2012398826418145,0,False,False
|
||||
chrome-release-x86_64,rust,3,3,0.70680679846555,17.66219299999966,1.4400073710551953,0,False,False
|
||||
chrome-release-x86_64,rust,3,4,0.6980055924504995,17.47521000000006,1.2819238333420178,0,False,False
|
||||
chrome-release-x86_64,rust,3,5,0.695583320222795,17.529558000000634,1.1938785408731905,0,False,False
|
||||
chrome-release-x86_64,rust,3,6,0.6995064103975892,17.51279499999964,0.9395267749082555,0,True,False
|
||||
chrome-release-x86_64,cpp,3,1,0.6590290050953627,18.40479200000027,1.5404602740375084,0,False,False
|
||||
chrome-release-x86_64,cpp,3,2,0.675702671520412,18.900601999999708,1.5974452110619333,0,False,False
|
||||
chrome-release-x86_64,cpp,3,3,0.6543124746531248,18.23222100000021,1.3262455403283409,0,False,False
|
||||
chrome-release-x86_64,cpp,3,4,0.6712256008759141,18.633360999999695,1.2464348763164605,0,False,False
|
||||
chrome-release-x86_64,cpp,3,5,0.6519671278074384,18.312046999999893,1.1165486234775959,0,False,False
|
||||
chrome-release-x86_64,cpp,3,6,0.6552704209461808,18.271827000000485,1.309647094160852,0,False,False
|
||||
chrome-release-x86_64,cpp,3,7,0.9005500543862581,21.252105999999912,1.07478090095462,0,False,False
|
||||
chrome-release-x86_64,cpp,3,8,0.6837977236136794,19.069460000000163,1.2292231567861034,0,False,False
|
||||
chrome-release-x86_64,cpp,3,9,0.6637545553967357,18.43612099999973,0.17156793289647657,0,True,False
|
||||
chrome-release-arm64,baseline,0,1,1.3006816981360316,22.827398000000358,0.10194807929083656,0,True,True
|
||||
chrome-release-arm64,rust,0,1,0.8121169749647379,19.965950999999222,0.49752561487353764,0,True,True
|
||||
chrome-release-arm64,cpp,0,1,0.7550802044570446,20.614731000000575,0.3380687286741324,0,True,True
|
||||
chrome-release-arm64,rust,1,1,0.8210204560309649,20.1580399999998,0.22162662914093662,0,True,False
|
||||
chrome-release-arm64,cpp,1,1,0.7613450707867742,20.80568600000015,0.7937452012384771,0,True,False
|
||||
chrome-release-arm64,baseline,1,1,0.8486796850338578,21.224753999999848,0.3596716159518848,0,True,False
|
||||
chrome-release-arm64,cpp,2,1,0.7736665364354849,21.265417999999954,0.22565536551547463,0,True,False
|
||||
chrome-release-arm64,baseline,2,1,0.8499096464365721,21.369407000000137,0.22425089911986554,0,True,False
|
||||
chrome-release-arm64,rust,2,1,0.8164611589163542,20.24154099999987,0.48803178056191837,0,True,False
|
||||
chrome-release-arm64,baseline,3,1,0.8537774104624987,21.42120100000011,0.1157198610169659,0,True,False
|
||||
chrome-release-arm64,rust,3,1,0.8166987579315901,20.16217800000004,0.30344359100276797,0,True,False
|
||||
chrome-release-arm64,cpp,3,1,0.7926303260028362,21.745092999999997,0.28374765818271913,0,True,False
|
||||
clang-release,baseline,0,1,0.24664609786123037,2.430601999999908,0.6868059254094339,0,True,True
|
||||
clang-release,rust,0,1,0.1079400209710002,2.38384700000006,0.05700388549711112,0,True,True
|
||||
clang-release,cpp,0,1,0.10171171370893717,2.720925999999963,0,0,True,True
|
||||
clang-release,rust,1,1,0.10874396562576294,2.3756570000000465,1.6952020636066014,0,False,False
|
||||
clang-release,rust,1,2,0.11047615297138691,2.404820999999629,0,0,True,False
|
||||
clang-release,cpp,1,1,0.1022625807672739,2.795412000000397,0.2404398570145912,0,True,False
|
||||
clang-release,baseline,1,1,0.10602515563368797,2.2546259999999165,0,0,True,False
|
||||
clang-release,cpp,2,1,0.09990910720080137,2.684368000000177,0,0,True,False
|
||||
clang-release,baseline,2,1,0.11094318982213736,2.28708000000006,0.38686464920117647,0,True,False
|
||||
clang-release,rust,2,1,0.10881776548922062,2.4214289999995344,0,0,True,False
|
||||
clang-release,baseline,3,1,0.11706917826086283,2.4164160000004813,0.6285514529000585,0,True,False
|
||||
clang-release,rust,3,1,0.10435341577976942,2.276261999999406,1.1857589671201416,0,False,False
|
||||
clang-release,rust,3,2,0.10830164887011051,2.317428000000291,0.30075252204432446,0,True,False
|
||||
clang-release,cpp,3,1,0.09824078809469938,2.686134000000038,0.1411430348749632,0,True,False
|
||||
clickhouse-debug,baseline,0,1,2.8698084373027086,29.167641000000458,0.16808055626276858,0,True,True
|
||||
clickhouse-debug,rust,0,1,1.015455847606063,24.023117999999158,0.20373312853563316,0,True,True
|
||||
clickhouse-debug,cpp,0,1,1.0795358149334788,29.185465000000022,0.33767754568919894,0,True,True
|
||||
clickhouse-debug,rust,1,1,0.9855645261704922,23.52679500000022,0.33808542267489494,0,True,False
|
||||
clickhouse-debug,cpp,1,1,0.9794556694105268,25.7039620000005,0.2409889543621399,0,True,False
|
||||
clickhouse-debug,baseline,1,1,1.0749836983159184,25.802844999999706,0.5089891152280623,0,True,False
|
||||
clickhouse-debug,cpp,2,1,0.9730810839682817,25.81633099999999,0.29151630441042614,0,True,False
|
||||
clickhouse-debug,baseline,2,1,1.0699447533115745,25.504483000000164,0.3603148661673166,0,True,False
|
||||
clickhouse-debug,rust,2,1,1.0110084414482117,23.900710999999774,0.33559462827073605,0,True,False
|
||||
clickhouse-debug,baseline,3,1,1.0476609477773309,25.424891000000116,0.30077383599586033,0,True,False
|
||||
clickhouse-debug,rust,3,1,1.0085525708273053,24.01042899999993,0.07889623052618452,0,True,False
|
||||
clickhouse-debug,cpp,3,1,0.9829497681930661,26.38040799999999,0.3047887179959575,0,True,False
|
||||
clickhouse-release,baseline,0,1,1.0150788761675358,10.482602000000043,0.16491132112004736,0,True,True
|
||||
clickhouse-release,rust,0,1,0.4385751010850072,9.698370999999952,0.20892430710109638,0,True,True
|
||||
clickhouse-release,cpp,0,1,0.40423948876559734,11.344069000000218,0.2620501111939603,0,True,True
|
||||
clickhouse-release,rust,1,1,0.447636597789824,9.677679999999782,0.7423879091988085,0,True,False
|
||||
clickhouse-release,cpp,1,1,0.41158016864210367,11.420591999999942,1.4563578348682253,0,False,False
|
||||
clickhouse-release,cpp,1,2,0.40632509626448154,11.172829999999976,1.8880694416090864,0,False,False
|
||||
clickhouse-release,cpp,1,3,0.4096398241817951,11.395172000000002,1.3300171649669035,0,False,False
|
||||
clickhouse-release,cpp,1,4,0.38933350518345833,11.01515900000004,1.3994197651378883,0,False,False
|
||||
clickhouse-release,cpp,1,5,0.40172909013926983,11.153510000000097,1.3852370031282806,0,False,False
|
||||
clickhouse-release,cpp,1,6,0.41083218809217215,11.364996000000247,1.4482896378127295,0,False,False
|
||||
clickhouse-release,cpp,1,7,0.41875101532787085,11.406629999999495,1.2020747065668158,0,False,False
|
||||
clickhouse-release,cpp,1,8,0.40698036551475525,11.191965000000437,1.2728746760016643,0,False,False
|
||||
clickhouse-release,cpp,1,9,0.4114992255344987,11.52803700000004,3.0181417576532295,0,False,False
|
||||
clickhouse-release,cpp,1,10,0.4000825146213174,11.182590999999775,1.2182711915058388,0,False,False
|
||||
clickhouse-release,cpp,1,11,0.3930873339995742,11.056188999999904,1.2307977318509848,0,False,False
|
||||
clickhouse-release,cpp,1,12,0.42947389278560877,11.791140000000269,1.3711194210478255,0,False,False
|
||||
clickhouse-release,cpp,1,13,0.4119727145880461,11.433865999999853,1.082921239404095,0,False,False
|
||||
clickhouse-release,cpp,1,14,0.40679179038852453,11.30481299999974,1.4139592201040025,0,False,False
|
||||
clickhouse-release,cpp,1,15,0.4079501535743475,11.34216699999979,1.857660764188517,0,False,False
|
||||
clickhouse-release,cpp,1,16,0.4076464865356684,11.258142000000134,1.5009524613285732,0,False,False
|
||||
clickhouse-release,cpp,1,17,0.4062745049595833,11.23962900000015,1.4777471605934855,0,False,False
|
||||
clickhouse-release,cpp,1,18,0.3967941626906395,11.263503000000128,1.5284927409340907,0,False,False
|
||||
clickhouse-release,cpp,1,19,0.3923265840858221,10.991946999999982,0.27541595668800617,0,True,False
|
||||
clickhouse-release,baseline,1,1,0.42354168836027384,9.636438999999882,0.3153432281848097,0,True,False
|
||||
clickhouse-release,cpp,2,1,0.4166426248848438,11.52303000000029,0.16073728316803568,0,True,False
|
||||
clickhouse-release,baseline,2,1,0.4328213632106781,9.61174099999971,0.4349577457871494,0,True,False
|
||||
clickhouse-release,rust,2,1,0.4306311486288905,9.661417000000256,0.2753702363830385,0,True,False
|
||||
clickhouse-release,baseline,3,1,0.443773727864027,9.879930999999942,0.11282551570375006,0,True,False
|
||||
clickhouse-release,rust,3,1,0.4337304374203086,9.588099000000057,0.3502198174953653,0,True,False
|
||||
clickhouse-release,cpp,3,1,0.4134255889803171,11.27687399999968,0.1526901038750744,0,True,False
|
||||
firefox-debug-arm64,baseline,0,1,1.918132908642292,24.778174000000035,0.23555510396905924,0,True,True
|
||||
firefox-debug-arm64,rust,0,1,0.8627032889053226,20.845902999999907,0.25976138398924,0,True,True
|
||||
firefox-debug-arm64,cpp,0,1,0.8613108778372407,22.13659400000006,0.3754811522943793,0,True,True
|
||||
firefox-debug-arm64,rust,1,1,0.894254632294178,20.684133000000656,0.017743263446440272,0,True,False
|
||||
firefox-debug-arm64,cpp,1,1,0.8531553745269775,22.347089999999298,0.3784890818009493,0,True,False
|
||||
firefox-debug-arm64,baseline,1,1,0.9270411655306816,22.186043000000154,0.48968375674668774,0,True,False
|
||||
firefox-debug-arm64,cpp,2,1,0.8781089326366782,22.59698900000012,0.27674356460068444,0,True,False
|
||||
firefox-debug-arm64,baseline,2,1,0.9042491223663092,22.28980700000011,0.3872749134436807,0,True,False
|
||||
firefox-debug-arm64,rust,2,1,0.8783345920965075,20.891679000000295,0.23717727089921098,0,True,False
|
||||
firefox-debug-arm64,baseline,3,1,0.949737899005413,22.733854999999494,0.3960513735403917,0,True,False
|
||||
firefox-debug-arm64,rust,3,1,0.9135586991906166,20.832100999999966,0.29324771547520695,0,True,False
|
||||
firefox-debug-arm64,cpp,3,1,0.8538148701190948,22.07092300000022,0.12775251816186023,0,True,False
|
||||
firefox-release,baseline,0,1,0.3293273588642478,3.5535739999995712,0,0,True,True
|
||||
firefox-release,rust,0,1,0.1994827138260007,3.1717969999999696,0.341899309359122,0,True,True
|
||||
firefox-release,cpp,0,1,0.1958952397108078,3.7346800000004805,0.6907774020831671,0,True,True
|
||||
firefox-release,rust,1,1,0.20268804300576448,3.3138609999996333,0.2769724177393189,0,True,False
|
||||
firefox-release,cpp,1,1,0.207690067589283,3.9152460000004794,0.1191871999593501,0,True,False
|
||||
firefox-release,baseline,1,1,0.2066859444603324,3.360535999999229,0.19093702096529883,0,True,False
|
||||
firefox-release,cpp,2,1,0.20215343590825796,3.7760080000007292,0.31655161257813075,0,True,False
|
||||
firefox-release,baseline,2,1,0.2099634725600481,3.3132729999997537,0.31780291051993287,0,True,False
|
||||
firefox-release,rust,2,1,0.19879572000354528,3.2787530000005063,0,0,True,False
|
||||
firefox-release,baseline,3,1,0.21790252719074488,3.384371000000101,0.07172472348657852,0,True,False
|
||||
firefox-release,rust,3,1,0.20053099747747183,3.2690399999992223,0.503463335041214,0,True,False
|
||||
firefox-release,cpp,3,1,0.198543562553823,3.8579939999999624,0.5137713838818347,0,True,False
|
||||
godot-debug,baseline,0,1,1.0096471989527345,10.559729000000516,0,0,True,True
|
||||
godot-debug,rust,0,1,0.38360483571887016,8.995890000000145,0,0,True,True
|
||||
godot-debug,cpp,0,1,0.3701927177608013,10.346949999999197,0.22434260783174698,0,True,True
|
||||
godot-debug,rust,1,1,0.37932420428842306,8.848916000000827,0.3192097879285677,0,True,False
|
||||
godot-debug,cpp,1,1,0.3630478670820594,10.279172999998991,0.4429911650754072,0,True,False
|
||||
godot-debug,baseline,1,1,0.46139616798609495,9.1871000000001,0.3964055404187982,0,True,False
|
||||
godot-debug,cpp,2,1,0.3664185646921396,10.355592000000797,0.09390354067938221,0,True,False
|
||||
godot-debug,baseline,2,1,0.46144085843116045,9.231319999999869,0.4305644017484171,0,True,False
|
||||
godot-debug,rust,2,1,0.3782152524217963,8.992639999999483,0.4160593669948264,0,True,False
|
||||
godot-debug,baseline,3,1,0.4647850962355733,9.320684999999685,0.02004151657964223,0,True,False
|
||||
godot-debug,rust,3,1,0.3713307501748204,8.926579000000856,0.5478162058638641,0,True,False
|
||||
godot-debug,cpp,3,1,0.3682374721392989,10.437176000000363,0.3607020206987912,0,True,False
|
||||
libmergedlo.so-debug,baseline,0,1,0.9755140347406268,12.50908999999956,0,0,True,True
|
||||
libmergedlo.so-debug,rust,0,1,0.46374180261045694,11.871447999999873,0.3850245891459529,0,True,True
|
||||
libmergedlo.so-debug,cpp,0,1,0.4302643248811364,12.086037999999462,0.6367295254612975,0,True,True
|
||||
libmergedlo.so-debug,rust,1,1,0.4602639880031347,11.529498000000785,0.17490397141959296,0,True,False
|
||||
libmergedlo.so-debug,cpp,1,1,0.4357814369723201,12.327054999999746,0.21328352679303636,0,True,False
|
||||
libmergedlo.so-debug,baseline,1,1,0.4614560306072235,11.545070000000123,0.40075323423181697,0,True,False
|
||||
libmergedlo.so-debug,cpp,2,1,0.43142488691955805,12.139191999999639,0.0714098855271758,0,True,False
|
||||
libmergedlo.so-debug,baseline,2,1,0.46444262843579054,11.81170800000018,0.3192902455860391,0,True,False
|
||||
libmergedlo.so-debug,rust,2,1,0.4639630224555731,11.65147799999977,0.12613505400617028,0,True,False
|
||||
libmergedlo.so-debug,baseline,3,1,0.48540631495416164,12.112609000000703,0.180036791522174,0,True,False
|
||||
libmergedlo.so-debug,rust,3,1,0.44702763948589563,11.320541999999477,0.06589749467061343,0,True,False
|
||||
libmergedlo.so-debug,cpp,3,1,0.43390912003815174,12.145950000000084,0.3319819576347698,0,True,False
|
||||
libmergedlo.so-release,baseline,0,1,0.2733275359496474,3.9860200000002806,0.5633534142865583,0,True,True
|
||||
libmergedlo.so-release,rust,0,1,0.19092033430933952,4.159447,0.736186666437618,0,True,True
|
||||
libmergedlo.so-release,cpp,0,1,0.18232943397015333,4.8121689999998125,0.7559448440584835,0,True,True
|
||||
libmergedlo.so-release,rust,1,1,0.18545596208423376,4.097788999999466,0.49721236556669046,0,True,False
|
||||
libmergedlo.so-release,cpp,1,1,0.18935968447476625,5.03205700000035,0.570042144372504,0,True,False
|
||||
libmergedlo.so-release,baseline,1,1,0.1825580419972539,4.040801000000101,0.5433833509095003,0,True,False
|
||||
libmergedlo.so-release,cpp,2,1,0.1769801415503025,4.658274999999776,0.6312854925372727,0,True,False
|
||||
libmergedlo.so-release,baseline,2,1,0.18649667501449585,4.105102999999872,0.6697009700722301,0,True,False
|
||||
libmergedlo.so-release,rust,2,1,0.18541520275175571,4.0798750000003565,0.27033922751718836,0,True,False
|
||||
libmergedlo.so-release,baseline,3,1,0.1898724427446723,4.031877999999779,0.6221123866244651,0,True,False
|
||||
libmergedlo.so-release,rust,3,1,0.1915513901039958,4.229150000000118,0.6309011590769797,0,True,False
|
||||
libmergedlo.so-release,cpp,3,1,0.19219235517084599,5.13889300000028,0.36997830028452594,0,True,False
|
||||
pytorch-debug,baseline,0,1,1.8228521086275578,22.076780999999755,0.007251820410102002,0,True,True
|
||||
pytorch-debug,rust,0,1,0.7198729394003749,19.23332500000015,0.3565559809760492,0,True,True
|
||||
pytorch-debug,cpp,0,1,0.7669839849695563,21.320928999999524,0.350816968019032,0,True,True
|
||||
pytorch-debug,rust,1,1,0.7340596420690417,19.226293000000624,0.2638845445731726,0,True,False
|
||||
pytorch-debug,cpp,1,1,0.7830084953457117,21.64299200000005,0.35377394170645143,0,True,False
|
||||
pytorch-debug,baseline,1,1,0.7775108329951763,20.132356000000073,0.31850873932851337,0,True,False
|
||||
pytorch-debug,cpp,2,1,0.7822334803640842,22.046070999999756,0.1584296727878751,0,True,False
|
||||
pytorch-debug,baseline,2,1,0.7786298748105764,20.01030400000036,0.2307848740830424,0,True,False
|
||||
pytorch-debug,rust,2,1,0.7639294769614935,19.39953399999922,0.3933164049717324,0,True,False
|
||||
pytorch-debug,baseline,3,1,0.7917701061815023,19.908642999999756,0.254312454880734,0,True,False
|
||||
pytorch-debug,rust,3,1,0.766916636377573,19.134108000000197,0.19023188026069793,0,True,False
|
||||
pytorch-debug,cpp,3,1,0.8385022832080722,21.878083000000515,0.2646587921508768,0,True,False
|
||||
pytorch-release,baseline,0,1,0.3241864172741771,3.433455999999751,0,0,True,True
|
||||
pytorch-release,rust,0,1,0.14879126474261284,3.1791199999997843,0.8124132638370146,0,True,True
|
||||
pytorch-release,cpp,0,1,0.14006427489221096,3.589034999999967,0.2210770543855923,0,True,True
|
||||
pytorch-release,rust,1,1,0.14491580054163933,3.069621000000552,0,0,True,False
|
||||
pytorch-release,cpp,1,1,0.1403951533138752,3.5616739999995843,0.4154416726039667,0,True,False
|
||||
pytorch-release,baseline,1,1,0.15012622438371181,3.174079999999776,0,0,True,False
|
||||
pytorch-release,cpp,2,1,0.1453827191144228,3.7235810000001948,0,0,True,False
|
||||
pytorch-release,baseline,2,1,0.1497138887643814,3.091787000000295,0,0,True,False
|
||||
pytorch-release,rust,2,1,0.14531308971345425,3.0822849999995015,1.2229799687335512,0,False,False
|
||||
pytorch-release,rust,2,2,0.15378016140311956,3.3792940000003,0.004590938290653231,0,True,False
|
||||
pytorch-release,baseline,3,1,0.14661182090640068,3.0583299999998417,0.42063460086142046,0,True,False
|
||||
pytorch-release,rust,3,1,0.14672112930566072,3.1460509999997157,0.16322803608286868,0,True,False
|
||||
pytorch-release,cpp,3,1,0.1371642965823412,3.438648000000285,0.009856775044355514,0,True,False
|
||||
tensorflow-debug,baseline,0,1,2.9069930287078023,63.78613000000041,0.29716961478007864,0,True,True
|
||||
tensorflow-debug,rust,0,1,2.4080017264932394,55.904726999999184,0.36763802816974944,0,True,True
|
||||
tensorflow-debug,cpp,0,1,2.1706328811123967,57.10918600000059,0.5347813595649465,0,True,True
|
||||
tensorflow-debug,rust,1,1,2.2639403883367777,54.93097300000045,0.3794388772473697,0,True,False
|
||||
tensorflow-debug,cpp,1,1,2.1512821447104216,57.437504999999874,0.3033051697705858,0,True,False
|
||||
tensorflow-debug,baseline,1,1,3.13249558955431,70.32438300000013,0.3593355427964894,0,True,False
|
||||
tensorflow-debug,cpp,2,1,2.148849952965975,57.452322999999524,0.37586477213055675,0,True,False
|
||||
tensorflow-debug,baseline,2,1,2.9270654441788793,64.12947500000064,0.3384020698059936,0,True,False
|
||||
tensorflow-debug,rust,2,1,2.2812811406329274,55.30753099999947,0.3210779180435603,0,True,False
|
||||
tensorflow-debug,baseline,3,1,2.9668450811877847,63.20316199999979,0.35621610464774806,0,True,False
|
||||
tensorflow-debug,rust,3,1,2.2988718012347817,55.580535000000054,0.3695138629503626,0,True,False
|
||||
tensorflow-debug,cpp,3,1,2.130319473333657,57.6323510000002,0.5293332817115093,0,True,False
|
||||
tensorflow-release,baseline,0,1,0.8549841567873955,11.427674999999908,0.06119996932928922,0,True,True
|
||||
tensorflow-release,rust,0,1,0.5112397819757462,9.778651999999965,0.3938425838921339,0,True,True
|
||||
tensorflow-release,cpp,0,1,0.47740479558706284,10.922763999999916,0.20367621812170777,0,True,True
|
||||
tensorflow-release,rust,1,1,0.49792977422475815,9.837475000000268,0.5874824330133734,0,True,False
|
||||
tensorflow-release,cpp,1,1,0.4793505519628525,10.971779999999853,0.45524094365259427,0,True,False
|
||||
tensorflow-release,baseline,1,1,0.6376465829089284,11.140965999999935,0.1082637353425519,0,True,False
|
||||
tensorflow-release,cpp,2,1,0.4620536705479026,10.926555000000008,0.3753784734237426,0,True,False
|
||||
tensorflow-release,baseline,2,1,0.6421017926186323,11.25173199999972,0.5735352384966468,0,True,False
|
||||
tensorflow-release,rust,2,1,0.4924679324030876,9.697301000000152,0.025786452392849817,0,True,False
|
||||
tensorflow-release,baseline,3,1,0.6392052136361599,11.131937000000107,0.2472805223328383,0,True,False
|
||||
tensorflow-release,rust,3,1,0.499392494559288,9.701866000000336,0.23655541800230603,0,True,False
|
||||
tensorflow-release,cpp,3,1,0.4799237083643675,10.975648999999976,0.34245234759540605,0,True,False
|
||||
|
@@ -0,0 +1,69 @@
|
||||
# Release performance comparison
|
||||
|
||||
Both Rust linkers use the standard all-target Cargo release build, with Rust
|
||||
1.97.1 and the repository's default release profile. The baseline is Rust
|
||||
commit `b11bcd8e6db919f3e6d26697e3b3dd09b133a890`; the updated build contains
|
||||
the changes documented in [upstream-sync.md](upstream-sync.md). The C++
|
||||
reference is `dad2695f3e62d0dd6024b0b795dc2f940907be12`, built by GCC 13.3
|
||||
with CMake `Release` (`-O3 -DNDEBUG`), mimalloc enabled and LTO disabled.
|
||||
|
||||
Measurements were taken on September 9, 2026, on an AMD Ryzen Threadripper
|
||||
7980X (64 cores, 128 hardware threads). Each linker used 32 worker threads,
|
||||
which is the default for both implementations on this machine. Workload
|
||||
names containing `debug` or `release` describe the input application; every
|
||||
linker binary measured here is a release build.
|
||||
|
||||
Each invocation ran in its workload directory under `~/bench`:
|
||||
|
||||
```sh
|
||||
LINKER @rsp --no-fork --threads=32 -o TEMPORARY_OUTPUT
|
||||
```
|
||||
|
||||
The output was removed after each invocation. `--no-fork` includes process
|
||||
teardown in the elapsed time and permits complete child CPU accounting,
|
||||
following the C++ benchmark suite's protocol. Each workload had one warm-up
|
||||
per linker followed by three measured rounds, rotating linker order. The
|
||||
table reports medians with warm input caches.
|
||||
|
||||
Before each invocation, the harness sampled machine CPU and disk activity
|
||||
for two seconds. It waited if CPU activity exceeded 1.5 cores or either the
|
||||
input or output filesystem's device was busy for more than 5% of that
|
||||
interval, checking again after 15 seconds. Inactive dirty pages did not
|
||||
delay a run. During each invocation it estimated other CPU activity from
|
||||
host busy time minus linker CPU time, discarding and retrying samples
|
||||
exceeding one other active core. Earlier measurements taken with different
|
||||
activity checks are excluded.
|
||||
|
||||
Across the 21 workloads, the geometric mean Rust/C++ time ratio is **1.029**, and updated/baseline Rust is **0.939**. The sum of median Rust times divided by the C++ sum is 1.032.
|
||||
|
||||
Lower ratios indicate faster Rust links. These measurements describe this host and these workloads.
|
||||
|
||||
The largest remaining gap is `chrome-debug-arm64`, where Rust takes 9.6% longer than C++.
|
||||
|
||||
| Workload | Baseline Rust (s) | Updated Rust (s) | C++ (s) | Rust / C++ |
|
||||
| --- | ---: | ---: | ---: | ---: |
|
||||
| clang-debug | 1.281 | 1.229 | 1.343 | 0.915 |
|
||||
| chrome-debug-x86_64 | 1.848 | 1.832 | 1.719 | 1.066 |
|
||||
| chrome-debug-arm64 | 2.035 | 2.002 | 1.828 | 1.096 |
|
||||
| firefox-debug | 0.849 | 0.770 | 0.750 | 1.027 |
|
||||
| godot-release | 0.093 | 0.089 | 0.084 | 1.068 |
|
||||
| blender-debug | 0.855 | 0.777 | 0.838 | 0.926 |
|
||||
| blender-release | 0.189 | 0.187 | 0.182 | 1.028 |
|
||||
| chrome-release-x86_64 | 0.741 | 0.700 | 0.668 | 1.047 |
|
||||
| chrome-release-arm64 | 0.850 | 0.817 | 0.774 | 1.056 |
|
||||
| clang-release | 0.111 | 0.109 | 0.100 | 1.089 |
|
||||
| clickhouse-debug | 1.070 | 1.009 | 0.979 | 1.030 |
|
||||
| clickhouse-release | 0.433 | 0.434 | 0.413 | 1.049 |
|
||||
| firefox-debug-arm64 | 0.927 | 0.894 | 0.854 | 1.047 |
|
||||
| firefox-release | 0.210 | 0.201 | 0.202 | 0.992 |
|
||||
| godot-debug | 0.461 | 0.378 | 0.366 | 1.032 |
|
||||
| libmergedlo.so-debug | 0.464 | 0.460 | 0.434 | 1.061 |
|
||||
| libmergedlo.so-release | 0.186 | 0.185 | 0.189 | 0.979 |
|
||||
| pytorch-debug | 0.779 | 0.764 | 0.783 | 0.976 |
|
||||
| pytorch-release | 0.150 | 0.147 | 0.140 | 1.045 |
|
||||
| tensorflow-debug | 2.967 | 2.281 | 2.149 | 1.062 |
|
||||
| tensorflow-release | 0.639 | 0.498 | 0.479 | 1.039 |
|
||||
|
||||
The [raw samples](upstream-sync-benchmark.csv) include warm-ups and rejected runs; only accepted, non-warm-up samples enter the medians.
|
||||
|
||||
The final run contains 189 accepted measurements, 63 accepted warm-ups, and 55 rejected attempts.
|
||||
@@ -0,0 +1,71 @@
|
||||
# Upstream synchronization
|
||||
|
||||
The Rust port incorporates the applicable C++ mold changes from
|
||||
`92eb17a07c60a88b3a1a8c9cef014ac84eaf0cf9` through
|
||||
`dad2695f3e62d0dd6024b0b795dc2f940907be12` (September 8, 2026).
|
||||
|
||||
| Upstream commits | Rust implementation |
|
||||
| --- | --- |
|
||||
| `60606576`, `18d17369` | Cache dynamic symbol names and string offsets; compute offsets by block prefix sums and write both dynamic tables in parallel. |
|
||||
| `cbcf79c0`, `85663581` | Stable parallel symbol partitioning and contiguous GNU hash sort keys. |
|
||||
| `07061983`, `9887339f`, `41d2f376` | Allocate auxiliary records by input file in first-use order, initialize records and assign indices in parallel, prefetch symbols and records, and inline small table insertion functions. |
|
||||
| `477db5f3`, `95137b23`, `ec7c8032` | Parallel PLT string sizing, defined-symbol version indices, and version conflict checks. |
|
||||
| `046a6449` | Evaluate the GNU symbol hash four bytes at a time. |
|
||||
| `2c402f70` | Cache consecutive debug-relocation symbol lookups and try the next string fragment before binary search; prefetch subsequent fragments. Each relocation stream owns its cache. |
|
||||
| `0f582b0a`, `f585fee6` | Hash fragments while splitting input; merge and lay out non-allocated sections in the background. The background task owns its metadata and publishes it before output section sorting. |
|
||||
| `f8cb3daf` | Stop pattern matching once its maximum priority is found; share priorities between consecutive patterns assigning the same version. |
|
||||
| `84188021` | Keep flat symbol-map shards, but mix out the fixed shard-selection bits before bucket placement and compare cached hashes before strings. Preserve hashbrown's high-bit lookup tags. |
|
||||
| `f9bdf4b4` | Create fragment-symbol records while reattaching relocations, then publish exactly those symbols to the central vector. Remove the preliminary relocation counting pass. |
|
||||
| `3a58944f`, `53715c07` | Scan global names and version separators together; reuse cached version flags for ordinary COMDAT signatures. |
|
||||
| `91252d8a`, `ff0cb501` | Skip redundant COMDAT updates on newly parsed files; size the CREL section table once. |
|
||||
| `fcd9dea7` | Test Debian 12 in CI. |
|
||||
|
||||
The remaining commits already have appropriate Rust equivalents:
|
||||
|
||||
- `95dbca67`: GC roots are collected in local vectors by Rayon workers.
|
||||
- `fa1e8edc`: `Symbol` already stores exact `u32` name lengths, and remains 48 bytes.
|
||||
- `e9de4def`: One parallel iteration already writes the disjoint symbol-table ranges belonging to chunks, objects, and shared libraries.
|
||||
- `4a11b784`: Rust uses vectors and mimalloc; the C++ thread-local arena block size has no corresponding allocator setting here.
|
||||
- `dad2695f`: Rust formats temporary output names without the C++ string expression that triggered `-Wrestrict`.
|
||||
|
||||
The shell test cases and their file modes remain identical to this upstream
|
||||
revision. Rust unit tests additionally cover the partition, hash, pattern
|
||||
priority, and fragment-hint changes.
|
||||
|
||||
Rust-specific details preserve performance without changing the upstream
|
||||
algorithms. Fragment reattachment keeps compact temporary records before
|
||||
constructing the final symbols, and retains a cheap CREL-header capacity
|
||||
estimate to avoid moving the central symbol vector. It does not scan
|
||||
relocations to count fragment symbols.
|
||||
|
||||
Auxiliary symbol records use four-byte optional indices, matching C++'s
|
||||
64-byte record size. Per-file allocation preserves first-use order without
|
||||
sorting global symbol IDs; the owning-file filter guarantees disjoint writes.
|
||||
A unit test covers duplicate IDs and preservation of existing records.
|
||||
GC avoids repeated writes when a shared library is
|
||||
already marked reachable and allocates child-work buffers lazily. The
|
||||
literal-pattern matcher inlines each byte transition. ICF feeds complete
|
||||
digest words to SipHash directly; the byte order and hash are unchanged.
|
||||
A unit test compares word updates with byte updates, including partial
|
||||
buffers and length wraparound.
|
||||
|
||||
Symbol address calculation inlines its common path and keeps rare
|
||||
discarded-section diagnostics out of line. PLT checks read the auxiliary
|
||||
record once. This preserves address and diagnostic behavior while reducing
|
||||
the cost of relocation and symbol-table writes.
|
||||
|
||||
Validation on September 9, 2026:
|
||||
|
||||
- The standard release build compiles all 20 linker targets.
|
||||
- The full integration run passed 5,447 cases, skipped 649, and failed none
|
||||
across the 14 available target configurations. Compiler/QEMU combinations
|
||||
for `aarch64_be`, `armeb`, `riscv32`, and `sh4aeb` were unavailable.
|
||||
- All 20 Rust library unit tests pass in release mode.
|
||||
- Workspace Clippy with all targets and `-D warnings`, formatting, and
|
||||
whitespace checks pass.
|
||||
|
||||
The [release performance comparison](upstream-sync-performance.md) covers 21
|
||||
workloads. Updated Rust takes 6.1% less time than the original Rust build by
|
||||
geometric mean and 2.9% more time than C++. The largest remaining C++ gap is
|
||||
9.5% on Chromium ARM64 debug. The report includes the build configuration,
|
||||
noise checks, and raw samples.
|
||||
@@ -1,56 +0,0 @@
|
||||
#!/bin/sh
|
||||
# This script installs binary packages needed to build mold.
|
||||
# Feel free to send me a PR if your OS is not on this list.
|
||||
|
||||
set -e
|
||||
. /etc/os-release
|
||||
|
||||
set -x
|
||||
|
||||
case "$ID" in
|
||||
ubuntu | pop | linuxmint | debian | raspbian | neon | zorin)
|
||||
apt-get update
|
||||
apt-get install -y cmake gcc g++ clang gdb
|
||||
;;
|
||||
fedora | fedora-* | amzn | rhel | centos)
|
||||
dnf install -y gcc-c++ cmake glibc-static libstdc++-static diffutils util-linux tar
|
||||
;;
|
||||
rocky | ol)
|
||||
dnf install -y gcc-c++ cmake diffutils util-linux
|
||||
;;
|
||||
opensuse-*)
|
||||
zypper install -y make cmake gcc-c++ glibc-devel-static tar diffutils util-linux gawk
|
||||
;;
|
||||
gentoo)
|
||||
emerge-webrsync
|
||||
FEATURES='getbinpkg binpkg-request-signature' emerge dev-build/cmake
|
||||
;;
|
||||
arch | archarm | artix | endeavouros | manjaro | cachyos)
|
||||
pacman -Sy --needed --noconfirm base-devel cmake util-linux
|
||||
;;
|
||||
void)
|
||||
xbps-install -Sy xbps bash make cmake gcc tar diffutils util-linux
|
||||
;;
|
||||
alpine)
|
||||
apk update
|
||||
apk add bash make linux-headers cmake gcc g++
|
||||
;;
|
||||
clear-linux-os)
|
||||
swupd update
|
||||
swupd bundle-add c-basic diffutils
|
||||
;;
|
||||
almalinux)
|
||||
dnf install -y gcc-toolset-13-gcc-c++ gcc-toolset-13-libstdc++-devel cmake diffutils
|
||||
;;
|
||||
altlinux)
|
||||
apt-get update
|
||||
apt-get install -y gcc-c++ make cmake ctest diffutils util-linux
|
||||
;;
|
||||
freebsd)
|
||||
pkg update
|
||||
pkg install -y cmake bash binutils gcc
|
||||
;;
|
||||
*)
|
||||
echo "Error: don't know anything about build dependencies on $ID-$VERSION_ID"
|
||||
exit 1
|
||||
esac
|
||||
@@ -1,19 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
. /etc/os-release
|
||||
|
||||
set -x
|
||||
|
||||
# This script install packages for -DMOLD_ENABLE_QEMU_TESTS=1
|
||||
# to enable cross-target tests.
|
||||
#
|
||||
# Feel free to send me a PR if your OS is not on this list.
|
||||
|
||||
case "$ID" in
|
||||
ubuntu | pop | linuxmint | debian | raspbian)
|
||||
apt-get install -y qemu-user {gcc,g++}-{i686,aarch64,riscv64,powerpc,powerpc64,powerpc64le,s390x,sparc64,m68k,sh4}-linux-gnu {gcc,g++}-arm-linux-gnueabihf
|
||||
;;
|
||||
*)
|
||||
echo "Error: don't know anything about build dependencies on $ID-$VERSION_ID"
|
||||
exit 1
|
||||
esac
|
||||
Executable
+30
@@ -0,0 +1,30 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
PREFIX=${PREFIX:-/usr/local}
|
||||
|
||||
srcdir=$(CDPATH= cd "$(dirname "$0")" && pwd)
|
||||
artifact_dir="${CARGO_TARGET_DIR:-$srcdir/target}/release"
|
||||
|
||||
if [ ! -x "$artifact_dir/mold" ] ||
|
||||
[ ! -f "$artifact_dir/mold-wrapper.so" ]; then
|
||||
echo "install-mold.sh: release artifacts are missing" >&2
|
||||
echo "Run 'cargo build --release' first." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
bindir="$DESTDIR$PREFIX/bin"
|
||||
libdir="$DESTDIR$PREFIX/lib/mold"
|
||||
libexecdir="$DESTDIR$PREFIX/libexec/mold"
|
||||
mandir="$DESTDIR$PREFIX/share/man/man1"
|
||||
docdir="$DESTDIR$PREFIX/share/doc/mold"
|
||||
|
||||
install -d "$bindir" "$libdir" "$libexecdir" "$mandir" "$docdir"
|
||||
install -m 755 "$artifact_dir/mold" "$bindir"
|
||||
install -m 755 "$artifact_dir/mold-wrapper.so" "$libdir"
|
||||
install -m 644 "$srcdir/docs/mold.1" "$mandir"
|
||||
install -m 644 "$srcdir/LICENSE" "$docdir"
|
||||
|
||||
ln -sf mold "$bindir/ld.mold"
|
||||
ln -sf ../../bin/mold "$libexecdir/ld"
|
||||
ln -sf mold.1 "$mandir/ld.mold.1"
|
||||
Executable
+68
@@ -0,0 +1,68 @@
|
||||
#!/bin/sh
|
||||
# This script installs binary packages needed to test mold.
|
||||
# Feel free to send me a PR if your OS is not on this list.
|
||||
|
||||
set -e
|
||||
. /etc/os-release
|
||||
|
||||
set -x
|
||||
|
||||
case "$ID" in
|
||||
ubuntu | pop | linuxmint | debian | raspbian | neon | zorin)
|
||||
apt-get update
|
||||
apt-get install -y curl gcc g++ clang gdb
|
||||
apt-get install -y qemu-user \
|
||||
gcc-i686-linux-gnu g++-i686-linux-gnu \
|
||||
gcc-aarch64-linux-gnu g++-aarch64-linux-gnu \
|
||||
gcc-riscv64-linux-gnu g++-riscv64-linux-gnu \
|
||||
gcc-powerpc-linux-gnu g++-powerpc-linux-gnu \
|
||||
gcc-powerpc64-linux-gnu g++-powerpc64-linux-gnu \
|
||||
gcc-powerpc64le-linux-gnu g++-powerpc64le-linux-gnu \
|
||||
gcc-s390x-linux-gnu g++-s390x-linux-gnu \
|
||||
gcc-sparc64-linux-gnu g++-sparc64-linux-gnu \
|
||||
gcc-m68k-linux-gnu g++-m68k-linux-gnu \
|
||||
gcc-sh4-linux-gnu g++-sh4-linux-gnu \
|
||||
gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf
|
||||
;;
|
||||
fedora | fedora-* | amzn | rhel | centos)
|
||||
dnf install -y curl gcc-c++ glibc-static libstdc++-static diffutils util-linux tar
|
||||
;;
|
||||
rocky | ol)
|
||||
dnf install -y curl gcc-c++ diffutils util-linux
|
||||
;;
|
||||
opensuse-*)
|
||||
zypper install -y bash curl gcc-c++ glibc-devel-static tar diffutils util-linux gawk
|
||||
;;
|
||||
gentoo)
|
||||
emerge-webrsync
|
||||
FEATURES='getbinpkg binpkg-request-signature' emerge net-misc/curl
|
||||
;;
|
||||
arch | archarm | artix | endeavouros | manjaro | cachyos)
|
||||
pacman -Sy --needed --noconfirm base-devel curl util-linux
|
||||
;;
|
||||
void)
|
||||
xbps-install -Sy xbps bash curl gcc tar diffutils util-linux
|
||||
;;
|
||||
alpine)
|
||||
apk update
|
||||
apk add bash curl linux-headers gcc g++
|
||||
;;
|
||||
clear-linux-os)
|
||||
swupd update
|
||||
swupd bundle-add c-basic diffutils
|
||||
;;
|
||||
almalinux)
|
||||
dnf install -y curl gcc-toolset-13-gcc-c++ gcc-toolset-13-libstdc++-devel diffutils
|
||||
;;
|
||||
altlinux)
|
||||
apt-get update
|
||||
apt-get install -y curl gcc-c++ diffutils util-linux
|
||||
;;
|
||||
freebsd)
|
||||
pkg update
|
||||
pkg install -y bash binutils curl gcc
|
||||
;;
|
||||
*)
|
||||
echo "Error: don't know anything about test dependencies on $ID-$VERSION_ID"
|
||||
exit 1
|
||||
esac
|
||||
@@ -1,146 +0,0 @@
|
||||
// This file implements the Aho-Corasick algorithm to search multiple
|
||||
// strings within an input string simultaneously. It is essentially a
|
||||
// trie with additional links. For details, see
|
||||
// https://en.wikipedia.org/wiki/Aho-Corasick_algorithm.
|
||||
//
|
||||
// We use it for simple glob patterns in version scripts or dynamic
|
||||
// list files. Here are some examples of glob patterns:
|
||||
//
|
||||
// qt_private_api_tag*
|
||||
// *16QAccessibleCache*
|
||||
// *32QAbstractFileIconProviderPrivate*
|
||||
// *17QPixmapIconEngine*
|
||||
//
|
||||
// Aho-Corasick can do only substring search, so it cannot handle
|
||||
// complex glob patterns such as `*foo*bar*`. We handle such patterns
|
||||
// with the Glob class.
|
||||
|
||||
#include "lib.h"
|
||||
|
||||
#include <queue>
|
||||
|
||||
namespace mold {
|
||||
|
||||
bool AhoCorasick::can_handle(std::string_view pat) {
|
||||
if (pat.starts_with('*'))
|
||||
pat.remove_prefix(1);
|
||||
if (pat.ends_with('*'))
|
||||
pat.remove_suffix(1);
|
||||
return !pat.empty() && pat.find_first_of("*?[\\") == pat.npos;
|
||||
}
|
||||
|
||||
i32 AhoCorasick::find_child(i32 node, u8 ch) const {
|
||||
if (node == 0)
|
||||
return root_children[ch];
|
||||
|
||||
for (i32 i = nodes[node].first_child; i != -1; i = nodes[i].next_sibling)
|
||||
if (nodes[i].ch == ch)
|
||||
return i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
i32 AhoCorasick::add_child(i32 node, u8 ch) {
|
||||
if (i32 child = find_child(node, ch); child != -1)
|
||||
return child;
|
||||
|
||||
i32 child = nodes.size();
|
||||
i32 sibling = nodes[node].first_child;
|
||||
nodes.emplace_back();
|
||||
nodes[child].next_sibling = sibling;
|
||||
nodes[child].ch = ch;
|
||||
nodes[node].first_child = child;
|
||||
|
||||
if (node == 0)
|
||||
root_children[ch] = child;
|
||||
return child;
|
||||
}
|
||||
|
||||
i64 AhoCorasick::find(std::string_view str) {
|
||||
if (nodes.empty())
|
||||
return -1;
|
||||
|
||||
i32 idx = 0;
|
||||
i64 val = -1;
|
||||
|
||||
auto walk = [&](u8 c) {
|
||||
for (i32 j = idx; j != -1; j = nodes[j].suffix_link) {
|
||||
if (i32 child = find_child(j, c); child != -1) {
|
||||
idx = child;
|
||||
val = std::max(val, nodes[child].value);
|
||||
return;
|
||||
}
|
||||
}
|
||||
idx = 0;
|
||||
};
|
||||
|
||||
walk('\0');
|
||||
for (u8 c : str) {
|
||||
walk(c);
|
||||
if (val == max_value)
|
||||
return val;
|
||||
}
|
||||
walk('\0');
|
||||
return val;
|
||||
}
|
||||
|
||||
bool AhoCorasick::add(std::string_view pat, i64 val) {
|
||||
assert(can_handle(pat));
|
||||
max_value = std::max(max_value, val);
|
||||
if (nodes.empty()) {
|
||||
root_children.fill(-1);
|
||||
nodes.emplace_back();
|
||||
}
|
||||
i32 idx = 0;
|
||||
|
||||
auto walk = [&](u8 c) { idx = add_child(idx, c); };
|
||||
|
||||
// We handle "foo" as if "\0foo\0", "*foo" as if "foo\0", "foo*" as
|
||||
// if "\0foo", and "*foo*" as if "foo". Aho-Corasick can do only
|
||||
// substring matching, so we use \0 as a beginning/end-of-string
|
||||
// markers.
|
||||
if (!pat.starts_with('*'))
|
||||
walk('\0');
|
||||
for (u8 c : pat)
|
||||
if (c != '*')
|
||||
walk(c);
|
||||
if (!pat.ends_with('*'))
|
||||
walk('\0');
|
||||
|
||||
nodes[idx].value = std::max(nodes[idx].value, val);
|
||||
return true;
|
||||
}
|
||||
|
||||
void AhoCorasick::compile() {
|
||||
if (nodes.empty())
|
||||
return;
|
||||
|
||||
// A failure link may refer to any node at the previous depth, so failure
|
||||
// links must be constructed breadth-first.
|
||||
std::queue<i32> queue;
|
||||
for (i32 child = nodes[0].first_child; child != -1;
|
||||
child = nodes[child].next_sibling) {
|
||||
nodes[child].suffix_link = 0;
|
||||
queue.push(child);
|
||||
}
|
||||
|
||||
while (!queue.empty()) {
|
||||
i32 idx = queue.front();
|
||||
queue.pop();
|
||||
|
||||
for (i32 child = nodes[idx].first_child; child != -1;
|
||||
child = nodes[child].next_sibling) {
|
||||
i32 suffix = nodes[idx].suffix_link;
|
||||
while (suffix != 0 && find_child(suffix, nodes[child].ch) == -1)
|
||||
suffix = nodes[suffix].suffix_link;
|
||||
|
||||
if (i32 next = find_child(suffix, nodes[child].ch); next != -1)
|
||||
suffix = next;
|
||||
|
||||
nodes[child].suffix_link = suffix;
|
||||
nodes[child].value = std::max(nodes[child].value, nodes[suffix].value);
|
||||
queue.push(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
-134
@@ -1,134 +0,0 @@
|
||||
#include "lib.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
# include <windows.h>
|
||||
#endif
|
||||
|
||||
namespace mold {
|
||||
|
||||
static constexpr u32 BLOCK_SIZE = 1024 * 1024;
|
||||
static constexpr u32 BLOCK_ALIGNMENT = 64;
|
||||
static constexpr u32 MAX_LOCAL_ALLOC = BLOCK_SIZE / 4;
|
||||
static constexpr u32 NUM_ARENA_SLOTS = 10;
|
||||
|
||||
// Reserved address space is made accessible in chunks of this size.
|
||||
static constexpr u64 COMMIT_CHUNK = 64 * 1024 * 1024;
|
||||
|
||||
static std::atomic<u32> next_arena_slot;
|
||||
static thread_local u32 positions[NUM_ARENA_SLOTS];
|
||||
static thread_local u32 ends[NUM_ARENA_SLOTS];
|
||||
|
||||
[[noreturn]]
|
||||
static void memory_error(std::string_view action, u64 size) {
|
||||
std::cerr << "mold: cannot " << action << ' ' << size
|
||||
<< " bytes of virtual memory\n";
|
||||
std::exit(1);
|
||||
}
|
||||
|
||||
ArenaResource::ArenaResource() : arena_slot(next_arena_slot++) {
|
||||
assert(arena_slot < NUM_ARENA_SLOTS);
|
||||
|
||||
// Reserve address space without making it accessible. An inaccessible
|
||||
// mapping is not charged against the system's commit limit even where
|
||||
// overcommit is disabled, and fork() does not charge it either. Pages
|
||||
// are made accessible by commit() as the arena grows.
|
||||
#ifdef _WIN32
|
||||
data = (u8 *)VirtualAlloc(nullptr, (SIZE_T)SIZE, MEM_RESERVE, PAGE_NOACCESS);
|
||||
if (!data)
|
||||
memory_error("reserve", SIZE);
|
||||
#else
|
||||
data = (u8 *)mmap(nullptr, SIZE, PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
|
||||
if (data == MAP_FAILED)
|
||||
memory_error("reserve", SIZE);
|
||||
#endif
|
||||
|
||||
#ifdef MADV_HUGEPAGE
|
||||
// Large links fill the beginning of the arena densely. Transparent huge
|
||||
// pages reduce address-translation overhead without populating unused pages.
|
||||
madvise(data, SIZE, MADV_HUGEPAGE);
|
||||
#endif
|
||||
}
|
||||
|
||||
ArenaResource::~ArenaResource() {
|
||||
#ifdef _WIN32
|
||||
VirtualFree(data, 0, MEM_RELEASE);
|
||||
#else
|
||||
munmap(data, SIZE);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Makes the reserved range up to `end` accessible. Memory is committed in
|
||||
// whole chunks so that the arena grows with few system calls.
|
||||
void ArenaResource::commit(u64 end) {
|
||||
std::scoped_lock lock(commit_mu);
|
||||
u64 cur = committed.load(std::memory_order_relaxed);
|
||||
if (end <= cur)
|
||||
return;
|
||||
|
||||
u64 next = align_to(end, COMMIT_CHUNK);
|
||||
#ifdef _WIN32
|
||||
if (!VirtualAlloc(data + cur, (SIZE_T)(next - cur), MEM_COMMIT, PAGE_READWRITE))
|
||||
memory_error("commit", next - cur);
|
||||
#else
|
||||
if (mprotect(data + cur, next - cur, PROT_READ | PROT_WRITE) == -1)
|
||||
memory_error("commit", next - cur);
|
||||
#endif
|
||||
committed.store(next, std::memory_order_release);
|
||||
}
|
||||
|
||||
void *ArenaResource::allocate(u64 size, u64 alignment) {
|
||||
assert(std::has_single_bit(alignment));
|
||||
|
||||
// ArenaPtr and base-relative indices both encode offsets in four-byte units.
|
||||
alignment = std::max<u64>(alignment, 4);
|
||||
|
||||
// Standard containers make many small allocations. Reserve them from a
|
||||
// thread-local block to avoid contending on the global bump pointer.
|
||||
if (size <= MAX_LOCAL_ALLOC && alignment <= BLOCK_ALIGNMENT) {
|
||||
// Offsets are from the beginning of the arena in four-byte units.
|
||||
u32 &pos = positions[arena_slot];
|
||||
u32 &end = ends[arena_slot];
|
||||
u32 size4 = align_to(size, 4) / 4;
|
||||
|
||||
if (pos) {
|
||||
u32 begin = align_to(pos, alignment / 4);
|
||||
if (begin <= end && size4 <= end - begin) {
|
||||
pos = begin + size4;
|
||||
return data + (u64)begin * 4;
|
||||
}
|
||||
}
|
||||
|
||||
u8 *begin = (u8 *)allocate_global(BLOCK_SIZE, BLOCK_ALIGNMENT);
|
||||
u32 begin4 = (begin - data) / 4;
|
||||
pos = begin4 + size4;
|
||||
end = begin4 + BLOCK_SIZE / 4;
|
||||
return begin;
|
||||
}
|
||||
|
||||
return allocate_global(size, alignment);
|
||||
}
|
||||
|
||||
void *ArenaResource::allocate_global(u64 size, u64 alignment) {
|
||||
u64 old = offset.load(std::memory_order_relaxed);
|
||||
u64 begin, end;
|
||||
|
||||
for (;;) {
|
||||
u64 padding = -old & (alignment - 1);
|
||||
if (old > SIZE || padding > SIZE - old || size > SIZE - old - padding) {
|
||||
std::cerr << "mold: cannot allocate more than " << SIZE
|
||||
<< " bytes for linker data structures on this host\n";
|
||||
std::exit(1);
|
||||
}
|
||||
|
||||
begin = old + padding;
|
||||
end = begin + size;
|
||||
if (offset.compare_exchange_weak(old, end, std::memory_order_relaxed))
|
||||
break;
|
||||
}
|
||||
|
||||
if (end > committed.load(std::memory_order_acquire))
|
||||
commit(end);
|
||||
return data + begin;
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
@@ -1,64 +0,0 @@
|
||||
// This is the same as std::atomic except that the default memory
|
||||
// order is relaxed instead of sequential consistency.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
|
||||
namespace mold {
|
||||
|
||||
template <typename T>
|
||||
struct Atomic : std::atomic<T> {
|
||||
static constexpr std::memory_order relaxed = std::memory_order_relaxed;
|
||||
|
||||
using std::atomic<T>::atomic;
|
||||
|
||||
Atomic(const Atomic<T> &other) : std::atomic<T>(other.load()) {}
|
||||
|
||||
Atomic<T> &operator=(const Atomic<T> &other) {
|
||||
store(other.load());
|
||||
return *this;
|
||||
}
|
||||
|
||||
void operator=(T val) { store(val); }
|
||||
operator T() const { return load(); }
|
||||
|
||||
void store(T val, std::memory_order order = relaxed) {
|
||||
std::atomic<T>::store(val, order);
|
||||
}
|
||||
|
||||
T load(std::memory_order order = relaxed) const {
|
||||
return std::atomic<T>::load(order);
|
||||
}
|
||||
|
||||
T exchange(T val) { return std::atomic<T>::exchange(val, relaxed); }
|
||||
|
||||
T fetch_or(T val, std::memory_order order = relaxed) {
|
||||
return std::atomic<T>::fetch_or(val, order);
|
||||
}
|
||||
|
||||
T fetch_and(T val, std::memory_order order = relaxed) {
|
||||
return std::atomic<T>::fetch_and(val, order);
|
||||
}
|
||||
|
||||
T operator|=(T val) {
|
||||
// Avoid exclusive cache-line ownership if all requested bits are set.
|
||||
T old = load();
|
||||
return (old & val) == val ? old : fetch_or(val);
|
||||
}
|
||||
|
||||
T operator++() { return std::atomic<T>::fetch_add(1, relaxed) + 1; }
|
||||
T operator--() { return std::atomic<T>::fetch_sub(1, relaxed) - 1; }
|
||||
T operator++(int) { return std::atomic<T>::fetch_add(1, relaxed); }
|
||||
T operator--(int) { return std::atomic<T>::fetch_sub(1, relaxed); }
|
||||
|
||||
bool test_and_set() {
|
||||
// A relaxed load + branch (assuming miss) takes only around 20 cycles,
|
||||
// while an atomic RMW can easily take hundreds on x86. We note that it's
|
||||
// common that another thread beat us in marking, so doing an optimistic
|
||||
// early test tends to improve performance in the ~20% ballpark.
|
||||
return load() || exchange(true);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace mold
|
||||
-174
@@ -1,174 +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 on boundaries of shards, compression ratio is sacrificed
|
||||
// a little bit. However, if a shard size is large enough, that loss
|
||||
// is negligible in practice.
|
||||
|
||||
#include "lib.h"
|
||||
|
||||
#include <tbb/parallel_for_each.h>
|
||||
#include <zlib.h>
|
||||
#include <zstd.h>
|
||||
|
||||
#define CHECK(fn) \
|
||||
do { \
|
||||
[[maybe_unused]] int r = (fn); \
|
||||
assert(r == Z_OK); \
|
||||
} while (0)
|
||||
|
||||
namespace mold {
|
||||
|
||||
static constexpr i64 SHARD_SIZE = 1024 * 1024;
|
||||
|
||||
Compressor::~Compressor() {
|
||||
for (std::span<u8> shard : shards)
|
||||
delete[] shard.data();
|
||||
}
|
||||
|
||||
static std::vector<std::span<u8>> split(std::span<u8> input) {
|
||||
std::vector<std::span<u8>> vec;
|
||||
while (!input.empty()) {
|
||||
i64 sz = std::min<i64>(SHARD_SIZE, input.size());
|
||||
vec.push_back(input.subspan(0, sz));
|
||||
input = input.subspan(sz);
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
static std::span<u8> zlib_compress(std::span<u8> input, int level) {
|
||||
// Initialize zlib stream. Since debug info is generally compressed
|
||||
// pretty well with lower compression levels, the default level is 1.
|
||||
z_stream strm = {};
|
||||
CHECK(deflateInit2(&strm, level, Z_DEFLATED, -15, 8, Z_DEFAULT_STRATEGY));
|
||||
|
||||
// Set an input buffer
|
||||
strm.avail_in = input.size();
|
||||
strm.next_in = input.data();
|
||||
|
||||
// Set an output buffer. deflateBound() returns an upper bound
|
||||
// on the compression size. +16 for Z_SYNC_FLUSH.
|
||||
i64 bufsize = deflateBound(&strm, strm.avail_in) + 16;
|
||||
u8 *buf = new u8[bufsize];
|
||||
|
||||
// Compress data. It writes all compressed bytes except the last
|
||||
// partial byte, so up to 7 bits can be held to be written to the
|
||||
// buffer.
|
||||
strm.avail_out = bufsize;
|
||||
strm.next_out = buf;
|
||||
CHECK(deflate(&strm, Z_BLOCK));
|
||||
|
||||
// This is a workaround for libbacktrace before 2022-04-06.
|
||||
//
|
||||
// Zlib is a bit stream, and what Z_SYNC_FLUSH does is to write a
|
||||
// three bit value indicating the start of an uncompressed data
|
||||
// block followed by four byte data 00 00 ff ff which indicates that
|
||||
// the length of the block is zero. libbacktrace uses its own zlib
|
||||
// inflate routine, and it had a bug that if that particular three
|
||||
// bit value happens to end at a byte boundary, it accidentally
|
||||
// skipped the next byte.
|
||||
//
|
||||
// In order to avoid triggering that bug, we should avoid calling
|
||||
// deflate() with Z_SYNC_FLUSH if the current bit position is 5.
|
||||
// If it's 5, we insert an empty block consisting of 10 bits so
|
||||
// that the bit position is 7 in the next byte.
|
||||
//
|
||||
// https://github.com/ianlancetaylor/libbacktrace/pull/87
|
||||
int nbits = 0;
|
||||
deflatePending(&strm, Z_NULL, &nbits);
|
||||
if (nbits == 5)
|
||||
CHECK(deflatePrime(&strm, 10, 2));
|
||||
CHECK(deflate(&strm, Z_SYNC_FLUSH));
|
||||
|
||||
deflateEnd(&strm);
|
||||
return {buf, (size_t)(bufsize - strm.avail_out)};
|
||||
}
|
||||
|
||||
ZlibCompressor::ZlibCompressor(u8 *buf, i64 size, i64 level) {
|
||||
std::vector<std::span<u8>> inputs = split(std::span(buf, size));
|
||||
std::vector<u32> adlers(inputs.size());
|
||||
shards.resize(inputs.size());
|
||||
|
||||
// Compress each shard
|
||||
tbb::parallel_for((i64)0, (i64)inputs.size(), [&](i64 i) {
|
||||
std::span<u8> in = inputs[i];
|
||||
adlers[i] = adler32(1, in.data(), in.size());
|
||||
shards[i] = zlib_compress(in, level);
|
||||
});
|
||||
|
||||
// Combine checksums
|
||||
checksum = adlers[0];
|
||||
for (i64 i = 1; i < inputs.size(); i++)
|
||||
checksum = adler32_combine(checksum, adlers[i], inputs[i].size());
|
||||
|
||||
// Comput the total size
|
||||
compressed_size = 8; // the header and the trailer
|
||||
for (std::span<u8> &shard : shards)
|
||||
compressed_size += shard.size();
|
||||
}
|
||||
|
||||
void ZlibCompressor::write_to(u8 *buf) {
|
||||
// Write a zlib-format header
|
||||
buf[0] = 0x78;
|
||||
buf[1] = 0x9c;
|
||||
|
||||
// Copy compressed data
|
||||
std::vector<i64> offsets(shards.size());
|
||||
offsets[0] = 2; // +2 for the header
|
||||
for (i64 i = 1; i < shards.size(); i++)
|
||||
offsets[i] = offsets[i - 1] + shards[i - 1].size();
|
||||
|
||||
tbb::parallel_for((i64)0, (i64)shards.size(), [&](i64 i) {
|
||||
memcpy(buf + offsets[i], shards[i].data(), shards[i].size());
|
||||
});
|
||||
|
||||
// Write a trailer
|
||||
u8 *end = buf + compressed_size;
|
||||
end[-6] = 3;
|
||||
end[-5] = 0;
|
||||
*(ub32 *)(end - 4) = checksum;
|
||||
}
|
||||
|
||||
static std::span<u8> zstd_compress(std::span<u8> input, int level) {
|
||||
i64 bufsize = ZSTD_COMPRESSBOUND(input.size());
|
||||
u8 *buf = new u8[bufsize];
|
||||
size_t sz = ZSTD_compress(buf, bufsize, input.data(), input.size(), level);
|
||||
assert(!ZSTD_isError(sz));
|
||||
return {buf, sz};
|
||||
}
|
||||
|
||||
ZstdCompressor::ZstdCompressor(u8 *buf, i64 size, i64 level) {
|
||||
std::vector<std::span<u8>> inputs = split(std::span(buf, size));
|
||||
shards.resize(inputs.size());
|
||||
|
||||
// Compress each shard
|
||||
tbb::parallel_for((i64)0, (i64)inputs.size(), [&](i64 i) {
|
||||
shards[i] = zstd_compress(inputs[i], level);
|
||||
});
|
||||
|
||||
compressed_size = 0;
|
||||
for (std::span<u8> &shard : shards)
|
||||
compressed_size += shard.size();
|
||||
}
|
||||
|
||||
void ZstdCompressor::write_to(u8 *buf) {
|
||||
// Copy compressed data
|
||||
std::vector<i64> offsets(shards.size());
|
||||
for (i64 i = 1; i < shards.size(); i++)
|
||||
offsets[i] = offsets[i - 1] + shards[i - 1].size();
|
||||
|
||||
tbb::parallel_for((i64)0, (i64)shards.size(), [&](i64 i) {
|
||||
memcpy(buf + offsets[i], shards[i].data(), shards[i].size());
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
@@ -1,30 +0,0 @@
|
||||
#define MOLD_VERSION "@mold_VERSION@"
|
||||
#define MOLD_LIBDIR "@CMAKE_INSTALL_FULL_LIBDIR@"
|
||||
#define MOLD_FIRST_TARGET @MOLD_FIRST_TARGET@
|
||||
|
||||
#cmakedefine01 HAVE_FALLOCATE
|
||||
#cmakedefine01 HAVE_MADVISE
|
||||
#cmakedefine01 HAVE_UNAME
|
||||
#cmakedefine01 MOLD_USE_MIMALLOC
|
||||
#cmakedefine01 MOLD_USE_SYSTEM_MIMALLOC
|
||||
|
||||
#cmakedefine01 HAVE_TARGET_X86_64
|
||||
#cmakedefine01 HAVE_TARGET_I386
|
||||
#cmakedefine01 HAVE_TARGET_ARM64LE
|
||||
#cmakedefine01 HAVE_TARGET_ARM64BE
|
||||
#cmakedefine01 HAVE_TARGET_ARM32LE
|
||||
#cmakedefine01 HAVE_TARGET_ARM32BE
|
||||
#cmakedefine01 HAVE_TARGET_RV32LE
|
||||
#cmakedefine01 HAVE_TARGET_RV32BE
|
||||
#cmakedefine01 HAVE_TARGET_RV64LE
|
||||
#cmakedefine01 HAVE_TARGET_RV64BE
|
||||
#cmakedefine01 HAVE_TARGET_PPC32
|
||||
#cmakedefine01 HAVE_TARGET_PPC64V1
|
||||
#cmakedefine01 HAVE_TARGET_PPC64V2
|
||||
#cmakedefine01 HAVE_TARGET_S390X
|
||||
#cmakedefine01 HAVE_TARGET_SPARC64
|
||||
#cmakedefine01 HAVE_TARGET_M68K
|
||||
#cmakedefine01 HAVE_TARGET_SH4LE
|
||||
#cmakedefine01 HAVE_TARGET_SH4BE
|
||||
#cmakedefine01 HAVE_TARGET_LOONGARCH64
|
||||
#cmakedefine01 HAVE_TARGET_LOONGARCH32
|
||||
@@ -1,60 +0,0 @@
|
||||
#include "lib.h"
|
||||
|
||||
#include <tbb/parallel_for_each.h>
|
||||
#include <zlib.h>
|
||||
|
||||
namespace mold {
|
||||
|
||||
// This function "forges" a CRC. That is, given the current and a desired
|
||||
// CRC32 value, crc32_solve() returns a binary blob to add to the end of
|
||||
// the original data to yield the desired CRC. Trailing garbage is ignored
|
||||
// by many bianry file formats, so you can create a file with a desired
|
||||
// CRC using crc32_solve(). We need it for --separate-debug-file.
|
||||
std::vector<u8> crc32_solve(u32 current, u32 desired) {
|
||||
constexpr u32 poly = 0xedb88320;
|
||||
u32 x = ~desired;
|
||||
|
||||
// Each iteration computes x = (x * x^-1) mod poly.
|
||||
for (i64 i = 0; i < 32; i++) {
|
||||
x = std::rotl(x, 1);
|
||||
x ^= (x & 1) * (poly << 1);
|
||||
}
|
||||
|
||||
x ^= ~current;
|
||||
|
||||
std::vector<u8> out(4);
|
||||
out[0] = x;
|
||||
out[1] = x >> 8;
|
||||
out[2] = x >> 16;
|
||||
out[3] = x >> 24;
|
||||
return out;
|
||||
}
|
||||
|
||||
// Compute a CRC for given data in parallel
|
||||
u32 compute_crc32(u32 crc, u8 *buf, i64 len) {
|
||||
struct Shard {
|
||||
u8 *buf;
|
||||
i64 len;
|
||||
u32 crc;
|
||||
};
|
||||
|
||||
constexpr i64 shard_size = 1024 * 1024; // 1 MiB
|
||||
std::vector<Shard> shards;
|
||||
|
||||
while (len > 0) {
|
||||
i64 sz = std::min(len, shard_size);
|
||||
shards.push_back({buf, sz, 0});
|
||||
buf += sz;
|
||||
len -= sz;
|
||||
}
|
||||
|
||||
tbb::parallel_for_each(shards, [](Shard &shard) {
|
||||
shard.crc = crc32(0, shard.buf, shard.len);
|
||||
});
|
||||
|
||||
for (Shard &shard : shards)
|
||||
crc = crc32_combine(crc, shard.crc, shard.len);
|
||||
return crc;
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
@@ -1,42 +0,0 @@
|
||||
#include "lib.h"
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
#ifndef _WIN32
|
||||
#include <cxxabi.h>
|
||||
#endif
|
||||
|
||||
#include "../third-party/rust-demangle/rust-demangle.h"
|
||||
|
||||
namespace mold {
|
||||
|
||||
std::optional<std::string_view> demangle_cpp(std::string_view name) {
|
||||
static thread_local char *buf;
|
||||
static thread_local size_t buflen;
|
||||
|
||||
// TODO(cwasser): Actually demangle Symbols on Windows using e.g.
|
||||
// `UnDecorateSymbolName` from Dbghelp, maybe even Itanium symbols?
|
||||
#ifndef _WIN32
|
||||
if (name.starts_with("_Z")) {
|
||||
int status;
|
||||
char *p = abi::__cxa_demangle(std::string(name).c_str(), buf, &buflen, &status);
|
||||
if (status == 0) {
|
||||
buf = p;
|
||||
return p;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
std::optional<std::string_view> demangle_rust(std::string_view name) {
|
||||
static thread_local char *buf;
|
||||
free(buf);
|
||||
buf = rust_demangle(std::string(name).c_str(), 0);
|
||||
if (buf)
|
||||
return buf;
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
@@ -1,42 +0,0 @@
|
||||
#include "lib.h"
|
||||
|
||||
#include <filesystem>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#ifdef __APPLE__
|
||||
# include <mach-o/dyld.h>
|
||||
#endif
|
||||
|
||||
#ifdef __FreeBSD__
|
||||
# include <sys/sysctl.h>
|
||||
#endif
|
||||
|
||||
namespace mold {
|
||||
|
||||
// Returns the path of the mold executable itself
|
||||
std::string get_self_path() {
|
||||
#if __APPLE__ || _WIN32
|
||||
fprintf(stderr, "mold: get_self_path is not supported");
|
||||
exit(1);
|
||||
#elif __FreeBSD__
|
||||
// /proc may not be mounted on FreeBSD. The proper way to get the
|
||||
// current executable's path is to use sysctl(2).
|
||||
int mib[4];
|
||||
mib[0] = CTL_KERN;
|
||||
mib[1] = KERN_PROC;
|
||||
mib[2] = KERN_PROC_PATHNAME;
|
||||
mib[3] = -1;
|
||||
|
||||
size_t size;
|
||||
sysctl(mib, 4, NULL, &size, NULL, 0);
|
||||
|
||||
std::string path;
|
||||
path.resize(size);
|
||||
sysctl(mib, 4, path.data(), &size, NULL, 0);
|
||||
return path;
|
||||
#else
|
||||
return std::filesystem::read_symlink("/proc/self/exe").string();
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
@@ -1,50 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# Work around a deadlock in portage's binary package extraction.
|
||||
#
|
||||
# tar_stream_reader.close() joins its stdin-feeder thread and then waits
|
||||
# for the external decompressor to exit without draining the
|
||||
# decompressor's remaining stdout. tarfile stops reading at the tar
|
||||
# end-of-archive marker, so trailing archive padding is left in the
|
||||
# pipe. If that residue exceeds the pipe buffer, the decompressor
|
||||
# blocks writing forever and close() never returns, freezing the whole
|
||||
# emerge. Small pipe buffers make this reachable in practice: the
|
||||
# kernel shrinks new pipes to one page once a user exceeds
|
||||
# fs.pipe-user-pages-soft, which our 80 parallel containers do.
|
||||
#
|
||||
# The fix is to drain the decompressor's stdout to EOF before joining
|
||||
# and waiting. This script patches the installed gpkg.py at container
|
||||
# image build time and fails loudly if the code no longer matches, so
|
||||
# a portage upgrade that changes (or fixes) this code is noticed.
|
||||
|
||||
import sys
|
||||
import portage.gpkg
|
||||
|
||||
path = portage.gpkg.__file__
|
||||
src = open(path).read()
|
||||
|
||||
old = """ if self.proc is not None:
|
||||
self.thread.join()
|
||||
"""
|
||||
|
||||
new = """ if self.proc is not None:
|
||||
# Drain remaining decompressor output before joining and
|
||||
# waiting; otherwise the decompressor can block forever on
|
||||
# a full stdout pipe. Injected by mold's lib/gentoo-test.sh.
|
||||
try:
|
||||
while self.read_io.read(65536):
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
self.thread.join()
|
||||
"""
|
||||
|
||||
if src.count(old) != 1:
|
||||
sys.exit(f"gpkg workaround: expected pattern not found in {path}; "
|
||||
"portage changed, update lib/gentoo-gpkg-workaround.py")
|
||||
|
||||
with open(path, "w") as f:
|
||||
f.write(src.replace(old, new))
|
||||
|
||||
import py_compile
|
||||
py_compile.compile(path, doraise=True)
|
||||
print(f"gpkg deadlock workaround applied to {path}")
|
||||
@@ -1,186 +0,0 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# This test script takes a Gentoo package name and tries to build it
|
||||
# using mold in a Podman environment. We chose Gentoo Linux as a test
|
||||
# target, because its source-based packages allow us to build programs
|
||||
# locally and run their test suites without any hassle.
|
||||
#
|
||||
# We first build a package with mold acting as /usr/bin/ld. Only if
|
||||
# that fails, we build the same package again with GNU ld to tell
|
||||
# whether the failure is mold's fault. The package's own test suite
|
||||
# runs in both cases; FEATURES=test is scoped to just the package
|
||||
# under test via package.env, because applying it to the entire
|
||||
# dependency graph makes a large fraction of the tree unresolvable.
|
||||
# Results are classified into three directories:
|
||||
#
|
||||
# success: the package built fine with mold (or, for a control
|
||||
# build, with GNU ld)
|
||||
# failure: the package built with GNU ld but not with mold, which
|
||||
# means it hit a mold bug
|
||||
# broken: the package didn't build even with GNU ld, so the
|
||||
# failure is not mold's fault
|
||||
#
|
||||
# You can get a complete list of Gentoo packages available for testing
|
||||
# with the following command:
|
||||
#
|
||||
# podman run --rm mold-gentoo emerge --color n -s '' | \
|
||||
# perl -ne 'next unless m!^\*\s+(\S+/\S+)!; print "$1\n"'
|
||||
|
||||
package="$1"
|
||||
|
||||
if [ "$package" = "" ]; then
|
||||
echo "Usage: $0 gentoo-package-name"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
set -x
|
||||
|
||||
# Test results are keyed by the commit hash embedded in the mold
|
||||
# binary, so refuse to run with a binary that doesn't have one.
|
||||
git_hash=$(./dist/mold --version | perl -ne '/\(([0-9a-f]{40})\b/ and print $1;')
|
||||
|
||||
if [ "$git_hash" = "" ]; then
|
||||
echo "Cannot get a git hash from ./dist/mold --version. Run this script at the mold source root, with dist/mold built from a git checkout."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cache_dir=/var/cache/mold-gentoo
|
||||
|
||||
# Create a Podman image. The lock prevents parallel invocations of
|
||||
# this script from racing to build the same image.
|
||||
#
|
||||
# A few notes on the container configuration:
|
||||
#
|
||||
# - We accept ~amd64 (testing) packages to test the newest version of
|
||||
# everything. The stage3 image is built for the stable profile, so
|
||||
# we update the whole system to ~amd64 once here; without that,
|
||||
# half-upgraded packages (e.g. perl and its virtuals) cause
|
||||
# unsolvable dependency conflicts in every container.
|
||||
#
|
||||
# - EMERGE_DEFAULT_OPTS makes emerge automatically apply the USE flag
|
||||
# adjustments it would otherwise just print and give up on. An
|
||||
# ephemeral container has no /etc/portage/package.use accumulated by
|
||||
# an administrator, so without this, thousands of packages are
|
||||
# unbuildable out of the box.
|
||||
#
|
||||
# - freetype and harfbuzz mutually depend on each other, so we
|
||||
# pre-install them with the documented two-step bootstrap. During
|
||||
# that step, pillow is built without truetype support to break
|
||||
# another dependency cycle (docutils -> pillow -> harfbuzz -> glib
|
||||
# -> docutils). The override is removed afterwards; once the cycle
|
||||
# participants are installed, they satisfy buildtime dependencies
|
||||
# and the cycle can no longer form.
|
||||
#
|
||||
# - qtbase's test suite requires icu (REQUIRED_USE), which autounmask
|
||||
# doesn't resolve on its own, hence the package.use entry.
|
||||
#
|
||||
# - gcc doesn't auto-select a newly installed major version, so we
|
||||
# switch to the newest slot explicitly after the world update.
|
||||
#
|
||||
# - lib/gentoo-gpkg-workaround.py patches a deadlock in portage's
|
||||
# binary package extraction. See that file for details.
|
||||
#
|
||||
# - static-libs is not in USE because it contradicts REQUIRED_USE
|
||||
# constraints in some packages (e.g. lvm2 forbids static-libs with
|
||||
# udev), and building static archives doesn't exercise the linker
|
||||
# anyway. Where a dependency genuinely needs it, emerge re-enables
|
||||
# it per package via autounmask.
|
||||
#
|
||||
# - /etc/portage/env/test.conf allows marking a single package for
|
||||
# FEATURES=test through package.env, which is how we scope test
|
||||
# suites to just the package under test.
|
||||
#
|
||||
# - The image build compiles a couple hundred packages from source,
|
||||
# because the Gentoo binhost only carries stable versions built with
|
||||
# default USE flags, which rarely match our ~amd64/USE combination.
|
||||
# To pay that cost only once, the build publishes what it compiles
|
||||
# to the GNU ld binary package pool and reuses it, so a rebuilt
|
||||
# image only compiles packages whose version has changed since the
|
||||
# last build. The toolchain step still uses the binhost, where our
|
||||
# configuration does match (e.g. clang). Since it verifies binhost
|
||||
# package signatures and our own pool is unsigned, it runs with a
|
||||
# private PKGDIR so the two never mix.
|
||||
(
|
||||
flock 9
|
||||
if ! podman image exists mold-gentoo; then
|
||||
cat <<EOF | podman build -v $cache_dir/distfiles:/var/cache/distfiles -v $cache_dir/ccache:/ccache -v $cache_dir/binpkgs-ld:/var/cache/binpkgs -v `pwd`/lib:/mold-lib:ro -t mold-gentoo - || exit 1
|
||||
FROM docker.io/gentoo/stage3
|
||||
RUN emerge-webrsync
|
||||
RUN echo 'USE="X ssl elogind -systemd truetype jpeg jpeg2k tiff zstd -perl udev lcms alsa"' >> /etc/portage/make.conf && \
|
||||
echo 'ACCEPT_KEYWORDS="~amd64"' >> /etc/portage/make.conf && \
|
||||
echo 'ACCEPT_LICENSE="* -@EULA"' >> /etc/portage/make.conf && \
|
||||
echo 'FEATURES="nostrip ccache binpkg-multi-instance -ipc-sandbox -network-sandbox -pid-sandbox -sandbox"' >> /etc/portage/make.conf && \
|
||||
echo 'CCACHE_DIR="/ccache"' >> /etc/portage/make.conf && \
|
||||
echo 'MAKEOPTS="-j$(nproc) --load-average=100"' >> /etc/portage/make.conf && \
|
||||
echo 'EMERGE_DEFAULT_OPTS="--autounmask-continue=y --autounmask-license=n --autounmask-backtrack=y --backtrack=100 --jobs=16 --load-average=100"' >> /etc/portage/make.conf && \
|
||||
mkdir -p /etc/portage/env /etc/portage/package.use && \
|
||||
echo 'FEATURES="test"' > /etc/portage/env/test.conf && \
|
||||
printf 'media-libs/freetype harfbuzz\ndev-qt/qtbase icu\n' > /etc/portage/package.use/mold && \
|
||||
echo 'dev-python/pillow -truetype' > /etc/portage/package.use/cycle-break && \
|
||||
FEATURES=buildpkg emerge --usepkg --update --deep --newuse @world && \
|
||||
gcc-config \$(gcc-config -l | tail -n 1 | awk '{print \$2}') && env-update && \
|
||||
USE=-harfbuzz FEATURES=buildpkg emerge --oneshot media-libs/freetype && \
|
||||
FEATURES=buildpkg emerge --usepkg --oneshot media-libs/freetype media-libs/harfbuzz && \
|
||||
rm /etc/portage/package.use/cycle-break && \
|
||||
PKGDIR=/var/tmp/binpkgs FEATURES='getbinpkg binpkg-request-signature' emerge gdb lld llvm-core/clang vim emacs strace ccache dev-build/cmake dev-vcs/git && \
|
||||
rm -rf /var/tmp/portage /var/tmp/binpkgs
|
||||
RUN python3 /mold-lib/gentoo-gpkg-workaround.py
|
||||
EOF
|
||||
fi
|
||||
) 9> /tmp/mold-gentoo-image.lock || exit 1
|
||||
|
||||
if [ "$package" = dev-libs/concurrencykit ]; then
|
||||
echo "Skipping known broken package: $package"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Build a given package in Podman.
|
||||
#
|
||||
# Build artifacts are shared between containers through host
|
||||
# directories bind-mounted into every container:
|
||||
#
|
||||
# /var/cache/mold-gentoo/ccache: compiler cache
|
||||
# /var/cache/mold-gentoo/distfiles: source tarballs, so that each
|
||||
# distfile is downloaded only once across all containers and runs
|
||||
# /var/cache/mold-gentoo/binpkgs-{mold,ld}: binary packages, so that each
|
||||
# dependency is built only once instead of once per container that
|
||||
# needs it
|
||||
#
|
||||
# Every emerge runs with FEATURES=buildpkg to publish what it built to
|
||||
# the binary package pool, and dependencies are installed with
|
||||
# --usepkg from the pool when an exact version and USE match exists.
|
||||
# The package under test itself is always built from source. In the
|
||||
# mold run, dependencies are still linked with mold and their binaries
|
||||
# are still exercised by the test suites of everything built on top of
|
||||
# them; the pool just memoizes that work across containers. The mold
|
||||
# and GNU ld runs use separate pools so that a control build can never
|
||||
# install mold-linked binaries.
|
||||
cmd1='(cd /usr/bin; ln -sf /mold/dist/mold $(realpath ld))'
|
||||
cmd2="echo '$package test.conf' > /etc/portage/package.env"
|
||||
cmd3="FEATURES=buildpkg emerge --usepkg --onlydeps $package"
|
||||
cmd4="FEATURES=buildpkg emerge $package"
|
||||
filename=`echo "$package" | sed 's!/!_!g'`
|
||||
podman="podman run --rm --pids-limit=-1 --cap-add=SYS_PTRACE -v `pwd`:/mold:ro -v $cache_dir/ccache:/ccache -v $cache_dir/distfiles:/var/cache/distfiles"
|
||||
run="mold-gentoo timeout -v -k 15s 3h chrt --idle 0 nice -n 19 bash -c"
|
||||
dir=gentoo/$git_hash
|
||||
|
||||
mkdir -p "$dir"/success "$dir"/failure "$dir"/broken
|
||||
|
||||
# Build the package with mold. If this succeeds, we don't need a
|
||||
# control build.
|
||||
$podman -v $cache_dir/binpkgs-mold:/var/cache/binpkgs $run "$cmd1 && $cmd2 && $cmd3 && $cmd4" >& "$dir"/"$filename".mold
|
||||
if [ $? = 0 ]; then
|
||||
mv "$dir"/"$filename".mold "$dir"/success
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Build the package again with GNU ld to tell whether the failure is
|
||||
# mold's fault.
|
||||
$podman -v $cache_dir/binpkgs-ld:/var/cache/binpkgs $run "$cmd2 && $cmd3 && $cmd4" >& "$dir"/"$filename".ld
|
||||
if [ $? = 0 ]; then
|
||||
mv "$dir"/"$filename".mold "$dir"/failure
|
||||
mv "$dir"/"$filename".ld "$dir"/success
|
||||
else
|
||||
mv "$dir"/"$filename".mold "$dir"/broken
|
||||
mv "$dir"/"$filename".ld "$dir"/broken
|
||||
fi
|
||||
-367
@@ -1,367 +0,0 @@
|
||||
// This file implements the glob matcher used for symbol name patterns.
|
||||
// Exact, prefix and suffix patterns are matched directly, and simple
|
||||
// substring patterns are combined into an Aho-Corasick matcher. The
|
||||
// remaining patterns are matched with the non-recursive algorithm described
|
||||
// at https://research.swtch.com/glob. If there are many such patterns, a
|
||||
// bit-parallel NFA matches them together.
|
||||
|
||||
#include "lib.h"
|
||||
|
||||
namespace mold {
|
||||
|
||||
std::optional<Glob::Pattern> Glob::Pattern::compile(std::string_view pat,
|
||||
i64 value) {
|
||||
std::vector<Token> tokens;
|
||||
|
||||
while (!pat.empty()) {
|
||||
u8 c = pat[0];
|
||||
pat.remove_prefix(1);
|
||||
|
||||
switch (c) {
|
||||
case '[': {
|
||||
// Here are a few bracket pattern examples:
|
||||
//
|
||||
// [abc]: a, b or c
|
||||
// [$\]!]: $, ] or !
|
||||
// [a-czg-i]: a, b, c, z, g, h, or i
|
||||
// [!a-z]: Any character except lowercase letters
|
||||
//
|
||||
// Both `!` and `^` are accepted as negation markers. `!` is the
|
||||
// POSIX/shell convention used by other linkers. `^` was mold's
|
||||
// original syntax and is kept for backward compatibility.
|
||||
tokens.emplace_back(BRACKET);
|
||||
std::bitset<256> &chars = tokens.back().chars;
|
||||
bool negate = false;
|
||||
bool closed = false;
|
||||
|
||||
if (!pat.empty() && (pat[0] == '!' || pat[0] == '^')) {
|
||||
negate = true;
|
||||
pat.remove_prefix(1);
|
||||
}
|
||||
|
||||
while (!pat.empty()) {
|
||||
if (pat[0] == ']') {
|
||||
pat.remove_prefix(1);
|
||||
closed = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (pat[0] == '\\') {
|
||||
pat.remove_prefix(1);
|
||||
if (pat.empty())
|
||||
return {};
|
||||
}
|
||||
|
||||
if (pat.size() >= 3 && pat[1] == '-') {
|
||||
u8 start = pat[0];
|
||||
u8 end = pat[2];
|
||||
pat.remove_prefix(3);
|
||||
|
||||
if (end == '\\') {
|
||||
if (pat.empty())
|
||||
return {};
|
||||
end = pat[0];
|
||||
pat.remove_prefix(1);
|
||||
}
|
||||
|
||||
if (end < start)
|
||||
return {};
|
||||
|
||||
for (i64 i = start; i <= end; i++)
|
||||
chars[i] = true;
|
||||
} else {
|
||||
chars[(u8)pat[0]] = true;
|
||||
pat.remove_prefix(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (!closed)
|
||||
return {};
|
||||
|
||||
if (negate)
|
||||
chars.flip();
|
||||
break;
|
||||
}
|
||||
case '?':
|
||||
tokens.emplace_back(QUESTION);
|
||||
break;
|
||||
case '*':
|
||||
if (tokens.empty() || tokens.back().kind != STAR)
|
||||
tokens.emplace_back(STAR);
|
||||
break;
|
||||
case '\\':
|
||||
if (pat.empty())
|
||||
return {};
|
||||
if (tokens.empty() || tokens.back().kind != STRING)
|
||||
tokens.emplace_back(STRING);
|
||||
tokens.back().str += pat[0];
|
||||
pat.remove_prefix(1);
|
||||
break;
|
||||
default:
|
||||
if (tokens.empty() || tokens.back().kind != STRING)
|
||||
tokens.emplace_back(STRING);
|
||||
tokens.back().str += c;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return Pattern(std::move(tokens), value);
|
||||
}
|
||||
|
||||
bool Glob::Pattern::match(std::string_view str) const {
|
||||
if (!tokens.empty() && tokens.back().kind == STRING &&
|
||||
!str.ends_with(tokens.back().str))
|
||||
return false;
|
||||
|
||||
i64 x = 0;
|
||||
i64 y = 0;
|
||||
i64 next_x = -1;
|
||||
i64 next_y = -1;
|
||||
|
||||
while (x < str.size() || y < tokens.size()) {
|
||||
if (y < tokens.size()) {
|
||||
const Token &tok = tokens[y];
|
||||
|
||||
switch (tok.kind) {
|
||||
case STRING:
|
||||
if (str.substr(x).starts_with(tok.str)) {
|
||||
x += tok.str.size();
|
||||
y++;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
case STAR:
|
||||
next_y = y++;
|
||||
next_x = x + 1;
|
||||
|
||||
if (y < tokens.size() && tokens[y].kind == STRING) {
|
||||
const std::string &s = tokens[y].str;
|
||||
i64 pos = str.find(s, x);
|
||||
if (pos == str.npos)
|
||||
return false;
|
||||
next_x = pos + 1;
|
||||
x = pos + s.size();
|
||||
y++;
|
||||
}
|
||||
continue;
|
||||
case QUESTION:
|
||||
if (x < str.size()) {
|
||||
x++;
|
||||
y++;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
case BRACKET:
|
||||
if (x < str.size() && tok.chars[(u8)str[x]]) {
|
||||
x++;
|
||||
y++;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Retry the last star after assigning one more input byte to it.
|
||||
if (next_x != -1 && next_x <= str.size()) {
|
||||
x = next_x;
|
||||
y = next_y;
|
||||
continue;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void Glob::Nfa::compile(std::span<const Pattern> patterns) {
|
||||
i64 num_states = 0;
|
||||
|
||||
for (const Pattern &pattern : patterns) {
|
||||
num_states++;
|
||||
|
||||
for (const Pattern::Token &tok : pattern.tokens) {
|
||||
if (tok.kind == Pattern::STRING)
|
||||
num_states += tok.str.size();
|
||||
else if (tok.kind != Pattern::STAR)
|
||||
num_states++;
|
||||
}
|
||||
}
|
||||
|
||||
i64 num_words = (num_states + 63) / 64;
|
||||
initial_states.resize(num_words);
|
||||
star_states.resize(num_words);
|
||||
accept_states.resize(num_words);
|
||||
char_masks.resize(256 * num_words);
|
||||
values.resize(num_states, -1);
|
||||
|
||||
auto set_bit = [](std::vector<u64> &vec, i64 pos) {
|
||||
vec[pos / 64] |= 1ULL << (pos % 64);
|
||||
};
|
||||
|
||||
i64 state = 0;
|
||||
|
||||
for (const Pattern &pattern : patterns) {
|
||||
set_bit(initial_states, state);
|
||||
|
||||
for (const Pattern::Token &tok : pattern.tokens) {
|
||||
switch (tok.kind) {
|
||||
case Pattern::STRING:
|
||||
for (u8 c : tok.str) {
|
||||
state++;
|
||||
char_masks[c * num_words + state / 64] |= 1ULL << (state % 64);
|
||||
}
|
||||
break;
|
||||
case Pattern::STAR:
|
||||
set_bit(star_states, state);
|
||||
break;
|
||||
case Pattern::QUESTION:
|
||||
state++;
|
||||
for (i64 c = 0; c < 256; c++)
|
||||
char_masks[c * num_words + state / 64] |= 1ULL << (state % 64);
|
||||
break;
|
||||
case Pattern::BRACKET:
|
||||
state++;
|
||||
for (i64 c = 0; c < 256; c++)
|
||||
if (tok.chars[c])
|
||||
char_masks[c * num_words + state / 64] |= 1ULL << (state % 64);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
set_bit(accept_states, state);
|
||||
values[state] = pattern.value;
|
||||
state++;
|
||||
}
|
||||
}
|
||||
|
||||
i64 Glob::Nfa::match(std::string_view str) const {
|
||||
static thread_local std::vector<u64> states;
|
||||
states.assign(initial_states.begin(), initial_states.end());
|
||||
|
||||
i64 num_words = states.size();
|
||||
|
||||
for (u8 c : str) {
|
||||
const u64 *mask = char_masks.data() + c * num_words;
|
||||
u64 carry = 0;
|
||||
|
||||
for (i64 i = 0; i < num_words; i++) {
|
||||
u64 old = states[i];
|
||||
u64 next = (old << 1) | carry;
|
||||
states[i] = (old & star_states[i]) | (next & mask[i]);
|
||||
carry = old >> 63;
|
||||
}
|
||||
}
|
||||
|
||||
i64 value = -1;
|
||||
|
||||
for (i64 i = 0; i < num_words; i++) {
|
||||
u64 word = states[i] & accept_states[i];
|
||||
|
||||
while (word) {
|
||||
i64 bit = std::countr_zero(word);
|
||||
value = std::max(value, values[i * 64 + bit]);
|
||||
word &= word - 1;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
static bool is_literal(std::string_view pat) {
|
||||
return pat.find_first_of("*?[\\") == pat.npos;
|
||||
}
|
||||
|
||||
bool Glob::add(std::string_view pat, i64 val) {
|
||||
assert(val >= 0);
|
||||
assert(!is_compiled);
|
||||
is_empty = false;
|
||||
max_value = std::max(max_value, val);
|
||||
|
||||
// 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 == "*") {
|
||||
match_all = std::max(match_all, val);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (is_literal(pat)) {
|
||||
exacts.push_back({std::string(pat), val});
|
||||
return true;
|
||||
}
|
||||
|
||||
if (pat.ends_with('*') && is_literal(pat.substr(0, pat.size() - 1))) {
|
||||
prefixes.push_back({std::string(pat.substr(0, pat.size() - 1)), val});
|
||||
return true;
|
||||
}
|
||||
|
||||
if (pat.starts_with('*') && is_literal(pat.substr(1))) {
|
||||
suffixes.push_back({std::string(pat.substr(1)), val});
|
||||
return true;
|
||||
}
|
||||
|
||||
// If the pattern requires only a single substring search, the
|
||||
// Aho-Corasick algorithm is even faster than our glob matcher.
|
||||
if (aho_corasick.can_handle(pat))
|
||||
return aho_corasick.add(pat, val);
|
||||
|
||||
if (std::optional<Pattern> pattern = Pattern::compile(pat, val)) {
|
||||
patterns.push_back(std::move(*pattern));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
i64 Glob::find(std::string_view str) {
|
||||
std::call_once(once, [&] {
|
||||
// If the same name was added more than once, keep only the entry
|
||||
// with the largest value, as find() returns the largest match.
|
||||
// Sorting by (name, negated value) places that entry first in
|
||||
// each run of duplicates, which is the one unique() keeps.
|
||||
ranges::sort(exacts, {}, [](const LiteralPattern &p) {
|
||||
return std::pair<std::string_view, i64>(p.pat, -p.value);
|
||||
});
|
||||
|
||||
auto dup = ranges::unique(exacts, {}, &LiteralPattern::pat);
|
||||
exacts.erase(dup.begin(), dup.end());
|
||||
|
||||
if (patterns.size() >= 64) {
|
||||
nfa.compile(patterns);
|
||||
patterns.clear();
|
||||
}
|
||||
|
||||
aho_corasick.compile();
|
||||
is_compiled = true;
|
||||
});
|
||||
|
||||
i64 val = match_all;
|
||||
if (val == max_value)
|
||||
return val;
|
||||
|
||||
auto it = ranges::lower_bound(exacts, str, {}, &LiteralPattern::pat);
|
||||
if (it != exacts.end() && it->pat == str)
|
||||
val = std::max(val, it->value);
|
||||
|
||||
for (const LiteralPattern &p : prefixes)
|
||||
if (val < p.value && str.starts_with(p.pat))
|
||||
val = p.value;
|
||||
|
||||
for (const LiteralPattern &p : suffixes)
|
||||
if (val < p.value && str.ends_with(p.pat))
|
||||
val = p.value;
|
||||
|
||||
if (val == max_value)
|
||||
return val;
|
||||
|
||||
val = std::max(val, aho_corasick.find(str));
|
||||
if (val == max_value)
|
||||
return val;
|
||||
|
||||
if (!nfa.empty())
|
||||
val = std::max(val, nfa.match(str));
|
||||
|
||||
for (const Pattern &p : patterns)
|
||||
if (val < p.value && p.match(str))
|
||||
val = p.value;
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
@@ -1,25 +0,0 @@
|
||||
// This file implements HyperLogLog algorithm, which estimates
|
||||
// the number of unique items in a given multiset.
|
||||
//
|
||||
// For more info, read
|
||||
// https://engineering.fb.com/2018/12/13/data-infrastructure/hyperloglog
|
||||
|
||||
#include "lib.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
namespace mold {
|
||||
|
||||
i64 HyperLogLog::get_cardinality() const {
|
||||
u8 buckets[NBUCKETS] = {};
|
||||
for (const Sketch &sketch : sketches)
|
||||
for (i64 i = 0; i < NBUCKETS; i++)
|
||||
buckets[i] = std::max(buckets[i], sketch.buckets[i]);
|
||||
|
||||
double z = 0;
|
||||
for (i64 val : buckets)
|
||||
z += std::ldexp(1.0, -val);
|
||||
return ALPHA * NBUCKETS * NBUCKETS / z;
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
-127
@@ -1,127 +0,0 @@
|
||||
// This file defines integral types for file input/output. We need to use
|
||||
// these types instead of the plain integers (such as uint32_t or int32_t)
|
||||
// when reading from/writing to an mmap'ed file area for the following
|
||||
// reasons:
|
||||
//
|
||||
// 1. mold is always a cross linker and should not depend on what host it
|
||||
// is running on. For example, users should be able to run mold on a
|
||||
// little-endian x86 machine to create a big-endian s390x binary.
|
||||
//
|
||||
// 2. Even though data members in all ELF data strucutres are naturally
|
||||
// aligned, they are not guaranteed to be aligned on memory because of
|
||||
// archive files. Archive files (.a files) align each file only to a
|
||||
// 2 byte boundary, so anything larger than 2 bytes may be misaligned
|
||||
// in an mmap'ed memory. Misaligned access is an undefined behavior in
|
||||
// C/C++, so we shouldn't cast an arbitrary pointer to a uint32_t, for
|
||||
// example, to read a 32 bit value.
|
||||
//
|
||||
// The data types defined in this file are independent of the host byte
|
||||
// order and are designed to avoid unaligned access.
|
||||
//
|
||||
// Note that in C/C++, memcpy is a portable and efficient way to access
|
||||
// unaligned data, as it is typically treated as an intrinsic. Compilers
|
||||
// can easily optimize memcpy calls in this file into a single load or
|
||||
// store instruction.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <bit>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <type_traits>
|
||||
|
||||
#if !defined(__LITTLE_ENDIAN__) && !defined(__BIG_ENDIAN__)
|
||||
# if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||
# define __LITTLE_ENDIAN__ 1
|
||||
# elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
|
||||
# define __BIG_ENDIAN__ 1
|
||||
# else
|
||||
# error "unknown host byte order"
|
||||
# endif
|
||||
#endif
|
||||
|
||||
namespace mold {
|
||||
|
||||
template <typename T, bool is_le, int size = sizeof(T)>
|
||||
class Integer {
|
||||
public:
|
||||
constexpr Integer() = default;
|
||||
|
||||
constexpr Integer(T v) {
|
||||
if (std::is_constant_evaluated() || size == 3) {
|
||||
for (int i = 0; i < size; i++)
|
||||
buf[is_le ? i : (size - i - 1)] = v >> (i * 8);
|
||||
} else {
|
||||
if (!is_native)
|
||||
v = bswap(v);
|
||||
memcpy(buf, &v, size);
|
||||
}
|
||||
}
|
||||
|
||||
operator T() const {
|
||||
if constexpr (size == 3) {
|
||||
if constexpr (is_le)
|
||||
return buf[2] << 16 | buf[1] << 8 | buf[0];
|
||||
return buf[0] << 16 | buf[1] << 8 | buf[2];
|
||||
} else {
|
||||
T v;
|
||||
memcpy(&v, buf, size);
|
||||
return is_native ? v : bswap(v);
|
||||
}
|
||||
}
|
||||
|
||||
Integer &operator=(T v) { new (this) Integer(v); return *this; }
|
||||
Integer &operator++() { return *this = *this + 1; }
|
||||
Integer operator++(int) { auto x = *this; ++*this; return x; }
|
||||
Integer &operator--() { return *this = *this - 1; }
|
||||
Integer operator--(int) { auto x = *this; --*this; return x; }
|
||||
Integer &operator+=(T v) { return *this = *this + v; }
|
||||
Integer &operator-=(T v) { return *this = *this - v; }
|
||||
Integer &operator&=(T v) { return *this = *this & v; }
|
||||
Integer &operator|=(T v) { return *this = *this | v; }
|
||||
|
||||
private:
|
||||
static constexpr bool is_native =
|
||||
(std::endian::native == (is_le ? std::endian::little : std::endian::big));
|
||||
|
||||
static T bswap(T v) {
|
||||
switch (size) {
|
||||
case 2: return __builtin_bswap16(v);
|
||||
case 4: return __builtin_bswap32(v);
|
||||
case 8: return __builtin_bswap64(v);
|
||||
default: __builtin_unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t buf[size];
|
||||
};
|
||||
|
||||
using i8 = int8_t;
|
||||
using i16 = int16_t;
|
||||
using i32 = int32_t;
|
||||
using i64 = int64_t;
|
||||
|
||||
using u8 = uint8_t;
|
||||
using u16 = uint16_t;
|
||||
using u32 = uint32_t;
|
||||
using u64 = uint64_t;
|
||||
|
||||
using il16 = Integer<i16, true>;
|
||||
using il32 = Integer<i32, true>;
|
||||
using il64 = Integer<i64, true>;
|
||||
|
||||
using ul16 = Integer<u16, true>;
|
||||
using ul24 = Integer<u32, true, 3>;
|
||||
using ul32 = Integer<u32, true>;
|
||||
using ul64 = Integer<u64, true>;
|
||||
|
||||
using ib16 = Integer<i16, false>;
|
||||
using ib32 = Integer<i32, false>;
|
||||
using ib64 = Integer<i64, false>;
|
||||
|
||||
using ub16 = Integer<u16, false>;
|
||||
using ub24 = Integer<u32, false, 3>;
|
||||
using ub32 = Integer<u32, false>;
|
||||
using ub64 = Integer<u64, false>;
|
||||
|
||||
} // namespace mold
|
||||
-118
@@ -1,118 +0,0 @@
|
||||
#include "lib.h"
|
||||
|
||||
#include <functional>
|
||||
#include <iomanip>
|
||||
#include <ios>
|
||||
#include <tbb/concurrent_vector.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <sys/resource.h>
|
||||
#include <sys/time.h>
|
||||
#endif
|
||||
|
||||
namespace mold {
|
||||
|
||||
i64 Counter::get_value() {
|
||||
return values.combine(std::plus());
|
||||
}
|
||||
|
||||
void Counter::print() {
|
||||
ranges::stable_sort(instances, {}, [](Counter *x) { return x->get_value(); });
|
||||
|
||||
for (Counter *c : instances)
|
||||
std::cout << std::setw(20) << std::right << c->name
|
||||
<< "=" << c->get_value() << "\n";
|
||||
}
|
||||
|
||||
static i64 now_nsec() {
|
||||
return (i64)std::chrono::steady_clock::now().time_since_epoch().count();
|
||||
}
|
||||
|
||||
static std::pair<i64, i64> get_usage() {
|
||||
#ifdef _WIN32
|
||||
auto to_nsec = [](FILETIME t) -> i64 {
|
||||
return (((u64)t.dwHighDateTime << 32) + (u64)t.dwLowDateTime) * 100;
|
||||
};
|
||||
|
||||
FILETIME creation, exit, kernel, user;
|
||||
GetProcessTimes(GetCurrentProcess(), &creation, &exit, &kernel, &user);
|
||||
return {to_nsec(user), to_nsec(kernel)};
|
||||
#else
|
||||
auto to_nsec = [](struct timeval t) -> i64 {
|
||||
return (i64)t.tv_sec * 1'000'000'000 + t.tv_usec * 1'000;
|
||||
};
|
||||
|
||||
struct rusage ru;
|
||||
getrusage(RUSAGE_SELF, &ru);
|
||||
return {to_nsec(ru.ru_utime), to_nsec(ru.ru_stime)};
|
||||
#endif
|
||||
}
|
||||
|
||||
TimerRecord::TimerRecord(std::string name, TimerRecord *parent)
|
||||
: name(name), parent(parent) {
|
||||
start = now_nsec();
|
||||
std::tie(user, sys) = get_usage();
|
||||
if (parent)
|
||||
parent->children.push_back(this);
|
||||
}
|
||||
|
||||
void TimerRecord::stop() {
|
||||
if (stopped)
|
||||
return;
|
||||
stopped = true;
|
||||
|
||||
i64 user2;
|
||||
i64 sys2;
|
||||
std::tie(user2, sys2) = get_usage();
|
||||
|
||||
end = now_nsec();
|
||||
user = user2 - user;
|
||||
sys = sys2 - sys;
|
||||
}
|
||||
|
||||
static void print_rec(TimerRecord &rec, i64 indent) {
|
||||
printf(" % 8.3f % 8.3f % 8.3f %s%s\n",
|
||||
((double)rec.user / 1'000'000'000),
|
||||
((double)rec.sys / 1'000'000'000),
|
||||
(((double)rec.end - rec.start) / 1'000'000'000),
|
||||
std::string(indent * 2, ' ').c_str(),
|
||||
rec.name.c_str());
|
||||
|
||||
ranges::stable_sort(rec.children, {}, &TimerRecord::start);
|
||||
|
||||
for (TimerRecord *child : rec.children)
|
||||
print_rec(*child, indent + 1);
|
||||
}
|
||||
|
||||
void print_timer_records(
|
||||
tbb::concurrent_vector<std::unique_ptr<TimerRecord>> &records) {
|
||||
for (i64 i = records.size() - 1; i >= 0; i--)
|
||||
records[i]->stop();
|
||||
|
||||
for (i64 i = 0; i < records.size(); i++) {
|
||||
TimerRecord &inner = *records[i];
|
||||
if (inner.parent)
|
||||
continue;
|
||||
|
||||
for (i64 j = i - 1; j >= 0; j--) {
|
||||
TimerRecord &outer = *records[j];
|
||||
if (outer.start <= inner.start && inner.end <= outer.end) {
|
||||
inner.parent = &outer;
|
||||
outer.children.push_back(&inner);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << " User System Real Name\n";
|
||||
|
||||
for (std::unique_ptr<TimerRecord> &rec : records)
|
||||
if (!rec->parent)
|
||||
print_rec(*rec, 0);
|
||||
|
||||
std::cout << std::flush;
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
@@ -1,20 +0,0 @@
|
||||
#include "lib.h"
|
||||
|
||||
#include <random>
|
||||
|
||||
namespace mold {
|
||||
|
||||
void get_random_bytes(u8 *buf, i64 size) {
|
||||
std::random_device rand;
|
||||
i64 i = 0;
|
||||
|
||||
for (; i < size - 4; i += 4) {
|
||||
u32 val = rand();
|
||||
memcpy(buf + i, &val, 4);
|
||||
}
|
||||
|
||||
u32 val = rand();
|
||||
memcpy(buf + i, &val, size - i);
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
-144
@@ -1,144 +0,0 @@
|
||||
// This is a header-only C++20 implementation of SipHash based on the
|
||||
// reference implementation. To use, just copy this header file into
|
||||
// your project and #include it.
|
||||
//
|
||||
// https://github.com/rui314/siphash/blob/main/siphash.h
|
||||
|
||||
#include <bit>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
||||
template <int C_ROUNDS, int D_ROUNDS, int OUTLEN>
|
||||
class SipHashTmpl {
|
||||
public:
|
||||
static_assert(OUTLEN == 64 || OUTLEN == 128);
|
||||
|
||||
SipHashTmpl(void *key) {
|
||||
uint64_t k0 = read64(key);
|
||||
uint64_t k1 = read64((char *)key + 8);
|
||||
|
||||
v0 = 0x736f6d6570736575 ^ k0;
|
||||
v1 = 0x646f72616e646f6d ^ k1;
|
||||
v2 = 0x6c7967656e657261 ^ k0;
|
||||
v3 = 0x7465646279746573 ^ k1;
|
||||
|
||||
if (OUTLEN == 128)
|
||||
v1 ^= 0xee;
|
||||
}
|
||||
|
||||
void update(void *msgp, int64_t msglen) {
|
||||
char *msg = (char *)msgp;
|
||||
sum += msglen;
|
||||
|
||||
if (buflen) {
|
||||
if (buflen + msglen < 8) {
|
||||
memcpy(buf + buflen, msg, msglen);
|
||||
buflen += msglen;
|
||||
return;
|
||||
}
|
||||
|
||||
int j = 8 - buflen;
|
||||
memcpy(buf + buflen, msg, j);
|
||||
compress(read64(buf));
|
||||
|
||||
msg += j;
|
||||
msglen -= j;
|
||||
buflen = 0;
|
||||
}
|
||||
|
||||
while (msglen >= 8) {
|
||||
compress(read64(msg));
|
||||
msg += 8;
|
||||
msglen -= 8;
|
||||
}
|
||||
|
||||
memcpy(buf, msg, msglen);
|
||||
buflen = msglen;
|
||||
}
|
||||
|
||||
void finish(void *out) {
|
||||
memset(buf + buflen, 0, 8 - buflen);
|
||||
compress(((uint64_t)sum << 56) | read64(buf));
|
||||
|
||||
v2 ^= (OUTLEN == 128) ? 0xee : 0xff;
|
||||
finalize();
|
||||
write64(out, v0 ^ v1 ^ v2 ^ v3);
|
||||
|
||||
if (OUTLEN == 128) {
|
||||
v1 ^= 0xdd;
|
||||
finalize();
|
||||
write64((char *)out + 8, v0 ^ v1 ^ v2 ^ v3);
|
||||
}
|
||||
}
|
||||
|
||||
static void hash(void *out, void *key, void *in, int inlen) {
|
||||
SipHashTmpl<C_ROUNDS, D_ROUNDS, OUTLEN> h(key);
|
||||
h.update(in, inlen);
|
||||
h.finish(out);
|
||||
}
|
||||
|
||||
private:
|
||||
uint64_t v0, v1, v2, v3;
|
||||
uint8_t buf[8];
|
||||
uint8_t buflen = 0;
|
||||
uint8_t sum = 0;
|
||||
|
||||
uint64_t read64(void *loc) {
|
||||
uint64_t val;
|
||||
memcpy(&val, loc, 8);
|
||||
if (std::endian::native == std::endian::big)
|
||||
val = bswap(val);
|
||||
return val;
|
||||
}
|
||||
|
||||
void write64(void *loc, uint64_t val) {
|
||||
if (std::endian::native == std::endian::big)
|
||||
val = bswap(val);
|
||||
memcpy(loc, &val, 8);
|
||||
}
|
||||
|
||||
uint64_t bswap(uint64_t val) {
|
||||
return ((val << 56) & 0xff00000000000000) |
|
||||
((val << 40) & 0x00ff000000000000) |
|
||||
((val << 24) & 0x0000ff0000000000) |
|
||||
((val << 8) & 0x000000ff00000000) |
|
||||
((val >> 8) & 0x00000000ff000000) |
|
||||
((val >> 24) & 0x0000000000ff0000) |
|
||||
((val >> 40) & 0x000000000000ff00) |
|
||||
((val >> 56) & 0x00000000000000ff);
|
||||
}
|
||||
|
||||
void round() {
|
||||
v0 += v1;
|
||||
v1 = std::rotl(v1, 13);
|
||||
v1 ^= v0;
|
||||
v0 = std::rotl(v0, 32);
|
||||
v2 += v3;
|
||||
v3 = std::rotl(v3, 16);
|
||||
v3 ^= v2;
|
||||
v0 += v3;
|
||||
v3 = std::rotl(v3, 21);
|
||||
v3 ^= v0;
|
||||
v2 += v1;
|
||||
v1 = std::rotl(v1, 17);
|
||||
v1 ^= v2;
|
||||
v2 = std::rotl(v2, 32);
|
||||
}
|
||||
|
||||
void compress(uint64_t m) {
|
||||
v3 ^= m;
|
||||
for (int i = 0; i < C_ROUNDS; i++)
|
||||
round();
|
||||
v0 ^= m;
|
||||
}
|
||||
|
||||
void finalize() {
|
||||
for (int i = 0; i < D_ROUNDS; i++)
|
||||
round();
|
||||
}
|
||||
};
|
||||
|
||||
using SipHash = SipHashTmpl<2, 4, 64>;
|
||||
using SipHash128 = SipHashTmpl<2, 4, 128>;
|
||||
using SipHash13 = SipHashTmpl<1, 3, 64>;
|
||||
using SipHash13_128 = SipHashTmpl<1, 3, 128>;
|
||||
-119
@@ -1,119 +0,0 @@
|
||||
// This file contains functions to create a tar file.
|
||||
|
||||
#include "lib.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
# define ftruncate _chsize_s
|
||||
#endif
|
||||
|
||||
namespace mold {
|
||||
|
||||
static constexpr i64 BLOCK_SIZE = 512;
|
||||
|
||||
// A tar file consists of one or more Ustar header followed by data.
|
||||
// Each Ustar header represents a single file in an archive.
|
||||
//
|
||||
// tar is an old file format, and its `name` field is only 100 bytes long.
|
||||
// If `name` is longer than 100 bytes, we can emit a PAX header before a
|
||||
// Ustar header to store a long filename.
|
||||
//
|
||||
// For simplicity, we always emit a PAX header even for a short filename.
|
||||
struct UstarHeader {
|
||||
char name[100];
|
||||
char mode[8];
|
||||
char uid[8];
|
||||
char gid[8];
|
||||
char size[12];
|
||||
char mtime[12];
|
||||
char checksum[8];
|
||||
char typeflag[1];
|
||||
char linkname[100];
|
||||
char magic[6];
|
||||
char version[2];
|
||||
char uname[32];
|
||||
char gname[32];
|
||||
char devmajor[8];
|
||||
char devminor[8];
|
||||
char prefix[155];
|
||||
char pad[12];
|
||||
};
|
||||
|
||||
static_assert(sizeof(UstarHeader) == BLOCK_SIZE);
|
||||
|
||||
static void finalize(UstarHeader &hdr) {
|
||||
memset(hdr.checksum, ' ', sizeof(hdr.checksum));
|
||||
memcpy(hdr.magic, "ustar", 5);
|
||||
memcpy(hdr.version, "00", 2);
|
||||
|
||||
// Compute checksum
|
||||
int sum = 0;
|
||||
for (i64 i = 0; i < sizeof(hdr); i++)
|
||||
sum += ((u8 *)&hdr)[i];
|
||||
|
||||
// We need to convince the compiler that sum isn't too big to silence
|
||||
// -Werror=format-truncation.
|
||||
if (sum >= 01'000'000)
|
||||
unreachable();
|
||||
snprintf(hdr.checksum, sizeof(hdr.checksum), "%06o", sum);
|
||||
}
|
||||
|
||||
static std::string encode_path(std::string basedir, std::string path) {
|
||||
path = path_clean(basedir + "/" + path);
|
||||
|
||||
// Construct a string which contains something like
|
||||
// "16 path=foo/bar\n" where 16 is the size of the string
|
||||
// including the size string itself.
|
||||
i64 len = std::string(" path=\n").size() + path.size();
|
||||
i64 total = std::to_string(len).size() + len;
|
||||
total = std::to_string(total).size() + len;
|
||||
return std::to_string(total) + " path=" + path + "\n";
|
||||
}
|
||||
|
||||
std::unique_ptr<TarWriter>
|
||||
TarWriter::open(std::string output_path, std::string basedir) {
|
||||
FILE *out = fopen(output_path.c_str(), "w");
|
||||
if (!out)
|
||||
return nullptr;
|
||||
return std::unique_ptr<TarWriter>(new TarWriter(out, basedir));
|
||||
}
|
||||
|
||||
TarWriter::~TarWriter() {
|
||||
fclose(out);
|
||||
}
|
||||
|
||||
void TarWriter::append(std::string path, std::string_view data) {
|
||||
// Write PAX header
|
||||
UstarHeader pax = {};
|
||||
|
||||
std::string attr = encode_path(basedir, path);
|
||||
|
||||
if (attr.size() >= 0100'000'000'000)
|
||||
unreachable();
|
||||
snprintf(pax.size, sizeof(pax.size), "%011zo", attr.size());
|
||||
pax.name[0] = '/';
|
||||
pax.typeflag[0] = 'x';
|
||||
finalize(pax);
|
||||
fwrite(&pax, sizeof(pax), 1, out);
|
||||
|
||||
// Write pathname
|
||||
fwrite(attr.data(), attr.size(), 1, out);
|
||||
fseek(out, align_to(ftell(out), BLOCK_SIZE), SEEK_SET);
|
||||
|
||||
// Write Ustar header
|
||||
UstarHeader ustar = {};
|
||||
memcpy(ustar.mode, "0000664", 8);
|
||||
if (data.size() >= 0100'000'000'000)
|
||||
unreachable();
|
||||
snprintf(ustar.size, sizeof(ustar.size), "%011zo", data.size());
|
||||
finalize(ustar);
|
||||
fwrite(&ustar, sizeof(ustar), 1, out);
|
||||
|
||||
// Write file contents
|
||||
fwrite(data.data(), data.size(), 1, out);
|
||||
fseek(out, align_to(ftell(out), BLOCK_SIZE), SEEK_SET);
|
||||
|
||||
// A tar file must ends with two empty blocks
|
||||
(void)!ftruncate(fileno(out), ftell(out) + BLOCK_SIZE * 2);
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
@@ -1,32 +0,0 @@
|
||||
# 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.
|
||||
if(EXISTS "${SOURCE_DIR}/.git")
|
||||
execute_process(
|
||||
COMMAND git -C "${SOURCE_DIR}" rev-parse HEAD
|
||||
OUTPUT_VARIABLE HASH
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
ERROR_QUIET
|
||||
RESULT_VARIABLE EXIT_CODE)
|
||||
|
||||
if(NOT EXIT_CODE EQUAL 0)
|
||||
set(HASH "")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Create new file contents and update a given file if necessary.
|
||||
if("${HASH}" STREQUAL "")
|
||||
set(NEW_CONTENTS "")
|
||||
else()
|
||||
set(NEW_CONTENTS "#define MOLD_GIT_HASH \"${HASH}\"\n")
|
||||
endif()
|
||||
|
||||
if(EXISTS "${OUTPUT_FILE}")
|
||||
file(READ "${OUTPUT_FILE}" OLD_CONTENTS)
|
||||
if(NOT "${NEW_CONTENTS}" STREQUAL "${OLD_CONTENTS}")
|
||||
file(WRITE "${OUTPUT_FILE}" "${NEW_CONTENTS}")
|
||||
endif()
|
||||
else()
|
||||
file(WRITE "${OUTPUT_FILE}" "${NEW_CONTENTS}")
|
||||
endif()
|
||||
@@ -0,0 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "stable"
|
||||
@@ -1,917 +0,0 @@
|
||||
// ARM32 is a bit special from the linker's viewpoint because ARM
|
||||
// processors support two different instruction encodings: Thumb and
|
||||
// ARM (in a narrower sense). Thumb instructions are either 16 bits or
|
||||
// 32 bits, while ARM instructions are all 32 bits. Feature-wise,
|
||||
// Thumb is a subset of ARM, so not all ARM instructions are
|
||||
// representable in Thumb.
|
||||
//
|
||||
// ARM processors originally supported only ARM instructions. Thumb
|
||||
// instructions were later added to increase code density.
|
||||
//
|
||||
// ARM processors runs in either ARM mode or Thumb mode. The mode can
|
||||
// be switched using BX (branch and mode exchange)-family instructions.
|
||||
// We need to use that instructions to, for example, call a function
|
||||
// encoded in Thumb from a function encoded in ARM. Sometimes, the
|
||||
// linker even has to emit interworking thunk code to switch mode.
|
||||
//
|
||||
// ARM instructions are aligned to 4 byte boundaries. Thumb are to 2
|
||||
// byte boundaries. So the least significant bit of a function address
|
||||
// is always 0.
|
||||
//
|
||||
// To distinguish Thumb functions from ARM fucntions, the LSB of a
|
||||
// function address is repurposed as a boolean flag. If the LSB is 0,
|
||||
// the function referred to by the address is encoded in ARM;
|
||||
// otherwise, Thumb.
|
||||
//
|
||||
// For example, if a symbol `foo` is of type STT_FUNC and has value
|
||||
// 0x2001, `foo` is a function using Thumb instructions whose address
|
||||
// is 0x2000 (not 0x2001, as Thumb instructions are always 2-byte
|
||||
// aligned). Likewise, if a function pointer has value 0x2001, it
|
||||
// refers a Thumb function at 0x2000.
|
||||
//
|
||||
// https://github.com/ARM-software/abi-aa/blob/main/aaelf32/aaelf32.rst
|
||||
|
||||
#if MOLD_ARM32LE || MOLD_ARM32BE
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
#include <tbb/parallel_for.h>
|
||||
#include <tbb/parallel_for_each.h>
|
||||
|
||||
namespace mold {
|
||||
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
template <>
|
||||
i64 get_addend(u8 *loc, const ElfRel<E> &rel) {
|
||||
U32<E> *arm = (U32<E> *)loc;
|
||||
U16<E> *thm = (U16<E> *)loc;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_ARM_ABS32:
|
||||
case R_ARM_REL32:
|
||||
case R_ARM_BASE_PREL:
|
||||
case R_ARM_GOTOFF32:
|
||||
case R_ARM_GOT_PREL:
|
||||
case R_ARM_GOT_BREL:
|
||||
case R_ARM_TLS_GD32:
|
||||
case R_ARM_TLS_LDM32:
|
||||
case R_ARM_TLS_LDO32:
|
||||
case R_ARM_TLS_IE32:
|
||||
case R_ARM_TLS_LE32:
|
||||
case R_ARM_TLS_GOTDESC:
|
||||
case R_ARM_TARGET1:
|
||||
case R_ARM_TARGET2:
|
||||
return (I32<E>)*arm;
|
||||
case R_ARM_THM_JUMP8:
|
||||
return sign_extend(thm[0], 8) << 1;
|
||||
case R_ARM_THM_JUMP11:
|
||||
return sign_extend(thm[0], 11) << 1;
|
||||
case R_ARM_THM_JUMP19: {
|
||||
// https://developer.arm.com/documentation/ddi0597/2024-12/Base-Instructions/B--Branch-
|
||||
u32 S = bit(thm[0], 10);
|
||||
u32 J2 = bit(thm[1], 11);
|
||||
u32 J1 = bit(thm[1], 13);
|
||||
u32 imm6 = bits(thm[0], 5, 0);
|
||||
u32 imm11 = bits(thm[1], 10, 0);
|
||||
u32 val = (S << 20) | (J2 << 19) | (J1 << 18) | (imm6 << 12) | (imm11 << 1);
|
||||
return sign_extend(val, 21);
|
||||
}
|
||||
case R_ARM_THM_CALL:
|
||||
case R_ARM_THM_JUMP24:
|
||||
case R_ARM_THM_TLS_CALL: {
|
||||
// https://developer.arm.com/documentation/ddi0597/2024-12/Base-Instructions/BL--BLX--immediate---Branch-with-Link-and-optional-Exchange--immediate--
|
||||
u32 S = bit(thm[0], 10);
|
||||
u32 J1 = bit(thm[1], 13);
|
||||
u32 J2 = bit(thm[1], 11);
|
||||
u32 I1 = !(J1 ^ S);
|
||||
u32 I2 = !(J2 ^ S);
|
||||
u32 imm10 = bits(thm[0], 9, 0);
|
||||
u32 imm11 = bits(thm[1], 10, 0);
|
||||
u32 val = (S << 24) | (I1 << 23) | (I2 << 22) | (imm10 << 12) | (imm11 << 1);
|
||||
return sign_extend(val, 25);
|
||||
}
|
||||
case R_ARM_CALL:
|
||||
case R_ARM_JUMP24:
|
||||
case R_ARM_PLT32:
|
||||
case R_ARM_TLS_CALL:
|
||||
return sign_extend(*arm, 24) << 2;
|
||||
case R_ARM_MOVW_PREL_NC:
|
||||
case R_ARM_MOVW_ABS_NC:
|
||||
case R_ARM_MOVT_PREL:
|
||||
case R_ARM_MOVT_ABS: {
|
||||
// https://developer.arm.com/documentation/ddi0597/2024-12/Base-Instructions/MOV--MOVS--immediate---Move--immediate--
|
||||
u32 imm4 = bits(*arm, 19, 16);
|
||||
u32 imm12 = bits(*arm, 11, 0);
|
||||
u32 val = (imm4 << 12) | imm12;
|
||||
return sign_extend(val, 16);
|
||||
}
|
||||
case R_ARM_PREL31:
|
||||
return sign_extend(*arm, 31);
|
||||
case R_ARM_THM_MOVW_PREL_NC:
|
||||
case R_ARM_THM_MOVW_ABS_NC:
|
||||
case R_ARM_THM_MOVT_PREL:
|
||||
case R_ARM_THM_MOVT_ABS: {
|
||||
// https://developer.arm.com/documentation/ddi0597/2024-12/Base-Instructions/MOVT--Move-Top-
|
||||
u32 imm4 = bits(thm[0], 3, 0);
|
||||
u32 i = bit(thm[0], 10);
|
||||
u32 imm3 = bits(thm[1], 14, 12);
|
||||
u32 imm8 = bits(thm[1], 7, 0);
|
||||
u32 val = (imm4 << 12) | (i << 11) | (imm3 << 8) | imm8;
|
||||
return sign_extend(val, 16);
|
||||
}
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void write_arm_mov(u8 *loc, u32 val) {
|
||||
u32 imm12 = bits(val, 11, 0);
|
||||
u32 imm4 = bits(val, 15, 12);
|
||||
*(U32<E> *)loc = (*(U32<E> *)loc & 0xfff0'f000) | (imm4 << 16) | imm12;
|
||||
}
|
||||
|
||||
static void write_thm_b21(u8 *loc, u32 val) {
|
||||
u32 S = bit(val, 20);
|
||||
u32 J2 = bit(val, 19);
|
||||
u32 J1 = bit(val, 18);
|
||||
u32 imm6 = bits(val, 17, 12);
|
||||
u32 imm11 = bits(val, 11, 1);
|
||||
|
||||
U16<E> *buf = (U16<E> *)loc;
|
||||
buf[0] = (buf[0] & 0b1111'1011'1100'0000) | (S << 10) | imm6;
|
||||
buf[1] = (buf[1] & 0b1101'0000'0000'0000) | (J1 << 13) | (J2 << 11) | imm11;
|
||||
}
|
||||
|
||||
static void write_thm_b25(u8 *loc, u32 val) {
|
||||
u32 S = bit(val, 24);
|
||||
u32 I1 = bit(val, 23);
|
||||
u32 I2 = bit(val, 22);
|
||||
u32 J1 = !I1 ^ S;
|
||||
u32 J2 = !I2 ^ S;
|
||||
u32 imm10 = bits(val, 21, 12);
|
||||
u32 imm11 = bits(val, 11, 1);
|
||||
|
||||
U16<E> *buf = (U16<E> *)loc;
|
||||
buf[0] = (buf[0] & 0b1111'1000'0000'0000) | (S << 10) | imm10;
|
||||
buf[1] = (buf[1] & 0b1101'0000'0000'0000) | (J1 << 13) | (J2 << 11) | imm11;
|
||||
}
|
||||
|
||||
static void write_thm_mov(u8 *loc, u32 val) {
|
||||
u32 imm4 = bits(val, 15, 12);
|
||||
u32 i = bit(val, 11);
|
||||
u32 imm3 = bits(val, 10, 8);
|
||||
u32 imm8 = bits(val, 7, 0);
|
||||
|
||||
U16<E> *buf = (U16<E> *)loc;
|
||||
buf[0] = (buf[0] & 0b1111'1011'1111'0000) | (i << 10) | imm4;
|
||||
buf[1] = (buf[1] & 0b1000'1111'0000'0000) | (imm3 << 12) | imm8;
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_addend(u8 *loc, i64 val, const ElfRel<E> &rel) {
|
||||
switch (rel.r_type) {
|
||||
case R_ARM_NONE:
|
||||
break;
|
||||
case R_ARM_ABS32:
|
||||
case R_ARM_REL32:
|
||||
case R_ARM_BASE_PREL:
|
||||
case R_ARM_GOTOFF32:
|
||||
case R_ARM_GOT_PREL:
|
||||
case R_ARM_GOT_BREL:
|
||||
case R_ARM_TLS_GD32:
|
||||
case R_ARM_TLS_LDM32:
|
||||
case R_ARM_TLS_LDO32:
|
||||
case R_ARM_TLS_IE32:
|
||||
case R_ARM_TLS_LE32:
|
||||
case R_ARM_TLS_GOTDESC:
|
||||
case R_ARM_TARGET1:
|
||||
case R_ARM_TARGET2:
|
||||
*(U32<E> *)loc = val;
|
||||
break;
|
||||
case R_ARM_THM_JUMP8:
|
||||
*(U16<E> *)loc = (*(U16<E> *)loc & 0xff00) | bits(val, 8, 1);
|
||||
break;
|
||||
case R_ARM_THM_JUMP11:
|
||||
*(U16<E> *)loc = (*(U16<E> *)loc & 0xf800) | bits(val, 11, 1);
|
||||
break;
|
||||
case R_ARM_THM_CALL:
|
||||
case R_ARM_THM_JUMP24:
|
||||
case R_ARM_THM_TLS_CALL:
|
||||
write_thm_b25(loc, val);
|
||||
break;
|
||||
case R_ARM_CALL:
|
||||
case R_ARM_JUMP24:
|
||||
case R_ARM_PLT32:
|
||||
*(U32<E> *)loc = (*(U32<E> *)loc & 0xff00'0000) | bits(val, 25, 2);
|
||||
break;
|
||||
case R_ARM_MOVW_PREL_NC:
|
||||
case R_ARM_MOVW_ABS_NC:
|
||||
case R_ARM_MOVT_PREL:
|
||||
case R_ARM_MOVT_ABS:
|
||||
write_arm_mov(loc, val);
|
||||
break;
|
||||
case R_ARM_PREL31:
|
||||
*(U32<E> *)loc = (*(U32<E> *)loc & 0x8000'0000) | (val & 0x7fff'ffff);
|
||||
break;
|
||||
case R_ARM_THM_MOVW_PREL_NC:
|
||||
case R_ARM_THM_MOVW_ABS_NC:
|
||||
case R_ARM_THM_MOVT_PREL:
|
||||
case R_ARM_THM_MOVT_ABS:
|
||||
write_thm_mov(loc, val);
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
constexpr ul32 insn[] = {
|
||||
0xe52d'e004, // push {lr}
|
||||
0xe59f'e004, // ldr lr, 2f
|
||||
0xe08f'e00e, // 1: add lr, pc, lr
|
||||
0xe5be'f008, // ldr pc, [lr, #8]!
|
||||
0x0000'0000, // 2: .word .got.plt - 1b - 8
|
||||
0x0000'0000, // (padding)
|
||||
0x0000'0000, // (padding)
|
||||
0x0000'0000, // (padding)
|
||||
};
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(U32<E> *)(buf + 16) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr - 16;
|
||||
}
|
||||
|
||||
static constexpr ul32 plt_entry[] = {
|
||||
0xe59f'c004, // 1: ldr ip, 2f
|
||||
0xe08c'c00f, // add ip, ip, pc
|
||||
0xe59c'f000, // ldr pc, [ip]
|
||||
0x0000'0000, // 2: .word sym@GOT - 1b
|
||||
};
|
||||
|
||||
template <>
|
||||
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
memcpy(buf, plt_entry, sizeof(plt_entry));
|
||||
*(U32<E> *)(buf + 12) = sym.get_gotplt_addr(ctx) - sym.get_plt_addr(ctx) - 12;
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
memcpy(buf, plt_entry, sizeof(plt_entry));
|
||||
*(U32<E> *)(buf + 12) = sym.get_got_pltgot_addr(ctx) - sym.get_plt_addr(ctx) - 12;
|
||||
}
|
||||
|
||||
template <>
|
||||
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, InputSection<E> &isec,
|
||||
const ElfRel<E> &rel, u64 offset, u64 val) {
|
||||
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_NONE:
|
||||
break;
|
||||
case R_ARM_ABS32:
|
||||
*(U32<E> *)loc = val;
|
||||
break;
|
||||
case R_ARM_REL32:
|
||||
*(U32<E> *)loc = val - this->shdr.sh_addr - offset;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << "unsupported relocation in .eh_frame: " << rel;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE || rel.r_type == R_ARM_V4BX)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file->symbols[rel.r_sym];
|
||||
if (sym.get_type() == STT_TLS && sym.is_remaining_undef_weak())
|
||||
continue;
|
||||
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
u64 S = sym.get_addr(ctx);
|
||||
u64 A = get_addend(*this, rel);
|
||||
u64 P = get_addr() + rel.r_offset;
|
||||
u64 T = is_thumb_func(ctx, sym);
|
||||
u64 G = sym.get_got_idx(ctx) * sizeof(Word<E>);
|
||||
u64 GOT = ctx.got->shdr.sh_addr;
|
||||
|
||||
auto check = [&](i64 val, i64 lo, i64 hi) {
|
||||
check_range(ctx, i, val, lo, hi);
|
||||
};
|
||||
|
||||
auto get_thumb_thunk_addr = [&] { return sym.get_thunk_addr(ctx, P); };
|
||||
auto get_arm_thunk_addr = [&] { return sym.get_thunk_addr(ctx, P) + 4; };
|
||||
|
||||
auto get_tlsdesc_trampoline_addr = [&] {
|
||||
auto it = ranges::upper_bound(output_section->thunks, P, {},
|
||||
[](std::unique_ptr<Thunk<E>> &thunk) {
|
||||
return thunk->get_addr();
|
||||
});
|
||||
return (*it)->get_addr();
|
||||
};
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_ARM_ABS32:
|
||||
case R_ARM_TARGET1:
|
||||
break;
|
||||
case R_ARM_REL32:
|
||||
*(U32<E> *)loc = S + A - P;
|
||||
break;
|
||||
case R_ARM_THM_CALL: {
|
||||
if (sym.is_remaining_undef_weak()) {
|
||||
// On ARM, calling an weak undefined symbol jumps to the
|
||||
// next instruction.
|
||||
*(U32<E> *)loc = 0x8000'f3af; // NOP.W
|
||||
break;
|
||||
}
|
||||
|
||||
// THM_CALL relocation refers to either BL or BLX instruction.
|
||||
// They are different in only one bit. We need to use BLX if the
|
||||
// jump target is an ARM function. Otherwise, use BL.
|
||||
i64 val1 = S + A - P;
|
||||
i64 val2 = align_to(S + A - P, 4);
|
||||
bool arm = is_arm_func(ctx, sym);
|
||||
|
||||
if (!arm && is_int(val1, 25)) {
|
||||
*(U16<E> *)(loc + 2) |= 0x1000; // BL
|
||||
write_thm_b25(loc, val1);
|
||||
} else if (arm && is_int(val2, 25)) {
|
||||
*(U16<E> *)(loc + 2) &= ~0x1000; // BLX
|
||||
write_thm_b25(loc, val2);
|
||||
} else {
|
||||
*(U16<E> *)(loc + 2) |= 0x1000; // BL
|
||||
write_thm_b25(loc, get_thumb_thunk_addr() + A - P);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case R_ARM_BASE_PREL:
|
||||
*(U32<E> *)loc = GOT + A - P;
|
||||
break;
|
||||
case R_ARM_GOTOFF32:
|
||||
*(U32<E> *)loc = ((S + A) | T) - GOT;
|
||||
break;
|
||||
case R_ARM_GOT_PREL:
|
||||
case R_ARM_TARGET2:
|
||||
*(U32<E> *)loc = GOT + G + A - P;
|
||||
break;
|
||||
case R_ARM_GOT_BREL:
|
||||
*(U32<E> *)loc = G + A;
|
||||
break;
|
||||
case R_ARM_CALL: {
|
||||
if (sym.is_remaining_undef_weak()) {
|
||||
*(U32<E> *)loc = 0xe320'f000; // NOP
|
||||
break;
|
||||
}
|
||||
|
||||
// Just like THM_CALL, ARM_CALL relocation refers to either BL or
|
||||
// BLX instruction. We may need to rewrite BL → BLX or BLX → BL.
|
||||
bool is_bl = ((*(U32<E> *)loc & 0xff00'0000) == 0xeb00'0000);
|
||||
bool is_blx = ((*(U32<E> *)loc & 0xfe00'0000) == 0xfa00'0000);
|
||||
if (!is_bl && !is_blx)
|
||||
Fatal(ctx) << *this << ": R_ARM_CALL refers to neither BL nor BLX";
|
||||
|
||||
i64 val = S + A - P;
|
||||
if (is_int(val, 26)) {
|
||||
if (T) {
|
||||
*(U32<E> *)loc = 0xfa00'0000; // BLX
|
||||
*(U32<E> *)loc |= (bit(val, 1) << 24) | bits(val, 25, 2);
|
||||
} else {
|
||||
*(U32<E> *)loc = 0xeb00'0000; // BL
|
||||
*(U32<E> *)loc |= bits(val, 25, 2);
|
||||
}
|
||||
} else {
|
||||
*(U32<E> *)loc = 0xeb00'0000; // BL
|
||||
*(U32<E> *)loc |= bits(get_arm_thunk_addr() + A - P, 25, 2);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case R_ARM_JUMP24: {
|
||||
if (sym.is_remaining_undef_weak()) {
|
||||
*(U32<E> *)loc = 0xe320'f000; // NOP
|
||||
break;
|
||||
}
|
||||
|
||||
// These relocs refers to a B (unconditional branch) instruction.
|
||||
// Unlike BL or BLX, we can't rewrite B to BX in place when the
|
||||
// processor mode switch is required because BX doesn't takes an
|
||||
// immediate; it takes only a register. So if mode switch is
|
||||
// required, we jump to a linker-synthesized thunk which does the
|
||||
// job with a longer code sequence.
|
||||
i64 val = S + A - P;
|
||||
if (T || !is_int(val, 26))
|
||||
val = get_arm_thunk_addr() + A - P;
|
||||
*(U32<E> *)loc = (*(U32<E> *)loc & 0xff00'0000) | bits(val, 25, 2);
|
||||
break;
|
||||
}
|
||||
case R_ARM_PLT32:
|
||||
if (sym.is_remaining_undef_weak()) {
|
||||
*(U32<E> *)loc = 0xe320'f000; // NOP
|
||||
} else {
|
||||
u64 val = (T ? get_arm_thunk_addr() : S) + A - P;
|
||||
*(U32<E> *)loc = (*(U32<E> *)loc & 0xff00'0000) | bits(val, 25, 2);
|
||||
}
|
||||
break;
|
||||
case R_ARM_THM_JUMP8:
|
||||
check(S + A - P, -(1 << 8), 1 << 8);
|
||||
*(U16<E> *)loc &= 0xff00;
|
||||
*(U16<E> *)loc |= bits(S + A - P, 8, 1);
|
||||
break;
|
||||
case R_ARM_THM_JUMP11:
|
||||
check(S + A - P, -(1 << 11), 1 << 11);
|
||||
*(U16<E> *)loc &= 0xf800;
|
||||
*(U16<E> *)loc |= bits(S + A - P, 11, 1);
|
||||
break;
|
||||
case R_ARM_THM_JUMP19:
|
||||
check(S + A - P, -(1 << 20), 1 << 20);
|
||||
write_thm_b21(loc, S + A - P);
|
||||
break;
|
||||
case R_ARM_THM_JUMP24: {
|
||||
if (sym.is_remaining_undef_weak()) {
|
||||
*(U32<E> *)loc = 0x8000'f3af; // NOP
|
||||
break;
|
||||
}
|
||||
|
||||
// Just like R_ARM_JUMP24, we need to jump to a thunk if we need to
|
||||
// switch processor mode.
|
||||
i64 val = S + A - P;
|
||||
if (is_arm_func(ctx, sym) || !is_int(val, 25))
|
||||
val = get_thumb_thunk_addr() + A - P;
|
||||
write_thm_b25(loc, val);
|
||||
break;
|
||||
}
|
||||
case R_ARM_MOVW_PREL_NC:
|
||||
write_arm_mov(loc, ((S + A) | T) - P);
|
||||
break;
|
||||
case R_ARM_MOVW_ABS_NC:
|
||||
write_arm_mov(loc, (S + A) | T);
|
||||
break;
|
||||
case R_ARM_THM_MOVW_PREL_NC:
|
||||
write_thm_mov(loc, ((S + A) | T) - P);
|
||||
break;
|
||||
case R_ARM_PREL31:
|
||||
check(S + A - P, -(1LL << 30), 1LL << 30);
|
||||
*(U32<E> *)loc &= 0x8000'0000;
|
||||
*(U32<E> *)loc |= (S + A - P) & 0x7fff'ffff;
|
||||
break;
|
||||
case R_ARM_THM_MOVW_ABS_NC:
|
||||
write_thm_mov(loc, (S + A) | T);
|
||||
break;
|
||||
case R_ARM_MOVT_PREL:
|
||||
write_arm_mov(loc, (S + A - P) >> 16);
|
||||
break;
|
||||
case R_ARM_THM_MOVT_PREL:
|
||||
write_thm_mov(loc, (S + A - P) >> 16);
|
||||
break;
|
||||
case R_ARM_MOVT_ABS:
|
||||
write_arm_mov(loc, (S + A) >> 16);
|
||||
break;
|
||||
case R_ARM_THM_MOVT_ABS:
|
||||
write_thm_mov(loc, (S + A) >> 16);
|
||||
break;
|
||||
case R_ARM_TLS_GD32:
|
||||
*(U32<E> *)loc = sym.get_tlsgd_addr(ctx) + A - P;
|
||||
break;
|
||||
case R_ARM_TLS_LDM32:
|
||||
*(U32<E> *)loc = ctx.got->get_tlsld_addr(ctx) + A - P;
|
||||
break;
|
||||
case R_ARM_TLS_LDO32:
|
||||
*(U32<E> *)loc = S + A - ctx.dtp_addr;
|
||||
break;
|
||||
case R_ARM_TLS_IE32:
|
||||
*(U32<E> *)loc = sym.get_gottp_addr(ctx) + A - P;
|
||||
break;
|
||||
case R_ARM_TLS_LE32:
|
||||
*(U32<E> *)loc = S + A - ctx.tp_addr;
|
||||
break;
|
||||
case R_ARM_TLS_GOTDESC:
|
||||
// ARM32 TLSDESC uses the following code sequence to materialize
|
||||
// a TP-relative address in r0.
|
||||
//
|
||||
// ldr r0, .L2
|
||||
// .L1: bl foo
|
||||
// R_ARM_TLS_CALL
|
||||
// .L2: .word foo + . - .L1
|
||||
// R_ARM_TLS_GOTDESC
|
||||
//
|
||||
// We may relax the instructions to the following if its TP-relative
|
||||
// address is known at link-time
|
||||
//
|
||||
// ldr r0, .L2
|
||||
// .L1: nop
|
||||
// ...
|
||||
// .L2: .word foo(tpoff)
|
||||
//
|
||||
// or to the following if the TP-relative address is known at
|
||||
// process startup time.
|
||||
//
|
||||
// ldr r0, .L2
|
||||
// .L1: ldr r0, [pc, r0]
|
||||
// ...
|
||||
// .L2: .word foo(gottpoff) + . - .L1
|
||||
if (sym.has_tlsdesc(ctx)) {
|
||||
// A is odd if the corresponding TLS_CALL is Thumb.
|
||||
*(U32<E> *)loc = sym.get_tlsdesc_addr(ctx) - P + A - ((A & 1) ? 6 : 4);
|
||||
} else if (sym.has_gottp(ctx)) {
|
||||
*(U32<E> *)loc = sym.get_gottp_addr(ctx) - P + A - ((A & 1) ? 5 : 8);
|
||||
} else {
|
||||
*(U32<E> *)loc = S - ctx.tp_addr;
|
||||
}
|
||||
break;
|
||||
case R_ARM_TLS_CALL:
|
||||
if (sym.has_tlsdesc(ctx)) {
|
||||
*(U32<E> *)loc = 0xeb00'0000; // bl 0
|
||||
*(U32<E> *)loc |= bits(get_tlsdesc_trampoline_addr() - P - 8, 25, 2);
|
||||
} else if (sym.has_gottp(ctx)) {
|
||||
*(U32<E> *)loc = 0xe79f'0000; // ldr r0, [pc, r0]
|
||||
} else {
|
||||
*(U32<E> *)loc = 0xe320'f000; // nop
|
||||
}
|
||||
break;
|
||||
case R_ARM_THM_TLS_CALL:
|
||||
if (sym.has_tlsdesc(ctx)) {
|
||||
u64 val = align_to(get_tlsdesc_trampoline_addr() - P - 4, 4);
|
||||
write_thm_b25(loc, val);
|
||||
*(U16<E> *)(loc + 2) &= ~0x1000; // rewrite BL with BLX
|
||||
} else if (sym.has_gottp(ctx)) {
|
||||
// Since `ldr r0, [pc, r0]` is not representable in Thumb,
|
||||
// we use two instructions instead.
|
||||
*(U16<E> *)loc = 0x4478; // add r0, pc
|
||||
*(U16<E> *)(loc + 2) = 0x6800; // ldr r0, [r0]
|
||||
} else {
|
||||
*(U32<E> *)loc = 0x8000'f3af; // nop.w
|
||||
}
|
||||
break;
|
||||
default:
|
||||
Error(ctx) << *this << ": unknown relocation: " << rel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
for_each_reloc(ctx, [&](const ElfRel<E> &rel, i64) ALWAYS_INLINE {
|
||||
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
|
||||
return;
|
||||
|
||||
Symbol<E> &sym = *file->symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
SectionFragment<E> *frag;
|
||||
i64 frag_addend;
|
||||
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
|
||||
|
||||
u64 S = frag ? frag->get_addr(ctx) : sym.get_addr(ctx);
|
||||
u64 A = frag ? frag_addend : get_addend(*this, rel);
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_ARM_ABS32:
|
||||
if (std::optional<u64> val = get_tombstone(sym, frag))
|
||||
*(U32<E> *)loc = *val;
|
||||
else
|
||||
*(U32<E> *)loc = S + A;
|
||||
break;
|
||||
case R_ARM_TLS_LDO32:
|
||||
if (std::optional<u64> val = get_tombstone(sym, frag))
|
||||
*(U32<E> *)loc = *val;
|
||||
else
|
||||
*(U32<E> *)loc = S + A - ctx.dtp_addr;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
|
||||
<< rel;
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
assert(shdr().sh_flags & SHF_ALLOC);
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
// Scan relocations
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file->symbols[rel.r_sym];
|
||||
|
||||
if (sym.is_ifunc())
|
||||
sym.flags |= NEEDS_GOT | NEEDS_PLT;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_ARM_MOVW_ABS_NC:
|
||||
case R_ARM_THM_MOVW_ABS_NC:
|
||||
scan_absrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_ARM_THM_CALL:
|
||||
case R_ARM_CALL:
|
||||
case R_ARM_JUMP24:
|
||||
case R_ARM_PLT32:
|
||||
case R_ARM_THM_JUMP24:
|
||||
if (sym.is_imported)
|
||||
sym.flags |= NEEDS_PLT;
|
||||
break;
|
||||
case R_ARM_GOT_PREL:
|
||||
case R_ARM_GOT_BREL:
|
||||
case R_ARM_TARGET2:
|
||||
sym.flags |= NEEDS_GOT;
|
||||
break;
|
||||
case R_ARM_MOVT_PREL:
|
||||
case R_ARM_THM_MOVT_PREL:
|
||||
case R_ARM_PREL31:
|
||||
scan_pcrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_ARM_TLS_GD32:
|
||||
sym.flags |= NEEDS_TLSGD;
|
||||
break;
|
||||
case R_ARM_TLS_LDM32:
|
||||
ctx.needs_tlsld = true;
|
||||
break;
|
||||
case R_ARM_TLS_IE32:
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
break;
|
||||
case R_ARM_TLS_CALL:
|
||||
case R_ARM_THM_TLS_CALL:
|
||||
scan_tlsdesc(ctx, sym);
|
||||
break;
|
||||
case R_ARM_TLS_LE32:
|
||||
check_tlsle(ctx, sym, rel);
|
||||
break;
|
||||
case R_ARM_ABS32:
|
||||
case R_ARM_TARGET1:
|
||||
case R_ARM_MOVT_ABS:
|
||||
case R_ARM_THM_MOVT_ABS:
|
||||
case R_ARM_REL32:
|
||||
case R_ARM_BASE_PREL:
|
||||
case R_ARM_GOTOFF32:
|
||||
case R_ARM_THM_JUMP8:
|
||||
case R_ARM_THM_JUMP11:
|
||||
case R_ARM_THM_JUMP19:
|
||||
case R_ARM_MOVW_PREL_NC:
|
||||
case R_ARM_THM_MOVW_PREL_NC:
|
||||
case R_ARM_TLS_LDO32:
|
||||
case R_ARM_V4BX:
|
||||
case R_ARM_TLS_GOTDESC:
|
||||
break;
|
||||
default:
|
||||
Error(ctx) << *this << ": unknown relocation: " << rel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void Thunk<E>::copy_buf(Context<E> &ctx) {
|
||||
// TLS trampoline code. ARM32's TLSDESC is designed so that this
|
||||
// common piece of code is factored out from object files to reduce
|
||||
// output size. Since no one provide, the linker has to synthesize it.
|
||||
constexpr ul32 hdr[] = {
|
||||
0xe08e'0000, // add r0, lr, r0
|
||||
0xe590'1004, // ldr r1, [r0, #4]
|
||||
0xe12f'ff11, // bx r1
|
||||
0xe320'f000, // nop
|
||||
};
|
||||
|
||||
// This is a range extension and mode switch thunk.
|
||||
// It has two entry points: +0 for Thumb and +4 for ARM.
|
||||
constexpr u8 entry[] = {
|
||||
// .thumb
|
||||
0x78, 0x47, // bx pc # jumps to 1f
|
||||
0xc0, 0x46, // nop
|
||||
// .arm
|
||||
0x00, 0xc0, 0x9f, 0xe5, // 1: ldr ip, 3f
|
||||
0x0f, 0xf0, 0x8c, 0xe0, // 2: add pc, ip, pc
|
||||
0x00, 0x00, 0x00, 0x00, // 3: .word sym - 2b
|
||||
};
|
||||
|
||||
static_assert(E::thunk_hdr_size == sizeof(hdr));
|
||||
static_assert(E::thunk_size == sizeof(entry));
|
||||
|
||||
u8 *base = ctx.buf + output_section.shdr.sh_offset + offset;
|
||||
memcpy(base, hdr, sizeof(hdr));
|
||||
|
||||
for (i64 i = 0; i < symbols.size(); i++) {
|
||||
u64 S = symbols[i]->get_addr(ctx);
|
||||
u64 P = get_addr() + offsets[i];
|
||||
u8 *buf = base + offsets[i];
|
||||
memcpy(buf, entry, sizeof(entry));
|
||||
*(U32<E> *)(buf + 12) = S - P - 16;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
u64 get_eflags(Context<E> &ctx) {
|
||||
if constexpr (E::is_le)
|
||||
return EF_ARM_EABI_VER5;
|
||||
else
|
||||
return EF_ARM_EABI_VER5 | EF_ARM_BE8;
|
||||
}
|
||||
|
||||
template <>
|
||||
void create_arm_exidx_section<E>(Context<E> &ctx) {
|
||||
for (i64 i = 0; i < ctx.chunks.size(); i++) {
|
||||
OutputSection<E> *osec = ctx.chunks[i]->to_osec();
|
||||
|
||||
if (osec && osec->shdr.sh_type == SHT_ARM_EXIDX) {
|
||||
auto *sec = new Arm32ExidxSection(*osec);
|
||||
ctx.extra.exidx = sec;
|
||||
ctx.chunks[i] = sec;
|
||||
ctx.chunk_pool.emplace_back(sec);
|
||||
|
||||
for (InputSection<E> *isec : osec->members)
|
||||
isec->kill();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void Arm32ExidxSection<E>::compute_section_size(Context<E> &ctx) {
|
||||
output_section.compute_section_size(ctx);
|
||||
this->shdr.sh_size = output_section.shdr.sh_size + 8; // +8 for sentinel
|
||||
}
|
||||
|
||||
template <>
|
||||
void Arm32ExidxSection<E>::update_shdr(Context<E> &ctx) {
|
||||
// .ARM.exidx's sh_link should be set to the .text section index.
|
||||
// Runtime doesn't care about it, but the binutils's strip command does.
|
||||
if (Chunk<E> *chunk = find_chunk(ctx, ".text"))
|
||||
this->shdr.sh_link = chunk->shndx;
|
||||
}
|
||||
|
||||
// Returns the end of the text segment
|
||||
static u64 get_text_end(Context<E> &ctx) {
|
||||
u64 ret = 0;
|
||||
for (Chunk<E> *chunk : ctx.chunks)
|
||||
if (chunk->shdr.sh_flags & SHF_EXECINSTR)
|
||||
ret = std::max<u64>(ret, chunk->shdr.sh_addr + chunk->shdr.sh_size);
|
||||
return ret;
|
||||
}
|
||||
|
||||
// ARM executables use an .ARM.exidx section to look up an exception
|
||||
// handling record for the current instruction pointer. The table needs
|
||||
// to be sorted by their addresses.
|
||||
//
|
||||
// Other target uses .eh_frame_hdr instead for the same purpose.
|
||||
// I don't know why only ARM uses the different mechanism, but it's
|
||||
// likely that it's due to some historical reason.
|
||||
//
|
||||
// This function returns contents of .ARM.exidx.
|
||||
template <>
|
||||
std::vector<u8> Arm32ExidxSection<E>::get_contents(Context<E> &ctx) {
|
||||
// .ARM.exidx records consists of a signed 31-bit relative address
|
||||
// and a 32-bit value. The relative address indicates the start
|
||||
// address of a function that the record covers. The value is one of
|
||||
// the followings:
|
||||
//
|
||||
// 1. CANTUNWIND indicating that there's no unwinding info for the function,
|
||||
// 2. a compact unwinding record encoded into a 32-bit value, or
|
||||
// 3. a 31-bit relative address which points to a larger record in
|
||||
// the .ARM.extab section.
|
||||
//
|
||||
// CANTUNWIND is value 1. The most significant bit is set in (2) but
|
||||
// not in (3). So we can distinguished them just by looking at a value.
|
||||
const u32 CANTUNWIND = 1;
|
||||
|
||||
struct Entry {
|
||||
U32<E> addr;
|
||||
U32<E> val;
|
||||
};
|
||||
|
||||
// We reserve one extra slot for the sentinel
|
||||
i64 num_entries = output_section.shdr.sh_size / sizeof(Entry) + 1;
|
||||
std::vector<u8> buf(num_entries * sizeof(Entry));
|
||||
Entry *ent = (Entry *)buf.data();
|
||||
|
||||
// Write section contents to the buffer
|
||||
output_section.shdr.sh_addr = this->shdr.sh_addr;
|
||||
output_section.write_to(ctx, buf.data());
|
||||
|
||||
// Fill in sentinel fields
|
||||
u64 sentinel_addr = this->shdr.sh_addr + sizeof(Entry) * (num_entries - 1);
|
||||
ent[num_entries - 1].addr = get_text_end(ctx) - sentinel_addr;
|
||||
ent[num_entries - 1].val = CANTUNWIND;
|
||||
|
||||
// Entry's addresses are relative to themselves. In order to sort
|
||||
// records by address, we first translate them so that the addresses
|
||||
// are relative to the beginning of the section.
|
||||
auto is_relative = [](u32 val) {
|
||||
return val != CANTUNWIND && !(val & 0x8000'0000);
|
||||
};
|
||||
|
||||
tbb::parallel_for((i64)0, num_entries, [&](i64 i) {
|
||||
i64 offset = sizeof(Entry) * i;
|
||||
ent[i].addr = sign_extend(ent[i].addr, 31) + offset;
|
||||
if (is_relative(ent[i].val))
|
||||
ent[i].val = 0x7fff'ffff & (ent[i].val + offset);
|
||||
});
|
||||
|
||||
ranges::sort(ent, ent + num_entries, {}, &Entry::addr);
|
||||
|
||||
// Remove duplicate adjacent entries. That is, if two adjacent functions
|
||||
// have the same compact unwind info or are both CANTUNWIND, we can
|
||||
// merge them into a single address range.
|
||||
auto tail = ranges::unique(ent, ent + num_entries, {}, &Entry::val);
|
||||
num_entries -= tail.size();
|
||||
buf.resize(num_entries * sizeof(Entry));
|
||||
|
||||
// Make addresses relative to themselves.
|
||||
tbb::parallel_for((i64)0, num_entries, [&](i64 i) {
|
||||
i64 offset = sizeof(Entry) * i;
|
||||
ent[i].addr = 0x7fff'ffff & (ent[i].addr - offset);
|
||||
if (is_relative(ent[i].val))
|
||||
ent[i].val = 0x7fff'ffff & (ent[i].val - offset);
|
||||
});
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
template <>
|
||||
void Arm32ExidxSection<E>::remove_duplicate_entries(Context<E> &ctx) {
|
||||
this->shdr.sh_size = get_contents(ctx).size();
|
||||
}
|
||||
|
||||
template <>
|
||||
void Arm32ExidxSection<E>::copy_buf(Context<E> &ctx) {
|
||||
std::vector<u8> contents = get_contents(ctx);
|
||||
assert(this->shdr.sh_size == contents.size());
|
||||
write_vector(ctx.buf + this->shdr.sh_offset, contents);
|
||||
}
|
||||
|
||||
// Even though using ARM32 in big-endian mode is very rare, the processor
|
||||
// technically supports both little- and big-endian modes. There are two
|
||||
// variants of big-endian mode: BE32 and BE8. In BE32, instructions and
|
||||
// data are encoded in big-endian. In BE8, instructions are encoded in
|
||||
// little-endian, and only data is in big-endian. BE8 is the de facto
|
||||
// standard for ARMv6 or later. We support only BE8.
|
||||
//
|
||||
// A tricky thing is that instructions in an object file are always
|
||||
// big-endian if the file is compiled for big-endian mode. In other words,
|
||||
// the compiler always emit code in BE32 if -mbig-endian is specified. It
|
||||
// is the linker's responsibility to rewrite instructions from big-endian
|
||||
// to little-endian for an BE8 output. This function does that.
|
||||
//
|
||||
// The text section may contain a mix of 32-bit ARM instructions, 16-bit
|
||||
// Thumb instructions, and data. We need to distinguish them to swap 4
|
||||
// bytes, 2 bytes, or not swap bytes, respectively. The beginning of ARM
|
||||
// code, Thumb code, and data is labeled with a mapping symbol of $a, $t,
|
||||
// and $d, respectively. We use mapping symbols to determine what to do
|
||||
// with the text section.
|
||||
//
|
||||
// This function is called after we copy the input section contents to the
|
||||
// output file. We rewrite instructions in the output buffer in place.
|
||||
#if MOLD_ARM32BE
|
||||
void arm32be_swap_bytes(Context<E> &ctx) {
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
// Collect mapping symbols
|
||||
std::vector<Symbol<E> *> syms;
|
||||
|
||||
for (Symbol<E> *sym : file->get_local_syms())
|
||||
if (InputSection<E> *isec = sym->get_input_section())
|
||||
if (isec->is_alive() && (isec->shdr().sh_flags & SHF_EXECINSTR))
|
||||
if (std::string_view x = sym->name();
|
||||
x == "$a" || x.starts_with("$a.") ||
|
||||
x == "$t" || x.starts_with("$t.") ||
|
||||
x == "$d" || x.starts_with("$d."))
|
||||
syms.push_back(sym);
|
||||
|
||||
// Group mapping symbols by input section and sort by address
|
||||
ranges::stable_sort(syms, {}, [](const Symbol<E> *sym) {
|
||||
return std::tuple{(uintptr_t)sym->get_input_section(), sym->value};
|
||||
});
|
||||
|
||||
// Swap bytes
|
||||
for (i64 i = 0; i < syms.size(); i++) {
|
||||
Symbol<E> &sym = *syms[i];
|
||||
if (sym.name().starts_with("$d"))
|
||||
continue;
|
||||
|
||||
InputSection<E> &isec = *sym.get_input_section();
|
||||
u8 *base = ctx.buf + isec.output_section->shdr.sh_offset + isec.offset;
|
||||
u8 *start = base + sym.value;
|
||||
u8 *end;
|
||||
|
||||
if (i + 1 < syms.size() && syms[i + 1]->get_input_section() == &isec)
|
||||
end = base + syms[i + 1]->value;
|
||||
else
|
||||
end = base + isec.sh_size;
|
||||
|
||||
i64 sz = sym.name().starts_with("$a") ? 4 : 2;
|
||||
for (u8 *p = start; p < end; p += sz)
|
||||
std::reverse(p, p + sz);
|
||||
}
|
||||
});
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace mold
|
||||
|
||||
#endif
|
||||
@@ -1,729 +0,0 @@
|
||||
// This file contains ARM64-specific code. Being new, the ARM64's ELF
|
||||
// psABI doesn't have anything peculiar. ARM64 is a clean RISC
|
||||
// instruction set that supports PC-relative load/store instructions.
|
||||
//
|
||||
// Unlike ARM32, instructions length doesn't vary. All ARM64
|
||||
// instructions are 4 bytes long.
|
||||
//
|
||||
// Branch instructions used for function call can jump within ±128 MiB.
|
||||
// We need to create range extension thunks to support binaries whose
|
||||
// .text is larger than that.
|
||||
//
|
||||
// Unlike most other targets, the TLSDESC access model is used by default
|
||||
// for -fPIC to access thread-local variables instead of the less
|
||||
// efficient GD model. You can still enable GD but it needs the
|
||||
// -mtls-dialect=trad flag. Since GD is used rarely, we don't need to
|
||||
// implement GD → LE relaxation.
|
||||
//
|
||||
// https://github.com/ARM-software/abi-aa/blob/main/aaelf64/aaelf64.rst
|
||||
|
||||
#if MOLD_ARM64LE || MOLD_ARM64BE
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
namespace mold {
|
||||
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
static void write_adrp(u8 *buf, u64 val) {
|
||||
*(ul32 *)buf |= (bits(val, 13, 12) << 29) | (bits(val, 32, 14) << 5);
|
||||
}
|
||||
|
||||
static void write_adr(u8 *buf, u64 val) {
|
||||
*(ul32 *)buf |= (bits(val, 1, 0) << 29) | (bits(val, 20, 2) << 5);
|
||||
}
|
||||
|
||||
static void write_movn_movz(u8 *buf, i64 val) {
|
||||
*(ul32 *)buf &= 0b0000'0000'0110'0000'0000'0000'0001'1111;
|
||||
|
||||
if (val >= 0)
|
||||
*(ul32 *)buf |= 0xd280'0000 | (bits(val, 15, 0) << 5); // rewrite to movz
|
||||
else
|
||||
*(ul32 *)buf |= 0x9280'0000 | (bits(~val, 15, 0) << 5); // rewrite to movn
|
||||
}
|
||||
|
||||
static u64 page(u64 val) {
|
||||
return val & 0xffff'ffff'ffff'f000;
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
constexpr ul32 insn[] = {
|
||||
0xa9bf'7bf0, // stp x16, x30, [sp,#-16]!
|
||||
0x9000'0010, // adrp x16, .got.plt[2]
|
||||
0xf940'0211, // ldr x17, [x16, .got.plt[2]]
|
||||
0x9100'0210, // add x16, x16, .got.plt[2]
|
||||
0xd61f'0220, // br x17
|
||||
0xd420'7d00, // brk
|
||||
0xd420'7d00, // brk
|
||||
0xd420'7d00, // brk
|
||||
};
|
||||
|
||||
u64 gotplt = ctx.gotplt->shdr.sh_addr + 16;
|
||||
u64 plt = ctx.plt->shdr.sh_addr;
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
write_adrp(buf + 4, page(gotplt) - page(plt + 4));
|
||||
*(ul32 *)(buf + 8) |= bits(gotplt, 11, 3) << 10;
|
||||
*(ul32 *)(buf + 12) |= (gotplt & 0xfff) << 10;
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
constexpr ul32 insn[] = {
|
||||
0x9000'0010, // adrp x16, .got.plt[n]
|
||||
0xf940'0211, // ldr x17, [x16, .got.plt[n]]
|
||||
0x9100'0210, // add x16, x16, .got.plt[n]
|
||||
0xd61f'0220, // br x17
|
||||
};
|
||||
|
||||
u64 gotplt = sym.get_gotplt_addr(ctx);
|
||||
u64 plt = sym.get_plt_addr(ctx);
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
write_adrp(buf, page(gotplt) - page(plt));
|
||||
*(ul32 *)(buf + 4) |= bits(gotplt, 11, 3) << 10;
|
||||
*(ul32 *)(buf + 8) |= (gotplt & 0xfff) << 10;
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
constexpr ul32 insn[] = {
|
||||
0x9000'0010, // adrp x16, GOT[n]
|
||||
0xf940'0211, // ldr x17, [x16, GOT[n]]
|
||||
0xd61f'0220, // br x17
|
||||
0xd420'7d00, // brk
|
||||
};
|
||||
|
||||
u64 got = sym.get_got_pltgot_addr(ctx);
|
||||
u64 plt = sym.get_plt_addr(ctx);
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
write_adrp(buf, page(got) - page(plt));
|
||||
*(ul32 *)(buf + 4) |= bits(got, 11, 3) << 10;
|
||||
}
|
||||
|
||||
template <>
|
||||
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, InputSection<E> &isec,
|
||||
const ElfRel<E> &rel, u64 offset, u64 val) {
|
||||
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
|
||||
u64 P = this->shdr.sh_addr + offset;
|
||||
|
||||
auto check = [&](i64 val, i64 lo, i64 hi) {
|
||||
check_range(ctx, isec, rel, val, lo, hi);
|
||||
};
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_NONE:
|
||||
break;
|
||||
case R_AARCH64_ABS64:
|
||||
*(U64<E> *)loc = val;
|
||||
break;
|
||||
case R_AARCH64_PREL32:
|
||||
check(val - P, -(1LL << 31), 1LL << 31);
|
||||
*(U32<E> *)loc = val - P;
|
||||
break;
|
||||
case R_AARCH64_PREL64:
|
||||
*(U64<E> *)loc = val - P;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << "unsupported relocation in .eh_frame: " << rel;
|
||||
}
|
||||
}
|
||||
|
||||
static bool is_adrp(u8 *loc) {
|
||||
// https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/ADRP--Form-PC-relative-address-to-4KB-page-
|
||||
u32 insn = *(ul32 *)loc;
|
||||
return (bits(insn, 31, 24) & 0b1001'1111) == 0b1001'0000;
|
||||
}
|
||||
|
||||
static bool is_ldr(u8 *loc) {
|
||||
// https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/LDR--immediate---Load-Register--immediate--
|
||||
u32 insn = *(ul32 *)loc;
|
||||
return (bits(insn, 31, 20) & 0b1111'1111'1100) == 0b1111'1001'0100;
|
||||
}
|
||||
|
||||
static bool is_add(u8 *loc) {
|
||||
// https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/ADD--immediate---Add--immediate--
|
||||
u32 insn = *(ul32 *)loc;
|
||||
return (bits(insn, 31, 20) & 0b1111'1111'1100) == 0b1001'0001'0000;
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file->symbols[rel.r_sym];
|
||||
if (sym.get_type() == STT_TLS && sym.is_remaining_undef_weak())
|
||||
continue;
|
||||
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
u64 S = sym.get_addr(ctx);
|
||||
u64 A = rel.r_addend;
|
||||
u64 P = get_addr() + rel.r_offset;
|
||||
u64 G = sym.get_got_idx(ctx) * sizeof(Word<E>);
|
||||
u64 GOT = ctx.got->shdr.sh_addr;
|
||||
|
||||
auto check = [&](i64 val, i64 lo, i64 hi) {
|
||||
check_range(ctx, i, val, lo, hi);
|
||||
};
|
||||
|
||||
auto rewrite = [&](i64 idx, u32 ty) {
|
||||
if (ctx.arg.emit_relocs)
|
||||
rels[idx].r_type = ty;
|
||||
};
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_AARCH64_ABS64:
|
||||
break;
|
||||
case R_AARCH64_LDST8_ABS_LO12_NC:
|
||||
case R_AARCH64_ADD_ABS_LO12_NC:
|
||||
*(ul32 *)loc |= bits(S + A, 11, 0) << 10;
|
||||
break;
|
||||
case R_AARCH64_LDST16_ABS_LO12_NC:
|
||||
*(ul32 *)loc |= bits(S + A, 11, 1) << 10;
|
||||
break;
|
||||
case R_AARCH64_LDST32_ABS_LO12_NC:
|
||||
*(ul32 *)loc |= bits(S + A, 11, 2) << 10;
|
||||
break;
|
||||
case R_AARCH64_LDST64_ABS_LO12_NC:
|
||||
*(ul32 *)loc |= bits(S + A, 11, 3) << 10;
|
||||
break;
|
||||
case R_AARCH64_LDST128_ABS_LO12_NC:
|
||||
*(ul32 *)loc |= bits(S + A, 11, 4) << 10;
|
||||
break;
|
||||
case R_AARCH64_MOVW_UABS_G0:
|
||||
check(S + A, 0, 1 << 16);
|
||||
*(ul32 *)loc |= bits(S + A, 15, 0) << 5;
|
||||
break;
|
||||
case R_AARCH64_MOVW_UABS_G0_NC:
|
||||
*(ul32 *)loc |= bits(S + A, 15, 0) << 5;
|
||||
break;
|
||||
case R_AARCH64_MOVW_UABS_G1:
|
||||
check(S + A, 0, 1LL << 32);
|
||||
*(ul32 *)loc |= bits(S + A, 31, 16) << 5;
|
||||
break;
|
||||
case R_AARCH64_MOVW_UABS_G1_NC:
|
||||
*(ul32 *)loc |= bits(S + A, 31, 16) << 5;
|
||||
break;
|
||||
case R_AARCH64_MOVW_UABS_G2:
|
||||
check(S + A, 0, 1LL << 48);
|
||||
*(ul32 *)loc |= bits(S + A, 47, 32) << 5;
|
||||
break;
|
||||
case R_AARCH64_MOVW_UABS_G2_NC:
|
||||
*(ul32 *)loc |= bits(S + A, 47, 32) << 5;
|
||||
break;
|
||||
case R_AARCH64_MOVW_UABS_G3:
|
||||
*(ul32 *)loc |= bits(S + A, 63, 48) << 5;
|
||||
break;
|
||||
case R_AARCH64_MOVW_PREL_G0:
|
||||
check(S + A - P, -(1 << 15), 1 << 15);
|
||||
write_movn_movz(loc, S + A - P);
|
||||
break;
|
||||
case R_AARCH64_MOVW_PREL_G0_NC:
|
||||
*(ul32 *)loc |= bits(S + A - P, 15, 0) << 5;
|
||||
break;
|
||||
case R_AARCH64_MOVW_PREL_G1:
|
||||
check(S + A - P, -(1LL << 31), 1LL << 31);
|
||||
write_movn_movz(loc, (i64)(S + A - P) >> 16);
|
||||
break;
|
||||
case R_AARCH64_MOVW_PREL_G1_NC:
|
||||
*(ul32 *)loc |= bits(S + A - P, 31, 16) << 5;
|
||||
break;
|
||||
case R_AARCH64_MOVW_PREL_G2:
|
||||
check(S + A - P, -(1LL << 47), 1LL << 47);
|
||||
write_movn_movz(loc, (i64)(S + A - P) >> 32);
|
||||
break;
|
||||
case R_AARCH64_MOVW_PREL_G2_NC:
|
||||
*(ul32 *)loc |= bits(S + A - P, 47, 32) << 5;
|
||||
break;
|
||||
case R_AARCH64_MOVW_PREL_G3:
|
||||
*(ul32 *)loc |= bits(S + A - P, 63, 48) << 5;
|
||||
break;
|
||||
case R_AARCH64_ADR_GOT_PAGE:
|
||||
if (sym.has_got(ctx)) {
|
||||
i64 val = page(G + GOT + A) - page(P);
|
||||
check(val, -(1LL << 32), 1LL << 32);
|
||||
write_adrp(loc, val);
|
||||
} else {
|
||||
// Relax GOT-loading ADRP+LDR to an immediate ADRP+ADD
|
||||
i64 val = page(S + A) - page(P);
|
||||
check(val, -(1LL << 32), 1LL << 32);
|
||||
write_adrp(loc, val);
|
||||
|
||||
u32 reg = bits(*(ul32 *)loc, 4, 0);
|
||||
*(ul32 *)(loc + 4) = 0x9100'0000 | (reg << 5) | reg; // ADD
|
||||
*(ul32 *)(loc + 4) |= bits(S + A, 11, 0) << 10;
|
||||
|
||||
rewrite(i, R_AARCH64_ADR_PREL_PG_HI21);
|
||||
rewrite(i + 1, R_AARCH64_ADD_ABS_LO12_NC);
|
||||
i++;
|
||||
}
|
||||
break;
|
||||
case R_AARCH64_ADR_PREL_PG_HI21:
|
||||
case R_AARCH64_ADR_PREL_PG_HI21_NC: {
|
||||
// The ARM64 psABI defines that an `ADRP x0, foo` and `ADD x0, x0,
|
||||
// :lo12: foo` instruction pair to materialize a PC-relative address
|
||||
// in a register can be relaxed to `NOP` followed by `ADR x0, foo`
|
||||
// if foo is in PC ± 1 MiB.
|
||||
if (ctx.arg.relax && sym.is_pcrel_linktime_const(ctx) &&
|
||||
i + 1 < rels.size()) {
|
||||
i64 val = S + A - P - 4;
|
||||
const ElfRel<E> &rel2 = rels[i + 1];
|
||||
if (is_int(val, 21) &&
|
||||
rel2.r_type == R_AARCH64_ADD_ABS_LO12_NC &&
|
||||
rel2.r_sym == rel.r_sym &&
|
||||
rel2.r_offset == rel.r_offset + 4 &&
|
||||
rel2.r_addend == rel.r_addend &&
|
||||
is_adrp(loc) &&
|
||||
is_add(loc + 4)) {
|
||||
u32 reg1 = bits(*(ul32 *)loc, 4, 0);
|
||||
u32 reg2 = bits(*(ul32 *)(loc + 4), 4, 0);
|
||||
if (reg1 == reg2) {
|
||||
*(ul32 *)loc = 0xd503'201f; // nop
|
||||
*(ul32 *)(loc + 4) = 0x1000'0000 | reg1; // adr
|
||||
write_adr(loc + 4, val);
|
||||
rewrite(i, R_NONE);
|
||||
rewrite(i + 1, R_AARCH64_ADR_PREL_LO21);
|
||||
i++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
i64 val = page(S + A) - page(P);
|
||||
if (rel.r_type == R_AARCH64_ADR_PREL_PG_HI21)
|
||||
check(val, -(1LL << 32), 1LL << 32);
|
||||
write_adrp(loc, val);
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_ADR_PREL_LO21:
|
||||
check(S + A - P, -(1LL << 20), 1LL << 20);
|
||||
write_adr(loc, S + A - P);
|
||||
break;
|
||||
case R_AARCH64_CALL26:
|
||||
case R_AARCH64_JUMP26: {
|
||||
if (sym.is_remaining_undef_weak()) {
|
||||
// On ARM, calling an weak undefined symbol jumps to the
|
||||
// next instruction.
|
||||
*(ul32 *)loc = 0xd503'201f; // nop
|
||||
break;
|
||||
}
|
||||
|
||||
i64 val = S + A - P;
|
||||
if (!is_int(val, 28))
|
||||
val = sym.get_thunk_addr(ctx, P) + A - P;
|
||||
*(ul32 *)loc |= bits(val, 27, 2);
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_PLT32:
|
||||
check(S + A - P, -(1LL << 31), 1LL << 31);
|
||||
*(U32<E> *)loc = S + A - P;
|
||||
break;
|
||||
case R_AARCH64_GOTPCREL32:
|
||||
check(G + GOT + A - P, -(1LL << 31), 1LL << 31);
|
||||
*(U32<E> *)loc = G + GOT + A - P;
|
||||
break;
|
||||
case R_AARCH64_CONDBR19:
|
||||
case R_AARCH64_LD_PREL_LO19:
|
||||
check(S + A - P, -(1LL << 20), 1LL << 20);
|
||||
*(ul32 *)loc |= bits(S + A - P, 20, 2) << 5;
|
||||
break;
|
||||
case R_AARCH64_PREL16:
|
||||
check(S + A - P, -(1LL << 15), 1LL << 16);
|
||||
*(U16<E> *)loc = S + A - P;
|
||||
break;
|
||||
case R_AARCH64_PREL32:
|
||||
check(S + A - P, -(1LL << 31), 1LL << 32);
|
||||
*(U32<E> *)loc = S + A - P;
|
||||
break;
|
||||
case R_AARCH64_PREL64:
|
||||
*(U64<E> *)loc = S + A - P;
|
||||
break;
|
||||
case R_AARCH64_LD64_GOT_LO12_NC:
|
||||
*(ul32 *)loc |= bits(G + GOT + A, 11, 3) << 10;
|
||||
break;
|
||||
case R_AARCH64_LD64_GOTPAGE_LO15: {
|
||||
i64 val = G + GOT + A - page(GOT);
|
||||
check(val, 0, 1 << 15);
|
||||
*(ul32 *)loc |= bits(val, 14, 3) << 10;
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_TLSIE_ADR_GOTTPREL_PAGE21: {
|
||||
i64 val = page(sym.get_gottp_addr(ctx) + A) - page(P);
|
||||
check(val, -(1LL << 32), 1LL << 32);
|
||||
write_adrp(loc, val);
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_TLSIE_LD64_GOTTPREL_LO12_NC:
|
||||
*(ul32 *)loc |= bits(sym.get_gottp_addr(ctx) + A, 11, 3) << 10;
|
||||
break;
|
||||
case R_AARCH64_TLSLE_MOVW_TPREL_G0: {
|
||||
i64 val = S + A - ctx.tp_addr;
|
||||
check(val, -(1 << 15), 1 << 15);
|
||||
write_movn_movz(loc, val);
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_TLSLE_MOVW_TPREL_G0_NC:
|
||||
*(ul32 *)loc |= bits(S + A - ctx.tp_addr, 15, 0) << 5;
|
||||
break;
|
||||
case R_AARCH64_TLSLE_MOVW_TPREL_G1: {
|
||||
i64 val = S + A - ctx.tp_addr;
|
||||
check(val, -(1LL << 31), 1LL << 31);
|
||||
write_movn_movz(loc, val >> 16);
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_TLSLE_MOVW_TPREL_G1_NC:
|
||||
*(ul32 *)loc |= bits(S + A - ctx.tp_addr, 31, 16) << 5;
|
||||
break;
|
||||
case R_AARCH64_TLSLE_MOVW_TPREL_G2: {
|
||||
i64 val = S + A - ctx.tp_addr;
|
||||
check(val, -(1LL << 47), 1LL << 47);
|
||||
write_movn_movz(loc, val >> 32);
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_TLSLE_ADD_TPREL_HI12: {
|
||||
i64 val = S + A - ctx.tp_addr;
|
||||
check(val, 0, 1LL << 24);
|
||||
*(ul32 *)loc |= bits(val, 23, 12) << 10;
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_TLSLE_ADD_TPREL_LO12:
|
||||
check(S + A - ctx.tp_addr, 0, 1 << 12);
|
||||
*(ul32 *)loc |= bits(S + A - ctx.tp_addr, 11, 0) << 10;
|
||||
break;
|
||||
case R_AARCH64_TLSLE_ADD_TPREL_LO12_NC:
|
||||
*(ul32 *)loc |= bits(S + A - ctx.tp_addr, 11, 0) << 10;
|
||||
break;
|
||||
case R_AARCH64_TLSGD_ADR_PAGE21: {
|
||||
i64 val = page(sym.get_tlsgd_addr(ctx) + A) - page(P);
|
||||
check(val, -(1LL << 32), 1LL << 32);
|
||||
write_adrp(loc, val);
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_TLSGD_ADD_LO12_NC:
|
||||
*(ul32 *)loc |= bits(sym.get_tlsgd_addr(ctx) + A, 11, 0) << 10;
|
||||
break;
|
||||
case R_AARCH64_TLSDESC_ADR_PAGE21:
|
||||
// ARM64 TLSDESC uses the following code sequence to materialize
|
||||
// a TP-relative address in x0.
|
||||
//
|
||||
// adrp x0, 0
|
||||
// R_AARCH64_TLSDESC_ADR_PAGE21 foo
|
||||
// ldr x1, [x0]
|
||||
// R_AARCH64_TLSDESC_LD64_LO12 foo
|
||||
// add x0, x0, #0
|
||||
// R_AARCH64_TLSDESC_ADD_LO12 foo
|
||||
// blr x1
|
||||
// R_AARCH64_TLSDESC_CALL foo
|
||||
//
|
||||
// We may relax the instructions to the following if its TP-relative
|
||||
// address is known at link-time
|
||||
//
|
||||
// nop
|
||||
// nop
|
||||
// movz x0, :tls_offset_hi:foo, lsl #16
|
||||
// movk x0, :tls_offset_lo:foo
|
||||
//
|
||||
// or to the following if the TP-relative address is known at
|
||||
// process startup time.
|
||||
//
|
||||
// nop
|
||||
// nop
|
||||
// adrp x0, :gottprel:foo
|
||||
// ldr x0, [x0, :gottprel_lo12:foo]
|
||||
if (sym.has_tlsdesc(ctx)) {
|
||||
i64 val = page(sym.get_tlsdesc_addr(ctx) + A) - page(P);
|
||||
check(val, -(1LL << 32), 1LL << 32);
|
||||
write_adrp(loc, val);
|
||||
} else {
|
||||
*(ul32 *)loc = 0xd503'201f; // nop
|
||||
rewrite(i, R_NONE);
|
||||
}
|
||||
break;
|
||||
case R_AARCH64_TLSDESC_LD64_LO12:
|
||||
if (sym.has_tlsdesc(ctx)) {
|
||||
*(ul32 *)loc |= bits(sym.get_tlsdesc_addr(ctx) + A, 11, 3) << 10;
|
||||
} else {
|
||||
*(ul32 *)loc = 0xd503'201f; // nop
|
||||
rewrite(i, R_NONE);
|
||||
}
|
||||
break;
|
||||
case R_AARCH64_TLSDESC_ADD_LO12:
|
||||
if (sym.has_tlsdesc(ctx)) {
|
||||
*(ul32 *)loc |= bits(sym.get_tlsdesc_addr(ctx) + A, 11, 0) << 10;
|
||||
} else if (sym.has_gottp(ctx)) {
|
||||
*(ul32 *)loc = 0x9000'0000; // adrp x0, 0
|
||||
write_adrp(loc, page(sym.get_gottp_addr(ctx) + A) - page(P));
|
||||
rewrite(i, R_AARCH64_TLSIE_ADR_GOTTPREL_PAGE21);
|
||||
} else {
|
||||
*(ul32 *)loc = 0xd2a0'0000; // movz x0, 0, lsl #16
|
||||
*(ul32 *)loc |= bits(S + A - ctx.tp_addr, 32, 16) << 5;
|
||||
rewrite(i, R_AARCH64_TLSLE_MOVW_TPREL_G1);
|
||||
}
|
||||
break;
|
||||
case R_AARCH64_TLSDESC_CALL:
|
||||
if (sym.has_tlsdesc(ctx)) {
|
||||
// Do nothing
|
||||
} else if (sym.has_gottp(ctx)) {
|
||||
*(ul32 *)loc = 0xf940'0000; // ldr x0, [x0, 0]
|
||||
*(ul32 *)loc |= bits(sym.get_gottp_addr(ctx) + A, 11, 3) << 10;
|
||||
rewrite(i, R_AARCH64_TLSIE_LD64_GOTTPREL_LO12_NC);
|
||||
} else {
|
||||
*(ul32 *)loc = 0xf280'0000; // movk x0, 0
|
||||
*(ul32 *)loc |= bits(S + A - ctx.tp_addr, 15, 0) << 5;
|
||||
rewrite(i, R_AARCH64_TLSLE_MOVW_TPREL_G0_NC);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
for_each_reloc(ctx, [&](const ElfRel<E> &rel, i64 i) ALWAYS_INLINE {
|
||||
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
|
||||
return;
|
||||
|
||||
Symbol<E> &sym = *file->symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
SectionFragment<E> *frag;
|
||||
i64 frag_addend;
|
||||
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
|
||||
|
||||
u64 S = frag ? frag->get_addr(ctx) : sym.get_addr(ctx);
|
||||
u64 A = frag ? frag_addend : (i64)rel.r_addend;
|
||||
|
||||
auto check = [&](i64 val, i64 lo, i64 hi) {
|
||||
check_range(ctx, i, val, lo, hi);
|
||||
};
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_AARCH64_ABS64:
|
||||
if (std::optional<u64> val = get_tombstone(sym, frag))
|
||||
*(U64<E> *)loc = *val;
|
||||
else
|
||||
*(U64<E> *)loc = S + A;
|
||||
break;
|
||||
case R_AARCH64_ABS32:
|
||||
check(S + A, 0, 1LL << 32);
|
||||
*(U32<E> *)loc = S + A;
|
||||
break;
|
||||
case R_AARCH64_TLS_DTPREL64:
|
||||
if (std::optional<u64> val = get_tombstone(sym, frag))
|
||||
*(U64<E> *)loc = *val;
|
||||
else
|
||||
*(U64<E> *)loc = S + A - ctx.dtp_addr;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
|
||||
<< rel;
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
assert(shdr().sh_flags & SHF_ALLOC);
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
// Scan relocations
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file->symbols[rel.r_sym];
|
||||
u8 *loc = contents + rel.r_offset;
|
||||
|
||||
if (sym.is_ifunc())
|
||||
sym.flags |= NEEDS_GOT | NEEDS_PLT;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_AARCH64_MOVW_UABS_G3:
|
||||
scan_absrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_AARCH64_ADR_GOT_PAGE:
|
||||
// An ADR_GOT_PAGE and GOT_LO12_NC relocation pair is used to load a
|
||||
// symbol's address from GOT. If the GOT value is a link-time
|
||||
// constant, we may be able to rewrite the ADRP+LDR instruction pair
|
||||
// with an ADRP+ADD, eliminating a GOT memory load.
|
||||
if (ctx.arg.relax && sym.is_pcrel_linktime_const(ctx) &&
|
||||
i + 1 < rels.size()) {
|
||||
// ADRP+LDR must be consecutive and use the same register to relax.
|
||||
const ElfRel<E> &rel2 = rels[i + 1];
|
||||
if (rel2.r_type == R_AARCH64_LD64_GOT_LO12_NC &&
|
||||
rel2.r_offset == rel.r_offset + 4 &&
|
||||
rel2.r_sym == rel.r_sym &&
|
||||
rel.r_addend == 0 &&
|
||||
rel2.r_addend == 0 &&
|
||||
is_adrp(loc) &&
|
||||
is_ldr(loc + 4)) {
|
||||
u32 rd = bits(*(ul32 *)loc, 4, 0);
|
||||
u32 rn = bits(*(ul32 *)(loc + 4), 9, 5);
|
||||
u32 rt = bits(*(ul32 *)(loc + 4), 4, 0);
|
||||
if (rd == rn && rn == rt) {
|
||||
i++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
sym.flags |= NEEDS_GOT;
|
||||
break;
|
||||
case R_AARCH64_LD64_GOT_LO12_NC:
|
||||
case R_AARCH64_LD64_GOTPAGE_LO15:
|
||||
case R_AARCH64_GOTPCREL32:
|
||||
sym.flags |= NEEDS_GOT;
|
||||
break;
|
||||
case R_AARCH64_CALL26:
|
||||
case R_AARCH64_JUMP26:
|
||||
case R_AARCH64_PLT32:
|
||||
if (sym.is_imported)
|
||||
sym.flags |= NEEDS_PLT;
|
||||
break;
|
||||
case R_AARCH64_TLSIE_ADR_GOTTPREL_PAGE21:
|
||||
case R_AARCH64_TLSIE_LD64_GOTTPREL_LO12_NC:
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
break;
|
||||
case R_AARCH64_ADR_PREL_PG_HI21:
|
||||
case R_AARCH64_ADR_PREL_PG_HI21_NC:
|
||||
scan_pcrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_AARCH64_TLSGD_ADR_PAGE21:
|
||||
sym.flags |= NEEDS_TLSGD;
|
||||
break;
|
||||
case R_AARCH64_TLSDESC_CALL:
|
||||
scan_tlsdesc(ctx, sym);
|
||||
break;
|
||||
case R_AARCH64_TLSLE_MOVW_TPREL_G2:
|
||||
case R_AARCH64_TLSLE_ADD_TPREL_LO12:
|
||||
case R_AARCH64_TLSLE_ADD_TPREL_LO12_NC:
|
||||
check_tlsle(ctx, sym, rel);
|
||||
break;
|
||||
case R_AARCH64_ABS64:
|
||||
case R_AARCH64_ADD_ABS_LO12_NC:
|
||||
case R_AARCH64_ADR_PREL_LO21:
|
||||
case R_AARCH64_CONDBR19:
|
||||
case R_AARCH64_LD_PREL_LO19:
|
||||
case R_AARCH64_LDST16_ABS_LO12_NC:
|
||||
case R_AARCH64_LDST32_ABS_LO12_NC:
|
||||
case R_AARCH64_LDST64_ABS_LO12_NC:
|
||||
case R_AARCH64_LDST128_ABS_LO12_NC:
|
||||
case R_AARCH64_LDST8_ABS_LO12_NC:
|
||||
case R_AARCH64_MOVW_UABS_G0:
|
||||
case R_AARCH64_MOVW_UABS_G0_NC:
|
||||
case R_AARCH64_MOVW_UABS_G1:
|
||||
case R_AARCH64_MOVW_UABS_G1_NC:
|
||||
case R_AARCH64_MOVW_UABS_G2:
|
||||
case R_AARCH64_MOVW_UABS_G2_NC:
|
||||
case R_AARCH64_MOVW_PREL_G0:
|
||||
case R_AARCH64_MOVW_PREL_G0_NC:
|
||||
case R_AARCH64_MOVW_PREL_G1:
|
||||
case R_AARCH64_MOVW_PREL_G1_NC:
|
||||
case R_AARCH64_MOVW_PREL_G2:
|
||||
case R_AARCH64_MOVW_PREL_G2_NC:
|
||||
case R_AARCH64_MOVW_PREL_G3:
|
||||
case R_AARCH64_PREL16:
|
||||
case R_AARCH64_PREL32:
|
||||
case R_AARCH64_PREL64:
|
||||
case R_AARCH64_TLSGD_ADD_LO12_NC:
|
||||
case R_AARCH64_TLSLE_MOVW_TPREL_G0:
|
||||
case R_AARCH64_TLSLE_MOVW_TPREL_G0_NC:
|
||||
case R_AARCH64_TLSLE_MOVW_TPREL_G1:
|
||||
case R_AARCH64_TLSLE_MOVW_TPREL_G1_NC:
|
||||
case R_AARCH64_TLSLE_ADD_TPREL_HI12:
|
||||
case R_AARCH64_TLSDESC_ADR_PAGE21:
|
||||
case R_AARCH64_TLSDESC_LD64_LO12:
|
||||
case R_AARCH64_TLSDESC_ADD_LO12:
|
||||
break;
|
||||
default:
|
||||
Error(ctx) << *this << ": unknown relocation: " << rel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The size of a thunk entry varies on ARM64 depending on the distance to
|
||||
// the branch target. This function computes the size of each thunk entry.
|
||||
template <>
|
||||
void Thunk<E>::shrink_size(Context<E> &ctx) {
|
||||
offsets.clear();
|
||||
offsets.push_back(0);
|
||||
i64 off = 0;
|
||||
|
||||
// The distance between S and P is only reduced by shrink_size(), but
|
||||
// page(S) – page(P) may still increase by one page due to address
|
||||
// changes, so we add a safety margin.
|
||||
//
|
||||
// For example, page(0x1200) – page(0x1000) is 0, whereas
|
||||
// page(0x1100) – page(0xfff) is 0x1000, even though the latter
|
||||
// distance is shorter than the former.
|
||||
auto is_small = [](i64 prel) {
|
||||
return is_int(prel + 0x1000, 33) && is_int(prel - 0x1000, 33);
|
||||
};
|
||||
|
||||
for (Symbol<E> *sym : symbols) {
|
||||
u64 S = sym->get_addr(ctx);
|
||||
u64 P = get_addr() + off;
|
||||
i64 prel = page(S) - page(P);
|
||||
off += is_small(prel) ? 12 : 24;
|
||||
offsets.push_back(off);
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void Thunk<E>::copy_buf(Context<E> &ctx) {
|
||||
// Short thunk with a 33 bit displacement
|
||||
constexpr ul32 insn1[] = {
|
||||
0x9000'0010, // adrp x16, 0
|
||||
0x9100'0210, // add x16, x16
|
||||
0xd61f'0200, // br x16
|
||||
};
|
||||
|
||||
// Long thunk with a 64 bit displacement
|
||||
constexpr ul32 insn2[] = {
|
||||
0x1000'0010, // adr x16, 0
|
||||
0xd2a0'0011, // movz x17, 0, lsl #16
|
||||
0xf2c0'0011, // movk x17, 0, lsl #32
|
||||
0xf2e0'0011, // movk x17, 0, lsl #48
|
||||
0x8b11'0210, // add x16, x16, x17
|
||||
0xd61f'0200, // br x16
|
||||
};
|
||||
|
||||
u8 *base = ctx.buf + output_section.shdr.sh_offset + offset;
|
||||
|
||||
for (i64 i = 0; i < symbols.size(); i++) {
|
||||
u64 S = symbols[i]->get_addr(ctx);
|
||||
u64 P = get_addr() + offsets[i];
|
||||
u8 *buf = base + offsets[i];
|
||||
|
||||
if (offsets[i + 1] - offsets[i] == sizeof(insn1)) {
|
||||
i64 prel = page(S) - page(P);
|
||||
assert(is_int(prel, 33));
|
||||
memcpy(buf, insn1, sizeof(insn1));
|
||||
write_adrp(buf, prel);
|
||||
*(ul32 *)(buf + 4) |= bits(S, 11, 0) << 10;
|
||||
} else {
|
||||
memcpy(buf, insn2, sizeof(insn2));
|
||||
write_adr(buf, bits(S - P, 15, 0));
|
||||
*(ul32 *)(buf + 4) |= bits(S - P, 31, 16) << 5;
|
||||
*(ul32 *)(buf + 8) |= bits(S - P, 47, 32) << 5;
|
||||
*(ul32 *)(buf + 12) |= bits(S - P, 63, 48) << 5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template class Thunk<E>;
|
||||
|
||||
} // namespace mold
|
||||
|
||||
#endif
|
||||
@@ -1,604 +0,0 @@
|
||||
// i386 is similar to x86-64 but lacks PC-relative memory access
|
||||
// instructions. So it's not straightforward to support position-
|
||||
// independent code (PIC) on that target.
|
||||
//
|
||||
// If an object file is compiled with -fPIC, a function that needs to load
|
||||
// a value from memory first obtains its own address with the following
|
||||
// code
|
||||
//
|
||||
// call __x86.get_pc_thunk.bx
|
||||
//
|
||||
// where __x86.get_pc_thunk.bx is defined as
|
||||
//
|
||||
// __x86.get_pc_thunk.bx:
|
||||
// mov (%esp), %ebx # move the return address to %ebx
|
||||
// ret
|
||||
//
|
||||
// . With the function's own address (or, more precisely, the address
|
||||
// immediately after the call instruction), the function can compute an
|
||||
// absolute address of a variable with its address + link-time constant.
|
||||
//
|
||||
// Executing call-mov-ret isn't very cheap, and allocating one register to
|
||||
// store PC isn't cheap too, especially given that i386 has only 8
|
||||
// general-purpose registers. But that's the cost of PIC on i386. You need
|
||||
// to pay it when creating a .so and a position-independent executable.
|
||||
//
|
||||
// When a position-independent function calls another function, it sets
|
||||
// %ebx to the address of .got. Position-independent PLT entries use that
|
||||
// register to load values from .got.plt/.got.
|
||||
//
|
||||
// If we are creating a position-dependent executable (PDE), we can't
|
||||
// assume that %ebx is set to .got. For PDE, we need to create position-
|
||||
// dependent PLT entries which don't use %ebx.
|
||||
//
|
||||
// https://github.com/rui314/psabi/blob/main/i386.pdf
|
||||
|
||||
#if MOLD_I386
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
namespace mold {
|
||||
|
||||
using E = I386;
|
||||
|
||||
template <>
|
||||
i64 get_addend(u8 *loc, const ElfRel<E> &rel) {
|
||||
switch (rel.r_type) {
|
||||
case R_386_8:
|
||||
case R_386_PC8:
|
||||
return *(i8 *)loc;
|
||||
case R_386_16:
|
||||
case R_386_PC16:
|
||||
return *(il16 *)loc;
|
||||
case R_386_32:
|
||||
case R_386_PC32:
|
||||
case R_386_GOT32:
|
||||
case R_386_GOT32X:
|
||||
case R_386_PLT32:
|
||||
case R_386_GOTOFF:
|
||||
case R_386_GOTPC:
|
||||
case R_386_TLS_LDM:
|
||||
case R_386_TLS_GOTIE:
|
||||
case R_386_TLS_LE:
|
||||
case R_386_TLS_IE:
|
||||
case R_386_TLS_GD:
|
||||
case R_386_TLS_LDO_32:
|
||||
case R_386_SIZE32:
|
||||
case R_386_TLS_GOTDESC:
|
||||
return *(il32 *)loc;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_addend(u8 *loc, i64 val, const ElfRel<E> &rel) {
|
||||
switch (rel.r_type) {
|
||||
case R_386_NONE:
|
||||
break;
|
||||
case R_386_8:
|
||||
case R_386_PC8:
|
||||
*loc = val;
|
||||
break;
|
||||
case R_386_16:
|
||||
case R_386_PC16:
|
||||
*(ul16 *)loc = val;
|
||||
break;
|
||||
case R_386_32:
|
||||
case R_386_PC32:
|
||||
case R_386_GOT32:
|
||||
case R_386_GOT32X:
|
||||
case R_386_PLT32:
|
||||
case R_386_GOTOFF:
|
||||
case R_386_GOTPC:
|
||||
case R_386_TLS_LDM:
|
||||
case R_386_TLS_GOTIE:
|
||||
case R_386_TLS_LE:
|
||||
case R_386_TLS_IE:
|
||||
case R_386_TLS_GD:
|
||||
case R_386_TLS_LDO_32:
|
||||
case R_386_SIZE32:
|
||||
case R_386_TLS_GOTDESC:
|
||||
*(ul32 *)loc = val;
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
if (ctx.arg.pic) {
|
||||
static const u8 insn[] = {
|
||||
0x51, // push %ecx
|
||||
0x8d, 0x8b, 0, 0, 0, 0, // lea GOTPLT+4(%ebx), %ecx
|
||||
0xff, 0x31, // push (%ecx)
|
||||
0xff, 0x61, 0x04, // jmp *0x4(%ecx)
|
||||
0xcc, 0xcc, 0xcc, 0xcc, // (padding)
|
||||
};
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 3) = ctx.gotplt->shdr.sh_addr - ctx.got->shdr.sh_addr + 4;
|
||||
} else {
|
||||
static const u8 insn[] = {
|
||||
0x51, // push %ecx
|
||||
0xb9, 0, 0, 0, 0, // mov GOTPLT+4, %ecx
|
||||
0xff, 0x31, // push (%ecx)
|
||||
0xff, 0x61, 0x04, // jmp *0x4(%ecx)
|
||||
0xcc, 0xcc, 0xcc, 0xcc, 0xcc, // (padding)
|
||||
};
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 2) = ctx.gotplt->shdr.sh_addr + 4;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
if (ctx.arg.pic) {
|
||||
static const u8 insn[] = {
|
||||
0xb9, 0, 0, 0, 0, // mov $reloc_offset, %ecx
|
||||
0xff, 0xa3, 0, 0, 0, 0, // jmp *foo@GOT(%ebx)
|
||||
0xcc, 0xcc, 0xcc, 0xcc, 0xcc, // (padding)
|
||||
};
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 1) = sym.get_plt_idx(ctx) * sizeof(ElfRel<E>);
|
||||
*(ul32 *)(buf + 7) = sym.get_gotplt_addr(ctx) - ctx.got->shdr.sh_addr;
|
||||
} else {
|
||||
static const u8 insn[] = {
|
||||
0xb9, 0, 0, 0, 0, // mov $reloc_offset, %ecx
|
||||
0xff, 0x25, 0, 0, 0, 0, // jmp *foo@GOT
|
||||
0xcc, 0xcc, 0xcc, 0xcc, 0xcc, // (padding)
|
||||
};
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 1) = sym.get_plt_idx(ctx) * sizeof(ElfRel<E>);
|
||||
*(ul32 *)(buf + 7) = sym.get_gotplt_addr(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
if (ctx.arg.pic) {
|
||||
static const u8 insn[] = {
|
||||
0xff, 0xa3, 0, 0, 0, 0, // jmp *foo@GOT(%ebx)
|
||||
0xcc, 0xcc, // (padding)
|
||||
};
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 2) = sym.get_got_pltgot_addr(ctx) - ctx.got->shdr.sh_addr;
|
||||
} else {
|
||||
static const u8 insn[] = {
|
||||
0xff, 0x25, 0, 0, 0, 0, // jmp *foo@GOT
|
||||
0xcc, 0xcc, // (padding)
|
||||
};
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 2) = sym.get_got_pltgot_addr(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, InputSection<E> &isec,
|
||||
const ElfRel<E> &rel, u64 offset, u64 val) {
|
||||
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_NONE:
|
||||
break;
|
||||
case R_386_32:
|
||||
*(ul32 *)loc = val;
|
||||
break;
|
||||
case R_386_PC32:
|
||||
*(ul32 *)loc = val - this->shdr.sh_addr - offset;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << "unsupported relocation in .eh_frame: " << rel;
|
||||
}
|
||||
}
|
||||
|
||||
static u32 relax_got32x(u8 *loc) {
|
||||
// mov imm(%reg1), %reg2 -> lea imm(%reg1), %reg2
|
||||
if (loc[0] == 0x8b)
|
||||
return 0x8d00 | loc[1];
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Relax GD to LE
|
||||
static void relax_gd_to_le(u8 *loc, ElfRel<E> rel, u64 val) {
|
||||
static const u8 insn[] = {
|
||||
0x65, 0xa1, 0, 0, 0, 0, // mov %gs:0, %eax
|
||||
0x81, 0xc0, 0, 0, 0, 0, // add $tp_offset, %eax
|
||||
};
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_386_PLT32:
|
||||
case R_386_PC32:
|
||||
memcpy(loc - 3, insn, sizeof(insn));
|
||||
*(ul32 *)(loc + 5) = val;
|
||||
break;
|
||||
case R_386_GOT32:
|
||||
case R_386_GOT32X:
|
||||
memcpy(loc - 2, insn, sizeof(insn));
|
||||
*(ul32 *)(loc + 6) = val;
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
// Relax LD to LE
|
||||
static void relax_ld_to_le(u8 *loc, ElfRel<E> rel, u64 tls_size) {
|
||||
switch (rel.r_type) {
|
||||
case R_386_PLT32:
|
||||
case R_386_PC32: {
|
||||
static const u8 insn[] = {
|
||||
0x65, 0xa1, 0, 0, 0, 0, // mov %gs:0, %eax
|
||||
0x2d, 0, 0, 0, 0, // sub $tls_size, %eax
|
||||
};
|
||||
memcpy(loc - 2, insn, sizeof(insn));
|
||||
*(ul32 *)(loc + 5) = tls_size;
|
||||
break;
|
||||
}
|
||||
case R_386_GOT32:
|
||||
case R_386_GOT32X: {
|
||||
static const u8 insn[] = {
|
||||
0x65, 0xa1, 0, 0, 0, 0, // mov %gs:0, %eax
|
||||
0x81, 0xe8, 0, 0, 0, 0, // sub $tls_size, %eax
|
||||
};
|
||||
memcpy(loc - 2, insn, sizeof(insn));
|
||||
*(ul32 *)(loc + 6) = tls_size;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
static u32 relax_tlsdesc_to_ie(u8 *loc) {
|
||||
switch ((loc[0] << 8) | loc[1]) {
|
||||
case 0x8d83: return 0x8b83; // lea 0(%ebx), %eax -> mov 0(%ebx), %eax
|
||||
case 0x8d9b: return 0x8b9b; // lea 0(%ebx), %ebx -> mov 0(%ebx), %ebx
|
||||
case 0x8d8b: return 0x8b8b; // lea 0(%ebx), %ecx -> mov 0(%ebx), %ecx
|
||||
case 0x8d93: return 0x8b93; // lea 0(%ebx), %edx -> mov 0(%ebx), %edx
|
||||
case 0x8db3: return 0x8bb3; // lea 0(%ebx), %esi -> mov 0(%ebx), %esi
|
||||
case 0x8dbb: return 0x8bbb; // lea 0(%ebx), %edi -> mov 0(%ebx), %edi
|
||||
case 0x8da3: return 0x8ba3; // lea 0(%ebx), %esp -> mov 0(%ebx), %esp
|
||||
case 0x8dab: return 0x8bab; // lea 0(%ebx), %ebp -> mov 0(%ebx), %ebp
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static u32 relax_tlsdesc_to_le(u8 *loc) {
|
||||
switch ((loc[0] << 8) | loc[1]) {
|
||||
case 0x8d83: return 0x90b8; // lea 0(%ebx), %eax -> mov $0, %eax
|
||||
case 0x8d9b: return 0x90bb; // lea 0(%ebx), %ebx -> mov $0, %ebx
|
||||
case 0x8d8b: return 0x90b9; // lea 0(%ebx), %ecx -> mov $0, %ecx
|
||||
case 0x8d93: return 0x90ba; // lea 0(%ebx), %edx -> mov $0, %edx
|
||||
case 0x8db3: return 0x90be; // lea 0(%ebx), %esi -> mov $0, %esi
|
||||
case 0x8dbb: return 0x90bf; // lea 0(%ebx), %edi -> mov $0, %edi
|
||||
case 0x8da3: return 0x90bc; // lea 0(%ebx), %esp -> mov $0, %esp
|
||||
case 0x8dab: return 0x90bd; // lea 0(%ebx), %ebp -> mov $0, %ebp
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file->symbols[rel.r_sym];
|
||||
if (sym.get_type() == STT_TLS && sym.is_remaining_undef_weak())
|
||||
continue;
|
||||
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
u64 S = sym.get_addr(ctx);
|
||||
u64 A = get_addend(*this, rel);
|
||||
u64 P = get_addr() + rel.r_offset;
|
||||
u64 G = sym.get_got_idx(ctx) * sizeof(Word<E>);
|
||||
u64 GOT = ctx.got->shdr.sh_addr;
|
||||
|
||||
auto check = [&](i64 val, i64 lo, i64 hi) {
|
||||
check_range(ctx, i, val, lo, hi);
|
||||
};
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_386_8:
|
||||
check(S + A, 0, 1 << 8);
|
||||
*loc = S + A;
|
||||
break;
|
||||
case R_386_16:
|
||||
check(S + A, 0, 1 << 16);
|
||||
*(ul16 *)loc = S + A;
|
||||
break;
|
||||
case R_386_32:
|
||||
break;
|
||||
case R_386_PC8:
|
||||
check(S + A - P, -(1 << 7), 1 << 7);
|
||||
*loc = S + A - P;
|
||||
break;
|
||||
case R_386_PC16:
|
||||
check(S + A - P, -(1 << 15), 1 << 15);
|
||||
*(ul16 *)loc = S + A - P;
|
||||
break;
|
||||
case R_386_PC32:
|
||||
case R_386_PLT32:
|
||||
*(ul32 *)loc = S + A - P;
|
||||
break;
|
||||
case R_386_GOT32:
|
||||
*(ul32 *)loc = G + A;
|
||||
break;
|
||||
case R_386_GOT32X:
|
||||
if (sym.has_got(ctx)) {
|
||||
*(ul32 *)loc = G + A;
|
||||
} else {
|
||||
u32 insn = relax_got32x(loc - 2);
|
||||
assert(insn);
|
||||
loc[-2] = insn >> 8;
|
||||
loc[-1] = insn;
|
||||
*(ul32 *)loc = S + A - GOT;
|
||||
if (ctx.arg.emit_relocs)
|
||||
rel.r_type = R_386_GOTOFF;
|
||||
}
|
||||
break;
|
||||
case R_386_GOTOFF:
|
||||
*(ul32 *)loc = S + A - GOT;
|
||||
break;
|
||||
case R_386_GOTPC:
|
||||
*(ul32 *)loc = GOT + A - P;
|
||||
break;
|
||||
case R_386_TLS_GOTIE:
|
||||
*(ul32 *)loc = sym.get_gottp_addr(ctx) + A - GOT;
|
||||
break;
|
||||
case R_386_TLS_LE:
|
||||
*(ul32 *)loc = S + A - ctx.tp_addr;
|
||||
break;
|
||||
case R_386_TLS_IE:
|
||||
*(ul32 *)loc = sym.get_gottp_addr(ctx) + A;
|
||||
break;
|
||||
case R_386_TLS_GD:
|
||||
if (sym.has_tlsgd(ctx))
|
||||
*(ul32 *)loc = sym.get_tlsgd_addr(ctx) + A - GOT;
|
||||
else
|
||||
relax_gd_to_le(loc, rels[++i], S - ctx.tp_addr);
|
||||
break;
|
||||
case R_386_TLS_LDM:
|
||||
if (ctx.got->has_tlsld(ctx))
|
||||
*(ul32 *)loc = ctx.got->get_tlsld_addr(ctx) + A - GOT;
|
||||
else
|
||||
relax_ld_to_le(loc, rels[++i], ctx.tp_addr - ctx.tls_begin);
|
||||
break;
|
||||
case R_386_TLS_LDO_32:
|
||||
*(ul32 *)loc = S + A - ctx.dtp_addr;
|
||||
break;
|
||||
case R_386_SIZE32:
|
||||
*(ul32 *)loc = sym.esym().st_size + A;
|
||||
break;
|
||||
case R_386_TLS_GOTDESC:
|
||||
// i386 TLSDESC uses the following code sequence to materialize
|
||||
// a TP-relative address in %eax.
|
||||
//
|
||||
// lea 0(%ebx), %eax
|
||||
// R_386_TLS_GOTDESC foo
|
||||
// call *(%eax)
|
||||
// R_386_TLS_DESC_CALL foo
|
||||
//
|
||||
// We may relax the instructions to the following if its TP-relative
|
||||
// address is known at link-time
|
||||
//
|
||||
// mov $foo@TPOFF, %eax
|
||||
// nop
|
||||
//
|
||||
// or to the following if the TP-relative address is known at
|
||||
// process startup time.
|
||||
//
|
||||
// mov foo@GOTTPOFF(%ebx), %eax
|
||||
// nop
|
||||
//
|
||||
// We allow the following alternative code sequence too because
|
||||
// LLVM emits such code.
|
||||
//
|
||||
// lea 0(%ebx), %reg
|
||||
// R_386_TLS_GOTDESC foo
|
||||
// mov %reg, %eax
|
||||
// call *(%eax)
|
||||
// R_386_TLS_DESC_CALL foo
|
||||
//
|
||||
// Note that the compiler always uses the local-exec TLS model
|
||||
// for -fno-pic, so TLSDESC code is always PIC (i.e. uses %ebx to
|
||||
// store the address of GOT.)
|
||||
if (sym.has_tlsdesc(ctx)) {
|
||||
*(ul32 *)loc = sym.get_tlsdesc_addr(ctx) + A - GOT;
|
||||
} else if (sym.has_gottp(ctx)) {
|
||||
u32 insn = relax_tlsdesc_to_ie(loc - 2);
|
||||
if (!insn)
|
||||
Fatal(ctx) << *this << ": illegal instruction sequence for TLSDESC";
|
||||
loc[-2] = insn >> 8;
|
||||
loc[-1] = insn;
|
||||
*(ul32 *)loc = sym.get_gottp_addr(ctx) + A - GOT;
|
||||
} else {
|
||||
u32 insn = relax_tlsdesc_to_le(loc - 2);
|
||||
if (!insn)
|
||||
Fatal(ctx) << *this << ": illegal instruction sequence for TLSDESC";
|
||||
loc[-2] = insn >> 8;
|
||||
loc[-1] = insn;
|
||||
*(ul32 *)loc = S + A - ctx.tp_addr;
|
||||
}
|
||||
break;
|
||||
case R_386_TLS_DESC_CALL:
|
||||
if (!sym.has_tlsdesc(ctx)) {
|
||||
// call *(%eax) -> nop
|
||||
loc[0] = 0x66;
|
||||
loc[1] = 0x90;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
for_each_reloc(ctx, [&](const ElfRel<E> &rel, i64 i) ALWAYS_INLINE {
|
||||
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
|
||||
return;
|
||||
|
||||
Symbol<E> &sym = *file->symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
SectionFragment<E> *frag;
|
||||
i64 frag_addend;
|
||||
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
|
||||
|
||||
u64 S = frag ? frag->get_addr(ctx) : sym.get_addr(ctx);
|
||||
u64 A = frag ? frag_addend : get_addend(*this, rel);
|
||||
u64 GOT = ctx.got->shdr.sh_addr;
|
||||
|
||||
auto check = [&](i64 val, i64 lo, i64 hi) {
|
||||
check_range(ctx, i, val, lo, hi);
|
||||
};
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_386_8:
|
||||
check(S + A, 0, 1 << 8);
|
||||
*loc = S + A;
|
||||
break;
|
||||
case R_386_16:
|
||||
check(S + A, 0, 1 << 16);
|
||||
*(ul16 *)loc = S + A;
|
||||
break;
|
||||
case R_386_32:
|
||||
if (std::optional<u64> val = get_tombstone(sym, frag))
|
||||
*(ul32 *)loc = *val;
|
||||
else
|
||||
*(ul32 *)loc = S + A;
|
||||
break;
|
||||
case R_386_PC8:
|
||||
check(S + A, -(1 << 7), 1 << 7);
|
||||
*loc = S + A;
|
||||
break;
|
||||
case R_386_PC16:
|
||||
check(S + A, -(1 << 15), 1 << 15);
|
||||
*(ul16 *)loc = S + A;
|
||||
break;
|
||||
case R_386_PC32:
|
||||
*(ul32 *)loc = S + A;
|
||||
break;
|
||||
case R_386_GOTPC:
|
||||
*(ul32 *)loc = GOT + A;
|
||||
break;
|
||||
case R_386_GOTOFF:
|
||||
*(ul32 *)loc = S + A - GOT;
|
||||
break;
|
||||
case R_386_TLS_LDO_32:
|
||||
if (std::optional<u64> val = get_tombstone(sym, frag))
|
||||
*(ul32 *)loc = *val;
|
||||
else
|
||||
*(ul32 *)loc = S + A - ctx.dtp_addr;
|
||||
break;
|
||||
case R_386_SIZE32:
|
||||
*(ul32 *)loc = sym.esym().st_size + A;
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
assert(shdr().sh_flags & SHF_ALLOC);
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
// Scan relocations
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file->symbols[rel.r_sym];
|
||||
u8 *loc = contents + rel.r_offset;
|
||||
|
||||
if (sym.is_ifunc())
|
||||
sym.flags |= NEEDS_GOT | NEEDS_PLT;
|
||||
|
||||
if (rel.r_type == R_386_TLS_GD || rel.r_type == R_386_TLS_LDM) {
|
||||
if (i + 1 == rels.size())
|
||||
Fatal(ctx) << *this << ": " << rel << " must be followed by PLT or GOT32";
|
||||
|
||||
if (u32 ty = rels[i + 1].r_type;
|
||||
ty != R_386_PLT32 && ty != R_386_PC32 &&
|
||||
ty != R_386_GOT32 && ty != R_386_GOT32X)
|
||||
Fatal(ctx) << *this << ": " << rel << " must be followed by PLT or GOT32";
|
||||
}
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_386_8:
|
||||
case R_386_16:
|
||||
scan_absrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_386_PC8:
|
||||
case R_386_PC16:
|
||||
case R_386_PC32:
|
||||
scan_pcrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_386_GOT32:
|
||||
case R_386_GOTPC:
|
||||
sym.flags |= NEEDS_GOT;
|
||||
break;
|
||||
case R_386_GOT32X:
|
||||
// We always want to relax GOT32X even if --no-relax is given
|
||||
// because static PIE doesn't work without it.
|
||||
if (sym.is_pcrel_linktime_const(ctx) && relax_got32x(loc - 2)) {
|
||||
// Do nothing
|
||||
} else {
|
||||
sym.flags |= NEEDS_GOT;
|
||||
}
|
||||
break;
|
||||
case R_386_PLT32:
|
||||
if (sym.is_imported)
|
||||
sym.flags |= NEEDS_PLT;
|
||||
break;
|
||||
case R_386_TLS_GOTIE:
|
||||
case R_386_TLS_IE:
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
break;
|
||||
case R_386_TLS_GD:
|
||||
// We always relax if -static because libc.a doesn't contain
|
||||
// __tls_get_addr().
|
||||
if (ctx.arg.static_ || (ctx.arg.relax && sym.is_tprel_linktime_const(ctx)))
|
||||
i++;
|
||||
else
|
||||
sym.flags |= NEEDS_TLSGD;
|
||||
break;
|
||||
case R_386_TLS_LDM:
|
||||
// We always relax if -static because libc.a doesn't contain
|
||||
// __tls_get_addr().
|
||||
if (ctx.arg.static_ || (ctx.arg.relax && !ctx.arg.shared))
|
||||
i++;
|
||||
else
|
||||
ctx.needs_tlsld = true;
|
||||
break;
|
||||
case R_386_TLS_GOTDESC:
|
||||
scan_tlsdesc(ctx, sym);
|
||||
break;
|
||||
case R_386_TLS_LE:
|
||||
check_tlsle(ctx, sym, rel);
|
||||
break;
|
||||
case R_386_32:
|
||||
case R_386_GOTOFF:
|
||||
case R_386_TLS_LDO_32:
|
||||
case R_386_SIZE32:
|
||||
case R_386_TLS_DESC_CALL:
|
||||
break;
|
||||
default:
|
||||
Error(ctx) << *this << ": unknown relocation: " << rel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,322 +0,0 @@
|
||||
// This file contains code for the Motorola 68000 series microprocessors,
|
||||
// which is often abbreviated as m68k. Running a Unix-like system on a
|
||||
// m68k-based machine today is a retro-computing hobby activity, but the
|
||||
// processor was a popular choice to build Unix computers during '80s.
|
||||
// Early Sun workstations for example used m68k. Macintosh until 1994 were
|
||||
// based on m68k as well until they switched to PowerPC (and then to x86
|
||||
// and to ARM.)
|
||||
//
|
||||
// From the linker's point of view, it is not hard to support m68k. It's
|
||||
// just a 32-bit big-endian CISC ISA. Compared to comtemporary i386,
|
||||
// m68k's psABI is actually simpler because m68k has PC-relative memory
|
||||
// access instructions and therefore can support position-independent
|
||||
// code without too much hassle.
|
||||
//
|
||||
// https://github.com/rui314/psabi/blob/main/m68k.pdf
|
||||
|
||||
#if MOLD_M68K
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
namespace mold {
|
||||
|
||||
using E = M68K;
|
||||
|
||||
template <>
|
||||
void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
static const u8 insn[] = {
|
||||
0x2f, 0x00, // move.l %d0, -(%sp)
|
||||
0x2f, 0x3b, 0x01, 0x70, 0, 0, 0, 0, // move.l (GOTPLT+4, %pc), -(%sp)
|
||||
0x4e, 0xfb, 0x01, 0x71, 0, 0, 0, 0, // jmp ([GOTPLT+8, %pc])
|
||||
};
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ub32 *)(buf + 6) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr;
|
||||
*(ub32 *)(buf + 14) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr - 4;
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
static const u8 insn[] = {
|
||||
0x20, 0x3c, 0, 0, 0, 0, // move.l PLT_OFFSET, %d0
|
||||
0x4e, 0xfb, 0x01, 0x71, 0, 0, 0, 0, // jmp ([GOTPLT_ENTRY, %pc])
|
||||
};
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ub32 *)(buf + 2) = sym.get_plt_idx(ctx) * sizeof(ElfRel<E>);
|
||||
*(ub32 *)(buf + 10) = sym.get_gotplt_addr(ctx) - sym.get_plt_addr(ctx) - 8;
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
static const u8 insn[] = {
|
||||
0x4e, 0xfb, 0x01, 0x71, 0, 0, 0, 0, // jmp ([GOT_ENTRY, %pc])
|
||||
};
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ub32 *)(buf + 4) = sym.get_got_pltgot_addr(ctx) - sym.get_plt_addr(ctx) - 2;
|
||||
}
|
||||
|
||||
template <>
|
||||
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, InputSection<E> &isec,
|
||||
const ElfRel<E> &rel, u64 offset, u64 val) {
|
||||
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_NONE:
|
||||
break;
|
||||
case R_68K_32:
|
||||
*(ub32 *)loc = val;
|
||||
break;
|
||||
case R_68K_PC32:
|
||||
*(ub32 *)loc = val - this->shdr.sh_addr - offset;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << "unsupported relocation in .eh_frame: " << rel;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file->symbols[rel.r_sym];
|
||||
if (sym.get_type() == STT_TLS && sym.is_remaining_undef_weak())
|
||||
continue;
|
||||
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
u64 S = sym.get_addr(ctx);
|
||||
u64 A = rel.r_addend;
|
||||
u64 P = get_addr() + rel.r_offset;
|
||||
u64 G = sym.get_got_idx(ctx) * sizeof(Word<E>);
|
||||
u64 GOT = ctx.got->shdr.sh_addr;
|
||||
|
||||
auto check = [&](i64 val, i64 lo, i64 hi) {
|
||||
check_range(ctx, i, val, lo, hi);
|
||||
};
|
||||
|
||||
auto write16 = [&](u64 val) {
|
||||
check(val, 0, 1 << 16);
|
||||
*(ub16 *)loc = val;
|
||||
};
|
||||
|
||||
auto write16s = [&](u64 val) {
|
||||
check(val, -(1 << 15), 1 << 15);
|
||||
*(ub16 *)loc = val;
|
||||
};
|
||||
|
||||
auto write8 = [&](u64 val) {
|
||||
check(val, 0, 1 << 8);
|
||||
*loc = val;
|
||||
};
|
||||
|
||||
auto write8s = [&](u64 val) {
|
||||
check(val, -(1 << 7), 1 << 7);
|
||||
*loc = val;
|
||||
};
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_68K_32:
|
||||
break;
|
||||
case R_68K_16:
|
||||
write16(S + A);
|
||||
break;
|
||||
case R_68K_8:
|
||||
write8(S + A);
|
||||
break;
|
||||
case R_68K_PC32:
|
||||
case R_68K_PLT32:
|
||||
*(ub32 *)loc = S + A - P;
|
||||
break;
|
||||
case R_68K_PC16:
|
||||
case R_68K_PLT16:
|
||||
write16s(S + A - P);
|
||||
break;
|
||||
case R_68K_PC8:
|
||||
case R_68K_PLT8:
|
||||
write8s(S + A - P);
|
||||
break;
|
||||
case R_68K_GOTPCREL32:
|
||||
*(ub32 *)loc = GOT + A - P;
|
||||
break;
|
||||
case R_68K_GOTPCREL16:
|
||||
write16s(GOT + A - P);
|
||||
break;
|
||||
case R_68K_GOTPCREL8:
|
||||
write8s(GOT + A - P);
|
||||
break;
|
||||
case R_68K_GOTOFF32:
|
||||
*(ub32 *)loc = G + A;
|
||||
break;
|
||||
case R_68K_GOTOFF16:
|
||||
write16(G + A);
|
||||
break;
|
||||
case R_68K_GOTOFF8:
|
||||
write8(G + A);
|
||||
break;
|
||||
case R_68K_TLS_GD32:
|
||||
*(ub32 *)loc = sym.get_tlsgd_addr(ctx) + A - GOT;
|
||||
break;
|
||||
case R_68K_TLS_GD16:
|
||||
write16(sym.get_tlsgd_addr(ctx) + A - GOT);
|
||||
break;
|
||||
case R_68K_TLS_GD8:
|
||||
write8(sym.get_tlsgd_addr(ctx) + A - GOT);
|
||||
break;
|
||||
case R_68K_TLS_LDM32:
|
||||
*(ub32 *)loc = ctx.got->get_tlsld_addr(ctx) + A - GOT;
|
||||
break;
|
||||
case R_68K_TLS_LDM16:
|
||||
write16(ctx.got->get_tlsld_addr(ctx) + A - GOT);
|
||||
break;
|
||||
case R_68K_TLS_LDM8:
|
||||
write8(ctx.got->get_tlsld_addr(ctx) + A - GOT);
|
||||
break;
|
||||
case R_68K_TLS_LDO32:
|
||||
*(ub32 *)loc = S + A - ctx.dtp_addr;
|
||||
break;
|
||||
case R_68K_TLS_LDO16:
|
||||
write16s(S + A - ctx.dtp_addr);
|
||||
break;
|
||||
case R_68K_TLS_LDO8:
|
||||
write8s(S + A - ctx.dtp_addr);
|
||||
break;
|
||||
case R_68K_TLS_IE32:
|
||||
*(ub32 *)loc = sym.get_gottp_addr(ctx) + A - GOT;
|
||||
break;
|
||||
case R_68K_TLS_IE16:
|
||||
write16(sym.get_gottp_addr(ctx) + A - GOT);
|
||||
break;
|
||||
case R_68K_TLS_IE8:
|
||||
write8(sym.get_gottp_addr(ctx) + A - GOT);
|
||||
break;
|
||||
case R_68K_TLS_LE32:
|
||||
*(ub32 *)loc = S + A - ctx.tp_addr;
|
||||
break;
|
||||
case R_68K_TLS_LE16:
|
||||
write16(S + A - ctx.tp_addr);
|
||||
break;
|
||||
case R_68K_TLS_LE8:
|
||||
write8(S + A - ctx.tp_addr);
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
for_each_reloc(ctx, [&](const ElfRel<E> &rel, i64) ALWAYS_INLINE {
|
||||
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
|
||||
return;
|
||||
|
||||
Symbol<E> &sym = *file->symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
SectionFragment<E> *frag;
|
||||
i64 frag_addend;
|
||||
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
|
||||
|
||||
u64 S = frag ? frag->get_addr(ctx) : sym.get_addr(ctx);
|
||||
u64 A = frag ? frag_addend : (i64)rel.r_addend;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_68K_32:
|
||||
if (std::optional<u64> val = get_tombstone(sym, frag))
|
||||
*(ub32 *)loc = *val;
|
||||
else
|
||||
*(ub32 *)loc = S + A;
|
||||
break;
|
||||
case R_68K_TLS_LDO32:
|
||||
if (std::optional<u64> val = get_tombstone(sym, frag))
|
||||
*(ub32 *)loc = *val;
|
||||
else
|
||||
*(ub32 *)loc = S + A - ctx.dtp_addr;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
|
||||
<< rel;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
assert(shdr().sh_flags & SHF_ALLOC);
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file->symbols[rel.r_sym];
|
||||
|
||||
if (sym.is_ifunc())
|
||||
Error(ctx) << sym << ": GNU ifunc symbol is not supported on m68k";
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_68K_16:
|
||||
case R_68K_8:
|
||||
scan_absrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_68K_PC32:
|
||||
case R_68K_PC16:
|
||||
case R_68K_PC8:
|
||||
scan_pcrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_68K_GOTPCREL32:
|
||||
case R_68K_GOTPCREL16:
|
||||
case R_68K_GOTPCREL8:
|
||||
case R_68K_GOTOFF32:
|
||||
case R_68K_GOTOFF16:
|
||||
case R_68K_GOTOFF8:
|
||||
sym.flags |= NEEDS_GOT;
|
||||
break;
|
||||
case R_68K_PLT32:
|
||||
case R_68K_PLT16:
|
||||
case R_68K_PLT8:
|
||||
if (sym.is_imported)
|
||||
sym.flags |= NEEDS_PLT;
|
||||
break;
|
||||
case R_68K_TLS_GD32:
|
||||
case R_68K_TLS_GD16:
|
||||
case R_68K_TLS_GD8:
|
||||
sym.flags |= NEEDS_TLSGD;
|
||||
break;
|
||||
case R_68K_TLS_LDM32:
|
||||
case R_68K_TLS_LDM16:
|
||||
case R_68K_TLS_LDM8:
|
||||
ctx.needs_tlsld = true;
|
||||
break;
|
||||
case R_68K_TLS_IE32:
|
||||
case R_68K_TLS_IE16:
|
||||
case R_68K_TLS_IE8:
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
break;
|
||||
case R_68K_TLS_LE32:
|
||||
case R_68K_TLS_LE16:
|
||||
case R_68K_TLS_LE8:
|
||||
check_tlsle(ctx, sym, rel);
|
||||
break;
|
||||
case R_68K_32:
|
||||
case R_68K_TLS_LDO32:
|
||||
case R_68K_TLS_LDO16:
|
||||
case R_68K_TLS_LDO8:
|
||||
break;
|
||||
default:
|
||||
Error(ctx) << *this << ": unknown relocation: " << rel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
|
||||
#endif
|
||||
Loaded 100 of 3641 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user