3 ms·
> WASM implemented return values in a way that was different from register hardware, and it makes efficient codegen of Common Lisp more challenging. This was br
by titzer 5y ago
> WASM implemented return values in a way that was different from register hardware, and it makes efficient codegen of Common Lisp more challenging. This was brought to the attention of the committee while WASM was still in flux, and they (perhaps rightfully) decided CL was insufficiently important to change things.
Can you refresh my memory here? What exactly is different about Wasm return values than any other function-oriented language?
- throwaway81523 5y agoCommon Lisp supports multiple value returns, which C doesn't. Go sort of does, though.
- titzer 5y agoWasm supports multiple value returns.
- aidenn0 5y agoCommon Lisp will often have auxiliary return values that are often not needed (e.g. the mathematical floor function returns the remainder as a second value). Unused extra values are silently discarded. So you can do, for example (+ (floor x y) z) without worrying about the second value to floor. A lisp compiler for a register machine will usually return the first N values in registers (for some small value of N), so the common case of using only the primary return value generates exactly the same code regardless of how many values are actually returned. I don't remember the details, but however wasm happens to implement multiple return values, you can't just pretend that a function that returns 2 values only returns one.
- titzer 5y agoReturn values are pushed onto the Wasm operand stack in left-to-right order. Engines use registers for params/returns up to a point in optimized code and then spill to the stack. So at the call site you can just drop the return values you don't want and should end up with machine code that does exactly what you want.
- deleted 5y ago[deleted]
- aidenn0 5y agowasm functions must have a fixed number of results[1]. Lisp functions may have variadic results. A wasm function call must explicitly pop every result off of the stack[2]. These rules add significant overhead to implementing: (foo (bar)) Which calls foo with just the first result of bar, and the number of results yielded by bar is possibly unknown. In psuedo-assembly for a register machine, implementing this is roughly: CALL bar MOVE ResultReg1 -> ArgumentReg1 CALL foo And this works regardless of how many values the function bar happens to return[3]. Any implementation that is correct for any number of results of "bar" will be slow for the common case of bar yielding one or two results. 1: https://webassembly.github.io/spec/core/syntax/types.html#syntax-functype https://webassembly.github.io/spec/core/syntax/types.html#sy... 2: https://webassembly.github.io/spec/core/exec/instructions.html#function-calls https://webassembly.github.io/spec/core/exec/instructions.ht... 3: Showing only the caller side of things, when the caller doesn't care about extra values hides some complexity of implementation of returning values because you also need to be able to detect at run-time how many values were actually returned. e.g. SBCL on x86 uses a condition flag to indicate if there are multiple values, because branching on a condition flag lets you handle the only-1 value case efficiently.
- titzer 5y agoOk, I see. Yes, there is some overhead if you want not just multiple returns, but variadic returns. Most virtual machines and programming languages make this fairly inefficient because of fixed arity of returns. AFAICT, targeting anything other than machine code is going to cost you in this case.