[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