Skip to content
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

Add a block1 operator that returns the values of the first expression and discards the rest. #650

Closed
ghost opened this issue Apr 9, 2016 · 12 comments

Comments

@ghost
Copy link

ghost commented Apr 9, 2016

The block operator returns the values of the last expression but it is a common pattern to need to return values from other expressions in a block and this current requires the use of local variables. Adding a block1 operator that returns the values of the first expression would allow these pattern to be expressed as expressions without using local variables. The combination of block and block1 allows the results of an arbitrary expression to be returned.

For example to return the values of the second expression in an effective block of expressions the following is currently required:

(block
  (expression1)
  (set_local $v1 (expression2))
  (expression3)
  (get_local $v1))

With a block1 operator this could be:

(block
  (expression1)
  (block1
     (expression2)
     (expression 3)))

When the result expression has multiple values, using local variables in this pattern becomes even more verbose making block1 more compelling.

@sunfishcode Can you see an obvious way for the llvm backend to exploit this?

@sunfishcode
Copy link
Member

It wouldn't be difficult to use this from LLVM, however it's limited to single-use values, and there are limitations on what order things can happen in, so I wouldn't expect it to make a big difference.

Also, I'm generally disinclined with multiple-value designs that require tuple types on the value stack, because that requires additional linguistic support for constructing and deconstructing tuples, and ultimately, tuples aren't a concept we want to emphasize in wasm, in general, because it's a low-level language.

This is why I've been investigating the approach of expanding multiple-value results into one value per stack entry, an experiment which has so far had mixed results. The obvious alternative is to just have instructions that define extra results by setting local variables, which is conceptually very simple.

@ghost
Copy link
Author

ghost commented Apr 12, 2016

@sunfishcode Thank you for the feedback. The thought was that block1 might allow some data flow to be expressed as an expression even when there are some ordering constraints that would otherwise prohibit emitting an expression, and I see the llvm backend trying to reorder code to fit the expression pattern. Perhaps I can explore the potential in a wasm transform.

I am also 'disinclined with multiple-value designs that require tuple types on the value stack', which seems to be where CIL has ended up (using a struct) and perhaps by not planning ahead. But what characteristics make a multiple-value design 'require tuple types on the value stack'?

So long as it works in a functional programming style it seems possible to avoid having a tuple on the values stack. So long as the tuple can not be stored in local variables and it is not possible to destructively modify it without creating a new tuple. Do you have any other characteristics in mind?

Please consider the wip proposal at WebAssembly/wabt#66 which compiles the wasm to a very simple stack machine code with one value per value stack entry, and does validation and compilation in a single pass and does it very cleanly using the pre-order encoding. I am building it up to have a range of values operators that fit the type system and avoid a tuple/struct object, if only to demonstrate the design space. It does need to track the multiple values types during compilation but these could all be unbundled in the first pass while decoding and validating and doing the SSA conversion.

Or are you saying that you don't support multiple value support in wasm? That you don't support expressions having multiple values? I agree they are not necessary, but neither are single value expressions. But I would still like support for returning multiple values from functions even if the caller needs to immediately destructure them into local variables. But then efficient code generation is very dependant on the runtime compilers being able to optimize local variable copying, and wasm might then just use a register based instruction set and avoid expressions entirely.

I understand you are exploring post-order encoding, and it might be interesting that with the pre-order encoding this compiler knows the end of each block top-level expression just after reading it so can emit the code as it goes, and with the block expression count knows which expressions discard values so can discard after each expression. With a post-order encoding I expect the code generation will need to be delayed until the end of each block and such a compiler would be significantly more complex (this practical wasm compilers will emit code in a second pass so this is not a significant matter).

@sunfishcode
Copy link
Member

I think everyone agrees that returning multiple values from calls is valuable functionality. The question is how to do it. I think the call_multiple idea discussed in other issues has the nice property that it's immediately obvious what it does and how it interacts with other language features. Other approaches are possible, and some ideas are being developed, and the burden is on those other approaches to demonstrate their advantages over call_multiple. If an approach starts imposing tuple types, constructor/destructure operators, and new kinds of type checking, that's a significant disadvantage for it.

Also, I don't expect it will be complex to write a post-order one-pass code generator. There are limitations on how much optimization can be done this way, but it won't need to wait until the end of a block.

@ghost
Copy link
Author

ghost commented Apr 12, 2016

@sunfishcode Sorry I am getting a little lost, could you please point me to 'the call_multiple idea discussed in other issues'?

I was aware of a prior multiple-value proposal in the ml-proto, which was removed in WebAssembly/spec#53 but it extended call and had implicit tuple constructors in break and return, and destructuring. The call operator could return a tuple of values, and this bundle could be passed through if switch break and return. Discussions have suggested there would be a tuple constructor too to allow returning multiple values from an block fall-through etc. This does not appear to fit your description of not being 'an approach starts imposing tuple types, constructor/destructure operators, and new kinds of type checking'. It's only claim to being 'immediately obvious' seems to be that it echos SML.

I have revised the description of my proposal and if some area of 'how it interacts with other language feature' is not clear then please let me know and I will clarify this too?

Also do you have something concrete in mind for a 'post-order one-pass code generator.' While I can certainly imagine an code generator it is not obvious to me how it could generate code anywhere near competitive with the pre-order one-pass code generator, so if you have some insights I would really appreciate some help understanding this?

In particular, it seems to me that this interpreter would need to emulate the expression stack of the post-order decoder, that is keep a stack of expression results at runtime and could only discard at the end of a block? Also for multiple-value expressions it would need to explicitly represent them on the expression stack, and even without multiple-value expression it would already need to represent zero-values (void) on the expression stack, whereas the pre-order one pass compiler unbundles them and compiles to a stack machine.

@rossberg
Copy link
Member

The proposed operator seems rather ad-hoc and limited to specific cases. If we want support for more flexible local data flow then it would be much more useful and general to simply introduce a simple let construct.

@ghost
Copy link
Author

ghost commented Apr 13, 2016

@rossberg-chromium Yes, I have suggest let too, but it was not well received. Another thought was that perhaps special local variables could solve the problem in another more general way, and I tried to put some thoughts together in #653 but clearly it's a little beyond me at present. For example if there were local variables that could only be assigned once per block and were not live beyond the end of a block then these might be light weight temps, but probably someone with a good command of the SSA etc could come up with something much more general and useful. The multiple-value expressions and block1 support is trying to solve a problem of efficiently passing around values, but perhaps there is another solution.

@rossberg
Copy link
Member

We have talked about let a few times, and there's some sympathy, but it's clearly a post-MVP matter.

@ghost
Copy link
Author

ghost commented Apr 13, 2016

@rossberg-chromium I don't see it as a post-MVP issue, rather the core scaffolding of the language and there should be a plan and implementations from the start. The objections to let seemed to have some merit - that let complicates the decoder SSA algorithm and turns static resources into dynamic resources and I don't think anything will change here post-MVP.

The use of let to solve the block1 use case would be the following, which is already more verbose:

(mv_let ($l1 $l2 $l3)
  (exp1)
  (exp2)
  (values $l1 $l2 $l3))

It's not really needed as local variables could be used. In the following the parent accesses the values via get_local:

(block
  (mv_set_local ($i1 $i2 $i3) (exp1))
  (exp2))

The issue then is if the SSA decoder could be more efficient in passing around these values. It leads to considering a substantial design change, dropping the expressions and having all operators read/write to local variables. Doing so would also clear the unreachable type issues as this operator would not write to any local variables - similar to requiring unreachable to only be used in a context that discards the result.

@ghost ghost closed this as completed Apr 16, 2016
@qwertie
Copy link

qwertie commented Apr 17, 2016

I've been meaning to say, this feature strikes me as useful in any situation where "destructor" or "finally" logic has to run:

int Foo()
{
   HasDestructor thing();
   return thing.Computation1() + thing.Computation2();
}

Once inlining is done, this will often happen inside functions, not just at the edges. Plus, a compiler that optimizes for size it might find more ways to use block1.

Where did the discussion of let take place?

@ghost
Copy link
Author

ghost commented Apr 17, 2016

@qwertie For the discussion and objections to let see #381 I followed up with this alternative #429 and a more recent rehash that uses a fixed set of temp variables in #653

While block1 can push the applicability of the expression pattern a little, wasm already has local variables that handle the pattern. What if the code needed to return multiple values from a block, then multiple-value expression support would be need, and a type system to reason about it, and the expression pattern is still limited to single consumer patterns. At this point I think wasm needs to be great at working with local variables, and if it is then there seems to be little need for expressions anyway.

I am exploring some ideas that could still bias a register based encoding towards efficiently encoding the expression patterns. For example specialized unary opcodes that read and write to the last register written and binary opcodes that read from the last register written and the register before and write to the register before (just as if there were an expression value stack in the registers). This might just be another crazy idea, but something like this might bias wasm to efficiently encoding expressions and encourage developers to emit code in this pattern, and this might help presenting code as expressions.

@qwertie
Copy link

qwertie commented Apr 17, 2016

Thanks, but something called "let" is not discussed in any of those.

@ghost
Copy link
Author

ghost commented Apr 17, 2016

@qwertie See 'block scoped variables' aka let variables.

This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants