llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT--> @llvm/pr-subscribers-pgo Author: Mircea Trofin (mtrofin) <details> <summary>Changes</summary> Collect profiles for functions we encounter when collecting a contextual profile, that are not associated with a call site. This is expected to happen for signal handlers, but it also - problematically - currently happens for mem{memset|copy|move|set}, which are currently inserted after profile instrumentation. Collecting a "regular" flat profile in these cases would hide the problem - that we loose better profile opportunities. --- Patch is 31.40 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/131417.diff 14 Files Affected: - (modified) compiler-rt/lib/ctx_profile/CtxInstrContextNode.h (+1) - (modified) compiler-rt/lib/ctx_profile/CtxInstrProfiling.cpp (+26-8) - (modified) compiler-rt/lib/ctx_profile/CtxInstrProfiling.h (+15) - (modified) compiler-rt/lib/ctx_profile/tests/CtxInstrProfilingTest.cpp (+1-1) - (modified) compiler-rt/test/ctx_profile/TestCases/generate-context.cpp (+14-1) - (modified) llvm/include/llvm/ProfileData/CtxInstrContextNode.h (+1) - (modified) llvm/include/llvm/ProfileData/PGOCtxProfReader.h (+17-9) - (modified) llvm/include/llvm/ProfileData/PGOCtxProfWriter.h (+4-2) - (modified) llvm/lib/ProfileData/PGOCtxProfReader.cpp (+48-17) - (modified) llvm/lib/ProfileData/PGOCtxProfWriter.cpp (+23-4) - (modified) llvm/lib/Transforms/Instrumentation/PGOInstrumentation.cpp (+13) - (added) llvm/test/tools/llvm-ctxprof-util/Inputs/valid-with-unhandled.yaml (+26) - (modified) llvm/test/tools/llvm-ctxprof-util/llvm-ctxprof-util.test (+15-6) - (modified) llvm/unittests/ProfileData/PGOCtxProfReaderWriterTest.cpp (+7-7) ``````````diff diff --git a/compiler-rt/lib/ctx_profile/CtxInstrContextNode.h b/compiler-rt/lib/ctx_profile/CtxInstrContextNode.h index 55962df57fb58..a176662b5cb3d 100644 --- a/compiler-rt/lib/ctx_profile/CtxInstrContextNode.h +++ b/compiler-rt/lib/ctx_profile/CtxInstrContextNode.h @@ -121,6 +121,7 @@ class ProfileWriter { public: virtual void startContextSection() = 0; virtual void writeContextual(const ctx_profile::ContextNode &RootNode, + const ctx_profile::ContextNode *Unhandled, uint64_t TotalRootEntryCount) = 0; virtual void endContextSection() = 0; diff --git a/compiler-rt/lib/ctx_profile/CtxInstrProfiling.cpp b/compiler-rt/lib/ctx_profile/CtxInstrProfiling.cpp index 6ef7076d93e31..26f22926a5704 100644 --- a/compiler-rt/lib/ctx_profile/CtxInstrProfiling.cpp +++ b/compiler-rt/lib/ctx_profile/CtxInstrProfiling.cpp @@ -246,22 +246,37 @@ ContextNode *getFlatProfile(FunctionData &Data, GUID Guid, ContextNode *getUnhandledContext(FunctionData &Data, GUID Guid, uint32_t NumCounters) { - // 1) if we are under a root (regardless if this thread is collecting or not a + + // 1) if we are currently collecting a contextual profile, fetch a ContextNode + // in the `Unhandled` set. We want to do this regardless of `ProfilingStarted` + // to (hopefully) offset the penalty of creating these contexts to before + // profiling. + // + // 2) if we are under a root (regardless if this thread is collecting or not a // contextual profile for that root), do not collect a flat profile. We want // to keep flat profiles only for activations that can't happen under a root, // to avoid confusing profiles. We can, for example, combine flattened and // flat profiles meaningfully, as we wouldn't double-count anything. // - // 2) to avoid lengthy startup, don't bother with flat profiles until the + // 3) to avoid lengthy startup, don't bother with flat profiles until the // profiling started. We would reset them anyway when profiling starts. // HOWEVER. This does lose profiling for message pumps: those functions are // entered once and never exit. They should be assumed to be entered before // profiling starts - because profiling should start after the server is up // and running (which is equivalent to "message pumps are set up"). - if (IsUnderContext || !__sanitizer::atomic_load_relaxed(&ProfilingStarted)) - return TheScratchContext; - return markAsScratch( - onContextEnter(*getFlatProfile(Data, Guid, NumCounters))); + ContextRoot *R = __llvm_ctx_profile_current_context_root; + if (!R) { + if (IsUnderContext || !__sanitizer::atomic_load_relaxed(&ProfilingStarted)) + return TheScratchContext; + else + return markAsScratch( + onContextEnter(*getFlatProfile(Data, Guid, NumCounters))); + } + auto It = R->Unhandled.insert({Guid, nullptr}); + if (It.second) + It.first->second = + getCallsiteSlow(Guid, &R->FirstUnhandledCalleeNode, NumCounters, 0); + return markAsScratch(onContextEnter(*It.first->second)); } ContextNode *__llvm_ctx_profile_get_context(FunctionData *Data, void *Callee, @@ -396,6 +411,8 @@ void __llvm_ctx_profile_start_collection() { ++NumMemUnits; resetContextNode(*Root->FirstNode); + if (Root->FirstUnhandledCalleeNode) + resetContextNode(*Root->FirstUnhandledCalleeNode); __sanitizer::atomic_store_relaxed(&Root->TotalEntries, 0); } __sanitizer::atomic_store_relaxed(&ProfilingStarted, true); @@ -416,8 +433,9 @@ bool __llvm_ctx_profile_fetch(ProfileWriter &Writer) { __sanitizer::Printf("[ctxprof] Contextual Profile is %s\n", "invalid"); return false; } - Writer.writeContextual(*Root->FirstNode, __sanitizer::atomic_load_relaxed( - &Root->TotalEntries)); + Writer.writeContextual( + *Root->FirstNode, Root->FirstUnhandledCalleeNode, + __sanitizer::atomic_load_relaxed(&Root->TotalEntries)); } Writer.endContextSection(); Writer.startFlatSection(); diff --git a/compiler-rt/lib/ctx_profile/CtxInstrProfiling.h b/compiler-rt/lib/ctx_profile/CtxInstrProfiling.h index cdbfa571247b7..5478a3b67ac25 100644 --- a/compiler-rt/lib/ctx_profile/CtxInstrProfiling.h +++ b/compiler-rt/lib/ctx_profile/CtxInstrProfiling.h @@ -10,6 +10,7 @@ #define CTX_PROFILE_CTXINSTRPROFILING_H_ #include "CtxInstrContextNode.h" +#include "sanitizer_common/sanitizer_dense_map.h" #include "sanitizer_common/sanitizer_mutex.h" #include <sanitizer/common_interface_defs.h> @@ -83,6 +84,20 @@ struct ContextRoot { // Count the number of entries - regardless if we could take the `Taken` mutex ::__sanitizer::atomic_uint64_t TotalEntries = {}; + // Profiles for functions we encounter when collecting a contexutal profile, + // that are not associated with a callsite. This is expected to happen for + // signal handlers, but it also - problematically - currently happens for + // mem{memset|copy|move|set}, which are currently inserted after profile + // instrumentation. + // `Unhandled` serves 2 purposes: + // 1. identifying such cases (like the memops) + // 2. collecting a profile for them, which can be at least used as a flat + // profile + ::__sanitizer::DenseMap<GUID, ContextNode *> Unhandled; + // Keep the unhandled contexts in a list, as we allocate them, as it makes it + // simpler to send to the writer when the profile is fetched. + ContextNode *FirstUnhandledCalleeNode = nullptr; + // Taken is used to ensure only one thread traverses the contextual graph - // either to read it or to write it. On server side, the same entrypoint will // be entered by numerous threads, but over time, the profile aggregated by diff --git a/compiler-rt/lib/ctx_profile/tests/CtxInstrProfilingTest.cpp b/compiler-rt/lib/ctx_profile/tests/CtxInstrProfilingTest.cpp index ccb8f0e87fcdd..83756fed0d6e6 100644 --- a/compiler-rt/lib/ctx_profile/tests/CtxInstrProfilingTest.cpp +++ b/compiler-rt/lib/ctx_profile/tests/CtxInstrProfilingTest.cpp @@ -240,7 +240,7 @@ TEST_F(ContextTest, Dump) { TestProfileWriter(ContextRoot *Root, size_t Entries) : Root(Root), Entries(Entries) {} - void writeContextual(const ContextNode &Node, + void writeContextual(const ContextNode &Node, const ContextNode *Unhandled, uint64_t TotalRootEntryCount) override { EXPECT_EQ(TotalRootEntryCount, Entries); EXPECT_EQ(EnteredSectionCount, 1); diff --git a/compiler-rt/test/ctx_profile/TestCases/generate-context.cpp b/compiler-rt/test/ctx_profile/TestCases/generate-context.cpp index 319f17debe48f..3dc53637a35d8 100644 --- a/compiler-rt/test/ctx_profile/TestCases/generate-context.cpp +++ b/compiler-rt/test/ctx_profile/TestCases/generate-context.cpp @@ -5,7 +5,8 @@ // RUN: cp %llvm_src/include/llvm/ProfileData/CtxInstrContextNode.h %t_include/ // // Compile with ctx instrumentation "on". We treat "theRoot" as callgraph root. -// RUN: %clangxx %s %ctxprofilelib -I%t_include -O2 -o %t.bin -mllvm -profile-context-root=theRoot +// RUN: %clangxx %s %ctxprofilelib -I%t_include -O2 -o %t.bin -mllvm -profile-context-root=theRoot \ +// RUN: -mllvm -ctx-prof-skip-callsite-instr=skip_me // // Run the binary, and observe the profile fetch handler's output. // RUN: %t.bin | FileCheck %s @@ -20,11 +21,14 @@ extern "C" bool __llvm_ctx_profile_fetch(ProfileWriter &); // avoid name mangling extern "C" { +__attribute__((noinline)) void skip_me() {} + __attribute__((noinline)) void someFunction(int I) { if (I % 2) printf("check odd\n"); else printf("check even\n"); + skip_me(); } // block inlining because the pre-inliner otherwise will inline this - it's @@ -36,6 +40,7 @@ __attribute__((noinline)) void theRoot() { for (auto I = 0; I < 2; ++I) { someFunction(I); } + skip_me(); } __attribute__((noinline)) void flatFct() { @@ -85,9 +90,13 @@ class TestProfileWriter : public ProfileWriter { } void writeContextual(const ContextNode &RootNode, + const ContextNode *Unhandled, uint64_t EntryCount) override { std::cout << "Entering Root " << RootNode.guid() << " with total entry count " << EntryCount << std::endl; + for (const auto *P = Unhandled; P; P = P->next()) + std::cout << "Unhandled GUID: " << P->guid() << " entered " + << P->entrycount() << " times" << std::endl; printProfile(RootNode, "", ""); } @@ -119,6 +128,8 @@ class TestProfileWriter : public ProfileWriter { // branches would be taken once, so the second counter is 1. // CHECK-NEXT: Entered Context Section // CHECK-NEXT: Entering Root 8657661246551306189 with total entry count 1 +// skip_me is entered 4 times: 3 via `someFunction`, and once from `theRoot` +// CHECK-NEXT: Unhandled GUID: 17928815489886282963 entered 4 times // CHECK-NEXT: Guid: 8657661246551306189 // CHECK-NEXT: Entries: 1 // CHECK-NEXT: 2 counters and 3 callsites @@ -135,6 +146,8 @@ class TestProfileWriter : public ProfileWriter { // CHECK-NEXT: Counter values: 2 1 // CHECK-NEXT: Exited Context Section // CHECK-NEXT: Entered Flat Section +// This is `skip_me`. Entered 3 times via `someFunction` +// CHECK-NEXT: Flat: 17928815489886282963 3 // CHECK-NEXT: Flat: 6759619411192316602 3,1 // This is flatFct (guid: 14569438697463215220) // CHECK-NEXT: Flat: 14569438697463215220 1,2 diff --git a/llvm/include/llvm/ProfileData/CtxInstrContextNode.h b/llvm/include/llvm/ProfileData/CtxInstrContextNode.h index 55962df57fb58..a176662b5cb3d 100644 --- a/llvm/include/llvm/ProfileData/CtxInstrContextNode.h +++ b/llvm/include/llvm/ProfileData/CtxInstrContextNode.h @@ -121,6 +121,7 @@ class ProfileWriter { public: virtual void startContextSection() = 0; virtual void writeContextual(const ctx_profile::ContextNode &RootNode, + const ctx_profile::ContextNode *Unhandled, uint64_t TotalRootEntryCount) = 0; virtual void endContextSection() = 0; diff --git a/llvm/include/llvm/ProfileData/PGOCtxProfReader.h b/llvm/include/llvm/ProfileData/PGOCtxProfReader.h index 65be54323998b..37c2f3d856c73 100644 --- a/llvm/include/llvm/ProfileData/PGOCtxProfReader.h +++ b/llvm/include/llvm/ProfileData/PGOCtxProfReader.h @@ -74,6 +74,12 @@ class IndexNode { }; } // namespace internal +// Setting initial capacity to 1 because all contexts must have at least 1 +// counter, and then, because all contexts belonging to a function have the same +// size, there'll be at most one other heap allocation. +using CtxProfFlatProfile = + std::map<GlobalValue::GUID, SmallVector<uint64_t, 1>>; + /// A node (context) in the loaded contextual profile, suitable for mutation /// during IPO passes. We generally expect a fraction of counters and /// callsites to be populated. We continue to model counters as vectors, but @@ -93,11 +99,16 @@ class PGOCtxProfContext final : public internal::IndexNode { GlobalValue::GUID GUID = 0; SmallVector<uint64_t, 16> Counters; const std::optional<uint64_t> RootEntryCount; + std::optional<CtxProfFlatProfile> Unhandled{}; CallsiteMapTy Callsites; - PGOCtxProfContext(GlobalValue::GUID G, SmallVectorImpl<uint64_t> &&Counters, - std::optional<uint64_t> RootEntryCount = std::nullopt) - : GUID(G), Counters(std::move(Counters)), RootEntryCount(RootEntryCount) { + PGOCtxProfContext( + GlobalValue::GUID G, SmallVectorImpl<uint64_t> &&Counters, + std::optional<uint64_t> RootEntryCount = std::nullopt, + std::optional<CtxProfFlatProfile> &&Unhandled = std::nullopt) + : GUID(G), Counters(std::move(Counters)), RootEntryCount(RootEntryCount), + Unhandled(std::move(Unhandled)) { + assert(RootEntryCount.has_value() == Unhandled.has_value()); } Expected<PGOCtxProfContext &> @@ -121,6 +132,8 @@ class PGOCtxProfContext final : public internal::IndexNode { bool isRoot() const { return RootEntryCount.has_value(); } uint64_t getTotalRootEntryCount() const { return RootEntryCount.value(); } + const CtxProfFlatProfile &getUnhandled() const { return Unhandled.value(); } + uint64_t getEntrycount() const { assert(!Counters.empty() && "Functions are expected to have at their entry BB instrumented, so " @@ -170,12 +183,6 @@ class PGOCtxProfContext final : public internal::IndexNode { } }; -// Setting initial capacity to 1 because all contexts must have at least 1 -// counter, and then, because all contexts belonging to a function have the same -// size, there'll be at most one other heap allocation. -using CtxProfFlatProfile = - std::map<GlobalValue::GUID, SmallVector<uint64_t, 1>>; - using CtxProfContextualProfiles = std::map<GlobalValue::GUID, PGOCtxProfContext>; struct PGOCtxProfile { @@ -205,6 +212,7 @@ class PGOCtxProfileReader final { Error loadContexts(CtxProfContextualProfiles &P); Error loadFlatProfiles(CtxProfFlatProfile &P); + Error loadFlatProfileList(CtxProfFlatProfile &P); public: PGOCtxProfileReader(StringRef Buffer) diff --git a/llvm/include/llvm/ProfileData/PGOCtxProfWriter.h b/llvm/include/llvm/ProfileData/PGOCtxProfWriter.h index b2bb8fea10cfe..36ad5622a8544 100644 --- a/llvm/include/llvm/ProfileData/PGOCtxProfWriter.h +++ b/llvm/include/llvm/ProfileData/PGOCtxProfWriter.h @@ -36,7 +36,8 @@ enum PGOCtxProfileBlockIDs { ContextNodeBlockID = ContextRootBlockID + 1, FlatProfilesSectionBlockID = ContextNodeBlockID + 1, FlatProfileBlockID = FlatProfilesSectionBlockID + 1, - LAST_VALID = FlatProfileBlockID + UnhandledBlockID = FlatProfileBlockID + 1, + LAST_VALID = UnhandledBlockID }; /// Write one or more ContextNodes to the provided raw_fd_stream. @@ -94,6 +95,7 @@ class PGOCtxProfileWriter final : public ctx_profile::ProfileWriter { void startContextSection() override; void writeContextual(const ctx_profile::ContextNode &RootNode, + const ctx_profile::ContextNode *Unhandled, uint64_t TotalRootEntryCount) override; void endContextSection() override; @@ -104,7 +106,7 @@ class PGOCtxProfileWriter final : public ctx_profile::ProfileWriter { // constants used in writing which a reader may find useful. static constexpr unsigned CodeLen = 2; - static constexpr uint32_t CurrentVersion = 3; + static constexpr uint32_t CurrentVersion = 4; static constexpr unsigned VBREncodingBits = 6; static constexpr StringRef ContainerMagic = "CTXP"; }; diff --git a/llvm/lib/ProfileData/PGOCtxProfReader.cpp b/llvm/lib/ProfileData/PGOCtxProfReader.cpp index f53f2956a7b7e..ec801d43c8588 100644 --- a/llvm/lib/ProfileData/PGOCtxProfReader.cpp +++ b/llvm/lib/ProfileData/PGOCtxProfReader.cpp @@ -97,7 +97,7 @@ PGOCtxProfileReader::readProfile(PGOCtxProfileBlockIDs Kind) { std::optional<SmallVector<uint64_t, 16>> Counters; std::optional<uint32_t> CallsiteIndex; std::optional<uint64_t> TotalEntryCount; - + std::optional<CtxProfFlatProfile> Unhandled; SmallVector<uint64_t, 1> RecordValues; const bool ExpectIndex = Kind == PGOCtxProfileBlockIDs::ContextNodeBlockID; @@ -108,14 +108,24 @@ PGOCtxProfileReader::readProfile(PGOCtxProfileBlockIDs Kind) { auto GotAllWeNeed = [&]() { return Guid.has_value() && Counters.has_value() && (!ExpectIndex || CallsiteIndex.has_value()) && - (!IsRoot || TotalEntryCount.has_value()); + (!IsRoot || TotalEntryCount.has_value()) && + (!IsRoot || Unhandled.has_value()); }; + while (!GotAllWeNeed()) { RecordValues.clear(); EXPECT_OR_RET(Entry, advance()); - if (Entry->Kind != BitstreamEntry::Record) + if (Entry->Kind != BitstreamEntry::Record) { + if (IsRoot && Entry->Kind == BitstreamEntry::SubBlock && + Entry->ID == PGOCtxProfileBlockIDs::UnhandledBlockID) { + RET_ON_ERR(enterBlockWithID(PGOCtxProfileBlockIDs::UnhandledBlockID)); + Unhandled = CtxProfFlatProfile(); + RET_ON_ERR(loadFlatProfileList(*Unhandled)); + continue; + } return wrongValue( "Expected records before encountering more subcontexts"); + } EXPECT_OR_RET(ReadRecord, Cursor.readRecord(bitc::UNABBREV_RECORD, RecordValues)); switch (*ReadRecord) { @@ -152,7 +162,8 @@ PGOCtxProfileReader::readProfile(PGOCtxProfileBlockIDs Kind) { } } - PGOCtxProfContext Ret(*Guid, std::move(*Counters), TotalEntryCount); + PGOCtxProfContext Ret(*Guid, std::move(*Counters), TotalEntryCount, + std::move(Unhandled)); while (canEnterBlockWithID(PGOCtxProfileBlockIDs::ContextNodeBlockID)) { EXPECT_OR_RET(SC, readProfile(PGOCtxProfileBlockIDs::ContextNodeBlockID)); @@ -212,9 +223,7 @@ Error PGOCtxProfileReader::loadContexts(CtxProfContextualProfiles &P) { return Error::success(); } -Error PGOCtxProfileReader::loadFlatProfiles(CtxProfFlatProfile &P) { - RET_ON_ERR( - enterBlockWithID(PGOCtxProfileBlockIDs::FlatProfilesSectionBlockID)); +Error PGOCtxProfileReader::loadFlatProfileList(CtxProfFlatProfile &P) { while (canEnterBlockWithID(PGOCtxProfileBlockIDs::FlatProfileBlockID)) { EXPECT_OR_RET(E, readProfile(PGOCtxProfileBlockIDs::FlatProfileBlockID)); auto Guid = E->second.guid(); @@ -224,6 +233,12 @@ Error PGOCtxProfileReader::loadFlatProfiles(CtxProfFlatProfile &P) { return Error::success(); } +Error PGOCtxProfileReader::loadFlatProfiles(CtxProfFlatProfile &P) { + RET_ON_ERR( + enterBlockWithID(PGOCtxProfileBlockIDs::FlatProfilesSectionBlockID)); + return loadFlatProfileList(P); +} + Expected<PGOCtxProfile> PGOCtxProfileReader::loadProfiles() { RET_ON_ERR(readMetadata()); PGOCtxProfile Ret; @@ -287,10 +302,13 @@ void toYaml(yaml::Output &Out, Out.endSequence(); } +void toYaml(yaml::Output &Out, const CtxProfFlatProfile &Flat); + void toYaml(yaml::Output &Out, GlobalValue::GUID Guid, const SmallVectorImpl<uint64_t> &Counters, const PGOCtxProfContext::CallsiteMapTy &Callsites, - std::optional<uint64_t> TotalRootEntryCount = std::nullopt) { + std::optional<uint64_t> TotalRootEntryCount = std::nullopt, + CtxProfFlatProfile Unhandled = {}) { yaml::EmptyContext Empty; Out.beginMapping(); void *SaveInfo = nullptr; @@ -318,6 +336,14 @@ void toYaml(yaml::Output &Out, GlobalValue::GUID Guid, Out.endFlowSequence(); Out.postflightKey(nullptr); } + + if (!Unhandled.empty()) { + assert(TotalRootEntryCount.has_value()); + Out.preflightKey("Unhandled", false, false, UseDefault, SaveInfo); + toYaml(Out, Unhandled); + Out.postflightKey(nullptr); + } + if (!Callsites.empty()) { Out.preflightKey("Callsites", true, false, UseDefault, SaveInfo); toYaml(Out, Callsites); @@ -326,10 +352,22 @@ void toYaml(yaml::Output &Out, GlobalValue::GUID Guid, Out.endMapping(); } +void toYaml(yaml::Output &Out, const CtxProfFlatProfile &Flat) { + void *SaveInfo = nullptr; + Out.beginSequence(); + size_t ElemID = 0; + for (const auto &[Guid, Counters] : Flat) { + Out.preflightElement(ElemID++, SaveInfo); + toYaml(Out, Guid, Counters, {}); + Out.postflightElement(nullptr); + } + Out.endSequence(); +} + void toYaml(yaml::Output &Out, const PGOCtxProfContext &Ctx) { if (Ctx.isRoot()) toYaml(Out, Ctx.guid(), Ctx.counters(), Ctx.callsites(), - Ctx.getTotalRootEntryCount()); + Ctx.getTotalRootEntryCount(), Ctx.getUnhandled()); else toYaml(Out, Ctx.guid(), Ctx.counters(), Ctx.callsites()); } @@ -348,14 +386,7 @@ void llvm::convertCtxProfToYaml(raw_ostream &OS, const PGOCtxProfile &Profile) { } if (!Profile.FlatProfiles.empty()) { Out.preflightKey("FlatProfiles", false, false, UseDefault, SaveInfo); - Out.beginSequence(); - size_t ElemID = 0; - for (const auto &[Guid, Counters] : Profile.FlatProfiles) { - Out.preflightElement(ElemID++, SaveInfo); - toYaml(Out, Guid, Counters, {}); - Out.postflightElement(nullptr); - } - Out.endSequence(); + toYaml(Out, Profile.FlatProfiles); Out.postflightKey(nullptr); } Out.endMapping(); diff --git a/llvm/lib/ProfileData/PGOCtxProfWriter.cpp b/llvm/lib/ProfileData/PGOCtxProfWriter.cpp index 910842647591d..fe41438b7d0a7 100644 --- a/llvm/li... [truncated] `````````` </details> https://github.com/llvm/llvm-project/pull/131417 _______________________________________________ llvm-branch-commits mailing list llvm-branch-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-branch-commits