335 changes: 335 additions & 0 deletions .ci/metrics/requirements.lock.txt

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions .ci/metrics/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pygithub==2.5.0
7 changes: 3 additions & 4 deletions .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,9 @@
# to receive an approval from a "code owner" in particular -- any LLVM project
# member can approve pull requests.
#
# Note that GitHub's concept of "code owner" is independent from LLVM's own
# "code owner" concept, they merely happen to share terminology. See
# https://llvm.org/docs/DeveloperPolicy.html#code-owners, as well as the
# CODE_OWNERS.txt files in the respective subproject directories.
# This is independent of LLVM's own "maintainer" concept.
# See https://llvm.org/docs/DeveloperPolicy.html#maintainers as well as the
# Maintainers.* files in the the respective subproject directories.

/libcxx/ @llvm/reviewers-libcxx
/libcxxabi/ @llvm/reviewers-libcxxabi
Expand Down
9 changes: 9 additions & 0 deletions .github/new-issues-labeler.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,12 @@

'bolt':
- '/\bbolt(?!\-)\b/i'

'infra:commit-access-request':
- '/Request Commit Access/'

'false-positive':
- '\bfalse[- ]positive\b'

'false-negative':
- '\bfalse[- ]negative\b'
78 changes: 78 additions & 0 deletions .github/workflows/build-metrics-container.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
name: Build Metrics Container

permissions:
contents: read

on:
push:
branches:
- main
paths:
- .github/workflows/build-metrics-container.yml
- '.ci/metrics/**'
pull_request:
branches:
- main
paths:
- .github/workflows/build-metrics-container.yml
- '.ci/metrics/**'

jobs:
build-metrics-container:
if: github.repository_owner == 'llvm'
runs-on: ubuntu-latest
outputs:
container-name: ${{ steps.vars.outputs.container-name }}
container-name-tag: ${{ steps.vars.outputs.container-name-tag }}
container-filename: ${{ steps.vars.outputs.container-filename }}
steps:
- name: Checkout LLVM
uses: actions/checkout@v4
with:
sparse-checkout: .ci/metrics/
- name: Write Variables
id: vars
run: |
tag=`date +%s`
container_name="ghcr.io/$GITHUB_REPOSITORY_OWNER/metrics"
echo "container-name=$container_name" >> $GITHUB_OUTPUT
echo "container-name-tag=$container_name:$tag" >> $GITHUB_OUTPUT
echo "container-filename=$(echo $container_name:$tag | sed -e 's/\//-/g' -e 's/:/-/g').tar" >> $GITHUB_OUTPUT
- name: Build Container
working-directory: ./.ci/metrics
run: |
podman build -t ${{ steps.vars.outputs.container-name-tag }} -f Dockerfile .
# Save the container so we have it in case the push fails. This also
# allows us to separate the push step into a different job so we can
# maintain minimal permissions while building the container.
- name: Save Container Image
run: |
podman save ${{ steps.vars.outputs.container-name-tag }} > ${{ steps.vars.outputs.container-filename }}
- name: Upload Container Image
uses: actions/upload-artifact@v4
with:
name: container
path: ${{ steps.vars.outputs.container-filename }}
retention-days: 14

