diff --git a/src/passes/Heap2Local.cpp b/src/passes/Heap2Local.cpp index 534ff2f3d7d..a9465b1d7f2 100644 --- a/src/passes/Heap2Local.cpp +++ b/src/passes/Heap2Local.cpp @@ -228,7 +228,7 @@ struct EscapeAnalyzer { }; // Analyze an allocation to see if it escapes or not. - bool escapes(StructNew* allocation) { + bool escapes(Expression* allocation) { // A queue of flows from children to parents. When something is in the queue // here then it assumed that it is ok for the allocation to be at the child // (that is, we have already checked the child before placing it in the @@ -330,7 +330,7 @@ struct EscapeAnalyzer { return false; } - ParentChildInteraction getParentChildInteraction(StructNew* allocation, + ParentChildInteraction getParentChildInteraction(Expression* allocation, Expression* parent, Expression* child) { // If there is no parent then we are the body of the function, and that @@ -340,7 +340,7 @@ struct EscapeAnalyzer { } struct Checker : public Visitor { - StructNew* allocation; + Expression* allocation; Expression* child; // Assume escaping (or some other problem we cannot analyze) unless we are @@ -415,8 +415,28 @@ struct EscapeAnalyzer { escapes = false; fullyConsumes = true; } + void visitArraySet(ArraySet* curr) { + if (!curr->index->is()) { + // Array operations on nonconstant indexes do not escape in the normal + // sense, but they do escape from our being able to analyze them, so + // stop as soon as we see one. + return; + } - // TODO Array and I31 operations + // As StructGet. + if (curr->ref == child) { + escapes = false; + fullyConsumes = true; + } + } + void visitArrayGet(ArrayGet* curr) { + if (!curr->index->is()) { + return; + } + escapes = false; + fullyConsumes = true; + } + // TODO other GC operations } checker; checker.allocation = allocation; @@ -776,6 +796,210 @@ struct Struct2Local : PostWalker { } }; +// An optimizer that handles the rewriting to turn a nonescaping array +// allocation into a struct allocation. Struct2Local can then be run on that +// allocation. +// TODO: As with Struct2Local doing a single rewrite walk at the end (for all +// structs) would be more efficient, but more complex. +struct Array2Struct : PostWalker { + Expression* allocation; + EscapeAnalyzer& analyzer; + Function* func; + Builder builder; + + Array2Struct(Expression* allocation, + EscapeAnalyzer& analyzer, + Function* func, + Module& wasm) + : allocation(allocation), analyzer(analyzer), func(func), builder(wasm) { + + // Build the struct type we need: as many fields as the size of the array, + // all of the same type as the array's element. + numFields = getArrayNewSize(allocation); + auto arrayType = allocation->type.getHeapType(); + auto element = arrayType.getArray().element; + FieldList fields; + for (Index i = 0; i < numFields; i++) { + fields.push_back(element); + } + HeapType structType = Struct(fields); + + // 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 the new expressions we created as reached by the analysis. 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 calling it twice in that case. + noteIsReached(structNew); + noteIsReached(arrayNewReplacement); + + // 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 + // depend on them to be valid. + // + // Note that |reached| contains array.get operations, which are reached in + // the analysis, and so we will update their types if they happen to have + // the array type (which can be the case of an array of arrays). But that is + // fine to do as the array.get is rewritten to a struct.get which is then + // lowered away to locals anyhow. + auto nullArray = Type(arrayType, Nullable); + auto nonNullArray = Type(arrayType, NonNullable); + auto nullStruct = Type(structType, Nullable); + auto nonNullStruct = Type(structType, NonNullable); + for (auto* reached : analyzer.reached) { + // We must check subtyping here because the allocation may be upcast as it + // flows around. If we do see such upcasting then we are refining here and + // must refinalize. + if (Type::isSubType(nullArray, reached->type)) { + if (nullArray != reached->type) { + refinalize = true; + } + reached->type = nullStruct; + } else if (Type::isSubType(nonNullArray, reached->type)) { + if (nonNullArray != reached->type) { + refinalize = true; + } + reached->type = nonNullStruct; + } + } + + // Technically we should also fix up the types of locals as well, but after + // Struct2Local those locals will no longer be used anyhow (the locals hold + // allocations that are removed), so avoid that work (though it makes the + // IR temporarily invalid in between Array2Struct and Struct2Local). + + // Replace the things we need to using the visit* methods. + walk(func->body); + + if (refinalize) { + ReFinalize().walkFunctionInModule(func, &wasm); + } + } + + // In rare cases we may need to refinalize, as with Struct2Local. + bool refinalize = false; + + // The number of slots in the array (which will become the number of fields in + // the struct). + Index numFields; + + // The StructNew that replaces the ArrayNew*. The user of this class can then + // 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) { + replaceCurrent(arrayNewReplacement); + noteCurrentIsReached(); + } + } + + void visitArrayNewFixed(ArrayNewFixed* curr) { + if (curr == allocation) { + replaceCurrent(arrayNewReplacement); + noteCurrentIsReached(); + } + } + + void visitArraySet(ArraySet* curr) { + if (!analyzer.reached.count(curr)) { + return; + } + + // If this is an OOB array.set then we trap. + auto index = getIndex(curr->index); + if (index >= numFields) { + replaceCurrent(builder.makeBlock({builder.makeDrop(curr->ref), + builder.makeDrop(curr->value), + builder.makeUnreachable()})); + // We added an unreachable, and must propagate that type. + refinalize = true; + return; + } + + // Convert the ArraySet into a StructSet. + replaceCurrent(builder.makeStructSet(index, curr->ref, curr->value)); + noteCurrentIsReached(); + } + + void visitArrayGet(ArrayGet* curr) { + if (!analyzer.reached.count(curr)) { + return; + } + + // If this is an OOB array.get then we trap. + auto index = getIndex(curr->index); + if (index >= numFields) { + replaceCurrent(builder.makeSequence(builder.makeDrop(curr->ref), + builder.makeUnreachable())); + // We added an unreachable, and must propagate that type. + refinalize = true; + return; + } + + // Convert the ArrayGet into a StructGet. + replaceCurrent( + builder.makeStructGet(index, curr->ref, curr->type, curr->signed_)); + noteCurrentIsReached(); + } + + // Get the value in an expression we know must contain a constant index. + Index getIndex(Expression* curr) { + return curr->cast()->value.getUnsigned(); + } + + // Inform the analyzer that the current expression (which we just replaced) + // has been reached in its analysis. We are replacing something it reached, + // and want it to consider it as its equivalent. + void noteCurrentIsReached() { noteIsReached(getCurrent()); } + + void noteIsReached(Expression* curr) { analyzer.reached.insert(curr); } + + // Given an ArrayNew or ArrayNewFixed, return the size of the array that is + // being allocated. + Index getArrayNewSize(Expression* allocation) { + if (auto* arrayNew = allocation->dynCast()) { + return getIndex(arrayNew->size); + } else if (auto* arrayNewFixed = allocation->dynCast()) { + return arrayNewFixed->values.size(); + } else { + WASM_UNREACHABLE("bad allocation"); + } + } +}; + // Core Heap2Local optimization that operates on a function: Builds up the data // structures we need (LocalGraph, etc.) that we will use across multiple // analyses of allocations, and then runs those analyses and optimizes where @@ -800,44 +1024,121 @@ struct Heap2Local { // repeated work, see above. std::unordered_set seen; - // All the allocations in the function. - // TODO: Arrays (of constant size) as well, if all element accesses use - // constant indexes. One option might be to first convert such - // nonescaping arrays into structs. - FindAll allocations(func->body); + // Find all the relevant allocations in the function: StructNew, ArrayNew, + // ArrayNewFixed. + struct AllocationFinder : public PostWalker { + std::vector structNews; + std::vector arrayNews; + + void visitStructNew(StructNew* curr) { + // Ignore unreachable allocations that DCE will remove anyhow. + if (curr->type != Type::unreachable) { + structNews.push_back(curr); + } + } + void visitArrayNew(ArrayNew* curr) { + // Only new arrays of fixed size are relevant for us. + if (curr->type != Type::unreachable && isValidSize(curr->size)) { + arrayNews.push_back(curr); + } + } + void visitArrayNewFixed(ArrayNewFixed* curr) { + if (curr->type != Type::unreachable && + isValidSize(curr->values.size())) { + arrayNews.push_back(curr); + } + } + + bool isValidSize(Expression* size) { + // The size of an array is valid if it is constant, and its value is + // valid. + if (auto* c = size->dynCast()) { + return isValidSize(c->value.getUnsigned()); + } + return false; + } - for (auto* allocation : allocations.list) { + bool isValidSize(Index size) { + // Set a reasonable limit on the size here, as valid wasm can contain + // things like (array.new (i32.const -1)) which will likely fail at + // runtime on a VM limitation on array size. We also are converting a + // heap allocation to a stack allocation, which can be noticeable in + // some cases, so to be careful here use a fairly small limit. + return size < 20; + } + } finder; + finder.walk(func->body); + + // First, lower non-escaping arrays into structs. That allows us to handle + // arrays in a single place, and let all the rest of this pass assume we are + // working on structs. We are in fact only optimizing struct-like arrays + // here, that is, arrays of a fixed size and whose items are accessed using + // constant indexes, so they are effectively structs, and turning them into + // such allows uniform handling later. + for (auto* allocation : finder.arrayNews) { // The point of this optimization is to replace heap allocations with // locals, so we must be able to place the data in locals. if (!canHandleAsLocals(allocation->type)) { continue; } + EscapeAnalyzer analyzer( + seen, localGraph, parents, branchTargets, passOptions, wasm); + 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); + } + } + + // Next, process all structNews. + for (auto* allocation : finder.structNews) { + // As above, we must be able to use locals for this data. + if (!canHandleAsLocals(allocation->type)) { + continue; + } + // Check for escaping, noting relevant information as we go. If this does - // not escape, optimize it. + // not escape, optimize it into locals. EscapeAnalyzer analyzer( seen, localGraph, parents, branchTargets, passOptions, wasm); if (!analyzer.escapes(allocation)) { - Struct2Local optimizer(allocation, analyzer, func, wasm); + Struct2Local(allocation, analyzer, func, wasm); } } } + bool canHandleAsLocal(const Field& field) { + if (!TypeUpdating::canHandleAsLocal(field.type)) { + return false; + } + if (field.isPacked()) { + // TODO: support packed fields by adding coercions/truncations. + return false; + } + return true; + } + bool canHandleAsLocals(Type type) { if (type == Type::unreachable) { return false; } - auto& fields = type.getHeapType().getStruct().fields; - for (auto field : fields) { - if (!TypeUpdating::canHandleAsLocal(field.type)) { - return false; - } - if (field.isPacked()) { - // TODO: support packed fields by adding coercions/truncations. - return false; + + auto heapType = type.getHeapType(); + if (heapType.isStruct()) { + auto& fields = heapType.getStruct().fields; + for (auto field : fields) { + if (!canHandleAsLocal(field)) { + return false; + } } + return true; } - return true; + + assert(heapType.isArray()); + return canHandleAsLocal(heapType.getArray().element); } }; diff --git a/test/lit/passes/heap2local.wast b/test/lit/passes/heap2local.wast index be6538a948f..6bdfa0be85f 100644 --- a/test/lit/passes/heap2local.wast +++ b/test/lit/passes/heap2local.wast @@ -1,4 +1,4 @@ -;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. ;; (remove-unused-names allows the pass to see that blocks flow values) ;; RUN: foreach %s %t wasm-opt -all --remove-unused-names --heap2local -S -o - | filecheck %s @@ -7,8 +7,18 @@ ;; CHECK: (type $struct.A (struct (field (mut i32)) (field (mut f64)))) (type $struct.A (struct (field (mut i32)) (field (mut f64)))) + ;; CHECK: (type $1 (func)) + + ;; CHECK: (type $2 (func (result f64))) + ;; CHECK: (type $struct.recursive (struct (field (mut (ref null $struct.recursive))))) + ;; CHECK: (type $4 (func (param (ref null $struct.A)))) + + ;; CHECK: (type $5 (func (result i32))) + + ;; CHECK: (type $6 (func (result anyref))) + ;; CHECK: (type $struct.packed (struct (field (mut i8)))) (type $struct.packed (struct (field (mut i8)))) @@ -18,6 +28,14 @@ (type $struct.nonnullable (struct (field (ref $struct.A)))) + ;; CHECK: (type $8 (func (param i32) (result f64))) + + ;; CHECK: (type $9 (func (param (ref null $struct.recursive)))) + + ;; CHECK: (type $10 (func (param (ref $struct.A)))) + + ;; CHECK: (type $11 (func (param i32))) + ;; CHECK: (func $simple (type $1) ;; CHECK-NEXT: (local $0 i32) ;; CHECK-NEXT: (local $1 f64) @@ -1957,6 +1975,8 @@ (module ;; CHECK: (type $A (sub (struct (field (ref null $A))))) (type $A (sub (struct (field (ref null $A))))) + ;; CHECK: (type $1 (func (result anyref))) + ;; CHECK: (type $B (sub $A (struct (field (ref $A))))) (type $B (sub $A (struct (field (ref $A))))) @@ -2062,6 +2082,8 @@ (type $A (sub (struct (field (mut i32))))) (type $B (sub $A (struct (field (mut i32))))) + ;; CHECK: (type $0 (func)) + ;; CHECK: (func $func (type $0) ;; CHECK-NEXT: (local $0 i32) ;; CHECK-NEXT: (local $1 i32) @@ -2105,6 +2127,10 @@ ;; CHECK: (type $struct (struct (field (mut anyref)))) (type $struct (struct (field (mut anyref)))) + ;; CHECK: (type $1 (func)) + + ;; CHECK: (type $2 (func (result anyref))) + ;; CHECK: (func $multiple-interactions (type $1) ;; CHECK-NEXT: (local $temp (ref $struct)) ;; CHECK-NEXT: (local $1 anyref) @@ -2216,3 +2242,1072 @@ (local.get $temp2) ) ) + +;; Arrays. +(module + ;; CHECK: (type $array (array (mut i32))) + (type $array (array (mut i32))) + + ;; CHECK: (type $1 (struct (field (mut i32)))) + + ;; CHECK: (type $2 (func (result i32))) + + ;; CHECK: (type $3 (func)) + + ;; CHECK: (type $4 (func (param i32) (result i32))) + + ;; CHECK: (type $5 (struct (field (mut i32)) (field (mut i32)))) + + ;; CHECK: (type $6 (func (param 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 $8 (struct )) + + ;; 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: (func $array.new_default (type $3) + ;; 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 nullref) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (i32.const 30) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; 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: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 40) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $array.new_default + (local $temp (ref $array)) + ;; This fixed-size array can be replaced with locals. + (local.set $temp + (array.new_default $array + (i32.const 3) + ) + ) + (array.set $array + (local.get $temp) + (i32.const 0) + (i32.const 10) + ) + (array.set $array + (local.get $temp) + (i32.const 1) + (i32.const 20) + ) + (array.set $array + (local.get $temp) + (i32.const 2) + (i32.const 30) + ) + (drop + (array.get $array + (local.get $temp) + (i32.const 0) + ) + ) + (drop + (array.get $array + (local.get $temp) + (i32.const 1) + ) + ) + (drop + (array.get $array + (local.get $temp) + (i32.const 2) + ) + ) + ;; OOB operations trap at runtime. + (array.set $array + (local.get $temp) + (i32.const 3) + (i32.const 40) + ) + (drop + (array.get $array + (local.get $temp) + (i32.const 3) + ) + ) + ) + + ;; CHECK: (func $array.new (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: (local $4 i32) + ;; CHECK-NEXT: (local $5 i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref null $5)) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (i32.const 1337) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (local.set $4 + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $5 + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (local.get $5) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $array.new (result i32) + (local $temp (ref $array)) + ;; This is also optimizable. + (local.set $temp + (array.new $array + (i32.const 1337) + (i32.const 2) + ) + ) + (array.get $array + (local.get $temp) + (i32.const 1) + ) + ) + + ;; CHECK: (func $array.new_fixed (type $4) (param $x i32) (result i32) + ;; CHECK-NEXT: (local $temp (ref $array)) + ;; CHECK-NEXT: (local $2 i32) + ;; CHECK-NEXT: (local $3 i32) + ;; CHECK-NEXT: (local $4 i32) + ;; CHECK-NEXT: (local $5 i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (local.set $4 + ;; CHECK-NEXT: (call $get-i32) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $5 + ;; CHECK-NEXT: (i32.const 1337) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (local.get $5) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $array.new_fixed (param $x i32) (result i32) + (local $temp (ref $array)) + ;; This is also optimizable. + (local.set $temp + (array.new_fixed $array 2 + (call $get-i32) ;; test side effects in a value + (i32.const 1337) + ) + ) + (array.get $array + (local.get $temp) + (i32.const 0) + ) + ) + + ;; CHECK: (func $array.nonconstant_size (type $4) (param $x i32) (result i32) + ;; CHECK-NEXT: (local $temp (ref $array)) + ;; CHECK-NEXT: (local.set $temp + ;; CHECK-NEXT: (array.new $array + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (array.get $array + ;; CHECK-NEXT: (local.get $temp) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $array.nonconstant_size (param $x i32) (result i32) + (local $temp (ref $array)) + (local.set $temp + (array.new $array + (i32.const 42) + ;; We cannot optimize a nonconstant size. + (local.get $x) + ) + ) + (array.get $array + (local.get $temp) + (i32.const 0) + ) + ) + + ;; CHECK: (func $array.nonconstant_get (type $4) (param $x i32) (result i32) + ;; CHECK-NEXT: (local $temp (ref $array)) + ;; CHECK-NEXT: (local.set $temp + ;; CHECK-NEXT: (array.new $array + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: (i32.const 3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (array.get $array + ;; CHECK-NEXT: (local.get $temp) + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $array.nonconstant_get (param $x i32) (result i32) + (local $temp (ref $array)) + (local.set $temp + (array.new $array + (i32.const 42) + (i32.const 3) + ) + ) + (array.get $array + (local.get $temp) + ;; We cannot optimize a nonconstant get. + (local.get $x) + ) + ) + + ;; CHECK: (func $array.nonconstant_set (type $6) (param $x i32) + ;; CHECK-NEXT: (local $temp (ref $array)) + ;; CHECK-NEXT: (local.set $temp + ;; CHECK-NEXT: (array.new $array + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: (i32.const 3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (array.set $array + ;; CHECK-NEXT: (local.get $temp) + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 100) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $array.nonconstant_set (param $x i32) + (local $temp (ref $array)) + (local.set $temp + (array.new $array + (i32.const 42) + (i32.const 3) + ) + ) + (array.set $array + (local.get $temp) + ;; We cannot optimize a nonconstant set. + (local.get $x) + (i32.const 100) + ) + ) + + ;; CHECK: (func $array.local.super (type $3) + ;; CHECK-NEXT: (local $temp anyref) + ;; CHECK-NEXT: (local $1 i32) + ;; CHECK-NEXT: (local $2 i32) + ;; CHECK-NEXT: (local $3 i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (i32.const 42) + ;; 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: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (i32.const 100) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $array.local.super + ;; This local is a supertype, and the allocation flows through a cast, all + ;; of which we handle. + (local $temp (ref null any)) + (local.set $temp + (array.new $array + (i32.const 42) + (i32.const 1) + ) + ) + (array.set $array + (ref.cast (ref $array) + (local.get $temp) + ) + (i32.const 0) + (i32.const 100) + ) + ) + + ;; CHECK: (func $array.folded (type $2) (result i32) + ;; 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: (local $5 i32) + ;; CHECK-NEXT: (local $6 i32) + ;; CHECK-NEXT: (local $7 i32) + ;; CHECK-NEXT: (local $8 i32) + ;; CHECK-NEXT: (local $9 i32) + ;; CHECK-NEXT: (local $10 i32) + ;; CHECK-NEXT: (local $11 i32) + ;; CHECK-NEXT: (local $12 i32) + ;; CHECK-NEXT: (local $13 i32) + ;; CHECK-NEXT: (local $14 i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref null $7)) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (call $get-i32) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (local.set $8 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $9 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $10 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $11 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $12 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $13 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $14 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (local.get $8) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (local.get $9) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (local.get $10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $4 + ;; CHECK-NEXT: (local.get $11) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $5 + ;; CHECK-NEXT: (local.get $12) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $6 + ;; CHECK-NEXT: (local.get $13) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $7 + ;; CHECK-NEXT: (local.get $14) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $7) + ;; CHECK-NEXT: ) + (func $array.folded (result i32) + ;; Test a form without local.get/set operations. + (array.get $array + (array.new $array + (call $get-i32) + (i32.const 7) + ) + (i32.const 6) + ) + ) + + ;; CHECK: (func $array.folded.multiple (type $3) + ;; 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: (local $5 i32) + ;; CHECK-NEXT: (local $6 i32) + ;; CHECK-NEXT: (local $7 i32) + ;; CHECK-NEXT: (local $8 i32) + ;; CHECK-NEXT: (local $9 i32) + ;; CHECK-NEXT: (local $10 i32) + ;; CHECK-NEXT: (local $11 i32) + ;; CHECK-NEXT: (local $12 i32) + ;; CHECK-NEXT: (local $13 i32) + ;; CHECK-NEXT: (local $14 i32) + ;; CHECK-NEXT: (local $15 i32) + ;; CHECK-NEXT: (local $16 i32) + ;; CHECK-NEXT: (local $17 i32) + ;; CHECK-NEXT: (local $18 i32) + ;; CHECK-NEXT: (local $19 i32) + ;; CHECK-NEXT: (local $20 i32) + ;; CHECK-NEXT: (local $21 i32) + ;; CHECK-NEXT: (local $22 i32) + ;; CHECK-NEXT: (local $23 i32) + ;; CHECK-NEXT: (local $24 i32) + ;; CHECK-NEXT: (local $25 i32) + ;; CHECK-NEXT: (local $26 i32) + ;; CHECK-NEXT: (local $27 i32) + ;; CHECK-NEXT: (local $28 i32) + ;; CHECK-NEXT: (local $29 i32) + ;; CHECK-NEXT: (local $30 i32) + ;; CHECK-NEXT: (local $31 i32) + ;; CHECK-NEXT: (local $32 i32) + ;; CHECK-NEXT: (local $33 i32) + ;; CHECK-NEXT: (local $34 i32) + ;; CHECK-NEXT: (local $35 i32) + ;; CHECK-NEXT: (local $36 i32) + ;; CHECK-NEXT: (local $37 i32) + ;; CHECK-NEXT: (local $38 i32) + ;; CHECK-NEXT: (local $39 i32) + ;; CHECK-NEXT: (local $40 i32) + ;; CHECK-NEXT: (local $41 i32) + ;; CHECK-NEXT: (local $42 i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref null $8)) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (call $get-i32) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref null $1)) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (call $get-i32) + ;; 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: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref null $9)) + ;; CHECK-NEXT: (local.set $4 + ;; CHECK-NEXT: (call $get-i32) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (local.set $24 + ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $25 + ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $26 + ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $27 + ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $28 + ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $29 + ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $30 + ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $31 + ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $32 + ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $33 + ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $34 + ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $35 + ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $36 + ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $37 + ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $38 + ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $39 + ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $40 + ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $41 + ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $42 + ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $5 + ;; CHECK-NEXT: (local.get $24) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $6 + ;; CHECK-NEXT: (local.get $25) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $7 + ;; CHECK-NEXT: (local.get $26) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $8 + ;; CHECK-NEXT: (local.get $27) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $9 + ;; CHECK-NEXT: (local.get $28) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $10 + ;; CHECK-NEXT: (local.get $29) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $11 + ;; CHECK-NEXT: (local.get $30) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $12 + ;; CHECK-NEXT: (local.get $31) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $13 + ;; CHECK-NEXT: (local.get $32) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $14 + ;; CHECK-NEXT: (local.get $33) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $15 + ;; CHECK-NEXT: (local.get $34) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $16 + ;; CHECK-NEXT: (local.get $35) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $17 + ;; CHECK-NEXT: (local.get $36) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $18 + ;; CHECK-NEXT: (local.get $37) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $19 + ;; CHECK-NEXT: (local.get $38) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $20 + ;; CHECK-NEXT: (local.get $39) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $21 + ;; CHECK-NEXT: (local.get $40) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $22 + ;; CHECK-NEXT: (local.get $41) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $23 + ;; CHECK-NEXT: (local.get $42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $11) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (array.get $array + ;; CHECK-NEXT: (array.new $array + ;; CHECK-NEXT: (call $get-i32) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 6) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $array.folded.multiple + ;; We allow sizes 0-19, so these will be optimized. + (drop + (array.new $array + (call $get-i32) + (i32.const 0) + ) + ) + (drop + (array.get $array + (array.new $array + (call $get-i32) + (i32.const 1) + ) + (i32.const 0) + ) + ) + (drop + (array.get $array + (array.new $array + (call $get-i32) + (i32.const 19) + ) + (i32.const 6) + ) + ) + + ;; 20 is too much, however. + (drop + (array.get $array + (array.new $array + (call $get-i32) + (i32.const 20) + ) + (i32.const 6) + ) + ) + ) + + ;; CHECK: (func $array.nested.refinalize.get (type $2) (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $array.nested.refinalize.get (result i32) + ;; The get here is of an index that is out of bounds, and will trap. We + ;; should refinalize so the unreachability is propagated and we do not + ;; error on validation. + (array.get $array + (array.new_default $array + (i32.const 0) + ) + (i32.const 0) + ) + ) + + ;; CHECK: (func $array.nested.refinalize.set (type $3) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $array.nested.refinalize.set + ;; As above, but with a set. + (array.set $array + (array.new_default $array + (i32.const 0) + ) + (i32.const 0) + (i32.const 42) + ) + ) + + ;; 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: (local.set $1 + ;; CHECK-NEXT: (i32.const 1) + ;; 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: (drop + ;; CHECK-NEXT: (block (result (ref null $1)) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref null $1)) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref null $1)) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref null $1)) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $array.flowing.type (result i32) + (local $temp (ref $array)) + ;; The array reference here flows through some places that need to be + ;; updated when we optimize. In particular the blocks' types will change. + (local.set $temp + (array.new $array + (i32.const 1) + (i32.const 1) + ) + ) + (drop + (block $nullable (result (ref null $array)) + (local.get $temp) + ) + ) + (drop + (block $non-nullable (result (ref $array)) + (local.get $temp) + ) + ) + ;; Test supertypes too. + (drop + (block $non-nullable (result (ref array)) + (local.get $temp) + ) + ) + (drop + (block $non-nullable (result (ref null array)) + (local.get $temp) + ) + ) + ;; Read from the array as well. + (array.get $array + (local.get $temp) + (i32.const 0) + ) + ) + + ;; CHECK: (func $get-i32 (type $2) (result i32) + ;; CHECK-NEXT: (i32.const 1337) + ;; CHECK-NEXT: ) + (func $get-i32 (result i32) + ;; Helper for the above. + (i32.const 1337) + ) +) + +;; Arrays with reference values. +(module + ;; CHECK: (type $array (array (ref null $array))) + (type $array (array (ref null $array))) + + + ;; CHECK: (type $1 (struct (field (ref null $array)))) + + ;; CHECK: (type $2 (func)) + + ;; CHECK: (type $3 (struct (field (ref null $array)) (field (ref null $array)) (field (ref null $array)))) + + ;; CHECK: (type $4 (func (result anyref))) + + ;; CHECK: (func $nested (type $2) + ;; CHECK-NEXT: (local $0 (ref null $array)) + ;; CHECK-NEXT: (local $1 (ref null $array)) + ;; CHECK-NEXT: (local $2 (ref null $array)) + ;; CHECK-NEXT: (local $3 (ref null $array)) + ;; CHECK-NEXT: (local $4 (ref null $array)) + ;; 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: (local.set $0 + ;; CHECK-NEXT: (array.new $array + ;; CHECK-NEXT: (array.new_default $array + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (local.set $4 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $5 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $6 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (local.get $5) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (local.get $6) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $nested + ;; Nested array.new operations, all of whom can be optimized away in + ;; principle. We do only a single iteration here for now, which optimizes + ;; away the outermost array.new (see TODO in the pass about iterations). + (drop + (array.new $array + (array.new $array + (array.new_default $array + (i32.const 1) + ) + (i32.const 2) + ) + (i32.const 3) + ) + ) + ) + + ;; CHECK: (func $nested-unreachable (type $2) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block ;; (replaces unreachable ArrayNew we can't emit) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $nested-unreachable + ;; The array.get in the middle is out of bounds, and will cause the outer + ;; array.new to become unreachable. + (drop + (array.new $array + (array.get $array + (array.new_default $array + (i32.const 0) + ) + (i32.const 0) + ) + (i32.const 0) + ) + ) + ) + + ;; CHECK: (func $array.flowing.type (type $4) (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: (local.set $1 + ;; CHECK-NEXT: (ref.null none) + ;; 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: (drop + ;; CHECK-NEXT: (block (result (ref null $1)) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref null $1)) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref null $1)) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref null $1)) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result (ref null $array)) + ;; CHECK-NEXT: (block (result (ref null $array)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $array.flowing.type (result anyref) + (local $temp (ref $array)) + ;; This is similar to $array.flowing.type from above, but now the array's + ;; values are references. + (local.set $temp + (array.new $array + (ref.null $array) + (i32.const 1) + ) + ) + (drop + (block $nullable (result (ref null $array)) + (local.get $temp) + ) + ) + (drop + (block $non-nullable (result (ref $array)) + (local.get $temp) + ) + ) + ;; Test supertypes too. + (drop + (block $non-nullable (result (ref array)) + (local.get $temp) + ) + ) + (drop + (block $non-nullable (result (ref null array)) + (local.get $temp) + ) + ) + ;; This block's type should end up valid. In particular this test checks + ;; that we do not get confused by the array's type, which we rewrite to the + ;; struct type in Array2Struct - this array.get's type is the array type, + ;; but only because the value we read is the array type, and not because the + ;; allocation reaches here. + (block (result anyref) + (array.get $array + (local.get $temp) + (i32.const 0) + ) + ) + ) +)