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 rustwill 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>
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 >
On Tue, Jan 4, 2022 at 5:57 PM Nikita Popov <nikita.ppv at gmail.com> 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. >Something I realized while looking through existing code is that there are two different cases of "not visible after unwind/return" that usually get handled: For allocas, this statement is absolute, and independent of whether the alloca is captured. For returns from allocation functions, this only holds if the pointer does not escape. If it does escape, then a write before unwind/return might be visible to the caller. For the sret unwind case, we can also make an absolute statement. But for the Rust move argument case above, we can't. The pointer can be captured (by moving it further) and accessed by the caller. One case we can treat like an alloca, the other like a malloc(). Not really sure what to make of this yet. Regards, Nikita -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://lists.llvm.org/pipermail/llvm-dev/attachments/20220106/b9508244/attachment.html>