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

Sanjoy Das via llvm-dev llvm-dev at lists.llvm.org
Thu Jan 5 08:55:35 PST 2017


Hi Hal,

On Thu, Jan 5, 2017 at 6:12 AM, Hal Finkel <hfinkel at anl.gov> wrote:
>
> 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.

If the call to @readnone_mayunwind_fn throws and I've DSE'ed the "*a =
10" store, the exception handler will fail the *a == 10 assert (assume
*a is not 10 to begin with).  The function call itself is readnone,
but its exceptional continuation may read any part of the heap.

This isn't a big deal, but it means that "readnone may-unwind" will
effectively have to be treated as "readonly may-unwind" -- I don't see
any optimization that would be applicable to one and not the other.
Maybe we should just move ahead with that (that readnone may-unwind is
allowed, but if you want readnone-like optimizations then you need to
also mark it as nounwind)?

-- Sanjoy

>
>  -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