[llvm-dev] [RFC] Adding CPS call support
Kavon Farvardin via llvm-dev
llvm-dev at lists.llvm.org
Wed Apr 19 10:38:28 PDT 2017
> The semantics around inlining alone are problematic enough to warrant serious hesitation.
There are nicer ways to embed CPS call/return into LLVM; I just figured that there would not be much support for adding a new terminator because it would change a lot of code. Ideally we would have a block terminator like:
cps call ghccc @bar (.. args ..) returnsto label %retpt
Where the "returnsto" is optional to cover both CPS calls and returns. Another alternative would be to emulate this terminator using an intrinsic.
So, my question is: would there be more support for a version of this terminator (whether as an intrinsic or not) instead of what I was proposing earlier?
~kavon
> On Apr 18, 2017, at 7:24 PM, Philip Reames via llvm-dev <llvm-dev at lists.llvm.org> wrote:
>
>
>
> On 04/17/2017 08:30 AM, Kavon Farvardin via llvm-dev wrote:
>> Summary
>> =======
>>
>> There is a need for dedicated continuation-passing style (CPS) calls in LLVM to
>> support functional languages. Herein I describe the problem and propose a
>> solution. Feedback and/or tips are greatly appreciated, as our goal is to
>> implement these changes so they can be merged into LLVM trunk.
>>
>>
>> Problem
>> =======
>>
>> Implementations of functional languages like Haskell and ML (e.g., GHC and
>> Manticore) use a continuation-passing style (CPS) transformation in order to
>> manage the call stack explicitly. This is done prior to generating LLVM IR, so
>> the implicit call stack within LLVM is not used for call and return.
>>
>> When making a non-tail call while in CPS, we initialize a stack frame for the
>> return through our own stack pointer, and then pass that stack pointer to the
>> callee when we jump to it. It is here when we run into a problem in LLVM.
>>
>> Consider the following CPS call to @bar and how it will return:
>>
>> ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
>>
>> define void @foo (i8** %sp, ...) {
>> someBlk:
>> ; ...
>> ; finish stack frame by writing return address
>> %retAddr = blockaddress(@foo, %retpt)
>> store i8* %retAddr, i8** %sp
>> ; jump to @bar
>> tail call void @bar(i8** %sp, ...)
>>
>> retpt: ; <- how can @bar "call" %retpt?
>> %sp2 = ???
>> %val = ???
>> ; ...
>>
>> }
>>
>> define void @bar (i8** %sp, ...) {
>> ; perform a return
>> %retAddr0 = load i8*, i8** %sp
>> %retAddr1 = bitcast i8* %retAddr0 to void (i8**, i64)*
>> %val = bitcast i64 1 to i64
>> ; jump back to %retpt in @foo, passing %sp and %val
>> tail call void %retAddr1(i8** %sp, i64 %val)
>> }
>>
>> ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
>>
>> There is currently no way to jump back to %retpt from another function, as block
>> addresses have restricted usage in LLVM [1]. Our main difficulty is that we
>> cannot jump to a block address without knowing its calling convention, i.e., the
>> particular machine registers (or memory locations) that the block expects
>> incoming values to be passed in.
>>
>> The workaround we have been using in GHC for LLVM is to break apart every
>> function, placing the code for the continuation of each call into a new
>> function. We do this only so that we can store a function pointer instead of a
>> block address to our stack. This particularly gross transformation inhibits
>> optimizations in both GHC and LLVM, and we would like to remove the need for it.
> Can you give a bit more information on what this transformation breaks optimization wise? You're essentially explicitly representing the continuation and converting all calls into tail calls with an explicit continuation argument. I would expect the LLVM optimizer to do reasonable well with such a representation.
>>
>>
>> Proposal
>> ========
>>
>> I believe the lowest-impact method of fixing this problem with LLVM is the
>> following:
>>
>> First, we add a special 'cps' call instruction marker to be used on non-tail
>> calls. Then, we use a specialized calling convention for these non-tail calls,
>> which fix the returned values to specific locations in the machine code [2].
>>
>> To help illustrate what's going on, let's rewrite the above example using the
>> proposed 'cps' call:
>>
>> ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
>>
>> define { ... } @foo (i8** %sp, ...) {
>> someBlk:
>> ; ...
>> ; finish stack frame by writing return address
>> %retAddr = blockaddress(@foo, %retpt)
>> store i8* %retAddr, i8** %sp
>> ; jump to @bar
>> %retVals = cps call ghccc {i8**, i64} @bar (i8** %sp, ...)
>> br label %retpt
>>
>> retpt:
>> %sp2 = extractvalue {i8**, i64} %retVals, 0
>> %val = extractvalue {i8**, i64} %retVals, 1
>> ; ...
>>
>> }
>>
>> define {i8**, i64} @bar (i8** %sp, ...) {
>> ; perform a return
>> %retAddr0 = load i8*, i8** %sp
>> %retAddr1 = bitcast i8* %retAddr0 to {i8**, i64} (i8**, i64)*
>> %val = bitcast i64 1 to i64
>> ; jump back to %retpt in @foo, passing %sp and %val
>> tail call ghccc void %retAddr1(i8** %sp, i64 %val)
>>
>> unreachable ; <- ideally this would be our terminator,
>> ; but 'unreachable' breaks TCO, so we will
>> ; emit a ret of the struct "returned" by the call.
>> }
>>
>> ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
>>
>> The important point here is that the 'cps' marked call will lower to a jump. The
>> 'cps' call marker means that the callee knows how to return using the arguments
>> explicitly passed to it, i.e., the stack pointer %sp. The callee cannot use a
>> 'ret' instruction if it is 'cps' called.
>>
>> Either before or during 'cps' call lowering, any instructions following the
>> 'cps' call to @bar are sunk into the the block %retpt, and the unconditional
>> branch to %retpt is deleted/ignored. We include that branch to preserve
>> control-flow information for LLVM IR optimization passes.
>>
>> The 'extractvalue' instructions are what ensure the calling convention of
>> %retpt, since the fields of the struct %retVals are returned in physical
>> registers dictated by the (modified) ghccc convention. Those same physical
>> registers are used by the ghccc tail call in @bar when it jumps back to %retpt.
>> So, the call & return convention of ghccc ensures that everything matches up.
>>
>>
>> Interaction with LLVM
>> =====================
>>
>> (1) Caller-saved Values
>>
>> One may wonder how this would work if there are caller-saved values of the 'cps'
>> call. But, in our first example, which closely matches what CPS code looks like,
>> the call to @bar was in tail position. Thus, in the second example, there are no
>> caller-saved values for the 'cps' call to @bar, as all live values were passed
>> as arguments in the call.
>>
>> This caller-saved part is a bit subtle. It works fine in my experience [2] when
>> @bar is a function not visible to LLVM. My impression is that even if @bar is
>> visible to LLVM, there is still no issue, but if you can think of any corner
>> cases that would be great!
>>
>> (2) Inlining
>>
>> My gut feeling is that we cannot inline a 'cps' marked call-site without more
>> effort. This is because we might end up with something odd like this once the
>> dust settles:
>>
>> %retAddr = blockaddress(@foo, %retpt)
>> %retAddr1 = bitcast i8* %retAddr to {i8**, i64} (i8**, i64)*
>> tail call ghccc %retAddr1 ( %sp, ... )
>>
>> We could add a pass that turns the above sequence into just an unconditional
>> branch to %retpt, using a phi-node to replace each 'extractvalue' instruction in
>> that block.
>>
>> I'm not sure whether inlining in LLVM is important for us yet, as we tend to
>> inline quite a lot before generating LLVM IR. I don't think this additional fix-
>> up pass would be too difficult to implement if it's desired.
>>
>>
>> Implementation Sketch and Conclusion
>> ====================================
>>
>> My current plan is to add this special lowering of 'cps' calls during the
>> translation from LLVM IR to the SelectionDAG. I welcome any suggestions or tips
>> on the best way to approach this. An important goal for us is to merge this into
>> trunk since we do not want to bundle a special version of LLVM with GHC.
>>
>> Please let me know soon if you have any objections to this feature.
>>
>> Thanks for reading,
>> Kavon
> Honestly, this proposed design seems seriously problematic. The semantics around inlining alone are problematic enough to warrant serious hesitation. I think you need to take a step back, describe the original problem you're trying to solve and why the standard conversion approaches don't work out. You'd need to strongly motivate a change of this magnitude and I really don't feel like you've done so so far.
>>
>>
>> References
>> ==========
>>
>> [1] http://llvm.org/docs/LangRef.html#blockaddress
>> [2] http://kavon.farvard.in/papers/ml16-cwc-llvm.pdf
>>
>>
>> _______________________________________________
>> LLVM Developers mailing list
>> llvm-dev at lists.llvm.org
>> http://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-dev
>
> _______________________________________________
> LLVM Developers mailing list
> llvm-dev at lists.llvm.org
> http://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-dev
More information about the llvm-dev
mailing list