Index: docs/LangRef.rst =================================================================== --- docs/LangRef.rst +++ docs/LangRef.rst @@ -15042,6 +15042,148 @@ ``@llvm.experimental.guard`` cannot be invoked. +'``llvm.experimental.widenable.condition``' Intrinsic +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Syntax: +""""""" + +:: + + declare i1 @llvm.experimental.widenable.condition() + +Overview: +""""""""" + +This intrinsic represents "widenable conditions" which are +boolean expressions with the following property: whether this +expression is `true` or `false`, the program executes correctly +and no undefined behavior is caused by this condition. + +Together with :ref:`deoptimization operand bundles `, +``@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. + +Arguments: +"""""""""" + +None. + +Semantics: +"""""""""" + +The intrinsic ``@llvm.experimental.widenable.condition()`` +always non-deterministically returns `true` or `false`, and +it is guaranteed that any returned value leads to correct +program execution and creates no undefined behavior in code. + +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 +boolean 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, ) ["deopt"()] + ; 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() [ "deopt"() ] + +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. + +Widening: +"""""""""" + +By definition, it is legal to replace the result of +``@llvm.experimental.widenable.condition`` with any boolean +expressions. When we make replacement of + +.. code-block:: text + + %widenable_condition = call i1 @llvm.experimental.widenable.condition() + +with + +.. code-block:: text + + %widenable_condition = call i1 @llvm.experimental.widenable.condition() + %new_cond = and i1 %any_other_cond, %widenable_condition + +where `%any_other_cond` is an arbitrary `i1` value, +such transform is called widening, in sense that it widens +the space of situations when this condition is `false`. In +application to guards expressed as explicit 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. By making guard widening, we may impose +more strict conditions on `guarded` block and bail to the +deopt when the new condition is not met. + +Lowering: +""""""""" + +It is always correct to replace the result of +``@llvm.experimental.widenable.condition`` +with any boolean expression. + + '``llvm.load.relative``' Intrinsic ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Index: include/llvm/IR/Intrinsics.td =================================================================== --- include/llvm/IR/Intrinsics.td +++ include/llvm/IR/Intrinsics.td @@ -830,6 +830,10 @@ 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]>; Index: include/llvm/InitializePasses.h =================================================================== --- include/llvm/InitializePasses.h +++ include/llvm/InitializePasses.h @@ -140,6 +140,7 @@ void initializeExpandMemCmpPassPass(PassRegistry&); void initializeExpandPostRAPass(PassRegistry&); void initializeExpandReductionsPass(PassRegistry&); +void initializeMakeGuardsExplicitLegacyPassPass(PassRegistry&); void initializeExternalAAWrapperPassPass(PassRegistry&); void initializeFEntryInserterPass(PassRegistry&); void initializeFinalizeMachineBundlesPass(PassRegistry&); Index: lib/Passes/PassBuilder.cpp =================================================================== --- lib/Passes/PassBuilder.cpp +++ lib/Passes/PassBuilder.cpp @@ -128,6 +128,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" Index: lib/Passes/PassRegistry.def =================================================================== --- lib/Passes/PassRegistry.def +++ lib/Passes/PassRegistry.def @@ -158,6 +158,7 @@ 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()) Index: lib/Transforms/Scalar/CMakeLists.txt =================================================================== --- lib/Transforms/Scalar/CMakeLists.txt +++ lib/Transforms/Scalar/CMakeLists.txt @@ -45,6 +45,7 @@ LowerAtomic.cpp LowerExpectIntrinsic.cpp LowerGuardIntrinsic.cpp + MakeGuardsExplicit.cpp MemCpyOptimizer.cpp MergeICmps.cpp MergedLoadStoreMotion.cpp Index: lib/Transforms/Scalar/Scalar.cpp =================================================================== --- lib/Transforms/Scalar/Scalar.cpp +++ lib/Transforms/Scalar/Scalar.cpp @@ -50,6 +50,7 @@ initializeNewGVNLegacyPassPass(Registry); initializeEarlyCSELegacyPassPass(Registry); initializeEarlyCSEMemSSALegacyPassPass(Registry); + initializeMakeGuardsExplicitLegacyPassPass(Registry); initializeGVNHoistLegacyPassPass(Registry); initializeGVNSinkLegacyPassPass(Registry); initializeFlattenCFGPassPass(Registry);