diff --git a/src/passes/Heap2Local.cpp b/src/passes/Heap2Local.cpp index 215c4e5f601..b5360a6b4e8 100644 --- a/src/passes/Heap2Local.cpp +++ b/src/passes/Heap2Local.cpp @@ -611,9 +611,11 @@ struct Struct2Local : PostWalker { Struct2Local(StructNew* allocation, EscapeAnalyzer& analyzer, Function* func, - Module& wasm) + Module& wasm, + bool refinalize_ = false) : allocation(allocation), analyzer(analyzer), func(func), wasm(wasm), - builder(wasm), fields(allocation->type.getHeapType().getStruct().fields) { + builder(wasm), fields(allocation->type.getHeapType().getStruct().fields), + refinalize(refinalize_) { // Allocate locals to store the allocation's fields and descriptor in. for (auto field : fields) { @@ -1294,43 +1296,6 @@ struct Array2Struct : PostWalker { typeBuilder[0].setShared(arrayType.getShared()); structType = (*typeBuilder.build())[0]; - // Generate a StructNew to replace the ArrayNew*. - if (auto* arrayNew = allocation->dynCast()) { - if (arrayNew->isWithDefault()) { - structNew = builder.makeStructNew(structType, {}); - arrayNewReplacement = structNew; - } else { - // The ArrayNew is writing the same value to each slot of the array. To - // do the same for the struct, we store that value in an local and - // generate multiple local.gets of it. - auto local = builder.addVar(func, element.type); - auto* set = builder.makeLocalSet(local, arrayNew->init); - std::vector gets; - for (Index i = 0; i < numFields; i++) { - gets.push_back(builder.makeLocalGet(local, element.type)); - } - structNew = builder.makeStructNew(structType, gets); - // The ArrayNew* will be replaced with a block containing the local.set - // and the structNew. - arrayNewReplacement = builder.makeSequence(set, structNew); - } - } else if (auto* arrayNewFixed = allocation->dynCast()) { - // Simply use the same values as the array. - structNew = builder.makeStructNew(structType, arrayNewFixed->values); - arrayNewReplacement = structNew; - } else { - WASM_UNREACHABLE("bad allocation"); - } - - // Mark new expressions we created as flowing out the allocation. We need to - // inform the analysis of this because Struct2Local will only process such - // code (it depends on the analysis to tell it what the allocation is and - // where it flowed). Note that the two values here may be identical but - // there is no harm to doing this twice in that case. - analyzer.reachedInteractions[structNew] = - analyzer.reachedInteractions[arrayNewReplacement] = - ParentChildInteraction::Flows; - // Update types along the path reached by the allocation: whenever we see // the array type, it should be the struct type. Note that we do this before // the walk that is after us, because the walk may read these types and @@ -1370,10 +1335,6 @@ struct Array2Struct : PostWalker { // Replace the things we need to using the visit* methods. walk(func->body); - - if (refinalize) { - ReFinalize().walkFunctionInModule(func, &wasm); - } } Expression* replaceCurrent(Expression* expression) { @@ -1393,19 +1354,45 @@ struct Array2Struct : PostWalker { // optimize that StructNew using Struct2Local. StructNew* structNew; - // The replacement for the original ArrayNew*. Typically this is |structNew|, - // unless we have additional code we need alongside it. - Expression* arrayNewReplacement; - void visitArrayNew(ArrayNew* curr) { if (curr == allocation) { + // Generate a StructNew to replace the ArrayNew. + Expression* arrayNewReplacement; + if (curr->isWithDefault()) { + structNew = builder.makeStructNew(structType, {}); + arrayNewReplacement = structNew; + } else { + // The ArrayNew is writing the same value to each slot of the array. To + // do the same for the struct, we store that value in a local and + // generate multiple local.gets of it. + auto elementType = originalType.getHeapType().getArray().element.type; + auto local = builder.addVar(func, elementType); + auto* set = builder.makeLocalSet(local, curr->init); + std::vector gets; + for (Index i = 0; i < numFields; i++) { + gets.push_back(builder.makeLocalGet(local, elementType)); + } + structNew = builder.makeStructNew(structType, gets); + // The ArrayNew* will be replaced with a block containing the local.set + // and the structNew. + arrayNewReplacement = builder.makeSequence(set, structNew); + } + // Mark the new StructNew as flowing out the allocation so Struct2Local + // will process it. (replaceCurrent below does the same for + // arrayNewReplacement.) + analyzer.reachedInteractions[structNew] = ParentChildInteraction::Flows; replaceCurrent(arrayNewReplacement); } } void visitArrayNewFixed(ArrayNewFixed* curr) { if (curr == allocation) { - replaceCurrent(arrayNewReplacement); + // Simply use the same values as the array. Ensure the type remains the + // struct type even if an operand became unreachable so Struct2Local can + // still inspect the struct type before refinalization. + structNew = builder.makeStructNew(structType, curr->values); + structNew->type = Type(structType, NonNullable, Exact); + replaceCurrent(structNew); } } @@ -1640,9 +1627,12 @@ struct Heap2Local { if (!analyzer.escapes(allocation)) { // Convert the allocation and all its uses into a struct. Then convert // the struct into locals. - auto* structNew = - Array2Struct(allocation, analyzer, func, wasm).structNew; - Struct2Local(structNew, analyzer, func, wasm); + Array2Struct array2Struct(allocation, analyzer, func, wasm); + Struct2Local(array2Struct.structNew, + analyzer, + func, + wasm, + array2Struct.refinalize); resetAnalysisData(); } } diff --git a/test/lit/passes/heap2local.wast b/test/lit/passes/heap2local.wast index 63d1422a484..02e183a776e 100644 --- a/test/lit/passes/heap2local.wast +++ b/test/lit/passes/heap2local.wast @@ -2979,27 +2979,27 @@ ;; CHECK: (type $array (array (mut i32))) (type $array (array (mut i32))) - ;; CHECK: (type $1 (struct (field (mut i32)))) + ;; CHECK: (type $1 (func)) - ;; CHECK: (type $2 (func)) + ;; CHECK: (type $2 (func (result i32))) - ;; CHECK: (type $3 (func (result i32))) + ;; CHECK: (type $3 (func (param i32) (result i32))) - ;; CHECK: (type $4 (func (param i32) (result i32))) + ;; CHECK: (type $4 (struct (field (mut i32)) (field (mut i32)))) - ;; CHECK: (type $5 (struct (field (mut i32)) (field (mut i32)))) + ;; CHECK: (type $5 (func (param i32))) - ;; CHECK: (type $6 (func (param i32))) + ;; CHECK: (type $6 (struct (field (mut i32)) (field (mut i32)) (field (mut i32)) (field (mut i32)) (field (mut i32)) (field (mut i32)) (field (mut i32)))) - ;; CHECK: (type $7 (struct (field (mut i32)) (field (mut i32)) (field (mut i32)) (field (mut i32)) (field (mut i32)) (field (mut i32)) (field (mut i32)))) + ;; CHECK: (type $7 (struct)) - ;; CHECK: (type $8 (struct)) + ;; CHECK: (type $8 (struct (field (mut i32)))) ;; CHECK: (type $9 (struct (field (mut i32)) (field (mut i32)) (field (mut i32)) (field (mut i32)) (field (mut i32)) (field (mut i32)) (field (mut i32)) (field (mut i32)) (field (mut i32)) (field (mut i32)) (field (mut i32)) (field (mut i32)) (field (mut i32)) (field (mut i32)) (field (mut i32)) (field (mut i32)) (field (mut i32)) (field (mut i32)) (field (mut i32)))) ;; CHECK: (type $10 (func (param anyref))) - ;; CHECK: (func $array.new_default (type $2) + ;; CHECK: (func $array.new_default (type $1) ;; CHECK-NEXT: (local $temp (ref $array)) ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 i32) @@ -3139,7 +3139,7 @@ ) ) - ;; CHECK: (func $array.new (type $3) (result i32) + ;; CHECK: (func $array.new (type $2) (result i32) ;; CHECK-NEXT: (local $temp (ref $array)) ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 i32) @@ -3147,7 +3147,7 @@ ;; CHECK-NEXT: (local $4 i32) ;; CHECK-NEXT: (local $5 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (ref null $5)) + ;; CHECK-NEXT: (block (result (ref null $4)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 1337) ;; CHECK-NEXT: ) @@ -3190,7 +3190,7 @@ ) ) - ;; CHECK: (func $array.new_fixed (type $4) (param $x i32) (result i32) + ;; CHECK: (func $array.new_fixed (type $3) (param $x i32) (result i32) ;; CHECK-NEXT: (local $temp (ref $array)) ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (local $3 i32) @@ -3235,7 +3235,7 @@ ) ) - ;; CHECK: (func $array.nonconstant_size (type $4) (param $x i32) (result i32) + ;; CHECK: (func $array.nonconstant_size (type $3) (param $x i32) (result i32) ;; CHECK-NEXT: (local $temp (ref $array)) ;; CHECK-NEXT: (local.set $temp ;; CHECK-NEXT: (array.new $array @@ -3263,7 +3263,7 @@ ) ) - ;; CHECK: (func $array.nonconstant_get (type $4) (param $x i32) (result i32) + ;; CHECK: (func $array.nonconstant_get (type $3) (param $x i32) (result i32) ;; CHECK-NEXT: (local $temp (ref $array)) ;; CHECK-NEXT: (local.set $temp ;; CHECK-NEXT: (array.new $array @@ -3291,7 +3291,7 @@ ) ) - ;; CHECK: (func $array.nonconstant_set (type $6) (param $x i32) + ;; CHECK: (func $array.nonconstant_set (type $5) (param $x i32) ;; CHECK-NEXT: (local $temp (ref $array)) ;; CHECK-NEXT: (local.set $temp ;; CHECK-NEXT: (array.new $array @@ -3321,7 +3321,7 @@ ) ) - ;; CHECK: (func $array.local.super (type $2) + ;; CHECK: (func $array.local.super (type $1) ;; CHECK-NEXT: (local $temp anyref) ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 i32) @@ -3370,7 +3370,7 @@ ) ) - ;; CHECK: (func $array.folded (type $3) (result i32) + ;; CHECK: (func $array.folded (type $2) (result i32) ;; CHECK-NEXT: (local $0 i32) ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 i32) @@ -3387,7 +3387,7 @@ ;; CHECK-NEXT: (local $13 i32) ;; CHECK-NEXT: (local $14 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (ref null $7)) + ;; CHECK-NEXT: (block (result (ref null $6)) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (call $get-i32) ;; CHECK-NEXT: ) @@ -3451,7 +3451,7 @@ ) ) - ;; CHECK: (func $array.folded.multiple (type $2) + ;; CHECK: (func $array.folded.multiple (type $1) ;; CHECK-NEXT: (local $0 i32) ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 i32) @@ -3496,7 +3496,7 @@ ;; CHECK-NEXT: (local $41 i32) ;; CHECK-NEXT: (local $42 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (ref null $8)) + ;; CHECK-NEXT: (block (result (ref null $7)) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (call $get-i32) ;; CHECK-NEXT: ) @@ -3508,7 +3508,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (ref null $1)) + ;; CHECK-NEXT: (block (result (ref null $8)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (call $get-i32) ;; CHECK-NEXT: ) @@ -3704,7 +3704,7 @@ ) ) - ;; CHECK: (func $array.nested.refinalize.get (type $3) (result i32) + ;; CHECK: (func $array.nested.refinalize.get (type $2) (result i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (ref.null none) @@ -3724,7 +3724,7 @@ ) ) - ;; CHECK: (func $array.nested.refinalize.set (type $2) + ;; CHECK: (func $array.nested.refinalize.set (type $1) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (ref.null none) @@ -3746,13 +3746,13 @@ ) ) - ;; CHECK: (func $array.flowing.type (type $3) (result i32) + ;; CHECK: (func $array.flowing.type (type $2) (result i32) ;; CHECK-NEXT: (local $temp (ref $array)) ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (local $3 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (ref null $1)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) @@ -3768,22 +3768,22 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (ref null $1)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (ref null $1)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (ref null $1)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (ref null $1)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -3832,7 +3832,7 @@ ) ) - ;; CHECK: (func $get-i32 (type $3) (result i32) + ;; CHECK: (func $get-i32 (type $2) (result i32) ;; CHECK-NEXT: (i32.const 1337) ;; CHECK-NEXT: ) (func $get-i32 (result i32) @@ -3840,7 +3840,7 @@ (i32.const 1337) ) - ;; CHECK: (func $ref-test (type $2) + ;; CHECK: (func $ref-test (type $1) ;; CHECK-NEXT: (local $0 i32) ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 i32) @@ -3957,15 +3957,13 @@ (type $array (array (ref null $array))) - ;; CHECK: (type $1 (struct (field (ref null $array)))) - - ;; CHECK: (type $2 (func)) + ;; CHECK: (type $1 (func)) - ;; CHECK: (type $3 (struct (field (ref null $array)) (field (ref null $array)) (field (ref null $array)))) + ;; CHECK: (type $2 (struct (field (ref null $array)) (field (ref null $array)) (field (ref null $array)))) - ;; CHECK: (type $4 (func (result anyref))) + ;; CHECK: (type $3 (func (result anyref))) - ;; CHECK: (func $nested (type $2) + ;; CHECK: (func $nested (type $1) ;; CHECK-NEXT: (local $0 (ref null $array)) ;; CHECK-NEXT: (local $1 (ref null $array)) ;; CHECK-NEXT: (local $2 (ref null $array)) @@ -3974,7 +3972,7 @@ ;; CHECK-NEXT: (local $5 (ref null $array)) ;; CHECK-NEXT: (local $6 (ref null $array)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (ref null $3)) + ;; CHECK-NEXT: (block (result (ref null $2)) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (array.new $array ;; CHECK-NEXT: (array.new_default $array @@ -4024,7 +4022,7 @@ ) ) - ;; CHECK: (func $nested-unreachable (type $2) + ;; CHECK: (func $nested-unreachable (type $1) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block ;; (replaces unreachable ArrayNew we can't emit) ;; CHECK-NEXT: (drop @@ -4060,13 +4058,13 @@ ) ) - ;; CHECK: (func $array.flowing.type (type $4) (result anyref) + ;; CHECK: (func $array.flowing.type (type $3) (result anyref) ;; CHECK-NEXT: (local $temp (ref $array)) ;; CHECK-NEXT: (local $1 (ref null $array)) ;; CHECK-NEXT: (local $2 (ref null $array)) ;; CHECK-NEXT: (local $3 (ref null $array)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (ref null $1)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) @@ -4082,22 +4080,22 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (ref null $1)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (ref null $1)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (ref null $1)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (ref null $1)) + ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -4802,9 +4800,12 @@ ) (module + ;; CHECK: (type $0 (func)) + + ;; CHECK: (type $array (array (mut i32))) (type $array (array (mut i32))) - ;; CHECK: (type $0 (func)) + ;; CHECK: (type $2 (struct (field (mut i32)))) ;; CHECK: (func $unreachable-flow (type $0) ;; CHECK-NEXT: (local $0 i32) @@ -4854,5 +4855,190 @@ ) ) ) + + ;; CHECK: (func $unreachable-loop-array-new (type $0) + ;; CHECK-NEXT: (local $x (ref null $array)) + ;; CHECK-NEXT: (local $1 i32) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: (loop $loop + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $loop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $unreachable-loop-array-new + ;; In an unreachable loop, the array initializer can read from the local + ;; that exclusively holds the array allocation itself. Make sure replacing + ;; the array.get inside the initializer is not overwritten when replacing + ;; the array.new. + (local $x (ref null $array)) + (unreachable) + (loop $loop + (local.set $x + (array.new $array + (array.get $array + (local.get $x) + (i32.const 0) + ) + (i32.const 0) + ) + ) + (br $loop) + ) + ) + + ;; CHECK: (func $unreachable-loop-array-new-valid (type $0) + ;; CHECK-NEXT: (local $x (ref null $array)) + ;; CHECK-NEXT: (local $1 i32) + ;; CHECK-NEXT: (local $2 i32) + ;; CHECK-NEXT: (local $3 i32) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: (loop $loop + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref null $2)) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $loop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $unreachable-loop-array-new-valid + ;; Same as above, but with an in-bounds array.get inside the initializer. + (local $x (ref null $array)) + (unreachable) + (loop $loop + (local.set $x + (array.new $array + (array.get $array + (local.get $x) + (i32.const 0) + ) + (i32.const 1) + ) + ) + (br $loop) + ) + ) + + ;; CHECK: (func $unreachable-loop-array-new-fixed (type $0) + ;; CHECK-NEXT: (local $x (ref null $array)) + ;; CHECK-NEXT: (local $1 i32) + ;; CHECK-NEXT: (local $2 i32) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: (loop $loop + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $loop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $unreachable-loop-array-new-fixed + ;; Same as above, but with array.new_fixed and a direct out-of-bounds + ;; array.get operand that becomes unreachable. + (local $x (ref null $array)) + (unreachable) + (loop $loop + (local.set $x + (array.new_fixed $array 1 + (array.get $array + (local.get $x) + (i32.const 1) + ) + ) + ) + (br $loop) + ) + ) + + ;; CHECK: (func $unreachable-loop-array-new-fixed-nested (type $0) + ;; CHECK-NEXT: (local $x (ref null $array)) + ;; CHECK-NEXT: (local $1 i32) + ;; CHECK-NEXT: (local $2 i32) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: (loop $loop + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $loop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $unreachable-loop-array-new-fixed-nested + ;; Same as above, but the out-of-bounds array.get is nested inside another + ;; expression. + (local $x (ref null $array)) + (unreachable) + (loop $loop + (local.set $x + (array.new_fixed $array 1 + (i32.add + (array.get $array + (local.get $x) + (i32.const 1) + ) + (i32.const 0) + ) + ) + ) + (br $loop) + ) + ) )