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

Typed continuations: resume instructions #6083

Merged
merged 27 commits into from
Jan 11, 2024
Merged

Conversation

frank-emrich
Copy link
Contributor

This PR is part of a series that adds basic support for the typed continuations proposal.

This particular PR adds support for the resume instruction. The most notable missing feature is validation, which is not implemented, yet.

@frank-emrich
Copy link
Contributor Author

frank-emrich commented Nov 5, 2023

There are currently a few FIXMEs which are basically questions of mine, one of them currently causing my new lit test to fail (see my inline Github comment on that particular FIXME).

// We could also store this information in the Resume node itself, but
// that would mean storing the signature of all the Tags that we have a
// handle clause for.
func(name, Type::none);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The issue described here is what's causing the lit test suite to fail:
Enabling BINARYEN_PASS_DEBUG means that validateBinaryenIR eventually re-finalises each block by calling Block::finalize(). This can cause situations where the types collected by BranchSeeker fully determine the new type given to the block, ignoring the type annotation on it. This means that the code snippet here, where Type::none is passed as a placeholder for the real type causes problems, because Block::finalize() will obtain this from BranchSeeker and uses it as the overall type of the block. This then makes validateBinaryenIR fail.

This failure seems to be a relatively recent change, since the test suite was passing a few weeks ago when I did the majority of the work on this PR... But I guess it just shows that my "solution" of passing Type::none here was never quite right to begin with.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cc @aheejin, I guess the new EH design will also run into the same problem here: the types of values carried on the branches from the catch_table instruction will depend on the tags on that instruction, so we will need the module here to look up the types of those tags.

@frank-emrich, I don't think we have a choice but to plumb the module through to here sooner or later, and probably sooner, since I can't think of a good workaround 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've worked on this for a bit, which mostly involved changing all use sites of BranchSeeker, and have a question:

Currently, BranchSeeker is mostly used by calling its has and count functions. These don't care about the types sent to the branches types at all. In fact, there are few uses of BranchSeeker that do inspect the types field afterwards. And we only need access to the Module in order to correctly calculate the types sent to the branch. That means we would have to change Branchseeker::count and BranchSeeker::has to take a Module parameter, even though the types field is ignored.

This suggests that it may be useful to change the existing BranchSeeker such that it continues offering has and count with the existing signatures but remove the types field. This could be implemented entirely using operateOnScopeNameUses, no type (or module) information needed.

We then define a new BranchTypeSeeker that behaves more like the old BranchSeeker, which uses operateOnScopeNameUsesAndSentTypes, does accumulate the types sent to the branch, and needs access to the Module to do so to implement typed continuations and the new style EH.

This question came up when changing the signature of the various finalize methods of Block: The existing finalize() must definitely take a Module in the future, because it uses BranchSeeker to actually inspect the types sent to branches.
However, Block::finalize(Type type_) andBlock::finalize(Type type_, Breakability breakability) only use BranchUtils::BranchSeeker::has and don't care about the types sent to the block. Thus, if we go through with the splitting of BranchSeeker mentioned above, we could avoid also needing to add a Module parameter to these two.

I've mostly gone through with changing the signatures of all three Block::finalize functions now, so I don't care much which option we take here, but it felt a bit unfortunate to change all these use sites of Branchseeker::count and BranchSeeker::has, even though they don't actually care about the extra typing information.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This refactoring sounds good to me.

One option might be for the finalize methods that would need to take a Module to instead conservatively keep the type unchanged, then have special logic in the ReFinalize that takes the module into account to do better refinalization.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have created a separate PR with the Module parameter plumbing: #6096. I will rebase this PR once the other one has landed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After the discussion in #6096 I will now implement storing the sent types directly in Resume nodes, instead of using the Module here to determine them.

The most straightforward implementation is an ArenaVector<Type> whose i-th entry contains the type sent to the i-th block (i.e, curr->handlerBlocks[i]). Let's assume this vector is called sentTypes.

There is one different issue here that affects the overall implementation of operateOnScopeNameUsesAndSentTypes: Since it is implemented using operateOnScopeNameUses, a Resume instruction with n handler clauses results in n calls to the lambda defined in operateOnScopeNameUsesAndSentTypes, with the name of the corresponding handler block as the name argument each time. However, in the lambda we then have would run code like this:

  ...
  } else if (auto* res = expr->dynCast<Resume>()) {
    for (usize i = 0; < res->handlerBlocks.size(); i++) {
      auto handlerBlock = res->handlerBlocks[i];
      if (handlerBlock == name) {
        func(name, curr->sentTypes[i]);
      }
    }
  } else { ...

In total, this is quadratic in the number of handler clauses.

Which of the following solutions would you prefer:

  1. operateOnScopeNameUses takes a lambda whose parameter is a vector of names. We would effectively call the lambda "in bulk" for those node types that have more than one scope name use. But this would require creating singleton vector for all the other node types, so this doesn't seem efficient.
  2. We could pass some kind of index to the lambda, indicating that the currently passed Name is the i-th block name used by the particular node. But that doesn't seem great either, the index would be meaningless for all node types other than Resume.
  3. sentTypes could be a map from block names to types instead of just a vector of types. That seems relatively clean, but uses more space, and it would need to be integrated into the logic that updates tag names in case a naming section is encountered in a binary.
  4. We may implement operateOnScopeNameUsesAndSentValues without using operateOnScopeNameUses at all: Instead, it could just be a visitor on Expressions. In the case for Resume nodes, we would then process all the blocks at once and invoke func for each handler block.

I'm personally in favor of the last option: After all, the lambda passed to operateOnScopeNameUses in operateOnScopeNameUsesAndSentValues already disambiguates the different node types, operateOnScopeNameUses is basically just used to pre-filter the nodes and extract the name of the used block.

Note that this entire discussion equally applies to Try nodes once the updated EH proposal is implemented.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

4 makes sense to me as well.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How many handler clauses do you expect to have with resume? With try_table, at least LLVM at the moment will generate instructions with only one clause (it is either for the C++ tag/label or the catch_all label. We don't generate both at the same time. But even if we do it's gonna be only two), so we are not really gonna be affected by the quadratic loop. If what you would have in average is less than 5, I'm not sure if this is something we should worry about?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've now re-implemented operateOnScopeNameUsesAndSentTypes using a Visitor.

@aheejin There are some compilation schemes where you may create resume instructions with tag clauses that handle more or less every single tag defined. However, even then I'd surprised to see more than a dozen or two. However, given that there are source languages with effect handlers that would like to target our instructions directly, it's basically up to what people do in those source languages. Having the quadratic behavior here is probably fine unless someone translates some crazy generated code using effect handlers into wasm.

But given that the solution using a visitor was relatively simple to implement, I'd say let's just err on the side of caution.

src/ir/branch-utils.h Outdated Show resolved Hide resolved
src/ir/effects.h Outdated Show resolved Hide resolved
src/wasm/wasm-binary.cpp Outdated Show resolved Hide resolved
// We could also store this information in the Resume node itself, but
// that would mean storing the signature of all the Tags that we have a
// handle clause for.
func(name, Type::none);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This refactoring sounds good to me.

One option might be for the finalize methods that would need to take a Module to instead conservatively keep the type unchanged, then have special logic in the ReFinalize that takes the module into account to do better refinalization.

@frank-emrich
Copy link
Contributor Author

I've now addressed the first round of comments.

One thing that I've noticed is that updated_lit_checks.py cannot distinguish between tag definitions (tag $mytag ...) in the tags section of a module and handler clauses appearing in resume instructions: (resume $ct (tag $mytag $myblock) ...). As result, the script messes up the CHECK clauses. As temporary workaround, I've removed the "Assertions have been generated by update_lit_checks.py ..." header line from my new test file and removed the erroneously added CHECK lines manually.

I've had a quick look at the logic in updated_lit_checks.py. In general, we could distinguish the two kinds of (tag ...) expressions based on whether or not something like a (resume or (func appears beforehand. Anything indicating that we're not in the tag section any more should suffice. I've tried doing this with a negative lookahead before tag, but Python only accepts them if they have a constant length, which doesn't quite work for what I tried to do.

@tlively
Copy link
Member

tlively commented Nov 15, 2023

I think the update_lit_checks.py issue will be solved if you put the (tag ...) clause on the same line as the (resume ... rather than on its own line.

@frank-emrich
Copy link
Contributor Author

I think the update_lit_checks.py issue will be solved if you put the (tag ...) clause on the same line as the (resume ... rather than on its own line.

That doesn't seem to have worked if I just move it on its own line in the input function. Did you mean changing the printer such that it goes on its own line in the output? That seems tricky, because there may be many such clauses, so at some point they have to go on their own lines, rather than unconditionally keeping them all on the same line as the resume.

@tlively
Copy link
Member

tlively commented Nov 27, 2023

Yes, I think you'd have to change the printer to put the tag clauses on the same line as (resume as well. Even though this does not visually scale to large numbers of tag clauses, it is at least consistent with what we do with e.g. function parameters and results, br_table labels, etc. In general our printer never tries to be clever and break up long lines.

@frank-emrich
Copy link
Contributor Author

I've changed the printing now so that all the (tag ...) clauses appear on the same line as the resume. That did indeed fix the issue with update_lit_checks.py.

In general, things should be ready now for you to have a look at.

aheejin added a commit to aheejin/binaryen that referenced this pull request Dec 14, 2023
This adds basic support for the new instructions in the new EH proposal
passed at the Oct CG hybrid CG meeting:
https://github.com/WebAssembly/meetings/blob/main/main/2023/CG-10.md
https://github.com/WebAssembly/exception-handling/blob/main/proposals/exception-handling/Exceptions.md

This mainly adds two instructions: `try_table` and `throw_ref`. This is
the bare minimum required to read and write text and binary format, and
does not include analyses or optimizations. (It includes some analysis
required for validation of existing instructions.) Validation for
the new instructions is not yet included.

`try_table` faces the same problem with the `resume` instruction in
 WebAssembly#6083 that without the module-level tag info, we are unable to know the
'sent types' of `try_table`. This solves it with a similar approach
taken in WebAssembly#6083: this adds `Module*` parameter to `finalize` methods,
which defaults to `nullptr` when not given. The `Module*` parameter is
given when called from the binary and text parser, and we cache those
tag types in `sentTypes` array within `TryTable` class. In later
optimization passes, as long as they don't touch tags, it is fine to
call `finalize` without the `Module*`. Refer to
WebAssembly#6083 (comment)
and WebAssembly#6096 for related discussions when `resume` was added.
Copy link
Member

@tlively tlively left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally looks good, just a few final comments.

func(name, br->getSentType());
} else {
assert(expr->is<Try>() || expr->is<Rethrow>()); // delegate or rethrow
struct SentTypesVisitor : public Visitor<SentTypesVisitor> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just in the interest of minimizing the change, can this go back to being an if-else chain?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

src/wasm.h Outdated Show resolved Hide resolved
@@ -3913,6 +3914,7 @@ class ModuleRunnerBase : public ExpressionRunner<SubType> {
multiValues.pop_back();
return ret;
}
Flow visitResume(Resume* curr) { return Flow(NONCONSTANT_FLOW); }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should probably be WASM_UNREACHABLE("unimplemented"). NONCONSTANT_FLOW is only used in the ExpressionRunner up above.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

// on null.
parent.implicitTrap = true;

if (parent.features.hasExceptionHandling() && parent.tryDepth == 0) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this because we're modeling suspending as throwing? Probably worth a comment if so.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was just inspired by the following happening at the end of visitCall here :

// When EH is enabled, any call can throw.
if (parent.features.hasExceptionHandling() && parent.tryDepth == 0) {
    parent.throws_ = true;
}

I'm not sure if it's really necessary for Resume. @aheejin would you mind having a look?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can a resume instruction throw an exception? If not, we can remove this. (Calls can, which is why they have it.)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, if the resumed continuation throws, it will bubble out from the resume instruction, just like an exception from a called function would.

@@ -331,6 +331,8 @@ struct SubtypingDiscoverer : public OverriddenVisitor<SubType> {
void visitStringIterMove(StringIterMove* curr) {}
void visitStringSliceWTF(StringSliceWTF* curr) {}
void visitStringSliceIter(StringSliceIter* curr) {}

void visitResume(Resume* curr) { WASM_UNREACHABLE("not implemented"); }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should just note that the body is a subtype of the resume's type.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think It's slightly more complicated than that: We may also need to report subtyping relations induced by values being sent to handler blocks: If a resume instruction has a (tag $t $h) clause, then the n param types of $t must match the first n result types of block $h. I guess this means that we would have to record these relationships here and skipped it so far.

src/passes/Print.cpp Outdated Show resolved Hide resolved
Copy link
Member

@aheejin aheejin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking good; mostly nits. I appreciate your discussions on the tag types in #6083 (comment) and #6096, which I referred to before I submitted #6181.

src/wasm.h Outdated Show resolved Hide resolved
Comment on lines 2737 to 2749
o << ' ';
printHeapType(curr->contType);

// We deliberate keep all (tag ...) clauses on the same line as the resume
// itself to work around a quirk in update_lit_checks.py
for (size_t i = 0; i < curr->handlerTags.size(); i++) {
o << maybeSpace << '(';
printMedium(o, "tag ");
printName(curr->handlerTags[i], o);
o << " ";
printName(curr->handlerBlocks[i], o);
o << ")";
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 on moving this to PrintExpressionContents::visitResume. PrintSExpression seems to be mainly for child expressions.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@@ -899,6 +899,17 @@ switch (DELEGATE_ID) {
DELEGATE_END(StringSliceIter);
break;
}

case Expression::Id::ResumeId: {
DELEGATE_START(Resume);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems we need to add sentTypes here as well. See #6181 (comment)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment on lines 1322 to 1327
ArenaVector<Type>& Resume::getSentTypes() {
if (this->sentTypes.size() != this->handlerBlocks.size()) {
Fatal() << "Types sent to blocks have not been determined, yet.";
}
return this->sentTypes;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to make sentTypes a private member and have this function separately? I think we can access it directly, and then this if check might better be in the validator.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment on lines 4044 to 4046
auto resume = allocator.alloc<Resume>();
curr = resume;
visitResume(resume);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
auto resume = allocator.alloc<Resume>();
curr = resume;
visitResume(resume);
visitResume((curr = allocator.alloc<Resume>())->cast<Resume>());

I'm not sure why MemorySize and MemoryGrow above didn't use it, but can we do this one-liner we used for other instructions above?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@@ -0,0 +1,94 @@
;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've been moving basic tests into test/lit/basic/ (#6156 and #6160) and make commands and CHECK lines consistent across files (#6159). Can you move this test to test/lit/basic/ as well and make the RUN commands the same with the tests there?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

src/wasm.h Outdated
Comment on lines 1977 to 1978
void finalize();
void finalize(Module* wasm);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we

Suggested change
void finalize();
void finalize(Module* wasm);
void finalize(Module* wasm = nullptr);

Would this be simpler?

Also it would be nice if we have some comments, as suggested with TryTable in https://github.com/WebAssembly/binaryen/pull/6181/files#r1426054122.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment on lines 1329 to 1347
void Resume::finalize() {
// Only performing some validation, but no finalization. The finalize(Module*)
// function must have been called previously.

if (!(this->contType.isContinuation() &&
this->contType.getContinuation().type.isSignature())) {
Fatal() << "ill-formed Resume expression";
}

if (this->sentTypes.size() != this->handlerBlocks.size()) {
Fatal() << "Resume node was not finalized";
}
}

void Resume::finalize(Module* wasm) {
if (!(this->contType.isContinuation() &&
this->contType.getContinuation().type.isSignature())) {
Fatal() << "ill-formed Resume expression";
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we move this (validation in Resume::finalize() and Resume::finalize(Module* wasm) to wasm-validator.cpp? Also if we do

void finalize(Module* wasm = nullptr);

in wasm.h, we wouldn't need two methods.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

;; CHECK-TEXT: (type $3 (func (param (ref $ct)) (result i32)))

;; CHECK-TEXT: (tag $t (result i32))
(tag $t (result i32))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we have a test with resume using a tag with non-empty params as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, I've just changed $t in this test.

Comment on lines 1374 to 1383
if (tgps.size() > 0) {
TypeList sentValueTypes;
sentValueTypes.reserve(tgps.size() + 1);

sentValueTypes.insert(sentValueTypes.begin(), tgps.begin(), tgps.end());
sentValueTypes.push_back(ctPrimeRef);
this->sentTypes[i] = Type(sentValueTypes);
} else {
this->sentTypes[i] = ctPrimeRef;
}
Copy link
Member

@aheejin aheejin Dec 16, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need these separate cases? The body within if would work fine even if tgps is empty.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point! The else branch is probably the more common case, where the handler block only receives the continuation, but no additional payloads. So separating the two cases just boils down to avoiding the allocation for a singleton std::vector<Type> (namely, sentValueTypes). Would you prefer avoiding the separate else case?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The current code looks good to me as is.

aheejin added a commit that referenced this pull request Dec 19, 2023
This adds basic support for the new instructions in the new EH proposal
passed at the Oct CG hybrid CG meeting:
https://github.com/WebAssembly/meetings/blob/main/main/2023/CG-10.md
https://github.com/WebAssembly/exception-handling/blob/main/proposals/exception-handling/Exceptions.md

This mainly adds two instructions: `try_table` and `throw_ref`. This is
the bare minimum required to read and write text and binary format, and
does not include analyses or optimizations. (It includes some analysis
required for validation of existing instructions.) Validation for
the new instructions is not yet included.

`try_table` faces the same problem with the `resume` instruction in
 #6083 that without the module-level tag info, we are unable to know the
'sent types' of `try_table`. This solves it with a similar approach
taken in #6083: this adds `Module*` parameter to `finalize` methods,
which defaults to `nullptr` when not given. The `Module*` parameter is
given when called from the binary and text parser, and we cache those
tag types in `sentTypes` array within `TryTable` class. In later
optimization passes, as long as they don't touch tags, it is fine to
call `finalize` without the `Module*`. Refer to
#6083 (comment)
and #6096 for related discussions when `resume` was added.
@frank-emrich
Copy link
Contributor Author

frank-emrich commented Jan 8, 2024

Thanks for the reviews, @tlively and @aheejin. I've addressed all comments, with most of them being resolved now.
In general, I made things a bit more consistent with #6181. I've also run the fuzzer for 10k iterations.

Copy link
Member

@aheejin aheejin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

shouldBeTrue(
curr->sentTypes.size() == curr->handlerBlocks.size(),
curr,
"sentTypes cache in Resume instruction has not been initialised");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"sentTypes cache in Resume instruction has not been initialised");
"sentTypes cache in Resume instruction has not been initialized");

Nit: Binaryen codebase seems to use American spelling, so...

Comment on lines 1374 to 1383
if (tgps.size() > 0) {
TypeList sentValueTypes;
sentValueTypes.reserve(tgps.size() + 1);

sentValueTypes.insert(sentValueTypes.begin(), tgps.begin(), tgps.end());
sentValueTypes.push_back(ctPrimeRef);
this->sentTypes[i] = Type(sentValueTypes);
} else {
this->sentTypes[i] = ctPrimeRef;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The current code looks good to me as is.

@tlively tlively merged commit b4dee3d into WebAssembly:main Jan 11, 2024
14 checks passed
@tlively
Copy link
Member

tlively commented Jan 11, 2024

Oops, I saw your spelling nit just after I merged this, @aheejin. I'll fix it.

radekdoulik pushed a commit to dotnet/binaryen that referenced this pull request Jul 12, 2024
This adds basic support for the new instructions in the new EH proposal
passed at the Oct CG hybrid CG meeting:
https://github.com/WebAssembly/meetings/blob/main/main/2023/CG-10.md
https://github.com/WebAssembly/exception-handling/blob/main/proposals/exception-handling/Exceptions.md

This mainly adds two instructions: `try_table` and `throw_ref`. This is
the bare minimum required to read and write text and binary format, and
does not include analyses or optimizations. (It includes some analysis
required for validation of existing instructions.) Validation for
the new instructions is not yet included.

`try_table` faces the same problem with the `resume` instruction in
 WebAssembly#6083 that without the module-level tag info, we are unable to know the
'sent types' of `try_table`. This solves it with a similar approach
taken in WebAssembly#6083: this adds `Module*` parameter to `finalize` methods,
which defaults to `nullptr` when not given. The `Module*` parameter is
given when called from the binary and text parser, and we cache those
tag types in `sentTypes` array within `TryTable` class. In later
optimization passes, as long as they don't touch tags, it is fine to
call `finalize` without the `Module*`. Refer to
WebAssembly#6083 (comment)
and WebAssembly#6096 for related discussions when `resume` was added.
radekdoulik pushed a commit to dotnet/binaryen that referenced this pull request Jul 12, 2024
This PR is part of a series that adds basic support for the [typed continuations proposal](https://github.com/wasmfx/specfx).

This particular PR adds support for the `resume` instruction. The most notable missing feature is validation, which is not implemented, yet.
@gkdn gkdn mentioned this pull request Aug 31, 2024
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

Successfully merging this pull request may close these issues.

4 participants