From f2007662758365c4f045dcf7dea98a6cb5595d2c Mon Sep 17 00:00:00 2001 From: Sanjoy Das Date: Thu, 31 Mar 2016 00:18:46 +0000 Subject: [PATCH] Introduce a @llvm.experimental.guard intrinsic Summary: As discussed on llvm-dev[1]. This change adds the basic boilerplate code around having this intrinsic in LLVM: - Changes in Intrinsics.td, and the IR Verifier - A lowering pass to lower @llvm.experimental.guard to normal control flow - Inliner support [1]: http://lists.llvm.org/pipermail/llvm-dev/2016-February/095523.html Reviewers: reames, atrick, chandlerc, rnk, JosephTremoulet, echristo Subscribers: mcrosier, llvm-commits Differential Revision: http://reviews.llvm.org/D18527 llvm-svn: 264976 --- docs/LangRef.rst | 44 +++++++ include/llvm/IR/Intrinsics.td | 4 + include/llvm/InitializePasses.h | 1 + include/llvm/Transforms/Scalar.h | 6 + lib/IR/Verifier.cpp | 8 ++ lib/Transforms/Scalar/CMakeLists.txt | 1 + lib/Transforms/Scalar/LowerGuardIntrinsic.cpp | 108 ++++++++++++++++++ lib/Transforms/Scalar/Scalar.cpp | 1 + lib/Transforms/Utils/InlineFunction.cpp | 12 +- test/Transforms/Inline/guard-intrinsic.ll | 39 +++++++ test/Transforms/LowerGuardIntrinsic/basic.ll | 62 ++++++++++ test/Verifier/guard-intrinsic.ll | 26 +++++ 12 files changed, 307 insertions(+), 5 deletions(-) create mode 100644 lib/Transforms/Scalar/LowerGuardIntrinsic.cpp create mode 100644 test/Transforms/Inline/guard-intrinsic.ll create mode 100644 test/Transforms/LowerGuardIntrinsic/basic.ll create mode 100644 test/Verifier/guard-intrinsic.ll diff --git a/docs/LangRef.rst b/docs/LangRef.rst index c5704b649fb..23bef902898 100644 --- a/docs/LangRef.rst +++ b/docs/LangRef.rst @@ -12181,6 +12181,50 @@ ensure that this symbol is defined). The call arguments to arguments of the specified types, and not as varargs. +'``llvm.experimental.guard``' Intrinsic +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Syntax: +""""""" + +:: + + declare void @llvm.experimental.guard(i1, ...) [ "deopt"(...) ] + +Overview: +""""""""" + +This intrinsic, together with :ref:`deoptimization operand bundles +`, allows frontends to express guards or checks on +optimistic assumptions made during compilation. The semantics of +``@llvm.experimental.guard`` is defined in terms of +``@llvm.experimental.deoptimize`` -- its body is defined to be +equivalent to: + +.. code-block:: llvm + + define void @llvm.experimental.guard(i1 %pred, ) { + %realPred = and i1 %pred, undef + br i1 %realPred, label %continue, label %leave + + leave: + call void @llvm.experimental.deoptimize() [ "deopt"() ] + ret void + + continue: + ret void + } + +In words, ``@llvm.experimental.guard`` executes the attached +``"deopt"`` continuation if (but **not** only if) its first argument +is ``false``. Since the optimizer is allowed to replace the ``undef`` +with an arbitrary value, it can optimize guard to fail "spuriously", +i.e. without the original condition being false (hence the "not only +if"); and this allows for "check widening" type optimizations. + +``@llvm.experimental.guard`` cannot be invoked. + + Stack Map Intrinsics -------------------- diff --git a/include/llvm/IR/Intrinsics.td b/include/llvm/IR/Intrinsics.td index 5f3c7e3ef40..b056cdbb6ca 100644 --- a/include/llvm/IR/Intrinsics.td +++ b/include/llvm/IR/Intrinsics.td @@ -597,6 +597,10 @@ def int_debugtrap : Intrinsic<[]>, def int_experimental_deoptimize : Intrinsic<[llvm_any_ty], [llvm_vararg_ty], [Throws]>; +// Support for speculative runtime guards +def int_experimental_guard : Intrinsic<[], [llvm_i1_ty, llvm_vararg_ty], + [Throws]>; + // NOP: calls/invokes to this intrinsic are removed by codegen def int_donothing : Intrinsic<[], [], [IntrNoMem]>; diff --git a/include/llvm/InitializePasses.h b/include/llvm/InitializePasses.h index 16f978ae72f..125d2a19318 100644 --- a/include/llvm/InitializePasses.h +++ b/include/llvm/InitializePasses.h @@ -188,6 +188,7 @@ void initializeLoopIdiomRecognizePass(PassRegistry&); void initializeLowerAtomicPass(PassRegistry&); void initializeLowerBitSetsPass(PassRegistry&); void initializeLowerExpectIntrinsicPass(PassRegistry&); +void initializeLowerGuardIntrinsicPass(PassRegistry&); void initializeLowerIntrinsicsPass(PassRegistry&); void initializeLowerInvokePass(PassRegistry&); void initializeLowerSwitchPass(PassRegistry&); diff --git a/include/llvm/Transforms/Scalar.h b/include/llvm/Transforms/Scalar.h index 6117c05e556..c2bd77c1277 100644 --- a/include/llvm/Transforms/Scalar.h +++ b/include/llvm/Transforms/Scalar.h @@ -372,6 +372,12 @@ FunctionPass *createSinkingPass(); // Pass *createLowerAtomicPass(); +//===----------------------------------------------------------------------===// +// +// LowerGuardIntrinsic - Lower guard intrinsics to normal control flow. +// +Pass *createLowerGuardIntrinsicPass(); + //===----------------------------------------------------------------------===// // // ValuePropagation - Propagate CFG-derived value information diff --git a/lib/IR/Verifier.cpp b/lib/IR/Verifier.cpp index cda31503638..5495b0f39eb 100644 --- a/lib/IR/Verifier.cpp +++ b/lib/IR/Verifier.cpp @@ -4106,6 +4106,14 @@ void Verifier::visitIntrinsicCallSite(Intrinsic::ID ID, CallSite CS) { break; } + case Intrinsic::experimental_guard: { + Assert(CS.isCall(), "experimental_guard cannot be invoked", CS); + Assert(CS.countOperandBundlesOfType(LLVMContext::OB_deopt) == 1, + "experimental_guard must have exactly one " + "\"deopt\" operand bundle"); + break; + } + case Intrinsic::experimental_deoptimize: { Assert(CS.isCall(), "experimental_deoptimize cannot be invoked", CS); Assert(CS.countOperandBundlesOfType(LLVMContext::OB_deopt) == 1, diff --git a/lib/Transforms/Scalar/CMakeLists.txt b/lib/Transforms/Scalar/CMakeLists.txt index 2684cee838f..f62cf6b92fe 100644 --- a/lib/Transforms/Scalar/CMakeLists.txt +++ b/lib/Transforms/Scalar/CMakeLists.txt @@ -32,6 +32,7 @@ add_llvm_library(LLVMScalarOpts LoopVersioningLICM.cpp LowerAtomic.cpp LowerExpectIntrinsic.cpp + LowerGuardIntrinsic.cpp MemCpyOptimizer.cpp MergedLoadStoreMotion.cpp NaryReassociate.cpp diff --git a/lib/Transforms/Scalar/LowerGuardIntrinsic.cpp b/lib/Transforms/Scalar/LowerGuardIntrinsic.cpp new file mode 100644 index 00000000000..5f5dd197b44 --- /dev/null +++ b/lib/Transforms/Scalar/LowerGuardIntrinsic.cpp @@ -0,0 +1,108 @@ +//===- LowerGuardIntrinsic.cpp - Lower the guard intrinsic ---------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This pass lowers the llvm.experimental.guard intrinsic to a conditional call +// to @llvm.experimental.deoptimize. Once this happens, the guard can no longer +// be widened. +// +//===----------------------------------------------------------------------===// + +#include "llvm/Transforms/Scalar.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/IR/BasicBlock.h" +#include "llvm/IR/Function.h" +#include "llvm/IR/InstIterator.h" +#include "llvm/IR/Instructions.h" +#include "llvm/IR/Intrinsics.h" +#include "llvm/IR/IRBuilder.h" +#include "llvm/IR/Module.h" +#include "llvm/Pass.h" +#include "llvm/Transforms/Utils/BasicBlockUtils.h" + +using namespace llvm; + +namespace { +struct LowerGuardIntrinsic : public FunctionPass { + static char ID; + LowerGuardIntrinsic() : FunctionPass(ID) { + initializeLowerGuardIntrinsicPass(*PassRegistry::getPassRegistry()); + } + + bool runOnFunction(Function &F) override; +}; +} + +static void MakeGuardControlFlowExplicit(Function *DeoptIntrinsic, + CallInst *CI) { + OperandBundleDef DeoptOB(*CI->getOperandBundle(LLVMContext::OB_deopt)); + SmallVector Args(std::next(CI->arg_begin()), CI->arg_end()); + + auto *CheckBB = CI->getParent(); + auto *DeoptBlockTerm = + SplitBlockAndInsertIfThen(CI->getArgOperand(0), CI, true); + + auto *CheckBI = cast(CheckBB->getTerminator()); + + // SplitBlockAndInsertIfThen inserts control flow that branches to + // DeoptBlockTerm if the condition is true. We want the opposite. + CheckBI->swapSuccessors(); + + CheckBI->getSuccessor(0)->setName("guarded"); + CheckBI->getSuccessor(1)->setName("deopt"); + + IRBuilder<> B(DeoptBlockTerm); + auto *DeoptCall = B.CreateCall(DeoptIntrinsic, Args, {DeoptOB}, ""); + + if (DeoptIntrinsic->getReturnType()->isVoidTy()) { + B.CreateRetVoid(); + } else { + DeoptCall->setName("deoptcall"); + B.CreateRet(DeoptCall); + } + + DeoptBlockTerm->eraseFromParent(); +} + +bool LowerGuardIntrinsic::runOnFunction(Function &F) { + // Check if we can cheaply rule out the possibility of not having any work to + // do. + auto *GuardDecl = F.getParent()->getFunction( + Intrinsic::getName(Intrinsic::experimental_guard)); + if (!GuardDecl || GuardDecl->use_empty()) + return false; + + SmallVector ToLower; + for (auto &I : instructions(F)) + if (auto *CI = dyn_cast(&I)) + if (auto *F = CI->getCalledFunction()) + if (F->getIntrinsicID() == Intrinsic::experimental_guard) + ToLower.push_back(CI); + + if (ToLower.empty()) + return false; + + auto *DeoptIntrinsic = Intrinsic::getDeclaration( + F.getParent(), Intrinsic::experimental_deoptimize, {F.getReturnType()}); + + for (auto *CI : ToLower) { + MakeGuardControlFlowExplicit(DeoptIntrinsic, CI); + CI->eraseFromParent(); + } + + return true; +} + +char LowerGuardIntrinsic::ID = 0; +INITIALIZE_PASS(LowerGuardIntrinsic, "lower-guard-intrinsic", + "Lower the guard intrinsic to normal control flow", false, + false) + +Pass *llvm::createLowerGuardIntrinsicPass() { + return new LowerGuardIntrinsic(); +} diff --git a/lib/Transforms/Scalar/Scalar.cpp b/lib/Transforms/Scalar/Scalar.cpp index 10b14623033..a8c0c14f738 100644 --- a/lib/Transforms/Scalar/Scalar.cpp +++ b/lib/Transforms/Scalar/Scalar.cpp @@ -62,6 +62,7 @@ void llvm::initializeScalarOpts(PassRegistry &Registry) { initializeLoopIdiomRecognizePass(Registry); initializeLowerAtomicPass(Registry); initializeLowerExpectIntrinsicPass(Registry); + initializeLowerGuardIntrinsicPass(Registry); initializeMemCpyOptPass(Registry); initializeMergedLoadStoreMotionPass(Registry); initializeNaryReassociatePass(Registry); diff --git a/lib/Transforms/Utils/InlineFunction.cpp b/lib/Transforms/Utils/InlineFunction.cpp index 31cf5fbdc85..50eff521318 100644 --- a/lib/Transforms/Utils/InlineFunction.cpp +++ b/lib/Transforms/Utils/InlineFunction.cpp @@ -428,12 +428,14 @@ static BasicBlock *HandleCallsInBlockInlinedThroughInvoke( continue; // We do not need to (and in fact, cannot) convert possibly throwing calls - // to @llvm.experimental_deoptimize into invokes. The caller's "segment" of - // the deoptimization continuation attached to the newly inlined - // @llvm.experimental_deoptimize call should contain the exception handling - // logic, if any. + // to @llvm.experimental_deoptimize (resp. @llvm.experimental.guard) into + // invokes. The caller's "segment" of the deoptimization continuation + // attached to the newly inlined @llvm.experimental_deoptimize + // (resp. @llvm.experimental.guard) call should contain the exception + // handling logic, if any. if (auto *F = CI->getCalledFunction()) - if (F->getIntrinsicID() == Intrinsic::experimental_deoptimize) + if (F->getIntrinsicID() == Intrinsic::experimental_deoptimize || + F->getIntrinsicID() == Intrinsic::experimental_guard) continue; if (auto FuncletBundle = CI->getOperandBundle(LLVMContext::OB_funclet)) { diff --git a/test/Transforms/Inline/guard-intrinsic.ll b/test/Transforms/Inline/guard-intrinsic.ll new file mode 100644 index 00000000000..76d683df6e9 --- /dev/null +++ b/test/Transforms/Inline/guard-intrinsic.ll @@ -0,0 +1,39 @@ +; RUN: opt -S -always-inline < %s | FileCheck %s + +declare void @llvm.experimental.guard(i1, ...) + +define i8 @callee(i1* %c_ptr) alwaysinline { + %c = load volatile i1, i1* %c_ptr + call void(i1, ...) @llvm.experimental.guard(i1 %c, i32 1) [ "deopt"(i32 1) ] + ret i8 5 +} + +define void @caller_0(i1* %c, i8* %ptr) { +; CHECK-LABEL: @caller_0( +entry: +; CHECK: [[COND:%[^ ]+]] = load volatile i1, i1* %c +; CHECK-NEXT: call void (i1, ...) @llvm.experimental.guard(i1 [[COND]], i32 1) [ "deopt"(i32 2, i32 1) ] +; CHECK-NEXT: store i8 5, i8* %ptr + + %v = call i8 @callee(i1* %c) [ "deopt"(i32 2) ] + store i8 %v, i8* %ptr + ret void +} + +define i32 @caller_1(i1* %c, i8* %ptr) personality i8 3 { +; CHECK-LABEL: @caller_1( +; CHECK: [[COND:%[^ ]+]] = load volatile i1, i1* %c +; CHECK-NEXT: call void (i1, ...) @llvm.experimental.guard(i1 [[COND]], i32 1) [ "deopt"(i32 3, i32 1) ] +; CHECK-NEXT: br label %normal +entry: + %v = invoke i8 @callee(i1* %c) [ "deopt"(i32 3) ] to label %normal + unwind label %unwind + +unwind: + %lp = landingpad i32 cleanup + ret i32 43 + +normal: + store i8 %v, i8* %ptr + ret i32 42 +} diff --git a/test/Transforms/LowerGuardIntrinsic/basic.ll b/test/Transforms/LowerGuardIntrinsic/basic.ll new file mode 100644 index 00000000000..ae696cf4269 --- /dev/null +++ b/test/Transforms/LowerGuardIntrinsic/basic.ll @@ -0,0 +1,62 @@ +; RUN: opt -S -lower-guard-intrinsic < %s | FileCheck %s + +declare void @llvm.experimental.guard(i1, ...) + +define i8 @f_basic(i1* %c_ptr) { +; CHECK-LABEL: @f_basic( + + %c = load volatile i1, i1* %c_ptr + call void(i1, ...) @llvm.experimental.guard(i1 %c, i32 1) [ "deopt"(i32 1) ] + ret i8 5 + +; CHECK: br i1 %c, label %guarded, label %deopt +; CHECK: deopt: +; CHECK-NEXT: %deoptcall = call i8 (...) @llvm.experimental.deoptimize.i8(i32 1) [ "deopt"(i32 1) ] +; CHECK-NEXT: ret i8 %deoptcall +; CHECK: guarded: +; CHECK-NEXT: ret i8 5 +} + +define void @f_void_return_ty(i1* %c_ptr) { +; CHECK-LABEL: @f_void_return_ty( + + %c = load volatile i1, i1* %c_ptr + call void(i1, ...) @llvm.experimental.guard(i1 %c, i32 1) [ "deopt"() ] + ret void + +; CHECK: br i1 %c, label %guarded, label %deopt +; CHECK: deopt: +; CHECK-NEXT: call void (...) @llvm.experimental.deoptimize.isVoid(i32 1) [ "deopt"() ] +; CHECK-NEXT: ret void +; CHECK: guarded: +; CHECK-NEXT: ret void +} + +define void @f_multiple_args(i1* %c_ptr) { +; CHECK-LABEL: @f_multiple_args( + + %c = load volatile i1, i1* %c_ptr + call void(i1, ...) @llvm.experimental.guard(i1 %c, i32 1, i32 2, double 500.0) [ "deopt"(i32 2, i32 3) ] + ret void + +; CHECK: br i1 %c, label %guarded, label %deopt +; CHECK: deopt: +; CHECK-NEXT: call void (...) @llvm.experimental.deoptimize.isVoid(i32 1, i32 2, double 5.000000e+02) [ "deopt"(i32 2, i32 3) ] +; CHECK-NEXT: ret void +; CHECK: guarded: +; CHECK-NEXT: ret void +} + +define i32 @f_zero_args(i1* %c_ptr) { +; CHECK-LABEL: @f_zero_args( + %c = load volatile i1, i1* %c_ptr + call void(i1, ...) @llvm.experimental.guard(i1 %c) [ "deopt"(i32 2, i32 3) ] + ret i32 500 + +; CHECK: br i1 %c, label %guarded, label %deopt +; CHECK: deopt: +; CHECK-NEXT: %deoptcall = call i32 (...) @llvm.experimental.deoptimize.i32() [ "deopt"(i32 2, i32 3) ] +; CHECK-NEXT: ret i32 %deoptcall +; CHECK: guarded: +; CHECK-NEXT: ret i32 500 +} diff --git a/test/Verifier/guard-intrinsic.ll b/test/Verifier/guard-intrinsic.ll new file mode 100644 index 00000000000..b0f4c3cb77f --- /dev/null +++ b/test/Verifier/guard-intrinsic.ll @@ -0,0 +1,26 @@ +; RUN: not opt -S -verify < %s 2>&1 | FileCheck %s + +declare void @llvm.experimental.guard(i1, ...) + +declare void @unknown() + +define void @f_nodeopt() { +entry: + call void(i1, ...) @llvm.experimental.guard(i1 undef, i32 1, i32 2) +; CHECK: guard must have exactly one "deopt" operand bundle + ret void +} + +define void @f_invoke() personality i8 3 { +entry: + invoke void(i1, ...) @llvm.experimental.guard(i1 undef, i32 0, float 0.0) [ "deopt"() ] to label %ok unwind label %not_ok +; CHECK: guard cannot be invoked + +ok: + ret void + +not_ok: + %0 = landingpad { i8*, i32 } + filter [0 x i8*] zeroinitializer + ret void +}