[llvm-dev] RFC: Add guard intrinsics to LLVM

Andrew Trick via llvm-dev llvm-dev at lists.llvm.org
Fri Feb 19 11:12:19 PST 2016


> On Feb 16, 2016, at 6:06 PM, Sanjoy Das <sanjoy at playingwithpointers.com> wrote:
> 
> This is a proposal to add guard intrinsics to LLVM.
> 
> Couple of glossary items: when I say "interpreter" I mean "the most
> conservative tier in the compilation stack" which can be an actual
> interpreter, a "splat compiler" or even a regular JIT that doesn't
> make optimistic assumptions.  By "bailing out to the interpreter" I
> mean "side exit" as defined by Philip in
> http://www.philipreames.com/Blog/2015/05/20/deoptimization-terminology/
> 
> 
> # high level summary
> 
> Guard intrinsics are intended to represent "if (!cond) leave();" like
> control flow in a structured manner.  This kind of control flow is
> abundant in IR coming from safe languages due to things like range
> checks and null checks (and overflow checks, in some cases).  From a
> high level, there are two distinct motivations for introducing guard
> intrinsics:
> 
> - To keep control flow as simple and "straight line"-like as is
>   reasonable
> 
> - To allow certain kinds of "widening" transforms that cannot be
>   soundly done in an explicit "check-and-branch" control flow
>   representation
> 
> ## straw man specification
> 
> Signature:
> 
> ```
> declare void @llvm.guard_on(i1 %predicate) ;; requires [ "deopt"(...) ]
> ```

Thanks Sanjoy. Sorry it took a couple days for me to read this.

I like this approach a lot. I can envision multiple intrinsics leveraging the basic framework of operand bundles, but each with different semantics and lowering. I'm no longer considering extending the patchpoint design to cover these cases. Your semantics for @llvm.guard_on are perfect for managed runtimes.

Just to give you the idea of a similar intrinsic we may want to introduce... I would also like to add a @llvm.trap_on(i1 %pred) intrinsic, with side effects and lowering as such:

  br i1 %cond, label %succeed, label %fail

fail:
  call void @llvm.trap()
  unreachable

This clearly doesn't need operand bundles, but using an intrinsic would permit special code motion semantics. It could be hoisted and merged with other traps, but the condition could never be widened beyond the union of the original conditions. Unlike deoptimizing guards, it would need to be sequenced with memory barriers, but could otherwise be hoisted as readnone. The semantics of "non deoptimizing guards" are different enough that they should be a different intrinsic. @trap_on would essentially be halfway between @guard_on and @exception_on.

> ## memory effects (unresolved)
> 
> [I haven't come up with a good model for the memory effects of
> `@llvm.guard_on`, suggestions are very welcome.]
> 
> I'd really like to model `@llvm.guard_on` as a readonly function,
> since it does not write to memory if it returns; and e.g. forwarding
> loads across a call to `@llvm.guard_on` should be legal.

Of course it would be nice to consider these intrinsics readonly. (One thing we never fixed with patchpoints is the memory effects and that was probably hurting performance.) But has this bug been fixed yet: http://llvm.org/pr18912 Optimizer should consider "maythrow" calls (those without "nounwind) as having side effects? I vaguely remember some work being done to improve the situation, but last I checked LICM was still violating it, and who knows what else?

BTW, if you do want readonly semantics, why would you want readonly to be implicit instead of explicit?

> However, I'm in a quandary around representing the "may never return"
> aspect of `@llvm.guard_on`: I have to make it illegal to, say, hoist a
> load form `%ptr` across a guard on `%ptr != null`.  There are couple
> of ways I can think of dealing with this, none of them are both easy
> and neat:
> 
> - State that since `@llvm.guard_on` could have had an infinite loop
>   in it, it may never return. Unfortunately, the LLVM IR has some
>   rough edges on readonly infinite loops (since C++ disallows that),
>   so LLVM will require some preparatory work before we can do this
>   soundly.
> 
> - State that `@llvm.guard_on` can unwind, and thus has non-local
>   control flow.  This can actually work (and is pretty close to
>   the actual semantics), but is somewhat of hack since
>   `@llvm.guard_on` doesn't _really_ throw an exception.
> 
>  - Special case `@llvm.guard_on` (yuck!).
> 
> What do you think?


I think you would need to mark @guard_on as may-unwind AND fix any lingering assumptions in LLVM that readonly calls are nounwind. (Loads can be CSE's across unwind calls but can't be hoisted across them).

An alternative is to *not* mark them as readonly but rely on alias analysis. I think doing that would properly model the "unwind" path for the purpose of IPA/Inlining. Introducing TBAA on calls should be easy and sufficient to unblock the memory optimization you need.

Of course, IPA still needs to know about side exits, which it really should with any may-unwind call. Other than that, I don't see the problem.

Another good point mentioned later in the thread is that a readonly callee should *not* imply a readonly @guard_on or other "deopt" call site. The simple solution for this is to disallow "deopt" of readonly calls.

-Andy


More information about the llvm-dev mailing list