426 changes: 426 additions & 0 deletions llvm/lib/Transforms/IPO/Attributor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ STATISTIC(NumAttributesManifested,
"Number of abstract attributes manifested in IR");
STATISTIC(NumFnNoUnwind, "Number of functions marked nounwind");

STATISTIC(NumFnUniqueReturned, "Number of function with unique return");
STATISTIC(NumFnKnownReturns, "Number of function with known return values");
STATISTIC(NumFnArgumentReturned,
"Number of function arguments marked returned");

// TODO: Determine a good default value.
//
// In the LLVM-TS and SPEC2006, 32 seems to not induce compile time overheads
Expand Down Expand Up @@ -91,11 +96,97 @@ static void bookkeeping(AbstractAttribute::ManifestPosition MP,
case Attribute::NoUnwind:
NumFnNoUnwind++;
return;
case Attribute::Returned:
NumFnArgumentReturned++;
return;
default:
return;
}
}

template <typename StateTy>
using followValueCB_t = std::function<bool(Value *, StateTy &State)>;
template <typename StateTy>
using visitValueCB_t = std::function<void(Value *, StateTy &State)>;

/// Recursively visit all values that might become \p InitV at some point. This
/// will be done by looking through cast instructions, selects, phis, and calls
/// with the "returned" attribute. The callback \p FollowValueCB is asked before
/// a potential origin value is looked at. If no \p FollowValueCB is passed, a
/// default one is used that will make sure we visit every value only once. Once
/// we cannot look through the value any further, the callback \p VisitValueCB
/// is invoked and passed the current value and the \p State. To limit how much
/// effort is invested, we will never visit more than \p MaxValues values.
template <typename StateTy>
static bool genericValueTraversal(
Value *InitV, StateTy &State, visitValueCB_t<StateTy> &VisitValueCB,
followValueCB_t<StateTy> *FollowValueCB = nullptr, int MaxValues = 8) {

SmallPtrSet<Value *, 16> Visited;
followValueCB_t<bool> DefaultFollowValueCB = [&](Value *Val, bool &) {
return Visited.insert(Val).second;
};

if (!FollowValueCB)
FollowValueCB = &DefaultFollowValueCB;

SmallVector<Value *, 16> Worklist;
Worklist.push_back(InitV);

int Iteration = 0;
do {
Value *V = Worklist.pop_back_val();

// Check if we should process the current value. To prevent endless
// recursion keep a record of the values we followed!
if (!(*FollowValueCB)(V, State))
continue;

// Make sure we limit the compile time for complex expressions.
if (Iteration++ >= MaxValues)
return false;

// Explicitly look through calls with a "returned" attribute if we do
// not have a pointer as stripPointerCasts only works on them.
if (V->getType()->isPointerTy()) {
V = V->stripPointerCasts();
} else {
CallSite CS(V);
if (CS && CS.getCalledFunction()) {
Value *NewV = nullptr;
for (Argument &Arg : CS.getCalledFunction()->args())
if (Arg.hasReturnedAttr()) {
NewV = CS.getArgOperand(Arg.getArgNo());
break;
}
if (NewV) {
Worklist.push_back(NewV);
continue;
}
}
}

// Look through select instructions, visit both potential values.
if (auto *SI = dyn_cast<SelectInst>(V)) {
Worklist.push_back(SI->getTrueValue());
Worklist.push_back(SI->getFalseValue());
continue;
}

// Look through phi nodes, visit all operands.
if (auto *PHI = dyn_cast<PHINode>(V)) {
Worklist.append(PHI->op_begin(), PHI->op_end());
continue;
}

// Once a leaf is reached we inform the user through the callback.
VisitValueCB(V, State);
} while (!Worklist.empty());

// All values have been visited.
return true;
}

