[llvm-dev] sret read after unwind

Johannes Doerfert via llvm-dev llvm-dev at lists.llvm.org
Tue Jan 4 08:27:05 PST 2022


On 1/4/22 03:39, Nikita Popov wrote:
> On Mon, Jan 3, 2022 at 6:33 PM Johannes Doerfert <johannesdoerfert at gmail.com>
> wrote:
>
>> I somewhat missed this thread and while I should maybe respond
>> to a few of the other mails too I figured I start with a conceptual
>> question I have reading this:
>>
>> Who and when is the attribute added? If it is implied by sret that's
>> a good start. For the non-sret deduction it seems very specialized.
>> I mean, now we have something for the unwind case but not a different
>> "early exit" or if it is read/writeonly rather than readnone.
>>
> I'm mainly interested in frontend-annotated cases here, rather than deduced
> ones. The primary use case there is adding it to sret arguments (and only
> changing sret semantics would be "good enough" for me, I guess). However,
> there is another frontend-annotated case I have my eyes on, which is move
> arguments in rust. These could be modeled by a combination of
> noreadonunwind and noreadonreturn to indicate that the value will not be
> used after the call at all, regardless of how it exits. (This would be kind
> of similar to a byval argument, just without the ABI implication that an
> actual copy gets inserted.)

OK. That's interesting. I'm not fluent enough in rust, can you
elaborate what the semantics there would be, maybe an IR example?

Spitballing: `byval(nocopy, %a)` might be worth thinking about
given the short description.


>
> Note that as proposed, the noreadonunwind attribute would be the "writeonly
> on unwind" combination (and noreadonreturn the "writeonly on return"
> combination). I can see that there are conjugated "readonly on unwind" and
> "readonly on return" attributes that could be defined here, but I can't
> think of any circumstances under which these would actually be useful for
> optimization purposes. How would the presence or absence of later writes
> impact optimization in the current function?

Just as an example, `readonly on unwind` allows you to do GVN/CSE
from prior to the call to the "unwind path". Return then on the
"return path". That is not inside the call but in the caller.
Does that make sense?


>
> The argument about invoke vs. call instruction call sites only holds for
>> sret args anyway, so maybe what you are designing here is too sret
>> specific.
>>
> Not sure I follow, why would that argument only hold for sret?

```
static void I_will_unwind(int *A) {
   *A = 42;
   may_unwind();
   *A = 4711;
   unwind();
}
void someone_might_catch_me_as_I_also_unwind(int *A) {
   /* call */ I_will_unwind(A);
}
```

Maybe I misunderstood your idea but doesn't the above show how
we have only call instruction call sites and we still cannot
assume the store is dead on the unwind path? If you check
transitively throughout the entire call chain it's different,
but that is not how I read your first mail. I figured it works
for sret because the memory does not outlive the caller.

~ Johannes


>
> Regards,
> Nikita
>
>
>> Long term I'd like us to have a proper "side-effect" encoding with values
>> and that could include conditions, e.g.,
>>
>> ```
>> sideeffects(       write(_unknown_, %arg), read(_unknown_),
>>               unwind{write(_unknown_), read(_unknown_)},
>>               cond(load %arg eq 0, {read($arg)})
>>              )
>> ```
>>
>> While this is still long away (probably), I'm not convinced an attribute
>> that is specific to unwind *and* readnone is the right intermediate step.
>> It should compose better with readonly/writeonly/readnone at least.
>>
>> All that said, would your deduction strategy alone solve the problem?
>> So, the cases you care about could they be optimized by looking at the
>> call sites and determining if none is an invoke?
>>
>> ~ Johannes
>>
>>
>> On 12/4/21 04:39, Nikita Popov via llvm-dev wrote:
>>> Hi,
>>>
>>> Consider the following IR:
>>>
>>> declare void @may_unwind()
>>> define void @test(i32* noalias sret(i32) %out) {
>>>       store i32 0, i32* %out
>>>       call void @may_unwind()
>>>       store i32 1, i32* %out
>>>       ret void
>>> }
>>>
>>> Currently, we can't remove the first store as dead, because the
>>> @may_unwind() call may unwind, and the caller might read %out at that
>>> point, making the first store visible.
>>>
>>> Similarly, it prevents call slot optimization in the following example,
>>> because the call may unwind and make an early write to the sret argument
>>> visible:
>>>
>>> declare void @may_unwind(i32*)
>>> declare void @llvm.memcpy.p0i8.p0i8.i64(i8*, i8*, i64, i1)
>>> define void @test(i32* noalias sret(i32) %arg) {
>>>       %tmp = alloca i32
>>>       call void @may_unwind(i32* nocapture %tmp)
>>>       %tmp.8 = bitcast i32* %tmp to i8*
>>>       %arg.8 = bitcast i32* %arg to i8*
>>>       call void @llvm.memcpy.p0i8.p0i8.i64(i8* align 4 %arg.8, i8* align 4
>>> %tmp.8, i64 4, i1 false)
>>>       ret void
>>> }
>>>
>>> I would like to address this in some form. The easiest way would be to
>>> change LangRef to specify that sret arguments cannot be read on unwind
>>> paths. I think that matches how sret arguments are generally used.
>>>
>>> Alternatively, this could be handled using a separate attribute that can
>> be
>>> applied to any argument, something along the lines of "i32* nounwindread
>>> sret(i32) %arg". The benefit would be that this is decoupled from sret
>> ABI
>>> semantics and could potentially be inferred (e.g. if the function is only
>>> ever used with call and not invoke, this should be a given).
>>>
>>> Any thoughts on this? Is this a problem worth solving, and if yes, would
>> a
>>> new attribute be preferred over restricting sret semantics?
>>>
>>> Regards,
>>> Nikita
>>>
>>>
>>> _______________________________________________
>>> LLVM Developers mailing list
>>> llvm-dev at lists.llvm.org
>>> https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-dev


More information about the llvm-dev mailing list