Skip to content

Commit

Permalink
Add an @llvm.sideeffect intrinsic
Browse files Browse the repository at this point in the history
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
  • Loading branch information
Dan Gohman committed Nov 8, 2017
1 parent c707c6f commit 2c74fe9
Show file tree
Hide file tree
Showing 31 changed files with 446 additions and 6 deletions.
30 changes: 30 additions & 0 deletions llvm/docs/LangRef.rst
Expand Up @@ -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
--------------------

Expand Down
1 change: 1 addition & 0 deletions llvm/include/llvm/Analysis/TargetTransformInfoImpl.h
Expand Up @@ -152,6 +152,7 @@ class TargetTransformInfoImplBase {

case Intrinsic::annotation:
case Intrinsic::assume:
case Intrinsic::sideeffect:
case Intrinsic::dbg_declare:
case Intrinsic::dbg_value:
case Intrinsic::invariant_start:
Expand Down
1 change: 1 addition & 0 deletions llvm/include/llvm/CodeGen/BasicTTIImpl.h
Expand Up @@ -1023,6 +1023,7 @@ class BasicTTIImplBase : public TargetTransformInfoImplCRTPBase<T> {
// 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)
Expand Down
6 changes: 6 additions & 0 deletions llvm/include/llvm/IR/Intrinsics.td
Expand Up @@ -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]>;
Expand Down
1 change: 1 addition & 0 deletions llvm/lib/Analysis/AliasSetTracker.cpp
Expand Up @@ -436,6 +436,7 @@ void AliasSetTracker::addUnknown(Instruction *Inst) {
break;
// FIXME: Add lifetime/invariant intrinsics (See: PR30807).
case Intrinsic::assume:
case Intrinsic::sideeffect:
return;
}
}
Expand Down
4 changes: 3 additions & 1 deletion llvm/lib/Analysis/ValueTracking.cpp
Expand Up @@ -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:
Expand Down Expand Up @@ -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.
Expand Down
3 changes: 2 additions & 1 deletion llvm/lib/Analysis/VectorUtils.cpp
Expand Up @@ -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;
}
Expand Down
2 changes: 2 additions & 0 deletions llvm/lib/CodeGen/SelectionDAG/FastISel.cpp
Expand Up @@ -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;
Expand Down
3 changes: 2 additions & 1 deletion llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp
Expand Up @@ -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: {
Expand Down
6 changes: 6 additions & 0 deletions llvm/lib/Transforms/Scalar/EarlyCSE.cpp
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion llvm/lib/Transforms/Scalar/GVNHoist.cpp
Expand Up @@ -1110,7 +1110,8 @@ class GVNHoist {
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())
Expand Down
4 changes: 4 additions & 0 deletions llvm/lib/Transforms/Utils/Evaluator.cpp
Expand Up @@ -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");
Expand Down
5 changes: 5 additions & 0 deletions llvm/lib/Transforms/Vectorize/LoadStoreVectorizer.cpp
Expand Up @@ -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"
Expand Down Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion llvm/lib/Transforms/Vectorize/LoopVectorize.cpp
Expand Up @@ -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;
}

Expand Down
4 changes: 3 additions & 1 deletion llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp
Expand Up @@ -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;
Expand Down
9 changes: 9 additions & 0 deletions llvm/test/CodeGen/Generic/intrinsics.ll
Expand Up @@ -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
}
12 changes: 12 additions & 0 deletions llvm/test/Transforms/DCE/int_sideeffect.ll
@@ -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
}
15 changes: 15 additions & 0 deletions llvm/test/Transforms/DeadStoreElimination/int_sideeffect.ll
@@ -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
}
27 changes: 27 additions & 0 deletions llvm/test/Transforms/EarlyCSE/int_sideeffect.ll
@@ -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
}
21 changes: 21 additions & 0 deletions llvm/test/Transforms/FunctionAttrs/int_sideeffect.ll
@@ -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
}
51 changes: 51 additions & 0 deletions llvm/test/Transforms/GVN/int_sideeffect.ll
@@ -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
}
30 changes: 30 additions & 0 deletions llvm/test/Transforms/GVNHoist/int_sideeffect.ll
@@ -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
}

30 changes: 30 additions & 0 deletions llvm/test/Transforms/GVNSink/int_sideeffect.ll
@@ -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
}

0 comments on commit 2c74fe9

Please sign in to comment.