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

Renato Golin via llvm-dev llvm-dev at lists.llvm.org
Wed Mar 2 11:33:08 PST 2016


On 2 March 2016 at 18:48, John McCall <rjmccall at apple.com> wrote:
> 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.

I'm assuming you already have code in the back-end that does that in
the way you want, as you said earlier you may want to use variable
number of registers for PCS.


> 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.

How do you differentiate the @foo's below?

struct A { i32, float };
struct B { float, i32 };

define @foo (A, i32) -> @foo(i32, float, i32);

and

define @foo (i32, B) -> @foo(i32, float, i32);


> 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.

Oh, yeah, some back-ends will fiddle with struct return. Not all
languages have single-value-return restrictions, but I think that ship
has sailed already for IR.

That's another reason to try and pass all by pointer at the end of the
parameter list, instead of receive as an argument and return.


> 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.

Oh, I see. In that case, any assumption on the variable would have to
be invalidated, maybe use global volatile variables, or special
built-ins, so that no optimisation tries to get away with it. But that
would mess up your optimal code, especially if they have to get passed
in registers.


> 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.

Excellent!


> 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.

This should be ok for internal functions, but maybe not for global /
public interfaces. The ARM ABI has specific behaviour guarantees for
public interfaces (like large alignment) that would be prohibitively
bad for all functions, but ok for public ones.

If hells break loose, you could enforce that for public interfaces only.


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

And even though most of those access could be optimised away, there's
no guarantee.

Another option would be to have a special built-in to recognise
context/error variables, and plug in a late IR pass to clean up
everything. But I'd only recommend that if we can't find another way
around.


> 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*)

Indeed, very messy.

I'm going on a tangent, here, may be all rubbish, but...

C++ handles exception handling with the exception being thrown
allocated in library code, not the program. If, like C++, Swift can
only handle one exception at a time, why can't the error variable be a
global?

The ARM back-end accepts the -rreserve-r9 option, and others seem to
have similar options, so you could use that to force your global
variable to live on the platform register.

That way, all your error handling built-ins deal with that global
variable, which the back-end knows is on registers. You will need a
special DAG node, but I'm assuming you already have/want one. You also
drop any problem with arguments and PCS, at least for the error part.

cheers,
--renato


More information about the llvm-dev mailing list