diff --git a/llvm/include/llvm/ADT/EnumeratedArray.h b/llvm/include/llvm/ADT/EnumeratedArray.h --- a/llvm/include/llvm/ADT/EnumeratedArray.h +++ b/llvm/include/llvm/ADT/EnumeratedArray.h @@ -38,6 +38,7 @@ static_cast &>(*this)[Index]); } + inline IndexType size() { return Size; } private: ValueType Underlying[Size]; diff --git a/llvm/include/llvm/Transforms/IPO/Attributor.h b/llvm/include/llvm/Transforms/IPO/Attributor.h --- a/llvm/include/llvm/Transforms/IPO/Attributor.h +++ b/llvm/include/llvm/Transforms/IPO/Attributor.h @@ -1036,6 +1036,14 @@ identifyDefaultAbstractAttributes(const_cast(F)); } + /// Helper function to remove callsite. + void removeCallSite(CallInst *CI) { + if (!CI) + return; + + CGUpdater.removeCallSite(*CI); + } + /// Record that \p U is to be replaces with \p NV after information was /// manifested. This also triggers deletion of trivially dead istructions. bool changeUseAfterManifest(Use &U, Value &NV) { diff --git a/llvm/lib/Transforms/IPO/OpenMPOpt.cpp b/llvm/lib/Transforms/IPO/OpenMPOpt.cpp --- a/llvm/lib/Transforms/IPO/OpenMPOpt.cpp +++ b/llvm/lib/Transforms/IPO/OpenMPOpt.cpp @@ -53,8 +53,47 @@ static constexpr auto TAG = "[" DEBUG_TYPE "]"; #endif +/// Helper struct to store tracked ICV values at specif instructions. +struct ICVValue { + Instruction *Inst; + Value *TrackedValue; + + ICVValue(Instruction *I, Value *Val) : Inst(I), TrackedValue(Val) {} +}; + +namespace llvm { + +// Provide DenseMapInfo for ICVValue +template <> struct DenseMapInfo { + using InstInfo = DenseMapInfo; + using ValueInfo = DenseMapInfo; + + static inline ICVValue getEmptyKey() { + return ICVValue(InstInfo::getEmptyKey(), ValueInfo::getEmptyKey()); + }; + + static inline ICVValue getTombstoneKey() { + return ICVValue(InstInfo::getTombstoneKey(), ValueInfo::getTombstoneKey()); + }; + + static unsigned getHashValue(const ICVValue &ICVVal) { + return detail::combineHashValue( + InstInfo::getHashValue(ICVVal.Inst), + ValueInfo::getHashValue(ICVVal.TrackedValue)); + } + + static bool isEqual(const ICVValue &LHS, const ICVValue &RHS) { + return InstInfo::isEqual(LHS.Inst, RHS.Inst) && + ValueInfo::isEqual(LHS.TrackedValue, RHS.TrackedValue); + } +}; + +} // end namespace llvm + namespace { +struct AAICVTracker; + /// OpenMP specific information. For now, stores RFIs and ICVs also needed for /// Attributor runs. struct OMPInformationCache : public InformationCache { @@ -119,11 +158,14 @@ /// Uses of this runtime function per function containing the use. using UseVector = SmallVector; + /// Clear UsesMap for runtime function. + void clearUsesMap() { UsesMap.clear(); } + /// Return the vector of uses in function \p F. UseVector &getOrCreateUseVector(Function *F) { - std::unique_ptr &UV = UsesMap[F]; + std::shared_ptr &UV = UsesMap[F]; if (!UV) - UV = std::make_unique(); + UV = std::make_shared(); return *UV; } @@ -179,7 +221,7 @@ private: /// Map from functions to all uses of this runtime function contained in /// them. - DenseMap> UsesMap; + DenseMap> UsesMap; }; /// The slice of the module we are allowed to look at. @@ -262,34 +304,45 @@ return true; } - /// Helper to initialize all runtime function information for those defined - /// in OpenMPKinds.def. - void initializeRuntimeFunctions() { - // Helper to collect all uses of the decleration in the UsesMap. - auto CollectUses = [&](RuntimeFunctionInfo &RFI) { - unsigned NumUses = 0; - if (!RFI.Declaration) - return NumUses; - OMPBuilder.addAttributes(RFI.Kind, *RFI.Declaration); + // Helper to collect all uses of the decleration in the UsesMap. + unsigned collectUses(RuntimeFunctionInfo &RFI, bool CollectStats = true) { + unsigned NumUses = 0; + if (!RFI.Declaration) + return NumUses; + OMPBuilder.addAttributes(RFI.Kind, *RFI.Declaration); + if (CollectStats) { NumOpenMPRuntimeFunctionsIdentified += 1; NumOpenMPRuntimeFunctionUsesIdentified += RFI.Declaration->getNumUses(); + } - // TODO: We directly convert uses into proper calls and unknown uses. - for (Use &U : RFI.Declaration->uses()) { - if (Instruction *UserI = dyn_cast(U.getUser())) { - if (ModuleSlice.count(UserI->getFunction())) { - RFI.getOrCreateUseVector(UserI->getFunction()).push_back(&U); - ++NumUses; - } - } else { - RFI.getOrCreateUseVector(nullptr).push_back(&U); + // TODO: We directly convert uses into proper calls and unknown uses. + for (Use &U : RFI.Declaration->uses()) { + if (Instruction *UserI = dyn_cast(U.getUser())) { + if (ModuleSlice.count(UserI->getFunction())) { + RFI.getOrCreateUseVector(UserI->getFunction()).push_back(&U); ++NumUses; } + } else { + RFI.getOrCreateUseVector(nullptr).push_back(&U); + ++NumUses; } - return NumUses; - }; + } + return NumUses; + } + // Helper function to recollect uses of all runtime functions. + void recollectUses() { + for (int Idx = 0; Idx < RFIs.size(); ++Idx) { + auto &RFI = RFIs[static_cast(Idx)]; + RFI.clearUsesMap(); + collectUses(RFI, /*CollectStats*/ false); + } + } + + /// Helper to initialize all runtime function information for those defined + /// in OpenMPKinds.def. + void initializeRuntimeFunctions() { Module &M = *((*ModuleSlice.begin())->getParent()); // Helper macros for handling __VA_ARGS__ in OMP_RTL @@ -327,7 +380,7 @@ RFI.ReturnType = OMPBuilder._ReturnType; \ RFI.ArgumentTypes = std::move(ArgsTypes); \ RFI.Declaration = F; \ - unsigned NumUses = CollectUses(RFI); \ + unsigned NumUses = collectUses(RFI); \ (void)NumUses; \ LLVM_DEBUG({ \ dbgs() << TAG << RFI.Name << (RFI.Declaration ? "" : " not") \ @@ -352,9 +405,9 @@ OpenMPOpt(SmallVectorImpl &SCC, CallGraphUpdater &CGUpdater, OptimizationRemarkGetter OREGetter, - OMPInformationCache &OMPInfoCache) + OMPInformationCache &OMPInfoCache, Attributor &A) : M(*(*SCC.begin())->getParent()), SCC(SCC), CGUpdater(CGUpdater), - OREGetter(OREGetter), OMPInfoCache(OMPInfoCache) {} + OREGetter(OREGetter), OMPInfoCache(OMPInfoCache), A(A) {} /// Run all OpenMP optimizations on the underlying SCC/ModuleSlice. bool run() { @@ -385,6 +438,11 @@ } } + Changed |= runAttributor(); + + // Recollect uses, in case Attributor deleted any. + OMPInfoCache.recollectUses(); + Changed |= deduplicateRuntimeCalls(); Changed |= deleteParallelRegions(); @@ -746,9 +804,206 @@ /// OpenMP-specific information cache. Also Used for Attributor runs. OMPInformationCache &OMPInfoCache; + + /// Attributor instance. + Attributor &A; + + /// Helper function to run Attributor on SCC. + bool runAttributor() { + if (SCC.empty()) + return false; + + registerAAs(); + + ChangeStatus Changed = A.run(); + + LLVM_DEBUG(dbgs() << "[Attributor] Done with " << SCC.size() + << " functions, result: " << Changed << ".\n"); + + return Changed == ChangeStatus::CHANGED; + } + + /// Populate the Attributor with abstract attribute opportunities in the + /// function. + void registerAAs() { + for (Function *F : SCC) { + if (F->isDeclaration()) + continue; + + A.getOrCreateAAFor(IRPosition::function(*F)); + } + } +}; + +/// Abstract Attribute for tracking ICV values. +struct AAICVTracker : public StateWrapper { + using Base = StateWrapper; + AAICVTracker(const IRPosition &IRP, Attributor &A) : Base(IRP) {} + + /// Returns true if value is assumed to be tracked. + bool isAssumedTracked() const { return getAssumed(); } + + /// Returns true if value is known to be tracked. + bool isKnownTracked() const { return getAssumed(); } + + /// Create an abstract attribute biew for the position \p IRP. + static AAICVTracker &createForPosition(const IRPosition &IRP, Attributor &A); + + /// Return the value with which \p I can be replaced for specific \p ICV. + virtual Value *getReplacementValue(InternalControlVar ICV, + const Instruction *I, Attributor &A) = 0; + + /// See AbstractAttribute::getName() + const std::string getName() const override { return "AAICVTracker"; } + + static const char ID; +}; + +struct AAICVTrackerFunction : public AAICVTracker { + AAICVTrackerFunction(const IRPosition &IRP, Attributor &A) + : AAICVTracker(IRP, A) {} + + // FIXME: come up with better string. + const std::string getAsStr() const override { return "ICVTracker"; } + + // FIXME: come up with some stats. + void trackStatistics() const override {} + + /// TODO: decide whether to deduplicate here, or use current + /// deduplicateRuntimeCalls function. + ChangeStatus manifest(Attributor &A) override { + ChangeStatus Changed = ChangeStatus::UNCHANGED; + + for (InternalControlVar &ICV : TrackableICVs) + if (deduplicateICVGetters(ICV, A)) + Changed = ChangeStatus::CHANGED; + + return Changed; + } + + bool deduplicateICVGetters(InternalControlVar &ICV, Attributor &A) { + auto &OMPInfoCache = static_cast(A.getInfoCache()); + auto &ICVInfo = OMPInfoCache.ICVs[ICV]; + auto &GetterRFI = OMPInfoCache.RFIs[ICVInfo.Getter]; + + bool Changed = false; + + auto ReplaceAndDeleteCB = [&](Use &U, Function &Caller) { + CallInst *CI = OpenMPOpt::getCallIfRegularCall(U, &GetterRFI); + Instruction *UserI = cast(U.getUser()); + Value *ReplVal = getReplacementValue(ICV, UserI, A); + + if (!ReplVal || !CI) + return false; + + A.removeCallSite(CI); + CI->replaceAllUsesWith(ReplVal); + CI->eraseFromParent(); + Changed = true; + return true; + }; + + GetterRFI.foreachUse(ReplaceAndDeleteCB); + return Changed; + } + + // Map of ICV to their values at specific program point. + EnumeratedArray, InternalControlVar, + InternalControlVar::ICV___last> + ICVValuesMap; + + // Currently only nthreads is being tracked. + // this array will only grow with time. + InternalControlVar TrackableICVs[1] = {ICV_nthreads}; + + ChangeStatus updateImpl(Attributor &A) override { + ChangeStatus HasChanged = ChangeStatus::UNCHANGED; + + Function *F = getAnchorScope(); + + auto &OMPInfoCache = static_cast(A.getInfoCache()); + + for (InternalControlVar ICV : TrackableICVs) { + auto &SetterRFI = OMPInfoCache.RFIs[OMPInfoCache.ICVs[ICV].Setter]; + + auto TrackValues = [&](Use &U, Function &) { + CallInst *CI = OpenMPOpt::getCallIfRegularCall(U); + if (!CI) + return false; + + // FIXME: handle setters with more that 1 arguments. + /// Track new value. + if (ICVValuesMap[ICV].insert(ICVValue(CI, CI->getArgOperand(0)))) + HasChanged = ChangeStatus::CHANGED; + + return false; + }; + + SetterRFI.foreachUse(TrackValues, F); + } + + return HasChanged; + } + + /// Return the value with which \p I can be replaced for specific \p ICV. + Value *getReplacementValue(InternalControlVar ICV, const Instruction *I, + Attributor &A) override { + const BasicBlock *CurrBB = I->getParent(); + + auto &ValuesSet = ICVValuesMap[ICV]; + auto &OMPInfoCache = static_cast(A.getInfoCache()); + auto &GetterRFI = OMPInfoCache.RFIs[OMPInfoCache.ICVs[ICV].Getter]; + + for (const auto &ICVVal : ValuesSet) { + if (CurrBB == ICVVal.Inst->getParent()) { + if (!ICVVal.Inst->comesBefore(I)) + continue; + + // both instructions are in the same BB and at \p I we know the ICV + // value. + while (I != ICVVal.Inst) { + // we don't yet know if a call might update an ICV. + // TODO: check callsite AA for value. + if (const auto *CB = dyn_cast(I)) + if (CB->getCalledFunction() != GetterRFI.Declaration) + return nullptr; + + I = I->getPrevNode(); + } + + // No call in between, return the value. + return ICVVal.TrackedValue; + } + } + + // No value was tracked. + return nullptr; + } }; } // namespace +const char AAICVTracker::ID = 0; + +AAICVTracker &AAICVTracker::createForPosition(const IRPosition &IRP, + Attributor &A) { + AAICVTracker *AA = nullptr; + switch (IRP.getPositionKind()) { + case IRPosition::IRP_INVALID: + case IRPosition::IRP_FLOAT: + case IRPosition::IRP_ARGUMENT: + case IRPosition::IRP_RETURNED: + case IRPosition::IRP_CALL_SITE_RETURNED: + case IRPosition::IRP_CALL_SITE_ARGUMENT: + case IRPosition::IRP_CALL_SITE: + llvm_unreachable("ICVTracker can only be created for function position!"); + case IRPosition::IRP_FUNCTION: + AA = new (A.Allocator) AAICVTrackerFunction(IRP, A); + break; + } + + return *AA; +} + PreservedAnalyses OpenMPOptPass::run(LazyCallGraph::SCC &C, CGSCCAnalysisManager &AM, LazyCallGraph &CG, CGSCCUpdateResult &UR) { @@ -785,8 +1040,10 @@ OMPInformationCache InfoCache(*(Functions.back()->getParent()), AG, Allocator, /*CGSCC*/ &Functions, ModuleSlice); + Attributor A(Functions, InfoCache, CGUpdater); + // TODO: Compute the module slice we are allowed to look at. - OpenMPOpt OMPOpt(SCC, CGUpdater, OREGetter, InfoCache); + OpenMPOpt OMPOpt(SCC, CGUpdater, OREGetter, InfoCache, A); bool Changed = OMPOpt.run(); (void)Changed; return PreservedAnalyses::all(); @@ -850,8 +1107,10 @@ Allocator, /*CGSCC*/ &Functions, ModuleSlice); + Attributor A(Functions, InfoCache, CGUpdater); + // TODO: Compute the module slice we are allowed to look at. - OpenMPOpt OMPOpt(SCC, CGUpdater, OREGetter, InfoCache); + OpenMPOpt OMPOpt(SCC, CGUpdater, OREGetter, InfoCache, A); return OMPOpt.run(); } diff --git a/llvm/test/Transforms/OpenMP/dead_use.ll b/llvm/test/Transforms/OpenMP/dead_use.ll new file mode 100644 --- /dev/null +++ b/llvm/test/Transforms/OpenMP/dead_use.ll @@ -0,0 +1,73 @@ +; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --function-signature +; RUN: opt -S -openmpopt < %s | FileCheck %s +; RUN: opt -S -passes=openmpopt < %s | FileCheck %s +%struct.ident_t = type { i32, i32, i32, i32, i8* } + +@.str = private unnamed_addr constant [23 x i8] c";unknown;unknown;0;0;;\00", align 1 +@0 = private unnamed_addr global %struct.ident_t { i32 0, i32 2, i32 0, i32 0, i8* getelementptr inbounds ([23 x i8], [23 x i8]* @.str, i32 0, i32 0) }, align 8 + +; Function Attrs: nounwind uwtable +define dso_local i32 @b() #0 { +; CHECK-LABEL: define {{[^@]+}}@b() #0 +; CHECK-NEXT: [[TMP1:%.*]] = alloca i32, align 4 +; CHECK-NEXT: [[TMP2:%.*]] = call i32 @a() +; CHECK-NEXT: [[TMP3:%.*]] = load i32, i32* [[TMP1]], align 4 +; CHECK-NEXT: ret i32 [[TMP3]] +; + %1 = alloca i32, align 4 + %2 = call i32 @a() + %3 = load i32, i32* %1, align 4 + ret i32 %3 +} + +; Function Attrs: nounwind uwtable +define internal i32 @a() #0 { +; CHECK-LABEL: define {{[^@]+}}@a() #0 +; CHECK-NEXT: [[TMP1:%.*]] = alloca i32, align 4 +; CHECK-NEXT: [[TMP2:%.*]] = call i32 @b() +; CHECK-NEXT: call void (%struct.ident_t*, i32, void (i32*, i32*, ...)*, ...) @__kmpc_fork_call(%struct.ident_t* @0, i32 0, void (i32*, i32*, ...)* bitcast (void (i32*, i32*)* @.omp_outlined. to void (i32*, i32*, ...)*)) +; CHECK-NEXT: [[TMP3:%.*]] = load i32, i32* [[TMP1]], align 4 +; CHECK-NEXT: ret i32 [[TMP3]] +; + %1 = alloca i32, align 4 + %2 = call i32 @b() + call void (%struct.ident_t*, i32, void (i32*, i32*, ...)*, ...) @__kmpc_fork_call(%struct.ident_t* @0, i32 0, void (i32*, i32*, ...)* bitcast (void (i32*, i32*)* @.omp_outlined. to void (i32*, i32*, ...)*)) + %3 = load i32, i32* %1, align 4 + ret i32 %3 +} + +; Function Attrs: norecurse nounwind uwtable +define internal void @.omp_outlined.(i32* noalias %0, i32* noalias %1) #1 { +; CHECK-LABEL: define {{[^@]+}}@.omp_outlined. +; CHECK-SAME: (i32* noalias [[TMP0:%.*]], i32* noalias [[TMP1:%.*]]) #1 +; CHECK-NEXT: [[TMP3:%.*]] = alloca i32*, align 8 +; CHECK-NEXT: [[TMP4:%.*]] = alloca i32*, align 8 +; CHECK-NEXT: store i32* [[TMP0]], i32** [[TMP3]], align 8, !tbaa !2 +; CHECK-NEXT: store i32* [[TMP1]], i32** [[TMP4]], align 8, !tbaa !2 +; CHECK-NEXT: ret void +; + %3 = alloca i32*, align 8 + %4 = alloca i32*, align 8 + store i32* %0, i32** %3, align 8, !tbaa !2 + store i32* %1, i32** %4, align 8, !tbaa !2 + ret void +} + +; Function Attrs: nounwind +declare !callback !6 void @__kmpc_fork_call(%struct.ident_t*, i32, void (i32*, i32*, ...)*, ...) #2 + +attributes #0 = { nounwind uwtable "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "frame-pointer"="none" "less-precise-fpmad"="false" "min-legal-vector-width"="0" "no-infs-fp-math"="false" "no-jump-tables"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" } +attributes #1 = { norecurse nounwind uwtable "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "frame-pointer"="none" "less-precise-fpmad"="false" "min-legal-vector-width"="0" "no-infs-fp-math"="false" "no-jump-tables"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" } +attributes #2 = { nounwind } + +!llvm.module.flags = !{!0} +!llvm.ident = !{!1} + +!0 = !{i32 1, !"wchar_size", i32 4} +!1 = !{!"Debian clang version 11.0.0-++20200709100646+c92a8c0a0f6-1~exp1~20200709201313.3348"} +!2 = !{!3, !3, i64 0} +!3 = !{!"any pointer", !4, i64 0} +!4 = !{!"omnipotent char", !5, i64 0} +!5 = !{!"Simple C/C++ TBAA"} +!6 = !{!7} +!7 = !{i64 2, i64 -1, i64 -1, i1 true} diff --git a/llvm/test/Transforms/OpenMP/icv_tracking.ll b/llvm/test/Transforms/OpenMP/icv_tracking.ll --- a/llvm/test/Transforms/OpenMP/icv_tracking.ll +++ b/llvm/test/Transforms/OpenMP/icv_tracking.ll @@ -11,16 +11,12 @@ ; CHECK-LABEL: define {{[^@]+}}@foo ; CHECK-SAME: (i32 [[TMP0:%.*]], i32 [[TMP1:%.*]]) ; CHECK-NEXT: tail call void @omp_set_num_threads(i32 [[TMP0]]) -; CHECK-NEXT: [[TMP3:%.*]] = tail call i32 @omp_get_max_threads() ; CHECK-NEXT: tail call void @omp_set_num_threads(i32 [[TMP1]]) -; CHECK-NEXT: [[TMP4:%.*]] = tail call i32 @omp_get_max_threads() -; CHECK-NEXT: [[TMP5:%.*]] = tail call i32 @omp_get_max_threads() -; CHECK-NEXT: [[TMP6:%.*]] = tail call i32 @omp_get_max_threads() -; CHECK-NEXT: tail call void @use(i32 [[TMP4]]) -; CHECK-NEXT: tail call void @use(i32 [[TMP5]]) +; CHECK-NEXT: tail call void @use(i32 [[TMP1]]) +; CHECK-NEXT: tail call void @use(i32 [[TMP1]]) ; CHECK-NEXT: tail call void (%struct.ident_t*, i32, void (i32*, i32*, ...)*, ...) @__kmpc_fork_call(%struct.ident_t* nonnull @0, i32 0, void (i32*, i32*, ...)* bitcast (void (i32*, i32*)* @.omp_outlined. to void (i32*, i32*, ...)*)) -; CHECK-NEXT: [[TMP7:%.*]] = tail call i32 @omp_get_max_threads() -; CHECK-NEXT: tail call void @use(i32 [[TMP7]]) +; CHECK-NEXT: [[TMP3:%.*]] = tail call i32 @omp_get_max_threads() +; CHECK-NEXT: tail call void @use(i32 [[TMP3]]) ; CHECK-NEXT: ret i32 0 ; tail call void @omp_set_num_threads(i32 %0) @@ -51,15 +47,13 @@ ; CHECK-NEXT: [[TMP4:%.*]] = tail call i32 @omp_get_max_threads() ; CHECK-NEXT: tail call void @use(i32 [[TMP4]]) ; CHECK-NEXT: tail call void @omp_set_num_threads(i32 10) -; CHECK-NEXT: [[TMP5:%.*]] = tail call i32 @omp_get_max_threads() -; CHECK-NEXT: tail call void @use(i32 [[TMP5]]) +; CHECK-NEXT: tail call void @use(i32 10) ; CHECK-NEXT: ret void ; ; FIXME: this value should be tracked and the rest of the getters deduplicated and replaced with it. %3 = tail call i32 @omp_get_max_threads() %4 = tail call i32 @omp_get_max_threads() tail call void @use(i32 %4) -; FIXME: this value ( min(%3, 10) ) should be tracked and the rest of the getters deduplicated and replaced with it. tail call void @omp_set_num_threads(i32 10) %5 = tail call i32 @omp_get_max_threads() tail call void @use(i32 %5) @@ -74,10 +68,9 @@ ; CHECK-NEXT: [[TMP3:%.*]] = icmp sgt i32 [[TMP0]], [[TMP1]] ; CHECK-NEXT: [[TMP4:%.*]] = select i1 [[TMP3]], i32 [[TMP0]], i32 [[TMP1]] ; CHECK-NEXT: tail call void @omp_set_num_threads(i32 [[TMP4]]) -; CHECK-NEXT: [[TMP5:%.*]] = tail call i32 @omp_get_max_threads() ; CHECK-NEXT: tail call void (%struct.ident_t*, i32, void (i32*, i32*, ...)*, ...) @__kmpc_fork_call(%struct.ident_t* nonnull @0, i32 0, void (i32*, i32*, ...)* bitcast (void (i32*, i32*)* @.omp_outlined..1 to void (i32*, i32*, ...)*)) -; CHECK-NEXT: [[TMP6:%.*]] = tail call i32 @omp_get_max_threads() -; CHECK-NEXT: tail call void @use(i32 [[TMP6]]) +; CHECK-NEXT: [[TMP5:%.*]] = tail call i32 @omp_get_max_threads() +; CHECK-NEXT: tail call void @use(i32 [[TMP5]]) ; CHECK-NEXT: ret i32 0 ; %3 = icmp sgt i32 %0, %1 @@ -97,10 +90,9 @@ ; CHECK-NEXT: [[TMP3:%.*]] = tail call i32 @omp_get_max_threads() ; CHECK-NEXT: tail call void @use(i32 [[TMP3]]) ; CHECK-NEXT: tail call void @omp_set_num_threads(i32 10) +; CHECK-NEXT: tail call void @use(i32 10) ; CHECK-NEXT: [[TMP4:%.*]] = tail call i32 @omp_get_max_threads() ; CHECK-NEXT: tail call void @use(i32 [[TMP4]]) -; CHECK-NEXT: [[TMP5:%.*]] = tail call i32 @omp_get_max_threads() -; CHECK-NEXT: tail call void @use(i32 [[TMP5]]) ; CHECK-NEXT: ret void ; %3 = tail call i32 @omp_get_max_threads()