push-metrics-container:
if: github.event_name == 'push'
needs:
- build-metrics-container
permissions:
packages: write
runs-on: ubuntu-24.04
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- name: Download Container
uses: actions/download-artifact@v4
with:
name: container
- name: Push Container
run: |
podman load -i ${{ needs.build-metrics-container.outputs.container-filename }}
podman tag ${{ needs.build-metrics-container.outputs.container-name-tag }} ${{ needs.build-metrics-container.outputs.container-name }}:latest
podman login -u ${{ github.actor }} -p $GITHUB_TOKEN ghcr.io
podman push ${{ needs.build-metrics-container.outputs.container-name-tag }}
podman push ${{ needs.build-metrics-container.outputs.container-name }}:latest
29 changes: 29 additions & 0 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -112,58 +112,87 @@ jobs:
sudo apt-get update
# swig and graphviz are lldb specific dependencies
sudo apt-get install -y cmake ninja-build swig graphviz
- name: Setup output folder
run: mkdir built-docs
- name: Build LLVM docs
if: steps.docs-changed-subprojects.outputs.llvm_any_changed == 'true'
run: |
cmake -B llvm-build -GNinja -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_SPHINX=ON ./llvm
TZ=UTC ninja -C llvm-build docs-llvm-html docs-llvm-man
mkdir built-docs/llvm
cp -r llvm-build/docs/* built-docs/llvm/
- name: Build Clang docs
if: steps.docs-changed-subprojects.outputs.clang_any_changed == 'true'
run: |
cmake -B clang-build -GNinja -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_PROJECTS="clang" -DLLVM_ENABLE_SPHINX=ON ./llvm
TZ=UTC ninja -C clang-build docs-clang-html docs-clang-man
mkdir built-docs/clang
cp -r clang-build/docs/* built-docs/clang/
- name: Build clang-tools-extra docs
if: steps.docs-changed-subprojects.outputs.clang-tools-extra_any_changed == 'true'
run: |
cmake -B clang-tools-extra-build -GNinja -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_PROJECTS="clang;clang-tools-extra" -DLLVM_ENABLE_SPHINX=ON ./llvm
TZ=UTC ninja -C clang-tools-extra-build docs-clang-tools-html docs-clang-tools-man
mkdir built-docs/clang-tools-extra
cp -r clang-tools-extra-build/docs/* built-docs/clang-tools-extra/
- name: Build LLDB docs
if: steps.docs-changed-subprojects.outputs.lldb_any_changed == 'true'
run: |
cmake -B lldb-build -GNinja -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_PROJECTS="clang;lldb" -DLLVM_ENABLE_SPHINX=ON ./llvm
TZ=UTC ninja -C lldb-build docs-lldb-html docs-lldb-man
mkdir built-docs/lldb
cp -r lldb-build/docs/* built-docs/lldb/
- name: Build libunwind docs
if: steps.docs-changed-subprojects.outputs.libunwind_any_changed == 'true'
run: |
cmake -B libunwind-build -GNinja -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_RUNTIMES="libunwind" -DLLVM_ENABLE_SPHINX=ON ./runtimes
TZ=UTC ninja -C libunwind-build docs-libunwind-html
mkdir built-docs/libunwind
cp -r libunwind-build/libunwind/docs/* built-docs/libunwind
- name: Build libcxx docs
if: steps.docs-changed-subprojects.outputs.libcxx_any_changed == 'true'
run: |
cmake -B libcxx-build -GNinja -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_RUNTIMES="libcxxabi;libcxx;libunwind" -DLLVM_ENABLE_SPHINX=ON ./runtimes
TZ=UTC ninja -C libcxx-build docs-libcxx-html
mkdir built-docs/libcxx
cp -r libcxx-build/libcxx/docs/* built-docs/libcxx/
- name: Build libc docs
if: steps.docs-changed-subprojects.outputs.libc_any_changed == 'true'
run: |
cmake -B libc-build -GNinja -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_RUNTIMES="libc" -DLLVM_ENABLE_SPHINX=ON ./runtimes
TZ=UTC ninja -C libc-build docs-libc-html
mkdir built-docs/libc
cp -r libc-build/libc/docs/* built-docs/libc/
- name: Build LLD docs
if: steps.docs-changed-subprojects.outputs.lld_any_changed == 'true'
run: |
cmake -B lld-build -GNinja -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_PROJECTS="lld" -DLLVM_ENABLE_SPHINX=ON ./llvm
TZ=UTC ninja -C lld-build docs-lld-html
mkdir built-docs/lld
cp -r lld-build/docs/* built-docs/lld/
- name: Build OpenMP docs
if: steps.docs-changed-subprojects.outputs.openmp_any_changed == 'true'
run: |
cmake -B openmp-build -GNinja -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_PROJECTS="clang;openmp" -DLLVM_ENABLE_SPHINX=ON ./llvm
TZ=UTC ninja -C openmp-build docs-openmp-html
mkdir built-docs/openmp
cp -r openmp-build/docs/* built-docs/openmp/
- name: Build Polly docs
if: steps.docs-changed-subprojects.outputs.polly_any_changed == 'true'
run: |
cmake -B polly-build -GNinja -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_PROJECTS="polly" -DLLVM_ENABLE_SPHINX=ON ./llvm
TZ=UTC ninja -C polly-build docs-polly-html docs-polly-man
mkdir built-docs/polly
cp -r polly-build/docs/* built-docs/polly/
- name: Build Flang docs
if: steps.docs-changed-subprojects.outputs.flang_any_changed == 'true'
run: |
cmake -B flang-build -GNinja -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_PROJECTS="clang;mlir;flang" -DLLVM_ENABLE_SPHINX=ON ./llvm
TZ=UTC ninja -C flang-build docs-flang-html
mkdir built-docs/flang
cp -r flang-build/docs/* built-docs/flang/
- name: Upload docs
uses: actions/upload-artifact@v4
with:
name: docs-output
path: built-docs/
35 changes: 11 additions & 24 deletions .github/workflows/libcxx-build-and-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,24 +33,11 @@ concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
cancel-in-progress: true


env:
# LLVM POST-BRANCH bump version
# LLVM POST-BRANCH add compiler test for ToT - 1, e.g. "Clang 17"
# LLVM RELEASE bump remove compiler ToT - 3, e.g. "Clang 15"
LLVM_HEAD_VERSION: "19" # Used compiler, update POST-BRANCH.
LLVM_PREVIOUS_VERSION: "18"
LLVM_OLDEST_VERSION: "17"
GCC_STABLE_VERSION: "13"
LLVM_SYMBOLIZER_PATH: "/usr/bin/llvm-symbolizer-19"
CLANG_CRASH_DIAGNOSTICS_DIR: "crash_diagnostics"


jobs:
stage1:
if: github.repository_owner == 'llvm'
runs-on: libcxx-runners-set
container: ghcr.io/libcxx/actions-builder:testing-2024-09-21
runs-on: libcxx-self-hosted-linux
container: ghcr.io/llvm/libcxx-linux-builder:0fd6f684b9c84c32d6cbfd9742402e788b2879f1
continue-on-error: false
strategy:
fail-fast: false
Expand Down Expand Up @@ -86,8 +73,8 @@ jobs:
**/crash_diagnostics/*
stage2:
if: github.repository_owner == 'llvm'
runs-on: libcxx-runners-set
container: ghcr.io/libcxx/actions-builder:testing-2024-09-21
runs-on: libcxx-self-hosted-linux
container: ghcr.io/llvm/libcxx-linux-builder:0fd6f684b9c84c32d6cbfd9742402e788b2879f1
needs: [ stage1 ]
continue-on-error: false
strategy:
Expand Down Expand Up @@ -161,21 +148,21 @@ jobs:
'generic-static',
'bootstrapping-build'
]
machine: [ 'libcxx-runners-set' ]
machine: [ 'libcxx-self-hosted-linux' ]
include:
- config: 'generic-cxx26'
machine: libcxx-runners-set
machine: libcxx-self-hosted-linux
- config: 'generic-asan'
machine: libcxx-runners-set
machine: libcxx-self-hosted-linux
- config: 'generic-tsan'
machine: libcxx-runners-set
machine: libcxx-self-hosted-linux
- config: 'generic-ubsan'
machine: libcxx-runners-set
machine: libcxx-self-hosted-linux
# Use a larger machine for MSAN to avoid timeout and memory allocation issues.
- config: 'generic-msan'
machine: libcxx-runners-set
machine: libcxx-self-hosted-linux
runs-on: ${{ matrix.machine }}
container: ghcr.io/libcxx/actions-builder:testing-2024-09-21
container: ghcr.io/llvm/libcxx-linux-builder:0fd6f684b9c84c32d6cbfd9742402e788b2879f1
steps:
- uses: actions/checkout@v4
- name: ${{ matrix.config }}
Expand Down
10 changes: 8 additions & 2 deletions .github/workflows/libcxx-build-containers.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,23 @@ jobs:
build-and-push:
runs-on: ubuntu-latest
if: github.repository_owner == 'llvm'
permissions:
packages: write

steps:
- uses: actions/checkout@v4

- name: Build the Linux builder image
working-directory: libcxx/utils/ci
run: docker compose build actions-builder
env:
TAG: ${{ github.sha }}

# - name: Build the Android builder image
# working-directory: libcxx/utils/ci
# run: docker compose build android-buildkite-builder
# env:
# TAG: ${{ github.sha }}

- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
Expand All @@ -54,12 +60,12 @@ jobs:
run: |
docker compose push actions-builder
env:
TAG: libcxx-linux-builder:${{ github.sha }}
TAG: ${{ github.sha }}

# - name: Push the Android builder image
# if: github.event_name == 'push'
# working-directory: libcxx/utils/ci
# run: |
# docker compose push android-buildkite-builder
# env:
# TAG: libcxx-android-builder:${{ github.sha }}
# TAG: ${{ github.sha }}
88 changes: 88 additions & 0 deletions .github/workflows/libcxx-restart-preempted-jobs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -130,3 +130,91 @@ jobs:
run_id: context.payload.workflow_run.id
})
await create_check_run('success', 'Restarted workflow run due to preempted job')
restart-test:
if: github.repository_owner == 'llvm' && (github.event.workflow_run.conclusion == 'failure' || github.event.workflow_run.conclusion == 'cancelled') && github.event.actor.login == 'ldionne' # TESTING ONLY
name: "Restart Job"
permissions:
statuses: read
checks: write
actions: write
runs-on: ubuntu-latest
steps:
- name: "Restart Job"
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea #v7.0.1
with:
script: |
const FAILURE_REGEX = /Process completed with exit code 1./
const PREEMPTION_REGEX = /The runner has received a shutdown signal|The operation was canceled/
function log(msg) {
core.notice(msg)
}
const wf_run = context.payload.workflow_run
log(`Running on "${wf_run.display_title}" by @${wf_run.actor.login} (event: ${wf_run.event})\nWorkflow run URL: ${wf_run.html_url}`)
log('Listing check runs for suite')
const check_suites = await github.rest.checks.listForSuite({
owner: context.repo.owner,
repo: context.repo.repo,
check_suite_id: context.payload.workflow_run.check_suite_id,
per_page: 100 // FIXME: We don't have 100 check runs yet, but we should handle this better.
})
preemptions = [];
legitimate_failures = [];
for (check_run of check_suites.data.check_runs) {
log(`Checking check run: ${check_run.id}`);
if (check_run.status != 'completed') {
log('Check run was not completed. Skipping.');
continue;
}
if (check_run.conclusion != 'failure' && check_run.conclusion != 'cancelled') {
log(`Check run had conclusion: ${check_run.conclusion}. Skipping.`);
continue;
}
annotations = await github.rest.checks.listAnnotations({
owner: context.repo.owner,
repo: context.repo.repo,
check_run_id: check_run.id
})
preemption_annotation = annotations.data.find(function(annotation) {
return annotation.annotation_level == 'failure' &&
annotation.message.match(PREEMPTION_REGEX) != null;
});
if (preemption_annotation != null) {
log(`Found preemption message: ${preemption_annotation.message}`);
preemptions.push(check_run);
break;
}
failure_annotation = annotations.data.find(function(annotation) {
return annotation.annotation_level == 'failure' &&
annotation.message.match(FAILURE_REGEX) != null;
});
if (failure_annotation != null) {
log(`Found legitimate failure annotation: ${failure_annotation.message}`);
legitimate_failures.push(check_run);
break;
}
}
if (preemptions) {
log('Found some preempted jobs');
if (legitimate_failures) {
log('Also found some legitimate failures, so not restarting the workflow.');
} else {
log('Did not find any legitimate failures. Restarting workflow.');
await github.rest.actions.reRunWorkflowFailedJobs({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: context.payload.workflow_run.id
})
}
} else {
log('Did not find any preempted jobs. Not restarting the workflow.');
}
File renamed without changes.
54 changes: 54 additions & 0 deletions bolt/include/bolt/Core/BinaryFunction.h
Original file line number Diff line number Diff line change
Expand Up @@ -527,6 +527,11 @@ class BinaryFunction {
/// fragment of the function.
SmallVector<MCSymbol *, 0> LSDASymbols;

/// Each function fragment may have another fragment containing all landing
/// pads for it. If that's the case, the LP fragment will be stored in the
/// vector below with indexing starting with the main fragment.
SmallVector<std::optional<FragmentNum>, 0> LPFragments;

/// Map to discover which CFIs are attached to a given instruction offset.
/// Maps an instruction offset into a FrameInstructions offset.
/// This is only relevant to the buildCFG phase and is discarded afterwards.
Expand Down Expand Up @@ -1885,6 +1890,42 @@ class BinaryFunction {
return LSDASymbols[F.get()];
}

/// If all landing pads for the function fragment \p F are located in fragment
/// \p LPF, designate \p LPF as a landing-pad fragment for \p F. Passing
/// std::nullopt in LPF, means that landing pads for \p F are located in more
/// than one fragment.
void setLPFragment(const FragmentNum F, std::optional<FragmentNum> LPF) {
if (F.get() >= LPFragments.size())
LPFragments.resize(F.get() + 1);

LPFragments[F.get()] = LPF;
}

/// If function fragment \p F has a designated landing pad fragment, i.e. a
/// fragment that contains all landing pads for throwers in \p F, then return
/// that landing pad fragment number. If \p F does not need landing pads,
/// return \p F. Return nullptr if landing pads for \p F are scattered among
/// several function fragments.
std::optional<FragmentNum> getLPFragment(const FragmentNum F) {
if (!isSplit()) {
assert(F == FragmentNum::main() && "Invalid fragment number");
return FragmentNum::main();
}

if (F.get() >= LPFragments.size())
return std::nullopt;

return LPFragments[F.get()];
}

/// Return a symbol corresponding to a landing pad fragment for fragment \p F.
/// See getLPFragment().
MCSymbol *getLPStartSymbol(const FragmentNum F) {
if (std::optional<FragmentNum> LPFragment = getLPFragment(F))
return getSymbol(*LPFragment);
return nullptr;
}

void setOutputDataAddress(uint64_t Address) { OutputDataOffset = Address; }

uint64_t getOutputDataAddress() const { return OutputDataOffset; }
Expand Down Expand Up @@ -2366,6 +2407,19 @@ inline raw_ostream &operator<<(raw_ostream &OS,
return OS;
}

/// Compare function by index if it is valid, fall back to the original address
/// otherwise.
inline bool compareBinaryFunctionByIndex(const BinaryFunction *A,
const BinaryFunction *B) {
if (A->hasValidIndex() && B->hasValidIndex())
return A->getIndex() < B->getIndex();
if (A->hasValidIndex() && !B->hasValidIndex())
return true;
if (!A->hasValidIndex() && B->hasValidIndex())
return false;
return A->getAddress() < B->getAddress();
}

} // namespace bolt

// GraphTraits specializations for function basic block graphs (CFGs)
Expand Down
9 changes: 9 additions & 0 deletions bolt/include/bolt/Core/BinarySection.h
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ class BinarySection {
// been renamed)
uint64_t OutputAddress{0}; // Section address for the rewritten binary.
uint64_t OutputSize{0}; // Section size in the rewritten binary.
// Can exceed OutputContents with padding.
uint64_t OutputFileOffset{0}; // File offset in the rewritten binary file.
StringRef OutputContents; // Rewritten section contents.
const uint64_t SectionNumber; // Order in which the section was created.
Expand Down Expand Up @@ -474,6 +475,11 @@ class BinarySection {
/// Use name \p SectionName for the section during the emission.
void emitAsData(MCStreamer &Streamer, const Twine &SectionName) const;

/// Write finalized contents of the section. If OutputSize exceeds the size of
/// the OutputContents, append zero padding to the stream and return the
/// number of byte written which should match the OutputSize.
uint64_t write(raw_ostream &OS) const;

using SymbolResolverFuncTy = llvm::function_ref<uint64_t(const MCSymbol *)>;

/// Flush all pending relocations to patch original contents of sections
Expand All @@ -497,6 +503,9 @@ class BinarySection {
IsFinalized = true;
}

/// When writing section contents, add \p PaddingSize zero bytes at the end.
void addPadding(uint64_t PaddingSize) { OutputSize += PaddingSize; }

/// Reorder the contents of this section according to /p Order. If
/// /p Inplace is true, the entire contents of the section is reordered,
/// otherwise the new contents contain only the reordered data.
Expand Down
8 changes: 8 additions & 0 deletions bolt/include/bolt/Profile/DataAggregator.h
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,9 @@ class DataAggregator : public DataReader {
std::string BuildIDBinaryName;

/// Memory map info for a single file as recorded in perf.data
/// When a binary has multiple text segments, the Size is computed as the
/// difference of the last address of these segments from the BaseAddress.
/// The base addresses of all text segments must be the same.
struct MMapInfo {
uint64_t BaseAddress{0}; /// Base address of the mapped binary.
uint64_t MMapAddress{0}; /// Address of the executable segment.
Expand Down Expand Up @@ -493,6 +496,11 @@ class DataAggregator : public DataReader {
/// and return a file name matching a given \p FileBuildID.
std::optional<StringRef> getFileNameForBuildID(StringRef FileBuildID);

/// Get a constant reference to the parsed binary mmap entries.
const std::unordered_map<uint64_t, MMapInfo> &getBinaryMMapInfo() {
return BinaryMMapInfo;
}

friend class YAMLProfileWriter;
};
} // namespace bolt
Expand Down
8 changes: 1 addition & 7 deletions bolt/lib/Core/BinaryContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1607,13 +1607,7 @@ std::vector<BinaryFunction *> BinaryContext::getSortedFunctions() {
SortedFunctions.begin(),
[](BinaryFunction &BF) { return &BF; });

llvm::stable_sort(SortedFunctions,
[](const BinaryFunction *A, const BinaryFunction *B) {
if (A->hasValidIndex() && B->hasValidIndex()) {
return A->getIndex() < B->getIndex();
}
return A->hasValidIndex();
});
llvm::stable_sort(SortedFunctions, compareBinaryFunctionByIndex);
return SortedFunctions;
}

Expand Down
163 changes: 88 additions & 75 deletions bolt/lib/Core/BinaryEmitter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ class BinaryEmitter {

void emitCFIInstruction(const MCCFIInstruction &Inst) const;

/// Emit exception handling ranges for the function.
/// Emit exception handling ranges for the function fragment.
void emitLSDA(BinaryFunction &BF, const FunctionFragment &FF);

/// Emit line number information corresponding to \p NewLoc. \p PrevLoc
Expand Down Expand Up @@ -416,17 +416,6 @@ void BinaryEmitter::emitFunctionBody(BinaryFunction &BF, FunctionFragment &FF,
BF.duplicateConstantIslands();
}

if (!FF.empty() && FF.front()->isLandingPad()) {
assert(!FF.front()->isEntryPoint() &&
"Landing pad cannot be entry point of function");
// If the first block of the fragment is a landing pad, it's offset from the
// start of the area that the corresponding LSDA describes is zero. In this
// case, the call site entries in that LSDA have 0 as offset to the landing
// pad, which the runtime interprets as "no handler". To prevent this,
// insert some padding.
Streamer.emitBytes(BC.MIB->getTrapFillValue());
}

// Track the first emitted instruction with debug info.
bool FirstInstr = true;
for (BinaryBasicBlock *const BB : FF) {
Expand Down Expand Up @@ -906,17 +895,6 @@ void BinaryEmitter::emitLSDA(BinaryFunction &BF, const FunctionFragment &FF) {
if (Sites.empty())
return;

// Calculate callsite table size. Size of each callsite entry is:
//
// sizeof(start) + sizeof(length) + sizeof(LP) + sizeof(uleb128(action))
//
// or
//
// sizeof(dwarf::DW_EH_PE_data4) * 3 + sizeof(uleb128(action))
uint64_t CallSiteTableLength = llvm::size(Sites) * 4 * 3;
for (const auto &FragmentCallSite : Sites)
CallSiteTableLength += getULEB128Size(FragmentCallSite.second.Action);

Streamer.switchSection(BC.MOFI->getLSDASection());

const unsigned TTypeEncoding = BF.getLSDATypeEncoding();
Expand All @@ -937,74 +915,97 @@ void BinaryEmitter::emitLSDA(BinaryFunction &BF, const FunctionFragment &FF) {
// Emit the LSDA header.

// If LPStart is omitted, then the start of the FDE is used as a base for
// landing pad displacements. Then if a cold fragment starts with
// landing pad displacements. Then, if a cold fragment starts with
// a landing pad, this means that the first landing pad offset will be 0.
// As a result, the exception handling runtime will ignore this landing pad
// because zero offset denotes the absence of a landing pad.
// For this reason, when the binary has fixed starting address we emit LPStart
// as 0 and output the absolute value of the landing pad in the table.
// However, C++ runtime will treat 0 as if there is no landing pad, thus we
// cannot emit LP offset as 0.
//
// If the base address can change, we cannot use absolute addresses for
// landing pads (at least not without runtime relocations). Hence, we fall
// back to emitting landing pads relative to the FDE start.
// As we are emitting label differences, we have to guarantee both labels are
// defined in the same section and hence cannot place the landing pad into a
// cold fragment when the corresponding call site is in the hot fragment.
// Because of this issue and the previously described issue of possible
// zero-offset landing pad we have to place landing pads in the same section
// as the corresponding invokes for shared objects.
// As a solution, for fixed-address binaries we set LPStart to 0, and for
// position-independent binaries we offset LP start by one byte.
bool NeedsLPAdjustment = false;
std::function<void(const MCSymbol *)> emitLandingPad;
if (BC.HasFixedLoadAddress) {

// Check if there's a symbol associated with a landing pad fragment.
const MCSymbol *LPStartSymbol = BF.getLPStartSymbol(FF.getFragmentNum());
if (!LPStartSymbol) {
// Since landing pads are not in the same fragment, we fall back to emitting
// absolute addresses for this FDE.
if (opts::Verbosity >= 2) {
BC.outs() << "BOLT-INFO: falling back to generating absolute-address "
<< "exception ranges for " << BF << '\n';
}

assert(BC.HasFixedLoadAddress &&
"Cannot emit absolute-address landing pads for PIE/DSO");

Streamer.emitIntValue(dwarf::DW_EH_PE_udata4, 1); // LPStart format
Streamer.emitIntValue(0, 4); // LPStart
emitLandingPad = [&](const MCSymbol *LPSymbol) {
if (!LPSymbol)
Streamer.emitIntValue(0, 4);
else
if (LPSymbol)
Streamer.emitSymbolValue(LPSymbol, 4);
else
Streamer.emitIntValue(0, 4);
};
} else {
Streamer.emitIntValue(dwarf::DW_EH_PE_omit, 1); // LPStart format
std::optional<FragmentNum> LPFN = BF.getLPFragment(FF.getFragmentNum());
const FunctionFragment &LPFragment = BF.getLayout().getFragment(*LPFN);
NeedsLPAdjustment =
(!LPFragment.empty() && LPFragment.front()->isLandingPad());

// Emit LPStart encoding and optionally LPStart.
if (NeedsLPAdjustment || LPStartSymbol != StartSymbol) {
Streamer.emitIntValue(dwarf::DW_EH_PE_pcrel | dwarf::DW_EH_PE_sdata4, 1);
MCSymbol *DotSymbol = BC.Ctx->createTempSymbol("LPBase");
Streamer.emitLabel(DotSymbol);

const MCExpr *LPStartExpr = MCBinaryExpr::createSub(
MCSymbolRefExpr::create(LPStartSymbol, *BC.Ctx),
MCSymbolRefExpr::create(DotSymbol, *BC.Ctx), *BC.Ctx);
if (NeedsLPAdjustment)
LPStartExpr = MCBinaryExpr::createSub(
LPStartExpr, MCConstantExpr::create(1, *BC.Ctx), *BC.Ctx);
Streamer.emitValue(LPStartExpr, 4);
} else {
// DW_EH_PE_omit means FDE start (StartSymbol) will be used as LPStart.
Streamer.emitIntValue(dwarf::DW_EH_PE_omit, 1);
}
emitLandingPad = [&](const MCSymbol *LPSymbol) {
if (!LPSymbol)
Streamer.emitIntValue(0, 4);
else
Streamer.emitAbsoluteSymbolDiff(LPSymbol, StartSymbol, 4);
if (LPSymbol) {
const MCExpr *LPOffsetExpr = MCBinaryExpr::createSub(
MCSymbolRefExpr::create(LPSymbol, *BC.Ctx),
MCSymbolRefExpr::create(LPStartSymbol, *BC.Ctx), *BC.Ctx);
if (NeedsLPAdjustment)
LPOffsetExpr = MCBinaryExpr::createAdd(
LPOffsetExpr, MCConstantExpr::create(1, *BC.Ctx), *BC.Ctx);
Streamer.emitULEB128Value(LPOffsetExpr);
} else {
Streamer.emitULEB128IntValue(0);
}
};
}

Streamer.emitIntValue(TTypeEncoding, 1); // TType format

// See the comment in EHStreamer::emitExceptionTable() on to use
// uleb128 encoding (which can use variable number of bytes to encode the same
// value) to ensure type info table is properly aligned at 4 bytes without
// iteratively fixing sizes of the tables.
unsigned CallSiteTableLengthSize = getULEB128Size(CallSiteTableLength);
unsigned TTypeBaseOffset =
sizeof(int8_t) + // Call site format
CallSiteTableLengthSize + // Call site table length size
CallSiteTableLength + // Call site table length
BF.getLSDAActionTable().size() + // Actions table size
BF.getLSDATypeTable().size() * TTypeEncodingSize; // Types table size
unsigned TTypeBaseOffsetSize = getULEB128Size(TTypeBaseOffset);
unsigned TotalSize = sizeof(int8_t) + // LPStart format
sizeof(int8_t) + // TType format
TTypeBaseOffsetSize + // TType base offset size
TTypeBaseOffset; // TType base offset
unsigned SizeAlign = (4 - TotalSize) & 3;
MCSymbol *TTBaseLabel = nullptr;
if (TTypeEncoding != dwarf::DW_EH_PE_omit) {
TTBaseLabel = BC.Ctx->createTempSymbol("TTBase");
MCSymbol *TTBaseRefLabel = BC.Ctx->createTempSymbol("TTBaseRef");
Streamer.emitAbsoluteSymbolDiffAsULEB128(TTBaseLabel, TTBaseRefLabel);
Streamer.emitLabel(TTBaseRefLabel);
}

if (TTypeEncoding != dwarf::DW_EH_PE_omit)
// Account for any extra padding that will be added to the call site table
// length.
Streamer.emitULEB128IntValue(TTypeBaseOffset,
/*PadTo=*/TTypeBaseOffsetSize + SizeAlign);
// Emit encoding of entries in the call site table. The format is used for the
// call site start, length, and corresponding landing pad.
if (!LPStartSymbol)
Streamer.emitIntValue(dwarf::DW_EH_PE_sdata4, 1);
else
Streamer.emitIntValue(dwarf::DW_EH_PE_uleb128, 1);

