summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas Lively <tlively@google.com>2023-11-16 00:56:39 +0100
committerGitHub <noreply@github.com>2023-11-15 15:56:39 -0800
commitbf7635728e80ddda845e0b893b775e75a154e48e (patch)
tree78ab40302d5a45e748b7e328eff0df3a125a41cc
parent20fe882b47c4b2570c70b2f9d82189c5b2144d03 (diff)
downloadbinaryen-bf7635728e80ddda845e0b893b775e75a154e48e.tar.gz
binaryen-bf7635728e80ddda845e0b893b775e75a154e48e.tar.bz2
binaryen-bf7635728e80ddda845e0b893b775e75a154e48e.zip
Implement more TypeGeneralizing transfer functions (#6118)
Finish the transfer functions for all expressions except for string instructions, exception handling instructions, tuple instructions, and branch instructions that carry values. The latter require more work in the CFG builder because dropping the extra stack values happens after the branch but before the target block.
-rw-r--r--src/passes/TypeGeneralizing.cpp589
-rw-r--r--src/wasm/wasm-validator.cpp26
-rw-r--r--test/lit/passes/type-generalizing.wast1335
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)
+ )
+ )
)