[cfe-dev] [llvm-dev] RFC: Implementing the Swift calling convention in LLVM and Clang (original) (raw)

Renato Golin via cfe-dev cfe-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 cfe-dev mailing list