[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 12:03:09 PST 2016


> On Mar 2, 2016, at 11:33 AM, Renato Golin <renato.golin at linaro.org> wrote:
> 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);

We don’t need to.  We don't use the intermediary convention’s rules for aggregates.
The Swift rule for aggregate arguments is literally “if it’s too complex according to
<foo>, pass it indirectly; otherwise, expand it into a sequence of scalar values and
pass them separately”.  If that means it’s partially passed in registers and partially
on the stack, that’s okay; we might need to re-assemble it in the callee, but the
first part of the rule limits how expensive that can ever get.

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

That’s pretty sub-optimal compared to just returning in registers.  Also, most
backends do have the ability to return small structs in multiple registers already.

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

I don’t understand what you mean here.  The out-parameter is still explicit in
LLVM IR.  Nothing about this is novel, except that C frontends generally won’t
combine indirect results with direct results.  Worst case, if pervasive LLVM
assumptions prevent us from combining the sret attribute with a direct result,
we just won’t use the sret attribute.

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

Right.  The backend isn’t great about removing memory operations that survive to it.

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

Swift does not run in an independent environment; it has to interact with
existing C code.  That existing code does not reserve any registers globally
for this use.  Even if that were feasible, we don’t actually want to steal a
register globally from all the C code on the system that probably never
interacts with Swift.

John.


More information about the llvm-dev mailing list