[llvm] 58f324a - [Attributor] Function level undefined behavior attribute
Johannes Doerfert via llvm-commits
llvm-commits at lists.llvm.org
Tue Dec 24 17:23:36 PST 2019
Author: Johannes Doerfert
Date: 2019-12-24T19:23:08-06:00
New Revision: 58f324a468ffc66398199f1a55218e10b718e495
URL: https://github.com/llvm/llvm-project/commit/58f324a468ffc66398199f1a55218e10b718e495
DIFF: https://github.com/llvm/llvm-project/commit/58f324a468ffc66398199f1a55218e10b718e495.diff
LOG: [Attributor] Function level undefined behavior attribute
_Eventually_, this attribute will be assigned to a function if it
contains undefined behavior. As a first small step, I tried to make it
loop through the load instructions in a function (eventually, the plan
is to check if a load instructions causes undefined behavior, because
e.g. dereferences a null pointer - Also eventually, this won't happen in
initialize() but in updateImpl()).
Patch By: Stefanos Baziotis (@baziotis)
Reviewed By: jdoerfert
Differential Revision: https://reviews.llvm.org/D71435
Added:
llvm/test/Transforms/Attributor/undefined_behavior.ll
Modified:
llvm/include/llvm/Transforms/IPO/Attributor.h
llvm/lib/Transforms/IPO/Attributor.cpp
Removed:
################################################################################
diff --git a/llvm/include/llvm/Transforms/IPO/Attributor.h b/llvm/include/llvm/Transforms/IPO/Attributor.h
index b931d0f57461..25b3573a1905 100644
--- a/llvm/include/llvm/Transforms/IPO/Attributor.h
+++ b/llvm/include/llvm/Transforms/IPO/Attributor.h
@@ -1686,6 +1686,32 @@ struct AAWillReturn
static const char ID;
};
+/// An abstract attribute for undefined behavior.
+struct AAUndefinedBehavior
+ : public StateWrapper<BooleanState, AbstractAttribute>,
+ public IRPosition {
+ AAUndefinedBehavior(const IRPosition &IRP) : IRPosition(IRP) {}
+
+ /// Return true if "undefined behavior" is assumed.
+ bool isAssumedToCauseUB() const { return getAssumed(); }
+
+ /// Return true if "undefined behavior" is assumed for a specific instruction.
+ virtual bool isAssumedToCauseUB(Instruction *I) const = 0;
+
+ /// Return true if "undefined behavior" is known.
+ bool isKnownToCauseUB() const { return getKnown(); }
+
+ /// Return an IR position, see struct IRPosition.
+ const IRPosition &getIRPosition() const override { return *this; }
+
+ /// Create an abstract attribute view for the position \p IRP.
+ static AAUndefinedBehavior &createForPosition(const IRPosition &IRP,
+ Attributor &A);
+
+ /// Unique ID (due to the unique address)
+ static const char ID;
+};
+
/// An abstract interface to determine reachability of point A to B.
struct AAReachability : public StateWrapper<BooleanState, AbstractAttribute>,
public IRPosition {
diff --git a/llvm/lib/Transforms/IPO/Attributor.cpp b/llvm/lib/Transforms/IPO/Attributor.cpp
index cd305ee18ebf..1bb3b41c6b6c 100644
--- a/llvm/lib/Transforms/IPO/Attributor.cpp
+++ b/llvm/lib/Transforms/IPO/Attributor.cpp
@@ -1987,6 +1987,98 @@ struct AANoRecurseCallSite final : AANoRecurseImpl {
void trackStatistics() const override { STATS_DECLTRACK_CS_ATTR(norecurse); }
};
+/// -------------------- Undefined-Behavior Attributes ------------------------
+
+struct AAUndefinedBehaviorImpl : public AAUndefinedBehavior {
+ AAUndefinedBehaviorImpl(const IRPosition &IRP) : AAUndefinedBehavior(IRP) {}
+
+ /// See AbstractAttribute::updateImpl(...).
+ ChangeStatus updateImpl(Attributor &A) override {
+ size_t PrevSize = NoUBLoads.size();
+
+ // TODO: We should not only check for load instructions.
+ auto InspectLoadForUB = [&](Instruction &I) {
+ // Skip instructions that are already saved.
+ if (NoUBLoads.count(&I) || UBLoads.count(&I))
+ return true;
+
+ Value *PtrOp = cast<LoadInst>(&I)->getPointerOperand();
+
+ // A load is considered UB only if it dereferences a constant
+ // null pointer.
+ if (!isa<ConstantPointerNull>(PtrOp)) {
+ NoUBLoads.insert(&I);
+ return true;
+ }
+ Type *PtrTy = PtrOp->getType();
+
+ // Because we only consider loads inside functions,
+ // assume that a parent function exists.
+ const Function *F = I.getFunction();
+
+ // A dereference on constant null is only considered UB
+ // if null dereference is _not_ defined for the target platform.
+ // TODO: Expand it to not only check constant values.
+ if (!llvm::NullPointerIsDefined(F, PtrTy->getPointerAddressSpace()))
+ UBLoads.insert(&I);
+ else
+ NoUBLoads.insert(&I);
+ return true;
+ };
+
+ A.checkForAllInstructions(InspectLoadForUB, *this, {Instruction::Load});
+ if (PrevSize != NoUBLoads.size())
+ return ChangeStatus::CHANGED;
+ return ChangeStatus::UNCHANGED;
+ }
+
+ bool isAssumedToCauseUB(Instruction *I) const override {
+ return UBLoads.count(I);
+ }
+
+ ChangeStatus manifest(Attributor &A) override {
+ if (!UBLoads.size())
+ return ChangeStatus::UNCHANGED;
+ for (Instruction *I : UBLoads)
+ changeToUnreachable(I, /* UseLLVMTrap */ false);
+ return ChangeStatus::CHANGED;
+ }
+
+ /// See AbstractAttribute::getAsStr()
+ const std::string getAsStr() const override {
+ return getAssumed() ? "undefined-behavior" : "no-ub";
+ }
+
+protected:
+ // A set of all the (live) load instructions that _are_ assumed to cause UB.
+ SmallPtrSet<Instruction *, 8> UBLoads;
+
+private:
+ // A set of all the (live) load instructions that are _not_ assumed to cause
+ // UB.
+ // Note: The correctness of the procedure depends on the fact that this
+ // set stops changing after some point. "Change" here means that the size
+ // of the set changes. The size of this set is monotonically increasing
+ // (we only add items to it) and is upper bounded by the number of load
+ // instructions in the processed function (we can never save more elements
+ // in this set than this number). Hence, the size of this set, at some
+ // point, will stop increasing, effectively reaching a fixpoint.
+ SmallPtrSet<Instruction *, 8> NoUBLoads;
+};
+
+struct AAUndefinedBehaviorFunction final : AAUndefinedBehaviorImpl {
+ AAUndefinedBehaviorFunction(const IRPosition &IRP)
+ : AAUndefinedBehaviorImpl(IRP) {}
+
+ /// See AbstractAttribute::trackStatistics()
+ void trackStatistics() const override {
+ STATS_DECL(UndefinedBehaviorInstruction, Instruction,
+ "Number of instructions known to have UB");
+ BUILD_STAT_NAME(UndefinedBehaviorInstruction, Instruction) +=
+ UBLoads.size();
+ }
+};
+
/// ------------------------ Will-Return Attributes ----------------------------
// Helper function that checks whether a function has any cycle.
@@ -5523,6 +5615,9 @@ void Attributor::identifyDefaultAbstractAttributes(Function &F) {
// Every function might be "will-return".
getOrCreateAAFor<AAWillReturn>(FPos);
+ // Every function might contain instructions that cause "undefined behavior".
+ getOrCreateAAFor<AAUndefinedBehavior>(FPos);
+
// Every function can be nounwind.
getOrCreateAAFor<AANoUnwind>(FPos);
@@ -5827,6 +5922,7 @@ const char AANoFree::ID = 0;
const char AANonNull::ID = 0;
const char AANoRecurse::ID = 0;
const char AAWillReturn::ID = 0;
+const char AAUndefinedBehavior::ID = 0;
const char AANoAlias::ID = 0;
const char AAReachability::ID = 0;
const char AANoReturn::ID = 0;
@@ -5949,6 +6045,7 @@ CREATE_ALL_ABSTRACT_ATTRIBUTE_FOR_POSITION(AANoFree)
CREATE_FUNCTION_ONLY_ABSTRACT_ATTRIBUTE_FOR_POSITION(AAHeapToStack)
CREATE_FUNCTION_ONLY_ABSTRACT_ATTRIBUTE_FOR_POSITION(AAReachability)
+CREATE_FUNCTION_ONLY_ABSTRACT_ATTRIBUTE_FOR_POSITION(AAUndefinedBehavior)
CREATE_NON_RET_ABSTRACT_ATTRIBUTE_FOR_POSITION(AAMemoryBehavior)
diff --git a/llvm/test/Transforms/Attributor/undefined_behavior.ll b/llvm/test/Transforms/Attributor/undefined_behavior.ll
new file mode 100644
index 000000000000..873f16a50bec
--- /dev/null
+++ b/llvm/test/Transforms/Attributor/undefined_behavior.ll
@@ -0,0 +1,38 @@
+; 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 specifically designed for the "undefined behavior" abstract function attribute.
+; We want to verify that whenever undefined behavior is assumed, the code becomes unreachable.
+; We use FIXME's to indicate problems and missing attributes.
+
+; ATTRIBUTOR: define void @wholly_unreachable()
+; ATTRIBUTOR-NEXT: unreachable
+define void @wholly_unreachable() {
+ %a = load i32, i32* null
+ ret void
+}
+
+; ATTRIBUTOR: define void @single_bb_unreachable(i1 %cond)
+; ATTRIBUTOR-NEXT: br i1 %cond, label %t, label %e
+; ATTRIBUTOR-EMPTY:
+; ATTRIBUTOR-NEXT: t:
+; ATTRIBUTOR-NEXT: unreachable
+; ATTRIBUTOR-EMPTY:
+; ATTRIBUTOR-NEXT: e:
+; ATTRIBUTOR-NEXT: ret void
+define void @single_bb_unreachable(i1 %cond) {
+ br i1 %cond, label %t, label %e
+t:
+ %b = load i32, i32* null
+ br label %e
+e:
+ ret void
+}
+
+; ATTRIBUTOR: define void @null_pointer_is_defined()
+; ATTRIBUTOR-NEXT: %a = load i32, i32* null
+define void @null_pointer_is_defined() "null-pointer-is-valid"="true" {
+ %a = load i32, i32* null
+ ret void
+}
More information about the llvm-commits
mailing list