Join GitHub today
GitHub is home to over 31 million developers working together to host and review code, manage projects, and build software together.
Sign upidea: "Multi-return" functions for faster Rust `Result` and/or simpler exceptions #553
Comments
sunfishcode
added
the
E-compiler-design
label
Oct 9, 2018
This comment has been minimized.
This comment has been minimized.
|
This is a cool idea! It's very interesting to think about using Cranelift to explore custom calling conventions for Rust. (For actual unwinding, Rust's hands are tied in practice by the practical need to interoperate with existing C/C++ ABIs. And the DWARF unwinding logic is already provided, so all we have to do is provide the appropriate tables. But for One of my ideas in this space is to use the CF flag on x86 (other architectures have other architecture-specific things we could look at) to hold the discriminant of If anyone is interested in laying the foundations for this kind of exploration in Cranelift, I think the first step here is to generalize Cranelift IR's concept of a function signature to permit a tuple of return types. |
This comment has been minimized.
This comment has been minimized.
Glad to here it! That's what i was thinking / hoping wouldn't get in the way of any other things. Sounds like a nice fun refactor. |
This comment has been minimized.
This comment has been minimized.
|
One further thought here: I think Cranelift IR wants a concept of a primary return type, optionally with secondary return types added. This might look a little unusual from the abstract perspective where it's just a tuple of return types, however I think it makes sense at the level of Cranelift IR. When we get the the point where we're adding a version of the |
This comment has been minimized.
This comment has been minimized.
|
Well next like to do the empty list for diverging functions, since they don't return at all. Couldn't a convention that the first return type is the normal one suffice? For your CF example, the ABI is the same (flag must be checked either way). For mine, the only loss is short-hands like the |
This comment has been minimized.
This comment has been minimized.
|
Oh, cool. It hadn't occurred to me that diverging functions would just have an empty tuple. But I see how that makes sense. Currently in Cranelift IR, the last instruction in an EBB is required to use one of the opcodes which is specially designated as a "terminator". Regular calls aren't terminators, so for a call to a diverging function, maybe we could use a special There might be ways we could fix that, though this may take a bit more design work. |
This comment has been minimized.
This comment has been minimized.
|
I went with a |
This comment has been minimized.
This comment has been minimized.
|
I've started implementing things. https://github.com/Ericson2314/cranelift/tree/multi-fun-sig is the signature generalization. https://github.com/Ericson2314/cranelift/tree/call_table is the boilerplate to add For the former, most of the work is boring
From the limited amount I know so far, I'd like to make link unconditionally part of the legalized signature:
|
This comment has been minimized.
This comment has been minimized.
|
Catching up here: Yes, making the x86 return value on the stack a "link" argument (when it needs one) sounds reasonable to me. |
This comment has been minimized.
This comment has been minimized.
|
Thanks. How about the cranelift/lib/codegen/src/isa/mod.rs Line 265 in bb26748 |
This comment has been minimized.
This comment has been minimized.
|
For the link register, Cranelift certainly does need to be aware of the link register For stack slots, where are they handled differently for the "current" case? |
This comment has been minimized.
This comment has been minimized.
I forget in the 2 weeks since I last touched the code :). I'm guess-remembering they may have been just implicit in both cases.
I think the solution is to get rid of the First, the machine's operation: Since the callee consumes the link (register or stack slock)---i.e after return the stack poiner is bumped or the register can contain something else---the If one thinks thinks of ABIs as types like then we have something like:
So it's sort of like a higher order function ( My guess is |
This comment has been minimized.
This comment has been minimized.
|
I think you're right, and I agree that it would be nice to make caller and callee work the same way here. However I don't yet see how we can do that cleanly. If we put the link in the callee's signature, we'll need to also add a |
This comment has been minimized.
This comment has been minimized.
|
Where are these "Basic IR invariants" in the code? I'm very much willing to try teach them the higher-order nature of this stuff :). |
This comment has been minimized.
This comment has been minimized.
|
Instructions can't use values they themselves define: https://github.com/CraneStation/cranelift/blob/master/lib/codegen/src/verifier/mod.rs#L868 Call arguments must match signature parameters: https://github.com/CraneStation/cranelift/blob/master/lib/codegen/src/verifier/mod.rs#L1219 |
This comment has been minimized.
This comment has been minimized.
|
Thanks! |
This comment has been minimized.
This comment has been minimized.
|
Sweet! So I think the first bit can stay the same. The second bit I'd change by adding some extra link args to match against the inferred virtual parms, so everything verifies up. Those args wouldn't come from any particular instruction per the normal way, but rather be made up out of thin air since they come from the "first half" of the call instruction. Initially, they'd be the same for every call instruction (for a given ISA), but once we get multi return, different call instructions would yield different virtual args. [BTW, this also gets us closer to using Looking at that code reminds me, is there any issue to add argument/returns for |
This comment has been minimized.
This comment has been minimized.
|
[Argument/returns for |
This comment has been minimized.
This comment has been minimized.
|
Ok, I'll be interested to see what this ends up looking like :-). Adding arguments to
Suppose all these edges are branched via One idea we've tossed around is to change
we might have:
or so. Now we can do things between this branch and say All that said, if you have other ideas here, I'm interested! |
This comment has been minimized.
This comment has been minimized.
|
A few more notes:
But also, thinking about this more, I think we may not actually need |
This comment has been minimized.
This comment has been minimized.
gnzlbg
commented
Mar 14, 2019
Note that if a foreign Being able to interoperate with C++ ABIs that unwind would require |
Ericson2314 commentedOct 9, 2018
From the paper "Multi-return Function Call" (http://www.ccs.neu.edu/home/shivers/papers/mrlc-jfp.pdf).
The basic idea from the perspective of compiled code is to include multiple return pointers in a stack frame so functions can return to different places.
Compared to
Result<T,E>This is denotationally the same as return a value of a Rust enum with 1 variant according to each of the return pointer slots, with fields according to the returned data associated with that slot (registers, spilled stack slots, etc). But with the naive enum calling convention of adding a tag field, the caller needs to branch on the tag field, even if the enum value was just created before the return so nothing is in principle unpredictable. In the common case of a function "rethrowing" a
Err, (Err(e0) => ... return Err(e1) ...math arm), the native way results results on O(n) branches (one per stack frame) one each of theErrtags, while this way allows the error return pointer to point to disjoint control flow for the failure case, catching and rethrowing without additional branches, so the only 1 branch is the original failure condition.Compared to unwinding
Success and failure control flow is implemented identically, avoiding significant effort on the part of compiler writers in maintaining a completely separate implementation concepts while optimizations can work with both, and don't get stuck on the success failure boundary. At run time, the lack of any DWARF-like interpreters reduces dependencies and simplifies things too.
In short, we have the asymptotic efficiency of unwinding with the implementation (compiler and run-time) efficiency of enum return.
I talked to @eddyb about this once and he said to talk to someone on
#cranelift, but alas I am on IRC less these days and I forgot their nick. Opening this to make sure the idea isn't lost completely due to my negligence. Cheers.