[llvm-dev] sret read after unwind

Johannes Doerfert via llvm-dev llvm-dev at lists.llvm.org
Tue Jan 4 09:15:50 PST 2022


On 1/4/22 10:57, Nikita Popov wrote:
> On Tue, Jan 4, 2022 at 5:27 PM Johannes Doerfert <johannesdoerfert at gmail.com>
> wrote:
>
>> 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?
>>
> To give a silly example, take these two functions in rust (
> https://rust.godbolt.org/z/9cvefedsP):
>
> pub fn test1(mut s: String) {
>      s = "foobar".to_string();
> }
> pub fn test2(s: &mut String) {
>      *s = "foobar".to_string();
> }
>
>  From an ABI perspective, these are basically the same. In both cases rust
> will lower this to passing in a String*. However, because String is a
> non-Copy type, any call "test(s)" will move the "s" variable, which means
> that "s" cannot be used after the call anymore. For that reason, the store
> "s =" would be definitely dead in the first example, and usually not dead
> in the second example.
>
I see. Again just as an idea, what if we make the "overwriting stores"
explicit instead of using attributes.

```
s = "foobar".to_string();
// other code
virtual_memset(s, sizeof(s), 0);
ret void
```

Now DSE will do the work for us.
It is not clear if we could do something similar for the other cases
though.

Whatever we do, I can see how this is information that is worth encoding.


>> Spitballing: `byval(nocopy, %a)` might be worth thinking about
>> given the short description.
>>
> Yeah, I guess that would work -- though I'd rather not mix ABI and
> optimization attributes in that way...
>
>>> 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?
>
> Let me check if I understood the idea right: We have a invoke with a
> hypothetical "readonly on unwind" / "no write on unwind" attribute. In the
> landing pad, there is a non-analyzable write and the pointer has previously
> escaped, and then later there is a read from the argument. The
> non-analyzable write blocks AA, while the "readonly on unwind" guarantee
> could make a sufficiently smart AA see that this write cannot write into
> the argument memory. Is that the idea? I feel like "readonly on unwind"
> isn't the right way to model that situation, rather the argument could be
> marked as invariant in the unwind path using one of our existing ways to
> denote invariance.
>
> But I suspect I still didn't quite get what you have in mind here. An
> example would help.

Maybe I am confused but I thought something like this pseudo-code
could be optimized, readonly_on_return is similar.

```
int a = 42;
invoke foo(/* readonly_on_unwind */ &a);
lp:
   return a; // a == 42
cont:
   return a; // a unknown
```


>
>>> 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.
>>
> Ah yes, this was imprecise in the original mail. We need that a) it's only
> used with call (if we don't want to analyze the unwind paths to be more
> precise) and b) is noreadonunwind itself. Where the latter might be because
> it's based on an argument with that attribute, or because it's an alloca,
> which is always noreadonunwind.

Right, something along those lines.

~ Johannes


>
> Regards,
> Nikita
>


More information about the llvm-dev mailing list