/// Helper to identify the correct offset into an attribute list.
static unsigned getAttrIndex(AbstractAttribute::ManifestPosition MP,
unsigned ArgNo = 0) {
Expand Down Expand Up @@ -303,6 +394,331 @@ ChangeStatus AANoUnwindFunction::updateImpl(Attributor &A) {
return ChangeStatus::UNCHANGED;
}

/// --------------------- Function Return Values -------------------------------

/// "Attribute" that collects all potential returned values and the return
/// instructions that they arise from.
///
/// If there is a unique returned value R, the manifest method will:
/// - mark R with the "returned" attribute, if R is an argument.
class AAReturnedValuesImpl final : public AAReturnedValues, AbstractState {

/// Mapping of values potentially returned by the associated function to the
/// return instructions that might return them.
DenseMap<Value *, SmallPtrSet<ReturnInst *, 2>> ReturnedValues;

/// State flags
///
///{
bool IsFixed;
bool IsValidState;
bool HasOverdefinedReturnedCalls;
///}

/// Collect values that could become \p V in the set \p Values, each mapped to
/// \p ReturnInsts.
void collectValuesRecursively(
Attributor &A, Value *V, SmallPtrSetImpl<ReturnInst *> &ReturnInsts,
DenseMap<Value *, SmallPtrSet<ReturnInst *, 2>> &Values) {

visitValueCB_t<bool> VisitValueCB = [&](Value *Val, bool &) {
assert(!isa<Instruction>(Val) ||
&getAnchorScope() == cast<Instruction>(Val)->getFunction());
Values[Val].insert(ReturnInsts.begin(), ReturnInsts.end());
};

bool UnusedBool;
bool Success = genericValueTraversal(V, UnusedBool, VisitValueCB);

// If we did abort the above traversal we haven't see all the values.
// Consequently, we cannot know if the information we would derive is
// accurate so we give up early.
if (!Success)
indicatePessimisticFixpoint();
}

public:
/// See AbstractAttribute::AbstractAttribute(...).
AAReturnedValuesImpl(Function &F, InformationCache &InfoCache)
: AAReturnedValues(F, InfoCache) {
// We do not have an associated argument yet.
AssociatedVal = nullptr;
}

/// See AbstractAttribute::initialize(...).
void initialize(Attributor &A) override {
// Reset the state.
AssociatedVal = nullptr;
IsFixed = false;
IsValidState = true;
HasOverdefinedReturnedCalls = false;
ReturnedValues.clear();

Function &F = cast<Function>(getAnchoredValue());

// The map from instruction opcodes to those instructions in the function.
auto &OpcodeInstMap = InfoCache.getOpcodeInstMapForFunction(F);

// Look through all arguments, if one is marked as returned we are done.
for (Argument &Arg : F.args()) {
if (Arg.hasReturnedAttr()) {

auto &ReturnInstSet = ReturnedValues[&Arg];
for (Instruction *RI : OpcodeInstMap[Instruction::Ret])
ReturnInstSet.insert(cast<ReturnInst>(RI));

indicateOptimisticFixpoint();
return;
}
}

// If no argument was marked as returned we look at all return instructions
// and collect potentially returned values.
for (Instruction *RI : OpcodeInstMap[Instruction::Ret]) {
SmallPtrSet<ReturnInst *, 1> RISet({cast<ReturnInst>(RI)});
collectValuesRecursively(A, cast<ReturnInst>(RI)->getReturnValue(), RISet,
ReturnedValues);
}
}

/// See AbstractAttribute::manifest(...).
virtual ChangeStatus manifest(Attributor &A) override;

/// See AbstractAttribute::getState(...).
virtual AbstractState &getState() override { return *this; }

/// See AbstractAttribute::getState(...).
virtual const AbstractState &getState() const override { return *this; }

/// See AbstractAttribute::getManifestPosition().
virtual ManifestPosition getManifestPosition() const override {
return MP_ARGUMENT;
}

/// See AbstractAttribute::updateImpl(Attributor &A).
virtual ChangeStatus updateImpl(Attributor &A) override;

/// Return the number of potential return values, -1 if unknown.
size_t getNumReturnValues() const {
return isValidState() ? ReturnedValues.size() : -1;
}

/// Return an assumed unique return value if a single candidate is found. If
/// there cannot be one, return a nullptr. If it is not clear yet, return the
/// Optional::NoneType.
Optional<Value *> getAssumedUniqueReturnValue() const;

/// See AbstractState::checkForallReturnedValues(...).
virtual bool
checkForallReturnedValues(std::function<bool(Value &)> &Pred) const override;

/// Pretty print the attribute similar to the IR representation.
virtual const std::string getAsStr() const override;

/// See AbstractState::isAtFixpoint().
bool isAtFixpoint() const override { return IsFixed; }

/// See AbstractState::isValidState().
bool isValidState() const override { return IsValidState; }

/// See AbstractState::indicateOptimisticFixpoint(...).
void indicateOptimisticFixpoint() override {
IsFixed = true;
IsValidState &= true;
}
void indicatePessimisticFixpoint() override {
IsFixed = true;
IsValidState = false;
}
};

ChangeStatus AAReturnedValuesImpl::manifest(Attributor &A) {
ChangeStatus Changed = ChangeStatus::UNCHANGED;

// Bookkeeping.
assert(isValidState());
NumFnKnownReturns++;

// Check if we have an assumed unique return value that we could manifest.
Optional<Value *> UniqueRV = getAssumedUniqueReturnValue();

if (!UniqueRV.hasValue() || !UniqueRV.getValue())
return Changed;

// Bookkeeping.
NumFnUniqueReturned++;

// If the assumed unique return value is an argument, annotate it.
if (auto *UniqueRVArg = dyn_cast<Argument>(UniqueRV.getValue())) {
AssociatedVal = UniqueRVArg;
Changed = AbstractAttribute::manifest(A) | Changed;
}

return Changed;
}

const std::string AAReturnedValuesImpl::getAsStr() const {
return (isAtFixpoint() ? "returns(#" : "may-return(#") +
(isValidState() ? std::to_string(getNumReturnValues()) : "?") + ")";
}

Optional<Value *> AAReturnedValuesImpl::getAssumedUniqueReturnValue() const {
// If checkForallReturnedValues provides a unique value, ignoring potential
// undef values that can also be present, it is assumed to be the actual
// return value and forwarded to the caller of this method. If there are
// multiple, a nullptr is returned indicating there cannot be a unique
// returned value.
Optional<Value *> UniqueRV;

std::function<bool(Value &)> Pred = [&](Value &RV) -> bool {
// If we found a second returned value and neither the current nor the saved
// one is an undef, there is no unique returned value. Undefs are special
// since we can pretend they have any value.
if (UniqueRV.hasValue() && UniqueRV != &RV &&
!(isa<UndefValue>(RV) || isa<UndefValue>(UniqueRV.getValue()))) {
UniqueRV = nullptr;
return false;
}

// Do not overwrite a value with an undef.
if (!UniqueRV.hasValue() || !isa<UndefValue>(RV))
UniqueRV = &RV;

return true;
};

if (!checkForallReturnedValues(Pred))
UniqueRV = nullptr;

return UniqueRV;
}

bool AAReturnedValuesImpl::checkForallReturnedValues(
std::function<bool(Value &)> &Pred) const {
if (!isValidState())
return false;

// Check all returned values but ignore call sites as long as we have not
// encountered an overdefined one during an update.
for (auto &It : ReturnedValues) {
Value *RV = It.first;

ImmutableCallSite ICS(RV);
if (ICS && !HasOverdefinedReturnedCalls)
continue;

if (!Pred(*RV))
return false;
}

return true;
}

ChangeStatus AAReturnedValuesImpl::updateImpl(Attributor &A) {

// Check if we know of any values returned by the associated function,
// if not, we are done.
if (getNumReturnValues() == 0) {
indicateOptimisticFixpoint();
return ChangeStatus::UNCHANGED;
}

// Check if any of the returned values is a call site we can refine.
decltype(ReturnedValues) AddRVs;
bool HasCallSite = false;

// Look at all returned call sites.
for (auto &It : ReturnedValues) {
SmallPtrSet<ReturnInst *, 2> &ReturnInsts = It.second;
Value *RV = It.first;
LLVM_DEBUG(dbgs() << "[AAReturnedValues] Potentially returned value " << *RV
<< "\n");

// Only call sites can change during an update, ignore the rest.
CallSite RetCS(RV);
if (!RetCS)
continue;

// For now, any call site we see will prevent us from directly fixing the
// state. However, if the information on the callees is fixed, the call
// sites will be removed and we will fix the information for this state.
HasCallSite = true;

// Try to find a assumed unique return value for the called function.
auto *RetCSAA = A.getAAFor<AAReturnedValuesImpl>(*this, *RV);
if (!RetCSAA || !RetCSAA->isValidState()) {
HasOverdefinedReturnedCalls = true;
LLVM_DEBUG(dbgs() << "[AAReturnedValues] Returned call site (" << *RV
<< ") with " << (RetCSAA ? "invalid" : "no")
<< " associated state\n");
continue;
}

// Try to find a assumed unique return value for the called function.
Optional<Value *> AssumedUniqueRV = RetCSAA->getAssumedUniqueReturnValue();

// If no assumed unique return value was found due to the lack of
// candidates, we may need to resolve more calls (through more update
// iterations) or the called function will not return. Either way, we simply
// stick with the call sites as return values. Because there were not
// multiple possibilities, we do not treat it as overdefined.
if (!AssumedUniqueRV.hasValue())
continue;

// If multiple, non-refinable values were found, there cannot be a unique
// return value for the called function. The returned call is overdefined!
if (!AssumedUniqueRV.getValue()) {
HasOverdefinedReturnedCalls = true;
LLVM_DEBUG(dbgs() << "[AAReturnedValues] Returned call site has multiple "
"potentially returned values\n");
continue;
}

LLVM_DEBUG({
bool UniqueRVIsKnown = RetCSAA->isAtFixpoint();
dbgs() << "[AAReturnedValues] Returned call site "
<< (UniqueRVIsKnown ? "known" : "assumed")
<< " unique return value: " << *AssumedUniqueRV << "\n";
});

// The assumed unique return value.
Value *AssumedRetVal = AssumedUniqueRV.getValue();

// If the assumed unique return value is an argument, lookup the matching
// call site operand and recursively collect new returned values.
// If it is not an argument, it is just put into the set of returned values
// as we would have already looked through casts, phis, and similar values.
if (Argument *AssumedRetArg = dyn_cast<Argument>(AssumedRetVal))
collectValuesRecursively(A,
RetCS.getArgOperand(AssumedRetArg->getArgNo()),
ReturnInsts, AddRVs);
else
AddRVs[AssumedRetVal].insert(ReturnInsts.begin(), ReturnInsts.end());
}

// Keep track of any change to trigger updates on dependent attributes.
ChangeStatus Changed = ChangeStatus::UNCHANGED;

for (auto &It : AddRVs) {
assert(!It.second.empty() && "Entry does not add anything.");
auto &ReturnInsts = ReturnedValues[It.first];
for (ReturnInst *RI : It.second)
if (ReturnInsts.insert(RI).second) {
LLVM_DEBUG(dbgs() << "[AAReturnedValues] Add new returned value "
<< *It.first << " => " << *RI << "\n");
Changed = ChangeStatus::CHANGED;
}
}

// If there is no call site in the returned values we are done.
if (!HasCallSite) {
indicateOptimisticFixpoint();
return ChangeStatus::CHANGED;
}

return Changed;
}

/// ----------------------------------------------------------------------------
/// Attributor
/// ----------------------------------------------------------------------------
Expand Down Expand Up @@ -448,6 +864,15 @@ void Attributor::identifyDefaultAbstractAttributes(
// Every function can be nounwind.
registerAA(*new AANoUnwindFunction(F, InfoCache));

// Return attributes are only appropriate if the return type is non void.
Type *ReturnType = F.getReturnType();
if (!ReturnType->isVoidTy()) {
// Argument attribute "returned" --- Create only one per function even
// though it is an argument attribute.
if (!Whitelist || Whitelist->count(AAReturnedValues::ID))
registerAA(*new AAReturnedValuesImpl(F, InfoCache));
}

// Walk all instructions to find more attribute opportunities and also
// interesting instructions that might be queried by abstract attributes
// during their initialization or update.
Expand All @@ -474,6 +899,7 @@ void Attributor::identifyDefaultAbstractAttributes(
case Instruction::CleanupRet:
case Instruction::CatchSwitch:
case Instruction::Resume:
case Instruction::Ret:
IsInterestingOpcode = true;
}
if (IsInterestingOpcode)
Expand Down
9 changes: 3 additions & 6 deletions llvm/test/Transforms/FunctionAttrs/arg_nocapture.ll
Original file line number Diff line number Diff line change
Expand Up @@ -112,18 +112,15 @@ entry:

; TEST SCC with various calls, casts, and comparisons agains NULL
;
; FIXME: returned missing for %a
; FIXME: no-capture missing for %a
; CHECK: define float* @scc_A(i32* readnone %a)
; CHECK: define float* @scc_A(i32* readnone returned %a)
;
; FIXME: returned missing for %a
; FIXME: no-capture missing for %a
; CHECK: define i64* @scc_B(double* readnone %a)
; CHECK: define i64* @scc_B(double* readnone returned %a)
;
; FIXME: returned missing for %a
; FIXME: readnone missing for %s
; FIXME: no-capture missing for %a
; CHECK: define i8* @scc_C(i16* %a)
; CHECK: define i8* @scc_C(i16* returned %a)
;
; float *scc_A(int *a) {
; return (float*)(a ? (int*)scc_A((int*)scc_B((double*)scc_C((short*)a))) : a);
Expand Down
310 changes: 232 additions & 78 deletions llvm/test/Transforms/FunctionAttrs/arg_returned.ll
Original file line number Diff line number Diff line change
@@ -1,22 +1,31 @@
; RUN: opt -functionattrs -attributor -attributor-disable=false -S < %s | FileCheck %s
; RUN: opt -functionattrs -attributor -attributor-disable=false -attributor-verify=true -S < %s | FileCheck %s
; RUN: opt -functionattrs -S < %s | FileCheck %s --check-prefix=FNATTR
; RUN: opt -attributor -attributor-disable=false -S < %s | FileCheck %s --check-prefix=ATTRIBUTOR
; RUN: opt -attributor -attributor-disable=false -functionattrs -S < %s | FileCheck %s --check-prefix=BOTH
;
; Test cases specifically designed for the "returned" argument attribute.
; We use FIXME's to indicate problems and missing attributes.
;

; TEST SCC test returning an integer value argument
;
; CHECK: Function Attrs: noinline norecurse nounwind readnone uwtable
; CHECK: define i32 @sink_r0(i32 returned %r)
;
; FIXME: returned on %r missing:
; CHECK: Function Attrs: noinline nounwind readnone uwtable
; CHECK: define i32 @scc_r1(i32 %a, i32 %r, i32 %b)
;
; FIXME: returned on %r missing:
; CHECK: Function Attrs: noinline nounwind readnone uwtable
; CHECK: define i32 @scc_r2(i32 %a, i32 %b, i32 %r)
; BOTH: Function Attrs: noinline norecurse nounwind readnone uwtable
; BOTH-NEXT: define i32 @sink_r0(i32 returned %r)
; BOTH: Function Attrs: noinline nounwind readnone uwtable
; BOTH-NEXT: define i32 @scc_r1(i32 %a, i32 returned %r, i32 %b)
; BOTH: Function Attrs: noinline nounwind readnone uwtable
; BOTH-NEXT: define i32 @scc_r2(i32 %a, i32 %b, i32 returned %r)
; BOTH: Function Attrs: noinline nounwind readnone uwtable
; BOTH-NEXT: define i32 @scc_rX(i32 %a, i32 %b, i32 %r)
;
; FNATTR: define i32 @sink_r0(i32 returned %r)
; FNATTR: define i32 @scc_r1(i32 %a, i32 %r, i32 %b)
; FNATTR: define i32 @scc_r2(i32 %a, i32 %b, i32 %r)
; FNATTR: define i32 @scc_rX(i32 %a, i32 %b, i32 %r)
;
; ATTRIBUTOR: define i32 @sink_r0(i32 returned %r)
; ATTRIBUTOR: define i32 @scc_r1(i32 %a, i32 returned %r, i32 %b)
; ATTRIBUTOR: define i32 @scc_r2(i32 %a, i32 %b, i32 returned %r)
; ATTRIBUTOR: define i32 @scc_rX(i32 %a, i32 %b, i32 %r)
;
; int scc_r1(int a, int b, int r);
; int scc_r2(int a, int b, int r);
Expand Down Expand Up @@ -150,16 +159,20 @@ return: ; preds = %cond.end, %if.then3

; TEST SCC test returning a pointer value argument
;
; CHECK: Function Attrs: noinline norecurse nounwind readnone uwtable
; CHECK: define double* @ptr_sink_r0(double* readnone returned %r)
; BOTH: Function Attrs: noinline norecurse nounwind readnone uwtable
; BOTH-NEXT: define double* @ptr_sink_r0(double* readnone returned %r)
; BOTH: Function Attrs: noinline 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-NEXT: define double* @ptr_scc_r2(double* readnone %a, double* readnone %b, double* readnone returned %r)
;
; FIXME: returned on %r missing:
; CHECK: Function Attrs: noinline nounwind readnone uwtable
; CHECK: define double* @ptr_scc_r1(double* %a, double* readnone %r, double* nocapture readnone %b)
; 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)
;
; FIXME: returned on %r missing:
; CHECK: Function Attrs: noinline nounwind readnone uwtable
; CHECK: 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)
;
; double* ptr_scc_r1(double* a, double* b, double* r);
; double* ptr_scc_r2(double* a, double* b, double* r);
Expand Down Expand Up @@ -237,41 +250,95 @@ return: ; preds = %cond.end, %if.then3
}


