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

Sanjoy Das via llvm-dev llvm-dev at lists.llvm.org
Wed Feb 17 21:59:48 PST 2016

On Wed, Feb 17, 2016 at 8:53 PM, Philip Reames
<listmail at philipreames.com> wrote:

> I think you're jumping ahead a bit here.  I'm not sure the semantics are
> anywhere near as weird as you're framing them to be.  :)

I now think this weirdness actually does not have to do anything with
guard_on or bail_to_interpeter, but it has to do with deopt bundles
itself.  Our notion of of "deopt bundles are readonly" is broken to
begin with, and that is what's manifesting as the complication we're
seeing here.

Consider something like

declare @foo() readonly
def @bar() { call @foo() [ "deopt"(XXX) ] }
def @baz() { call @bar() [ "deopt"(YYY) ] }

Right now according to the semantics of "deopt" operand bundles as in
the LangRef, every call site above is readonly.  However, it is
possible for @baz() to write to memory if @bar is deoptimized at the
call site with the call to @foo.

You could say that it isn't legal to mark @foo as readonly, since the
action of deoptimizing one's caller is not a readonly operation.  But
that doesn't work in cases like this:

global *ptr
declare @foo() readwrite
def @bar() { call @foo() [ "deopt"(XXX) ]; *ptr = 42 }
def @baz() { call @bar() [ "deopt"(YYY) ]; int v0 = *ptr }

Naively, it looks like an inter-proc CSE can forward 42 to v0, but
that's unsound, since @bar could get deoptimized at the call to
@foo(), and then who knows what'll get written to *ptr.

My interpretation here is that we're not modeling the deopt
continuations correctly.  Above, the XXX continuation is a delimited
continuation that terminates at the boundary of @bar, and seen from
its caller, the memory effect (and any other effect) of @bar has to
take into account that the "remainder" of @bar() after @foo has
returned is either what it can see in the IR, or the XXX continuation
(which it //could// analyze in theory, but in practice is unlikely

This is kind of a bummer since what I said above directly contradicts
the "As long as the behavior of an operand bundle is describable
within these restrictions, LLVM does not need to have special
knowledge of the operand bundle to not miscompile programs containing
it." bit in the LangRef.  :(

> Essentially, we'd be introducing an aliasing rule along the following:
> "reads nothing on normal path, reads/writes world if guard is taken (in
> which case, does not return)."  Yes, implementing that will be a bit
> complicated, but I don't see this as a fundamental issue.

Yup, and this is a property of deopt operand bundles, not just guards.

>> How is it more general?
> You can express a guard as a conditional branch to a @bail_to_interpreter
> construct.  Without the @bail_to_interpreter (which is the thing which has
> those weird aliasing properties we're talking about), you're stuck.

I thought earlier you were suggesting bail_to_interpreter is more
general than side_exit (when I thought they were one and the same
thing), not that bail_to_interpreter is more general than guard.

Aside: theoretically, if you have @guard() as a primitive then
bail_to_interpreter is just @guard(false).

-- Sanjoy

More information about the llvm-dev mailing list