diff --git a/llvm/include/llvm/IR/InstrTypes.h b/llvm/include/llvm/IR/InstrTypes.h --- a/llvm/include/llvm/IR/InstrTypes.h +++ b/llvm/include/llvm/IR/InstrTypes.h @@ -1570,6 +1570,10 @@ setAttributes(PAL); } + /// Strip context-sensitive attributes that may not be valid in the location + /// the call is hoisted to. + void stripParamAttrAfterHoist(); + /// adds the dereferenceable attribute to the list of attributes. void addDereferenceableAttr(unsigned i, uint64_t Bytes) { AttributeList PAL = getAttributes(); diff --git a/llvm/lib/IR/Instructions.cpp b/llvm/lib/IR/Instructions.cpp --- a/llvm/lib/IR/Instructions.cpp +++ b/llvm/lib/IR/Instructions.cpp @@ -335,6 +335,34 @@ return nullptr; } +void CallBase::stripParamAttrAfterHoist() { + AttributeList AL = getAttributes(); + if (AL.isEmpty()) + return; + // Whitelist the set of attributes we should strip from the call, if these + // are present. We do not need to worry if these are global properties that is + // being stripped, i.e. valid everywhere since they are present on function + // declaration. We always check for these parameter attributes on function + // declaration if they are not present at call. + // We use a list to avoid illegally stripping attributes that are ABI related, such + // as signext and zeroext. + + // Whitelist attributes we should strip. + static constexpr Attribute::AttrKind ParamAttrsToStrip[] = { + Attribute::NonNull, Attribute::Alignment, Attribute::NoAlias, + Attribute::Dereferenceable}; + for (unsigned ArgNo = 0; ArgNo < getNumArgOperands(); ArgNo++) { + auto PAttr = AL.getParamAttributes(ArgNo); + // List of attributes that are present in parameter that we need to remove. + AttrBuilder R; + auto Index = ArgNo + AttributeList::FirstArgIndex; + for (auto Attr : ParamAttrsToStrip) + if (AL.hasAttribute(Index, Attr)) + R.addAttribute(Attr); + setAttributes(getAttributes().removeAttributes(getContext(), Index, R)); + } +} + /// Determine whether the argument or parameter has the given attribute. bool CallBase::paramHasAttr(unsigned ArgNo, Attribute::AttrKind Kind) const { assert(ArgNo < getNumArgOperands() && "Param index out of bounds!"); diff --git a/llvm/lib/Transforms/Scalar/LICM.cpp b/llvm/lib/Transforms/Scalar/LICM.cpp --- a/llvm/lib/Transforms/Scalar/LICM.cpp +++ b/llvm/lib/Transforms/Scalar/LICM.cpp @@ -1789,6 +1789,13 @@ !SafetyInfo->isGuaranteedToExecute(I, DT, CurLoop)) I.dropUnknownNonDebugMetadata(); + // If I is a call and it is not guaranteed to execute in the loop, then moving + // to the preheader means we should strip parameter attributes on the call + // since we may be hoisting above conditions that allowed inferring those + // attributes. They may not be valid at the preheader. + if (isa(I) && !SafetyInfo->isGuaranteedToExecute(I, DT, CurLoop)) + cast(&I)->stripParamAttrAfterHoist(); + if (isa(I)) // Move the new node to the end of the phi list in the destination block. moveInstructionBefore(I, *Dest->getFirstNonPHI(), *SafetyInfo, MSSAU, SE); diff --git a/llvm/test/Transforms/LICM/call-hoisting.ll b/llvm/test/Transforms/LICM/call-hoisting.ll --- a/llvm/test/Transforms/LICM/call-hoisting.ll +++ b/llvm/test/Transforms/LICM/call-hoisting.ll @@ -23,6 +23,38 @@ ret void } +declare i32 @spec(i32* %p) readonly argmemonly nounwind speculatable + +; We should strip the nonnull callsite attribute on spec call's argument since it is +; may not be valid when hoisted to preheader. +define void @test_strip_attribute(i32* noalias %loc, i32* noalias %sink, i32* %q) { +; CHECK-LABEL: test_strip_attribute +; CHECK-LABEL: entry +; CHECK-NEXT: %ret = call i32 @load(i32* %loc) +; CHECK-NEXT: %nullchk = icmp eq i32* %q, null +; CHECK-NEXT: %ret2 = call i32 @spec(i32* %q) +entry: + br label %loop + +loop: + %iv = phi i32 [0, %entry], [%iv.next, %isnull ] + %ret = call i32 @load(i32* %loc) + %nullchk = icmp eq i32* %q, null + br i1 %nullchk, label %isnull, label %nonnullbb + +nonnullbb: + %ret2 = call i32 @spec(i32* nonnull %q) + br label %isnull + +isnull: + store volatile i32 %ret, i32* %sink + %iv.next = add i32 %iv, 1 + %cmp = icmp slt i32 %iv, 200 + br i1 %cmp, label %loop, label %exit + +exit: + ret void +} declare void @store(i32 %val, i32* %p) argmemonly writeonly nounwind