; TEST a singleton SCC with a lot of recursive calls
; TEST a no-return singleton SCC
;
; int* ret0(int *a) {
; return *a ? a : ret0(ret0(ret0(...ret0(a)...)));
; int* rt0(int *a) {
; return *a ? a : rt0(a);
; }
;
; FIXME: returned on %a missing:
; CHECK: Function Attrs: noinline nounwind readonly uwtable
; CHECK: define i32* @ret0(i32* readonly %a)
define i32* @ret0(i32* %a) #0 {
; FIXME: no-return missing
; FNATTR: define i32* @rt0(i32* readonly %a)
; BOTH: Function Attrs: noinline nounwind readonly uwtable
; BOTH-NEXT: define i32* @rt0(i32* readonly returned %a)
define i32* @rt0(i32* %a) #0 {
entry:
%v = load i32, i32* %a, align 4
%tobool = icmp ne i32 %v, 0
%call = call i32* @ret0(i32* %a)
%call1 = call i32* @ret0(i32* %call)
%call2 = call i32* @ret0(i32* %call1)
%call3 = call i32* @ret0(i32* %call2)
%call4 = call i32* @ret0(i32* %call3)
%call5 = call i32* @ret0(i32* %call4)
%call6 = call i32* @ret0(i32* %call5)
%call7 = call i32* @ret0(i32* %call6)
%call8 = call i32* @ret0(i32* %call7)
%call9 = call i32* @ret0(i32* %call8)
%call10 = call i32* @ret0(i32* %call9)
%call11 = call i32* @ret0(i32* %call10)
%call12 = call i32* @ret0(i32* %call11)
%call13 = call i32* @ret0(i32* %call12)
%call14 = call i32* @ret0(i32* %call13)
%call15 = call i32* @ret0(i32* %call14)
%call16 = call i32* @ret0(i32* %call15)
%call17 = call i32* @ret0(i32* %call16)
%sel = select i1 %tobool, i32* %a, i32* %call17
%call = call i32* @rt0(i32* %a)
%sel = select i1 %tobool, i32* %a, i32* %call
ret i32* %sel
}

