Skip to content

Commit

Permalink
Introduce llvm.experimental.widenable_condition intrinsic
Browse files Browse the repository at this point in the history
This patch introduces a new instinsic `@llvm.experimental.widenable_condition`
that allows explicit representation for guards. It is an alternative to using
`@llvm.experimental.guard` intrinsic that does not contain implicit control flow.

We keep finding places where `@llvm.experimental.guard` is not supported or
treated too conservatively, and there are 2 reasons to that:

- `@llvm.experimental.guard` has memory write side effect to model implicit control flow,
  and this sometimes confuses passes and analyzes that work with memory;
- Not all passes and analysis are aware of the semantics of guards. These passes treat them
  as regular throwing call and have no idea that the condition of guard may be used to prove
  something. One well-known place which had caused us troubles in the past is explicit loop
  iteration count calculation in SCEV. Another example is new loop unswitching which is not
  aware of guards. Whenever a new pass appears, we potentially have this problem there.

Rather than go and fix all these places (and commit to keep track of them and add support
in future), it seems more reasonable to leverage the existing optimizer's logic as much as possible.
The only significant difference between guards and regular explicit branches is that guard's condition
can be widened. It means that a guard contains (explicitly or implicitly) a `deopt` block successor,
and it is always legal to go there no matter what the guard condition is. The other successor is
a guarded block, and it is only legal to go there if the condition is true.

This patch introduces a new explicit form of guards alternative to `@llvm.experimental.guard`
intrinsic. Now a widenable guard can be represented in the CFG explicitly like this:


    %widenable_condition = call i1 @llvm.experimental.widenable.condition()
    %new_condition = and i1 %cond, %widenable_condition
    br i1 %new_condition, label %guarded, label %deopt

  guarded:
    ; Guarded instructions

  deopt:
    call type @llvm.experimental.deoptimize(<args...>) [ "deopt"(<deopt_args...>) ]

The new intrinsic `@llvm.experimental.widenable.condition` has semantics of an
`undef`, but the intrinsic prevents the optimizer from folding it early. This form
should exploit all optimization boons provided to `br` instuction, and it still can be
widened by replacing the result of `@llvm.experimental.widenable.condition()`
with `and` with any arbitrary boolean value (as long as the branch that is taken when
it is `false` has a deopt and has no side-effects).

For more motivation, please check llvm-dev discussion "[llvm-dev] Giving up using
implicit control flow in guards".

This patch introduces this new intrinsic with respective LangRef changes and a pass
that converts old-style guards (expressed as intrinsics) into the new form.

The naming discussion is still ungoing. Merging this to unblock further items. We can
later change the name of this intrinsic.

Reviewed By: reames, fedor.sergeev, sanjoy
Differential Revision: https://reviews.llvm.org/D51207

llvm-svn: 348593
  • Loading branch information
Max Kazantsev committed Dec 7, 2018
1 parent d6e6e23 commit b9e65cb
Show file tree
Hide file tree
Showing 11 changed files with 532 additions and 0 deletions.
139 changes: 139 additions & 0 deletions llvm/docs/LangRef.rst
Expand Up @@ -15604,6 +15604,145 @@ if"); and this allows for "check widening" type optimizations.
``@llvm.experimental.guard`` cannot be invoked.


'``llvm.experimental.widenable.condition``' Intrinsic
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Syntax:
"""""""

::

declare i1 @llvm.experimental.widenable.condition()

Overview:
"""""""""

This intrinsic represents a "widenable condition" which is
boolean expressions with the following property: whether this
expression is `true` or `false`, the program is correct and
well-defined.

Together with :ref:`deoptimization operand bundles <deopt_opbundles>`,
``@llvm.experimental.widenable.condition`` allows frontends to
express guards or checks on optimistic assumptions made during
compilation and represent them as branch instructions on special
conditions.

While this may appear similar in semantics to `undef`, it is very
different in that an invocation produces a particular, singular
value. It is also intended to be lowered late, and remain available
for specific optimizations and transforms that can benefit from its
special properties.

Arguments:
""""""""""

None.

Semantics:
""""""""""

The intrinsic ``@llvm.experimental.widenable.condition()``
returns either `true` or `false`. For each evaluation of a call
to this intrinsic, the program must be valid and correct both if
it returns `true` and if it returns `false`. This allows
transformation passes to replace evaluations of this intrinsic
with either value whenever one is beneficial.

When used in a branch condition, it allows us to choose between
two alternative correct solutions for the same problem, like
in example below:

.. code-block:: text

%cond = call i1 @llvm.experimental.widenable.condition()
br i1 %cond, label %solution_1, label %solution_2

label %fast_path:
; Apply memory-consuming but fast solution for a task.

label %slow_path:
; Cheap in memory but slow solution.

Whether the result of intrinsic's call is `true` or `false`,
it should be correct to pick either solution. We can switch
between them by replacing the result of
``@llvm.experimental.widenable.condition`` with different
`i1` expressions.

This is how it can be used to represent guards as widenable branches:

.. code-block:: text

block:
; Unguarded instructions
call void @llvm.experimental.guard(i1 %cond, <args...>) ["deopt"(<deopt_args...>)]
; Guarded instructions

Can be expressed in an alternative equivalent form of explicit branch using
``@llvm.experimental.widenable.condition``:

.. code-block:: text

block:
; Unguarded instructions
%widenable_condition = call i1 @llvm.experimental.widenable.condition()
%guard_condition = and i1 %cond, %widenable_condition
br i1 %guard_condition, label %guarded, label %deopt

guarded:
; Guarded instructions

deopt:
call type @llvm.experimental.deoptimize(<args...>) [ "deopt"(<deopt_args...>) ]

So the block `guarded` is only reachable when `%cond` is `true`,
and it should be valid to go to the block `deopt` whenever `%cond`
is `true` or `false`.

``@llvm.experimental.widenable.condition`` will never throw, thus
it cannot be invoked.

Guard widening:
"""""""""""""""

When ``@llvm.experimental.widenable.condition()`` is used in
condition of a guard represented as explicit branch, it is
legal to widen the guard's condition with any additional
conditions.

Guard widening looks like replacement of

.. code-block:: text

%widenable_cond = call i1 @llvm.experimental.widenable.condition()
%guard_cond = and i1 %cond, %widenable_cond
br i1 %guard_cond, label %guarded, label %deopt

with

.. code-block:: text

%widenable_cond = call i1 @llvm.experimental.widenable.condition()
%new_cond = and i1 %any_other_cond, %widenable_cond
%new_guard_cond = and i1 %cond, %new_cond
br i1 %new_guard_cond, label %guarded, label %deopt

for this branch. Here `%any_other_cond` is an arbitrarily chosen
well-defined `i1` value. By making guard widening, we may
impose stricter conditions on `guarded` block and bail to the
deopt when the new condition is not met.

