-
Notifications
You must be signed in to change notification settings - Fork 725
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
wasm2c: implement the exception-handling proposal #1930
Conversation
a0bb82e
to
2c8913e
Compare
134db6b
to
8080754
Compare
8080754
to
ef1d88d
Compare
Rebased on current main branch per conversation at #1939. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
First pass.. looks good. Didn't get a chance to read through all the new CWriter methods but if the tests pass thats pretty awesome! Thanks for all the work on this.
@@ -375,6 +375,7 @@ Result NameApplier::OnCatchExpr(TryExpr*, Catch* expr) { | |||
} | |||
|
|||
Result NameApplier::OnDelegateExpr(TryExpr* expr) { | |||
PopLabel(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why was this not breaking other things?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good question... I think nothing actually checks that the "right" names get applied to things (other than the actual try-delegate test which obviously we weren't running), and this wouldn't even have been evident in the output of wasm2wat --generate-names
before there was a NameGenerator::BeginTryExpr.
@@ -170,6 +170,7 @@ struct OutputBuffer { | |||
class MemoryStream : public Stream { | |||
public: | |||
WABT_DISALLOW_COPY_AND_ASSIGN(MemoryStream); | |||
MemoryStream(MemoryStream&&) = default; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sigh... it's a little ugly.
- Pre-this change, CWriter has a single
MemoryStream func_stream_
member, which is an internal temporary stream that the whole function body gets temporarily written into (inWrite(const Func& func)
). This is used so that it can first scan the function body to learn the stack vars used (while writing the function body to the internal temporary stream), then write the stack var declarations to the real output, and then finally copy func_stream_ to the real output. - Post-this change, CWriter stores a vector of MemoryStreams (
std::vector<std::pair<std::string, MemoryStream>> func_sections_
), each tagged with the label of a caught exception (or with an empty tag if the section is unconditionally included). We do this because we don't know whether the exception is going to be rethrown until later (if we see a subsequent rethrow instruction that rethrows that caught exception), and I didn't want to emit the code to copy the exception to the stack unless it's really needed. To put a MemoryStream in a vector, it needs to have a move constructor.
We could make this simpler at the cost of copying every caught exception to the stack (whether needed or not).
* Stop execution immediately and jump back to the call to `wasm_rt_try`. | ||
* The result of `wasm_rt_try` will be the provided trap reason. | ||
* Stop execution immediately and jump back to the call to `wasm_rt_impl_try`. | ||
* The result of `wasm_rt_impl_try` will be the provided trap reason. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't wasm_rt_try
still the public interface (defined below)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sadly, no -- the old function was always called wasm_rt_impl_try
once the separation into header and impl happened (but the comment was not updated). This is still the right function for "entering" a WebAssembly function from the host and catching any traps (including the new "uncaught exception" trap). Maybe we should add something about this in the wasm2c README.
wasm_rt_try
is a new internal thing that is used by the CWriter codegen at the beginning of a try
block (and maps to WASM_RT_SETJMP in the default runtime and probably any practical runtime for this interface). It's not meant to be used by external code. Maybe we should give it a more internal-y name?
@@ -404,6 +437,16 @@ std::string CWriter::MangleMultivalueTypes(const TypeVector& types) { | |||
return result; | |||
} | |||
|
|||
// static | |||
std::string CWriter::MangleTagTypes(const TypeVector& types) { | |||
assert(types.size() >= 2); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why can't a tag have a single type (e.g. i32)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It can, but then we don't define a new struct for it -- we just use the single type directly (following the approach used for multi-value).
if (condition.empty() || func_includes_.count(condition)) { | ||
stream_->WriteData(buf->data.data(), buf->data.size()); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you explain this part?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it's part of #1930 (comment) (this is where we print out the sections of the function, and especially the sections that save caught exceptions to be rethrown later -- if there was a rethrow instruction that identifies that caught exception).
be6bfc9
to
2a6e033
Compare
And use the `wasm_rt_` prefix for the one non-static global. While reviewing #1930 I noticed these globals could/should be static.
And use the `wasm_rt_` prefix for the one non-static global. While reviewing #1930 I noticed these globals could/should be static.
And use the `wasm_rt_` prefix for the one non-static global. While reviewing #1930 I noticed these globals could/should be static.
(The imports.txt and exports.txt tests require multi-table and are disabled until reference types is supported.)
6f94106
to
1b68135
Compare
(This PR is behind #1814, #1877, and #1887, but if there is interest in landing it sooner, it could be rebased on top of the current ToT.)This PR adds support for the exception-handling proposal to wasm2c. The strategy involves setjmp at every try-catch and try-delegate, and then throwing/rethrowing uses goto (if the nearest catch is in the same function) or longjmp. There is no use of malloc/free; the wasm runtime maintains static storage for one "active exception," and one unwind target in a jmp_buf. (I couldn't think of a way to implement "zero-cost" exception handling in portable C, even with libunwind.)
This also includes code in generate-names.cc (to generate names for try blocks) and apply-names.cc (to apply the correct names to delegate targets). I think this is essentially the apply-names version of the same fix that was done to resolve-names in 6448318. (@takikawa, would you be able to take a look?)