; TEST a no-return singleton SCC
;
; int* rt1(int *a) {
; return *a ? undef : rt1(a);
; }
;
; FIXME: no-return missing
; FNATTR: define noalias i32* @rt1(i32* nocapture readonly %a)
; BOTH: Function Attrs: noinline nounwind readonly uwtable
; BOTH-NEXT: define noalias i32* @rt1(i32* nocapture readonly %a)
define i32* @rt1(i32* %a) #0 {
entry:
%v = load i32, i32* %a, align 4
%tobool = icmp ne i32 %v, 0
%call = call i32* @rt1(i32* %a)
%sel = select i1 %tobool, i32* undef, i32* %call
ret i32* %sel
}

; TEST another SCC test
;
; FNATTR: define i32* @rt2_helper(i32* %a)
; FNATTR: define i32* @rt2(i32* readnone %a, i32* readnone %b)
; BOTH: define i32* @rt2_helper(i32* %a)
; BOTH: define i32* @rt2(i32* readnone %a, i32* readnone %b)
define i32* @rt2_helper(i32* %a) #0 {
entry:
%call = call i32* @rt2(i32* %a, i32* %a)
ret i32* %call
}

define i32* @rt2(i32* %a, i32 *%b) #0 {
entry:
%cmp = icmp eq i32* %a, null
br i1 %cmp, label %if.then, label %if.end

if.then:
%call = call i32* @rt2_helper(i32* %a)
br label %if.end

if.end:
%sel = phi i32* [ %b, %entry], [%call, %if.then]
ret i32* %sel
}

; TEST another SCC test
;
; FNATTR: define i32* @rt3_helper(i32* %a, i32* %b)
; FNATTR: define i32* @rt3(i32* readnone %a, i32* readnone %b)
; BOTH: define i32* @rt3_helper(i32* %a, i32* returned %b)
; BOTH: define i32* @rt3(i32* readnone %a, i32* readnone returned %b)
define i32* @rt3_helper(i32* %a, i32* %b) #0 {
entry:
%call = call i32* @rt3(i32* %a, i32* %b)
ret i32* %call
}

define i32* @rt3(i32* %a, i32 *%b) #0 {
entry:
%cmp = icmp eq i32* %a, null
br i1 %cmp, label %if.then, label %if.end

if.then:
%call = call i32* @rt3_helper(i32* %a, i32* %b)
br label %if.end

if.end:
%sel = phi i32* [ %b, %entry], [%call, %if.then]
ret i32* %sel
}

; TEST address taken function with call to an external functions
;
Expand All @@ -282,11 +349,12 @@ entry:
; return r;
; }
;
; CHECK: Function Attrs: noinline nounwind uwtable
; CHECK: declare void @unknown_fn(i32* (i32*)*)
; BOTH: declare void @unknown_fn(i32* (i32*)*)
;
; CHECK: Function Attrs: noinline nounwind uwtable
; CHECK: define i32* @calls_unknown_fn(i32* readnone returned %r)
; BOTH: Function Attrs: noinline nounwind uwtable
; BOTH-NEXT: define i32* @calls_unknown_fn(i32* readnone returned %r)
; FNATTR: define i32* @calls_unknown_fn(i32* readnone returned %r)
; ATTRIBUTOR: define i32* @calls_unknown_fn(i32* returned %r)
declare void @unknown_fn(i32* (i32*)*) #0

