423 changes: 423 additions & 0 deletions .ci/generate_test_report.py

Large diffs are not rendered by default.

23 changes: 17 additions & 6 deletions .ci/monolithic-linux.sh
Original file line number Diff line number Diff line change
Expand Up @@ -28,26 +28,34 @@ if [[ -n "${CLEAR_CACHE:-}" ]]; then
ccache --clear
fi

function show-stats {
function at-exit {
mkdir -p artifacts
ccache --print-stats > artifacts/ccache_stats.txt

# If building fails there will be no results files.
shopt -s nullglob
python3 "${MONOREPO_ROOT}"/.ci/generate_test_report.py ":linux: Linux x64 Test Results" \
"linux-x64-test-results" "${BUILD_DIR}"/test-results.*.xml
}
trap show-stats EXIT
trap at-exit EXIT

projects="${1}"
targets="${2}"

lit_args="-v --xunit-xml-output ${BUILD_DIR}/test-results.xml --use-unique-output-file-name --timeout=1200 --time-tests"

echo "--- cmake"
pip install -q -r "${MONOREPO_ROOT}"/mlir/python/requirements.txt
pip install -q -r "${MONOREPO_ROOT}"/lldb/test/requirements.txt
pip install -q -r "${MONOREPO_ROOT}"/.ci/requirements.txt
cmake -S "${MONOREPO_ROOT}"/llvm -B "${BUILD_DIR}" \
-D LLVM_ENABLE_PROJECTS="${projects}" \
-G Ninja \
-D CMAKE_BUILD_TYPE=Release \
-D LLVM_ENABLE_ASSERTIONS=ON \
-D LLVM_BUILD_EXAMPLES=ON \
-D COMPILER_RT_BUILD_LIBFUZZER=OFF \
-D LLVM_LIT_ARGS="-v --xunit-xml-output ${BUILD_DIR}/test-results.xml --timeout=1200 --time-tests" \
-D LLVM_LIT_ARGS="${lit_args}" \
-D LLVM_ENABLE_LLD=ON \
-D CMAKE_CXX_FLAGS=-gmlt \
-D LLVM_CCACHE_BUILD=ON \
Expand Down Expand Up @@ -87,7 +95,8 @@ if [[ "${runtimes}" != "" ]]; then
-D CMAKE_BUILD_TYPE=RelWithDebInfo \
-D CMAKE_INSTALL_PREFIX="${INSTALL_DIR}" \
-D LIBCXX_TEST_PARAMS="std=c++03" \
-D LIBCXXABI_TEST_PARAMS="std=c++03"
-D LIBCXXABI_TEST_PARAMS="std=c++03" \
-D LLVM_LIT_ARGS="${lit_args}"

echo "--- ninja runtimes C++03"

Expand All @@ -104,7 +113,8 @@ if [[ "${runtimes}" != "" ]]; then
-D CMAKE_BUILD_TYPE=RelWithDebInfo \
-D CMAKE_INSTALL_PREFIX="${INSTALL_DIR}" \
-D LIBCXX_TEST_PARAMS="std=c++26" \
-D LIBCXXABI_TEST_PARAMS="std=c++26"
-D LIBCXXABI_TEST_PARAMS="std=c++26" \
-D LLVM_LIT_ARGS="${lit_args}"

echo "--- ninja runtimes C++26"

Expand All @@ -121,7 +131,8 @@ if [[ "${runtimes}" != "" ]]; then
-D CMAKE_BUILD_TYPE=RelWithDebInfo \
-D CMAKE_INSTALL_PREFIX="${INSTALL_DIR}" \
-D LIBCXX_TEST_PARAMS="enable_modules=clang" \
-D LIBCXXABI_TEST_PARAMS="enable_modules=clang"
-D LIBCXXABI_TEST_PARAMS="enable_modules=clang" \
-D LLVM_LIT_ARGS="${lit_args}"

echo "--- ninja runtimes clang modules"

Expand Down
12 changes: 9 additions & 3 deletions .ci/monolithic-windows.sh
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,23 @@ if [[ -n "${CLEAR_CACHE:-}" ]]; then
fi

sccache --zero-stats
function show-stats {
function at-exit {
mkdir -p artifacts
sccache --show-stats >> artifacts/sccache_stats.txt

# If building fails there will be no results files.
shopt -s nullglob
python "${MONOREPO_ROOT}"/.ci/generate_test_report.py ":windows: Windows x64 Test Results" \
"windows-x64-test-results" "${BUILD_DIR}"/test-results.*.xml
}
trap show-stats EXIT
trap at-exit EXIT

projects="${1}"
targets="${2}"

echo "--- cmake"
pip install -q -r "${MONOREPO_ROOT}"/mlir/python/requirements.txt
pip install -q -r "${MONOREPO_ROOT}"/.ci/requirements.txt

# The CMAKE_*_LINKER_FLAGS to disable the manifest come from research
# on fixing a build reliability issue on the build server, please
Expand All @@ -53,7 +59,7 @@ cmake -S "${MONOREPO_ROOT}"/llvm -B "${BUILD_DIR}" \
-D LLVM_ENABLE_ASSERTIONS=ON \
-D LLVM_BUILD_EXAMPLES=ON \
-D COMPILER_RT_BUILD_LIBFUZZER=OFF \
-D LLVM_LIT_ARGS="-v --xunit-xml-output ${BUILD_DIR}/test-results.xml --timeout=1200 --time-tests" \
-D LLVM_LIT_ARGS="-v --xunit-xml-output ${BUILD_DIR}/test-results.xml --use-unique-output-file-name --timeout=1200 --time-tests" \
-D COMPILER_RT_BUILD_ORC=OFF \
-D CMAKE_C_COMPILER_LAUNCHER=sccache \
-D CMAKE_CXX_COMPILER_LAUNCHER=sccache \
Expand Down
1 change: 1 addition & 0 deletions .ci/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
junitparser==3.2.0
3 changes: 3 additions & 0 deletions .github/new-issues-labeler.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,6 @@

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

'infra:commit-access-request':
- '/Request Commit Access/'
14 changes: 14 additions & 0 deletions .github/new-prs-labeler.yml
Original file line number Diff line number Diff line change
Expand Up @@ -587,6 +587,12 @@ llvm:ir:
- llvm/docs/LangRef.rst
- llvm/unittests/IR/**

llvm:SandboxIR:
- llvm/lib/SandboxIR/**
- llvm/include/llvm/SandboxIR/**
- llvm/docs/SandboxIR.md
- llvm/unittests/SandboxIR/**

llvm:analysis:
- llvm/lib/Analysis/**
- llvm/include/llvm/Analysis/**
Expand All @@ -605,6 +611,14 @@ llvm:transforms:
- llvm/test/Transforms/**
- llvm/unittests/Transforms/**

llvm:instcombine:
- llvm/lib/Analysis/InstructionSimplify.cpp
- llvm/lib/Transforms/InstCombine/**
- llvm/include/llvm/Transforms/InstCombine/
- llvm/include/llvm/Analysis/InstructionSimplify.h
- llvm/test/Transforms/InstCombine/**
- llvm/test/Transforms/InstSimplify/**

clangd:
- clang-tools-extra/clangd/**

Expand Down
31 changes: 15 additions & 16 deletions .github/workflows/libcxx-build-and-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,11 @@ env:
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 +85,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 +160,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 All @@ -202,13 +201,13 @@ jobs:
matrix:
include:
- config: generic-cxx03
os: macos-latest
os: macos-15
- config: generic-cxx23
os: macos-latest
os: macos-15
- config: generic-modules
os: macos-latest
os: macos-15
- config: apple-configuration
os: macos-latest
os: macos-15
- config: apple-system
os: macos-13
- config: apple-system-hardened
Expand Down
71 changes: 71 additions & 0 deletions .github/workflows/libcxx-build-containers.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# This file defines an action that builds the various Docker images used to run
# libc++ CI whenever modifications to those Docker files are pushed to `main`.
#
# The images are pushed to the LLVM package registry at https://github.com/orgs/llvm/packages
# and tagged appropriately. The selection of which Docker image version is used by the libc++
# CI nodes at any given point is controlled from the workflow files themselves.

name: Build Docker images for libc++ CI

permissions:
contents: read
packages: write

on:
push:
branches:
- main
paths:
- 'libcxx/utils/ci/**'
- '.github/workflows/libcxx-build-containers.yml'
pull_request:
branches:
- main
paths:
- 'libcxx/utils/ci/**'
- '.github/workflows/libcxx-build-containers.yml'

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
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Push the Linux builder image
if: github.event_name == 'push'
working-directory: libcxx/utils/ci
run: |
docker compose push actions-builder
env:
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: ${{ github.sha }}
24 changes: 20 additions & 4 deletions bolt/include/bolt/Core/BinaryContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -723,12 +723,28 @@ class BinaryContext {
/// Stats for stale profile matching:
/// the total number of basic blocks in the profile
uint32_t NumStaleBlocks{0};
/// the number of matched basic blocks
uint32_t NumMatchedBlocks{0};
/// the number of exactly matched basic blocks
uint32_t NumExactMatchedBlocks{0};
/// the number of loosely matched basic blocks
uint32_t NumLooseMatchedBlocks{0};
/// the number of exactly pseudo probe matched basic blocks
uint32_t NumPseudoProbeExactMatchedBlocks{0};
/// the number of loosely pseudo probe matched basic blocks
uint32_t NumPseudoProbeLooseMatchedBlocks{0};
/// the number of call matched basic blocks
uint32_t NumCallMatchedBlocks{0};
/// the total count of samples in the profile
uint64_t StaleSampleCount{0};
/// the count of matched samples
uint64_t MatchedSampleCount{0};
/// the count of exactly matched samples
uint64_t ExactMatchedSampleCount{0};
/// the count of loosely matched samples
uint64_t LooseMatchedSampleCount{0};
/// the count of exactly pseudo probe matched samples
uint64_t PseudoProbeExactMatchedSampleCount{0};
/// the count of loosely pseudo probe matched samples
uint64_t PseudoProbeLooseMatchedSampleCount{0};
/// the count of call matched samples
uint64_t CallMatchedSampleCount{0};
/// the number of stale functions that have matching number of blocks in
/// the profile
uint64_t NumStaleFuncsWithEqualBlockCount{0};
Expand Down
11 changes: 4 additions & 7 deletions bolt/include/bolt/Core/Exceptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,17 +43,14 @@ class CFIReaderWriter {

/// Generate .eh_frame_hdr from old and new .eh_frame sections.
///
/// Take FDEs from the \p NewEHFrame unless their initial_pc is listed
/// in \p FailedAddresses. All other entries are taken from the
/// Take FDEs from the \p NewEHFrame. All other entries are taken from the
/// \p OldEHFrame.
///
/// \p EHFrameHeaderAddress specifies location of .eh_frame_hdr,
/// and is required for relative addressing used in the section.
std::vector<char>
generateEHFrameHeader(const DWARFDebugFrame &OldEHFrame,
const DWARFDebugFrame &NewEHFrame,
uint64_t EHFrameHeaderAddress,
std::vector<uint64_t> &FailedAddresses) const;
std::vector<char> generateEHFrameHeader(const DWARFDebugFrame &OldEHFrame,
const DWARFDebugFrame &NewEHFrame,
uint64_t EHFrameHeaderAddress) const;

using FDEsMap = std::map<uint64_t, const dwarf::FDE *>;

Expand Down
8 changes: 8 additions & 0 deletions bolt/include/bolt/Core/MCPlusBuilder.h
Original file line number Diff line number Diff line change
Expand Up @@ -1536,6 +1536,14 @@ class MCPlusBuilder {
llvm_unreachable("not implemented");
}

/// Match function \p BF to a long veneer for absolute code. Return true if
/// the match was successful and populate \p TargetAddress with an address of
/// the function veneer jumps to.
virtual bool matchAbsLongVeneer(const BinaryFunction &BF,
uint64_t &TargetAddress) const {
llvm_unreachable("not implemented");
}

virtual bool matchAdrpAddPair(const MCInst &Adrp, const MCInst &Add) const {
llvm_unreachable("not implemented");
return false;
Expand Down
3 changes: 3 additions & 0 deletions bolt/include/bolt/Profile/ProfileYAMLMapping.h
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,9 @@ struct InlineTreeNode {
uint32_t CallSiteProbe;
// Index in PseudoProbeDesc.GUID, UINT32_MAX for same as previous (omitted)
uint32_t GUIDIndex;
// Decoded contents, ParentIndexDelta becomes absolute value.
uint64_t GUID;
uint64_t Hash;
bool operator==(const InlineTreeNode &) const { return false; }
};
} // end namespace bolt
Expand Down
74 changes: 73 additions & 1 deletion bolt/include/bolt/Profile/YAMLProfileReader.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
#include <unordered_set>

namespace llvm {
class MCDecodedPseudoProbeInlineTree;

namespace bolt {

class YAMLProfileReader : public ProfileReaderBase {
Expand Down Expand Up @@ -43,6 +45,9 @@ class YAMLProfileReader : public ProfileReaderBase {
using ProfileLookupMap =
DenseMap<uint32_t, yaml::bolt::BinaryFunctionProfile *>;

using GUIDInlineTreeMap =
std::unordered_map<uint64_t, const MCDecodedPseudoProbeInlineTree *>;

/// A class for matching binary functions in functions in the YAML profile.
/// First, a call graph is constructed for both profiled and binary functions.
/// Then functions are hashed based on the names of their callee/caller
Expand Down Expand Up @@ -96,6 +101,61 @@ class YAMLProfileReader : public ProfileReaderBase {
YamlBFAdjacencyMap;
};

// A class for matching inline tree nodes between profile and binary.
// Provides the mapping from profile inline tree node id to a
// corresponding binary MCDecodedPseudoProbeInlineTree node.
//
// The whole mapping process is the following:
//
// (profile) (binary)
// | blocks ^
// v |
// yaml::bolt::BinaryBasicBlockProfile ~= FlowBlock
// ||| probes ^ (majority vote)
// v ||| BBPseudoProbeToBlock
// yaml::bolt::PseudoProbeInfo MCDecodedPseudoProbe
// | InlineTreeIndex ^
// v | probe id
// [ profile node id (uint32_t) -> MCDecodedPseudoProbeInlineTree *]
// InlineTreeNodeMapTy
class InlineTreeNodeMapTy {
DenseMap<uint32_t, const MCDecodedPseudoProbeInlineTree *> Map;

void mapInlineTreeNode(uint32_t ProfileNodeIdx,
const MCDecodedPseudoProbeInlineTree *BinaryNode) {
auto Res = Map.try_emplace(ProfileNodeIdx, BinaryNode);
assert(Res.second &&
"Duplicate mapping from profile node index to binary inline tree");
(void)Res;
}

public:
/// Returns matched InlineTree * for a given profile inline_tree_id.
const MCDecodedPseudoProbeInlineTree *
getInlineTreeNode(uint32_t ProfileInlineTreeNodeId) const {
auto It = Map.find(ProfileInlineTreeNodeId);
if (It == Map.end())
return nullptr;
return It->second;
}

// Match up \p YamlInlineTree with binary inline tree rooted at \p Root.
// Return the number of matched nodes.
//
// This function populates the mapping from profile inline tree node id to a
// corresponding binary MCDecodedPseudoProbeInlineTree node.
size_t matchInlineTrees(
const MCPseudoProbeDecoder &Decoder,
const std::vector<yaml::bolt::InlineTreeNode> &YamlInlineTree,
const MCDecodedPseudoProbeInlineTree *Root);
};

// Partial probe matching specification: matched inline tree and corresponding
// BinaryFunctionProfile
using ProbeMatchSpec =
std::pair<InlineTreeNodeMapTy,
std::reference_wrapper<yaml::bolt::BinaryFunctionProfile>>;

private:
/// Adjustments for basic samples profiles (without LBR).
bool NormalizeByInsnCount{false};
Expand Down Expand Up @@ -129,6 +189,13 @@ class YAMLProfileReader : public ProfileReaderBase {
/// BinaryFunction pointers indexed by YamlBP functions.
std::vector<BinaryFunction *> ProfileBFs;

// Pseudo probe function GUID to inline tree node
GUIDInlineTreeMap TopLevelGUIDToInlineTree;

// Mapping from a binary function to its partial match specification
// (YAML profile and its inline tree mapping to binary).
DenseMap<BinaryFunction *, std::vector<ProbeMatchSpec>> BFToProbeMatchSpecs;

/// Populate \p Function profile with the one supplied in YAML format.
bool parseFunctionProfile(BinaryFunction &Function,
const yaml::bolt::BinaryFunctionProfile &YamlBF);
Expand All @@ -139,7 +206,8 @@ class YAMLProfileReader : public ProfileReaderBase {

/// Infer function profile from stale data (collected on older binaries).
bool inferStaleProfile(BinaryFunction &Function,
const yaml::bolt::BinaryFunctionProfile &YamlBF);
const yaml::bolt::BinaryFunctionProfile &YamlBF,
const ArrayRef<ProbeMatchSpec> ProbeMatchSpecs);

/// Initialize maps for profile matching.
void buildNameMaps(BinaryContext &BC);
Expand All @@ -156,6 +224,10 @@ class YAMLProfileReader : public ProfileReaderBase {
/// Matches functions using the call graph.
size_t matchWithCallGraph(BinaryContext &BC);

/// Matches functions using the call graph.
/// Populates BF->partial probe match spec map.
size_t matchWithPseudoProbes(BinaryContext &BC);

/// Matches functions with similarly named profiled functions.
size_t matchWithNameSimilarity(BinaryContext &BC);

Expand Down
8 changes: 0 additions & 8 deletions bolt/include/bolt/Rewrite/RewriteInstance.h
Original file line number Diff line number Diff line change
Expand Up @@ -556,14 +556,6 @@ class RewriteInstance {
return ErrOrSection ? &ErrOrSection.get() : nullptr;
}

/// Keep track of functions we fail to write in the binary. We need to avoid
/// rewriting CFI info for these functions.
std::vector<uint64_t> FailedAddresses;

/// Keep track of which functions didn't fit in their original space in the
/// last emission, so that we may either decide to split or not optimize them.
std::set<uint64_t> LargeFunctions;

/// Section header string table.
StringTableBuilder SHStrTab;

Expand Down
54 changes: 19 additions & 35 deletions bolt/lib/Core/BinaryEmitter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -906,17 +906,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 Down Expand Up @@ -975,36 +964,24 @@ void BinaryEmitter::emitLSDA(BinaryFunction &BF, const FunctionFragment &FF) {

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;

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);
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);
}

// 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 @@ -1020,6 +997,7 @@ void BinaryEmitter::emitLSDA(BinaryFunction &BF, const FunctionFragment &FF) {
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 +1016,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 +1043,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
3 changes: 3 additions & 0 deletions bolt/lib/Core/BinaryFunction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2580,6 +2580,7 @@ struct CFISnapshot {
case MCCFIInstruction::OpNegateRAStateWithPC:
case MCCFIInstruction::OpLLVMDefAspaceCfa:
case MCCFIInstruction::OpLabel:
case MCCFIInstruction::OpValOffset:
llvm_unreachable("unsupported CFI opcode");
break;
case MCCFIInstruction::OpRememberState:
Expand Down Expand Up @@ -2719,6 +2720,7 @@ struct CFISnapshotDiff : public CFISnapshot {
case MCCFIInstruction::OpNegateRAStateWithPC:
case MCCFIInstruction::OpLLVMDefAspaceCfa:
case MCCFIInstruction::OpLabel:
case MCCFIInstruction::OpValOffset:
llvm_unreachable("unsupported CFI opcode");
return false;
case MCCFIInstruction::OpRememberState:
Expand Down Expand Up @@ -2869,6 +2871,7 @@ BinaryFunction::unwindCFIState(int32_t FromState, int32_t ToState,
case MCCFIInstruction::OpNegateRAStateWithPC:
case MCCFIInstruction::OpLLVMDefAspaceCfa:
case MCCFIInstruction::OpLabel:
case MCCFIInstruction::OpValOffset:
llvm_unreachable("unsupported CFI opcode");
break;
case MCCFIInstruction::OpGnuArgsSize:
Expand Down
22 changes: 6 additions & 16 deletions bolt/lib/Core/Exceptions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,7 @@ Error BinaryFunction::parseLSDA(ArrayRef<uint8_t> LSDASectionData,
DWARFDataExtractor Data(
StringRef(reinterpret_cast<const char *>(LSDASectionData.data()),
LSDASectionData.size()),
BC.DwCtx->getDWARFObj().isLittleEndian(),
BC.DwCtx->getDWARFObj().getAddressSize());
BC.AsmInfo->isLittleEndian(), BC.AsmInfo->getCodePointerSize());
uint64_t Offset = getLSDAAddress() - LSDASectionAddress;
assert(Data.isValidOffset(Offset) && "wrong LSDA address");

Expand Down Expand Up @@ -666,16 +665,13 @@ bool CFIReaderWriter::fillCFIInfoFor(BinaryFunction &Function) const {
return true;
}

std::vector<char> CFIReaderWriter::generateEHFrameHeader(
const DWARFDebugFrame &OldEHFrame, const DWARFDebugFrame &NewEHFrame,
uint64_t EHFrameHeaderAddress,
std::vector<uint64_t> &FailedAddresses) const {
std::vector<char>
CFIReaderWriter::generateEHFrameHeader(const DWARFDebugFrame &OldEHFrame,
const DWARFDebugFrame &NewEHFrame,
uint64_t EHFrameHeaderAddress) const {
// Common PC -> FDE map to be written into .eh_frame_hdr.
std::map<uint64_t, uint64_t> PCToFDE;

// Presort array for binary search.
llvm::sort(FailedAddresses);

// Initialize PCToFDE using NewEHFrame.
for (dwarf::FrameEntry &Entry : NewEHFrame.entries()) {
const dwarf::FDE *FDE = dyn_cast<dwarf::FDE>(&Entry);
Expand All @@ -690,13 +686,7 @@ std::vector<char> CFIReaderWriter::generateEHFrameHeader(
continue;

// Add the address to the map unless we failed to write it.
if (!std::binary_search(FailedAddresses.begin(), FailedAddresses.end(),
FuncAddress)) {
LLVM_DEBUG(dbgs() << "BOLT-DEBUG: FDE for function at 0x"
<< Twine::utohexstr(FuncAddress) << " is at 0x"
<< Twine::utohexstr(FDEAddress) << '\n');
PCToFDE[FuncAddress] = FDEAddress;
}
PCToFDE[FuncAddress] = FDEAddress;
};

LLVM_DEBUG(dbgs() << "BOLT-DEBUG: new .eh_frame contains "
Expand Down
60 changes: 53 additions & 7 deletions bolt/lib/Passes/BinaryPasses.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -588,10 +588,18 @@ Error CheckLargeFunctions::runOnFunctions(BinaryContext &BC) {
uint64_t HotSize, ColdSize;
std::tie(HotSize, ColdSize) =
BC.calculateEmittedSize(BF, /*FixBranches=*/false);
if (HotSize > BF.getMaxSize()) {
uint64_t MainFragmentSize = HotSize;
if (BF.hasIslandsInfo()) {
MainFragmentSize +=
offsetToAlignment(BF.getAddress() + MainFragmentSize,
Align(BF.getConstantIslandAlignment()));
MainFragmentSize += BF.estimateConstantIslandSize();
}
if (MainFragmentSize > BF.getMaxSize()) {
if (opts::PrintLargeFunctions)
BC.outs() << "BOLT-INFO: " << BF << " size exceeds allocated space by "
<< (HotSize - BF.getMaxSize()) << " bytes\n";
BC.outs() << "BOLT-INFO: " << BF << " size of " << MainFragmentSize
<< " bytes exceeds allocated space by "
<< (MainFragmentSize - BF.getMaxSize()) << " bytes\n";
BF.setSimple(false);
}
};
Expand Down Expand Up @@ -1549,10 +1557,48 @@ Error PrintProgramStats::runOnFunctions(BinaryContext &BC) {
"BOLT-INFO: inference found an exact match for %.2f%% of basic blocks"
" (%zu out of %zu stale) responsible for %.2f%% samples"
" (%zu out of %zu stale)\n",
100.0 * BC.Stats.NumMatchedBlocks / BC.Stats.NumStaleBlocks,
BC.Stats.NumMatchedBlocks, BC.Stats.NumStaleBlocks,
100.0 * BC.Stats.MatchedSampleCount / BC.Stats.StaleSampleCount,
BC.Stats.MatchedSampleCount, BC.Stats.StaleSampleCount);
100.0 * BC.Stats.NumExactMatchedBlocks / BC.Stats.NumStaleBlocks,
BC.Stats.NumExactMatchedBlocks, BC.Stats.NumStaleBlocks,
100.0 * BC.Stats.ExactMatchedSampleCount / BC.Stats.StaleSampleCount,
BC.Stats.ExactMatchedSampleCount, BC.Stats.StaleSampleCount);
BC.outs() << format(
"BOLT-INFO: inference found an exact pseudo probe match for %.2f%% of "
"basic blocks (%zu out of %zu stale) responsible for %.2f%% samples"
" (%zu out of %zu stale)\n",
100.0 * BC.Stats.NumPseudoProbeExactMatchedBlocks /
BC.Stats.NumStaleBlocks,
BC.Stats.NumPseudoProbeExactMatchedBlocks, BC.Stats.NumStaleBlocks,
100.0 * BC.Stats.PseudoProbeExactMatchedSampleCount /
BC.Stats.StaleSampleCount,
BC.Stats.PseudoProbeExactMatchedSampleCount, BC.Stats.StaleSampleCount);
BC.outs() << format(
"BOLT-INFO: inference found a loose pseudo probe match for %.2f%% of "
"basic blocks (%zu out of %zu stale) responsible for %.2f%% samples"
" (%zu out of %zu stale)\n",
100.0 * BC.Stats.NumPseudoProbeLooseMatchedBlocks /
BC.Stats.NumStaleBlocks,
BC.Stats.NumPseudoProbeLooseMatchedBlocks, BC.Stats.NumStaleBlocks,
100.0 * BC.Stats.PseudoProbeLooseMatchedSampleCount /
BC.Stats.StaleSampleCount,
BC.Stats.PseudoProbeLooseMatchedSampleCount, BC.Stats.StaleSampleCount);
BC.outs() << format(
"BOLT-INFO: inference found a call match for %.2f%% of basic "
"blocks"
" (%zu out of %zu stale) responsible for %.2f%% samples"
" (%zu out of %zu stale)\n",
100.0 * BC.Stats.NumCallMatchedBlocks / BC.Stats.NumStaleBlocks,
BC.Stats.NumCallMatchedBlocks, BC.Stats.NumStaleBlocks,
100.0 * BC.Stats.CallMatchedSampleCount / BC.Stats.StaleSampleCount,
BC.Stats.CallMatchedSampleCount, BC.Stats.StaleSampleCount);
BC.outs() << format(
"BOLT-INFO: inference found a loose match for %.2f%% of basic "
"blocks"
" (%zu out of %zu stale) responsible for %.2f%% samples"
" (%zu out of %zu stale)\n",
100.0 * BC.Stats.NumLooseMatchedBlocks / BC.Stats.NumStaleBlocks,
BC.Stats.NumLooseMatchedBlocks, BC.Stats.NumStaleBlocks,
100.0 * BC.Stats.LooseMatchedSampleCount / BC.Stats.StaleSampleCount,
BC.Stats.LooseMatchedSampleCount, BC.Stats.StaleSampleCount);
}

if (const uint64_t NumUnusedObjects = BC.getNumUnusedProfiledObjects()) {
Expand Down
48 changes: 32 additions & 16 deletions bolt/lib/Passes/VeneerElimination.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,30 +29,47 @@ static llvm::cl::opt<bool>
namespace llvm {
namespace bolt {

static bool isPossibleVeneer(const BinaryFunction &BF) {
return BF.isAArch64Veneer() || BF.getOneName().starts_with("__AArch64");
}

Error VeneerElimination::runOnFunctions(BinaryContext &BC) {
if (!opts::EliminateVeneers || !BC.isAArch64())
return Error::success();

std::map<uint64_t, BinaryFunction> &BFs = BC.getBinaryFunctions();
std::unordered_map<const MCSymbol *, const MCSymbol *> VeneerDestinations;
uint64_t VeneersCount = 0;
for (auto &It : BFs) {
BinaryFunction &VeneerFunction = It.second;
if (!VeneerFunction.isAArch64Veneer())
uint64_t NumEliminatedVeneers = 0;
for (BinaryFunction &BF : llvm::make_second_range(BC.getBinaryFunctions())) {
if (!isPossibleVeneer(BF))
continue;

VeneersCount++;
VeneerFunction.setPseudo(true);
MCInst &FirstInstruction = *(VeneerFunction.begin()->begin());
const MCSymbol *VeneerTargetSymbol =
BC.MIB->getTargetSymbol(FirstInstruction, 1);
assert(VeneerTargetSymbol && "Expecting target symbol for instruction");
for (const MCSymbol *Symbol : VeneerFunction.getSymbols())
if (BF.isIgnored())
continue;

const MCSymbol *VeneerTargetSymbol = 0;
uint64_t TargetAddress;
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);
}

if (!VeneerTargetSymbol)
continue;

for (const MCSymbol *Symbol : BF.getSymbols())
VeneerDestinations[Symbol] = VeneerTargetSymbol;

NumEliminatedVeneers++;
BF.setPseudo(true);
}

BC.outs() << "BOLT-INFO: number of removed linker-inserted veneers: "
<< VeneersCount << "\n";
<< NumEliminatedVeneers << '\n';

// Handle veneers to veneers in case they occur
for (auto &Entry : VeneerDestinations) {
Expand All @@ -65,9 +82,8 @@ Error VeneerElimination::runOnFunctions(BinaryContext &BC) {
}

uint64_t VeneerCallers = 0;
for (auto &It : BFs) {
BinaryFunction &Function = It.second;
for (BinaryBasicBlock &BB : Function) {
for (BinaryFunction &BF : llvm::make_second_range(BC.getBinaryFunctions())) {
for (BinaryBasicBlock &BB : BF) {
for (MCInst &Instr : BB) {
if (!BC.MIB->isCall(Instr) || BC.MIB->isIndirectCall(Instr))
continue;
Expand Down
353 changes: 274 additions & 79 deletions bolt/lib/Profile/StaleProfileMatching.cpp

Large diffs are not rendered by default.

118 changes: 116 additions & 2 deletions bolt/lib/Profile/YAMLProfileReader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/edit_distance.h"
#include "llvm/Demangle/Demangle.h"
#include "llvm/MC/MCPseudoProbe.h"
#include "llvm/Support/CommandLine.h"

using namespace llvm;
Expand Down Expand Up @@ -49,6 +50,8 @@ llvm::cl::opt<bool>
llvm::cl::opt<bool> ProfileUseDFS("profile-use-dfs",
cl::desc("use DFS order for YAML profile"),
cl::Hidden, cl::cat(BoltOptCategory));

extern llvm::cl::opt<bool> StaleMatchingWithPseudoProbes;
} // namespace opts

namespace llvm {
Expand Down Expand Up @@ -349,8 +352,13 @@ bool YAMLProfileReader::parseFunctionProfile(
if (YamlBF.NumBasicBlocks != BF.size())
++BC.Stats.NumStaleFuncsWithEqualBlockCount;

if (opts::InferStaleProfile && inferStaleProfile(BF, YamlBF))
ProfileMatched = true;
if (!opts::InferStaleProfile)
return false;
ArrayRef<ProbeMatchSpec> ProbeMatchSpecs;
auto BFIt = BFToProbeMatchSpecs.find(&BF);
if (BFIt != BFToProbeMatchSpecs.end())
ProbeMatchSpecs = BFIt->second;
ProfileMatched = inferStaleProfile(BF, YamlBF, ProbeMatchSpecs);
}
if (ProfileMatched)
BF.markProfiled(YamlBP.Header.Flags);
Expand Down Expand Up @@ -585,6 +593,101 @@ size_t YAMLProfileReader::matchWithCallGraph(BinaryContext &BC) {
return MatchedWithCallGraph;
}

size_t YAMLProfileReader::InlineTreeNodeMapTy::matchInlineTrees(
const MCPseudoProbeDecoder &Decoder,
const std::vector<yaml::bolt::InlineTreeNode> &DecodedInlineTree,
const MCDecodedPseudoProbeInlineTree *Root) {
// Match inline tree nodes by GUID, checksum, parent, and call site.
for (const auto &[InlineTreeNodeId, InlineTreeNode] :
llvm::enumerate(DecodedInlineTree)) {
uint64_t GUID = InlineTreeNode.GUID;
uint64_t Hash = InlineTreeNode.Hash;
uint32_t ParentId = InlineTreeNode.ParentIndexDelta;
uint32_t CallSiteProbe = InlineTreeNode.CallSiteProbe;
const MCDecodedPseudoProbeInlineTree *Cur = nullptr;
if (!InlineTreeNodeId) {
Cur = Root;
} else if (const MCDecodedPseudoProbeInlineTree *Parent =
getInlineTreeNode(ParentId)) {
for (const MCDecodedPseudoProbeInlineTree &Child :
Parent->getChildren()) {
if (Child.Guid == GUID) {
if (std::get<1>(Child.getInlineSite()) == CallSiteProbe)
Cur = &Child;
break;
}
}
}
// Don't match nodes if the profile is stale (mismatching binary FuncHash
// and YAML Hash)
if (Cur && Decoder.getFuncDescForGUID(Cur->Guid)->FuncHash == Hash)
mapInlineTreeNode(InlineTreeNodeId, Cur);
}
return Map.size();
}

// Decode index deltas and indirection through \p YamlPD. Return modified copy
// of \p YamlInlineTree with populated decoded fields (GUID, Hash, ParentIndex).
static std::vector<yaml::bolt::InlineTreeNode>
decodeYamlInlineTree(const yaml::bolt::ProfilePseudoProbeDesc &YamlPD,
std::vector<yaml::bolt::InlineTreeNode> YamlInlineTree) {
uint32_t ParentId = 0;
uint32_t PrevGUIDIdx = 0;
for (yaml::bolt::InlineTreeNode &InlineTreeNode : YamlInlineTree) {
uint32_t GUIDIdx = InlineTreeNode.GUIDIndex;
if (GUIDIdx != UINT32_MAX)
PrevGUIDIdx = GUIDIdx;
else
GUIDIdx = PrevGUIDIdx;
uint32_t HashIdx = YamlPD.GUIDHashIdx[GUIDIdx];
ParentId += InlineTreeNode.ParentIndexDelta;
InlineTreeNode.GUID = YamlPD.GUID[GUIDIdx];
InlineTreeNode.Hash = YamlPD.Hash[HashIdx];
InlineTreeNode.ParentIndexDelta = ParentId;
}
return YamlInlineTree;
}

size_t YAMLProfileReader::matchWithPseudoProbes(BinaryContext &BC) {
if (!opts::StaleMatchingWithPseudoProbes)
return 0;

const MCPseudoProbeDecoder *Decoder = BC.getPseudoProbeDecoder();
const yaml::bolt::ProfilePseudoProbeDesc &YamlPD = YamlBP.PseudoProbeDesc;

// Set existing BF->YamlBF match into ProbeMatchSpecs for (local) probe
// matching.
assert(Decoder &&
"If pseudo probes are in use, pseudo probe decoder should exist");
for (auto [YamlBF, BF] : llvm::zip_equal(YamlBP.Functions, ProfileBFs)) {
// BF is preliminary name-matched function to YamlBF
// MatchedBF is final matched function
BinaryFunction *MatchedBF = YamlProfileToFunction.lookup(YamlBF.Id);
if (!BF)
BF = MatchedBF;
if (!BF)
continue;
uint64_t GUID = BF->getGUID();
if (!GUID)
continue;
auto It = TopLevelGUIDToInlineTree.find(GUID);
if (It == TopLevelGUIDToInlineTree.end())
continue;
const MCDecodedPseudoProbeInlineTree *Node = It->second;
assert(Node && "Malformed TopLevelGUIDToInlineTree");
auto &MatchSpecs = BFToProbeMatchSpecs[BF];
auto &InlineTreeMap =
MatchSpecs.emplace_back(InlineTreeNodeMapTy(), YamlBF).first;
std::vector<yaml::bolt::InlineTreeNode> ProfileInlineTree =
decodeYamlInlineTree(YamlPD, YamlBF.InlineTree);
// Erase unsuccessful match
if (!InlineTreeMap.matchInlineTrees(*Decoder, ProfileInlineTree, Node))
MatchSpecs.pop_back();
}

return 0;
}

size_t YAMLProfileReader::matchWithNameSimilarity(BinaryContext &BC) {
if (opts::NameSimilarityFunctionMatchingThreshold == 0)
return 0;
Expand Down Expand Up @@ -716,6 +819,15 @@ Error YAMLProfileReader::readProfile(BinaryContext &BC) {
}
}

if (opts::StaleMatchingWithPseudoProbes) {
const MCPseudoProbeDecoder *Decoder = BC.getPseudoProbeDecoder();
assert(Decoder &&
"If pseudo probes are in use, pseudo probe decoder should exist");
for (const MCDecodedPseudoProbeInlineTree &TopLev :
Decoder->getDummyInlineRoot().getChildren())
TopLevelGUIDToInlineTree[TopLev.Guid] = &TopLev;
}

// Map profiled function ids to names.
for (yaml::bolt::BinaryFunctionProfile &YamlBF : YamlBP.Functions)
IdToYamLBF[YamlBF.Id] = &YamlBF;
Expand All @@ -725,6 +837,8 @@ Error YAMLProfileReader::readProfile(BinaryContext &BC) {
const size_t MatchedWithLTOCommonName = matchWithLTOCommonName();
const size_t MatchedWithCallGraph = matchWithCallGraph(BC);
const size_t MatchedWithNameSimilarity = matchWithNameSimilarity(BC);
[[maybe_unused]] const size_t MatchedWithPseudoProbes =
matchWithPseudoProbes(BC);

for (auto [YamlBF, BF] : llvm::zip_equal(YamlBP.Functions, ProfileBFs))
if (!YamlBF.Used && BF && !ProfiledFunctions.count(BF))
Expand Down
2 changes: 1 addition & 1 deletion bolt/lib/Profile/YAMLProfileWriter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ YAMLProfileWriter::convertBFInlineTree(const MCPseudoProbeDecoder &Decoder,
else
PrevGUIDIdx = GUIDIdx;
YamlInlineTree.emplace_back(yaml::bolt::InlineTreeNode{
Node.ParentId - PrevParent, Node.InlineSite, GUIDIdx});
Node.ParentId - PrevParent, Node.InlineSite, GUIDIdx, 0, 0});
PrevParent = Node.ParentId;
}
return {YamlInlineTree, InlineTreeNodeId};
Expand Down
7 changes: 4 additions & 3 deletions bolt/lib/Rewrite/PseudoProbeRewriter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ static cl::opt<PrintPseudoProbesOptions> PrintPseudoProbes(
cl::Hidden, cl::cat(BoltCategory));

extern cl::opt<bool> ProfileWritePseudoProbes;
extern cl::opt<bool> StaleMatchingWithPseudoProbes;
} // namespace opts

namespace {
Expand Down Expand Up @@ -92,14 +93,14 @@ class PseudoProbeRewriter final : public MetadataRewriter {
};

Error PseudoProbeRewriter::preCFGInitializer() {
if (opts::ProfileWritePseudoProbes)
parsePseudoProbe(true);
if (opts::ProfileWritePseudoProbes || opts::StaleMatchingWithPseudoProbes)
parsePseudoProbe(opts::ProfileWritePseudoProbes);

return Error::success();
}

Error PseudoProbeRewriter::postEmitFinalizer() {
if (!opts::ProfileWritePseudoProbes)
if (!opts::StaleMatchingWithPseudoProbes)
parsePseudoProbe();
updatePseudoProbes();

Expand Down
109 changes: 58 additions & 51 deletions bolt/lib/Rewrite/RewriteInstance.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3807,7 +3807,6 @@ void RewriteInstance::mapCodeSections(BOLTLinker::SectionMapper MapSection) {
if (!Function.isEmitted())
continue;

bool TooLarge = false;
ErrorOr<BinarySection &> FuncSection = Function.getCodeSection();
assert(FuncSection && "cannot find section for function");
FuncSection->setOutputAddress(Function.getAddress());
Expand All @@ -3818,11 +3817,8 @@ void RewriteInstance::mapCodeSections(BOLTLinker::SectionMapper MapSection) {
MapSection(*FuncSection, Function.getAddress());
Function.setImageAddress(FuncSection->getAllocAddress());
Function.setImageSize(FuncSection->getOutputSize());
if (Function.getImageSize() > Function.getMaxSize()) {
assert(!BC->isX86() && "Unexpected large function.");
TooLarge = true;
FailedAddresses.emplace_back(Function.getAddress());
}
assert(Function.getImageSize() <= Function.getMaxSize() &&
"Unexpected large function");

// Map jump tables if updating in-place.
if (opts::JumpTables == JTS_BASIC) {
Expand Down Expand Up @@ -3852,29 +3848,18 @@ void RewriteInstance::mapCodeSections(BOLTLinker::SectionMapper MapSection) {
assert(ColdSection && "cannot find section for cold part");
// Cold fragments are aligned at 16 bytes.
NextAvailableAddress = alignTo(NextAvailableAddress, 16);
if (TooLarge) {
// The corresponding FDE will refer to address 0.
FF.setAddress(0);
FF.setImageAddress(0);
FF.setImageSize(0);
FF.setFileOffset(0);
} else {
FF.setAddress(NextAvailableAddress);
FF.setImageAddress(ColdSection->getAllocAddress());
FF.setImageSize(ColdSection->getOutputSize());
FF.setFileOffset(getFileOffsetForAddress(NextAvailableAddress));
ColdSection->setOutputAddress(FF.getAddress());
}
FF.setAddress(NextAvailableAddress);
FF.setImageAddress(ColdSection->getAllocAddress());
FF.setImageSize(ColdSection->getOutputSize());
FF.setFileOffset(getFileOffsetForAddress(NextAvailableAddress));
ColdSection->setOutputAddress(FF.getAddress());

LLVM_DEBUG(
dbgs() << formatv(
"BOLT: mapping cold fragment {0:x+} to {1:x+} with size {2:x+}\n",
FF.getImageAddress(), FF.getAddress(), FF.getImageSize()));
MapSection(*ColdSection, FF.getAddress());

if (TooLarge)
BC->deregisterSection(*ColdSection);

NextAvailableAddress += FF.getImageSize();
}

Expand Down Expand Up @@ -5806,42 +5791,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);

const uint64_t EHFrameHdrOutputAddress = NextAvailableAddress;
const uint64_t EHFrameHdrFileOffset =
getFileOffsetForAddress(NextAvailableAddress);
EHFrameHdrOutputAddress = NextAvailableAddress;
EHFrameHdrFileOffset = getFileOffsetForAddress(NextAvailableAddress);

std::vector<char> NewEHFrameHdr = CFIRdWrt->generateEHFrameHeader(
RelocatedEHFrame, NewEHFrame, EHFrameHdrOutputAddress, FailedAddresses);
NewEHFrameHdr = CFIRdWrt->generateEHFrameHeader(
RelocatedEHFrame, NewEHFrame, EHFrameHdrOutputAddress);

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);
}

// 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
64 changes: 64 additions & 0 deletions bolt/lib/Target/AArch64/AArch64MCPlusBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,16 @@
#include "MCTargetDesc/AArch64MCExpr.h"
#include "MCTargetDesc/AArch64MCTargetDesc.h"
#include "Utils/AArch64BaseInfo.h"
#include "bolt/Core/BinaryBasicBlock.h"
#include "bolt/Core/BinaryFunction.h"
#include "bolt/Core/MCPlusBuilder.h"
#include "llvm/BinaryFormat/ELF.h"
#include "llvm/MC/MCContext.h"
#include "llvm/MC/MCFixupKindInfo.h"
#include "llvm/MC/MCInstBuilder.h"
#include "llvm/MC/MCInstrInfo.h"
#include "llvm/MC/MCRegisterInfo.h"
#include "llvm/Support/DataExtractor.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/ErrorHandling.h"

Expand Down Expand Up @@ -1320,6 +1323,67 @@ class AArch64MCPlusBuilder : public MCPlusBuilder {
return 3;
}

/// Match the following pattern:
///
/// LDR x16, .L1
/// BR x16
/// L1:
/// .quad Target
///
/// Populate \p TargetAddress with the Target value on successful match.
bool matchAbsLongVeneer(const BinaryFunction &BF,
uint64_t &TargetAddress) const override {
if (BF.size() != 1 || BF.getMaxSize() < 16)
return false;

if (!BF.hasConstantIsland())
return false;

const BinaryBasicBlock &BB = BF.front();
if (BB.size() != 2)
return false;

const MCInst &LDRInst = BB.getInstructionAtIndex(0);
if (LDRInst.getOpcode() != AArch64::LDRXl)
return false;

if (!LDRInst.getOperand(0).isReg() ||
LDRInst.getOperand(0).getReg() != AArch64::X16)
return false;

const MCSymbol *TargetSym = getTargetSymbol(LDRInst, 1);
if (!TargetSym)
return false;

const MCInst &BRInst = BB.getInstructionAtIndex(1);
if (BRInst.getOpcode() != AArch64::BR)
return false;
if (!BRInst.getOperand(0).isReg() ||
BRInst.getOperand(0).getReg() != AArch64::X16)
return false;

const BinaryFunction::IslandInfo &IInfo = BF.getIslandInfo();
if (IInfo.HasDynamicRelocations)
return false;

auto Iter = IInfo.Offsets.find(8);
if (Iter == IInfo.Offsets.end() || Iter->second != TargetSym)
return false;

// Extract the absolute value stored inside the island.
StringRef SectionContents = BF.getOriginSection()->getContents();
StringRef FunctionContents = SectionContents.substr(
BF.getAddress() - BF.getOriginSection()->getAddress(), BF.getMaxSize());

const BinaryContext &BC = BF.getBinaryContext();
DataExtractor DE(FunctionContents, BC.AsmInfo->isLittleEndian(),
BC.AsmInfo->getCodePointerSize());
uint64_t Offset = 8;
TargetAddress = DE.getAddress(&Offset);

return true;
}

bool matchAdrpAddPair(const MCInst &Adrp, const MCInst &Add) const override {
if (!isADRP(Adrp) || !isAddXri(Add))
return false;
Expand Down
51 changes: 51 additions & 0 deletions bolt/test/AArch64/veneer-lld-abs.s
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
## Check that llvm-bolt correctly recognizes long absolute thunks 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 \
# RUN: -fuse-ld=lld -Wl,-q
# RUN: llvm-objdump -d %t.exe | FileCheck --check-prefix=CHECK-INPUT %s
# RUN: llvm-objcopy --remove-section .rela.mytext %t.exe
# RUN: llvm-bolt %t.exe -o %t.bolt --elim-link-veneers=true --lite=0
# RUN: llvm-objdump -d -j .text %t.bolt | \
# RUN: FileCheck --check-prefix=CHECK-OUTPUT %s

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

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

.global __AArch64AbsLongThunk_foo
.type __AArch64AbsLongThunk_foo, %function
__AArch64AbsLongThunk_foo:
ldr x16, .L1
br x16
# CHECK-INPUT-LABEL: <__AArch64AbsLongThunk_foo>:
# CHECK-INPUT-NEXT: ldr
# CHECK-INPUT-NEXT: br
.L1:
.quad foo
.size __AArch64AbsLongThunk_foo, .-__AArch64AbsLongThunk_foo

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

# CHECK-OUTPUT-NOT: __AArch64AbsLongThunk_foo

.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>
ret
.size _start, .-_start
65 changes: 65 additions & 0 deletions bolt/test/X86/match-blocks-with-pseudo-probes-inline.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
## Test stale block matching with pseudo probes including inline tree matching.
# RUN: split-file %s %t
# RUN: llvm-bolt \
# RUN: %S/../../../llvm/test/tools/llvm-profgen/Inputs/inline-cs-pseudoprobe.perfbin \
# RUN: -o %t.bolt -data %t/yaml -infer-stale-profile -v=2 \
# RUN: --stale-matching-with-pseudo-probes 2>&1 | FileCheck %s

# CHECK: BOLT-WARNING: 3 (100.0% of all profiled) functions have invalid (possibly stale) profile
# CHECK: BOLT-INFO: inference found an exact pseudo probe match for 100.00% of basic blocks (3 out of 3 stale)

#--- yaml
---
header:
profile-version: 1
binary-name: 'inline-cs-pseudoprobe.perfbin'
binary-build-id: '<unknown>'
profile-flags: [ lbr ]
profile-origin: perf data aggregator
profile-events: ''
dfs-order: false
hash-func: xxh3
functions:
- name: bar
fid: 9
hash: 0x1
exec: 1
nblocks: 1
blocks:
- bid: 0
insns: 11
hash: 0x1
exec: 1
probes: [ { blx: 9 } ]
inline_tree: [ { } ]
- name: foo
fid: 10
hash: 0x2
exec: 1
nblocks: 6
blocks:
- bid: 0
insns: 3
hash: 0x2
exec: 1
succ: [ { bid: 3, cnt: 0 } ]
probes: [ { blx: 3 } ]
inline_tree: [ { g: 1 }, { g: 0, cs: 8 } ]
- name: main
fid: 11
hash: 0x3
exec: 1
nblocks: 6
blocks:
- bid: 0
insns: 3
hash: 0x3
exec: 1
succ: [ { bid: 3, cnt: 0 } ]
probes: [ { blx: 3, id: 1 }, { blx: 1 } ]
inline_tree: [ { g: 2 }, { g: 1, cs: 2 }, { g: 0, p: 1, cs: 8 } ]
pseudo_probe_desc:
gs: [ 0xE413754A191DB537, 0x5CF8C24CDB18BDAC, 0xDB956436E78DD5FA ]
gh: [ 2, 0, 1 ]
hs: [ 0x200205A19C5B4, 0x10000FFFFFFFF, 0x10E852DA94 ]
...
63 changes: 63 additions & 0 deletions bolt/test/X86/match-blocks-with-pseudo-probes.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
## Tests stale block matching with pseudo probes.

# REQUIRES: system-linux
# RUN: split-file %s %t
# RUN: llvm-mc -filetype=obj -triple x86_64-unknown-unknown %t/main.s -o %t.o
# RUN: %clang %cflags %t.o -o %t.exe -Wl,-q -nostdlib
# RUN: llvm-bolt %t.exe -o %t.out --data %t/yaml -v=2 \
# RUN: --print-cfg --funcs=main --infer-stale-profile \
# RUN: --stale-matching-with-pseudo-probes 2>&1 | FileCheck %s

# CHECK: BOLT-INFO: inference found an exact pseudo probe match for 100.00% of basic blocks (1 out of 1 stale)

#--- main.s
.text
.globl main # -- Begin function main
.p2align 4, 0x90
.type main,@function
main: # @main
# %bb.0:
pushq %rbp
movq %rsp, %rbp
movl $0, -4(%rbp)
.pseudoprobe 15822663052811949562 1 0 0 main
xorl %eax, %eax
popq %rbp
retq
.Lfunc_end0:
.size main, .Lfunc_end0-main
# -- End function
.section .pseudo_probe_desc,"",@progbits
.quad -2624081020897602054
.quad 4294967295
.byte 4
.ascii "main"

#--- yaml
---
header:
profile-version: 1
binary-name: 'match-blocks-with-pseudo-probes.s.tmp.exe'
binary-build-id: '<unknown>'
profile-flags: [ lbr ]
profile-origin: branch profile reader
profile-events: ''
dfs-order: false
hash-func: xxh3
functions:
- name: main
fid: 0
hash: 0x0000000000000001
exec: 1
nblocks: 6
blocks:
- bid: 1
hash: 0xFFFFFFFFFFFFFFF1
insns: 1
succ: [ { bid: 3, cnt: 1} ]
probes: [ { blx: 1 } ]
inline_tree: [ { g: 0 } ]
pseudo_probe_desc:
gs: [ 0xDB956436E78DD5FA ]
gh: [ 0 ]
hs: [ 0xFFFFFFFF ]
7 changes: 4 additions & 3 deletions bolt/test/X86/match-functions-with-calls-as-anchors.test
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,16 @@
# RUN: llvm-mc -filetype=obj -triple x86_64-unknown-unknown %t/main.s -o %t.o
# RUN: %clang %cflags %t.o -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 --debug 2>&1 | FileCheck %s
# RUN: --dyno-stats --print-cfg --infer-stale-profile=1 --debug-only=bolt-prof \
# RUN: 2>&1 | FileCheck %s

# CHECK: BOLT-INFO: applying profile inference for "qux"
# CHECK: Matched yaml block (bid = 1) with hash 4 to BB (index = 0) with hash 314e1bc10000
# CHECK: loose match
# CHECK: call match

# CHECK: BOLT-INFO: applying profile inference for "fred"
# CHECK: Matched yaml block (bid = 1) with hash 5 to BB (index = 0) with hash 7541bc10000
# CHECK: loose match
# CHECK: call match

#--- main.s
.globl foo # -- Begin function foo
Expand Down
4 changes: 2 additions & 2 deletions bolt/test/X86/reader-stale-yaml.test
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,10 @@ CHECK2: pre-processing profile using YAML profile reader
CHECK2: applying profile inference for "SolveCubic"
CHECK2: Matched yaml block (bid = 0) with hash 4600940a609c0000 to BB (index = 0) with hash 4600940a609c0000
CHECK2-NEXT: exact match
CHECK2: Matched yaml block (bid = 1) with hash 167a1f084f130088 to BB (index = 1) with hash 167a1f084f130088
CHECK2-NEXT: exact match
CHECK2: Matched yaml block (bid = 13) with hash a8d50000f81902a7 to BB (index = 13) with hash a8d5aa43f81902a7
CHECK2-NEXT: loose match
CHECK2: Matched yaml block (bid = 1) with hash 167a1f084f130088 to BB (index = 1) with hash 167a1f084f130088
CHECK2-NEXT: exact match
CHECK2: Matched yaml block (bid = 3) with hash c516000073dc00a0 to BB (index = 3) with hash c516b1c973dc00a0
CHECK2-NEXT: loose match
CHECK2: Matched yaml block (bid = 5) with hash 6446e1ea500111 to BB (index = 5) with hash 6446e1ea500111
Expand Down
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
5 changes: 3 additions & 2 deletions bolt/unittests/Core/MCPlusBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -90,14 +90,15 @@ INSTANTIATE_TEST_SUITE_P(AArch64, MCPlusBuilderTester,
::testing::Values(Triple::aarch64));

TEST_P(MCPlusBuilderTester, AliasX0) {
uint64_t AliasesX0[] = {AArch64::W0, AArch64::X0, AArch64::W0_W1,
uint64_t AliasesX0[] = {AArch64::W0, AArch64::W0_HI,
AArch64::X0, AArch64::W0_W1,
AArch64::X0_X1, AArch64::X0_X1_X2_X3_X4_X5_X6_X7};
size_t AliasesX0Count = sizeof(AliasesX0) / sizeof(*AliasesX0);
testRegAliases(Triple::aarch64, AArch64::X0, AliasesX0, AliasesX0Count);
}

TEST_P(MCPlusBuilderTester, AliasSmallerX0) {
uint64_t AliasesX0[] = {AArch64::W0, AArch64::X0};
uint64_t AliasesX0[] = {AArch64::W0, AArch64::W0_HI, AArch64::X0};
size_t AliasesX0Count = sizeof(AliasesX0) / sizeof(*AliasesX0);
testRegAliases(Triple::aarch64, AArch64::X0, AliasesX0, AliasesX0Count, true);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@ ExpandModularHeadersPPCallbacks::ExpandModularHeadersPPCallbacks(
Diags.setSourceManager(&Sources);
// FIXME: Investigate whatever is there better way to initialize DiagEngine
// or whatever DiagEngine can be shared by multiple preprocessors
ProcessWarningOptions(Diags, Compiler.getDiagnosticOpts());
ProcessWarningOptions(Diags, Compiler.getDiagnosticOpts(),
Compiler.getVirtualFileSystem());

LangOpts.Modules = false;

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 @@ -400,7 +400,9 @@ void SizeofExpressionCheck::check(const MatchFinder::MatchResult &Result) {
"suspicious usage of 'sizeof(array)/sizeof(...)';"
" denominator differs from the size of array elements")
<< E->getLHS()->getSourceRange() << E->getRHS()->getSourceRange();
} else if (NumTy && DenomTy && NumTy == DenomTy) {
} else if (NumTy && DenomTy && NumTy == DenomTy &&
!NumTy->isDependentType()) {
// Dependent type should not be compared.
diag(E->getOperatorLoc(),
"suspicious usage of 'sizeof(...)/sizeof(...)'; both expressions "
"have the same type")
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 @@ -299,10 +299,11 @@ static bool applyDiceHeuristic(StringRef Arg, StringRef Param,

/// Checks if ArgType binds to ParamType regarding reference-ness and
/// cv-qualifiers.
static bool areRefAndQualCompatible(QualType ArgType, QualType ParamType) {
static bool areRefAndQualCompatible(QualType ArgType, QualType ParamType,
const ASTContext &Ctx) {
return !ParamType->isReferenceType() ||
ParamType.getNonReferenceType().isAtLeastAsQualifiedAs(
ArgType.getNonReferenceType());
ArgType.getNonReferenceType(), Ctx);
}

static bool isPointerOrArray(QualType TypeToCheck) {
Expand All @@ -311,12 +312,12 @@ static bool isPointerOrArray(QualType TypeToCheck) {

/// Checks whether ArgType is an array type identical to ParamType's array type.
/// Enforces array elements' qualifier compatibility as well.
static bool isCompatibleWithArrayReference(QualType ArgType,
QualType ParamType) {
static bool isCompatibleWithArrayReference(QualType ArgType, QualType ParamType,
const ASTContext &Ctx) {
if (!ArgType->isArrayType())
return false;
// Here, qualifiers belong to the elements of the arrays.
if (!ParamType.isAtLeastAsQualifiedAs(ArgType))
if (!ParamType.isAtLeastAsQualifiedAs(ArgType, Ctx))
return false;

return ParamType.getUnqualifiedType() == ArgType.getUnqualifiedType();
Expand All @@ -342,12 +343,13 @@ static QualType convertToPointeeOrArrayElementQualType(QualType TypeToConvert) {
/// every * in ParamType to the right of that cv-qualifier, except the last
/// one, must also be const-qualified.
static bool arePointersStillQualCompatible(QualType ArgType, QualType ParamType,
bool &IsParamContinuouslyConst) {
bool &IsParamContinuouslyConst,
const ASTContext &Ctx) {
// The types are compatible, if the parameter is at least as qualified as the
// argument, and if it is more qualified, it has to be const on upper pointer
// levels.
bool AreTypesQualCompatible =
ParamType.isAtLeastAsQualifiedAs(ArgType) &&
ParamType.isAtLeastAsQualifiedAs(ArgType, Ctx) &&
(!ParamType.hasQualifiers() || IsParamContinuouslyConst);
// Check whether the parameter's constness continues at the current pointer
// level.
Expand All @@ -359,9 +361,10 @@ static bool arePointersStillQualCompatible(QualType ArgType, QualType ParamType,
/// Checks whether multilevel pointers are compatible in terms of levels,
/// qualifiers and pointee type.
static bool arePointerTypesCompatible(QualType ArgType, QualType ParamType,
bool IsParamContinuouslyConst) {
bool IsParamContinuouslyConst,
const ASTContext &Ctx) {
if (!arePointersStillQualCompatible(ArgType, ParamType,
IsParamContinuouslyConst))
IsParamContinuouslyConst, Ctx))
return false;

do {
Expand All @@ -372,7 +375,7 @@ static bool arePointerTypesCompatible(QualType ArgType, QualType ParamType,
// Check whether cv-qualifiers permit compatibility on
// current level.
if (!arePointersStillQualCompatible(ArgType, ParamType,
IsParamContinuouslyConst))
IsParamContinuouslyConst, Ctx))
return false;

if (ParamType.getUnqualifiedType() == ArgType.getUnqualifiedType())
Expand All @@ -396,7 +399,7 @@ static bool areTypesCompatible(QualType ArgType, QualType ParamType,
return true;

// Check for constness and reference compatibility.
if (!areRefAndQualCompatible(ArgType, ParamType))
if (!areRefAndQualCompatible(ArgType, ParamType, Ctx))
return false;

bool IsParamReference = ParamType->isReferenceType();
Expand Down Expand Up @@ -434,7 +437,7 @@ static bool areTypesCompatible(QualType ArgType, QualType ParamType,
// When ParamType is an array reference, ArgType has to be of the same-sized
// array-type with cv-compatible element type.
if (IsParamReference && ParamType->isArrayType())
return isCompatibleWithArrayReference(ArgType, ParamType);
return isCompatibleWithArrayReference(ArgType, ParamType, Ctx);

bool IsParamContinuouslyConst =
!IsParamReference || ParamType.getNonReferenceType().isConstQualified();
Expand All @@ -444,7 +447,7 @@ static bool areTypesCompatible(QualType ArgType, QualType ParamType,
ParamType = convertToPointeeOrArrayElementQualType(ParamType);

// Check qualifier compatibility on the next level.
if (!ParamType.isAtLeastAsQualifiedAs(ArgType))
if (!ParamType.isAtLeastAsQualifiedAs(ArgType, Ctx))
return false;

if (ParamType.getUnqualifiedType() == ArgType.getUnqualifiedType())
Expand Down Expand Up @@ -472,8 +475,8 @@ static bool areTypesCompatible(QualType ArgType, QualType ParamType,
if (!(ParamType->isAnyPointerType() && ArgType->isAnyPointerType()))
return false;

return arePointerTypesCompatible(ArgType, ParamType,
IsParamContinuouslyConst);
return arePointerTypesCompatible(ArgType, ParamType, IsParamContinuouslyConst,
Ctx);
}

static bool isOverloadedUnaryOrBinarySymbolOperator(const FunctionDecl *FD) {
Expand Down
7 changes: 4 additions & 3 deletions clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -418,7 +418,7 @@ ExceptionAnalyzer::ExceptionInfo::filterIgnoredExceptions(
if (TD->getDeclName().isIdentifier()) {
if ((IgnoreBadAlloc &&
(TD->getName() == "bad_alloc" && TD->isInStdNamespace())) ||
(IgnoredTypes.count(TD->getName()) > 0))
(IgnoredTypes.contains(TD->getName())))
TypesToDelete.push_back(T);
}
}
Expand Down Expand Up @@ -449,7 +449,8 @@ void ExceptionAnalyzer::ExceptionInfo::reevaluateBehaviour() {
ExceptionAnalyzer::ExceptionInfo ExceptionAnalyzer::throwsException(
const FunctionDecl *Func, const ExceptionInfo::Throwables &Caught,
llvm::SmallSet<const FunctionDecl *, 32> &CallStack) {
if (!Func || CallStack.count(Func) || (!CallStack.empty() && !canThrow(Func)))
if (!Func || CallStack.contains(Func) ||
(!CallStack.empty() && !canThrow(Func)))
return ExceptionInfo::createNonThrowing();

if (const Stmt *Body = Func->getBody()) {
Expand Down Expand Up @@ -507,7 +508,7 @@ ExceptionAnalyzer::ExceptionInfo ExceptionAnalyzer::throwsException(
for (unsigned I = 0; I < Try->getNumHandlers(); ++I) {
const CXXCatchStmt *Catch = Try->getHandler(I);

// Everything is catched through 'catch(...)'.
// Everything is caught through 'catch(...)'.
if (!Catch->getExceptionDecl()) {
ExceptionInfo Rethrown = throwsException(
Catch->getHandlerBlock(), Uncaught.getExceptionTypes(), CallStack);
Expand Down
4 changes: 2 additions & 2 deletions clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.h
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,8 @@ class ExceptionAnalyzer {
/// Recalculate the 'Behaviour' for example after filtering.
void reevaluateBehaviour();

/// Keep track if the entity related to this 'ExceptionInfo' can in princple
/// throw, if it's unknown or if it won't throw.
/// Keep track if the entity related to this 'ExceptionInfo' can in
/// principle throw, if it's unknown or if it won't throw.
State Behaviour;

/// Keep track if the entity contains any unknown elements to keep track
Expand Down
13 changes: 5 additions & 8 deletions clang-tools-extra/clangd/ClangdLSPServer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1419,15 +1419,12 @@ void ClangdLSPServer::applyConfiguration(
const ConfigurationSettings &Settings) {
// Per-file update to the compilation database.
llvm::StringSet<> ModifiedFiles;
for (auto &Entry : Settings.compilationDatabaseChanges) {
PathRef File = Entry.first;
auto Old = CDB->getCompileCommand(File);
auto New =
tooling::CompileCommand(std::move(Entry.second.workingDirectory), File,
std::move(Entry.second.compilationCommand),
for (auto &[File, Command] : Settings.compilationDatabaseChanges) {
auto Cmd =
tooling::CompileCommand(std::move(Command.workingDirectory), File,
std::move(Command.compilationCommand),
/*Output=*/"");
if (Old != New) {
CDB->setCompileCommand(File, std::move(New));
if (CDB->setCompileCommand(File, std::move(Cmd))) {
ModifiedFiles.insert(File);
}
}
Expand Down
15 changes: 11 additions & 4 deletions clang-tools-extra/clangd/GlobalCompilationDatabase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -807,20 +807,27 @@ tooling::CompileCommand OverlayCDB::getFallbackCommand(PathRef File) const {
return Cmd;
}

void OverlayCDB::setCompileCommand(PathRef File,
bool OverlayCDB::setCompileCommand(PathRef File,
std::optional<tooling::CompileCommand> Cmd) {
// We store a canonical version internally to prevent mismatches between set
// and get compile commands. Also it assures clients listening to broadcasts
// doesn't receive different names for the same file.
std::string CanonPath = removeDots(File);
{
std::unique_lock<std::mutex> Lock(Mutex);
if (Cmd)
Commands[CanonPath] = std::move(*Cmd);
else
if (Cmd) {
if (auto [It, Inserted] =
Commands.try_emplace(CanonPath, std::move(*Cmd));
!Inserted) {
if (It->second == *Cmd)
return false;
It->second = *Cmd;
}
} else
Commands.erase(CanonPath);
}
OnCommandChanged.broadcast({CanonPath});
return true;
}

DelegatingCDB::DelegatingCDB(const GlobalCompilationDatabase *Base)
Expand Down
4 changes: 3 additions & 1 deletion clang-tools-extra/clangd/GlobalCompilationDatabase.h
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,9 @@ class OverlayCDB : public DelegatingCDB {
tooling::CompileCommand getFallbackCommand(PathRef File) const override;

/// Sets or clears the compilation command for a particular file.
void
/// Returns true if the command was changed (including insertion and removal),
/// false if it was unchanged.
bool
setCompileCommand(PathRef File,
std::optional<tooling::CompileCommand> CompilationCommand);

Expand Down
317 changes: 213 additions & 104 deletions clang-tools-extra/clangd/ModulesBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
#include "clang/Frontend/FrontendActions.h"
#include "clang/Serialization/ASTReader.h"
#include "clang/Serialization/InMemoryModuleCache.h"
#include "llvm/ADT/ScopeExit.h"
#include <queue>

namespace clang {
namespace clangd {
Expand Down Expand Up @@ -124,10 +126,58 @@ struct ModuleFile {
llvm::sys::fs::remove(ModuleFilePath);
}

StringRef getModuleName() const { return ModuleName; }

StringRef getModuleFilePath() const { return ModuleFilePath; }

private:
std::string ModuleName;
std::string ModuleFilePath;
};

// ReusablePrerequisiteModules - stands for PrerequisiteModules for which all
// the required modules are built successfully. All the module files
// are owned by the modules builder.
class ReusablePrerequisiteModules : public PrerequisiteModules {
public:
ReusablePrerequisiteModules() = default;

ReusablePrerequisiteModules(const ReusablePrerequisiteModules &Other) =
default;
ReusablePrerequisiteModules &
operator=(const ReusablePrerequisiteModules &) = default;
ReusablePrerequisiteModules(ReusablePrerequisiteModules &&) = delete;
ReusablePrerequisiteModules
operator=(ReusablePrerequisiteModules &&) = delete;

~ReusablePrerequisiteModules() override = default;

void adjustHeaderSearchOptions(HeaderSearchOptions &Options) const override {
// Appending all built module files.
for (const auto &RequiredModule : RequiredModules)
Options.PrebuiltModuleFiles.insert_or_assign(
RequiredModule->getModuleName().str(),
RequiredModule->getModuleFilePath().str());
}

bool canReuse(const CompilerInvocation &CI,
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem>) const override;

bool isModuleUnitBuilt(llvm::StringRef ModuleName) const {
return BuiltModuleNames.contains(ModuleName);
}

void addModuleFile(std::shared_ptr<const ModuleFile> ModuleFile) {
BuiltModuleNames.insert(ModuleFile->getModuleName());
RequiredModules.emplace_back(std::move(ModuleFile));
}

private:
llvm::SmallVector<std::shared_ptr<const ModuleFile>, 8> RequiredModules;
// A helper class to speedup the query if a module is built.
llvm::StringSet<> BuiltModuleNames;
};

bool IsModuleFileUpToDate(PathRef ModuleFilePath,
const PrerequisiteModules &RequisiteModules,
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> VFS) {
Expand Down Expand Up @@ -192,89 +242,20 @@ bool IsModuleFilesUpToDate(
});
}

// StandalonePrerequisiteModules - stands for PrerequisiteModules for which all
// the required modules are built successfully. All the module files
// are owned by the StandalonePrerequisiteModules class.
//
// Any of the built module files won't be shared with other instances of the
// class. So that we can avoid worrying thread safety.
//
// We don't need to worry about duplicated module names here since the standard
// guarantees the module names should be unique to a program.
class StandalonePrerequisiteModules : public PrerequisiteModules {
public:
StandalonePrerequisiteModules() = default;

StandalonePrerequisiteModules(const StandalonePrerequisiteModules &) = delete;
StandalonePrerequisiteModules
operator=(const StandalonePrerequisiteModules &) = delete;
StandalonePrerequisiteModules(StandalonePrerequisiteModules &&) = delete;
StandalonePrerequisiteModules
operator=(StandalonePrerequisiteModules &&) = delete;

~StandalonePrerequisiteModules() override = default;

void adjustHeaderSearchOptions(HeaderSearchOptions &Options) const override {
// Appending all built module files.
for (auto &RequiredModule : RequiredModules)
Options.PrebuiltModuleFiles.insert_or_assign(
RequiredModule.ModuleName, RequiredModule.ModuleFilePath);
}

bool canReuse(const CompilerInvocation &CI,
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem>) const override;

bool isModuleUnitBuilt(llvm::StringRef ModuleName) const {
return BuiltModuleNames.contains(ModuleName);
}

void addModuleFile(llvm::StringRef ModuleName,
llvm::StringRef ModuleFilePath) {
RequiredModules.emplace_back(ModuleName, ModuleFilePath);
BuiltModuleNames.insert(ModuleName);
}

private:
llvm::SmallVector<ModuleFile, 8> RequiredModules;
// A helper class to speedup the query if a module is built.
llvm::StringSet<> BuiltModuleNames;
};

// Build a module file for module with `ModuleName`. The information of built
// module file are stored in \param BuiltModuleFiles.
llvm::Error buildModuleFile(llvm::StringRef ModuleName,
const GlobalCompilationDatabase &CDB,
const ThreadsafeFS &TFS, ProjectModules &MDB,
PathRef ModuleFilesPrefix,
StandalonePrerequisiteModules &BuiltModuleFiles) {
if (BuiltModuleFiles.isModuleUnitBuilt(ModuleName))
return llvm::Error::success();

PathRef ModuleUnitFileName = MDB.getSourceForModuleName(ModuleName);
// It is possible that we're meeting third party modules (modules whose
// source are not in the project. e.g, the std module may be a third-party
// module for most projects) or something wrong with the implementation of
// ProjectModules.
// FIXME: How should we treat third party modules here? If we want to ignore
// third party modules, we should return true instead of false here.
// Currently we simply bail out.
if (ModuleUnitFileName.empty())
return llvm::createStringError("Failed to get the primary source");

/// Build a module file for module with `ModuleName`. The information of built
/// module file are stored in \param BuiltModuleFiles.
llvm::Expected<ModuleFile>
buildModuleFile(llvm::StringRef ModuleName, PathRef ModuleUnitFileName,
const GlobalCompilationDatabase &CDB, const ThreadsafeFS &TFS,
const ReusablePrerequisiteModules &BuiltModuleFiles) {
// Try cheap operation earlier to boil-out cheaply if there are problems.
auto Cmd = CDB.getCompileCommand(ModuleUnitFileName);
if (!Cmd)
return llvm::createStringError(
llvm::formatv("No compile command for {0}", ModuleUnitFileName));

for (auto &RequiredModuleName : MDB.getRequiredModules(ModuleUnitFileName)) {
// Return early if there are errors building the module file.
if (llvm::Error Err = buildModuleFile(RequiredModuleName, CDB, TFS, MDB,
ModuleFilesPrefix, BuiltModuleFiles))
return llvm::createStringError(
llvm::formatv("Failed to build dependency {0}: {1}",
RequiredModuleName, llvm::toString(std::move(Err))));
}
llvm::SmallString<256> ModuleFilesPrefix =
getUniqueModuleFilesPath(ModuleUnitFileName);

Cmd->Output = getModuleFilePath(ModuleName, ModuleFilesPrefix);

Expand Down Expand Up @@ -316,58 +297,186 @@ llvm::Error buildModuleFile(llvm::StringRef ModuleName,
if (Clang->getDiagnostics().hasErrorOccurred())
return llvm::createStringError("Compilation failed");

BuiltModuleFiles.addModuleFile(ModuleName, Inputs.CompileCommand.Output);
return llvm::Error::success();
return ModuleFile{ModuleName, Inputs.CompileCommand.Output};
}

bool ReusablePrerequisiteModules::canReuse(
const CompilerInvocation &CI,
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> VFS) const {
if (RequiredModules.empty())
return true;

llvm::SmallVector<llvm::StringRef> BMIPaths;
for (auto &MF : RequiredModules)
BMIPaths.push_back(MF->getModuleFilePath());
return IsModuleFilesUpToDate(BMIPaths, *this, VFS);
}

class ModuleFileCache {
public:
ModuleFileCache(const GlobalCompilationDatabase &CDB) : CDB(CDB) {}
const GlobalCompilationDatabase &getCDB() const { return CDB; }

std::shared_ptr<const ModuleFile> getModule(StringRef ModuleName);

void add(StringRef ModuleName, std::shared_ptr<const ModuleFile> ModuleFile) {
std::lock_guard<std::mutex> Lock(ModuleFilesMutex);

ModuleFiles[ModuleName] = ModuleFile;
}

void remove(StringRef ModuleName);

private:
const GlobalCompilationDatabase &CDB;

llvm::StringMap<std::weak_ptr<const ModuleFile>> ModuleFiles;
// Mutex to guard accesses to ModuleFiles.
std::mutex ModuleFilesMutex;
};

std::shared_ptr<const ModuleFile>
ModuleFileCache::getModule(StringRef ModuleName) {
std::lock_guard<std::mutex> Lock(ModuleFilesMutex);

auto Iter = ModuleFiles.find(ModuleName);
if (Iter == ModuleFiles.end())
return nullptr;

if (auto Res = Iter->second.lock())
return Res;

ModuleFiles.erase(Iter);
return nullptr;
}

void ModuleFileCache::remove(StringRef ModuleName) {
std::lock_guard<std::mutex> Lock(ModuleFilesMutex);

ModuleFiles.erase(ModuleName);
}

/// Collect the directly and indirectly required module names for \param
/// ModuleName in topological order. The \param ModuleName is guaranteed to
/// be the last element in \param ModuleNames.
llvm::SmallVector<StringRef> getAllRequiredModules(ProjectModules &MDB,
StringRef ModuleName) {
llvm::SmallVector<llvm::StringRef> ModuleNames;
llvm::StringSet<> ModuleNamesSet;

auto VisitDeps = [&](StringRef ModuleName, auto Visitor) -> void {
ModuleNamesSet.insert(ModuleName);

for (StringRef RequiredModuleName :
MDB.getRequiredModules(MDB.getSourceForModuleName(ModuleName)))
if (ModuleNamesSet.insert(RequiredModuleName).second)
Visitor(RequiredModuleName, Visitor);

ModuleNames.push_back(ModuleName);
};
VisitDeps(ModuleName, VisitDeps);

return ModuleNames;
}

} // namespace

class ModulesBuilder::ModulesBuilderImpl {
public:
ModulesBuilderImpl(const GlobalCompilationDatabase &CDB) : Cache(CDB) {}

const GlobalCompilationDatabase &getCDB() const { return Cache.getCDB(); }

llvm::Error
getOrBuildModuleFile(StringRef ModuleName, const ThreadsafeFS &TFS,
ProjectModules &MDB,
ReusablePrerequisiteModules &BuiltModuleFiles);

private:
ModuleFileCache Cache;
};

llvm::Error ModulesBuilder::ModulesBuilderImpl::getOrBuildModuleFile(
StringRef ModuleName, const ThreadsafeFS &TFS, ProjectModules &MDB,
ReusablePrerequisiteModules &BuiltModuleFiles) {
if (BuiltModuleFiles.isModuleUnitBuilt(ModuleName))
return llvm::Error::success();

PathRef ModuleUnitFileName = MDB.getSourceForModuleName(ModuleName);
/// It is possible that we're meeting third party modules (modules whose
/// source are not in the project. e.g, the std module may be a third-party
/// module for most project) or something wrong with the implementation of
/// ProjectModules.
/// FIXME: How should we treat third party modules here? If we want to ignore
/// third party modules, we should return true instead of false here.
/// Currently we simply bail out.
if (ModuleUnitFileName.empty())
return llvm::createStringError(
llvm::formatv("Don't get the module unit for module {0}", ModuleName));

// Get Required modules in topological order.
auto ReqModuleNames = getAllRequiredModules(MDB, ModuleName);
for (llvm::StringRef ReqModuleName : ReqModuleNames) {
if (BuiltModuleFiles.isModuleUnitBuilt(ModuleName))
continue;

if (auto Cached = Cache.getModule(ReqModuleName)) {
if (IsModuleFileUpToDate(Cached->getModuleFilePath(), BuiltModuleFiles,
TFS.view(std::nullopt))) {
log("Reusing module {0} from {1}", ModuleName,
Cached->getModuleFilePath());
BuiltModuleFiles.addModuleFile(std::move(Cached));
continue;
}
Cache.remove(ReqModuleName);
}

llvm::Expected<ModuleFile> MF = buildModuleFile(
ModuleName, ModuleUnitFileName, getCDB(), TFS, BuiltModuleFiles);
if (llvm::Error Err = MF.takeError())
return Err;

log("Built module {0} to {1}", ModuleName, MF->getModuleFilePath());
auto BuiltModuleFile = std::make_shared<const ModuleFile>(std::move(*MF));
Cache.add(ModuleName, BuiltModuleFile);
BuiltModuleFiles.addModuleFile(std::move(BuiltModuleFile));
}

return llvm::Error::success();
}

std::unique_ptr<PrerequisiteModules>
ModulesBuilder::buildPrerequisiteModulesFor(PathRef File,
const ThreadsafeFS &TFS) const {
std::unique_ptr<ProjectModules> MDB = CDB.getProjectModules(File);
const ThreadsafeFS &TFS) {
std::unique_ptr<ProjectModules> MDB = Impl->getCDB().getProjectModules(File);
if (!MDB) {
elog("Failed to get Project Modules information for {0}", File);
return std::make_unique<FailedPrerequisiteModules>();
}

std::vector<std::string> RequiredModuleNames = MDB->getRequiredModules(File);
if (RequiredModuleNames.empty())
return std::make_unique<StandalonePrerequisiteModules>();

llvm::SmallString<256> ModuleFilesPrefix = getUniqueModuleFilesPath(File);

log("Trying to build required modules for {0} in {1}", File,
ModuleFilesPrefix);

auto RequiredModules = std::make_unique<StandalonePrerequisiteModules>();
return std::make_unique<ReusablePrerequisiteModules>();

auto RequiredModules = std::make_unique<ReusablePrerequisiteModules>();
for (llvm::StringRef RequiredModuleName : RequiredModuleNames) {
// Return early if there is any error.
if (llvm::Error Err =
buildModuleFile(RequiredModuleName, CDB, TFS, *MDB.get(),
ModuleFilesPrefix, *RequiredModules.get())) {
if (llvm::Error Err = Impl->getOrBuildModuleFile(
RequiredModuleName, TFS, *MDB.get(), *RequiredModules.get())) {
elog("Failed to build module {0}; due to {1}", RequiredModuleName,
toString(std::move(Err)));
return std::make_unique<FailedPrerequisiteModules>();
}
}

log("Built required modules for {0} in {1}", File, ModuleFilesPrefix);

return std::move(RequiredModules);
}

bool StandalonePrerequisiteModules::canReuse(
const CompilerInvocation &CI,
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> VFS) const {
if (RequiredModules.empty())
return true;

SmallVector<StringRef> BMIPaths;
for (auto &MF : RequiredModules)
BMIPaths.push_back(MF.ModuleFilePath);
return IsModuleFilesUpToDate(BMIPaths, *this, VFS);
ModulesBuilder::ModulesBuilder(const GlobalCompilationDatabase &CDB) {
Impl = std::make_unique<ModulesBuilderImpl>(CDB);
}

ModulesBuilder::~ModulesBuilder() {}

} // namespace clangd
} // namespace clang
8 changes: 5 additions & 3 deletions clang-tools-extra/clangd/ModulesBuilder.h
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,8 @@ class PrerequisiteModules {
/// different versions and different source files.
class ModulesBuilder {
public:
ModulesBuilder(const GlobalCompilationDatabase &CDB) : CDB(CDB) {}
ModulesBuilder(const GlobalCompilationDatabase &CDB);
~ModulesBuilder();

ModulesBuilder(const ModulesBuilder &) = delete;
ModulesBuilder(ModulesBuilder &&) = delete;
Expand All @@ -94,10 +95,11 @@ class ModulesBuilder {
ModulesBuilder &operator=(ModulesBuilder &&) = delete;

std::unique_ptr<PrerequisiteModules>
buildPrerequisiteModulesFor(PathRef File, const ThreadsafeFS &TFS) const;
buildPrerequisiteModulesFor(PathRef File, const ThreadsafeFS &TFS);

private:
const GlobalCompilationDatabase &CDB;
class ModulesBuilderImpl;
std::unique_ptr<ModulesBuilderImpl> Impl;
};

} // namespace clangd
Expand Down
29 changes: 29 additions & 0 deletions clang-tools-extra/clangd/Protocol.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -511,6 +511,35 @@ bool fromJSON(const llvm::json::Value &Params, ClientCapabilities &R,
if (auto EditsNearCursor = Completion->getBoolean("editsNearCursor"))
R.CompletionFixes |= *EditsNearCursor;
}
if (auto *References = TextDocument->getObject("references")) {
if (auto ContainerSupport = References->getBoolean("container")) {
R.ReferenceContainer |= *ContainerSupport;
}
}
if (auto *Diagnostics = TextDocument->getObject("publishDiagnostics")) {
if (auto CodeActions = Diagnostics->getBoolean("codeActionsInline")) {
R.DiagnosticFixes |= *CodeActions;
}
}
if (auto *InactiveRegions =
TextDocument->getObject("inactiveRegionsCapabilities")) {
if (auto InactiveRegionsSupport =
InactiveRegions->getBoolean("inactiveRegions")) {
R.InactiveRegions |= *InactiveRegionsSupport;
}
}
}
if (auto *Window = Experimental->getObject("window")) {
if (auto Implicit =
Window->getBoolean("implicitWorkDoneProgressCreate")) {
R.ImplicitProgressCreation |= *Implicit;
}
}
if (auto *OffsetEncoding = Experimental->get("offsetEncoding")) {
R.offsetEncoding.emplace();
if (!fromJSON(*OffsetEncoding, *R.offsetEncoding,
P.field("offsetEncoding")))
return false;
}
}

Expand Down
3 changes: 3 additions & 0 deletions clang-tools-extra/clangd/Protocol.h
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,7 @@ struct ClientCapabilities {
std::optional<SymbolKindBitset> WorkspaceSymbolKinds;

/// Whether the client accepts diagnostics with codeActions attached inline.
/// This is a clangd extension.
/// textDocument.publishDiagnostics.codeActionsInline.
bool DiagnosticFixes = false;

Expand All @@ -475,6 +476,7 @@ struct ClientCapabilities {

/// Client supports displaying a container string for results of
/// textDocument/reference (clangd extension)
/// textDocument.references.container
bool ReferenceContainer = false;

/// Client supports hierarchical document symbols.
Expand Down Expand Up @@ -563,6 +565,7 @@ struct ClientCapabilities {

/// Whether the client supports the textDocument/inactiveRegions
/// notification. This is a clangd extension.
/// textDocument.inactiveRegionsCapabilities.inactiveRegions
bool InactiveRegions = false;
};
bool fromJSON(const llvm::json::Value &, ClientCapabilities &,
Expand Down
21 changes: 17 additions & 4 deletions clang-tools-extra/clangd/XRefs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/Casting.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/raw_ostream.h"
#include <optional>
Expand Down Expand Up @@ -2275,7 +2276,7 @@ incomingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index) {
// Initially store the ranges in a map keyed by SymbolID of the caller.
// This allows us to group different calls with the same caller
// into the same CallHierarchyIncomingCall.
llvm::DenseMap<SymbolID, std::vector<Range>> CallsIn;
llvm::DenseMap<SymbolID, std::vector<Location>> CallsIn;
// We can populate the ranges based on a refs request only. As we do so, we
// also accumulate the container IDs into a lookup request.
LookupRequest ContainerLookup;
Expand All @@ -2285,7 +2286,7 @@ incomingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index) {
elog("incomingCalls failed to convert location: {0}", Loc.takeError());
return;
}
CallsIn[R.Container].push_back(Loc->range);
CallsIn[R.Container].push_back(*Loc);

ContainerLookup.IDs.insert(R.Container);
});
Expand All @@ -2294,9 +2295,21 @@ incomingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index) {
Index->lookup(ContainerLookup, [&](const Symbol &Caller) {
auto It = CallsIn.find(Caller.ID);
assert(It != CallsIn.end());
if (auto CHI = symbolToCallHierarchyItem(Caller, Item.uri.file()))
if (auto CHI = symbolToCallHierarchyItem(Caller, Item.uri.file())) {
std::vector<Range> FromRanges;
for (const Location &L : It->second) {
if (L.uri != CHI->uri) {
// Call location not in same file as caller.
// This can happen in some edge cases. There's not much we can do,
// since the protocol only allows returning ranges interpreted as
// being in the caller's file.
continue;
}
FromRanges.push_back(L.range);
}
Results.push_back(
CallHierarchyIncomingCall{std::move(*CHI), std::move(It->second)});
CallHierarchyIncomingCall{std::move(*CHI), std::move(FromRanges)});
}
});
// Sort results by name of container.
llvm::sort(Results, [](const CallHierarchyIncomingCall &A,
Expand Down
67 changes: 48 additions & 19 deletions clang-tools-extra/clangd/refactor/tweaks/DefineOutline.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -109,14 +109,13 @@ findContextForNS(llvm::StringRef TargetNS, const DeclContext *CurContext) {
// afterwards it can be shared with define-inline code action.
llvm::Expected<std::string>
getFunctionSourceAfterReplacements(const FunctionDecl *FD,
const tooling::Replacements &Replacements) {
const tooling::Replacements &Replacements,
bool TargetFileIsHeader) {
const auto &SM = FD->getASTContext().getSourceManager();
auto OrigFuncRange = toHalfOpenFileRange(
SM, FD->getASTContext().getLangOpts(), FD->getSourceRange());
if (!OrigFuncRange)
return error("Couldn't get range for function.");
assert(!FD->getDescribedFunctionTemplate() &&
"Define out-of-line doesn't apply to function templates.");

// Get new begin and end positions for the qualified function definition.
unsigned FuncBegin = SM.getFileOffset(OrigFuncRange->getBegin());
Expand All @@ -129,24 +128,38 @@ getFunctionSourceAfterReplacements(const FunctionDecl *FD,
if (!QualifiedFunc)
return QualifiedFunc.takeError();

auto Source = QualifiedFunc->substr(FuncBegin, FuncEnd - FuncBegin + 1);
std::string TemplatePrefix;
auto AddToTemplatePrefixIfApplicable = [&](const Decl *D) {
const TemplateParameterList *Params = D->getDescribedTemplateParams();
if (!Params)
return;
for (Decl *P : *Params) {
if (auto *TTP = dyn_cast<TemplateTypeParmDecl>(P))
TTP->removeDefaultArgument();
else if (auto *NTTP = dyn_cast<NonTypeTemplateParmDecl>(P))
NTTP->removeDefaultArgument();
else if (auto *TTPD = dyn_cast<TemplateTemplateParmDecl>(P))
TTPD->removeDefaultArgument();
}
std::string S;
llvm::raw_string_ostream Stream(S);
Params->print(Stream, FD->getASTContext());
if (!S.empty())
*S.rbegin() = '\n'; // Replace space with newline
TemplatePrefix.insert(0, S);
};
AddToTemplatePrefixIfApplicable(FD);
if (auto *MD = llvm::dyn_cast<CXXMethodDecl>(FD)) {
for (const CXXRecordDecl *Parent = MD->getParent(); Parent;
Parent =
llvm::dyn_cast_or_null<const CXXRecordDecl>(Parent->getParent())) {
if (const TemplateParameterList *Params =
Parent->getDescribedTemplateParams()) {
std::string S;
llvm::raw_string_ostream Stream(S);
Params->print(Stream, FD->getASTContext());
if (!S.empty())
*S.rbegin() = '\n'; // Replace space with newline
TemplatePrefix.insert(0, S);
}
AddToTemplatePrefixIfApplicable(Parent);
}
}

auto Source = QualifiedFunc->substr(FuncBegin, FuncEnd - FuncBegin + 1);
if (TargetFileIsHeader)
Source.insert(0, "inline ");
if (!TemplatePrefix.empty())
Source.insert(0, TemplatePrefix);
return Source;
Expand Down Expand Up @@ -202,7 +215,8 @@ deleteTokensWithKind(const syntax::TokenBuffer &TokBuf, tok::TokenKind Kind,
llvm::Expected<std::string>
getFunctionSourceCode(const FunctionDecl *FD, const DeclContext *TargetContext,
const syntax::TokenBuffer &TokBuf,
const HeuristicResolver *Resolver) {
const HeuristicResolver *Resolver,
bool TargetFileIsHeader) {
auto &AST = FD->getASTContext();
auto &SM = AST.getSourceManager();

Expand Down Expand Up @@ -337,7 +351,8 @@ getFunctionSourceCode(const FunctionDecl *FD, const DeclContext *TargetContext,

if (Errors)
return std::move(Errors);
return getFunctionSourceAfterReplacements(FD, DeclarationCleanups);
return getFunctionSourceAfterReplacements(FD, DeclarationCleanups,
TargetFileIsHeader);
}

struct InsertionPoint {
Expand Down Expand Up @@ -419,15 +434,15 @@ class DefineOutline : public Tweak {
Source->isOutOfLine())
return false;

// Bail out if this is a function template or specialization, as their
// Bail out if this is a function template specialization, as their
// definitions need to be visible in all including translation units.
if (Source->getDescribedFunctionTemplate())
return false;
if (Source->getTemplateSpecializationInfo())
return false;

auto *MD = llvm::dyn_cast<CXXMethodDecl>(Source);
if (!MD) {
if (Source->getDescribedFunctionTemplate())
return false;
// Can't outline free-standing functions in the same file.
return !SameFile;
}
Expand All @@ -450,6 +465,19 @@ class DefineOutline : public Tweak {
}
}

// For function templates, the same limitations as for class templates
// apply.
if (const TemplateParameterList *Params =
MD->getDescribedTemplateParams()) {
// FIXME: Is this really needed? It inhibits application on
// e.g. std::enable_if.
for (NamedDecl *P : *Params) {
if (!P->getIdentifier())
return false;
}
SameFile = true;
}

// The refactoring is meaningless for unnamed classes and namespaces,
// unless we're outlining in the same file
for (const DeclContext *DC = MD->getParent(); DC; DC = DC->getParent()) {
Expand Down Expand Up @@ -485,7 +513,8 @@ class DefineOutline : public Tweak {

auto FuncDef = getFunctionSourceCode(
Source, InsertionPoint->EnclosingNamespace, Sel.AST->getTokens(),
Sel.AST->getHeuristicResolver());
Sel.AST->getHeuristicResolver(),
SameFile && isHeaderFile(Sel.AST->tuPath(), Sel.AST->getLangOpts()));
if (!FuncDef)
return FuncDef.takeError();

Expand Down
19 changes: 11 additions & 8 deletions clang-tools-extra/clangd/refactor/tweaks/ExtractFunction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
#include "clang/AST/ASTContext.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclBase.h"
#include "clang/AST/ExprCXX.h"
#include "clang/AST/NestedNameSpecifier.h"
#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/AST/Stmt.h"
Expand All @@ -70,7 +71,6 @@
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/Casting.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/raw_os_ostream.h"
#include <optional>

namespace clang {
Expand All @@ -95,18 +95,21 @@ enum FunctionDeclKind {
OutOfLineDefinition
};

// A RootStmt is a statement that's fully selected including all it's children
// and it's parent is unselected.
// A RootStmt is a statement that's fully selected including all its children
// and its parent is unselected.
// Check if a node is a root statement.
bool isRootStmt(const Node *N) {
if (!N->ASTNode.get<Stmt>())
return false;
// Root statement cannot be partially selected.
if (N->Selected == SelectionTree::Partial)
return false;
// Only DeclStmt can be an unselected RootStmt since VarDecls claim the entire
// selection range in selectionTree.
if (N->Selected == SelectionTree::Unselected && !N->ASTNode.get<DeclStmt>())
// A DeclStmt can be an unselected RootStmt since VarDecls claim the entire
// selection range in selectionTree. Additionally, a CXXOperatorCallExpr of a
// binary operation can be unselected because its children claim the entire
// selection range in the selection tree (e.g. <<).
if (N->Selected == SelectionTree::Unselected && !N->ASTNode.get<DeclStmt>() &&
!N->ASTNode.get<CXXOperatorCallExpr>())
return false;
return true;
}
Expand Down Expand Up @@ -913,8 +916,8 @@ Expected<Tweak::Effect> ExtractFunction::apply(const Selection &Inputs) {

tooling::Replacements OtherEdit(
createForwardDeclaration(*ExtractedFunc, SM));
if (auto PathAndEdit = Tweak::Effect::fileEdit(SM, SM.getFileID(*FwdLoc),
OtherEdit))
if (auto PathAndEdit =
Tweak::Effect::fileEdit(SM, SM.getFileID(*FwdLoc), OtherEdit))
MultiFileEffect->ApplyEdits.try_emplace(PathAndEdit->first,
PathAndEdit->second);
else
Expand Down
29 changes: 29 additions & 0 deletions clang-tools-extra/clangd/unittests/CallHierarchyTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,35 @@ TEST(CallHierarchy, HierarchyOnVar) {
fromRanges(Source.range("Callee")))));
}

TEST(CallHierarchy, CallInDifferentFileThanCaller) {
Annotations Header(R"cpp(
#define WALDO void caller() {
)cpp");
Annotations Source(R"cpp(
void call^ee();
WALDO
callee();
}
)cpp");
auto TU = TestTU::withCode(Source.code());
TU.HeaderCode = Header.code();
auto AST = TU.build();
auto Index = TU.index();

std::vector<CallHierarchyItem> Items =
prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename));
ASSERT_THAT(Items, ElementsAre(withName("callee")));

auto Incoming = incomingCalls(Items[0], Index.get());

// The only call site is in the source file, which is a different file from
// the declaration of the function containing the call, which is in the
// header. The protocol does not allow us to represent such calls, so we drop
// them. (The call hierarchy item itself is kept.)
EXPECT_THAT(Incoming,
ElementsAre(AllOf(from(withName("caller")), fromRanges())));
}

} // namespace
} // namespace clangd
} // namespace clang
Loading