Skip to content

Commit 0f0f5a5

Browse files
committed
Complete the support for try_table.
This includes adding interpreter support for `exnref` values. Since they must carry a `Name tag` in addition to `Literals payload`, they did not fit into existing `GCData`. Therefore, we add another possible type in the big `union` for `ExnData`. Alternatively, we could make a non-`funcref` `Name` value a valid choice for a single `Literal`, but that would require inventing a fake `Type` for such literals, such as `Type::tag`, which would have no spec equivalent. This highlighted the fact that using `Name`s will not work when cross module boundaries (for `funcref` nor `exnref` nor the thrown `WasmException`s). Since fixing this is beyond the scope of this commit, we leave TODO's in place. Other than the above, the changes are straightforward. They are inspired from existing handling of `Try` and `Break`.
1 parent 23a1a1a commit 0f0f5a5

29 files changed

+2276
-254
lines changed

scripts/fuzz_opt.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -350,10 +350,20 @@ def is_git_repo():
350350
'typed_continuations_contnew.wast',
351351
'typed_continuations_contbind.wast',
352352
'typed_continuations_suspend.wast',
353-
# New EH implementation is in progress
353+
# TODO: make sure the fuzzer supports the new EH
354+
'coalesce-locals-eh.wast',
355+
'code-folding-eh.wast',
356+
'code-pushing-eh.wast',
357+
'dce-eh.wast',
358+
'eh.wast',
359+
'eh-gc.wast',
354360
'exception-handling.wast',
361+
'global-effects.wast',
362+
'local-subtyping.wast',
363+
'renamings.wat',
355364
'translate-to-new-eh.wast',
356365
'rse-eh.wast',
366+
'vacuum-eh.wast',
357367
]
358368

359369

src/ir/ReFinalize.cpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,12 @@ void ReFinalize::visitTableGrow(TableGrow* curr) { curr->finalize(); }
127127
void ReFinalize::visitTableFill(TableFill* curr) { curr->finalize(); }
128128
void ReFinalize::visitTableCopy(TableCopy* curr) { curr->finalize(); }
129129
void ReFinalize::visitTry(Try* curr) { curr->finalize(); }
130-
void ReFinalize::visitTryTable(TryTable* curr) { curr->finalize(); }
130+
void ReFinalize::visitTryTable(TryTable* curr) {
131+
curr->finalize();
132+
for (size_t i = 0; i < curr->catchDests.size(); i++) {
133+
updateBreakValueType(curr->catchDests[i], curr->sentTypes[i]);
134+
}
135+
}
131136
void ReFinalize::visitThrow(Throw* curr) { curr->finalize(); }
132137
void ReFinalize::visitRethrow(Rethrow* curr) { curr->finalize(); }
133138
void ReFinalize::visitThrowRef(ThrowRef* curr) { curr->finalize(); }

src/ir/effects.h

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,14 @@ class EffectAnalyzer {
431431
self->pushTask(doStartTry, currp);
432432
return;
433433
}
434+
if (auto* tryTable = curr->dynCast<TryTable>()) {
435+
// We need to increment try depth before starting.
436+
self->pushTask(doEndTryTable, currp);
437+
self->pushTask(doVisitTryTable, currp);
438+
self->pushTask(scan, &tryTable->body);
439+
self->pushTask(doStartTryTable, currp);
440+
return;
441+
}
434442
PostWalker<InternalAnalyzer, OverriddenVisitor<InternalAnalyzer>>::scan(
435443
self, currp);
436444
}
@@ -472,6 +480,24 @@ class EffectAnalyzer {
472480
self->parent.catchDepth--;
473481
}
474482

