stack-switching icon indicating copy to clipboard operation
stack-switching copied to clipboard

We also need resume_throwref

Open fgmccabe opened this issue 8 months ago • 13 comments

Wasmfx has a way of sending an exception to a suspended coroutine: resume_throw.

However, the current EH proposal has two ways of throwing exceptions: throw and throw_ref.

We need to add the equivalent instruction to the stack switching proposal.

fgmccabe avatar Mar 25 '25 16:03 fgmccabe

Interesting point. We could do that, but I don't think it's really necessary, as both forms currently serve very different purposes: throw_ref rethrows a caught exception, it is the primitive to implement guarded handlers or finally-clauses; resume_throw is the primitive to effectively abort a continuation. Maybe I'm just thinking too narrowly, but I can't think of a use case or language mechanism where a rethrow would be turned into cancellation of another continuation.

rossberg avatar Mar 25 '25 17:03 rossberg

Think about the scenario where an exception in one coroutine needs to be propagated to another coroutine -- mediated via teh green thread scheduler.

fgmccabe avatar Mar 25 '25 18:03 fgmccabe

An additional note: a strictly symmetric design (dare I say bag'o stacks?) would not need to special case exception handling. IMO, whenever a couroutine wakes up from being suspended it is likely necessary to check on why it was woken up.

fgmccabe avatar Mar 25 '25 18:03 fgmccabe

Hm, if the coroutines implement green threads, wouldn't that correspond to injecting asynchronous exceptions? When would that be needed or desirable?

rossberg avatar Mar 25 '25 19:03 rossberg

It's hard to anticipate the use cases, so I am not sure if green threads would need this functionality. But from the view of EH and stack switching composability, it makes sense to me that if code can catch and rethrow an unknown exnref, it should be able to do so across a stack boundary. I'm not sure why we'd limit it to just throwing on another stack to just statically-known tags.

titzer avatar Mar 25 '25 20:03 titzer

Suppose a green thread needs to have an exception ... as part of the application logic, not part of the protocol (say). Where does the exception 'come from'? Not the scheduler (by definition, it's an application defined exception). One source of exceptions is the coroutine running to produce the value the green thread is waiting for. That coroutine has to tell the scheduler 'throw this' to my consumer.

Now, it is totally possible to do all of this in 'user space'. In fact, I would prefer that. However, we have exceptions in wasm. If you want to use exceptions to model this kind of pattern, then you need to 'rethrow' an exception you got from elsewhere.

Having resume_throw is useful, primarily imo so that the scheduler can transmit scheduling level exceptions (such as to terminate yourself due to no more resources).

Thinking more, we would also likely need switch_throw and switch_throw_ref. For similar reasons.

fgmccabe avatar Mar 25 '25 20:03 fgmccabe

It's a bit of a stretch, but it would be possible for a producer to implement delimited continuations on top of symmetric stack switching, for example if they wanted to implement some custom handler search procedure. In that case an exception propagating up the userspace stack would have to be manually propagated between the underlying continuations using something like switch_throw_ref.

tlively avatar Mar 25 '25 20:03 tlively

Thinking more, we would also likely need switch_throw and switch_throw_ref. For similar reasons.

An alternative to do switch_throw would be to do a resume_throw + try_table + suspend + throw, but of course that requires the cooperation of the handler, so switch_throw is more expressive in the same way switch is more expressive than suspend + resume.

The problem with switch_throw is that it has nowhere to put the return continuation besides the exception payload. I'm having trouble thinking of any source language feature or pattern that could naturally be lowered to anything involving continuations in exception payloads, but perhaps I am also thinking too narrowly :)

tlively avatar Mar 25 '25 20:03 tlively

At the moment, the return cont type is baked in to the forward cont type:

switch $ct1 $e : [t1* (ref null $ct1)] -> [t2*]

but, if switch took and generated two values:

switch $ct1 $e : (ref null $ct1) [t1*] -> (ref null $ctx) [t2*]

then switch_throw would be pretty straightforward:

switch_throw $ct1 $e $x : (ref null $ct1) [te*] -> (ref null $ctx) [t2*]

This has two tags: one for the prompt label and one for the exception, and the exception tag $x would govern te*, and (probably) $ct1 would not be constrained so much (we are not sending any values to $ct1).

(I may be mangling the notation a bit here)

Just noodling ...

fgmccabe avatar Mar 25 '25 22:03 fgmccabe

@tlively The rationale for switch_throw_ref is the same as for resume_throw_ref: to support application patterns involving coroutines communicating with each other. (In general, throwing an exception is simply a form of 'returning a result'). However, it is likely that the rationale for switch_throw is more limited; but I suggest it as a way of completing the picture.

fgmccabe avatar Mar 27 '25 17:03 fgmccabe

@fgmccabe, do you agree that switch_throw and switch_throw_ref would have to put the return continuation (the one used to return to the original stack) in the thrown exception payload? Can you think of a more concrete use case for that?

tlively avatar Mar 27 '25 20:03 tlively

@tlively, Well, actually, I was trying to see if a more 'separated' approach could be made to work. In my original formulation for bag of stacks, the return continuation was a separate argument to switch. I did not fold it into the type of the continuation. I.e., switch was an inherently multi-valued instruction: switch results in two values: the returns from being switched back to and the new forward continuation.

If you use that pattern, then, in the case of switch_throw, there would be two values being sent and two values coming back: the exception/tag and the continuation and the return values and new continuation respectively.

But, if we are forced to fold the forward/return continuations into the payload, then you would likely need to put the reverse continuation in the exception. And, yes, this is bizzarre. However, this is, IMO, an artifact of how we are specifying the instructions and does not come from actual requirements.

fgmccabe avatar Mar 27 '25 20:03 fgmccabe

If you use that pattern, then, in the case of switch_throw, there would be two values being sent and two values coming back: the exception/tag and the continuation and the return values and new continuation respectively.

But when you switch to the target stack, how would it receive the return continuation? It can't receive the values normally because an exception is being thrown. Even if you used this pattern for the types, you would still have to put the return continuation in the exception payload. As you say, this is bizarre :)

tlively avatar Mar 27 '25 20:03 tlively

After thinking more, I retract the suggestion to add switch_throw. Since propagating an exception in this way should really be part of an application's coroutine protocol. (Note that bag o stacks required all exceptions to be propagated via the application protocol.) This reasoning also weakens the justification for resume_throw_ref; but I will, for now, leave that suggestion on the table.

fgmccabe avatar Apr 01 '25 18:04 fgmccabe