Index: llvm/trunk/docs/LangRef.rst =================================================================== --- llvm/trunk/docs/LangRef.rst +++ llvm/trunk/docs/LangRef.rst @@ -1493,6 +1493,16 @@ Annotated functions may still raise an exception, i.a., ``nounwind`` is not implied. If an invocation of an annotated function does not return control back to a point in the call stack, the behavior is undefined. +``nosync`` + This function attribute indicates that the function does not communicate + (synchronize) with another thread through memory or other well-defined means. + Synchronization is considered possible in the presence of `atomic` accesses + that enforce an order, thus not "unordered" and "monotonic", `volatile` accesses, + as well as `convergent` function calls. Note that through `convergent` function calls + non-memory communication, e.g., cross-lane operations, are possible and are also + considered synchronization. However `convergent` does not contradict `nosync`. + If an annotated function does ever synchronize with another thread, + the behavior is undefined. ``nounwind`` This function attribute indicates that the function never raises an exception. If the function does raise an exception, its runtime Index: llvm/trunk/include/llvm/Bitcode/LLVMBitCodes.h =================================================================== --- llvm/trunk/include/llvm/Bitcode/LLVMBitCodes.h +++ llvm/trunk/include/llvm/Bitcode/LLVMBitCodes.h @@ -629,7 +629,8 @@ ATTR_KIND_SPECULATIVE_LOAD_HARDENING = 59, ATTR_KIND_IMMARG = 60, ATTR_KIND_WILLRETURN = 61, - ATTR_KIND_NOFREE = 62 + ATTR_KIND_NOFREE = 62, + ATTR_KIND_NOSYNC = 63 }; enum ComdatSelectionKindCodes { Index: llvm/trunk/include/llvm/IR/Attributes.td =================================================================== --- llvm/trunk/include/llvm/IR/Attributes.td +++ llvm/trunk/include/llvm/IR/Attributes.td @@ -109,6 +109,9 @@ /// Mark the function as not returning. def NoReturn : EnumAttr<"noreturn">; +/// Function does not synchronize. +def NoSync : EnumAttr<"nosync">; + /// Disable Indirect Branch Tracking. def NoCfCheck : EnumAttr<"nocf_check">; Index: llvm/trunk/include/llvm/Transforms/IPO/Attributor.h =================================================================== --- llvm/trunk/include/llvm/Transforms/IPO/Attributor.h +++ llvm/trunk/include/llvm/Transforms/IPO/Attributor.h @@ -377,7 +377,7 @@ /// state will catch up with the assumed one, for a pessimistic fixpoint it is /// the other way around. struct IntegerState : public AbstractState { - /// Undrlying integer type, we assume 32 bits to be enough. + /// Underlying integer type, we assume 32 bits to be enough. using base_t = uint32_t; /// Initialize the (best) state. @@ -664,20 +664,40 @@ }; struct AANoUnwind : public AbstractAttribute { - /// An abstract interface for all nosync attributes. - AANoUnwind(Value &V, InformationCache &InfoCache) - : AbstractAttribute(V, InfoCache) {} + /// An abstract interface for all nosync attributes. + AANoUnwind(Value &V, InformationCache &InfoCache) + : AbstractAttribute(V, InfoCache) {} - /// See AbstractAttribute::getAttrKind()/ - virtual Attribute::AttrKind getAttrKind() const override { return ID; } + /// See AbstractAttribute::getAttrKind()/ + virtual Attribute::AttrKind getAttrKind() const override { return ID; } + + static constexpr Attribute::AttrKind ID = Attribute::NoUnwind; + + /// Returns true if nounwind is assumed. + virtual bool isAssumedNoUnwind() const = 0; + + /// Returns true if nounwind is known. + virtual bool isKnownNoUnwind() const = 0; +}; + +struct AANoSync : public AbstractAttribute { + /// An abstract interface for all nosync attributes. + AANoSync(Value &V, InformationCache &InfoCache) + : AbstractAttribute(V, InfoCache) {} + + /// See AbstractAttribute::getAttrKind(). + virtual Attribute::AttrKind getAttrKind() const override { + return ID; + } - static constexpr Attribute::AttrKind ID = Attribute::NoUnwind; + static constexpr Attribute::AttrKind ID = + Attribute::AttrKind(Attribute::NoSync); - /// Returns true if nounwind is assumed. - virtual bool isAssumedNoUnwind() const = 0; + /// Returns true if "nosync" is assumed. + virtual bool isAssumedNoSync() const = 0; - /// Returns true if nounwind is known. - virtual bool isKnownNoUnwind() const = 0; + /// Returns true if "nosync" is known. + virtual bool isKnownNoSync() const = 0; }; } // end namespace llvm Index: llvm/trunk/lib/AsmParser/LLLexer.cpp =================================================================== --- llvm/trunk/lib/AsmParser/LLLexer.cpp +++ llvm/trunk/lib/AsmParser/LLLexer.cpp @@ -658,6 +658,7 @@ KEYWORD(nonnull); KEYWORD(noredzone); KEYWORD(noreturn); + KEYWORD(nosync); KEYWORD(nocf_check); KEYWORD(nounwind); KEYWORD(optforfuzzing); Index: llvm/trunk/lib/AsmParser/LLParser.cpp =================================================================== --- llvm/trunk/lib/AsmParser/LLParser.cpp +++ llvm/trunk/lib/AsmParser/LLParser.cpp @@ -1287,6 +1287,7 @@ case lltok::kw_nonlazybind: B.addAttribute(Attribute::NonLazyBind); break; case lltok::kw_noredzone: B.addAttribute(Attribute::NoRedZone); break; case lltok::kw_noreturn: B.addAttribute(Attribute::NoReturn); break; + case lltok::kw_nosync: B.addAttribute(Attribute::NoSync); break; case lltok::kw_nocf_check: B.addAttribute(Attribute::NoCfCheck); break; case lltok::kw_norecurse: B.addAttribute(Attribute::NoRecurse); break; case lltok::kw_nounwind: B.addAttribute(Attribute::NoUnwind); break; Index: llvm/trunk/lib/AsmParser/LLToken.h =================================================================== --- llvm/trunk/lib/AsmParser/LLToken.h +++ llvm/trunk/lib/AsmParser/LLToken.h @@ -203,6 +203,7 @@ kw_nonnull, kw_noredzone, kw_noreturn, + kw_nosync, kw_nocf_check, kw_nounwind, kw_optforfuzzing, Index: llvm/trunk/lib/Bitcode/Reader/BitcodeReader.cpp =================================================================== --- llvm/trunk/lib/Bitcode/Reader/BitcodeReader.cpp +++ llvm/trunk/lib/Bitcode/Reader/BitcodeReader.cpp @@ -1280,6 +1280,9 @@ return 1ULL << 62; case Attribute::NoFree: return 1ULL << 63; + case Attribute::NoSync: + llvm_unreachable("nosync attribute not supported in raw format"); + break; case Attribute::Dereferenceable: llvm_unreachable("dereferenceable attribute not supported in raw format"); break; @@ -1305,7 +1308,8 @@ if (I == Attribute::Dereferenceable || I == Attribute::DereferenceableOrNull || I == Attribute::ArgMemOnly || - I == Attribute::AllocSize) + I == Attribute::AllocSize || + I == Attribute::NoSync) continue; if (uint64_t A = (Val & getRawAttributeMask(I))) { if (I == Attribute::Alignment) @@ -1466,6 +1470,8 @@ return Attribute::NoRedZone; case bitc::ATTR_KIND_NO_RETURN: return Attribute::NoReturn; + case bitc::ATTR_KIND_NOSYNC: + return Attribute::NoSync; case bitc::ATTR_KIND_NOCF_CHECK: return Attribute::NoCfCheck; case bitc::ATTR_KIND_NO_UNWIND: Index: llvm/trunk/lib/Bitcode/Writer/BitcodeWriter.cpp =================================================================== --- llvm/trunk/lib/Bitcode/Writer/BitcodeWriter.cpp +++ llvm/trunk/lib/Bitcode/Writer/BitcodeWriter.cpp @@ -659,6 +659,8 @@ return bitc::ATTR_KIND_NO_RED_ZONE; case Attribute::NoReturn: return bitc::ATTR_KIND_NO_RETURN; + case Attribute::NoSync: + return bitc::ATTR_KIND_NOSYNC; case Attribute::NoCfCheck: return bitc::ATTR_KIND_NOCF_CHECK; case Attribute::NoUnwind: Index: llvm/trunk/lib/IR/Attributes.cpp =================================================================== --- llvm/trunk/lib/IR/Attributes.cpp +++ llvm/trunk/lib/IR/Attributes.cpp @@ -335,6 +335,8 @@ return "noredzone"; if (hasAttribute(Attribute::NoReturn)) return "noreturn"; + if (hasAttribute(Attribute::NoSync)) + return "nosync"; if (hasAttribute(Attribute::WillReturn)) return "willreturn"; if (hasAttribute(Attribute::NoCfCheck)) Index: llvm/trunk/lib/IR/Verifier.cpp =================================================================== --- llvm/trunk/lib/IR/Verifier.cpp +++ llvm/trunk/lib/IR/Verifier.cpp @@ -1493,6 +1493,7 @@ static bool isFuncOnlyAttr(Attribute::AttrKind Kind) { switch (Kind) { case Attribute::NoReturn: + case Attribute::NoSync: case Attribute::WillReturn: case Attribute::NoCfCheck: case Attribute::NoUnwind: Index: llvm/trunk/lib/Transforms/IPO/Attributor.cpp =================================================================== --- llvm/trunk/lib/Transforms/IPO/Attributor.cpp +++ llvm/trunk/lib/Transforms/IPO/Attributor.cpp @@ -23,6 +23,7 @@ #include "llvm/IR/Argument.h" #include "llvm/IR/Attributes.h" #include "llvm/IR/InstIterator.h" +#include "llvm/IR/IntrinsicInst.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/Debug.h" #include "llvm/Support/raw_ostream.h" @@ -48,6 +49,7 @@ STATISTIC(NumFnKnownReturns, "Number of function with known return values"); STATISTIC(NumFnArgumentReturned, "Number of function arguments marked returned"); +STATISTIC(NumFnNoSync, "Number of functions marked nosync"); // TODO: Determine a good default value. // @@ -99,6 +101,9 @@ case Attribute::Returned: NumFnArgumentReturned++; return; + case Attribute::NoSync: + NumFnNoSync++; + break; default: return; } @@ -719,6 +724,191 @@ return Changed; } +/// ------------------------ NoSync Function Attribute ------------------------- + +struct AANoSyncFunction : AANoSync, BooleanState { + + AANoSyncFunction(Function &F, InformationCache &InfoCache) + : AANoSync(F, InfoCache) {} + + /// See AbstractAttribute::getState() + /// { + AbstractState &getState() override { return *this; } + const AbstractState &getState() const override { return *this; } + /// } + + /// See AbstractAttribute::getManifestPosition(). + virtual ManifestPosition getManifestPosition() const override { + return MP_FUNCTION; + } + + virtual const std::string getAsStr() const override { + return getAssumed() ? "nosync" : "may-sync"; + } + + /// See AbstractAttribute::updateImpl(...). + virtual ChangeStatus updateImpl(Attributor &A) override; + + /// See AANoSync::isAssumedNoSync() + virtual bool isAssumedNoSync() const override { return getAssumed(); } + + /// See AANoSync::isKnownNoSync() + virtual bool isKnownNoSync() const override { return getKnown(); } + + /// Helper function used to determine whether an instruction is non-relaxed + /// atomic. In other words, if an atomic instruction does not have unordered + /// or monotonic ordering + static bool isNonRelaxedAtomic(Instruction *I); + + /// Helper function used to determine whether an instruction is volatile. + static bool isVolatile(Instruction *I); + + /// Helper function uset to check if intrinsic is volatile (memcpy, memmove, memset). + static bool isNoSyncIntrinsic(Instruction *I); +}; + +bool AANoSyncFunction::isNonRelaxedAtomic(Instruction *I) { + if (!I->isAtomic()) + return false; + + AtomicOrdering Ordering; + switch (I->getOpcode()) { + case Instruction::AtomicRMW: + Ordering = cast(I)->getOrdering(); + break; + case Instruction::Store: + Ordering = cast(I)->getOrdering(); + break; + case Instruction::Load: + Ordering = cast(I)->getOrdering(); + break; + case Instruction::Fence: { + auto *FI = cast(I); + if (FI->getSyncScopeID() == SyncScope::SingleThread) + return false; + Ordering = FI->getOrdering(); + break; + } + case Instruction::AtomicCmpXchg: { + AtomicOrdering Success = cast(I)->getSuccessOrdering(); + AtomicOrdering Failure = cast(I)->getFailureOrdering(); + // Only if both are relaxed, than it can be treated as relaxed. + // Otherwise it is non-relaxed. + if (Success != AtomicOrdering::Unordered && + Success != AtomicOrdering::Monotonic) + return true; + if (Failure != AtomicOrdering::Unordered && + Failure != AtomicOrdering::Monotonic) + return true; + return false; + } + default: + llvm_unreachable( + "New atomic operations need to be known in the attributor."); + } + + // Relaxed. + if (Ordering == AtomicOrdering::Unordered || + Ordering == AtomicOrdering::Monotonic) + return false; + return true; +} + +/// Checks if an intrinsic is nosync. Currently only checks mem* intrinsics. +/// FIXME: We should ipmrove the handling of intrinsics. +bool AANoSyncFunction::isNoSyncIntrinsic(Instruction *I) { + if (auto *II = dyn_cast(I)) { + switch (II->getIntrinsicID()) { + /// Element wise atomic memory intrinsics are can only be unordered, + /// therefore nosync. + case Intrinsic::memset_element_unordered_atomic: + case Intrinsic::memmove_element_unordered_atomic: + case Intrinsic::memcpy_element_unordered_atomic: + return true; + case Intrinsic::memset: + case Intrinsic::memmove: + case Intrinsic::memcpy: + if (!cast(II)->isVolatile()) + return true; + return false; + default: + return false; + } + } + return false; +} + +bool AANoSyncFunction::isVolatile(Instruction *I) { + assert(!ImmutableCallSite(I) && !isa(I) && + "Calls should not be checked here"); + + switch (I->getOpcode()) { + case Instruction::AtomicRMW: + return cast(I)->isVolatile(); + case Instruction::Store: + return cast(I)->isVolatile(); + case Instruction::Load: + return cast(I)->isVolatile(); + case Instruction::AtomicCmpXchg: + return cast(I)->isVolatile(); + default: + return false; + } +} + +ChangeStatus AANoSyncFunction::updateImpl(Attributor &A) { + Function &F = getAnchorScope(); + + /// We are looking for volatile instructions or Non-Relaxed atomics. + /// FIXME: We should ipmrove the handling of intrinsics. + for (Instruction *I : InfoCache.getReadOrWriteInstsForFunction(F)) { + ImmutableCallSite ICS(I); + auto *NoSyncAA = A.getAAFor(*this, *I); + + if (isa(I) && isNoSyncIntrinsic(I)) + continue; + + if (ICS && (!NoSyncAA || !NoSyncAA->isAssumedNoSync()) && + !ICS.hasFnAttr(Attribute::NoSync)) { + indicatePessimisticFixpoint(); + return ChangeStatus::CHANGED; + } + + if(ICS) + continue; + + if (!isVolatile(I) && !isNonRelaxedAtomic(I)) + continue; + + indicatePessimisticFixpoint(); + return ChangeStatus::CHANGED; + } + + auto &OpcodeInstMap = InfoCache.getOpcodeInstMapForFunction(F); + auto Opcodes = {(unsigned)Instruction::Invoke, (unsigned)Instruction::CallBr, + (unsigned)Instruction::Call}; + + for (unsigned Opcode : Opcodes) { + for (Instruction *I : OpcodeInstMap[Opcode]) { + // At this point we handled all read/write effects and they are all + // nosync, so they can be skipped. + if (I->mayReadOrWriteMemory()) + continue; + + ImmutableCallSite ICS(I); + + // non-convergent and readnone imply nosync. + if (!ICS.isConvergent()) + continue; + + indicatePessimisticFixpoint(); + return ChangeStatus::CHANGED; + } + } + + return ChangeStatus::UNCHANGED; +} + /// ---------------------------------------------------------------------------- /// Attributor /// ---------------------------------------------------------------------------- @@ -864,6 +1054,9 @@ // Every function can be nounwind. registerAA(*new AANoUnwindFunction(F, InfoCache)); + // Every function might be marked "nosync" + registerAA(*new AANoSyncFunction(F, InfoCache)); + // Return attributes are only appropriate if the return type is non void. Type *ReturnType = F.getReturnType(); if (!ReturnType->isVoidTy()) { Index: llvm/trunk/lib/Transforms/Utils/CodeExtractor.cpp =================================================================== --- llvm/trunk/lib/Transforms/Utils/CodeExtractor.cpp +++ llvm/trunk/lib/Transforms/Utils/CodeExtractor.cpp @@ -809,6 +809,7 @@ case Attribute::NoBuiltin: case Attribute::NoCapture: case Attribute::NoReturn: + case Attribute::NoSync: case Attribute::None: case Attribute::NonNull: case Attribute::ReadNone: Index: llvm/trunk/test/Bitcode/attributes.ll =================================================================== --- llvm/trunk/test/Bitcode/attributes.ll +++ llvm/trunk/test/Bitcode/attributes.ll @@ -203,8 +203,8 @@ define void @f34() ; CHECK: define void @f34() { - call void @nobuiltin() nobuiltin -; CHECK: call void @nobuiltin() #38 + call void @nobuiltin() nobuiltin +; CHECK: call void @nobuiltin() #39 ret void; } @@ -362,6 +362,12 @@ ret void } +; CHECK: define void @f62() #38 +define void @f62() nosync +{ + ret void +} + ; CHECK: attributes #0 = { noreturn } ; CHECK: attributes #1 = { nounwind } ; CHECK: attributes #2 = { readnone } @@ -400,4 +406,5 @@ ; CHECK: attributes #35 = { shadowcallstack } ; CHECK: attributes #36 = { willreturn } ; CHECK: attributes #37 = { nofree } -; CHECK: attributes #38 = { nobuiltin } +; CHECK: attributes #38 = { nosync } +; CHECK: attributes #39 = { nobuiltin } Index: llvm/trunk/test/Transforms/FunctionAttrs/arg_returned.ll =================================================================== --- llvm/trunk/test/Transforms/FunctionAttrs/arg_returned.ll +++ llvm/trunk/test/Transforms/FunctionAttrs/arg_returned.ll @@ -8,13 +8,13 @@ ; TEST SCC test returning an integer value argument ; -; BOTH: Function Attrs: noinline norecurse nounwind readnone uwtable +; BOTH: Function Attrs: noinline norecurse nosync nounwind readnone uwtable ; BOTH-NEXT: define i32 @sink_r0(i32 returned %r) -; BOTH: Function Attrs: noinline nounwind readnone uwtable +; BOTH: Function Attrs: noinline nosync nounwind readnone uwtable ; BOTH-NEXT: define i32 @scc_r1(i32 %a, i32 returned %r, i32 %b) -; BOTH: Function Attrs: noinline nounwind readnone uwtable +; BOTH: Function Attrs: noinline nosync nounwind readnone uwtable ; BOTH-NEXT: define i32 @scc_r2(i32 %a, i32 %b, i32 returned %r) -; BOTH: Function Attrs: noinline nounwind readnone uwtable +; BOTH: Function Attrs: noinline nosync nounwind readnone uwtable ; BOTH-NEXT: define i32 @scc_rX(i32 %a, i32 %b, i32 %r) ; ; FNATTR: define i32 @sink_r0(i32 returned %r) @@ -159,20 +159,23 @@ ; TEST SCC test returning a pointer value argument ; -; BOTH: Function Attrs: noinline norecurse nounwind readnone uwtable +; BOTH: Function Attrs: noinline norecurse nosync nounwind readnone uwtable ; BOTH-NEXT: define double* @ptr_sink_r0(double* readnone returned %r) -; BOTH: Function Attrs: noinline nounwind readnone uwtable +; BOTH: Function Attrs: noinline nosync nounwind readnone uwtable ; BOTH-NEXT: define double* @ptr_scc_r1(double* %a, double* readnone returned %r, double* nocapture readnone %b) -; BOTH: Function Attrs: noinline nounwind readnone uwtable +; BOTH: Function Attrs: noinline nosync nounwind readnone uwtable ; BOTH-NEXT: define double* @ptr_scc_r2(double* readnone %a, double* readnone %b, double* readnone returned %r) ; ; FNATTR: define double* @ptr_sink_r0(double* readnone returned %r) ; FNATTR: define double* @ptr_scc_r1(double* %a, double* readnone %r, double* nocapture readnone %b) ; FNATTR: define double* @ptr_scc_r2(double* readnone %a, double* readnone %b, double* readnone %r) ; -; ATTRIBUTOR: define double* @ptr_sink_r0(double* returned %r) -; ATTRIBUTOR: define double* @ptr_scc_r1(double* %a, double* returned %r, double* %b) -; ATTRIBUTOR: define double* @ptr_scc_r2(double* %a, double* %b, double* returned %r) +; ATTRIBUTOR: Function Attrs: noinline nosync nounwind uwtable +; ATTRIBUTOR-NEXT: define double* @ptr_sink_r0(double* returned %r) +; ATTRIBUTOR: Function Attrs: noinline nosync nounwind uwtable +; ATTRIBUTOR-NEXT: define double* @ptr_scc_r1(double* %a, double* returned %r, double* %b) +; ATTRIBUTOR: Function Attrs: noinline nosync nounwind uwtable +; ATTRIBUTOR-NEXT: define double* @ptr_scc_r2(double* %a, double* %b, double* returned %r) ; ; double* ptr_scc_r1(double* a, double* b, double* r); ; double* ptr_scc_r2(double* a, double* b, double* r); @@ -258,7 +261,7 @@ ; ; FIXME: no-return missing ; FNATTR: define i32* @rt0(i32* readonly %a) -; BOTH: Function Attrs: noinline nounwind readonly uwtable +; BOTH: Function Attrs: noinline nosync nounwind readonly uwtable ; BOTH-NEXT: define i32* @rt0(i32* readonly returned %a) define i32* @rt0(i32* %a) #0 { entry: @@ -277,7 +280,7 @@ ; ; FIXME: no-return missing ; FNATTR: define noalias i32* @rt1(i32* nocapture readonly %a) -; BOTH: Function Attrs: noinline nounwind readonly uwtable +; BOTH: Function Attrs: noinline nosync nounwind readonly uwtable ; BOTH-NEXT: define noalias i32* @rt1(i32* nocapture readonly %a) define i32* @rt1(i32* %a) #0 { entry: @@ -438,11 +441,12 @@ ; return b == 0? b : x; ; } ; -; BOTH: Function Attrs: noinline norecurse nounwind readnone uwtable +; BOTH: Function Attrs: noinline norecurse nosync nounwind readnone uwtable ; BOTH-NEXT: define double @select_and_phi(double returned %b) ; ; FNATTR: define double @select_and_phi(double %b) -; ATTRIBUTOR: define double @select_and_phi(double returned %b) +; ATTRIBUTOR: Function Attrs: noinline nosync nounwind uwtable +; ATTRIBUTOR-NEXT: define double @select_and_phi(double returned %b) define double @select_and_phi(double %b) #0 { entry: %cmp = fcmp ogt double %b, 0.000000e+00 @@ -468,11 +472,13 @@ ; return b == 0? b : x; ; } ; -; BOTH: Function Attrs: noinline nounwind readnone uwtable +; BOTH: Function Attrs: noinline nosync nounwind readnone uwtable ; BOTH-NEXT: define double @recursion_select_and_phi(i32 %a, double returned %b) ; ; FNATTR: define double @recursion_select_and_phi(i32 %a, double %b) -; ATTRIBUTOR: define double @recursion_select_and_phi(i32 %a, double returned %b) +; +; ATTRIBUTOR: Function Attrs: noinline nosync nounwind uwtable +; ATTRIBUTOR-NEXT: define double @recursion_select_and_phi(i32 %a, double returned %b) define double @recursion_select_and_phi(i32 %a, double %b) #0 { entry: %dec = add nsw i32 %a, -1 @@ -497,11 +503,13 @@ ; return (double*)b; ; } ; -; BOTH: Function Attrs: noinline norecurse nounwind readnone uwtable +; BOTH: Function Attrs: noinline norecurse nosync nounwind readnone uwtable ; BOTH-NEXT: define double* @bitcast(i32* readnone returned %b) ; ; FNATTR: define double* @bitcast(i32* readnone %b) -; ATTRIBUTOR: define double* @bitcast(i32* returned %b) +; +; ATTRIBUTOR: Function Attrs: noinline nosync nounwind uwtable +; ATTRIBUTOR-NEXT: define double* @bitcast(i32* returned %b) define double* @bitcast(i32* %b) #0 { entry: %bc0 = bitcast i32* %b to double* @@ -518,11 +526,13 @@ ; return b != 0 ? b : x; ; } ; -; BOTH: Function Attrs: noinline norecurse nounwind readnone uwtable +; BOTH: Function Attrs: noinline norecurse nosync nounwind readnone uwtable ; BOTH-NEXT: define double* @bitcasts_select_and_phi(i32* readnone returned %b) ; ; FNATTR: define double* @bitcasts_select_and_phi(i32* readnone %b) -; ATTRIBUTOR: define double* @bitcasts_select_and_phi(i32* returned %b) +; +; ATTRIBUTOR: Function Attrs: noinline nosync nounwind uwtable +; ATTRIBUTOR-NEXT: define double* @bitcasts_select_and_phi(i32* returned %b) define double* @bitcasts_select_and_phi(i32* %b) #0 { entry: %bc0 = bitcast i32* %b to double* @@ -554,11 +564,13 @@ ; /* return undef */ ; } ; -; BOTH: Function Attrs: noinline norecurse nounwind readnone uwtable +; BOTH: Function Attrs: noinline norecurse nosync nounwind readnone uwtable ; BOTH-NEXT: define double* @ret_arg_arg_undef(i32* readnone returned %b) ; ; FNATTR: define double* @ret_arg_arg_undef(i32* readnone %b) -; ATTRIBUTOR: define double* @ret_arg_arg_undef(i32* returned %b) +; +; ATTRIBUTOR: Function Attrs: noinline nosync nounwind uwtable +; ATTRIBUTOR-NEXT: define double* @ret_arg_arg_undef(i32* returned %b) define double* @ret_arg_arg_undef(i32* %b) #0 { entry: %bc0 = bitcast i32* %b to double* @@ -590,11 +602,13 @@ ; /* return undef */ ; } ; -; BOTH: Function Attrs: noinline norecurse nounwind readnone uwtable +; BOTH: Function Attrs: noinline norecurse nosync nounwind readnone uwtable ; BOTH-NEXT: define double* @ret_undef_arg_arg(i32* readnone returned %b) ; ; FNATTR: define double* @ret_undef_arg_arg(i32* readnone %b) -; ATTRIBUTOR: define double* @ret_undef_arg_arg(i32* returned %b) +; +; ATTRIBUTOR: Function Attrs: noinline nosync nounwind uwtable +; ATTRIBUTOR-NEXT: define double* @ret_undef_arg_arg(i32* returned %b) define double* @ret_undef_arg_arg(i32* %b) #0 { entry: %bc0 = bitcast i32* %b to double* @@ -626,7 +640,7 @@ ; /* return undef */ ; } ; -; BOTH: Function Attrs: noinline norecurse nounwind readnone uwtable +; BOTH: Function Attrs: noinline norecurse nosync nounwind readnone uwtable ; BOTH-NEXT: define double* @ret_undef_arg_undef(i32* readnone returned %b) ; ; FNATTR: define double* @ret_undef_arg_undef(i32* readnone %b) @@ -730,8 +744,8 @@ attributes #0 = { noinline nounwind uwtable } ; BOTH-NOT: attributes # -; BOTH-DAG: attributes #{{[0-9]*}} = { noinline norecurse nounwind readnone uwtable } -; BOTH-DAG: attributes #{{[0-9]*}} = { noinline nounwind readnone uwtable } -; BOTH-DAG: attributes #{{[0-9]*}} = { noinline nounwind readonly uwtable } +; BOTH-DAG: attributes #{{[0-9]*}} = { noinline norecurse nosync nounwind readnone uwtable } +; BOTH-DAG: attributes #{{[0-9]*}} = { noinline nosync nounwind readnone uwtable } +; BOTH-DAG: attributes #{{[0-9]*}} = { noinline nosync nounwind readonly uwtable } ; BOTH-DAG: attributes #{{[0-9]*}} = { noinline nounwind uwtable } ; BOTH-NOT: attributes # Index: llvm/trunk/test/Transforms/FunctionAttrs/fn_noreturn.ll =================================================================== --- llvm/trunk/test/Transforms/FunctionAttrs/fn_noreturn.ll +++ llvm/trunk/test/Transforms/FunctionAttrs/fn_noreturn.ll @@ -20,7 +20,7 @@ ; } ; ; FIXME: no-return missing -; CHECK: Function Attrs: noinline nounwind readnone uwtable +; CHECK: Function Attrs: noinline nosync nounwind readnone uwtable ; CHECK: define void @srec0() ; define void @srec0() #0 { @@ -37,7 +37,7 @@ ; } ; ; FIXME: no-return missing -; CHECK: Function Attrs: noinline nounwind readnone uwtable +; CHECK: Function Attrs: noinline nosync nounwind readnone uwtable ; CHECK: define i32 @srec16(i32 %a) ; define i32 @srec16(i32 %a) #0 { @@ -69,7 +69,7 @@ ; } ; ; FIXME: no-return missing -; CHECK: Function Attrs: noinline norecurse nounwind readnone uwtable +; CHECK: Function Attrs: noinline norecurse nosync nounwind readnone uwtable ; CHECK: define i32 @endless_loop(i32 %a) ; define i32 @endless_loop(i32 %a) #0 { @@ -89,7 +89,7 @@ ; } ; ; FIXME: no-return missing -; CHECK: Function Attrs: noinline norecurse nounwind readnone uwtable +; CHECK: Function Attrs: noinline norecurse nosync nounwind readnone uwtable ; CHECK: define i32 @dead_return(i32 returned %a) ; define i32 @dead_return(i32 %a) #0 { @@ -111,7 +111,7 @@ ; } ; ; FIXME: no-return missing -; CHECK: Function Attrs: noinline nounwind readnone uwtable +; CHECK: Function Attrs: noinline nosync nounwind readnone uwtable ; CHECK: define i32 @multiple_noreturn_calls(i32 %a) ; define i32 @multiple_noreturn_calls(i32 %a) #0 { Index: llvm/trunk/test/Transforms/FunctionAttrs/nosync.ll =================================================================== --- llvm/trunk/test/Transforms/FunctionAttrs/nosync.ll +++ llvm/trunk/test/Transforms/FunctionAttrs/nosync.ll @@ -0,0 +1,352 @@ +; RUN: opt -functionattrs -S < %s | FileCheck %s --check-prefix=FNATTR +; RUN: opt -attributor -attributor-disable=false -S < %s | FileCheck %s --check-prefix=ATTRIBUTOR +target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128" + +; Test cases designed for the nosync function attribute. +; FIXME's are used to indicate problems and missing attributes. + +; struct RT { +; char A; +; int B[10][20]; +; char C; +; }; +; struct ST { +; int X; +; double Y; +; struct RT Z; +; }; +; +; int *foo(struct ST *s) { +; return &s[1].Z.B[5][13]; +; } + +; TEST 1 +; non-convergent and readnone implies nosync +%struct.RT = type { i8, [10 x [20 x i32]], i8 } +%struct.ST = type { i32, double, %struct.RT } + +; FNATTR: Function Attrs: norecurse nounwind optsize readnone ssp uwtable +; FNATTR-NEXT: define nonnull i32* @foo(%struct.ST* readnone %s) +; ATTRIBUTOR: Function Attrs: nosync nounwind optsize readnone ssp uwtable +; ATTRIBUTOR-NEXT: define i32* @foo(%struct.ST* %s) +define i32* @foo(%struct.ST* %s) nounwind uwtable readnone optsize ssp { +entry: + %arrayidx = getelementptr inbounds %struct.ST, %struct.ST* %s, i64 1, i32 2, i32 1, i64 5, i64 13 + ret i32* %arrayidx +} + +; TEST 2 +; atomic load with monotonic ordering +; int load_monotonic(_Atomic int *num) { +; int n = atomic_load_explicit(num, memory_order_relaxed); +; return n; +; } + +; FNATTR: Function Attrs: nofree norecurse nounwind uwtable +; FNATTR-NEXT: define i32 @load_monotonic(i32* nocapture readonly) +; ATTRIBUTOR: Function Attrs: norecurse nosync nounwind uwtable +; ATTRIBUTOR-NEXT: define i32 @load_monotonic(i32* nocapture readonly) +define i32 @load_monotonic(i32* nocapture readonly) norecurse nounwind uwtable { + %2 = load atomic i32, i32* %0 monotonic, align 4 + ret i32 %2 +} + + +; TEST 3 +; atomic store with monotonic ordering. +; void store_monotonic(_Atomic int *num) { +; atomic_load_explicit(num, memory_order_relaxed); +; } + +; FNATTR: Function Attrs: nofree norecurse nounwind uwtable +; FNATTR-NEXT: define void @store_monotonic(i32* nocapture) +; ATTRIBUTOR: Function Attrs: norecurse nosync nounwind uwtable +; ATTRIBUTOR-NEXT: define void @store_monotonic(i32* nocapture) +define void @store_monotonic(i32* nocapture) norecurse nounwind uwtable { + store atomic i32 10, i32* %0 monotonic, align 4 + ret void +} + +; TEST 4 - negative, should not deduce nosync +; atomic load with acquire ordering. +; int load_acquire(_Atomic int *num) { +; int n = atomic_load_explicit(num, memory_order_acquire); +; return n; +; } + +; FNATTR: Function Attrs: nofree norecurse nounwind uwtable +; FNATTR-NEXT: define i32 @load_acquire(i32* nocapture readonly) +; ATTRIBUTOR: Function Attrs: norecurse nounwind uwtable +; ATTRIBUTOR-NOT: nosync +; ATTRIBUTOR-NEXT: define i32 @load_acquire(i32* nocapture readonly) +define i32 @load_acquire(i32* nocapture readonly) norecurse nounwind uwtable { + %2 = load atomic i32, i32* %0 acquire, align 4 + ret i32 %2 +} + +; TEST 5 - negative, should not deduce nosync +; atomic load with release ordering +; void load_release(_Atomic int *num) { +; atomic_store_explicit(num, 10, memory_order_release); +; } + +; FNATTR: Function Attrs: nofree norecurse nounwind uwtable +; FNATTR-NEXT: define void @load_release(i32* nocapture) +; ATTRIBUTOR: Function Attrs: norecurse nounwind uwtable +; ATTRIBUTOR-NOT: nosync +; ATTRIBUTOR-NEXT: define void @load_release(i32* nocapture) +define void @load_release(i32* nocapture) norecurse nounwind uwtable { + store atomic volatile i32 10, i32* %0 release, align 4 + ret void +} + +; TEST 6 - negative volatile, relaxed atomic + +; FNATTR: Function Attrs: nofree norecurse nounwind uwtable +; FNATTR-NEXT: define void @load_volatile_release(i32* nocapture) +; ATTRIBUTOR: Function Attrs: norecurse nounwind uwtable +; ATTRIBUTOR-NOT: nosync +; ATTRIBUTOR-NEXT: define void @load_volatile_release(i32* nocapture) +define void @load_volatile_release(i32* nocapture) norecurse nounwind uwtable { + store atomic volatile i32 10, i32* %0 release, align 4 + ret void +} + +; TEST 7 - negative, should not deduce nosync +; volatile store. +; void volatile_store(volatile int *num) { +; *num = 14; +; } + +; FNATTR: Function Attrs: nofree norecurse nounwind uwtable +; FNATTR-NEXT: define void @volatile_store(i32*) +; ATTRIBUTOR: Function Attrs: norecurse nounwind uwtable +; ATTRIBUTOR-NOT: nosync +; ATTRIBUTOR-NEXT: define void @volatile_store(i32*) +define void @volatile_store(i32*) norecurse nounwind uwtable { + store volatile i32 14, i32* %0, align 4 + ret void +} + +; TEST 8 - negative, should not deduce nosync +; volatile load. +; int volatile_load(volatile int *num) { +; int n = *num; +; return n; +; } + +; FNATTR: Function Attrs: nofree norecurse nounwind uwtable +; FNATTR-NEXT: define i32 @volatile_load(i32*) +; ATTRIBUTOR: Function Attrs: norecurse nounwind uwtable +; ATTRIBUTOR-NOT: nosync +; ATTRIBUTOR-NEXT: define i32 @volatile_load(i32*) +define i32 @volatile_load(i32*) norecurse nounwind uwtable { + %2 = load volatile i32, i32* %0, align 4 + ret i32 %2 +} + +; TEST 9 + +; FNATTR: Function Attrs: noinline nosync nounwind uwtable +; FNATTR-NEXT: declare void @nosync_function() +; ATTRIBUTOR: Function Attrs: noinline nosync nounwind uwtable +; ATTRIBUTOR-NEXT: declare void @nosync_function() +declare void @nosync_function() noinline nounwind uwtable nosync + +; FNATTR: Function Attrs: noinline nounwind uwtable +; FNATTR-NEXT: define void @call_nosync_function() +; ATTRIBUTOR: Function Attrs: noinline nosync nounwind uwtable +; ATTRIBUTOR-next: define void @call_nosync_function() +define void @call_nosync_function() nounwind uwtable noinline { + tail call void @nosync_function() noinline nounwind uwtable + ret void +} + +; TEST 10 - negative, should not deduce nosync + +; FNATTR: Function Attrs: noinline nounwind uwtable +; FNATTR-NEXT: declare void @might_sync() +; ATTRIBUTOR: Function Attrs: noinline nounwind uwtable +; ATTRIBUTOR-NEXT: declare void @might_sync() +declare void @might_sync() noinline nounwind uwtable + +; FNATTR: Function Attrs: noinline nounwind uwtable +; FNATTR-NEXT: define void @call_might_sync() +; ATTRIBUTOR: Function Attrs: noinline nounwind uwtable +; ATTRIBUTOR-NOT: nosync +; ATTRIBUTOR-NEXT: define void @call_might_sync() +define void @call_might_sync() nounwind uwtable noinline { + tail call void @might_sync() noinline nounwind uwtable + ret void +} + +; TEST 11 - negative, should not deduce nosync +; volatile operation in same scc. Call volatile_load defined in TEST 8. + +; FNATTR: Function Attrs: nofree noinline nounwind uwtable +; FNATTR-NEXT: define i32 @scc1(i32*) +; ATTRIBUTOR: Function Attrs: noinline nounwind uwtable +; ATTRIBUTOR-NOT: nosync +; ATTRIBUTOR-NEXT: define i32 @scc1(i32*) +define i32 @scc1(i32*) noinline nounwind uwtable { + tail call void @scc2(i32* %0); + %val = tail call i32 @volatile_load(i32* %0); + ret i32 %val; +} + +; FNATTR: Function Attrs: nofree noinline nounwind uwtable +; FNATTR-NEXT: define void @scc2(i32*) +; ATTRIBUTOR: Function Attrs: noinline nounwind uwtable +; ATTRIBUTOR-NOT: nosync +; ATTRIBUTOR-NEXT: define void @scc2(i32*) +define void @scc2(i32*) noinline nounwind uwtable { + tail call i32 @scc1(i32* %0); + ret void; +} + +; TEST 12 - fences, negative +; +; void foo1(int *a, std::atomic flag){ +; *a = 100; +; atomic_thread_fence(std::memory_order_release); +; flag.store(true, std::memory_order_relaxed); +; } +; +; void bar(int *a, std::atomic flag){ +; while(!flag.load(std::memory_order_relaxed)) +; ; +; +; atomic_thread_fence(std::memory_order_acquire); +; int b = *a; +; } + +%"struct.std::atomic" = type { %"struct.std::__atomic_base" } +%"struct.std::__atomic_base" = type { i8 } + +; FNATTR: Function Attrs: nofree norecurse nounwind +; FNATTR-NEXT: define void @foo1(i32* nocapture, %"struct.std::atomic"* nocapture) +; ATTRIBUTOR-NOT: nosync +; ATTRIBUTOR: define void @foo1(i32*, %"struct.std::atomic"*) +define void @foo1(i32*, %"struct.std::atomic"*) { + store i32 100, i32* %0, align 4 + fence release + %3 = getelementptr inbounds %"struct.std::atomic", %"struct.std::atomic"* %1, i64 0, i32 0, i32 0 + store atomic i8 1, i8* %3 monotonic, align 1 + ret void +} + +; FNATTR: Function Attrs: nofree norecurse nounwind +; FNATTR-NEXT: define void @bar(i32* nocapture readnone, %"struct.std::atomic"* nocapture readonly) +; ATTRIBUTOR-NOT: nosync +; ATTRIBUTOR: define void @bar(i32*, %"struct.std::atomic"*) +define void @bar(i32 *, %"struct.std::atomic"*) { + %3 = getelementptr inbounds %"struct.std::atomic", %"struct.std::atomic"* %1, i64 0, i32 0, i32 0 + br label %4 + +4: ; preds = %4, %2 + %5 = load atomic i8, i8* %3 monotonic, align 1 + %6 = and i8 %5, 1 + %7 = icmp eq i8 %6, 0 + br i1 %7, label %4, label %8 + +8: ; preds = %4 + fence acquire + ret void +} + +; TEST 13 - Fence syncscope("singlethread") seq_cst +; FNATTR: Function Attrs: nofree norecurse nounwind +; FNATTR-NEXT: define void @foo1_singlethread(i32* nocapture, %"struct.std::atomic"* nocapture) +; ATTRIBUTOR: Function Attrs: nosync +; ATTRIBUTOR: define void @foo1_singlethread(i32*, %"struct.std::atomic"*) +define void @foo1_singlethread(i32*, %"struct.std::atomic"*) { + store i32 100, i32* %0, align 4 + fence syncscope("singlethread") release + %3 = getelementptr inbounds %"struct.std::atomic", %"struct.std::atomic"* %1, i64 0, i32 0, i32 0 + store atomic i8 1, i8* %3 monotonic, align 1 + ret void +} + +; FNATTR: Function Attrs: nofree norecurse nounwind +; FNATTR-NEXT: define void @bar_singlethread(i32* nocapture readnone, %"struct.std::atomic"* nocapture readonly) +; ATTRIBUTOR: Function Attrs: nosync +; ATTRIBUTOR: define void @bar_singlethread(i32*, %"struct.std::atomic"*) +define void @bar_singlethread(i32 *, %"struct.std::atomic"*) { + %3 = getelementptr inbounds %"struct.std::atomic", %"struct.std::atomic"* %1, i64 0, i32 0, i32 0 + br label %4 + +4: ; preds = %4, %2 + %5 = load atomic i8, i8* %3 monotonic, align 1 + %6 = and i8 %5, 1 + %7 = icmp eq i8 %6, 0 + br i1 %7, label %4, label %8 + +8: ; preds = %4 + fence syncscope("singlethread") acquire + ret void +} + +declare void @llvm.memcpy(i8* %dest, i8* %src, i32 %len, i1 %isvolatile) +declare void @llvm.memset(i8* %dest, i8 %val, i32 %len, i1 %isvolatile) + +; TEST 14 - negative, checking volatile intrinsics. + +; ATTRIBUTOR: Function Attrs: nounwind +; ATTRIBUTOR-NOT: nosync +; ATTRIBUTOR-NEXT: define i32 @memcpy_volatile(i8* %ptr1, i8* %ptr2) +define i32 @memcpy_volatile(i8* %ptr1, i8* %ptr2) { + call void @llvm.memcpy(i8* %ptr1, i8* %ptr2, i32 8, i1 1) + ret i32 4 +} + +; TEST 15 - positive, non-volatile intrinsic. + +; ATTRIBUTOR: Function Attrs: nosync +; ATTRIBUTOR-NEXT: define i32 @memset_non_volatile(i8* %ptr1, i8 %val) +define i32 @memset_non_volatile(i8* %ptr1, i8 %val) { + call void @llvm.memset(i8* %ptr1, i8 %val, i32 8, i1 0) + ret i32 4 +} + +; TEST 16 - negative, inline assembly. + +; ATTRIBUTOR: define i32 @inline_asm_test(i32 %x) +define i32 @inline_asm_test(i32 %x) { + call i32 asm "bswap $0", "=r,r"(i32 %x) + ret i32 4 +} + +declare void @readnone_test() convergent readnone + +; ATTRIBUTOR: define void @convergent_readnone() +; TEST 17 - negative. Convergent +define void @convergent_readnone(){ + call void @readnone_test() + ret void +} + +; ATTRIBUTOR: Function Attrs: nounwind +; ATTRIBUTOR-NEXT: declare void @llvm.x86.sse2.clflush(i8*) +declare void @llvm.x86.sse2.clflush(i8*) +@a = common global i32 0, align 4 + +; TEST 18 - negative. Synchronizing intrinsic + +; ATTRIBUTOR: Function Attrs: nounwind +; ATTRIBUTOR-NOT: nosync +; ATTRIBUTOR-NEXT: define void @i_totally_sync() +define void @i_totally_sync() { + tail call void @llvm.x86.sse2.clflush(i8* bitcast (i32* @a to i8*)) + ret void +} + +declare float @llvm.cos(float %val) readnone + +; TEST 19 - positive, readnone & non-convergent intrinsic. + +; ATTRIBUTOR: Function Attrs: nosync nounwind +; ATTRIBUTOR-NEXT: define i32 @cos_test(float %x) +define i32 @cos_test(float %x) { + call float @llvm.cos(float %x) + ret i32 4 +} Index: llvm/trunk/test/Transforms/FunctionAttrs/nounwind.ll =================================================================== --- llvm/trunk/test/Transforms/FunctionAttrs/nounwind.ll +++ llvm/trunk/test/Transforms/FunctionAttrs/nounwind.ll @@ -4,7 +4,7 @@ ; TEST 1 ; CHECK: Function Attrs: norecurse nounwind readnone ; CHECK-NEXT: define i32 @foo1() -; ATTRIBUTOR: Function Attrs: nounwind +; ATTRIBUTOR: Function Attrs: nosync nounwind ; ATTRIBUTOR-NEXT: define i32 @foo1() define i32 @foo1() { ret i32 1 @@ -13,7 +13,7 @@ ; TEST 2 ; CHECK: Function Attrs: nounwind readnone ; CHECK-NEXT: define i32 @scc1_foo() -; ATTRIBUTOR: Function Attrs: nounwind +; ATTRIBUTOR: Function Attrs: nosync nounwind ; ATTRIBUTOR-NEXT: define i32 @scc1_foo() define i32 @scc1_foo() { %1 = call i32 @scc1_bar() @@ -24,7 +24,7 @@ ; TEST 3 ; CHECK: Function Attrs: nounwind readnone ; CHECK-NEXT: define i32 @scc1_bar() -; ATTRIBUTOR: Function Attrs: nounwind +; ATTRIBUTOR: Function Attrs: nosync nounwind ; ATTRIBUTOR-NEXT: define i32 @scc1_bar() define i32 @scc1_bar() { %1 = call i32 @scc1_foo() Index: llvm/trunk/test/Transforms/FunctionAttrs/read_write_returned_arguments_scc.ll =================================================================== --- llvm/trunk/test/Transforms/FunctionAttrs/read_write_returned_arguments_scc.ll +++ llvm/trunk/test/Transforms/FunctionAttrs/read_write_returned_arguments_scc.ll @@ -30,7 +30,7 @@ ; target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128" -; CHECK: Function Attrs: nofree nounwind +; CHECK: Function Attrs: nofree nosync nounwind ; CHECK-NEXT: define i32* @external_ret2_nrw(i32* %n0, i32* %r0, i32* returned %w0) define i32* @external_ret2_nrw(i32* %n0, i32* %r0, i32* %w0) { entry: @@ -41,7 +41,7 @@ ret i32* %call3 } -; CHECK: Function Attrs: nofree nounwind +; CHECK: Function Attrs: nofree nosync nounwind ; CHECK-NEXT: define internal i32* @internal_ret0_nw(i32* returned %n0, i32* %w0) define internal i32* @internal_ret0_nw(i32* %n0, i32* %w0) { entry: @@ -70,7 +70,7 @@ ret i32* %retval.0 } -; CHECK: Function Attrs: nofree nounwind +; CHECK: Function Attrs: nofree nosync nounwind ; CHECK-NEXT: define internal i32* @internal_ret1_rrw(i32* %r0, i32* returned %r1, i32* %w0) define internal i32* @internal_ret1_rrw(i32* %r0, i32* %r1, i32* %w0) { entry: @@ -102,7 +102,7 @@ ret i32* %retval.0 } -; CHECK: Function Attrs: nofree norecurse nounwind +; CHECK: Function Attrs: nofree norecurse nosync nounwind ; CHECK-NEXT: define i32* @external_sink_ret2_nrw(i32* readnone %n0, i32* nocapture readonly %r0, i32* returned %w0) define i32* @external_sink_ret2_nrw(i32* %n0, i32* %r0, i32* %w0) { entry: @@ -121,7 +121,7 @@ ret i32* %w0 } -; CHECK: Function Attrs: nofree nounwind +; CHECK: Function Attrs: nofree nosync nounwind ; CHECK-NEXT: define internal i32* @internal_ret1_rw(i32* %r0, i32* returned %w0) define internal i32* @internal_ret1_rw(i32* %r0, i32* %w0) { entry: @@ -147,7 +147,7 @@ ret i32* %retval.0 } -; CHECK: Function Attrs: nofree nounwind +; CHECK: Function Attrs: nofree nosync nounwind ; CHECK-NEXT: define i32* @external_source_ret2_nrw(i32* %n0, i32* %r0, i32* returned %w0) define i32* @external_source_ret2_nrw(i32* %n0, i32* %r0, i32* %w0) { entry: @@ -160,6 +160,6 @@ ; for a subset relation. ; ; CHECK-NOT: attributes # -; CHECK: attributes #{{.*}} = { nofree nounwind } -; CHECK: attributes #{{.*}} = { nofree norecurse nounwind } +; CHECK: attributes #{{.*}} = { nofree nosync nounwind } +; CHECK: attributes #{{.*}} = { nofree norecurse nosync nounwind } ; CHECK-NOT: attributes #