[llvm-dev] RFC: Allow readnone and readonly functions to throw exceptions

Sanjoy Das via llvm-dev llvm-dev at lists.llvm.org
Wed Jan 4 20:35:26 PST 2017


I just realized that there's an annoying corner case to this scheme --
I can't DSE stores across readnone maythrow function calls because the
exception handler could read memory. That is, in:

try {
  *a = 10;
  call void @readnone_mayunwind_fn();
  *a = 20;
} catch (...) {
  assert(*a == 10);
}

I can't DSE the `*a = 10` store.

As far as I can tell, the most restrictive memory attribute for a
potentially throwing function is readonly.  "readnone may-unwind" does
not make sense.  "readonly may-unwind" is fine because even if the EH
handler writes to memory, the code in the normal execution path does
not have worry about the memory clobbers.

I thought I had this figured out, but now it looks like I gotta think more. :)

@Danny: I agree with your assessment of the example; unless the
compiler knows that `cos` won't throw (which it may very well know
since it is the standard library function, but I don't know GCC
internals), the transform is wrong.

-- Sanjoy


On Tue, Jan 3, 2017 at 11:52 AM, Daniel Berlin <dberlin at dberlin.org> wrote:
>
>
> On Tue, Jan 3, 2017 at 10:47 AM, Michael Kuperstein via llvm-dev
> <llvm-dev at lists.llvm.org> wrote:
>>
>>
>>
>> On Tue, Jan 3, 2017 at 9:59 AM, Sanjoy Das via llvm-dev
>> <llvm-dev at lists.llvm.org> wrote:
>>>
>>> Hi Michael,
>>>
>>> On Mon, Jan 2, 2017 at 11:49 PM, Michael Kuperstein
>>> <michael.kuperstein at gmail.com> wrote:
>>> > This sounds right to me.
>>> >
>>> > IIUC, historically, readonly and readnone are meant to model the "pure"
>>> > and
>>> > "const" GCC attributes. These attributes make pretty strong guarantees:
>>> >
>>> > "[a pure] function can be subject to common subexpression elimination
>>> > and
>>> > loop optimization just as an arithmetic operator would be. These
>>> > functions
>>> > should be declared with the attribute pure [...] Interesting non-pure
>>> > functions are functions with infinite loops or those depending on
>>> > volatile
>>> > memory or other system resource, that may change between two
>>> > consecutive
>>> > calls (such as feof in a multithreading environment)."
>>> >
>>> > In particular, pure/const imply termination - something that's not
>>> > entirely
>>> > clear w.r.t readonly. However, apparently, they don't imply nothrow.
>>> > I've
>>> > actually always thought they *do* imply it - and said so on-list :-) -
>>> > but
>>> > it looks like GCC itself doesn't interpret them that way. E.g. see John
>>> > Regher's example here: https://t.co/REzy5m1tT3
>>> > So there's at least one use-case for possibly throwing
>>> > readonly/readnone.
>>>
>>> One important thing to note then: clang marks const and pure functions
>>> as nounwind *explicitly*.  That needs to be fixed.
>>>
>>> https://godbolt.org/g/SMF4C9
>>>
>>
>> Hah. So it does.
>> Eric, this was originally your change. Do I understand GCC's behavior
>> incorrectly?
>
>
> No, but I was in the office when Kenny wrote ipa-pure-const, which is the
> equivalent to llvm's pass to find function attributions, and it mostly
> wasn't thought about.
>
> GCC isn't as consistent as one may think here.
>
>            /* Non-looping const functions always return normally.
>           Otherwise the call might not return or have side-effects
>           that forbids hoisting possibly trapping expressions
>           before it.  */
>            int flags = gimple_call_flags (stmt);
>            if (!(flags & ECF_CONST)
>            || (flags & ECF_LOOPING_CONST_OR_PURE))
>          BB_MAY_NOTRETURN (block) = 1;
>          }
>
> It also, for example, will do this:
>  double cos (double) __attribute__ ((const));
>  double sin (double) __attribute__ ((const));
>  double f(double a)
>  {
>    double b;
>    double c,d;
>    double (*fp) (double) __attribute__ ((const));
>    /* Partially redundant call */
>    if (a < 2.0)
>      {
>        fp = sin;
>        c = fp (a);
>      }
>    else
>      {
>        c = 1.0;
>        fp = cos;
>      }
>    d = fp (a);
>    return d + c;
>  }
>
>
> into
>  double cos (double) __attribute__ ((const));
>  double sin (double) __attribute__ ((const));
>  double f(double a)
>  {
>    double b;
>    double c,d;
>    double (*fp) (double) __attribute__ ((const));
>    /* Partially redundant call */
>    if (a < 2.0)
>      {
>        fp = sin;
>        c = fp (a);
>      }
>    else
>      {
>        c = 1.0;
>        fp = cos;
>        temp = fp(a)
>      }
>    d = phi(c, temp)
>    return d + c;
>  }
>
> This only works if the second call to sin is guaranteed not to throw, no?
>
> In any case, optimizations check throwing separately from pure/const, but
> i'm not sure it's well thought out here.
>
> Note that GCC also has a notion of possibly infinite looping pure/const as
> well:)
>


More information about the llvm-dev mailing list