diff options
-rw-r--r-- | src/ir/eh-utils.cpp | 84 | ||||
-rw-r--r-- | src/ir/eh-utils.h | 11 | ||||
-rw-r--r-- | src/wasm/wasm-binary.cpp | 5 | ||||
-rw-r--r-- | test/lit/binary/stacky-eh.test | 66 | ||||
-rw-r--r-- | test/lit/binary/stacky-eh.test.wasm | bin | 0 -> 47 bytes |
5 files changed, 121 insertions, 45 deletions
diff --git a/src/ir/eh-utils.cpp b/src/ir/eh-utils.cpp index 37af738ce..3d369573d 100644 --- a/src/ir/eh-utils.cpp +++ b/src/ir/eh-utils.cpp @@ -107,54 +107,56 @@ bool containsValidDanglingPop(Expression* catchBody) { return pop != nullptr && !isPopNested; } +void handleBlockNestedPop(Try* try_, Function* func, Module& wasm) { + Builder builder(wasm); + for (Index i = 0; i < try_->catchTags.size(); i++) { + Name tagName = try_->catchTags[i]; + auto* tag = wasm.getTag(tagName); + if (tag->sig.params == Type::none) { + continue; + } + + auto* catchBody = try_->catchBodies[i]; + bool isPopNested = false; + Expression** popPtr = nullptr; + Expression* pop = getFirstPop(catchBody, isPopNested, popPtr); + assert(pop && "Pop has not been found in this catch"); + + // Change code like + // (catch $e + // ... + // (block + // (pop i32) + // ) + // ) + // into + // (catch $e + // (local.set $new + // (pop i32) + // ) + // ... + // (block + // (local.get $new) + // ) + // ) + if (isPopNested) { + assert(popPtr); + Index newLocal = builder.addVar(func, pop->type); + try_->catchBodies[i] = + builder.makeSequence(builder.makeLocalSet(newLocal, pop), catchBody); + *popPtr = builder.makeLocalGet(newLocal, pop->type); + } + } +} + void handleBlockNestedPops(Function* func, Module& wasm) { if (!wasm.features.hasExceptionHandling()) { return; } - - Builder builder(wasm); FindAll<Try> trys(func->body); for (auto* try_ : trys.list) { - for (Index i = 0; i < try_->catchTags.size(); i++) { - Name tagName = try_->catchTags[i]; - auto* tag = wasm.getTag(tagName); - if (tag->sig.params == Type::none) { - continue; - } - - auto* catchBody = try_->catchBodies[i]; - bool isPopNested = false; - Expression** popPtr = nullptr; - Expression* pop = getFirstPop(catchBody, isPopNested, popPtr); - assert(pop && "Pop has not been found in this catch"); - - // Change code like - // (catch $e - // ... - // (block - // (pop i32) - // ) - // ) - // into - // (catch $e - // (local.set $new - // (pop i32) - // ) - // ... - // (block - // (local.get $new) - // ) - // ) - if (isPopNested) { - assert(popPtr); - Index newLocal = builder.addVar(func, pop->type); - try_->catchBodies[i] = - builder.makeSequence(builder.makeLocalSet(newLocal, pop), catchBody); - *popPtr = builder.makeLocalGet(newLocal, pop->type); - } - } + handleBlockNestedPop(try_, func, wasm); } - // Pops we handled can be of non-defaultable types, so we may have created // non-nullable type locals. Fix them. TypeUpdating::handleNonDefaultableLocals(func, wasm); diff --git a/src/ir/eh-utils.h b/src/ir/eh-utils.h index c0d6e59ea..25677a32e 100644 --- a/src/ir/eh-utils.h +++ b/src/ir/eh-utils.h @@ -31,10 +31,13 @@ namespace EHUtils { // catch body, which is invalid. That will be taken care of in validation. bool containsValidDanglingPop(Expression* catchBody); -// Fixes up 'pop's nested in blocks, which are currently not supported without -// block param types, by creating a new local, putting a (local.set $new (pop -// type)) right after 'catch', and putting a '(local.get $new)' where the 'pop' -// used to be. +// Given a 'Try' expression, fixes up 'pop's nested in blocks, which are +// currently not supported without block param types, by creating a new local, +// putting a (local.set $new (pop type)) right after 'catch', and putting a +// '(local.get $new)' where the 'pop' used to be. +void handleBlockNestedPop(Try* try_, Function* func, Module& wasm); + +// Calls handleBlockNestedPop for each 'Try's in a given function. void handleBlockNestedPops(Function* func, Module& wasm); } // namespace EHUtils diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index d8a3c2f74..57a2bc95c 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -17,6 +17,7 @@ #include <algorithm> #include <fstream> +#include "ir/eh-utils.h" #include "ir/module-utils.h" #include "ir/table-utils.h" #include "ir/type-updating.h" @@ -6419,6 +6420,10 @@ void WasmBinaryBuilder::visitTryOrTryInBlock(Expression*& out) { } exceptionTargetNames.erase(catchLabel); } + + // If catch bodies contained stacky code, 'pop's can be nested within a block. + // Fix that up. + EHUtils::handleBlockNestedPop(curr, currFunction, wasm); curr->finalize(curr->type); // For simplicity, we create an inner block within the catch body too, but the diff --git a/test/lit/binary/stacky-eh.test b/test/lit/binary/stacky-eh.test new file mode 100644 index 000000000..09406bfea --- /dev/null +++ b/test/lit/binary/stacky-eh.test @@ -0,0 +1,66 @@ +# Verify stacky EH binary can be parsed correctly. +# +# stacky-eh.test.wasm contains below: +# try +# nop +# catch 0 # tag type i32 +# i32.const 3 +# local.set 1 +# local.set 2 +# +# (This binary was generated by +# 'wasm-opt --optimize-level=3 --generate-stack-ir -optimize-stack-ir') +# +# This code is 'stacky' in Binaryen parlance. In the binary reader, Binaryen has +# a special routine of creating a block and a local.get/local.set to read stacky +# code into Binaryen AST. So if we don't do any post-fixup, the 'catch' block +# becomes: +# (catch $e-i32 +# (local.set 2 +# (block (result i32) +# (local.set $new +# (pop i32) +# ) +# (local.set 1 +# (i32.const 3) +# ) +# (local.get $new) +# ) +# ) +# ) +# Here the 'block' and `local $new' are newly created to read the stacky code. +# But now the 'pop' ends up nested within the 'block', which is invalid. This +# test tests if this invalid code is correctly fixed up in the binary reader. +# The fixup will hoist the 'pop' and create another local to store it right +# after 'catch'. + +RUN: wasm-opt -all %s.wasm --print | filecheck %s + +# CHECK: (func $0 +# CHECK-NEXT: (local $0 i32) +# CHECK-NEXT: (local $1 i32) +# CHECK-NEXT: (local $2 i32) +# CHECK-NEXT: (local $3 i32) +# CHECK-NEXT: (local $4 i32) +# CHECK-NEXT: (try $label$3 +# CHECK-NEXT: (do +# CHECK-NEXT: (nop) +# CHECK-NEXT: ) +# CHECK-NEXT: (catch $tag$0 +# CHECK-NEXT: (local.set $4 +# CHECK-NEXT: (pop i32) +# CHECK-NEXT: ) +# CHECK-NEXT: (local.set $2 +# CHECK-NEXT: (block (result i32) +# CHECK-NEXT: (local.set $3 +# CHECK-NEXT: (local.get $4) +# CHECK-NEXT: ) +# CHECK-NEXT: (local.set $1 +# CHECK-NEXT: (i32.const 3) +# CHECK-NEXT: ) +# CHECK-NEXT: (local.get $3) +# CHECK-NEXT: ) +# CHECK-NEXT: ) +# CHECK-NEXT: ) +# CHECK-NEXT: ) +# CHECK-NEXT: ) diff --git a/test/lit/binary/stacky-eh.test.wasm b/test/lit/binary/stacky-eh.test.wasm Binary files differnew file mode 100644 index 000000000..992273cae --- /dev/null +++ b/test/lit/binary/stacky-eh.test.wasm |