-
Notifications
You must be signed in to change notification settings - Fork 13
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Consistent parameter order between cont.bind and switch tags #67
Comments
IMO the most important desugaring is of For that desugaring you want the continuation operand to |
Also, unless I'm mistaken, your motivating use case of desugaring
When binding from right to left, this rewriting works for binding any number operands, not just when binding all of them. |
I am not sure that I fully buy the desugaring argument for cont.bind. My reasoning is that that desugaring (into a resume followed by a suspend) requires changing the operand. A similar argument would allow us to claim that int32.mul was a sugared version of f32.mul. |
Yes, the desugaring is nontrivial and requires more than local changes, but it's still worth it to make it as simple as possible despite that. The simpler desugaring is, the simpler re-sugaring is, and the re-sugarings might be useful optimizations that we actually want to do in e.g. Binaryen. |
To be clear, something that cannot be performed locally and requires global knowledge is not a desugaring but a general transformation. That is a qualitative difference: desugarings are compositional with the rest of the language and can hence e.g. be used in an interpreter or to define semantics, whereas a general transformation requires a global transformation pass over the entire module/program, which only works in a proper compiler or tool pipeline. (And of course, a construct being expressible by global transformation generally is a vacuous statement, because it ultimately is universally true for Turing-complete languages.) For the purposes we are interested in here it is hence relevant that this is a proper desugaring. Concretely, the expression of cont.bind via suspend/resume that you allude to is not of that nature afaics, for example because it would need to add tag definitions depending on all uses of cont.bind. On-topic, re parameter order: the relevant pattern is one where stack switches of different type need to be funnelled through a single common handler or abstraction, which requires all the continuations to have a unified type. Once you start implementing various examples, that quickly emerges as a common pattern. It can be expressed by cont-binding the differing arguments at the code site initiating the switch/suspend, only leaving the common ones. The target continuation always is common, naturally — otherwise the handler/abstraction couldn't do anything — so must be the last to be affected by cont.bind. |
Sure, instead of "desugaring" I should have used "rewriting." I agree that these non-local rewritings are not useful for interpreters or specification, but they are useful for optimizers, and it is not vacuous to try to make the design as optimizable as possible. So if I understand correctly, you want to be able to desugar or rewrite |
The scenario is more like having an abstraction, e.g., a function, to perform the actual switch (and perhaps some house-keeping) but having to use that with continuations of conceptually heterogeneous type. To make that work, you'll have to replace, say, multiple sites like
and
(note the different number of ops), but in a type-correct way, which requires that the continuations passed to $yield all have the same type and fixed parameters. The way to do that is e.g. by changing the above to
and
such that $yield takes a continuation of uniform type $ct. If $yield is implemented by switch, then this $ct still receives the continuation parameter. So for these cont.bind's to work, that parameter must be the last to bind. FWIW, this is a pattern very familiar from languages with currying, where you typically structure functions such that the least varying parameter comes first. |
I think it's more consistent with the rest of Wasm to keep the continuation as the last argument. I have a slight preference for left-to-right binding for At the implementation level, with left-to-right binding, a continuation has a value stack that just grows normally by pushing more arguments onto it. If we have right-to-left continuation binding, then the value stack has a reserved range (i.e. a "hole") that gets filled top-to-bottom, with value entries in the middle being uninitialized. Left to right, suspended continuation:
After binding:
Then resuming:
In contrast, a suspended continuation with right-to-left has a hole where values will go, right to-left:
After binding:
Then resuming:
[1] I say naturally, because it wouldn't matter except that we made a choice with how locals are numbered at function calls that ended up making "value stack grows towards higher addresses" a smidgen more efficient. For an |
@rossberg, your desired rewriting makes perfect sense, but I don't see how it requires that the continuation reference for |
@tlively, switch will occur inside the $yield function, but the continuation passed to $yield must already have a type suitable for switch — in particular, that receives the suspended continuation in the last position. Hence, if at the same time cont.bind works right-to-left, then you can't meaningfully use it for any sort of continuation also used with switch. @titzer, I'd also prefer keeping cont.bind as is. But if we change it, then we must change the switch continuation accordingly. |
@rossberg Oh, I see, thanks. It is unfortunate that This all reinforces my belief that we should just leave If it comes down to a choice between having right-to-left binding |
I would push back against the 'do it later' argument. This is (IMO) a potentially critical optimization for Kotlin & Swift (for different reasons). Remember that, in the best case, in V8, a switch it likely to cost of the order of 10 function calls. (Certainly not less than five, currently more like 20). |
If we move the continuation produced by a |
I would also push back strongly on dropping cont.bind. It was needed in at least half the use cases we looked at, and there is no efficient alternative. Moreover, that would just increase the likelihood of making a forward-incompatible decision elsewhere.
Not sure I follow — suspend does not produce a continuation. Suspend handlers do, but those do not have a built-in continuation parameter. |
I'm thinking of the label types. We can make the label types receive the suspended continuation before other values for consistency with switched-to continuations receiving the switched-from continuation before other values. |
What if we had |
Ah, I see. Yes, that should probably change in the same way.
Given that the only argument for changing the order was to avoid shuffling registers in some implementations, how would that help? Then we can just as well keep the natural left-to-right. |
It’s also worth noting that all the usual methods of implementing partial application for functions in user space (i.e passing bound context as a parameter) are also still available for continuations, and would be much more efficient than implementing partial application with stack switching. I’m still not convinced that real-world producers would have enough trouble with user space solutions to merit |
I'd expect explicit passing of context parameters to be more expensive, because you usually have extra allocations and indirections, and you have to lose type information and recover it with casts (when using GC types). In contrast, cont.bind is statically type-safe and can be implemented allocation-free, which is possible thanks to the linearity of continuations. We can always revisit any part of a proposal. That said, I doubt we can easily get away without cont.bind, primarily using as an effective way to work around the limitations of the type system. |
I previously wrote,
With the new understanding that right-to-left binding would come along with received continuations coming before passed arguments for both switches and suspend handlers, I'm switching my preference to left-to-right binding, assuming we do have to have |
Should we switch the explainer to use left-to-right binding then? |
I think that makes sense for now, but we should make sure @fgmccabe is on board. |
I am 'ok' with keeping the left-to-right ordering. Just know that you are baking in additional complexity and performance costs for the foreseeable future. |
The explainer currently proposes to change the order in which
cont.bind
binds arguments. However, it needs to remain possible to usecont.bind
to supply the (regular) arguments to a continuation used withswitch
, otherwise certain usage patterns that usecont.bind
to unify continuation argument types would break (see comment for some context).If we keep the changed order of
cont.bind
, we hence need to consistently change the position of the continuation parameter to a switch tag from last to first:The text was updated successfully, but these errors were encountered: