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

Chandler Carruth via llvm-dev llvm-dev at lists.llvm.org
Tue Feb 23 10:55:06 PST 2016

On Tue, Feb 23, 2016 at 9:33 AM Sanjoy Das <sanjoy at playingwithpointers.com>

> On Mon, Feb 22, 2016 at 11:18 PM, Chandler Carruth <chandlerc at gmail.com>
> wrote:
> >> # step A: Introduce an `interposable` function attribute
> >>
> >> We can bike shed on the name and the exact specification, but the
> >> general idea is that you cannot do IPA / IPO over callsites calling
> >> `interposable` functions without inlining them.  This attribute will
> >> (usually) have to be used on function bodies that can deoptimize (e.g.
> has
> >> a
> >> side exit / guard it in); but also has more general use cases.
> >
> >
> > Note that we already have this *exact* concept in the IR via linkage for
> > better or worse. I think it is really confusing as you are currently
> I was going to have a more detailed discussion on this in the (yet to
> be started) review thread for `interposable`: we'd like to be able to
> inline `interposable` functions.  The "interposition" can only happen
> in physical function boundaries, so opt is allowed to do as much
> IPA/IPO it wants once it makes the physical function boundary go away
> via inlining.  None of linkage types seem to have this property.
> Part of the challenge here is to specify the attribute in a way that
> allows inlining, but not IPA without inlining.  In fact, maybe it is
> best to not call it "interposable" at all?

Yea, this is something *very* different from interposable. GCC and other
compilers that work to support symbol interposition make specific efforts
to not inline them in specific ways (that frankly I don't fully understand,
as it doesn't seem to be always which is what the definition of
interposable indicates to me...).

> Actually, I think one of the problems we're trying to solve with
> `interposable` is applicable to the available_externally linkage as
> well.  Say we have
> ```
> void foo() available_externally {
>   %t0 = load atomic %ptr
>   %t1 = load atomic %ptr
>   if (%t0 != %t1) print("X");
> }
> void main() {
>   foo();
>   print("Y");
> }
> ```
> Now the possible behaviors of the above program are {print("X"),
> print("Y")} or {print("Y")}.  But if we run opt then we have
> ```
> void foo() available_externally readnone nounwind {
>   ;; After CSE'ing the two loads and folding the condition
> }
> void main() {
>   foo();
>   print("Y");
> }
> ```
> and some generic reordering
> ```
> void foo() available_externally readnone nounwind {
>   ;; After CSE'ing the two loads and folding the condition
> }
> void main() {
>   print("Y");
>   foo();  // legal since we're moving a readnone nounwind function that
>           // was guaranteed to execute (hence can't have UB)
> }
> ```
> Now if we do not inline @foo(), and instead re-link the call site in
> @main to some non-optimized copy (or differently optimized copy) of
> foo, then it is possible for the program to have the behavior
> {print("Y"); print ("X")}, which was disallowed in the earlier
> program.
> In other words, opt refined the semantics of @foo() (i.e. reduced the
> set of behaviors it may have) in ways that would make later
> optimizations invalid if we de-refine the implementation of @foo().
> Given this, I'd say we don't need a new attribute / linkage type, and
> can add our restriction to the available_externally linkage.

Interesting example, I agree it seems quite broken. Even more interesting,
I can't see anything we do in LLVM that prevents this from breaking
essentially everywhere. =[[[[[[

link_once and link_once_odr at least seem equally broken because we don't
put the caller and callee into a single comdat or anything to ensure that
the optimized one is selected at link time.

But there are also multiple different kinds of overriding we should think

1) Can the definition get replaced at link time (or at runtime via an
interpreter) with a differently *optimized* variant stemming from the same
definition (thus it has the same behavior but not the same refinement).
This is the "ODR" guarantee in some linkages (and vaguely implied for

2) Can the definition get replaced at link time (or at runtime via an
interpreter) with a function that has fundamentally different behavior

3) To support replacing the definition, the call edge must be preserved.

To support interposition you need #3, the most restrictive model. LLVM (i
think) actually does a decent job of modeling this as we say that the
function is totally opaque. We don't do IPA or inlining. But I don't think
that's what you're looking for.

I'm curious whether your use case is actually in the #1 bucket or #2
bucket. That is, I'm wondering if there is any way in which the "different
implementation" would actually break in the face of optimizations on things
like *non-deduced* function attributes, etc.

If your use case looks more like #1, then I actually think this is what we
want for link_once_odr and available_externally. You probably want the
former rather than the latter as you don't want it to be discardable.

If your use case looks more like #2, then I think its essentially
"link_once" or "link_any", and it isn't clear that LLVM does a great job of
modeling this today.

I'd be mildly interested in factoring the discarding semantics from the
"what do other definitions look like" semantics. The former are what I
think fit cleanly into linkages, and the latter I think we wedged into them
because they seemed to correspond in some cases and because attributes used
to be very limited in number.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.llvm.org/pipermail/llvm-dev/attachments/20160223/ecb0ddf1/attachment.html>

More information about the llvm-dev mailing list