define i32* @calls_unknown_fn(i32* %r) #0 {
Expand All @@ -313,6 +381,12 @@ define i32* @calls_unknown_fn(i32* %r) #0 {
;
; CHECK: Function Attrs: noinline nounwind uwtable
; CHECK: define i32* @calls_maybe_redefined_fn(i32* returned %r)
;
; BOTH: Function Attrs: noinline nounwind uwtable
; BOTH-NEXT: define linkonce_odr i32* @maybe_redefined_fn(i32* %r)
;
; BOTH: Function Attrs: noinline nounwind uwtable
; BOTH-NEXT: define i32* @calls_maybe_redefined_fn(i32* returned %r)
define linkonce_odr i32* @maybe_redefined_fn(i32* %r) #0 {
entry:
ret i32* %r
Expand All @@ -324,6 +398,36 @@ entry:
ret i32* %r
}

; TEST return call to a function that might be redifined at link time
;
; int *maybe_redefined_fn2(int *r) {
; return r;
; }
;
; int *calls_maybe_redefined_fn2(int *r) {
; return maybe_redefined_fn2(r);
; }
;
; Verify the maybe-redefined function is not annotated:
;
; BOTH: Function Attrs: noinline nounwind uwtable
; BOTH-NEXT: define linkonce_odr i32* @maybe_redefined_fn2(i32* %r)
; BOTH: Function Attrs: noinline nounwind uwtable
; BOTH-NEXT: define i32* @calls_maybe_redefined_fn2(i32* %r)
;
; FNATTR: define i32* @calls_maybe_redefined_fn2(i32* %r)
; ATTRIBUTOR: define i32* @calls_maybe_redefined_fn2(i32* %r)
define linkonce_odr i32* @maybe_redefined_fn2(i32* %r) #0 {
entry:
ret i32* %r
}

define i32* @calls_maybe_redefined_fn2(i32* %r) #0 {
entry:
%call = call i32* @maybe_redefined_fn2(i32* %r)
ret i32* %call
}


; TEST returned argument goes through select and phi
;
Expand All @@ -334,9 +438,11 @@ entry:
; return b == 0? b : x;
; }
;
; FIXME: returned on %b missing:
; CHECK: Function Attrs: noinline norecurse nounwind readnone uwtable
; CHECK: define double @select_and_phi(double %b)
; BOTH: Function Attrs: noinline norecurse 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)
define double @select_and_phi(double %b) #0 {
entry:
%cmp = fcmp ogt double %b, 0.000000e+00
Expand All @@ -362,9 +468,11 @@ if.end: ; preds = %if.then, %entry
; return b == 0? b : x;
; }
;
; FIXME: returned on %b missing:
; CHECK: Function Attrs: noinline nounwind readnone uwtable
; CHECK: define double @recursion_select_and_phi(i32 %a, double %b)
; BOTH: Function Attrs: noinline 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)
define double @recursion_select_and_phi(i32 %a, double %b) #0 {
entry:
%dec = add nsw i32 %a, -1
Expand All @@ -389,9 +497,11 @@ if.end: ; preds = %if.then, %entry
; return (double*)b;
; }
;
; FIXME: returned on %b missing:
; CHECK: Function Attrs: noinline norecurse nounwind readnone uwtable
; CHECK: define double* @bitcast(i32* readnone %b)
; BOTH: Function Attrs: noinline norecurse 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)
define double* @bitcast(i32* %b) #0 {
entry:
%bc0 = bitcast i32* %b to double*
Expand All @@ -408,9 +518,11 @@ entry:
; return b != 0 ? b : x;
; }
;
; FIXME: returned on %b missing:
; CHECK: Function Attrs: noinline norecurse nounwind readnone uwtable
; CHECK: define double* @bitcasts_select_and_phi(i32* readnone %b)
; BOTH: Function Attrs: noinline norecurse 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)
define double* @bitcasts_select_and_phi(i32* %b) #0 {
entry:
%bc0 = bitcast i32* %b to double*
Expand Down Expand Up @@ -442,8 +554,11 @@ if.end: ; preds = %if.then, %entry
; /* return undef */
; }
;
; CHECK: Function Attrs: noinline norecurse nounwind readnone uwtable
; CHECK: define double* @ret_arg_arg_undef(i32* readnone %b)
; BOTH: Function Attrs: noinline norecurse 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)
define double* @ret_arg_arg_undef(i32* %b) #0 {
entry:
%bc0 = bitcast i32* %b to double*
Expand Down Expand Up @@ -475,8 +590,11 @@ ret_undef:
; /* return undef */
; }
;
; CHECK: Function Attrs: noinline norecurse nounwind readnone uwtable
; CHECK: define double* @ret_undef_arg_arg(i32* readnone %b)
; BOTH: Function Attrs: noinline norecurse 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)
define double* @ret_undef_arg_arg(i32* %b) #0 {
entry:
%bc0 = bitcast i32* %b to double*
Expand Down Expand Up @@ -508,8 +626,11 @@ ret_arg1:
; /* return undef */
; }
;
; CHECK: Function Attrs: noinline norecurse nounwind readnone uwtable
; CHECK: define double* @ret_undef_arg_undef(i32* readnone %b)
; BOTH: Function Attrs: noinline norecurse 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)
; ATTRIBUTOR: define double* @ret_undef_arg_undef(i32* returned %b)
define double* @ret_undef_arg_undef(i32* %b) #0 {
entry:
%bc0 = bitcast i32* %b to double*
Expand All @@ -534,13 +655,17 @@ ret_undef1:
; int* ret_arg_or_unknown(int* b) {
; if (b == 0)
; return b;
; return unknown(b);
; return unknown();
; }
;
; Verify we do not assume b is returned>
; Verify we do not assume b is returned
;
; CHECK: define i32* @ret_arg_or_unknown(i32* %b)
; CHECK: define i32* @ret_arg_or_unknown_through_phi(i32* %b)
; FNATTR: define i32* @ret_arg_or_unknown(i32* %b)
; FNATTR: define i32* @ret_arg_or_unknown_through_phi(i32* %b)
; ATTRIBUTOR: define i32* @ret_arg_or_unknown(i32* %b)
; ATTRIBUTOR: define i32* @ret_arg_or_unknown_through_phi(i32* %b)
; BOTH: define i32* @ret_arg_or_unknown(i32* %b)
; BOTH: define i32* @ret_arg_or_unknown_through_phi(i32* %b)
declare i32* @unknown(i32*)

define i32* @ret_arg_or_unknown(i32* %b) #0 {
Expand Down Expand Up @@ -573,11 +698,40 @@ r:
ret i32* %phi
}

; TEST inconsistent IR in dead code.
;
; FNATTR: define i32 @deadblockcall1(i32 %A)
; FNATTR: define i32 @deadblockcall2(i32 %A)
; ATTRIBUTOR: define i32 @deadblockcall1(i32 returned %A)
; ATTRIBUTOR: define i32 @deadblockcall2(i32 returned %A)
; BOTH: define i32 @deadblockcall1(i32 returned %A)
; BOTH: define i32 @deadblockcall2(i32 returned %A)
define i32 @deadblockcall1(i32 %A) #0 {
entry:
ret i32 %A
unreachableblock:
%B = call i32 @deadblockcall1(i32 %B)
ret i32 %B
}

declare i32 @deadblockcall_helper(i32 returned %A);

define i32 @deadblockcall2(i32 %A) #0 {
entry:
ret i32 %A
unreachableblock1:
%B = call i32 @deadblockcall_helper(i32 %B)
ret i32 %B
unreachableblock2:
%C = call i32 @deadblockcall1(i32 %C)
ret i32 %C
}

attributes #0 = { noinline nounwind uwtable }

; CHECK-NOT: attributes #
; CHECK-DAG: attributes #{{[0-9]*}} = { noinline norecurse nounwind readnone uwtable }
; CHECK-DAG: attributes #{{[0-9]*}} = { noinline nounwind readnone uwtable }
; CHECK-DAG: attributes #{{[0-9]*}} = { noinline nounwind readonly uwtable }
; CHECK-DAG: attributes #{{[0-9]*}} = { noinline nounwind uwtable }
; CHECK-NOT: attributes #
; 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 nounwind uwtable }
; BOTH-NOT: attributes #
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"

; CHECK: Function Attrs: nofree nounwind
; CHECK-NEXT: define i32* @external_ret2_nrw(i32* %n0, i32* %r0, i32* %w0)
; 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:
%call = call i32* @internal_ret0_nw(i32* %n0, i32* %w0)
Expand All @@ -42,7 +42,7 @@ entry:
}

; CHECK: Function Attrs: nofree nounwind
; CHECK-NEXT: define internal i32* @internal_ret0_nw(i32* %n0, i32* %w0)
; CHECK-NEXT: define internal i32* @internal_ret0_nw(i32* returned %n0, i32* %w0)
define internal i32* @internal_ret0_nw(i32* %n0, i32* %w0) {
entry:
%r0 = alloca i32, align 4
Expand Down Expand Up @@ -71,7 +71,7 @@ return: ; preds = %if.end, %if.then
}

; CHECK: Function Attrs: nofree nounwind
; CHECK-NEXT: define internal i32* @internal_ret1_rrw(i32* %r0, i32* %r1, i32* %w0)
; 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:
%0 = load i32, i32* %r0, align 4
Expand Down Expand Up @@ -122,7 +122,7 @@ return: ; preds = %if.end, %if.then
}

; CHECK: Function Attrs: nofree nounwind
; CHECK-NEXT: define internal i32* @internal_ret1_rw(i32* %r0, i32* %w0)
; CHECK-NEXT: define internal i32* @internal_ret1_rw(i32* %r0, i32* returned %w0)
define internal i32* @internal_ret1_rw(i32* %r0, i32* %w0) {
entry:
%0 = load i32, i32* %r0, align 4
Expand All @@ -148,7 +148,7 @@ return: ; preds = %if.end, %if.then
}

; CHECK: Function Attrs: nofree nounwind
; CHECK-NEXT: define i32* @external_source_ret2_nrw(i32* %n0, i32* %r0, i32* %w0)
; 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:
%call = call i32* @external_sink_ret2_nrw(i32* %n0, i32* %r0, i32* %w0)
Expand Down