[llvm-dev] sret read after unwind

Philip Reames via llvm-dev
Mon, 13 Dec 2021 18:39:47 -0000

On 12/7/21 12:29 AM, Nikita Popov wrote:
> On Mon, Dec 6, 2021 at 10:51 PM Philip Reames
> <listmail at philipreames.com <mailto:listmail at
philipreames.com>> wrote:
>
>     I think this is an interesting problem.
>
>     I'd probably lean towards the use of a separate attribute, but not
>     strongly so.
>
>     The example which makes me prefer the separate attribute would be
>     a function with an out-param.  It's very unlikely that an
>     out-param will be read on the exception path.  Being able to
>     perform DSE for such out params seems quite interesting.
>
> Right. I think it's mainly a question of whether we'd be able to
infer
> the attribute in practice, in cases where it's not annotated by the
> frontend (which it should do in the sret case). I think this is
> possible at least for the case where all calls to the function pass in
> an alloca to this parameter (or another argument with nounwindread I
> guess) and don't use a landingpad for the call. However, I believe we
> do inference in this direction (RPO rather than PO) only in the module
> optimization pipeline, which means that DSE/MemCpyOpt might not be
> able to make use of the inferred information.

A couple of points here:

 1. I often see attributes for which inference isn't a goal as being
    under specified.  It's too easy not to think about all the corner
    cases up front, and that bites us later.  I think it's important to
    have specified the valid inference rules as part of the initial
    definition discussion, even if we don't implement them.  It forces
    us to think through the subtleties.
 2. I think you're alloc rule can be extended to any unescaped
    allocation for which we can indentify all accesses and that none are
    reachable on the exceptional path.  The trivial call rule (there is
    no exceptional path) is one sub-case of that.  This backwards walk
    may seem expensive, but I think we already do it in DSE, and could
    leave converting callsite attributes to functions attributes to a
    later RPO phase.
 3. You're right that we don't really do RPO today.  See point (1).  I
    wouldn't want to add such just for this.

Aside: sret has the under-specified problem today.  I have no idea when
it would be legal to infer sret.

>     However, I'll note that the same problem can be framed as an
>     escape problem.  That is, we have an annotation not that a value
>     is dead on the exception path, but that it hasn't been captured on
>     entry to the routine.  Then, we can apply local reasoning to show
>     that the first store can't be visible to may_unwind, and eliminate
it.
>
> I don't think this would solve the same problem. In the given examples
> the pointer is already not visible to @may_unwind because it is
> noalias. "noalias" here is a weaker version of "not captured
before":
> The pointer may be captured, but it's illegal to write through the
> captured pointer, which is sufficient for alias analysis. The problem
> with unwinding is that after the unwind, the calling function may read
> the stored value in a landingpad, which does not require any capture
> of the pointer.
You are completely correct, particularly on that last point.  Don't know
what I was thinking when I first responded.
>
> Regards,
> Nikita
>
>     I'd want to give the escape framing more thought as that seems
>     potentially more general.  Does knowing that an argument does not
>     point to escaped memory on entry help on all of your motivating
>     examples?
>
>     Philip
>
>     On 12/4/21 2:39 AM, 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  <mailto:llvm-dev at
lists.llvm.org>
>>     https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-dev
<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/20211213/3f95d648/attachment-0001.html>