Lowering:
"""""""""

Default lowering strategy is replacing the result of
call of ``@llvm.experimental.widenable.condition`` with
constant `true`. However it is always correct to replace
it with any other `i1` value. Any pass can
freely do it if it can benefit from non-default lowering.


'``llvm.load.relative``' Intrinsic
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Expand Down
4 changes: 4 additions & 0 deletions llvm/include/llvm/IR/Intrinsics.td
Expand Up @@ -928,6 +928,10 @@ def int_experimental_deoptimize : Intrinsic<[llvm_any_ty], [llvm_vararg_ty],
def int_experimental_guard : Intrinsic<[], [llvm_i1_ty, llvm_vararg_ty],
[Throws]>;

// Supports widenable conditions for guards represented as explicit branches.
def int_experimental_widenable_condition : Intrinsic<[llvm_i1_ty], [],
[IntrInaccessibleMemOnly]>;

// NOP: calls/invokes to this intrinsic are removed by codegen
def int_donothing : Intrinsic<[], [], [IntrNoMem]>;

Expand Down
1 change: 1 addition & 0 deletions llvm/include/llvm/InitializePasses.h
Expand Up @@ -140,6 +140,7 @@ void initializeExpandISelPseudosPass(PassRegistry&);
void initializeExpandMemCmpPassPass(PassRegistry&);
void initializeExpandPostRAPass(PassRegistry&);
void initializeExpandReductionsPass(PassRegistry&);
void initializeMakeGuardsExplicitLegacyPassPass(PassRegistry&);
void initializeExternalAAWrapperPassPass(PassRegistry&);
void initializeFEntryInserterPass(PassRegistry&);
void initializeFinalizeMachineBundlesPass(PassRegistry&);
Expand Down
47 changes: 47 additions & 0 deletions llvm/include/llvm/Transforms/Scalar/MakeGuardsExplicit.h
@@ -0,0 +1,47 @@
//===-- MakeGuardsExplicit.h - Turn guard intrinsics into guard branches --===//
//
// 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 the new form of
// guard represented as widenable explicit branch to the deopt block. The
// difference between this pass and LowerGuardIntrinsic is that after this pass
// the guard represented as intrinsic:
//
// call void(i1, ...) @llvm.experimental.guard(i1 %old_cond) [ "deopt"() ]
//
// transforms to a guard represented as widenable explicit branch:
//
// %widenable_cond = call i1 @llvm.experimental.widenable.condition()
// br i1 (%old_cond & %widenable_cond), label %guarded, label %deopt
//
// Here:
// - The semantics of @llvm.experimental.widenable.condition allows to replace
// %widenable_cond with the construction (%widenable_cond & %any_other_cond)
// without loss of correctness;
// - %guarded is the lower part of old guard intrinsic's parent block split by
// the intrinsic call;
// - %deopt is a block containing a sole call to @llvm.experimental.deoptimize
// intrinsic.
//
// Therefore, this branch preserves the property of widenability.
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_TRANSFORMS_SCALAR_MAKEGUARDSEXPLICIT_H
#define LLVM_TRANSFORMS_SCALAR_MAKEGUARDSEXPLICIT_H

#include "llvm/IR/PassManager.h"

namespace llvm {

struct MakeGuardsExplicitPass : public PassInfoMixin<MakeGuardsExplicitPass> {
PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM);
};

} // namespace llvm

#endif //LLVM_TRANSFORMS_SCALAR_MAKEGUARDSEXPLICIT_H
1 change: 1 addition & 0 deletions llvm/lib/Passes/PassBuilder.cpp
Expand Up @@ -131,6 +131,7 @@
#include "llvm/Transforms/Scalar/LowerAtomic.h"
#include "llvm/Transforms/Scalar/LowerExpectIntrinsic.h"
#include "llvm/Transforms/Scalar/LowerGuardIntrinsic.h"
#include "llvm/Transforms/Scalar/MakeGuardsExplicit.h"
#include "llvm/Transforms/Scalar/MemCpyOptimizer.h"
#include "llvm/Transforms/Scalar/MergedLoadStoreMotion.h"
#include "llvm/Transforms/Scalar/NaryReassociate.h"
Expand Down
1 change: 1 addition & 0 deletions llvm/lib/Passes/PassRegistry.def
Expand Up @@ -165,6 +165,7 @@ FUNCTION_PASS("dot-cfg-only", CFGOnlyPrinterPass())
FUNCTION_PASS("early-cse", EarlyCSEPass(/*UseMemorySSA=*/false))
FUNCTION_PASS("early-cse-memssa", EarlyCSEPass(/*UseMemorySSA=*/true))
FUNCTION_PASS("ee-instrument", EntryExitInstrumenterPass(/*PostInlining=*/false))
FUNCTION_PASS("make-guards-explicit", MakeGuardsExplicitPass())
FUNCTION_PASS("post-inline-ee-instrument", EntryExitInstrumenterPass(/*PostInlining=*/true))
FUNCTION_PASS("gvn-hoist", GVNHoistPass())
FUNCTION_PASS("instcombine", InstCombinePass())
Expand Down
1 change: 1 addition & 0 deletions llvm/lib/Transforms/Scalar/CMakeLists.txt
Expand Up @@ -45,6 +45,7 @@ add_llvm_library(LLVMScalarOpts
LowerAtomic.cpp
LowerExpectIntrinsic.cpp
LowerGuardIntrinsic.cpp
MakeGuardsExplicit.cpp
MemCpyOptimizer.cpp
MergeICmps.cpp
MergedLoadStoreMotion.cpp
Expand Down
120 changes: 120 additions & 0 deletions llvm/lib/Transforms/Scalar/MakeGuardsExplicit.cpp
@@ -0,0 +1,120 @@
//===- MakeGuardsExplicit.cpp - Turn guard intrinsics into guard branches -===//
//
// 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 the new form of
// guard represented as widenable explicit branch to the deopt block. The
// difference between this pass and LowerGuardIntrinsic is that after this pass
// the guard represented as intrinsic:
//
// call void(i1, ...) @llvm.experimental.guard(i1 %old_cond) [ "deopt"() ]
//
// transforms to a guard represented as widenable explicit branch:
//
// %widenable_cond = call i1 @llvm.experimental.widenable.condition()
// br i1 (%old_cond & %widenable_cond), label %guarded, label %deopt
//
// Here:
// - The semantics of @llvm.experimental.widenable.condition allows to replace
// %widenable_cond with the construction (%widenable_cond & %any_other_cond)
// without loss of correctness;
// - %guarded is the lower part of old guard intrinsic's parent block split by
// the intrinsic call;
// - %deopt is a block containing a sole call to @llvm.experimental.deoptimize
// intrinsic.
//
// Therefore, this branch preserves the property of widenability.
//
//===----------------------------------------------------------------------===//

#include "llvm/Transforms/Scalar/MakeGuardsExplicit.h"
#include "llvm/Analysis/GuardUtils.h"
#include "llvm/IR/InstIterator.h"
#include "llvm/IR/IntrinsicInst.h"
#include "llvm/IR/Intrinsics.h"
#include "llvm/IR/IRBuilder.h"
#include "llvm/Pass.h"
#include "llvm/Transforms/Scalar.h"
#include "llvm/Transforms/Utils/GuardUtils.h"

using namespace llvm;

namespace {
struct MakeGuardsExplicitLegacyPass : public FunctionPass {
static char ID;
MakeGuardsExplicitLegacyPass() : FunctionPass(ID) {
initializeMakeGuardsExplicitLegacyPassPass(*PassRegistry::getPassRegistry());
}

bool runOnFunction(Function &F) override;
};
}

static void turnToExplicitForm(CallInst *Guard, Function *DeoptIntrinsic) {
// Replace the guard with an explicit branch (just like in GuardWidening).
BasicBlock *BB = Guard->getParent();
makeGuardControlFlowExplicit(DeoptIntrinsic, Guard);
BranchInst *ExplicitGuard = cast<BranchInst>(BB->getTerminator());
assert(ExplicitGuard->isConditional() && "Must be!");

// We want the guard to be expressed as explicit control flow, but still be
// widenable. For that, we add Widenable Condition intrinsic call to the
// guard's condition.
IRBuilder<> B(ExplicitGuard);
auto *WidenableCondition =
B.CreateIntrinsic(Intrinsic::experimental_widenable_condition,
{}, {}, nullptr, "widenable_cond");
WidenableCondition->setCallingConv(Guard->getCallingConv());
auto *NewCond =
B.CreateAnd(ExplicitGuard->getCondition(), WidenableCondition);
NewCond->setName("exiplicit_guard_cond");
ExplicitGuard->setCondition(NewCond);
Guard->eraseFromParent();
}

static bool explicifyGuards(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<CallInst *, 8> GuardIntrinsics;
for (auto &I : instructions(F))
if (isGuard(&I))
GuardIntrinsics.push_back(cast<CallInst>(&I));

if (GuardIntrinsics.empty())
return false;

auto *DeoptIntrinsic = Intrinsic::getDeclaration(
F.getParent(), Intrinsic::experimental_deoptimize, {F.getReturnType()});
DeoptIntrinsic->setCallingConv(GuardDecl->getCallingConv());

for (auto *Guard : GuardIntrinsics)
turnToExplicitForm(Guard, DeoptIntrinsic);

return true;
}

bool MakeGuardsExplicitLegacyPass::runOnFunction(Function &F) {
return explicifyGuards(F);
}

char MakeGuardsExplicitLegacyPass::ID = 0;
INITIALIZE_PASS(MakeGuardsExplicitLegacyPass, "make-guards-explicit",
"Lower the guard intrinsic to explicit control flow form",
false, false)

PreservedAnalyses MakeGuardsExplicitPass::run(Function &F,
FunctionAnalysisManager &) {
if (explicifyGuards(F))
return PreservedAnalyses::none();
return PreservedAnalyses::all();
}
1 change: 1 addition & 0 deletions llvm/lib/Transforms/Scalar/Scalar.cpp
Expand Up @@ -52,6 +52,7 @@ void llvm::initializeScalarOpts(PassRegistry &Registry) {
initializeNewGVNLegacyPassPass(Registry);
initializeEarlyCSELegacyPassPass(Registry);
initializeEarlyCSEMemSSALegacyPassPass(Registry);
initializeMakeGuardsExplicitLegacyPassPass(Registry);
initializeGVNHoistLegacyPassPass(Registry);
initializeGVNSinkLegacyPassPass(Registry);
initializeFlattenCFGPassPass(Registry);
Expand Down

0 comments on commit b9e65cb

Please sign in to comment.