[llvm-dev] RFC: Implementing the Swift calling convention in LLVM and Clang

John McCall via llvm-dev llvm-dev at lists.llvm.org
Wed Mar 2 10:48:24 PST 2016


> On Mar 2, 2016, at 1:33 AM, Renato Golin <renato.golin at linaro.org> wrote:
> 
> On 2 March 2016 at 01:14, John McCall via llvm-dev
> <llvm-dev at lists.llvm.org> wrote:
>> Hi, all.
>>  - We sometimes want to return more values in registers than the convention normally does, and we want to be able to use both integer and floating-point registers.  For example, we want to return a value of struct A, above, purely in registers.  For the most part, I don’t think this is a problem to layer on to an existing IR convention: C frontends will generally use explicit sret arguments when the convention requires them, and so the Swift lowering will produce result types that don’t have legal interpretations as direct results under the C convention.  But we can use a different IR convention if it’s necessary to disambiguate Swift’s desired treatment from the target's normal attempts to retroactively match the C convention.
> 
> Is this a back-end decision, or do you expect the front-end to tell
> the back-end (via annotation) which parameters will be in regs? Unless
> you also have back-end patches, I don't think the latter is going to
> work well. For example, the ARM back-end has a huge section related to
> passing structures in registers, which conforms to the ARM EABI, not
> necessarily your Swift ABI.
> 
> Not to mention that this creates the versioning problem, where two
> different LLVM releases can produce slightly different PCS register
> usage (due to new features or bugs), and thus require re-compilation
> of all libraries. This, however, is not a problem for your current
> request, just a comment.

The frontend will not tell the backend explicitly which parameters will be
in registers; it will just pass a bunch of independent scalar values, and
the backend will assign them to registers or the stack as appropriate.

Our intent is to completely bypass all of the passing-structures-in-registers
code in the backend by simply not exposing the backend to any parameters
of aggregate type.  The frontend will turn a struct into (say) an i32, a float,
and an i8; if the first two get passed in registers and the last gets passed
on the stack, so be it.

The only difficulty with this plan is that, when we have multiple results, we
don’t have a choice but to return a struct type.  To the extent that backends
try to infer that the function actually needs to be sret, instead of just trying
to find a way to return all the components of the struct type in appropriate
registers, that will be sub-optimal for us.  If that’s a pervasive problem, then
we probably just need to introduce a swift calling convention in LLVM.

>>  - We sometimes have both direct results and indirect results.  It would be nice to take advantage of the sret convention even in the presence of direct results on targets that do use a different (profitable) ABI treatment for it.  I don’t know how well-supported this is in LLVM.
> 
> I'm not sure what you mean by direct or indirect results here. But if
> this is a language feature, as long as the IR semantics is correct, I
> don't see any problem.

A direct result is something that’s returned in registers.  An indirect
result is something that’s returned by storing it in an implicit out-parameter.
I would like to be able to form calls like this:

  %temp = alloca %my_big_struct_type
  call i32 @my_swift_function(sret %my_big_struct_type* %temp)

This doesn’t normally happen today in LLVM IR because when C frontends
use an sret result, they set the direct IR result to void.

Like I said, I don’t think this is a serious problem, but I wanted to float the idea
before assuming that.

>>  - We want a special “context” treatment for a certain argument.  A pointer-sized value is passed in an integer register; the same value should be present in that register after the call.  In some cases, the caller may pass a context argument to a function that doesn’t expect one, and this should not trigger undefined behavior.  Both of these rules suggest that the context argument be passed in a register which is normally callee-save.
> 
> I think it's going to be harder to get all opts to behave in the way
> you want them to. And may also require back-end changes to make sure
> those registers are saved in the right frame, or reserved from
> register allocation, or popped back after the call, etc.

I don’t expect the optimizer to be a problem, but I just realized that the main
reason is something I didn’t talk about in my first post.  See below.

That this will require some support from the backend is a given.

>> The Clang impact is relatively minor; it is focused on allowing the Swift runtime to define functions that use the convention.  It adds a new calling convention attribute, a few new parameter attributes constrained to that calling convention, and some relatively un-invasive call lowering code in IR generation.
> 
> This sounds like a normal change to support language perks, no big
> deal. But I'm not a Clang expert, nor I've seen the code.
> 
> 
>>  - Using sret together with a direct result may or may not “just work".  I certainly don’t see a reason why it shouldn’t work in the middle-end.  Obviously, some targets can’t support it, but we can avoid doing this on those targets.
> 
> All sret problems I've seen were back-end related (ABI conformance).
> But I wasn't paying attention to the middle-end.
> 
> 
>>  - Opting in to the two argument treatments requires new parameter attributes.  We discussed using separate calling conventions; unfortunately, error and context arguments can appear either separately or together, so we’d really need several new conventions for all the valid combinations.  Furthermore, calling a context-free function with an ignored context argument could turn into a call to a function using a mismatched calling convention, which LLVM IR generally treats as undefined behavior.  Also, it wasn’t obvious that just a calling convention would be sufficient for the error treatment; see the next bullet.
> 
> Why not treat context and error like C's default arguments? Or like
> named arguments in Python?

> 
> Surely the front-end can easily re-order the arguments (according to
> some ABI) and make sure every function that may be called with
> context/error has it as the last arguments, and default them to null.
> You can then later do an inter-procedural pass to clean it up for all
> static functions that are never called with those arguments, etc.

Oh, sorry, I forgot to talk about that.  Yes, the frontend already rearranges
these arguments to the end, which means the optimizer’s default behavior
of silently dropping extra call arguments ends up doing the right thing.

I’m reluctant to say that the convention always requires these arguments.
If we have to do that, we can, but I’d rather not; it would involve generating
a lot of unnecessary IR and would probably create unnecessary
code-generation differences, and I don’t think it would be sufficient for
error results anyway.

>>  - The “error” treatment requires some way to (1) pass and receive the value in the caller and (2) receive and change the value in the callee.  The best way we could think of to represent this was to pretend that the argument is actually passed indirectly; the value is “passed” by storing to the pointer and “received” by loading from it.  To simplify backend lowering, we require the argument to be a special kind of swifterror alloca that can only be loaded, stored, and passed as a swifterror argument; in the callee, swifterror arguments have similar restrictions.  This ends up being fairly invasive in the backend, unfortunately.
> 
> I think this logic is too high-level for the back-end to deal with.
> This looks like a simple run of the mill pointer argument that can be
> null (and is by default), but if it's not, the callee can change the
> object pointed by but not the pointer itself, ie, "void foo(exception
> * const Error = null)". I don't understand why you need this argument
> to be of a special kind of SDNode.

We don’t want checking or setting the error result to actually involve memory
access.

An alternative to the pseudo-indirect-result approach would be to model
the result as an explicit result.  That would really mess up the IR, though.
The ability to call a non-throwing function as a throwing function means
we’d have to provide this extra explicit result on every single function with
the Swift convention, because the optimizer is definitely not going to
gracefully handle result-type mismatches; so even a function as simple as
  func foo() -> Int32
would have to be lowered into IR as
  define { i32, i8* } @foo(i8*)

John.


More information about the llvm-dev mailing list