Add an @llvm.sideeffect intrinsic

This patch implements Chandler's idea [0] for supporting languages that
require support for infinite loops with side effects, such as Rust, providing
part of a solution to bug 965 [1].

Specifically, it adds an `llvm.sideeffect()` intrinsic, which has no actual
effect, but which appears to optimization passes to have obscure side effects,
such that they don't optimize away loops containing it. It also teaches
several optimization passes to ignore this intrinsic, so that it doesn't
significantly impact optimization in most cases.

As discussed on llvm-dev [2], this patch is the first of two major parts.
The second part, to change LLVM's semantics to have defined behavior
on infinite loops by default, with a function attribute for opting into
potential-undefined-behavior, will be implemented and posted for review in
a separate patch.

[0] http://lists.llvm.org/pipermail/llvm-dev/2015-July/088103.html
[1] https://bugs.llvm.org/show_bug.cgi?id=965
[2] http://lists.llvm.org/pipermail/llvm-dev/2017-October/118632.html

Differential Revision: https://reviews.llvm.org/D38336

llvm-svn: 317729
This commit is contained in:
Dan Gohman 2017-11-08 21:59:51 +00:00
parent c707c6f3a7
commit 2c74fe977d
31 changed files with 446 additions and 6 deletions

View File

@ -14267,6 +14267,36 @@ not overflow at link time under the medium code model if ``x`` is an
a constant initializer folded into a function body. This intrinsic can be
used to avoid the possibility of overflows when loading from such a constant.
'``llvm.sideeffect``' Intrinsic
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Syntax:
"""""""
::
declare void @llvm.sideeffect() inaccessiblememonly nounwind
Overview:
"""""""""
The ``llvm.sideeffect`` intrinsic doesn't perform any operation. Optimizers
treat it as having side effects, so it can be inserted into a loop to
indicate that the loop shouldn't be assumed to terminate (which could
potentially lead to the loop being optimized away entirely), even if it's
an infinite loop with no other side effects.
Arguments:
""""""""""
None.
Semantics:
""""""""""
This intrinsic actually does nothing, but optimizers must assume that it
has externally observable side effects.
Stack Map Intrinsics
--------------------

View File

@ -152,6 +152,7 @@ public:
case Intrinsic::annotation:
case Intrinsic::assume:
case Intrinsic::sideeffect:
case Intrinsic::dbg_declare:
case Intrinsic::dbg_value:
case Intrinsic::invariant_start:

View File

@ -1023,6 +1023,7 @@ public:
// FIXME: We should return 0 whenever getIntrinsicCost == TCC_Free.
case Intrinsic::lifetime_start:
case Intrinsic::lifetime_end:
case Intrinsic::sideeffect:
return 0;
case Intrinsic::masked_store:
return static_cast<T *>(this)

View File

@ -810,6 +810,12 @@ def int_experimental_guard : Intrinsic<[], [llvm_i1_ty, llvm_vararg_ty],
// NOP: calls/invokes to this intrinsic are removed by codegen
def int_donothing : Intrinsic<[], [], [IntrNoMem]>;
// This instruction has no actual effect, though it is treated by the optimizer
// has having opaque side effects. This may be inserted into loops to ensure
// that they are not removed even if they turn out to be empty, for languages
// which specify that infinite loops must be preserved.
def int_sideeffect : Intrinsic<[], [], [IntrInaccessibleMemOnly]>;
// Intrisics to support half precision floating point format
let IntrProperties = [IntrNoMem] in {
def int_convert_to_fp16 : Intrinsic<[llvm_i16_ty], [llvm_anyfloat_ty]>;

View File

@ -436,6 +436,7 @@ void AliasSetTracker::addUnknown(Instruction *Inst) {
break;
// FIXME: Add lifetime/invariant intrinsics (See: PR30807).
case Intrinsic::assume:
case Intrinsic::sideeffect:
return;
}
}

View File

@ -433,6 +433,7 @@ static bool isAssumeLikeIntrinsic(const Instruction *I) {
default: break;
// FIXME: This list is repeated from NoTTI::getIntrinsicCost.
case Intrinsic::assume:
case Intrinsic::sideeffect:
case Intrinsic::dbg_declare:
case Intrinsic::dbg_value:
case Intrinsic::invariant_start:
@ -3857,7 +3858,8 @@ bool llvm::isGuaranteedToTransferExecutionToSuccessor(const Instruction *I) {
// FIXME: This isn't aggressive enough; a call which only writes to a global
// is guaranteed to return.
return CS.onlyReadsMemory() || CS.onlyAccessesArgMemory() ||
match(I, m_Intrinsic<Intrinsic::assume>());
match(I, m_Intrinsic<Intrinsic::assume>()) ||
match(I, m_Intrinsic<Intrinsic::sideeffect>());
}
// Other instructions return normally.

View File

@ -91,7 +91,8 @@ Intrinsic::ID llvm::getVectorIntrinsicIDForCall(const CallInst *CI,
return Intrinsic::not_intrinsic;
if (isTriviallyVectorizable(ID) || ID == Intrinsic::lifetime_start ||
ID == Intrinsic::lifetime_end || ID == Intrinsic::assume)
ID == Intrinsic::lifetime_end || ID == Intrinsic::assume ||
ID == Intrinsic::sideeffect)
return ID;
return Intrinsic::not_intrinsic;
}

View File

@ -1132,6 +1132,8 @@ bool FastISel::selectIntrinsicCall(const IntrinsicInst *II) {
case Intrinsic::lifetime_end:
// The donothing intrinsic does, well, nothing.
case Intrinsic::donothing:
// Neither does the sideeffect intrinsic.
case Intrinsic::sideeffect:
// Neither does the assume intrinsic; it's also OK not to codegen its operand.
case Intrinsic::assume:
return true;

View File

@ -5702,7 +5702,8 @@ SelectionDAGBuilder::visitIntrinsicCall(const CallInst &I, unsigned Intrinsic) {
return nullptr;
case Intrinsic::assume:
case Intrinsic::var_annotation:
// Discard annotate attributes and assumptions
case Intrinsic::sideeffect:
// Discard annotate attributes, assumptions, and artificial side-effects.
return nullptr;
case Intrinsic::codeview_annotation: {

View File

@ -709,6 +709,12 @@ bool EarlyCSE::processNode(DomTreeNode *Node) {
continue;
}
// Skip sideeffect intrinsics, for the same reason as assume intrinsics.
if (match(Inst, m_Intrinsic<Intrinsic::sideeffect>())) {
DEBUG(dbgs() << "EarlyCSE skipping sideeffect: " << *Inst << '\n');
continue;
}
// Skip invariant.start intrinsics since they only read memory, and we can
// forward values across it. Also, we dont need to consume the last store
// since the semantics of invariant.start allow us to perform DSE of the

View File

@ -1110,7 +1110,8 @@ private:
else if (auto *Call = dyn_cast<CallInst>(&I1)) {
if (auto *Intr = dyn_cast<IntrinsicInst>(Call)) {
if (isa<DbgInfoIntrinsic>(Intr) ||
Intr->getIntrinsicID() == Intrinsic::assume)
Intr->getIntrinsicID() == Intrinsic::assume ||
Intr->getIntrinsicID() == Intrinsic::sideeffect)
continue;
}
if (Call->mayHaveSideEffects())

View File

@ -433,6 +433,10 @@ bool Evaluator::EvaluateBlock(BasicBlock::iterator CurInst,
DEBUG(dbgs() << "Skipping assume intrinsic.\n");
++CurInst;
continue;
} else if (II->getIntrinsicID() == Intrinsic::sideeffect) {
DEBUG(dbgs() << "Skipping sideeffect intrinsic.\n");
++CurInst;
continue;
}
DEBUG(dbgs() << "Unknown intrinsic. Can not evaluate.\n");

View File

@ -34,6 +34,7 @@
#include "llvm/IR/InstrTypes.h"
#include "llvm/IR/Instruction.h"
#include "llvm/IR/Instructions.h"
#include "llvm/IR/IntrinsicInst.h"
#include "llvm/IR/Module.h"
#include "llvm/IR/Type.h"
#include "llvm/IR/User.h"
@ -500,6 +501,10 @@ Vectorizer::getVectorizablePrefix(ArrayRef<Instruction *> Chain) {
MemoryInstrs.push_back(&I);
else
ChainInstrs.push_back(&I);
} else if (isa<IntrinsicInst>(&I) &&
cast<IntrinsicInst>(&I)->getIntrinsicID() ==
Intrinsic::sideeffect) {
// Ignore llvm.sideeffect calls.
} else if (IsLoadChain && (I.mayWriteToMemory() || I.mayThrow())) {
DEBUG(dbgs() << "LSV: Found may-write/throw operation: " << I << '\n');
break;

View File

@ -8117,7 +8117,7 @@ bool LoopVectorizationPlanner::tryToWiden(Instruction *I, VPBasicBlock *VPBB,
if (CallInst *CI = dyn_cast<CallInst>(I)) {
Intrinsic::ID ID = getVectorIntrinsicIDForCall(CI, TLI);
if (ID && (ID == Intrinsic::assume || ID == Intrinsic::lifetime_end ||
ID == Intrinsic::lifetime_start))
ID == Intrinsic::lifetime_start || ID == Intrinsic::sideeffect))
return false;
}

View File

@ -3612,7 +3612,9 @@ void BoUpSLP::BlockScheduling::initScheduleData(Instruction *FromI,
"new ScheduleData already in scheduling region");
SD->init(SchedulingRegionID, I);
if (I->mayReadOrWriteMemory()) {
if (I->mayReadOrWriteMemory() &&
(!isa<IntrinsicInst>(I) ||
cast<IntrinsicInst>(I)->getIntrinsicID() != Intrinsic::sideeffect)) {
// Update the linked list of memory accessing instructions.
if (CurrentLoadStore) {
CurrentLoadStore->NextLoadStore = SD;

View File

@ -45,3 +45,12 @@ define i8* @barrier(i8* %p) {
%q = call i8* @llvm.invariant.group.barrier(i8* %p)
ret i8* %q
}
; sideeffect
declare void @llvm.sideeffect()
define void @test_sideeffect() {
call void @llvm.sideeffect()
ret void
}

View File

@ -0,0 +1,12 @@
; RUN: opt -S < %s -instcombine | FileCheck %s
declare void @llvm.sideeffect()
; Don't DCE llvm.sideeffect calls.
; CHECK-LABEL: dce
; CHECK: call void @llvm.sideeffect()
define void @dce() {
call void @llvm.sideeffect()
ret void
}

View File

@ -0,0 +1,15 @@
; RUN: opt -S < %s -dse | FileCheck %s
declare void @llvm.sideeffect()
; Dead store elimination across a @llvm.sideeffect.
; CHECK-LABEL: dse
; CHECK: store
; CHECK-NOT: store
define void @dse(float* %p) {
store float 0.0, float* %p
call void @llvm.sideeffect()
store float 0.0, float* %p
ret void
}

View File

@ -0,0 +1,27 @@
; RUN: opt -S < %s -early-cse | FileCheck %s
declare void @llvm.sideeffect()
; Store-to-load forwarding across a @llvm.sideeffect.
; CHECK-LABEL: s2l
; CHECK-NOT: load
define float @s2l(float* %p) {
store float 0.0, float* %p
call void @llvm.sideeffect()
%t = load float, float* %p
ret float %t
}
; Redundant load elimination across a @llvm.sideeffect.
; CHECK-LABEL: rle
; CHECK: load
; CHECK-NOT: load
define float @rle(float* %p) {
%r = load float, float* %p
call void @llvm.sideeffect()
%s = load float, float* %p
%t = fadd float %r, %s
ret float %t
}

View File

@ -0,0 +1,21 @@
; RUN: opt -S < %s -functionattrs | FileCheck %s
declare void @llvm.sideeffect()
; Don't add readnone or similar attributes when an @llvm.sideeffect() intrinsic
; is present.
; CHECK: define void @test() {
define void @test() {
call void @llvm.sideeffect()
ret void
}
; CHECK: define void @loop() {
define void @loop() {
br label %loop
loop:
call void @llvm.sideeffect()
br label %loop
}

View File

@ -0,0 +1,51 @@
; RUN: opt -S < %s -gvn | FileCheck %s
declare void @llvm.sideeffect()
; Store-to-load forwarding across a @llvm.sideeffect.
; CHECK-LABEL: s2l
; CHECK-NOT: load
define float @s2l(float* %p) {
store float 0.0, float* %p
call void @llvm.sideeffect()
%t = load float, float* %p
ret float %t
}
; Redundant load elimination across a @llvm.sideeffect.
; CHECK-LABEL: rle
; CHECK: load
; CHECK-NOT: load
define float @rle(float* %p) {
%r = load float, float* %p
call void @llvm.sideeffect()
%s = load float, float* %p
%t = fadd float %r, %s
ret float %t
}
; LICM across a @llvm.sideeffect.
; CHECK-LABEL: licm
; CHECK: load
; CHECK: loop:
; CHECK-NOT: load
define float @licm(i64 %n, float* nocapture readonly %p) #0 {
bb0:
br label %loop
loop:
%i = phi i64 [ 0, %bb0 ], [ %t5, %loop ]
%sum = phi float [ 0.000000e+00, %bb0 ], [ %t4, %loop ]
call void @llvm.sideeffect()
%t3 = load float, float* %p
%t4 = fadd float %sum, %t3
%t5 = add i64 %i, 1
%t6 = icmp ult i64 %t5, %n
br i1 %t6, label %loop, label %bb2
bb2:
ret float %t4
}

View File

@ -0,0 +1,30 @@
; RUN: opt -S < %s -gvn-hoist | FileCheck %s
declare void @llvm.sideeffect()
; GVN hoisting across a @llvm.sideeffect.
; CHECK-LABEL: scalarsHoisting
; CHECK: = fsub
; CHECK: br i1 %cmp,
; CHECK-NOT: fsub
define float @scalarsHoisting(float %d, float %m, float %a, i1 %cmp) {
entry:
br i1 %cmp, label %if.then, label %if.else
if.then:
call void @llvm.sideeffect()
%sub0 = fsub float %m, %a
%mul = fmul float %sub0, %d
br label %if.end
if.else:
%sub1 = fsub float %m, %a
%div = fdiv float %sub1, %d
br label %if.end
if.end:
%phi = phi float [ %mul, %if.then ], [ %div, %if.else ]
ret float %phi
}

View File

@ -0,0 +1,30 @@
; RUN: opt -S < %s -gvn-sink | FileCheck %s
declare void @llvm.sideeffect()
; GVN sinking across a @llvm.sideeffect.
; CHECK-LABEL: scalarsSinking
; CHECK-NOT: fmul
; CHECK: = phi
; CHECK: = fmul
define float @scalarsSinking(float %d, float %m, float %a, i1 %cmp) {
entry:
br i1 %cmp, label %if.then, label %if.else
if.then:
call void @llvm.sideeffect()
%sub = fsub float %m, %a
%mul0 = fmul float %sub, %d
br label %if.end
if.else:
%add = fadd float %m, %a
%mul1 = fmul float %add, %d
br label %if.end
if.end:
%phi = phi float [ %mul0, %if.then ], [ %mul1, %if.else ]
ret float %phi
}

View File

@ -0,0 +1,16 @@
; RUN: opt -S < %s -globalopt | FileCheck %s
; Static evaluation across a @llvm.sideeffect.
; CHECK-NOT: store
declare void @llvm.sideeffect()
@llvm.global_ctors = appending global [1 x { i32, void ()* }] [ { i32, void ()* } { i32 65535, void ()* @ctor } ]
@G = global i32 0
define internal void @ctor() {
store i32 1, i32* @G
call void @llvm.sideeffect()
ret void
}

View File

@ -0,0 +1,14 @@
; RUN: opt -S < %s -instcombine | FileCheck %s
declare void @llvm.sideeffect()
; Store-to-load forwarding across a @llvm.sideeffect.
; CHECK-LABEL: s2l
; CHECK-NOT: load
define float @s2l(float* %p) {
store float 0.0, float* %p
call void @llvm.sideeffect()
%t = load float, float* %p
ret float %t
}

View File

@ -0,0 +1,27 @@
; RUN: opt -S < %s -licm | FileCheck %s
declare void @llvm.sideeffect()
; LICM across a @llvm.sideeffect.
; CHECK-LABEL: licm
; CHECK: load
; CHECK: loop:
; CHECK-NOT: load
define float @licm(i64 %n, float* nocapture readonly %p) #0 {
bb0:
br label %loop
loop:
%i = phi i64 [ 0, %bb0 ], [ %t5, %loop ]
%sum = phi float [ 0.000000e+00, %bb0 ], [ %t4, %loop ]
call void @llvm.sideeffect()
%t3 = load float, float* %p
%t4 = fadd float %sum, %t3
%t5 = add i64 %i, 1
%t6 = icmp ult i64 %t5, %n
br i1 %t6, label %loop, label %bb2
bb2:
ret float %t4
}

View File

@ -0,0 +1,26 @@
; RUN: opt -S < %s -load-store-vectorizer | FileCheck %s
declare void @llvm.sideeffect()
; load-store vectorization across a @llvm.sideeffect.
; CHECK-LABEL: test
; CHECK: load <4 x float>
; CHECK: store <4 x float>
define void @test(float* %p) {
%p0 = getelementptr float, float* %p, i64 0
%p1 = getelementptr float, float* %p, i64 1
%p2 = getelementptr float, float* %p, i64 2
%p3 = getelementptr float, float* %p, i64 3
%l0 = load float, float* %p0, align 16
%l1 = load float, float* %p1
%l2 = load float, float* %p2
call void @llvm.sideeffect()
%l3 = load float, float* %p3
store float %l0, float* %p0, align 16
call void @llvm.sideeffect()
store float %l1, float* %p1
store float %l2, float* %p2
store float %l3, float* %p3
ret void
}

View File

@ -0,0 +1,23 @@
; RUN: opt -S < %s -loop-idiom | FileCheck %s
declare void @llvm.sideeffect()
; Loop idiom recognition across a @llvm.sideeffect.
; CHECK-LABEL: zero
; CHECK: llvm.memset
define void @zero(float* %p, i64 %n) nounwind {
bb7.lr.ph:
br label %bb7
bb7:
%i.02 = phi i64 [ 0, %bb7.lr.ph ], [ %tmp13, %bb7 ]
%tmp10 = getelementptr inbounds float, float* %p, i64 %i.02
store float 0.000000e+00, float* %tmp10, align 4
%tmp13 = add i64 %i.02, 1
%tmp6 = icmp ult i64 %tmp13, %n
br i1 %tmp6, label %bb7, label %bb14
bb14:
ret void
}

View File

@ -0,0 +1,24 @@
; RUN: opt -S < %s -loop-vectorize -force-vector-width=4 | FileCheck %s
declare void @llvm.sideeffect()
; Vectorization across a @llvm.sideeffect.
; CHECK-LABEL: store_ones
; CHECK: store <4 x float>
define void @store_ones(float* %p, i64 %n) nounwind {
bb7.lr.ph:
br label %bb7
bb7:
%i.02 = phi i64 [ 0, %bb7.lr.ph ], [ %tmp13, %bb7 ]
call void @llvm.sideeffect()
%tmp10 = getelementptr inbounds float, float* %p, i64 %i.02
store float 1.0, float* %tmp10, align 4
%tmp13 = add i64 %i.02, 1
%tmp6 = icmp ult i64 %tmp13, %n
br i1 %tmp6, label %bb7, label %bb14
bb14:
ret void
}

View File

@ -0,0 +1,27 @@
; RUN: opt -S < %s -newgvn | FileCheck %s
declare void @llvm.sideeffect()
; Store-to-load forwarding across a @llvm.sideeffect.
; CHECK-LABEL: s2l
; CHECK-NOT: load
define float @s2l(float* %p) {
store float 0.0, float* %p
call void @llvm.sideeffect()
%t = load float, float* %p
ret float %t
}
; Redundant load elimination across a @llvm.sideeffect.
; CHECK-LABEL: rle
; CHECK: load
; CHECK-NOT: load
define float @rle(float* %p) {
%r = load float, float* %p
call void @llvm.sideeffect()
%s = load float, float* %p
%t = fadd float %r, %s
ret float %t
}

View File

@ -0,0 +1,25 @@
; RUN: opt -S < %s -slp-vectorizer -slp-max-reg-size=128 -slp-min-reg-size=128 | FileCheck %s
declare void @llvm.sideeffect()
; SLP vectorization across a @llvm.sideeffect.
; CHECK-LABEL: test
; CHECK: store <4 x float>
define void @test(float* %p) {
%p0 = getelementptr float, float* %p, i64 0
%p1 = getelementptr float, float* %p, i64 1
%p2 = getelementptr float, float* %p, i64 2
%p3 = getelementptr float, float* %p, i64 3
%l0 = load float, float* %p0
%l1 = load float, float* %p1
%l2 = load float, float* %p2
call void @llvm.sideeffect()
%l3 = load float, float* %p3
store float %l0, float* %p0
call void @llvm.sideeffect()
store float %l1, float* %p1
store float %l2, float* %p2
store float %l3, float* %p3
ret void
}