Skip to content
This repository was archived by the owner on Apr 25, 2025. It is now read-only.

Describe some Level 1+N features #42

Merged
merged 9 commits into from
Apr 28, 2018
Merged

Describe some Level 1+N features #42

merged 9 commits into from
Apr 28, 2018

Conversation

eholk
Copy link
Contributor

@eholk eholk commented Feb 12, 2018

This provides some rough detail about possible future extensions, and more detail about the leveling approach.

Feel free to add new features! These are still meant to be in flux, but the goal is to try to capture what we might want to do in the future so we can stay future proof.

Copy link
Contributor

@KarlSchimpf KarlSchimpf left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm


This proposal allows exception handlers to return a value to the site where the
exception was thrown. Resumable exceptions enable or are equivalent to a number
of advanced control flow features such as generators, coroutines, delimited

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a bold claim if you don't know the specifics. For example, the example below shows that the code that can resume (in the catch) is closely tied to the resumable code (the try). In the general case of coroutines these two can be in completely unrelated pieces of code, and there can even be multiple pieces of code that can resume the same coroutines. It be nice to at least hint at how these can be supported.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@aardappel, the extension of exception handling with resumption is equivalent to what is known in recent research as "effect handlers". Turns out that they are powerful enough to express almost any side effect you can think of. Essentially, they provide a structured form of delimited continuations. If you're brave enough and don't mind exploiting a little bit of UB, they can even be implemented on top of C.

To express coroutines, you would define a Yield effect (aka resumable exception), and the handler would be the scheduler loop that when thrown to stores the current exception/continuation in a queue and instead resumes the next one. Underneath, this basically means switching stacks.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@aardappel, I just pushed another change that shows how to take one of the effect handling examples on eff-lang.org and translates it into a high-level pseudocode that matches the new resumable exceptions instructions. I think you raise a good point, so I will try and include examples for the rest of the features I listed before landing the PR.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rossberg Thanks for the "Implementing Algebraic Effects in C" link, the C code in there clears up a lot of confusion. Having to resume stack copies in the same location as they were created sound problematic, but I am sure wasm implementations can improve on this and omit these "fragments", since they have more knowledge of the stack layout.

@eholk Thanks for the co-routine example, that clears up my confusion on whether a resumable exception can escape its catch-block.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@aardappel, no problem! It was a good exercise for me to figure out how to represent all of these control structures at once. I'm still trying to get shift/reset delimited continuations working, but once I do I'll upload the change and be ready to merge this PR.

One thing I realized about having escaping resumable exceptions is that we'll probably need to parameterize except_ref by its resumption type.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, except_ref would need to track the result type of the try that created it, not the type of the resumption -- that is only known once you have extracted a tag. More concretely, typing rules would be roughly like this:

(throw $e) : [t1*] -> [t2*]
if $e : [t1*] -> [t2*]

(try[t3*] A catch $e* B) : [] -> [t3*]
if A : [] -> [t3*]
   B : [(caught t3*)] -> [t3*]

(rethrow) : [t4* (caught t3*)] -> [t5*]

(handle[t6*] $e A else B) : [(caught t3*)] -> [t6*]
if $e : [t1*] -> [t2*]
   A : [t1* (resumption t2* t3*)] -> [t6*]
   B : [] -> [t6*]

(resume) : [t2* (resumption t2* t3*)] -> [t3*]

(Renaming except_ref to caught here.)

In practice, we would probably want to annotate exception types and some of the instructions with a flag saying whether they are resumable. Then we could also distinguish a plain type (caught) from the variant (caught resumable t*), so that it doesn't complicate typing in the current exception proposal.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking (resume) would be typed kind of like br, so it would be [t2* (resumption t2*)] -> unreachable. Basically, once you resume an exception, the values you passed to it would return out of the throw instruction and then control flow would pick up there as if the exception were never thrown, never returning back to the resume instruction. How do you end up returning from a resume?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That’s the magic of delimited continuations. ;) The resume returns when the try that caught that exception terminates — you don’t return to the context of the try (which might already have been exited before you even resume) but that of the resume. That’s what makes it delimited, because you only capture the continuation up to where you entered the try. If you actually were to return to the previous context of the try then you would essentially have captured the full continuation.

What happens may be a bit more intuitive if you think about it in terms of how stacks are switched:

  1. when you enter a try, a new stack is created and execution switches to that stack, remembering the previous stack
  2. when the stack is completed (i.e., the try terminates regularly), then this stack is no longer needed and execution restores the previous stack
  3. when the try body throws, then execution also switches back to the previous stack, while the try's own stack gets captured in the caught exception package (as the representation of the resumption)
  4. when you resume, execution again switches back to the captured stack, but from whatever the current stack is; i.e., the current stack is what is remembered now as the previous one that you return to in case of (2.)

(In many cases the creation of an additional stack can be optimised away, in particular for a try where the caught exception can be shown to not escape the catch.)

Note that your own example of coroutines very much assumes that resume returns. ;) Though the assignment to c1/c2 should be removed now that you moved the assignments into the handler and the tries do not actually have a result. But the previous version demonstrated exactly how the result type of the resume is the same as the result type of the associated try.


One downside of this form is that it does not directly support running different
code for different exception types. This can still be accomplished, however, by
dyanmically inspecting the exception inside the catch block.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem is that each of the exception indices may have a different type. Say we had:

try:
   ...
catch 0, 1:
  ...
end

and the exception at index 0 had type [i32] while the exception at index 1 had type [f64 f64]. We wouldn't know what values to push onto the stack since the same code is run for each exception. In other words, this doesn't work for the combined catch-and-unpack operation.

Copy link
Member

@rossberg rossberg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!

// Start the consumer
c2 = try:
consume()
catch e: yeild():
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yield

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

c1 = try:
produce()
catch e: yield():
e
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: May be clearer if the assignment to c1 occurs inside here (same below).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea. Done.

end
```

Note that the `catch exteption_index` and `catch` forms of the catch clause should be given different opcodes.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo: exception_index

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.


One downside of this form is that it does not directly support running different
code for different exception types. This can still be accomplished, however, by
dyanmically inspecting the exception inside the catch block.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo: dynamically

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@eholk
Copy link
Contributor Author

eholk commented Apr 13, 2018

I finally worked out how to finish off the delimited continuation example, so I'm ready to land this assuming it looks okay to everyone else.

@eholk eholk merged commit af22f21 into WebAssembly:master Apr 28, 2018
ioannad pushed a commit to ioannad/exception-handling that referenced this pull request Jun 6, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants