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

merging loop constructs #261

Closed
svdb0 opened this issue Jul 11, 2015 · 8 comments
Closed

merging loop constructs #261

svdb0 opened this issue Jul 11, 2015 · 8 comments
Milestone

Comments

@svdb0
Copy link

svdb0 commented Jul 11, 2015

AstSemantics.md mentions that WebAssembly has two loop constructs: 'do_while', and 'forever'.
I note that there is no equivalent to 'while() { ... }'.
This may be because it is functionally equivalent [1] to

if (guard) {
    do {
        statements;
    } while (guard);
}

Considering that there is also a 'break' expression, both can however also be expressed with just 'forever':

/* do { statements; } while (guard) */
forever {
    statements;
    if (!guard)
        break;
}

/* while (guard) { statements; } */
forever {
    if (!guard)
        break;
    statements;
}

I would suggest either removing 'do_while', or adding a 'while', depending on whether the design criteria favour either frugality and elegance, or ease of reading of the textual form. In the latter case, maybe there should be a construct for 'for' loops as well.

Either way, having 'do_while', but not 'while' seems inconsistent.

In case 'do_while' is eliminated, I would also suggest renaming 'forever' to just 'loop'; it is shorter, and in the presence of 'break', 'forever' is not really forever.

One could go even further, and combine 'block' and 'forever'.
Either by substituting 'block' by:

forever {
    statements;
    break;
}

Or, by substituting 'forever' by:

block {
    statements;
    continue;
}

The latter gets rid of another symmetry: right now 'break' can be used to jump to the end of a loop or block, while 'continue' can only be used to jump to the beginning of a loop (and not a block).

[1] Note however that the guard expression is duplicated here.

@svdb0
Copy link
Author

svdb0 commented Jul 11, 2015

Afterthought: even in the current situations, it might be useful to allow 'continue' to jump to the beginning of a 'block'. Right now, a goto statement to an earlier line would have to be translated to something like:

do {
    statements1;
    if (...)
        continue;
    statements2;
} while (false);

@sunfishcode
Copy link
Member

The reason for having do_while and not while or even for is that WebAssembly is meant to be low-level and easy to translate into efficient machine code. do_while is essentially WebAssembly's "conditional branch backward" instruction. Compilers typically translate while loops into guarded do_while-style constructs before optimizing them anyway, so this form is pretty natural at this level.

continue in a do_while still tests the exit condition, so your last example would be better translated as a forever with a break at the bottom. If that becomes a popular pattern, it might make sense to add a new loop type for it. I don't know what we'd call it though; perhaps continue_only_loop?

(There is also a question of whether forever should just be do_while with true, but that's more about aesthetics.)

@sunfishcode
Copy link
Member

Thinking about this a little more, if we want minimalism, one could argue that this hypothetical continue_only_loop would be the loop construct we'd want. This is very similar to your idea above allowing regular block to have continue, though it's nice to identify loops up front. Assuming we'd then name it just loop, we could have:

do {
  body();
} while (condition());

look like

loop {
  body();
  if (condition()) continue;
}

and

forever {
  body();
}

look like

loop {
  body();
  continue;
}

Note that without a continue, the bottom of the loop construct would exit the loop. So this loop is just like block except that it is permitted to have continue. And:

  • Unconditional back branches would always be continue.
  • Conditional back branches would always be if (x) continue.

It's worth considering.

@MI3Guy
Copy link

MI3Guy commented Jul 15, 2015

The problem with just having a loop construct is it would make encoding a loop with continue in the body much more difficult. Continue would skip past the condition check and go back to the start of the loop.

@sunfishcode
Copy link
Member

Yeah, we'd have to do an extra nesting level:

loop {
  L0: {
    body();
    ...
    break L0;
    ...
  }
  if (condition()) continue;
}

(Note that I'm not presently proposing we do this. I'm just exploring the idea.) It's a little visually cluttered, but the translation to branching is in relief.

@sunfishcode
Copy link
Member

Taking this idea further, we could do the same thing for forward branches. I think this path tends toward the following control opcode set:

  • block
  • loop (fallthrough, as discussed above)
  • break (aka branch-forward)
  • continue (aka branch-backward)
  • conditional break (if (x) break;)
  • conditional continue (if (x) continue;)
  • switch (as is)

Then this:

if (x) {
  body();
}

might look like this:

{
  br !x, L0;
  body();
} L0;

and this:

if (x) {
  body();
} else {
  other();
}

might look like this:

{
  {
    br !x, L0;
    body();
    jmp L1;
  } L0;
  other();
} L1;

Unconditional break/continue could just be conditional break/continue with a constant, but since unconditional branches is typically a different instruction, it's nice to make the distinction. Also, block and loop could be the same thing if we permitted block to have continue, but it's nice to identify loops up front.

Advantages of this approach over the status quo include:

  • Each opcode has one control purpose.
  • Each control purpose has one opcode.
  • It "feels" lower-level, which may seem frivolous, but may help people understand the system better.
  • It's still well-structured (with associated advantages) and it still has no random-access labels (labels can still be encoded as nesting levels rather than as program offsets).
  • It aligns well with Steady On: An alternative to Tilt #44.

Disadvantages include:

  • Branching has a less human-friendly appearance than if and if-else
  • It requires more nodes for common patterns. Although, macros would compress this reasonably well.

@flagxor
Copy link
Member

flagxor commented Sep 22, 2015

As I mentioned at the meeting, FWIW this has some resemblance to the 4 core operations ANS Forth defines its other structured control flow in terms of:
IF --- (branch ahead if false)
AHEAD --- (unconditional branch ahead)
UNTIL --- (branch backward if false)
AGAIN --- (unconditional branch backward)
https://www.complang.tuwien.ac.at/forth/gforth/Docs-html/Arbitrary-control-structures.html
This at least demonstates these are strictly structured and might be pretty easy for language implementers to work with. Implementations don't get much simpler than Forth :-)

@sunfishcode
Copy link
Member

With #427 merged, there is now only one loop construct.

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

No branches or pull requests

4 participants