llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT--> @llvm/pr-subscribers-llvm-ir @llvm/pr-subscribers-llvm-transforms Author: KRM7 <details> <summary>Changes</summary> Enable speculative single-target devirtualization with LTO (disabled by default, can be enabled using -fdevirtualize-speculatively). The DevirtSpeculatively member variable is also removed from WPD, so that the optimization is controlled entirely by the -devirtualize-speculatively flag. The -enable-devirtualize-speculatively flag is now only used to add the WPD pass to the non-LTO pipeline. --- Patch is 47.34 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/233380.diff 20 Files Affected: - (modified) clang/docs/UsersManual.md (-2) - (modified) clang/lib/Driver/ToolChains/CommonArgs.cpp (+8) - (modified) clang/test/Driver/lto.c (+8) - (modified) llvm/include/llvm/AsmParser/LLToken.h (+1) - (modified) llvm/include/llvm/IR/ModuleSummaryIndex.h (+1) - (modified) llvm/include/llvm/IR/ModuleSummaryIndexYAML.h (+2) - (modified) llvm/include/llvm/Transforms/IPO/WholeProgramDevirt.h (+2-5) - (modified) llvm/lib/AsmParser/LLLexer.cpp (+1) - (modified) llvm/lib/AsmParser/LLParser.cpp (+3) - (modified) llvm/lib/IR/AsmWriter.cpp (+4-1) - (modified) llvm/lib/Passes/PassBuilderPipelines.cpp (+1-2) - (modified) llvm/lib/Passes/PassesOptions.td (+1-1) - (modified) llvm/lib/Transforms/IPO/WholeProgramDevirt.cpp (+74-69) - (modified) llvm/test/Assembler/thinlto-summary.ll (+2-2) - (added) llvm/test/ThinLTO/X86/devirt_speculative.ll (+71) - (added) llvm/test/ThinLTO/X86/devirt_speculative_mixed_vis.ll (+105) - (added) llvm/test/Transforms/WholeProgramDevirt/devirt-single-impl-check-cfi.ll (+68) - (added) llvm/test/Transforms/WholeProgramDevirt/speculative-devirt-cfi.ll (+68) - (added) llvm/test/Transforms/WholeProgramDevirt/speculative-devirt-mixed-vis.ll (+77) - (modified) llvm/test/Transforms/WholeProgramDevirt/speculative-devirt-single-impl.ll (+8-6) ``````````diff diff --git a/clang/docs/UsersManual.md b/clang/docs/UsersManual.md index 96d1c4e009c71a..2c2c131b656e03 100644 --- a/clang/docs/UsersManual.md +++ b/clang/docs/UsersManual.md @@ -2589,8 +2589,6 @@ if.end.icp: ; preds = %if.false.orig_indir ret void ``` -This feature is temporarily ignored at the LLVM side when LTO is enabled. -TODO: Update the comment when the LLVM side supports this feature for LTO. This feature is turned off by default. ::: diff --git a/clang/lib/Driver/ToolChains/CommonArgs.cpp b/clang/lib/Driver/ToolChains/CommonArgs.cpp index 7c832ef405fa51..329f5317ea47a9 100644 --- a/clang/lib/Driver/ToolChains/CommonArgs.cpp +++ b/clang/lib/Driver/ToolChains/CommonArgs.cpp @@ -1222,6 +1222,14 @@ void tools::addLTOOptions(const ToolChain &ToolChain, const ArgList &Args, (A->getOption().matches(options::OPT_fglobal_isel) ? "1" : "0"))); } + if (Arg *A = Args.getLastArg(options::OPT_fdevirtualize_speculatively, + options::OPT_fno_devirtualize_speculatively)) + CmdArgs.push_back(Args.MakeArgString( + Twine(PluginOptPrefix) + "-devirtualize-speculatively=" + + (A->getOption().matches(options::OPT_fdevirtualize_speculatively) + ? "1" + : "0"))); + // If an explicit debugger tuning argument appeared, pass it along. if (Arg *A = Args.getLastArg(options::OPT_gTune_Group, options::OPT_ggdbN_Group)) { diff --git a/clang/test/Driver/lto.c b/clang/test/Driver/lto.c index 838eb740863752..be18231f5d335c 100644 --- a/clang/test/Driver/lto.c +++ b/clang/test/Driver/lto.c @@ -127,6 +127,14 @@ // CHECK-NO-SLP: "-plugin-opt=-vectorize-slp=0" // CHECK-SLP: "-plugin-opt=-vectorize-slp=1" +// RUN: %clang --target=x86_64-unknown-linux -### %s -flto -fno-devirtualize-speculatively 2> %t +// RUN: FileCheck --check-prefix=CHECK-NO-DEVIRTUALIZE-SPECULATIVELY < %t %s +// RUN: %clang --target=x86_64-unknown-linux -### %s -flto -fdevirtualize-speculatively 2> %t +// RUN: FileCheck --check-prefix=CHECK-DEVIRTUALIZE-SPECULATIVELY < %t %s +// +// CHECK-NO-DEVIRTUALIZE-SPECULATIVELY: "-plugin-opt=-devirtualize-speculatively=0" +// CHECK-DEVIRTUALIZE-SPECULATIVELY: "-plugin-opt=-devirtualize-speculatively=1" + // -flto passes -time-passes when -ftime-report is passed // RUN: %clang --target=x86_64-unknown-linux-gnu -### %s -flto -ftime-report 2> %t // RUN: FileCheck --check-prefix=CHECK-TIME-REPORT < %t %s diff --git a/llvm/include/llvm/AsmParser/LLToken.h b/llvm/include/llvm/AsmParser/LLToken.h index 2679a630c2dd95..e5816ca301527e 100644 --- a/llvm/include/llvm/AsmParser/LLToken.h +++ b/llvm/include/llvm/AsmParser/LLToken.h @@ -467,6 +467,7 @@ enum Kind { kw_indir, kw_singleImpl, kw_branchFunnel, + kw_speculative, kw_singleImplName, kw_resByArg, kw_byArg, diff --git a/llvm/include/llvm/IR/ModuleSummaryIndex.h b/llvm/include/llvm/IR/ModuleSummaryIndex.h index 4f68582b65c4eb..f9d6640f50852a 100644 --- a/llvm/include/llvm/IR/ModuleSummaryIndex.h +++ b/llvm/include/llvm/IR/ModuleSummaryIndex.h @@ -1349,6 +1349,7 @@ struct WholeProgramDevirtResolution { BranchFunnel, ///< When retpoline mitigation is enabled, use a branch funnel ///< that is defined in the merged module. Otherwise same as ///< Indir. + Speculative, ///< Speculative devirtualization } TheKind = Indir; std::string SingleImplName; diff --git a/llvm/include/llvm/IR/ModuleSummaryIndexYAML.h b/llvm/include/llvm/IR/ModuleSummaryIndexYAML.h index 158c03e77306c9..441433eb381adc 100644 --- a/llvm/include/llvm/IR/ModuleSummaryIndexYAML.h +++ b/llvm/include/llvm/IR/ModuleSummaryIndexYAML.h @@ -102,6 +102,8 @@ template <> struct ScalarEnumerationTraits<WholeProgramDevirtResolution::Kind> { io.enumCase(value, "SingleImpl", WholeProgramDevirtResolution::SingleImpl); io.enumCase(value, "BranchFunnel", WholeProgramDevirtResolution::BranchFunnel); + io.enumCase(value, "Speculative", + WholeProgramDevirtResolution::Speculative); } }; diff --git a/llvm/include/llvm/Transforms/IPO/WholeProgramDevirt.h b/llvm/include/llvm/Transforms/IPO/WholeProgramDevirt.h index c3a394ab4fb720..0dd61bbd41a4ab 100644 --- a/llvm/include/llvm/Transforms/IPO/WholeProgramDevirt.h +++ b/llvm/include/llvm/Transforms/IPO/WholeProgramDevirt.h @@ -227,14 +227,11 @@ struct WholeProgramDevirtPass ModuleSummaryIndex *ExportSummary; const ModuleSummaryIndex *ImportSummary; bool UseCommandLine = false; - bool DevirtSpeculatively = false; WholeProgramDevirtPass() : ExportSummary(nullptr), ImportSummary(nullptr), UseCommandLine(true) {} WholeProgramDevirtPass(ModuleSummaryIndex *ExportSummary, - const ModuleSummaryIndex *ImportSummary, - bool DevirtSpeculatively = false) - : ExportSummary(ExportSummary), ImportSummary(ImportSummary), - DevirtSpeculatively(DevirtSpeculatively) { + const ModuleSummaryIndex *ImportSummary) + : ExportSummary(ExportSummary), ImportSummary(ImportSummary) { assert(!(ExportSummary && ImportSummary)); } LLVM_ABI PreservedAnalyses run(Module &M, ModuleAnalysisManager &); diff --git a/llvm/lib/AsmParser/LLLexer.cpp b/llvm/lib/AsmParser/LLLexer.cpp index 5e52dc8dcb2478..20212fc26451aa 100644 --- a/llvm/lib/AsmParser/LLLexer.cpp +++ b/llvm/lib/AsmParser/LLLexer.cpp @@ -884,6 +884,7 @@ lltok::Kind LLLexer::LexIdentifier() { KEYWORD(indir); KEYWORD(singleImpl); KEYWORD(branchFunnel); + KEYWORD(speculative); KEYWORD(singleImplName); KEYWORD(resByArg); KEYWORD(byArg); diff --git a/llvm/lib/AsmParser/LLParser.cpp b/llvm/lib/AsmParser/LLParser.cpp index 60b7824342aa38..03a266931632cf 100644 --- a/llvm/lib/AsmParser/LLParser.cpp +++ b/llvm/lib/AsmParser/LLParser.cpp @@ -10047,6 +10047,9 @@ bool LLParser::parseWpdRes(WholeProgramDevirtResolution &WPDRes) { case lltok::kw_branchFunnel: WPDRes.TheKind = WholeProgramDevirtResolution::BranchFunnel; break; + case lltok::kw_speculative: + WPDRes.TheKind = WholeProgramDevirtResolution::Speculative; + break; default: return error(Lex.getLoc(), "unexpected WholeProgramDevirtResolution kind"); } diff --git a/llvm/lib/IR/AsmWriter.cpp b/llvm/lib/IR/AsmWriter.cpp index 1cbcfb195764b3..a30ff85e33160b 100644 --- a/llvm/lib/IR/AsmWriter.cpp +++ b/llvm/lib/IR/AsmWriter.cpp @@ -3402,6 +3402,8 @@ getWholeProgDevirtResKindName(WholeProgramDevirtResolution::Kind K) { return "singleImpl"; case WholeProgramDevirtResolution::BranchFunnel: return "branchFunnel"; + case WholeProgramDevirtResolution::Speculative: + return "speculative"; } llvm_unreachable("invalid WholeProgramDevirtResolution kind"); } @@ -3496,7 +3498,8 @@ void AssemblyWriter::printWPDRes(const WholeProgramDevirtResolution &WPDRes) { Out << "wpdRes: (kind: "; Out << getWholeProgDevirtResKindName(WPDRes.TheKind); - if (WPDRes.TheKind == WholeProgramDevirtResolution::SingleImpl) + if (WPDRes.TheKind == WholeProgramDevirtResolution::SingleImpl || + WPDRes.TheKind == WholeProgramDevirtResolution::Speculative) Out << ", singleImplName: \"" << WPDRes.SingleImplName << "\""; if (!WPDRes.ResByArg.empty()) { diff --git a/llvm/lib/Passes/PassBuilderPipelines.cpp b/llvm/lib/Passes/PassBuilderPipelines.cpp index 125c8e442f27b6..b4ce758e314e94 100644 --- a/llvm/lib/Passes/PassBuilderPipelines.cpp +++ b/llvm/lib/Passes/PassBuilderPipelines.cpp @@ -1620,8 +1620,7 @@ PassBuilder::buildModuleOptimizationPipeline(OptimizationLevel Level, MPM.addPass(AssignGUIDPass()); MPM.addPass(WholeProgramDevirtPass( /*ExportSummary*/ nullptr, - /*ImportSummary*/ nullptr, - /*DevirtSpeculatively*/ PTO.DevirtualizeSpeculatively)); + /*ImportSummary*/ nullptr)); MPM.addPass(DropTypeTestsPass()); // Given that the devirtualization creates more opportunities for inlining, // we run the Inliner again here to maximize the optimization gain we diff --git a/llvm/lib/Passes/PassesOptions.td b/llvm/lib/Passes/PassesOptions.td index 4398d5c06bff2b..d37930c7ca39d8 100644 --- a/llvm/lib/Passes/PassesOptions.td +++ b/llvm/lib/Passes/PassesOptions.td @@ -38,7 +38,7 @@ defm : BoolField<"enable-constraint-elimination", "true", // TODO: There is a similar flag in WPD pass, we should consolidate them by // parsing the option only once in PassBuilder and share it across both places. defm : BoolField<"enable-devirtualize-speculatively", "false", - "Enable speculative devirtualization optimization">; + "Enable speculative devirtualization without LTO">; defm : BoolField<"enable-dfa-jump-thread", "true", "Enable DFA jump threading">; defm : BoolField<"enable-global-analyses", "true", diff --git a/llvm/lib/Transforms/IPO/WholeProgramDevirt.cpp b/llvm/lib/Transforms/IPO/WholeProgramDevirt.cpp index 88c57d2195bbda..ba0718882d77e0 100644 --- a/llvm/lib/Transforms/IPO/WholeProgramDevirt.cpp +++ b/llvm/lib/Transforms/IPO/WholeProgramDevirt.cpp @@ -160,8 +160,6 @@ static cl::opt<std::string> ClWriteSummary( "bitcode, otherwise YAML"), cl::Hidden); -// TODO: This option eventually should support any public visibility vtables -// with/out LTO. static cl::opt<bool> ClDevirtualizeSpeculatively( "devirtualize-speculatively", cl::desc("Enable speculative devirtualization optimization"), @@ -611,11 +609,9 @@ struct DevirtModule { std::map<CallInst *, unsigned> NumUnsafeUsesForTypeTest; PatternList FunctionsToSkip; - const bool DevirtSpeculatively; DevirtModule(Module &M, ModuleAnalysisManager &MAM, ModuleSummaryIndex *ExportSummary, - const ModuleSummaryIndex *ImportSummary, - bool DevirtSpeculatively) + const ModuleSummaryIndex *ImportSummary) : M(M), MAM(MAM), FAM(MAM.getResult<FunctionAnalysisManagerModuleProxy>(M).getManager()), ExportSummary(ExportSummary), ImportSummary(ImportSummary), @@ -628,8 +624,7 @@ struct DevirtModule { RemarksEnabled(areRemarksEnabled()), OREGetter([&](Function &F) -> OptimizationRemarkEmitter & { return FAM.getResult<OptimizationRemarkEmitterAnalysis>(F); - }), - DevirtSpeculatively(DevirtSpeculatively) { + }) { assert(!(ExportSummary && ImportSummary)); FunctionsToSkip.init(SkipFunctionNames); } @@ -645,18 +640,18 @@ struct DevirtModule { std::vector<VTableBits> &Bits, DenseMap<Metadata *, std::set<TypeMemberInfo>> &TypeIdMap); - bool - tryFindVirtualCallTargets(std::vector<VirtualCallTarget> &TargetsForSlot, - const std::set<TypeMemberInfo> &TypeMemberInfos, - uint64_t ByteOffset, - ModuleSummaryIndex *ExportSummary); + bool tryFindVirtualCallTargets( + std::vector<VirtualCallTarget> &TargetsForSlot, + const std::set<TypeMemberInfo> &TypeMemberInfos, uint64_t ByteOffset, + ModuleSummaryIndex *ExportSummary, bool &HasPublicTarget); void applySingleImplDevirt(VTableSlotInfo &SlotInfo, Constant *TheFn, - bool &IsExported); + bool HasPublicTarget, bool &IsExported); bool trySingleImplDevirt(ModuleSummaryIndex *ExportSummary, MutableArrayRef<VirtualCallTarget> TargetsForSlot, VTableSlotInfo &SlotInfo, - WholeProgramDevirtResolution *Res); + WholeProgramDevirtResolution *Res, + bool HasPublicTarget); void applyICallBranchFunnel(VTableSlotInfo &SlotInfo, Function &JT, bool &IsExported); @@ -743,8 +738,7 @@ struct DevirtModule { // Lower the module using the action and summary passed as command line // arguments. For testing purposes only. - static bool runForTesting(Module &M, ModuleAnalysisManager &MAM, - bool DevirtSpeculatively); + static bool runForTesting(Module &M, ModuleAnalysisManager &MAM); }; struct DevirtIndex { @@ -779,13 +773,14 @@ struct DevirtIndex { bool tryFindVirtualCallTargets(std::vector<ValueInfo> &TargetsForSlot, const TypeIdCompatibleVtableInfo TIdInfo, - uint64_t ByteOffset); + uint64_t ByteOffset, bool &HasPublicTarget); bool trySingleImplDevirt(MutableArrayRef<ValueInfo> TargetsForSlot, VTableSlotSummary &SlotSummary, VTableSlotInfo &SlotInfo, WholeProgramDevirtResolution *Res, - std::set<ValueInfo> &DevirtTargets); + std::set<ValueInfo> &DevirtTargets, + bool HasPublicTarget); void run(); }; @@ -794,22 +789,17 @@ struct DevirtIndex { PreservedAnalyses WholeProgramDevirtPass::run(Module &M, ModuleAnalysisManager &MAM) { if (UseCommandLine) { - if (!DevirtModule::runForTesting(M, MAM, ClDevirtualizeSpeculatively)) + if (!DevirtModule::runForTesting(M, MAM)) return PreservedAnalyses::all(); return PreservedAnalyses::none(); } std::optional<ModuleSummaryIndex> Index; - if (!ExportSummary && !ImportSummary && DevirtSpeculatively) { - // Build the ExportSummary from the module. - assert(!ExportSummary && - "ExportSummary is expected to be empty in non-LTO mode"); + if (!ExportSummary && !ImportSummary && ClDevirtualizeSpeculatively) { ProfileSummaryInfo PSI(M); - Index.emplace(buildModuleSummaryIndex(M, nullptr, &PSI)); - ExportSummary = Index.has_value() ? &Index.value() : nullptr; + ExportSummary = &Index.emplace(buildModuleSummaryIndex(M, nullptr, &PSI)); } - if (!DevirtModule(M, MAM, ExportSummary, ImportSummary, DevirtSpeculatively) - .run()) + if (!DevirtModule(M, MAM, ExportSummary, ImportSummary).run()) return PreservedAnalyses::all(); return PreservedAnalyses::none(); } @@ -907,9 +897,7 @@ void llvm::updatePublicTypeTestCalls(Module &M, CI->replaceAllUsesWith(NewCI); CI->eraseFromParent(); } - } else { - // TODO: Don't replace public type tests when speculative devirtualization - // gets enabled in LTO mode. + } else if (!ClDevirtualizeSpeculatively) { auto *True = ConstantInt::getTrue(M.getContext()); for (Use &U : make_early_inc_range(PublicTypeTestFunc->uses())) { auto *CI = cast<CallInst>(U.getUser()); @@ -1014,8 +1002,7 @@ static Error checkCombinedSummaryForTesting(ModuleSummaryIndex *Summary) { return ErrorSuccess(); } -bool DevirtModule::runForTesting(Module &M, ModuleAnalysisManager &MAM, - bool DevirtSpeculatively) { +bool DevirtModule::runForTesting(Module &M, ModuleAnalysisManager &MAM) { std::unique_ptr<ModuleSummaryIndex> Summary = std::make_unique<ModuleSummaryIndex>(/*HaveGVs=*/false); @@ -1044,8 +1031,7 @@ bool DevirtModule::runForTesting(Module &M, ModuleAnalysisManager &MAM, ClSummaryAction == PassSummaryAction::Export ? Summary.get() : nullptr, ClSummaryAction == PassSummaryAction::Import ? Summary.get() - : nullptr, - DevirtSpeculatively) + : nullptr) .run(); if (!ClWriteSummary.empty()) { @@ -1104,16 +1090,19 @@ void DevirtModule::buildTypeIdentifierMap( bool DevirtModule::tryFindVirtualCallTargets( std::vector<VirtualCallTarget> &TargetsForSlot, const std::set<TypeMemberInfo> &TypeMemberInfos, uint64_t ByteOffset, - ModuleSummaryIndex *ExportSummary) { + ModuleSummaryIndex *ExportSummary, bool &HasPublicTarget) { + HasPublicTarget = false; + for (const TypeMemberInfo &TM : TypeMemberInfos) { if (!TM.Bits->GV->isConstant()) return false; - // Without DevirtSpeculatively, we cannot perform whole program - // devirtualization analysis on a vtable with public LTO visibility. - if (!DevirtSpeculatively && TM.Bits->GV->getVCallVisibility() == - GlobalObject::VCallVisibilityPublic) - return false; + if (TM.Bits->GV->getVCallVisibility() == + GlobalObject::VCallVisibilityPublic) { + HasPublicTarget = true; + if (!ClDevirtualizeSpeculatively) + return false; + } Function *Fn = nullptr; Constant *C = nullptr; @@ -1131,12 +1120,6 @@ bool DevirtModule::tryFindVirtualCallTargets( if (Fn->getName() == "__cxa_pure_virtual") continue; - // In most cases empty functions will be overridden by the - // implementation of the derived class, so we can skip them. - if (DevirtSpeculatively && Fn->getReturnType()->isVoidTy() && - Fn->getInstructionCount() <= 1) - continue; - // We can disregard unreachable functions as possible call targets, as // unreachable functions shouldn't be called. if (mustBeUnreachableFunction(Fn, ExportSummary)) @@ -1158,7 +1141,10 @@ bool DevirtModule::tryFindVirtualCallTargets( bool DevirtIndex::tryFindVirtualCallTargets( std::vector<ValueInfo> &TargetsForSlot, - const TypeIdCompatibleVtableInfo TIdInfo, uint64_t ByteOffset) { + const TypeIdCompatibleVtableInfo TIdInfo, uint64_t ByteOffset, + bool &HasPublicTarget) { + HasPublicTarget = false; + for (const TypeIdOffsetVtableInfo &P : TIdInfo) { // Find a representative copy of the vtable initializer. // We can have multiple available_externally, linkonce_odr and weak_odr @@ -1187,8 +1173,11 @@ bool DevirtIndex::tryFindVirtualCallTargets( VS = CurVS; // We cannot perform whole program devirtualization analysis on a vtable // with public LTO visibility. - if (VS->getVCallVisibility() == GlobalObject::VCallVisibilityPublic) - return false; + if (VS->getVCallVisibility() == GlobalObject::VCallVisibilityPublic) { + HasPublicTarget = true; + if (!ClDevirtualizeSpeculatively) + return false; + } break; } } @@ -1214,7 +1203,8 @@ bool DevirtIndex::tryFindVirtualCallTargets( } void DevirtModule::applySingleImplDevirt(VTableSlotInfo &SlotInfo, - Constant *TheFn, bool &IsExported) { + Constant *TheFn, bool HasPublicTarget, + bool &IsExported) { // Don't devirtualize function if we're told to skip it // in -wholeprogramdevirt-skip. if (FunctionsToSkip.match(TheFn->stripPointerCasts()->getName())) @@ -1257,7 +1247,7 @@ void DevirtModule::applySingleImplDevirt(VTableSlotInfo &SlotInfo, // add support to compare the virtual function pointer to the // devirtualized target. In case of a mismatch, fall back to indirect // call. - if (DevirtCheckMode == WPDCheckMode::Fallback || DevirtSpeculatively) { + if (DevirtCheckMode == WPDCheckMode::Fallback || HasPublicTarget) { MDNode *Weights = MDBuilder(M.getContext()).createLikelyBranchWeights(); // Version the indirect call site. If the called value is equal to the // given callee, 'NewInst' will be executed, otherwise the original call @@ -1278,6 +1268,7 @@ void DevirtModule::applySingleImplDevirt(VTableSlotInfo &SlotInfo, // In either trapping or non-checking mode, devirtualize original call. else { // Devirtualize unconditionally. + assert(!HasPublicTarget); CB.setCalledOperand(Callee); // Since the call site is now direct, we must clear metadata that // is only appropriate for indirect calls. This includes !prof and @@ -1295,7 +1286,7 @@ void DevirtModule::applySingleImplDevirt(VTableSlotInfo &SlotInfo, } // This use is no longer unsafe. - if (VCallSite.NumUnsafeUses) + if (!HasPublicTarget && VCallSite.NumUnsafeUses) --*VCallSite.NumUnsafeUses; } if (CSInfo.isExported()) @@ -1338,7 +1329,7 @@ static bool addCalls(VTable... [truncated] `````````` </details> https://github.com/llvm/llvm-project/pull/233380 _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
