diff options
-rw-r--r-- | src/passes/TypeGeneralizing.cpp | 589 | ||||
-rw-r--r-- | src/wasm/wasm-validator.cpp | 26 | ||||
-rw-r--r-- | test/lit/passes/type-generalizing.wast | 1335 |
3 files changed, 1854 insertions, 96 deletions
diff --git a/src/passes/TypeGeneralizing.cpp b/src/passes/TypeGeneralizing.cpp index 0d811aa52..6b9df6510 100644 --- a/src/passes/TypeGeneralizing.cpp +++ b/src/passes/TypeGeneralizing.cpp @@ -250,7 +250,8 @@ struct TransferFn : OverriddenVisitor<TransferFn> { void visitFunctionExit() { // We cannot change the types of results. Push a requirement that the stack // end up with the correct type. - if (auto result = func->getResults(); result.isRef()) { + auto result = func->getResults(); + if (result.isRef()) { push(result); } } @@ -281,26 +282,96 @@ struct TransferFn : OverriddenVisitor<TransferFn> { void visitBlock(Block* curr) {} void visitIf(If* curr) {} void visitLoop(Loop* curr) {} + void visitBreak(Break* curr) { - // TODO: pop extra elements off stack, keeping only those at the top that - // will be sent along. - WASM_UNREACHABLE("TODO"); + if (curr->condition) { + // `br_if` pops everything but the sent value off the stack if the branch + // is taken, but if the branch is not taken, it only pops the condition. + // We must therefore propagate all requirements from the fallthrough + // successor but only the requirements for the sent value, if any, from + // the branch successor. We don't have any way to differentiate between + // requirements received from the two successors, however, so we cannot + // yet do anything correct here! + // + // Here is a sample program that would break if we tried to conservatively + // preserve the join of the requirements from both successors: + // + // (module + // (func $func_any (param funcref anyref) + // (unreachable) + // ) + // + // (func $extern_any-any (param externref anyref) (result anyref) + // (unreachable) + // ) + // + // (func $br-if-bad + // (local $bang externref) + // (call $func_any ;; 2. Requires [func, any] + // (ref.null nofunc) + // (block $l (result anyref) + // (call $extern_any-any ;; 1. Requires [extern, any] + // (local.get $bang) + // (br_if $l ;; 3. After join, requires [unreachable, any] + // (ref.null none) + // (i32.const 0) + // ) + // ) + // ) + // ) + // ) + // ) + // + // To fix this, we need to insert an extra basic block encompassing the + // liminal space between where the br_if determines it should take the + // branch and when control arrives at the branch target. This is when the + // extra values are popped off the stack. + WASM_UNREACHABLE("TODO"); + } else { + // `br` pops everything but the sent value off the stack, so do not + // require anything of values on the stack except for that sent value, if + // it exists. + if (curr->value && curr->value->type.isRef()) { + auto type = pop(); + clearStack(); + push(type); + } else { + // No sent value. Do not require anything. + clearStack(); + } + } } void visitSwitch(Switch* curr) { - // TODO: pop extra elements off stack, keeping only those at the top that - // will be sent along. - WASM_UNREACHABLE("TODO"); + // Just like `br`, do not require anything of the values on the stack except + // for the sent value, if it exists. + if (curr->value && curr->value->type.isRef()) { + auto type = pop(); + clearStack(); + push(type); + } else { + clearStack(); + } + } + + template<typename T> void handleCall(T* curr, Type params) { + if (curr->type.isRef()) { + pop(); + } + for (auto param : params) { + // Cannot generalize beyond param types without interprocedural analysis. + if (param.isRef()) { + push(param); + } + } } void visitCall(Call* curr) { - // TODO: pop ref types from results, push ref types from params - WASM_UNREACHABLE("TODO"); + handleCall(curr, wasm.getFunction(curr->target)->getParams()); } void visitCallIndirect(CallIndirect* curr) { - // TODO: pop ref types from results, push ref types from params - WASM_UNREACHABLE("TODO"); + handleCall(curr, curr->heapType.getSignature().params); } void visitLocalGet(LocalGet* curr) { @@ -323,81 +394,469 @@ struct TransferFn : OverriddenVisitor<TransferFn> { push(getLocal(curr->index)); } - void visitGlobalGet(GlobalGet* curr) { WASM_UNREACHABLE("TODO"); } - void visitGlobalSet(GlobalSet* curr) { WASM_UNREACHABLE("TODO"); } - void visitLoad(Load* curr) { WASM_UNREACHABLE("TODO"); } - void visitStore(Store* curr) { WASM_UNREACHABLE("TODO"); } - void visitAtomicRMW(AtomicRMW* curr) { WASM_UNREACHABLE("TODO"); } - void visitAtomicCmpxchg(AtomicCmpxchg* curr) { WASM_UNREACHABLE("TODO"); } - void visitAtomicWait(AtomicWait* curr) { WASM_UNREACHABLE("TODO"); } - void visitAtomicNotify(AtomicNotify* curr) { WASM_UNREACHABLE("TODO"); } - void visitAtomicFence(AtomicFence* curr) { WASM_UNREACHABLE("TODO"); } - void visitSIMDExtract(SIMDExtract* curr) { WASM_UNREACHABLE("TODO"); } - void visitSIMDReplace(SIMDReplace* curr) { WASM_UNREACHABLE("TODO"); } - void visitSIMDShuffle(SIMDShuffle* curr) { WASM_UNREACHABLE("TODO"); } - void visitSIMDTernary(SIMDTernary* curr) { WASM_UNREACHABLE("TODO"); } - void visitSIMDShift(SIMDShift* curr) { WASM_UNREACHABLE("TODO"); } - void visitSIMDLoad(SIMDLoad* curr) { WASM_UNREACHABLE("TODO"); } - void visitSIMDLoadStoreLane(SIMDLoadStoreLane* curr) { - WASM_UNREACHABLE("TODO"); + void visitGlobalGet(GlobalGet* curr) { + if (curr->type.isRef()) { + // Cannot generalize globals without interprocedural analysis. + pop(); + } + } + + void visitGlobalSet(GlobalSet* curr) { + auto type = wasm.getGlobal(curr->name)->type; + if (type.isRef()) { + // Cannot generalize globals without interprocedural analysis. + push(type); + } } - void visitMemoryInit(MemoryInit* curr) { WASM_UNREACHABLE("TODO"); } - void visitDataDrop(DataDrop* curr) { WASM_UNREACHABLE("TODO"); } - void visitMemoryCopy(MemoryCopy* curr) { WASM_UNREACHABLE("TODO"); } - void visitMemoryFill(MemoryFill* curr) { WASM_UNREACHABLE("TODO"); } + + void visitLoad(Load* curr) {} + void visitStore(Store* curr) {} + void visitAtomicRMW(AtomicRMW* curr) {} + void visitAtomicCmpxchg(AtomicCmpxchg* curr) {} + void visitAtomicWait(AtomicWait* curr) {} + void visitAtomicNotify(AtomicNotify* curr) {} + void visitAtomicFence(AtomicFence* curr) {} + void visitSIMDExtract(SIMDExtract* curr) {} + void visitSIMDReplace(SIMDReplace* curr) {} + void visitSIMDShuffle(SIMDShuffle* curr) {} + void visitSIMDTernary(SIMDTernary* curr) {} + void visitSIMDShift(SIMDShift* curr) {} + void visitSIMDLoad(SIMDLoad* curr) {} + void visitSIMDLoadStoreLane(SIMDLoadStoreLane* curr) {} + void visitMemoryInit(MemoryInit* curr) {} + void visitDataDrop(DataDrop* curr) {} + void visitMemoryCopy(MemoryCopy* curr) {} + void visitMemoryFill(MemoryFill* curr) {} void visitConst(Const* curr) {} void visitUnary(Unary* curr) {} void visitBinary(Binary* curr) {} - void visitSelect(Select* curr) { WASM_UNREACHABLE("TODO"); } + + void visitSelect(Select* curr) { + if (curr->type.isRef()) { + // The inputs may be as general as the output. + auto type = pop(); + push(type); + push(type); + } + } + void visitDrop(Drop* curr) { if (curr->type.isRef()) { pop(); } } - void visitReturn(Return* curr) { WASM_UNREACHABLE("TODO"); } - void visitMemorySize(MemorySize* curr) { WASM_UNREACHABLE("TODO"); } - void visitMemoryGrow(MemoryGrow* curr) { WASM_UNREACHABLE("TODO"); } + + // This is handled by propagating the stack backward from the exit block. + void visitReturn(Return* curr) {} + + void visitMemorySize(MemorySize* curr) {} + void visitMemoryGrow(MemoryGrow* curr) {} + void visitUnreachable(Unreachable* curr) { // Require nothing about values flowing into an unreachable. clearStack(); } + void visitPop(Pop* curr) { WASM_UNREACHABLE("TODO"); } - void visitRefNull(RefNull* curr) { WASM_UNREACHABLE("TODO"); } - void visitRefIsNull(RefIsNull* curr) { WASM_UNREACHABLE("TODO"); } - void visitRefFunc(RefFunc* curr) { WASM_UNREACHABLE("TODO"); } - void visitRefEq(RefEq* curr) { WASM_UNREACHABLE("TODO"); } - void visitTableGet(TableGet* curr) { WASM_UNREACHABLE("TODO"); } - void visitTableSet(TableSet* curr) { WASM_UNREACHABLE("TODO"); } - void visitTableSize(TableSize* curr) { WASM_UNREACHABLE("TODO"); } - void visitTableGrow(TableGrow* curr) { WASM_UNREACHABLE("TODO"); } - void visitTableFill(TableFill* curr) { WASM_UNREACHABLE("TODO"); } - void visitTableCopy(TableCopy* curr) { WASM_UNREACHABLE("TODO"); } + + void visitRefNull(RefNull* curr) { pop(); } + + void visitRefIsNull(RefIsNull* curr) { + // ref.is_null works on any reference type, so do not impose any + // constraints. We still need to push something, so push bottom. + push(Type::none); + } + + void visitRefFunc(RefFunc* curr) { pop(); } + + void visitRefEq(RefEq* curr) { + // Both operands must be eqref. + auto eqref = Type(HeapType::eq, Nullable); + push(eqref); + push(eqref); + } + + void visitTableGet(TableGet* curr) { + // Cannot generalize table types yet. + pop(); + } + + void visitTableSet(TableSet* curr) { + // Cannot generalize table types yet. + push(wasm.getTable(curr->table)->type); + } + + void visitTableSize(TableSize* curr) {} + void visitTableGrow(TableGrow* curr) {} + + void visitTableFill(TableFill* curr) { + // Cannot generalize table types yet. + push(wasm.getTable(curr->table)->type); + } + + void visitTableCopy(TableCopy* curr) { + // Cannot generalize table types yet. + } + void visitTry(Try* curr) { WASM_UNREACHABLE("TODO"); } void visitThrow(Throw* curr) { WASM_UNREACHABLE("TODO"); } void visitRethrow(Rethrow* curr) { WASM_UNREACHABLE("TODO"); } void visitTupleMake(TupleMake* curr) { WASM_UNREACHABLE("TODO"); } void visitTupleExtract(TupleExtract* curr) { WASM_UNREACHABLE("TODO"); } + void visitRefI31(RefI31* curr) { pop(); } void visitI31Get(I31Get* curr) { push(Type(HeapType::i31, Nullable)); } - void visitCallRef(CallRef* curr) { WASM_UNREACHABLE("TODO"); } - void visitRefTest(RefTest* curr) { WASM_UNREACHABLE("TODO"); } - void visitRefCast(RefCast* curr) { WASM_UNREACHABLE("TODO"); } - void visitBrOn(BrOn* curr) { WASM_UNREACHABLE("TODO"); } - void visitStructNew(StructNew* curr) { WASM_UNREACHABLE("TODO"); } - void visitStructGet(StructGet* curr) { WASM_UNREACHABLE("TODO"); } - void visitStructSet(StructSet* curr) { WASM_UNREACHABLE("TODO"); } - void visitArrayNew(ArrayNew* curr) { WASM_UNREACHABLE("TODO"); } - void visitArrayNewData(ArrayNewData* curr) { WASM_UNREACHABLE("TODO"); } - void visitArrayNewElem(ArrayNewElem* curr) { WASM_UNREACHABLE("TODO"); } - void visitArrayNewFixed(ArrayNewFixed* curr) { WASM_UNREACHABLE("TODO"); } - void visitArrayGet(ArrayGet* curr) { WASM_UNREACHABLE("TODO"); } - void visitArraySet(ArraySet* curr) { WASM_UNREACHABLE("TODO"); } - void visitArrayLen(ArrayLen* curr) { WASM_UNREACHABLE("TODO"); } - void visitArrayCopy(ArrayCopy* curr) { WASM_UNREACHABLE("TODO"); } - void visitArrayFill(ArrayFill* curr) { WASM_UNREACHABLE("TODO"); } - void visitArrayInitData(ArrayInitData* curr) { WASM_UNREACHABLE("TODO"); } - void visitArrayInitElem(ArrayInitElem* curr) { WASM_UNREACHABLE("TODO"); } - void visitRefAs(RefAs* curr) { WASM_UNREACHABLE("TODO"); } + + void visitCallRef(CallRef* curr) { + auto sigType = curr->target->type.getHeapType(); + if (sigType.isBottom()) { + // This will be emitted as an unreachable, so impose no requirements on + // the arguments, but do require that the target continue to have bottom + // type. + clearStack(); + push(Type(HeapType::nofunc, Nullable)); + return; + } + + auto sig = sigType.getSignature(); + auto numParams = sig.params.size(); + std::optional<Type> resultReq; + if (sig.results.isRef()) { + resultReq = pop(); + } + + // We have a choice here: We can either try to generalize the type of the + // incoming function reference or the type of the incoming function + // arguments. Because function parameters are contravariant, generalizing + // the function type inhibits generalizing the arguments and vice versa. + // Attempt to split the difference by generalizing the function type only as + // much as we can without imposing stronger requirements on the arguments. + auto targetReq = sigType; + while (true) { + auto candidateReq = targetReq.getDeclaredSuperType(); + if (!candidateReq) { + // There is no more general type we can require. + break; + } + + auto candidateSig = candidateReq->getSignature(); + + if (resultReq && *resultReq != candidateSig.results && + Type::isSubType(*resultReq, candidateSig.results)) { + // Generalizing further would violate the requirement on the result + // type. + break; + } + + for (size_t i = 0; i < numParams; ++i) { + if (candidateSig.params[i] != sig.params[i]) { + // Generalizing further would restrict how much we could generalize + // this argument, so we choose not to generalize futher. + // TODO: Experiment with making the opposite choice. + goto done; + } + } + + // We can generalize. + targetReq = *candidateReq; + } + done: + + // Push the new requirements for the parameters. + auto targetSig = targetReq.getSignature(); + for (auto param : targetSig.params) { + if (param.isRef()) { + push(param); + } + } + // The new requirement for the call target. + push(Type(targetReq, Nullable)); + } + + void visitRefTest(RefTest* curr) { + // Do not require anything of the input. + push(Type::none); + } + + void visitRefCast(RefCast* curr) { + // We do not have to require anything of the input, and not doing so might + // allow us generalize the output of previous casts enough that they can be + // optimized out. On the other hand, allowing the input to this cast to be + // generalized might prevent us from optimizing this cast out, so this is + // not a clear-cut decision. For now, leave the input unconstrained for + // simplicity. TODO: Experiment with requiring the LUB of the output + // requirement and the current input instead. + pop(); + push(Type::none); + } + + void visitBrOn(BrOn* curr) { + // Like br_if, these instructions do different things to the stack depending + // on whether the branch is taken or not. For branches that drop the tested + // value, we need to push a requirement for that value, but for branches + // that propagate the tested value, we need to propagate the existing + // requirement instead. Like br_if, these instructions will require extra + // basic blocks on the branches that drop values. + WASM_UNREACHABLE("TODO"); + } + + void visitStructNew(StructNew* curr) { + // We cannot yet generalize allocations. Push requirements for the types + // needed to initialize the struct. + pop(); + if (!curr->isWithDefault()) { + auto type = curr->type.getHeapType(); + for (const auto& field : type.getStruct().fields) { + if (field.type.isRef()) { + push(field.type); + } + } + } + } + + HeapType + generalizeStructType(HeapType type, + Index index, + std::optional<Type> reqFieldType = std::nullopt) { + // Find the most general struct type for which this access could be valid, + // i.e. the most general supertype that still has a field at the given index + // where the field is a subtype of the required type, if any. + while (true) { + auto candidateType = type.getDeclaredSuperType(); + if (!candidateType) { + // Cannot get any more general. + break; + } + const auto& candidateFields = candidateType->getStruct().fields; + if (candidateFields.size() <= index) { + // Cannot get any more general and still have a field at the necessary + // index. + break; + } + if (reqFieldType) { + auto candidateFieldType = candidateFields[index].type; + if (candidateFieldType != *reqFieldType && + Type::isSubType(*reqFieldType, candidateFieldType)) { + // Cannot generalize without violating the requirements on the field. + break; + } + } + type = *candidateType; + } + return type; + } + + void visitStructGet(StructGet* curr) { + auto type = curr->ref->type.getHeapType(); + if (type.isBottom()) { + // This will be emitted as unreachable. Do not require anything of the + // input, except that the ref remain bottom. + clearStack(); + push(Type(HeapType::none, Nullable)); + return; + } + std::optional<Type> reqFieldType; + if (curr->type.isRef()) { + reqFieldType = pop(); + } + auto generalized = generalizeStructType(type, curr->index, reqFieldType); + push(Type(generalized, Nullable)); + } + + void visitStructSet(StructSet* curr) { + auto type = curr->ref->type.getHeapType(); + if (type.isBottom()) { + // This will be emitted as unreachable. Do not require anything of the + // input except that the ref remain bottom. + clearStack(); + push(Type(HeapType::none, Nullable)); + if (curr->value->type.isRef()) { + push(Type::none); + } + return; + } + auto generalized = generalizeStructType(type, curr->index); + push(Type(generalized, Nullable)); + push(generalized.getStruct().fields[curr->index].type); + } + + void visitArrayNew(ArrayNew* curr) { + // We cannot yet generalize allocations. Push a requirement for the + // reference type needed to initialize the array, if any. + pop(); + if (!curr->isWithDefault()) { + auto type = curr->type.getHeapType(); + auto fieldType = type.getArray().element.type; + if (fieldType.isRef()) { + push(fieldType); + } + } + } + + void visitArrayNewData(ArrayNewData* curr) { + // We cannot yet generalize allocations. + pop(); + } + + void visitArrayNewElem(ArrayNewElem* curr) { + // We cannot yet generalize allocations or tables. + pop(); + } + + void visitArrayNewFixed(ArrayNewFixed* curr) { + // We cannot yet generalize allocations. Push a requirements for the + // reference type needed to initialize the array, if any. + pop(); + auto type = curr->type.getHeapType(); + auto fieldType = type.getArray().element.type; + if (fieldType.isRef()) { + for (size_t i = 0, n = curr->values.size(); i < n; ++i) { + push(fieldType); + } + } + } + + HeapType + generalizeArrayType(HeapType type, + std::optional<Type> reqFieldType = std::nullopt) { + // Find the most general array type for which this access could be valid. + while (true) { + auto candidateType = type.getDeclaredSuperType(); + if (!candidateType) { + // Cannot get any more general. + break; + } + if (reqFieldType) { + auto candidateFieldType = candidateType->getArray().element.type; + if (candidateFieldType != *reqFieldType && + Type::isSubType(*reqFieldType, candidateFieldType)) { + // Cannot generalize without violating requirements on the field. + break; + } + } + type = *candidateType; + } + return type; + } + + void visitArrayGet(ArrayGet* curr) { + auto type = curr->ref->type.getHeapType(); + if (type.isBottom()) { + // This will be emitted as unreachable. Do not require anything of the + // input, except that the ref remain bottom. + clearStack(); + push(Type(HeapType::none, Nullable)); + return; + } + std::optional<Type> reqFieldType; + if (curr->type.isRef()) { + reqFieldType = pop(); + } + auto generalized = generalizeArrayType(type, reqFieldType); + push(Type(generalized, Nullable)); + } + + void visitArraySet(ArraySet* curr) { + auto type = curr->ref->type.getHeapType(); + if (type.isBottom()) { + // This will be emitted as unreachable. Do not require anything of the + // input, except that the ref remain bottom. + clearStack(); + push(Type(HeapType::none, Nullable)); + if (curr->value->type.isRef()) { + push(Type::none); + } + return; + } + auto generalized = generalizeArrayType(type); + push(Type(generalized, Nullable)); + auto elemType = generalized.getArray().element.type; + if (elemType.isRef()) { + push(elemType); + } + } + + void visitArrayLen(ArrayLen* curr) { + // The input must be an array. + push(Type(HeapType::array, Nullable)); + } + + void visitArrayCopy(ArrayCopy* curr) { + auto destType = curr->destRef->type.getHeapType(); + auto srcType = curr->srcRef->type.getHeapType(); + if (destType.isBottom() || srcType.isBottom()) { + // This will be emitted as unreachable. Do not require anything of the + // input, exept that the bottom refs remain bottom. + clearStack(); + auto nullref = Type(HeapType::none, Nullable); + push(destType.isBottom() ? nullref : Type::none); + push(srcType.isBottom() ? nullref : Type::none); + return; + } + // Model the copy as a get + set. + ArraySet set; + set.ref = curr->destRef; + set.index = nullptr; + set.value = nullptr; + visitArraySet(&set); + ArrayGet get; + get.ref = curr->srcRef; + get.index = nullptr; + get.type = srcType.getArray().element.type; + visitArrayGet(&get); + } + + void visitArrayFill(ArrayFill* curr) { + // Model the fill as a set. + ArraySet set; + set.ref = curr->ref; + set.value = curr->value; + visitArraySet(&set); + } + + void visitArrayInitData(ArrayInitData* curr) { + auto type = curr->ref->type.getHeapType(); + if (type.isBottom()) { + // This will be emitted as unreachable. Do not require anything of the + // input, except that the ref remain bottom. + clearStack(); + push(Type(HeapType::none, Nullable)); + return; + } + auto generalized = generalizeArrayType(type); + push(Type(generalized, Nullable)); + } + + void visitArrayInitElem(ArrayInitElem* curr) { + auto type = curr->ref->type.getHeapType(); + if (type.isBottom()) { + // This will be emitted as unreachable. Do not require anything of the + // input, except that the ref remain bottom. + clearStack(); + push(Type(HeapType::none, Nullable)); + return; + } + auto generalized = generalizeArrayType(type); + push(Type(generalized, Nullable)); + // Cannot yet generalize table types. + } + + void visitRefAs(RefAs* curr) { + auto type = pop(); + switch (curr->op) { + case RefAsNonNull: + push(Type(type.getHeapType(), Nullable)); + return; + case ExternInternalize: + push(Type(HeapType::ext, type.getNullability())); + return; + case ExternExternalize: + push(Type(HeapType::any, type.getNullability())); + return; + } + WASM_UNREACHABLE("unexpected op"); + } + void visitStringNew(StringNew* curr) { WASM_UNREACHABLE("TODO"); } void visitStringConst(StringConst* curr) { WASM_UNREACHABLE("TODO"); } void visitStringMeasure(StringMeasure* curr) { WASM_UNREACHABLE("TODO"); } diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index 68d1f786d..cb54e1597 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -2967,28 +2967,30 @@ void FunctionValidator::visitArrayCopy(ArrayCopy* curr) { if (curr->type == Type::unreachable) { return; } - if (!shouldBeSubType(curr->srcRef->type, - Type(HeapType::array, Nullable), - curr, - "array.copy source should be an array reference")) { + if (!shouldBeTrue(curr->srcRef->type.isRef(), + curr, + "array.copy source should be a reference")) { return; } - if (!shouldBeSubType(curr->destRef->type, - Type(HeapType::array, Nullable), - curr, - "array.copy destination should be an array reference")) { + if (!shouldBeTrue(curr->destRef->type.isRef(), + curr, + "array.copy destination should be a reference")) { return; } auto srcHeapType = curr->srcRef->type.getHeapType(); auto destHeapType = curr->destRef->type.getHeapType(); - if (srcHeapType == HeapType::none || - !shouldBeTrue(srcHeapType.isArray(), + // Normally both types need to be references to specifc arrays, but if either + // of the types are bottom, we don't further constrain the other at all + // because this will be emitted as an unreachable. + if (srcHeapType.isBottom() || destHeapType.isBottom()) { + return; + } + if (!shouldBeTrue(srcHeapType.isArray(), curr, "array.copy source should be an array reference")) { return; } - if (destHeapType == HeapType::none || - !shouldBeTrue(destHeapType.isArray(), + if (!shouldBeTrue(destHeapType.isArray(), curr, "array.copy destination should be an array reference")) { return; diff --git a/test/lit/passes/type-generalizing.wast b/test/lit/passes/type-generalizing.wast index fed327727..c874cc76c 100644 --- a/test/lit/passes/type-generalizing.wast +++ b/test/lit/passes/type-generalizing.wast @@ -3,20 +3,54 @@ ;; RUN: foreach %s %t wasm-opt --experimental-type-generalizing -all -S -o - | filecheck %s (module + ;; CHECK: (type $void (func)) + (type $void (func)) - ;; CHECK: (type $0 (func (result eqref))) + ;; CHECK: (type $1 (func (result eqref))) - ;; CHECK: (type $1 (func)) + ;; CHECK: (type $2 (func (param eqref))) - ;; CHECK: (type $2 (func (param anyref))) + ;; CHECK: (type $3 (func (param eqref anyref))) - ;; CHECK: (type $3 (func (param i31ref))) + ;; CHECK: (type $4 (func (param anyref))) - ;; CHECK: (type $4 (func (param anyref eqref))) + ;; CHECK: (type $5 (func (param i31ref))) - ;; CHECK: (type $5 (func (param eqref))) + ;; CHECK: (type $6 (func (param anyref eqref))) - ;; CHECK: (func $unconstrained (type $1) + ;; CHECK: (type $7 (func (result i32))) + + ;; CHECK: (type $8 (func (result nullref))) + + ;; CHECK: (type $9 (func (result structref))) + + ;; CHECK: (type $10 (func (result (ref eq)))) + + ;; CHECK: (type $11 (func (param (ref noextern)) (result anyref))) + + ;; CHECK: (type $12 (func (param (ref noextern)) (result (ref any)))) + + ;; CHECK: (type $13 (func (result externref))) + + ;; CHECK: (type $14 (func (result (ref extern)))) + + ;; CHECK: (type $15 (func (param anyref anyref))) + + ;; CHECK: (global $global-eq (mut eqref) (ref.null none)) + (global $global-eq (mut eqref) (ref.null none)) + + ;; CHECK: (global $global-i32 (mut i32) (i32.const 0)) + (global $global-i32 (mut i32) (i32.const 0)) + + ;; CHECK: (table $func-table 0 0 funcref) + (table $func-table 0 0 funcref) + + ;; CHECK: (table $eq-table 0 0 eqref) + (table $eq-table 0 0 eqref) + + ;; CHECK: (elem declare func $ref-func) + + ;; CHECK: (func $unconstrained (type $void) ;; CHECK-NEXT: (local $x i32) ;; CHECK-NEXT: (local $y anyref) ;; CHECK-NEXT: (local $z (anyref i32)) @@ -31,7 +65,7 @@ (local $z (anyref i32)) ) - ;; CHECK: (func $implicit-return (type $0) (result eqref) + ;; CHECK: (func $implicit-return (type $1) (result eqref) ;; CHECK-NEXT: (local $var eqref) ;; CHECK-NEXT: (local.get $var) ;; CHECK-NEXT: ) @@ -42,7 +76,7 @@ (local.get $var) ) - ;; CHECK: (func $implicit-return-unreachable (type $0) (result eqref) + ;; CHECK: (func $implicit-return-unreachable (type $1) (result eqref) ;; CHECK-NEXT: (local $var anyref) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) @@ -54,7 +88,7 @@ (local.get $var) ) - ;; CHECK: (func $if (type $0) (result eqref) + ;; CHECK: (func $if (type $1) (result eqref) ;; CHECK-NEXT: (local $x eqref) ;; CHECK-NEXT: (local $y eqref) ;; CHECK-NEXT: (if (result eqref) @@ -63,7 +97,7 @@ ;; CHECK-NEXT: (local.get $y) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - (func $if (result (eqref)) + (func $if (result eqref) (local $x i31ref) (local $y i31ref) (if (result i31ref) @@ -75,7 +109,147 @@ ) ) - ;; CHECK: (func $local-set (type $1) + ;; CHECK: (func $loop (type $1) (result eqref) + ;; CHECK-NEXT: (local $var eqref) + ;; CHECK-NEXT: (loop $loop-in (result eqref) + ;; CHECK-NEXT: (local.get $var) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $loop (result eqref) + (local $var i31ref) + ;; Require that typeof($var) <: eqref. + (loop (result i31ref) + (local.get $var) + ) + ) + + ;; CHECK: (func $br-sent (type $1) (result eqref) + ;; CHECK-NEXT: (local $var1 anyref) + ;; CHECK-NEXT: (local $var2 eqref) + ;; CHECK-NEXT: (block $l (result eqref) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $var1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $l + ;; CHECK-NEXT: (local.get $var2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $br-sent (result eqref) + (local $var1 i31ref) + (local $var2 i31ref) + (block $l (result i31ref) + ;; The call will be DCEd out, but this test and the implementation for `br` + ;; should be forward-compatible with a future where we do not have to run DCE + ;; before this pass. + (call $helper-any_any + ;; No requirements on $var1 + (local.get $var1) + ;; Require that typeof($var2) <: eqref. + (br $l + (local.get $var2) + ) + ) + ) + ) + + ;; CHECK: (func $br-no-sent (type $void) + ;; CHECK-NEXT: (local $var anyref) + ;; CHECK-NEXT: (block $l + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $var) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $l) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $br-no-sent + (local $var i31ref) + (block $l + ;; This call is DCEd out just like in the previous test. + (call $helper-any_any + ;; No requirements on $var + (local.get $var) + (br $l) + ) + ) + ) + + ;; CHECK: (func $br_table-sent (type $3) (param $eq eqref) (param $any anyref) + ;; CHECK-NEXT: (local $var eqref) + ;; CHECK-NEXT: (local.set $eq + ;; CHECK-NEXT: (block $l1 (result eqref) + ;; CHECK-NEXT: (local.set $any + ;; CHECK-NEXT: (block $l2 (result eqref) + ;; CHECK-NEXT: (br_table $l1 $l2 + ;; CHECK-NEXT: (local.get $var) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $br_table-sent (param $eq eqref) (param $any anyref) + (local $var i31ref) + ;; Require typeof($var) <: eqref. + (local.set $eq + (block $l1 (result i31ref) + ;; Require typeof($var) <: anyref. + (local.set $any + (block $l2 (result i31ref) + (br_table $l1 $l2 + (local.get $var) + (i32.const 0) + ) + ) + ) + (unreachable) + ) + ) + ) + + ;; CHECK: (func $br_table-sent-reversed (type $3) (param $eq eqref) (param $any anyref) + ;; CHECK-NEXT: (local $var eqref) + ;; CHECK-NEXT: (local.set $any + ;; CHECK-NEXT: (block $l1 (result eqref) + ;; CHECK-NEXT: (local.set $eq + ;; CHECK-NEXT: (block $l2 (result eqref) + ;; CHECK-NEXT: (br_table $l1 $l2 + ;; CHECK-NEXT: (local.get $var) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $br_table-sent-reversed (param $eq eqref) (param $any anyref) + ;; Same as above, but with the sources of requirements flipped. + (local $var i31ref) + ;; Require typeof($var) <: anyref. + (local.set $any + (block $l1 (result i31ref) + ;; Require typeof($var) <: eqref. + (local.set $eq + (block $l2 (result i31ref) + (br_table $l1 $l2 + (local.get $var) + (i32.const 0) + ) + ) + ) + (unreachable) + ) + ) + ) + + ;; CHECK: (func $local-set (type $void) ;; CHECK-NEXT: (local $var anyref) ;; CHECK-NEXT: (local.set $var ;; CHECK-NEXT: (ref.i31 @@ -94,7 +268,7 @@ ) ) - ;; CHECK: (func $local-get-set (type $2) (param $dest anyref) + ;; CHECK: (func $local-get-set (type $4) (param $dest anyref) ;; CHECK-NEXT: (local $var anyref) ;; CHECK-NEXT: (local.set $dest ;; CHECK-NEXT: (local.get $var) @@ -109,7 +283,7 @@ ) ) - ;; CHECK: (func $local-get-set-unreachable (type $3) (param $dest i31ref) + ;; CHECK: (func $local-get-set-unreachable (type $5) (param $dest i31ref) ;; CHECK-NEXT: (local $var anyref) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) @@ -126,7 +300,7 @@ ) ) - ;; CHECK: (func $local-get-set-join (type $4) (param $dest1 anyref) (param $dest2 eqref) + ;; CHECK: (func $local-get-set-join (type $6) (param $dest1 anyref) (param $dest2 eqref) ;; CHECK-NEXT: (local $var eqref) ;; CHECK-NEXT: (local.set $dest1 ;; CHECK-NEXT: (local.get $var) @@ -148,7 +322,7 @@ ) ) - ;; CHECK: (func $local-get-set-chain (type $0) (result eqref) + ;; CHECK: (func $local-get-set-chain (type $1) (result eqref) ;; CHECK-NEXT: (local $a eqref) ;; CHECK-NEXT: (local $b eqref) ;; CHECK-NEXT: (local $c eqref) @@ -176,7 +350,7 @@ (local.get $c) ) - ;; CHECK: (func $local-get-set-chain-out-of-order (type $0) (result eqref) + ;; CHECK: (func $local-get-set-chain-out-of-order (type $1) (result eqref) ;; CHECK-NEXT: (local $a eqref) ;; CHECK-NEXT: (local $b eqref) ;; CHECK-NEXT: (local $c eqref) @@ -205,7 +379,7 @@ (local.get $c) ) - ;; CHECK: (func $local-tee (type $5) (param $dest eqref) + ;; CHECK: (func $local-tee (type $2) (param $dest eqref) ;; CHECK-NEXT: (local $var eqref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.tee $dest @@ -231,7 +405,7 @@ ) ) - ;; CHECK: (func $i31-get (type $1) + ;; CHECK: (func $i31-get (type $void) ;; CHECK-NEXT: (local $nullable i31ref) ;; CHECK-NEXT: (local $nonnullable i31ref) ;; CHECK-NEXT: (local.set $nonnullable @@ -274,4 +448,1127 @@ ) ) ) + + ;; CHECK: (func $call (type $2) (param $x eqref) + ;; CHECK-NEXT: (local $var eqref) + ;; CHECK-NEXT: (call $call + ;; CHECK-NEXT: (local.get $var) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $call (param $x eqref) + ;; This will be optimized to eqref. + (local $var i31ref) + ;; Requires typeof($var) <: eqref. + (call $call + (local.get $var) + ) + ) + + ;; CHECK: (func $call_indirect (type $void) + ;; CHECK-NEXT: (local $var eqref) + ;; CHECK-NEXT: (call_indirect $func-table (type $2) + ;; CHECK-NEXT: (local.get $var) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $call_indirect + ;; This will be optimized to eqref. + (local $var i31ref) + ;; Requires typeof($var) <: eqref. + (call_indirect (param eqref) + (local.get $var) + (i32.const 0) + ) + ) + + ;; CHECK: (func $global-get (type $void) + ;; CHECK-NEXT: (local $var anyref) + ;; CHECK-NEXT: (local $i32 i32) + ;; CHECK-NEXT: (local.set $var + ;; CHECK-NEXT: (global.get $global-eq) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $i32 + ;; CHECK-NEXT: (global.get $global-i32) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $global-get + (local $var eqref) + (local $i32 i32) + ;; The global type will remain unchanged and the local will be generalized. + (local.set $var + (global.get $global-eq) + ) + ;; Non-reference typed globals are ok, too. + (local.set $i32 + (global.get $global-i32) + ) + ) + + ;; CHECK: (func $global-set (type $void) + ;; CHECK-NEXT: (local $var eqref) + ;; CHECK-NEXT: (local $i32 i32) + ;; CHECK-NEXT: (global.set $global-eq + ;; CHECK-NEXT: (local.get $var) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.set $global-i32 + ;; CHECK-NEXT: (local.get $i32) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $global-set + (local $var i31ref) + (local $i32 i32) + ;; Requires typeof($var) <: eqref. + (global.set $global-eq + (local.get $var) + ) + ;; Non-reference typed globals are ok, too. + (global.set $global-i32 + (local.get $i32) + ) + ) + + ;; CHECK: (func $select (type $1) (result eqref) + ;; CHECK-NEXT: (local $var1 eqref) + ;; CHECK-NEXT: (local $var2 eqref) + ;; CHECK-NEXT: (select (result eqref) + ;; CHECK-NEXT: (local.get $var1) + ;; CHECK-NEXT: (local.get $var2) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $select (result eqref) + ;; Both of these will be generalized to eqref. + (local $var1 i31ref) + (local $var2 i31ref) + ;; Requires typeof($var1) <: eqref and typeof($var2) <: eqref. + (select (result i31ref) + (local.get $var1) + (local.get $var2) + (i32.const 0) + ) + ) + + ;; CHECK: (func $ref-null (type $void) + ;; CHECK-NEXT: (local $var anyref) + ;; CHECK-NEXT: (local.set $var + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ref-null + (local $var i31ref) + ;; No constraints on $var. + (local.set $var + (ref.null none) + ) + ) + + ;; CHECK: (func $ref-is-null (type $void) + ;; CHECK-NEXT: (local $var anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.is_null + ;; CHECK-NEXT: (local.get $var) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ref-is-null + (local $var i31ref) + (drop + ;; No constraints on $var. + (ref.is_null + (local.get $var) + ) + ) + ) + + ;; CHECK: (func $ref-func (type $void) + ;; CHECK-NEXT: (local $var funcref) + ;; CHECK-NEXT: (local.set $var + ;; CHECK-NEXT: (ref.func $ref-func) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ref-func + (local $var (ref null $void)) + ;; No constraints on $var. + (local.set $var + (ref.func $ref-func) + ) + ) + + ;; CHECK: (func $ref-eq (type $void) + ;; CHECK-NEXT: (local $var1 eqref) + ;; CHECK-NEXT: (local $var2 eqref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.eq + ;; CHECK-NEXT: (local.get $var1) + ;; CHECK-NEXT: (local.get $var2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ref-eq + (local $var1 i31ref) + (local $var2 i31ref) + (drop + ;; Require that typeof($var1) <: eqref and that typeof($var2) <: eqref. + (ref.eq + (local.get $var1) + (local.get $var2) + ) + ) + ) + + ;; CHECK: (func $table-get (type $void) + ;; CHECK-NEXT: (local $var anyref) + ;; CHECK-NEXT: (local.set $var + ;; CHECK-NEXT: (table.get $eq-table + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $table-get + (local $var eqref) + ;; No constraints on $var. + (local.set $var + (table.get $eq-table + (i32.const 0) + ) + ) + ) + + ;; CHECK: (func $table-set (type $void) + ;; CHECK-NEXT: (local $var eqref) + ;; CHECK-NEXT: (table.set $eq-table + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (local.get $var) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $table-set + (local $var i31ref) + ;; Require typeof($var) <: eqref. + (table.set $eq-table + (i32.const 0) + (local.get $var) + ) + ) + + ;; CHECK: (func $table-fill (type $void) + ;; CHECK-NEXT: (local $var eqref) + ;; CHECK-NEXT: (table.fill $eq-table + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (local.get $var) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $table-fill + (local $var i31ref) + ;; Require typeof($var) <: eqref. + (table.fill $eq-table + (i32.const 0) + (local.get $var) + (i32.const 0) + ) + ) + + ;; CHECK: (func $ref-test (type $7) (result i32) + ;; CHECK-NEXT: (local $var anyref) + ;; CHECK-NEXT: (ref.test nullref + ;; CHECK-NEXT: (local.get $var) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ref-test (result i32) + (local $var i31ref) + ;; No constraint on $var. + (ref.test structref + (local.get $var) + ) + ) + + ;; CHECK: (func $ref-cast (type $void) + ;; CHECK-NEXT: (local $var anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast i31ref + ;; CHECK-NEXT: (local.get $var) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ref-cast + (local $var i31ref) + ;; No constraint on $var. + (drop + (ref.cast i31ref + (local.get $var) + ) + ) + ) + + ;; CHECK: (func $ref-cast-limited (type $1) (result eqref) + ;; CHECK-NEXT: (local $var anyref) + ;; CHECK-NEXT: (ref.cast i31ref + ;; CHECK-NEXT: (local.get $var) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ref-cast-limited (result eqref) + (local $var i31ref) + ;; No constraint on $var. + ;; TODO: We could eliminate the cast if we did constrain $var. + (ref.cast i31ref + (local.get $var) + ) + ) + + ;; CHECK: (func $ref-cast-more-limited (type $8) (result nullref) + ;; CHECK-NEXT: (local $var anyref) + ;; CHECK-NEXT: (ref.cast nullref + ;; CHECK-NEXT: (local.get $var) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ref-cast-more-limited (result nullref) + (local $var i31ref) + ;; No constraint on $var. + (ref.cast nullref + (local.get $var) + ) + ) + + ;; CHECK: (func $ref-cast-lub (type $9) (result structref) + ;; CHECK-NEXT: (local $var anyref) + ;; CHECK-NEXT: (ref.cast nullref + ;; CHECK-NEXT: (local.get $var) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ref-cast-lub (result structref) + (local $var i31ref) + ;; No constraint on $var. + (ref.cast structref + (local.get $var) + ) + ) + + ;; CHECK: (func $ref-as-non-null (type $10) (result (ref eq)) + ;; CHECK-NEXT: (local $var eqref) + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $var) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ref-as-non-null (result (ref eq)) + (local $var i31ref) + ;; Require that typeof($var) <: eqref. + (ref.as_non_null + (local.get $var) + ) + ) + + ;; CHECK: (func $any-convert-extern-nullable (type $11) (param $x (ref noextern)) (result anyref) + ;; CHECK-NEXT: (local $var externref) + ;; CHECK-NEXT: (local.set $var + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (extern.internalize + ;; CHECK-NEXT: (local.get $var) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $any-convert-extern-nullable (param $x (ref noextern)) (result anyref) + (local $var (ref noextern)) + (local.set $var + (local.get $x) + ) + ;; Require that typeof($var) <: externref. + (extern.internalize + (local.get $var) + ) + ) + + ;; CHECK: (func $any-convert-extern-non-nullable (type $12) (param $x (ref noextern)) (result (ref any)) + ;; CHECK-NEXT: (local $var (ref extern)) + ;; CHECK-NEXT: (local.set $var + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (extern.internalize + ;; CHECK-NEXT: (local.get $var) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $any-convert-extern-non-nullable (param $x (ref noextern)) (result (ref any)) + (local $var (ref noextern)) + (local.set $var + (local.get $x) + ) + ;; Require that typeof($var) <: (ref extern). + (extern.internalize + (local.get $var) + ) + ) + + ;; CHECK: (func $extern-convert-any-nullable (type $13) (result externref) + ;; CHECK-NEXT: (local $var anyref) + ;; CHECK-NEXT: (local.set $var + ;; CHECK-NEXT: (ref.i31 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (extern.externalize + ;; CHECK-NEXT: (local.get $var) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $extern-convert-any-nullable (result externref) + (local $var (ref i31)) + (local.set $var + (i31.new + (i32.const 0) + ) + ) + ;; Require that typeof($var) <: anyref. + (extern.externalize + (local.get $var) + ) + ) + + ;; CHECK: (func $extern-convert-any-non-nullable (type $14) (result (ref extern)) + ;; CHECK-NEXT: (local $var (ref any)) + ;; CHECK-NEXT: (local.set $var + ;; CHECK-NEXT: (ref.i31 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (extern.externalize + ;; CHECK-NEXT: (local.get $var) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $extern-convert-any-non-nullable (result (ref extern)) + (local $var (ref i31)) + (local.set $var + (i31.new + (i32.const 0) + ) + ) + ;; Require that typeof($var) <: anyref. + (extern.externalize + (local.get $var) + ) + ) + + ;; CHECK: (func $helper-any_any (type $15) (param $0 anyref) (param $1 anyref) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $helper-any_any (param anyref anyref) + (unreachable) + ) +) + +(module + ;; CHECK: (type $top (sub (func (param i31ref) (result anyref)))) + (type $top (sub (func (param i31ref) (result anyref)))) + ;; CHECK: (type $mid (sub $top (func (param eqref) (result anyref)))) + (type $mid (sub $top (func (param eqref) (result anyref)))) + ;; CHECK: (type $2 (func (result eqref))) + + ;; CHECK: (type $bot (sub $mid (func (param eqref) (result eqref)))) + (type $bot (sub $mid (func (param eqref) (result eqref)))) + + ;; CHECK: (type $4 (func (result anyref))) + + ;; CHECK: (func $call-ref-params-limited (type $4) (result anyref) + ;; CHECK-NEXT: (local $f (ref null $mid)) + ;; CHECK-NEXT: (local $arg eqref) + ;; CHECK-NEXT: (call_ref $mid + ;; CHECK-NEXT: (local.get $arg) + ;; CHECK-NEXT: (local.get $f) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $call-ref-params-limited (result anyref) + (local $f (ref null $bot)) + (local $arg i31ref) + ;; Require that typeof($f) <: $mid and that typeof($arg) <: eqref. In + ;; principle we could generalize $f up to $top, but then we wouldn't be able + ;; to generalize $arg at all. + (call_ref $bot + (local.get $arg) + (local.get $f) + ) + ) + + ;; CHECK: (func $call-ref-results-limited (type $2) (result eqref) + ;; CHECK-NEXT: (local $f (ref null $bot)) + ;; CHECK-NEXT: (local $arg eqref) + ;; CHECK-NEXT: (call_ref $bot + ;; CHECK-NEXT: (local.get $arg) + ;; CHECK-NEXT: (local.get $f) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $call-ref-results-limited (result eqref) + (local $f (ref null $bot)) + (local $arg i31ref) + ;; Require that typeof($f) <: $bot because anything better would require a + ;; cast on the output. Also require that typeof($arg) <: eqref. + (call_ref $bot + (local.get $arg) + (local.get $f) + ) + ) + + ;; CHECK: (func $call-ref-impossible (type $2) (result eqref) + ;; CHECK-NEXT: (local $f nullfuncref) + ;; CHECK-NEXT: (local $arg anyref) + ;; CHECK-NEXT: (block ;; (replaces something unreachable we can't emit) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $arg) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $f) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $call-ref-impossible (result eqref) + (local $f nullfuncref) + (local $arg i31ref) + ;; Require that typeof($f) <: nullref, but do not constrain $arg because the + ;; call_ref will not be reached. + (call_ref $bot + (local.get $arg) + (local.get $f) + ) + ) +) + +(module + ;; CHECK: (type $top (sub (func (result anyref)))) + (type $top (sub (func (result anyref)))) + (type $mid (sub $top (func (result eqref)))) + (type $bot (sub $mid (func (result i31ref)))) + + ;; CHECK: (type $1 (func (result anyref))) + + ;; CHECK: (func $call-ref-no-limit (type $1) (result anyref) + ;; CHECK-NEXT: (local $f (ref null $top)) + ;; CHECK-NEXT: (call_ref $top + ;; CHECK-NEXT: (local.get $f) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $call-ref-no-limit (result anyref) + (local $f (ref null $bot)) + ;; Require that typeof($f) <: $top because that does not limit us in any way + ;; and we cannot possibly do better. + (call_ref $bot + (local.get $f) + ) + ) +) + +(module + + ;; CHECK: (type $0 (func (result anyref))) + + ;; CHECK: (type $top (sub (struct (field (mut eqref)) (field eqref)))) + (type $top (sub (struct (field (mut eqref)) (field eqref)))) + ;; CHECK: (type $mid (sub $top (struct (field (mut eqref)) (field eqref) (field (mut eqref))))) + (type $mid (sub $top (struct (field (mut eqref)) (field eqref) (field (mut eqref))))) + ;; CHECK: (type $3 (func)) + + ;; CHECK: (type $bot (sub $mid (struct (field (mut eqref)) (field i31ref) (field (mut eqref))))) + (type $bot (sub $mid (struct (field (mut eqref)) (field i31ref) (field (mut eqref))))) + + ;; CHECK: (type $struct (struct (field eqref) (field anyref))) + (type $struct (struct (field eqref) (field anyref))) + + ;; CHECK: (type $6 (func (result i31ref))) + + ;; CHECK: (func $struct-new (type $0) (result anyref) + ;; CHECK-NEXT: (local $var1 eqref) + ;; CHECK-NEXT: (local $var2 anyref) + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (local.get $var1) + ;; CHECK-NEXT: (local.get $var2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $struct-new (result anyref) + (local $var1 i31ref) + (local $var2 i31ref) + ;; Require that typeof($var1) <: eqref and that typeof($var2) <: anyref. + (struct.new $struct + (local.get $var1) + (local.get $var2) + ) + ) + + ;; CHECK: (func $struct-get (type $0) (result anyref) + ;; CHECK-NEXT: (local $var (ref null $top)) + ;; CHECK-NEXT: (struct.get $top 1 + ;; CHECK-NEXT: (local.get $var) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $struct-get (result anyref) + (local $var (ref null $bot)) + ;; Require that typeof($var) <: (ref null $top) because it has a field of the + ;; right type at index 1. + (struct.get $bot 1 + (local.get $var) + ) + ) + + ;; CHECK: (func $struct-get-type (type $6) (result i31ref) + ;; CHECK-NEXT: (local $var (ref null $bot)) + ;; CHECK-NEXT: (struct.get $bot 1 + ;; CHECK-NEXT: (local.get $var) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $struct-get-type (result i31ref) + (local $var (ref null $bot)) + ;; Require that typeof($var) <: (ref null $bot) because further supertypes do + ;; not satisfy the requirement on the result type. + (struct.get $bot 1 + (local.get $var) + ) + ) + + ;; CHECK: (func $struct-get-index (type $0) (result anyref) + ;; CHECK-NEXT: (local $var (ref null $mid)) + ;; CHECK-NEXT: (struct.get $mid 2 + ;; CHECK-NEXT: (local.get $var) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $struct-get-index (result anyref) + (local $var (ref null $bot)) + ;; Require that typeof($var) <: (ref null $mid) because further supertypes do + ;; not have a field at index 2. + (struct.get $bot 2 + (local.get $var) + ) + ) + + ;; CHECK: (func $struct-get-impossible (type $0) (result anyref) + ;; CHECK-NEXT: (local $var nullref) + ;; CHECK-NEXT: (block ;; (replaces something unreachable we can't emit) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $var) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $struct-get-impossible (result anyref) + (local $var nullref) + (struct.get $bot 0 + (local.get $var) + ) + ) + + ;; CHECK: (func $struct-set (type $3) + ;; CHECK-NEXT: (local $ref (ref null $top)) + ;; CHECK-NEXT: (local $val eqref) + ;; CHECK-NEXT: (struct.set $top 0 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: (local.get $val) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $struct-set + (local $ref (ref null $bot)) + (local $val i31ref) + ;; Require that typeof($ref) <: (ref null $top) because it has a field at + ;; index 0 and require that typeof($val) <: eqref because that is the type of + ;; the field. + (struct.set $bot 0 + (local.get $ref) + (local.get $val) + ) + ) + + ;; CHECK: (func $struct-set-index (type $3) + ;; CHECK-NEXT: (local $ref (ref null $mid)) + ;; CHECK-NEXT: (local $val eqref) + ;; CHECK-NEXT: (struct.set $mid 2 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: (local.get $val) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $struct-set-index + (local $ref (ref null $bot)) + (local $val i31ref) + ;; Require that typeof($ref) <: (ref null $mid) because further supertypes do + ;; not have a field at index 2 and require that typeof($val) <: eqref because + ;; that is the type of the field. + (struct.set $bot 2 + (local.get $ref) + (local.get $val) + ) + ) + + ;; CHECK: (func $struct-set-impossible (type $3) + ;; CHECK-NEXT: (local $ref nullref) + ;; CHECK-NEXT: (local $val anyref) + ;; CHECK-NEXT: (block ;; (replaces something unreachable we can't emit) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $val) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $struct-set-impossible + (local $ref nullref) + (local $val nullref) + ;; Require that typeof($ref) <: nullref, but do not constrain $val. + (struct.set $bot 0 + (local.get $ref) + (local.get $val) + ) + ) +) + +(module + ;; CHECK: (type $0 (func)) + + ;; CHECK: (type $super-mut (sub (array (mut eqref)))) + + ;; CHECK: (type $super (sub (array eqref))) + (type $super (sub (array (field eqref)))) + ;; CHECK: (type $3 (func (result anyref))) + + ;; CHECK: (type $mut-bytes (sub (array (mut i8)))) + + ;; CHECK: (type $bytes (sub (array i8))) + + ;; CHECK: (type $sub (sub $super (array i31ref))) + (type $sub (sub $super (array (field i31ref)))) + + (type $super-mut (sub (array (field (mut eqref))))) + (type $sub-mut (sub $super-mut (array (field (mut eqref))))) + + (type $bytes (sub (array i8))) + (type $sub-bytes (sub $bytes (array i8))) + + (type $mut-bytes (sub (array (mut i8)))) + (type $sub-mut-bytes (sub $mut-bytes (array (mut i8)))) + + ;; CHECK: (data $data "") + + ;; CHECK: (elem $elem i31ref) + (elem $elem i31ref) + + (data $data "") + + ;; CHECK: (func $array-new (type $3) (result anyref) + ;; CHECK-NEXT: (local $val eqref) + ;; CHECK-NEXT: (array.new $super + ;; CHECK-NEXT: (local.get $val) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $array-new (result anyref) + (local $val i31ref) + ;; Require that typeof($val) <: eqref. + (array.new $super + (local.get $val) + (i32.const 0) + ) + ) + + ;; CHECK: (func $array-new-data (type $0) + ;; CHECK-NEXT: (local $val anyref) + ;; CHECK-NEXT: (local.set $val + ;; CHECK-NEXT: (array.new_data $bytes $data + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $array-new-data + (local $val arrayref) + ;; No constraint on $val. + (local.set $val + (array.new_data $bytes $data + (i32.const 0) + (i32.const 0) + ) + ) + ) + + ;; CHECK: (func $array-new-elem (type $0) + ;; CHECK-NEXT: (local $val anyref) + ;; CHECK-NEXT: (local.set $val + ;; CHECK-NEXT: (array.new_elem $sub $elem + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $array-new-elem + (local $val arrayref) + ;; No constraint on $val. + (local.set $val + (array.new_elem $sub $elem + (i32.const 0) + (i32.const 0) + ) + ) + ) + + ;; CHECK: (func $array-new-fixed (type $3) (result anyref) + ;; CHECK-NEXT: (local $val1 eqref) + ;; CHECK-NEXT: (local $val2 eqref) + ;; CHECK-NEXT: (array.new_fixed $super 2 + ;; CHECK-NEXT: (local.get $val1) + ;; CHECK-NEXT: (local.get $val2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $array-new-fixed (result anyref) + (local $val1 i31ref) + (local $val2 i31ref) + ;; Require that typeof($val1) <: eqref and that typeof($val2) <: eqref. + (array.new_fixed $super 2 + (local.get $val1) + (local.get $val2) + ) + ) + + ;; CHECK: (func $array-get (type $3) (result anyref) + ;; CHECK-NEXT: (local $val (ref null $super)) + ;; CHECK-NEXT: (array.get $super + ;; CHECK-NEXT: (local.get $val) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $array-get (result anyref) + (local $val (ref null $sub)) + ;; Require that typeof($val) <: (ref null $super). + (array.get $sub + (local.get $val) + (i32.const 0) + ) + ) + + ;; CHECK: (func $array-get-impossible (type $3) (result anyref) + ;; CHECK-NEXT: (local $val nullref) + ;; CHECK-NEXT: (block ;; (replaces something unreachable we can't emit) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $val) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $array-get-impossible (result anyref) + (local $val nullref) + ;; Require that typeof($val) <: nullref. + (array.get $sub + (local.get $val) + (i32.const 0) + ) + ) + + ;; CHECK: (func $array-set (type $0) + ;; CHECK-NEXT: (local $ref (ref null $super-mut)) + ;; CHECK-NEXT: (local $val eqref) + ;; CHECK-NEXT: (array.set $super-mut + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (local.get $val) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $array-set + (local $ref (ref null $sub-mut)) + (local $val i31ref) + ;; Require that typeof($ref) <: (ref null $super-mut) and that typeof($val) <: + ;; eqref. + (array.set $sub-mut + (local.get $ref) + (i32.const 0) + (local.get $val) + ) + ) + + ;; CHECK: (func $array-set-impossible (type $0) + ;; CHECK-NEXT: (local $ref nullref) + ;; CHECK-NEXT: (local $val anyref) + ;; CHECK-NEXT: (block ;; (replaces something unreachable we can't emit) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $val) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $array-set-impossible + (local $ref nullref) + (local $val i31ref) + ;; Require that typeof($ref) <: nullref and do not constrain $ref. + (array.set $sub-mut + (local.get $ref) + (i32.const 0) + (local.get $val) + ) + ) + + ;; CHECK: (func $array-len (type $0) + ;; CHECK-NEXT: (local $ref arrayref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (array.len + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $array-len + (local $ref (ref null $super)) + (drop + ;; Require that typeof($ref) <: arrayref. + (array.len + (local.get $ref) + ) + ) + ) + + ;; CHECK: (func $array-copy-ref (type $0) + ;; CHECK-NEXT: (local $dest (ref null $super-mut)) + ;; CHECK-NEXT: (local $src (ref null $super)) + ;; CHECK-NEXT: (array.copy $super-mut $super + ;; CHECK-NEXT: (local.get $dest) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (local.get $src) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $array-copy-ref + (local $dest (ref null $sub-mut)) + (local $src (ref null $sub)) + ;; Require that typeof($dest) <: $super-mut and that typeof($src) <: $super. + (array.copy $sub-mut $sub + (local.get $dest) + (i32.const 0) + (local.get $src) + (i32.const 0) + (i32.const 0) + ) + ) + + ;; CHECK: (func $array-copy-i8 (type $0) + ;; CHECK-NEXT: (local $dest (ref null $mut-bytes)) + ;; CHECK-NEXT: (local $src (ref null $bytes)) + ;; CHECK-NEXT: (array.copy $mut-bytes $bytes + ;; CHECK-NEXT: (local.get $dest) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (local.get $src) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $array-copy-i8 + ;; Same as above, but now the copied element type is not a ref. + (local $dest (ref null $sub-mut-bytes)) + (local $src (ref null $sub-bytes)) + ;; Require that typeof($dest) <: $mut-bytes and that typeof($src) <: $bytes. + (array.copy $sub-mut-bytes $sub-bytes + (local.get $dest) + (i32.const 0) + (local.get $src) + (i32.const 0) + (i32.const 0) + ) + ) + + ;; CHECK: (func $array-copy-impossible-dest (type $0) + ;; CHECK-NEXT: (local $dest nullref) + ;; CHECK-NEXT: (local $src anyref) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (local.get $dest) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (local.get $src) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $array-copy-impossible-dest + ;; Same as above, but now the dest is bottom. + (local $dest nullref) + (local $src (ref null $sub)) + ;; Require that typeof($dest) <: nullref but do not constrain $src. + (array.copy $sub-mut $sub + (local.get $dest) + (i32.const 0) + (local.get $src) + (i32.const 0) + (i32.const 0) + ) + ) + + ;; CHECK: (func $array-copy-impossible-src (type $0) + ;; CHECK-NEXT: (local $dest anyref) + ;; CHECK-NEXT: (local $src nullref) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (local.get $dest) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (local.get $src) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $array-copy-impossible-src + ;; Same as above, but now the src is bottom instead. + (local $dest (ref null $sub-mut)) + (local $src nullref) + ;; Require that typeof($src) <: nullref but do not constrain $dest. + (array.copy $sub-mut $sub + (local.get $dest) + (i32.const 0) + (local.get $src) + (i32.const 0) + (i32.const 0) + ) + ) + + ;; CHECK: (func $array-copy-impossible-both (type $0) + ;; CHECK-NEXT: (local $dest nullref) + ;; CHECK-NEXT: (local $src nullref) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (local.get $dest) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (local.get $src) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $array-copy-impossible-both + ;; Same as above, but now both src and dest are bottom. + (local $dest nullref) + (local $src nullref) + ;; Do not constrain $src or $dest. + (array.copy $sub-mut $sub + (local.get $dest) + (i32.const 0) + (local.get $src) + (i32.const 0) + (i32.const 0) + ) + ) + + ;; CHECK: (func $array-fill (type $0) + ;; CHECK-NEXT: (local $ref (ref null $super-mut)) + ;; CHECK-NEXT: (local $val eqref) + ;; CHECK-NEXT: (array.fill $super-mut + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (local.get $val) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $array-fill + (local $ref (ref null $sub-mut)) + (local $val i31ref) + ;; Require that typeof($ref) <: (ref null $super-mut) and that typeof($val) <: + ;; eqref. + (array.fill $sub-mut + (local.get $ref) + (i32.const 0) + (local.get $val) + (i32.const 0) + ) + ) + + ;; CHECK: (func $array-fill-impossible (type $0) + ;; CHECK-NEXT: (local $ref nullref) + ;; CHECK-NEXT: (local $val anyref) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (local.get $val) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $array-fill-impossible + (local $ref nullref) + (local $val i31ref) + ;; Require that typeof($ref) <: nullref, but do not constrain $val. + (array.fill $sub-mut + (local.get $ref) + (i32.const 0) + (local.get $val) + (i32.const 0) + ) + ) + + ;; CHECK: (func $array-init-data (type $0) + ;; CHECK-NEXT: (local $ref (ref null $mut-bytes)) + ;; CHECK-NEXT: (array.init_data $mut-bytes $data + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $array-init-data + (local $ref (ref null $sub-mut-bytes)) + ;; Require that typeof($ref) <: (ref null $mut-bytes). + (array.init_data $sub-mut-bytes $data + (local.get $ref) + (i32.const 0) + (i32.const 0) + (i32.const 0) + ) + ) + + ;; CHECK: (func $array-init-data-impossible (type $0) + ;; CHECK-NEXT: (local $ref nullref) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $array-init-data-impossible + (local $ref nullref) + ;; Require that typeof($ref) <: nullref. + (array.init_data $sub-mut-bytes $data + (local.get $ref) + (i32.const 0) + (i32.const 0) + (i32.const 0) + ) + ) + + ;; CHECK: (func $array-init-elem (type $0) + ;; CHECK-NEXT: (local $ref (ref null $super-mut)) + ;; CHECK-NEXT: (array.init_elem $super-mut $elem + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $array-init-elem + (local $ref (ref null $sub-mut)) + ;; Require that typeof($ref) <: (ref null $super-mut). + (array.init_elem $sub-mut $elem + (local.get $ref) + (i32.const 0) + (i32.const 0) + (i32.const 0) + ) + ) + + ;; CHECK: (func $array-init-elem-impossible (type $0) + ;; CHECK-NEXT: (local $ref nullref) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $array-init-elem-impossible + (local $ref nullref) + ;; Require that typeof($ref) <: nullref. + (array.init_elem $sub-mut $elem + (local.get $ref) + (i32.const 0) + (i32.const 0) + (i32.const 0) + ) + ) ) |