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

Forwards-compatibility with low-level primitives #108

Open
@RossTate

Description

@RossTate

In order to discuss forwards-compatibility, it's probably best to first get us all on the same page as to how the primitives in #105 would likely express the constructs in this proposal.

The first thing we need is a "universally understood" mark: mark try-catch-exnref : [exnref] -> unreachable.

Next, translate try ([ti*] -> [to*]) instr* catch instr* end to the following:

escape $hatch {
    mark try-catch-exnref { // [exnref] -> unreachable
        escape-to $hatch // uses the exnref on the stack
    } within {
        instr* // body of try
    }
} hatch [exnref] {
    instr* // body of catch
} [to*] // output type

Then translate throw $event to the following:

exnref.new $event // put exnref onto stack
call $throw_exnref

where

func throw_exnref : [exnref] -> unreachable {
    stack.walk {
        stack.next-mark try-catch-exnref {
            stack.exec-mark // passing exnref on stack
        }
    }
}

and similarly translate rethrow to simply call $throw_exnref.

br_on_exn remains as is, since that's more about reference types than about stacks or control.

One takeaway from this translation is that, theoretically speaking, this proposal is already forwards-compatible with the primitives in #105. However, practically speaking, we care about more than just getting programs to run; we want programs to run correctly, including programs that call other programs or are called by other programs. This is where the importance of stack conventions comes in.

As an example, suppose module A is compiled in the style of #105 whereas module B is compiled in the style of this proposal. If module A calls B, providing B with a callback into A, and B calls that callback that happens to throw an exception (in A), then we have a situation where B's stack frames are sandwiched between A's throw and (presumably) A's catch but where A's exceptions are not implemented using try-catch. Module A would like to let module B clean up its stack, but module A has its own unwinding state it wants to maintain (e.g. Python building the stack trace as it unwinds the stack). How should module A proceed?

In the current proposal, unwinding code always usurps control and then typically rethrows control to the next try-catch-exnref mark. That is, it assumes try-catch-exnref is the sole way to unwind the stack. If, on the other hand, the current proposal were revised to separate unwinding code (e.g. on_unwind instr* do *instr* end) from exception-handling code (still try/catch), then #105 could translate on_unwind to a "universally understood" mark unwinder : [] -> [] and module A's unwinding code could choose to execute unwinder marks it sees and ignore any try-catch-exnref marks. As an added benefit, this would work regardless of how B were compiled (assuming B chose to abide by the unwinder convention), so module A would not need to adjust its implementation strategy to account for B's specific choice of implementation strategy.

Hopefully that gives a since of where the compatibility problem really lies and how the current proposal might be changed to help with forwards-compatibility. It all comes down to what kind of conventions we want to support. More conventions means better compatibility between newer wasm programs and older wasm programs. Fewer conventions means fewer changes, including even no changes. It's possible that the on_unwind separation above is the sweet spot or that the sweet spot is to simply leave the proposal as is. Regardless of what we decide to do, the current proposal is optimized for a particularly common kind of exception semantics, and I think we should and can maintain that optimization for a common case.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions