diff options
-rw-r--r-- | src/wasm-stack.h | 100 | ||||
-rw-r--r-- | test/extra-unreachable.wast | 180 | ||||
-rw-r--r-- | test/extra-unreachable.wast.from-wast | 139 | ||||
-rw-r--r-- | test/extra-unreachable.wast.fromBinary | 19 | ||||
-rw-r--r-- | test/extra-unreachable.wast.fromBinary.noDebugInfo | 19 | ||||
-rw-r--r-- | test/passes/remove-unused-brs_generate-stack-ir_print-stack-ir.txt | 1 | ||||
-rwxr-xr-x | test/unit/input/tail_call_target_feature.wasm | bin | 113 -> 112 bytes |
7 files changed, 412 insertions, 46 deletions
diff --git a/src/wasm-stack.h b/src/wasm-stack.h index f4d116d77..64718b7b2 100644 --- a/src/wasm-stack.h +++ b/src/wasm-stack.h @@ -382,11 +382,33 @@ void BinaryenIRWriter<SubType>::visitCall(Call* curr) { for (auto* operand : curr->operands) { visit(operand); } - emit(curr); - // TODO FIXME: this and similar can be removed - if (curr->type == unreachable) { + + // For non-control-flow value-returning instructions, if the type of an + // expression is unreachable, we emit an unreachable and don't emit the + // instruction itself. If we don't emit an unreachable, instructions that + // follow can have a validation failure in wasm binary format. For example: + // [unreachable] (f32.add + // [unreachable] (i32.eqz + // [unreachable] (unreachable) + // ) + // ... + // ) + // This is a valid prgram in binaryen IR, because the unreachable type + // propagates out of an expression, making both i32.eqz and f32.add + // unreachable. But in binary format, this becomes: + // unreachable + // i32.eqz + // f32.add ;; validation failure; it takes an i32! + // And here f32.add causes validation failure in wasm validation. So in this + // case we add an unreachable to prevent following instructions to consume + // the current value (here i32.eqz). + // + // The same applies for other expressions. + if (curr->type == unreachable && !curr->isReturn) { emitUnreachable(); + return; } + emit(curr); } template<typename SubType> @@ -395,10 +417,11 @@ void BinaryenIRWriter<SubType>::visitCallIndirect(CallIndirect* curr) { visit(operand); } visit(curr->target); - emit(curr); - if (curr->type == unreachable) { + if (curr->type == unreachable && !curr->isReturn) { emitUnreachable(); + return; } + emit(curr); } template<typename SubType> @@ -409,10 +432,11 @@ void BinaryenIRWriter<SubType>::visitLocalGet(LocalGet* curr) { template<typename SubType> void BinaryenIRWriter<SubType>::visitLocalSet(LocalSet* curr) { visit(curr->value); - emit(curr); - if (curr->type == unreachable) { + if (curr->isTee() && curr->type == unreachable) { emitUnreachable(); + return; } + emit(curr); } template<typename SubType> @@ -430,7 +454,6 @@ template<typename SubType> void BinaryenIRWriter<SubType>::visitLoad(Load* curr) { visit(curr->ptr); if (curr->type == unreachable) { - // don't even emit it; we don't know the right type emitUnreachable(); return; } @@ -441,27 +464,14 @@ template<typename SubType> void BinaryenIRWriter<SubType>::visitStore(Store* curr) { visit(curr->ptr); visit(curr->value); - if (curr->type == unreachable) { - // don't even emit it; we don't know the right type - emitUnreachable(); - return; - } emit(curr); } template<typename SubType> void BinaryenIRWriter<SubType>::visitAtomicRMW(AtomicRMW* curr) { visit(curr->ptr); - // stop if the rest isn't reachable anyhow - if (curr->ptr->type == unreachable) { - return; - } visit(curr->value); - if (curr->value->type == unreachable) { - return; - } if (curr->type == unreachable) { - // don't even emit it; we don't know the right type emitUnreachable(); return; } @@ -471,20 +481,9 @@ void BinaryenIRWriter<SubType>::visitAtomicRMW(AtomicRMW* curr) { template<typename SubType> void BinaryenIRWriter<SubType>::visitAtomicCmpxchg(AtomicCmpxchg* curr) { visit(curr->ptr); - // stop if the rest isn't reachable anyhow - if (curr->ptr->type == unreachable) { - return; - } visit(curr->expected); - if (curr->expected->type == unreachable) { - return; - } visit(curr->replacement); - if (curr->replacement->type == unreachable) { - return; - } if (curr->type == unreachable) { - // don't even emit it; we don't know the right type emitUnreachable(); return; } @@ -494,16 +493,10 @@ void BinaryenIRWriter<SubType>::visitAtomicCmpxchg(AtomicCmpxchg* curr) { template<typename SubType> void BinaryenIRWriter<SubType>::visitAtomicWait(AtomicWait* curr) { visit(curr->ptr); - // stop if the rest isn't reachable anyhow - if (curr->ptr->type == unreachable) { - return; - } visit(curr->expected); - if (curr->expected->type == unreachable) { - return; - } visit(curr->timeout); - if (curr->timeout->type == unreachable) { + if (curr->type == unreachable) { + emitUnreachable(); return; } emit(curr); @@ -512,12 +505,9 @@ void BinaryenIRWriter<SubType>::visitAtomicWait(AtomicWait* curr) { template<typename SubType> void BinaryenIRWriter<SubType>::visitAtomicNotify(AtomicNotify* curr) { visit(curr->ptr); - // stop if the rest isn't reachable anyhow - if (curr->ptr->type == unreachable) { - return; - } visit(curr->notifyCount); - if (curr->notifyCount->type == unreachable) { + if (curr->type == unreachable) { + emitUnreachable(); return; } emit(curr); @@ -526,6 +516,10 @@ void BinaryenIRWriter<SubType>::visitAtomicNotify(AtomicNotify* curr) { template<typename SubType> void BinaryenIRWriter<SubType>::visitSIMDExtract(SIMDExtract* curr) { visit(curr->vec); + if (curr->type == unreachable) { + emitUnreachable(); + return; + } emit(curr); } @@ -533,6 +527,10 @@ template<typename SubType> void BinaryenIRWriter<SubType>::visitSIMDReplace(SIMDReplace* curr) { visit(curr->vec); visit(curr->value); + if (curr->type == unreachable) { + emitUnreachable(); + return; + } emit(curr); } @@ -540,6 +538,10 @@ template<typename SubType> void BinaryenIRWriter<SubType>::visitSIMDShuffle(SIMDShuffle* curr) { visit(curr->left); visit(curr->right); + if (curr->type == unreachable) { + emitUnreachable(); + return; + } emit(curr); } @@ -548,6 +550,10 @@ void BinaryenIRWriter<SubType>::visitSIMDBitselect(SIMDBitselect* curr) { visit(curr->left); visit(curr->right); visit(curr->cond); + if (curr->type == unreachable) { + emitUnreachable(); + return; + } emit(curr); } @@ -555,6 +561,10 @@ template<typename SubType> void BinaryenIRWriter<SubType>::visitSIMDShift(SIMDShift* curr) { visit(curr->vec); visit(curr->shift); + if (curr->type == unreachable) { + emitUnreachable(); + return; + } emit(curr); } diff --git a/test/extra-unreachable.wast b/test/extra-unreachable.wast new file mode 100644 index 000000000..441498de6 --- /dev/null +++ b/test/extra-unreachable.wast @@ -0,0 +1,180 @@ +(module + (type $ii (param i32) (result i32)) + (memory (shared 1 1)) + (table 0 funcref) + (global $g (mut f32) (f32.const 0)) + + (func $foo (param i32) (result i32) (i32.const 0)) + + (func $test_function_block + ;; block, which has unreachable type, can be omitted here because it is the + ;; only expression within a function. We emit an extra unreachable at the + ;; end. + (block + (unreachable) + (nop) + ) + ) + + (func $test + ;; block has unreachable type. We emit an unreachable at the end of the + ;; block and also outside the block too. + (block + (i32.eqz (unreachable)) + ) + + ;; If an if's condition is unreachable, don't emit the if and emit an + ;; unreachable instead. + (if + (unreachable) + (nop) + (nop) + ) + + ;; If an if is unreachable, i.e., the both sides are unreachable, we emit + ;; an extra unreachable after the if. + (if + (i32.const 1) + (unreachable) + (unreachable) + ) + + ;; If a br_if's type is unreachable, emit an extra unreachable after it + (block + (br_if 0 (unreachable)) + ) + + ;; If a br_table is not reachable, emit an unreachable instead + (block $l + (block $default + (br_table $l $default + (unreachable) + ) + ) + ) + + ;; For all tests below, when a value-returning expression is unreachable, + ;; emit an unreachable instead of the operation, to prevent it from being + ;; consumed by the next operation. + ;; + ;; Here global.set is used to consume the value. If an unreachable is not + ;; emitted, the instruction itself will be consumed by global.set and will + ;; result in a type validation failure, because it expects a f32 but gets an + ;; i32. The reason we use global.set is the instruction does not return a + ;; value, so it will be emitted even if its argument is unreachable. + + ;; call / call_indirect + (global.set $g + (call $foo (unreachable)) + ) + (global.set $g + (call_indirect (type $ii) (unreachable)) + ) + + ;; unary + (global.set $g + (i32.eqz (unreachable)) + ) + + ;; binary + (global.set $g + (i32.add + (unreachable) + (i32.const 0) + ) + ) + + ;; select + (global.set $g + (select + (unreachable) + (i32.const 0) + (i32.const 0) + ) + ) + + ;; load + (global.set $g + (i32.load (unreachable)) + ) + (global.set $g + (i32.atomic.load (unreachable)) + ) + + ;; atomic.rmw + (global.set $g + (i32.atomic.rmw.add + (unreachable) + (i32.const 0) + ) + ) + + ;; atomic.cmpxchg + (global.set $g + (i32.atomic.rmw.cmpxchg + (unreachable) + (i32.const 0) + (i32.const 0) + ) + ) + + ;; atomic.wait + (global.set $g + (i32.atomic.wait + (unreachable) + (i32.const 0) + (i64.const 0) + ) + ) + + ;; atomic.notify + (global.set $g + (atomic.notify + (unreachable) + (i32.const 0) + ) + ) + + ;; SIMD extract + (global.set $g + (i32x4.extract_lane 0 + (unreachable) + ) + ) + + ;; SIMD replace + (global.set $g + (i32x4.replace_lane 0 + (unreachable) + (i32.const 0) + ) + ) + + ;; SIMD shuffle + (global.set $g + (v8x16.shuffle 0 17 2 19 4 21 6 23 8 25 10 27 12 29 14 31 + (unreachable) + (unreachable) + ) + ) + + ;; SIMD bitselect + (global.set $g + (v128.bitselect + (unreachable) + (unreachable) + (unreachable) + ) + ) + + ;; SIMD shift + (global.set $g + (i32x4.shl + (unreachable) + (i32.const 0) + ) + ) + + ;; TODO Add exception handling instructions + ) +) diff --git a/test/extra-unreachable.wast.from-wast b/test/extra-unreachable.wast.from-wast new file mode 100644 index 000000000..5d794148e --- /dev/null +++ b/test/extra-unreachable.wast.from-wast @@ -0,0 +1,139 @@ +(module + (type $ii (func)) + (type $FUNCSIG$ii (func (param i32) (result i32))) + (memory $0 (shared 1 1)) + (table $0 0 funcref) + (global $g (mut f32) (f32.const 0)) + (func $foo (; 0 ;) (type $FUNCSIG$ii) (param $0 i32) (result i32) + (i32.const 0) + ) + (func $test_function_block (; 1 ;) (type $ii) + (block $block + (unreachable) + (nop) + ) + ) + (func $test (; 2 ;) (type $ii) + (block $block + (i32.eqz + (unreachable) + ) + ) + (if + (unreachable) + (nop) + (nop) + ) + (if + (i32.const 1) + (unreachable) + (unreachable) + ) + (block $block1 + (br_if $block1 + (unreachable) + ) + ) + (block $l + (block $default + (br_table $l $default + (unreachable) + ) + ) + ) + (global.set $g + (call $foo + (unreachable) + ) + ) + (global.set $g + (call_indirect (type $ii) + (unreachable) + ) + ) + (global.set $g + (i32.eqz + (unreachable) + ) + ) + (global.set $g + (i32.add + (unreachable) + (i32.const 0) + ) + ) + (global.set $g + (select + (unreachable) + (i32.const 0) + (i32.const 0) + ) + ) + (global.set $g + (i32.load + (unreachable) + ) + ) + (global.set $g + (i32.atomic.load + (unreachable) + ) + ) + (global.set $g + (i32.atomic.rmw.add + (unreachable) + (i32.const 0) + ) + ) + (global.set $g + (i32.atomic.rmw.cmpxchg + (unreachable) + (i32.const 0) + (i32.const 0) + ) + ) + (global.set $g + (i32.atomic.wait + (unreachable) + (i32.const 0) + (i64.const 0) + ) + ) + (global.set $g + (atomic.notify + (unreachable) + (i32.const 0) + ) + ) + (global.set $g + (i32x4.extract_lane 0 + (unreachable) + ) + ) + (global.set $g + (i32x4.replace_lane 0 + (unreachable) + (i32.const 0) + ) + ) + (global.set $g + (v8x16.shuffle 0 17 2 19 4 21 6 23 8 25 10 27 12 29 14 31 + (unreachable) + (unreachable) + ) + ) + (global.set $g + (v128.bitselect + (unreachable) + (unreachable) + (unreachable) + ) + ) + (global.set $g + (i32x4.shl + (unreachable) + (i32.const 0) + ) + ) + ) +) diff --git a/test/extra-unreachable.wast.fromBinary b/test/extra-unreachable.wast.fromBinary new file mode 100644 index 000000000..11c23e009 --- /dev/null +++ b/test/extra-unreachable.wast.fromBinary @@ -0,0 +1,19 @@ +(module + (type $0 (func)) + (type $1 (func (param i32) (result i32))) + (memory $0 (shared 1 1)) + (table $0 0 funcref) + (global $global$0 (mut f32) (f32.const 0)) + (func $foo (; 0 ;) (type $1) (param $0 i32) (result i32) + (i32.const 0) + ) + (func $test_function_block (; 1 ;) (type $0) + (unreachable) + ) + (func $test (; 2 ;) (type $0) + (block $label$1 + (unreachable) + ) + ) +) + diff --git a/test/extra-unreachable.wast.fromBinary.noDebugInfo b/test/extra-unreachable.wast.fromBinary.noDebugInfo new file mode 100644 index 000000000..1daf0114c --- /dev/null +++ b/test/extra-unreachable.wast.fromBinary.noDebugInfo @@ -0,0 +1,19 @@ +(module + (type $0 (func)) + (type $1 (func (param i32) (result i32))) + (memory $0 (shared 1 1)) + (table $0 0 funcref) + (global $global$0 (mut f32) (f32.const 0)) + (func $0 (; 0 ;) (type $1) (param $0 i32) (result i32) + (i32.const 0) + ) + (func $1 (; 1 ;) (type $0) + (unreachable) + ) + (func $2 (; 2 ;) (type $0) + (block $label$1 + (unreachable) + ) + ) +) + diff --git a/test/passes/remove-unused-brs_generate-stack-ir_print-stack-ir.txt b/test/passes/remove-unused-brs_generate-stack-ir_print-stack-ir.txt index 80c850db5..937fe59a3 100644 --- a/test/passes/remove-unused-brs_generate-stack-ir_print-stack-ir.txt +++ b/test/passes/remove-unused-brs_generate-stack-ir_print-stack-ir.txt @@ -9,7 +9,6 @@ unreachable end unreachable - local.tee $var$0 unreachable unreachable end diff --git a/test/unit/input/tail_call_target_feature.wasm b/test/unit/input/tail_call_target_feature.wasm Binary files differindex 651c92ec8..be4b73caf 100755 --- a/test/unit/input/tail_call_target_feature.wasm +++ b/test/unit/input/tail_call_target_feature.wasm |