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

Hal Finkel via llvm-dev llvm-dev at lists.llvm.org
Thu Jan 5 06:12:21 PST 2017


On 01/04/2017 10:35 PM, Sanjoy Das via llvm-dev wrote:
> 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.

Why not? I've not followed this thread in detail, but it seems like 
you're discussing allowing the modeling of EH schemes that don't access 
accessible memory. In that case, a may-unwind readnone function is just 
one that makes its decision about if/what to throw based only on its 
arguments.

  -Hal

>   "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:)
>>
> _______________________________________________
> LLVM Developers mailing list
> llvm-dev at lists.llvm.org
> http://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-dev

-- 
Hal Finkel
Lead, Compiler Technology and Programming Languages
Leadership Computing Facility
Argonne National Laboratory



More information about the llvm-dev mailing list