483+
static void doStartTryTable(InternalAnalyzer* self, Expression** currp) {
484+
TryTable* curr = (*currp)->cast<TryTable>();
485+
// We only count 'try_table's with a 'catch_all' because instructions
486+
// within a 'try_table' without a 'catch_all' can still throw outside of
487+
// the try.
488+
if (curr->hasCatchAll()) {
489+
self->parent.tryDepth++;
490+
}
491+
}
492+
493+
static void doEndTryTable(InternalAnalyzer* self, Expression** currp) {
494+
TryTable* curr = (*currp)->cast<TryTable>();
495+
if (curr->hasCatchAll()) {
496+
assert(self->parent.tryDepth > 0 && "try depth cannot be negative");
497+
self->parent.tryDepth--;
498+
}
499+
}
500+
475501
void visitBlock(Block* curr) {
476502
if (curr->name.is()) {
477503
parent.breakTargets.erase(curr->name); // these were internal breaks

src/ir/linear-execution.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,11 @@ struct LinearExecutionWalker : public PostWalker<SubType, VisitorType> {
171171
self->pushTask(SubType::scan, &curr->cast<Try>()->body);
172172
break;
173173
}
174+
case Expression::Id::TryTableId: {
175+
self->pushTask(SubType::doVisitTryTable, currp);
176+
self->pushTask(SubType::doNoteNonLinear, currp);
177+
break;
178+
}
174179
case Expression::Id::ThrowId: {
175180
self->pushTask(SubType::doVisitThrow, currp);
176181
self->pushTask(SubType::doNoteNonLinear, currp);

src/literal.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ namespace wasm {
3232

3333
class Literals;
3434
struct GCData;
35+
struct ExnData;
3536

3637
class Literal {
3738
// store only integers, whose bits are deterministic. floats
@@ -44,6 +45,7 @@ class Literal {
4445
int64_t i64;
4546
uint8_t v128[16];
4647
// funcref function name. `isNull()` indicates a `null` value.
48+
// TODO: handle cross-module calls using something other than a Name here.
4749
Name func;
4850
// A reference to GC data, either a Struct or an Array. For both of those we
4951
// store the referred data as a Literals object (which is natural for an
@@ -56,6 +58,8 @@ class Literal {
5658
// reference as its sole value even though internal i31 references do not
5759
// have a gcData.
5860
std::shared_ptr<GCData> gcData;
61+
// A reference to Exn data.
62+
std::shared_ptr<ExnData> exnData;
5963
};
6064

6165
public:
@@ -85,6 +89,7 @@ class Literal {
8589
assert(type.isSignature());
8690
}
8791
explicit Literal(std::shared_ptr<GCData> gcData, HeapType type);
92+
explicit Literal(std::shared_ptr<ExnData> exnData);
8893
explicit Literal(std::string_view string);
8994
Literal(const Literal& other);
9095
Literal& operator=(const Literal& other);
@@ -96,6 +101,7 @@ class Literal {
96101
// Whether this is GC data, that is, something stored on the heap (aside from
97102
// a null or i31). This includes structs, arrays, and also strings.
98103
bool isData() const { return type.isData(); }
104+
bool isExn() const { return type.isExn(); }
99105
bool isString() const { return type.isString(); }
100106

101107
bool isNull() const { return type.isNull(); }
@@ -303,6 +309,7 @@ class Literal {
303309
return func;
304310
}
305311
std::shared_ptr<GCData> getGCData() const;
312+
std::shared_ptr<ExnData> getExnData() const;
306313

307314
// careful!
308315
int32_t* geti32Ptr() {
@@ -732,6 +739,18 @@ struct GCData {
732739
GCData(HeapType type, Literals values) : type(type), values(values) {}
733740
};
734741

742+
// The data of a (ref exn) literal.
743+
struct ExnData {
744+
// The tag of this exn data.
745+
// TODO: handle cross-module calls using something other than a Name here.
746+
Name tag;
747+
748+
// The payload of this exn data.
749+
Literals payload;
750+
751+
ExnData(Name tag, Literals payload) : tag(tag), payload(payload) {}
752+
};
753+
735754
} // namespace wasm
736755

737756
namespace std {

src/passes/CodeFolding.cpp

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -347,11 +347,12 @@ struct CodeFolding : public WalkerPass<ControlFlowWalker<CodeFolding>> {
347347
// out of the try scope changes the program's behavior, because the
348348
// expression that would otherwise have been caught by the try now
349349
// throws up to the next try scope or even up to the caller. We restrict
350-
// the move if 'outOf' contains a 'try' anywhere in it. This is a
351-
// conservative approximation because there can be cases that 'try' is
352-
// within the expression that may throw so it is safe to take the
353-
// expression out.
354-
if (effects.throws() && !FindAll<Try>(outOf).list.empty()) {
350+
// the move if 'outOf' contains a 'try' or 'try_table' anywhere in it.
351+
// This is a conservative approximation because there can be cases that
352+
// 'try'/'try_table' is within the expression that may throw so it is
353+
// safe to take the expression out.
354+
if (effects.throws() &&
355+
(FindAll<Try>(outOf).has() || FindAll<TryTable>(outOf).has())) {
355356
return false;
356357
}
357358
}

src/passes/DeadCodeElimination.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,12 @@ struct DeadCodeElimination
185185
tryy->body->type == Type::unreachable && allCatchesUnreachable) {
186186
typeUpdater.changeType(tryy, Type::unreachable);
187187
}
188+
} else if (auto* tryTable = curr->dynCast<TryTable>()) {
189+
// try_table can finish normally only if its body finishes normally.
190+
if (tryTable->type != Type::unreachable &&
191+
tryTable->body->type == Type::unreachable) {
192+
typeUpdater.changeType(tryTable, Type::unreachable);
193+
}
188194
} else {
189195
WASM_UNREACHABLE("unimplemented DCE control flow structure");
190196
}

src/passes/Vacuum.cpp

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ struct Vacuum : public WalkerPass<ExpressionStackWalker<Vacuum>> {
8787
// Some instructions have special handling in visit*, and we should do
8888
// nothing for them here.
8989
if (curr->is<Drop>() || curr->is<Block>() || curr->is<If>() ||
90-
curr->is<Loop>() || curr->is<Try>()) {
90+
curr->is<Loop>() || curr->is<Try>() || curr->is<TryTable>()) {
9191
return curr;
9292
}
9393
// Check if this expression itself has side effects, ignoring children.
@@ -435,6 +435,22 @@ struct Vacuum : public WalkerPass<ExpressionStackWalker<Vacuum>> {
435435
}
436436
}
437437

438+
void visitTryTable(TryTable* curr) {
439+
// If try's body does not throw, the whole try_table can be replaced with
440+
// the try's body.
441+
if (!EffectAnalyzer(getPassOptions(), *getModule(), curr->body).throws()) {
442+
replaceCurrent(curr->body);
443+
return;
444+
}
445+
446+
// TODO Should we attempt the same thing as in `visitTry` when the body
447+
// only throws, and there is a catch_all? We cannot `nop` the `TryTable`,
448+
// though. Instead, we should turn it into an unconditional `br` to the
449+
// target of the `catch_all`. Also, for a `catch_all_ref`, we cannot do that
450+
// optimization at all since we would not know how to create the correct
451+
// `exnref`.
452+
}
453+
438454
void visitFunction(Function* curr) {
439455
auto* optimized =
440456
optimize(curr->body, curr->getResults() != Type::none, true);

src/wasm-interpreter.h

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
namespace wasm {
4848

4949
struct WasmException {
50+
// TODO: handle cross-module calls using something other than a Name here.
5051
Name tag;
5152
Literals values;
5253
};
@@ -204,6 +205,15 @@ class ExpressionRunner : public OverriddenVisitor<SubType, Flow> {
204205
return Literal(allocation, type.getHeapType());
205206
}
206207

208+
// Same as makeGCData but for ExnData.
209+
Literal makeExnData(Name tag, const Literals& payload) {
210+
auto allocation = std::make_shared<ExnData>(tag, payload);
211+
#if __has_feature(leak_sanitizer) || __has_feature(address_sanitizer)
212+
__lsan_ignore_object(allocation.get());
213+
#endif
214+
return Literal(allocation);
215+
}
216+
207217
public:
208218
// Indicates no limit of maxDepth or maxLoopIterations.
209219
static const Index NO_LIMIT = 0;
@@ -1418,7 +1428,26 @@ class ExpressionRunner : public OverriddenVisitor<SubType, Flow> {
14181428
WASM_UNREACHABLE("throw");
14191429
}
14201430
Flow visitRethrow(Rethrow* curr) { WASM_UNREACHABLE("unimp"); }
1421-
Flow visitThrowRef(ThrowRef* curr) { WASM_UNREACHABLE("unimp"); }
1431+
Flow visitThrowRef(ThrowRef* curr) {
1432+
NOTE_ENTER("ThrowRef");
1433+
Flow flow = visit(curr->exnref);
1434+
if (flow.breaking()) {
1435+
return flow;
1436+
}
1437+
const auto& exnref = flow.getSingleValue();
1438+
NOTE_EVAL1(exnref);
1439+
if (exnref.isNull()) {
1440+
trap("null ref");
1441+
}
1442+
const auto& exnData = exnref.getExnData();
1443+
WasmException exn;
1444+
exn.tag = exnData->tag;
1445+
for (auto item : exnData->payload) {
1446+
exn.values.push_back(item);
1447+
}
1448+
throwException(exn);
1449+
WASM_UNREACHABLE("throw");
1450+
}
14221451
Flow visitRefI31(RefI31* curr) {
14231452
NOTE_ENTER("RefI31");
14241453
Flow flow = visit(curr->value);
@@ -2432,6 +2461,10 @@ class ConstantExpressionRunner : public ExpressionRunner<SubType> {
24322461
NOTE_ENTER("Try");
24332462
return Flow(NONCONSTANT_FLOW);
24342463
}
2464+
Flow visitTryTable(TryTable* curr) {
2465+
NOTE_ENTER("TryTable");
2466+
return Flow(NONCONSTANT_FLOW);
2467+
}
24352468
Flow visitRethrow(Rethrow* curr) {
24362469
NOTE_ENTER("Rethrow");
24372470
return Flow(NONCONSTANT_FLOW);
@@ -4046,6 +4079,31 @@ class ModuleRunnerBase : public ExpressionRunner<SubType> {
40464079
throw;
40474080
}
40484081
}
4082+
Flow visitTryTable(TryTable* curr) {
4083+
NOTE_ENTER("TryTable");
4084+
try {
4085+
return self()->visit(curr->body);
4086+
} catch (const WasmException& e) {
4087+
for (size_t i = 0; i < curr->catchTags.size(); i++) {
4088+
auto catchTag = curr->catchTags[i];
4089+
if (!catchTag.is() || catchTag == e.tag) {
4090+
Flow ret;
4091+
ret.breakTo = curr->catchDests[i];
4092+
if (catchTag.is()) {
4093+
for (auto item : e.values) {
4094+
ret.values.push_back(item);
4095+
}
4096+
}
4097+
if (curr->catchRefs[i]) {
4098+
ret.values.push_back(self()->makeExnData(e.tag, e.values));
4099+
}
4100+
return ret;
4101+
}
4102+
}
4103+
// This exception is not caught by this try-catch. Rethrow it.
4104+
throw;
4105+
}
4106+
}
40494107
Flow visitRethrow(Rethrow* curr) {
40504108
for (int i = exceptionStack.size() - 1; i >= 0; i--) {
40514109
if (exceptionStack[i].second == curr->target) {

src/wasm-type.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ class Type {
170170
bool isSignature() const;
171171
bool isStruct() const;
172172
bool isArray() const;
173+
bool isExn() const;
173174
bool isString() const;
174175
bool isDefaultable() const;
175176

@@ -388,6 +389,7 @@ class HeapType {
388389
bool isContinuation() const { return getKind() == HeapTypeKind::Cont; }
389390
bool isStruct() const { return getKind() == HeapTypeKind::Struct; }
390391
bool isArray() const { return getKind() == HeapTypeKind::Array; }
392+
bool isExn() const { return isMaybeShared(HeapType::exn); }
391393
bool isString() const { return isMaybeShared(HeapType::string); }
392394
bool isBottom() const;
393395
bool isOpen() const;

0 commit comments

Comments
 (0)