@@ -191,6 +191,9 @@ function keep-modified-pr | ||
} | ||
function check-targets() { | ||
+ # Do not use "check-all" here because if there is "check-all" plus a | ||
+ # project specific target like "check-clang", that project's tests | ||
+ # will be run twice. | ||
projects=${@} | ||
for project in ${projects}; do | ||
case ${project} in | ||
@@ -198,7 +201,7 @@ function check-targets() { | ||
echo "check-clang-too | ||
;; | ||
compiler-rt) | ||
- echo "check-all" | ||
+ echo "check-compiler- | ||
;; | ||
cross-project-te | ||
echo "check-cross-pro | ||
@@ -216,10 +219,10 @@ function check-targets() { | ||
echo "check-lldb" | ||
;; | ||
pstl) | ||
- echo "check-all" | ||
+ # Currently we do not run pstl tests in CI. | ||
;; | ||
libclc) | ||
- echo "check-all" | ||
+ # Currently there is no testing for libclc. | ||
;; | ||
*) | ||
echo "check-${project |
@@ -68,7 +68,7 @@ | ||
/mlir/lib/Dialec | ||
/mlir/lib/Dialec | ||
/mlir/lib/Dialec | ||
-/mlir/lib/Diale | ||
+/mlir/lib/Diale | ||
# MemRef Dialect in MLIR. | ||
/mlir/lib/Dialec | ||
@@ -82,9 +82,9 @@ | ||
/mlir/**/*Vector | ||
/mlir/**/*Vector | ||
/mlir/**/*X86Vec | ||
-/mlir/include/m | ||
+/mlir/include/m | ||
/mlir/include/ml | ||
-/mlir/lib/Diale | ||
+/mlir/lib/Diale | ||
/mlir/lib/Dialec | ||
/mlir/lib/Dialec | ||
/mlir/**/*Emulat |
@@ -69,7 +69,7 @@ PGO: | ||
- llvm/**/llvm-pro | ||
- llvm/**/llvm-pro | ||
-vectorization: | ||
+vectorizers: | ||
- llvm/lib/Transfo | ||
- llvm/include/llv | ||
@@ -668,7 +668,7 @@ mlgo: | ||
- llvm/lib/CodeGen | ||
- llvm/unittests/C | ||
- llvm/test/CodeGe | ||
- - llvm/utils/mlgo- | ||
+ - llvm/utils/mlgo- | ||
tools:llvm-exege | ||
- llvm/tools/llvm- | ||
@@ -1008,3 +1008,8 @@ bazel: | ||
offload: | ||
- offload/** | ||
+ | ||
+tablegen: | ||
+ - llvm/include/Tab | ||
+ - llvm/lib/TableGe | ||
+ - llvm/utils/Table |
@@ -358,11 +358,10 @@ def main(): | ||
gh = github.Github(lo | ||
org = gh.get_organizat | ||
repo = org.get_repo("ll | ||
- team = org.get_team_by_ | ||
one_year_ago = datetime.datetim | ||
triage_list = {} | ||
- for member in team.get_members | ||
- triage_list[memb | ||
+ for collaborator in repo.get_collabo | ||
+ triage_list[coll | ||
print("Start:", len(triage_list) | ||
# Step 0 Check if users have requested commit access in the last year. |
@@ -2,7 +2,7 @@ FROM docker.io/librar | ||
ENV LLVM_SYSROOT=/op | ||
FROM base as stage1-toolchain | ||
-ENV LLVM_VERSION=18. | ||
+ENV LLVM_VERSION=19. | ||
RUN apt-get update && \ | ||
apt-get install -y \ |
@@ -49,7 +49,8 @@ env: | ||
jobs: | ||
stage1: | ||
if: github.repositor | ||
- runs-on: libcxx-runners-8 | ||
+ runs-on: libcxx-runners-s | ||
+ container: ghcr.io/libcxx/a | ||
continue-on-erro | ||
strategy: | ||
fail-fast: false | ||
@@ -79,12 +80,14 @@ jobs: | ||
path: | | ||
**/test-results. | ||
**/*.abilist | ||
+ **/CMakeConfigur | ||
**/CMakeError.lo | ||
**/CMakeOutput.l | ||
**/crash_diagnos | ||
stage2: | ||
if: github.repositor | ||
- runs-on: libcxx-runners-8 | ||
+ runs-on: libcxx-runners-s | ||
+ container: ghcr.io/libcxx/a | ||
needs: [ stage1 ] | ||
continue-on-erro | ||
strategy: | ||
@@ -123,6 +126,7 @@ jobs: | ||
path: | | ||
**/test-results. | ||
**/*.abilist | ||
+ **/CMakeConfigur | ||
**/CMakeError.lo | ||
**/CMakeOutput.l | ||
**/crash_diagnos | ||
@@ -155,25 +159,23 @@ jobs: | ||
'generic-no-rtti | ||
'generic-optimiz | ||
'generic-static' | ||
- # TODO Find a better place for the benchmark and bootstrapping builds to live. They're either very expensive | ||
- # or don't provide much value since the benchmark run results are too noise on the bots. | ||
- 'benchmarks', | ||
'bootstrapping-b | ||
] | ||
- machine: [ 'libcxx-runners- | ||
+ machine: [ 'libcxx-runners- | ||
include: | ||
- config: 'generic-cxx26' | ||
- machine: libcxx-runners-8 | ||
+ machine: libcxx-runners-s | ||
- config: 'generic-asan' | ||
- machine: libcxx-runners-8 | ||
+ machine: libcxx-runners-s | ||
- config: 'generic-tsan' | ||
- machine: libcxx-runners-8 | ||
+ machine: libcxx-runners-s | ||
- config: 'generic-ubsan' | ||
- machine: libcxx-runners-8 | ||
+ machine: libcxx-runners-s | ||
# Use a larger machine for MSAN to avoid timeout and memory allocation issues. | ||
- config: 'generic-msan' | ||
- machine: libcxx-runners-8 | ||
+ machine: libcxx-runners-s | ||
runs-on: ${{ matrix.machine }} | ||
+ container: ghcr.io/libcxx/a | ||
steps: | ||
- uses: actions/checkout | ||
- name: ${{ matrix.config }} | ||
@@ -188,22 +190,30 @@ jobs: | ||
path: | | ||
**/test-results. | ||
**/*.abilist | ||
+ **/CMakeConfigur | ||
**/CMakeError.lo | ||
**/CMakeOutput.l | ||
**/crash_diagnos | ||
macos: | ||
- runs-on: macos-14 | ||
needs: [ stage1 ] | ||
strategy: | ||
- fail-fast: true | ||
+ fail-fast: false | ||
matrix: | ||
- config: [ | ||
- generic-cxx03, | ||
- generic-cxx23, | ||
- generic-modules, | ||
- apple-configurat | ||
- ] | ||
+ include: | ||
+ - config: generic-cxx03 | ||
+ os: macos-latest | ||
+ - config: generic-cxx23 | ||
+ os: macos-latest | ||
+ - config: generic-modules | ||
+ os: macos-latest | ||
+ - config: apple-configurat | ||
+ os: macos-latest | ||
+ - config: apple-system | ||
+ os: macos-13 | ||
+ - config: apple-system-har | ||
+ os: macos-13 | ||
+ runs-on: ${{ matrix.os }} | ||
steps: | ||
- uses: actions/checkout | ||
- uses: maxim-lobanov/se | ||
@@ -223,6 +233,7 @@ jobs: | ||
path: | | ||
**/test-results. | ||
**/*.abilist | ||
+ **/CMakeConfigur | ||
**/CMakeError.lo | ||
**/CMakeOutput.l | ||
**/crash_diagnos | ||
@@ -242,6 +253,7 @@ jobs: | ||
- { config: mingw-dll, mingw: true } | ||
- { config: mingw-static, mingw: true } | ||
- { config: mingw-dll-i686, mingw: true } | ||
+ - { config: mingw-incomplete | ||
steps: | ||
- uses: actions/checkout | ||
- name: Install dependencies | ||
@@ -260,6 +272,12 @@ jobs: | ||
del llvm-mingw*.zip | ||
mv llvm-mingw* c:\llvm-mingw | ||
echo "c:\llvm-mingw\b | ||
+ - name: Simulate a from-scratch build of llvm-mingw | ||
+ if: ${{ matrix.config == 'mingw-incomplet | ||
+ run: | | ||
+ rm -r c:\llvm-mingw\in | ||
+ rm -r c:\llvm-mingw\*- | ||
+ rm -r c:\llvm-mingw\*- | ||
- name: Add Git Bash to the path | ||
run: | | ||
echo "c:\Program Files\Git\usr\bi |
@@ -43,6 +43,7 @@ on: | ||
- '.github/workflo | ||
- '.github/workflo | ||
- '.github/workflo | ||
+ - 'clang/cmake/cac | ||
concurrency: | ||
group: ${{ github.workflow }}-${{ github.event.pul |
@@ -328,7 +328,7 @@ jobs: | ||
run: | | ||
# Build some of the mlir tools that take a long time to link | ||
if [ "${{ needs.prepare.ou | ||
- ninja -C ${{ steps.setup-stag | ||
+ ninja -C ${{ steps.setup-stag | ||
fi | ||
ninja -C ${{ steps.setup-stag | ||
mlir-bytecode-pa | ||
@@ -420,6 +420,14 @@ jobs: | ||
attestations: write # For artifact attestations | ||
steps: | ||
+ - name: Checkout Release Scripts | ||
+ uses: actions/checkout | ||
+ with: | ||
+ sparse-checkout: | ||
+ llvm/utils/relea | ||
+ llvm/utils/git/r | ||
+ sparse-checkout- | ||
+ | ||
- name: 'Download artifact' | ||
uses: actions/download | ||
with: | ||
@@ -442,11 +450,14 @@ jobs: | ||
name: ${{ needs.prepare.ou | ||
path: ${{ needs.prepare.ou | ||
+ - name: Install Python Requirements | ||
+ run: | | ||
+ pip install --require-hashes | ||
+ | ||
- name: Upload Release | ||
shell: bash | ||
run: | | ||
- sudo apt install python3-github | ||
- ./llvm-project/l | ||
+ ./llvm/utils/rel | ||
--token ${{ github.token }} \ | ||
--release ${{ needs.prepare.ou | ||
upload \ |
@@ -72,17 +72,20 @@ jobs: | ||
ref: main | ||
fetch-depth: 0 | ||
path: www-releases | ||
+ persist-credenti | ||
- name: Upload Release Notes | ||
if: env.upload | ||
env: | ||
- WWW_RELEASES_TOK | ||
+ GH_TOKEN: ${{ secrets.WWW_RELE | ||
run: | | ||
- mkdir -p ../www-releases/ | ||
- mv ./docs-build/htm | ||
- cd ../www-releases | ||
+ mkdir -p www-releases/${{ | ||
+ mv ./docs-build/htm | ||
+ cd www-releases | ||
+ git checkout -b ${{ inputs.release-v | ||
git add ${{ inputs.release-v | ||
git config user.email "llvmbot@llvm.or | ||
git config user.name "llvmbot" | ||
git commit -a -m "Add ${{ inputs.release-v | ||
- git push "https://$WWW_RE | ||
+ git push --force "https://$GH_TOK | ||
+ gh pr create -f -B main -H ${{ inputs.release-v |
@@ -51,6 +51,7 @@ autoconf/autom4t | ||
/CMakeSettings.j | ||
# CLion project configuration | ||
/.idea | ||
+/cmake-build* | ||
#=============== | ||
# Directories to ignore (do not add trailing '/'s, they skip symlinks). |
@@ -19,6 +19,7 @@ | ||
#include "bolt/Core/MCPlu | ||
#include "llvm/ADT/GraphT | ||
#include "llvm/ADT/String | ||
+#include "llvm/Config/llv | ||
#include "llvm/MC/MCInst. | ||
#include "llvm/MC/MCSymbo | ||
#include "llvm/Support/Er | ||
@@ -818,6 +819,9 @@ public: | ||
return OutputAddressRan | ||
} | ||
+ uint64_t getOutputStartAd | ||
+ uint64_t getOutputEndAddr | ||
+ | ||
bool hasLocSyms() const { return LocSyms != nullptr; } | ||
/// Return mapping of input offsets to symbols in the output. |
@@ -71,14 +71,15 @@ struct SegmentInfo { | ||
uint64_t FileOffset; /// Offset in the file. | ||
uint64_t FileSize; /// Size in file. | ||
uint64_t Alignment; /// Alignment of the segment. | ||
+ bool IsExecutable; /// Is the executable bit set on the Segment? | ||
void print(raw_ostrea | ||
- OS << "SegmentInfo { Address: 0x" | ||
- << Twine::utohexstr | ||
- << Twine::utohexstr | ||
+ OS << "SegmentInfo { Address: 0x" << Twine::utohexstr | ||
+ << ", Size: 0x" << Twine::utohexstr | ||
<< Twine::utohexstr | ||
<< Twine::utohexstr | ||
- << Twine::utohexstr | ||
+ << Twine::utohexstr | ||
+ << "}"; | ||
}; | ||
}; | ||
@@ -736,7 +737,7 @@ public: | ||
uint64_t StaleSampleCount | ||
/// the count of exactly matched samples | ||
uint64_t ExactMatchedSamp | ||
- /// the count of exactly matched samples | ||
+ /// the count of loosely matched samples | ||
uint64_t LooseMatchedSamp | ||
/// the count of exactly pseudo probe matched samples | ||
uint64_t PseudoProbeExact |
@@ -226,7 +226,6 @@ inline raw_ostream &operator<<(raw_ | ||
Sep = ",\n "; | ||
TotalCount += AccessInfo.Count | ||
} | ||
- SS.flush(); | ||
OS << TotalCount << " total counts : " << TempString; | ||
return OS; |
@@ -117,7 +117,6 @@ inline raw_ostream &operator<<(raw_ | ||
TotalCount += CSP.Count; | ||
TotalMispreds += CSP.Mispreds; | ||
} | ||
- SS.flush(); | ||
OS << TotalCount << " (" << TotalMispreds << " misses) :" << TempString; | ||
return OS; | ||
@@ -387,6 +386,9 @@ private: | ||
/// Raw branch count for this function in the profile. | ||
uint64_t RawBranchCount{0 | ||
+ /// Dynamically executed function bytes, used for density computation. | ||
+ uint64_t SampleCountInByt | ||
+ | ||
/// Indicates the type of profile the function is using. | ||
uint16_t ProfileFlags{PF_ | ||
@@ -906,6 +908,10 @@ public: | ||
return BB && BB->getOffset() == Offset ? BB : nullptr; | ||
} | ||
+ const BinaryBasicBlock | ||
+ return const_cast<Binar | ||
+ } | ||
+ | ||
/// Retrieve the landing pad BB associated with invoke instruction \p Invoke | ||
/// that is in \p BB. Return nullptr if none exists | ||
BinaryBasicBlock | ||
@@ -1845,6 +1851,9 @@ public: | ||
/// to this function. | ||
void setRawBranchCoun | ||
+ /// Return the number of dynamically executed bytes, from raw perf data. | ||
+ uint64_t getSampleCountIn | ||
+ | ||
/// Return the execution count for functions with known profile. | ||
/// Return 0 if the function has no profile. | ||
uint64_t getKnownExecutio |
@@ -314,7 +314,7 @@ public: | ||
BC.errs() | ||
<< "BOLT-ERROR: unable to find TypeUnit for Type Unit at offset 0x" | ||
- << DU.getOffset() << "\n"; | ||
+ << Twine::utohexstr | ||
return nullptr; | ||
} | ||
@@ -123,7 +123,8 @@ public: | ||
const_iterator begin() const; | ||
iterator end(); | ||
const_iterator end() const; | ||
- const BinaryBasicBlock | ||
+ BinaryBasicBlock | ||
+ BinaryBasicBlock | ||
friend class FunctionLayout; | ||
}; |
@@ -0,0 +1,61 @@ | ||
+//===- bolt/Passes/Cont | ||
+// | ||
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | ||
+// See https://llvm.org | ||
+// SPDX-License-Ide | ||
+// | ||
+//===---------- | ||
+// | ||
+// This pass checks how well the BOLT input profile satisfies the following | ||
+// "CFG continuity" property of a perfect profile: | ||
+// | ||
+// Each positive-executi | ||
+// should be *reachable* from a positive-executi | ||
+// entry block through a positive-executi | ||
+// | ||
+// More specifically, for each of the hottest 1000 functions, the pass | ||
+// calculates the function’s fraction of basic block execution counts | ||
+// that is *unreachable*. It then reports the 95th percentile of the | ||
+// distribution of the 1000 unreachable fractions in a single BOLT-INFO line. | ||
+// The smaller the reported value is, the better the BOLT profile | ||
+// satisfies the CFG continuity property. | ||
+ | ||
+// The default value of 1000 above can be changed via the hidden BOLT option | ||
+// `-num-functions- | ||
+// If more detailed stats are needed, `-v=1` can be used: the hottest N | ||
+// functions will be grouped into 5 equally-sized buckets, from the hottest | ||
+// to the coldest; for each bucket, various summary statistics of the | ||
+// distribution of the unreachable fractions and the raw unreachable execution | ||
+// counts will be reported. | ||
+// | ||
+//===---------- | ||
+ | ||
+#ifndef BOLT_PASSES_CONT | ||
+#define BOLT_PASSES_CONT | ||
+ | ||
+#include "bolt/Passes/Bin | ||
+#include <vector> | ||
+ | ||
+namespace llvm { | ||
+ | ||
+class raw_ostream; | ||
+ | ||
+namespace bolt { | ||
+class BinaryContext; | ||
+ | ||
+/// Compute and report to the user the function CFG continuity quality | ||
+class PrintContinuityS | ||
+public: | ||
+ explicit PrintContinuityS | ||
+ : BinaryFunctionPa | ||
+ | ||
+ bool shouldOptimize(c | ||
+ const char *getName() const override { return "continuity-stat | ||
+ bool shouldPrint(cons | ||
+ Error runOnFunctions(B | ||
+}; | ||
+ | ||
+} // namespace bolt | ||
+} // namespace llvm | ||
+ | ||
+#endif // BOLT_PASSES_CONT |
@@ -63,6 +63,19 @@ class LongJmpPass : public BinaryFunctionPa | ||
uint32_t NumColdStubs{0}; | ||
uint32_t NumSharedStubs{0 | ||
+ /// The shortest distance for any branch instruction on AArch64. | ||
+ static constexpr size_t ShortestJumpBits | ||
+ static constexpr size_t ShortestJumpSpan | ||
+ | ||
+ /// The longest single-instructi | ||
+ static constexpr size_t LongestJumpBits = 28; | ||
+ static constexpr size_t LongestJumpSpan = 1ULL << (LongestJumpBits | ||
+ | ||
+ /// Relax all internal function branches including those between fragments. | ||
+ /// Assume that fragments are placed in different sections but are within | ||
+ /// 128MB of each other. | ||
+ void relaxLocalBranch | ||
+ | ||
/// -- Layout estimation methods -- | ||
/// Try to do layout before running the emitter, by looking at BinaryFunctions | ||
/// and MCInsts -- this is an estimation. To be correct for longjmp inserter |
@@ -141,15 +141,13 @@ private: | ||
uint64_t FuncOutputAddres | ||
/// Write the serialized address translation table for a function. | ||
- template <bool Cold> | ||
- void writeMaps(std::m | ||
- raw_ostream &OS); | ||
+ template <bool Cold> void writeMaps(uint64 | ||
/// Read the serialized address translation table for a function. | ||
/// Return a parse error if failed. | ||
template <bool Cold> | ||
- void parseMaps(std::v | ||
- DataExtractor &DE, uint64_t &Offset, Error &Err); | ||
+ void parseMaps(uint64 | ||
+ Error &Err); | ||
/// Returns the bitmask with set bits corresponding to indices of BRANCHENTRY | ||
/// entries in function address translation map. | ||
@@ -161,6 +159,9 @@ private: | ||
std::map<uint64_ | ||
+ /// Ordered vector with addresses of hot functions. | ||
+ std::vector<uint | ||
+ | ||
/// Map a function to its basic blocks count | ||
std::unordered_m | ||
@@ -266,7 +266,8 @@ private: | ||
uint64_t Mispreds); | ||
/// Register a \p Branch. | ||
- bool doBranch(uint64_ | ||
+ bool doBranch(uint64_ | ||
+ bool IsPreagg); | ||
/// Register a trace between two LBR entries supplied in execution order. | ||
bool doTrace(const LBREntry &First, const LBREntry &Second, |
@@ -96,9 +96,8 @@ template <> struct MappingTraits<bo | ||
namespace bolt { | ||
struct PseudoProbeInfo { | ||
uint32_t InlineTreeIndex = 0; | ||
- uint64_t BlockMask = 0; // bitset with probe indices | ||
- // Assume BlockMask == 1 if no other probes are set | ||
- std::vector<uint | ||
+ uint64_t BlockMask = 0; // bitset with probe indices from 1 to 64 | ||
+ std::vector<uint | ||
std::vector<uint | ||
std::vector<uint | ||
std::vector<uint | ||
@@ -113,10 +112,10 @@ struct PseudoProbeInfo { | ||
template <> struct MappingTraits<bo | ||
static void mapping(IO &YamlIO, bolt::PseudoProb | ||
- YamlIO.mapOption | ||
- YamlIO.mapOption | ||
- YamlIO.mapOption | ||
- YamlIO.mapOption | ||
+ YamlIO.mapOption | ||
+ YamlIO.mapOption | ||
+ YamlIO.mapOption | ||
+ YamlIO.mapOption | ||
YamlIO.mapOption | ||
YamlIO.mapOption | ||
} | ||
@@ -170,18 +169,21 @@ template <> struct MappingTraits<bo | ||
}; | ||
namespace bolt { | ||
-struct InlineTreeInfo { | ||
+struct InlineTreeNode { | ||
uint32_t ParentIndexDelta | ||
uint32_t CallSiteProbe; | ||
- // Index in PseudoProbeDesc. | ||
+ // Index in PseudoProbeDesc. | ||
uint32_t GUIDIndex; | ||
- bool operator==(const | ||
+ // Decoded contents, ParentIndexDelta | ||
+ uint64_t GUID; | ||
+ uint64_t Hash; | ||
+ bool operator==(const | ||
}; | ||
} // end namespace bolt | ||
-template <> struct MappingTraits<bo | ||
- static void mapping(IO &YamlIO, bolt::InlineTree | ||
- YamlIO.mapOption | ||
+template <> struct MappingTraits<bo | ||
+ static void mapping(IO &YamlIO, bolt::InlineTree | ||
+ YamlIO.mapOption | ||
YamlIO.mapOption | ||
YamlIO.mapOption | ||
} | ||
@@ -192,7 +194,7 @@ template <> struct MappingTraits<bo | ||
} // end namespace llvm | ||
LLVM_YAML_IS_SEQ | ||
-LLVM_YAML_IS_FL | ||
+LLVM_YAML_IS_FL | ||
namespace llvm { | ||
namespace yaml { | ||
@@ -205,7 +207,7 @@ struct BinaryFunctionPr | ||
llvm::yaml::Hex6 | ||
uint64_t ExecCount{0}; | ||
std::vector<Bina | ||
- std::vector<Inli | ||
+ std::vector<Inli | ||
bool Used{false}; | ||
}; | ||
} // end namespace bolt | ||
@@ -220,7 +222,7 @@ template <> struct MappingTraits<bo | ||
YamlIO.mapOption | ||
std::vector<bolt | ||
YamlIO.mapOption | ||
- std::vector<bolt | ||
+ std::vector<bolt | ||
} | ||
}; | ||
@@ -271,23 +273,24 @@ template <> struct MappingTraits<bo | ||
}; | ||
namespace bolt { | ||
-struct PseudoProbeDesc { | ||
+struct ProfilePseudoPro | ||
std::vector<Hex6 | ||
std::vector<Hex6 | ||
- std::vector<uint | ||
+ std::vector<uint | ||
- bool operator==(const | ||
+ bool operator==(const | ||
// Only treat empty Desc as equal | ||
return GUID.empty() && Other.GUID.empty | ||
- Other.Hash.empty | ||
+ Other.Hash.empty | ||
+ Other.GUIDHashId | ||
} | ||
}; | ||
} // end namespace bolt | ||
-template <> struct MappingTraits<bo | ||
- static void mapping(IO &YamlIO, bolt::PseudoProb | ||
+template <> struct MappingTraits<bo | ||
+ static void mapping(IO &YamlIO, bolt::ProfilePse | ||
YamlIO.mapRequir | ||
- YamlIO.mapRequir | ||
+ YamlIO.mapRequir | ||
YamlIO.mapRequir | ||
} | ||
}; | ||
@@ -295,7 +298,7 @@ template <> struct MappingTraits<bo | ||
} // end namespace llvm | ||
LLVM_YAML_IS_SEQ | ||
-LLVM_YAML_IS_SE | ||
+LLVM_YAML_IS_SE | ||
namespace llvm { | ||
namespace yaml { | ||
@@ -304,7 +307,7 @@ namespace bolt { | ||
struct BinaryProfile { | ||
BinaryProfileHea | ||
std::vector<Bina | ||
- PseudoProbeDesc PseudoProbeDesc; | ||
+ ProfilePseudoPro | ||
}; | ||
} // namespace bolt | ||
@@ -313,7 +316,7 @@ template <> struct MappingTraits<bo | ||
YamlIO.mapRequir | ||
YamlIO.mapRequir | ||
YamlIO.mapOption | ||
- bolt::PseudoProb | ||
+ bolt::ProfilePse | ||
} | ||
}; | ||
@@ -101,6 +101,61 @@ public: | ||
YamlBFAdjacencyM | ||
}; | ||
+ // A class for matching inline tree nodes between profile and binary. | ||
+ // Provides the mapping from profile inline tree node id to a | ||
+ // corresponding binary MCDecodedPseudoP | ||
+ // | ||
+ // The whole mapping process is the following: | ||
+ // | ||
+ // (profile) (binary) | ||
+ // | blocks ^ | ||
+ // v | | ||
+ // yaml::bolt::Bina | ||
+ // ||| probes ^ (majority vote) | ||
+ // v ||| BBPseudoProbeToB | ||
+ // yaml::bolt::Pseu | ||
+ // | InlineTreeIndex ^ | ||
+ // v | probe id | ||
+ // [ profile node id (uint32_t) -> MCDecodedPseudoP | ||
+ // InlineTreeNodeMa | ||
+ class InlineTreeNodeMa | ||
+ DenseMap<uint32_ | ||
+ | ||
+ void mapInlineTreeNod | ||
+ const MCDecodedPseudoP | ||
+ auto Res = Map.try_emplace( | ||
+ assert(Res.secon | ||
+ "Duplicate mapping from profile node index to binary inline tree"); | ||
+ (void)Res; | ||
+ } | ||
+ | ||
+ public: | ||
+ /// Returns matched InlineTree * for a given profile inline_tree_id. | ||
+ const MCDecodedPseudoP | ||
+ getInlineTreeNod | ||
+ auto It = Map.find(Profile | ||
+ 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 MCDecodedPseudoP | ||
+ size_t matchInlineTrees | ||
+ const MCPseudoProbeDec | ||
+ const std::vector<yaml | ||
+ const MCDecodedPseudoP | ||
+ }; | ||
+ | ||
+ // Partial probe matching specification: matched inline tree and corresponding | ||
+ // BinaryFunctionPr | ||
+ using ProbeMatchSpec = | ||
+ std::pair<Inline | ||
+ std::reference_w | ||
+ | ||
private: | ||
/// Adjustments for basic samples profiles (without LBR). | ||
bool NormalizeByInsnC | ||
@@ -110,7 +165,7 @@ private: | ||
yaml::bolt::Bina | ||
/// Map a function ID from a YAML profile to a BinaryFunction object. | ||
- std::vector<Bina | ||
+ DenseMap<uint32_ | ||
using FunctionSet = std::unordered_s | ||
/// To keep track of functions that have a matched profile before the profile | ||
@@ -137,6 +192,10 @@ private: | ||
// Pseudo probe function GUID to inline tree node | ||
GUIDInlineTreeMa | ||
+ // Mapping from a binary function to its partial match specification | ||
+ // (YAML profile and its inline tree mapping to binary). | ||
+ DenseMap<BinaryF | ||
+ | ||
/// Populate \p Function profile with the one supplied in YAML format. | ||
bool parseFunctionPro | ||
const yaml::bolt::Bina | ||
@@ -147,7 +206,8 @@ private: | ||
/// Infer function profile from stale data (collected on older binaries). | ||
bool inferStaleProfil | ||
- const yaml::bolt::Bina | ||
+ const yaml::bolt::Bina | ||
+ const ArrayRef<ProbeMa | ||
/// Initialize maps for profile matching. | ||
void buildNameMaps(Bi | ||
@@ -164,14 +224,16 @@ private: | ||
/// Matches functions using the call graph. | ||
size_t matchWithCallGra | ||
+ /// Matches functions using the call graph. | ||
+ /// Populates BF->partial probe match spec map. | ||
+ size_t matchWithPseudoP | ||
+ | ||
/// Matches functions with similarly named profiled functions. | ||
size_t matchWithNameSim | ||
/// Update matched YAML -> BinaryFunction pair. | ||
void matchProfileToFu | ||
BinaryFunction &BF) { | ||
- if (YamlBF.Id >= YamlProfileToFun | ||
- YamlProfileToFun | ||
YamlProfileToFun | ||
YamlBF.Used = true; | ||
@@ -43,18 +43,18 @@ public: | ||
GUIDNumMap HashIdxMap; | ||
}; | ||
- static std::tuple<std:: | ||
+ static std::tuple<std:: | ||
convertBFInlineT | ||
const InlineTreeDesc &InlineTree, uint64_t GUID); | ||
+ static std::tuple<yaml: | ||
+ convertPseudoPro | ||
+ | ||
static yaml::bolt::Bina | ||
convert(const BinaryFunction &BF, bool UseDFS, | ||
const InlineTreeDesc &InlineTree, | ||
const BoltAddressTrans | ||
- static std::tuple<yaml: | ||
- convertPseudoPro | ||
- | ||
/// Set CallSiteInfo destination fields from \p Symbol and return a target | ||
/// BinaryFunction for that symbol. | ||
static const BinaryFunction * | ||
@@ -71,8 +71,8 @@ private: | ||
uint32_t InlineSite; | ||
}; | ||
static std::vector<Inli | ||
- getInlineTree(co | ||
- const MCDecodedPseudoP | ||
+ collectInlineTre | ||
+ const MCDecodedPseudoP | ||
// 0 - block probe, 1 - indirect call, 2 - direct call | ||
using ProbeList = std::array<Small |
@@ -510,12 +510,11 @@ private: | ||
}; | ||
/// Different types of X86-64 PLT sections. | ||
- const PLTSectionInfo X86_64_PLTSectio | ||
- { ".plt", 16 }, | ||
- { ".plt.got", 8 }, | ||
- { ".plt.sec", 8 }, | ||
- { nullptr, 0 } | ||
- }; | ||
+ const PLTSectionInfo X86_64_PLTSectio | ||
+ {".plt.got", 8}, | ||
+ {".plt.sec", 8}, | ||
+ {".iplt", 16}, | ||
+ {nullptr, 0}}; | ||
/// AArch64 PLT sections. | ||
const PLTSectionInfo AArch64_PLTSecti |
@@ -55,6 +55,7 @@ extern llvm::cl::opt<bo | ||
enum ProfileFormatKin | ||
extern llvm::cl::opt<Pr | ||
+extern llvm::cl::opt<bo | ||
extern llvm::cl::opt<bo | ||
extern llvm::cl::opt<bo | ||
extern llvm::cl::opt<bo |
@@ -1294,8 +1294,8 @@ bool BinaryContext::h | ||
Veneer->getOrCre | ||
Veneer->setMaxSi | ||
Veneer->updateSt | ||
- LLVM_DEBUG(dbgs( | ||
- << "\n"); | ||
+ LLVM_DEBUG(dbgs( | ||
+ << Twine::utohexstr | ||
return true; | ||
}; | ||
@@ -2021,6 +2021,9 @@ BinaryContext::g | ||
// Find a segment with a matching file offset. | ||
for (auto &KV : SegmentMapInfo) { | ||
const SegmentInfo &SegInfo = KV.second; | ||
+ // Only consider executable segments. | ||
+ if (!SegInfo.IsExec | ||
+ continue; | ||
// FileOffset is got from perf event, | ||
// and it is equal to alignDown(SegInf | ||
// If the pagesize is not equal to SegInfo.Alignmen |
@@ -165,6 +165,12 @@ bool shouldPrint(cons | ||
} | ||
} | ||
+ std::optional<St | ||
+ if (Origin && llvm::any_of(opt | ||
+ return Name == *Origin; | ||
+ })) | ||
+ return true; | ||
+ | ||
return false; | ||
} | ||
@@ -2571,6 +2577,7 @@ private: | ||
case MCCFIInstruction | ||
case MCCFIInstruction | ||
case MCCFIInstruction | ||
+ case MCCFIInstruction | ||
case MCCFIInstruction | ||
case MCCFIInstruction | ||
llvm_unreachable | ||
@@ -2709,6 +2716,7 @@ struct CFISnapshotDiff : public CFISnapshot { | ||
case MCCFIInstruction | ||
case MCCFIInstruction | ||
case MCCFIInstruction | ||
+ case MCCFIInstruction | ||
case MCCFIInstruction | ||
case MCCFIInstruction | ||
llvm_unreachable | ||
@@ -2858,6 +2866,7 @@ BinaryFunction:: | ||
case MCCFIInstruction | ||
case MCCFIInstruction | ||
case MCCFIInstruction | ||
+ case MCCFIInstruction | ||
case MCCFIInstruction | ||
case MCCFIInstruction | ||
llvm_unreachable | ||
@@ -3678,9 +3687,8 @@ BinaryFunction:: | ||
BinaryBasicBlock | ||
Stack.pop(); | ||
- if (Visited.find(BB | ||
+ if (!Visited.insert | ||
continue; | ||
- Visited.insert(B | ||
DFS.push_back(BB | ||
for (BinaryBasicBloc | ||
@@ -3873,11 +3881,8 @@ void BinaryFunction:: | ||
JumpTable *JT = getJumpTable(Ins | ||
if (!JT) | ||
continue; | ||
- auto Iter = JumpTables.find( | ||
- if (Iter == JumpTables.end() | ||
- JumpTables.inser | ||
+ if (JumpTables.inse | ||
continue; | ||
- } | ||
// This instruction is an indirect jump using a jump table, but it is | ||
// using the same jump table of another jump. Try all our tricks to | ||
// extract the jump table symbol and make it point to a new, duplicated JT |
@@ -57,11 +57,9 @@ getDWOName(llvm: | ||
"DW_AT_dwo_name/ | ||
if (DwarfOutputPath | ||
DWOName = std::string(sys: | ||
- auto Iter = NameToIndexMap.f | ||
- if (Iter == NameToIndexMap.e | ||
- Iter = NameToIndexMap.i | ||
- DWOName.append(s | ||
- ++Iter->second; | ||
+ uint32_t &Index = NameToIndexMap[D | ||
+ DWOName.append(s | ||
+ ++Index; | ||
} | ||
DWOName.append(" | ||
return DWOName; | ||
@@ -283,8 +281,7 @@ void DIEBuilder::buil | ||
for (auto &Row : TUIndex.getRows( | ||
uint64_t Signature = Row.getSignature | ||
// manually populate TypeUnit to UnitVector | ||
- DwarfContext->ge | ||
- true); | ||
+ DwarfContext->ge | ||
} | ||
} | ||
const unsigned int CUNum = getCUNum(DwarfCo |
@@ -33,7 +33,9 @@ FunctionFragment | ||
return const_iterator(L | ||
} | ||
-const BinaryBasicBlock | ||
+BinaryBasicBloc | ||
+ | ||
+BinaryBasicBloc | ||
FunctionLayout:: | ||
@@ -145,7 +145,7 @@ std::string hashBlockLoose(B | ||
continue; | ||
} | ||
- std::string Mnemonic = BC.InstPrinter-> | ||
+ std::string Mnemonic = BC.InstPrinter-> | ||
llvm::erase_if(M | ||
Opcodes.insert(M | ||
} |
@@ -56,13 +56,19 @@ void ADRRelaxationPas | ||
continue; | ||
} | ||
- // Don't relax adr if it points to the same function and it is not split | ||
- // and BF initial size is < 1MB. | ||
+ // Don't relax ADR if it points to the same function and is in the main | ||
+ // fragment and BF initial size is < 1MB. | ||
const unsigned OneMB = 0x100000; | ||
- if (!BF.isSplit() && BF.getSize() < OneMB) { | ||
+ if (BF.getSize() < OneMB) { | ||
BinaryFunction *TargetBF = BC.getFunctionFo | ||
- if (TargetBF && TargetBF == &BF) | ||
+ if (TargetBF == &BF && !BB.isSplit()) | ||
continue; | ||
+ | ||
+ // No relaxation needed if ADR references a basic block in the same | ||
+ // fragment. | ||
+ if (BinaryBasicBloc | ||
+ if (BB.getFragmentN | ||
+ continue; | ||
} | ||
MCPhysReg Reg; |
@@ -15,6 +15,7 @@ | ||
#include "bolt/Core/Paral | ||
#include "bolt/Passes/Reo | ||
#include "bolt/Passes/Reo | ||
+#include "bolt/Utils/Comm | ||
#include "llvm/Support/Co | ||
#include <atomic> | ||
#include <mutex> | ||
@@ -223,6 +224,18 @@ static cl::opt<unsigned | ||
"functions section"), | ||
cl::init(100), cl::Hidden, cl::cat(BoltCate | ||
+// Profile density options, synced with llvm-profgen/Pro | ||
+static cl::opt<int> ProfileDensityCu | ||
+ "profile-density | ||
+ cl::desc("Total samples cutoff for functions used to calculate " | ||
+ "profile density.")); | ||
+ | ||
+static cl::opt<double> ProfileDensityTh | ||
+ "profile-density | ||
+ cl::desc("If the profile density is below the given threshold, it " | ||
+ "will be suggested to increase the sampling rate."), | ||
+ cl::Optional); | ||
+ | ||
} // namespace opts | ||
namespace llvm { | ||
@@ -1383,6 +1396,7 @@ Error PrintProgramStat | ||
uint64_t StaleSampleCount | ||
uint64_t InferredSampleCo | ||
std::vector<cons | ||
+ std::vector<std: | ||
const char *StaleFuncsHeade | ||
for (auto &BFI : BC.getBinaryFunc | ||
const BinaryFunction &Function = BFI.second; | ||
@@ -1441,6 +1455,22 @@ Error PrintProgramStat | ||
StaleSampleCount | ||
++NumAllStaleFun | ||
} | ||
+ | ||
+ if (opts::ShowDensi | ||
+ uint64_t Size = Function.getSize | ||
+ // In case of BOLT split functions registered in BAT, executed traces are | ||
+ // automatically attributed to the main fragment. Add up function sizes | ||
+ // for all fragments. | ||
+ if (IsHotParentOfBO | ||
+ for (const BinaryFunction *Fragment : Function.getFrag | ||
+ Size += Fragment->getSiz | ||
+ double Density = (double)1.0 * Function.getSamp | ||
+ FuncDensityList. | ||
+ LLVM_DEBUG(BC.ou | ||
+ << Function.getSamp | ||
+ << Size << ", density " << Density | ||
+ << ", sample count " << SampleCount << '\n'); | ||
+ } | ||
} | ||
BC.NumProfiledFu | ||
BC.NumStaleProfi | ||
@@ -1722,6 +1752,50 @@ Error PrintProgramStat | ||
BC.outs() << ". Use -print-unknown to see the list."; | ||
BC.outs() << '\n'; | ||
} | ||
+ | ||
+ if (opts::ShowDensi | ||
+ double Density = 0.0; | ||
+ // Sorted by the density in descending order. | ||
+ llvm::stable_sor | ||
+ [&](const std::pair<double | ||
+ const std::pair<double | ||
+ if (A.first != B.first) | ||
+ return A.first > B.first; | ||
+ return A.second < B.second; | ||
+ }); | ||
+ | ||
+ uint64_t AccumulatedSampl | ||
+ uint32_t I = 0; | ||
+ assert(opts::Pro | ||
+ "The cutoff value is greater than 1000000(100%)"); | ||
+ while (AccumulatedSamp | ||
+ TotalSampleCount | ||
+ static_cast<floa | ||
+ 1000000 && | ||
+ I < FuncDensityList. | ||
+ AccumulatedSampl | ||
+ Density = FuncDensityList[ | ||
+ I++; | ||
+ } | ||
+ if (Density == 0.0) { | ||
+ BC.errs() << "BOLT-WARNING: the output profile is empty or the " | ||
+ "--profile-densi | ||
+ "set too low. Please check your command.\n"; | ||
+ } else if (Density < opts::ProfileDen | ||
+ BC.errs() | ||
+ << "BOLT-WARNING: BOLT is estimated to optimize better with " | ||
+ << format("%.1f", opts::ProfileDen | ||
+ << "x more samples. Please consider increasing sampling rate or " | ||
+ "profiling for longer duration to get more samples.\n"; | ||
+ } | ||
+ | ||
+ BC.outs() << "BOLT-INFO: Functions with density >= " | ||
+ << format("%.1f", Density) << " account for " | ||
+ << format("%.2f", | ||
+ static_cast<doub | ||
+ 10000) | ||
+ << "% total sample counts.\n"; | ||
+ } | ||
return Error::success() | ||
} | ||
@@ -26,6 +26,7 @@ add_llvm_library | ||
PatchEntries.cpp | ||
PettisAndHansen. | ||
PLTCall.cpp | ||
+ ContinuityStats. | ||
RegAnalysis.cpp | ||
RegReAssign.cpp | ||
ReorderAlgorithm |
@@ -0,0 +1,250 @@ | ||
+//===- bolt/Passes/Cont | ||
+// | ||
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | ||
+// See https://llvm.org | ||
+// SPDX-License-Ide | ||
+// | ||
+//===---------- | ||
+// | ||
+// This file implements the continuity stats calculation pass. | ||
+// | ||
+//===---------- | ||
+ | ||
+#include "bolt/Passes/Con | ||
+#include "bolt/Core/Binar | ||
+#include "bolt/Core/Binar | ||
+#include "bolt/Utils/Comm | ||
+#include "llvm/Support/Co | ||
+#include <queue> | ||
+#include <unordered_map> | ||
+#include <unordered_set> | ||
+ | ||
+#define DEBUG_TYPE "bolt-opts" | ||
+ | ||
+using namespace llvm; | ||
+using namespace bolt; | ||
+ | ||
+namespace opts { | ||
+extern cl::opt<unsigned | ||
+cl::opt<unsigne | ||
+ "num-functions-f | ||
+ cl::desc("number | ||
+ "CFG discontinuity stats of."), | ||
+ cl::init(1000), cl::ZeroOrMore, cl::Hidden, cl::cat(BoltOptC | ||
+} // namespace opts | ||
+ | ||
+namespace { | ||
+using FunctionListType | ||
+using function_iterato | ||
+ | ||
+template <typename T> | ||
+void printDistributio | ||
+ bool Fraction = false) { | ||
+ if (values.empty()) | ||
+ return; | ||
+ // Sort values from largest to smallest and print the MAX, TOP 1%, 5%, 10%, | ||
+ // 20%, 50%, 80%, MIN. If Fraction is true, then values are printed as | ||
+ // fractions instead of integers. | ||
+ std::sort(values | ||
+ | ||
+ auto printLine = [&](std::string Text, double Percent) { | ||
+ int Rank = int(values.size( | ||
+ if (Percent == 0) | ||
+ Rank = values.size() - 1; | ||
+ if (Fraction) | ||
+ OS << " " << Text << std::string(9 - Text.length(), ' ') << ": " | ||
+ << format("%.2lf%%" | ||
+ else | ||
+ OS << " " << Text << std::string(9 - Text.length(), ' ') << ": " | ||
+ << values[Rank] << "\n"; | ||
+ }; | ||
+ | ||
+ printLine("MAX", | ||
+ const int percentages[] = {1, 5, 10, 20, 50, 80}; | ||
+ for (size_t i = 0; i < sizeof(percentag | ||
+ printLine("TOP " + std::to_string(p | ||
+ } | ||
+ printLine("MIN", | ||
+} | ||
+ | ||
+void printCFGContinui | ||
+ iterator_range<f | ||
+ // Given a perfect profile, every positive-executi | ||
+ // connected to an entry of the function through a positive-executi | ||
+ // directed path in the control flow graph. | ||
+ std::vector<size | ||
+ std::vector<size | ||
+ std::vector<doub | ||
+ | ||
+ for (auto it = Functions.begin( | ||
+ const BinaryFunction *Function = *it; | ||
+ if (Function->size( | ||
+ continue; | ||
+ | ||
+ // Compute the sum of all BB execution counts (ECs). | ||
+ size_t NumPosECBBs = 0; | ||
+ size_t SumAllBBEC = 0; | ||
+ for (const BinaryBasicBlock | ||
+ const size_t BBEC = BB.getKnownExecu | ||
+ NumPosECBBs += BBEC > 0 ? 1 : 0; | ||
+ SumAllBBEC += BBEC; | ||
+ } | ||
+ | ||
+ // Perform BFS on subgraph of CFG induced by positive weight edges. | ||
+ // Compute the number of BBs reachable from the entry(s) of the function and | ||
+ // the sum of their execution counts (ECs). | ||
+ std::unordered_m | ||
+ std::unordered_s | ||
+ std::queue<unsig | ||
+ for (const BinaryBasicBlock | ||
+ // Make sure BB.getIndex() is not already in IndexToBB. | ||
+ assert(IndexToBB | ||
+ IndexToBB[BB.get | ||
+ if (BB.isEntryPoint | ||
+ Queue.push(BB.ge | ||
+ Visited.insert(B | ||
+ } | ||
+ } | ||
+ while (!Queue.empty()) | ||
+ const unsigned BBIndex = Queue.front(); | ||
+ const BinaryBasicBlock | ||
+ Queue.pop(); | ||
+ auto SuccBIIter = BB->branch_info_ | ||
+ for (const BinaryBasicBlock | ||
+ const uint64_t Count = SuccBIIter->Coun | ||
+ if (Count == BinaryBasicBlock | ||
+ ++SuccBIIter; | ||
+ continue; | ||
+ } | ||
+ if (!Visited.insert | ||
+ ++SuccBIIter; | ||
+ continue; | ||
+ } | ||
+ Queue.push(Succ- | ||
+ ++SuccBIIter; | ||
+ } | ||
+ } | ||
+ | ||
+ const size_t NumReachableBBs = Visited.size(); | ||
+ | ||
+ // Loop through Visited, and sum the corresponding BBs' execution counts | ||
+ // (ECs). | ||
+ size_t SumReachableBBEC | ||
+ for (const unsigned BBIndex : Visited) { | ||
+ const BinaryBasicBlock | ||
+ SumReachableBBEC | ||
+ } | ||
+ | ||
+ const size_t NumPosECBBsUnrea | ||
+ NumPosECBBs - NumReachableBBs; | ||
+ const size_t SumUnreachableBB | ||
+ const double FractionECUnreac | ||
+ (double)SumUnrea | ||
+ | ||
+ if (opts::Verbosity | ||
+ OS << "Non-trivial CFG discontinuity observed in function " | ||
+ << Function->getPri | ||
+ LLVM_DEBUG(Funct | ||
+ } | ||
+ | ||
+ NumUnreachables. | ||
+ SumECUnreachable | ||
+ FractionECUnreac | ||
+ } | ||
+ | ||
+ if (FractionECUnrea | ||
+ return; | ||
+ | ||
+ std::sort(Fracti | ||
+ const int Rank = int(FractionECUn | ||
+ OS << format("top 5%% function CFG discontinuity is %.2lf%%\n", | ||
+ FractionECUnreac | ||
+ | ||
+ if (opts::Verbosity | ||
+ OS << "abbreviations: EC = execution count, POS BBs = positive EC BBs\n" | ||
+ << "distribution of NUM(unreachable POS BBs) among all focal " | ||
+ "functions\n"; | ||
+ printDistributio | ||
+ | ||
+ OS << "distribution of SUM_EC(unreachab | ||
+ "functions\n"; | ||
+ printDistributio | ||
+ | ||
+ OS << "distribution of [(SUM_EC(unreach | ||
+ "POS BBs))] among all focal functions\n"; | ||
+ printDistributio | ||
+ } | ||
+} | ||
+ | ||
+void printAll(BinaryC | ||
+ size_t NumTopFunctions) | ||
+ // Sort the list of functions by execution counts (reverse). | ||
+ llvm::sort(Valid | ||
+ [&](const BinaryFunction *A, const BinaryFunction *B) { | ||
+ return A->getKnownExecu | ||
+ }); | ||
+ | ||
+ const size_t RealNumTopFuncti | ||
+ std::min(NumTopF | ||
+ | ||
+ iterator_range<f | ||
+ ValidFunctions.b | ||
+ | ||
+ BC.outs() << format("BOLT-INF | ||
+ RealNumTopFuncti | ||
+ printCFGContinui | ||
+ | ||
+ // Print more detailed bucketed stats if requested. | ||
+ if (opts::Verbosity | ||
+ const size_t PerBucketSize = RealNumTopFuncti | ||
+ BC.outs() << format( | ||
+ "Detailed stats for 5 buckets, each with %zu functions:\n", | ||
+ PerBucketSize); | ||
+ | ||
+ // For each bucket, print the CFG continuity stats of the functions in the | ||
+ // bucket. | ||
+ for (size_t BucketIndex = 0; BucketIndex < 5; ++BucketIndex) { | ||
+ const size_t StartIndex = BucketIndex * PerBucketSize; | ||
+ const size_t EndIndex = StartIndex + PerBucketSize; | ||
+ iterator_range<f | ||
+ ValidFunctions.b | ||
+ ValidFunctions.b | ||
+ const size_t MaxFunctionExecu | ||
+ ValidFunctions[S | ||
+ const size_t MinFunctionExecu | ||
+ ValidFunctions[E | ||
+ BC.outs() << format("-------- | ||
+ "|\n------------ | ||
+ BucketIndex + 1) | ||
+ << format( | ||
+ "execution counts of the %zu functions in the bucket: " | ||
+ "%zu-%zu\n", | ||
+ EndIndex - StartIndex, MinFunctionExecu | ||
+ MaxFunctionExecu | ||
+ printCFGContinui | ||
+ } | ||
+ } | ||
+} | ||
+} // namespace | ||
+ | ||
+bool PrintContinuityS | ||
+ if (BF.empty() || !BF.hasValidProf | ||
+ return false; | ||
+ | ||
+ return BinaryFunctionPa | ||
+} | ||
+ | ||
+Error PrintContinuityS | ||
+ // Create a list of functions with valid profiles. | ||
+ FunctionListType | ||
+ for (const auto &BFI : BC.getBinaryFunc | ||
+ const BinaryFunction *Function = &BFI.second; | ||
+ if (PrintContinuity | ||
+ ValidFunctions.p | ||
+ } | ||
+ if (ValidFunctions. | ||
+ return Error::success() | ||
+ | ||
+ printAll(BC, ValidFunctions, opts::NumFunctio | ||
+ return Error::success() | ||
+} |
@@ -109,9 +109,8 @@ static bool hasAArch64Exclus | ||
BinaryBasicBlock | ||
bool IsLoad = BBQueue.front(). | ||
BBQueue.pop(); | ||
- if (Visited.find(BB | ||
+ if (!Visited.insert | ||
continue; | ||
- Visited.insert(B | ||
for (const MCInst &Inst : *BB) { | ||
// Two loads one after another - skip whole function | ||
@@ -126,8 +125,7 @@ static bool hasAArch64Exclus | ||
if (BC.MIB->isAArch | ||
IsLoad = true; | ||
- if (IsLoad && BBToSkip.find(BB | ||
- BBToSkip.insert( | ||
+ if (IsLoad && BBToSkip.insert( | ||
if (opts::Verbosity | ||
outs() << "BOLT-INSTRUMENT | ||
<< " due to exclusive instruction in function " |
@@ -11,18 +11,26 @@ | ||
//===----------- | ||
#include "bolt/Passes/Lon | ||
+#include "bolt/Core/Paral | ||
+#include "llvm/Support/Ma | ||
#define DEBUG_TYPE "longjmp" | ||
using namespace llvm; | ||
namespace opts { | ||
+extern cl::OptionCatego | ||
extern cl::OptionCatego | ||
extern llvm::cl::opt<un | ||
extern cl::opt<unsigned | ||
extern cl::opt<bool> UseOldText; | ||
extern cl::opt<bool> HotFunctionsAtEn | ||
+static cl::opt<bool> | ||
+ CompactCodeModel | ||
+ cl::desc("genera | ||
+ cl::init(false), | ||
+ | ||
static cl::opt<bool> GroupStubs("grou | ||
cl::desc("share stubs across functions"), | ||
cl::init(true), cl::cat(BoltOptC | ||
@@ -61,10 +69,10 @@ static BinaryBasicBlock | ||
if (Next != E && (*Next)->isCold( | ||
return *I; | ||
} | ||
- llvm_unreachable | ||
+ llvm_unreachable | ||
} | ||
-static bool shouldInsertStub | ||
+static bool mayNeedStub(cons | ||
return (BC.MIB->isBranc | ||
!BC.MIB->isIndir | ||
} | ||
@@ -324,9 +332,8 @@ uint64_t LongJmpPass::ten | ||
uint64_t LongJmpPass::ten | ||
const BinaryContext &BC, std::vector<Bina | ||
uint64_t DotAddress) { | ||
- | ||
// Compute hot cold frontier | ||
- uint32_t LastHotIndex = -1u; | ||
+ int64_t LastHotIndex = -1u; | ||
uint32_t CurrentIndex = 0; | ||
if (opts::HotFuncti | ||
for (BinaryFunction *BF : SortedFunctions) | ||
@@ -351,19 +358,20 @@ uint64_t LongJmpPass::ten | ||
// Hot | ||
CurrentIndex = 0; | ||
bool ColdLayoutDone = false; | ||
+ auto runColdLayout = [&]() { | ||
+ DotAddress = tentativeLayoutR | ||
+ ColdLayoutDone = true; | ||
+ if (opts::HotFuncti | ||
+ DotAddress = alignTo(DotAddre | ||
+ }; | ||
for (BinaryFunction *Func : SortedFunctions) | ||
if (!BC.shouldEmit( | ||
HotAddresses[Fun | ||
continue; | ||
} | ||
- if (!ColdLayoutDone | ||
- DotAddress = | ||
- tentativeLayoutR | ||
- ColdLayoutDone = true; | ||
- if (opts::HotFuncti | ||
- DotAddress = alignTo(DotAddre | ||
- } | ||
+ if (!ColdLayoutDone | ||
+ runColdLayout(); | ||
DotAddress = alignTo(DotAddre | ||
uint64_t Pad = | ||
@@ -382,6 +390,11 @@ uint64_t LongJmpPass::ten | ||
DotAddress += Func->estimateCo | ||
++CurrentIndex; | ||
} | ||
+ | ||
+ // Ensure that tentative code layout always runs for cold blocks. | ||
+ if (!ColdLayoutDone | ||
+ runColdLayout(); | ||
+ | ||
// BBs | ||
for (BinaryFunction *Func : SortedFunctions) | ||
tentativeBBLayou | ||
@@ -565,7 +578,7 @@ Error LongJmpPass::rel | ||
if (BC.MIB->isPseud | ||
continue; | ||
- if (!shouldInsertSt | ||
+ if (!mayNeedStub(BC | ||
DotAddress += InsnSize; | ||
continue; | ||
} | ||
@@ -629,7 +642,283 @@ Error LongJmpPass::rel | ||
return Error::success() | ||
} | ||
+void LongJmpPass::rel | ||
+ BinaryContext &BC = BF.getBinaryCont | ||
+ auto &MIB = BC.MIB; | ||
+ | ||
+ // Quick path. | ||
+ if (!BF.isSplit() && BF.estimateSize( | ||
+ return; | ||
+ | ||
+ auto isBranchOffsetIn | ||
+ const unsigned Bits = MIB->getPCRelEnc | ||
+ return isIntN(Bits, Offset); | ||
+ }; | ||
+ | ||
+ auto isBlockInRange = [&](const MCInst &Inst, uint64_t InstAddress, | ||
+ const BinaryBasicBlock | ||
+ const int64_t Offset = BB.getOutputStar | ||
+ return isBranchOffsetIn | ||
+ }; | ||
+ | ||
+ // Keep track of *all* function trampolines that are going to be added to the | ||
+ // function layout at the end of relaxation. | ||
+ std::vector<std: | ||
+ FunctionTrampoli | ||
+ | ||
+ // Function fragments are relaxed independently. | ||
+ for (FunctionFragmen | ||
+ // Fill out code size estimation for the fragment. Use output BB address | ||
+ // ranges to store offsets from the start of the function fragment. | ||
+ uint64_t CodeSize = 0; | ||
+ for (BinaryBasicBloc | ||
+ BB->setOutputSta | ||
+ CodeSize += BB->estimateSize | ||
+ BB->setOutputEnd | ||
+ } | ||
+ | ||
+ // Dynamically-upda | ||
+ uint64_t FragmentSize = CodeSize; | ||
+ | ||
+ // Size of the trampoline in bytes. | ||
+ constexpr uint64_t TrampolineSize = 4; | ||
+ | ||
+ // Trampolines created for the fragment. DestinationBB -> TrampolineBB. | ||
+ // NB: here we store only the first trampoline created for DestinationBB. | ||
+ DenseMap<const BinaryBasicBlock | ||
+ | ||
+ // Create a trampoline code after \p BB or at the end of the fragment if BB | ||
+ // is nullptr. If \p UpdateOffsets is true, update FragmentSize and offsets | ||
+ // for basic blocks affected by the insertion of the trampoline. | ||
+ auto addTrampolineAft | ||
+ BinaryBasicBlock | ||
+ bool UpdateOffsets = true) { | ||
+ FunctionTrampoli | ||
+ BF.createBasicBl | ||
+ BinaryBasicBlock | ||
+ | ||
+ MCInst Inst; | ||
+ { | ||
+ auto L = BC.scopeLock(); | ||
+ MIB->createUncon | ||
+ } | ||
+ TrampolineBB->ad | ||
+ TrampolineBB->ad | ||
+ TrampolineBB->se | ||
+ const uint64_t TrampolineAddres | ||
+ BB ? BB->getOutputEnd | ||
+ TrampolineBB->se | ||
+ TrampolineBB->se | ||
+ TrampolineBB->se | ||
+ | ||
+ if (!FragmentTrampo | ||
+ FragmentTrampoli | ||
+ | ||
+ if (!UpdateOffsets) | ||
+ return TrampolineBB; | ||
+ | ||
+ FragmentSize += TrampolineSize; | ||
+ | ||
+ // If the trampoline was added at the end of the fragment, offsets of | ||
+ // other fragments should stay intact. | ||
+ if (!BB) | ||
+ return TrampolineBB; | ||
+ | ||
+ // Update offsets for blocks after BB. | ||
+ for (BinaryBasicBloc | ||
+ if (IBB->getOutputS | ||
+ IBB->setOutputSt | ||
+ TrampolineSize); | ||
+ IBB->setOutputEn | ||
+ } | ||
+ } | ||
+ | ||
+ // Update offsets for trampolines in this fragment that are placed after | ||
+ // the new trampoline. Note that trampoline blocks are not part of the | ||
+ // function/fragmen | ||
+ // from relaxLocalBranch | ||
+ for (auto &Pair : FunctionTrampoli | ||
+ BinaryBasicBlock | ||
+ if (IBB->getFragmen | ||
+ continue; | ||
+ if (IBB == TrampolineBB) | ||
+ continue; | ||
+ if (IBB->getOutputS | ||
+ IBB->setOutputSt | ||
+ TrampolineSize); | ||
+ IBB->setOutputEn | ||
+ } | ||
+ } | ||
+ | ||
+ return TrampolineBB; | ||
+ }; | ||
+ | ||
+ // Pre-populate trampolines by splitting unconditional branches from the | ||
+ // containing basic block. | ||
+ for (BinaryBasicBloc | ||
+ MCInst *Inst = BB->getLastNonPs | ||
+ if (!Inst || !MIB->isUncondit | ||
+ continue; | ||
+ | ||
+ const MCSymbol *TargetSymbol = MIB->getTargetSy | ||
+ BB->eraseInstruc | ||
+ BB->setOutputEnd | ||
+ | ||
+ BinaryBasicBlock | ||
+ BinaryBasicBlock | ||
+ | ||
+ BinaryBasicBlock | ||
+ addTrampolineAft | ||
+ BB->replaceSucce | ||
+ } | ||
+ | ||
+ /// Relax the branch \p Inst in basic block \p BB that targets \p TargetBB. | ||
+ /// \p InstAddress contains offset of the branch from the start of the | ||
+ /// containing function fragment. | ||
+ auto relaxBranch = [&](BinaryBasicB | ||
+ uint64_t InstAddress, BinaryBasicBlock | ||
+ BinaryFunction *BF = BB->getParent(); | ||
+ | ||
+ // Use branch taken count for optimal relaxation. | ||
+ const uint64_t Count = BB->getBranchInf | ||
+ assert(Count != BinaryBasicBlock | ||
+ "Expected valid branch execution count"); | ||
+ | ||
+ // Try to reuse an existing trampoline without introducing any new code. | ||
+ BinaryBasicBlock | ||
+ if (TrampolineBB && isBlockInRange(I | ||
+ BB->replaceSucce | ||
+ TrampolineBB->se | ||
+ Count); | ||
+ auto L = BC.scopeLock(); | ||
+ MIB->replaceBran | ||
+ return; | ||
+ } | ||
+ | ||
+ // For cold branches, check if we can introduce a trampoline at the end | ||
+ // of the fragment that is within the branch reach. Note that such | ||
+ // trampoline may change address later and become unreachable in which | ||
+ // case we will need further relaxation. | ||
+ const int64_t OffsetToEnd = FragmentSize - InstAddress; | ||
+ if (Count == 0 && isBranchOffsetIn | ||
+ TrampolineBB = addTrampolineAft | ||
+ BB->replaceSucce | ||
+ auto L = BC.scopeLock(); | ||
+ MIB->replaceBran | ||
+ | ||
+ return; | ||
+ } | ||
+ | ||
+ // Insert a new block after the current one and use it as a trampoline. | ||
+ TrampolineBB = addTrampolineAft | ||
+ | ||
+ // If the other successor is a fall-through, invert the condition code. | ||
+ const BinaryBasicBlock | ||
+ BF->getLayout(). | ||
+ if (BB->getConditio | ||
+ BB->swapConditio | ||
+ auto L = BC.scopeLock(); | ||
+ MIB->reverseBran | ||
+ } else { | ||
+ auto L = BC.scopeLock(); | ||
+ MIB->replaceBran | ||
+ } | ||
+ BB->replaceSucce | ||
+ }; | ||
+ | ||
+ bool MayNeedRelaxatio | ||
+ uint64_t NumIterations = 0; | ||
+ do { | ||
+ MayNeedRelaxatio | ||
+ ++NumIterations; | ||
+ for (auto BBI = FF.begin(); BBI != FF.end(); ++BBI) { | ||
+ BinaryBasicBlock | ||
+ uint64_t NextInstOffset = BB->getOutputSta | ||
+ for (MCInst &Inst : *BB) { | ||
+ const size_t InstAddress = NextInstOffset; | ||
+ if (!MIB->isPseudo( | ||
+ NextInstOffset += 4; | ||
+ | ||
+ if (!mayNeedStub(BF | ||
+ continue; | ||
+ | ||
+ const size_t BitsAvailable = MIB->getPCRelEnc | ||
+ | ||
+ // Span of +/-128MB. | ||
+ if (BitsAvailable == LongestJumpBits) | ||
+ continue; | ||
+ | ||
+ const MCSymbol *TargetSymbol = MIB->getTargetSy | ||
+ BinaryBasicBlock | ||
+ assert(TargetBB && | ||
+ "Basic block target expected for conditional branch."); | ||
+ | ||
+ // Check if the relaxation is needed. | ||
+ if (TargetBB->getFr | ||
+ isBlockInRange(I | ||
+ continue; | ||
+ | ||
+ relaxBranch(BB, Inst, InstAddress, TargetBB); | ||
+ | ||
+ MayNeedRelaxatio | ||
+ } | ||
+ } | ||
+ | ||
+ // We may have added new instructions, but the whole fragment is less than | ||
+ // the minimum branch span. | ||
+ if (FragmentSize < ShortestJumpSpan | ||
+ MayNeedRelaxatio | ||
+ | ||
+ } while (MayNeedRelaxati | ||
+ | ||
+ LLVM_DEBUG({ | ||
+ if (NumIterations > 2) { | ||
+ dbgs() << "BOLT-DEBUG: relaxed fragment " << FF.getFragmentNu | ||
+ << " of " << BF << " in " << NumIterations << " iterations\n"; | ||
+ } | ||
+ }); | ||
+ (void)NumIterati | ||
+ } | ||
+ | ||
+ // Add trampoline blocks from all fragments to the layout. | ||
+ DenseMap<BinaryB | ||
+ Insertions; | ||
+ for (std::pair<Binar | ||
+ FunctionTrampoli | ||
+ if (!Pair.second) | ||
+ continue; | ||
+ Insertions[Pair. | ||
+ } | ||
+ | ||
+ for (auto &Pair : Insertions) { | ||
+ BF.insertBasicBl | ||
+ /*UpdateLayout*/ | ||
+ /*RecomputeLPs*/ | ||
+ } | ||
+} | ||
+ | ||
Error LongJmpPass::run | ||
+ | ||
+ if (opts::CompactCo | ||
+ BC.outs() | ||
+ << "BOLT-INFO: relaxing branches for compact code model (<128MB)\n"; | ||
+ | ||
+ ParallelUtilitie | ||
+ relaxLocalBranch | ||
+ }; | ||
+ | ||
+ ParallelUtilitie | ||
+ [&](const BinaryFunction &BF) { | ||
+ return !BC.shouldEmit(B | ||
+ }; | ||
+ | ||
+ ParallelUtilitie | ||
+ BC, ParallelUtilitie | ||
+ SkipPredicate, "RelaxLocalBranc | ||
+ | ||
+ return Error::success() | ||
+ } | ||
+ | ||
BC.outs() << "BOLT-INFO: Starting stub-insertion pass\n"; | ||
std::vector<Bina | ||
bool Modified; |
@@ -181,7 +181,6 @@ std::string createRetpolineF | ||
if (BrInfo.isReg()) | ||
BC.InstPrinter-> | ||
TagOS << "_"; | ||
- TagOS.flush(); | ||
return Tag; | ||
} | ||
@@ -212,7 +211,6 @@ std::string createRetpolineF | ||
BC.InstPrinter-> | ||
} | ||
- TagOS.flush(); | ||
return Tag; | ||
} | ||
@@ -73,12 +73,12 @@ Error VeneerEliminatio | ||
continue; | ||
const MCSymbol *TargetSymbol = BC.MIB->getTarge | ||
- if (VeneerDestinati | ||
+ auto It = VeneerDestinatio | ||
+ if (It == VeneerDestinatio | ||
continue; | ||
VeneerCallers++; | ||
- BC.MIB->replaceB | ||
- BC.Ctx.get()); | ||
+ BC.MIB->replaceB | ||
} | ||
} | ||
} |
@@ -143,8 +143,8 @@ void BoltAddressTrans | ||
// Output addresses are delta-encoded | ||
uint64_t PrevAddress = 0; | ||
- writeMaps</*Cold | ||
- writeMaps</*Cold | ||
+ writeMaps</*Cold | ||
+ writeMaps</*Cold | ||
BC.outs() << "BOLT-INFO: Wrote " << Maps.size() << " BAT maps\n"; | ||
BC.outs() << "BOLT-INFO: Wrote " << FuncHashes.getNu | ||
@@ -182,8 +182,7 @@ size_t BoltAddressTrans | ||
} | ||
template <bool Cold> | ||
-void BoltAddressTrans | ||
- uint64_t &PrevAddress, raw_ostream &OS) { | ||
+void BoltAddressTrans | ||
const uint32_t NumFuncs = | ||
llvm::count_if(l | ||
return Cold == ColdPartSource.c | ||
@@ -213,9 +212,9 @@ void BoltAddressTrans | ||
: 0; | ||
uint32_t Skew = 0; | ||
if (Cold) { | ||
- auto HotEntryIt = Maps.find(ColdPa | ||
- assert(HotEntryI | ||
- size_t HotIndex = std::distance(Ma | ||
+ auto HotEntryIt = llvm::lower_boun | ||
+ assert(HotEntryI | ||
+ size_t HotIndex = std::distance(Ho | ||
encodeULEB128(Ho | ||
PrevIndex = HotIndex; | ||
// Skew of all input offsets for cold fragments is simply the first input | ||
@@ -223,6 +222,7 @@ void BoltAddressTrans | ||
Skew = Map.begin()->sec | ||
encodeULEB128(Sk | ||
} else { | ||
+ HotFuncs.push_ba | ||
// Function hash | ||
size_t BFHash = getBFHash(HotInp | ||
LLVM_DEBUG(dbgs( | ||
@@ -311,17 +311,15 @@ std::error_code BoltAddressTrans | ||
return make_error_code( | ||
Error Err(Error::succe | ||
- std::vector<uint | ||
uint64_t PrevAddress = 0; | ||
- parseMaps</*Cold | ||
- parseMaps</*Cold | ||
+ parseMaps</*Cold | ||
+ parseMaps</*Cold | ||
OS << "BOLT-INFO: Parsed " << Maps.size() << " BAT entries\n"; | ||
return errorToErrorCode | ||
} | ||
template <bool Cold> | ||
-void BoltAddressTrans | ||
- uint64_t &PrevAddress, DataExtractor &DE, | ||
+void BoltAddressTrans | ||
uint64_t &Offset, Error &Err) { | ||
const uint32_t NumFunctions = DE.getULEB128(&O | ||
LLVM_DEBUG(dbgs( |
@@ -638,8 +638,12 @@ void DataAggregator:: | ||
: BinaryFunction:: | ||
for (auto &BFI : BC.getBinaryFunc | ||
BinaryFunction &BF = BFI.second; | ||
- if (getBranchData(B | ||
+ FuncBranchData *FBD = getBranchData(BF | ||
+ if (FBD || getFuncSampleDat | ||
BF.markProfiled( | ||
+ if (FBD) | ||
+ BF.RawBranchCoun | ||
+ } | ||
} | ||
for (auto &FuncBranches : NamesToBranches) | ||
@@ -774,42 +778,75 @@ bool DataAggregator:: | ||
} | ||
bool DataAggregator:: | ||
- uint64_t Mispreds) { | ||
- bool IsReturn = false; | ||
- auto handleAddress = [&](uint64_t &Addr, bool IsFrom) -> BinaryFunction * { | ||
- if (BinaryFunction *Func = getBinaryFunctio | ||
- Addr -= Func->getAddress | ||
- if (IsFrom) { | ||
- auto checkReturn = [&](auto MaybeInst) { | ||
- IsReturn = MaybeInst && BC->MIB->isRetur | ||
- }; | ||
- if (Func->hasInstru | ||
- checkReturn(Func | ||
- else | ||
- checkReturn(Func | ||
- } | ||
+ uint64_t Mispreds, bool IsPreagg) { | ||
+ // Returns whether \p Offset in \p Func contains a return instruction. | ||
+ auto checkReturn = [&](const BinaryFunction &Func, const uint64_t Offset) { | ||
+ auto isReturn = [&](auto MI) { return MI && BC->MIB->isRetur | ||
+ return Func.hasInstruct | ||
+ ? isReturn(Func.ge | ||
+ : isReturn(Func.di | ||
+ }; | ||
- if (BAT) | ||
- Addr = BAT->translate(F | ||
+ // Returns whether \p Offset in \p Func may be a call continuation excluding | ||
+ // entry points and landing pads. | ||
+ auto checkCallCont = [&](const BinaryFunction &Func, const uint64_t Offset) { | ||
+ // No call continuation at a function start. | ||
+ if (!Offset) | ||
+ return false; | ||
+ | ||
+ // FIXME: support BAT case where the function might be in empty state | ||
+ // (split fragments declared non-simple). | ||
+ if (!Func.hasCFG()) | ||
+ return false; | ||
+ | ||
+ // The offset should not be an entry point or a landing pad. | ||
+ const BinaryBasicBlock | ||
+ return ContBB && !ContBB->isEntry | ||
+ }; | ||
- if (BinaryFunction *ParentFunc = getBATParentFunc | ||
- Func = ParentFunc; | ||
- if (IsFrom) | ||
- NumColdSamples += Count; | ||
- } | ||
+ // Mutates \p Addr to an offset into the containing function, performing BAT | ||
+ // offset translation and parent lookup. | ||
+ // | ||
+ // Returns the containing function (or BAT parent) and whether the address | ||
+ // corresponds to a return (if \p IsFrom) or a call continuation (otherwise). | ||
+ auto handleAddress = [&](uint64_t &Addr, bool IsFrom) { | ||
+ BinaryFunction *Func = getBinaryFunctio | ||
+ if (!Func) | ||
+ return std::pair{Func, false}; | ||
- return Func; | ||
- } | ||
- return nullptr; | ||
+ Addr -= Func->getAddress | ||
+ | ||
+ bool IsRetOrCallCont = | ||
+ IsFrom ? checkReturn(*Fun | ||
+ | ||
+ if (BAT) | ||
+ Addr = BAT->translate(F | ||
+ | ||
+ BinaryFunction *ParentFunc = getBATParentFunc | ||
+ if (!ParentFunc) | ||
+ return std::pair{Func, IsRetOrCallCont} | ||
+ | ||
+ if (IsFrom) | ||
+ NumColdSamples += Count; | ||
+ | ||
+ return std::pair{Parent | ||
}; | ||
- BinaryFunction *FromFunc = handleAddress(Fr | ||
+ uint64_t ToOrig = To; | ||
+ auto [FromFunc, IsReturn] = handleAddress(Fr | ||
+ auto [ToFunc, IsCallCont] = handleAddress(To | ||
+ if (!FromFunc && !ToFunc) | ||
+ return false; | ||
+ | ||
+ // Record call to continuation trace. | ||
+ if (IsPreagg && FromFunc != ToFunc && (IsReturn || IsCallCont)) { | ||
+ LBREntry First{ToOrig - 1, ToOrig - 1, false}; | ||
+ LBREntry Second{ToOrig, ToOrig, false}; | ||
+ return doTrace(First, Second, Count); | ||
+ } | ||
// Ignore returns. | ||
if (IsReturn) | ||
return true; | ||
- BinaryFunction *ToFunc = handleAddress(To | ||
- if (!FromFunc && !ToFunc) | ||
- return false; | ||
// Treat recursive control transfers as inter-branches. | ||
if (FromFunc == ToFunc && To != 0) { | ||
@@ -826,10 +863,19 @@ bool DataAggregator:: | ||
BinaryFunction *ToFunc = getBinaryFunctio | ||
if (!FromFunc || !ToFunc) { | ||
LLVM_DEBUG({ | ||
- dbgs() << "Out of range trace starting in " << FromFunc->getPri | ||
- << formatv(" @ {0:x}", First.To - FromFunc->getAdd | ||
- << " and ending in " << ToFunc->getPrint | ||
- << formatv(" @ {0:x}\n", Second.From - ToFunc->getAddre | ||
+ dbgs() << "Out of range trace starting in "; | ||
+ if (FromFunc) | ||
+ dbgs() << formatv("{0} @ {1:x}", *FromFunc, | ||
+ First.To - FromFunc->getAdd | ||
+ else | ||
+ dbgs() << Twine::utohexstr | ||
+ dbgs() << " and ending in "; | ||
+ if (ToFunc) | ||
+ dbgs() << formatv("{0} @ {1:x}", *ToFunc, | ||
+ Second.From - ToFunc->getAddre | ||
+ else | ||
+ dbgs() << Twine::utohexstr | ||
+ dbgs() << '\n'; | ||
}); | ||
NumLongRangeTrac | ||
return false; | ||
@@ -845,6 +891,12 @@ bool DataAggregator:: | ||
return false; | ||
} | ||
+ // Set ParentFunc to BAT parent function or FromFunc itself. | ||
+ BinaryFunction *ParentFunc = getBATParentFunc | ||
+ if (!ParentFunc) | ||
+ ParentFunc = FromFunc; | ||
+ ParentFunc->Samp | ||
+ | ||
std::optional<Bo | ||
BAT ? BAT->getFallthro | ||
Second.From) | ||
@@ -864,13 +916,12 @@ bool DataAggregator:: | ||
<< FromFunc->getPri | ||
<< Twine::utohexstr | ||
<< Twine::utohexstr | ||
- BinaryFunction *ParentFunc = getBATParentFunc | ||
for (auto [From, To] : *FTs) { | ||
if (BAT) { | ||
From = BAT->translate(F | ||
To = BAT->translate(F | ||
} | ||
- doIntraBranch(Pa | ||
+ doIntraBranch(*P | ||
} | ||
return true; | ||
@@ -1611,7 +1662,8 @@ void DataAggregator:: | ||
for (const auto &AggrLBR : BranchLBRs) { | ||
const Trace &Loc = AggrLBR.first; | ||
const TakenBranchInfo &Info = AggrLBR.second; | ||
- doBranch(Loc.Fro | ||
+ doBranch(Loc.Fro | ||
+ /*IsPreagg*/ false); | ||
} | ||
} | ||
@@ -1772,7 +1824,7 @@ void DataAggregator:: | ||
switch (AggrEntry.Entry | ||
case AggregatedLBREnt | ||
doBranch(AggrEnt | ||
- AggrEntry.Mispre | ||
+ AggrEntry.Mispre | ||
break; | ||
case AggregatedLBREnt | ||
case AggregatedLBREnt | ||
@@ -2043,7 +2095,8 @@ std::error_code DataAggregator:: | ||
// size of the mapping, but we know it should not exceed the segment | ||
// alignment value. Hence we are performing an approximate check. | ||
return SegInfo.Address >= MMapInfo.MMapAdd | ||
- SegInfo.Address - MMapInfo.MMapAdd | ||
+ SegInfo.Address - MMapInfo.MMapAdd | ||
+ SegInfo.IsExecut | ||
}); | ||
if (!MatchFound) { | ||
errs() << "PERF2BOLT-WARNI |
@@ -228,23 +228,33 @@ public: | ||
NO_MATCH | ||
}; | ||
- /// Find the most similar flow block for a profile block given its hashes and | ||
- /// pseudo probe information. | ||
+ /// Find the most similar flow block for a profile block given blended hash. | ||
std::pair<const FlowBlock *, MatchMethod> | ||
- matchBlock(Blend | ||
- const ArrayRef<yaml::b | ||
- const ArrayRef<yaml::b | ||
+ matchBlockStrict | ||
const auto &[Block, ExactHash] = matchWithOpcodes | ||
if (Block && ExactHash) | ||
return {Block, MATCH_EXACT}; | ||
+ return {nullptr, NO_MATCH}; | ||
+ } | ||
+ | ||
+ /// Find the most similar flow block for a profile block given pseudo probes. | ||
+ std::pair<const FlowBlock *, MatchMethod> matchBlockProbe( | ||
+ const ArrayRef<yaml::b | ||
+ const YAMLProfileReade | ||
const auto &[ProbeBlock, ExactProbe] = | ||
- matchWithPseudoP | ||
+ matchWithPseudoP | ||
if (ProbeBlock) | ||
return {ProbeBlock, ExactProbe ? MATCH_PROBE_EXAC | ||
- if (const FlowBlock *BestBlock = matchWithCalls(B | ||
- return {BestBlock, MATCH_CALL}; | ||
- if (Block) | ||
- return {Block, MATCH_OPCODE}; | ||
+ return {nullptr, NO_MATCH}; | ||
+ } | ||
+ | ||
+ /// Find the most similar flow block for a profile block given its hashes. | ||
+ std::pair<const FlowBlock *, MatchMethod> | ||
+ matchBlockLoose( | ||
+ if (const FlowBlock *CallBlock = matchWithCalls(B | ||
+ return {CallBlock, MATCH_CALL}; | ||
+ if (const FlowBlock *OpcodeBlock = matchWithOpcodes | ||
+ return {OpcodeBlock, MATCH_OPCODE}; | ||
return {nullptr, NO_MATCH}; | ||
} | ||
@@ -256,28 +266,10 @@ public: | ||
return Hash1.InstrHash == Hash2.InstrHash; | ||
} | ||
- /// Returns matched InlineTree * for a given profile inline_tree_id. | ||
- const MCDecodedPseudoP | ||
- getInlineTreeNod | ||
- auto It = InlineTreeNodeMa | ||
- if (It == InlineTreeNodeMa | ||
- return nullptr; | ||
- return It->second; | ||
- } | ||
- | ||
- void mapInlineTreeNod | ||
- const MCDecodedPseudoP | ||
- auto Res = InlineTreeNodeMa | ||
- assert(Res.secon | ||
- "Duplicate mapping from profile node index to binary inline tree"); | ||
- (void)Res; | ||
- } | ||
- | ||
private: | ||
using HashBlockPairTyp | ||
std::unordered_m | ||
std::unordered_m | ||
- DenseMap<uint32_ | ||
DenseMap<const MCDecodedPseudoP | ||
// Uses OpcodeHash to find the most similar block for a given hash. | ||
@@ -322,72 +314,71 @@ private: | ||
return BestBlock; | ||
} | ||
- /// Matches a profile block with an binary block based on pseudo probes. | ||
+ /// Matches a profile block with a binary block based on pseudo probes. | ||
/// Returns the best matching block (or nullptr) and whether the match is | ||
/// unambiguous. | ||
std::pair<const FlowBlock *, bool> matchWithPseudoP | ||
const ArrayRef<yaml::b | ||
- const ArrayRef<yaml::b | ||
+ const YAMLProfileReade | ||
+ | ||
if (!opts::StaleMat | ||
return {nullptr, false}; | ||
DenseMap<const FlowBlock *, uint32_t> FlowBlockMatchCo | ||
- auto match = [&](uint32_t NodeId, uint64_t ProbeId) -> const FlowBlock * { | ||
- const MCDecodedPseudoP | ||
- if (!Node) | ||
+ auto matchProfileProb | ||
+ uint64_t ProbeId) -> const FlowBlock * { | ||
+ const MCDecodedPseudoP | ||
+ InlineTreeNodeMa | ||
+ if (!BinaryNode) | ||
return nullptr; | ||
- const MCDecodedPseudoP | ||
- for (const MCDecodedPseudoP | ||
- if (Probe.getIndex( | ||
- continue; | ||
- BinaryProbe = &Probe; | ||
- break; | ||
- } | ||
- if (!BinaryProbe) | ||
+ const MCDecodedPseudoP | ||
+ nullptr); | ||
+ ArrayRef<MCDecod | ||
+ auto BinaryProbeIt = llvm::lower_boun | ||
+ BinaryProbes, Dummy, [](const auto &LHS, const auto &RHS) { | ||
+ return LHS.getIndex() < RHS.getIndex(); | ||
+ }); | ||
+ if (BinaryProbeIt == BinaryNode->getP | ||
+ BinaryProbeIt->g | ||
return nullptr; | ||
- auto It = BBPseudoProbeToB | ||
+ auto It = BBPseudoProbeToB | ||
if (It == BBPseudoProbeToB | ||
return nullptr; | ||
return It->second; | ||
}; | ||
- auto matchProbe = [&](const yaml::bolt::Pseu | ||
- uint32_t NodeId, bool IsBlock1) { | ||
- if (IsBlock1) { | ||
- ++FlowBlockMatch | ||
- return; | ||
- } | ||
+ auto matchPseudoProbe | ||
+ &ProfileProbe, | ||
+ uint32_t NodeId) { | ||
for (uint64_t Index = 0; Index < 64; ++Index) | ||
- if (Probe.BlockMask | ||
- ++FlowBlockMatch | ||
- for (const auto &Probes : | ||
- {Probe.BlockProb | ||
- for (uint64_t Probe : Probes) | ||
- ++FlowBlockMatch | ||
+ if (ProfileProbe.Bl | ||
+ ++FlowBlockMatch | ||
+ for (const auto &ProfileProbes : | ||
+ {ProfileProbe.Bl | ||
+ ProfileProbe.Cal | ||
+ for (uint64_t ProfileProbe : ProfileProbes) | ||
+ ++FlowBlockMatch | ||
}; | ||
- for (const yaml::bolt::Pseu | ||
- bool IsBlock1 = Probe.BlockMask == 0 && Probe.BlockProbe | ||
- Probe.IndCallPro | ||
- if (!Probe.InlineTr | ||
- for (uint32_t Node : Probe.InlineTree | ||
- matchProbe(Probe | ||
+ for (const yaml::bolt::Pseu | ||
+ if (!ProfileProbe.I | ||
+ for (uint32_t ProfileInlineTre | ||
+ matchPseudoProbe | ||
else | ||
- matchProbe(Probe | ||
+ matchPseudoProbe | ||
} | ||
uint32_t BestMatchCount = 0; | ||
uint32_t TotalMatchCount = 0; | ||
const FlowBlock *BestMatchBlock = nullptr; | ||
- for (auto &[Block, Count] : FlowBlockMatchCo | ||
+ for (const auto &[FlowBlock, Count] : FlowBlockMatchCo | ||
TotalMatchCount += Count; | ||
- if (Count > BestMatchCount || | ||
- (Count == BestMatchCount && !BestMatchBlock) | ||
- BestMatchBlock = Block; | ||
- BestMatchCount = Count; | ||
- } | ||
+ if (Count < BestMatchCount || (Count == BestMatchCount && BestMatchBlock)) | ||
+ continue; | ||
+ BestMatchBlock = FlowBlock; | ||
+ BestMatchCount = Count; | ||
} | ||
- return {BestMatchBlock, | ||
+ return {BestMatchBlock, | ||
} | ||
}; | ||
@@ -565,17 +556,18 @@ createFlowFuncti | ||
/// is to extract as much information from the stale profile as possible. Here | ||
/// we assume that each basic block is specified via a hash value computed from | ||
/// its content and the hashes of the unchanged basic blocks stay the same | ||
-/// across different revisions of the binary. | ||
+/// across different revisions of the binary. Blocks may also have pseudo probe | ||
+/// information in the profile and the binary which is used for matching. | ||
/// Whenever there is a count in the profile with the hash corresponding to one | ||
/// of the basic blocks in the binary, the count is "matched" to the block. | ||
/// Similarly, if both the source and the target of a count in the profile are | ||
/// matched to a jump in the binary, the count is recorded in CFG. | ||
-size_t matchWeightsByHa | ||
+size_t matchWeights( | ||
BinaryContext &BC, const BinaryFunction:: | ||
const yaml::bolt::Bina | ||
HashFunction HashFunction, YAMLProfileReade | ||
- const BinaryFunction &BF, const yaml::bolt::Pseu | ||
- const YAMLProfileReade | ||
+ const BinaryFunction &BF, | ||
+ const ArrayRef<YAMLPro | ||
assert(Func.Bloc | ||
@@ -617,56 +609,55 @@ size_t matchWeightsByHa | ||
if (const BinaryBasicBlock | ||
BF.getBasicBlock | ||
Matcher.mapProbe | ||
- | ||
- // Match inline tree nodes by GUID, checksum, parent, and call site. | ||
- uint32_t ParentId = 0; | ||
- uint32_t PrevGUIDIdx = 0; | ||
- uint32_t Index = 0; | ||
- for (const yaml::bolt::Inli | ||
- uint64_t GUIDIdx = InlineTreeNode.G | ||
- if (GUIDIdx) | ||
- PrevGUIDIdx = GUIDIdx; | ||
- else | ||
- GUIDIdx = PrevGUIDIdx; | ||
- assert(GUIDIdx - 1 < YamlPD.GUID.size | ||
- assert(GUIDIdx - 1 < YamlPD.GUIDHash. | ||
- uint64_t GUID = YamlPD.GUID[GUID | ||
- uint32_t HashIdx = YamlPD.GUIDHash[ | ||
- assert(HashIdx < YamlPD.Hash.size | ||
- uint64_t Hash = YamlPD.Hash[Hash | ||
- uint32_t InlineTreeNodeId | ||
- ParentId += InlineTreeNode.P | ||
- uint32_t CallSiteProbe = InlineTreeNode.C | ||
- const MCDecodedPseudoP | ||
- if (!InlineTreeNode | ||
- auto It = TopLevelGUIDToIn | ||
- if (It != TopLevelGUIDToIn | ||
- Cur = It->second; | ||
- } else if (const MCDecodedPseudoP | ||
- Matcher.getInlin | ||
- for (const MCDecodedPseudoP | ||
- Parent->getChild | ||
- if (Child.Guid == GUID) { | ||
- if (std::get<1>(Chi | ||
- Cur = &Child; | ||
- break; | ||
- } | ||
- } | ||
- } | ||
- if (Cur && Decoder->getFunc | ||
- Matcher.mapInlin | ||
- } | ||
} | ||
Matcher.init(Blo | ||
- // Index in yaml profile => corresponding (matched) block | ||
- DenseMap<uint64_ | ||
- // Match blocks from the profile to the blocks in CFG | ||
+ using FlowBlockTy = | ||
+ std::pair<const FlowBlock *, const yaml::bolt::Bina | ||
+ using ProfileBlockMatc | ||
+ // Binary profile => block index => matched block + its block profile | ||
+ DenseMap<const yaml::bolt::Bina | ||
+ MatchedBlocks; | ||
+ | ||
+ // Map of FlowBlock and matching method. | ||
+ DenseMap<const FlowBlock *, StaleMatcher::Ma | ||
+ | ||
+ auto addMatchedBlock = | ||
+ [&](std::pair<co | ||
+ const yaml::bolt::Bina | ||
+ const yaml::bolt::Bina | ||
+ const auto &[MatchedBlock, Method] = BlockMethod; | ||
+ if (!MatchedBlock) | ||
+ return; | ||
+ // Don't override earlier matches | ||
+ if (MatchedFlowBloc | ||
+ return; | ||
+ MatchedFlowBlock | ||
+ MatchedBlocks[&Y | ||
+ }; | ||
+ | ||
+ // Match blocks from the profile to the blocks in CFG by strict hash. | ||
+ for (const yaml::bolt::Bina | ||
+ // Update matching stats. | ||
+ ++BC.Stats.NumSt | ||
+ BC.Stats.StaleSa | ||
+ | ||
+ assert(YamlBB.Ha | ||
+ BlendedBlockHash | ||
+ addMatchedBlock( | ||
+ } | ||
+ // Match blocks from the profile to the blocks in CFG by pseudo probes. | ||
+ for (const auto &[InlineNodeMap, | ||
+ for (const yaml::bolt::Bina | ||
+ if (!BB.PseudoProbe | ||
+ addMatchedBlock( | ||
+ YamlBP, BB); | ||
+ } | ||
+ // Match blocks from the profile to the blocks in CFG with loose methods. | ||
for (const yaml::bolt::Bina | ||
assert(YamlBB.Ha | ||
BlendedBlockHash | ||
- const FlowBlock *MatchedBlock = nullptr; | ||
std::string CallHashStr = hashBlockCalls(I | ||
uint64_t CallHash = 0; | ||
if (!CallHashStr.em | ||
@@ -677,103 +668,103 @@ size_t matchWeightsByHa | ||
else | ||
llvm_unreachable | ||
} | ||
- StaleMatcher::Ma | ||
- std::tie(Matched | ||
- YamlHash, CallHash, YamlBB.PseudoPro | ||
+ auto [MatchedBlock, Method] = Matcher.matchBlo | ||
if (MatchedBlock == nullptr && YamlBB.Index == 0) { | ||
MatchedBlock = Blocks[0]; | ||
// Report as loose match | ||
Method = StaleMatcher::MA | ||
} | ||
- if (MatchedBlock != nullptr) { | ||
- const BinaryBasicBlock | ||
- MatchedBlocks[Ya | ||
+ if (!MatchedBlock) { | ||
+ LLVM_DEBUG(dbgs( | ||
+ << ")" << " with hash " << Twine::utohexstr | ||
+ << "\n"); | ||
+ continue; | ||
+ } | ||
+ addMatchedBlock( | ||
+ } | ||
+ | ||
+ // Match jumps from the profile to the jumps from CFG | ||
+ std::vector<uint | ||
+ std::vector<uint | ||
+ | ||
+ for (const auto &[YamlBF, MatchMap] : MatchedBlocks) { | ||
+ for (const auto &[YamlBBIdx, FlowBlockProfile | ||
+ const auto &[MatchedBlock, YamlBB] = FlowBlockProfile | ||
+ StaleMatcher::Ma | ||
BlendedBlockHash | ||
- LLVM_DEBUG(dbgs( | ||
- << " with hash " << Twine::utohexstr | ||
+ LLVM_DEBUG(dbgs( | ||
+ << " with hash " << Twine::utohexstr | ||
<< " to BB (index = " << MatchedBlock->In | ||
<< " with hash " << Twine::utohexstr | ||
<< "\n"); | ||
+ uint64_t ExecCount = YamlBB->ExecCoun | ||
// Update matching stats accounting for the matched block. | ||
switch (Method) { | ||
case StaleMatcher::MA | ||
++BC.Stats.NumEx | ||
- BC.Stats.ExactMa | ||
+ BC.Stats.ExactMa | ||
LLVM_DEBUG(dbgs( | ||
break; | ||
case StaleMatcher::MA | ||
++BC.Stats.NumPs | ||
- BC.Stats.PseudoP | ||
+ BC.Stats.PseudoP | ||
LLVM_DEBUG(dbgs( | ||
break; | ||
case StaleMatcher::MA | ||
++BC.Stats.NumPs | ||
- BC.Stats.PseudoP | ||
+ BC.Stats.PseudoP | ||
LLVM_DEBUG(dbgs( | ||
break; | ||
case StaleMatcher::MA | ||
++BC.Stats.NumCa | ||
- BC.Stats.CallMat | ||
+ BC.Stats.CallMat | ||
LLVM_DEBUG(dbgs( | ||
break; | ||
case StaleMatcher::MA | ||
++BC.Stats.NumLo | ||
- BC.Stats.LooseMa | ||
+ BC.Stats.LooseMa | ||
LLVM_DEBUG(dbgs( | ||
break; | ||
case StaleMatcher::NO | ||
LLVM_DEBUG(dbgs( | ||
} | ||
- if (YamlBB.NumInstr | ||
- ++BC.Stats.NumSt | ||
- } else { | ||
- LLVM_DEBUG( | ||
- dbgs() << "Couldn't match yaml block (bid = " << YamlBB.Index << ")" | ||
- << " with hash " << Twine::utohexstr | ||
} | ||
- // Update matching stats. | ||
- ++BC.Stats.NumSt | ||
- BC.Stats.StaleSa | ||
- } | ||
- | ||
- // Match jumps from the profile to the jumps from CFG | ||
- std::vector<uint | ||
- std::vector<uint | ||
- for (const yaml::bolt::Bina | ||
- for (const yaml::bolt::Succ | ||
- if (YamlSI.Count == 0) | ||
- continue; | ||
+ for (const yaml::bolt::Bina | ||
+ for (const yaml::bolt::Succ | ||
+ if (YamlSI.Count == 0) | ||
+ continue; | ||
- // Try to find the jump for a given (src, dst) pair from the profile and | ||
- // assign the jump weight based on the profile count | ||
- const uint64_t SrcIndex = YamlBB.Index; | ||
- const uint64_t DstIndex = YamlSI.Index; | ||
- | ||
- const FlowBlock *MatchedSrcBlock | ||
- const FlowBlock *MatchedDstBlock | ||
- | ||
- if (MatchedSrcBlock | ||
- // Find a jump between the two blocks | ||
- FlowJump *Jump = nullptr; | ||
- for (FlowJump *SuccJump : MatchedSrcBlock- | ||
- if (SuccJump->Targe | ||
- Jump = SuccJump; | ||
- break; | ||
+ // Try to find the jump for a given (src, dst) pair from the profile and | ||
+ // assign the jump weight based on the profile count | ||
+ const uint64_t SrcIndex = YamlBB.Index; | ||
+ const uint64_t DstIndex = YamlSI.Index; | ||
+ | ||
+ const FlowBlock *MatchedSrcBlock | ||
+ const FlowBlock *MatchedDstBlock | ||
+ | ||
+ if (MatchedSrcBlock | ||
+ // Find a jump between the two blocks | ||
+ FlowJump *Jump = nullptr; | ||
+ for (FlowJump *SuccJump : MatchedSrcBlock- | ||
+ if (SuccJump->Targe | ||
+ Jump = SuccJump; | ||
+ break; | ||
+ } | ||
+ } | ||
+ // Assign the weight, if the corresponding jump is found | ||
+ if (Jump != nullptr) { | ||
+ Jump->Weight = YamlSI.Count; | ||
+ Jump->HasUnknown | ||
} | ||
} | ||
- // Assign the weight, if the corresponding jump is found | ||
- if (Jump != nullptr) { | ||
- Jump->Weight = YamlSI.Count; | ||
- Jump->HasUnknown | ||
- } | ||
+ // Assign the weight for the src block, if it is found | ||
+ if (MatchedSrcBlock | ||
+ OutWeight[Matche | ||
+ // Assign the weight for the dst block, if it is found | ||
+ if (MatchedDstBlock | ||
+ InWeight[Matched | ||
} | ||
- // Assign the weight for the src block, if it is found | ||
- if (MatchedSrcBlock | ||
- OutWeight[Matche | ||
- // Assign the weight for the dst block, if it is found | ||
- if (MatchedDstBlock | ||
- InWeight[Matched | ||
} | ||
} | ||
@@ -787,7 +778,7 @@ size_t matchWeightsByHa | ||
Block.Weight = std::max(OutWeig | ||
} | ||
- return MatchedBlocks.si | ||
+ return MatchedBlocks[&Y | ||
} | ||
/// The function finds all blocks that are (i) reachable from the Entry block | ||
@@ -1005,7 +996,8 @@ void assignProfile(Bi | ||
} | ||
bool YAMLProfileReade | ||
- BinaryFunction &BF, const yaml::bolt::Bina | ||
+ BinaryFunction &BF, const yaml::bolt::Bina | ||
+ const ArrayRef<ProbeMa | ||
NamedRegionTimer | ||
"Rewrite passes", opts::TimeRewrit | ||
@@ -1029,9 +1021,8 @@ bool YAMLProfileReade | ||
// Match as many block/jump counts from the stale profile as possible | ||
size_t MatchedBlocks = | ||
- matchWeightsByHa | ||
- YamlBP.Header.Ha | ||
- YamlBP.PseudoPro | ||
+ matchWeights(BF. | ||
+ YamlBP.Header.Ha | ||
// Adjust the flow function by marking unreachable blocks Unlikely so that | ||
// they don't get any counts assigned. |
@@ -241,9 +241,7 @@ bool YAMLProfileReade | ||
BB.setExecutionC | ||
for (const yaml::bolt::Call | ||
- BinaryFunction *Callee = YamlCSI.DestId < YamlProfileToFun | ||
- ? YamlProfileToFun | ||
- : nullptr; | ||
+ BinaryFunction *Callee = YamlProfileToFun | ||
bool IsFunction = Callee ? true : false; | ||
MCSymbol *CalleeSymbol = nullptr; | ||
if (IsFunction) | ||
@@ -354,8 +352,13 @@ bool YAMLProfileReade | ||
if (YamlBF.NumBasic | ||
++BC.Stats.NumSt | ||
- if (opts::InferStal | ||
- ProfileMatched = true; | ||
+ if (!opts::InferSta | ||
+ return false; | ||
+ ArrayRef<ProbeMa | ||
+ auto BFIt = BFToProbeMatchSp | ||
+ if (BFIt != BFToProbeMatchSp | ||
+ ProbeMatchSpecs = BFIt->second; | ||
+ ProfileMatched = inferStaleProfil | ||
} | ||
if (ProfileMatched) | ||
BF.markProfiled( | ||
@@ -590,6 +593,101 @@ size_t YAMLProfileReade | ||
return MatchedWithCallG | ||
} | ||
+size_t YAMLProfileReade | ||
+ const MCPseudoProbeDec | ||
+ const std::vector<yaml | ||
+ const MCDecodedPseudoP | ||
+ // Match inline tree nodes by GUID, checksum, parent, and call site. | ||
+ for (const auto &[InlineTreeNode | ||
+ llvm::enumerate( | ||
+ uint64_t GUID = InlineTreeNode.G | ||
+ uint64_t Hash = InlineTreeNode.H | ||
+ uint32_t ParentId = InlineTreeNode.P | ||
+ uint32_t CallSiteProbe = InlineTreeNode.C | ||
+ const MCDecodedPseudoP | ||
+ if (!InlineTreeNode | ||
+ Cur = Root; | ||
+ } else if (const MCDecodedPseudoP | ||
+ getInlineTreeNod | ||
+ for (const MCDecodedPseudoP | ||
+ Parent->getChild | ||
+ if (Child.Guid == GUID) { | ||
+ if (std::get<1>(Chi | ||
+ Cur = &Child; | ||
+ break; | ||
+ } | ||
+ } | ||
+ } | ||
+ // Don't match nodes if the profile is stale (mismatching binary FuncHash | ||
+ // and YAML Hash) | ||
+ if (Cur && Decoder.getFuncD | ||
+ mapInlineTreeNod | ||
+ } | ||
+ 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 | ||
+decodeYamlInlin | ||
+ std::vector<yaml | ||
+ uint32_t ParentId = 0; | ||
+ uint32_t PrevGUIDIdx = 0; | ||
+ for (yaml::bolt::Inl | ||
+ uint32_t GUIDIdx = InlineTreeNode.G | ||
+ if (GUIDIdx != UINT32_MAX) | ||
+ PrevGUIDIdx = GUIDIdx; | ||
+ else | ||
+ GUIDIdx = PrevGUIDIdx; | ||
+ uint32_t HashIdx = YamlPD.GUIDHashI | ||
+ ParentId += InlineTreeNode.P | ||
+ InlineTreeNode.G | ||
+ InlineTreeNode.H | ||
+ InlineTreeNode.P | ||
+ } | ||
+ return YamlInlineTree; | ||
+} | ||
+ | ||
+size_t YAMLProfileReade | ||
+ if (!opts::StaleMat | ||
+ return 0; | ||
+ | ||
+ const MCPseudoProbeDec | ||
+ const yaml::bolt::Prof | ||
+ | ||
+ // 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( | ||
+ // BF is preliminary name-matched function to YamlBF | ||
+ // MatchedBF is final matched function | ||
+ BinaryFunction *MatchedBF = YamlProfileToFun | ||
+ if (!BF) | ||
+ BF = MatchedBF; | ||
+ if (!BF) | ||
+ continue; | ||
+ uint64_t GUID = BF->getGUID(); | ||
+ if (!GUID) | ||
+ continue; | ||
+ auto It = TopLevelGUIDToIn | ||
+ if (It == TopLevelGUIDToIn | ||
+ continue; | ||
+ const MCDecodedPseudoP | ||
+ assert(Node && "Malformed TopLevelGUIDToIn | ||
+ auto &MatchSpecs = BFToProbeMatchSp | ||
+ auto &InlineTreeMap = | ||
+ MatchSpecs.empla | ||
+ std::vector<yaml | ||
+ decodeYamlInline | ||
+ // Erase unsuccessful match | ||
+ if (!InlineTreeMap. | ||
+ MatchSpecs.pop_b | ||
+ } | ||
+ | ||
+ return 0; | ||
+} | ||
+ | ||
size_t YAMLProfileReade | ||
if (opts::NameSimil | ||
return 0; | ||
@@ -646,11 +744,7 @@ size_t YAMLProfileReade | ||
// equal number of blocks. | ||
if (NamespaceToProf | ||
continue; | ||
- auto NamespaceToBFsIt | ||
- if (NamespaceToBFsI | ||
- NamespaceToBFs[N | ||
- else | ||
- NamespaceToBFsIt | ||
+ NamespaceToBFs[N | ||
} | ||
// Iterates through all profiled functions and binary functions belonging to | ||
@@ -710,7 +804,7 @@ Error YAMLProfileReade | ||
break; | ||
} | ||
} | ||
- YamlProfileToFun | ||
+ YamlProfileToFun | ||
// Computes hash for binary functions. | ||
if (opts::MatchProf | ||
@@ -743,6 +837,7 @@ Error YAMLProfileReade | ||
const size_t MatchedWithLTOCo | ||
const size_t MatchedWithCallG | ||
const size_t MatchedWithNameS | ||
+ const size_t MatchedWithPseud | ||
for (auto [YamlBF, BF] : llvm::zip_equal( | ||
if (!YamlBF.Used && BF && !ProfiledFunctio | ||
@@ -772,12 +867,7 @@ Error YAMLProfileReade | ||
NormalizeByCalls | ||
uint64_t NumUnused = 0; | ||
for (yaml::bolt::Bin | ||
- if (YamlBF.Id >= YamlProfileToFun | ||
- // Such profile was ignored. | ||
- ++NumUnused; | ||
- continue; | ||
- } | ||
- if (BinaryFunction *BF = YamlProfileToFun | ||
+ if (BinaryFunction *BF = YamlProfileToFun | ||
parseFunctionPro | ||
else | ||
++NumUnused; |
@@ -18,7 +18,6 @@ | ||
#include "llvm/Support/Co | ||
#include "llvm/Support/Fi | ||
#include "llvm/Support/ra | ||
-#include <queue> | ||
#undef DEBUG_TYPE | ||
#define DEBUG_TYPE "bolt-prof" | ||
@@ -61,32 +60,31 @@ const BinaryFunction *YAMLProfileWrit | ||
} | ||
std::vector<YAML | ||
-YAMLProfileWrit | ||
- const MCDecodedPseudoP | ||
+YAMLProfileWrit | ||
+ const MCPseudoProbeDec | ||
+ const MCDecodedPseudoP | ||
auto getHash = [&](const MCDecodedPseudoP | ||
return Decoder.getFuncD | ||
}; | ||
- assert(Root); | ||
- std::vector<Inli | ||
- InlineTreeNode Node{Root, Root->Guid, getHash(*Root), 0, 0}; | ||
- InlineTree.empla | ||
+ std::vector<Inli | ||
+ {InlineTreeNode{ | ||
uint32_t ParentId = 0; | ||
while (ParentId != InlineTree.size( | ||
const MCDecodedPseudoP | ||
- for (const MCDecodedPseudoP | ||
- InlineTreeNode Node{&Child, Child.Guid, getHash(Child), ParentId, | ||
- std::get<1>(Chil | ||
- InlineTree.empla | ||
- } | ||
+ for (const MCDecodedPseudoP | ||
+ InlineTree.empla | ||
+ InlineTreeNode{& | ||
+ std::get<1>(Chil | ||
++ParentId; | ||
} | ||
return InlineTree; | ||
} | ||
-std::tuple<yaml | ||
+std::tuple<yaml | ||
+ YAMLProfileWrite | ||
YAMLProfileWrite | ||
- yaml::bolt::Pseu | ||
+ yaml::bolt::Prof | ||
InlineTreeDesc InlineTree; | ||
for (const MCDecodedPseudoP | ||
@@ -125,7 +123,7 @@ YAMLProfileWrite | ||
Desc.GUID.emplac | ||
InlineTree.GUIDI | ||
uint64_t Hash = Decoder.getFuncD | ||
- Desc.GUIDHash.em | ||
+ Desc.GUIDHashIdx | ||
} | ||
return {Desc, InlineTree}; | ||
@@ -176,39 +174,35 @@ YAMLProfileWrite | ||
else | ||
YamlBPI.InlineTr | ||
handleMask(BPI.B | ||
- // Assume BlockMask == 1 if no other probes are set | ||
- if (YamlBPI.BlockMa | ||
- YamlBPI.IndCallP | ||
- YamlBPI.BlockMas | ||
} | ||
return YamlProbes; | ||
} | ||
-std::tuple<std: | ||
+std::tuple<std: | ||
YAMLProfileWrite | ||
YAMLProfileWrite | ||
const InlineTreeDesc &InlineTree, | ||
uint64_t GUID) { | ||
DenseMap<const MCDecodedPseudoP | ||
- std::vector<yaml | ||
+ std::vector<yaml | ||
auto It = InlineTree.TopLe | ||
if (It == InlineTree.TopLe | ||
return {YamlInlineTree, | ||
const MCDecodedPseudoP | ||
- assert(Root); | ||
+ assert(Root && "Malformed TopLevelGUIDToIn | ||
uint32_t Index = 0; | ||
uint32_t PrevParent = 0; | ||
uint32_t PrevGUIDIdx = 0; | ||
- for (const auto &Node : getInlineTree(De | ||
+ for (const auto &Node : collectInlineTre | ||
InlineTreeNodeId | ||
auto GUIDIdxIt = InlineTree.GUIDI | ||
- assert(GUIDIdxIt | ||
- uint32_t GUIDIdx = GUIDIdxIt->secon | ||
+ assert(GUIDIdxIt | ||
+ uint32_t GUIDIdx = GUIDIdxIt->secon | ||
if (GUIDIdx == PrevGUIDIdx) | ||
- GUIDIdx = 0; | ||
+ GUIDIdx = UINT32_MAX; | ||
else | ||
PrevGUIDIdx = GUIDIdx; | ||
- YamlInlineTree.e | ||
+ YamlInlineTree.e | ||
Node.ParentId - PrevParent, Node.InlineSite, | ||
PrevParent = Node.ParentId; | ||
} |
@@ -12,6 +12,7 @@ | ||
#include "bolt/Passes/All | ||
#include "bolt/Passes/Asm | ||
#include "bolt/Passes/CMO | ||
+#include "bolt/Passes/Con | ||
#include "bolt/Passes/Fix | ||
#include "bolt/Passes/Fix | ||
#include "bolt/Passes/Fra | ||
@@ -373,6 +374,8 @@ Error BinaryFunctionPa | ||
if (opts::PrintProf | ||
Manager.register | ||
+ Manager.register | ||
+ | ||
Manager.register | ||
Manager.register |
@@ -1362,7 +1362,7 @@ void DWARFRewriter::u | ||
Die.getTag() == dwarf::DW_TAG_co | ||
if (opts::Verbosity | ||
errs() << "BOLT-WARNING: cannot update ranges for DIE in Unit offset 0x" | ||
- << Unit.getOffset() | ||
+ << Twine::utohexstr | ||
} | ||
} | ||
@@ -526,11 +526,9 @@ Error RewriteInstance: | ||
NextAvailableOff | ||
Phdr.p_offset + Phdr.p_filesz); | ||
- BC->SegmentMapIn | ||
- Phdr.p_memsz, | ||
- Phdr.p_offset, | ||
- Phdr.p_filesz, | ||
- Phdr.p_align}; | ||
+ BC->SegmentMapIn | ||
+ Phdr.p_vaddr, Phdr.p_memsz, Phdr.p_offset, | ||
+ Phdr.p_filesz, Phdr.p_align, ((Phdr.p_flags & ELF::PF_X) != 0)}; | ||
if (BC->TheTriple-> | ||
Phdr.p_vaddr >= BinaryContext::K | ||
BC->IsLinuxKerne | ||
@@ -791,9 +789,44 @@ void RewriteInstance: | ||
BinarySection Section(*BC, *cantFail(Sym.ge | ||
return Section.isAlloca | ||
}; | ||
+ auto checkSymbolInSec | ||
+ // Sometimes, we encounter symbols with addresses outside their section. If | ||
+ // such symbols happen to fall into another section, they can interfere with | ||
+ // disassembly. Notably, this occurs with AArch64 marker symbols ($d and $t) | ||
+ // that belong to .eh_frame, but end up pointing into .text. | ||
+ // As a workaround, we ignore all symbols that lie outside their sections. | ||
+ auto Section = cantFail(S.Symbo | ||
+ | ||
+ // Accept all absolute symbols. | ||
+ if (Section == InputFile->secti | ||
+ return true; | ||
+ | ||
+ uint64_t SecStart = Section->getAddr | ||
+ uint64_t SecEnd = SecStart + Section->getSize | ||
+ uint64_t SymEnd = S.Address + ELFSymbolRef(S.S | ||
+ if (S.Address >= SecStart && SymEnd <= SecEnd) | ||
+ return true; | ||
+ | ||
+ auto SymType = cantFail(S.Symbo | ||
+ // Skip warnings for common benign cases. | ||
+ if (opts::Verbosity | ||
+ return false; // E.g. ELF::STT_TLS. | ||
+ | ||
+ auto SymName = S.Symbol.getName | ||
+ auto SecName = cantFail(S.Symbo | ||
+ BC->errs() << "BOLT-WARNING: ignoring symbol " | ||
+ << (SymName ? *SymName : "[unnamed]") << " at 0x" | ||
+ << Twine::utohexstr | ||
+ << (SecName ? *SecName : "[unnamed]") << "\n"; | ||
+ | ||
+ return false; | ||
+ }; | ||
for (const SymbolRef &Symbol : InputFile->symbo | ||
- if (isSymbolInMemor | ||
- SortedSymbols.pu | ||
+ if (isSymbolInMemor | ||
+ SymbolInfo SymInfo{cantFail | ||
+ if (checkSymbolInSe | ||
+ SortedSymbols.pu | ||
+ } | ||
auto CompareSymbols = [this](const SymbolInfo &A, const SymbolInfo &B) { | ||
if (A.Address != B.Address) | ||
@@ -1533,7 +1566,7 @@ void RewriteInstance: | ||
MCSymbol *Symbol = Rel->Symbol; | ||
if (!Symbol) { | ||
- if (!BC->isAArch64( | ||
+ if (BC->isRISCV() || !Rel->Addend || !Rel->isIRelativ | ||
return; | ||
// IFUNC trampoline without symbol | ||
@@ -4247,7 +4280,6 @@ void RewriteInstance: | ||
<< "command line:"; | ||
for (int I = 0; I < Argc; ++I) | ||
DescOS << " " << Argv[I]; | ||
- DescOS.flush(); | ||
// Encode as GNU GOLD VERSION so it is easily printable by 'readelf -n' | ||
const std::string BoltInfo = | ||
@@ -4270,7 +4302,6 @@ void RewriteInstance: | ||
raw_string_ostre | ||
BAT->write(*BC, DescOS); | ||
- DescOS.flush(); | ||
const std::string BoltInfo = | ||
BinarySection::e |
@@ -314,7 +314,6 @@ std::string InstrumentationR | ||
} | ||
// Our string table lives immediately after descriptions vector | ||
OS << Summary->StringT | ||
- OS.flush(); | ||
return TablesStr; | ||
} |
@@ -175,6 +175,10 @@ cl::opt<std::str | ||
cl::desc("save recorded profile to a file"), | ||
cl::cat(BoltOutp | ||
+cl::opt<bool> ShowDensity("sho | ||
+ cl::desc("show profile density details"), | ||
+ cl::Optional, cl::cat(Aggregat | ||
+ | ||
cl::opt<bool> SplitEH("split-e | ||
cl::Hidden, cl::cat(BoltOptC | ||
@@ -0,0 +1,56 @@ | ||
+--- !ELF | ||
+FileHeader: | ||
+ Class: ELFCLASS64 | ||
+ Data: ELFDATA2LSB | ||
+ Type: ET_EXEC | ||
+ Machine: EM_AARCH64 | ||
+ Entry: 0x2a0000 | ||
+ProgramHeaders: | ||
+ - Type: PT_PHDR | ||
+ Flags: [ PF_R ] | ||
+ VAddr: 0x40 | ||
+ Align: 0x8 | ||
+ FileSize: 0xa8 | ||
+ MemSize: 0xa8 | ||
+ Offset: 0x40 | ||
+ - Type: PT_LOAD | ||
+ Flags: [ PF_R ] | ||
+ VAddr: 0x0 | ||
+ Align: 0x10000 | ||
+ FileSize: 0xf8 | ||
+ MemSize: 0xf8 | ||
+ Offset: 0x0 | ||
+ - Type: PT_LOAD | ||
+ Flags: [ PF_X, PF_R ] | ||
+ VAddr: 0x2a0000 | ||
+ Align: 0x10000 | ||
+ FirstSec: .text | ||
+ LastSec: .ignored | ||
+Sections: | ||
+ - Name: .text | ||
+ Type: SHT_PROGBITS | ||
+ Flags: [ SHF_ALLOC, SHF_EXECINSTR ] | ||
+ Address: 0x2a0000 | ||
+ AddressAlign: 0x4 | ||
+ Content: 400580d2c0035fd6 | ||
+ - Name: .ignored | ||
+ Type: SHT_PROGBITS | ||
+ Flags: [ SHF_ALLOC ] | ||
+ Address: 0x2a0008 | ||
+ AddressAlign: 0x8 | ||
+ Size: 0x8 | ||
+ - Name: .eh_frame | ||
+ Type: SHT_PROGBITS | ||
+ Flags: [ SHF_ALLOC ] | ||
+ Address: 0x2a0010 | ||
+ AddressAlign: 0x8 | ||
+ Content: 1000000000000000 | ||
+Symbols: | ||
+ - Name: func | ||
+ Section: .text | ||
+ Value: 0x2a0000 | ||
+ Size: 0x8 | ||
+ - Name: '$d.42' | ||
+ Section: .ignored | ||
+ Value: 0x2a0004 | ||
+... |
@@ -0,0 +1,49 @@ | ||
+## Check that llvm-bolt will not unnecessarily relax ADR instruction. | ||
+ | ||
+# RUN: llvm-mc -filetype=obj -triple aarch64-unknown- | ||
+# RUN: %clang %cflags %t.o -o %t.exe -Wl,-q -static | ||
+# RUN: llvm-bolt %t.exe -o %t.bolt --split-function | ||
+# RUN: llvm-objdump -d --disassemble-sy | ||
+# RUN: llvm-objdump -d --disassemble-sy | ||
+# RUN: | FileCheck --check-prefix=C | ||
+ | ||
+## ADR below references its containing function that is split. But ADR is always | ||
+## in the main fragment, thus there is no need to relax it. | ||
+ .text | ||
+ .globl _start | ||
+ .type _start, %function | ||
+_start: | ||
+# CHECK: <_start>: | ||
+ .cfi_startproc | ||
+ adr x1, _start | ||
+# CHECK-NOT: adrp | ||
+# CHECK: adr | ||
+ cmp x1, x11 | ||
+ b.hi .L1 | ||
+ mov x0, #0x0 | ||
+.L1: | ||
+ ret x30 | ||
+ .cfi_endproc | ||
+.size _start, .-_start | ||
+ | ||
+ | ||
+## In foo, ADR is in the split fragment but references the main one. Thus, it | ||
+## needs to be relaxed into ADRP + ADD. | ||
+ .globl foo | ||
+ .type foo, %function | ||
+foo: | ||
+ .cfi_startproc | ||
+ cmp x1, x11 | ||
+ b.hi .L2 | ||
+ mov x0, #0x0 | ||
+.L2: | ||
+# CHECK-FOO: <foo.cold.0>: | ||
+ adr x1, foo | ||
+# CHECK-FOO: adrp | ||
+# CHECK-FOO-NEXT: add | ||
+ ret x30 | ||
+ .cfi_endproc | ||
+.size foo, .-foo | ||
+ | ||
+## Force relocation mode. | ||
+ .reloc 0, R_AARCH64_NONE |
@@ -0,0 +1,92 @@ | ||
+## Check that llvm-bolt successfully relaxes branches for compact (<128MB) code | ||
+## model. | ||
+ | ||
+# RUN: llvm-mc -filetype=obj -triple aarch64-unknown- | ||
+# RUN: link_fdata %s %t.o %t.fdata | ||
+# RUN: llvm-strip --strip-unneeded | ||
+# RUN: %clang %cflags %t.o -o %t.exe -Wl,-q -static | ||
+# RUN: llvm-bolt %t.exe -o %t.bolt --data %t.fdata --split-function | ||
+# RUN: --keep-nops --compact-code-m | ||
+# RUN: llvm-objdump -d \ | ||
+# RUN: --disassemble-sy | ||
+# RUN: | FileCheck %s | ||
+# RUN: llvm-nm -nS %t.bolt | FileCheck %s --check-prefix=C | ||
+ | ||
+## Fragments of _start and foo will be separated by large_function which is over | ||
+## 1MB in size - larger than all conditional branches can cover requiring branch | ||
+## relaxation. | ||
+ | ||
+# CHECK-NM: _start | ||
+# CHECK-NM: foo | ||
+# CHECK-NM: 0000000000124f84 | ||
+# CHECK-NM: _start.cold.0 | ||
+# CHECK-NM: foo.cold.0 | ||
+ | ||
+ .text | ||
+ .globl _start | ||
+ .type _start, %function | ||
+_start: | ||
+# CHECK: <_start>: | ||
+# FDATA: 0 [unknown] 0 1 _start 0 0 100 | ||
+ .cfi_startproc | ||
+ cmp x0, 1 | ||
+ b.eq .L0 | ||
+# CHECK: b.eq | ||
+# CHECK-NEXT: b | ||
+# CHECK-NEXT: b | ||
+ | ||
+ bl large_function | ||
+.L0: | ||
+ ret x30 | ||
+ .cfi_endproc | ||
+.size _start, .-_start | ||
+ | ||
+## Check that long branch in foo() is reused during relaxation. I.e. we should | ||
+## see just one branch to the cold fragment. | ||
+ | ||
+ .globl foo | ||
+ .type foo, %function | ||
+foo: | ||
+# CHECK: <foo>: | ||
+# FDATA: 0 [unknown] 0 1 foo 0 0 100 | ||
+ .cfi_startproc | ||
+ cmp x0, 0 | ||
+.T0: | ||
+ b.eq .ERROR | ||
+# CHECK: b {{.*}} <foo.cold.0> | ||
+# CHECK-NOT: b {{.*}} <foo.cold.0> | ||
+# FDATA: 1 foo #.T0# 1 foo #.T1# 0 100 | ||
+.T1: | ||
+ bl large_function | ||
+ cmp x0, 1 | ||
+.T2: | ||
+ b.eq .ERROR | ||
+# FDATA: 1 foo #.T2# 1 foo #.T3# 0 100 | ||
+.T3: | ||
+ mov x1, x0 | ||
+ mov x0, 0 | ||
+ ret x30 | ||
+ | ||
+# CHECK: <foo.cold.0>: | ||
+# CHECK-NEXT: mov x0, #0x1 | ||
+# CHECK-NEXT: ret | ||
+.ERROR: | ||
+ mov x0, 1 | ||
+ ret x30 | ||
+ .cfi_endproc | ||
+.size foo, .-foo | ||
+ | ||
+ .globl large_function | ||
+ .type large_function, %function | ||
+large_function: | ||
+# FDATA: 0 [unknown] 0 1 large_function 0 0 100 | ||
+ .cfi_startproc | ||
+ .rept 300000 | ||
+ nop | ||
+ .endr | ||
+ ret x30 | ||
+ .cfi_endproc | ||
+.size large_function, .-large_function | ||
+ | ||
+## Force relocation mode. | ||
+ .reloc 0, R_AARCH64_NONE |
@@ -8,15 +8,15 @@ | ||
# RUN: %clang %cflags -fPIC -pie %t.o -o %t.rela.exe -nostdlib \ | ||
# RUN: -Wl,-q -Wl,-z,notext | ||
# RUN: llvm-bolt %t.rela.exe -o %t.rela.bolt --use-old-text=0 | ||
-# RUN: llvm-objdump -j .text -d --show-all-symbo | ||
+# RUN: llvm-objdump -j .text -d -z --show-all-symbo | ||
# RUN: llvm-readelf -rsW %t.rela.bolt | FileCheck --check-prefix=E | ||
// .relr.dyn | ||
# RUN: %clang %cflags -fPIC -pie %t.o -o %t.relr.exe -nostdlib \ | ||
# RUN: -Wl,-q -Wl,-z,notext -Wl,--pack-dyn-r | ||
# RUN: llvm-objcopy --remove-section | ||
# RUN: llvm-bolt %t.relr.exe -o %t.relr.bolt --use-old-text=0 | ||
-# RUN: llvm-objdump -j .text -d --show-all-symbo | ||
-# RUN: llvm-objdump -j .text -d %t.relr.bolt | \ | ||
+# RUN: llvm-objdump -j .text -d -z --show-all-symbo | ||
+# RUN: llvm-objdump -j .text -d -z %t.relr.bolt | \ | ||
# RUN: FileCheck %s --check-prefix=A | ||
# RUN: llvm-readelf -rsW %t.relr.bolt | FileCheck --check-prefix=R | ||
# RUN: llvm-readelf -SW %t.relr.bolt | FileCheck --check-prefix=R |
@@ -1,62 +0,0 @@ | ||
-// This test checks that IFUNC trampoline is properly recognised by BOLT | ||
- | ||
-// With -O0 indirect call is performed on IPLT trampoline. IPLT trampoline | ||
-// has IFUNC symbol. | ||
-// RUN: %clang %cflags -nostdlib -O0 -no-pie %s -fuse-ld=lld \ | ||
-// RUN: -o %t.O0.exe -Wl,-q | ||
-// RUN: llvm-bolt %t.O0.exe -o %t.O0.bolt.exe \ | ||
-// RUN: --print-disasm --print-only=_st | ||
-// RUN: FileCheck --check-prefix=C | ||
-// RUN: llvm-readelf -aW %t.O0.bolt.exe | \ | ||
-// RUN: FileCheck --check-prefix=R | ||
- | ||
-// Non-pie static executable doesn't generate PT_DYNAMIC, check relocation | ||
-// is readed successfully and IPLT trampoline has been identified by bolt. | ||
-// RUN: %clang %cflags -nostdlib -O3 %s -fuse-ld=lld -no-pie \ | ||
-// RUN: -o %t.O3_nopie.exe -Wl,-q | ||
-// RUN: llvm-readelf -l %t.O3_nopie.exe | \ | ||
-// RUN: FileCheck --check-prefix=N | ||
-// RUN: llvm-bolt %t.O3_nopie.exe -o %t.O3_nopie.bolt | ||
-// RUN: --print-disasm --print-only=_st | ||
-// RUN: FileCheck --check-prefix=C | ||
-// RUN: llvm-readelf -aW %t.O3_nopie.bolt | ||
-// RUN: FileCheck --check-prefix=R | ||
- | ||
-// With -O3 direct call is performed on IPLT trampoline. IPLT trampoline | ||
-// doesn't have associated symbol. The ifunc symbol has the same address as | ||
-// IFUNC resolver function. | ||
-// RUN: %clang %cflags -nostdlib -O3 %s -fuse-ld=lld -fPIC -pie \ | ||
-// RUN: -o %t.O3_pie.exe -Wl,-q | ||
-// RUN: llvm-bolt %t.O3_pie.exe -o %t.O3_pie.bolt.e | ||
-// RUN: --print-disasm --print-only=_st | ||
-// RUN: FileCheck --check-prefix=C | ||
-// RUN: llvm-readelf -aW %t.O3_pie.bolt.e | ||
-// RUN: FileCheck --check-prefix=R | ||
- | ||
-// Check that IPLT trampoline located in .plt section are normally handled by | ||
-// BOLT. The gnu-ld linker doesn't use separate .iplt section. | ||
-// RUN: %clang %cflags -nostdlib -O3 %s -fuse-ld=lld -fPIC -pie \ | ||
-// RUN: -T %p/Inputs/iplt.l | ||
-// RUN: llvm-bolt %t.iplt_O3_pie.e | ||
-// RUN: --print-disasm --print-only=_st | ||
-// RUN: FileCheck --check-prefix=C | ||
-// RUN: llvm-readelf -aW %t.iplt_O3_pie.b | ||
-// RUN: FileCheck --check-prefix=R | ||
- | ||
-// NON_DYN_CHECK-NO | ||
- | ||
-// CHECK: {{(bl? "(resolver_foo|i | ||
- | ||
-// REL_CHECK: R_AARCH64_IRELAT | ||
-// REL_CHECK: [[#REL_SYMB_ADDR | ||
- | ||
-static void foo() {} | ||
-static void bar() {} | ||
- | ||
-extern int use_foo; | ||
- | ||
-static void *resolver_foo(vo | ||
- | ||
-__attribute__(( | ||
- | ||
-void _start() { ifoo(); } |
@@ -0,0 +1,49 @@ | ||
+// With -O0 indirect call is performed on IPLT trampoline. IPLT trampoline | ||
+// has IFUNC symbol. | ||
+// RUN: %clang %cflags -nostdlib -O0 -no-pie %p/../Inputs/ifu | ||
+// RUN: -o %t.O0.exe -Wl,-q | ||
+// RUN: llvm-bolt %t.O0.exe -o %t.O0.bolt.exe \ | ||
+// RUN: --print-disasm --print-only=_st | ||
+// RUN: FileCheck --check-prefix=C | ||
+// RUN: llvm-readelf -aW %t.O0.bolt.exe | \ | ||
+// RUN: FileCheck --check-prefix=R | ||
+ | ||
+// Non-pie static executable doesn't generate PT_DYNAMIC, check relocation | ||
+// is readed successfully and IPLT trampoline has been identified by bolt. | ||
+// RUN: %clang %cflags -nostdlib -O3 %p/../Inputs/ifu | ||
+// RUN: -o %t.O3_nopie.exe -Wl,-q | ||
+// RUN: llvm-readelf -l %t.O3_nopie.exe | \ | ||
+// RUN: FileCheck --check-prefix=N | ||
+// RUN: llvm-bolt %t.O3_nopie.exe -o %t.O3_nopie.bolt | ||
+// RUN: --print-disasm --print-only=_st | ||
+// RUN: FileCheck --check-prefix=C | ||
+// RUN: llvm-readelf -aW %t.O3_nopie.bolt | ||
+// RUN: FileCheck --check-prefix=R | ||
+ | ||
+// With -O3 direct call is performed on IPLT trampoline. IPLT trampoline | ||
+// doesn't have associated symbol. The ifunc symbol has the same address as | ||
+// IFUNC resolver function. | ||
+// RUN: %clang %cflags -nostdlib -O3 %p/../Inputs/ifu | ||
+// RUN: -o %t.O3_pie.exe -Wl,-q | ||
+// RUN: llvm-bolt %t.O3_pie.exe -o %t.O3_pie.bolt.e | ||
+// RUN: --print-disasm --print-only=_st | ||
+// RUN: FileCheck --check-prefix=C | ||
+// RUN: llvm-readelf -aW %t.O3_pie.bolt.e | ||
+// RUN: FileCheck --check-prefix=R | ||
+ | ||
+// Check that IPLT trampoline located in .plt section are normally handled by | ||
+// BOLT. The gnu-ld linker doesn't use separate .iplt section. | ||
+// RUN: %clang %cflags -nostdlib -O3 %p/../Inputs/ifu | ||
+// RUN: -T %p/../Inputs/ipl | ||
+// RUN: llvm-bolt %t.iplt_O3_pie.e | ||
+// RUN: --print-disasm --print-only=_st | ||
+// RUN: FileCheck --check-prefix=C | ||
+// RUN: llvm-readelf -aW %t.iplt_O3_pie.b | ||
+// RUN: FileCheck --check-prefix=R | ||
+ | ||
+// NON_DYN_CHECK-NO | ||
+ | ||
+// CHECK: {{(bl? "(resolver_foo|i | ||
+ | ||
+// REL_CHECK: R_AARCH64_IRELAT | ||
+// REL_CHECK: [[#REL_SYMB_ADDR |
@@ -0,0 +1,27 @@ | ||
+# This test checks that tentative code layout for cold blocks always runs. | ||
+# It commonly happens when using lite mode with split functions. | ||
+ | ||
+# REQUIRES: system-linux, asserts | ||
+ | ||
+# RUN: %clang %cflags -o %t %s | ||
+# RUN: %clang %s %cflags -Wl,-q -o %t | ||
+# RUN: link_fdata --no-lbr %s %t %t.fdata | ||
+# RUN: llvm-bolt %t -o %t.bolt --data %t.fdata -split-functions | ||
+# RUN: -debug 2>&1 | FileCheck %s | ||
+ | ||
+ .text | ||
+ .globl foo | ||
+ .type foo, %function | ||
+foo: | ||
+.entry_bb: | ||
+# FDATA: 1 foo #.entry_bb# 10 | ||
+ cmp x0, #0 | ||
+ b.eq .Lcold_bb1 | ||
+ ret | ||
+.Lcold_bb1: | ||
+ ret | ||
+ | ||
+## Force relocation mode. | ||
+.reloc 0, R_AARCH64_NONE | ||
+ | ||
+# CHECK: foo{{.*}} cold tentative: {{.*}} |
@@ -0,0 +1,61 @@ | ||
+// Check that marker symbols ($d, $x) denoting data embedded in code are ignored | ||
+// if they fall outside their respective sections. | ||
+ | ||
+// RUN: yaml2obj %S/Inputs/spurio | ||
+// RUN: llvm-bolt %t.exe -o %t.bolt 2>&1 | FileCheck %s | ||
+// CHECK: 1 out of 1 functions were overwritten | ||
+// RUN: llvm-objdump -j .text -d %t.bolt | FileCheck %s -check-prefix=CH | ||
+// CHECK-DISASM: func | ||
+// CHECK-DISASM: 2a0000: d2800540 mov | ||
+// CHECK-DISASM: 2a0004: d65f03c0 ret | ||
+ | ||
+// The YAML encodes the following assembly and debug information: | ||
+ | ||
+ .text | ||
+ .globl func | ||
+ .type func, %function | ||
+func: | ||
+ mov x0, #42 | ||
+// $d.42: (symbol in .ignored, with an address in .text) | ||
+ ret | ||
+ | ||
+// .eh_frame contains minimal DWARF with a CFA operation on the `ret`. BOLT | ||
+// should ignore the spurious `$d.42`. If it doesn't, then it will stop | ||
+// disassembling after the `mov` and will fail to process the second | ||
+// DW_CFA_def_cfa_o | ||
+// | ||
+// CIE | ||
+// length: 00000010 | ||
+// CIE_id: 00000000 | ||
+// version: 01 | ||
+// augmentation: | ||
+// "zR" 7a 52 00 | ||
+// - read augmentation data | ||
+// - read FDE pointer encoding | ||
+// code_alignment_f | ||
+// data_alignment_f | ||
+// return_address_r | ||
+// | ||
+// augmentation data: | ||
+// length: 01 | ||
+// FDE pointers are absptr+sdata4 0b | ||
+// | ||
+// initial_instruct | ||
+// DW_CFA_def_cfa (31, 0): 0c 1f 00 | ||
+// | ||
+// Encoding: 10000000'0000000 | ||
+// | ||
+// FDE | ||
+// length: 00000014 | ||
+// CIE_pointer: 00000018 (backwards offset from here to CIE) | ||
+// initial_location | ||
+// address_range: 00000008 | ||
+// augmentation data: | ||
+// length: 00 | ||
+// instructions: | ||
+// DW_CFA_def_cfa_o | ||
+// DW_CFA_advance_l | ||
+// DW_CFA_def_cfa_o | ||
+// DW_CFA_nop 00 00 | ||
+// | ||
+// Encoding: 14000000'1800000 |
@@ -3,7 +3,7 @@ | ||
// RUN: %clang %cflags -Wl,-z,notext -shared -Wl,-q %s -o %t.so | ||
// RUN: llvm-bolt %t.so -o %t.so.bolt | ||
// RUN: llvm-nm -n %t.so.bolt > %t.out.txt | ||
-// RUN: llvm-objdump -dj .rodata %t.so.bolt >> %t.out.txt | ||
+// RUN: llvm-objdump -z -dj .rodata %t.so.bolt >> %t.out.txt | ||
// RUN: FileCheck %s --input-file=%t. | ||
# CHECK: w func_1 |
@@ -0,0 +1,12 @@ | ||
+// This test checks that IFUNC trampoline is properly recognised by BOLT | ||
+ | ||
+static void foo() {} | ||
+static void bar() {} | ||
+ | ||
+extern int use_foo; | ||
+ | ||
+static void *resolver_foo(vo | ||
+ | ||
+__attribute__(( | ||
+ | ||
+void _start() { ifoo(); } |
@@ -0,0 +1,132 @@ | ||
+## Ensures that a call continuation fallthrough count is set when using | ||
+## pre-aggregated perf data. | ||
+ | ||
+# RUN: %clangxx %cxxflags %s -o %t -Wl,-q -nostdlib | ||
+# RUN: link_fdata %s %t %t.pa1 PREAGG | ||
+# RUN: link_fdata %s %t %t.pa2 PREAGG2 | ||
+# RUN: link_fdata %s %t %t.pa3 PREAGG3 | ||
+# RUN: link_fdata %s %t %t.pa4 PREAGG4 | ||
+ | ||
+## Check normal case: fallthrough is not LP or secondary entry. | ||
+# RUN: llvm-strip --strip-unneeded | ||
+# RUN: llvm-bolt %t.exe --pa -p %t.pa1 -o %t.out \ | ||
+# RUN: --print-cfg --print-only=mai | ||
+ | ||
+## Check that getFallthroughsI | ||
+## call continuation | ||
+# RUN: llvm-bolt %t.exe --pa -p %t.pa2 -o %t.out2 \ | ||
+# RUN: --print-cfg --print-only=mai | ||
+ | ||
+## Check that we don't treat secondary entry points as call continuation sites. | ||
+# RUN: llvm-bolt %t --pa -p %t.pa3 -o %t.out \ | ||
+# RUN: --print-cfg --print-only=mai | ||
+ | ||
+## Check fallthrough to a landing pad case. | ||
+# RUN: llvm-bolt %t.exe --pa -p %t.pa4 -o %t.out \ | ||
+# RUN: --print-cfg --print-only=mai | ||
+ | ||
+ .globl foo | ||
+ .type foo, %function | ||
+foo: | ||
+ pushq %rbp | ||
+ movq %rsp, %rbp | ||
+ popq %rbp | ||
+Lfoo_ret: | ||
+ retq | ||
+.size foo, .-foo | ||
+ | ||
+ .globl main | ||
+ .type main, %function | ||
+main: | ||
+.Lfunc_begin0: | ||
+ .cfi_startproc | ||
+ .cfi_personality | ||
+ .cfi_lsda 27, .Lexception0 | ||
+ pushq %rbp | ||
+ movq %rsp, %rbp | ||
+ subq $0x20, %rsp | ||
+ movl $0x0, -0x4(%rbp) | ||
+ movl %edi, -0x8(%rbp) | ||
+ movq %rsi, -0x10(%rbp) | ||
+ callq puts@PLT | ||
+## Target is a call continuation | ||
+# PREAGG: B X:0 #Ltmp1# 2 0 | ||
+# CHECK: callq puts@PLT | ||
+# CHECK-NEXT: count: 2 | ||
+ | ||
+Ltmp1: | ||
+ movq -0x10(%rbp), %rax | ||
+ movq 0x8(%rax), %rdi | ||
+ movl %eax, -0x14(%rbp) | ||
+ | ||
+Ltmp4: | ||
+ cmpl $0x0, -0x14(%rbp) | ||
+ je Ltmp0 | ||
+# CHECK2: je .Ltmp0 | ||
+# CHECK2-NEXT: count: 3 | ||
+ | ||
+ movl $0xa, -0x18(%rbp) | ||
+ callq foo | ||
+## Target is a call continuation | ||
+# PREAGG: B #Lfoo_ret# #Ltmp3# 1 0 | ||
+# CHECK: callq foo | ||
+# CHECK-NEXT: count: 1 | ||
+ | ||
+## PLT call continuation fallthrough spanning the call | ||
+# PREAGG2: F #Ltmp1# #Ltmp3_br# 3 | ||
+# CHECK2: callq foo | ||
+# CHECK2-NEXT: count: 3 | ||
+ | ||
+## Target is a secondary entry point | ||
+# PREAGG3: B X:0 #Ltmp3# 2 0 | ||
+# CHECK3: callq foo | ||
+# CHECK3-NEXT: count: 0 | ||
+ | ||
+## Target is a landing pad | ||
+# PREAGG4: B X:0 #Ltmp3# 2 0 | ||
+# CHECK4: callq puts@PLT | ||
+# CHECK4-NEXT: count: 0 | ||
+ | ||
+Ltmp3: | ||
+ cmpl $0x0, -0x18(%rbp) | ||
+Ltmp3_br: | ||
+ jmp Ltmp2 | ||
+ | ||
+Ltmp2: | ||
+ movl -0x18(%rbp), %eax | ||
+ addl $-0x1, %eax | ||
+ movl %eax, -0x18(%rbp) | ||
+ jmp Ltmp3 | ||
+ jmp Ltmp4 | ||
+ jmp Ltmp1 | ||
+ | ||
+Ltmp0: | ||
+ xorl %eax, %eax | ||
+ addq $0x20, %rsp | ||
+ popq %rbp | ||
+ retq | ||
+.Lfunc_end0: | ||
+ .cfi_endproc | ||
+.size main, .-main | ||
+ | ||
+ .section .gcc_except_tabl | ||
+ .p2align 2, 0x0 | ||
+GCC_except_tabl | ||
+.Lexception0: | ||
+ .byte 255 # @LPStart Encoding = omit | ||
+ .byte 255 # @TType Encoding = omit | ||
+ .byte 1 # Call site Encoding = uleb128 | ||
+ .uleb128 .Lcst_end0-.Lcst | ||
+.Lcst_begin0: | ||
+ .uleb128 .Lfunc_begin0-.L | ||
+ .uleb128 .Lfunc_end0-.Lfu | ||
+ .uleb128 Ltmp3-.Lfunc_beg | ||
+ .byte 0 # has no landing pad | ||
+ .byte 0 # On action: cleanup | ||
+.Lcst_end0: | ||
+ .p2align 2, 0x0 | ||
+ .hidden DW.ref.__gxx_per | ||
+ .weak DW.ref.__gxx_per | ||
+ .section .data.DW.ref.__g | ||
+ .p2align 3, 0x0 | ||
+ .type DW.ref.__gxx_per |
@@ -0,0 +1,4 @@ | ||
+## Check profile discontinuity reporting | ||
+RUN: yaml2obj %p/Inputs/blarge | ||
+RUN: llvm-bolt %t.exe -o %t.out --pa -p %p/Inputs/blarge | ||
+CHECK: among the hottest 5 functions top 5% function CFG discontinuity is 100.00% |
@@ -0,0 +1,47 @@ | ||
+// Check if BOLT can process ifunc symbols from .plt section | ||
+// RUN: %clang %cflags -nostdlib -no-pie %p/../Inputs/ifu | ||
+// RUN: -o %t.exe -Wl,-q | ||
+// RUN: llvm-bolt %t.exe -o %t.bolt.exe \ | ||
+// RUN: --print-disasm --print-only=_st | ||
+// RUN: FileCheck --check-prefix=C | ||
+// RUN: llvm-readelf -aW %t.bolt.exe | \ | ||
+// RUN: FileCheck --check-prefix=R | ||
+ | ||
+// Check if BOLT can process ifunc symbols from .plt section in non-pie static | ||
+// executable case. | ||
+// RUN: %clang %cflags -nostdlib %p/../Inputs/ifu | ||
+// RUN: -o %t.nopie.exe -Wl,-q | ||
+// RUN: llvm-readelf -l %t.nopie.exe | \ | ||
+// RUN: FileCheck --check-prefix=N | ||
+// RUN: llvm-bolt %t.nopie.exe -o %t.nopie.bolt.ex | ||
+// RUN: --print-disasm --print-only=_st | ||
+// RUN: FileCheck --check-prefix=C | ||
+// RUN: llvm-readelf -aW %t.nopie.bolt.ex | ||
+// RUN: FileCheck --check-prefix=R | ||
+ | ||
+// Check if BOLT can process ifunc symbols from .plt section in pie executable | ||
+// case. | ||
+// RUN: %clang %cflags -nostdlib %p/../Inputs/ifu | ||
+// RUN: -o %t.pie.exe -Wl,-q | ||
+// RUN: llvm-bolt %t.pie.exe -o %t.pie.bolt.exe \ | ||
+// RUN: --print-disasm --print-only=_st | ||
+// RUN: FileCheck --check-prefix=C | ||
+// RUN: llvm-readelf -aW %t.pie.bolt.exe | \ | ||
+// RUN: FileCheck --check-prefix=R | ||
+ | ||
+// Check that IPLT trampoline located in .plt section are normally handled by | ||
+// BOLT. The gnu-ld linker doesn't use separate .iplt section. | ||
+// RUN: %clang %cflags -nostdlib %p/../Inputs/ifu | ||
+// RUN: -T %p/../Inputs/ipl | ||
+// RUN: llvm-bolt %t.iplt_pie.exe -o %t.iplt_pie.bolt | ||
+// RUN: --print-disasm --print-only=_st | ||
+// RUN: FileCheck --check-prefix=C | ||
+// RUN: llvm-readelf -aW %t.iplt_pie.bolt | ||
+// RUN: FileCheck --check-prefix=R | ||
+ | ||
+// NON_DYN_CHECK-NO | ||
+ | ||
+// CHECK: callq "resolver_foo/1@ | ||
+ | ||
+// REL_CHECK: R_X86_64_IRELATI | ||
+// REL_CHECK: [[#REL_SYMB_ADDR |
@@ -6,7 +6,7 @@ RUN: yaml2obj %p/Inputs/blarge | ||
RUN: llvm-bolt %t.exe -o %t.null --data %p/Inputs/blarge | ||
RUN: --reorder-blocks | ||
RUN: | FileCheck --check-prefix=C | ||
-RUN: cat %t.log | FileCheck %s --check-prefix=C | ||
+RUN: FileCheck %s --check-prefix=C | ||
CHECK-NOT: BOLT-INFO | ||
CHECK-NOT: BOLT-WARNING | ||
@@ -16,4 +16,4 @@ CHECK-NOT: BOLT-ERROR | ||
CHECK-LOG: BOLT-INFO: Target architecture | ||
CHECK-LOG: BOLT-INFO: BOLT version | ||
CHECK-LOG: BOLT-INFO: basic block reordering modified layout | ||
-CHECK-LOG: Binary Function "usqrt" | ||
+CHECK-LOG: Binary Function "main" |
@@ -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 | ||
+# RUN: -o %t.bolt -data %t/yaml -infer-stale-pro | ||
+# RUN: --stale-matching | ||
+ | ||
+# 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: | ||
+ binary-name: 'inline-cs-pseud | ||
+ binary-build-id: | ||
+ 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_de | ||
+ gs: [ 0xE413754A191DB5 | ||
+ gh: [ 2, 0, 1 ] | ||
+ hs: [ 0x200205A19C5B4, | ||
+... |
@@ -55,8 +55,8 @@ functions: | ||
hash: 0xFFFFFFFFFFFFFF | ||
insns: 1 | ||
succ: [ { bid: 3, cnt: 1} ] | ||
- probes: [ { } ] | ||
- inline_tree: [ { g: 1 } ] | ||
+ probes: [ { blx: 1 } ] | ||
+ inline_tree: [ { g: 0 } ] | ||
pseudo_probe_des | ||
gs: [ 0xDB956436E78DD5 | ||
gh: [ 0 ] |
@@ -11,7 +11,21 @@ REQUIRES: system-linux | ||
RUN: yaml2obj %p/Inputs/blarge | ||
RUN: perf2bolt %t.exe -o %t --pa -p %p/Inputs/pre-ag | ||
-RUN: --profile-use-df | ||
+RUN: --show-density \ | ||
+RUN: --profile-densit | ||
+RUN: --profile-use-df | ||
+ | ||
+CHECK-P2B: BOLT-INFO: 4 out of 7 functions in the binary (57.1%) have non-empty execution profile | ||
+CHECK-P2B: BOLT-INFO: Functions with density >= 21.7 account for 97.00% total sample counts. | ||
+ | ||
+RUN: perf2bolt %t.exe -o %t --pa -p %p/Inputs/pre-ag | ||
+RUN: --show-density \ | ||
+RUN: --profile-densit | ||
+RUN: --profile-use-df | ||
+ | ||
+CHECK-WARNING: BOLT-INFO: 4 out of 7 functions in the binary (57.1%) have non-empty execution profile | ||
+CHECK-WARNING: BOLT-WARNING: BOLT is estimated to optimize better with 2.8x more samples. | ||
+CHECK-WARNING: BOLT-INFO: Functions with density >= 21.7 account for 97.00% total sample counts. | ||
RUN: llvm-bolt %t.exe -data %t -o %t.null | FileCheck %s | ||
RUN: llvm-bolt %t.exe -data %t.new -o %t.null | FileCheck %s |
@@ -0,0 +1,29 @@ | ||
+## Check that --print-only flag works with sections. | ||
+ | ||
+# REQUIRES: system-linux | ||
+ | ||
+# RUN: llvm-mc -filetype=obj -triple x86_64-unknown-l | ||
+# RUN: ld.lld %t.o -o %t.exe | ||
+# RUN: llvm-bolt %t.exe -o %t.out --print-cfg --print-only=unu | ||
+# RUN: | FileCheck %s | ||
+ | ||
+# CHECK: Binary Function "foo" | ||
+# CHECK-NOT: Binary Function "_start" | ||
+ | ||
+ .text | ||
+ .globl _start | ||
+ .type _start, %function | ||
+_start: | ||
+ .cfi_startproc | ||
+ ret | ||
+ .cfi_endproc | ||
+ .size _start, .-_start | ||
+ | ||
+ .section unused_code,"ax" | ||
+ .globl foo | ||
+ .type foo, %function | ||
+foo: | ||
+ .cfi_startproc | ||
+ ret | ||
+ .cfi_endproc | ||
+ .size foo, .-foo |
@@ -14,18 +14,18 @@ | ||
# RUN: FileCheck --input-file %t.yaml2 %s --check-prefix CHECK-YAML | ||
# CHECK-YAML: name: bar | ||
# CHECK-YAML: - bid: 0 | ||
-# CHECK-YAML: probes: [ { blk: 9 } ] | ||
-# CHECK-YAML: inline_tree: [ { g: 1 } ] | ||
+# CHECK-YAML: probes: [ { blx: 9 } ] | ||
+# CHECK-YAML: inline_tree: [ { } ] | ||
# | ||
# CHECK-YAML: name: foo | ||
# CHECK-YAML: - bid: 0 | ||
-# CHECK-YAML: probes: [ { blk: 3 } ] | ||
-# CHECK-YAML: inline_tree: [ { g: 2 }, { g: 1, cs: 8 } ] | ||
+# CHECK-YAML: probes: [ { blx: 3 } ] | ||
+# CHECK-YAML: inline_tree: [ { g: 1 }, { g: 0, cs: 8 } ] | ||
# | ||
# CHECK-YAML: name: main | ||
# CHECK-YAML: - bid: 0 | ||
-# CHECK-YAML: probes: [ { blk: 3, id: 1 }, { } ] | ||
-# CHECK-YAML: inline_tree: [ { g: 3 }, { g: 2, cs: 2 }, { g: 1, p: 1, cs: 8 } ] | ||
+# CHECK-YAML: probes: [ { blx: 3, id: 1 }, { blx: 1 } ] | ||
+# CHECK-YAML: inline_tree: [ { g: 2 }, { g: 1, cs: 2 }, { g: 0, p: 1, cs: 8 } ] | ||
# | ||
# CHECK-YAML: pseudo_probe_des | ||
# CHECK-YAML-NEXT: | ||
@@ -34,8 +34,8 @@ | ||
# | ||
## Check that without --profile-write- | ||
## generated | ||
-# RUN: perf2bolt %S/../../../llvm | ||
-# RUN: FileCheck --input-file %t.yaml %s --check-prefix CHECK-NO-OPT | ||
+# RUN: perf2bolt %S/../../../llvm | ||
+# RUN: FileCheck --input-file %t.yaml3 %s --check-prefix CHECK-NO-OPT | ||
# CHECK-NO-OPT-NOT | ||
# CHECK-NO-OPT-NOT | ||
# CHECK-NO-OPT-NOT |
@@ -1,6 +1,45 @@ | ||
# REQUIRES: system-linux | ||
-# RUN: llvm-bolt %S/../../../llvm | ||
+# RUN: llvm-bolt %S/../../../llvm | ||
+# PREAGG: B X:0 #foo# 1 0 | ||
+# PREAGG: B X:0 #bar# 1 0 | ||
+# PREAGG: B X:0 #main# 1 0 | ||
+ | ||
+## Check pseudo-probes in regular YAML profile (non-BOLTed binary) | ||
+# RUN: link_fdata %s %S/../../../llvm | ||
+# RUN: perf2bolt %S/../../../llvm | ||
+# RUN: FileCheck --input-file %t.yaml %s --check-prefix CHECK-YAML | ||
+## Check pseudo-probes in BAT YAML profile (BOLTed binary) | ||
+# RUN: link_fdata %s %t.bolt %t.preagg2 PREAGG | ||
+# RUN: perf2bolt %t.bolt -p %t.preagg2 --pa -w %t.yaml2 -o %t.fdata2 --profile-write- | ||
+# RUN: FileCheck --input-file %t.yaml2 %s --check-prefix CHECK-YAML | ||
+# CHECK-YAML: name: bar | ||
+# CHECK-YAML: - bid: 0 | ||
+# CHECK-YAML: probes: [ { blx: 9 } ] | ||
+# CHECK-YAML: inline_tree: [ { } ] | ||
+# | ||
+# CHECK-YAML: name: foo | ||
+# CHECK-YAML: - bid: 0 | ||
+# CHECK-YAML: probes: [ { blx: 3 } ] | ||
+# CHECK-YAML: inline_tree: [ { g: 2 } ] | ||
+# | ||
+# CHECK-YAML: name: main | ||
+# CHECK-YAML: - bid: 0 | ||
+# CHECK-YAML: probes: [ { blx: 1, call: [ 2 ] } ] | ||
+# CHECK-YAML: inline_tree: [ { g: 1 } ] | ||
+# | ||
+# CHECK-YAML: pseudo_probe_des | ||
+# CHECK-YAML-NEXT: | ||
+# CHECK-YAML-NEXT: | ||
+# CHECK-YAML-NEXT: | ||
+# | ||
+## Check that without --profile-write- | ||
+## generated | ||
+# RUN: perf2bolt %S/../../../llvm | ||
+# RUN: FileCheck --input-file %t.yaml3 %s --check-prefix CHECK-NO-OPT | ||
+# CHECK-NO-OPT-NOT | ||
+# CHECK-NO-OPT-NOT | ||
+# CHECK-NO-OPT-NOT | ||
;; Report of decoding input pseudo probe binaries | ||
; CHECK: GUID: 6699318081062747 |
@@ -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 | ||
CHECK2-NEXT: exact match | ||
-CHECK2: Matched yaml block (bid = 1) with hash 167a1f084f130088 | ||
-CHECK2-NEXT: exact match | ||
CHECK2: Matched yaml block (bid = 13) with hash a8d50000f81902a7 | ||
CHECK2-NEXT: loose match | ||
+CHECK2: Matched yaml block (bid = 1) with hash 167a1f084f130088 | ||
+CHECK2-NEXT: exact match | ||
CHECK2: Matched yaml block (bid = 3) with hash c516000073dc00a0 | ||
CHECK2-NEXT: loose match | ||
CHECK2: Matched yaml block (bid = 5) with hash 6446e1ea500111 to BB (index = 5) with hash 6446e1ea500111 |
@@ -1,6 +1,6 @@ | ||
host_linux_tripl | ||
-common_linker_f | ||
-flags = f"--target={host | ||
+common_linker_f | ||
+flags = f"--target={host | ||
config.substitut | ||
config.substitut |
@@ -0,0 +1,45 @@ | ||
+## Test that merge-fdata correctly handles YAML header with an uninitialized | ||
+## fields. a.yaml does not have hash-func set and it used to crash merge-fdata. | ||
+ | ||
+# REQUIRES: system-linux | ||
+ | ||
+# RUN: split-file %s %t | ||
+# RUN: not merge-fdata %t/a.yaml %t/b.yaml 2>&1 | FileCheck %s | ||
+ | ||
+# CHECK: cannot merge profiles with different hash functions | ||
+ | ||
+#--- a.yaml | ||
+--- | ||
+header: | ||
+ profile-version: | ||
+ binary-name: 'a.out' | ||
+ binary-build-id: | ||
+ profile-flags: [ lbr ] | ||
+ profile-origin: branch profile reader | ||
+ profile-events: '' | ||
+ dfs-order: false | ||
+functions: | ||
+ - name: 'main' | ||
+ fid: 1 | ||
+ hash: 0x50BBA3441D4364 | ||
+ exec: 1 | ||
+ nblocks: 0 | ||
+... | ||
+#--- b.yaml | ||
+--- | ||
+header: | ||
+ profile-version: | ||
+ binary-name: 'a.out' | ||
+ binary-build-id: | ||
+ profile-flags: [ lbr ] | ||
+ profile-origin: branch profile reader | ||
+ profile-events: '' | ||
+ dfs-order: false | ||
+ hash-func: xxh3 | ||
+functions: | ||
+ - name: 'main' | ||
+ fid: 1 | ||
+ hash: 0x50BBA3441D4364 | ||
+ exec: 1 | ||
+ nblocks: 0 | ||
+... |
@@ -1,4 +1,5 @@ | ||
import shutil | ||
+import subprocess | ||
-if shutil.which("pe | ||
- config.available |
@@ -3,15 +3,12 @@ | ||
REQUIRES: system-linux, perf | ||
RUN: %clang %S/Inputs/perf_t | ||
-RUN: perf record -e cycles:u -o %t2 -- %t | ||
+RUN: perf record -Fmax -e cycles:u -o %t2 -- %t | ||
RUN: perf2bolt %t -p=%t2 -o %t3 -nl -ignore-build-id | ||
CHECK-NOT: PERF2BOLT-ERROR | ||
CHECK-NOT: !! WARNING !! This high mismatch ratio indicates the input binary is probably not the same binary used during profiling collection. | ||
RUN: %clang %S/Inputs/perf_t | ||
-RUN: perf record -e cycles:u -o %t5 -- %t4 | ||
-RUN: perf2bolt %t4 -p=%t5 -o %t6 -nl -ignore-build-id | ||
- | ||
-CHECK-NO-PIE-NO | ||
-CHECK-NO-PIE-NO |
@@ -129,6 +129,7 @@ void perf2boltMode(in | ||
exit(1); | ||
} | ||
opts::AggregateO | ||
+ opts::ShowDensit | ||
} | ||
void boltDiffMode(int | ||
@@ -202,9 +203,9 @@ int main(int argc, char **argv) { | ||
ToolName = argv[0]; | ||
- if (llvm::sys::path | ||
+ if (llvm::sys::path | ||
perf2boltMode(ar | ||
- else if (llvm::sys::path | ||
+ else if (llvm::sys::path | ||
boltDiffMode(arg | ||
else | ||
boltMode(argc, argv); |
@@ -145,6 +145,10 @@ void mergeProfileHead | ||
errs() << "WARNING: merging profiles with different sampling events\n"; | ||
MergedHeader.Eve | ||
} | ||
+ | ||
+ if (MergedHeader.Ha | ||
+ report_error("me | ||
+ "cannot merge profiles with different hash functions"); | ||
} | ||
void mergeBasicBlockP | ||
@@ -386,6 +390,7 @@ int main(int argc, char **argv) { | ||
// Merged information for all functions. | ||
StringMap<Binary | ||
+ bool FirstHeader = true; | ||
for (std::string &InputDataFilena | ||
ErrorOr<std::uni | ||
MemoryBuffer::ge | ||
@@ -409,7 +414,12 @@ int main(int argc, char **argv) { | ||
} | ||
// Merge the header. | ||
- mergeProfileHead | ||
+ if (FirstHeader) { | ||
+ MergedHeader = BP.Header; | ||
+ FirstHeader = false; | ||
+ } else { | ||
+ mergeProfileHead | ||
+ } | ||
// Do the function merge. | ||
for (BinaryFunctionP |
@@ -160,13 +160,14 @@ TEST_P(BinaryCon | ||
TEST_P(BinaryCon | ||
// Check that base address calculation is correct for a binary with the | ||
// following segment layout: | ||
- BC->SegmentMapIn | ||
+ BC->SegmentMapIn | ||
+ SegmentInfo{0, 0x10e8c2b4, 0, 0x10e8c2b4, 0x1000, true}; | ||
BC->SegmentMapIn | ||
- SegmentInfo{0x10 | ||
+ SegmentInfo{0x10 | ||
BC->SegmentMapIn | ||
- SegmentInfo{0x4a | ||
+ SegmentInfo{0x4a | ||
BC->SegmentMapIn | ||
- SegmentInfo{0x4b | ||
+ SegmentInfo{0x4b | ||
std::optional<ui | ||
BC->getBaseAddre | ||
@@ -181,13 +182,13 @@ TEST_P(BinaryCon | ||
// Check that base address calculation is correct for a binary if the | ||
// alignment in ELF file are different from pagesize. | ||
// The segment layout is as follows: | ||
- BC->SegmentMapIn | ||
+ BC->SegmentMapIn | ||
BC->SegmentMapIn | ||
- SegmentInfo{0x31 | ||
+ SegmentInfo{0x31 | ||
BC->SegmentMapIn | ||
- SegmentInfo{0x41 | ||
+ SegmentInfo{0x41 | ||
BC->SegmentMapIn | ||
- SegmentInfo{0x54 | ||
+ SegmentInfo{0x54 | ||
std::optional<ui | ||
BC->getBaseAddre | ||
@@ -197,3 +198,22 @@ TEST_P(BinaryCon | ||
BaseAddress = BC->getBaseAddre | ||
ASSERT_FALSE(Bas | ||
} | ||
+ | ||
+TEST_P(BinaryCo | ||
+ // Check that the correct segment is used to compute the base address | ||
+ // when multiple segments are close together in the ELF file (closer | ||
+ // than the required alignment in the process space). | ||
+ // See https://github.c | ||
+ BC->SegmentMapIn | ||
+ BC->SegmentMapIn | ||
+ SegmentInfo{0x11 | ||
+ BC->SegmentMapIn | ||
+ SegmentInfo{0x22 | ||
+ BC->SegmentMapIn | ||
+ SegmentInfo{0x33 | ||
+ | ||
+ std::optional<ui | ||
+ BC->getBaseAddre | ||
+ ASSERT_TRUE(Base | ||
+ ASSERT_EQ(*BaseA | ||
+} |
@@ -27,7 +27,6 @@ add_subdirectory | ||
add_subdirectory | ||
add_subdirectory | ||
add_subdirectory | ||
-add_subdirector | ||
add_subdirectory | ||
option(CLANG_TOO |
@@ -23,7 +23,7 @@ D: clang-tidy | ||
N: Manuel Klimek | ||
E: klimek@google.co | ||
-D: clang-rename, all parts of clang-tools-extr | ||
+D: all parts of clang-tools-extr | ||
N: Sam McCall | ||
E: sammccall@google |
@@ -2,7 +2,7 @@ set(LLVM_LINK_CO | ||
Support | ||
) | ||
-add_clang_libra | ||
+add_clang_libra | ||
lib/Tooling/Appl | ||
DEPENDS |
@@ -148,11 +148,8 @@ groupReplacement | ||
if (auto Entry = SM.getFileManage | ||
if (SourceTU) { | ||
- auto &Replaces = DiagReplacements | ||
- auto It = Replaces.find(R) | ||
- if (It == Replaces.end()) | ||
- Replaces.emplace | ||
- else if (It->second != SourceTU) | ||
+ auto [It, Inserted] = DiagReplacements | ||
+ if (!Inserted && It->second != SourceTU) | ||
// This replacement is a duplicate of one suggested by another TU. | ||
return; | ||
} |
@@ -3,7 +3,7 @@ set(LLVM_LINK_CO | ||
Support | ||
) | ||
-add_clang_libra | ||
+add_clang_libra | ||
ChangeNamespace. | ||
DEPENDS |
@@ -606,9 +606,8 @@ void ChangeNamespaceT | ||
Result.Nodes.get | ||
// If this reference has been processed as a function call, we do not | ||
// process it again. | ||
- if (ProcessedFuncRe | ||
+ if (!ProcessedFuncR | ||
return; | ||
- ProcessedFuncRef | ||
const auto *Func = Result.Nodes.get | ||
assert(Func); | ||
const auto *Context = Result.Nodes.get |
@@ -4,7 +4,7 @@ set(LLVM_LINK_CO | ||
FrontendOpenMP | ||
) | ||
-add_clang_libra | ||
+add_clang_libra | ||
BitcodeReader.cp | ||
BitcodeWriter.cp | ||
ClangDoc.cpp |
@@ -55,4 +55,8 @@ std::string getTagType(TagTy | ||
} // namespace doc | ||
} // namespace clang | ||
+namespace llvm { | ||
+extern template class Registry<clang:: | ||
+} // namespace llvm | ||
+ | ||
#endif // LLVM_CLANG_TOOLS |
@@ -300,8 +300,7 @@ Example usage for a project using a compile commands database: | ||
llvm::StringMap< | ||
Executor->get()- | ||
[&](StringRef Key, StringRef Value) { | ||
- auto R = USRToBitcode.try | ||
- R.first->second. | ||
+ USRToBitcode[Key | ||
}); | ||
// Collects all Infos according to their unique USR value. This map is added |
@@ -2,7 +2,7 @@ set(LLVM_LINK_CO | ||
support | ||
) | ||
-add_clang_libra | ||
+add_clang_libra | ||
IncludeFixer.cpp | ||
IncludeFixerCont | ||
InMemorySymbolIn |
@@ -21,7 +21,7 @@ InMemorySymbolIn | ||
std::vector<Symb | ||
InMemorySymbolIn | ||
- auto I = LookupTable.find | ||
+ auto I = LookupTable.find | ||
if (I != LookupTable.end( | ||
return I->second; | ||
return {}; |
@@ -27,7 +27,8 @@ public: | ||
search(llvm::Str | ||
private: | ||
- std::map<std::st | ||
+ std::map<std::st | ||
+ std::less<>> | ||
LookupTable; | ||
}; | ||
@@ -3,7 +3,7 @@ set(LLVM_LINK_CO | ||
FrontendOpenMP | ||
) | ||
-add_clang_libra | ||
+add_clang_libra | ||
FindAllSymbols.c | ||
FindAllSymbolsAc | ||
FindAllMacros.cp |
@@ -1,4 +1,4 @@ | ||
-add_clang_libra | ||
+add_clang_libra | ||
IncludeFixerPlug | ||
LINK_LIBS |
@@ -3,7 +3,7 @@ set(LLVM_LINK_CO | ||
FrontendOpenMP | ||
) | ||
-add_clang_libra | ||
+add_clang_libra | ||
Move.cpp | ||
HelperDeclRefGra | ||
@@ -76,11 +76,10 @@ HelperDeclRefGra | ||
llvm::DenseSet<c | ||
std::function<vo | ||
[&](const CallGraphNode *Node) { | ||
- if (ConnectedNodes. | ||
+ if (!ConnectedNodes | ||
return; | ||
- ConnectedNodes.i | ||
- for (auto It = Node->begin(), End = Node->end(); It != End; ++It) | ||
- VisitNode(*It); | ||
+ for (const CallGraphNode::C | ||
+ VisitNode(Callee | ||
}; | ||
VisitNode(RootNo |
@@ -199,7 +199,7 @@ int main(int argc, const char **argv) { | ||
for (auto I = Files.begin(), E = Files.end(); I != E; ++I) { | ||
OS << " {\n"; | ||
OS << " \"FilePath\": \"" << *I << "\",\n"; | ||
- const auto Entry = FileMgr.getFile( | ||
+ const auto Entry = FileMgr.getOptio | ||
auto ID = SM.translateFile | ||
std::string Content; | ||
llvm::raw_string |
@@ -4,7 +4,7 @@ set(LLVM_LINK_CO | ||
FrontendOpenMP | ||
) | ||
-add_clang_libra | ||
+add_clang_libra | ||
Query.cpp | ||
QueryParser.cpp | ||
@@ -44,7 +44,9 @@ bool HelpQuery::run(l | ||
" set bind-root (true|false) " | ||
"Set whether to bind the root matcher to \"root\".\n" | ||
" set print-matcher (true|false) " | ||
- "Set whether to print the current matcher,\n" | ||
+ "Set whether to print the current matcher.\n" | ||
+ " set enable-profile (true|false) " | ||
+ "Set whether to enable matcher profiling.\n" | ||
" set traversal <kind> " | ||
"Set traversal kind of clang-query session. Available kinds are:\n" | ||
" AsIs " | ||
@@ -82,10 +84,24 @@ namespace { | ||
struct CollectBoundNode | ||
std::vector<Boun | ||
- CollectBoundNode | ||
+ StringRef Unit; | ||
+ CollectBoundNode | ||
+ : Bindings(Binding | ||
void run(const MatchFinder::Mat | ||
Bindings.push_ba | ||
} | ||
+ StringRef getID() const override { return Unit; } | ||
+}; | ||
+ | ||
+struct QueryProfiler { | ||
+ llvm::StringMap< | ||
+ | ||
+ ~QueryProfiler() | ||
+ llvm::TimerGroup | ||
+ Records); | ||
+ TG.print(llvm::e | ||
+ llvm::errs().flu | ||
+ } | ||
}; | ||
} // namespace | ||
@@ -93,8 +109,19 @@ struct CollectBoundNode | ||
bool MatchQuery::run( | ||
unsigned MatchCount = 0; | ||
+ std::optional<Qu | ||
+ if (QS.EnableProfil | ||
+ Profiler.emplace | ||
+ | ||
for (auto &AST : QS.ASTs) { | ||
- MatchFinder Finder; | ||
+ ast_matchers::Ma | ||
+ std::optional<ll | ||
+ if (QS.EnableProfil | ||
+ Records.emplace( | ||
+ FinderOptions.Ch | ||
+ } | ||
+ | ||
+ MatchFinder Finder(FinderOpt | ||
std::vector<Boun | ||
DynTypedMatcher MaybeBoundMatche | ||
if (QS.BindRoot) { | ||
@@ -102,7 +129,8 @@ bool MatchQuery::run( | ||
if (M) | ||
MaybeBoundMatche | ||
} | ||
- CollectBoundNode | ||
+ StringRef OrigSrcName = AST->getOriginal | ||
+ CollectBoundNode | ||
if (!Finder.addDyna | ||
OS << "Not a valid top-level matcher.\n"; | ||
return false; | ||
@@ -111,6 +139,8 @@ bool MatchQuery::run( | ||
ASTContext &Ctx = AST->getASTConte | ||
Ctx.getParentMap | ||
Finder.matchAST( | ||
+ if (QS.EnableProfil | ||
+ Profiler->Record | ||
if (QS.PrintMatcher | ||
SmallVector<Stri | ||
@@ -146,7 +176,7 @@ bool MatchQuery::run( | ||
TD.emitDiagnosti | ||
FullSourceLoc(R. | ||
DiagnosticsEngin | ||
- CharSourceRange: | ||
+ CharSourceRange: | ||
} | ||
} | ||
if (QS.PrintOutput) |
@@ -182,6 +182,7 @@ enum ParsedQueryVaria | ||
PQV_Output, | ||
PQV_BindRoot, | ||
PQV_PrintMatcher | ||
+ PQV_EnableProfil | ||
PQV_Traversal | ||
}; | ||
@@ -285,6 +286,7 @@ QueryRef QueryParser::doP | ||
.Case("output", PQV_Output) | ||
.Case("bind-root | ||
.Case("print-mat | ||
+ .Case("enable-pr | ||
.Case("traversal | ||
.Default(PQV_Inv | ||
if (VarStr.empty()) | ||
@@ -303,6 +305,9 @@ QueryRef QueryParser::doP | ||
case PQV_PrintMatcher | ||
Q = parseSetBool(&Qu | ||
break; | ||
+ case PQV_EnableProfil | ||
+ Q = parseSetBool(&Qu | ||
+ break; | ||
case PQV_Traversal: | ||
Q = parseSetTraversa | ||
break; |
@@ -26,7 +26,7 @@ public: | ||
QuerySession(llv | ||
: ASTs(ASTs), PrintOutput(fals | ||
DetailedASTOutpu | ||
- Terminate(false) | ||
+ EnableProfile(fa | ||
llvm::ArrayRef<s | ||
@@ -36,6 +36,7 @@ public: | ||
bool BindRoot; | ||
bool PrintMatcher; | ||
+ bool EnableProfile; | ||
bool Terminate; | ||
TraversalKind TK; |
@@ -3,7 +3,7 @@ set(LLVM_LINK_CO | ||
support | ||
) | ||
-add_clang_libra | ||
+add_clang_libra | ||
ReorderFieldsAct | ||
DEPENDS |
@@ -8,7 +8,7 @@ configure_file( | ||
${CMAKE_CURRENT_ | ||
include_director | ||
-add_clang_libra | ||
+add_clang_libra | ||
ClangTidy.cpp | ||
ClangTidyCheck.c | ||
ClangTidyModule. | ||
@@ -35,7 +35,6 @@ clang_target_lin | ||
clangFrontend | ||
clangLex | ||
clangRewrite | ||
- clangSema | ||
clangSerializati | ||
clangTooling | ||
clangToolingCore |
@@ -336,6 +336,7 @@ private: | ||
std::unique_ptr< | ||
std::unique_ptr< | ||
std::vector<std: | ||
+ void anchor() override {}; | ||
}; | ||
} // namespace | ||
@@ -458,7 +459,6 @@ ClangTidyASTCons | ||
if (!AnalyzerOption | ||
setStaticAnalyze | ||
AnalyzerOptions. | ||
- AnalyzerOptions. | ||
std::unique_ptr< | ||
ento::CreateAnal | ||
AnalysisConsumer | ||
@@ -672,6 +672,18 @@ getAllChecksAndO | ||
Buffer.append(An | ||
Result.Names.ins | ||
} | ||
+ for (std::string OptionName : { | ||
+#define GET_CHECKER_OPTI | ||
+#define CHECKER_OPTION(T | ||
+ RELEASE, HIDDEN) \ | ||
+ Twine(AnalyzerCh | ||
+ | ||
+#include "clang/StaticAna | ||
+#undef CHECKER_OPTION | ||
+#undef GET_CHECKER_OPTI | ||
+ }) { | ||
+ Result.Options.i | ||
+ } | ||
#endif // CLANG_TIDY_ENABL | ||
Context.setOptio |
@@ -380,7 +380,6 @@ void ClangTidyDiagnos | ||
++Context.Stats. | ||
// Ignored a warning, should ignore related notes as well | ||
LastErrorWasIgno | ||
- Context.DiagEngi | ||
for (const auto &Error : SuppressionError | ||
Context.diag(Err | ||
return; | ||
@@ -457,7 +456,6 @@ void ClangTidyDiagnos | ||
if (Info.hasSourceM | ||
checkFilters(Inf | ||
- Context.DiagEngi | ||
for (const auto &Error : SuppressionError | ||
Context.diag(Err | ||
} |
@@ -18,4 +18,8 @@ using ClangTidyModuleR | ||
} // namespace clang::tidy | ||
+namespace llvm { | ||
+extern template class Registry<clang:: | ||
+} // namespace llvm | ||
+ | ||
#endif // LLVM_CLANG_TOOLS |
@@ -116,9 +116,8 @@ void ExpandModularHea | ||
if (!MF) | ||
return; | ||
// Avoid processing a ModuleFile more than once. | ||
- if (VisitedModules. | ||
+ if (!VisitedModules | ||
return; | ||
- VisitedModules.i | ||
// Visit all the input files of this module and mark them to record their | ||
// contents later. |
@@ -3,7 +3,7 @@ set(LLVM_LINK_CO | ||
FrontendOpenMP | ||
) | ||
-add_clang_libra | ||
+add_clang_libra | ||
AbseilTidyModule | ||
CleanupCtadCheck | ||
DurationAddition |
@@ -244,7 +244,7 @@ std::optional<Du | ||
{"ToDoubleNanose | ||
{"ToInt64Nanosec | ||
- auto ScaleIter = ScaleMap.find(st | ||
+ auto ScaleIter = ScaleMap.find(Na | ||
if (ScaleIter == ScaleMap.end()) | ||
return std::nullopt; | ||
@@ -260,7 +260,7 @@ std::optional<Du | ||
{"ToUnixMicros", | ||
{"ToUnixNanos", DurationScale::N | ||
- auto ScaleIter = ScaleMap.find(st | ||
+ auto ScaleIter = ScaleMap.find(Na | ||
if (ScaleIter == ScaleMap.end()) | ||
return std::nullopt; | ||
@@ -8,9 +8,6 @@ | ||
# | ||
# ===------------- | ||
-from __future__ import print_function | ||
-from __future__ import unicode_literals | ||
- | ||
import argparse | ||
import io | ||
import itertools | ||
@@ -19,10 +16,13 @@ import re | ||
import sys | ||
import textwrap | ||
+# FIXME Python 3.9: Replace typing.Tuple with builtins.tuple. | ||
+from typing import Optional, Tuple, Match | ||
+ | ||
# Adapts the module's CMakelist file. Returns 'True' if it could add a new | ||
# entry and 'False' if the entry already existed. | ||
-def adapt_cmake(modu | ||
+def adapt_cmake(modu | ||
filename = os.path.join(mod | ||
# The documentation files are encoded using UTF-8, however on Windows the | ||
@@ -57,14 +57,14 @@ def adapt_cmake(modu | ||
# Adds a header for the new check. | ||
def write_header( | ||
- module_path, | ||
- module, | ||
- namespace, | ||
- check_name, | ||
- check_name_camel | ||
- description, | ||
- lang_restrict, | ||
-): | ||
+ module_path: str, | ||
+ module: str, | ||
+ namespace: str, | ||
+ check_name: str, | ||
+ check_name_camel | ||
+ description: str, | ||
+ lang_restrict: str, | ||
+) -> None: | ||
wrapped_desc = "\n".join( | ||
textwrap.wrap( | ||
description, width=80, initial_indent=" | ||
@@ -139,7 +139,9 @@ public: | ||
# Adds the implementation of the new check. | ||
-def write_implementa | ||
+def write_implementa | ||
+ module_path: str, module: str, namespace: str, check_name_camel | ||
+) -> None: | ||
filename = os.path.join(mod | ||
print("Creating %s..." % filename) | ||
with io.open(filename | ||
@@ -187,7 +189,7 @@ void %(check_name)s:: | ||
# Returns the source filename that implements the module. | ||
-def get_module_filen | ||
+def get_module_filen | ||
modulecpp = list( | ||
filter( | ||
lambda p: p.lower() == module.lower() + "tidymodule.cpp" | ||
@@ -198,7 +200,9 @@ def get_module_filen | ||
# Modifies the module to include the new check. | ||
-def adapt_module(mod | ||
+def adapt_module( | ||
+ module_path: str, module: str, check_name: str, check_name_camel | ||
+) -> None: | ||
filename = get_module_filen | ||
with io.open(filename | ||
lines = f.readlines() | ||
@@ -217,10 +221,10 @@ def adapt_module(mod | ||
+ '");\n' | ||
) | ||
- lines = iter(lines) | ||
+ lines_iter = iter(lines) | ||
try: | ||
while True: | ||
- line = next(lines) | ||
+ line = next(lines_iter) | ||
if not header_added: | ||
match = re.search('#incl | ||
if match: | ||
@@ -247,10 +251,11 @@ def adapt_module(mod | ||
# If we didn't find the check name on this line, look on the | ||
# next one. | ||
prev_line = line | ||
- line = next(lines) | ||
+ line = next(lines_iter) | ||
match = re.search(' *"([^"]*)"', line) | ||
if match: | ||
current_check_na | ||
+ assert current_check_na | ||
if current_check_na | ||
check_added = True | ||
f.write(check_de | ||
@@ -262,7 +267,9 @@ def adapt_module(mod | ||
# Adds a release notes entry. | ||
-def add_release_note | ||
+def add_release_note | ||
+ module_path: str, module: str, check_name: str, description: str | ||
+) -> None: | ||
wrapped_desc = "\n".join( | ||
textwrap.wrap( | ||
description, width=80, initial_indent=" | ||
@@ -324,9 +331,14 @@ def add_release_note | ||
# Adds a test for the check. | ||
-def write_test(modul | ||
- if test_standard: | ||
- test_standard = f"-std={test_sta | ||
+def write_test( | ||
+ module_path: str, | ||
+ module: str, | ||
+ check_name: str, | ||
+ test_extension: str, | ||
+ test_standard: Optional[str], | ||
+) -> None: | ||
+ test_standard = f"-std={test_sta | ||
check_name_dashe | ||
filename = os.path.normpath | ||
os.path.join( | ||
@@ -362,7 +374,7 @@ void awesome_f2(); | ||
) | ||
-def get_actual_filen | ||
+def get_actual_filen | ||
if not os.path.isdir(di | ||
return "" | ||
name = os.path.join(dir | ||
@@ -376,7 +388,7 @@ def get_actual_filen | ||
# Recreates the list of checks in the docs/clang-tidy/ | ||
-def update_checks_li | ||
+def update_checks_li | ||
docs_dir = os.path.join(cla | ||
filename = os.path.normpath | ||
# Read the content of the current list.rst file | ||
@@ -390,12 +402,12 @@ def update_checks_li | ||
for file in filter( | ||
lambda s: s.endswith(".rst | ||
): | ||
- doc_files.append | ||
+ doc_files.append | ||
doc_files.sort() | ||
# We couldn't find the source file from the check name, so try to find the | ||
# class name that corresponds to the check in the module file. | ||
- def filename_from_mo | ||
+ def filename_from_mo | ||
module_path = os.path.join(cla | ||
if not os.path.isdir(mo | ||
return "" | ||
@@ -433,7 +445,7 @@ def update_checks_li | ||
return "" | ||
# Examine code looking for a c'tor definition to get the base class name. | ||
- def get_base_class(c | ||
+ def get_base_class(c | ||
check_class_name | ||
ctor_pattern = check_class_name | ||
matches = re.search(r"\s+" | ||
@@ -452,7 +464,7 @@ def update_checks_li | ||
return "" | ||
# Some simple heuristics to figure out if a check has an autofix or not. | ||
- def has_fixits(code) | ||
+ def has_fixits(code: | ||
for needle in [ | ||
"FixItHint", | ||
"ReplacementText | ||
@@ -464,7 +476,7 @@ def update_checks_li | ||
return False | ||
# Try to figure out of the check supports fixits. | ||
- def has_auto_fix(che | ||
+ def has_auto_fix(che | ||
dirname, _, check_name = check_name.parti | ||
check_file = get_actual_filen | ||
@@ -499,7 +511,7 @@ def update_checks_li | ||
return "" | ||
- def process_doc(doc_ | ||
+ def process_doc(doc_ | ||
check_name = doc_file[0] + "-" + doc_file[1].repl | ||
with io.open(os.path. | ||
@@ -508,13 +520,13 @@ def update_checks_li | ||
if match: | ||
# Orphan page, don't list it. | ||
- return "", "" | ||
+ return "", None | ||
match = re.search(r".*:h | ||
# Is it a redirect? | ||
return check_name, match | ||
- def format_link(doc_ | ||
+ def format_link(doc_ | ||
check_name, match = process_doc(doc_ | ||
if not match and check_name and not check_name.start | ||
return " :doc:`%(check_na | ||
@@ -526,7 +538,7 @@ def update_checks_li | ||
else: | ||
return "" | ||
- def format_link_alia | ||
+ def format_link_alia | ||
check_name, match = process_doc(doc_ | ||
if (match or (check_name.star | ||
module = doc_file[0] | ||
@@ -543,6 +555,7 @@ def update_checks_li | ||
ref_end = "_" | ||
else: | ||
redirect_parts = re.search(r"^\.\ | ||
+ assert redirect_parts | ||
title = redirect_parts[1 | ||
target = redirect_parts[1 | ||
autofix = has_auto_fix(tit | ||
@@ -599,7 +612,7 @@ def update_checks_li | ||
# Adds a documentation for the check. | ||
-def write_docs(modul | ||
+def write_docs(modul | ||
check_name_dashe | ||
filename = os.path.normpath | ||
os.path.join( | ||
@@ -623,15 +636,15 @@ FIXME: Describe what patterns does the check detect and why. Give examples. | ||
) | ||
-def get_camel_name(c | ||
+def get_camel_name(c | ||
return "".join(map(lamb | ||
-def get_camel_check_ | ||
+def get_camel_check_ | ||
return get_camel_name(c | ||
-def main(): | ||
+def main() -> None: | ||
language_to_exte | ||
"c": "c", | ||
"c++": "cpp", | ||
@@ -756,6 +769,8 @@ def main(): | ||
) | ||
elif language in ["objc", "objc++"]: | ||
language_restric | ||
+ else: | ||
+ raise ValueError(f"Uns | ||
write_header( | ||
module_path, | ||
@@ -769,7 +784,7 @@ def main(): | ||
write_implementa | ||
adapt_module(mod | ||
add_release_note | ||
- test_extension = language_to_exte | ||
+ test_extension = language_to_exte | ||
write_test(modul | ||
write_docs(modul | ||
update_checks_li |
@@ -3,7 +3,7 @@ set(LLVM_LINK_CO | ||
support | ||
) | ||
-add_clang_libra | ||
+add_clang_libra | ||
AlteraTidyModule | ||
IdDependentBackw | ||
KernelNameRestri |
@@ -78,16 +78,22 @@ void IdDependentBackw | ||
IdDependentBackw | ||
IdDependentBackw | ||
+ if (!Expression) | ||
+ return nullptr; | ||
+ | ||
if (const auto *Declaration = dyn_cast<DeclRef | ||
// It is a DeclRefExpr, so check if it's an ID-dependent variable. | ||
- const auto *CheckVariable = dyn_cast<VarDecl | ||
+ const auto *CheckVariable = | ||
+ dyn_cast_if_pres | ||
+ if (!CheckVariable) | ||
+ return nullptr; | ||
auto FoundVariable = IdDepVarsMap.fin | ||
if (FoundVariable == IdDepVarsMap.end | ||
return nullptr; | ||
return &(FoundVariable- | ||
} | ||
for (const auto *Child : Expression->chil | ||
- if (const auto *ChildExpression | ||
+ if (const auto *ChildExpression | ||
if (IdDependencyRec | ||
return Result; | ||
return nullptr; | ||
@@ -95,16 +101,21 @@ IdDependentBackw | ||
IdDependentBackw | ||
IdDependentBackw | ||
+ if (!Expression) | ||
+ return nullptr; | ||
+ | ||
if (const auto *MemberExpressio | ||
const auto *CheckField = | ||
- dyn_cast<FieldDe | ||
+ dyn_cast_if_pres | ||
+ if (!CheckField) | ||
+ return nullptr; | ||
auto FoundField = IdDepFieldsMap.f | ||
if (FoundField == IdDepFieldsMap.e | ||
return nullptr; | ||
return &(FoundField->se | ||
} | ||
for (const auto *Child : Expression->chil | ||
- if (const auto *ChildExpression | ||
+ if (const auto *ChildExpression | ||
if (IdDependencyRec | ||
return Result; | ||
return nullptr; |
@@ -3,7 +3,7 @@ set(LLVM_LINK_CO | ||
FrontendOpenMP | ||
) | ||
-add_clang_libra | ||
+add_clang_libra | ||
AndroidTidyModul | ||
CloexecAccept4Ch | ||
CloexecAcceptChe |
@@ -3,7 +3,7 @@ set(LLVM_LINK_CO | ||
FrontendOpenMP | ||
) | ||
-add_clang_libra | ||
+add_clang_libra | ||
BoostTidyModule. | ||
UseRangesCheck.c | ||
UseToStringCheck |
@@ -204,7 +204,7 @@ utils::UseRanges | ||
ReplacerMap Results; | ||
static const Signature SingleSig = {{0}}; | ||
static const Signature TwoSig = {{0}, {2}}; | ||
- static const auto AddFrom = | ||
+ const auto AddFrom = | ||
[&Results](llvm: | ||
std::initializer | ||
llvm::SmallStrin | ||
@@ -214,17 +214,17 @@ utils::UseRanges | ||
} | ||
}; | ||
- static const auto AddFromStd = | ||
- [](llvm::Intrusi | ||
- std::initializer | ||
+ const auto AddFromStd = | ||
+ [&](llvm::Intrus | ||
+ std::initializer | ||
AddFrom(Replacer | ||
}; | ||
- static const auto AddFromBoost = | ||
- [](llvm::Intrusi | ||
- std::initializer | ||
- std::pair<String | ||
- NamespaceAndName | ||
+ const auto AddFromBoost = | ||
+ [&](llvm::Intrus | ||
+ std::initializer | ||
+ std::pair<String | ||
+ NamespaceAndName | ||
for (auto [Namespace, Names] : NamespaceAndName | ||
AddFrom(Replacer | ||
SmallString<64>{ |
@@ -0,0 +1,46 @@ | ||
+//===--- BitwisePointerCa | ||
+// | ||
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | ||
+// See https://llvm.org | ||
+// SPDX-License-Ide | ||
+// | ||
+//===---------- | ||
+ | ||
+#include "BitwisePointerC | ||
+#include "clang/ASTMatche | ||
+ | ||
+using namespace clang::ast_match | ||
+ | ||
+namespace clang::tidy::bug | ||
+ | ||
+void BitwisePointerCa | ||
+ if (getLangOpts().C | ||
+ auto IsPointerType = refersToType(qua | ||
+ Finder->addMatch | ||
+ hasName("::std:: | ||
+ hasTemplateArgum | ||
+ hasTemplateArgum | ||
+ .bind("bit_cast" | ||
+ this); | ||
+ } | ||
+ | ||
+ auto IsDoublePointerT | ||
+ hasType(qualType | ||
+ Finder->addMatch | ||
+ hasArgument(1, IsDoublePointerT | ||
+ hasDeclaration(f | ||
+ .bind("memcpy"), | ||
+ this); | ||
+} | ||
+ | ||
+void BitwisePointerCa | ||
+ if (const auto *Call = Result.Nodes.get | ||
+ diag(Call->getBe | ||
+ "do not use 'std::bit_cast' to cast between pointers") | ||
+ << Call->getSourceR | ||
+ else if (const auto *Call = Result.Nodes.get | ||
+ diag(Call->getBe | ||
+ << Call->getSourceR | ||
+} | ||
+ | ||
+} // namespace clang::tidy::bug |
@@ -0,0 +1,34 @@ | ||
+//===--- BitwisePointerCa | ||
+// | ||
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | ||
+// See https://llvm.org | ||
+// SPDX-License-Ide | ||
+// | ||
+//===---------- | ||
+ | ||
+#ifndef LLVM_CLANG_TOOLS | ||
+#define LLVM_CLANG_TOOLS | ||
+ | ||
+#include "../ClangTidyChe | ||
+ | ||
+namespace clang::tidy::bug | ||
+ | ||
+/// Warns about code that tries to cast between pointers by means of | ||
+/// ``std::bit_cast` | ||
+/// | ||
+/// For the user-facing documentation see: | ||
+/// http://clang.llv | ||
+class BitwisePointerCa | ||
+public: | ||
+ BitwisePointerCa | ||
+ : ClangTidyCheck(N | ||
+ void registerMatchers | ||
+ void check(const ast_matchers::Ma | ||
+ bool isLanguageVersio | ||
+ return LangOpts.CPlusPl | ||
+ } | ||
+}; | ||
+ | ||
+} // namespace clang::tidy::bug | ||
+ | ||
+#endif // LLVM_CLANG_TOOLS |
@@ -14,6 +14,7 @@ | ||
#include "AssertSideEffec | ||
#include "AssignmentInIfC | ||
#include "BadSignalToKill | ||
+#include "BitwisePointerC | ||
#include "BoolPointerImpl | ||
#include "BranchCloneChec | ||
#include "CastingThroughV | ||
@@ -48,6 +49,7 @@ | ||
#include "MultipleStateme | ||
#include "NoEscapeCheck.h | ||
#include "NonZeroEnumToBo | ||
+#include "Nondeterministi | ||
#include "NotNullTerminat | ||
#include "OptionalValueCo | ||
#include "ParentVirtualCa | ||
@@ -78,6 +80,7 @@ | ||
#include "SuspiciousStrin | ||
#include "SwappedArgument | ||
#include "SwitchMissingDe | ||
+#include "TaggedUnionMemb | ||
#include "TerminatingCont | ||
#include "ThrowKeywordMis | ||
#include "TooSmallLoopVar | ||
@@ -108,6 +111,8 @@ public: | ||
"bugprone-assign | ||
CheckFactories.r | ||
"bugprone-bad-si | ||
+ CheckFactories.r | ||
+ "bugprone-bitwis | ||
CheckFactories.r | ||
"bugprone-bool-p | ||
CheckFactories.r | ||
@@ -170,6 +175,8 @@ public: | ||
"bugprone-multip | ||
CheckFactories.r | ||
"bugprone-multip | ||
+ CheckFactories.r | ||
+ "bugprone-nondet | ||
CheckFactories.r | ||
"bugprone-option | ||
CheckFactories.r | ||
@@ -229,6 +236,8 @@ public: | ||
"bugprone-suspic | ||
CheckFactories.r | ||
"bugprone-swappe | ||
+ CheckFactories.r | ||
+ "bugprone-tagged | ||
CheckFactories.r | ||
"bugprone-termin | ||
CheckFactories.r |
@@ -3,11 +3,12 @@ set(LLVM_LINK_CO | ||
FrontendOpenMP | ||
) | ||
-add_clang_libra | ||
+add_clang_libra | ||
ArgumentCommentC | ||
AssertSideEffect | ||
AssignmentInIfCo | ||
BadSignalToKillT | ||
+ BitwisePointerCa | ||
BoolPointerImpli | ||
BranchCloneCheck | ||
BugproneTidyModu | ||
@@ -44,6 +45,7 @@ add_clang_librar | ||
MultipleNewInOne | ||
MultipleStatemen | ||
NoEscapeCheck.cp | ||
+ Nondeterministic | ||
NonZeroEnumToBoo | ||
NotNullTerminate | ||
OptionalValueCon | ||
@@ -73,6 +75,7 @@ add_clang_librar | ||
SuspiciousSemico | ||
SuspiciousString | ||
SwappedArguments | ||
+ TaggedUnionMembe | ||
TerminatingConti | ||
ThrowKeywordMiss | ||
TooSmallLoopVari |
@@ -38,7 +38,9 @@ void CastingThroughVo | ||
const auto ST = *Result.Nodes.ge | ||
const auto VT = *Result.Nodes.ge | ||
const auto *CE = Result.Nodes.get | ||
- diag(CE->getExpr | ||
+ diag(CE->getExpr | ||
+ "do not cast %0 to %1 through %2; use reinterpret_cast | ||
+ << ST << TT << VT; | ||
} | ||
} // namespace clang::tidy::bug |
@@ -165,7 +165,7 @@ void CrtpConstructorA | ||
WithFriendHintIf | ||
diag(Ctor->getLo | ||
- "%0 contructor allows the CRTP to be %select{inherite | ||
+ "%0 constructor allows the CRTP to be %select{inherite | ||
"from|constructe | ||
"it private%select{| | ||
<< Access << IsPublic << NeedsFriend |
@@ -97,8 +97,8 @@ DanglingHandleCh | ||
ClangTidyContext | ||
: ClangTidyCheck(N | ||
HandleClasses(ut | ||
- "HandleClasses", | ||
- "std::basic_stri | ||
+ "HandleClasses", | ||
+ "string_view;std | ||
IsAHandle(cxxRec | ||
void DanglingHandleCh |
@@ -107,7 +107,6 @@ static std::string getNameOfNamespa | ||
std::string Ns; | ||
llvm::raw_string | ||
NsDecl->printQua | ||
- OStream.flush(); | ||
return Ns.empty() ? "(global)" : Ns; | ||
} | ||
@@ -147,12 +146,13 @@ void ForwardDeclarati | ||
} | ||
// Check if a definition in another namespace exists. | ||
const auto DeclName = CurDecl->getName | ||
- if (!DeclNameToDefi | ||
+ auto It = DeclNameToDefini | ||
+ if (It == DeclNameToDefini | ||
continue; // No definition in this translation unit, we can skip it. | ||
} | ||
// Make a warning for each definition with the same name (in other | ||
// namespaces). | ||
- const auto &Definitions = DeclNameToDefini | ||
+ const auto &Definitions = It->second; | ||
for (const auto *Def : Definitions) { | ||
diag(CurDecl->ge | ||
"no definition found for %0, but a definition with " |
@@ -9,7 +9,6 @@ | ||
#include "ForwardingRefer | ||
#include "clang/AST/ASTCo | ||
#include "clang/ASTMatche | ||
-#include <algorithm> | ||
using namespace clang::ast_match | ||
@@ -19,14 +18,14 @@ namespace { | ||
// Check if the given type is related to std::enable_if. | ||
AST_MATCHER(Qual | ||
auto CheckTemplate = [](const TemplateSpeciali | ||
- if (!Spec || !Spec->getTempla | ||
+ if (!Spec) | ||
return false; | ||
- } | ||
- const NamedDecl *TypeDecl = | ||
- Spec->getTemplat | ||
- return TypeDecl->isInSt | ||
- (TypeDecl->getNa | ||
- TypeDecl->getNam | ||
+ | ||
+ const TemplateDecl *TDecl = Spec->getTemplat | ||
+ | ||
+ return TDecl && TDecl->isInStdNa | ||
+ (TDecl->getName( | ||
+ TDecl->getName() | ||
}; | ||
const Type *BaseType = Node.getTypePtr( | ||
// Case: pointer or reference to enable_if. |
@@ -0,0 +1,78 @@ | ||
+//===----- Nondeterministic | ||
+// | ||
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | ||
+// See https://llvm.org | ||
+// SPDX-License-Ide | ||
+// | ||
+//===---------- | ||
+ | ||
+#include "Nondeterministi | ||
+#include "clang/AST/ASTCo | ||
+#include "clang/Lex/Lexer | ||
+ | ||
+using namespace clang::ast_match | ||
+ | ||
+namespace clang::tidy::bug | ||
+ | ||
+void Nondeterministic | ||
+ MatchFinder *Finder) { | ||
+ | ||
+ auto LoopVariable = varDecl(hasType( | ||
+ qualType(hasCano | ||
+ | ||
+ auto RangeInit = declRefExpr(to(v | ||
+ hasType(recordDe | ||
+ "std::unordered_ | ||
+ "std::unordered_ | ||
+ .bind("recorddec | ||
+ | ||
+ Finder->addMatch | ||
+ hasRangeInit(Ran | ||
+ .bind("cxxForRan | ||
+ this); | ||
+ | ||
+ auto SortFuncM = callee(functionD | ||
+ "std::is_sorted" | ||
+ "std::partition" | ||
+ | ||
+ auto IteratesPointerE | ||
+ 0, | ||
+ cxxMemberCallExp | ||
+ hasCanonicalType | ||
+ | ||
+ Finder->addMatch | ||
+ callExpr(allOf(S | ||
+ this); | ||
+} | ||
+ | ||
+void Nondeterministic | ||
+ const MatchFinder::Mat | ||
+ const auto *ForRangePointer | ||
+ Result.Nodes.get | ||
+ | ||
+ if ((ForRangePointe | ||
+ const auto *RangeInit = Result.Nodes.get | ||
+ if (const auto *ClassTemplate = | ||
+ Result.Nodes.get | ||
+ "recorddecl")) { | ||
+ const TemplateArgument | ||
+ ClassTemplate->g | ||
+ const bool IsAlgoArgPointer | ||
+ TemplateArgs[0]. | ||
+ | ||
+ if (IsAlgoArgPointe | ||
+ SourceRange R = RangeInit->getSo | ||
+ diag(R.getBegin( | ||
+ } | ||
+ } | ||
+ return; | ||
+ } | ||
+ const auto *SortPointers = Result.Nodes.get | ||
+ | ||
+ if ((SortPointers) && !(SortPointers-> | ||
+ SourceRange R = SortPointers->ge | ||
+ diag(R.getBegin( | ||
+ } | ||
+} | ||
+ | ||
+} // namespace clang::tidy::bug |
@@ -0,0 +1,39 @@ | ||
+//=== Nondeterministic | ||
+// | ||
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | ||
+// See https://llvm.org | ||
+// SPDX-License-Ide | ||
+// | ||
+//===---------- | ||
+ | ||
+#ifndef LLVM_CLANG_TOOLS | ||
+#define LLVM_CLANG_TOOLS | ||
+ | ||
+#include "../ClangTidyChe | ||
+ | ||
+namespace clang::tidy::bug | ||
+ | ||
+/// Finds nondeterministic | ||
+/// check also finds calls to sorting-like algorithms on a container of | ||
+/// pointers. | ||
+/// | ||
+/// For the user-facing documentation see: | ||
+/// http://clang.llv | ||
+class Nondeterministic | ||
+public: | ||
+ Nondeterministic | ||
+ ClangTidyContext | ||
+ : ClangTidyCheck(N | ||
+ bool isLanguageVersio | ||
+ return LangOpts.CPlusPl | ||
+ } | ||
+ void registerMatchers | ||
+ void check(const ast_matchers::Ma | ||
+ std::optional<Tr | ||
+ return TK_IgnoreUnlessS | ||
+ } | ||
+}; | ||
+ | ||
+} // namespace clang::tidy::bug | ||
+ | ||
+#endif // LLVM_CLANG_TOOLS |
@@ -7,19 +7,17 @@ | ||
//===----------- | ||
#include "PosixReturnChec | ||
-#include "../utils/Matche | ||
#include "clang/AST/ASTCo | ||
#include "clang/ASTMatche | ||
+#include "clang/ASTMatche | ||
#include "clang/Lex/Lexer | ||
using namespace clang::ast_match | ||
namespace clang::tidy::bug | ||
-static StringRef getFunctionSpell | ||
- const char *BindingStr) { | ||
- const CallExpr *MatchedCall = cast<CallExpr>( | ||
- (Result.Nodes.ge | ||
+static StringRef getFunctionSpell | ||
+ const auto *MatchedCall = Result.Nodes.get | ||
const SourceManager &SM = *Result.SourceMa | ||
return Lexer::getSource | ||
MatchedCall->get | ||
@@ -27,32 +25,40 @@ static StringRef getFunctionSpell | ||
} | ||
void PosixReturnCheck | ||
+ const auto PosixCall = | ||
+ callExpr(callee( | ||
+ anyOf(matchesNam | ||
+ unless(hasName(" | ||
+ .bind("call"); | ||
+ const auto ZeroIntegerLiter | ||
+ const auto NegIntegerLitera | ||
+ unaryOperator(ha | ||
+ | ||
Finder->addMatch | ||
binaryOperator( | ||
- hasOperatorName( | ||
- hasLHS(callExpr( | ||
- anyOf(matchesNam | ||
- unless(hasName(" | ||
- hasRHS(integerLi | ||
+ anyOf(allOf(hasO | ||
+ hasRHS(ZeroInteg | ||
+ allOf(hasOperato | ||
+ hasRHS(PosixCall | ||
.bind("ltzop"), | ||
this); | ||
Finder->addMatch | ||
binaryOperator( | ||
- hasOperatorName( | ||
- hasLHS(callExpr( | ||
- anyOf(matchesNam | ||
- unless(hasName(" | ||
- hasRHS(integerLi | ||
+ anyOf(allOf(hasO | ||
+ hasRHS(ZeroInteg | ||
+ allOf(hasOperato | ||
+ hasRHS(PosixCall | ||
.bind("atop"), | ||
this); | ||
+ Finder->addMatch | ||
+ hasOperands(Posi | ||
+ .bind("binop"), | ||
+ this); | ||
Finder->addMatch | ||
- binaryOperator( | ||
- hasAnyOperatorNa | ||
- hasLHS(callExpr( | ||
- anyOf(matchesNam | ||
- unless(hasName(" | ||
- hasRHS(unaryOper | ||
- hasUnaryOperand( | ||
+ binaryOperator(a | ||
+ hasLHS(PosixCall | ||
+ allOf(hasAnyOper | ||
+ hasLHS(NegIntege | ||
.bind("binop"), | ||
this); | ||
} | ||
@@ -61,10 +67,13 @@ void PosixReturnCheck | ||
if (const auto *LessThanZeroOp = | ||
Result.Nodes.get | ||
SourceLocation OperatorLoc = LessThanZeroOp-> | ||
+ StringRef NewBinOp = | ||
+ LessThanZeroOp-> | ||
+ : "<"; | ||
diag(OperatorLoc | ||
"always returns non-negative values") | ||
- << getFunctionSpell | ||
- << FixItHint::Creat | ||
+ << getFunctionSpell | ||
+ << FixItHint::Creat | ||
return; | ||
} | ||
if (const auto *AlwaysTrueOp = | ||
@@ -72,12 +81,12 @@ void PosixReturnCheck | ||
diag(AlwaysTrueO | ||
"the comparison always evaluates to true because %0 always returns " | ||
"non-negative values") | ||
- << getFunctionSpell | ||
+ << getFunctionSpell | ||
return; | ||
} | ||
const auto *BinOp = Result.Nodes.get | ||
diag(BinOp->getO | ||
- << getFunctionSpell | ||
+ << getFunctionSpell | ||
} | ||
} // namespace clang::tidy::bug |
@@ -7,6 +7,7 @@ | ||
//===----------- | ||
#include "ReturnConstRefF | ||
+#include "clang/AST/Expr. | ||
#include "clang/ASTMatche | ||
#include "clang/ASTMatche | ||
@@ -15,19 +16,24 @@ using namespace clang::ast_match | ||
namespace clang::tidy::bug | ||
void ReturnConstRefFr | ||
+ const auto DRef = ignoringParens( | ||
+ declRefExpr( | ||
+ to(parmVarDecl(h | ||
+ qualType(lValueR | ||
+ qualType(isConst | ||
+ .bind("type")))) | ||
+ .bind("param"))) | ||
+ .bind("dref")); | ||
+ const auto Func = | ||
+ functionDecl(has | ||
+ qualType(hasCano | ||
+ .bind("func"); | ||
+ | ||
+ Finder->addMatch | ||
Finder->addMatch | ||
- returnStmt( | ||
- hasReturnValue(d | ||
- to(parmVarDecl(h | ||
- qualType(lValueR | ||
- qualType(isConst | ||
- .bind("type")))) | ||
- .bind("param"))) | ||
- hasAncestor( | ||
- functionDecl(has | ||
- hasCanonicalType | ||
- .bind("func"))) | ||
- .bind("ret"), | ||
+ returnStmt(hasRe | ||
+ eachOf(hasTrueEx | ||
+ hasAncestor(Func | ||
this); | ||
} | ||
@@ -85,8 +91,8 @@ void ReturnConstRefFr | ||
const MatchFinder::Mat | ||
const auto *FD = Result.Nodes.get | ||
const auto *PD = Result.Nodes.get | ||
- const auto *R = Result.Nodes.get | ||
- const SourceRange Range = R->getRetValue() | ||
+ const auto *DRef = Result.Nodes.get | ||
+ const SourceRange Range = DRef->getSourceR | ||
if (Range.isInvalid | ||
return; | ||
@@ -48,6 +48,8 @@ AST_MATCHER_P2(E | ||
return false; | ||
} | ||
+AST_MATCHER(Exp | ||
+ | ||
CharUnits getSizeOfType(co | ||
if (!Ty || Ty->isIncomplete | ||
isa<DependentSiz | ||
@@ -68,7 +70,9 @@ SizeofExpression | ||
Options.get("War | ||
WarnOnSizeOfPoin | ||
Options.get("War | ||
- WarnOnSizeOfPoin | ||
+ WarnOnSizeOfPoin | ||
+ WarnOnOffsetDivi | ||
+ Options.get("War | ||
void SizeofExpression | ||
Options.store(Op | ||
@@ -80,6 +84,8 @@ void SizeofExpression | ||
Options.store(Op | ||
WarnOnSizeOfPoin | ||
Options.store(Op | ||
+ Options.store(Op | ||
+ WarnOnOffsetDivi | ||
} | ||
void SizeofExpression | ||
@@ -221,17 +227,15 @@ void SizeofExpression | ||
const auto ElemType = | ||
arrayType(hasEle | ||
const auto ElemPtrType = pointerType(poin | ||
+ const auto SizeofDivideExpr | ||
+ hasOperatorName( | ||
+ hasLHS( | ||
+ ignoringParenImp | ||
+ type(anyOf(ElemT | ||
+ hasRHS(ignoringP | ||
+ hasArgumentOfTyp | ||
- Finder->addMatch | ||
- binaryOperator( | ||
- hasOperatorName( | ||
- hasLHS(ignoringP | ||
- hasCanonicalType | ||
- .bind("num-type" | ||
- hasRHS(ignoringP | ||
- hasArgumentOfTyp | ||
- .bind("sizeof-di | ||
- this); | ||
+ Finder->addMatch | ||
// Detect expression like: sizeof(...) * sizeof(...)); most likely an error. | ||
Finder->addMatch | ||
@@ -257,8 +261,9 @@ void SizeofExpression | ||
.bind("sizeof-si | ||
this); | ||
- // Detect sizeof in pointer arithmetic like: N * sizeof(S) == P1 - P2 or | ||
- // (P1 - P2) / sizeof(S) where P1 and P2 are pointers to type S. | ||
+ // Detect sizeof usage in comparisons involving pointer arithmetics, such as | ||
+ // N * sizeof(T) == P1 - P2 or (P1 - P2) / sizeof(T), where P1 and P2 are | ||
+ // pointers to a type T. | ||
const auto PtrDiffExpr = binaryOperator( | ||
hasOperatorName( | ||
hasLHS(hasType(h | ||
@@ -285,6 +290,48 @@ void SizeofExpression | ||
hasRHS(ignoringP | ||
.bind("sizeof-in | ||
this); | ||
+ | ||
+ // SEI CERT ARR39-C. Do not add or subtract a scaled integer to a pointer. | ||
+ // Detect sizeof, alignof and offsetof usage in pointer arithmetics where | ||
+ // they are used to scale the numeric distance, which is scaled again by | ||
+ // the pointer arithmetic operator. This can result in forming invalid | ||
+ // offsets. | ||
+ // | ||
+ // Examples, where P is a pointer, N is some integer (both compile-time and | ||
+ // run-time): P + sizeof(T), P + sizeof(*P), P + N * sizeof(*P). | ||
+ // | ||
+ // This check does not warn on cases where the pointee type is "1 byte", | ||
+ // as those cases can often come from generics and also do not constitute a | ||
+ // problem because the size does not affect the scale used. | ||
+ const auto InterestingPtrTy | ||
+ pointerType(poin | ||
+ const auto SizeofLikeScaleE | ||
+ expr(anyOf(unary | ||
+ unaryExprOrTypeT | ||
+ offsetOfExpr())) | ||
+ .bind("sizeof-in | ||
+ const auto PtrArithmeticInt | ||
+ WarnOnOffsetDivi | ||
+ : binaryOperator(h | ||
+ // sizeof(...) * sizeof(...) and sizeof(...) / sizeof(...) is handled | ||
+ // by this check on another path. | ||
+ hasOperands(expr | ||
+ SizeofLikeScaleE | ||
+ const auto PtrArithmeticSca | ||
+ expr(anyOf(Sizeo | ||
+ unless(SizeofDiv | ||
+ | ||
+ Finder->addMatch | ||
+ expr(anyOf( | ||
+ binaryOperator(h | ||
+ hasOperands(hasT | ||
+ PtrArithmeticSca | ||
+ .bind("sizeof-in | ||
+ binaryOperator(h | ||
+ hasLHS(hasType(I | ||
+ hasRHS(PtrArithm | ||
+ .bind("sizeof-in | ||
+ this); | ||
} | ||
void SizeofExpression | ||
@@ -409,6 +456,43 @@ void SizeofExpression | ||
<< SizeOfExpr->getS | ||
<< E->getLHS()->get | ||
} | ||
+ } else if (const auto *E = Result.Nodes.get | ||
+ "sizeof-in-ptr-a | ||
+ const auto *PointeeTy = Result.Nodes.get | ||
+ const auto *ScaleExpr = | ||
+ Result.Nodes.get | ||
+ const CharUnits PointeeSize = getSizeOfType(Ct | ||
+ const int ScaleKind = [ScaleExpr]() { | ||
+ if (const auto *UTTE = dyn_cast<UnaryEx | ||
+ switch (UTTE->getKind() | ||
+ case UETT_SizeOf: | ||
+ return 0; | ||
+ case UETT_AlignOf: | ||
+ return 1; | ||
+ default: | ||
+ return -1; | ||
+ } | ||
+ | ||
+ if (isa<OffsetOfExp | ||
+ return 2; | ||
+ | ||
+ return -1; | ||
+ }(); | ||
+ | ||
+ if (ScaleKind != -1 && PointeeSize > CharUnits::One() | ||
+ diag(E->getExprL | ||
+ "suspicious usage of '%select{sizeof| | ||
+ "pointer arithmetic; this scaled value will be scaled again by the " | ||
+ "'%1' operator") | ||
+ << ScaleKind << E->getOpcodeStr( | ||
+ diag(E->getExprL | ||
+ "'%0' in pointer arithmetic internally scales with 'sizeof(%1)' == " | ||
+ "%2", | ||
+ DiagnosticIDs::N | ||
+ << E->getOpcodeStr( | ||
+ << PointeeTy->getAs | ||
+ << PointeeSize.getQ | ||
+ } | ||
} | ||
} | ||
@@ -13,7 +13,7 @@ | ||
namespace clang::tidy::bug | ||
-/// Find suspicious usages of sizeof expression. | ||
+/// Find suspicious usages of sizeof expressions. | ||
/// | ||
/// For the user-facing documentation see: | ||
/// http://clang.llv | ||
@@ -31,6 +31,7 @@ private: | ||
const bool WarnOnSizeOfComp | ||
const bool WarnOnSizeOfPoin | ||
const bool WarnOnSizeOfPoin | ||
+ const bool WarnOnOffsetDivi | ||
}; | ||
} // namespace clang::tidy::bug |
@@ -0,0 +1,199 @@ | ||
+//===--- TaggedUnionMembe | ||
+// | ||
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | ||
+// See https://llvm.org | ||
+// SPDX-License-Ide | ||
+// | ||
+//===---------- | ||
+ | ||
+#include "TaggedUnionMemb | ||
+#include "../utils/Option | ||
+#include "clang/ASTMatche | ||
+#include "llvm/ADT/STLExt | ||
+#include "llvm/ADT/SmallS | ||
+ | ||
+using namespace clang::ast_match | ||
+ | ||
+namespace clang::tidy::bug | ||
+ | ||
+static constexpr llvm::StringLite | ||
+static constexpr llvm::StringLite | ||
+ "EnableCountingE | ||
+static constexpr llvm::StringLite | ||
+ "CountingEnumPre | ||
+static constexpr llvm::StringLite | ||
+ "CountingEnumSuf | ||
+ | ||
+static constexpr bool StrictModeOption | ||
+static constexpr bool EnableCountingEn | ||
+static constexpr llvm::StringLite | ||
+ ""; | ||
+static constexpr llvm::StringLite | ||
+ "count"; | ||
+ | ||
+static constexpr llvm::StringLite | ||
+static constexpr llvm::StringLite | ||
+static constexpr llvm::StringLite | ||
+ | ||
+namespace { | ||
+ | ||
+AST_MATCHER_P2( | ||
+ ast_matchers::in | ||
+ StringRef, BindName) { | ||
+ // BoundNodesTreeBu | ||
+ // So to avoid losing previously saved binds, a temporary instance | ||
+ // is used for matching. | ||
+ // | ||
+ // For precedence, see commit: 5b07de1a5faf4a22 | ||
+ clang::ast_match | ||
+ | ||
+ const FieldDecl *FirstMatch = nullptr; | ||
+ for (const FieldDecl *Field : Node.fields()) { | ||
+ if (InnerMatcher.ma | ||
+ if (FirstMatch) { | ||
+ return false; | ||
+ } else { | ||
+ FirstMatch = Field; | ||
+ } | ||
+ } | ||
+ } | ||
+ | ||
+ if (FirstMatch) { | ||
+ Builder->setBind | ||
+ return true; | ||
+ } | ||
+ return false; | ||
+} | ||
+ | ||
+} // namespace | ||
+ | ||
+TaggedUnionMemb | ||
+ StringRef Name, ClangTidyContext | ||
+ : ClangTidyCheck(N | ||
+ StrictMode( | ||
+ Options.get(Stri | ||
+ EnableCountingEn | ||
+ Options.get(Enab | ||
+ EnableCountingEn | ||
+ CountingEnumPref | ||
+ Options.get(Coun | ||
+ CountingEnumPref | ||
+ CountingEnumSuff | ||
+ Options.get(Coun | ||
+ CountingEnumSuff | ||
+ if (!EnableCounting | ||
+ if (Options.get(Cou | ||
+ configurationDia | ||
+ "%1 is set") | ||
+ << Name << CountingEnumPref | ||
+ if (Options.get(Cou | ||
+ configurationDia | ||
+ "%1 is set") | ||
+ << Name << CountingEnumSuff | ||
+ } | ||
+} | ||
+ | ||
+void TaggedUnionMembe | ||
+ ClangTidyOptions | ||
+ Options.store(Op | ||
+ Options.store(Op | ||
+ EnableCountingEn | ||
+ Options.store(Op | ||
+ utils::options:: | ||
+ Options.store(Op | ||
+ utils::options:: | ||
+} | ||
+ | ||
+void TaggedUnionMembe | ||
+ | ||
+ auto UnionField = fieldDecl(hasTyp | ||
+ hasCanonicalType | ||
+ | ||
+ auto EnumField = fieldDecl(hasTyp | ||
+ qualType(hasCano | ||
+ | ||
+ auto hasOneUnionField | ||
+ auto hasOneEnumField = fieldCountOfKind | ||
+ | ||
+ Finder->addMatch | ||
+ hasOneEnumField, | ||
+ .bind(RootMatchB | ||
+ this); | ||
+} | ||
+ | ||
+bool TaggedUnionMembe | ||
+ if (llvm::any_of(Co | ||
+ return Name.starts_with | ||
+ })) | ||
+ return true; | ||
+ if (llvm::any_of(Co | ||
+ return Name.ends_with_i | ||
+ })) | ||
+ return true; | ||
+ return false; | ||
+} | ||
+ | ||
+std::pair<const | ||
+TaggedUnionMemb | ||
+ llvm::SmallSet<l | ||
+ | ||
+ const EnumConstantDecl | ||
+ for (const EnumConstantDecl | ||
+ EnumValues.inser | ||
+ LastEnumConstant | ||
+ } | ||
+ | ||
+ if (EnableCountingE | ||
+ isCountingEnumLi | ||
+ (LastEnumConstan | ||
+ return {EnumValues.size | ||
+ } | ||
+ | ||
+ return {EnumValues.size | ||
+} | ||
+ | ||
+void TaggedUnionMembe | ||
+ const MatchFinder::Mat | ||
+ const auto *Root = Result.Nodes.get | ||
+ const auto *UnionField = | ||
+ Result.Nodes.get | ||
+ const auto *TagField = Result.Nodes.get | ||
+ | ||
+ assert(Root && "Root is missing!"); | ||
+ assert(UnionFiel | ||
+ assert(TagField && "TagField is missing!"); | ||
+ if (!Root || !UnionField || !TagField) | ||
+ return; | ||
+ | ||
+ const auto *UnionDef = | ||
+ UnionField->getT | ||
+ const auto *EnumDef = llvm::dyn_cast<E | ||
+ TagField->getTyp | ||
+ | ||
+ assert(UnionDef && "UnionDef is missing!"); | ||
+ assert(EnumDef && "EnumDef is missing!"); | ||
+ if (!UnionDef || !EnumDef) | ||
+ return; | ||
+ | ||
+ const std::size_t UnionMemberCount | ||
+ auto [TagCount, CountingEnumCons | ||
+ | ||
+ if (UnionMemberCoun | ||
+ diag(Root->getLo | ||
+ "tagged union has more data members (%0) than tags (%1)!") | ||
+ << UnionMemberCount | ||
+ } else if (StrictMode && UnionMemberCount | ||
+ diag(Root->getLo | ||
+ "tagged union has fewer data members (%0) than tags (%1)!") | ||
+ << UnionMemberCount | ||
+ } | ||
+ | ||
+ if (CountingEnumCon | ||
+ diag(CountingEnu | ||
+ "assuming that this constant is just an auxiliary value and not " | ||
+ "used for indicating a valid union data member", | ||
+ DiagnosticIDs::N | ||
+ } | ||
+} | ||
+ | ||
+} // namespace clang::tidy::bug |
@@ -0,0 +1,41 @@ | ||
+//===--- TaggedUnionMembe | ||
+// | ||
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | ||
+// See https://llvm.org | ||
+// SPDX-License-Ide | ||
+// | ||
+//===---------- | ||
+ | ||
+#ifndef LLVM_CLANG_TOOLS | ||
+#define LLVM_CLANG_TOOLS | ||
+ | ||
+#include "../ClangTidyChe | ||
+ | ||
+namespace clang::tidy::bug | ||
+ | ||
+/// Gives warnings for tagged unions, where the number of tags is | ||
+/// different from the number of data members inside the union. | ||
+/// | ||
+/// For the user-facing documentation see: | ||
+/// http://clang.llv | ||
+class TaggedUnionMembe | ||
+public: | ||
+ TaggedUnionMembe | ||
+ void storeOptions(Cla | ||
+ void registerMatchers | ||
+ void check(const ast_matchers::Ma | ||
+ | ||
+private: | ||
+ const bool StrictMode; | ||
+ const bool EnableCountingEn | ||
+ const std::vector<Stri | ||
+ const std::vector<Stri | ||
+ | ||
+ std::pair<const std::size_t, const EnumConstantDecl | ||
+ getNumberOfEnumV | ||
+ bool isCountingEnumLi | ||
+}; | ||
+ | ||
+} // namespace clang::tidy::bug | ||
+ | ||
+#endif // LLVM_CLANG_TOOLS |
@@ -7,6 +7,7 @@ | ||
//===----------- | ||
#include "UnsafeFunctions | ||
+#include "../utils/Option | ||
#include "clang/AST/ASTCo | ||
#include "clang/ASTMatche | ||
#include "clang/Lex/PPCal | ||
@@ -18,6 +19,10 @@ using namespace llvm; | ||
namespace clang::tidy::bug | ||
+static constexpr llvm::StringLite | ||
+ "CustomFunctions | ||
+static constexpr llvm::StringLite | ||
+ "ReportDefaultFu | ||
static constexpr llvm::StringLite | ||
"ReportMoreUnsaf | ||
@@ -26,6 +31,8 @@ static constexpr llvm::StringLite | ||
static constexpr llvm::StringLite | ||
static constexpr llvm::StringLite | ||
"AdditionalFunct | ||
+static constexpr llvm::StringLite | ||
+ "CustomFunctionN | ||
static constexpr llvm::StringLite | ||
static std::optional<st | ||
@@ -127,57 +134,128 @@ static bool isAnnexKAvailabl | ||
return CacheVar.value() | ||
} | ||
+static std::vector<Unsa | ||
+parseCheckedFun | ||
+ const std::vector<Stri | ||
+ utils::options:: | ||
+ std::vector<Unsa | ||
+ Result.reserve(F | ||
+ | ||
+ for (StringRef Function : Functions) { | ||
+ if (Function.empty( | ||
+ continue; | ||
+ | ||
+ const auto [Name, Rest] = Function.split(' | ||
+ const auto [Replacement, Reason] = Rest.split(','); | ||
+ | ||
+ if (Name.trim().emp | ||
+ Context->configu | ||
+ "expected the name of an unsafe function") | ||
+ << OptionNameCustom | ||
+ continue; | ||
+ } | ||
+ | ||
+ Result.push_back | ||
+ {Name.trim().str | ||
+ matchers::Matche | ||
+ Replacement.trim | ||
+ } | ||
+ | ||
+ return Result; | ||
+} | ||
+ | ||
+static std::string serializeChecked | ||
+ const std::vector<Unsa | ||
+ std::vector<std: | ||
+ Result.reserve(F | ||
+ | ||
+ for (const auto &Entry : Functions) { | ||
+ if (Entry.Reason.em | ||
+ Result.push_back | ||
+ else | ||
+ Result.push_back | ||
+ Entry.Reason); | ||
+ } | ||
+ | ||
+ return llvm::join(Resul | ||
+} | ||
+ | ||
UnsafeFunctionsC | ||
ClangTidyContext | ||
: ClangTidyCheck(N | ||
+ CustomFunctions( | ||
+ Options.get(Opti | ||
+ ReportDefaultFun | ||
+ Options.get(Opti | ||
ReportMoreUnsafe | ||
Options.get(Opti | ||
void UnsafeFunctionsC | ||
+ Options.store(Op | ||
+ serializeChecked | ||
+ Options.store(Op | ||
Options.store(Op | ||
ReportMoreUnsafe | ||
} | ||
void UnsafeFunctionsC | ||
- if (getLangOpts().C | ||
- // Matching functions with safe replacements only in Annex K. | ||
- auto FunctionNamesWit | ||
- "::bsearch", "::ctime", "::fopen", "::fprintf", "::freopen", "::fscanf", | ||
- "::fwprintf", "::fwscanf", "::getenv", "::gmtime", "::localtime", | ||
- "::mbsrtowcs", "::mbstowcs", "::memcpy", "::memmove", "::memset", | ||
- "::printf", "::qsort", "::scanf", "::snprintf", "::sprintf", "::sscanf", | ||
- "::strcat", "::strcpy", "::strerror", "::strlen", "::strncat", | ||
- "::strncpy", "::strtok", "::swprintf", "::swscanf", "::vfprintf", | ||
- "::vfscanf", "::vfwprintf", "::vfwscanf", "::vprintf", "::vscanf", | ||
- "::vsnprintf", "::vsprintf", "::vsscanf", "::vswprintf", "::vswscanf", | ||
- "::vwprintf", "::vwscanf", "::wcrtomb", "::wcscat", "::wcscpy", | ||
- "::wcslen", "::wcsncat", "::wcsncpy", "::wcsrtombs", "::wcstok", | ||
- "::wcstombs", "::wctomb", "::wmemcpy", "::wmemmove", "::wprintf", | ||
- "::wscanf"); | ||
+ if (ReportDefaultFu | ||
+ if (getLangOpts().C | ||
+ // Matching functions with safe replacements only in Annex K. | ||
+ auto FunctionNamesWit | ||
+ "::bsearch", "::ctime", "::fopen", "::fprintf", "::freopen", | ||
+ "::fscanf", "::fwprintf", "::fwscanf", "::getenv", "::gmtime", | ||
+ "::localtime", "::mbsrtowcs", "::mbstowcs", "::memcpy", "::memmove", | ||
+ "::memset", "::printf", "::qsort", "::scanf", "::snprintf", | ||
+ "::sprintf", "::sscanf", "::strcat", "::strcpy", "::strerror", | ||
+ "::strlen", "::strncat", "::strncpy", "::strtok", "::swprintf", | ||
+ "::swscanf", "::vfprintf", "::vfscanf", "::vfwprintf", "::vfwscanf", | ||
+ "::vprintf", "::vscanf", "::vsnprintf", "::vsprintf", "::vsscanf", | ||
+ "::vswprintf", "::vswscanf", "::vwprintf", "::vwscanf", "::wcrtomb", | ||
+ "::wcscat", "::wcscpy", "::wcslen", "::wcsncat", "::wcsncpy", | ||
+ "::wcsrtombs", "::wcstok", "::wcstombs", "::wctomb", "::wmemcpy", | ||
+ "::wmemmove", "::wprintf", "::wscanf"); | ||
+ Finder->addMatch | ||
+ declRefExpr(to(f | ||
+ .bind(FunctionNa | ||
+ .bind(DeclRefId) | ||
+ this); | ||
+ } | ||
+ | ||
+ // Matching functions with replacements without Annex K. | ||
+ auto FunctionNamesMat | ||
+ hasAnyName("::as | ||
Finder->addMatch | ||
- declRefExpr(to(f | ||
- .bind(FunctionNa | ||
+ declRefExpr( | ||
+ to(functionDecl( | ||
.bind(DeclRefId) | ||
this); | ||
+ | ||
+ if (ReportMoreUnsaf | ||
+ // Matching functions with replacements without Annex K, at user request. | ||
+ auto AdditionalFuncti | ||
+ hasAnyName("::bc | ||
+ Finder->addMatch | ||
+ declRefExpr(to(f | ||
+ .bind(Additional | ||
+ .bind(DeclRefId) | ||
+ this); | ||
+ } | ||
} | ||
- // Matching functions with replacements without Annex K. | ||
- auto FunctionNamesMat | ||
- hasAnyName("::as | ||
- Finder->addMatch | ||
- declRefExpr(to(f | ||
- .bind(DeclRefId) | ||
- this); | ||
- | ||
- if (ReportMoreUnsaf | ||
- // Matching functions with replacements without Annex K, at user request. | ||
- auto AdditionalFuncti | ||
- hasAnyName("::bc | ||
- Finder->addMatch | ||
- declRefExpr(to(f | ||
- .bind(Additional | ||
- .bind(DeclRefId) | ||
- this); | ||
+ if (!CustomFunction | ||
+ std::vector<llvm | ||
+ FunctionNames.re | ||
+ | ||
+ for (const auto &Entry : CustomFunctions) | ||
+ FunctionNames.pu | ||
+ | ||
+ auto CustomFunctionsM | ||
+ | ||
+ Finder->addMatch | ||
+ .bind(CustomFunc | ||
+ .bind(DeclRefId) | ||
+ this); | ||
} | ||
} | ||
@@ -186,16 +264,46 @@ void UnsafeFunctionsC | ||
const auto *FuncDecl = cast<FunctionDec | ||
assert(DeclRef && FuncDecl && "No valid matched node in check()"); | ||
+ // Only one of these are matched at a time. | ||
const auto *AnnexK = Result.Nodes.get | ||
FunctionNamesWit | ||
const auto *Normal = Result.Nodes.get | ||
const auto *Additional = | ||
Result.Nodes.get | ||
- assert((AnnexK || Normal || Additional) && "No valid match category."); | ||
+ const auto *Custom = | ||
+ Result.Nodes.get | ||
+ assert((AnnexK || Normal || Additional || Custom) && | ||
+ "No valid match category."); | ||
bool AnnexKIsAvailabl | ||
isAnnexKAvailabl | ||
StringRef FunctionName = FuncDecl->getNam | ||
+ | ||
+ if (Custom) { | ||
+ for (const auto &Entry : CustomFunctions) | ||
+ if (Entry.Pattern.m | ||
+ StringRef Reason = | ||
+ Entry.Reason.emp | ||
+ | ||
+ if (Entry.Replaceme | ||
+ diag(DeclRef->ge | ||
+ << FuncDecl << Reason << Entry.Replacemen | ||
+ << DeclRef->getSour | ||
+ } else { | ||
+ diag(DeclRef->ge | ||
+ "function %0 %1; '%2' should be used instead") | ||
+ << FuncDecl << Reason << Entry.Replacemen | ||
+ << DeclRef->getSour | ||
+ } | ||
+ | ||
+ return; | ||
+ } | ||
+ } | ||
+ | ||
+ llvm_unreachable | ||
+ return; | ||
+ } | ||
+ | ||
const std::optional<st | ||
[&]() -> std::optional<st | ||
if (AnnexK) { |
@@ -10,6 +10,7 @@ | ||
#define LLVM_CLANG_TOOLS | ||
#include "../ClangTidyChe | ||
+#include "../utils/Matche | ||
#include <optional> | ||
namespace clang::tidy::bug | ||
@@ -32,7 +33,18 @@ public: | ||
Preprocessor *ModuleExpanderP | ||
void onEndOfTranslati | ||
+ struct CheckedFunction { | ||
+ std::string Name; | ||
+ matchers::Matche | ||
+ std::string Replacement; | ||
+ std::string Reason; | ||
+ }; | ||
+ | ||
private: | ||
+ const std::vector<Chec | ||
+ | ||
+ // If true, the default set of functions are reported. | ||
+ const bool ReportDefaultFun | ||
/// If true, additional functions from widely used API-s (such as POSIX) are | ||
/// added to the list of reported functions. | ||
const bool ReportMoreUnsafe |
@@ -14,6 +14,7 @@ | ||
#include "../bugprone/Res | ||
#include "../bugprone/Sig | ||
#include "../bugprone/Sig | ||
+#include "../bugprone/Siz | ||
#include "../bugprone/Spu | ||
#include "../bugprone/Sus | ||
#include "../bugprone/Unh | ||
@@ -281,6 +282,9 @@ public: | ||
"cert-oop58-cpp" | ||
// C checkers | ||
+ // ARR | ||
+ CheckFactories.r | ||
+ "cert-arr39-c"); | ||
// CON | ||
CheckFactories.r | ||
"cert-con36-c"); | ||
@@ -332,6 +336,12 @@ public: | ||
ClangTidyOptions | ||
ClangTidyOptions | ||
ClangTidyOptions | ||
+ Opts["cert-arr39 | ||
+ Opts["cert-arr39 | ||
+ Opts["cert-arr39 | ||
+ Opts["cert-arr39 | ||
+ Opts["cert-arr39 | ||
+ Opts["cert-arr39 | ||
Opts["cert-dcl16 | ||
Opts["cert-err33 | ||
Opts["cert-err33 |
@@ -3,7 +3,7 @@ set(LLVM_LINK_CO | ||
FrontendOpenMP | ||
) | ||
-add_clang_libra | ||
+add_clang_libra | ||
CERTTidyModule.c | ||
CommandProcessor | ||
DefaultOperatorN |
@@ -9,6 +9,7 @@ | ||
#include "FloatLoopCounte | ||
#include "clang/AST/ASTCo | ||
#include "clang/ASTMatche | ||
+#include "clang/ASTMatche | ||
using namespace clang::ast_match | ||
@@ -16,15 +17,30 @@ namespace clang::tidy::cer | ||
void FloatLoopCounter | ||
Finder->addMatch | ||
- forStmt(hasIncre | ||
+ forStmt(hasIncre | ||
+ declRefExpr(hasT | ||
+ to(varDecl().bin | ||
+ .bind("inc"))), | ||
+ hasCondition(for | ||
+ declRefExpr(hasT | ||
+ to(varDecl(equal | ||
+ .bind("cond")))) | ||
+ .bind("for"), | ||
this); | ||
} | ||
void FloatLoopCounter | ||
const auto *FS = Result.Nodes.get | ||
- diag(FS->getInc( | ||
- "floating-point type"); | ||
+ diag(FS->getInc( | ||
+ "floating-point type") | ||
+ << Result.Nodes.get | ||
+ << Result.Nodes.get | ||
+ | ||
+ if (!FS->getInc()-> | ||
+ if (const auto *V = Result.Nodes.get | ||
+ diag(V->getBegin | ||
+ DiagnosticIDs::N | ||
} | ||
} // namespace clang::tidy::cer |
@@ -3,7 +3,7 @@ set(LLVM_LINK_CO | ||
Support | ||
) | ||
-add_clang_libra | ||
+add_clang_libra | ||
ConcurrencyTidyM | ||
MtUnsafeCheck.cp | ||
ThreadCanceltype |
@@ -3,7 +3,7 @@ set(LLVM_LINK_CO | ||
Support | ||
) | ||
-add_clang_libra | ||
+add_clang_libra | ||
AvoidCapturingLa | ||
AvoidConstOrRefD | ||
AvoidDoWhileChec |
@@ -67,9 +67,7 @@ static bool canAdvanceAssign | ||
static void updateAssignment | ||
const FieldDecl *Field, const Expr *Init, const CXXConstructorDe | ||
llvm::DenseMap<c | ||
- auto It = AssignedFields.f | ||
- if (It == AssignedFields.e | ||
- It = AssignedFields.i | ||
+ auto It = AssignedFields.t | ||
if (!canAdvanceAssi | ||
// fast path for already decided field. | ||
@@ -85,7 +83,7 @@ static void updateAssignment | ||
memberExpr(hasOb | ||
member(fieldDecl | ||
auto DeclMatcher = declRefExpr( | ||
- to(varDecl(unles | ||
+ to(valueDecl(unl | ||
const bool HasDependence = !match(expr(anyO | ||
hasDescendant(Me | ||
hasDescendant(De |
@@ -474,10 +474,8 @@ void ProTypeMemberIni | ||
// It only includes fields that have not been fixed | ||
SmallPtrSet<cons | ||
forEachField(Cla | ||
- if (!HasRecordClass | ||
+ if (HasRecordClassM | ||
AllFieldsToInit. | ||
- HasRecordClassMe | ||
- } | ||
}); | ||
if (FieldsToInit.em | ||
return; |
@@ -23,8 +23,11 @@ void ProTypeUnionAcce | ||
void ProTypeUnionAcce | ||
const auto *Matched = Result.Nodes.get | ||
- diag(Matched->ge | ||
- "do not access members of unions; use (boost::)variant | ||
+ SourceLocation Loc = Matched->getMemb | ||
+ if (Loc.isInvalid() | ||
+ Loc = Matched->getBegi | ||
+ diag(Loc, "do not access members of unions; consider using (boost::)variant | ||
+ "instead"); | ||
} | ||
} // namespace clang::tidy::cpp |
@@ -3,7 +3,7 @@ set(LLVM_LINK_CO | ||
Support | ||
) | ||
-add_clang_libra | ||
+add_clang_libra | ||
AvoidSpinlockChe | ||
DarwinTidyModule | ||
DispatchOnceNons |
@@ -3,7 +3,7 @@ set(LLVM_LINK_CO | ||
Support | ||
) | ||
-add_clang_libra | ||
+add_clang_libra | ||
DefaultArguments | ||
DefaultArguments | ||
FuchsiaTidyModul |
@@ -3,7 +3,7 @@ set(LLVM_LINK_CO | ||
Support | ||
) | ||
-add_clang_libra | ||
+add_clang_libra | ||
AvoidCStyleCasts | ||
AvoidNSObjectNew | ||
AvoidThrowingObj |
@@ -3,7 +3,7 @@ set(LLVM_LINK_CO | ||
Support | ||
) | ||
-add_clang_libra | ||
+add_clang_libra | ||
ExceptionBasecla | ||
HICPPTidyModule. | ||
IgnoredRemoveRes |
@@ -3,7 +3,7 @@ set(LLVM_LINK_CO | ||
Support | ||
) | ||
-add_clang_libra | ||
+add_clang_libra | ||
LinuxKernelTidyM | ||
MustCheckErrsChe | ||
@@ -3,7 +3,7 @@ set(LLVM_LINK_CO | ||
Support | ||
) | ||
-add_clang_libra | ||
+add_clang_libra | ||
HeaderGuardCheck | ||
IncludeOrderChec | ||
LLVMTidyModule.c |
@@ -3,7 +3,7 @@ set(LLVM_LINK_CO | ||
Support | ||
) | ||
-add_clang_libra | ||
+add_clang_libra | ||
CalleeNamespaceC | ||
ImplementationIn | ||
InlineFunctionDe |
@@ -17,7 +17,7 @@ add_custom_comma | ||
add_custom_targe | ||
set_target_prope | ||
-add_clang_libra | ||
+add_clang_libra | ||
ConstCorrectness | ||
CoroutineHostile | ||
DefinitionsInHea |
@@ -137,11 +137,11 @@ static bool mayShadow(const NamedDecl *ND0, | ||
const ConfusableIdenti | ||
ConfusableIdenti | ||
const DeclContext *PrimaryContext = DC->getPrimaryCo | ||
- auto It = ContextInfos.fin | ||
- if (It != ContextInfos.end | ||
+ auto [It, Inserted] = ContextInfos.try | ||
+ if (!Inserted) | ||
return &It->second; | ||
- ContextInfo &Info = ContextInfos[Pri | ||
+ ContextInfo &Info = It->second; | ||
Info.PrimaryCont | ||
Info.NonTranspar | ||
@@ -172,8 +172,8 @@ void ConstCorrectness | ||
using namespace utils::fixit; | ||
if (VC == VariableCategory | ||
- Diag << addQualifierToVa | ||
- DeclSpec::TQ_con | ||
+ Diag << addQualifierToVa | ||
+ QualifierTarget: | ||
QualifierPolicy: | ||
// FIXME: Add '{}' for default initialization if no user-defined default | ||
// constructor exists and there is no initializer. | ||
@@ -181,8 +181,8 @@ void ConstCorrectness | ||
} | ||
if (VC == VariableCategory | ||
- Diag << addQualifierToVa | ||
- DeclSpec::TQ_con | ||
+ Diag << addQualifierToVa | ||
+ QualifierTarget: | ||
QualifierPolicy: | ||
return; | ||
} | ||
@@ -190,7 +190,7 @@ void ConstCorrectness | ||
if (VC == VariableCategory | ||
if (WarnPointersAsV | ||
Diag << addQualifierToVa | ||
- DeclSpec::TQ_con | ||
+ Qualifiers::Cons | ||
QualifierPolicy: | ||
} | ||
return; |
@@ -102,7 +102,7 @@ void DefinitionsInHea | ||
// inline is not allowed for main function. | ||
if (FD->isMain()) | ||
return; | ||
- diag(FD->getLoca | ||
+ diag(FD->getLoca | ||
DiagnosticIDs::N | ||
<< FixItHint::Creat | ||
} else if (const auto *VD = dyn_cast<VarDecl |
@@ -14,6 +14,16 @@ using namespace clang::ast_match | ||
namespace clang::tidy::mis | ||
+namespace { | ||
+ | ||
+AST_MATCHER_P(C | ||
+ ast_matchers::in | ||
+ unsigned N = Node.isExplicitO | ||
+ return (N < Node.parameters( | ||
+ InnerMatcher.mat | ||
+} | ||
+} // namespace | ||
+ | ||
void UnconventionalAs | ||
ast_matchers::Ma | ||
const auto HasGoodReturnTyp | ||
@@ -29,7 +39,7 @@ void UnconventionalAs | ||
hasName("operato | ||
.bind("method"); | ||
const auto IsSelfAssign = | ||
- cxxMethodDecl(Is | ||
+ cxxMethodDecl(Is | ||
.bind("method"); | ||
Finder->addMatch | ||
@@ -41,8 +51,7 @@ void UnconventionalAs | ||
rValueReferenceT | ||
Finder->addMatch | ||
- cxxMethodDecl(Is | ||
- hasParameter(0, parmVarDecl(hasT | ||
+ cxxMethodDecl(Is | ||
.bind("ArgumentT | ||
this); | ||
@@ -25,6 +25,13 @@ AST_MATCHER_P(De | ||
return false; | ||
} | ||
+AST_MATCHER_P(T | ||
+ DeclMatcher) { | ||
+ if (const TagDecl *ND = Node.getAsTagDec | ||
+ return DeclMatcher.matc | ||
+ return false; | ||
+} | ||
+ | ||
} // namespace | ||
// A function that helps to tell whether a TargetDecl in a UsingDecl will be | ||
@@ -61,7 +68,8 @@ void UnusedUsingDecls | ||
Finder->addMatch | ||
Finder->addMatch | ||
loc(elaboratedTy | ||
- hasUnqualifiedDe | ||
+ hasUnqualifiedDe | ||
+ type(asTagDecl(t | ||
this); | ||
// Cases where we can identify the UsingShadowDecl directly, rather than | ||
// just its target. | ||
@@ -139,12 +147,6 @@ void UnusedUsingDecls | ||
return; | ||
} | ||
- if (const auto *T = Result.Nodes.get | ||
- if (const auto *ND = T->getAsTagDecl( | ||
- RemoveNamedDecl( | ||
- return; | ||
- } | ||
- | ||
if (const auto *UsedShadow = | ||
Result.Nodes.get | ||
removeFromFoundD |
@@ -8,12 +8,15 @@ | ||
#include "UseInternalLink | ||
#include "../utils/FileEx | ||
+#include "../utils/LexerU | ||
#include "clang/AST/Decl. | ||
#include "clang/ASTMatche | ||
#include "clang/ASTMatche | ||
#include "clang/ASTMatche | ||
#include "clang/Basic/Sou | ||
#include "clang/Basic/Spe | ||
+#include "clang/Basic/Tok | ||
+#include "clang/Lex/Token | ||
#include "llvm/ADT/STLExt | ||
using namespace clang::ast_match | ||
@@ -113,7 +116,7 @@ static constexpr StringRef Message = | ||
void UseInternalLinka | ||
if (const auto *FD = Result.Nodes.get | ||
DiagnosticBuilde | ||
- SourceLocation FixLoc = FD->getTypeSpecS | ||
+ const SourceLocation FixLoc = FD->getInnerLocS | ||
if (FixLoc.isInvali | ||
return; | ||
if (FixMode == FixModeKind::Use | ||
@@ -128,7 +131,7 @@ void UseInternalLinka | ||
return; | ||
DiagnosticBuilde | ||
- SourceLocation FixLoc = VD->getTypeSpecS | ||
+ const SourceLocation FixLoc = VD->getInnerLocS | ||
if (FixLoc.isInvali | ||
return; | ||
if (FixMode == FixModeKind::Use |
@@ -9,6 +9,7 @@ | ||
#include "AvoidCArraysChe | ||
#include "clang/AST/ASTCo | ||
#include "clang/ASTMatche | ||
+#include "clang/ASTMatche | ||
using namespace clang::ast_match | ||
@@ -60,6 +61,7 @@ void AvoidCArraysChec | ||
Finder->addMatch | ||
typeLoc(hasValid | ||
+ optionally(hasPa | ||
unless(anyOf(has | ||
hasParent(varDec | ||
hasParent(fieldD | ||
@@ -72,11 +74,28 @@ void AvoidCArraysChec | ||
void AvoidCArraysChec | ||
const auto *ArrayType = Result.Nodes.get | ||
- | ||
+ const bool IsInParam = | ||
+ Result.Nodes.get | ||
+ const bool IsVLA = ArrayType->getTy | ||
+ enum class RecommendType { Array, Vector, Span }; | ||
+ llvm::SmallVecto | ||
+ if (IsVLA) { | ||
+ RecommendTypes.p | ||
+ } else if (ArrayType->getT | ||
+ // in function parameter, we also don't know the size of | ||
+ // IncompleteArrayT | ||
+ if (Result.Context- | ||
+ RecommendTypes.p | ||
+ else { | ||
+ RecommendTypes.p | ||
+ RecommendTypes.p | ||
+ } | ||
+ } else { | ||
+ RecommendTypes.p | ||
+ } | ||
diag(ArrayType-> | ||
- "do not declare %select{C-style| | ||
- "%select{std::ar | ||
- << ArrayType->getTy | ||
+ "do not declare %select{C-style| | ||
+ << IsVLA << llvm::join(Recom | ||
} | ||
} // namespace clang::tidy::mod |
@@ -3,7 +3,7 @@ set(LLVM_LINK_CO | ||
Support | ||
) | ||
-add_clang_libra | ||
+add_clang_libra | ||
AvoidBindCheck.c | ||
AvoidCArraysChec | ||
ConcatNestedName |
@@ -262,7 +262,7 @@ StatementMatcher | ||
/// EndVarName: 'j' (as a VarDecl) | ||
/// In the second example only: | ||
/// EndCallName: 'container.size( | ||
-/// 'size(contaner)' | ||
+/// 'size(container) | ||
/// | ||
/// Client code will need to make sure that: | ||
/// - The containers on which 'size()' is called is the container indexed. | ||
@@ -491,7 +491,7 @@ static bool isDirectMemberEx | ||
} | ||
/// Given an expression that represents an usage of an element from the | ||
-/// containter that we are iterating over, returns false when it can be | ||
+/// container that we are iterating over, returns false when it can be | ||
/// guaranteed this element cannot be modified as a result of this usage. | ||
static bool canBeModified(AS | ||
if (E->getType().is | ||
@@ -922,7 +922,7 @@ bool LoopConvertCheck | ||
const ast_matchers::Bo | ||
const ForStmt *Loop, | ||
LoopFixerKind FixerKind) { | ||
- // In self contained diagnosics mode we don't want dependancies on other | ||
+ // In self contained diagnostic mode we don't want dependencies on other | ||
// loops, otherwise, If we already modified the range of this for loop, don't | ||
// do any further updates on this iteration. | ||
if (areDiagsSelfCon |
@@ -777,7 +777,7 @@ bool ForLoopIndexUseV | ||
const LambdaCapture *C, | ||
Expr *Init) { | ||
if (C->capturesVari | ||
- const ValueDecl *VDecl = C->getCapturedVa | ||
+ ValueDecl *VDecl = C->getCapturedVa | ||
if (areSameVariable | ||
// FIXME: if the index is captured, it will count as an usage and the | ||
// alias (if any) won't work, because it is only used in case of having | ||
@@ -787,6 +787,8 @@ bool ForLoopIndexUseV | ||
: Usage::UK_Captur | ||
C->getLocation() | ||
} | ||
+ if (VDecl->isInitCa | ||
+ TraverseStmtImpl | ||
} | ||
return VisitorBase::Tra | ||
} | ||
@@ -816,6 +818,17 @@ bool ForLoopIndexUseV | ||
return true; | ||
} | ||
+bool ForLoopIndexUseV | ||
+ // All this pointer swapping is a mechanism for tracking immediate parentage | ||
+ // of Stmts. | ||
+ const Stmt *OldNextParent = NextStmtParent; | ||
+ CurrStmtParent = NextStmtParent; | ||
+ NextStmtParent = S; | ||
+ bool Result = VisitorBase::Tra | ||
+ NextStmtParent = OldNextParent; | ||
+ return Result; | ||
+} | ||
+ | ||
bool ForLoopIndexUseV | ||
// If this is an initialization expression for a lambda capture, prune the | ||
// traversal so that we don't end up diagnosing the contained DeclRefExpr as | ||
@@ -828,15 +841,7 @@ bool ForLoopIndexUseV | ||
return true; | ||
} | ||
} | ||
- | ||
- // All this pointer swapping is a mechanism for tracking immediate parentage | ||
- // of Stmts. | ||
- const Stmt *OldNextParent = NextStmtParent; | ||
- CurrStmtParent = NextStmtParent; | ||
- NextStmtParent = S; | ||
- bool Result = VisitorBase::Tra | ||
- NextStmtParent = OldNextParent; | ||
- return Result; | ||
+ return TraverseStmtImpl | ||
} | ||
std::string VariableNamer::c |
@@ -354,6 +354,8 @@ private: | ||
bool VisitDeclStmt(De | ||
bool TraverseStmt(Stm | ||
+ bool TraverseStmtImpl | ||
+ | ||
/// Add an expression to the list of expressions on which the container | ||
/// expression depends. | ||
void addComponent(con |
@@ -72,7 +72,11 @@ static FindArgsResult findArgs(const CallExpr *Call) { | ||
return Result; | ||
} | ||
-static SmallVector<FixI | ||
+// Returns `true` as `first` only if a nested call to `std::min` or | ||
+// `std::max` was found. Checking if `FixItHint`s were generated is not enough, | ||
+// as the explicit casts that the check introduces may be generated without a | ||
+// nested `std::min` or `std::max` call. | ||
+static std::pair<bool, SmallVector<FixI | ||
generateReplacem | ||
const CallExpr *TopCall, const FindArgsResult &Result, | ||
const bool IgnoreNonTrivial | ||
@@ -91,13 +95,15 @@ generateReplacem | ||
const bool IsResultTypeTriv | ||
if ((!IsResultTypeT | ||
- return FixItHints; | ||
+ return {false, FixItHints}; | ||
if (IsResultTypeTri | ||
static_cast<std: | ||
Match.Context->g | ||
IgnoreTrivialTyp | ||
- return FixItHints; | ||
+ return {false, FixItHints}; | ||
+ | ||
+ bool FoundNestedCall = false; | ||
for (const Expr *Arg : Result.Args) { | ||
const auto *InnerCall = dyn_cast<CallExp | ||
@@ -146,6 +152,9 @@ generateReplacem | ||
*Match.Context)) | ||
continue; | ||
+ // We have found a nested call | ||
+ FoundNestedCall = true; | ||
+ | ||
// remove the function call | ||
FixItHints.push_ | ||
FixItHint::Creat | ||
@@ -168,7 +177,7 @@ generateReplacem | ||
CharSourceRange: | ||
} | ||
- const SmallVector<FixI | ||
+ const auto [_, InnerReplacement | ||
Match, InnerCall, InnerResult, IgnoreNonTrivial | ||
IgnoreTrivialTyp | ||
@@ -189,7 +198,7 @@ generateReplacem | ||
} | ||
} | ||
- return FixItHints; | ||
+ return {FoundNestedCall | ||
} | ||
MinMaxUseInitial | ||
@@ -238,11 +247,11 @@ void MinMaxUseInitial | ||
const auto *TopCall = Match.Nodes.getN | ||
const FindArgsResult Result = findArgs(TopCall | ||
- const SmallVector<FixI | ||
+ const auto [FoundNestedCall | ||
generateReplacem | ||
IgnoreTrivialTyp | ||
- if (Replacements.em | ||
+ if (!FoundNestedCal | ||
return; | ||
const DiagnosticBuilde |
@@ -80,9 +80,13 @@ unsigned getNumberOfDesig | ||
}); | ||
} | ||
-AST_MATCHER(CXX | ||
+AST_MATCHER(CXX | ||
+ return Node.hasDefiniti | ||
+} | ||
-AST_MATCHER(CXX | ||
+AST_MATCHER(CXX | ||
+ return Node.hasDefiniti | ||
+} | ||
AST_MATCHER(Init | ||
if (const InitListExpr *SyntacticForm = |
@@ -35,10 +35,20 @@ AST_MATCHER(Type | ||
/// to null within. | ||
/// Finding sequences of explicit casts is necessary so that an entire sequence | ||
/// can be replaced instead of just the inner-most implicit cast. | ||
+/// | ||
+/// TODO/NOTE: The second "anyOf" below discards matches on a substituted type, | ||
+/// since we don't know if that would _always_ be a pointer type for all other | ||
+/// specializations, | ||
+/// that all specializations are expected to be for pointer types. Ideally this | ||
+/// would check for the "NULL" macro instead, but that'd be harder to express. | ||
+/// In practice, "NULL" is often defined as "__null", and this is a useful | ||
+/// condition. | ||
StatementMatcher | ||
auto ImplicitCastToNu | ||
anyOf(hasCastKin | ||
- unless(hasImplic | ||
+ anyOf(hasSourceE | ||
+ unless(hasImplic | ||
+ qualType(substTe | ||
unless(hasSource | ||
unless(hasImplic | ||
qualType(matcher |
@@ -8,7 +8,9 @@ | ||
#include "UseStartsEndsWi | ||
-#include "../utils/Option | ||
+#include "../utils/ASTUti | ||
+#include "../utils/Matche | ||
+#include "clang/ASTMatche | ||
#include "clang/Lex/Lexer | ||
#include <string> | ||
@@ -16,6 +18,63 @@ | ||
using namespace clang::ast_match | ||
namespace clang::tidy::mod | ||
+struct NotLengthExprFor | ||
+ NotLengthExprFor | ||
+ ASTContext *Context) | ||
+ : ID(std::move(ID) | ||
+ bool operator()(const | ||
+ // Match a string literal and an integer size or strlen() call. | ||
+ if (const auto *StringLiteralNo | ||
+ if (const auto *IntegerLiteralS | ||
+ return StringLiteralNod | ||
+ IntegerLiteralSi | ||
+ } | ||
+ | ||
+ if (const auto *StrlenNode = Node.get<CallExp | ||
+ if (StrlenNode->get | ||
+ StrlenNode->getN | ||
+ return true; | ||
+ } | ||
+ | ||
+ if (const auto *StrlenArgNode = dyn_cast<StringL | ||
+ StrlenNode->getA | ||
+ return StrlenArgNode->g | ||
+ } | ||
+ } | ||
+ } | ||
+ | ||
+ // Match a string variable and a call to length() or size(). | ||
+ if (const auto *ExprNode = Nodes.getNodeAs< | ||
+ if (const auto *MemberCallNode = Node.get<CXXMemb | ||
+ const CXXMethodDecl *MethodDeclNode = MemberCallNode-> | ||
+ const StringRef Name = MethodDeclNode-> | ||
+ if (!MethodDeclNode | ||
+ (Name != "size" && Name != "length")) { | ||
+ return true; | ||
+ } | ||
+ | ||
+ if (const auto *OnNode = | ||
+ dyn_cast<Expr>(M | ||
+ return !utils::areState | ||
+ ExprNode->Ignore | ||
+ *Context); | ||
+ } | ||
+ } | ||
+ } | ||
+ | ||
+ return true; | ||
+ } | ||
+ | ||
+private: | ||
+ std::string ID; | ||
+ DynTypedNode Node; | ||
+ ASTContext *Context; | ||
+}; | ||
+ | ||
+AST_MATCHER_P(E | ||
+ return Builder->removeB | ||
+ ID, DynTypedNode::cr | ||
+} | ||
UseStartsEndsWit | ||
ClangTidyContext | ||
@@ -23,140 +82,120 @@ UseStartsEndsWit | ||
void UseStartsEndsWit | ||
const auto ZeroLiteral = integerLiteral(e | ||
- const auto HasStartsWithMet | ||
- return hasMethod( | ||
- cxxMethodDecl(ha | ||
- .bind("starts_wi | ||
+ | ||
+ const auto ClassTypeWithMet | ||
+ const auto... Methods) { | ||
+ return cxxRecordDecl(an | ||
+ hasMethod(cxxMet | ||
+ returns(booleanT | ||
+ .bind(MethodBoun | ||
}; | ||
- const auto HasStartsWithMet | ||
- anyOf(HasStartsW | ||
- HasStartsWithMet | ||
- HasStartsWithMet | ||
- const auto ClassWithStartsW | ||
- HasStartsWithMet | ||
- cxxRecordDecl(Ha | ||
+ const auto OnClassWithStart | ||
+ ClassTypeWithMet | ||
+ "startswith", "StartsWith"); | ||
+ | ||
+ const auto OnClassWithEndsW | ||
+ "ends_with_fun", | ||
+ | ||
+ // Case 1: X.find(Y) [!=]= 0 -> starts_with. | ||
const auto FindExpr = cxxMemberCallExp | ||
- // A method call with no second argument or the second argument is zero... | ||
anyOf(argumentCo | ||
- // ... named find... | ||
- callee(cxxMethod | ||
- // ... on a class with a starts_with function. | ||
- on(hasType( | ||
- hasCanonicalType | ||
- // Bind search expression. | ||
- hasArgument(0, expr().bind("sea | ||
+ callee( | ||
+ cxxMethodDecl(ha | ||
+ .bind("find_fun" | ||
+ hasArgument(0, expr().bind("nee | ||
+ // Case 2: X.rfind(Y, 0) [!=]= 0 -> starts_with. | ||
const auto RFindExpr = cxxMemberCallExp | ||
- // A method call with a second argument of zero... | ||
hasArgument(1, ZeroLiteral), | ||
- // ... named rfind... | ||
- callee(cxxMethod | ||
- // ... on a class with a starts_with function. | ||
- on(hasType( | ||
- hasCanonicalType | ||
- // Bind search expression. | ||
- hasArgument(0, expr().bind("sea | ||
- | ||
- // Match a string literal and an integer or strlen() call matching the length. | ||
- const auto HasStringLiteral | ||
- const auto LengthArgIndex) { | ||
- return allOf( | ||
- hasArgument(Stri | ||
- hasArgument(Leng | ||
- anyOf(integerLit | ||
- callExpr(callee( | ||
- hasName("strlen" | ||
- hasArgument(0, stringLiteral(). | ||
- "strlen_arg")))) | ||
- }; | ||
- | ||
- // Match a string variable and a call to length() or size(). | ||
- const auto HasStringVariabl | ||
- const auto LengthArgIndex) { | ||
- return allOf( | ||
- hasArgument(Stri | ||
- decl().bind("str | ||
- hasArgument(Leng | ||
- cxxMemberCallExp | ||
- callee(cxxMethod | ||
- hasAnyName("size | ||
- on(declRefExpr( | ||
- to(decl(equalsBo | ||
- }; | ||
- | ||
- // Match either one of the two cases above. | ||
- const auto HasStringAndLeng | ||
- [HasStringLitera | ||
- const auto StringArgIndex, const auto LengthArgIndex) { | ||
- return anyOf( | ||
- HasStringLiteral | ||
- HasStringVariabl | ||
- }; | ||
+ callee(cxxMethod | ||
+ ofClass(OnClassW | ||
+ .bind("find_fun" | ||
+ hasArgument(0, expr().bind("nee | ||
+ // Case 3: X.compare(0, LEN(Y), Y) [!=]= 0 -> starts_with. | ||
const auto CompareExpr = cxxMemberCallExp | ||
- // A method call with three arguments... | ||
+ argumentCountIs( | ||
+ callee(cxxMethod | ||
+ ofClass(OnClassW | ||
+ .bind("find_fun" | ||
+ hasArgument(2, expr().bind("nee | ||
+ hasArgument(1, lengthExprForStr | ||
+ | ||
+ // Case 4: X.compare(LEN(X) | ||
+ const auto CompareEndsWithE | ||
argumentCountIs( | ||
- // ... where the first argument is zero... | ||
- hasArgument(0, ZeroLiteral), | ||
- // ... named compare... | ||
- callee(cxxMethod | ||
- // ... on a class with a starts_with function... | ||
- on(hasType( | ||
- hasCanonicalType | ||
- // ... where the third argument is some string and the second a length. | ||
- HasStringAndLeng | ||
- // Bind search expression. | ||
- hasArgument(2, expr().bind("sea | ||
- | ||
+ callee(cxxMethod | ||
+ ofClass(OnClassW | ||
+ .bind("find_fun" | ||
+ on(expr().bind(" | ||
+ hasArgument(1, lengthExprForStr | ||
+ hasArgument(0, | ||
+ binaryOperator(h | ||
+ hasLHS(lengthExp | ||
+ hasRHS(lengthExp | ||
+ | ||
+ // All cases comparing to 0. | ||
Finder->addMatch | ||
- // Match [=!]= with a zero on one side and (r?)find|compare | ||
binaryOperator( | ||
- hasAnyOperatorNa | ||
- hasOperands(cxxM | ||
+ matchers::isEqua | ||
+ hasOperands(cxxM | ||
+ CompareEndsWithE | ||
.bind("find_expr | ||
ZeroLiteral)) | ||
.bind("expr"), | ||
this); | ||
+ | ||
+ // Case 5: X.rfind(Y) [!=]= LEN(X) - LEN(Y) -> ends_with. | ||
+ Finder->addMatch | ||
+ binaryOperator( | ||
+ matchers::isEqua | ||
+ hasOperands( | ||
+ cxxMemberCallExp | ||
+ anyOf( | ||
+ argumentCountIs( | ||
+ allOf(argumentCo | ||
+ hasArgument( | ||
+ 1, | ||
+ anyOf(declRefExp | ||
+ memberExpr(membe | ||
+ callee(cxxMethod | ||
+ ofClass(OnClassW | ||
+ .bind("find_fun" | ||
+ on(expr().bind(" | ||
+ hasArgument(0, expr().bind("nee | ||
+ .bind("find_expr | ||
+ binaryOperator(h | ||
+ hasLHS(lengthExp | ||
+ hasRHS(lengthExp | ||
+ .bind("expr"), | ||
+ this); | ||
} | ||
void UseStartsEndsWit | ||
const auto *ComparisonExpr = Result.Nodes.get | ||
const auto *FindExpr = Result.Nodes.get | ||
const auto *FindFun = Result.Nodes.get | ||
- const auto *SearchExpr = Result.Nodes.get | ||
+ const auto *SearchExpr = Result.Nodes.get | ||
const auto *StartsWithFunct | ||
Result.Nodes.get | ||
+ const auto *EndsWithFunctio | ||
+ Result.Nodes.get | ||
+ assert(bool(Star | ||
+ const CXXMethodDecl *ReplacementFunc | ||
+ StartsWithFuncti | ||
- const auto *StringLiteralAr | ||
- Result.Nodes.get | ||
- const auto *IntegerLiteralS | ||
- Result.Nodes.get | ||
- const auto *StrlenArg = Result.Nodes.get | ||
- | ||
- // Filter out compare cases where the length does not match string literal. | ||
- if (StringLiteralAr | ||
- StringLiteralArg | ||
- IntegerLiteralSi | ||
+ if (ComparisonExpr- | ||
return; | ||
- } | ||
- | ||
- if (StringLiteralAr | ||
- StringLiteralArg | ||
- return; | ||
- } | ||
- | ||
- if (ComparisonExpr- | ||
- return; | ||
- } | ||
const bool Neg = ComparisonExpr-> | ||
auto Diagnostic = | ||
diag(FindExpr->g | ||
- << StartsWithFuncti | ||
+ << ReplacementFunct | ||
- // Remove possible arguments after search expression and ' [!=]= 0' suffix. | ||
+ // Remove possible arguments after search expression and ' [!=]= .+' suffix. | ||
Diagnostic << FixItHint::Creat | ||
CharSourceRange: | ||
Lexer::getLocFor | ||
@@ -164,21 +203,20 @@ void UseStartsEndsWit | ||
ComparisonExpr-> | ||
")"); | ||
- // Remove possible '0 [!=]= ' prefix. | ||
+ // Remove possible '.+ [!=]= ' prefix. | ||
Diagnostic << FixItHint::Creat | ||
ComparisonExpr-> | ||
- // Replace method name by 'starts_with'. | ||
+ // Replace method name by '(starts|ends)_w | ||
// Remove possible arguments before search expression. | ||
Diagnostic << FixItHint::Creat | ||
CharSourceRange: | ||
SearchExpr->getB | ||
- (StartsWithFunct | ||
+ (ReplacementFunc | ||
// Add possible negation '!'. | ||
- if (Neg) { | ||
+ if (Neg) | ||
Diagnostic << FixItHint::Creat | ||
- } | ||
} | ||
} // namespace clang::tidy::mod |
@@ -14,7 +14,7 @@ | ||
namespace clang::tidy::mod | ||
/// Checks for common roundabout ways to express ``starts_with`` and | ||
-/// ``ends_with`` and suggests replacing with ``starts_with`` when the method is | ||
+/// ``ends_with`` and suggests replacing with the simpler method when it is | ||
/// available. Notably, this will work with ``std::string`` and | ||
/// ``std::string_vi | ||
/// |
@@ -44,6 +44,7 @@ void UseStdFormatChec | ||
Preprocessor *PP, | ||
Preprocessor *ModuleExpanderP | ||
IncludeInserter. | ||
+ this->PP = PP; | ||
} | ||
void UseStdFormatChec | ||
@@ -75,9 +76,9 @@ void UseStdFormatChec | ||
utils::FormatStr | ||
ConverterConfig. | ||
- utils::FormatStr | ||
- FormatArgOffset, | ||
- getLangOpts()); | ||
+ utils::FormatStr | ||
+ Result.Context, StrFormat, FormatArgOffset, | ||
+ getLangOpts(), *Result.SourceMa | ||
const Expr *StrFormatCall = StrFormat->getCa | ||
if (!Converter.canA | ||
diag(StrFormat-> |
@@ -44,6 +44,7 @@ private: | ||
StringRef ReplacementForma | ||
utils::IncludeIn | ||
std::optional<St | ||
+ Preprocessor *PP = nullptr; | ||
}; | ||
} // namespace clang::tidy::mod |
@@ -68,6 +68,7 @@ void UseStdPrintCheck | ||
Preprocessor *PP, | ||
Preprocessor *ModuleExpanderP | ||
IncludeInserter. | ||
+ this->PP = PP; | ||
} | ||
static clang::ast_match | ||
@@ -131,7 +132,8 @@ void UseStdPrintCheck | ||
ConverterConfig. | ||
ConverterConfig. | ||
utils::FormatStr | ||
- Result.Context, Printf, FormatArgOffset, | ||
+ Result.Context, Printf, FormatArgOffset, | ||
+ *Result.SourceMa | ||
const Expr *PrintfCall = Printf->getCalle | ||
const StringRef ReplacementFunct | ||
? ReplacementPrint |
@@ -36,6 +36,7 @@ public: | ||
} | ||
private: | ||
+ Preprocessor *PP; | ||
bool StrictMode; | ||
std::vector<Stri | ||
std::vector<Stri |
@@ -3,7 +3,7 @@ set(LLVM_LINK_CO | ||
Support | ||
) | ||
-add_clang_libra | ||
+add_clang_libra | ||
BufferDerefCheck | ||
MPITidyModule.cp | ||
TypeMismatchChec |
@@ -3,7 +3,7 @@ set(LLVM_LINK_CO | ||
Support | ||
) | ||
-add_clang_libra | ||
+add_clang_libra | ||
AssertEquals.cpp | ||
AvoidNSErrorInit | ||
DeallocInCategor |
@@ -3,7 +3,7 @@ set(LLVM_LINK_CO | ||
Support | ||
) | ||
-add_clang_libra | ||
+add_clang_libra | ||
ExceptionEscapeC | ||
OpenMPTidyModule | ||
UseDefaultNoneCh |
@@ -46,38 +46,41 @@ void AvoidEndlCheck:: | ||
// Handle the more common streaming '... << std::endl' case | ||
const CharSourceRange TokenRange = | ||
CharSourceRange: | ||
- const StringRef SourceText = Lexer::getSource | ||
+ StringRef SourceText = Lexer::getSource | ||
TokenRange, *Result.SourceMa | ||
- | ||
+ if (SourceText.empt | ||
+ SourceText = "std::endl"; | ||
auto Diag = diag(Expression- | ||
"do not use '%0' with streams; use '\\n' instead") | ||
<< SourceText; | ||
- | ||
- Diag << FixItHint::Creat | ||
+ if (TokenRange.isVa | ||
+ Diag << FixItHint::Creat | ||
} else { | ||
// Handle the less common function call 'std::endl(...)' | ||
const auto *CallExpression = llvm::cast<CallE | ||
assert(CallExpre | ||
- const StringRef SourceText = Lexer::getSource | ||
+ StringRef SourceText = Lexer::getSource | ||
CharSourceRange: | ||
CallExpression-> | ||
*Result.SourceMa | ||
+ if (SourceText.empt | ||
+ SourceText = "std::endl"; | ||
+ auto Diag = diag(CallExpress | ||
+ "do not use '%0' with streams; use '\\n' instead") | ||
+ << SourceText; | ||
const CharSourceRange ArgTokenRange = CharSourceRange: | ||
CallExpression-> | ||
const StringRef ArgSourceText = Lexer::getSource | ||
ArgTokenRange, *Result.SourceMa | ||
- | ||
- const std::string ReplacementStrin | ||
- std::string(ArgS | ||
- | ||
- diag(CallExpress | ||
- "do not use '%0' with streams; use '\\n' instead") | ||
- << SourceText | ||
- << FixItHint::Creat | ||
- CharSourceRange: | ||
- ReplacementStrin | ||
+ const CharSourceRange ReplacementRange | ||
+ CharSourceRange: | ||
+ if (!ArgSourceText. | ||
+ const std::string ReplacementStrin | ||
+ std::string(ArgS | ||
+ Diag << FixItHint::Creat | ||
+ } | ||
} | ||
} | ||
@@ -3,7 +3,7 @@ set(LLVM_LINK_CO | ||
Support | ||
) | ||
-add_clang_libra | ||
+add_clang_libra | ||
AvoidEndlCheck.c | ||
EnumSizeCheck.cp | ||
FasterStringFind |
@@ -91,7 +91,7 @@ bool ForRangeCopyChec | ||
<< utils::fixit::ch | ||
if (!LoopVar.getTyp | ||
if (std::optional<F | ||
- LoopVar, Context, DeclSpec::TQ::TQ | ||
+ LoopVar, Context, Qualifiers::Cons | ||
Diagnostic << *Fix; | ||
} | ||
return true; | ||
@@ -129,7 +129,7 @@ bool ForRangeCopyChec | ||
"making it a const reference"); | ||
if (std::optional<F | ||
- LoopVar, Context, DeclSpec::TQ::TQ | ||
+ LoopVar, Context, Qualifiers::Cons | ||
Diag << *Fix << utils::fixit::ch | ||
return true; |
@@ -209,8 +209,9 @@ void MoveConstArgChec | ||
} | ||
if (const CXXRecordDecl *RecordDecl = ArgType->getAsCX | ||
- RecordDecl && !(RecordDecl->ha | ||
- RecordDecl->hasM | ||
+ RecordDecl && RecordDecl->hasD | ||
+ !(RecordDecl->ha | ||
+ RecordDecl->hasM | ||
const bool MissingMoveAssig | ||
const bool MissingMoveConst | ||
const bool MissingBoth = MissingMoveAssig |
@@ -36,7 +36,7 @@ void recordFixes(cons | ||
Diagnostic << utils::fixit::ch | ||
if (!Var.getType(). | ||
if (std::optional<F | ||
- Var, Context, DeclSpec::TQ::TQ | ||
+ Var, Context, Qualifiers::Cons | ||
Diagnostic << *Fix; | ||
} | ||
} |
@@ -172,7 +172,7 @@ void UnnecessaryValue | ||
// declaration. | ||
if (!CurrentParam.g | ||
if (std::optional<F | ||
- CurrentParam, Context, DeclSpec::TQ::TQ | ||
+ CurrentParam, Context, Qualifiers::Cons | ||
Diag << *Fix; | ||
} | ||
} |
@@ -1,4 +1,4 @@ | ||
-add_clang_libra | ||
+add_clang_libra | ||
ClangTidyPlugin. | ||
LINK_LIBS |
@@ -4,11 +4,12 @@ set(LLVM_LINK_CO | ||
TargetParser | ||
) | ||
-add_clang_libra | ||
+add_clang_libra | ||
PortabilityTidyM | ||
RestrictSystemIn | ||
SIMDIntrinsicsCh | ||
StdAllocatorCons | ||
+ TemplateVirtualM | ||
LINK_LIBS | ||
clangTidy |
@@ -12,6 +12,7 @@ | ||
#include "RestrictSystemI | ||
#include "SIMDIntrinsicsC | ||
#include "StdAllocatorCon | ||
+#include "TemplateVirtual | ||
namespace clang::tidy { | ||
namespace portability { | ||
@@ -25,6 +26,8 @@ public: | ||
"portability-sim | ||
CheckFactories.r | ||
"portability-std | ||
+ CheckFactories.r | ||
+ "portability-tem | ||
} | ||
}; | ||
@@ -0,0 +1,44 @@ | ||
+//===--- TemplateVirtualM | ||
+// | ||
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | ||
+// See https://llvm.org | ||
+// SPDX-License-Ide | ||
+// | ||
+//===---------- | ||
+ | ||
+#include "TemplateVirtual | ||
+#include "clang/ASTMatche | ||
+ | ||
+using namespace clang::ast_match | ||
+ | ||
+namespace clang::tidy::por | ||
+namespace { | ||
+AST_MATCHER(CXX | ||
+} // namespace | ||
+ | ||
+void TemplateVirtualM | ||
+ Finder->addMatch | ||
+ cxxMethodDecl(of | ||
+ unless(isExplici | ||
+ .bind("specializ | ||
+ isVirtual(), unless(isUsed()) | ||
+ unless(cxxDestru | ||
+ .bind("method"), | ||
+ this); | ||
+} | ||
+ | ||
+void TemplateVirtualM | ||
+ const MatchFinder::Mat | ||
+ const auto *ImplicitSpecial | ||
+ Result.Nodes.get | ||
+ const auto *MethodDecl = Result.Nodes.get | ||
+ | ||
+ diag(MethodDecl- | ||
+ "unspecified virtual member function instantiation; the virtual " | ||
+ "member function is not instantiated but it might be with a " | ||
+ "different compiler"); | ||
+ diag(ImplicitSpe | ||
+ "template instantiated here", DiagnosticIDs::N | ||
+} | ||
+ | ||
+} // namespace clang::tidy::por |
@@ -0,0 +1,38 @@ | ||
+//===--- TemplateVirtualM | ||
+// | ||
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | ||
+// See https://llvm.org | ||
+// SPDX-License-Ide | ||
+// | ||
+//===---------- | ||
+ | ||
+#ifndef LLVM_CLANG_TOOLS | ||
+#define LLVM_CLANG_TOOLS | ||
+ | ||
+#include "../ClangTidyChe | ||
+ | ||
+namespace clang::tidy::por | ||
+ | ||
+/// Upon instantiating a template class, non-virtual member functions don't have | ||
+/// to be instantiated unless they are used. Virtual member function | ||
+/// instantiation on the other hand is unspecified and depends on the | ||
+/// implementation of the compiler. This check intends to find cases when a | ||
+/// virtual member function is not instantiated but it might be with a different | ||
+/// compiler. | ||
+/// | ||
+/// For the user-facing documentation see: | ||
+/// http://clang.llv | ||
+class TemplateVirtualM | ||
+public: | ||
+ TemplateVirtualM | ||
+ : ClangTidyCheck(N | ||
+ void registerMatchers | ||
+ void check(const ast_matchers::Ma | ||
+ bool isLanguageVersio | ||
+ return LangOpts.CPlusPl | ||
+ } | ||
+}; | ||
+ | ||
+} // namespace clang::tidy::por | ||
+ | ||
+#endif // LLVM_CLANG_TOOLS |
@@ -84,7 +84,8 @@ struct AvoidUncondition | ||
return (Tok.getRawIdent | ||
Tok.getRawIdenti | ||
default: | ||
- return Tok.getKind() >= tok::l_square && Tok.getKind() <= tok::caretcaret; | ||
+ return Tok.getKind() >= tok::l_square && | ||
+ Tok.getKind() <= tok::greatergrea | ||
} | ||
} | ||
@@ -3,7 +3,7 @@ set(LLVM_LINK_CO | ||
Support | ||
) | ||
-add_clang_libra | ||
+add_clang_libra | ||
AvoidConstParams | ||
AvoidNestedCondi | ||
AvoidReturnWithV |
@@ -13,30 +13,40 @@ | ||
using namespace clang::ast_match | ||
namespace clang::tidy::rea | ||
- | ||
void ContainerContain | ||
- const auto SupportedContain | ||
- hasUnqualifiedDe | ||
- hasAnyName("::st | ||
- "::std::unordere | ||
- "::std::unordere | ||
- "::std::unordere | ||
+ const auto HasContainsMatch | ||
+ cxxMethodDecl(is | ||
+ hasName("contain | ||
+ hasParameter(0, hasType(hasUnqua | ||
+ equalsBoundNode( | ||
const auto CountCall = | ||
- cxxMemberCallExp | ||
- callee(cxxMethod | ||
- argumentCountIs( | ||
+ cxxMemberCallExp | ||
+ argumentCountIs( | ||
+ callee(cxxMethod | ||
+ hasName("count") | ||
+ hasParameter(0, hasType(hasUnqua | ||
+ type().bind("par | ||
+ ofClass(cxxRecor | ||
.bind("call"); | ||
const auto FindCall = | ||
- cxxMemberCallExp | ||
- callee(cxxMethod | ||
- argumentCountIs( | ||
+ cxxMemberCallExp | ||
+ argumentCountIs( | ||
+ callee(cxxMethod | ||
+ hasName("find"), | ||
+ hasParameter(0, hasType(hasUnqua | ||
+ type().bind("par | ||
+ ofClass(cxxRecor | ||
.bind("call"); | ||
- const auto EndCall = cxxMemberCallExp | ||
- callee(cxxMethod | ||
- argumentCountIs( | ||
+ const auto EndCall = cxxMemberCallExp | ||
+ argumentCountIs( | ||
+ callee( | ||
+ cxxMethodDecl(ha | ||
+ // In the matchers below, FindCall should always appear | ||
+ // before EndCall so 'parameterType' is properly bound. | ||
+ ofClass(cxxRecor | ||
const auto Literal0 = integerLiteral(e | ||
const auto Literal1 = integerLiteral(e | ||
@@ -52,50 +62,44 @@ void ContainerContain | ||
.bind("positiveC | ||
this); | ||
AddSimpleMatcher | ||
- binaryOperator(h | ||
- .bind("positiveC | ||
- AddSimpleMatcher | ||
- binaryOperator(h | ||
- .bind("positiveC | ||
- AddSimpleMatcher | ||
- binaryOperator(h | ||
- .bind("positiveC | ||
- AddSimpleMatcher | ||
- binaryOperator(h | ||
+ binaryOperation( | ||
.bind("positiveC | ||
AddSimpleMatcher | ||
- binaryOperator(h | ||
+ binaryOperation( | ||
.bind("positiveC | ||
AddSimpleMatcher | ||
- binaryOperator(h | ||
+ binaryOperation( | ||
.bind("positiveC | ||
+ AddSimpleMatcher | ||
+ hasRHS(Literal1) | ||
+ .bind("positiveC | ||
+ AddSimpleMatcher | ||
+ hasRHS(CountCall | ||
+ .bind("positiveC | ||
// Find inverted membership tests which use `count()`. | ||
AddSimpleMatcher | ||
- binaryOperator(h | ||
- .bind("negativeC | ||
- AddSimpleMatcher | ||
- binaryOperator(h | ||
- .bind("negativeC | ||
- AddSimpleMatcher | ||
- binaryOperator(h | ||
- .bind("negativeC | ||
- AddSimpleMatcher | ||
- binaryOperator(h | ||
+ binaryOperation( | ||
.bind("negativeC | ||
+ AddSimpleMatcher | ||
+ hasRHS(Literal0) | ||
+ .bind("negativeC | ||
+ AddSimpleMatcher | ||
+ hasRHS(CountCall | ||
+ .bind("negativeC | ||
AddSimpleMatcher | ||
- binaryOperator(h | ||
+ binaryOperation( | ||
.bind("negativeC | ||
AddSimpleMatcher | ||
- binaryOperator(h | ||
+ binaryOperation( | ||
.bind("negativeC | ||
// Find membership tests based on `find() == end()`. | ||
AddSimpleMatcher | ||
- binaryOperator(h | ||
+ binaryOperation( | ||
.bind("positiveC | ||
AddSimpleMatcher | ||
- binaryOperator(h | ||
+ binaryOperation( | ||
.bind("negativeC | ||
} | ||
@@ -13,8 +13,9 @@ | ||
namespace clang::tidy::rea | ||
-/// Finds usages of `container.count | ||
-/// replaced by a call to the `container.conta | ||
+/// Finds usages of `container.count | ||
+/// `container.find( | ||
+/// to the `container.conta | ||
/// | ||
/// For the user-facing documentation see: | ||
/// http://clang.llv | ||
@@ -24,10 +25,11 @@ public: | ||
: ClangTidyCheck(N | ||
void registerMatchers | ||
void check(const ast_matchers::Ma | ||
- | ||
-protected: | ||
bool isLanguageVersio | ||
- return LO.CPlusPlus20; | ||
+ return LO.CPlusPlus; | ||
+ } | ||
+ std::optional<Tr | ||
+ return TK_AsIs; | ||
} | ||
}; | ||
@@ -123,6 +123,13 @@ AST_MATCHER(Enum | ||
return !AllEnumeratorsA | ||
} | ||
+std::string getName(const EnumDecl *Decl) { | ||
+ if (!Decl->getDeclN | ||
+ return "<unnamed>"; | ||
+ | ||
+ return Decl->getQualifi | ||
+} | ||
+ | ||
} // namespace | ||
EnumInitialValue | ||
@@ -141,16 +148,18 @@ void EnumInitialValue | ||
} | ||
void EnumInitialValue | ||
- Finder->addMatch | ||
- enumDecl(unless( | ||
- .bind("inconsist | ||
- this); | ||
+ Finder->addMatch | ||
+ unless(hasConsis | ||
+ .bind("inconsist | ||
+ this); | ||
if (!AllowExplicitZ | ||
Finder->addMatch | ||
- enumDecl(hasZero | ||
+ enumDecl(isDefin | ||
+ .bind("zero_firs | ||
this); | ||
if (!AllowExplicitS | ||
- Finder->addMatch | ||
+ Finder->addMatch | ||
+ hasSequentialIni | ||
.bind("sequentia | ||
this); | ||
} | ||
@@ -158,10 +167,11 @@ void EnumInitialValue | ||
void EnumInitialValue | ||
if (const auto *Enum = Result.Nodes.get | ||
DiagnosticBuilde | ||
- diag(Enum->getBe | ||
- "inital values in enum %0 are not consistent, consider explicit " | ||
- "initialization of all, none or only the first enumerator") | ||
- << Enum; | ||
+ diag( | ||
+ Enum->getBeginLo | ||
+ "initial values in enum '%0' are not consistent, consider explicit " | ||
+ "initialization of all, none or only the first enumerator") | ||
+ << getName(Enum); | ||
for (const EnumConstantDecl | ||
if (ECD->getInitExp | ||
const SourceLocation EndLoc = Lexer::getLocFor | ||
@@ -181,16 +191,16 @@ void EnumInitialValue | ||
if (Loc.isInvalid() | ||
return; | ||
DiagnosticBuilde | ||
- "enumerator in %0 can be disregarded") | ||
- << Enum; | ||
+ "enumerator in '%0' can be disregarded") | ||
+ << getName(Enum); | ||
cleanInitialValu | ||
return; | ||
} | ||
if (const auto *Enum = Result.Nodes.get | ||
DiagnosticBuilde | ||
diag(Enum->getBe | ||
- "sequential initial value in %0 can be ignored") | ||
- << Enum; | ||
+ "sequential initial value in '%0' can be ignored") | ||
+ << getName(Enum); | ||
for (const EnumConstantDecl | ||
cleanInitialValu | ||
return; |
@@ -126,7 +126,7 @@ struct CognitiveComplex | ||
// Limit of 25 is the "upstream"'s default. | ||
static constexpr unsigned DefaultLimit = 25U; | ||
- // Based on the publicly-avaliab | ||
+ // Based on the publicly-availab | ||
// https://sonarclo | ||
// value ~20 would result in no allocs for 98% of functions, ~12 for 96%, ~10 | ||
// for 91%, ~8 for 88%, ~6 for 84%, ~4 for 77%, ~2 for 64%, and ~1 for 37%. |
@@ -1135,6 +1135,9 @@ StyleKind IdentifierNaming | ||
if (isa<TypeAliasDe | ||
return SK_TypeAlias; | ||
+ if (isa<NamespaceAl | ||
+ return SK_Namespace; | ||
+ | ||
if (const auto *Decl = dyn_cast<Namespa | ||
if (Decl->isAnonymo | ||
return SK_Invalid; |
@@ -10,6 +10,7 @@ | ||
#include "../utils/FixItH | ||
#include "clang/AST/ASTCo | ||
#include "clang/ASTMatche | ||
+#include "clang/ASTMatche | ||
#include "clang/Lex/Lexer | ||
#include "clang/Tooling/F | ||
#include <queue> | ||
@@ -26,6 +27,8 @@ AST_MATCHER(Stmt | ||
return SM.isMacroBodyEx | ||
} | ||
+AST_MATCHER(Stm | ||
+ | ||
bool isNULLMacroExpan | ||
SourceManager &SM = Context.getSourc | ||
const LangOptions &LO = Context.getLangO | ||
@@ -298,6 +301,11 @@ void ImplicitBoolConv | ||
hasCastKind(CK_F | ||
hasCastKind(CK_P | ||
hasCastKind(CK_M | ||
+ // Exclude cases of C23 comparison result. | ||
+ unless(allOf(isC | ||
+ hasSourceExpress | ||
+ binaryOperator(h | ||
+ ">", ">=", "==", "!=", "<", "<=")))))), | ||
// Exclude case of using if or while statements with variable | ||
// declaration, e.g.: | ||
// if (int var = functionCall()) {} |
@@ -8,16 +8,16 @@ | ||
# | ||
# ===------------- | ||
-from __future__ import unicode_literals | ||
- | ||
import argparse | ||
import glob | ||
import io | ||
import os | ||
import re | ||
+import sys | ||
+from typing import List | ||
-def replaceInFileReg | ||
+def replaceInFileReg | ||
if sFrom == sTo: | ||
return | ||
@@ -35,7 +35,7 @@ def replaceInFileReg | ||
f.write(txt) | ||
-def replaceInFile(fi | ||
+def replaceInFile(fi | ||
if sFrom == sTo: | ||
return | ||
txt = None | ||
@@ -51,7 +51,7 @@ def replaceInFile(fi | ||
f.write(txt) | ||
-def generateCommentL | ||
+def generateCommentL | ||
return "".join( | ||
[ | ||
"//===--- ", | ||
@@ -63,7 +63,7 @@ def generateCommentL | ||
) | ||
-def generateCommentL | ||
+def generateCommentL | ||
return "".join( | ||
[ | ||
"//===--- ", | ||
@@ -75,7 +75,7 @@ def generateCommentL | ||
) | ||
-def fileRename(fileN | ||
+def fileRename(fileN | ||
if sFrom not in fileName or sFrom == sTo: | ||
return fileName | ||
newFileName = fileName.replace | ||
@@ -84,7 +84,7 @@ def fileRename(fileN | ||
return newFileName | ||
-def deleteMatchingLi | ||
+def deleteMatchingLi | ||
lines = None | ||
with io.open(fileName | ||
lines = f.readlines() | ||
@@ -101,7 +101,7 @@ def deleteMatchingLi | ||
return True | ||
-def getListOfFiles(c | ||
+def getListOfFiles(c | ||
files = glob.glob(os.pat | ||
files += [ | ||
os.path.normpath | ||
@@ -124,7 +124,7 @@ def getListOfFiles(c | ||
# Adapts the module's CMakelist file. Returns 'True' if it could add a new | ||
# entry and 'False' if the entry already existed. | ||
-def adapt_cmake(modu | ||
+def adapt_cmake(modu | ||
filename = os.path.join(mod | ||
with io.open(filename | ||
lines = f.readlines() | ||
@@ -153,7 +153,9 @@ def adapt_cmake(modu | ||
# Modifies the module to include the new check. | ||
-def adapt_module(mod | ||
+def adapt_module( | ||
+ module_path: str, module: str, check_name: str, check_name_camel | ||
+) -> None: | ||
modulecpp = next( | ||
iter( | ||
filter( | ||
@@ -204,7 +206,9 @@ def adapt_module(mod | ||
# Adds a release notes entry. | ||
-def add_release_note | ||
+def add_release_note | ||
+ clang_tidy_path: | ||
+) -> None: | ||
filename = os.path.normpath | ||
os.path.join(cla | ||
) | ||
@@ -262,7 +266,7 @@ def add_release_note | ||
f.write(line) | ||
-def main(): | ||
+def main() -> None: | ||
parser = argparse.Argumen | ||
parser.add_argum | ||
parser.add_argum | ||
@@ -311,7 +315,7 @@ def main(): | ||
"Check name '%s' not found in %s. Exiting." | ||
% (check_name_came | ||
) | ||
- return 1 | ||
+ sys.exit(1) | ||
modulecpp = next( | ||
iter( |
@@ -9,7 +9,7 @@ set(LLVM_LINK_CO | ||
# Needed by LLVM's CMake checks because this file defines multiple targets. | ||
set(LLVM_OPTIONA | ||
-add_clang_libra | ||
+add_clang_libra | ||
ClangTidyMain.cp | ||
LINK_LIBS |
@@ -49,7 +49,7 @@ import tempfile | ||
import time | ||
import traceback | ||
from types import ModuleType | ||
-from typing import Any, Awaitable, Callable, List, Optional, Tuple, TypeVar | ||
+from typing import Any, Awaitable, Callable, List, Optional, TypeVar | ||
yaml: Optional[ModuleT | ||
@@ -621,4 +621,7 @@ async def main() -> None: | ||
if __name__ == "__main__": | ||
- asyncio.run(main | ||
+ try: | ||
+ asyncio.run(main | ||
+ except KeyboardInterrup | ||
+ pass |
@@ -3,7 +3,7 @@ set(LLVM_LINK_CO | ||
Support | ||
) | ||
-add_clang_libra | ||
+add_clang_libra | ||
Aliasing.cpp | ||
ASTUtils.cpp | ||
BracesAroundStat |
@@ -11,6 +11,7 @@ | ||
#include "clang/AST/ASTCo | ||
#include "clang/AST/ExprC | ||
#include "clang/AST/Type. | ||
+#include "clang/Sema/Decl | ||
#include "clang/Tooling/F | ||
#include <optional> | ||
@@ -71,15 +72,17 @@ static std::optional<Fi | ||
// Build a string that can be emitted as FixIt with either a space in before | ||
// or after the qualifier, either ' const' or 'const '. | ||
-static std::string buildQualifier(D | ||
+static std::string buildQualifier(Q | ||
bool WhitespaceBefore | ||
if (WhitespaceBefor | ||
- return (llvm::Twine(' ') + DeclSpec::getSpe | ||
- return (llvm::Twine(Dec | ||
+ return (llvm::Twine(' ') + Qualifiers::from | ||
+ .str(); | ||
+ return (llvm::Twine(Qua | ||
+ .str(); | ||
} | ||
static std::optional<Fi | ||
- DeclSpec::TQ Qualifier, | ||
+ Qualifiers::TQ Qualifier, | ||
QualifierTarget QualTarget, | ||
QualifierPolicy QualPolicy, | ||
const ASTContext &Context) { | ||
@@ -99,7 +102,7 @@ static std::optional<Fi | ||
} | ||
static std::optional<Fi | ||
- DeclSpec::TQ Qualifier, | ||
+ Qualifiers::TQ Qualifier, | ||
const ASTContext &Context) { | ||
if (locDangerous(Va | ||
return std::nullopt; | ||
@@ -112,7 +115,7 @@ static std::optional<Fi | ||
} | ||
static std::optional<Fi | ||
-changePointer(c | ||
+changePointer(c | ||
QualifierTarget QualTarget, QualifierPolicy QualPolicy, | ||
const ASTContext &Context) { | ||
// The pointer itself shall be marked as `const`. This is always to the right | ||
@@ -163,7 +166,7 @@ changePointer(co | ||
} | ||
static std::optional<Fi | ||
-changeReference | ||
+changeReference | ||
QualifierTarget QualTarget, QualifierPolicy QualPolicy, | ||
const ASTContext &Context) { | ||
if (QualPolicy == QualifierPolicy: | ||
@@ -183,7 +186,7 @@ changeReferencee | ||
std::optional<Fi | ||
const ASTContext &Context, | ||
- DeclSpec::TQ Qualifier, | ||
+ Qualifiers::TQ Qualifier, | ||
QualifierTarget QualTarget, | ||
QualifierPolicy QualPolicy) { | ||
assert((QualPoli |
@@ -11,7 +11,7 @@ | ||
#include "clang/AST/ASTCo | ||
#include "clang/AST/Decl. | ||
-#include "clang/Sema/Decl | ||
+#include "clang/AST/Type. | ||
#include <optional> | ||
namespace clang::tidy::uti | ||
@@ -41,7 +41,7 @@ enum class QualifierTarget { | ||
/// Requires that `Var` is isolated in written code like in `int foo = 42;`. | ||
std::optional<Fi | ||
addQualifierToVa | ||
- DeclSpec::TQ Qualifier, | ||
+ Qualifiers::TQ Qualifier, | ||
QualifierTarget QualTarget = QualifierTarget: | ||
QualifierPolicy QualPolicy = QualifierPolicy: | ||
@@ -18,6 +18,7 @@ | ||
#include "clang/ASTMatche | ||
#include "clang/Basic/Lan | ||
#include "clang/Lex/Lexer | ||
+#include "clang/Lex/Prepr | ||
#include "clang/Tooling/F | ||
#include "llvm/ADT/String | ||
#include "llvm/Support/De | ||
@@ -195,11 +196,10 @@ static bool castMismatchedIn | ||
return false; | ||
} | ||
-FormatStringCon | ||
- const CallExpr *Call, | ||
- unsigned FormatArgOffset, | ||
- const Configuration ConfigIn, | ||
- const LangOptions &LO) | ||
+FormatStringCon | ||
+ ASTContext *ContextIn, const CallExpr *Call, unsigned FormatArgOffset, | ||
+ const Configuration ConfigIn, const LangOptions &LO, SourceManager &SM, | ||
+ Preprocessor &PP) | ||
: Context(ContextI | ||
CastMismatchedIn | ||
castMismatchedIn | ||
@@ -208,11 +208,22 @@ FormatStringConv | ||
assert(ArgsOffse | ||
FormatExpr = llvm::dyn_cast<S | ||
Args[FormatArgOf | ||
+ | ||
if (!FormatExpr || !FormatExpr->isO | ||
// Function must have a narrow string literal as its first argument. | ||
conversionNotPos | ||
return; | ||
} | ||
+ | ||
+ if (const std::optional<St | ||
+ formatStringCont | ||
+ MaybeMacroName) { | ||
+ conversionNotPos | ||
+ ("format string contains unreplaceable macro '" + *MaybeMacroName + "'") | ||
+ .str()); | ||
+ return; | ||
+ } | ||
+ | ||
PrintfFormatStri | ||
// Assume that the output will be approximately the same size as the input, | ||
@@ -230,6 +241,50 @@ FormatStringConv | ||
finalizeFormatTe | ||
} | ||
+std::optional<S | ||
+FormatStringCon | ||
+ const CallExpr *Call, const StringLiteral *FormatExpr, SourceManager &SM, | ||
+ Preprocessor &PP) { | ||
+ // If a macro invocation surrounds the entire call then we don't want that to | ||
+ // inhibit conversion. The whole format string will appear to come from that | ||
+ // macro, as will the function call. | ||
+ std::optional<St | ||
+ if (SourceLocation BeginCallLoc = Call->getBeginLo | ||
+ BeginCallLoc.isM | ||
+ MaybeSurrounding | ||
+ Lexer::getImmedi | ||
+ | ||
+ for (auto I = FormatExpr->tokl | ||
+ I != E; ++I) { | ||
+ const SourceLocation &TokenLoc = *I; | ||
+ if (TokenLoc.isMacr | ||
+ const StringRef MacroName = | ||
+ Lexer::getImmedi | ||
+ | ||
+ if (MaybeSurroundin | ||
+ // glibc uses __PRI64_PREFIX and __PRIPTR_PREFIX to define the prefixes | ||
+ // for types that change size so we must look for multiple prefixes. | ||
+ if (!MacroName.star | ||
+ return MacroName; | ||
+ | ||
+ const SourceLocation TokenSpellingLoc | ||
+ const OptionalFileEntr | ||
+ SM.getFileEntryR | ||
+ if (!MaybeFileEntry | ||
+ return MacroName; | ||
+ | ||
+ HeaderSearch &HS = PP.getHeaderSear | ||
+ // Check if the file is a system header | ||
+ if (!isSystem(HS.ge | ||
+ llvm::sys::path: | ||
+ "inttypes.h") | ||
+ return MacroName; | ||
+ } | ||
+ } | ||
+ } | ||
+ return std::nullopt; | ||
+} | ||
+ | ||
void FormatStringConv | ||
std::string &FormatSpec) { | ||
ConversionSpecif |
@@ -40,7 +40,8 @@ public: | ||
FormatStringConv | ||
unsigned FormatArgOffset, | ||
- const LangOptions &LO); | ||
+ const LangOptions &LO, SourceManager &SM, | ||
+ Preprocessor &PP); | ||
bool canApply() const { return ConversionNotPos | ||
const std::string &conversionNotPo | ||
@@ -110,6 +111,10 @@ private: | ||
void appendFormatText | ||
void finalizeFormatTe | ||
+ static std::optional<St | ||
+ formatStringCont | ||
+ const StringLiteral *FormatExpr, | ||
+ SourceManager &SM, Preprocessor &PP); | ||
bool conversionNotPos | ||
ConversionNotPos | ||
return false; |
@@ -24,13 +24,15 @@ getPreviousToken | ||
if (Location.isInva | ||
return {Token, Location}; | ||
- auto StartOfFile = SM.getLocForStar | ||
+ const auto StartOfFile = SM.getLocForStar | ||
while (Location != StartOfFile) { | ||
Location = Lexer::GetBeginn | ||
if (!Lexer::getRawT | ||
(!SkipComments || !Token.is(tok::c | ||
break; | ||
} | ||
+ if (Location == StartOfFile) | ||
+ return {Token, Location}; | ||
Location = Location.getLocW | ||
} | ||
return {Token, Location}; |
@@ -85,15 +85,7 @@ public: | ||
NameList.begin() | ||
[](const llvm::StringRef Name) { return NameMatcher(Name | ||
} | ||
- bool matches( | ||
- const NamedDecl &Node, ast_matchers::in | ||
- ast_matchers::in | ||
- return llvm::any_of(Nam | ||
- return NM.match(Node); | ||
- }); | ||
- } | ||
-private: | ||
class NameMatcher { | ||
llvm::Regex Regex; | ||
enum class MatchMode { | ||
@@ -136,6 +128,15 @@ private: | ||
} | ||
}; | ||
+ bool matches( | ||
+ const NamedDecl &Node, ast_matchers::in | ||
+ ast_matchers::in | ||
+ return llvm::any_of(Nam | ||
+ return NM.match(Node); | ||
+ }); | ||
+ } | ||
+ | ||
+private: | ||
std::vector<Name | ||
}; | ||
@@ -3,7 +3,7 @@ set(LLVM_LINK_CO | ||
Support | ||
) | ||
-add_clang_libra | ||
+add_clang_libra | ||
TemporaryObjects | ||
ZirconTidyModule | ||
@@ -144,8 +144,13 @@ getQualification | ||
// since we stored inner-most parent first. | ||
std::string Result; | ||
llvm::raw_string | ||
- for (const auto *Parent : llvm::reverse(Pa | ||
+ for (const auto *Parent : llvm::reverse(Pa | ||
+ if (Parent != *Parents.rbegin( | ||
+ Parent->getAsRec | ||
+ Parent->getAsRec | ||
+ OS << "template "; | ||
Parent->print(OS | ||
+ } | ||
return OS.str(); | ||
} | ||
@@ -187,7 +192,6 @@ std::string printQualifiedNa | ||
// In clangd, context is usually available and paths are mostly noise. | ||
Policy.Anonymous | ||
ND.printQualifie | ||
- OS.flush(); | ||
assert(!StringRe | ||
return QName; | ||
} | ||
@@ -270,7 +274,6 @@ std::string printTemplateSpe | ||
// location information. | ||
printTemplateArg | ||
} | ||
- OS.flush(); | ||
return TemplateArgs; | ||
} | ||
@@ -303,7 +306,6 @@ std::string printObjCMethod( | ||
OS << ", ..."; | ||
OS << ']'; | ||
- OS.flush(); | ||
return Name; | ||
} | ||
@@ -314,7 +316,6 @@ std::string printObjCContain | ||
const ObjCInterfaceDec | ||
OS << getNameOrErrForO | ||
<< ')'; | ||
- OS.flush(); | ||
return Name; | ||
} | ||
if (const ObjCCategoryImpl | ||
@@ -322,7 +323,6 @@ std::string printObjCContain | ||
llvm::raw_string | ||
const ObjCInterfaceDec | ||
OS << getNameOrErrForO | ||
- OS.flush(); | ||
return Name; | ||
} | ||
return C.getNameAsStrin |
@@ -61,7 +61,7 @@ endif() | ||
include_director | ||
include_director | ||
-add_clang_libra | ||
+add_clang_libra | ||
AST.cpp | ||
ASTSignals.cpp | ||
ClangdLSPServer. | ||
@@ -183,7 +183,6 @@ target_link_libr | ||
${LLVM_PTHREAD_L | ||
clangIncludeClea | ||
- clangPseudo | ||
clangTidy | ||
clangTidyUtils | ||
@@ -451,6 +451,7 @@ void ClangdServer::co | ||
CodeCompleteOpts | ||
CodeCompleteOpts | ||
+ CodeCompleteOpts | ||
// FIXME(ibiryukov) | ||
// both the old and the new version in case only one of them matches. | ||
CodeCompleteResu |
@@ -21,6 +21,7 @@ | ||
#include "AST.h" | ||
#include "CodeCompletionS | ||
#include "Compiler.h" | ||
+#include "Config.h" | ||
#include "ExpectedTypes.h | ||
#include "Feature.h" | ||
#include "FileDistance.h" | ||
@@ -350,8 +351,7 @@ struct CodeCompletionBu | ||
CodeCompletionCo | ||
const CodeCompleteOpti | ||
bool IsUsingDeclarati | ||
- : ASTCtx(ASTCtx), | ||
- EnableFunctionAr | ||
+ : ASTCtx(ASTCtx), ArgumentLists(Op | ||
IsUsingDeclarati | ||
Completion.Depre | ||
add(C, SemaCCS, ContextKind); | ||
@@ -561,6 +561,15 @@ private: | ||
} | ||
std::string summarizeSnippet | ||
+ /// localize ArgumentLists tests for better readability | ||
+ const bool None = ArgumentLists == Config::Argument | ||
+ const bool Open = | ||
+ ArgumentLists == Config::Argument | ||
+ const bool Delim = ArgumentLists == Config::Argument | ||
+ const bool Full = | ||
+ ArgumentLists == Config::Argument | ||
+ (!None && !Open && !Delim); // <-- failsafe: Full is default | ||
+ | ||
if (IsUsingDeclarat | ||
return ""; | ||
auto *Snippet = onlyValue<&Bundl | ||
@@ -568,7 +577,7 @@ private: | ||
// All bundles are function calls. | ||
// FIXME(ibiryukov) | ||
// we need to complete 'forward<$1>($0) | ||
- return "($0)"; | ||
+ return None ? "" : (Open ? "(" : "($0)"); | ||
if (Snippet->empty( | ||
return ""; | ||
@@ -607,7 +616,7 @@ private: | ||
return ""; | ||
} | ||
} | ||
- if (EnableFunctionA | ||
+ if (Full) | ||
return *Snippet; | ||
// Replace argument snippets with a simplified pattern. | ||
@@ -622,9 +631,9 @@ private: | ||
bool EmptyArgs = llvm::StringRef( | ||
if (Snippet->front( | ||
- return EmptyArgs ? "<$1>()$0" : "<$1>($0)"; | ||
+ return None ? "" : (Open ? "<" : (EmptyArgs ? "<$1>()$0" : "<$1>($0)")); | ||
if (Snippet->front( | ||
- return EmptyArgs ? "()" : "($0)"; | ||
+ return None ? "" : (Open ? "(" : (EmptyArgs ? "()" : "($0)")); | ||
return *Snippet; // Not an arg snippet? | ||
} | ||
// 'CompletionItemK | ||
@@ -638,7 +647,7 @@ private: | ||
// e.g. Foo<${1:class}>. | ||
if (llvm::StringRef | ||
return "<>"; // can happen with defaulted template arguments. | ||
- return "<$0>"; | ||
+ return None ? "" : (Open ? "<" : "<$0>"); | ||
} | ||
return *Snippet; | ||
} | ||
@@ -654,7 +663,8 @@ private: | ||
ASTContext *ASTCtx; | ||
CodeCompletion Completion; | ||
llvm::SmallVecto | ||
- bool EnableFunctionAr | ||
+ /// the way argument lists are handled. | ||
+ Config::Argument | ||
// No snippets will be generated for using declarations and when the function | ||
// arguments are already present. | ||
bool IsUsingDeclarati | ||
@@ -1409,6 +1419,9 @@ bool semaCodeComplete | ||
Clang->getPrepro | ||
Clang->setCodeCo | ||
+ if (Input.Preamble. | ||
+ Input.Preamble.R | ||
+ | ||
SyntaxOnlyAction | ||
if (!Action.BeginSo | ||
log("BeginSource | ||
@@ -2122,7 +2135,7 @@ clang::CodeCompl | ||
// When an is used, Sema is responsible for completing the main file, | ||
// the index can provide results from the preamble. | ||
// Tell Sema not to deserialize the preamble to look for results. | ||
- Result.LoadExter | ||
+ Result.LoadExter | ||
Result.IncludeFi | ||
return Result; |
@@ -17,6 +17,7 @@ | ||
#include "ASTSignals.h" | ||
#include "Compiler.h" | ||
+#include "Config.h" | ||
#include "Protocol.h" | ||
#include "Quality.h" | ||
#include "index/Index.h" | ||
@@ -52,6 +53,11 @@ struct CodeCompleteOpti | ||
/// For example, private members are usually inaccessible. | ||
bool IncludeIneligibl | ||
+ /// Force sema to load decls from preamble even if an index is provided. | ||
+ /// This is helpful for cases the index can't provide symbols, e.g. with | ||
+ /// experimental c++20 modules | ||
+ bool ForceLoadPreambl | ||
+ | ||
/// Combine overloads into a single completion item where possible. | ||
/// If none, the implementation may choose an appropriate behavior. | ||
/// (In practice, ClangdLSPServer enables bundling if the client claims | ||
@@ -96,10 +102,6 @@ struct CodeCompleteOpti | ||
/// '->' on member access etc. | ||
bool IncludeFixIts = false; | ||
- /// Whether to generate snippets for function arguments on code-completion. | ||
- /// Needs snippets to be enabled as well. | ||
- bool EnableFunctionAr | ||
- | ||
/// Whether to include index symbols that are not defined in the scopes | ||
/// visible from the code completion point. This applies in contexts without | ||
/// explicit scope qualifiers. | ||
@@ -107,6 +109,10 @@ struct CodeCompleteOpti | ||
/// Such completions can insert scope qualifiers. | ||
bool AllScopes = false; | ||
+ /// The way argument list on calls '()' and generics '<>' are handled. | ||
+ Config::Argument | ||
+ Config::Argument | ||
+ | ||
/// Whether to use the clang parser, or fallback to text-based completion | ||
/// (using identifiers in the current file and symbol indexes). | ||
enum CodeCompletionPa |
@@ -126,11 +126,25 @@ struct Config { | ||
std::vector<std: | ||
} Style; | ||
+ /// controls the completion options for argument lists. | ||
+ enum class ArgumentListsPol | ||
+ /// nothing, no argument list and also NO Delimiters "()" or "<>". | ||
+ None, | ||
+ /// open, only opening delimiter "(" or "<". | ||
+ OpenDelimiter, | ||
+ /// empty pair of delimiters "()" or "<>". | ||
+ Delimiters, | ||
+ /// full name of both type and variable. | ||
+ FullPlaceholders | ||
+ }; | ||
+ | ||
/// Configures code completion feature. | ||
struct { | ||
/// Whether code completion includes results that are not visible in current | ||
/// scopes. | ||
bool AllScopes = true; | ||
+ /// controls the completion options for argument lists. | ||
+ ArgumentListsPol | ||
} Completion; | ||
/// Configures hover feature. | ||
@@ -148,6 +162,7 @@ struct Config { | ||
bool DeducedTypes = true; | ||
bool Designators = true; | ||
bool BlockEnd = false; | ||
+ bool DefaultArguments | ||
// Limit the length of type names in inlay hints. (0 means no limit) | ||
uint32_t TypeNameLimit = 32; | ||
} InlayHints; |
@@ -43,7 +43,6 @@ | ||
#include "llvm/Support/Re | ||
#include "llvm/Support/SM | ||
#include "llvm/Support/So | ||
-#include <algorithm> | ||
#include <memory> | ||
#include <optional> | ||
#include <string> | ||
@@ -622,6 +621,21 @@ struct FragmentCompiler | ||
C.Completion.All | ||
}); | ||
} | ||
+ if (F.ArgumentLists | ||
+ if (auto Val = | ||
+ compileEnum<Conf | ||
+ *F.ArgumentLists | ||
+ .map("None", Config::Argument | ||
+ .map("OpenDelimi | ||
+ Config::Argument | ||
+ .map("Delimiters | ||
+ .map("FullPlaceh | ||
+ Config::Argument | ||
+ .value()) | ||
+ Out.Apply.push_b | ||
+ C.Completion.Arg | ||
+ }); | ||
+ } | ||
} | ||
void compile(Fragment | ||
@@ -654,6 +668,11 @@ struct FragmentCompiler | ||
Out.Apply.push_b | ||
C.InlayHints.Blo | ||
}); | ||
+ if (F.DefaultArgume | ||
+ Out.Apply.push_b | ||
+ [Value(**F.Defau | ||
+ C.InlayHints.Def | ||
+ }); | ||
if (F.TypeNameLimit | ||
Out.Apply.push_b | ||
[Value(**F.TypeN |
@@ -32,6 +32,7 @@ | ||
#ifndef LLVM_CLANG_TOOLS | ||
#define LLVM_CLANG_TOOLS | ||
+#include "Config.h" | ||
#include "ConfigProvider. | ||
#include "llvm/Support/SM | ||
#include "llvm/Support/So | ||
@@ -308,6 +309,13 @@ struct Fragment { | ||
/// Whether code completion should include suggestions from scopes that are | ||
/// not visible. The required scope prefix will be inserted. | ||
std::optional<Lo | ||
+ /// How to present the argument list between '()' and '<>': | ||
+ /// valid values are enum Config::Argument | ||
+ /// None: Nothing at all | ||
+ /// OpenDelimiter: only opening delimiter "(" or "<" | ||
+ /// Delimiters: empty pair of delimiters "()" or "<>" | ||
+ /// FullPlaceholders | ||
+ std::optional<Lo | ||
}; | ||
CompletionBlock Completion; | ||
@@ -331,6 +339,9 @@ struct Fragment { | ||
std::optional<Lo | ||
/// Show defined symbol names at the end of a definition block. | ||
std::optional<Lo | ||
+ /// Show parameter names and default values of default arguments after all | ||
+ /// of the explicit arguments. | ||
+ std::optional<Lo | ||
/// Limit the length of type name hints. (0 means no limit) | ||
std::optional<Lo | ||
}; |
@@ -14,7 +14,6 @@ | ||
#include "llvm/Support/YA | ||
#include <optional> | ||
#include <string> | ||
-#include <system_error> | ||
namespace clang { | ||
namespace clangd { | ||
@@ -230,6 +229,10 @@ private: | ||
if (auto AllScopes = boolValue(N, "AllScopes")) | ||
F.AllScopes = *AllScopes; | ||
}); | ||
+ Dict.handle("Arg | ||
+ if (auto ArgumentLists = scalarValue(N, "ArgumentLists") | ||
+ F.ArgumentLists = *ArgumentLists; | ||
+ }); | ||
Dict.parse(N); | ||
} | ||
@@ -264,6 +267,10 @@ private: | ||
if (auto Value = boolValue(N, "BlockEnd")) | ||
F.BlockEnd = *Value; | ||
}); | ||
+ Dict.handle("Def | ||
+ if (auto Value = boolValue(N, "DefaultArgument | ||
+ F.DefaultArgumen | ||
+ }); | ||
Dict.handle("Typ | ||
if (auto Value = uint32Value(N, "TypeNameLimit") | ||
F.TypeNameLimit = *Value; |
@@ -319,7 +319,6 @@ std::string mainMessage(cons | ||
OS << "\n\n"; | ||
printDiag(OS, Note); | ||
} | ||
- OS.flush(); | ||
return capitalize(std:: | ||
} | ||
@@ -335,7 +334,6 @@ std::string noteMessage(cons | ||
OS << "\n\n"; | ||
printDiag(OS, Main); | ||
} | ||
- OS.flush(); | ||
return capitalize(std:: | ||
} | ||
@@ -187,6 +187,7 @@ class DumpVisitor : public RecursiveASTVisi | ||
TEMPLATE_KIND(Su | ||
TEMPLATE_KIND(Su | ||
TEMPLATE_KIND(Us | ||
+ TEMPLATE_KIND(De | ||
#undef TEMPLATE_KIND | ||
} | ||
llvm_unreachable |
@@ -8,6 +8,7 @@ | ||
#include "Feature.h" | ||
#include "clang/Basic/Ver | ||
+#include "llvm/Config/llv | ||
#include "llvm/Support/Co | ||
#include "llvm/TargetPars | ||
@@ -111,7 +111,7 @@ getWorkspaceSymb | ||
*Req.Limit *= 5; | ||
} | ||
TopN<ScoredSymbo | ||
- Req.Limit ? *Req.Limit : std::numeric_lim | ||
+ Req.Limit.value_ | ||
FuzzyMatcher Filter(Req.Query | ||
Index->fuzzyFind | ||
@@ -182,7 +182,6 @@ std::string getSymbolName(AS | ||
OS << (Method->isInsta | ||
Method->getSelec | ||
- OS.flush(); | ||
return Name; | ||
} | ||
return printName(Ctx, ND); |
@@ -75,8 +75,8 @@ public: | ||
IDs.push_back(HI | ||
} | ||
} | ||
- Out->MainFileInc | ||
- .first->second.p | ||
+ Out->MainFileInc | ||
+ Out->MainFileInc | ||
} | ||
// Record include graph (not just for main-file includes) |
@@ -150,7 +150,6 @@ std::string printDefinition( | ||
std::string Definition; | ||
llvm::raw_string | ||
D->print(OS, PP); | ||
- OS.flush(); | ||
return Definition; | ||
} | ||
@@ -179,7 +178,6 @@ HoverInfo::Print | ||
OS << TT->getDecl()->g | ||
} | ||
QT.print(OS, PP); | ||
- OS.flush(); | ||
const Config &Cfg = Config::current( | ||
if (!QT.isNull() && Cfg.Hover.ShowAK | ||
@@ -229,7 +227,6 @@ HoverInfo::Print | ||
// FIXME: TemplateTemplate | ||
// param was a "typename" or "class". | ||
OS << "> class"; | ||
- OS.flush(); | ||
return Result; | ||
} | ||
@@ -821,7 +818,6 @@ std::string typeAsDefinition | ||
OS << PType.Type; | ||
if (PType.AKA) | ||
OS << " // aka: " << *PType.AKA; | ||
- OS.flush(); | ||
return Result; | ||
} | ||
@@ -11,9 +11,11 @@ | ||
#include "Config.h" | ||
#include "HeuristicResolv | ||
#include "ParsedAST.h" | ||
+#include "Protocol.h" | ||
#include "SourceCode.h" | ||
#include "clang/AST/ASTDi | ||
#include "clang/AST/Decl. | ||
+#include "clang/AST/DeclB | ||
#include "clang/AST/Decla | ||
#include "clang/AST/Expr. | ||
#include "clang/AST/ExprC | ||
@@ -23,15 +25,22 @@ | ||
#include "clang/AST/Type. | ||
#include "clang/Basic/Bui | ||
#include "clang/Basic/Ope | ||
+#include "clang/Basic/Sou | ||
#include "clang/Basic/Sou | ||
#include "llvm/ADT/DenseS | ||
+#include "llvm/ADT/STLExt | ||
+#include "llvm/ADT/SmallV | ||
#include "llvm/ADT/String | ||
#include "llvm/ADT/String | ||
#include "llvm/ADT/Twine. | ||
#include "llvm/Support/Ca | ||
+#include "llvm/Support/Er | ||
+#include "llvm/Support/Fo | ||
#include "llvm/Support/Sa | ||
#include "llvm/Support/Sc | ||
#include "llvm/Support/ra | ||
+#include <algorithm> | ||
+#include <iterator> | ||
#include <optional> | ||
#include <string> | ||
@@ -372,6 +381,23 @@ maybeDropCxxExpl | ||
return Params; | ||
} | ||
+template <typename R> | ||
+std::string joinAndTruncate( | ||
+ std::string Out; | ||
+ llvm::raw_string | ||
+ llvm::ListSepara | ||
+ for (auto &&Element : Range) { | ||
+ OS << Sep; | ||
+ if (Out.size() + Element.size() >= MaxLength) { | ||
+ OS << "..."; | ||
+ break; | ||
+ } | ||
+ OS << Element; | ||
+ } | ||
+ OS.flush(); | ||
+ return Out; | ||
+} | ||
+ | ||
struct Callee { | ||
// Only one of Decl or Loc is set. | ||
// Loc is for calls through function pointers. | ||
@@ -422,7 +448,8 @@ public: | ||
Callee.Decl = E->getConstructo | ||
if (!Callee.Decl) | ||
return true; | ||
- processCall(Call | ||
+ processCall(Call | ||
+ {E->getArgs(), E->getNumArgs()} | ||
return true; | ||
} | ||
@@ -495,7 +522,7 @@ public: | ||
dyn_cast_or_null | ||
if (IsFunctor || Method->hasCXXEx | ||
Args = Args.drop_front( | ||
- processCall(Call | ||
+ processCall(Call | ||
return true; | ||
} | ||
@@ -709,10 +736,12 @@ public: | ||
private: | ||
using NameVec = SmallVector<Stri | ||
- void processCall(Call | ||
+ void processCall(Call | ||
+ llvm::ArrayRef<c | ||
assert(Callee.De | ||
- if (!Cfg.InlayHints | ||
+ if ((!Cfg.InlayHint | ||
+ Args.size() == 0) | ||
return; | ||
// The parameter name of a move or copy constructor is not very interesting. | ||
@@ -721,6 +750,9 @@ private: | ||
if (Ctor->isCopyOrM | ||
return; | ||
+ SmallVector<std: | ||
+ bool HasNonDefaultArg | ||
+ | ||
ArrayRef<const ParmVarDecl *> Params, ForwardedParams; | ||
// Resolve parameter packs to their forwarded parameter | ||
SmallVector<cons | ||
@@ -752,15 +784,44 @@ private: | ||
} | ||
StringRef Name = ParameterNames[I | ||
- bool NameHint = shouldHintName(A | ||
- bool ReferenceHint = shouldHintRefere | ||
- | ||
- if (NameHint || ReferenceHint) { | ||
+ const bool NameHint = | ||
+ shouldHintName(A | ||
+ const bool ReferenceHint = | ||
+ shouldHintRefere | ||
+ Cfg.InlayHints.P | ||
+ | ||
+ const bool IsDefault = isa<CXXDefaultAr | ||
+ HasNonDefaultArg | ||
+ if (IsDefault) { | ||
+ if (Cfg.InlayHints. | ||
+ const auto SourceText = Lexer::getSource | ||
+ CharSourceRange: | ||
+ AST.getSourceMan | ||
+ const auto Abbrev = | ||
+ (SourceText.size | ||
+ SourceText.conta | ||
+ ? "..." | ||
+ : SourceText; | ||
+ if (NameHint) | ||
+ FormattedDefault | ||
+ llvm::formatv("{ | ||
+ else | ||
+ FormattedDefault | ||
+ } | ||
+ } else if (NameHint || ReferenceHint) { | ||
addInlayHint(Arg | ||
InlayHintKind::P | ||
NameHint ? Name : "", ": "); | ||
} | ||
} | ||
+ | ||
+ if (!FormattedDefau | ||
+ std::string Hint = | ||
+ joinAndTruncate( | ||
+ addInlayHint(Sou | ||
+ InlayHintKind::D | ||
+ HasNonDefaultArg | ||
+ } | ||
} | ||
static bool isSetter(const FunctionDecl *Callee, const NameVec &ParamNames) { | ||
@@ -968,6 +1029,7 @@ private: | ||
CHECK_KIND(Type, | ||
CHECK_KIND(Desig | ||
CHECK_KIND(Block | ||
+ CHECK_KIND(Defau | ||
#undef CHECK_KIND | ||
} | ||
@@ -12,6 +12,7 @@ | ||
#include "clang/Frontend/ | ||
#include "clang/Frontend/ | ||
#include "clang/Serializa | ||
+#include "clang/Serializa | ||
namespace clang { | ||
namespace clangd { | ||
@@ -127,50 +128,68 @@ struct ModuleFile { | ||
std::string ModuleFilePath; | ||
}; | ||
-bool IsModuleFileUpTo | ||
- PathRef ModuleFilePath, | ||
- const PrerequisiteModu | ||
-IntrusiveRefCnt | ||
- CompilerInstance | ||
- | ||
+bool IsModuleFileUpTo | ||
+ const PrerequisiteModu | ||
+ llvm::IntrusiveR | ||
auto HSOpts = std::make_shared | ||
RequisiteModules | ||
HSOpts->ForceChe | ||
HSOpts->Validate | ||
+ clang::clangd::I | ||
+ IntrusiveRefCntP | ||
+ CompilerInstance | ||
+ /*ShouldOwnClien | ||
+ | ||
+ LangOptions LangOpts; | ||
+ LangOpts.SkipODR | ||
+ | ||
+ FileManager FileMgr(FileSyst | ||
+ | ||
+ SourceManager SourceMgr(*Diags | ||
+ | ||
+ HeaderSearch HeaderInfo(HSOpt | ||
+ /*Target=*/nullp | ||
+ | ||
+ TrivialModuleLoa | ||
+ Preprocessor PP(std::make_sha | ||
+ SourceMgr, HeaderInfo, ModuleLoader); | ||
+ | ||
+ IntrusiveRefCntP | ||
PCHContainerOper | ||
- std::unique_ptr< | ||
- ModuleFilePath.s | ||
- Diags, FileSystemOption | ||
+ ASTReader Reader(PP, *ModuleCache, /*ASTContext=*/n | ||
+ PCHOperations.ge | ||
- if (!Unit) | ||
- return false; | ||
+ // We don't need any listener here. By default it will use a validator | ||
+ // listener. | ||
+ Reader.setListen | ||
- auto Reader = Unit->getASTRead | ||
- if (!Reader) | ||
+ if (Reader.ReadAST( | ||
+ SourceLocation() | ||
+ ASTReader::ARR_N | ||
return false; | ||
bool UpToDate = true; | ||
- Reader->getModul | ||
- Reader->visitInp | ||
+ Reader.getModule | ||
+ Reader.visitInpu | ||
MF, /*IncludeSystem= | ||
[&](const serialization::I | ||
if (!IF.getFile() || IF.isOutOfDate() | ||
UpToDate = false; | ||
}); | ||
- | ||
return !UpToDate; | ||
}); | ||
- | ||
return UpToDate; | ||
} | ||
bool IsModuleFilesUpT | ||
llvm::SmallVecto | ||
- const PrerequisiteModu | ||
- return llvm::all_of(Mod | ||
- return IsModuleFileUpTo | ||
- }); | ||
+ const PrerequisiteModu | ||
+ llvm::IntrusiveR | ||
+ return llvm::all_of( | ||
+ ModuleFilePaths, | ||
+ return IsModuleFileUpTo | ||
+ }); | ||
} | ||
// StandalonePrereq | ||
@@ -347,7 +366,7 @@ bool StandalonePrereq | ||
SmallVector<Stri | ||
for (auto &MF : RequiredModules) | ||
BMIPaths.push_ba | ||
- return IsModuleFilesUpT | ||
+ return IsModuleFilesUpT | ||
} | ||
} // namespace clangd |
@@ -280,6 +280,8 @@ public: | ||
llvm::StringRef Check; | ||
while (!Checks.empty() | ||
std::tie(Check, Checks) = Checks.split(',' | ||
+ Check = Check.trim(); | ||
+ | ||
if (Check.empty()) | ||
continue; | ||
@@ -913,7 +913,6 @@ PreamblePatch PreamblePatch::c | ||
PP.PatchedMarks = std::move(Modifi | ||
PP.PatchedMacros | ||
dlog("Created preamble patch: {0}", Patch.str()); | ||
- Patch.flush(); | ||
return PP; | ||
} | ||
@@ -504,6 +504,16 @@ bool fromJSON(const llvm::json::Valu | ||
P.field("offsetE | ||
return false; | ||
} | ||
+ | ||
+ if (auto *Experimental = O->getObject("ex | ||
+ if (auto *TextDocument = Experimental->ge | ||
+ if (auto *Completion = TextDocument->ge | ||
+ if (auto EditsNearCursor = Completion->getB | ||
+ R.CompletionFixe | ||
+ } | ||
+ } | ||
+ } | ||
+ | ||
return true; | ||
} | ||
@@ -1477,6 +1487,7 @@ llvm::json::Valu | ||
return 2; | ||
case InlayHintKind::D | ||
case InlayHintKind::B | ||
+ case InlayHintKind::D | ||
// This is an extension, don't serialize. | ||
return nullptr; | ||
} | ||
@@ -1517,6 +1528,8 @@ llvm::raw_ostrea | ||
return "designator"; | ||
case InlayHintKind::B | ||
return "block-end"; | ||
+ case InlayHintKind::D | ||
+ return "default-argumen | ||
} | ||
llvm_unreachable | ||
}; |
@@ -1681,6 +1681,15 @@ enum class InlayHintKind { | ||
/// This is a clangd extension. | ||
BlockEnd = 4, | ||
+ /// An inlay hint that is for a default argument. | ||
+ /// | ||
+ /// An example of a parameter hint for a default argument: | ||
+ /// void foo(bool A = true); | ||
+ /// foo(^); | ||
+ /// Adds an inlay hint "A: true". | ||
+ /// This is a clangd extension. | ||
+ DefaultArgument = 6, | ||
+ | ||
/// Other ideas for hints that are not currently implemented: | ||
/// | ||
/// * Chaining hints, showing the types of intermediate expressions |
@@ -554,7 +554,6 @@ std::string sortText(float Score, llvm::StringRef Name) { | ||
llvm::write_hex( | ||
/*Width=*/2 * sizeof(Score)); | ||
OS << Name; | ||
- OS.flush(); | ||
return S; | ||
} | ||
@@ -1120,6 +1120,7 @@ public: | ||
case TemplateName::Su | ||
case TemplateName::Su | ||
case TemplateName::Us | ||
+ case TemplateName::De | ||
// Names that could be resolved to a TemplateDecl are handled elsewhere. | ||
break; | ||
} |
@@ -11,9 +11,6 @@ | ||
#include "Protocol.h" | ||
#include "Selection.h" | ||
#include "SourceCode.h" | ||
-#include "clang-pseudo/Br | ||
-#include "clang-pseudo/Di | ||
-#include "clang-pseudo/To | ||
#include "clang/AST/DeclB | ||
#include "clang/Basic/Sou | ||
#include "clang/Basic/Sou | ||
@@ -25,6 +22,9 @@ | ||
#include "llvm/ADT/String | ||
#include "llvm/Support/Ca | ||
#include "llvm/Support/Er | ||
+#include "support/Bracket | ||
+#include "support/Directi | ||
+#include "support/Token.h | ||
#include <optional> | ||
#include <queue> | ||
#include <vector> | ||
@@ -181,16 +181,16 @@ llvm::Expected<s | ||
// Related issue: https://github.c | ||
llvm::Expected<s | ||
getFoldingRanges | ||
- auto OrigStream = pseudo::lex(Code | ||
+ auto OrigStream = lex(Code, genericLangOpts( | ||
- auto DirectiveStructu | ||
- pseudo::chooseCo | ||
+ auto DirectiveStructu | ||
+ chooseConditiona | ||
// FIXME: Provide ranges in the disabled-PP regions as well. | ||
auto Preprocessed = DirectiveStructu | ||
- auto ParseableStream = cook(Preprocesse | ||
- pseudo::pairBrac | ||
+ auto ParseableStream = cook(Preprocesse | ||
+ pairBrackets(Par | ||
std::vector<Fold | ||
auto AddFoldingRange = [&](Position Start, Position End, | ||
@@ -205,19 +205,19 @@ getFoldingRanges | ||
FR.kind = Kind.str(); | ||
Result.push_back | ||
}; | ||
- auto OriginalToken = [&](const pseudo::Token &T) { | ||
+ auto OriginalToken = [&](const Token &T) { | ||
return OrigStream.token | ||
}; | ||
- auto StartOffset = [&](const pseudo::Token &T) { | ||
+ auto StartOffset = [&](const Token &T) { | ||
return OriginalToken(T) | ||
}; | ||
- auto StartPosition = [&](const pseudo::Token &T) { | ||
+ auto StartPosition = [&](const Token &T) { | ||
return offsetToPosition | ||
}; | ||
- auto EndOffset = [&](const pseudo::Token &T) { | ||
+ auto EndOffset = [&](const Token &T) { | ||
return StartOffset(T) + OriginalToken(T) | ||
}; | ||
- auto EndPosition = [&](const pseudo::Token &T) { | ||
+ auto EndPosition = [&](const Token &T) { | ||
return offsetToPosition | ||
}; | ||
auto Tokens = ParseableStream. | ||
@@ -235,7 +235,7 @@ getFoldingRanges | ||
} | ||
} | ||
} | ||
- auto IsBlockComment = [&](const pseudo::Token &T) { | ||
+ auto IsBlockComment = [&](const Token &T) { | ||
assert(T.Kind == tok::comment); | ||
return OriginalToken(T) | ||
Code.substr(Star | ||
@@ -246,10 +246,10 @@ getFoldingRanges | ||
T++; | ||
continue; | ||
} | ||
- pseudo::Token *FirstComment = T; | ||
+ Token *FirstComment = T; | ||
// Show starting sentinals (// and /*) of the comment. | ||
Position Start = offsetToPosition | ||
- pseudo::Token *LastComment = T; | ||
+ Token *LastComment = T; | ||
Position End = EndPosition(*T); | ||
while (T != Tokens.end() && T->Kind == tok::comment && | ||
StartPosition(*T |
@@ -814,8 +814,8 @@ llvm::SmallVecto | ||
// Checks whether \p FileName is a valid spelling of main file. | ||
bool isMainFile(llvm: | ||
- auto FE = SM.getFileManage | ||
- return FE && *FE == SM.getFileEntryF | ||
+ auto FE = SM.getFileManage | ||
+ return FE && FE == SM.getFileEntryR | ||
} | ||
} // namespace |
@@ -483,7 +483,6 @@ std::string convertGlobToReg | ||
} | ||
} | ||
RegStream << '$'; | ||
- RegStream.flush( | ||
return RegText; | ||
} | ||
@@ -46,7 +46,7 @@ public: | ||
[this](std::opti | ||
Value.reset(); | ||
if (Data && !Data->empty()) { | ||
- tidy::DiagCallba | ||
+ auto Diagnostics = [](const llvm::SMDiagnost | ||
switch (D.getKind()) { | ||
case llvm::SourceMgr: | ||
elog("tidy-confi | ||
@@ -149,7 +149,7 @@ static void mergeCheckList(s | ||
*Checks = llvm::join_items | ||
} | ||
-TidyProviderRef | ||
+TidyProvider provideEnvironme | ||
static const std::optional<st | ||
std::optional<st | ||
#ifdef _WIN32 | ||
@@ -167,7 +167,7 @@ TidyProviderRef provideEnvironme | ||
return [](tidy::ClangTi | ||
} | ||
-TidyProviderRef | ||
+TidyProvider provideDefaultCh | ||
// These default checks are chosen for: | ||
// - low false-positive rate | ||
// - providing a lot of value | ||
@@ -251,7 +251,7 @@ TidyProvider disableUnusableC | ||
}; | ||
} | ||
-TidyProviderRef | ||
+TidyProvider provideClangdCon | ||
return [](tidy::ClangTi | ||
const auto &CurTidyConfig = Config::current( | ||
if (!CurTidyConfig. |
@@ -30,11 +30,11 @@ using TidyProviderRef = llvm::function_r | ||
TidyProvider combine(std::vec | ||
[diff truncated] |