[llvm-dev] sret read after unwind

Nikita Popov via llvm-dev llvm-dev at lists.llvm.org
Tue Jan 4 08:57:42 PST 2022


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.


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

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

Regards,
Nikita
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.llvm.org/pipermail/llvm-dev/attachments/20220104/d9c37780/attachment.html>


More information about the llvm-dev mailing list