// Emit the landing pad call site table. We use signed data4 since we can emit
// a landing pad in a different part of the split function that could appear
// earlier in the address space than LPStart.
Streamer.emitIntValue(dwarf::DW_EH_PE_sdata4, 1);
Streamer.emitULEB128IntValue(CallSiteTableLength);
MCSymbol *CSTStartLabel = BC.Ctx->createTempSymbol("CSTStart");
MCSymbol *CSTEndLabel = BC.Ctx->createTempSymbol("CSTEnd");
Streamer.emitAbsoluteSymbolDiffAsULEB128(CSTEndLabel, CSTStartLabel);

Streamer.emitLabel(CSTStartLabel);
for (const auto &FragmentCallSite : Sites) {
const BinaryFunction::CallSite &CallSite = FragmentCallSite.second;
const MCSymbol *BeginLabel = CallSite.Start;
Expand All @@ -1015,11 +1016,17 @@ void BinaryEmitter::emitLSDA(BinaryFunction &BF, const FunctionFragment &FF) {

// Start of the range is emitted relative to the start of current
// function split part.
Streamer.emitAbsoluteSymbolDiff(BeginLabel, StartSymbol, 4);
Streamer.emitAbsoluteSymbolDiff(EndLabel, BeginLabel, 4);
if (!LPStartSymbol) {
Streamer.emitAbsoluteSymbolDiff(BeginLabel, StartSymbol, 4);
Streamer.emitAbsoluteSymbolDiff(EndLabel, BeginLabel, 4);
} else {
Streamer.emitAbsoluteSymbolDiffAsULEB128(BeginLabel, StartSymbol);
Streamer.emitAbsoluteSymbolDiffAsULEB128(EndLabel, BeginLabel);
}
emitLandingPad(CallSite.LP);
Streamer.emitULEB128IntValue(CallSite.Action);
}
Streamer.emitLabel(CSTEndLabel);

// Write out action, type, and type index tables at the end.
//
Expand All @@ -1038,6 +1045,8 @@ void BinaryEmitter::emitLSDA(BinaryFunction &BF, const FunctionFragment &FF) {
assert(TypeTable.size() == BF.getLSDATypeTable().size() &&
"indirect type table size mismatch");

Streamer.emitValueToAlignment(Align(TTypeAlignment));

for (int Index = TypeTable.size() - 1; Index >= 0; --Index) {
const uint64_t TypeAddress = TypeTable[Index];
switch (TTypeEncoding & 0x70) {
Expand All @@ -1063,6 +1072,10 @@ void BinaryEmitter::emitLSDA(BinaryFunction &BF, const FunctionFragment &FF) {
}
}
}

if (TTypeEncoding != dwarf::DW_EH_PE_omit)
Streamer.emitLabel(TTBaseLabel);

for (uint8_t const &Byte : BF.getLSDATypeIndexTable())
Streamer.emitIntValue(Byte, 1);
}
Expand Down
11 changes: 1 addition & 10 deletions bolt/lib/Core/BinaryFunction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -385,16 +385,7 @@ bool BinaryFunction::isForwardCall(const MCSymbol *CalleeSymbol) const {
if (CalleeBF) {
if (CalleeBF->isInjected())
return true;

if (hasValidIndex() && CalleeBF->hasValidIndex()) {
return getIndex() < CalleeBF->getIndex();
} else if (hasValidIndex() && !CalleeBF->hasValidIndex()) {
return true;
} else if (!hasValidIndex() && CalleeBF->hasValidIndex()) {
return false;
} else {
return getAddress() < CalleeBF->getAddress();
}
return compareBinaryFunctionByIndex(this, CalleeBF);
} else {
// Absolute symbol.
ErrorOr<uint64_t> CalleeAddressOrError = BC.getSymbolValue(*CalleeSymbol);
Expand Down
9 changes: 9 additions & 0 deletions bolt/lib/Core/BinarySection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,15 @@ void BinarySection::emitAsData(MCStreamer &Streamer,
Streamer.emitLabel(BC.Ctx->getOrCreateSymbol("__hot_data_end"));
}

uint64_t BinarySection::write(raw_ostream &OS) const {
const uint64_t NumValidContentBytes =
std::min<uint64_t>(getOutputContents().size(), getOutputSize());
OS.write(getOutputContents().data(), NumValidContentBytes);
if (getOutputSize() > NumValidContentBytes)
OS.write_zeros(getOutputSize() - NumValidContentBytes);
return getOutputSize();
}

void BinarySection::flushPendingRelocations(raw_pwrite_stream &OS,
SymbolResolverFuncTy Resolver) {
if (PendingRelocations.empty() && Patches.empty())
Expand Down
11 changes: 1 addition & 10 deletions bolt/lib/Passes/ReorderFunctions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -473,16 +473,7 @@ Error ReorderFunctions::runOnFunctions(BinaryContext &BC) {
[](BinaryFunction &BF) { return &BF; });

// Sort functions by index.
llvm::stable_sort(SortedFunctions,
[](const BinaryFunction *A, const BinaryFunction *B) {
if (A->hasValidIndex() && B->hasValidIndex())
return A->getIndex() < B->getIndex();
if (A->hasValidIndex() && !B->hasValidIndex())
return true;
if (!A->hasValidIndex() && B->hasValidIndex())
return false;
return A->getAddress() < B->getAddress();
});
llvm::stable_sort(SortedFunctions, compareBinaryFunctionByIndex);

for (const BinaryFunction *Func : SortedFunctions) {
if (!Func->hasValidIndex())
Expand Down
47 changes: 45 additions & 2 deletions bolt/lib/Passes/SplitFunctions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -901,8 +901,47 @@ void SplitFunctions::splitFunction(BinaryFunction &BF, SplitStrategy &S) {
// have to be placed in the same fragment. When we split them, create
// trampoline landing pads that will redirect the execution to real LPs.
TrampolineSetType Trampolines;
if (!BC.HasFixedLoadAddress && BF.hasEHRanges() && BF.isSplit())
Trampolines = createEHTrampolines(BF);
if (BF.hasEHRanges() && BF.isSplit()) {
// If all landing pads for this fragment are grouped in one (potentially
// different) fragment, we can set LPStart to the start of that fragment
// and avoid trampoline code.
bool NeedsTrampolines = false;
for (FunctionFragment &FF : BF.getLayout().fragments()) {
// Vector of fragments that contain landing pads for this fragment.
SmallVector<FragmentNum, 4> LandingPadFragments;
for (const BinaryBasicBlock *BB : FF)
for (const BinaryBasicBlock *LPB : BB->landing_pads())
LandingPadFragments.push_back(LPB->getFragmentNum());

// Eliminate duplicate entries from the vector.
llvm::sort(LandingPadFragments);
auto Last = llvm::unique(LandingPadFragments);
LandingPadFragments.erase(Last, LandingPadFragments.end());

if (LandingPadFragments.size() == 0) {
// If the fragment has no landing pads, we can safely set itself as its
// landing pad fragment.
BF.setLPFragment(FF.getFragmentNum(), FF.getFragmentNum());
} else if (LandingPadFragments.size() == 1) {
BF.setLPFragment(FF.getFragmentNum(), LandingPadFragments.front());
} else {
if (!BC.HasFixedLoadAddress) {
NeedsTrampolines = true;
break;
} else {
BF.setLPFragment(FF.getFragmentNum(), std::nullopt);
}
}
}

// Trampolines guarantee that all landing pads for any given fragment will
// be contained in the same fragment.
if (NeedsTrampolines) {
for (FunctionFragment &FF : BF.getLayout().fragments())
BF.setLPFragment(FF.getFragmentNum(), FF.getFragmentNum());
Trampolines = createEHTrampolines(BF);
}
}

// Check the new size to see if it's worth splitting the function.
if (BC.isX86() && LayoutUpdated) {
Expand Down Expand Up @@ -933,6 +972,10 @@ void SplitFunctions::splitFunction(BinaryFunction &BF, SplitStrategy &S) {
}
}

// Restore LP fragment for the main fragment if the splitting was undone.
if (BF.hasEHRanges() && !BF.isSplit())
BF.setLPFragment(FragmentNum::main(), FragmentNum::main());

// Fix branches if the splitting decision of the pass after function
// reordering is different from that of the pass before function reordering.
if (LayoutUpdated && BC.HasFinalizedFunctionOrder)
Expand Down
11 changes: 6 additions & 5 deletions bolt/lib/Passes/VeneerElimination.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,16 +46,17 @@ Error VeneerElimination::runOnFunctions(BinaryContext &BC) {
if (BF.isIgnored())
continue;

MCInst &FirstInstruction = *(BF.begin()->begin());
const MCSymbol *VeneerTargetSymbol = 0;
uint64_t TargetAddress;
if (BC.MIB->matchAbsLongVeneer(BF, TargetAddress)) {
if (BC.MIB->isTailCall(FirstInstruction)) {
VeneerTargetSymbol = BC.MIB->getTargetSymbol(FirstInstruction);
} else if (BC.MIB->matchAbsLongVeneer(BF, TargetAddress)) {
if (BinaryFunction *TargetBF =
BC.getBinaryFunctionAtAddress(TargetAddress))
VeneerTargetSymbol = TargetBF->getSymbol();
} else {
MCInst &FirstInstruction = *(BF.begin()->begin());
if (BC.MIB->hasAnnotation(FirstInstruction, "AArch64Veneer"))
VeneerTargetSymbol = BC.MIB->getTargetSymbol(FirstInstruction, 1);
} else if (BC.MIB->hasAnnotation(FirstInstruction, "AArch64Veneer")) {
VeneerTargetSymbol = BC.MIB->getTargetSymbol(FirstInstruction, 1);
}

if (!VeneerTargetSymbol)
Expand Down
43 changes: 29 additions & 14 deletions bolt/lib/Profile/DataAggregator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,12 @@ cl::opt<bool> ReadPreAggregated(
"pa", cl::desc("skip perf and read data from a pre-aggregated file format"),
cl::cat(AggregatorCategory));

cl::opt<std::string>
ReadPerfEvents("perf-script-events",
cl::desc("skip perf event collection by supplying a "
"perf-script output in a textual format"),
cl::ReallyHidden, cl::init(""), cl::cat(AggregatorCategory));

static cl::opt<bool>
TimeAggregator("time-aggr",
cl::desc("time BOLT aggregator"),
Expand Down Expand Up @@ -167,8 +173,9 @@ void DataAggregator::findPerfExecutable() {
void DataAggregator::start() {
outs() << "PERF2BOLT: Starting data aggregation job for " << Filename << "\n";

// Don't launch perf for pre-aggregated files
if (opts::ReadPreAggregated)
// Don't launch perf for pre-aggregated files or when perf input is specified
// by the user.
if (opts::ReadPreAggregated || !opts::ReadPerfEvents.empty())
return;

findPerfExecutable();
Expand Down Expand Up @@ -464,6 +471,13 @@ void DataAggregator::filterBinaryMMapInfo() {

int DataAggregator::prepareToParse(StringRef Name, PerfProcessInfo &Process,
PerfProcessErrorCallbackTy Callback) {
if (!opts::ReadPerfEvents.empty()) {
outs() << "PERF2BOLT: using pre-processed perf events for '" << Name
<< "' (perf-script-events)\n";
ParsingBuf = opts::ReadPerfEvents;
return 0;
}

std::string Error;
outs() << "PERF2BOLT: waiting for perf " << Name
<< " collection to finish...\n";
Expand Down Expand Up @@ -2056,15 +2070,6 @@ std::error_code DataAggregator::parseMMapEvents() {
if (FileMMapInfo.first == "(deleted)")
continue;

// Consider only the first mapping of the file for any given PID
auto Range = GlobalMMapInfo.equal_range(FileMMapInfo.first);
bool PIDExists = llvm::any_of(make_range(Range), [&](const auto &MI) {
return MI.second.PID == FileMMapInfo.second.PID;
});

if (PIDExists)
continue;

GlobalMMapInfo.insert(FileMMapInfo);
}

Expand Down Expand Up @@ -2116,12 +2121,22 @@ std::error_code DataAggregator::parseMMapEvents() {
<< " using file offset 0x" << Twine::utohexstr(MMapInfo.Offset)
<< ". Ignoring profile data for this mapping\n";
continue;
} else {
MMapInfo.BaseAddress = *BaseAddress;
}
MMapInfo.BaseAddress = *BaseAddress;
}

BinaryMMapInfo.insert(std::make_pair(MMapInfo.PID, MMapInfo));
// Try to add MMapInfo to the map and update its size. Large binaries may
// span to multiple text segments, so the mapping is inserted only on the
// first occurrence.
if (!BinaryMMapInfo.insert(std::make_pair(MMapInfo.PID, MMapInfo)).second)
assert(MMapInfo.BaseAddress == BinaryMMapInfo[MMapInfo.PID].BaseAddress &&
"Base address on multiple segment mappings should match");

// Update mapping size.
const uint64_t EndAddress = MMapInfo.MMapAddress + MMapInfo.Size;
const uint64_t Size = EndAddress - BinaryMMapInfo[MMapInfo.PID].BaseAddress;
if (Size > BinaryMMapInfo[MMapInfo.PID].Size)
BinaryMMapInfo[MMapInfo.PID].Size = Size;
}

if (BinaryMMapInfo.empty()) {
Expand Down
156 changes: 116 additions & 40 deletions bolt/lib/Rewrite/RewriteInstance.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2927,6 +2927,23 @@ void RewriteInstance::handleRelocation(const SectionRef &RelocatedSection,
LLVM_DEBUG(dbgs() << "BOLT-DEBUG: ignoring relocation from data to data\n");
}

static BinaryFunction *getInitFunctionIfStaticBinary(BinaryContext &BC) {
// Workaround for https://github.com/llvm/llvm-project/issues/100096
// ("[BOLT] GOT array pointer incorrectly rewritten"). In aarch64
// static glibc binaries, the .init section's _init function pointer can
// alias with a data pointer for the end of an array. GOT rewriting
// currently can't detect this and updates the data pointer to the
// moved _init, causing a runtime crash. Skipping _init on the other
// hand should be harmless.
if (!BC.IsStaticExecutable)
return nullptr;
const BinaryData *BD = BC.getBinaryDataByName("_init");
if (!BD || BD->getSectionName() != ".init")
return nullptr;
LLVM_DEBUG(dbgs() << "BOLT-DEBUG: skip _init in for GOT workaround.\n");
return BC.getBinaryFunctionAtAddress(BD->getAddress());
}

void RewriteInstance::selectFunctionsToProcess() {
// Extend the list of functions to process or skip from a file.
auto populateFunctionNames = [](cl::opt<std::string> &FunctionNamesFile,
Expand Down Expand Up @@ -3047,6 +3064,9 @@ void RewriteInstance::selectFunctionsToProcess() {
return true;
};

if (BinaryFunction *Init = getInitFunctionIfStaticBinary(*BC))
Init->setIgnored();

for (auto &BFI : BC->getBinaryFunctions()) {
BinaryFunction &Function = BFI.second;

Expand Down Expand Up @@ -3887,6 +3907,43 @@ void RewriteInstance::mapCodeSections(BOLTLinker::SectionMapper MapSection) {

void RewriteInstance::mapAllocatableSections(
BOLTLinker::SectionMapper MapSection) {

if (opts::UseOldText || opts::StrictMode) {
auto tryRewriteSection = [&](BinarySection &OldSection,
BinarySection &NewSection) {
if (OldSection.getSize() < NewSection.getOutputSize())
return;

BC->outs() << "BOLT-INFO: rewriting " << OldSection.getName()
<< " in-place\n";

NewSection.setOutputAddress(OldSection.getAddress());
NewSection.setOutputFileOffset(OldSection.getInputFileOffset());
MapSection(NewSection, OldSection.getAddress());

// Pad contents with zeros.
NewSection.addPadding(OldSection.getSize() - NewSection.getOutputSize());

// Prevent the original section name from appearing in the section header
// table.
OldSection.setAnonymous(true);
};

if (EHFrameSection) {
BinarySection *NewEHFrameSection =
getSection(getNewSecPrefix() + getEHFrameSectionName());
assert(NewEHFrameSection && "New contents expected for .eh_frame");
tryRewriteSection(*EHFrameSection, *NewEHFrameSection);
}
BinarySection *EHSection = getSection(".gcc_except_table");
BinarySection *NewEHSection =
getSection(getNewSecPrefix() + ".gcc_except_table");
if (EHSection) {
assert(NewEHSection && "New contents expected for .gcc_except_table");
tryRewriteSection(*EHSection, *NewEHSection);
}
}

// Allocate read-only sections first, then writable sections.
enum : uint8_t { ST_READONLY, ST_READWRITE };
for (uint8_t SType = ST_READONLY; SType <= ST_READWRITE; ++SType) {
Expand Down Expand Up @@ -4164,7 +4221,6 @@ void RewriteInstance::rewriteNoteSections() {
// New section size.
uint64_t Size = 0;
bool DataWritten = false;
uint8_t *SectionData = nullptr;
// Copy over section contents unless it's one of the sections we overwrite.
if (!willOverwriteSection(SectionName)) {
Size = Section.sh_size;
Expand Down Expand Up @@ -4196,12 +4252,7 @@ void RewriteInstance::rewriteNoteSections() {
if (BSec->getAllocAddress()) {
assert(!DataWritten && "Writing section twice.");
(void)DataWritten;
SectionData = BSec->getOutputData();

LLVM_DEBUG(dbgs() << "BOLT-DEBUG: " << (Size ? "appending" : "writing")
<< " contents to section " << SectionName << '\n');
OS.write(reinterpret_cast<char *>(SectionData), BSec->getOutputSize());
Size += BSec->getOutputSize();
Size += BSec->write(OS);
}

BSec->setOutputFileOffset(NextAvailableOffset);
Expand Down Expand Up @@ -4232,8 +4283,7 @@ void RewriteInstance::rewriteNoteSections() {
<< " of size " << Section.getOutputSize() << " at offset 0x"
<< Twine::utohexstr(Section.getOutputFileOffset()) << '\n');

OS.write(Section.getOutputContents().data(), Section.getOutputSize());
NextAvailableOffset += Section.getOutputSize();
NextAvailableOffset += Section.write(OS);
}
}

Expand Down Expand Up @@ -4347,6 +4397,10 @@ RewriteInstance::getOutputSections(ELFObjectFile<ELFT> *File,
BinarySection *BinSec = BC->getSectionForSectionRef(SecRef);
assert(BinSec && "Matching BinarySection should exist.");

// Exclude anonymous sections.
if (BinSec->isAnonymous())
continue;

addSection(Section, *BinSec);
}

Expand Down Expand Up @@ -5699,8 +5753,8 @@ void RewriteInstance::rewriteFile() {
<< Twine::utohexstr(Section.getAllocAddress()) << "\n of size "
<< Section.getOutputSize() << "\n at offset "
<< Section.getOutputFileOffset() << '\n';
OS.pwrite(reinterpret_cast<const char *>(Section.getOutputData()),
Section.getOutputSize(), Section.getOutputFileOffset());
OS.seek(Section.getOutputFileOffset());
Section.write(OS);
}

for (BinarySection &Section : BC->allocatableSections())
Expand Down Expand Up @@ -5791,42 +5845,64 @@ void RewriteInstance::writeEHFrameHeader() {
LLVM_DEBUG(dbgs() << "BOLT: writing a new " << getEHFrameHdrSectionName()
<< '\n');

NextAvailableAddress =
appendPadding(Out->os(), NextAvailableAddress, EHFrameHdrAlign);
// Try to overwrite the original .eh_frame_hdr if the size permits.
uint64_t EHFrameHdrOutputAddress = 0;
uint64_t EHFrameHdrFileOffset = 0;
std::vector<char> NewEHFrameHdr;
BinarySection *OldEHFrameHdrSection = getSection(getEHFrameHdrSectionName());
if (OldEHFrameHdrSection) {
NewEHFrameHdr = CFIRdWrt->generateEHFrameHeader(
RelocatedEHFrame, NewEHFrame, OldEHFrameHdrSection->getAddress());
if (NewEHFrameHdr.size() <= OldEHFrameHdrSection->getSize()) {
BC->outs() << "BOLT-INFO: rewriting " << getEHFrameHdrSectionName()
<< " in-place\n";
EHFrameHdrOutputAddress = OldEHFrameHdrSection->getAddress();
EHFrameHdrFileOffset = OldEHFrameHdrSection->getInputFileOffset();
} else {
OldEHFrameHdrSection->setOutputName(getOrgSecPrefix() +
getEHFrameHdrSectionName());
OldEHFrameHdrSection = nullptr;
}
}

// If there was not enough space, allocate more memory for .eh_frame_hdr.
if (!OldEHFrameHdrSection) {
NextAvailableAddress =
appendPadding(Out->os(), NextAvailableAddress, EHFrameHdrAlign);

EHFrameHdrOutputAddress = NextAvailableAddress;
EHFrameHdrFileOffset = getFileOffsetForAddress(NextAvailableAddress);

NewEHFrameHdr = CFIRdWrt->generateEHFrameHeader(
RelocatedEHFrame, NewEHFrame, EHFrameHdrOutputAddress);

const uint64_t EHFrameHdrOutputAddress = NextAvailableAddress;
const uint64_t EHFrameHdrFileOffset =
getFileOffsetForAddress(NextAvailableAddress);
NextAvailableAddress += NewEHFrameHdr.size();
if (!BC->BOLTReserved.empty() &&
(NextAvailableAddress > BC->BOLTReserved.end())) {
BC->errs() << "BOLT-ERROR: unable to fit " << getEHFrameHdrSectionName()
<< " into reserved space\n";
exit(1);
}

std::vector<char> NewEHFrameHdr = CFIRdWrt->generateEHFrameHeader(
RelocatedEHFrame, NewEHFrame, EHFrameHdrOutputAddress);
// Create a new entry in the section header table.
const unsigned Flags = BinarySection::getFlags(/*IsReadOnly=*/true,
/*IsText=*/false,
/*IsAllocatable=*/true);
BinarySection &EHFrameHdrSec = BC->registerOrUpdateSection(
getNewSecPrefix() + getEHFrameHdrSectionName(), ELF::SHT_PROGBITS,
Flags, nullptr, NewEHFrameHdr.size(), /*Alignment=*/1);
EHFrameHdrSec.setOutputFileOffset(EHFrameHdrFileOffset);
EHFrameHdrSec.setOutputAddress(EHFrameHdrOutputAddress);
EHFrameHdrSec.setOutputName(getEHFrameHdrSectionName());
}

Out->os().seek(EHFrameHdrFileOffset);
Out->os().write(NewEHFrameHdr.data(), NewEHFrameHdr.size());

const unsigned Flags = BinarySection::getFlags(/*IsReadOnly=*/true,
/*IsText=*/false,
/*IsAllocatable=*/true);
BinarySection *OldEHFrameHdrSection = getSection(getEHFrameHdrSectionName());
// Pad the contents if overwriting in-place.
if (OldEHFrameHdrSection)
OldEHFrameHdrSection->setOutputName(getOrgSecPrefix() +
getEHFrameHdrSectionName());

BinarySection &EHFrameHdrSec = BC->registerOrUpdateSection(
getNewSecPrefix() + getEHFrameHdrSectionName(), ELF::SHT_PROGBITS, Flags,
nullptr, NewEHFrameHdr.size(), /*Alignment=*/1);
EHFrameHdrSec.setOutputFileOffset(EHFrameHdrFileOffset);
EHFrameHdrSec.setOutputAddress(EHFrameHdrOutputAddress);
EHFrameHdrSec.setOutputName(getEHFrameHdrSectionName());

NextAvailableAddress += EHFrameHdrSec.getOutputSize();

if (!BC->BOLTReserved.empty() &&
(NextAvailableAddress > BC->BOLTReserved.end())) {
BC->errs() << "BOLT-ERROR: unable to fit " << getEHFrameHdrSectionName()
<< " into reserved space\n";
exit(1);
}
Out->os().write_zeros(OldEHFrameHdrSection->getSize() -
NewEHFrameHdr.size());

// Merge new .eh_frame with the relocated original so that gdb can locate all
// FDEs.
Expand Down
43 changes: 43 additions & 0 deletions bolt/test/AArch64/check-init-not-moved.s
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Regression test for https://github.com/llvm/llvm-project/issues/100096
# static glibc binaries crash on startup because _init is moved and
# shares its address with an array end pointer. The GOT rewriting can't
# tell the two pointers apart and incorrectly updates the _array_end
# address. Test checks that _init is not moved.

# RUN: llvm-mc -filetype=obj -triple aarch64-unknown-unknown %s -o %t.o
# RUN: %clang %cflags %t.o -o %t.exe -Wl,-q -static -Wl,--section-start=.data=0x1000 -Wl,--section-start=.init=0x1004
# RUN: llvm-bolt %t.exe -o %t.bolt
# RUN: llvm-nm %t.exe | FileCheck --check-prefix=CHECK-ORIGINAL %s
# RUN: llvm-nm %t.bolt | FileCheck --check-prefix=CHECK-BOLTED %s

.section .data
.globl _array_end
_array_start:
.word 0x0

_array_end:
.section .init,"ax",@progbits
.globl _init

# Check that bolt doesn't move _init.
#
# CHECK-ORIGINAL: 0000000000001004 T _init
# CHECK-BOLTED: 0000000000001004 T _init
_init:
ret

.section .text,"ax",@progbits
.globl _start

# Check that bolt is moving some other functions.
#
# CHECK-ORIGINAL: 0000000000001008 T _start
# CHECK-BOLTED-NOT: 0000000000001008 T _start
_start:
bl _init
adrp x0, #:got:_array_end
ldr x0, [x0, #:gotpage_lo15:_array_end]
adrp x0, #:got:_init
ldr x0, [x0, #:gotpage_lo15:_init]
ret

2 changes: 1 addition & 1 deletion bolt/test/AArch64/data-at-0-offset.c
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// RUN: %clang %cflags -O2 -fPIE -Wl,-q -pie %s -o %t.exe
// RUN: %clang %cflags -O2 -fPIE -std=gnu99 -Wl,-q -pie %s -o %t.exe
// RUN: llvm-bolt %t.exe -o %t.bolt 2>&1 | FileCheck %s
// CHECK-NOT: BOLT-WARNING: unable to disassemble instruction at offset

Expand Down
2 changes: 1 addition & 1 deletion bolt/test/AArch64/double_jump.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// A contrived example to test the double jump removal peephole.

// RUN: %clang %cflags -O0 %s -o %t.exe
// RUN: %clangxx %cxxflags -O0 %s -o %t.exe
// RUN: llvm-bolt %t.exe -o %t.bolt --peepholes=double-jumps | \
// RUN: FileCheck %s -check-prefix=CHECKBOLT
// RUN: llvm-objdump --no-print-imm-hex -d %t.bolt | FileCheck %s
Expand Down
59 changes: 41 additions & 18 deletions bolt/test/AArch64/veneer-lld-abs.s
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
## Check that llvm-bolt correctly recognizes long absolute thunks generated
## by LLD.
## Check that llvm-bolt correctly recognizes veneers/thunks for absolute code
## generated by LLD.

# RUN: llvm-mc -filetype=obj -triple aarch64-unknown-unknown %s -o %t.o
# RUN: %clang %cflags -fno-PIC -no-pie %t.o -o %t.exe -nostdlib \
Expand All @@ -12,40 +12,63 @@

.text
.balign 4
.global foo
.type foo, %function
foo:
adrp x1, foo
.global far_function
.type far_function, %function
far_function:
ret
.size foo, .-foo
.size far_function, .-far_function

.global near_function
.type near_function, %function
near_function:
ret
.size near_function, .-near_function

## Force relocations against .text.
.reloc 0, R_AARCH64_NONE

.section ".mytext", "ax"
.balign 4

.global __AArch64AbsLongThunk_foo
.type __AArch64AbsLongThunk_foo, %function
__AArch64AbsLongThunk_foo:
## This version of a thunk is always generated by LLD for function calls
## spanning more than 256MB.
.global __AArch64AbsLongThunk_far_function
.type __AArch64AbsLongThunk_far_function, %function
__AArch64AbsLongThunk_far_function:
ldr x16, .L1
br x16
# CHECK-INPUT-LABEL: <__AArch64AbsLongThunk_foo>:
# CHECK-INPUT-LABEL: <__AArch64AbsLongThunk_far_function>:
# CHECK-INPUT-NEXT: ldr
# CHECK-INPUT-NEXT: br
.L1:
.quad foo
.size __AArch64AbsLongThunk_foo, .-__AArch64AbsLongThunk_foo
.quad far_function
.size __AArch64AbsLongThunk_far_function, .-__AArch64AbsLongThunk_far_function

## If a callee is closer than 256MB away, LLD may generate a thunk with a direct
## jump to the callee. Note, that the name might still include "AbsLong".
.global __AArch64AbsLongThunk_near_function
.type __AArch64AbsLongThunk_near_function, %function
__AArch64AbsLongThunk_near_function:
b near_function
# CHECK-INPUT-LABEL: <__AArch64AbsLongThunk_near_function>:
# CHECK-INPUT-NEXT: b {{.*}} <near_function>
.size __AArch64AbsLongThunk_near_function, .-__AArch64AbsLongThunk_near_function

## Check that the thunk was removed from .text and _start() calls foo()
## Check that thunks were removed from .text, and _start calls functions
## directly.

# CHECK-OUTPUT-NOT: __AArch64AbsLongThunk_foo
# CHECK-OUTPUT-NOT: __AArch64AbsLongThunk_{{.*}}

.global _start
.type _start, %function
_start:
# CHECK-INPUT-LABEL: <_start>:
# CHECK-OUTPUT-LABEL: <_start>:
bl __AArch64AbsLongThunk_foo
# CHECK-INPUT-NEXT: bl {{.*}} <__AArch64AbsLongThunk_foo>
# CHECK-OUTPUT-NEXT: bl {{.*}} <foo>
bl __AArch64AbsLongThunk_far_function
bl __AArch64AbsLongThunk_near_function
# CHECK-INPUT-NEXT: bl {{.*}} <__AArch64AbsLongThunk_far_function>
# CHECK-INPUT-NEXT: bl {{.*}} <__AArch64AbsLongThunk_near_function>
# CHECK-OUTPUT-NEXT: bl {{.*}} <far_function>
# CHECK-OUTPUT-NEXT: bl {{.*}} <near_function>
ret
.size _start, .-_start
2 changes: 1 addition & 1 deletion bolt/test/R_ABS.pic.lld.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* with libc available.
* REQUIRES: system-linux
*
* RUN: %clang %cflags -fPIC -shared %s -o %t.so -Wl,-q -fuse-ld=lld
* RUN: %clangxx %cxxflags -fPIC -shared %s -o %t.so -Wl,-q -fuse-ld=lld
* RUN: llvm-bolt %t.so -o %t.so.bolt --relocs
*/

Expand Down
2 changes: 1 addition & 1 deletion bolt/test/X86/double-jump.test
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
## correctly on Windows e.g. subshell execution
REQUIRES: shell

RUN: %clang %cflags %p/Inputs/double_jump.cpp -o %t.exe
RUN: %clangxx %cxxflags %p/Inputs/double_jump.cpp -o %t.exe
RUN: (llvm-bolt %t.exe --peepholes=double-jumps \
RUN: --eliminate-unreachable -o %t 2>&1 \
RUN: && llvm-objdump -d %t --print-imm-hex --no-show-raw-insn) | FileCheck %s
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
; RUN: -split-dwarf-file=main.dwo -o main.o
; RUN: llvm-mc -dwarf-version=5 -filetype=obj -triple x86_64-unknown-linux %p/Inputs/dwarf5-df-inlined-subroutine-gc-sections-range-helper.s \
; RUN: -split-dwarf-file=helper.dwo -o helper.o
; RUN: %clang --target=x86_64-pc-linux -fuse-ld=lld -Wl,-gc-sections -Wl,-q -gdwarf-5 -gsplit-dwarf=split main.o helper.o -o main.exe
; RUN: %clang -fuse-ld=lld -Wl,-gc-sections -Wl,-q -gdwarf-5 -gsplit-dwarf=split main.o helper.o -o main.exe
; RUN: llvm-bolt main.exe -o main.exe.bolt --update-debug-sections
; RUN: llvm-dwarfdump --debug-addr main.exe > log.txt
; RUN: llvm-dwarfdump --debug-rnglists --verbose --show-form main.dwo >> log.txt
Expand Down
75 changes: 75 additions & 0 deletions bolt/test/X86/exceptions-compact.s
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
## Check that llvm-bolt is able to overwrite LSDA in ULEB128 format in-place for
## all types of binaries.

# REQUIRES: system-linux

# RUN: llvm-mc -filetype=obj -triple x86_64-unknown-linux %s -o %t.o
# RUN: ld.lld --no-pie %t.o -o %t.exe -q
# RUN: ld.lld --pie %t.o -o %t.pie -q
# RUN: ld.lld --shared %t.o -o %t.so -q
# RUN: llvm-bolt %t.exe -o %t.bolt --strict \
# RUN: | FileCheck --check-prefix=CHECK-BOLT %s
# RUN: llvm-bolt %t.pie -o %t.pie.bolt --strict \
# RUN: | FileCheck --check-prefix=CHECK-BOLT %s
# RUN: llvm-bolt %t.so -o %t.so.bolt --strict \
# RUN: | FileCheck --check-prefix=CHECK-BOLT %s

# CHECK-BOLT: rewriting .gcc_except_table in-place

# RUN: llvm-readelf -WS %t.bolt | FileCheck --check-prefix=CHECK-ELF %s
# RUN: llvm-readelf -WS %t.pie.bolt | FileCheck --check-prefix=CHECK-ELF %s
# RUN: llvm-readelf -WS %t.so.bolt | FileCheck --check-prefix=CHECK-ELF %s

# CHECK-ELF-NOT: .bolt.org.gcc_except_table

.text
.global foo
.type foo, %function
foo:
.cfi_startproc
ret
.cfi_endproc
.size foo, .-foo

.globl _start
.type _start, %function
_start:
.Lfunc_begin0:
.cfi_startproc
.cfi_lsda 27, .Lexception0
call foo
.Ltmp0:
call foo
.Ltmp1:
ret

## Landing pads.
.LLP1:
ret
.LLP0:
ret

.cfi_endproc
.Lfunc_end0:
.size _start, .-_start

## EH table.
.section .gcc_except_table,"a",@progbits
.p2align 2
GCC_except_table0:
.Lexception0:
.byte 255 # @LPStart Encoding = omit
.byte 255 # @TType Encoding = omit
.byte 1 # Call site Encoding = uleb128
.uleb128 .Lcst_end0-.Lcst_begin0
.Lcst_begin0:
.uleb128 .Lfunc_begin0-.Lfunc_begin0 # >> Call Site 1 <<
.uleb128 .Ltmp0-.Lfunc_begin0 # Call between .Lfunc_begin0 and .Ltmp0
.uleb128 .LLP0-.Lfunc_begin0 # jumps to .LLP0
.byte 0 # On action: cleanup
.uleb128 .Ltmp0-.Lfunc_begin0 # >> Call Site 2 <<
.uleb128 .Ltmp1-.Ltmp0 # Call between .Ltmp0 and .Ltmp1
.uleb128 .LLP1-.Lfunc_begin0 # jumps to .LLP1
.byte 0 # On action: cleanup
.Lcst_end0:

2 changes: 1 addition & 1 deletion bolt/test/X86/jmp-optimization.test
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
## correctly on Windows e.g. unsupported parameter expansion
REQUIRES: shell

RUN: %clang %cflags -O2 %S/Inputs/jmp_opt{,2,3}.cpp -o %t
RUN: %clangxx %cxxflags -O2 %S/Inputs/jmp_opt{,2,3}.cpp -o %t
RUN: llvm-bolt -inline-small-functions %t -o %t.bolt
RUN: llvm-objdump -d %t.bolt --print-imm-hex | FileCheck %s

Expand Down
2 changes: 1 addition & 1 deletion bolt/test/X86/match-functions-with-call-graph.test
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

# REQUIRES: system-linux
# RUN: split-file %s %t
# RUN: %clang %cflags %t/main.cpp -o %t.exe -Wl,-q -nostdlib
# RUN: %clangxx %cxxflags %t/main.cpp -o %t.exe -Wl,-q -nostdlib
# RUN: llvm-bolt %t.exe -o %t.out --data %t/yaml --profile-ignore-hash -v=1 \
# RUN: --dyno-stats --print-cfg --infer-stale-profile=1 --match-with-call-graph 2>&1 | FileCheck %s

Expand Down
86 changes: 86 additions & 0 deletions bolt/test/X86/pie-eh-split-undo.s
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# REQUIRES: system-linux

# RUN: llvm-mc -filetype=obj -triple x86_64-unknown-linux %s -o %t.o
# RUN: link_fdata %s %t.o %t.fdata
# RUN: llvm-strip --strip-unneeded %t.o
# RUN: ld.lld --pie %t.o -o %t.exe -q
# RUN: llvm-bolt %t.exe -o %t.out --data %t.fdata --split-functions --split-eh \
# RUN: --split-all-cold --print-after-lowering --print-only=_start 2>&1 \
# RUN: | FileCheck %s

## _start has two landing pads: one hot and one cold. Hence, BOLT will introduce
## a landing pad trampoline. However, the trampoline code will make the main
## split fragment larger than the whole function before split. Then BOLT will
## undo the splitting and remove the trampoline.

# CHECK: Binary Function "_start"
# CHECK: IsSplit :
# CHECK-SAME: 0

## Check that a landing pad trampoline was created, but contains no instructions
## and falls though to the real landing pad.

# CHECK: {{^[^[:space:]]+}} (0 instructions
# CHECK-NEXT: Landing Pad{{$}}
# CHECK: Exec Count
# CHECK-SAME: : 0
# CHECK: Successors:
# CHECK-SAME: [[LP:[^[:space:]]+]]
# CHECK-EMPTY:
# CHECK-NEXT: [[LP]]

.text
.global foo
.type foo, %function
foo:
.cfi_startproc
ret
.cfi_endproc
.size foo, .-foo

.globl _start
.type _start, %function
_start:
# FDATA: 0 [unknown] 0 1 _start 0 1 100
.Lfunc_begin0:
.cfi_startproc
.cfi_lsda 27, .Lexception0
call foo
.Ltmp0:
call foo
.Ltmp1:
ret

## Cold landing pad.
.LLP1:
ret

## Hot landing pad.
LLP0:
# FDATA: 0 [unknown] 0 1 _start #LLP0# 1 100
ret

.cfi_endproc
.Lfunc_end0:
.size _start, .-_start

## EH table.
.section .gcc_except_table,"a",@progbits
.p2align 2
GCC_except_table0:
.Lexception0:
.byte 255 # @LPStart Encoding = omit
.byte 255 # @TType Encoding = omit
.byte 1 # Call site Encoding = uleb128
.uleb128 .Lcst_end0-.Lcst_begin0
.Lcst_begin0:
.uleb128 .Lfunc_begin0-.Lfunc_begin0 # >> Call Site 1 <<
.uleb128 .Ltmp0-.Lfunc_begin0 # Call between .Lfunc_begin0 and .Ltmp0
.uleb128 LLP0-.Lfunc_begin0 # jumps to LLP0
.byte 0 # On action: cleanup
.uleb128 .Ltmp0-.Lfunc_begin0 # >> Call Site 2 <<
.uleb128 .Ltmp1-.Ltmp0 # Call between .Ltmp0 and .Ltmp1
.uleb128 .LLP1-.Lfunc_begin0 # jumps to .LLP1
.byte 0 # On action: cleanup
.Lcst_end0:

12 changes: 12 additions & 0 deletions bolt/test/eh-frame-hdr.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Check that llvm-bolt overwrites .eh_frame_hdr in-place.

REQUIRES: system-linux

RUN: %clang %cflags %p/Inputs/hello.c -o %t -Wl,-q
RUN: llvm-bolt %t -o %t.bolt --use-old-text \
RUN: | FileCheck %s --check-prefix=CHECK-BOLT
RUN: llvm-readelf -WS %t.bolt | FileCheck %s

CHECK-BOLT: rewriting .eh_frame_hdr in-place

CHECK-NOT: .bolt.org.eh_frame_hdr
8 changes: 8 additions & 0 deletions bolt/test/eh-frame-overwrite.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Check that llvm-bolt can overwrite .eh_frame section in-place.

REQUIRES: system-linux

RUN: %clang %cflags %p/Inputs/hello.c -o %t -Wl,-q
RUN: llvm-bolt %t -o %t.bolt --strict | FileCheck %s

CHECK: rewriting .eh_frame in-place
2 changes: 1 addition & 1 deletion bolt/test/pie.test
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
## on Linux systems where the host triple matches the target.
REQUIRES: system-linux

RUN: %clang %cflags -fPIC -pie %p/Inputs/jump_table_icp.cpp -o %t
RUN: %clangxx %cxxflags -fPIC -pie %p/Inputs/jump_table_icp.cpp -o %t
RUN: llvm-bolt %t -o %t.null 2>&1 | FileCheck %s

CHECK: BOLT-INFO: shared object or position-independent executable detected
2 changes: 1 addition & 1 deletion bolt/test/runtime/X86/Inputs/pie-exceptions-failed-split.s
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Assembly generated from building the followingC++ code with the following
# Assembly generated from building the following C++ code with the following
# command using trunk clang. Then, basic block at .LBB1_7 was moved before the
# landing pad.
#
Expand Down
2 changes: 1 addition & 1 deletion bolt/test/runtime/X86/instrumentation-indirect.c
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ int main(int argc, char **argv) {
/*
REQUIRES: system-linux,bolt-runtime,lit-max-individual-test-time
RUN: %clang %cflags %s -o %t.exe -Wl,-q -pie -fpie
RUN: %clang %cflags -D_GNU_SOURCE %s -o %t.exe -Wl,-q -pie -fpie
RUN: llvm-bolt %t.exe --instrument --instrumentation-file=%t.fdata \
RUN: --instrumentation-wait-forks=1 --conservative-instrumentation \
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,25 +11,16 @@ RUN: llvm-bolt %t -o %t.bolt --data %t.fdata --reorder-blocks=ext-tsp \
RUN: --split-functions --split-eh --print-after-lowering \
RUN: --print-only=_Z10throw_testiPPc 2>&1 | FileCheck %s

## Hot code in the test case gets larger after splitting because of jump
## instruction relaxation. Check that BOLT reverts the split correctly.
## Check that a landing pad is split from its thrower and does not require a
## trampoline LP.
CHECK: Binary Function "_Z10throw_testiPPc"
CHECK: IsSplit :
CHECK-SAME: 0

## Check that the landing pad trampoline was created, but contains no
## instructions and falls to the real landing pad.
CHECK: {{^[^[:space:]]+}} (0 instructions
CHECK-NEXT: Landing Pad{{$}}
CHECK: Exec Count
CHECK-SAME: : 0
CHECK: Successors:
CHECK-SAME: [[LP:[^[:space:]]+]]
CHECK-EMPTY:
CHECK-NEXT: [[LP]]
CHECK-DAG: Exec Count
CHECK-NOT: Exec Count
CHECK-DAG: callq __cxa_begin_catch
CHECK-SAME: 1
CHECK: callq {{.*}} # handler: [[LPAD:.*]];
CHECK-NOT: Landing Pad{{$}}
CHECK: HOT-COLD SPLIT POINT
CHECK: {{^}}[[LPAD]]
CHECK-NEXT: Landing Pad

## Verify the output still executes correctly when the exception path is being
## taken.
Expand Down
4 changes: 2 additions & 2 deletions bolt/test/runtime/bolt-reserved.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* new sections.
*/

// RUN: %clang %s -o %t.exe -Wl,-q
// RUN: %clangxx %s -o %t.exe -Wl,-q
// RUN: llvm-bolt %t.exe -o %t.bolt.exe 2>&1 | FileCheck %s
// RUN: %t.bolt.exe

Expand All @@ -16,7 +16,7 @@
* not enough for allocating new sections.
*/

// RUN: %clang %s -o %t.tiny.exe -Wl,--no-eh-frame-hdr -Wl,-q -DTINY
// RUN: %clangxx %s -o %t.tiny.exe -Wl,--no-eh-frame-hdr -Wl,-q -DTINY
// RUN: not llvm-bolt %t.tiny.exe -o %t.tiny.bolt.exe 2>&1 | \
// RUN: FileCheck %s --check-prefix=CHECK-TINY

Expand Down
3 changes: 3 additions & 0 deletions bolt/unittests/Core/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ set(LLVM_LINK_COMPONENTS
add_bolt_unittest(CoreTests
BinaryContext.cpp
MCPlusBuilder.cpp
MemoryMaps.cpp
DynoStats.cpp

DISABLE_LLVM_LINK_LLVM_DYLIB
Expand All @@ -17,6 +18,8 @@ target_link_libraries(CoreTests
PRIVATE
LLVMBOLTCore
LLVMBOLTRewrite
LLVMBOLTProfile
LLVMTestingSupport
)

foreach (tgt ${BOLT_TARGETS_TO_BUILD})
Expand Down
142 changes: 142 additions & 0 deletions bolt/unittests/Core/MemoryMaps.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
//===- bolt/unittest/Core/MemoryMaps.cpp ----------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "bolt/Core/BinaryContext.h"
#include "bolt/Profile/DataAggregator.h"
#include "llvm/BinaryFormat/ELF.h"
#include "llvm/DebugInfo/DWARF/DWARFContext.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/TargetSelect.h"
#include "llvm/Testing/Support/Error.h"
#include "gtest/gtest.h"

using namespace llvm;
using namespace llvm::object;
using namespace llvm::ELF;
using namespace bolt;

namespace opts {
extern cl::opt<std::string> ReadPerfEvents;
} // namespace opts

namespace {

/// Perform checks on memory map events normally captured in perf. Tests use
/// the 'opts::ReadPerfEvents' flag to emulate these events, passing a custom
/// 'perf script' output to DataAggregator.
struct MemoryMapsTester : public testing::TestWithParam<Triple::ArchType> {
void SetUp() override {
initalizeLLVM();
prepareElf();
initializeBOLT();
}

protected:
void initalizeLLVM() {
llvm::InitializeAllTargetInfos();
llvm::InitializeAllTargetMCs();
llvm::InitializeAllAsmParsers();
llvm::InitializeAllDisassemblers();
llvm::InitializeAllTargets();
llvm::InitializeAllAsmPrinters();
}

void prepareElf() {
memcpy(ElfBuf, "\177ELF", 4);
ELF64LE::Ehdr *EHdr = reinterpret_cast<typename ELF64LE::Ehdr *>(ElfBuf);
EHdr->e_ident[llvm::ELF::EI_CLASS] = llvm::ELF::ELFCLASS64;
EHdr->e_ident[llvm::ELF::EI_DATA] = llvm::ELF::ELFDATA2LSB;
EHdr->e_machine = GetParam() == Triple::aarch64 ? EM_AARCH64 : EM_X86_64;
MemoryBufferRef Source(StringRef(ElfBuf, sizeof(ElfBuf)), "ELF");
ObjFile = cantFail(ObjectFile::createObjectFile(Source));
}

void initializeBOLT() {
Relocation::Arch = ObjFile->makeTriple().getArch();
BC = cantFail(BinaryContext::createBinaryContext(
ObjFile->makeTriple(), ObjFile->getFileName(), nullptr, true,
DWARFContext::create(*ObjFile.get()), {llvm::outs(), llvm::errs()}));
ASSERT_FALSE(!BC);
}

char ElfBuf[sizeof(typename ELF64LE::Ehdr)] = {};
std::unique_ptr<ObjectFile> ObjFile;
std::unique_ptr<BinaryContext> BC;
};
} // namespace

#ifdef X86_AVAILABLE

INSTANTIATE_TEST_SUITE_P(X86, MemoryMapsTester,
::testing::Values(Triple::x86_64));

#endif

#ifdef AARCH64_AVAILABLE

INSTANTIATE_TEST_SUITE_P(AArch64, MemoryMapsTester,
::testing::Values(Triple::aarch64));

#endif

/// Check that the correct mmap size is computed when we have multiple text
/// segment mappings.
TEST_P(MemoryMapsTester, ParseMultipleSegments) {
const int Pid = 1234;
StringRef Filename = "BINARY";
opts::ReadPerfEvents = formatv(
"name 0 [000] 0.000000: PERF_RECORD_MMAP2 {0}/{0}: "
"[0xabc0000000(0x1000000) @ 0x11c0000 103:01 1573523 0]: r-xp {1}\n"
"name 0 [000] 0.000000: PERF_RECORD_MMAP2 {0}/{0}: "
"[0xabc2000000(0x8000000) @ 0x31d0000 103:01 1573523 0]: r-xp {1}\n",
Pid, Filename);

BC->SegmentMapInfo[0x11da000] =
SegmentInfo{0x11da000, 0x10da000, 0x11ca000, 0x10da000, 0x10000, true};
BC->SegmentMapInfo[0x31d0000] =
SegmentInfo{0x31d0000, 0x51ac82c, 0x31d0000, 0x3000000, 0x200000, true};

DataAggregator DA("");
BC->setFilename(Filename);
Error Err = DA.preprocessProfile(*BC);

// Ignore errors from perf2bolt when parsing memory events later on.
ASSERT_THAT_ERROR(std::move(Err), Succeeded());

auto &BinaryMMapInfo = DA.getBinaryMMapInfo();
auto El = BinaryMMapInfo.find(Pid);
// Check that memory mapping is present and has the expected size.
ASSERT_NE(El, BinaryMMapInfo.end());
ASSERT_EQ(El->second.Size, static_cast<uint64_t>(0xb1d0000));
}

/// Check that DataAggregator aborts when pre-processing an input binary
/// with multiple text segments that have different base addresses.
TEST_P(MemoryMapsTester, MultipleSegmentsMismatchedBaseAddress) {
const int Pid = 1234;
StringRef Filename = "BINARY";
opts::ReadPerfEvents = formatv(
"name 0 [000] 0.000000: PERF_RECORD_MMAP2 {0}/{0}: "
"[0xabc0000000(0x1000000) @ 0x11c0000 103:01 1573523 0]: r-xp {1}\n"
"name 0 [000] 0.000000: PERF_RECORD_MMAP2 {0}/{0}: "
"[0xabc2000000(0x8000000) @ 0x31d0000 103:01 1573523 0]: r-xp {1}\n",
Pid, Filename);

BC->SegmentMapInfo[0x11da000] =
SegmentInfo{0x11da000, 0x10da000, 0x11ca000, 0x10da000, 0x10000, true};
// Using '0x31d0fff' FileOffset which triggers a different base address
// for this second text segment.
BC->SegmentMapInfo[0x31d0000] =
SegmentInfo{0x31d0000, 0x51ac82c, 0x31d0fff, 0x3000000, 0x200000, true};

DataAggregator DA("");
BC->setFilename(Filename);
ASSERT_DEBUG_DEATH(
{ Error Err = DA.preprocessProfile(*BC); },
"Base address on multiple segment mappings should match");
}
4 changes: 2 additions & 2 deletions bolt/utils/bughunter.sh
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ if [[ $FAIL -eq "0" ]]; then
fi
else
echo "Did it pass? Type the return code [0 = pass, 1 = fail]"
read -n1 PASS
read -n1 FAIL
fi
if [[ $FAIL -eq "0" ]] ; then
echo " Warning: optimized binary passes."
Expand Down Expand Up @@ -205,7 +205,7 @@ while [[ "$CONTINUE" -ne "0" ]] ; do
echo " OPTIMIZED_BINARY failure=$FAIL"
else
echo "Did it pass? Type the return code [0 = pass, 1 = fail]"
read -n1 PASS
read -n1 FAIL
fi
else
FAIL=1
Expand Down
30 changes: 0 additions & 30 deletions clang-tools-extra/CODE_OWNERS.TXT

This file was deleted.

75 changes: 75 additions & 0 deletions clang-tools-extra/Maintainers.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
=============================
Clang Tools Extra Maintainers
=============================

This file is a list of the maintainers
(https://llvm.org/docs/DeveloperPolicy.html#maintainers) for clang-tools-extra.


Active Maintainers
==================
The following people are the active maintainers for the project. Please reach
out to them for code reviews, questions about their area of expertise, or other
assistance.

Lead Maintainer
---------------
| Aaron Ballman
| aaron@aaronballman.com (email), aaron.ballman (Phabricator), AaronBallman (GitHub), AaronBallman (Discourse), aaronballman (Discord), AaronBallman (IRC)


clang-tidy
----------
| Congcong Cai
| congcongcai0907@163.com (email), HerrCai0907 (GitHub), HerrCai0907 (Discourse)

| Julian Schmidt
| git.julian.schmidt@gmail.com (email), 5chmidti (GitHub), 5chmidti (Discourse), 5chmidti (Discord)

| Piotr Zegar
| me@piotrzegar.pl (email), PiotrZSL (GitHub), PiotrZSL (Discourse), PiotrZSL (Discord)


clang-query
-----------
| Aaron Ballman
| aaron@aaronballman.com (email), aaron.ballman (Phabricator), AaronBallman (GitHub), AaronBallman (Discourse), aaronballman (Discord), AaronBallman (IRC)


clang-doc
---------
| Paul Kirth
| paulkirth@google.com (email), ilovepi (GitHub), ilovepi (Discourse)

| Peter Chou
| peterchou411@gmail.com (email), PeterChou1 (GitHub), PeterChou1 (Discourse), .peterchou (Discord)


clangd
------
| Nathan Ridge
| zeratul976@hotmail.com (email), HighCommander4 (GitHub), HighCommander4 (Discourse), nridge (Discord)

| Chris Bieneman
| chris.bieneman@gmail.com (email), llvm-beanz (GitHub), beanz (Discord), beanz (Discourse)

| Kadir Çetinkaya
| kadircet@google.com (email), kadircet (GitHub) kadircet (Discourse), kadircet (Discord)


Inactive Maintainers
====================
The following people have graciously spent time performing maintainership
responsibilities but are no longer active in that role. Thank you for all your
help with the success of the project!

Emeritus Lead Maintainers
-------------------------
| Manuel Klimek (klimek@google.com (email), r4nt (GitHub))


Inactive component maintainers
------------------------------
| Nathan James (n.james93@hotmail.co.uk) -- clang-tidy
| Julie Hockett (juliehockett@google.com) -- clang-doc
| Sam McCall (sammccall@google.com (email), sam-mccall (GitHub, Discourse, Discord)) -- clangd
3 changes: 2 additions & 1 deletion clang-tools-extra/clang-include-fixer/IncludeFixer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,8 @@ bool IncludeFixerActionFactory::runInvocation(

// Create the compiler's actual diagnostics engine. We want to drop all
// diagnostics here.
Compiler.createDiagnostics(new clang::IgnoringDiagConsumer,
Compiler.createDiagnostics(Files->getVirtualFileSystem(),
new clang::IgnoringDiagConsumer,
/*ShouldOwnClient=*/true);
Compiler.createSourceManager(*Files);

Expand Down
29 changes: 26 additions & 3 deletions clang-tools-extra/clang-reorder-fields/ReorderFieldsAction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,28 @@ findMembersUsedInInitExpr(const CXXCtorInitializer *Initializer,
return Results;
}

/// Returns the full source range for the field declaration up to (not
/// including) the trailing semicolumn, including potential macro invocations,
/// e.g. `int a GUARDED_BY(mu);`.
static SourceRange getFullFieldSourceRange(const FieldDecl &Field,
const ASTContext &Context) {
SourceRange Range = Field.getSourceRange();
SourceLocation End = Range.getEnd();
const SourceManager &SM = Context.getSourceManager();
const LangOptions &LangOpts = Context.getLangOpts();
while (true) {
std::optional<Token> CurrentToken = Lexer::findNextToken(End, SM, LangOpts);

if (!CurrentToken || CurrentToken->is(tok::semi))
break;

if (CurrentToken->is(tok::eof))
return Range; // Something is wrong, return the original range.
End = CurrentToken->getLastLoc();
}
return SourceRange(Range.getBegin(), End);
}

/// Reorders fields in the definition of a struct/class.
///
/// At the moment reordering of fields with
Expand Down Expand Up @@ -145,9 +167,10 @@ static bool reorderFieldsInDefinition(
const auto FieldIndex = Field->getFieldIndex();
if (FieldIndex == NewFieldsOrder[FieldIndex])
continue;
addReplacement(Field->getSourceRange(),
Fields[NewFieldsOrder[FieldIndex]]->getSourceRange(),
Context, Replacements);
addReplacement(
getFullFieldSourceRange(*Field, Context),
getFullFieldSourceRange(*Fields[NewFieldsOrder[FieldIndex]], Context),
Context, Replacements);
}
return true;
}
Expand Down
221 changes: 221 additions & 0 deletions clang-tools-extra/clang-tidy/bugprone/BranchCloneCheck.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,212 @@ void BranchCloneCheck::registerMatchers(MatchFinder *Finder) {
this);
Finder->addMatcher(switchStmt().bind("switch"), this);
Finder->addMatcher(conditionalOperator().bind("condOp"), this);
Finder->addMatcher(
ifStmt((hasThen(hasDescendant(ifStmt())))).bind("ifWithDescendantIf"),
this);
}

/// Determines whether two statement trees are identical regarding
/// operators and symbols.
///
/// Exceptions: expressions containing macros or functions with possible side
/// effects are never considered identical.
/// Limitations: (t + u) and (u + t) are not considered identical.
/// t*(u + t) and t*u + t*t are not considered identical.
///
static bool isIdenticalStmt(const ASTContext &Ctx, const Stmt *Stmt1,
const Stmt *Stmt2, bool IgnoreSideEffects) {

if (!Stmt1 || !Stmt2)
return !Stmt1 && !Stmt2;

// If Stmt1 & Stmt2 are of different class then they are not
// identical statements.
if (Stmt1->getStmtClass() != Stmt2->getStmtClass())
return false;

const auto *Expr1 = dyn_cast<Expr>(Stmt1);
const auto *Expr2 = dyn_cast<Expr>(Stmt2);

if (Expr1 && Expr2) {
// If Stmt1 has side effects then don't warn even if expressions
// are identical.
if (!IgnoreSideEffects && Expr1->HasSideEffects(Ctx) &&
Expr2->HasSideEffects(Ctx))
return false;
// If either expression comes from a macro then don't warn even if
// the expressions are identical.
if ((Expr1->getExprLoc().isMacroID()) || (Expr2->getExprLoc().isMacroID()))
return false;

// If all children of two expressions are identical, return true.
Expr::const_child_iterator I1 = Expr1->child_begin();
Expr::const_child_iterator I2 = Expr2->child_begin();
while (I1 != Expr1->child_end() && I2 != Expr2->child_end()) {
if (!isIdenticalStmt(Ctx, *I1, *I2, IgnoreSideEffects))
return false;
++I1;
++I2;
}
// If there are different number of children in the statements, return
// false.
if (I1 != Expr1->child_end())
return false;
if (I2 != Expr2->child_end())
return false;
}

switch (Stmt1->getStmtClass()) {
default:
return false;
case Stmt::CallExprClass:
case Stmt::ArraySubscriptExprClass:
case Stmt::ArraySectionExprClass:
case Stmt::OMPArrayShapingExprClass:
case Stmt::OMPIteratorExprClass:
case Stmt::ImplicitCastExprClass:
case Stmt::ParenExprClass:
case Stmt::BreakStmtClass:
case Stmt::ContinueStmtClass:
case Stmt::NullStmtClass:
return true;
case Stmt::CStyleCastExprClass: {
const auto *CastExpr1 = cast<CStyleCastExpr>(Stmt1);
const auto *CastExpr2 = cast<CStyleCastExpr>(Stmt2);

return CastExpr1->getTypeAsWritten() == CastExpr2->getTypeAsWritten();
}
case Stmt::ReturnStmtClass: {
const auto *ReturnStmt1 = cast<ReturnStmt>(Stmt1);
const auto *ReturnStmt2 = cast<ReturnStmt>(Stmt2);

return isIdenticalStmt(Ctx, ReturnStmt1->getRetValue(),
ReturnStmt2->getRetValue(), IgnoreSideEffects);
}
case Stmt::ForStmtClass: {
const auto *ForStmt1 = cast<ForStmt>(Stmt1);
const auto *ForStmt2 = cast<ForStmt>(Stmt2);

if (!isIdenticalStmt(Ctx, ForStmt1->getInit(), ForStmt2->getInit(),
IgnoreSideEffects))
return false;
if (!isIdenticalStmt(Ctx, ForStmt1->getCond(), ForStmt2->getCond(),
IgnoreSideEffects))
return false;
if (!isIdenticalStmt(Ctx, ForStmt1->getInc(), ForStmt2->getInc(),
IgnoreSideEffects))
return false;
if (!isIdenticalStmt(Ctx, ForStmt1->getBody(), ForStmt2->getBody(),
IgnoreSideEffects))
return false;
return true;
}
case Stmt::DoStmtClass: {
const auto *DStmt1 = cast<DoStmt>(Stmt1);
const auto *DStmt2 = cast<DoStmt>(Stmt2);

if (!isIdenticalStmt(Ctx, DStmt1->getCond(), DStmt2->getCond(),
IgnoreSideEffects))
return false;
if (!isIdenticalStmt(Ctx, DStmt1->getBody(), DStmt2->getBody(),
IgnoreSideEffects))
return false;
return true;
}
case Stmt::WhileStmtClass: {
const auto *WStmt1 = cast<WhileStmt>(Stmt1);
const auto *WStmt2 = cast<WhileStmt>(Stmt2);

if (!isIdenticalStmt(Ctx, WStmt1->getCond(), WStmt2->getCond(),
IgnoreSideEffects))
return false;
if (!isIdenticalStmt(Ctx, WStmt1->getBody(), WStmt2->getBody(),
IgnoreSideEffects))
return false;
return true;
}
case Stmt::IfStmtClass: {
const auto *IStmt1 = cast<IfStmt>(Stmt1);
const auto *IStmt2 = cast<IfStmt>(Stmt2);

if (!isIdenticalStmt(Ctx, IStmt1->getCond(), IStmt2->getCond(),
IgnoreSideEffects))
return false;
if (!isIdenticalStmt(Ctx, IStmt1->getThen(), IStmt2->getThen(),
IgnoreSideEffects))
return false;
if (!isIdenticalStmt(Ctx, IStmt1->getElse(), IStmt2->getElse(),
IgnoreSideEffects))
return false;
return true;
}
case Stmt::CompoundStmtClass: {
const auto *CompStmt1 = cast<CompoundStmt>(Stmt1);
const auto *CompStmt2 = cast<CompoundStmt>(Stmt2);

if (CompStmt1->size() != CompStmt2->size())
return false;

if (!llvm::all_of(llvm::zip(CompStmt1->body(), CompStmt2->body()),
[&Ctx, IgnoreSideEffects](
std::tuple<const Stmt *, const Stmt *> stmtPair) {
const Stmt *stmt0 = std::get<0>(stmtPair);
const Stmt *stmt1 = std::get<1>(stmtPair);
return isIdenticalStmt(Ctx, stmt0, stmt1,
IgnoreSideEffects);
})) {
return false;
}

return true;
}
case Stmt::CompoundAssignOperatorClass:
case Stmt::BinaryOperatorClass: {
const auto *BinOp1 = cast<BinaryOperator>(Stmt1);
const auto *BinOp2 = cast<BinaryOperator>(Stmt2);
return BinOp1->getOpcode() == BinOp2->getOpcode();
}
case Stmt::CharacterLiteralClass: {
const auto *CharLit1 = cast<CharacterLiteral>(Stmt1);
const auto *CharLit2 = cast<CharacterLiteral>(Stmt2);
return CharLit1->getValue() == CharLit2->getValue();
}
case Stmt::DeclRefExprClass: {
const auto *DeclRef1 = cast<DeclRefExpr>(Stmt1);
const auto *DeclRef2 = cast<DeclRefExpr>(Stmt2);
return DeclRef1->getDecl() == DeclRef2->getDecl();
}
case Stmt::IntegerLiteralClass: {
const auto *IntLit1 = cast<IntegerLiteral>(Stmt1);
const auto *IntLit2 = cast<IntegerLiteral>(Stmt2);

llvm::APInt I1 = IntLit1->getValue();
llvm::APInt I2 = IntLit2->getValue();
if (I1.getBitWidth() != I2.getBitWidth())
return false;
return I1 == I2;
}
case Stmt::FloatingLiteralClass: {
const auto *FloatLit1 = cast<FloatingLiteral>(Stmt1);
const auto *FloatLit2 = cast<FloatingLiteral>(Stmt2);
return FloatLit1->getValue().bitwiseIsEqual(FloatLit2->getValue());
}
case Stmt::StringLiteralClass: {
const auto *StringLit1 = cast<StringLiteral>(Stmt1);
const auto *StringLit2 = cast<StringLiteral>(Stmt2);
return StringLit1->getBytes() == StringLit2->getBytes();
}
case Stmt::MemberExprClass: {
const auto *MemberStmt1 = cast<MemberExpr>(Stmt1);
const auto *MemberStmt2 = cast<MemberExpr>(Stmt2);
return MemberStmt1->getMemberDecl() == MemberStmt2->getMemberDecl();
}
case Stmt::UnaryOperatorClass: {
const auto *UnaryOp1 = cast<UnaryOperator>(Stmt1);
const auto *UnaryOp2 = cast<UnaryOperator>(Stmt2);
return UnaryOp1->getOpcode() == UnaryOp2->getOpcode();
}
}
}

void BranchCloneCheck::check(const MatchFinder::MatchResult &Result) {
Expand Down Expand Up @@ -269,6 +475,21 @@ void BranchCloneCheck::check(const MatchFinder::MatchResult &Result) {
return;
}

if (const auto *IS = Result.Nodes.getNodeAs<IfStmt>("ifWithDescendantIf")) {
const Stmt *Then = IS->getThen();
auto CS = dyn_cast<CompoundStmt>(Then);
if (CS && (!CS->body_empty())) {
const auto *InnerIf = dyn_cast<IfStmt>(*CS->body_begin());
if (InnerIf && isIdenticalStmt(Context, IS->getCond(), InnerIf->getCond(),
/*IgnoreSideEffects=*/false)) {
diag(IS->getBeginLoc(), "if with identical inner if statement");
diag(InnerIf->getBeginLoc(), "inner if starts here",
DiagnosticIDs::Note);
}
}
return;
}

llvm_unreachable("No if statement and no switch statement.");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ namespace {

AST_MATCHER_P(FunctionDecl, isEnabled, llvm::StringSet<>,
FunctionsThatShouldNotThrow) {
return FunctionsThatShouldNotThrow.count(Node.getNameAsString()) > 0;
return FunctionsThatShouldNotThrow.contains(Node.getNameAsString());
}

AST_MATCHER(FunctionDecl, isExplicitThrow) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,7 @@ void InfiniteLoopCheck::check(const MatchFinder::MatchResult &Result) {
}
}

if (ExprMutationAnalyzer::isUnevaluated(LoopStmt, *LoopStmt, *Result.Context))
if (ExprMutationAnalyzer::isUnevaluated(LoopStmt, *Result.Context))
return;

if (isAtLeastOneCondVarChanged(Func, LoopStmt, Cond, Result.Context))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
//===----------------------------------------------------------------------===//

#include "ReturnConstRefFromParameterCheck.h"
#include "clang/AST/Attrs.inc"
#include "clang/AST/Expr.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/ASTMatchers/ASTMatchers.h"
Expand All @@ -15,17 +16,28 @@ using namespace clang::ast_matchers;

namespace clang::tidy::bugprone {

namespace {

AST_MATCHER(ParmVarDecl, hasLifetimeBoundAttr) {
return Node.hasAttr<LifetimeBoundAttr>();
}

} // namespace

void ReturnConstRefFromParameterCheck::registerMatchers(MatchFinder *Finder) {
const auto DRef = ignoringParens(
declRefExpr(
to(parmVarDecl(hasType(hasCanonicalType(
qualType(lValueReferenceType(pointee(
qualType(isConstQualified()))))
.bind("type"))))
.bind("type"))),
hasDeclContext(functionDecl().bind("owner")),
unless(hasLifetimeBoundAttr()))
.bind("param")))
.bind("dref"));
const auto Func =
functionDecl(hasReturnTypeLoc(loc(
functionDecl(equalsBoundNode("owner"),
hasReturnTypeLoc(loc(
qualType(hasCanonicalType(equalsBoundNode("type"))))))
.bind("func");

Expand Down
9 changes: 5 additions & 4 deletions clang-tools-extra/clang-tidy/bugprone/UseAfterMoveCheck.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -315,9 +315,10 @@ void UseAfterMoveFinder::getReinits(
"::std::unordered_map", "::std::unordered_multiset",
"::std::unordered_multimap"))))));

auto StandardSmartPointerTypeMatcher = hasType(hasUnqualifiedDesugaredType(
recordType(hasDeclaration(cxxRecordDecl(hasAnyName(
"::std::unique_ptr", "::std::shared_ptr", "::std::weak_ptr"))))));
auto StandardResettableOwnerTypeMatcher = hasType(
hasUnqualifiedDesugaredType(recordType(hasDeclaration(cxxRecordDecl(
hasAnyName("::std::unique_ptr", "::std::shared_ptr",
"::std::weak_ptr", "::std::optional", "::std::any"))))));

// Matches different types of reinitialization.
auto ReinitMatcher =
Expand All @@ -340,7 +341,7 @@ void UseAfterMoveFinder::getReinits(
callee(cxxMethodDecl(hasAnyName("clear", "assign")))),
// reset() on standard smart pointers.
cxxMemberCallExpr(
on(expr(DeclRefMatcher, StandardSmartPointerTypeMatcher)),
on(expr(DeclRefMatcher, StandardResettableOwnerTypeMatcher)),
callee(cxxMethodDecl(hasName("reset")))),
// Methods that have the [[clang::reinitializes]] attribute.
cxxMemberCallExpr(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ static bool checkOverridingFunctionReturnType(const ASTContext *Context,

// The class type D should have the same cv-qualification as or less
// cv-qualification than the class type B.
if (DTy.isMoreQualifiedThan(BTy))
if (DTy.isMoreQualifiedThan(BTy, *Context))
return false;

return true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,79 +13,88 @@
using namespace clang::ast_matchers;

namespace clang::tidy::cppcoreguidelines {
namespace {

AST_MATCHER(FieldDecl, isMemberOfLambda) {
return Node.getParent()->isLambda();
static bool isCopyConstructible(CXXRecordDecl const &Node) {
if (Node.needsOverloadResolutionForCopyConstructor() &&
Node.needsImplicitCopyConstructor()) {
// unresolved
for (CXXBaseSpecifier const &BS : Node.bases()) {
CXXRecordDecl const *BRD = BS.getType()->getAsCXXRecordDecl();
if (BRD != nullptr && !isCopyConstructible(*BRD))
return false;
}
}
if (Node.hasSimpleCopyConstructor())
return true;
for (CXXConstructorDecl const *Ctor : Node.ctors())
if (Ctor->isCopyConstructor())
return !Ctor->isDeleted();
return false;
}

struct MemberFunctionInfo {
bool Declared{};
bool Deleted{};
};

struct MemberFunctionPairInfo {
MemberFunctionInfo Copy{};
MemberFunctionInfo Move{};
};

MemberFunctionPairInfo getConstructorsInfo(CXXRecordDecl const &Node) {
MemberFunctionPairInfo Constructors{};

for (CXXConstructorDecl const *Ctor : Node.ctors()) {
if (Ctor->isCopyConstructor()) {
Constructors.Copy.Declared = true;
if (Ctor->isDeleted())
Constructors.Copy.Deleted = true;
}
if (Ctor->isMoveConstructor()) {
Constructors.Move.Declared = true;
if (Ctor->isDeleted())
Constructors.Move.Deleted = true;
static bool isMoveConstructible(CXXRecordDecl const &Node) {
if (Node.needsOverloadResolutionForMoveConstructor() &&
Node.needsImplicitMoveConstructor()) {
// unresolved
for (CXXBaseSpecifier const &BS : Node.bases()) {
CXXRecordDecl const *BRD = BS.getType()->getAsCXXRecordDecl();
if (BRD != nullptr && !isMoveConstructible(*BRD))
return false;
}
}

return Constructors;
if (Node.hasSimpleMoveConstructor())
return true;
for (CXXConstructorDecl const *Ctor : Node.ctors())
if (Ctor->isMoveConstructor())
return !Ctor->isDeleted();
return false;
}

MemberFunctionPairInfo getAssignmentsInfo(CXXRecordDecl const &Node) {
MemberFunctionPairInfo Assignments{};

for (CXXMethodDecl const *Method : Node.methods()) {
if (Method->isCopyAssignmentOperator()) {
Assignments.Copy.Declared = true;
if (Method->isDeleted())
Assignments.Copy.Deleted = true;
static bool isCopyAssignable(CXXRecordDecl const &Node) {
if (Node.needsOverloadResolutionForCopyAssignment() &&
Node.needsImplicitCopyAssignment()) {
// unresolved
for (CXXBaseSpecifier const &BS : Node.bases()) {
CXXRecordDecl const *BRD = BS.getType()->getAsCXXRecordDecl();
if (BRD != nullptr && !isCopyAssignable(*BRD))
return false;
}
}
if (Node.hasSimpleCopyAssignment())
return true;
for (CXXMethodDecl const *Method : Node.methods())
if (Method->isCopyAssignmentOperator())
return !Method->isDeleted();
return false;
}

if (Method->isMoveAssignmentOperator()) {
Assignments.Move.Declared = true;
if (Method->isDeleted())
Assignments.Move.Deleted = true;
static bool isMoveAssignable(CXXRecordDecl const &Node) {
if (Node.needsOverloadResolutionForMoveAssignment() &&
Node.needsImplicitMoveAssignment()) {
// unresolved
for (CXXBaseSpecifier const &BS : Node.bases()) {
CXXRecordDecl const *BRD = BS.getType()->getAsCXXRecordDecl();
if (BRD != nullptr && !isMoveAssignable(*BRD))
return false;
}
}

return Assignments;
if (Node.hasSimpleMoveAssignment())
return true;
for (CXXMethodDecl const *Method : Node.methods())
if (Method->isMoveAssignmentOperator())
return !Method->isDeleted();
return false;
}

AST_MATCHER(CXXRecordDecl, isCopyableOrMovable) {
MemberFunctionPairInfo Constructors = getConstructorsInfo(Node);
MemberFunctionPairInfo Assignments = getAssignmentsInfo(Node);
namespace {

if (Node.hasSimpleCopyConstructor() ||
(Constructors.Copy.Declared && !Constructors.Copy.Deleted))
return true;
if (Node.hasSimpleMoveConstructor() ||
(Constructors.Move.Declared && !Constructors.Move.Deleted))
return true;
if (Node.hasSimpleCopyAssignment() ||
(Assignments.Copy.Declared && !Assignments.Copy.Deleted))
return true;
if (Node.hasSimpleMoveAssignment() ||
(Assignments.Move.Declared && !Assignments.Move.Deleted))
return true;
AST_MATCHER(FieldDecl, isMemberOfLambda) {
return Node.getParent()->isLambda();
}

return false;
AST_MATCHER(CXXRecordDecl, isCopyableOrMovable) {
return isCopyConstructible(Node) || isMoveConstructible(Node) ||
isCopyAssignable(Node) || isMoveAssignable(Node);
}

} // namespace
Expand Down
70 changes: 54 additions & 16 deletions clang-tools-extra/clang-tidy/misc/RedundantExpressionCheck.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -855,9 +855,6 @@ static bool areExprsMacroAndNonMacro(const Expr *&LhsExpr,
} // namespace

void RedundantExpressionCheck::registerMatchers(MatchFinder *Finder) {
const auto AnyLiteralExpr = ignoringParenImpCasts(
anyOf(cxxBoolLiteral(), characterLiteral(), integerLiteral()));

const auto BannedIntegerLiteral =
integerLiteral(expandedByMacro(KnownBannedMacroNames));
const auto IsInUnevaluatedContext = expr(anyOf(
Expand All @@ -866,19 +863,16 @@ void RedundantExpressionCheck::registerMatchers(MatchFinder *Finder) {
// Binary with equivalent operands, like (X != 2 && X != 2).
Finder->addMatcher(
traverse(TK_AsIs,
binaryOperator(
anyOf(isComparisonOperator(),
hasAnyOperatorName("-", "/", "%", "|", "&", "^", "&&",
"||", "=")),
operandsAreEquivalent(),
// Filter noisy false positives.
unless(isInTemplateInstantiation()),
unless(binaryOperatorIsInMacro()),
unless(hasType(realFloatingPointType())),
unless(hasEitherOperand(hasType(realFloatingPointType()))),
unless(hasLHS(AnyLiteralExpr)),
unless(hasDescendant(BannedIntegerLiteral)),
unless(IsInUnevaluatedContext))
binaryOperator(anyOf(isComparisonOperator(),
hasAnyOperatorName("-", "/", "%", "|", "&",
"^", "&&", "||", "=")),
operandsAreEquivalent(),
// Filter noisy false positives.
unless(isInTemplateInstantiation()),
unless(binaryOperatorIsInMacro()),
unless(hasAncestor(arraySubscriptExpr())),
unless(hasDescendant(BannedIntegerLiteral)),
unless(IsInUnevaluatedContext))
.bind("binary")),
this);

Expand Down Expand Up @@ -1238,6 +1232,50 @@ void RedundantExpressionCheck::check(const MatchFinder::MatchResult &Result) {
if (const auto *BinOp = Result.Nodes.getNodeAs<BinaryOperator>("binary")) {
// If the expression's constants are macros, check whether they are
// intentional.

//
// Special case for floating-point representation.
//
// If expressions on both sides of comparison operator are of type float,
// then for some comparison operators no warning shall be
// reported even if the expressions are identical from a symbolic point of
// view. Comparison between expressions, declared variables and literals
// are treated differently.
//
// != and == between float literals that have the same value should NOT
// warn. < > between float literals that have the same value SHOULD warn.
//
// != and == between the same float declaration should NOT warn.
// < > between the same float declaration SHOULD warn.
//
// != and == between eq. expressions that evaluates into float
// should NOT warn.
// < > between eq. expressions that evaluates into float
// should NOT warn.
//
const Expr *LHS = BinOp->getLHS()->IgnoreParenImpCasts();
const Expr *RHS = BinOp->getRHS()->IgnoreParenImpCasts();
const BinaryOperator::Opcode Op = BinOp->getOpcode();
const bool OpEqualEQorNE = ((Op == BO_EQ) || (Op == BO_NE));

const auto *DeclRef1 = dyn_cast<DeclRefExpr>(LHS);
const auto *DeclRef2 = dyn_cast<DeclRefExpr>(RHS);
const auto *FloatLit1 = dyn_cast<FloatingLiteral>(LHS);
const auto *FloatLit2 = dyn_cast<FloatingLiteral>(RHS);

if (DeclRef1 && DeclRef2 &&
DeclRef1->getType()->hasFloatingRepresentation() &&
DeclRef2->getType()->hasFloatingRepresentation() &&
(DeclRef1->getDecl() == DeclRef2->getDecl()) && OpEqualEQorNE) {
return;
}

if (FloatLit1 && FloatLit2 &&
FloatLit1->getValue().bitwiseIsEqual(FloatLit2->getValue()) &&
OpEqualEQorNE) {
return;
}

if (areSidesBinaryConstExpressions(BinOp, Result.Context)) {
const Expr *LhsConst = nullptr, *RhsConst = nullptr;
BinaryOperatorKind MainOpcode{}, SideOpcode{};
Expand Down
Loading