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

canonical abi should have a way to terminate the program #47

Open
kulakowski-wasm opened this issue Jun 15, 2022 · 4 comments
Open

canonical abi should have a way to terminate the program #47

kulakowski-wasm opened this issue Jun 15, 2022 · 4 comments
Milestone

Comments

@kulakowski-wasm
Copy link
Contributor

Eventually, this may be expressible via stack unwinding.

@lukewagner
Copy link
Member

Yes, something in this space makes sense. I'm assuming we want "termination" to not have the "something bad happened" connotation of trapping so that modules could use it regularly without hosts presenting the executions as errors to the developer. From a composability point of view (in which there are multiple components linked together, and one does a terminate), there are a few interesting options. E.g.: if component A calls component B and B "terminates":

  1. If A called B expecting a return value T, does A get a T?
    a. Does the terminate call get to provide that T value?
    b. Or is T required to be a variant, with "termination" corresponding to one of the cases?
  2. Or does the termination somehow terminate B also (like a trap)?
    a. Can anything (short of a "blast zone") stop the termination, or does it "bubble up" all the way to the host?

The least drastic option seems like 1a (which would almost correspond to wasm exception handling, except that wasm exception handling would allow the core code on the stack to catch the termination with catch_all, hence using stack switching instead, as you suggest). I have the impression a solution to this could be integrated with the async support (which is also built on stack switching and can communicate return values deep in stacks), but I wanted to check first if we're in the same rough design space.

@kulakowski-wasm
Copy link
Contributor Author

We're definitely in the same realm of design space. And I'm also thinking from the perspectives of "I have some preexisting C code that calls exit(3)" and "I have some code that was written from scratch to be in a component".

One constraint on the semantics we want, I think: It should be possible for an ordinary return from the entry point, and for a call to terminate deep in a call stack, to be externally the same. This is particularly relevant to the first use case I mentioned.

We may want to allow termination to also look differently from the outside via some option or other mechanism, too.

One reason that something like unwinding is appealing is that we may want to run atexit-style hooks, and so we need a place to return control to that is just short of actually exiting altogether. So unwinding just to that point, running some code, and then yielding control back to the host. Rather than just stopping execution altogether.

I also suspect that the answer to this will come from the task and async perspective.

@lukewagner
Copy link
Member

atexit() and making sure we're able to run user-defined handlers after the terminate import is called is a great callout.

And, just to confirm: is it our goal (or the ambient assumption of legacy code we need to uphold) to not run destructors when terminating (but do run atexit functions)?

And then, as a corollary of that: if we assume that the catch_all in the current core exception-handling proposal will be emitted by toolchains to run destructors during unwinding (which I think is still the intention, but anyone feel free to check me on that), so that throwing an exception always calls destructors (and, in general, properly "unwinds" the stack), then we can't simply say that toolchains should implement terminate themselves using core wasm EH; we need something outside of EH, yes?

@kulakowski-wasm
Copy link
Contributor Author

Yes and also:

There's a couple sets of dtors to think about. One is for objects on the stack. The other is for globals, including TLS globals. It's worth keeping in mind, because I can imagine that unwinding per se is concerned with the first, and a libc-level runtime (the thing we want terminate to yield control to, just above the host) with the latter.

Note also that https://en.cppreference.com/w/cpp/utility/program/quick_exit (also in C11) doesn't run TLS dtors. exit does. There's also handlers that run only at exit time but not quick_exit time. Neither exit nor quick_exit run stack object dtors.

There's also state that is cleaned up in exit but not quick_exit, like stdio stream flushing, which aren't necessarily object dtors but can handled by the same runtime mechanisms.

also cf rust-lang/rust#83994 for some of these things coming up in the rust stdlib context recently which may point at what kinds of things legacy code does (correctly or not).

@ricochet ricochet added this to the Preview 3 milestone Aug 22, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants