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.

Exceptions vs. non-local control constructs vs. unwinding #142

Closed
@RossTate

Description

@RossTate

Exceptions, non-local control constructs, and unwinding are all related but different entities. It is important to understand the distinction between these concepts, and to see that distinction consider the following C++ program:

#include <csetjmp>
int main() {
    jmp_buf env;
    int val = setjmp(env); // val is 0 on first call
    if (val==0) {
        try {
            longjmp(env, 5); // makes setjmp return 5 "instead"
        } catch (...) {
            return 1; // never executed
        }
    } else {
        return 2; // executed
    }
}

According to the C++ spec, this program returns 2, not 1. This is because although longjmp is a non-local control construct, it is not an exception. (And to clarify, the behavior of this program does not depend on how catch (...) is specified to interact with foreign exceptions because longjmp is not considered a foreign exception either.) So exceptions are distinct (but closely related to) from non-local control constructs.

I was careful to make sure this example involves no unwinding. How longjmp interacts with unwinding is not defined by the C++ spec, intentionally deferring it to the platform. (Similarly, the C++ spec does not specify how unwinding interacts with uncaught exceptions, intentionally deferring it to the platform because the behavior of single-phase vs. two-phase EH implementations differ here.) The GNU compilers do not have longjmp cause unwinding, whereas Visual Studio does by default (though you can turn it off). (Visual Studio also lets you configure whether foreign/system exceptions should cause unwinding and discusses why you would want unwinding for some circumstances and why you would not want unwinding for other circumstances.) So non-local control constructs are distinct (but closely related to) from unwinding. (To clarify, I am not advocating to add non-unwinding non-local control constructs in this proposal.)


Okay, so why do these distinctions matter? Well, just as many languages compiling to C use setjmp/longjmp to implement their own non-local control constructs (as it is the only non-local option), many languages compiling to WebAssembly will use throw/catch to implement their own non-local control constructs (again, as it is the only non-local option). We should anticipate this. Similarly, WebAssembly eventually add other non-local control constructs. We should leave room for this. unwind does both by providing a way to specifying unwinding code with no assumptions about why the stack is being unwound. It is closely related to unwinding clauses in other systems—fault, (part of) finally, unwind-protect, and (part of) dynamic-wind—all of which similarly specify/treat an unwinder as a block/function of type [] -> [].

Now, one particular non-local control construct that will need to be emulated with throw/catch is setjmp/longjmp. Because the spec gives us the option to have longjmp cause unwinding, this is mostly straightforward to do using some $longjmp exception event. But there's a problem if one translates catch (...) to catch_all: the catch_all will mistake the $longjmp event for an exception. That would make our example C++ program above incorrectly return 1. And while yes, you could hack the compilation of catch (...) to exclude the $longjmp event, that only excludes your own long jumps, failing to exclude other C/C++-as-wasm program's long jumps as well as other languages' non-local control constructs, which catch (...) seems to specifically not be intended to catch.

Hopefully this illustrates part of the rationale behind unwind, and hopefully this more concrete example better illustrates the concern about compositionality of catch_all that I had expressed more abstractly in #128.

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