diff options
author | Heejin Ahn <aheejin@gmail.com> | 2021-02-12 14:41:57 +0900 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-02-12 14:41:57 +0900 |
commit | f826df6e053e0541e16cc19ded8083cf8de7c59d (patch) | |
tree | 64189219a72c0ffdea2982ebcbecc4e79147ec7e /src/wasm/wasm-s-parser.cpp | |
parent | 8369064c09964ab77eae2af6f0d8cce58e412e11 (diff) | |
download | binaryen-f826df6e053e0541e16cc19ded8083cf8de7c59d.tar.gz binaryen-f826df6e053e0541e16cc19ded8083cf8de7c59d.tar.bz2 binaryen-f826df6e053e0541e16cc19ded8083cf8de7c59d.zip |
[EH] Support reading/writing of delegate (#3561)
This adds support for reading/writing of the new `delegate` instruction
in the folded wast format, the stack IR format, the poppy IR format, and
the binary format in Binaryen. We don't have a formal spec written down
yet, but please refer to WebAssembly/exception-handling#137 and
WebAssembly/exception-handling#146 for the informal semantics. In the
current version of spec `delegate` is basically a rethrow, but with
branch-like immediate argument so that it can bypass other
catches/delegates in between.
`delegate` is not represented as a new `Expression`, but it is rather
an option within a `Try` class, like `catch`/`catch_all`.
One special thing about `delegate` is, even though it is written
_within_ a `try` in the folded wat format, like
```wasm
(try
(do
...
)
(delegate $l)
)
```
In the unfolded wat format or in the binary format, `delegate` serves as
a scope end instruction so there is no separate `end`:
```wasm
try
...
delegate $l
```
`delegate` semantically targets an outer `catch` or `delegate`, but we
write `delegate` target as a `try` label because we only give labels to
block-like scoping expressions. So far we have not given `Try` a label
and used inner blocks or a wrapping block in case a branch targets the
`try`. But in case of `delegate`, it can syntactically only target `try`
and if it targets blocks or loops it is a validation failure.
So after discussions in #3497, we give `Try` a label but this label can
only be targeted by `delegate`s. Unfortunately this makes parsing and
writing of `Try` expression somewhat complicated. Also there is one
special case; if the immediate argument of `try` is the same as the
depth of control flow stack, this means the 'delegate' delegates to the
caller. To handle this case this adds a fake label
`DELEGATE_CALLER_TARGET`, and when writing it back to the wast format
writes it as an immediate value, unlike other cases in which we write
labels.
This uses `DELEGATE_FIELD_SCOPE_NAME_DEF/USE` to represent `try`'s label
and `delegate`'s target. There are many cases that `try` and
`delegate`'s labels need to be treated in the same way as block and
branch labels, such as for hashing or comparing. But there are routines
in which we automatically assume all label uses are branches. I thought
about adding a new kind of defines such as
`DELEGATE_FIELD_TRY_NAME_DEF/USE`, but I think it will also involve some
duplication of existing routines or classes. So at the moment this PR
chooses to use the existing `DELEGATE_FIELD_SCOPE_NAME_DEF/USE` for
`try` and `delegate` labels and makes only necessary amount of changes
in branch-utils. We can revisit this decision later if necessary.
Many of changes to the existing test cases are because now all `try`s
are automatically assigned a label. They will be removed in
`RemoveUnusedNames` pass in the same way as block labels if not targeted
by any delegates.
This only supports reading and writing and has not been tested against
any optimization passes yet.
---
Original unfolded wat file to generate test/try-delegate.wasm:
```wasm
(module
(event $e)
(func
try
try
delegate 0
catch $e
end)
(func
try
try
catch $e
i32.const 0
drop
try
delegate 1
end
catch $e
end
)
)
```
Diffstat (limited to 'src/wasm/wasm-s-parser.cpp')
-rw-r--r-- | src/wasm/wasm-s-parser.cpp | 56 |
1 files changed, 43 insertions, 13 deletions
diff --git a/src/wasm/wasm-s-parser.cpp b/src/wasm/wasm-s-parser.cpp index bcce3993e..d2b24fbed 100644 --- a/src/wasm/wasm-s-parser.cpp +++ b/src/wasm/wasm-s-parser.cpp @@ -1859,7 +1859,7 @@ Expression* SExpressionWasmBuilder::makeCallIndirect(Element& s, return ret; } -Name SExpressionWasmBuilder::getLabel(Element& s) { +Name SExpressionWasmBuilder::getLabel(Element& s, LabelType labelType) { if (s.dollared()) { return nameMapper.sourceToUnique(s.str()); } else { @@ -1876,10 +1876,14 @@ Name SExpressionWasmBuilder::getLabel(Element& s) { throw ParseException("invalid label", s.line, s.col); } if (offset == nameMapper.labelStack.size()) { - // a break to the function's scope. this means we need an automatic block, - // with a name - brokeToAutoBlock = true; - return FAKE_RETURN; + if (labelType == LabelType::Break) { + // a break to the function's scope. this means we need an automatic + // block, with a name + brokeToAutoBlock = true; + return FAKE_RETURN; + } + // This is a delegate that delegates to the caller + return DELEGATE_CALLER_TARGET; } return nameMapper.labelStack[nameMapper.labelStack.size() - 1 - offset]; } @@ -1975,12 +1979,13 @@ Expression* SExpressionWasmBuilder::makeRefEq(Element& s) { return ret; } -// try-catch-end is written in the folded wast format as +// try can be either in the form of try-catch or try-delegate. +// try-catch is written in the folded wast format as // (try // (do // ... // ) -// (catch +// (catch $e // ... // ) // ... @@ -1991,6 +1996,14 @@ Expression* SExpressionWasmBuilder::makeRefEq(Element& s) { // Any number of catch blocks can exist, including none. Zero or one catch_all // block can exist, and if it does, it should be at the end. There should be at // least one catch or catch_all body per try. +// +// try-delegate is written in the folded format as +// (try +// (do +// ... +// ) +// (delegate $label) +// ) Expression* SExpressionWasmBuilder::makeTry(Element& s) { auto ret = allocator.alloc<Try>(); Index i = 1; @@ -2001,7 +2014,7 @@ Expression* SExpressionWasmBuilder::makeTry(Element& s) { } else { sName = "try"; } - auto label = nameMapper.pushLabelName(sName); + ret->name = nameMapper.pushLabelName(sName); Type type = parseOptionalResultType(s, i); // signature if (!elementStartsWith(*s[i], "do")) { @@ -2027,21 +2040,38 @@ Expression* SExpressionWasmBuilder::makeTry(Element& s) { ret->catchBodies.push_back(makeMaybeBlock(*s[i++], 1, type)); } + // 'delegate' cannot target its own try. So we pop the name here. + nameMapper.popLabelName(ret->name); + + if (i < s.size() && elementStartsWith(*s[i], "delegate")) { + Element& inner = *s[i++]; + if (inner.size() != 2) { + throw ParseException("invalid delegate", inner.line, inner.col); + } + ret->delegateTarget = getLabel(*inner[1], LabelType::Delegate); + } + if (i != s.size()) { throw ParseException( "there should be at most one catch_all block at the end", s.line, s.col); } - if (ret->catchBodies.empty()) { - throw ParseException("no catch bodies", s.line, s.col); + if (ret->catchBodies.empty() && !ret->isDelegate()) { + throw ParseException("no catch bodies or delegate", s.line, s.col); } ret->finalize(type); - nameMapper.popLabelName(label); + // create a break target if we must - if (BranchUtils::BranchSeeker::has(ret, label)) { + if (BranchUtils::BranchSeeker::has(ret, ret->name)) { auto* block = allocator.alloc<Block>(); - block->name = label; + // We create a different name for the wrapping block, because try's name can + // be used by internal delegates + block->name = nameMapper.pushLabelName(sName); + // For simplicity, try's name canonly be targeted by delegates. Make the + // branches target the new wrapping block instead. + BranchUtils::replaceBranchTargets(ret, ret->name, block->name); block->list.push_back(ret); + nameMapper.popLabelName(block->name); block->finalize(type); return block; } |