dexonsmith added a comment.

In D83906#4181981 <https://reviews.llvm.org/D83906#4181981>, @hoy wrote:

> That said, the LLVM optimizer does not strictly subsume the front-end because 
> of how it fails to handle `linkonce_odr` functions as in 
> https://reviews.llvm.org/D18634. I'm wondering how common the `linkonce_odr` 
> linkage is for C++. In @wlei's example, none of the functions there is 
> `linkonce_odr`. Is there a particular source-level annotate that specifies 
> functions to be `linkonce_odr`?

In C++, you get `linkonce_odr` all over the place. It's basically all functions 
that are defined in C++ headers that are available for inlining.

- any function marked `inline`
- any function in a class/struct whose declaration is its definition 
(approximately all templated code)

A few exceptions:

- If a function is explicitly instantiated (e.g., member functions of `T<int>` 
if `template class T<int>;`), it gets `weak_odr`, which IIRC cannot be 
de-refined?
- If a function has local linkage (like free functions with `static inline`), 
it gets `internal`, which cannot be de-refined.
- If a function is marked `inline` inside an `extern "C"` block, it gets 
`available_externally`. This can also be de-refined (but without ODR, you 
wouldn't be tempted to optimize based on its attributes anyway).



> It sounds to me that at link time only equivalent symbols can replace each 
> other. Then de-refining some of those equivalent symbols should not affect 
> their semantics as far as nothrow is concerned? Just as @rjmccall pointed 
> out, the C++ language guarantee we're starting from is that the source 
> semantics of all versions of the function are identical.

The rule is subtly different. Only symbols that are source-equivalent can 
replace each other. But they aren't necessarily equivalent to the function you 
see, which may have been refined by optimization.

Here's a concrete example. Say we have function `maybe_nounwind` that is not 
`nounwind` at the source level, and a `catch_all` function that wraps it.

  // Defined in header.
  extern std::atomic<int> Global;
  
  // LLVM: linkonce_odr
  inline int maybe_nounwind(int In) {
    int Read1 = Global;
    int Read2 = Global;
    if (Read1 != Read2)
      throw 0;
  
    return /* Big, non-inlineable computation on In */;
  }
  
  // Defined in source.
  // LLVM: nounwind
  int catch_all(int In) {
    try {
      return maybe_nounwind(In);
    } catch (...) {
      return -1;
    }
  }

There's no UB here, since comparing two atomic loads is allowed. In rare cases, 
an unoptimized `maybe_nounwind` could throw, if another thread is changing the 
value of `Global` between the two loads.

But the optimizer will probably CSE the two atomic loads since it's allowed to 
assume that both loads happen at the same time. This refines `maybe_nounwind`. 
It'll turn into IR equivalent to:

  // Defined in header. Then optimized.
  // LLVM: linkonce_odr nounwind readnone
  inline int maybe_nounwind(int In) {
    return /* Big, non-inlineable computation on In */;
  }
  
  // Defined in source. Then optimized.
  // LLVM: nounwind
  int catch_all(int In) {
    try {
      return maybe_nounwind(In);
    } catch (...) {
      return -1;
    }
  }

It's important that `catch_all` is NOT optimized based on `maybe_nounwind`'s 
new `nounwind` attribute. At link time, it's possible for the linker to choose 
an unoptimized copy of `maybe_nounwind`. Just in case it does, `catch_all` 
needs to keep its `try/catch` block, since unoptimized `maybe_nounwind` can 
throw. Similarly, `catch_all` should not be marked `readnone`, even though the 
refined/optimized `maybe_nounwind` is `readnone`, since a de-refined copy reads 
from memory.

In D83906#4181876 <https://reviews.llvm.org/D83906#4181876>, @rjmccall wrote:

> There are *some* properties we can still assume about `linkonce_odr` 
> functions despite them being replaceable at link time.  The high-level 
> language guarantee we're starting from is that the source semantics of all 
> versions of the function are identical.  The version of the function we're 
> looking at has been transformed from the original source — it is, after all, 
> now LLVM IR, not C/C++ — but it has presumably faithfully preserved the 
> source semantics.  We can therefore rely on any properties of the semantics 
> that are required to be preserved by transformation, which includes things 
> like "does it terminate", "what value does it return", "what side effects 
> does it perform", and so on.  What we can't rely on are properties of the 
> implementation that are not required to be preserved by transformation, like 
> whether or not it uses a certain argument — transformations are permitted to 
> change that.



- At IRGen time, you know the LLVM attributes have not been adjusted after the 
optimized refined the function's behaviour. It should be safe to have IPA 
peepholes, as long as IRGen's other peepholes don't refine behaviour and add 
attributes based on that.
- In the optimizer, if you're looking at de-refineable function then you don't 
know which attributes come directly from the source and which were implied by 
optimizer refinements. You can't trust you'll get the same function attributes 
at runtime.


Repository:
  rG LLVM Github Monorepo

CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D83906/new/

https://reviews.llvm.org/D83906

_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to