Index: llvm/trunk/docs/LangRef.rst =================================================================== --- llvm/trunk/docs/LangRef.rst +++ llvm/trunk/docs/LangRef.rst @@ -1481,6 +1481,15 @@ - An operand bundle at a call site cannot change the implementation of the called function. Inter-procedural optimizations work as usual as long as they take into account the first two properties. +- The bundle operands for an unknown operand bundle escape in unknown + ways before control is transferred to the callee or invokee. +- Calls and invokes with operand bundles have unknown read / write + effect on the heap on entry and exit (even if the call target is + ``readnone`` or ``readonly``), unless they're overriden with + callsite specific attributes. +- An operand bundle at a call site cannot change the implementation + of the called function. Inter-procedural optimizations work as + usual as long as they take into account the first two properties. .. _moduleasm: Index: llvm/trunk/include/llvm/IR/InstrTypes.h =================================================================== --- llvm/trunk/include/llvm/IR/InstrTypes.h +++ llvm/trunk/include/llvm/IR/InstrTypes.h @@ -18,6 +18,7 @@ #include "llvm/ADT/Optional.h" #include "llvm/ADT/Twine.h" +#include "llvm/IR/Attributes.h" #include "llvm/IR/DerivedTypes.h" #include "llvm/IR/Instruction.h" #include "llvm/IR/OperandTraits.h" @@ -1232,8 +1233,50 @@ return None; } + /// \brief Return true if this operand bundle user has operand bundles that + /// may read from the heap. + bool hasReadingOperandBundles() const { + // Implementation note: this is a conservative implementation of operand + // bundle semantics, where *any* operand bundle forces a callsite to be at + // least readonly. + return hasOperandBundles(); + } + + /// \brief Return true if this operand bundle user has operand bundles that + /// may write to the heap. + bool hasClobberingOperandBundles() const { + // Implementation note: this is a conservative implementation of operand + // bundle semantics, where *any* operand bundle forces a callsite to be + // read-write. + return hasOperandBundles(); + } protected: + /// \brief Is the function attribute S disallowed by some operand bundle on + /// this operand bundle user? + bool isFnAttrDisallowedByOpBundle(StringRef S) const { + // Operand bundles only possibly disallow readnone and readonly attributes. + // All String attributes are fine. + return false; + } + + /// \brief Is the function attribute A disallowed by some operand bundle on + /// this operand bundle user? + bool isFnAttrDisallowedByOpBundle(Attribute::AttrKind A) const { + switch (A) { + default: + return false; + + case Attribute::ReadNone: + return hasReadingOperandBundles(); + + case Attribute::ReadOnly: + return hasClobberingOperandBundles(); + } + + llvm_unreachable("switch has a default case!"); + } + /// \brief Used to keep track of an operand bundle. See the main comment on /// OperandBundleUser above. struct BundleOpInfo { Index: llvm/trunk/include/llvm/IR/Instructions.h =================================================================== --- llvm/trunk/include/llvm/IR/Instructions.h +++ llvm/trunk/include/llvm/IR/Instructions.h @@ -1742,6 +1742,12 @@ template bool hasFnAttrImpl(AttrKind A) const { if (AttributeList.hasAttribute(AttributeSet::FunctionIndex, A)) return true; + + // Operand bundles override attributes on the called function, but don't + // override attributes directly present on the call instruction. + if (isFnAttrDisallowedByOpBundle(A)) + return false; + if (const Function *F = getCalledFunction()) return F->getAttributes().hasAttribute(AttributeSet::FunctionIndex, A); return false; Index: llvm/trunk/lib/IR/Instructions.cpp =================================================================== --- llvm/trunk/lib/IR/Instructions.cpp +++ llvm/trunk/lib/IR/Instructions.cpp @@ -563,6 +563,12 @@ bool InvokeInst::hasFnAttrImpl(Attribute::AttrKind A) const { if (AttributeList.hasAttribute(AttributeSet::FunctionIndex, A)) return true; + + // Operand bundles override attributes on the called function, but don't + // override attributes directly present on the invoke instruction. + if (isFnAttrDisallowedByOpBundle(A)) + return false; + if (const Function *F = getCalledFunction()) return F->getAttributes().hasAttribute(AttributeSet::FunctionIndex, A); return false; Index: llvm/trunk/test/Feature/OperandBundles/adce.ll =================================================================== --- llvm/trunk/test/Feature/OperandBundles/adce.ll +++ llvm/trunk/test/Feature/OperandBundles/adce.ll @@ -0,0 +1,41 @@ +; RUN: opt -S -adce < %s | FileCheck %s + +; While it is normally okay to DCE out calls to @readonly_function and +; @readnone_function, we cannot do that if they're carrying operand +; bundles since the presence of unknown operand bundles implies +; arbitrary memory effects. + +declare void @readonly_function() readonly nounwind +declare void @readnone_function() readnone nounwind + +define void @test0() { +; CHECK-LABEL: @test0( + entry: + call void @readonly_function() [ "tag"() ] +; CHECK: call void @readonly_function + ret void +} + +define void @test1() { +; CHECK-LABEL: @test1( + entry: + call void @readnone_function() [ "tag"() ] +; CHECK: call void @readnone_function + ret void +} + +define void @test2() { +; CHECK-LABEL: @test2( + entry: +; CHECK-NOT: @readonly_function( + call void @readonly_function() readonly [ "tag"() ] + ret void +} + +define void @test3() { +; CHECK-LABEL: @test3( + entry: +; CHECK-NOT: @readnone_function( + call void @readnone_function() readnone [ "tag"() ] + ret void +} Index: llvm/trunk/test/Feature/OperandBundles/early-cse.ll =================================================================== --- llvm/trunk/test/Feature/OperandBundles/early-cse.ll +++ llvm/trunk/test/Feature/OperandBundles/early-cse.ll @@ -0,0 +1,71 @@ +; RUN: opt -S -early-cse < %s | FileCheck %s + +; While it is normally okay to do memory optimizations over calls to +; @readonly_function and @readnone_function, we cannot do that if +; they're carrying operand bundles since the presence of unknown +; operand bundles implies arbitrary memory effects. + +declare void @readonly_function() readonly nounwind +declare void @readnone_function() readnone nounwind + +define i32 @test0(i32* %x) { +; CHECK-LABEL: @test0( + entry: + store i32 100, i32* %x +; CHECK: store i32 100, i32* %x + call void @readonly_function() [ "tag"() ] +; CHECK: call void @readonly_function() + + %v = load i32, i32* %x +; CHECK: %v = load i32, i32* %x +; CHECK: ret i32 %v + ret i32 %v +} + +define i32 @test1(i32* %x) { +; CHECK: @test1( + entry: + store i32 100, i32* %x +; CHECK: store i32 100, i32* %x + call void @readonly_function() readonly [ "tag"() ] +; CHECK-NOT: call void @readonly_function + %v = load i32, i32* %x + ret i32 %v +; CHECK: ret i32 100 +} + +define i32 @test3(i32* %x) { +; CHECK-LABEL: @test3( + entry: + store i32 100, i32* %x +; CHECK: store i32 100, i32* %x + call void @readonly_function() +; CHECK-NOT: call void @readonly_function + %v = load i32, i32* %x + ret i32 %v +; CHECK: ret i32 100 +} + +define void @test4(i32* %x) { +; CHECK-LABEL: @test4( + entry: + store i32 100, i32* %x +; CHECK: store i32 100, i32* %x + call void @readnone_function() [ "tag"() ] +; CHECK: call void @readnone_function + store i32 200, i32* %x +; CHECK: store i32 200, i32* %x + ret void +} + +define void @test5(i32* %x) { +; CHECK-LABEL: @test5( + entry: + store i32 100, i32* %x +; CHECK-NOT: store i32 100, i32* %x +; CHECK-NOT: call void @readnone_function + call void @readnone_function() readnone [ "tag"() ] + store i32 200, i32* %x +; CHECK: store i32 200, i32* %x + ret void +}