[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 09:19:37 PST 2017


On 01/05/2017 10:55 AM, Sanjoy Das wrote:
> 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)?

Yes, I think that makes sense. The attribute only applies to the 
function anyway, so what exception handlers might do (which is assumed 
to be reading/writing any memory that might be available to them) must 
be reasoned about separately.

  -Hal

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

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



More information about the llvm-dev mailing list