David Chisnall via llvm-dev
2021-Feb-22 09:50 UTC
[llvm-dev] Confusions around nocapture and sret
On 21/02/2021 18:39, Johannes Doerfert via llvm-dev wrote:> I strongly suggest to emit nocapture with sret in the frontend > instead.I don't think that is actually feasible. For example, consider this C++ file: ```c++ #include <set> struct Example; std::set<Example*> live_examples; struct Example { Example() { live_examples.insert(this); } ~Example() { live_examples.erase(live_examples.find(this)); } }; Example somefn() { Example e; return e; } ``` In this example, guaranteed copy elision means that somefn allocates `e` in the space provided for it in the caller, calling the constructor, which then captures the value. In the generated IR, the space for `e` has the `sret` attribute but it is definitely not nocapture. You can also trigger this in C, though in the C case it is undefined behaviour. Consider this example: ```c struct Foo { int a[5]; }; int x(struct Foo *); struct Foo f(void) { struct Foo foo; x(&foo); return foo; } ``` The source-language semantics guarantee that no pointers to `foo` outlive the invocation of `f`, which implies that `x` must not capture the argument. The optimisers take advantage of the fact that it would be UB to compare the address of foo after the end of `f` to any other allocation and we end up generating this IR after optimisation, eliding the copy: ``` ; Function Attrs: nounwind uwtable define dso_local void @f(%struct.Foo* noalias sret(%struct.Foo) align 4 %0) local_unnamed_addr #0 { %2 = tail call i32 @x(%struct.Foo* %0) #2 ret void } ``` Nothing in the IR says that `x`'s argument is nocapture. Whether this is permitted depends on what we want nocapture to mean. There are two possible interpretations: - The callee does not capture the argument, if the callee does capture the argument then the IR is ill-formed and we have a compiler bug. - The caller is free to assume that the callee does not capture the argument, if the callee does capture the argument then it is UB. The former allows the absence of nocapture to be interpreted as 'we can't statically prove that the argument is not captured'. This is very useful for memory-safety work, because it allows us to trust `nocapture` as a security property: we can emit any further analysis. The latter allows optimisations to be more aggressive but will sometimes generate more surprising code for users and may break some security properties if security-related transforms depend on this information. My personal bias is towards the former: we would like to be able to use `nocapture` in stack temporal safety work as a strong guarantee. As such, the front end could not insert it because transforms may later insert a capture. Alternatively, the module verifier should be updated to ensure that a nocapture argument is not passed to any other function except via a nocapture argument. David
Johannes Doerfert via llvm-dev
2021-Feb-22 17:24 UTC
[llvm-dev] Confusions around nocapture and sret
On 2/22/21 3:50 AM, David Chisnall via llvm-dev wrote:> On 21/02/2021 18:39, Johannes Doerfert via llvm-dev wrote: >> I strongly suggest to emit nocapture with sret in the frontend >> instead. > > I don't think that is actually feasible. For example, consider this > C++ file: > > ```c++ > #include <set> > > struct Example; > std::set<Example*> live_examples; > struct Example { > Example() > { > live_examples.insert(this); > } > ~Example() > { > live_examples.erase(live_examples.find(this)); > } > }; > > Example somefn() > { > Example e; > return e; > } > ``` > > In this example, guaranteed copy elision means that somefn allocates > `e` in the space provided for it in the caller, calling the > constructor, which then captures the value. In the generated IR, the > space for `e` has the `sret` attribute but it is definitely not > nocapture. >Right, copy elision. But the frontend could still know if it did something that could expose the pointer, e.g., in the C case below. My initial statement of "sret implies nocapture" is however not true in this case, I had C in mind. I guess "just" deriving nocapture is the best way to go after all.> You can also trigger this in C, though in the C case it is undefined > behaviour. Consider this example: > > ```c > struct Foo > { > int a[5]; > }; > > int x(struct Foo *); > > struct Foo f(void) > { > struct Foo foo; > x(&foo); > return foo; > } > ``` > > The source-language semantics guarantee that no pointers to `foo` > outlive the invocation of `f`, which implies that `x` must not capture > the argument. The optimisers take advantage of the fact that it would > be UB to compare the address of foo after the end of `f` to any other > allocation and we end up generating this IR after optimisation, > eliding the copy: > > ``` > ; Function Attrs: nounwind uwtable > define dso_local void @f(%struct.Foo* noalias sret(%struct.Foo) align > 4 %0) local_unnamed_addr #0 { > %2 = tail call i32 @x(%struct.Foo* %0) #2 > ret void > } > ``` > > Nothing in the IR says that `x`'s argument is nocapture. Whether this > is permitted depends on what we want nocapture to mean. There are two > possible interpretations: > > - The callee does not capture the argument, if the callee does > capture the argument then the IR is ill-formed and we have a compiler > bug. > - The caller is free to assume that the callee does not capture the > argument, if the callee does capture the argument then it is UB. > > The former allows the absence of nocapture to be interpreted as 'we > can't statically prove that the argument is not captured'. This is > very useful for memory-safety work, because it allows us to trust > `nocapture` as a security property: we can emit any further analysis. > > The latter allows optimisations to be more aggressive but will > sometimes generate more surprising code for users and may break some > security properties if security-related transforms depend on this > information. > > My personal bias is towards the former: we would like to be able to > use `nocapture` in stack temporal safety work as a strong guarantee. > As such, the front end could not insert it because transforms may > later insert a capture. Alternatively, the module verifier should be > updated to ensure that a nocapture argument is not passed to any other > function except via a nocapture argument. >I don't follow your explanation here, partially because the absence is never "we can't prove", it might just mean, we haven't tried yet. Given the way we use (almost) all attributes and the fact users can basically write: ```c void u(int*); void q(int* __attribute__((noescape) p) { u(p); } ``` I don't see why `nocapture` would not mean: "assume is not captured, if it is, UB". That said, if you want to use attributes for security policies, which I see as useful, you need to create new versions. FWIW, this is not only about `nocapture`. One more thing to consider is that we inherently use UB to argue when it comes to attribute inference as well as code transformations and thereby implicitly attribute inference on the transformed code. ~ Johannes> David > > > _______________________________________________ > LLVM Developers mailing list > llvm-dev at lists.llvm.org > https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-dev
Jameson Nash via llvm-dev
2021-Feb-22 17:31 UTC
[llvm-dev] Confusions around nocapture and sret
> end up generating this IR after optimisationAFAICT, that's the IR before optimization, as seen from `clang -S -emit-llvm -O0 -o - -x c -`.> I strongly suggest to emit nocapture with sret in the frontend instead.It seems like `clang -x c` doesn't emit nocapture for even trivial cases I tried (like the above, but without function `x`). Is that a bug then, or just not applicable to C? Regardless, I don't think this should be a verifier error for passing nocapture to maycapture, since it may be valid for a source language to know that a particular use of an argument is nocapture, even if the general function contract doesn't promise it. On Mon, Feb 22, 2021 at 4:51 AM David Chisnall via llvm-dev < llvm-dev at lists.llvm.org> wrote:> On 21/02/2021 18:39, Johannes Doerfert via llvm-dev wrote: > > I strongly suggest to emit nocapture with sret in the frontend > > instead. > > I don't think that is actually feasible. For example, consider this C++ > file: > > ```c++ > #include <set> > > struct Example; > std::set<Example*> live_examples; > struct Example { > Example() > { > live_examples.insert(this); > } > ~Example() > { > live_examples.erase(live_examples.find(this)); > } > }; > > Example somefn() > { > Example e; > return e; > } > ``` > > In this example, guaranteed copy elision means that somefn allocates `e` > in the space provided for it in the caller, calling the constructor, > which then captures the value. In the generated IR, the space for `e` > has the `sret` attribute but it is definitely not nocapture. > > You can also trigger this in C, though in the C case it is undefined > behaviour. Consider this example: > > ```c > struct Foo > { > int a[5]; > }; > > int x(struct Foo *); > > struct Foo f(void) > { > struct Foo foo; > x(&foo); > return foo; > } > ``` > > The source-language semantics guarantee that no pointers to `foo` > outlive the invocation of `f`, which implies that `x` must not capture > the argument. The optimisers take advantage of the fact that it would > be UB to compare the address of foo after the end of `f` to any other > allocation and we end up generating this IR after optimisation, eliding > the copy: > > ``` > ; Function Attrs: nounwind uwtable > define dso_local void @f(%struct.Foo* noalias sret(%struct.Foo) align 4 > %0) local_unnamed_addr #0 { > %2 = tail call i32 @x(%struct.Foo* %0) #2 > ret void > } > ``` > > Nothing in the IR says that `x`'s argument is nocapture. Whether this > is permitted depends on what we want nocapture to mean. There are two > possible interpretations: > > - The callee does not capture the argument, if the callee does capture > the argument then the IR is ill-formed and we have a compiler bug. > - The caller is free to assume that the callee does not capture the > argument, if the callee does capture the argument then it is UB. > > The former allows the absence of nocapture to be interpreted as 'we > can't statically prove that the argument is not captured'. This is very > useful for memory-safety work, because it allows us to trust `nocapture` > as a security property: we can emit any further analysis. > > The latter allows optimisations to be more aggressive but will sometimes > generate more surprising code for users and may break some security > properties if security-related transforms depend on this information. > > My personal bias is towards the former: we would like to be able to use > `nocapture` in stack temporal safety work as a strong guarantee. As > such, the front end could not insert it because transforms may later > insert a capture. Alternatively, the module verifier should be updated > to ensure that a nocapture argument is not passed to any other function > except via a nocapture argument. > > David > > > _______________________________________________ > LLVM Developers mailing list > llvm-dev at lists.llvm.org > https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-dev >-------------- next part -------------- An HTML attachment was scrubbed... URL: <http://lists.llvm.org/pipermail/llvm-dev/attachments/20210222/f6bbfd31/attachment.html>