This is an archive of the discontinued LLVM Phabricator instance.

[X86ISel] Implement byval lowering for Win64 calling convention
ClosedPublic

Authored by loladiro on Sep 9 2018, 2:01 PM.

Details

Summary

The IR reference for the byval attribute states:

This indicates that the pointer parameter should really be passed by value
to the function. The attribute implies that a hidden copy of the pointee is
made between the caller and the callee, so the callee is unable to modify
the value in the caller. This attribute is only valid on LLVM pointer arguments.

However, on Win64, this attribute is unimplemented and the raw pointer is
passed to the callee instead. This is problematic, because frontend authors
relying on the implicit hidden copy (as happens for every other calling
convention) will see the passed value silently (if mutable memory) or
loudly (by means of a crash) modified because the callee treats the
location as scratch memory space it is allowed to mutate.

At this point, it's worth taking a step back to understand the context.
In most calling conventions, aggregates that are too large to be passed
in registers, instead get *copied* to the stack at a fixed (computable
from the signature) offset of the stack pointer. At the LLVM, we hide
this hidden copy behind the byval attribute. The caller passes a pointer
to the desired data and the callee receives a pointer, but these pointers
are not the same. In particular, the pointer that the callee receives
points to temporary stack memory allocated as part of the call lowering.
In most calling conventions, this pointer is never realized in registers
or memory. The temporary memory is simply defined by an implicit
offset from the stack pointer at function entry.

Win64, uniquely, works differently. The structure is still passed in
memory, but instead of being stored at an implicit memory offset, the
caller computes a pointer to the temporary memory and passes it to
the callee as a regular pointer (taking up a register, or if all
registers are taken up, an additional stack slot). Presumably, this
was done to allow eliding the copy when passing aggregates through
several functions on the stack.

This explains why ignoring the byval attribute mostly works on Win64.
The argument simply gets passed as a pointer and as long as we're ok
with the callee trampling all over that memory, there are no ill effects.
However, it does contradict the documentation of the byval attribute
which specifies that there is to be an implicit copy.

Frontends can of course work around this by never emitting the byval
attribute for Win64 and creating allocas for the requisite temporary
stack slots (and that does appear to be what frontends are doing).
However, the presence of the byval attribute is not a trap for
frontend authors, since it seems to work, but silently modifies the
passed memory contrary to documentation.

I see two solutions:

  • Disallow the byval attribute in the verifier if using the Win64 calling convention.
  • Make it work by simply emitting a temporary stack copy as we would with any other calling convention (frontends can of course always not use the attribute if they want to elide the copy).

This patch implements the second option (make it work), though I would
be fine with the first also.

Ref: https://github.com/JuliaLang/julia/issues/28338

Diff Detail

Event Timeline

loladiro created this revision.Sep 9 2018, 2:01 PM
rnk accepted this revision.Sep 10 2018, 11:49 AM

lgtm with a tweaked comment. Thanks!

lib/Target/X86/X86ISelLowering.cpp
3625

I wouldn't call this a spill slot, I would say, copy the argument so that the caller does not see modifications.

This revision is now accepted and ready to land.Sep 10 2018, 11:49 AM

If you're a frontend author, I would suggest not using byval, ever; it mostly exists for compatibility with existing C calling conventions. (Of course, it should still work if the frontend requests it.)

loladiro updated this revision to Diff 165194.Sep 12 2018, 7:36 PM

Fix comment as requested by review, fix a small bug found in more extensive testing,
add a test case for the latter.

This revision was automatically updated to reflect the committed changes.