[llvm-dev] RFC: Add "operand bundles" to calls and invokes
David Majnemer via llvm-dev
llvm-dev at lists.llvm.org
Mon Aug 10 21:38:32 PDT 2015
On Sun, Aug 9, 2015 at 11:32 PM, Sanjoy Das <sanjoy at playingwithpointers.com>
wrote:
> We'd like to propose a scheme to attach "operand bundles" to call and
> invoke instructions. This is based on the offline discussion
> mentioned in
> http://lists.cs.uiuc.edu/pipermail/llvmdev/2015-July/088748.html.
>
> # Motivation & Definition
>
> Our motivation behind this is to track the state required for
> deoptimization (described briefly later) through the LLVM pipeline as
> a first-class IR citizen. We want to do this is a way that is
> generally useful.
>
> An "operand bundle" is a set of SSA values (called "bundle operands")
> tagged with a string (called the "bundle tag"). One or more of such
> bundles may be attached to a call or an invoke. The intended use of
> these values is to support "frame introspection"-like functionality
> for managed languages.
>
>
> # Abstract Syntax
>
> The syntax of a call instruction will be changed to look like this:
>
> <result> = [tail | musttail] call [cconv] [ret attrs] <ty> [<fnty>*]
> <fnptrval>(<function args>) [operand_bundle*] [fn attrs]
>
> where operand_bundle = tag '('[ value ] (',' value )* ')'
> value = normal SSA values
> tag = "< some name >"
>
> In other words, after the function arguments we now have an optional
> list of operand bundles of the form `"< bundle tag >"(bundle
> attributes, values...)`. There can be more than one operand bundle in
> a call. Two operand bundles in the same call instruction cannot have
> the same tag.
>
> We'd do something similar for invokes. I'll omit the invoke syntax
> from this RFC to keep things brief.
>
> An example:
>
> define i32 @f(i32 %x) {
> entry:
> %t = add i32 %x, 1
> ret i32 %t
> }
>
> define void @g(i16 %val, i8* %ptr) {
> entry:
> call void @f(i32 10) "some-bundle"(i32 42) "debug"(i32 100)
> call void @f(i32 20) "some-bundle"(i16 %val, i8* %ptr)
> }
>
> Note 1: Operand bundles are *not* part of a function's signature, and
> a given function may be called from multiple places with different
> kinds of operand bundles. This reflects the fact that the operand
> bundles are conceptually a part of the *call*, not the callee being
> dispatched to.
>
> Note 2: There may be tag specific requirements not mentioned here.
> E.g. we may add a rule in the future that says operand bundles with
> the tag `"integer-id"` may only contain exactly one constant integer.
>
>
> # IR Semantics
>
> Bundle operands (SSA values part of some operand bundle) are normal
> SSA values. They need to dominate the call or invoke instruction
> they're being passed into and can be optimized as usual. For
> instance, LLVM is allowed (and strongly encouraged!) to PRE / LICM a
> load feeding into an operand bundle if legal.
>
> Operand bundles are characterized by the `"< bundle tag >"` string
> associated with them.
>
> The overall strategy is:
>
> 1. The semantics are as conservative as is reasonable for operand
> bundles with tags that LLVM does not have a special understanding
> of. This way LLVM does not miscompile code by default.
>
> 2. LLVM understands the semantics of operand bundles with certain
> specific tags more precisely, and can optimize them better.
>
> This RFC talks mainly about (1). We will discuss (2) as we add smarts
> to LLVM about specific kinds of operand bundles.
>
> The IR-level semantics of an operand bundle with an arbitrary tag are:
>
> 1. The bundle operands passed in to a call escape in unknown ways
> before transferring control to the callee. For instance:
>
> declare void @opaque_runtime_fn()
>
> define void @f(i32* %v) { }
>
> define i32 @g() {
> %t = i32* @malloc(...)
> ;; "unknown" is a tag LLVM does not have any special knowledge of
> call void @f(i32* %t) "unknown"(i32* %t)
>
> store i32 42, i32* %t
> call void @opaque_runtime_fn();
> ret (load i32, i32* %t)
> }
>
> Normally (without the `"unknown"` bundle) it would be okay to
> optimize `@g` to return `42`. But the `"unknown"` operand bundle
> escapes `%t`, and the call to `@opaque_runtime_fn` can therefore
> modify the location pointed to by `%t`.
>
> 2. Calls and invokes with operand bundles have unknown read / write
> effect on the heap on entry and exit (even if the call target is
> `readnone` or `readonly`). For instance:
>
> define void @f(i32* %v) { }
>
> define i32 @g() {
> %t = i32* @malloc(...)
> %t.unescaped = i32* @malloc(...)
> ;; "unknown" is a tag LLVM does not have any special knowledge of
> call void @f(i32* %t) "unknown"(i32* %t)
> ret (load i32, i32* %t)
> }
>
> Normally it would be okay to optimize `@g` to return `undef`, but
> the `"unknown"` bundle potentially clobbers `%t`. Note that it
> clobbers `%t` only because it was *also escaped* by the
> `"unknown"` operand bundle -- it does not clobber `%t.unescaped`
> because it isn't reachable from the heap yet.
>
> However, it is okay to optimize
>
> define void @f(i32* %v) {
> store i32 10, i32* %v
> print(load i32, i32* %v)
> }
>
> define void @g() {
> %t = ...
> ;; "unknown" is a tag LLVM does not have any special knowledge of
> call void @f(i32* %t) "unknown"()
> }
>
> to
>
> define void @f(i32* %v) {
> store i32 10, i32* %v
> print(10)
> }
>
> define void @g() {
> %t = ...
> call void @f(i32* %t) "unknown"()
> }
>
> The arbitrary heap clobbering only happens on the boundaries of
> the call operation, and therefore we can still do store-load
> forwarding *within* `@f`.
>
> Since we haven't specified any "pure" LLVM way of accessing the
> contents of operand bundles, the client is required to model such
> accesses as calls to opaque functions (or inline assembly). This
> ensures that things like IPSCCP work as intended. E.g. it is legal to
> optimize
>
> define i32 @f(i32* %v) { ret i32 10 }
>
> define void @g() {
> %t = i32* @malloc(...)
> %v = call i32 @f(i32* %t) "unknown"(i32* %t)
> print(%v)
> }
>
> to
>
> define i32 @f(i32* %v) { ret i32 10 }
>
> define void @g() {
> %t = i32* @malloc(...)
> %v = call i32 @f(i32* %t) "unknown"(i32* %t)
> print(10)
> }
>
> LLVM won't generally be able to inline through calls and invokes with
> operand bundles -- the inliner does not know what to replace the
> arbitrary heap accesses implied on function entry and exit with.
> However, we intend to teach the inliner to inline through calls /
> invokes with some specific kinds of operand bundles.
>
>
> # Lowering
>
> The lowering strategy will be special cased for each bundle tag.
> There won't be any "generic" lowering strategy -- `llc` is expected to
> abort if it sees an operand bundle that it does not understand.
>
> There is no requirement that the operand bundles actually make it to
> the backend. Rewriting the operand bundles into "vanilla" LLVM IR at
> some point in the pipeline (instead of teaching codegen to lower them)
> is a perfectly reasonable lowering strategy.
>
>
> # Example use cases
>
> A couple of usage scenarios are very briefly described below:
>
> ## Deoptimization
>
> This is our motivating use case. Some managed environments expect to
> be able to discover the state of the abstract virtual machine at specific
> call
> sites. LLVM will be able to support this requirement by attaching a
> `"deopt"` operand bundle containing the state of the abstract virtual
> machine (as a vector of SSA values) at the appropriate call sites.
> There is a straightforward way
> to extend the inliner work with `"deopt"` operand bundles.
>
> `"deopt"` operand bundles will not have to be as pessimistic about
> heap effects as the general "unknown operand bundle" case -- they only
> imply a read from the entire heap on function entry or function exit,
> depending on what kind of deoptimization state we're interested in.
> They also don't imply escaping semantics.
>
>
> ## Value injection
>
> By passing in one or more `alloca`s to an `"injectable-value"` tagged
> operand bundle, languages can allow the runtime to overwrite the
> values of specific variables, while still preserving a significant
> amount of optimization potential.
>
>
>
> Thoughts?
>
This seems pretty useful, generic, call-site annotation mechanism. I
believe that this has immediate application outside of the context of GC.
Our exception handling personality routine has a desire to know whether
some code is inside a specific try or catch. We can feed the value coming
out of our EH pad back into the call-site, making it very clear which EH
pad the call-site is associated with.
> -- Sanjoy
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.llvm.org/pipermail/llvm-dev/attachments/20150811/4482258f/attachment.html>
More information about the llvm-dev
mailing list