From 950784a6cd7e38d7ac3278566fa1978b221b5a58 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 13 Oct 2026 15:07:32 -0700 Subject: [PATCH 01/36] work --- src/ir/subtype-exprs.h | 227 +++++- src/passes/OptimizeInstructions.cpp | 3 + src/passes/TypeGeneralizing.cpp | 1004 ++++-------------------- test/lit/passes/type-generalizing.wast | 100 +-- 4 files changed, 388 insertions(+), 946 deletions(-) diff --git a/src/ir/subtype-exprs.h b/src/ir/subtype-exprs.h index a9168061476..25b716a81aa 100644 --- a/src/ir/subtype-exprs.h +++ b/src/ir/subtype-exprs.h @@ -249,7 +249,10 @@ struct SubtypingDiscoverer : public OverriddenVisitor { self()->getModule()->getTable(curr->table)->type); } void visitTableSize(TableSize* curr) {} - void visitTableGrow(TableGrow* curr) {} + void visitTableGrow(TableGrow* curr) { + self()->noteSubtype(curr->value, + self()->getModule()->getTable(curr->table)->type); + } void visitTableFill(TableFill* curr) { self()->noteSubtype(curr->value, self()->getModule()->getTable(curr->table)->type); @@ -285,8 +288,14 @@ struct SubtypingDiscoverer : public OverriddenVisitor { } } void visitRethrow(Rethrow* curr) {} - void visitThrowRef(ThrowRef* curr) {} - void visitTupleMake(TupleMake* curr) {} + void visitThrowRef(ThrowRef* curr) { + self()->noteNonFlowSubtype(curr->exnref, Type(HeapType::exn, Nullable)); + } + void visitTupleMake(TupleMake* curr) { + for (auto* op : curr->operands) { + self()->noteNonFlowSubtype(op, op->type); + } + } void visitTupleExtract(TupleExtract* curr) {} void visitRefI31(RefI31* curr) {} void visitI31Get(I31Get* curr) { @@ -316,9 +325,10 @@ struct SubtypingDiscoverer : public OverriddenVisitor { // more? etc.), so we do the simple thing here for now of requiring the // target type not generalize. // - // Note that this could be |noteNonFlowSubtype| but since we are comparing - // a type to itself here, that does not matter. - self()->noteSubtype(curr->target, curr->target->type); + if (curr->target->type.isRef()) { + self()->noteNonFlowSubtype( + curr->target, Type(curr->target->type.getHeapType(), Nullable)); + } if (curr->target->type.isSignature()) { handleCall(curr, curr->target->type.getHeapType().getSignature()); @@ -327,9 +337,25 @@ struct SubtypingDiscoverer : public OverriddenVisitor { void visitRefTest(RefTest* curr) { self()->noteCast(curr->ref, curr->castType); } - void visitRefCast(RefCast* curr) { self()->noteCast(curr->ref, curr); } - void visitRefGetDesc(RefGetDesc* curr) {} + void visitRefCast(RefCast* curr) { + self()->noteCast(curr->ref, curr); + if (curr->desc && curr->desc->type.isRef()) { + self()->noteNonFlowSubtype(curr->desc, curr->desc->type.with(Nullable)); + } + } + void visitRefGetDesc(RefGetDesc* curr) { + if (curr->ref->type.isRef()) { + self()->noteNonFlowSubtype( + curr->ref, Type(curr->ref->type.getHeapType(), Nullable)); + } + } void visitBrOn(BrOn* curr) { + if (curr->ref->type.isRef()) { + self()->noteNonFlowSubtype(curr->ref, curr->ref->type); + } + if (curr->desc && curr->desc->type.isRef()) { + self()->noteNonFlowSubtype(curr->desc, curr->desc->type.with(Nullable)); + } switch (curr->op) { case BrOnNull: case BrOnNonNull: @@ -345,7 +371,15 @@ struct SubtypingDiscoverer : public OverriddenVisitor { self()->findBreakTarget(curr->name)); } void visitStructNew(StructNew* curr) { - if (!curr->type.isStruct() || curr->isWithDefault()) { + if (!curr->type.isStruct()) { + return; + } + if (curr->desc) { + auto descType = curr->type.getHeapType().getDescriptorType(); + assert(descType); + self()->noteSubtype(curr->desc, Type(*descType, Nullable, Exact)); + } + if (curr->isWithDefault()) { return; } const auto& fields = curr->type.getHeapType().getStruct().fields; @@ -354,8 +388,17 @@ struct SubtypingDiscoverer : public OverriddenVisitor { self()->noteSubtype(curr->operands[i], fields[i].type); } } - void visitStructGet(StructGet* curr) {} + void visitStructGet(StructGet* curr) { + if (curr->ref->type.isRef()) { + self()->noteNonFlowSubtype( + curr->ref, Type(curr->ref->type.getHeapType(), Nullable)); + } + } void visitStructSet(StructSet* curr) { + if (curr->ref->type.isRef()) { + self()->noteNonFlowSubtype( + curr->ref, Type(curr->ref->type.getHeapType(), Nullable)); + } if (!curr->ref->type.isStruct()) { return; } @@ -363,6 +406,10 @@ struct SubtypingDiscoverer : public OverriddenVisitor { self()->noteSubtype(curr->value, fields[curr->index].type); } void visitStructRMW(StructRMW* curr) { + if (curr->ref->type.isRef()) { + self()->noteNonFlowSubtype( + curr->ref, Type(curr->ref->type.getHeapType(), Nullable)); + } if (!curr->ref->type.isStruct()) { return; } @@ -370,6 +417,10 @@ struct SubtypingDiscoverer : public OverriddenVisitor { self()->noteSubtype(curr->value, fields[curr->index].type); } void visitStructCmpxchg(StructCmpxchg* curr) { + if (curr->ref->type.isRef()) { + self()->noteNonFlowSubtype( + curr->ref, Type(curr->ref->type.getHeapType(), Nullable)); + } if (!curr->ref->type.isStruct()) { return; } @@ -386,6 +437,10 @@ struct SubtypingDiscoverer : public OverriddenVisitor { void visitStructWait(StructWait* curr) { self()->noteSubtype(curr->waitqueue, Type(HeapTypes::sharedWaitqueue, Nullable)); + if (curr->ref->type.isRef()) { + self()->noteNonFlowSubtype( + curr->ref, Type(curr->ref->type.getHeapType(), Nullable)); + } if (!curr->ref->type.isStruct()) { return; } @@ -401,7 +456,7 @@ struct SubtypingDiscoverer : public OverriddenVisitor { self()->noteSubtype(curr->waitqueue, Type(HeapTypes::sharedWaitqueue, Nullable)); } - void visitPublish(Publish* curr) {} + void visitPublish(Publish* curr) { self()->noteSubtype(curr->ref, curr); } void visitArrayNew(ArrayNew* curr) { if (!curr->type.isArray() || curr->isWithDefault()) { return; @@ -428,34 +483,86 @@ struct SubtypingDiscoverer : public OverriddenVisitor { } } - void visitArrayGet(ArrayGet* curr) {} + void visitArrayGet(ArrayGet* curr) { + if (curr->ref->type.isRef()) { + self()->noteNonFlowSubtype( + curr->ref, Type(curr->ref->type.getHeapType(), Nullable)); + } + } void visitArraySet(ArraySet* curr) { + if (curr->ref->type.isRef()) { + self()->noteNonFlowSubtype( + curr->ref, Type(curr->ref->type.getHeapType(), Nullable)); + } if (!curr->ref->type.isArray()) { return; } auto array = curr->ref->type.getHeapType().getArray(); self()->noteSubtype(curr->value, array.element.type); } - void visitArrayLoad(ArrayLoad* curr) {} - void visitArrayStore(ArrayStore* curr) {} - void visitArrayLen(ArrayLen* curr) {} + void visitArrayLoad(ArrayLoad* curr) { + if (curr->ref->type.isRef()) { + self()->noteNonFlowSubtype( + curr->ref, Type(curr->ref->type.getHeapType(), Nullable)); + } + } + void visitArrayStore(ArrayStore* curr) { + if (curr->ref->type.isRef()) { + self()->noteNonFlowSubtype( + curr->ref, Type(curr->ref->type.getHeapType(), Nullable)); + } + } + void visitArrayLen(ArrayLen* curr) { + auto share = Unshared; + if (curr->ref->type.isRef() && curr->ref->type.getHeapType().isShared()) { + share = Shared; + } + self()->noteNonFlowSubtype( + curr->ref, Type(HeapTypes::array.getBasic(share), Nullable)); + } void visitArrayCopy(ArrayCopy* curr) { + if (curr->destRef->type.isNull() || curr->srcRef->type.isNull()) { + if (curr->destRef->type.isNull()) { + self()->noteNonFlowSubtype(curr->destRef, curr->destRef->type); + } + if (curr->srcRef->type.isNull()) { + self()->noteNonFlowSubtype(curr->srcRef, curr->srcRef->type); + } + return; + } if (!curr->srcRef->type.isArray() || !curr->destRef->type.isArray()) { return; } + self()->noteNonFlowSubtype( + curr->destRef, Type(curr->destRef->type.getHeapType(), Nullable)); + self()->noteNonFlowSubtype( + curr->srcRef, Type(curr->srcRef->type.getHeapType(), Nullable)); auto src = curr->srcRef->type.getHeapType().getArray(); auto dest = curr->destRef->type.getHeapType().getArray(); self()->noteSubtype(src.element.type, dest.element.type); } void visitArrayFill(ArrayFill* curr) { + if (curr->ref->type.isRef()) { + self()->noteNonFlowSubtype( + curr->ref, Type(curr->ref->type.getHeapType(), Nullable)); + } if (!curr->ref->type.isArray()) { return; } auto array = curr->ref->type.getHeapType().getArray(); self()->noteSubtype(curr->value, array.element.type); } - void visitArrayInitData(ArrayInitData* curr) {} + void visitArrayInitData(ArrayInitData* curr) { + if (curr->ref->type.isRef()) { + self()->noteNonFlowSubtype( + curr->ref, Type(curr->ref->type.getHeapType(), Nullable)); + } + } void visitArrayInitElem(ArrayInitElem* curr) { + if (curr->ref->type.isRef()) { + self()->noteNonFlowSubtype( + curr->ref, Type(curr->ref->type.getHeapType(), Nullable)); + } if (!curr->ref->type.isArray()) { return; } @@ -464,6 +571,10 @@ struct SubtypingDiscoverer : public OverriddenVisitor { self()->noteSubtype(seg->type, array.element.type); } void visitArrayRMW(ArrayRMW* curr) { + if (curr->ref->type.isRef()) { + self()->noteNonFlowSubtype( + curr->ref, Type(curr->ref->type.getHeapType(), Nullable)); + } if (!curr->ref->type.isArray()) { return; } @@ -471,6 +582,10 @@ struct SubtypingDiscoverer : public OverriddenVisitor { self()->noteSubtype(curr->value, array.element.type); } void visitArrayCmpxchg(ArrayCmpxchg* curr) { + if (curr->ref->type.isRef()) { + self()->noteNonFlowSubtype( + curr->ref, Type(curr->ref->type.getHeapType(), Nullable)); + } if (!curr->ref->type.isArray()) { return; } @@ -510,15 +625,54 @@ struct SubtypingDiscoverer : public OverriddenVisitor { return; } } - void visitStringNew(StringNew* curr) {} + void visitStringNew(StringNew* curr) { + switch (curr->op) { + case StringNewLossyUTF8Array: + self()->noteNonFlowSubtype(curr->ref, + Type(HeapTypes::getMutI8Array(), Nullable)); + break; + case StringNewWTF16Array: + self()->noteNonFlowSubtype(curr->ref, + Type(HeapTypes::getMutI16Array(), Nullable)); + break; + case StringNewFromCodePoint: + break; + } + } void visitStringConst(StringConst* curr) {} - void visitStringMeasure(StringMeasure* curr) {} - void visitStringEncode(StringEncode* curr) {} - void visitStringConcat(StringConcat* curr) {} - void visitStringEq(StringEq* curr) {} - void visitStringTest(StringTest* curr) {} - void visitStringWTF16Get(StringWTF16Get* curr) {} - void visitStringSliceWTF(StringSliceWTF* curr) {} + void visitStringMeasure(StringMeasure* curr) { + self()->noteNonFlowSubtype(curr->ref, Type(HeapType::string, Nullable)); + } + void visitStringEncode(StringEncode* curr) { + self()->noteNonFlowSubtype(curr->str, Type(HeapType::string, Nullable)); + switch (curr->op) { + case StringEncodeLossyUTF8Array: + self()->noteNonFlowSubtype(curr->array, + Type(HeapTypes::getMutI8Array(), Nullable)); + break; + case StringEncodeWTF16Array: + self()->noteNonFlowSubtype(curr->array, + Type(HeapTypes::getMutI16Array(), Nullable)); + break; + } + } + void visitStringConcat(StringConcat* curr) { + self()->noteNonFlowSubtype(curr->left, Type(HeapType::string, Nullable)); + self()->noteNonFlowSubtype(curr->right, Type(HeapType::string, Nullable)); + } + void visitStringEq(StringEq* curr) { + self()->noteNonFlowSubtype(curr->left, Type(HeapType::string, Nullable)); + self()->noteNonFlowSubtype(curr->right, Type(HeapType::string, Nullable)); + } + void visitStringTest(StringTest* curr) { + self()->noteNonFlowSubtype(curr->ref, Type(HeapType::ext, Nullable)); + } + void visitStringWTF16Get(StringWTF16Get* curr) { + self()->noteNonFlowSubtype(curr->ref, Type(HeapType::string, Nullable)); + } + void visitStringSliceWTF(StringSliceWTF* curr) { + self()->noteNonFlowSubtype(curr->ref, Type(HeapType::string, Nullable)); + } void visitContNew(ContNew* curr) { if (!curr->type.isContinuation()) { @@ -526,10 +680,15 @@ struct SubtypingDiscoverer : public OverriddenVisitor { } // The type of the function reference must remain a subtype of the function // type expected by the continuation. - self()->noteSubtype(curr->func->type.getHeapType(), - curr->type.getHeapType().getContinuation().type); + self()->noteSubtype( + curr->func, + Type(curr->type.getHeapType().getContinuation().type, Nullable)); } void visitContBind(ContBind* curr) { + if (curr->cont->type.isRef()) { + self()->noteNonFlowSubtype( + curr->cont, Type(curr->cont->type.getHeapType(), Nullable)); + } if (!curr->cont->type.isContinuation() || !curr->type.isContinuation()) { return; } @@ -596,6 +755,10 @@ struct SubtypingDiscoverer : public OverriddenVisitor { } } void visitResume(Resume* curr) { + if (curr->cont->type.isRef()) { + self()->noteNonFlowSubtype( + curr->cont, Type(curr->cont->type.getHeapType(), Nullable)); + } if (!curr->cont->type.isContinuation()) { return; } @@ -613,6 +776,10 @@ struct SubtypingDiscoverer : public OverriddenVisitor { } } void visitResumeThrow(ResumeThrow* curr) { + if (curr->cont->type.isRef()) { + self()->noteNonFlowSubtype( + curr->cont, Type(curr->cont->type.getHeapType(), Nullable)); + } if (!curr->cont->type.isContinuation()) { return; } @@ -628,9 +795,17 @@ struct SubtypingDiscoverer : public OverriddenVisitor { for (Index i = 0; i < curr->operands.size(); ++i) { self()->noteSubtype(curr->operands[i], params[i]); } + } else { + assert(curr->operands.size() == 1); + self()->noteNonFlowSubtype(curr->operands[0], + Type(HeapType::exn, Nullable)); } } void visitStackSwitch(StackSwitch* curr) { + if (curr->cont->type.isRef()) { + self()->noteNonFlowSubtype( + curr->cont, Type(curr->cont->type.getHeapType(), Nullable)); + } if (!curr->cont->type.isContinuation()) { return; } diff --git a/src/passes/OptimizeInstructions.cpp b/src/passes/OptimizeInstructions.cpp index 61eab9d8349..26954747c15 100644 --- a/src/passes/OptimizeInstructions.cpp +++ b/src/passes/OptimizeInstructions.cpp @@ -1589,6 +1589,9 @@ struct OptimizeInstructions // Given the large amount of potential benefit we can get from a successful // optimization in GUFA, any reduction there may be a bad idea, so we // should be very careful and probably *not* remove such casts. + // + // The only case where we are always ok to remove such casts is when lastOpts + // is true, because nothing significant runs after us that can be harmed. // If an instruction traps on a null input, there is no need for a // ref.as_non_null on that input: we will trap either way (and the binaryen diff --git a/src/passes/TypeGeneralizing.cpp b/src/passes/TypeGeneralizing.cpp index e907c2c4c8a..a25c3711de4 100644 --- a/src/passes/TypeGeneralizing.cpp +++ b/src/passes/TypeGeneralizing.cpp @@ -14,905 +14,155 @@ * limitations under the License. */ -#include "analysis/cfg.h" -#include "analysis/lattices/inverted.h" -#include "analysis/lattices/shared.h" -#include "analysis/lattices/stack.h" -#include "analysis/lattices/tuple.h" -#include "analysis/lattices/valtype.h" -#include "analysis/lattices/vector.h" -#include "analysis/monotone-analyzer.h" +// +// Generalize the types of locals as much as possible, both to eliminate +// unnecessarily refined types from the type section and (TODO) to weaken casts +// that cast to unnecessarily refined types. If the casts are weakened enough, +// they will be able to be removed by OptimizeInstructions. +// +// Uses SubtypingDiscoverer to find all subtyping constraints on expressions and +// locals, and computes the greatest lower bound (GLB) of the required supertypes +// for each local so that each local is given the least-refined valid type. +// + +#include +#include +#include + +#include "ir/glbs.h" +#include "ir/subtype-exprs.h" #include "ir/utils.h" #include "pass.h" #include "wasm-traversal.h" #include "wasm.h" -#ifndef TYPE_GENERALIZING_DEBUG -#define TYPE_GENERALIZING_DEBUG 0 -#endif - -#if TYPE_GENERALIZING_DEBUG -#define DBG(statement) statement -#else -#define DBG(statement) -#endif - -// Generalize the types of program locations as much as possible, both to -// eliminate unnecessarily refined types from the type section and (TODO) to -// weaken casts that cast to unnecessarily refined types. If the casts are -// weakened enough, they will be able to be removed by OptimizeInstructions. -// -// Perform a backward analysis tracking requirements on the types of program -// locations (currently just locals and stack values) to discover how much the -// type of each location can be generalized without breaking validation or -// changing program behavior. - namespace wasm { namespace { -using namespace analysis; - -// We will learn stricter and stricter requirements as we perform the analysis, -// so more specific types need to be higher up the lattice. -using TypeRequirement = Inverted; - -// Record a type requirement for each local variable. Shared the requirements -// across basic blocks. -using LocalTypeRequirements = SharedPath>; - -// The type requirements for each reference-typed value on the stack at a -// particular location. -using ValueStackTypeRequirements = Stack; - -// The full lattice used for the analysis. -using StateLattice = - analysis::Tuple; - -// Equip the state lattice with helpful accessors. -struct State : StateLattice { - using Element = StateLattice::Element; - - static constexpr int LocalsIndex = 0; - static constexpr int StackIndex = 1; - - State(Function* func) - : StateLattice{SharedPath{initLocals(func)}, initStack()} {} - - void push(Element& elem, Type type) const noexcept { - stackLattice().push(stack(elem), std::move(type)); - } - - Type pop(Element& elem) const noexcept { - return stackLattice().pop(stack(elem)); - } - - void clearStack(Element& elem) const noexcept { - stack(elem) = stackLattice().getBottom(); - } - - const std::vector& getLocals(Element& elem) const noexcept { - return *locals(elem); - } - - const std::vector& getLocals() const noexcept { - return *locals(getBottom()); - } - - Type getLocal(Element& elem, Index i) const noexcept { - return getLocals(elem)[i]; - } - - bool updateLocal(Element& elem, Index i, Type type) const noexcept { - return localsLattice().join( - locals(elem), - Vector::SingletonElement(i, std::move(type))); - } - -private: - static LocalTypeRequirements initLocals(Function* func) noexcept { - return SharedPath{Vector{Inverted{ValType{}}, func->getNumLocals()}}; - } - - static ValueStackTypeRequirements initStack() noexcept { - return Stack{Inverted{ValType{}}}; - } - - const LocalTypeRequirements& localsLattice() const noexcept { - return std::get(lattices); - } - - const ValueStackTypeRequirements& stackLattice() const noexcept { - return std::get(lattices); - } - - typename LocalTypeRequirements::Element& - locals(Element& elem) const noexcept { - return std::get(elem); - } - - const typename LocalTypeRequirements::Element& - locals(const Element& elem) const noexcept { - return std::get(elem); - } - - typename ValueStackTypeRequirements::Element& - stack(Element& elem) const noexcept { - return std::get(elem); - } - - const typename ValueStackTypeRequirements::Element& - stack(const Element& elem) const noexcept { - return std::get(elem); - } -}; - -struct TransferFn : OverriddenVisitor { - Module& wasm; +struct Generalizer + : public ControlFlowWalker> { Function* func; - State lattice; - typename State::Element* state = nullptr; - - // For each local, the set of blocks we may need to re-analyze when we update - // the constraint on the local. - std::vector> localDependents; - - // The set of basic blocks that may depend on the result of the current - // transfer. - std::unordered_set currDependents; - - TransferFn(Module& wasm, Function* func, CFG& cfg) - : wasm(wasm), func(func), lattice(func), - localDependents(func->getNumLocals()) { - // Initialize `localDependents`. Any block containing a `local.set l` may - // need to be re-analyzed whenever the constraint on `l` is updated. - auto numLocals = func->getNumLocals(); - std::vector> dependentSets(numLocals); - for (const auto& bb : cfg) { - for (const auto* inst : bb) { - if (auto set = inst->dynCast()) { - dependentSets[set->index].insert(&bb); - } - } - } - for (size_t i = 0, n = dependentSets.size(); i < n; ++i) { - localDependents[i] = std::vector( - dependentSets[i].begin(), dependentSets[i].end()); - } - } - - Type pop() noexcept { return lattice.pop(*state); } - void push(Type type) noexcept { lattice.push(*state, type); } - void clearStack() noexcept { lattice.clearStack(*state); } - Type getLocal(Index i) noexcept { return lattice.getLocal(*state, i); } - void updateLocal(Index i, Type type) noexcept { - if (lattice.updateLocal(*state, i, type)) { - currDependents.insert(localDependents[i].begin(), - localDependents[i].end()); - } - } - - void dumpState() { -#if TYPE_GENERALIZING_DEBUG - std::cerr << "locals: "; - for (size_t i = 0, n = lattice.getLocals(*state).size(); i < n; ++i) { - if (i != 0) { - std::cerr << ", "; - } - std::cerr << getLocal(i); - } - std::cerr << "\nstack: "; - auto& stack = std::get<1>(*state); - for (size_t i = 0, n = stack.size(); i < n; ++i) { - if (i != 0) { - std::cerr << ", "; - } - std::cerr << stack[i]; - } - std::cerr << "\n"; -#endif // TYPE_GENERALIZING_DEBUG - } - - std::unordered_set - transfer(const BasicBlock& bb, typename State::Element& elem) noexcept { - DBG(std::cerr << "transferring bb " << bb.getIndex() << "\n"); - state = &elem; - // This is a backward analysis: The constraints on a type depend on how it - // will be used in the future. Traverse the basic block in reverse and - // return the predecessors as the dependent blocks. - assert(currDependents.empty()); - const auto& preds = bb.preds(); - currDependents.insert(preds.begin(), preds.end()); + // For each expression and local, track the GLB of all types it is required to + // be a subtype of. + std::unordered_map exprGLBs; + std::vector localGLBs; - dumpState(); - if (bb.isExit()) { - DBG(std::cerr << "visiting exit\n"); - visitFunctionExit(); - dumpState(); - } - for (auto it = bb.rbegin(); it != bb.rend(); ++it) { - DBG(std::cerr << "visiting " << ShallowExpression{*it} << "\n"); - visit(*it); - dumpState(); - } - if (bb.isEntry()) { - DBG(std::cerr << "visiting entry\n"); - visitFunctionEntry(); - dumpState(); - } - DBG(std::cerr << "\n"); + // Map from an expression to the sub-expressions whose types flow out of it + // (or depend on its required output type, as in RefAs). + std::unordered_map> exprSubs; - state = nullptr; + // For each local, the values assigned to it via local.set / local.tee. + std::vector> localSetValues; - // Return the blocks that may need to be re-analyzed. - return std::move(currDependents); - } + using WorkItem = std::variant; + std::vector worklist; - void visitFunctionExit() { - // We cannot change the types of results. Push a requirement that the stack - // end up with the correct type. - auto result = func->getResults(); - if (result.isRef()) { - push(result); - } - } + Generalizer(Function* func) + : func(func), localGLBs(func->getNumLocals()), + localSetValues(func->getNumLocals()) {} - void visitFunctionEntry() { - // We cannot change the types of parameters, so require that they have their - // original types. - Index i = 0; - Index numParams = func->getNumParams(); - Index numLocals = func->getNumLocals(); - for (; i < numParams; ++i) { - updateLocal(i, func->getLocalType(i)); - } - // We also cannot change the types of any other non-ref locals. For - // reference-typed locals, we cannot generalize beyond their top type. - for (Index i = numParams; i < numLocals; ++i) { - auto type = func->getLocalType(i); - // TODO: Support optimizing tuple locals. - if (type.isRef()) { - updateLocal(i, Type(type.getHeapType().getTop(), Nullable)); - } else { - updateLocal(i, type); - } - } - } - - void visitNop(Nop* curr) {} - void visitBlock(Block* curr) {} - void visitIf(If* curr) {} - void visitLoop(Loop* curr) {} - - void visitBreak(Break* curr) { - 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) { - // 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 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) { - handleCall(curr, wasm.getFunction(curr->target)->getParams()); - } - - void visitCallIndirect(CallIndirect* curr) { - handleCall(curr, curr->heapType.getSignature().params); - } - - void visitLocalGet(LocalGet* curr) { - if (!curr->type.isRef()) { + void addConstraint(Expression* expr, Type type) { + if (!expr->type.isRef() || !type.isRef()) { return; } - // Propagate the requirement on the local.get output to the local. - updateLocal(curr->index, pop()); - } - - void visitLocalSet(LocalSet* curr) { - if (!curr->value->type.isRef()) { - return; - } - if (curr->isTee()) { - // Same as the local.get. - updateLocal(curr->index, pop()); - } - // Propagate the requirement on the local to our input value. - push(getLocal(curr->index)); - } - - 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 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 visitPause(Pause* 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 visitWideIntAddSub(WideIntAddSub* curr) {} - void visitWideIntMul(WideIntMul* curr) {} - - void visitSelect(Select* curr) { - if (curr->type.isRef()) { - // The inputs may be as general as the output. - auto type = pop(); - push(type); - push(type); + auto& glb = exprGLBs[expr]; + auto old = glb.getGLB(); + glb.note(type); + if (glb.getGLB() != old) { + worklist.push_back(expr); } } - void visitDrop(Drop* curr) { - if (curr->type.isRef()) { - pop(); - } - } - - // 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) { 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 visitTableInit(TableInit* curr) {} - - void visitElemDrop(ElemDrop* curr) {} - - void visitTry(Try* curr) { WASM_UNREACHABLE("TODO"); } - void visitTryTable(TryTable* curr) { WASM_UNREACHABLE("TODO"); } - void visitThrow(Throw* curr) { WASM_UNREACHABLE("TODO"); } - void visitRethrow(Rethrow* curr) { WASM_UNREACHABLE("TODO"); } - void visitThrowRef(ThrowRef* 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) { - 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)); + void addConstraint(Index local, Type type) { + if (!func->isVar(local) || !func->getLocalType(local).isRef() || + !type.isRef()) { return; } - - auto sig = sigType.getSignature(); - auto numParams = sig.params.size(); - std::optional resultReq; - if (sig.results.isRef()) { - resultReq = pop(); + auto& glb = localGLBs[local]; + auto old = glb.getGLB(); + glb.note(type); + if (glb.getGLB() != old) { + worklist.push_back(local); } - - // 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 further. - // 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 noteSubtype(Type sub, Type super) {} + void noteSubtype(HeapType sub, HeapType super) {} + void noteSubtype(Type sub, Expression* super) {} + void noteSubtype(Expression* sub, Type super) { addConstraint(sub, super); } + void noteSubtype(Expression* sub, Expression* super) { + exprSubs[super].push_back(sub); } - - 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 noteNonFlowSubtype(Expression* sub, Type super) { + addConstraint(sub, super); } + void noteCast(HeapType src, Type dst) {} + void noteCast(Expression* src, Type dst) {} + void noteCast(Expression* src, Expression* dst) {} - void visitRefGetDesc(RefGetDesc* curr) { WASM_UNREACHABLE("TODO"); } - - 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 visitLocalGet(LocalGet* curr) {} - 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); - } - } + void visitLocalSet(LocalSet* curr) { + auto* func = getFunction(); + auto localType = func->getLocalType(curr->index); + if (func->isParam(curr->index)) { + // Parameters have fixed types. + addConstraint(curr->value, localType); + } else if (localType.isRef()) { + localSetValues[curr->index].push_back(curr->value); } } - HeapType - generalizeStructType(HeapType type, - Index index, - std::optional 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; + void visitRefAs(RefAs* curr) { + // All RefAs ops have output types that depend on their input types, and we + // will refinalize them if their input types generalize. Propagate any + // requirement on the output back to the input. + exprSubs[curr].push_back(curr->value); + } + + void propagate() { + while (!worklist.empty()) { + auto item = worklist.back(); + worklist.pop_back(); + if (auto* exprPtr = std::get_if(&item)) { + auto* expr = *exprPtr; + auto type = exprGLBs[expr].getGLB(); + if (auto* get = expr->dynCast()) { + addConstraint(get->index, type); + } else if (auto* set = expr->dynCast()) { + if (set->isTee()) { + addConstraint(set->index, type); + } + } else if (auto* as = expr->dynCast()) { + auto share = type.getHeapType().getShared(); + switch (as->op) { + case RefAsNonNull: + type = type.with(Nullable); + break; + case AnyConvertExtern: + type = Type(HeapTypes::ext.getBasic(share), type.getNullability()); + break; + case ExternConvertAny: + type = Type(HeapTypes::any.getBasic(share), type.getNullability()); + 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 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)); - auto fieldType = generalized.getStruct().fields[curr->index].type; - if (fieldType.isRef()) { - push(fieldType); - } - } - - void visitStructRMW(StructRMW* curr) { WASM_UNREACHABLE("TODO"); } - - void visitStructCmpxchg(StructCmpxchg* curr) { WASM_UNREACHABLE("TODO"); } - - void visitStructWait(StructWait* curr) { WASM_UNREACHABLE("TODO"); } - - void visitWaitqueueNew(WaitqueueNew* curr) { WASM_UNREACHABLE("TODO"); } - - void visitWaitqueueNotify(WaitqueueNotify* curr) { WASM_UNREACHABLE("TODO"); } - - void visitPublish(Publish* curr) { WASM_UNREACHABLE("TODO"); } - - 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); - } - } - } - - void visitArrayRMW(ArrayRMW* curr) { WASM_UNREACHABLE("TODO"); } - - void visitArrayCmpxchg(ArrayCmpxchg* curr) { WASM_UNREACHABLE("TODO"); } - - HeapType - generalizeArrayType(HeapType type, - std::optional 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; + if (auto it = exprSubs.find(expr); it != exprSubs.end()) { + for (auto* sub : it->second) { + addConstraint(sub, type); + } + } + } else { + auto local = std::get(item); + auto type = localGLBs[local].getGLB(); + for (auto* val : localSetValues[local]) { + addConstraint(val, type); } } - 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 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 visitArrayLoad(ArrayLoad* curr) { WASM_UNREACHABLE("TODO"); } - void visitArrayStore(ArrayStore* curr) { WASM_UNREACHABLE("TODO"); } - - 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, except 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(); - if (type == Type::none) { - // No downstream requirement, so no requirement for the input either. - push(Type::none); - return; - } - switch (curr->op) { - case RefAsNonNull: - push(Type(type.getHeapType(), Nullable)); - return; - case AnyConvertExtern: - push(Type(HeapType::ext, type.getNullability())); - return; - case ExternConvertAny: - 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"); } - void visitStringEncode(StringEncode* curr) { WASM_UNREACHABLE("TODO"); } - void visitStringConcat(StringConcat* curr) { WASM_UNREACHABLE("TODO"); } - void visitStringEq(StringEq* curr) { WASM_UNREACHABLE("TODO"); } - void visitStringTest(StringTest* curr) { WASM_UNREACHABLE("TODO"); } - void visitStringWTF16Get(StringWTF16Get* curr) { WASM_UNREACHABLE("TODO"); } - void visitStringSliceWTF(StringSliceWTF* curr) { WASM_UNREACHABLE("TODO"); } - - void visitContNew(ContNew* curr) { WASM_UNREACHABLE("TODO"); } - void visitContBind(ContBind* curr) { WASM_UNREACHABLE("TODO"); } - void visitSuspend(Suspend* curr) { WASM_UNREACHABLE("TODO"); } - void visitResume(Resume* curr) { WASM_UNREACHABLE("TODO"); } - void visitResumeThrow(ResumeThrow* curr) { WASM_UNREACHABLE("TODO"); } - void visitStackSwitch(StackSwitch* curr) { WASM_UNREACHABLE("TODO"); } }; struct TypeGeneralizing : WalkerPass> { @@ -926,23 +176,35 @@ struct TypeGeneralizing : WalkerPass> { void runOnFunction(Module* wasm, Function* func) override { // First, remove unreachable code. If we didn't, the unreachable code could - // become invalid after this optimization because we do not materialize or - // analyze unreachable blocks. + // become invalid after this optimization because SubtypingDiscoverer does + // not constrain unreachable expressions in all cases. PassRunner runner(getPassRunner()); runner.add("dce"); runner.runOnFunction(func); - auto cfg = CFG::fromFunction(func, wasm); - DBG(cfg.print(std::cerr)); - TransferFn txfn(*wasm, func, cfg); - MonotoneCFGAnalyzer analyzer(txfn.lattice, txfn, cfg); - analyzer.evaluate(); + Generalizer generalizer(func); + generalizer.walkFunctionInModule(func, wasm); + generalizer.propagate(); // Optimize local types. TODO: Optimize casts as well. - localTypes = txfn.lattice.getLocals(); + auto numLocals = func->getNumLocals(); auto numParams = func->getNumParams(); - for (Index i = numParams; i < localTypes.size(); ++i) { - func->vars[i - numParams] = localTypes[i]; + localTypes.resize(numLocals); + for (Index i = 0; i < numParams; ++i) { + localTypes[i] = func->getLocalType(i); + } + for (Index i = numParams; i < numLocals; ++i) { + auto origType = func->getLocalType(i); + if (origType.isRef()) { + auto& glb = generalizer.localGLBs[i]; + auto newType = glb.noted() + ? glb.getGLB() + : Type(origType.getHeapType().getTop(), Nullable); + localTypes[i] = newType; + func->vars[i - numParams] = newType; + } else { + localTypes[i] = origType; + } } // Update gets and sets accordingly. diff --git a/test/lit/passes/type-generalizing.wast b/test/lit/passes/type-generalizing.wast index c9a1c2f9326..b112371888a 100644 --- a/test/lit/passes/type-generalizing.wast +++ b/test/lit/passes/type-generalizing.wast @@ -873,9 +873,9 @@ ;; 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 $f (ref null $bot)) ;; CHECK-NEXT: (local $arg eqref) - ;; CHECK-NEXT: (call_ref $mid + ;; CHECK-NEXT: (call_ref $bot ;; CHECK-NEXT: (local.get $arg) ;; CHECK-NEXT: (local.get $f) ;; CHECK-NEXT: ) @@ -939,14 +939,16 @@ (module ;; CHECK: (type $top (sub (func (result anyref)))) (type $top (sub (func (result anyref)))) + ;; CHECK: (type $mid (sub $top (func (result eqref)))) (type $mid (sub $top (func (result eqref)))) + ;; CHECK: (type $bot (sub $mid (func (result i31ref)))) (type $bot (sub $mid (func (result i31ref)))) - ;; CHECK: (type $1 (func (result anyref))) + ;; CHECK: (type $3 (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: (func $call-ref-no-limit (type $3) (result anyref) + ;; CHECK-NEXT: (local $f (ref null $bot)) + ;; CHECK-NEXT: (call_ref $bot ;; CHECK-NEXT: (local.get $f) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -962,14 +964,14 @@ (module + ;; CHECK: (type $0 (func (result anyref))) + + ;; CHECK: (type $1 (func)) + ;; 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 $2 (func (result anyref))) - - ;; 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))))) @@ -978,7 +980,7 @@ ;; CHECK: (type $6 (func (result i31ref))) - ;; CHECK: (func $struct-new (type $2) (result anyref) + ;; CHECK: (func $struct-new (type $0) (result anyref) ;; CHECK-NEXT: (local $var1 eqref) ;; CHECK-NEXT: (local $var2 anyref) ;; CHECK-NEXT: (struct.new $struct @@ -996,9 +998,9 @@ ) ) - ;; CHECK: (func $struct-get (type $2) (result anyref) - ;; CHECK-NEXT: (local $var (ref null $top)) - ;; CHECK-NEXT: (struct.get $top 1 + ;; CHECK: (func $struct-get (type $0) (result anyref) + ;; CHECK-NEXT: (local $var (ref null $bot)) + ;; CHECK-NEXT: (struct.get $bot 1 ;; CHECK-NEXT: (local.get $var) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -1026,9 +1028,9 @@ ) ) - ;; CHECK: (func $struct-get-index (type $2) (result anyref) - ;; CHECK-NEXT: (local $var (ref null $mid)) - ;; CHECK-NEXT: (struct.get $mid 2 + ;; CHECK: (func $struct-get-index (type $0) (result anyref) + ;; CHECK-NEXT: (local $var (ref null $bot)) + ;; CHECK-NEXT: (struct.get $bot 2 ;; CHECK-NEXT: (local.get $var) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -1041,7 +1043,7 @@ ) ) - ;; CHECK: (func $struct-get-impossible (type $2) (result anyref) + ;; CHECK: (func $struct-get-impossible (type $0) (result anyref) ;; CHECK-NEXT: (local $var nullref) ;; CHECK-NEXT: (block ;; (replaces unreachable StructGet we can't emit) ;; CHECK-NEXT: (drop @@ -1057,10 +1059,10 @@ ) ) - ;; CHECK: (func $struct-set (type $3) - ;; CHECK-NEXT: (local $ref (ref null $top)) + ;; CHECK: (func $struct-set (type $1) + ;; CHECK-NEXT: (local $ref (ref null $bot)) ;; CHECK-NEXT: (local $val eqref) - ;; CHECK-NEXT: (struct.set $top 0 + ;; CHECK-NEXT: (struct.set $bot 0 ;; CHECK-NEXT: (local.get $ref) ;; CHECK-NEXT: (local.get $val) ;; CHECK-NEXT: ) @@ -1077,10 +1079,10 @@ ) ) - ;; CHECK: (func $struct-set-index (type $3) - ;; CHECK-NEXT: (local $ref (ref null $mid)) + ;; CHECK: (func $struct-set-index (type $1) + ;; CHECK-NEXT: (local $ref (ref null $bot)) ;; CHECK-NEXT: (local $val eqref) - ;; CHECK-NEXT: (struct.set $mid 2 + ;; CHECK-NEXT: (struct.set $bot 2 ;; CHECK-NEXT: (local.get $ref) ;; CHECK-NEXT: (local.get $val) ;; CHECK-NEXT: ) @@ -1097,7 +1099,7 @@ ) ) - ;; CHECK: (func $struct-set-impossible (type $3) + ;; CHECK: (func $struct-set-impossible (type $1) ;; CHECK-NEXT: (local $ref nullref) ;; CHECK-NEXT: (local $val anyref) ;; CHECK-NEXT: (block ;; (replaces unreachable StructSet we can't emit) @@ -1124,26 +1126,26 @@ (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)))) + ;; CHECK: (type $3 (func (result anyref))) + + ;; CHECK: (type $super-mut (sub (array (mut eqref)))) (type $super-mut (sub (array (field (mut eqref))))) + ;; CHECK: (type $sub-mut (sub $super-mut (array (mut eqref)))) (type $sub-mut (sub $super-mut (array (field (mut eqref))))) + ;; CHECK: (type $bytes (sub (array i8))) (type $bytes (sub (array i8))) + ;; CHECK: (type $sub-bytes (sub $bytes (array i8))) (type $sub-bytes (sub $bytes (array i8))) + ;; CHECK: (type $mut-bytes (sub (array (mut i8)))) (type $mut-bytes (sub (array (mut i8)))) + ;; CHECK: (type $sub-mut-bytes (sub $mut-bytes (array (mut i8)))) (type $sub-mut-bytes (sub $mut-bytes (array (mut i8)))) ;; CHECK: (data $data "") @@ -1228,8 +1230,8 @@ ) ;; CHECK: (func $array-get (type $3) (result anyref) - ;; CHECK-NEXT: (local $val (ref null $super)) - ;; CHECK-NEXT: (array.get $super + ;; CHECK-NEXT: (local $val (ref null $sub)) + ;; CHECK-NEXT: (array.get $sub ;; CHECK-NEXT: (local.get $val) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -1265,9 +1267,9 @@ ) ;; CHECK: (func $array-set (type $0) - ;; CHECK-NEXT: (local $ref (ref null $super-mut)) + ;; CHECK-NEXT: (local $ref (ref null $sub-mut)) ;; CHECK-NEXT: (local $val eqref) - ;; CHECK-NEXT: (array.set $super-mut + ;; CHECK-NEXT: (array.set $sub-mut ;; CHECK-NEXT: (local.get $ref) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: (local.get $val) @@ -1331,9 +1333,9 @@ ) ;; 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 $dest (ref null $sub-mut)) + ;; CHECK-NEXT: (local $src (ref null $sub)) + ;; CHECK-NEXT: (array.copy $sub-mut $sub ;; CHECK-NEXT: (local.get $dest) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: (local.get $src) @@ -1355,9 +1357,9 @@ ) ;; 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 $dest (ref null $sub-mut-bytes)) + ;; CHECK-NEXT: (local $src (ref null $sub-bytes)) + ;; CHECK-NEXT: (array.copy $sub-mut-bytes $sub-bytes ;; CHECK-NEXT: (local.get $dest) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: (local.get $src) @@ -1488,9 +1490,9 @@ ) ;; CHECK: (func $array-fill (type $0) - ;; CHECK-NEXT: (local $ref (ref null $super-mut)) + ;; CHECK-NEXT: (local $ref (ref null $sub-mut)) ;; CHECK-NEXT: (local $val eqref) - ;; CHECK-NEXT: (array.fill $super-mut + ;; CHECK-NEXT: (array.fill $sub-mut ;; CHECK-NEXT: (local.get $ref) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: (local.get $val) @@ -1542,8 +1544,8 @@ ) ;; 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 $ref (ref null $sub-mut-bytes)) + ;; CHECK-NEXT: (array.init_data $sub-mut-bytes $data ;; CHECK-NEXT: (local.get $ref) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: (i32.const 0) @@ -1591,8 +1593,8 @@ ) ;; 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 $ref (ref null $sub-mut)) + ;; CHECK-NEXT: (array.init_elem $sub-mut $elem ;; CHECK-NEXT: (local.get $ref) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: (i32.const 0) From eb92c6f0757783249113b29d743536e4ba3d178d Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 13 Oct 2026 15:50:55 -0700 Subject: [PATCH 02/36] oar --- src/passes/TypeGeneralizing.cpp | 238 ++++++++++++++++++++++++- test/lit/passes/type-generalizing.wast | 100 +++++------ 2 files changed, 284 insertions(+), 54 deletions(-) diff --git a/src/passes/TypeGeneralizing.cpp b/src/passes/TypeGeneralizing.cpp index a25c3711de4..f403267e6ab 100644 --- a/src/passes/TypeGeneralizing.cpp +++ b/src/passes/TypeGeneralizing.cpp @@ -21,10 +21,12 @@ // they will be able to be removed by OptimizeInstructions. // // Uses SubtypingDiscoverer to find all subtyping constraints on expressions and -// locals, and computes the greatest lower bound (GLB) of the required supertypes -// for each local so that each local is given the least-refined valid type. +// locals, and computes the greatest lower bound (GLB) of the required +// supertypes for each local so that each local is given the least-refined valid +// type. // +#include #include #include #include @@ -40,6 +42,87 @@ namespace wasm { namespace { +// Find the most general supertype of a signature type that does not change +// parameter types (which are contravariant and would restrict argument +// generalization) and whose result type satisfies `resultReq`, if any. +HeapType +generalizeCallRefTarget(HeapType sigType, + std::optional resultReq = std::nullopt) { + auto sig = sigType.getSignature(); + auto numParams = sig.params.size(); + auto targetReq = sigType; + while (true) { + auto candidateReq = targetReq.getDeclaredSuperType(); + if (!candidateReq) { + break; + } + auto candidateSig = candidateReq->getSignature(); + if (resultReq && *resultReq != candidateSig.results && + Type::isSubType(*resultReq, candidateSig.results)) { + break; + } + bool paramsMatch = true; + for (size_t i = 0; i < numParams; ++i) { + if (candidateSig.params[i] != sig.params[i]) { + paramsMatch = false; + break; + } + } + if (!paramsMatch) { + break; + } + targetReq = *candidateReq; + } + return targetReq; +} + +// Find the most general supertype of a struct type that still has a field at +// `index` whose type satisfies `reqFieldType`, if any. +HeapType generalizeStructType(HeapType type, + Index index, + std::optional reqFieldType = std::nullopt) { + while (true) { + auto candidateType = type.getDeclaredSuperType(); + if (!candidateType) { + break; + } + const auto& candidateFields = candidateType->getStruct().fields; + if (candidateFields.size() <= index) { + break; + } + if (reqFieldType) { + auto candidateFieldType = candidateFields[index].type; + if (candidateFieldType != *reqFieldType && + Type::isSubType(*reqFieldType, candidateFieldType)) { + break; + } + } + type = *candidateType; + } + return type; +} + +// Find the most general supertype of an array type whose element type satisfies +// `reqElemType`, if any. +HeapType generalizeArrayType(HeapType type, + std::optional reqElemType = std::nullopt) { + while (true) { + auto candidateType = type.getDeclaredSuperType(); + if (!candidateType) { + break; + } + if (reqElemType) { + auto candidateElemType = candidateType->getArray().element.type; + if (candidateElemType != *reqElemType && + Type::isSubType(*reqElemType, candidateElemType)) { + break; + } + } + type = *candidateType; + } + return type; +} + struct Generalizer : public ControlFlowWalker> { Function* func; @@ -105,7 +188,6 @@ struct Generalizer void visitLocalGet(LocalGet* curr) {} void visitLocalSet(LocalSet* curr) { - auto* func = getFunction(); auto localType = func->getLocalType(curr->index); if (func->isParam(curr->index)) { // Parameters have fixed types. @@ -122,6 +204,138 @@ struct Generalizer exprSubs[curr].push_back(curr->value); } + void visitCallRef(CallRef* curr) { + if (curr->target->type.isNull()) { + addConstraint(curr->target, curr->target->type); + return; + } + if (!curr->target->type.isSignature()) { + return; + } + auto sigType = curr->target->type.getHeapType(); + handleCall(curr, sigType.getSignature()); + std::optional resultReq; + if (curr->isReturn) { + resultReq = func->getResults(); + } + auto generalized = generalizeCallRefTarget(sigType, resultReq); + addConstraint(curr->target, Type(generalized, Nullable)); + } + + void visitStructGet(StructGet* curr) { + if (curr->ref->type.isNull()) { + addConstraint(curr->ref, curr->ref->type); + return; + } + if (!curr->ref->type.isStruct()) { + return; + } + auto generalized = + generalizeStructType(curr->ref->type.getHeapType(), curr->index); + addConstraint(curr->ref, Type(generalized, Nullable)); + } + + void visitStructSet(StructSet* curr) { + if (curr->ref->type.isNull()) { + addConstraint(curr->ref, curr->ref->type); + return; + } + if (!curr->ref->type.isStruct()) { + return; + } + auto generalized = + generalizeStructType(curr->ref->type.getHeapType(), curr->index); + addConstraint(curr->ref, Type(generalized, Nullable)); + addConstraint(curr->value, generalized.getStruct().fields[curr->index].type); + } + + void visitArrayGet(ArrayGet* curr) { + if (curr->ref->type.isNull()) { + addConstraint(curr->ref, curr->ref->type); + return; + } + if (!curr->ref->type.isArray()) { + return; + } + auto generalized = generalizeArrayType(curr->ref->type.getHeapType()); + addConstraint(curr->ref, Type(generalized, Nullable)); + } + + void visitArraySet(ArraySet* curr) { + if (curr->ref->type.isNull()) { + addConstraint(curr->ref, curr->ref->type); + return; + } + if (!curr->ref->type.isArray()) { + return; + } + auto generalized = generalizeArrayType(curr->ref->type.getHeapType()); + addConstraint(curr->ref, Type(generalized, Nullable)); + addConstraint(curr->value, generalized.getArray().element.type); + } + + void visitArrayCopy(ArrayCopy* curr) { + if (curr->destRef->type.isNull() || curr->srcRef->type.isNull()) { + if (curr->destRef->type.isNull()) { + addConstraint(curr->destRef, curr->destRef->type); + } + if (curr->srcRef->type.isNull()) { + addConstraint(curr->srcRef, curr->srcRef->type); + } + return; + } + if (!curr->destRef->type.isArray() || !curr->srcRef->type.isArray()) { + return; + } + auto destGen = generalizeArrayType(curr->destRef->type.getHeapType()); + addConstraint(curr->destRef, Type(destGen, Nullable)); + auto elemType = destGen.getArray().element.type; + std::optional reqElemType; + if (elemType.isRef()) { + reqElemType = elemType; + } + auto srcGen = + generalizeArrayType(curr->srcRef->type.getHeapType(), reqElemType); + addConstraint(curr->srcRef, Type(srcGen, Nullable)); + } + + void visitArrayFill(ArrayFill* curr) { + if (curr->ref->type.isNull()) { + addConstraint(curr->ref, curr->ref->type); + return; + } + if (!curr->ref->type.isArray()) { + return; + } + auto generalized = generalizeArrayType(curr->ref->type.getHeapType()); + addConstraint(curr->ref, Type(generalized, Nullable)); + addConstraint(curr->value, generalized.getArray().element.type); + } + + void visitArrayInitData(ArrayInitData* curr) { + if (curr->ref->type.isNull()) { + addConstraint(curr->ref, curr->ref->type); + return; + } + if (!curr->ref->type.isArray()) { + return; + } + auto generalized = generalizeArrayType(curr->ref->type.getHeapType()); + addConstraint(curr->ref, Type(generalized, Nullable)); + } + + void visitArrayInitElem(ArrayInitElem* curr) { + if (curr->ref->type.isNull()) { + addConstraint(curr->ref, curr->ref->type); + return; + } + if (!curr->ref->type.isArray()) { + return; + } + auto generalized = generalizeArrayType(curr->ref->type.getHeapType()); + addConstraint(curr->ref, Type(generalized, Nullable)); + } + void propagate() { while (!worklist.empty()) { auto item = worklist.back(); @@ -148,6 +362,24 @@ struct Generalizer type = Type(HeapTypes::any.getBasic(share), type.getNullability()); break; } + } else if (auto* get = expr->dynCast()) { + if (get->ref->type.isStruct()) { + auto generalized = generalizeStructType( + get->ref->type.getHeapType(), get->index, type); + addConstraint(get->ref, Type(generalized, Nullable)); + } + } else if (auto* get = expr->dynCast()) { + if (get->ref->type.isArray()) { + auto generalized = + generalizeArrayType(get->ref->type.getHeapType(), type); + addConstraint(get->ref, Type(generalized, Nullable)); + } + } else if (auto* call = expr->dynCast()) { + if (call->target->type.isSignature()) { + auto generalized = + generalizeCallRefTarget(call->target->type.getHeapType(), type); + addConstraint(call->target, Type(generalized, Nullable)); + } } if (auto it = exprSubs.find(expr); it != exprSubs.end()) { for (auto* sub : it->second) { diff --git a/test/lit/passes/type-generalizing.wast b/test/lit/passes/type-generalizing.wast index b112371888a..c9a1c2f9326 100644 --- a/test/lit/passes/type-generalizing.wast +++ b/test/lit/passes/type-generalizing.wast @@ -873,9 +873,9 @@ ;; CHECK: (type $4 (func (result anyref))) ;; CHECK: (func $call-ref-params-limited (type $4) (result anyref) - ;; CHECK-NEXT: (local $f (ref null $bot)) + ;; CHECK-NEXT: (local $f (ref null $mid)) ;; CHECK-NEXT: (local $arg eqref) - ;; CHECK-NEXT: (call_ref $bot + ;; CHECK-NEXT: (call_ref $mid ;; CHECK-NEXT: (local.get $arg) ;; CHECK-NEXT: (local.get $f) ;; CHECK-NEXT: ) @@ -939,16 +939,14 @@ (module ;; CHECK: (type $top (sub (func (result anyref)))) (type $top (sub (func (result anyref)))) - ;; CHECK: (type $mid (sub $top (func (result eqref)))) (type $mid (sub $top (func (result eqref)))) - ;; CHECK: (type $bot (sub $mid (func (result i31ref)))) (type $bot (sub $mid (func (result i31ref)))) - ;; CHECK: (type $3 (func (result anyref))) + ;; CHECK: (type $1 (func (result anyref))) - ;; CHECK: (func $call-ref-no-limit (type $3) (result anyref) - ;; CHECK-NEXT: (local $f (ref null $bot)) - ;; CHECK-NEXT: (call_ref $bot + ;; 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: ) @@ -964,14 +962,14 @@ (module - ;; CHECK: (type $0 (func (result anyref))) - - ;; CHECK: (type $1 (func)) - ;; 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 $2 (func (result anyref))) + + ;; 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))))) @@ -980,7 +978,7 @@ ;; CHECK: (type $6 (func (result i31ref))) - ;; CHECK: (func $struct-new (type $0) (result anyref) + ;; CHECK: (func $struct-new (type $2) (result anyref) ;; CHECK-NEXT: (local $var1 eqref) ;; CHECK-NEXT: (local $var2 anyref) ;; CHECK-NEXT: (struct.new $struct @@ -998,9 +996,9 @@ ) ) - ;; CHECK: (func $struct-get (type $0) (result anyref) - ;; CHECK-NEXT: (local $var (ref null $bot)) - ;; CHECK-NEXT: (struct.get $bot 1 + ;; CHECK: (func $struct-get (type $2) (result anyref) + ;; CHECK-NEXT: (local $var (ref null $top)) + ;; CHECK-NEXT: (struct.get $top 1 ;; CHECK-NEXT: (local.get $var) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -1028,9 +1026,9 @@ ) ) - ;; CHECK: (func $struct-get-index (type $0) (result anyref) - ;; CHECK-NEXT: (local $var (ref null $bot)) - ;; CHECK-NEXT: (struct.get $bot 2 + ;; CHECK: (func $struct-get-index (type $2) (result anyref) + ;; CHECK-NEXT: (local $var (ref null $mid)) + ;; CHECK-NEXT: (struct.get $mid 2 ;; CHECK-NEXT: (local.get $var) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -1043,7 +1041,7 @@ ) ) - ;; CHECK: (func $struct-get-impossible (type $0) (result anyref) + ;; CHECK: (func $struct-get-impossible (type $2) (result anyref) ;; CHECK-NEXT: (local $var nullref) ;; CHECK-NEXT: (block ;; (replaces unreachable StructGet we can't emit) ;; CHECK-NEXT: (drop @@ -1059,10 +1057,10 @@ ) ) - ;; CHECK: (func $struct-set (type $1) - ;; CHECK-NEXT: (local $ref (ref null $bot)) + ;; CHECK: (func $struct-set (type $3) + ;; CHECK-NEXT: (local $ref (ref null $top)) ;; CHECK-NEXT: (local $val eqref) - ;; CHECK-NEXT: (struct.set $bot 0 + ;; CHECK-NEXT: (struct.set $top 0 ;; CHECK-NEXT: (local.get $ref) ;; CHECK-NEXT: (local.get $val) ;; CHECK-NEXT: ) @@ -1079,10 +1077,10 @@ ) ) - ;; CHECK: (func $struct-set-index (type $1) - ;; CHECK-NEXT: (local $ref (ref null $bot)) + ;; CHECK: (func $struct-set-index (type $3) + ;; CHECK-NEXT: (local $ref (ref null $mid)) ;; CHECK-NEXT: (local $val eqref) - ;; CHECK-NEXT: (struct.set $bot 2 + ;; CHECK-NEXT: (struct.set $mid 2 ;; CHECK-NEXT: (local.get $ref) ;; CHECK-NEXT: (local.get $val) ;; CHECK-NEXT: ) @@ -1099,7 +1097,7 @@ ) ) - ;; CHECK: (func $struct-set-impossible (type $1) + ;; CHECK: (func $struct-set-impossible (type $3) ;; CHECK-NEXT: (local $ref nullref) ;; CHECK-NEXT: (local $val anyref) ;; CHECK-NEXT: (block ;; (replaces unreachable StructSet we can't emit) @@ -1126,26 +1124,26 @@ (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)))) - ;; CHECK: (type $3 (func (result anyref))) - - ;; CHECK: (type $super-mut (sub (array (mut eqref)))) (type $super-mut (sub (array (field (mut eqref))))) - ;; CHECK: (type $sub-mut (sub $super-mut (array (mut eqref)))) (type $sub-mut (sub $super-mut (array (field (mut eqref))))) - ;; CHECK: (type $bytes (sub (array i8))) (type $bytes (sub (array i8))) - ;; CHECK: (type $sub-bytes (sub $bytes (array i8))) (type $sub-bytes (sub $bytes (array i8))) - ;; CHECK: (type $mut-bytes (sub (array (mut i8)))) (type $mut-bytes (sub (array (mut i8)))) - ;; CHECK: (type $sub-mut-bytes (sub $mut-bytes (array (mut i8)))) (type $sub-mut-bytes (sub $mut-bytes (array (mut i8)))) ;; CHECK: (data $data "") @@ -1230,8 +1228,8 @@ ) ;; CHECK: (func $array-get (type $3) (result anyref) - ;; CHECK-NEXT: (local $val (ref null $sub)) - ;; CHECK-NEXT: (array.get $sub + ;; CHECK-NEXT: (local $val (ref null $super)) + ;; CHECK-NEXT: (array.get $super ;; CHECK-NEXT: (local.get $val) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -1267,9 +1265,9 @@ ) ;; CHECK: (func $array-set (type $0) - ;; CHECK-NEXT: (local $ref (ref null $sub-mut)) + ;; CHECK-NEXT: (local $ref (ref null $super-mut)) ;; CHECK-NEXT: (local $val eqref) - ;; CHECK-NEXT: (array.set $sub-mut + ;; CHECK-NEXT: (array.set $super-mut ;; CHECK-NEXT: (local.get $ref) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: (local.get $val) @@ -1333,9 +1331,9 @@ ) ;; CHECK: (func $array-copy-ref (type $0) - ;; CHECK-NEXT: (local $dest (ref null $sub-mut)) - ;; CHECK-NEXT: (local $src (ref null $sub)) - ;; CHECK-NEXT: (array.copy $sub-mut $sub + ;; 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) @@ -1357,9 +1355,9 @@ ) ;; CHECK: (func $array-copy-i8 (type $0) - ;; CHECK-NEXT: (local $dest (ref null $sub-mut-bytes)) - ;; CHECK-NEXT: (local $src (ref null $sub-bytes)) - ;; CHECK-NEXT: (array.copy $sub-mut-bytes $sub-bytes + ;; 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) @@ -1490,9 +1488,9 @@ ) ;; CHECK: (func $array-fill (type $0) - ;; CHECK-NEXT: (local $ref (ref null $sub-mut)) + ;; CHECK-NEXT: (local $ref (ref null $super-mut)) ;; CHECK-NEXT: (local $val eqref) - ;; CHECK-NEXT: (array.fill $sub-mut + ;; CHECK-NEXT: (array.fill $super-mut ;; CHECK-NEXT: (local.get $ref) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: (local.get $val) @@ -1544,8 +1542,8 @@ ) ;; CHECK: (func $array-init-data (type $0) - ;; CHECK-NEXT: (local $ref (ref null $sub-mut-bytes)) - ;; CHECK-NEXT: (array.init_data $sub-mut-bytes $data + ;; 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) @@ -1593,8 +1591,8 @@ ) ;; CHECK: (func $array-init-elem (type $0) - ;; CHECK-NEXT: (local $ref (ref null $sub-mut)) - ;; CHECK-NEXT: (array.init_elem $sub-mut $elem + ;; 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) From 479cf9229dacfb98e4ebaf6b31a98f8ef7e722c3 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 13 Oct 2026 15:53:36 -0700 Subject: [PATCH 03/36] work --- src/ir/subtype-exprs.h | 60 ++++++++++++++++----------------- src/passes/TypeGeneralizing.cpp | 14 ++++---- 2 files changed, 38 insertions(+), 36 deletions(-) diff --git a/src/ir/subtype-exprs.h b/src/ir/subtype-exprs.h index 25b716a81aa..e784372a698 100644 --- a/src/ir/subtype-exprs.h +++ b/src/ir/subtype-exprs.h @@ -345,8 +345,8 @@ struct SubtypingDiscoverer : public OverriddenVisitor { } void visitRefGetDesc(RefGetDesc* curr) { if (curr->ref->type.isRef()) { - self()->noteNonFlowSubtype( - curr->ref, Type(curr->ref->type.getHeapType(), Nullable)); + self()->noteNonFlowSubtype(curr->ref, + Type(curr->ref->type.getHeapType(), Nullable)); } } void visitBrOn(BrOn* curr) { @@ -390,14 +390,14 @@ struct SubtypingDiscoverer : public OverriddenVisitor { } void visitStructGet(StructGet* curr) { if (curr->ref->type.isRef()) { - self()->noteNonFlowSubtype( - curr->ref, Type(curr->ref->type.getHeapType(), Nullable)); + self()->noteNonFlowSubtype(curr->ref, + Type(curr->ref->type.getHeapType(), Nullable)); } } void visitStructSet(StructSet* curr) { if (curr->ref->type.isRef()) { - self()->noteNonFlowSubtype( - curr->ref, Type(curr->ref->type.getHeapType(), Nullable)); + self()->noteNonFlowSubtype(curr->ref, + Type(curr->ref->type.getHeapType(), Nullable)); } if (!curr->ref->type.isStruct()) { return; @@ -407,8 +407,8 @@ struct SubtypingDiscoverer : public OverriddenVisitor { } void visitStructRMW(StructRMW* curr) { if (curr->ref->type.isRef()) { - self()->noteNonFlowSubtype( - curr->ref, Type(curr->ref->type.getHeapType(), Nullable)); + self()->noteNonFlowSubtype(curr->ref, + Type(curr->ref->type.getHeapType(), Nullable)); } if (!curr->ref->type.isStruct()) { return; @@ -418,8 +418,8 @@ struct SubtypingDiscoverer : public OverriddenVisitor { } void visitStructCmpxchg(StructCmpxchg* curr) { if (curr->ref->type.isRef()) { - self()->noteNonFlowSubtype( - curr->ref, Type(curr->ref->type.getHeapType(), Nullable)); + self()->noteNonFlowSubtype(curr->ref, + Type(curr->ref->type.getHeapType(), Nullable)); } if (!curr->ref->type.isStruct()) { return; @@ -438,8 +438,8 @@ struct SubtypingDiscoverer : public OverriddenVisitor { self()->noteSubtype(curr->waitqueue, Type(HeapTypes::sharedWaitqueue, Nullable)); if (curr->ref->type.isRef()) { - self()->noteNonFlowSubtype( - curr->ref, Type(curr->ref->type.getHeapType(), Nullable)); + self()->noteNonFlowSubtype(curr->ref, + Type(curr->ref->type.getHeapType(), Nullable)); } if (!curr->ref->type.isStruct()) { return; @@ -485,14 +485,14 @@ struct SubtypingDiscoverer : public OverriddenVisitor { void visitArrayGet(ArrayGet* curr) { if (curr->ref->type.isRef()) { - self()->noteNonFlowSubtype( - curr->ref, Type(curr->ref->type.getHeapType(), Nullable)); + self()->noteNonFlowSubtype(curr->ref, + Type(curr->ref->type.getHeapType(), Nullable)); } } void visitArraySet(ArraySet* curr) { if (curr->ref->type.isRef()) { - self()->noteNonFlowSubtype( - curr->ref, Type(curr->ref->type.getHeapType(), Nullable)); + self()->noteNonFlowSubtype(curr->ref, + Type(curr->ref->type.getHeapType(), Nullable)); } if (!curr->ref->type.isArray()) { return; @@ -502,14 +502,14 @@ struct SubtypingDiscoverer : public OverriddenVisitor { } void visitArrayLoad(ArrayLoad* curr) { if (curr->ref->type.isRef()) { - self()->noteNonFlowSubtype( - curr->ref, Type(curr->ref->type.getHeapType(), Nullable)); + self()->noteNonFlowSubtype(curr->ref, + Type(curr->ref->type.getHeapType(), Nullable)); } } void visitArrayStore(ArrayStore* curr) { if (curr->ref->type.isRef()) { - self()->noteNonFlowSubtype( - curr->ref, Type(curr->ref->type.getHeapType(), Nullable)); + self()->noteNonFlowSubtype(curr->ref, + Type(curr->ref->type.getHeapType(), Nullable)); } } void visitArrayLen(ArrayLen* curr) { @@ -543,8 +543,8 @@ struct SubtypingDiscoverer : public OverriddenVisitor { } void visitArrayFill(ArrayFill* curr) { if (curr->ref->type.isRef()) { - self()->noteNonFlowSubtype( - curr->ref, Type(curr->ref->type.getHeapType(), Nullable)); + self()->noteNonFlowSubtype(curr->ref, + Type(curr->ref->type.getHeapType(), Nullable)); } if (!curr->ref->type.isArray()) { return; @@ -554,14 +554,14 @@ struct SubtypingDiscoverer : public OverriddenVisitor { } void visitArrayInitData(ArrayInitData* curr) { if (curr->ref->type.isRef()) { - self()->noteNonFlowSubtype( - curr->ref, Type(curr->ref->type.getHeapType(), Nullable)); + self()->noteNonFlowSubtype(curr->ref, + Type(curr->ref->type.getHeapType(), Nullable)); } } void visitArrayInitElem(ArrayInitElem* curr) { if (curr->ref->type.isRef()) { - self()->noteNonFlowSubtype( - curr->ref, Type(curr->ref->type.getHeapType(), Nullable)); + self()->noteNonFlowSubtype(curr->ref, + Type(curr->ref->type.getHeapType(), Nullable)); } if (!curr->ref->type.isArray()) { return; @@ -572,8 +572,8 @@ struct SubtypingDiscoverer : public OverriddenVisitor { } void visitArrayRMW(ArrayRMW* curr) { if (curr->ref->type.isRef()) { - self()->noteNonFlowSubtype( - curr->ref, Type(curr->ref->type.getHeapType(), Nullable)); + self()->noteNonFlowSubtype(curr->ref, + Type(curr->ref->type.getHeapType(), Nullable)); } if (!curr->ref->type.isArray()) { return; @@ -583,8 +583,8 @@ struct SubtypingDiscoverer : public OverriddenVisitor { } void visitArrayCmpxchg(ArrayCmpxchg* curr) { if (curr->ref->type.isRef()) { - self()->noteNonFlowSubtype( - curr->ref, Type(curr->ref->type.getHeapType(), Nullable)); + self()->noteNonFlowSubtype(curr->ref, + Type(curr->ref->type.getHeapType(), Nullable)); } if (!curr->ref->type.isArray()) { return; diff --git a/src/passes/TypeGeneralizing.cpp b/src/passes/TypeGeneralizing.cpp index f403267e6ab..ec179ee52a7 100644 --- a/src/passes/TypeGeneralizing.cpp +++ b/src/passes/TypeGeneralizing.cpp @@ -45,9 +45,8 @@ namespace { // Find the most general supertype of a signature type that does not change // parameter types (which are contravariant and would restrict argument // generalization) and whose result type satisfies `resultReq`, if any. -HeapType -generalizeCallRefTarget(HeapType sigType, - std::optional resultReq = std::nullopt) { +HeapType generalizeCallRefTarget(HeapType sigType, + std::optional resultReq = std::nullopt) { auto sig = sigType.getSignature(); auto numParams = sig.params.size(); auto targetReq = sigType; @@ -246,7 +245,8 @@ struct Generalizer auto generalized = generalizeStructType(curr->ref->type.getHeapType(), curr->index); addConstraint(curr->ref, Type(generalized, Nullable)); - addConstraint(curr->value, generalized.getStruct().fields[curr->index].type); + addConstraint(curr->value, + generalized.getStruct().fields[curr->index].type); } void visitArrayGet(ArrayGet* curr) { @@ -356,10 +356,12 @@ struct Generalizer type = type.with(Nullable); break; case AnyConvertExtern: - type = Type(HeapTypes::ext.getBasic(share), type.getNullability()); + type = + Type(HeapTypes::ext.getBasic(share), type.getNullability()); break; case ExternConvertAny: - type = Type(HeapTypes::any.getBasic(share), type.getNullability()); + type = + Type(HeapTypes::any.getBasic(share), type.getNullability()); break; } } else if (auto* get = expr->dynCast()) { From 327c476fab0980de073c1599af05fb75e652c2c6 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 13 Oct 2026 17:06:06 -0700 Subject: [PATCH 04/36] fix --- src/passes/TypeGeneralizing.cpp | 9 +++---- test/lit/passes/type-generalizing.wast | 33 ++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/src/passes/TypeGeneralizing.cpp b/src/passes/TypeGeneralizing.cpp index ec179ee52a7..776d5f09ffd 100644 --- a/src/passes/TypeGeneralizing.cpp +++ b/src/passes/TypeGeneralizing.cpp @@ -56,8 +56,7 @@ HeapType generalizeCallRefTarget(HeapType sigType, break; } auto candidateSig = candidateReq->getSignature(); - if (resultReq && *resultReq != candidateSig.results && - Type::isSubType(*resultReq, candidateSig.results)) { + if (resultReq && !Type::isSubType(candidateSig.results, *resultReq)) { break; } bool paramsMatch = true; @@ -91,8 +90,7 @@ HeapType generalizeStructType(HeapType type, } if (reqFieldType) { auto candidateFieldType = candidateFields[index].type; - if (candidateFieldType != *reqFieldType && - Type::isSubType(*reqFieldType, candidateFieldType)) { + if (!Type::isSubType(candidateFieldType, *reqFieldType)) { break; } } @@ -112,8 +110,7 @@ HeapType generalizeArrayType(HeapType type, } if (reqElemType) { auto candidateElemType = candidateType->getArray().element.type; - if (candidateElemType != *reqElemType && - Type::isSubType(*reqElemType, candidateElemType)) { + if (!Type::isSubType(candidateElemType, *reqElemType)) { break; } } diff --git a/test/lit/passes/type-generalizing.wast b/test/lit/passes/type-generalizing.wast index c9a1c2f9326..b22af61f1f9 100644 --- a/test/lit/passes/type-generalizing.wast +++ b/test/lit/passes/type-generalizing.wast @@ -1639,3 +1639,36 @@ ) ) ) + +(module + ;; CHECK: (type $inner-super (sub (struct))) + (type $inner-super (sub (struct))) + ;; CHECK: (type $inner-sub (sub $inner-super (struct (field i32)))) + (type $inner-sub (sub $inner-super (struct (field i32)))) + ;; CHECK: (type $outer-super (sub (struct (field (ref $inner-super))))) + (type $outer-super (sub (struct (field (ref $inner-super))))) + ;; CHECK: (type $outer-sub (sub $outer-super (struct (field (ref $inner-sub))))) + (type $outer-sub (sub $outer-super (struct (field (ref $inner-sub))))) + + ;; CHECK: (type $4 (func (result i32))) + + ;; CHECK: (func $struct-get-non-null-field (type $4) (result i32) + ;; CHECK-NEXT: (local $var (ref null $outer-sub)) + ;; CHECK-NEXT: (struct.get $inner-sub 0 + ;; CHECK-NEXT: (struct.get $outer-sub 0 + ;; CHECK-NEXT: (local.get $var) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $struct-get-non-null-field (result i32) + (local $var (ref null $outer-sub)) + ;; The outer struct.get requires (ref null $inner-sub), which is nullable. + ;; $outer-super's field 0 has non-nullable type (ref $inner-super), which is + ;; incomparable with (ref null $inner-sub); we must still stop at $outer-sub. + (struct.get $inner-sub 0 + (struct.get $outer-sub 0 + (local.get $var) + ) + ) + ) +) From 78630e6592721bf42f0758c4dc834c0422bc79f5 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 13 Oct 2026 17:07:09 -0700 Subject: [PATCH 05/36] fuzz --- scripts/fuzz_opt.py | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index 3339df8e4fe..35ef6f19fba 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -2772,6 +2772,7 @@ def write_commands(commands, filename): ("--tail-call",), ("--tuple-optimization",), ("--type-finalizing",), + ("--experimental-type-generalizing",), ("--type-refining",), ("--type-refining-gufa",), ("--type-merging",), From 01ec083bb58a710378edc9a4794d0e0fc5bb7ce7 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 14 Oct 2026 08:52:57 -0700 Subject: [PATCH 06/36] undo --- scripts/fuzz_opt.py | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index 35ef6f19fba..3339df8e4fe 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -2772,7 +2772,6 @@ def write_commands(commands, filename): ("--tail-call",), ("--tuple-optimization",), ("--type-finalizing",), - ("--experimental-type-generalizing",), ("--type-refining",), ("--type-refining-gufa",), ("--type-merging",), From 7d4362d13e58ca1f3ca5968ff086d11ce4169ba7 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 14 Oct 2026 09:13:21 -0700 Subject: [PATCH 07/36] undo --- src/ir/subtype-exprs.h | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/ir/subtype-exprs.h b/src/ir/subtype-exprs.h index e784372a698..8243b05c9fd 100644 --- a/src/ir/subtype-exprs.h +++ b/src/ir/subtype-exprs.h @@ -291,11 +291,7 @@ struct SubtypingDiscoverer : public OverriddenVisitor { void visitThrowRef(ThrowRef* curr) { self()->noteNonFlowSubtype(curr->exnref, Type(HeapType::exn, Nullable)); } - void visitTupleMake(TupleMake* curr) { - for (auto* op : curr->operands) { - self()->noteNonFlowSubtype(op, op->type); - } - } + void visitTupleMake(TupleMake* curr) {} void visitTupleExtract(TupleExtract* curr) {} void visitRefI31(RefI31* curr) {} void visitI31Get(I31Get* curr) { @@ -325,10 +321,9 @@ struct SubtypingDiscoverer : public OverriddenVisitor { // more? etc.), so we do the simple thing here for now of requiring the // target type not generalize. // - if (curr->target->type.isRef()) { - self()->noteNonFlowSubtype( - curr->target, Type(curr->target->type.getHeapType(), Nullable)); - } + // Note that this could be |noteNonFlowSubtype| but since we are comparing + // a type to itself here, that does not matter. + self()->noteSubtype(curr->target, curr->target->type); if (curr->target->type.isSignature()) { handleCall(curr, curr->target->type.getHeapType().getSignature()); From 30d4880372be3324e9e49b04f2355c9a0ffff928 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 14 Oct 2026 09:15:52 -0700 Subject: [PATCH 08/36] undo --- src/ir/subtype-exprs.h | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/ir/subtype-exprs.h b/src/ir/subtype-exprs.h index 8243b05c9fd..d111c46e717 100644 --- a/src/ir/subtype-exprs.h +++ b/src/ir/subtype-exprs.h @@ -334,15 +334,8 @@ struct SubtypingDiscoverer : public OverriddenVisitor { } void visitRefCast(RefCast* curr) { self()->noteCast(curr->ref, curr); - if (curr->desc && curr->desc->type.isRef()) { - self()->noteNonFlowSubtype(curr->desc, curr->desc->type.with(Nullable)); - } } void visitRefGetDesc(RefGetDesc* curr) { - if (curr->ref->type.isRef()) { - self()->noteNonFlowSubtype(curr->ref, - Type(curr->ref->type.getHeapType(), Nullable)); - } } void visitBrOn(BrOn* curr) { if (curr->ref->type.isRef()) { From 62d6f450b58eac1393925024ffada36cf1835929 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 14 Oct 2026 09:17:00 -0700 Subject: [PATCH 09/36] undo --- src/ir/subtype-exprs.h | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/ir/subtype-exprs.h b/src/ir/subtype-exprs.h index d111c46e717..cfa3744fbea 100644 --- a/src/ir/subtype-exprs.h +++ b/src/ir/subtype-exprs.h @@ -338,12 +338,6 @@ struct SubtypingDiscoverer : public OverriddenVisitor { void visitRefGetDesc(RefGetDesc* curr) { } void visitBrOn(BrOn* curr) { - if (curr->ref->type.isRef()) { - self()->noteNonFlowSubtype(curr->ref, curr->ref->type); - } - if (curr->desc && curr->desc->type.isRef()) { - self()->noteNonFlowSubtype(curr->desc, curr->desc->type.with(Nullable)); - } switch (curr->op) { case BrOnNull: case BrOnNonNull: From 6df116abfaea2555792130ffd9e0977e2d9e78d4 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 14 Oct 2026 09:18:38 -0700 Subject: [PATCH 10/36] undo --- src/ir/subtype-exprs.h | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/ir/subtype-exprs.h b/src/ir/subtype-exprs.h index cfa3744fbea..10fb99e10cd 100644 --- a/src/ir/subtype-exprs.h +++ b/src/ir/subtype-exprs.h @@ -370,12 +370,7 @@ struct SubtypingDiscoverer : public OverriddenVisitor { self()->noteSubtype(curr->operands[i], fields[i].type); } } - void visitStructGet(StructGet* curr) { - if (curr->ref->type.isRef()) { - self()->noteNonFlowSubtype(curr->ref, - Type(curr->ref->type.getHeapType(), Nullable)); - } - } + void visitStructGet(StructGet* curr) {} void visitStructSet(StructSet* curr) { if (curr->ref->type.isRef()) { self()->noteNonFlowSubtype(curr->ref, From d5649c8b6cba668688eb560b84f71b1245420459 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 14 Oct 2026 09:35:07 -0700 Subject: [PATCH 11/36] undo --- src/ir/subtype-exprs.h | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/src/ir/subtype-exprs.h b/src/ir/subtype-exprs.h index 10fb99e10cd..6190e97cfd5 100644 --- a/src/ir/subtype-exprs.h +++ b/src/ir/subtype-exprs.h @@ -372,10 +372,6 @@ struct SubtypingDiscoverer : public OverriddenVisitor { } void visitStructGet(StructGet* curr) {} void visitStructSet(StructSet* curr) { - if (curr->ref->type.isRef()) { - self()->noteNonFlowSubtype(curr->ref, - Type(curr->ref->type.getHeapType(), Nullable)); - } if (!curr->ref->type.isStruct()) { return; } @@ -383,10 +379,6 @@ struct SubtypingDiscoverer : public OverriddenVisitor { self()->noteSubtype(curr->value, fields[curr->index].type); } void visitStructRMW(StructRMW* curr) { - if (curr->ref->type.isRef()) { - self()->noteNonFlowSubtype(curr->ref, - Type(curr->ref->type.getHeapType(), Nullable)); - } if (!curr->ref->type.isStruct()) { return; } @@ -394,10 +386,6 @@ struct SubtypingDiscoverer : public OverriddenVisitor { self()->noteSubtype(curr->value, fields[curr->index].type); } void visitStructCmpxchg(StructCmpxchg* curr) { - if (curr->ref->type.isRef()) { - self()->noteNonFlowSubtype(curr->ref, - Type(curr->ref->type.getHeapType(), Nullable)); - } if (!curr->ref->type.isStruct()) { return; } @@ -414,10 +402,6 @@ struct SubtypingDiscoverer : public OverriddenVisitor { void visitStructWait(StructWait* curr) { self()->noteSubtype(curr->waitqueue, Type(HeapTypes::sharedWaitqueue, Nullable)); - if (curr->ref->type.isRef()) { - self()->noteNonFlowSubtype(curr->ref, - Type(curr->ref->type.getHeapType(), Nullable)); - } if (!curr->ref->type.isStruct()) { return; } From 3b83bd7b0f22b4d70b39b38fb73347bcbf5d409b Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 14 Oct 2026 12:54:00 -0700 Subject: [PATCH 12/36] simpl --- src/ir/subtype-exprs.h | 25 +++---------------------- 1 file changed, 3 insertions(+), 22 deletions(-) diff --git a/src/ir/subtype-exprs.h b/src/ir/subtype-exprs.h index 6190e97cfd5..e6b9c5dfc2c 100644 --- a/src/ir/subtype-exprs.h +++ b/src/ir/subtype-exprs.h @@ -444,35 +444,16 @@ struct SubtypingDiscoverer : public OverriddenVisitor { } } - void visitArrayGet(ArrayGet* curr) { - if (curr->ref->type.isRef()) { - self()->noteNonFlowSubtype(curr->ref, - Type(curr->ref->type.getHeapType(), Nullable)); - } - } + void visitArrayGet(ArrayGet* curr) {} void visitArraySet(ArraySet* curr) { - if (curr->ref->type.isRef()) { - self()->noteNonFlowSubtype(curr->ref, - Type(curr->ref->type.getHeapType(), Nullable)); - } if (!curr->ref->type.isArray()) { return; } auto array = curr->ref->type.getHeapType().getArray(); self()->noteSubtype(curr->value, array.element.type); } - void visitArrayLoad(ArrayLoad* curr) { - if (curr->ref->type.isRef()) { - self()->noteNonFlowSubtype(curr->ref, - Type(curr->ref->type.getHeapType(), Nullable)); - } - } - void visitArrayStore(ArrayStore* curr) { - if (curr->ref->type.isRef()) { - self()->noteNonFlowSubtype(curr->ref, - Type(curr->ref->type.getHeapType(), Nullable)); - } - } + void visitArrayLoad(ArrayLoad* curr) {} + void visitArrayStore(ArrayStore* curr) {} void visitArrayLen(ArrayLen* curr) { auto share = Unshared; if (curr->ref->type.isRef() && curr->ref->type.getHeapType().isShared()) { From e2745e84b01fe68fb9bde976e3398f1e9367c6d3 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 14 Oct 2026 12:56:59 -0700 Subject: [PATCH 13/36] simpl --- src/ir/subtype-exprs.h | 52 +----------------------------------------- 1 file changed, 1 insertion(+), 51 deletions(-) diff --git a/src/ir/subtype-exprs.h b/src/ir/subtype-exprs.h index e6b9c5dfc2c..283cf89f602 100644 --- a/src/ir/subtype-exprs.h +++ b/src/ir/subtype-exprs.h @@ -463,48 +463,22 @@ struct SubtypingDiscoverer : public OverriddenVisitor { curr->ref, Type(HeapTypes::array.getBasic(share), Nullable)); } void visitArrayCopy(ArrayCopy* curr) { - if (curr->destRef->type.isNull() || curr->srcRef->type.isNull()) { - if (curr->destRef->type.isNull()) { - self()->noteNonFlowSubtype(curr->destRef, curr->destRef->type); - } - if (curr->srcRef->type.isNull()) { - self()->noteNonFlowSubtype(curr->srcRef, curr->srcRef->type); - } - return; - } if (!curr->srcRef->type.isArray() || !curr->destRef->type.isArray()) { return; } - self()->noteNonFlowSubtype( - curr->destRef, Type(curr->destRef->type.getHeapType(), Nullable)); - self()->noteNonFlowSubtype( - curr->srcRef, Type(curr->srcRef->type.getHeapType(), Nullable)); auto src = curr->srcRef->type.getHeapType().getArray(); auto dest = curr->destRef->type.getHeapType().getArray(); self()->noteSubtype(src.element.type, dest.element.type); } void visitArrayFill(ArrayFill* curr) { - if (curr->ref->type.isRef()) { - self()->noteNonFlowSubtype(curr->ref, - Type(curr->ref->type.getHeapType(), Nullable)); - } if (!curr->ref->type.isArray()) { return; } auto array = curr->ref->type.getHeapType().getArray(); self()->noteSubtype(curr->value, array.element.type); } - void visitArrayInitData(ArrayInitData* curr) { - if (curr->ref->type.isRef()) { - self()->noteNonFlowSubtype(curr->ref, - Type(curr->ref->type.getHeapType(), Nullable)); - } - } + void visitArrayInitData(ArrayInitData* curr) {} void visitArrayInitElem(ArrayInitElem* curr) { - if (curr->ref->type.isRef()) { - self()->noteNonFlowSubtype(curr->ref, - Type(curr->ref->type.getHeapType(), Nullable)); - } if (!curr->ref->type.isArray()) { return; } @@ -513,10 +487,6 @@ struct SubtypingDiscoverer : public OverriddenVisitor { self()->noteSubtype(seg->type, array.element.type); } void visitArrayRMW(ArrayRMW* curr) { - if (curr->ref->type.isRef()) { - self()->noteNonFlowSubtype(curr->ref, - Type(curr->ref->type.getHeapType(), Nullable)); - } if (!curr->ref->type.isArray()) { return; } @@ -524,10 +494,6 @@ struct SubtypingDiscoverer : public OverriddenVisitor { self()->noteSubtype(curr->value, array.element.type); } void visitArrayCmpxchg(ArrayCmpxchg* curr) { - if (curr->ref->type.isRef()) { - self()->noteNonFlowSubtype(curr->ref, - Type(curr->ref->type.getHeapType(), Nullable)); - } if (!curr->ref->type.isArray()) { return; } @@ -627,10 +593,6 @@ struct SubtypingDiscoverer : public OverriddenVisitor { Type(curr->type.getHeapType().getContinuation().type, Nullable)); } void visitContBind(ContBind* curr) { - if (curr->cont->type.isRef()) { - self()->noteNonFlowSubtype( - curr->cont, Type(curr->cont->type.getHeapType(), Nullable)); - } if (!curr->cont->type.isContinuation() || !curr->type.isContinuation()) { return; } @@ -697,10 +659,6 @@ struct SubtypingDiscoverer : public OverriddenVisitor { } } void visitResume(Resume* curr) { - if (curr->cont->type.isRef()) { - self()->noteNonFlowSubtype( - curr->cont, Type(curr->cont->type.getHeapType(), Nullable)); - } if (!curr->cont->type.isContinuation()) { return; } @@ -718,10 +676,6 @@ struct SubtypingDiscoverer : public OverriddenVisitor { } } void visitResumeThrow(ResumeThrow* curr) { - if (curr->cont->type.isRef()) { - self()->noteNonFlowSubtype( - curr->cont, Type(curr->cont->type.getHeapType(), Nullable)); - } if (!curr->cont->type.isContinuation()) { return; } @@ -744,10 +698,6 @@ struct SubtypingDiscoverer : public OverriddenVisitor { } } void visitStackSwitch(StackSwitch* curr) { - if (curr->cont->type.isRef()) { - self()->noteNonFlowSubtype( - curr->cont, Type(curr->cont->type.getHeapType(), Nullable)); - } if (!curr->cont->type.isContinuation()) { return; } From cc70c108853731162046543f4d220768332a4d8d Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 14 Oct 2026 14:47:06 -0700 Subject: [PATCH 14/36] form --- src/ir/subtype-exprs.h | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/ir/subtype-exprs.h b/src/ir/subtype-exprs.h index 283cf89f602..2ae49b1a239 100644 --- a/src/ir/subtype-exprs.h +++ b/src/ir/subtype-exprs.h @@ -332,11 +332,8 @@ struct SubtypingDiscoverer : public OverriddenVisitor { void visitRefTest(RefTest* curr) { self()->noteCast(curr->ref, curr->castType); } - void visitRefCast(RefCast* curr) { - self()->noteCast(curr->ref, curr); - } - void visitRefGetDesc(RefGetDesc* curr) { - } + void visitRefCast(RefCast* curr) { self()->noteCast(curr->ref, curr); } + void visitRefGetDesc(RefGetDesc* curr) {} void visitBrOn(BrOn* curr) { switch (curr->op) { case BrOnNull: From 3aad80b937ad0381471b2cbdeec22d51f876f063 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 14 Oct 2026 14:49:58 -0700 Subject: [PATCH 15/36] comment --- src/passes/TypeGeneralizing.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/passes/TypeGeneralizing.cpp b/src/passes/TypeGeneralizing.cpp index 776d5f09ffd..4826ad39d15 100644 --- a/src/passes/TypeGeneralizing.cpp +++ b/src/passes/TypeGeneralizing.cpp @@ -20,11 +20,6 @@ // that cast to unnecessarily refined types. If the casts are weakened enough, // they will be able to be removed by OptimizeInstructions. // -// Uses SubtypingDiscoverer to find all subtyping constraints on expressions and -// locals, and computes the greatest lower bound (GLB) of the required -// supertypes for each local so that each local is given the least-refined valid -// type. -// #include #include @@ -119,6 +114,12 @@ HeapType generalizeArrayType(HeapType type, return type; } +// +// Uses SubtypingDiscoverer to find all subtyping constraints on expressions and +// locals, and computes the greatest lower bound (GLB) of the required +// supertypes for each local so that each local is given the least-refined valid +// type. +// struct Generalizer : public ControlFlowWalker> { Function* func; From 7f496de661feb03208709a8ab2d3b72bc1e53b12 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 14 Oct 2026 14:53:03 -0700 Subject: [PATCH 16/36] simpl --- src/passes/TypeGeneralizing.cpp | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/passes/TypeGeneralizing.cpp b/src/passes/TypeGeneralizing.cpp index 4826ad39d15..1221476e3bd 100644 --- a/src/passes/TypeGeneralizing.cpp +++ b/src/passes/TypeGeneralizing.cpp @@ -54,16 +54,11 @@ HeapType generalizeCallRefTarget(HeapType sigType, if (resultReq && !Type::isSubType(candidateSig.results, *resultReq)) { break; } - bool paramsMatch = true; for (size_t i = 0; i < numParams; ++i) { if (candidateSig.params[i] != sig.params[i]) { - paramsMatch = false; - break; + return targetReq; } } - if (!paramsMatch) { - break; - } targetReq = *candidateReq; } return targetReq; From 13d2b1559e779900d63e060168b639bd5e538dfc Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 14 Oct 2026 14:55:50 -0700 Subject: [PATCH 17/36] simpl --- src/passes/TypeGeneralizing.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/passes/TypeGeneralizing.cpp b/src/passes/TypeGeneralizing.cpp index 1221476e3bd..202e50180b7 100644 --- a/src/passes/TypeGeneralizing.cpp +++ b/src/passes/TypeGeneralizing.cpp @@ -177,8 +177,6 @@ struct Generalizer void noteCast(Expression* src, Type dst) {} void noteCast(Expression* src, Expression* dst) {} - void visitLocalGet(LocalGet* curr) {} - void visitLocalSet(LocalSet* curr) { auto localType = func->getLocalType(curr->index); if (func->isParam(curr->index)) { From c3067a1e455be9e9ceb45301eb654b6cf7aa73f1 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 14 Oct 2026 15:27:14 -0700 Subject: [PATCH 18/36] update fuzz test --- ...e-to-fuzz_all-features_metrics_noprint.txt | 171 +++++++++--------- 1 file changed, 85 insertions(+), 86 deletions(-) diff --git a/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt b/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt index fd25a01a8d3..6788aaa443d 100644 --- a/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt +++ b/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt @@ -1,100 +1,99 @@ Metrics total - [exports] : 95 - [funcs] : 185 + [exports] : 76 + [funcs] : 154 [globals] : 13 [imports] : 13 [memories] : 1 [memory-data] : 31 - [table-data] : 62 + [table-data] : 54 [tables] : 2 [tags] : 2 - [total] : 84922 - [vars] : 4130 - ArrayCmpxchg : 22 - ArrayCopy : 38 - ArrayFill : 24 - ArrayGet : 354 - ArrayLen : 512 - ArrayNew : 920 - ArrayNewFixed : 314 - ArrayRMW : 17 - ArraySet : 69 - AtomicCmpxchg : 28 - AtomicFence : 61 - AtomicNotify : 19 - AtomicRMW : 29 - AtomicWait : 14 - Binary : 3224 - Block : 6159 - BrOn : 549 - Break : 850 - Call : 805 - CallIndirect : 173 - CallRef : 179 - Const : 11095 - ContNew : 107 + [total] : 64439 + [vars] : 3624 + ArrayCmpxchg : 20 + ArrayCopy : 21 + ArrayFill : 18 + ArrayGet : 235 + ArrayLen : 345 + ArrayNew : 651 + ArrayNewFixed : 264 + ArrayRMW : 16 + ArraySet : 40 + AtomicCmpxchg : 19 + AtomicFence : 42 + AtomicNotify : 17 + AtomicRMW : 26 + AtomicWait : 12 + Binary : 2325 + Block : 4572 + BrOn : 414 + Break : 619 + Call : 654 + CallIndirect : 121 + CallRef : 140 + Const : 8353 + ContNew : 83 DataDrop : 7 - Drop : 754 - ElemDrop : 16 - GlobalGet : 4189 - GlobalSet : 1867 - I31Get : 52 - If : 2179 - Load : 262 - LocalGet : 9178 - LocalSet : 3531 - Loop : 738 - MemoryCopy : 12 + Drop : 607 + ElemDrop : 9 + GlobalGet : 3182 + GlobalSet : 1337 + I31Get : 33 + If : 1577 + Load : 216 + LocalGet : 6879 + LocalSet : 2502 + Loop : 510 + MemoryCopy : 6 MemoryFill : 6 - MemoryGrow : 33 - MemoryInit : 15 - MemorySize : 34 - Nop : 570 - Pop : 163 - RefAs : 7978 - RefCast : 614 - RefEq : 213 - RefFunc : 1551 - RefGetDesc : 57 - RefI31 : 620 - RefIsNull : 53 - RefNull : 8785 - RefTest : 56 - Return : 385 - SIMDExtract : 111 + MemoryGrow : 24 + MemoryInit : 11 + MemorySize : 31 + Nop : 411 + Pop : 111 + RefAs : 6096 + RefCast : 491 + RefEq : 145 + RefFunc : 1222 + RefGetDesc : 42 + RefI31 : 460 + RefIsNull : 41 + RefNull : 7037 + RefTest : 37 + Return : 313 + SIMDExtract : 110 SIMDLoad : 3 - SIMDLoadStoreLane: 52 - SIMDReplace : 8 + SIMDLoadStoreLane: 32 + SIMDReplace : 6 SIMDShift : 1 SIMDShuffle : 1 - SIMDTernary : 2 - Select : 284 - Store : 117 - StringConst : 349 - StringEncode : 60 - StringEq : 62 - StringMeasure : 54 + SIMDTernary : 4 + Select : 237 + Store : 112 + StringConst : 269 + StringEncode : 45 + StringEq : 45 + StringMeasure : 34 StringNew : 3 - StringSliceWTF : 1 - StringWTF16Get : 47 - StructCmpxchg : 36 - StructGet : 408 - StructNew : 9381 - StructRMW : 37 - StructSet : 61 - Switch : 10 - TableGet : 1 - TableGrow : 67 - TableSet : 65 - TableSize : 77 - Throw : 111 - ThrowRef : 8 - Try : 332 - TryTable : 441 - TupleExtract : 246 - TupleMake : 241 - Unary : 1770 - Unreachable : 961 - WideIntAddSub : 20 - WideIntMul : 14 + StringWTF16Get : 33 + StructCmpxchg : 25 + StructGet : 304 + StructNew : 7498 + StructRMW : 26 + StructSet : 44 + Switch : 8 + TableGet : 2 + TableGrow : 56 + TableSet : 46 + TableSize : 59 + Throw : 109 + ThrowRef : 4 + Try : 251 + TryTable : 340 + TupleExtract : 168 + TupleMake : 194 + Unary : 1302 + Unreachable : 696 + WideIntAddSub : 11 + WideIntMul : 11 From 1bedbe6bab67daa29a95f04301c7083ae578b050 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 15 Oct 2026 09:48:32 -0700 Subject: [PATCH 19/36] reorder to execution order --- src/ir/subtype-exprs.h | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/ir/subtype-exprs.h b/src/ir/subtype-exprs.h index 2ae49b1a239..2ad50a07d68 100644 --- a/src/ir/subtype-exprs.h +++ b/src/ir/subtype-exprs.h @@ -353,19 +353,18 @@ struct SubtypingDiscoverer : public OverriddenVisitor { if (!curr->type.isStruct()) { return; } + if (!curr->isWithDefault()) { + const auto& fields = curr->type.getHeapType().getStruct().fields; + assert(fields.size() == curr->operands.size()); + for (size_t i = 0, size = fields.size(); i < size; ++i) { + self()->noteSubtype(curr->operands[i], fields[i].type); + } + } if (curr->desc) { auto descType = curr->type.getHeapType().getDescriptorType(); assert(descType); self()->noteSubtype(curr->desc, Type(*descType, Nullable, Exact)); } - if (curr->isWithDefault()) { - return; - } - const auto& fields = curr->type.getHeapType().getStruct().fields; - assert(fields.size() == curr->operands.size()); - for (size_t i = 0, size = fields.size(); i < size; ++i) { - self()->noteSubtype(curr->operands[i], fields[i].type); - } } void visitStructGet(StructGet* curr) {} void visitStructSet(StructSet* curr) { From 3c629de469a3c93d0fc61eaa27abf5e83e3873f3 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 15 Oct 2026 09:51:57 -0700 Subject: [PATCH 20/36] Update test/lit/passes/type-generalizing.wast Co-authored-by: Thomas Lively --- test/lit/passes/type-generalizing.wast | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/lit/passes/type-generalizing.wast b/test/lit/passes/type-generalizing.wast index b22af61f1f9..3fcf0643c4b 100644 --- a/test/lit/passes/type-generalizing.wast +++ b/test/lit/passes/type-generalizing.wast @@ -1664,7 +1664,7 @@ (local $var (ref null $outer-sub)) ;; The outer struct.get requires (ref null $inner-sub), which is nullable. ;; $outer-super's field 0 has non-nullable type (ref $inner-super), which is - ;; incomparable with (ref null $inner-sub); we must still stop at $outer-sub. + ;; incomparable with the i32 field of $inner-sub; we must still stop at $outer-sub. (struct.get $inner-sub 0 (struct.get $outer-sub 0 (local.get $var) From 7dd90b7f1ea232c3bbad33e0c54765ef15a2ac2c Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 15 Oct 2026 09:57:40 -0700 Subject: [PATCH 21/36] rephrease test --- test/lit/passes/type-generalizing.wast | 34 +++++++++++++------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/test/lit/passes/type-generalizing.wast b/test/lit/passes/type-generalizing.wast index 3fcf0643c4b..a2cc23287fc 100644 --- a/test/lit/passes/type-generalizing.wast +++ b/test/lit/passes/type-generalizing.wast @@ -1641,32 +1641,32 @@ ) (module - ;; CHECK: (type $inner-super (sub (struct))) - (type $inner-super (sub (struct))) - ;; CHECK: (type $inner-sub (sub $inner-super (struct (field i32)))) - (type $inner-sub (sub $inner-super (struct (field i32)))) - ;; CHECK: (type $outer-super (sub (struct (field (ref $inner-super))))) - (type $outer-super (sub (struct (field (ref $inner-super))))) - ;; CHECK: (type $outer-sub (sub $outer-super (struct (field (ref $inner-sub))))) - (type $outer-sub (sub $outer-super (struct (field (ref $inner-sub))))) + ;; CHECK: (type $outer-super (sub (struct))) + (type $outer-super (sub (struct))) + ;; CHECK: (type $outer-sub (sub $outer-super (struct (field i32)))) + (type $outer-sub (sub $outer-super (struct (field i32)))) + ;; CHECK: (type $inner-super (sub (struct (field (ref $outer-super))))) + (type $inner-super (sub (struct (field (ref $outer-super))))) + ;; CHECK: (type $inner-sub (sub $inner-super (struct (field (ref $outer-sub))))) + (type $inner-sub (sub $inner-super (struct (field (ref $outer-sub))))) ;; CHECK: (type $4 (func (result i32))) ;; CHECK: (func $struct-get-non-null-field (type $4) (result i32) - ;; CHECK-NEXT: (local $var (ref null $outer-sub)) - ;; CHECK-NEXT: (struct.get $inner-sub 0 - ;; CHECK-NEXT: (struct.get $outer-sub 0 + ;; CHECK-NEXT: (local $var (ref null $inner-sub)) + ;; CHECK-NEXT: (struct.get $outer-sub 0 + ;; CHECK-NEXT: (struct.get $inner-sub 0 ;; CHECK-NEXT: (local.get $var) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $struct-get-non-null-field (result i32) - (local $var (ref null $outer-sub)) - ;; The outer struct.get requires (ref null $inner-sub), which is nullable. - ;; $outer-super's field 0 has non-nullable type (ref $inner-super), which is - ;; incomparable with the i32 field of $inner-sub; we must still stop at $outer-sub. - (struct.get $inner-sub 0 - (struct.get $outer-sub 0 + (local $var (ref null $inner-sub)) + ;; The outer struct.get must return an i32, so it cannot be generalized from + ;; $outer-sub to $outer-super. That means we cannot generalize the inner + ;; struct.get from $inner-sub to $inner-super (which would emit $outer-super). + (struct.get $outer-sub 0 + (struct.get $inner-sub 0 (local.get $var) ) ) From 44bff470450a39203430c0a50b81ea96e0b8a9ba Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 15 Oct 2026 10:02:02 -0700 Subject: [PATCH 22/36] todo --- src/passes/TypeGeneralizing.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/passes/TypeGeneralizing.cpp b/src/passes/TypeGeneralizing.cpp index 202e50180b7..ab9cdbbb2e6 100644 --- a/src/passes/TypeGeneralizing.cpp +++ b/src/passes/TypeGeneralizing.cpp @@ -109,12 +109,13 @@ HeapType generalizeArrayType(HeapType type, return type; } -// -// Uses SubtypingDiscoverer to find all subtyping constraints on expressions and +// Use SubtypingDiscoverer to find all subtyping constraints on expressions and // locals, and computes the greatest lower bound (GLB) of the required // supertypes for each local so that each local is given the least-refined valid // type. // +// TODO: handle ref.as_non_null, br_on_null, publish, br_if, etc. better: we do +// not need to consider their results fixed. struct Generalizer : public ControlFlowWalker> { Function* func; From 84e4977b62f0e3329871977e624e8c6f46fcfbd8 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 15 Oct 2026 11:27:51 -0700 Subject: [PATCH 23/36] add tests for gaps --- test/lit/passes/type-generalizing.wast | 811 +++++++++++++++++++++++++ test/lit/passes/unsubtyping.wast | 31 + 2 files changed, 842 insertions(+) diff --git a/test/lit/passes/type-generalizing.wast b/test/lit/passes/type-generalizing.wast index a2cc23287fc..3f66486a9f0 100644 --- a/test/lit/passes/type-generalizing.wast +++ b/test/lit/passes/type-generalizing.wast @@ -1672,3 +1672,814 @@ ) ) ) + +(module + ;; CHECK: (type $0 (func (result eqref))) + + ;; CHECK: (type $1 (func (param eqref anyref))) + + ;; CHECK: (type $2 (func)) + + ;; CHECK: (type $3 (func (param (ref (shared noextern))) (result (ref null (shared any))))) + + ;; CHECK: (type $4 (func (param (ref (shared noextern))) (result (ref (shared any))))) + + ;; CHECK: (type $5 (func (result (ref null (shared extern))))) + + ;; CHECK: (type $6 (func (result (ref (shared extern))))) + + ;; CHECK: (table $eq-table 0 0 eqref) + (table $eq-table 0 0 eqref) + + ;; CHECK: (func $br_if (type $1) (param $eq eqref) (param $any anyref) + ;; CHECK-NEXT: (local $var1 eqref) + ;; CHECK-NEXT: (local $var2 eqref) + ;; CHECK-NEXT: (local.set $eq + ;; CHECK-NEXT: (block $l1 (result eqref) + ;; CHECK-NEXT: (local.set $any + ;; CHECK-NEXT: (br_if $l1 + ;; CHECK-NEXT: (local.get $var1) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $any + ;; CHECK-NEXT: (block $l2 (result eqref) + ;; CHECK-NEXT: (local.set $eq + ;; CHECK-NEXT: (br_if $l2 + ;; CHECK-NEXT: (local.get $var2) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $br_if (param $eq eqref) (param $any anyref) + (local $var1 i31ref) + (local $var2 i31ref) + ;; Require typeof($var1) <: eqref from the branch target. + (local.set $eq + (block $l1 (result i31ref) + (local.set $any + (br_if $l1 + (local.get $var1) + (i32.const 0) + ) + ) + (unreachable) + ) + ) + ;; Require typeof($var2) <: eqref from the value flowing through the br_if. + (local.set $any + (block $l2 (result i31ref) + (local.set $eq + (br_if $l2 + (local.get $var2) + (i32.const 0) + ) + ) + (unreachable) + ) + ) + ) + + ;; CHECK: (func $return (type $0) (result eqref) + ;; CHECK-NEXT: (local $var eqref) + ;; CHECK-NEXT: (return + ;; CHECK-NEXT: (local.get $var) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $return (result eqref) + (local $var i31ref) + ;; Require typeof($var) <: eqref. + (return + (local.get $var) + ) + ) + + ;; CHECK: (func $table-grow (type $2) + ;; CHECK-NEXT: (local $var eqref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (table.grow $eq-table + ;; CHECK-NEXT: (local.get $var) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $table-grow + (local $var i31ref) + ;; Require typeof($var) <: eqref. + (drop + (table.grow $eq-table + (local.get $var) + (i32.const 0) + ) + ) + ) + + ;; CHECK: (func $any-convert-extern-shared-nullable (type $3) (param $x (ref (shared noextern))) (result (ref null (shared any))) + ;; CHECK-NEXT: (local $var (ref null (shared extern))) + ;; CHECK-NEXT: (local.set $var + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (any.convert_extern + ;; CHECK-NEXT: (local.get $var) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $any-convert-extern-shared-nullable (param $x (ref (shared noextern))) (result (ref null (shared any))) + (local $var (ref (shared noextern))) + (local.set $var + (local.get $x) + ) + ;; Require that typeof($var) <: (ref null (shared extern)). + (any.convert_extern + (local.get $var) + ) + ) + + ;; CHECK: (func $any-convert-extern-shared-non-nullable (type $4) (param $x (ref (shared noextern))) (result (ref (shared any))) + ;; CHECK-NEXT: (local $var (ref (shared extern))) + ;; CHECK-NEXT: (local.set $var + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (any.convert_extern + ;; CHECK-NEXT: (local.get $var) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $any-convert-extern-shared-non-nullable (param $x (ref (shared noextern))) (result (ref (shared any))) + (local $var (ref (shared noextern))) + (local.set $var + (local.get $x) + ) + ;; Require that typeof($var) <: (ref (shared extern)). + (any.convert_extern + (local.get $var) + ) + ) + + ;; CHECK: (func $extern-convert-any-shared-nullable (type $5) (result (ref null (shared extern))) + ;; CHECK-NEXT: (local $var (ref null (shared any))) + ;; CHECK-NEXT: (local.set $var + ;; CHECK-NEXT: (ref.i31_shared + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (extern.convert_any + ;; CHECK-NEXT: (local.get $var) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $extern-convert-any-shared-nullable (result (ref null (shared extern))) + (local $var (ref (shared i31))) + (local.set $var + (ref.i31_shared + (i32.const 0) + ) + ) + ;; Require that typeof($var) <: (ref null (shared any)). + (extern.convert_any + (local.get $var) + ) + ) + + ;; CHECK: (func $extern-convert-any-shared-non-nullable (type $6) (result (ref (shared extern))) + ;; CHECK-NEXT: (local $var (ref (shared any))) + ;; CHECK-NEXT: (local.set $var + ;; CHECK-NEXT: (ref.i31_shared + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (extern.convert_any + ;; CHECK-NEXT: (local.get $var) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $extern-convert-any-shared-non-nullable (result (ref (shared extern))) + (local $var (ref (shared i31))) + (local.set $var + (ref.i31_shared + (i32.const 0) + ) + ) + ;; Require that typeof($var) <: (ref (shared any)). + (extern.convert_any + (local.get $var) + ) + ) + + ;; CHECK: (func $publish (type $0) (result eqref) + ;; CHECK-NEXT: (local $var1 anyref) + ;; CHECK-NEXT: (local $var2 eqref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (publish + ;; CHECK-NEXT: (local.get $var1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (publish + ;; CHECK-NEXT: (local.get $var2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $publish (result eqref) + (local $var1 i31ref) + (local $var2 i31ref) + ;; No constraint on $var1 because the publish is dropped. + (drop + (publish + (local.get $var1) + ) + ) + ;; Require that typeof($var2) <: eqref through the publish. + (publish + (local.get $var2) + ) + ) +) + +(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 $bot (sub $mid (func (param eqref) (result eqref)))) + (type $bot (sub $mid (func (param eqref) (result eqref)))) + + ;; CHECK: (type $3 (func (result anyref))) + + ;; CHECK: (type $4 (func (result eqref))) + + ;; CHECK: (func $return-call-ref-params-limited (type $3) (result anyref) + ;; CHECK-NEXT: (local $f (ref null $mid)) + ;; CHECK-NEXT: (local $arg eqref) + ;; CHECK-NEXT: (return_call_ref $mid + ;; CHECK-NEXT: (local.get $arg) + ;; CHECK-NEXT: (local.get $f) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $return-call-ref-params-limited (result anyref) + (local $f (ref null $bot)) + (local $arg i31ref) + ;; Same as $call-ref-params-limited, but with return_call_ref. + (return_call_ref $bot + (local.get $arg) + (local.get $f) + ) + ) + + ;; CHECK: (func $return-call-ref-results-limited (type $4) (result eqref) + ;; CHECK-NEXT: (local $f (ref null $bot)) + ;; CHECK-NEXT: (local $arg eqref) + ;; CHECK-NEXT: (return_call_ref $bot + ;; CHECK-NEXT: (local.get $arg) + ;; CHECK-NEXT: (local.get $f) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $return-call-ref-results-limited (result eqref) + (local $f (ref null $bot)) + (local $arg i31ref) + ;; Same as $call-ref-results-limited, but with return_call_ref, testing that + ;; the function's return type constrains the target signature. + (return_call_ref $bot + (local.get $arg) + (local.get $f) + ) + ) +) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $described (descriptor $desc) (struct (field eqref) (field anyref))) + (type $described (descriptor $desc) (struct (field eqref) (field anyref))) + ;; CHECK: (type $desc (describes $described) (struct)) + (type $desc (describes $described) (struct)) + ) + + ;; CHECK: (type $2 (func (result anyref))) + + ;; CHECK: (func $struct-new-desc (type $2) (result anyref) + ;; CHECK-NEXT: (local $var1 eqref) + ;; CHECK-NEXT: (local $var2 anyref) + ;; CHECK-NEXT: (local $desc (ref null (exact $desc))) + ;; CHECK-NEXT: (struct.new_desc $described + ;; CHECK-NEXT: (local.get $var1) + ;; CHECK-NEXT: (local.get $var2) + ;; CHECK-NEXT: (local.get $desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $struct-new-desc (result anyref) + (local $var1 i31ref) + (local $var2 i31ref) + (local $desc nullref) + ;; Require that typeof($var1) <: eqref, typeof($var2) <: anyref, and + ;; typeof($desc) <: (ref null (exact $desc)). + (struct.new_desc $described + (local.get $var1) + (local.get $var2) + (local.get $desc) + ) + ) + + ;; CHECK: (func $struct-new-default-desc (type $2) (result anyref) + ;; CHECK-NEXT: (local $desc (ref null (exact $desc))) + ;; CHECK-NEXT: (struct.new_default_desc $described + ;; CHECK-NEXT: (local.get $desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $struct-new-default-desc (result anyref) + (local $desc nullref) + ;; Require that typeof($desc) <: (ref null (exact $desc)). + (struct.new_default_desc $described + (local.get $desc) + ) + ) +) + +(module + ;; CHECK: (type $0 (func)) + + ;; CHECK: (type $mut-i31 (sub (array (mut i31ref)))) + + ;; CHECK: (type $super (sub (array eqref))) + (type $super (sub (array (field eqref)))) + ;; CHECK: (type $sub (sub $super (array i31ref))) + (type $sub (sub $super (array (field i31ref)))) + (type $mut-i31 (sub (array (field (mut i31ref))))) + (type $shared-array (shared (array i8))) + + ;; CHECK: (type $4 (func (result i31ref))) + + ;; CHECK: (func $array-get-type (type $4) (result i31ref) + ;; CHECK-NEXT: (local $val (ref null $sub)) + ;; CHECK-NEXT: (array.get $sub + ;; CHECK-NEXT: (local.get $val) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $array-get-type (result i31ref) + (local $val (ref null $sub)) + ;; Require that typeof($val) <: (ref null $sub) because further supertypes do + ;; not satisfy the requirement on the element type. + (array.get $sub + (local.get $val) + (i32.const 0) + ) + ) + + ;; CHECK: (func $array-len-shared (type $0) + ;; CHECK-NEXT: (local $ref (ref null (shared array))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (array.len + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $array-len-shared + (local $ref (ref null $shared-array)) + (drop + ;; Require that typeof($ref) <: (ref null (shared array)). + (array.len + (local.get $ref) + ) + ) + ) + + ;; CHECK: (func $array-copy-ref-limited (type $0) + ;; CHECK-NEXT: (local $dest (ref null $mut-i31)) + ;; CHECK-NEXT: (local $src (ref null $sub)) + ;; CHECK-NEXT: (array.copy $mut-i31 $sub + ;; 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-limited + (local $dest (ref null $mut-i31)) + (local $src (ref null $sub)) + ;; Require that typeof($dest) <: $mut-i31 and that typeof($src) <: $sub + ;; because $super's element type (eqref) is not a subtype of $mut-i31's + ;; element type (i31ref). + (array.copy $mut-i31 $sub + (local.get $dest) + (i32.const 0) + (local.get $src) + (i32.const 0) + (i32.const 0) + ) + ) +) + +(module + ;; CHECK: (type $outer-super (sub (struct))) + (type $outer-super (sub (struct))) + ;; CHECK: (type $outer-sub (sub $outer-super (struct (field i32)))) + (type $outer-sub (sub $outer-super (struct (field i32)))) + ;; CHECK: (type $2 (func (result i32))) + + ;; CHECK: (type $array-super (sub (array (ref $outer-super)))) + (type $array-super (sub (array (ref $outer-super)))) + ;; CHECK: (type $array-sub (sub $array-super (array (ref $outer-sub)))) + (type $array-sub (sub $array-super (array (ref $outer-sub)))) + ;; CHECK: (type $func-super (sub (func (result (ref $outer-super))))) + (type $func-super (sub (func (result (ref $outer-super))))) + ;; CHECK: (type $func-sub (sub $func-super (func (result (ref $outer-sub))))) + (type $func-sub (sub $func-super (func (result (ref $outer-sub))))) + + ;; CHECK: (func $array-get-non-null-elem (type $2) (result i32) + ;; CHECK-NEXT: (local $var (ref null $array-sub)) + ;; CHECK-NEXT: (struct.get $outer-sub 0 + ;; CHECK-NEXT: (array.get $array-sub + ;; CHECK-NEXT: (local.get $var) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $array-get-non-null-elem (result i32) + (local $var (ref null $array-sub)) + ;; Same as $struct-get-non-null-field, but with an inner array.get. + (struct.get $outer-sub 0 + (array.get $array-sub + (local.get $var) + (i32.const 0) + ) + ) + ) + + ;; CHECK: (func $call-ref-non-null-result (type $2) (result i32) + ;; CHECK-NEXT: (local $var (ref null $func-sub)) + ;; CHECK-NEXT: (struct.get $outer-sub 0 + ;; CHECK-NEXT: (call_ref $func-sub + ;; CHECK-NEXT: (local.get $var) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $call-ref-non-null-result (result i32) + (local $var (ref null $func-sub)) + ;; Same as $struct-get-non-null-field, but with an inner call_ref. + (struct.get $outer-sub 0 + (call_ref $func-sub + (local.get $var) + ) + ) + ) +) + +(module + ;; CHECK: (type $0 (func)) + + ;; CHECK: (type $1 (func (result stringref))) + + ;; CHECK: (type $i8-array (array (mut i8))) + (type $i8-array (array (mut i8))) + ;; CHECK: (type $i16-array (array (mut i16))) + (type $i16-array (array (mut i16))) + + ;; CHECK: (type $4 (func (result externref))) + + ;; CHECK: (type $5 (func (result i32))) + + ;; CHECK: (func $string-new-utf8 (type $1) (result stringref) + ;; CHECK-NEXT: (local $arr (ref null $i8-array)) + ;; CHECK-NEXT: (string.new_lossy_utf8_array + ;; CHECK-NEXT: (local.get $arr) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $string-new-utf8 (result stringref) + (local $arr nullref) + ;; Require typeof($arr) <: (ref null $i8-array). + (string.new_lossy_utf8_array + (local.get $arr) + (i32.const 0) + (i32.const 0) + ) + ) + + ;; CHECK: (func $string-new-wtf16 (type $1) (result stringref) + ;; CHECK-NEXT: (local $arr (ref null $i16-array)) + ;; CHECK-NEXT: (string.new_wtf16_array + ;; CHECK-NEXT: (local.get $arr) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $string-new-wtf16 (result stringref) + (local $arr nullref) + ;; Require typeof($arr) <: (ref null $i16-array). + (string.new_wtf16_array + (local.get $arr) + (i32.const 0) + (i32.const 0) + ) + ) + + ;; CHECK: (func $string-from-code-point-and-const (type $0) + ;; CHECK-NEXT: (local $s1 externref) + ;; CHECK-NEXT: (local $s2 externref) + ;; CHECK-NEXT: (local.set $s1 + ;; CHECK-NEXT: (string.from_code_point + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $s2 + ;; CHECK-NEXT: (string.const "hello") + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $string-from-code-point-and-const + (local $s1 stringref) + (local $s2 stringref) + ;; No constraints on $s1 or $s2, so they generalize to externref. + (local.set $s1 + (string.from_code_point + (i32.const 0) + ) + ) + (local.set $s2 + (string.const "hello") + ) + ) + + ;; CHECK: (func $string-measure (type $0) + ;; CHECK-NEXT: (local $s1 stringref) + ;; CHECK-NEXT: (local $s2 stringref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (string.measure_utf8 + ;; CHECK-NEXT: (local.get $s1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (string.measure_wtf16 + ;; CHECK-NEXT: (local.get $s2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $string-measure + (local $s1 nullexternref) + (local $s2 nullexternref) + ;; Require typeof($s1) <: stringref and typeof($s2) <: stringref. + (drop + (string.measure_utf8 + (local.get $s1) + ) + ) + (drop + (string.measure_wtf16 + (local.get $s2) + ) + ) + ) + + ;; CHECK: (func $string-encode-utf8 (type $0) + ;; CHECK-NEXT: (local $s stringref) + ;; CHECK-NEXT: (local $arr (ref null $i8-array)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (string.encode_lossy_utf8_array + ;; CHECK-NEXT: (local.get $s) + ;; CHECK-NEXT: (local.get $arr) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $string-encode-utf8 + (local $s nullexternref) + (local $arr nullref) + ;; Require typeof($s) <: stringref and typeof($arr) <: (ref null $i8-array). + (drop + (string.encode_lossy_utf8_array + (local.get $s) + (local.get $arr) + (i32.const 0) + ) + ) + ) + + ;; CHECK: (func $string-encode-wtf16 (type $0) + ;; CHECK-NEXT: (local $s stringref) + ;; CHECK-NEXT: (local $arr (ref null $i16-array)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (string.encode_wtf16_array + ;; CHECK-NEXT: (local.get $s) + ;; CHECK-NEXT: (local.get $arr) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $string-encode-wtf16 + (local $s nullexternref) + (local $arr nullref) + ;; Require typeof($s) <: stringref and typeof($arr) <: (ref null $i16-array). + (drop + (string.encode_wtf16_array + (local.get $s) + (local.get $arr) + (i32.const 0) + ) + ) + ) + + ;; CHECK: (func $string-concat (type $4) (result externref) + ;; CHECK-NEXT: (local $s1 stringref) + ;; CHECK-NEXT: (local $s2 stringref) + ;; CHECK-NEXT: (string.concat + ;; CHECK-NEXT: (local.get $s1) + ;; CHECK-NEXT: (local.get $s2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $string-concat (result externref) + (local $s1 nullexternref) + (local $s2 nullexternref) + ;; Require typeof($s1) <: stringref and typeof($s2) <: stringref. + (string.concat + (local.get $s1) + (local.get $s2) + ) + ) + + ;; CHECK: (func $string-eq (type $0) + ;; CHECK-NEXT: (local $s1 stringref) + ;; CHECK-NEXT: (local $s2 stringref) + ;; CHECK-NEXT: (local $s3 stringref) + ;; CHECK-NEXT: (local $s4 stringref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (string.eq + ;; CHECK-NEXT: (local.get $s1) + ;; CHECK-NEXT: (local.get $s2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (string.compare + ;; CHECK-NEXT: (local.get $s3) + ;; CHECK-NEXT: (local.get $s4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $string-eq + (local $s1 nullexternref) + (local $s2 nullexternref) + (local $s3 nullexternref) + (local $s4 nullexternref) + ;; Require all operands <: stringref. + (drop + (string.eq + (local.get $s1) + (local.get $s2) + ) + ) + (drop + (string.compare + (local.get $s3) + (local.get $s4) + ) + ) + ) + + ;; CHECK: (func $string-test (type $5) (result i32) + ;; CHECK-NEXT: (local $s externref) + ;; CHECK-NEXT: (string.test + ;; CHECK-NEXT: (local.get $s) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $string-test (result i32) + (local $s stringref) + ;; Require typeof($s) <: externref. + (string.test + (local.get $s) + ) + ) + + ;; CHECK: (func $string-wtf16-get (type $5) (result i32) + ;; CHECK-NEXT: (local $s stringref) + ;; CHECK-NEXT: (stringview_wtf16.get_codeunit + ;; CHECK-NEXT: (local.get $s) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $string-wtf16-get (result i32) + (local $s nullexternref) + ;; Require typeof($s) <: stringref. + (stringview_wtf16.get_codeunit + (local.get $s) + (i32.const 0) + ) + ) + + ;; CHECK: (func $string-slice-wtf (type $4) (result externref) + ;; CHECK-NEXT: (local $s stringref) + ;; CHECK-NEXT: (stringview_wtf16.slice + ;; CHECK-NEXT: (local.get $s) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $string-slice-wtf (result externref) + (local $s nullexternref) + ;; Require typeof($s) <: stringref. + (stringview_wtf16.slice + (local.get $s) + (i32.const 0) + (i32.const 0) + ) + ) +) + +(module + ;; CHECK: (type $func-void (func)) + + ;; CHECK: (type $cont-void (cont $func-void)) + + ;; CHECK: (type $super-func (sub (func (param eqref) (result eqref)))) + (type $super-func (sub (func (param eqref) (result eqref)))) + (type $sub-func (sub $super-func (func (param anyref) (result i31ref)))) + ;; CHECK: (type $3 (func (param (ref null $cont-void)))) + + ;; CHECK: (type $4 (func (param eqref))) + + ;; CHECK: (type $cont-super (cont $super-func)) + (type $cont-super (cont $super-func)) + (type $func-void (func)) + (type $cont-void (cont $func-void)) + + ;; CHECK: (tag $tag-eq (type $4) (param eqref)) + (tag $tag-eq (param eqref)) + + ;; CHECK: (func $throw (type $func-void) + ;; CHECK-NEXT: (local $val eqref) + ;; CHECK-NEXT: (throw $tag-eq + ;; CHECK-NEXT: (local.get $val) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $throw + (local $val i31ref) + ;; Require typeof($val) <: eqref. + (throw $tag-eq + (local.get $val) + ) + ) + + ;; CHECK: (func $throw-ref (type $func-void) + ;; CHECK-NEXT: (local $exn exnref) + ;; CHECK-NEXT: (throw_ref + ;; CHECK-NEXT: (local.get $exn) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $throw-ref + (local $exn nullexnref) + ;; Require typeof($exn) <: exnref. + (throw_ref + (local.get $exn) + ) + ) + + ;; CHECK: (func $cont-new (type $func-void) + ;; CHECK-NEXT: (local $f (ref null $super-func)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (cont.new $cont-super + ;; CHECK-NEXT: (local.get $f) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cont-new + (local $f (ref null $sub-func)) + ;; Require typeof($f) <: (ref null $super-func). + (drop + (cont.new $cont-super + (local.get $f) + ) + ) + ) + + ;; CHECK: (func $resume-throw (type $3) (param $c (ref null $cont-void)) + ;; CHECK-NEXT: (local $val eqref) + ;; CHECK-NEXT: (resume_throw $cont-void $tag-eq + ;; CHECK-NEXT: (local.get $val) + ;; CHECK-NEXT: (local.get $c) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $resume-throw (param $c (ref null $cont-void)) + (local $val i31ref) + ;; Require typeof($val) <: eqref. + (resume_throw $cont-void $tag-eq + (local.get $val) + (local.get $c) + ) + ) + + ;; CHECK: (func $resume-throw-ref (type $3) (param $c (ref null $cont-void)) + ;; CHECK-NEXT: (local $exn exnref) + ;; CHECK-NEXT: (resume_throw_ref $cont-void + ;; CHECK-NEXT: (local.get $exn) + ;; CHECK-NEXT: (local.get $c) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $resume-throw-ref (param $c (ref null $cont-void)) + (local $exn nullexnref) + ;; Require typeof($exn) <: exnref. + (resume_throw_ref $cont-void + (local.get $exn) + (local.get $c) + ) + ) +) diff --git a/test/lit/passes/unsubtyping.wast b/test/lit/passes/unsubtyping.wast index bbbeb8e7252..228d23c1d91 100644 --- a/test/lit/passes/unsubtyping.wast +++ b/test/lit/passes/unsubtyping.wast @@ -2059,3 +2059,34 @@ ) ) ) + +(module + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super (sub (struct))) + (type $super (sub (struct))) + ;; CHECK: (type $sub (sub $super (struct))) + (type $sub (sub $super (struct))) + + ;; CHECK: (type $2 (func)) + + ;; CHECK: (table $t 1 1 (ref null $super)) + (table $t 1 1 (ref null $super)) + + ;; CHECK: (func $table-grow (type $2) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (table.grow $t + ;; CHECK-NEXT: (struct.new_default $sub) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $table-grow + ;; This requires $sub <: $super. + (drop + (table.grow $t + (struct.new $sub) + (i32.const 0) + ) + ) + ) +) From 03f21b1addf3ff8b31b676438b985fa8ce153189 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 21 Oct 2026 13:11:58 -0700 Subject: [PATCH 24/36] comment --- src/passes/TypeGeneralizing.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/passes/TypeGeneralizing.cpp b/src/passes/TypeGeneralizing.cpp index ab9cdbbb2e6..ca231a45133 100644 --- a/src/passes/TypeGeneralizing.cpp +++ b/src/passes/TypeGeneralizing.cpp @@ -132,6 +132,7 @@ struct Generalizer // For each local, the values assigned to it via local.set / local.tee. std::vector> localSetValues; + // We use a worklist of things to process: expressions and local indexes. using WorkItem = std::variant; std::vector worklist; From 72bb9475ab71080ccaba2e325e8b30a2159d19fc Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 21 Oct 2026 13:14:47 -0700 Subject: [PATCH 25/36] mergefix --- src/passes/TypeGeneralizing.cpp | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/passes/TypeGeneralizing.cpp b/src/passes/TypeGeneralizing.cpp index ca231a45133..c85fc90d98a 100644 --- a/src/passes/TypeGeneralizing.cpp +++ b/src/passes/TypeGeneralizing.cpp @@ -197,7 +197,7 @@ struct Generalizer } void visitCallRef(CallRef* curr) { - if (curr->target->type.isNull()) { + if (curr->target->type.isBottomRef()) { addConstraint(curr->target, curr->target->type); return; } @@ -215,7 +215,7 @@ struct Generalizer } void visitStructGet(StructGet* curr) { - if (curr->ref->type.isNull()) { + if (curr->ref->type.isBottomRef()) { addConstraint(curr->ref, curr->ref->type); return; } @@ -228,7 +228,7 @@ struct Generalizer } void visitStructSet(StructSet* curr) { - if (curr->ref->type.isNull()) { + if (curr->ref->type.isBottomRef()) { addConstraint(curr->ref, curr->ref->type); return; } @@ -243,7 +243,7 @@ struct Generalizer } void visitArrayGet(ArrayGet* curr) { - if (curr->ref->type.isNull()) { + if (curr->ref->type.isBottomRef()) { addConstraint(curr->ref, curr->ref->type); return; } @@ -255,7 +255,7 @@ struct Generalizer } void visitArraySet(ArraySet* curr) { - if (curr->ref->type.isNull()) { + if (curr->ref->type.isBottomRef()) { addConstraint(curr->ref, curr->ref->type); return; } @@ -268,11 +268,11 @@ struct Generalizer } void visitArrayCopy(ArrayCopy* curr) { - if (curr->destRef->type.isNull() || curr->srcRef->type.isNull()) { - if (curr->destRef->type.isNull()) { + if (curr->destRef->type.isBottomRef() || curr->srcRef->type.isBottomRef()) { + if (curr->destRef->type.isBottomRef()) { addConstraint(curr->destRef, curr->destRef->type); } - if (curr->srcRef->type.isNull()) { + if (curr->srcRef->type.isBottomRef()) { addConstraint(curr->srcRef, curr->srcRef->type); } return; @@ -293,7 +293,7 @@ struct Generalizer } void visitArrayFill(ArrayFill* curr) { - if (curr->ref->type.isNull()) { + if (curr->ref->type.isBottomRef()) { addConstraint(curr->ref, curr->ref->type); return; } @@ -306,7 +306,7 @@ struct Generalizer } void visitArrayInitData(ArrayInitData* curr) { - if (curr->ref->type.isNull()) { + if (curr->ref->type.isBottomRef()) { addConstraint(curr->ref, curr->ref->type); return; } @@ -318,7 +318,7 @@ struct Generalizer } void visitArrayInitElem(ArrayInitElem* curr) { - if (curr->ref->type.isNull()) { + if (curr->ref->type.isBottomRef()) { addConstraint(curr->ref, curr->ref->type); return; } From 11b5a9b0714bf9b082bb9b7e55d0eeb7bf9ab1b7 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 22 Oct 2026 11:41:06 -0700 Subject: [PATCH 26/36] fix fuzz crashes on descriptor generalizing to any --- src/ir/subtype-exprs.h | 14 +++++- test/lit/passes/type-generalizing.wast | 65 ++++++++++++++++++++++++++ 2 files changed, 78 insertions(+), 1 deletion(-) diff --git a/src/ir/subtype-exprs.h b/src/ir/subtype-exprs.h index 4fbdaafacf1..e4b53432c42 100644 --- a/src/ir/subtype-exprs.h +++ b/src/ir/subtype-exprs.h @@ -335,7 +335,15 @@ struct SubtypingDiscoverer : public OverriddenVisitor { void visitRefTest(RefTest* curr) { self()->noteCast(curr->ref, curr->castType); } - void visitRefCast(RefCast* curr) { self()->noteCast(curr->ref, curr); } + void visitRefCast(RefCast* curr) { + self()->noteCast(curr->ref, curr); + + if (curr->desc) { + // As with CallRef, we must keep a descriptor valid: it must be a declared + // descriptor type. Until we have addStructSubtype, make it keep its type. + self()->noteSubtype(curr->desc, curr->desc->type); + } + } void visitRefGetDesc(RefGetDesc* curr) {} void visitBrOn(BrOn* curr) { switch (curr->op) { @@ -347,6 +355,10 @@ struct SubtypingDiscoverer : public OverriddenVisitor { case BrOnCastDescEq: case BrOnCastDescEqFail: self()->noteCast(curr->ref, curr->castType); + if (curr->desc) { + // As with RefCast, we must keep a descriptor valid. + self()->noteSubtype(curr->desc, curr->desc->type); + } break; } self()->noteSubtype(curr->getSentType(), diff --git a/test/lit/passes/type-generalizing.wast b/test/lit/passes/type-generalizing.wast index 3f66486a9f0..9277da18737 100644 --- a/test/lit/passes/type-generalizing.wast +++ b/test/lit/passes/type-generalizing.wast @@ -2483,3 +2483,68 @@ ) ) ) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $A (descriptor $A.desc) (struct)) + (type $A (descriptor $A.desc) (struct)) + ;; CHECK: (type $A.desc (describes $A) (struct)) + (type $A.desc (describes $A) (struct)) + ) + + ;; CHECK: (type $2 (func)) + + ;; CHECK: (type $3 (func (param anyref))) + + ;; CHECK: (func $ref.cast_desc_eq (type $2) + ;; CHECK-NEXT: (local $x (ref null $A.desc)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast_desc_eq (ref $A) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ref.cast_desc_eq + (local $x (ref null $A.desc)) + ;; We cannot generalize x to anyref: it is used as descriptor, and must + ;; remain a valid descriptor type. + (drop + (ref.cast_desc_eq (ref $A) + (ref.null eq) + (local.get $x) + ) + ) + ) + + ;; CHECK: (func $br-on-cast-desc-eq (type $3) (param $any anyref) + ;; CHECK-NEXT: (local $x (ref null $A.desc)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $l (result (ref null $A)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (br_on_cast_desc_eq $l anyref (ref null $A) + ;; CHECK-NEXT: (local.get $any) + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $br-on-cast-desc-eq (param $any anyref) + (local $x (ref null $A.desc)) + ;; As above, with br_on. + (drop + (block $l (result (ref null $A)) + (br_on_cast_desc_eq $l anyref (ref null $A) + (local.get $any) + (local.get $x) + ) + (unreachable) + ) + ) + ) + ) +) + From fcaf2fc1b6855b822c65ae2e34ed7d97b954efa2 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 22 Oct 2026 12:56:25 -0700 Subject: [PATCH 27/36] fix another crash, on br_on_non_null --- src/ir/subtype-exprs.h | 12 +++++++++++- test/lit/passes/type-generalizing.wast | 26 ++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/src/ir/subtype-exprs.h b/src/ir/subtype-exprs.h index e4b53432c42..005528c6dd3 100644 --- a/src/ir/subtype-exprs.h +++ b/src/ir/subtype-exprs.h @@ -346,9 +346,19 @@ struct SubtypingDiscoverer : public OverriddenVisitor { } void visitRefGetDesc(RefGetDesc* curr) {} void visitBrOn(BrOn* curr) { + auto* breakTarget = self()->findBreakTarget(curr->name); switch (curr->op) { case BrOnNull: + break; case BrOnNonNull: + // The value sent is made non-nullable and then sent. That means it + // must have the right heap type. + if (breakTarget->type.isRef()) { + // Enforce the heap type requirement by making sure we are a subtype + // of the target, made nullable (i.e., without any non-nullability + // requirement, as we are making it non-nullable anyhow). + self()->noteSubtype(curr->ref, breakTarget->type.with(Nullable)); + } break; case BrOnCast: case BrOnCastFail: @@ -362,7 +372,7 @@ struct SubtypingDiscoverer : public OverriddenVisitor { break; } self()->noteSubtype(curr->getSentType(), - self()->findBreakTarget(curr->name)); + breakTarget); } void visitStructNew(StructNew* curr) { if (!curr->type.isStruct()) { diff --git a/test/lit/passes/type-generalizing.wast b/test/lit/passes/type-generalizing.wast index 9277da18737..efca80b6a1f 100644 --- a/test/lit/passes/type-generalizing.wast +++ b/test/lit/passes/type-generalizing.wast @@ -2548,3 +2548,29 @@ ) ) +(module + ;; CHECK: (type $A (sub (struct))) + (type $A (sub (struct))) + + ;; CHECK: (type $1 (func (result (ref $A)))) + + ;; CHECK: (func $br_on_non_null (type $1) (result (ref $A)) + ;; CHECK-NEXT: (local $x (ref null $A)) + ;; CHECK-NEXT: (block $block (result (ref $A)) + ;; CHECK-NEXT: (br_on_non_null $block + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $br_on_non_null (result (ref $A)) + (local $x (ref null $A)) + ;; We cannot refine the local to funcref, as the result is (ref $A). + (block $block (result (ref $A)) + (br_on_non_null $block + (local.get $x) + ) + (unreachable) + ) + ) +) From 8ad69f0814f2917daf0672c94b9fe4489eeed98c Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 22 Oct 2026 13:01:47 -0700 Subject: [PATCH 28/36] whitespace.fix --- test/lit/passes/constraint-analysis.wast | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/lit/passes/constraint-analysis.wast b/test/lit/passes/constraint-analysis.wast index b5bba9857d5..d444460146d 100644 --- a/test/lit/passes/constraint-analysis.wast +++ b/test/lit/passes/constraint-analysis.wast @@ -7267,7 +7267,7 @@ (local.get $x) ) ) - + ;; CHECK: (func $asymmetric-contradiction (type $2) (param $0 i32) (param $1 i32) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (i32.and From b9bffdc2ace92bf817b1174025fc209792f0a38a Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 22 Oct 2026 13:23:15 -0700 Subject: [PATCH 29/36] fix.struct_array.atomics --- src/passes/TypeGeneralizing.cpp | 18 +++++++++++++++ test/lit/passes/type-generalizing.wast | 31 ++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/src/passes/TypeGeneralizing.cpp b/src/passes/TypeGeneralizing.cpp index c85fc90d98a..e9a3bd591b7 100644 --- a/src/passes/TypeGeneralizing.cpp +++ b/src/passes/TypeGeneralizing.cpp @@ -242,6 +242,15 @@ struct Generalizer generalized.getStruct().fields[curr->index].type); } + // TODO: optimize these like the above + void visitStructRMW(StructRMW* curr) { + // Ensure we do not generalize too much. + addConstraint(curr->ref, curr->ref->type); + } + void visitStructCmpxchg(StructCmpxchg* curr) { + addConstraint(curr->ref, curr->ref->type); + } + void visitArrayGet(ArrayGet* curr) { if (curr->ref->type.isBottomRef()) { addConstraint(curr->ref, curr->ref->type); @@ -329,6 +338,15 @@ struct Generalizer addConstraint(curr->ref, Type(generalized, Nullable)); } + // TODO: optimize these like the above + void visitArrayRMW(ArrayRMW* curr) { + // Ensure we do not generalize too much. + addConstraint(curr->ref, curr->ref->type); + } + void visitArrayCmpxchg(ArrayCmpxchg* curr) { + addConstraint(curr->ref, curr->ref->type); + } + void propagate() { while (!worklist.empty()) { auto item = worklist.back(); diff --git a/test/lit/passes/type-generalizing.wast b/test/lit/passes/type-generalizing.wast index efca80b6a1f..ea81750e8bc 100644 --- a/test/lit/passes/type-generalizing.wast +++ b/test/lit/passes/type-generalizing.wast @@ -2574,3 +2574,34 @@ ) ) ) + +(module + ;; CHECK: (type $0 (func (result i32))) + + ;; CHECK: (type $A (struct (field (mut i32)))) + (type $A (struct (field (mut i32)))) + + ;; CHECK: (func $struct.atomic (type $0) (result i32) + ;; CHECK-NEXT: (local $x (ref null $A)) + ;; CHECK-NEXT: (struct.atomic.rmw.cmpxchg $A 0 + ;; CHECK-NEXT: (local.tee $x + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $struct.atomic (result i32) + ;; TODO: Optimize struct/array atomics. For now, just do not error by + ;; generalizing too much so validation breaks. + (local $x (ref null $A)) + (struct.atomic.rmw.cmpxchg $A 0 + (local.tee $x + (ref.null none) + ) + (i32.const 0) + (i32.const 0) + ) + ) +) + From d9800a23df989387d78a74e2cd2b236abce2b37a Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 22 Oct 2026 13:23:23 -0700 Subject: [PATCH 30/36] format --- src/ir/subtype-exprs.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/ir/subtype-exprs.h b/src/ir/subtype-exprs.h index 005528c6dd3..66eb56d1a31 100644 --- a/src/ir/subtype-exprs.h +++ b/src/ir/subtype-exprs.h @@ -371,8 +371,7 @@ struct SubtypingDiscoverer : public OverriddenVisitor { } break; } - self()->noteSubtype(curr->getSentType(), - breakTarget); + self()->noteSubtype(curr->getSentType(), breakTarget); } void visitStructNew(StructNew* curr) { if (!curr->type.isStruct()) { From 740348a5079d2c267544ca078755180d0da725f3 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 22 Oct 2026 14:03:05 -0700 Subject: [PATCH 31/36] failing br_on --- src/ir/subtype-exprs.h | 7 ++++++ test/lit/passes/type-generalizing.wast | 30 +++++++++++++++++++++++++- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/src/ir/subtype-exprs.h b/src/ir/subtype-exprs.h index 66eb56d1a31..9bfaa7b85fe 100644 --- a/src/ir/subtype-exprs.h +++ b/src/ir/subtype-exprs.h @@ -369,6 +369,13 @@ struct SubtypingDiscoverer : public OverriddenVisitor { // As with RefCast, we must keep a descriptor valid. self()->noteSubtype(curr->desc, curr->desc->type); } + if (curr->op == BrOnCastFail || curr->op == BrOnCastDescEqFail) { + // We send the original input along when the cast fails, so the input + // must be sendable to the target. + if (breakTarget->type.isRef()) { + self()->noteSubtype(curr->ref, breakTarget->type); + } + } break; } self()->noteSubtype(curr->getSentType(), breakTarget); diff --git a/test/lit/passes/type-generalizing.wast b/test/lit/passes/type-generalizing.wast index ea81750e8bc..f239788d9c7 100644 --- a/test/lit/passes/type-generalizing.wast +++ b/test/lit/passes/type-generalizing.wast @@ -2573,6 +2573,35 @@ (unreachable) ) ) + + ;; CHECK: (func $br_on_cast_fail (type $1) (result (ref $A)) + ;; CHECK-NEXT: (local $A (ref $A)) + ;; CHECK-NEXT: (block $block (result (ref $A)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (br_on_cast_fail $block (ref $A) (ref $A) + ;; CHECK-NEXT: (local.tee $A + ;; CHECK-NEXT: (struct.new_default $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $br_on_cast_fail (result (ref $A)) + (local $A (ref $A)) + ;; The block must return $A, so the cast_fail must send it, and the local + ;; cannot be refined to anyref. + (block $block (result (ref $A)) + (drop + (br_on_cast_fail $block (ref $A) (ref $A) + (local.tee $A + (struct.new $A) + ) + ) + ) + (unreachable) + ) + ) ) (module @@ -2604,4 +2633,3 @@ ) ) ) - From 9f5cb464681347275bd4e0e4e8a4e251dc51e97c Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 22 Oct 2026 16:04:46 -0700 Subject: [PATCH 32/36] fix.tuple.make --- src/ir/subtype-exprs.h | 10 ++++++++- test/lit/passes/type-generalizing.wast | 31 +++++++++++++++++++++++--- 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/src/ir/subtype-exprs.h b/src/ir/subtype-exprs.h index 9bfaa7b85fe..7609c22d3e9 100644 --- a/src/ir/subtype-exprs.h +++ b/src/ir/subtype-exprs.h @@ -294,7 +294,15 @@ struct SubtypingDiscoverer : public OverriddenVisitor { void visitThrowRef(ThrowRef* curr) { self()->noteNonFlowSubtype(curr->exnref, Type(HeapType::exn, Nullable)); } - void visitTupleMake(TupleMake* curr) {} + void visitTupleMake(TupleMake* curr) { + if (curr->type != Type::unreachable) { + assert(curr->operands.size() == curr->type.size()); + // Ensure the inputs make sense for the tuple. + for (size_t i = 0, size = curr->operands.size(); i < size; ++i) { + self()->noteSubtype(curr->operands[i], curr->type[i]); + } + } + } void visitTupleExtract(TupleExtract* curr) {} void visitRefI31(RefI31* curr) {} void visitI31Get(I31Get* curr) { diff --git a/test/lit/passes/type-generalizing.wast b/test/lit/passes/type-generalizing.wast index f239788d9c7..f4014dce9c1 100644 --- a/test/lit/passes/type-generalizing.wast +++ b/test/lit/passes/type-generalizing.wast @@ -2605,12 +2605,14 @@ ) (module - ;; CHECK: (type $0 (func (result i32))) - ;; CHECK: (type $A (struct (field (mut i32)))) (type $A (struct (field (mut i32)))) - ;; CHECK: (func $struct.atomic (type $0) (result i32) + ;; CHECK: (type $1 (func (result i32))) + + ;; CHECK: (type $2 (func (result i32 (ref null $A)))) + + ;; CHECK: (func $struct.atomic (type $1) (result i32) ;; CHECK-NEXT: (local $x (ref null $A)) ;; CHECK-NEXT: (struct.atomic.rmw.cmpxchg $A 0 ;; CHECK-NEXT: (local.tee $x @@ -2632,4 +2634,27 @@ (i32.const 0) ) ) + + ;; CHECK: (func $tuple.make (type $2) (result i32 (ref null $A)) + ;; CHECK-NEXT: (local $x (ref null $A)) + ;; CHECK-NEXT: (local $tuple (tuple i32 (ref null $A))) + ;; CHECK-NEXT: (local.tee $tuple + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $tuple.make (result i32 (ref null $A)) + ;; TODO: Optimize tuples. For now, just do not error by generalizing operands + ;; of tuple.make too much so validation breaks. + (local $x (ref null $A)) + (local $tuple (tuple i32 (ref null $A))) + (local.tee $tuple + (tuple.make 2 + (i32.const 0) + (local.get $x) + ) + ) + ) ) From b8108cf227ccb2cd0fe199d2eb158f54346aaa2f Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 22 Oct 2026 16:14:36 -0700 Subject: [PATCH 33/36] fix ref.get_desc --- src/ir/subtype-exprs.h | 7 ++++++- test/lit/passes/type-generalizing.wast | 17 ++++++++++++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/ir/subtype-exprs.h b/src/ir/subtype-exprs.h index 7609c22d3e9..ed9e268d466 100644 --- a/src/ir/subtype-exprs.h +++ b/src/ir/subtype-exprs.h @@ -352,7 +352,12 @@ struct SubtypingDiscoverer : public OverriddenVisitor { self()->noteSubtype(curr->desc, curr->desc->type); } } - void visitRefGetDesc(RefGetDesc* curr) {} + void visitRefGetDesc(RefGetDesc* curr) { + // Similar to RefCastDesc, we must keep the input a valid struct type, one + // with a descriptor. Until we have addStructSubtype or such, make it keep + // its type. + self()->noteSubtype(curr->ref, curr->ref->type); + } void visitBrOn(BrOn* curr) { auto* breakTarget = self()->findBreakTarget(curr->name); switch (curr->op) { diff --git a/test/lit/passes/type-generalizing.wast b/test/lit/passes/type-generalizing.wast index f4014dce9c1..2d3c666758a 100644 --- a/test/lit/passes/type-generalizing.wast +++ b/test/lit/passes/type-generalizing.wast @@ -2497,6 +2497,8 @@ ;; CHECK: (type $3 (func (param anyref))) + ;; CHECK: (type $4 (func (result (ref $A.desc)))) + ;; CHECK: (func $ref.cast_desc_eq (type $2) ;; CHECK-NEXT: (local $x (ref null $A.desc)) ;; CHECK-NEXT: (drop @@ -2542,10 +2544,23 @@ (local.get $x) ) (unreachable) - ) ) ) ) + + ;; CHECK: (func $ref.get_desc (type $4) (result (ref $A.desc)) + ;; CHECK-NEXT: (local $x (ref null $A)) + ;; CHECK-NEXT: (ref.get_desc $A + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ref.get_desc (result (ref $A.desc)) + ;; We cannot generalize x to anyref: it must have a descriptor. + (local $x (ref null $A)) + (ref.get_desc $A + (local.get $x) + ) + ) ) (module From 47512389834742685dd9aa9dc4ca49db045e9f35 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 23 Oct 2026 08:50:03 -0700 Subject: [PATCH 34/36] separate out string tests, which cannot be fuzzed yet --- .../lit/passes/type-generalizing-strings.wast | 262 ++++++++++++++++++ test/lit/passes/type-generalizing.wast | 259 ----------------- 2 files changed, 262 insertions(+), 259 deletions(-) create mode 100644 test/lit/passes/type-generalizing-strings.wast diff --git a/test/lit/passes/type-generalizing-strings.wast b/test/lit/passes/type-generalizing-strings.wast new file mode 100644 index 00000000000..9917645712e --- /dev/null +++ b/test/lit/passes/type-generalizing-strings.wast @@ -0,0 +1,262 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; RUN: foreach %s %t wasm-opt --experimental-type-generalizing -all -S -o - | filecheck %s + +(module + ;; CHECK: (type $0 (func)) + + ;; CHECK: (type $1 (func (result stringref))) + + ;; CHECK: (type $i8-array (array (mut i8))) + (type $i8-array (array (mut i8))) + ;; CHECK: (type $i16-array (array (mut i16))) + (type $i16-array (array (mut i16))) + + ;; CHECK: (type $4 (func (result externref))) + + ;; CHECK: (type $5 (func (result i32))) + + ;; CHECK: (func $string-new-utf8 (type $1) (result stringref) + ;; CHECK-NEXT: (local $arr (ref null $i8-array)) + ;; CHECK-NEXT: (string.new_lossy_utf8_array + ;; CHECK-NEXT: (local.get $arr) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $string-new-utf8 (result stringref) + (local $arr nullref) + ;; Require typeof($arr) <: (ref null $i8-array). + (string.new_lossy_utf8_array + (local.get $arr) + (i32.const 0) + (i32.const 0) + ) + ) + + ;; CHECK: (func $string-new-wtf16 (type $1) (result stringref) + ;; CHECK-NEXT: (local $arr (ref null $i16-array)) + ;; CHECK-NEXT: (string.new_wtf16_array + ;; CHECK-NEXT: (local.get $arr) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $string-new-wtf16 (result stringref) + (local $arr nullref) + ;; Require typeof($arr) <: (ref null $i16-array). + (string.new_wtf16_array + (local.get $arr) + (i32.const 0) + (i32.const 0) + ) + ) + + ;; CHECK: (func $string-from-code-point-and-const (type $0) + ;; CHECK-NEXT: (local $s1 externref) + ;; CHECK-NEXT: (local $s2 externref) + ;; CHECK-NEXT: (local.set $s1 + ;; CHECK-NEXT: (string.from_code_point + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $s2 + ;; CHECK-NEXT: (string.const "hello") + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $string-from-code-point-and-const + (local $s1 stringref) + (local $s2 stringref) + ;; No constraints on $s1 or $s2, so they generalize to externref. + (local.set $s1 + (string.from_code_point + (i32.const 0) + ) + ) + (local.set $s2 + (string.const "hello") + ) + ) + + ;; CHECK: (func $string-measure (type $0) + ;; CHECK-NEXT: (local $s1 stringref) + ;; CHECK-NEXT: (local $s2 stringref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (string.measure_utf8 + ;; CHECK-NEXT: (local.get $s1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (string.measure_wtf16 + ;; CHECK-NEXT: (local.get $s2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $string-measure + (local $s1 nullexternref) + (local $s2 nullexternref) + ;; Require typeof($s1) <: stringref and typeof($s2) <: stringref. + (drop + (string.measure_utf8 + (local.get $s1) + ) + ) + (drop + (string.measure_wtf16 + (local.get $s2) + ) + ) + ) + + ;; CHECK: (func $string-encode-utf8 (type $0) + ;; CHECK-NEXT: (local $s stringref) + ;; CHECK-NEXT: (local $arr (ref null $i8-array)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (string.encode_lossy_utf8_array + ;; CHECK-NEXT: (local.get $s) + ;; CHECK-NEXT: (local.get $arr) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $string-encode-utf8 + (local $s nullexternref) + (local $arr nullref) + ;; Require typeof($s) <: stringref and typeof($arr) <: (ref null $i8-array). + (drop + (string.encode_lossy_utf8_array + (local.get $s) + (local.get $arr) + (i32.const 0) + ) + ) + ) + + ;; CHECK: (func $string-encode-wtf16 (type $0) + ;; CHECK-NEXT: (local $s stringref) + ;; CHECK-NEXT: (local $arr (ref null $i16-array)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (string.encode_wtf16_array + ;; CHECK-NEXT: (local.get $s) + ;; CHECK-NEXT: (local.get $arr) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $string-encode-wtf16 + (local $s nullexternref) + (local $arr nullref) + ;; Require typeof($s) <: stringref and typeof($arr) <: (ref null $i16-array). + (drop + (string.encode_wtf16_array + (local.get $s) + (local.get $arr) + (i32.const 0) + ) + ) + ) + + ;; CHECK: (func $string-concat (type $4) (result externref) + ;; CHECK-NEXT: (local $s1 stringref) + ;; CHECK-NEXT: (local $s2 stringref) + ;; CHECK-NEXT: (string.concat + ;; CHECK-NEXT: (local.get $s1) + ;; CHECK-NEXT: (local.get $s2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $string-concat (result externref) + (local $s1 nullexternref) + (local $s2 nullexternref) + ;; Require typeof($s1) <: stringref and typeof($s2) <: stringref. + (string.concat + (local.get $s1) + (local.get $s2) + ) + ) + + ;; CHECK: (func $string-eq (type $0) + ;; CHECK-NEXT: (local $s1 stringref) + ;; CHECK-NEXT: (local $s2 stringref) + ;; CHECK-NEXT: (local $s3 stringref) + ;; CHECK-NEXT: (local $s4 stringref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (string.eq + ;; CHECK-NEXT: (local.get $s1) + ;; CHECK-NEXT: (local.get $s2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (string.compare + ;; CHECK-NEXT: (local.get $s3) + ;; CHECK-NEXT: (local.get $s4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $string-eq + (local $s1 nullexternref) + (local $s2 nullexternref) + (local $s3 nullexternref) + (local $s4 nullexternref) + ;; Require all operands <: stringref. + (drop + (string.eq + (local.get $s1) + (local.get $s2) + ) + ) + (drop + (string.compare + (local.get $s3) + (local.get $s4) + ) + ) + ) + + ;; CHECK: (func $string-test (type $5) (result i32) + ;; CHECK-NEXT: (local $s externref) + ;; CHECK-NEXT: (string.test + ;; CHECK-NEXT: (local.get $s) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $string-test (result i32) + (local $s stringref) + ;; Require typeof($s) <: externref. + (string.test + (local.get $s) + ) + ) + + ;; CHECK: (func $string-wtf16-get (type $5) (result i32) + ;; CHECK-NEXT: (local $s stringref) + ;; CHECK-NEXT: (stringview_wtf16.get_codeunit + ;; CHECK-NEXT: (local.get $s) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $string-wtf16-get (result i32) + (local $s nullexternref) + ;; Require typeof($s) <: stringref. + (stringview_wtf16.get_codeunit + (local.get $s) + (i32.const 0) + ) + ) + + ;; CHECK: (func $string-slice-wtf (type $4) (result externref) + ;; CHECK-NEXT: (local $s stringref) + ;; CHECK-NEXT: (stringview_wtf16.slice + ;; CHECK-NEXT: (local.get $s) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $string-slice-wtf (result externref) + (local $s nullexternref) + ;; Require typeof($s) <: stringref. + (stringview_wtf16.slice + (local.get $s) + (i32.const 0) + (i32.const 0) + ) + ) +) diff --git a/test/lit/passes/type-generalizing.wast b/test/lit/passes/type-generalizing.wast index 2d3c666758a..5058d8b61b4 100644 --- a/test/lit/passes/type-generalizing.wast +++ b/test/lit/passes/type-generalizing.wast @@ -2126,265 +2126,6 @@ ) ) -(module - ;; CHECK: (type $0 (func)) - - ;; CHECK: (type $1 (func (result stringref))) - - ;; CHECK: (type $i8-array (array (mut i8))) - (type $i8-array (array (mut i8))) - ;; CHECK: (type $i16-array (array (mut i16))) - (type $i16-array (array (mut i16))) - - ;; CHECK: (type $4 (func (result externref))) - - ;; CHECK: (type $5 (func (result i32))) - - ;; CHECK: (func $string-new-utf8 (type $1) (result stringref) - ;; CHECK-NEXT: (local $arr (ref null $i8-array)) - ;; CHECK-NEXT: (string.new_lossy_utf8_array - ;; CHECK-NEXT: (local.get $arr) - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $string-new-utf8 (result stringref) - (local $arr nullref) - ;; Require typeof($arr) <: (ref null $i8-array). - (string.new_lossy_utf8_array - (local.get $arr) - (i32.const 0) - (i32.const 0) - ) - ) - - ;; CHECK: (func $string-new-wtf16 (type $1) (result stringref) - ;; CHECK-NEXT: (local $arr (ref null $i16-array)) - ;; CHECK-NEXT: (string.new_wtf16_array - ;; CHECK-NEXT: (local.get $arr) - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $string-new-wtf16 (result stringref) - (local $arr nullref) - ;; Require typeof($arr) <: (ref null $i16-array). - (string.new_wtf16_array - (local.get $arr) - (i32.const 0) - (i32.const 0) - ) - ) - - ;; CHECK: (func $string-from-code-point-and-const (type $0) - ;; CHECK-NEXT: (local $s1 externref) - ;; CHECK-NEXT: (local $s2 externref) - ;; CHECK-NEXT: (local.set $s1 - ;; CHECK-NEXT: (string.from_code_point - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $s2 - ;; CHECK-NEXT: (string.const "hello") - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $string-from-code-point-and-const - (local $s1 stringref) - (local $s2 stringref) - ;; No constraints on $s1 or $s2, so they generalize to externref. - (local.set $s1 - (string.from_code_point - (i32.const 0) - ) - ) - (local.set $s2 - (string.const "hello") - ) - ) - - ;; CHECK: (func $string-measure (type $0) - ;; CHECK-NEXT: (local $s1 stringref) - ;; CHECK-NEXT: (local $s2 stringref) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (string.measure_utf8 - ;; CHECK-NEXT: (local.get $s1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (string.measure_wtf16 - ;; CHECK-NEXT: (local.get $s2) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $string-measure - (local $s1 nullexternref) - (local $s2 nullexternref) - ;; Require typeof($s1) <: stringref and typeof($s2) <: stringref. - (drop - (string.measure_utf8 - (local.get $s1) - ) - ) - (drop - (string.measure_wtf16 - (local.get $s2) - ) - ) - ) - - ;; CHECK: (func $string-encode-utf8 (type $0) - ;; CHECK-NEXT: (local $s stringref) - ;; CHECK-NEXT: (local $arr (ref null $i8-array)) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (string.encode_lossy_utf8_array - ;; CHECK-NEXT: (local.get $s) - ;; CHECK-NEXT: (local.get $arr) - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $string-encode-utf8 - (local $s nullexternref) - (local $arr nullref) - ;; Require typeof($s) <: stringref and typeof($arr) <: (ref null $i8-array). - (drop - (string.encode_lossy_utf8_array - (local.get $s) - (local.get $arr) - (i32.const 0) - ) - ) - ) - - ;; CHECK: (func $string-encode-wtf16 (type $0) - ;; CHECK-NEXT: (local $s stringref) - ;; CHECK-NEXT: (local $arr (ref null $i16-array)) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (string.encode_wtf16_array - ;; CHECK-NEXT: (local.get $s) - ;; CHECK-NEXT: (local.get $arr) - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $string-encode-wtf16 - (local $s nullexternref) - (local $arr nullref) - ;; Require typeof($s) <: stringref and typeof($arr) <: (ref null $i16-array). - (drop - (string.encode_wtf16_array - (local.get $s) - (local.get $arr) - (i32.const 0) - ) - ) - ) - - ;; CHECK: (func $string-concat (type $4) (result externref) - ;; CHECK-NEXT: (local $s1 stringref) - ;; CHECK-NEXT: (local $s2 stringref) - ;; CHECK-NEXT: (string.concat - ;; CHECK-NEXT: (local.get $s1) - ;; CHECK-NEXT: (local.get $s2) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $string-concat (result externref) - (local $s1 nullexternref) - (local $s2 nullexternref) - ;; Require typeof($s1) <: stringref and typeof($s2) <: stringref. - (string.concat - (local.get $s1) - (local.get $s2) - ) - ) - - ;; CHECK: (func $string-eq (type $0) - ;; CHECK-NEXT: (local $s1 stringref) - ;; CHECK-NEXT: (local $s2 stringref) - ;; CHECK-NEXT: (local $s3 stringref) - ;; CHECK-NEXT: (local $s4 stringref) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (string.eq - ;; CHECK-NEXT: (local.get $s1) - ;; CHECK-NEXT: (local.get $s2) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (string.compare - ;; CHECK-NEXT: (local.get $s3) - ;; CHECK-NEXT: (local.get $s4) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $string-eq - (local $s1 nullexternref) - (local $s2 nullexternref) - (local $s3 nullexternref) - (local $s4 nullexternref) - ;; Require all operands <: stringref. - (drop - (string.eq - (local.get $s1) - (local.get $s2) - ) - ) - (drop - (string.compare - (local.get $s3) - (local.get $s4) - ) - ) - ) - - ;; CHECK: (func $string-test (type $5) (result i32) - ;; CHECK-NEXT: (local $s externref) - ;; CHECK-NEXT: (string.test - ;; CHECK-NEXT: (local.get $s) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $string-test (result i32) - (local $s stringref) - ;; Require typeof($s) <: externref. - (string.test - (local.get $s) - ) - ) - - ;; CHECK: (func $string-wtf16-get (type $5) (result i32) - ;; CHECK-NEXT: (local $s stringref) - ;; CHECK-NEXT: (stringview_wtf16.get_codeunit - ;; CHECK-NEXT: (local.get $s) - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $string-wtf16-get (result i32) - (local $s nullexternref) - ;; Require typeof($s) <: stringref. - (stringview_wtf16.get_codeunit - (local.get $s) - (i32.const 0) - ) - ) - - ;; CHECK: (func $string-slice-wtf (type $4) (result externref) - ;; CHECK-NEXT: (local $s stringref) - ;; CHECK-NEXT: (stringview_wtf16.slice - ;; CHECK-NEXT: (local.get $s) - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $string-slice-wtf (result externref) - (local $s nullexternref) - ;; Require typeof($s) <: stringref. - (stringview_wtf16.slice - (local.get $s) - (i32.const 0) - (i32.const 0) - ) - ) -) - (module ;; CHECK: (type $func-void (func)) From ee64c80932481c9c2d33acf4a02fa5e0ae6e4d03 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 23 Oct 2026 09:35:48 -0700 Subject: [PATCH 35/36] more cast fixes --- src/ir/subtype-exprs.h | 17 ++++- test/lit/passes/type-generalizing.wast | 90 ++++++++++++++++++++++++++ 2 files changed, 106 insertions(+), 1 deletion(-) diff --git a/src/ir/subtype-exprs.h b/src/ir/subtype-exprs.h index ed9e268d466..b13050b46ec 100644 --- a/src/ir/subtype-exprs.h +++ b/src/ir/subtype-exprs.h @@ -362,6 +362,10 @@ struct SubtypingDiscoverer : public OverriddenVisitor { auto* breakTarget = self()->findBreakTarget(curr->name); switch (curr->op) { case BrOnNull: + // If we do not branch, we flow out the existing value as non-null. + if (curr->type.isRef()) { + self()->noteSubtype(curr->ref, curr->type.with(Nullable)); + } break; case BrOnNonNull: // The value sent is made non-nullable and then sent. That means it @@ -382,7 +386,18 @@ struct SubtypingDiscoverer : public OverriddenVisitor { // As with RefCast, we must keep a descriptor valid. self()->noteSubtype(curr->desc, curr->desc->type); } - if (curr->op == BrOnCastFail || curr->op == BrOnCastDescEqFail) { + if (curr->op == BrOnCast || curr->op == BrOnCastDescEq) { + // If we do not branch, we flow out the original input when the cast + // fails. If the cast type is nullable then nulls take the branch, so + // the fallthrough value is made non-nullable. + if (curr->type.isRef()) { + auto type = curr->type; + if (curr->castType.isNullableRef()) { + type = type.with(Nullable); + } + self()->noteSubtype(curr->ref, type); + } + } else { // We send the original input along when the cast fails, so the input // must be sendable to the target. if (breakTarget->type.isRef()) { diff --git a/test/lit/passes/type-generalizing.wast b/test/lit/passes/type-generalizing.wast index 5058d8b61b4..0b9e138a601 100644 --- a/test/lit/passes/type-generalizing.wast +++ b/test/lit/passes/type-generalizing.wast @@ -2414,3 +2414,93 @@ ) ) ) + +(module + ;; CHECK: (type $A (sub (struct))) + (type $A (sub (struct))) + ;; CHECK: (type $1 (func (result (ref $A)))) + + ;; CHECK: (type $B (sub $A (struct))) + (type $B (sub $A (struct))) + + ;; CHECK: (func $br_on_cast_fail_nullable (type $1) (result (ref $A)) + ;; CHECK-NEXT: (local $x (ref null $A)) + ;; CHECK-NEXT: (block $block (result (ref $A)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (br_on_cast_fail $block (ref null $A) (ref null $B) + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $br_on_cast_fail_nullable (result (ref $A)) + (local $x (ref null $B)) + ;; The cast type is nullable, so nulls succeed the cast and do not branch. + ;; The sent value is therefore non-nullable, allowing $x to generalize to + ;; nullable (ref null $A) even though $block expects non-nullable (ref $A). + (block $block (result (ref $A)) + (drop + (br_on_cast_fail $block (ref null $B) (ref null $B) + (local.get $x) + ) + ) + (unreachable) + ) + ) + + ;; CHECK: (func $br_on_null (type $1) (result (ref $A)) + ;; CHECK-NEXT: (local $x (ref null $A)) + ;; CHECK-NEXT: (block $block + ;; CHECK-NEXT: (return + ;; CHECK-NEXT: (br_on_null $block + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $br_on_null (result (ref $A)) + (local $x (ref null $A)) + ;; The fallthrough value is returned as (ref $A), so $x cannot generalize to + ;; anyref, though it remains nullable. + (block $block + (return + (br_on_null $block + (local.get $x) + ) + ) + ) + (unreachable) + ) + + ;; CHECK: (func $br_on_cast_fallthrough (type $1) (result (ref $A)) + ;; CHECK-NEXT: (local $x (ref null $A)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $block (result (ref null $A)) + ;; CHECK-NEXT: (return + ;; CHECK-NEXT: (br_on_cast $block (ref null $A) (ref null $A) + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $br_on_cast_fallthrough (result (ref $A)) + (local $x (ref null $A)) + ;; The fallthrough value is returned as (ref $A), so $x cannot generalize to + ;; anyref, though it remains nullable because nulls take the branch. + (drop + (block $block (result (ref null $A)) + (return + (br_on_cast $block (ref null $A) (ref null $A) + (local.get $x) + ) + ) + ) + ) + (unreachable) + ) +) + From b1e6c1416edc2efaf48275d9eda11b5ccc5dacec Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 23 Oct 2026 10:03:24 -0700 Subject: [PATCH 36/36] more fixes --- src/ir/subtype-exprs.h | 5 ++ src/passes/TypeGeneralizing.cpp | 12 ++++ test/lit/passes/type-generalizing.wast | 88 +++++++++++++++++++++++++- 3 files changed, 103 insertions(+), 2 deletions(-) diff --git a/src/ir/subtype-exprs.h b/src/ir/subtype-exprs.h index b13050b46ec..907befd782b 100644 --- a/src/ir/subtype-exprs.h +++ b/src/ir/subtype-exprs.h @@ -717,6 +717,11 @@ struct SubtypingDiscoverer : public OverriddenVisitor { if (!curr->cont->type.isContinuation()) { return; } + + // Like CallRef, we cannot generalize the ref into a basic type, it must + // here remain a Continuation. + self()->noteSubtype(curr->cont, curr->cont->type); + processResumeHandlers( curr->cont->type, curr->handlerTags, curr->handlerBlocks); // The types we send to the resumed continuation must remain subtypes of the diff --git a/src/passes/TypeGeneralizing.cpp b/src/passes/TypeGeneralizing.cpp index e9a3bd591b7..824a9a89d95 100644 --- a/src/passes/TypeGeneralizing.cpp +++ b/src/passes/TypeGeneralizing.cpp @@ -118,6 +118,10 @@ HeapType generalizeArrayType(HeapType type, // not need to consider their results fixed. struct Generalizer : public ControlFlowWalker> { + + using Super = + ControlFlowWalker>; + Function* func; // For each expression and local, track the GLB of all types it is required to @@ -246,9 +250,13 @@ struct Generalizer void visitStructRMW(StructRMW* curr) { // Ensure we do not generalize too much. addConstraint(curr->ref, curr->ref->type); + + Super::visitStructRMW(curr); } void visitStructCmpxchg(StructCmpxchg* curr) { addConstraint(curr->ref, curr->ref->type); + + Super::visitStructCmpxchg(curr); } void visitArrayGet(ArrayGet* curr) { @@ -342,9 +350,13 @@ struct Generalizer void visitArrayRMW(ArrayRMW* curr) { // Ensure we do not generalize too much. addConstraint(curr->ref, curr->ref->type); + + Super::visitArrayRMW(curr); } void visitArrayCmpxchg(ArrayCmpxchg* curr) { addConstraint(curr->ref, curr->ref->type); + + Super::visitArrayCmpxchg(curr); } void propagate() { diff --git a/test/lit/passes/type-generalizing.wast b/test/lit/passes/type-generalizing.wast index 0b9e138a601..2d6335c6e39 100644 --- a/test/lit/passes/type-generalizing.wast +++ b/test/lit/passes/type-generalizing.wast @@ -2427,8 +2427,10 @@ ;; CHECK-NEXT: (local $x (ref null $A)) ;; CHECK-NEXT: (block $block (result (ref $A)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (br_on_cast_fail $block (ref null $A) (ref null $B) - ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (br_on_cast_fail $block (ref $A) (ref $B) + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) @@ -2504,3 +2506,85 @@ ) ) +(module + ;; CHECK: (type $void (func)) + + ;; CHECK: (type $val (sub (struct))) + (type $val (sub (struct))) + (type $subval (sub $val (struct))) + ;; CHECK: (type $cont (cont $void)) + + ;; CHECK: (type $struct (struct (field (mut (ref null $val))))) + (type $struct (struct (field (mut (ref null $val))))) + ;; CHECK: (type $arr (array (mut (ref null $val)))) + (type $arr (array (mut (ref null $val)))) + (type $void (func)) + (type $cont (cont $void)) + + ;; CHECK: (func $struct.atomic.xchg (type $void) + ;; CHECK-NEXT: (local $s (ref null $struct)) + ;; CHECK-NEXT: (local $v (ref null $val)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.atomic.rmw.xchg $struct 0 + ;; CHECK-NEXT: (local.get $s) + ;; CHECK-NEXT: (local.get $v) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $struct.atomic.xchg + (local $s (ref null $struct)) + (local $v (ref null $subval)) + ;; $s must remain (ref null $struct), while $v can generalize to (ref null $val) + ;; (the field type), not anyref. + (drop + (struct.atomic.rmw.xchg $struct 0 + (local.get $s) + (local.get $v) + ) + ) + ) + + ;; CHECK: (func $array.atomic.cmpxchg (type $void) + ;; CHECK-NEXT: (local $a (ref null $arr)) + ;; CHECK-NEXT: (local $exp eqref) + ;; CHECK-NEXT: (local $rep (ref null $val)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (array.atomic.rmw.cmpxchg $arr + ;; CHECK-NEXT: (local.get $a) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (local.get $exp) + ;; CHECK-NEXT: (local.get $rep) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $array.atomic.cmpxchg + (local $a (ref null $arr)) + (local $exp (ref null $subval)) + (local $rep (ref null $subval)) + ;; $a must remain (ref null $arr), $exp can generalize to eqref, and $rep can + ;; generalize to (ref null $val). + (drop + (array.atomic.rmw.cmpxchg $arr + (local.get $a) + (i32.const 0) + (local.get $exp) + (local.get $rep) + ) + ) + ) + + ;; CHECK: (func $resume.local (type $void) + ;; CHECK-NEXT: (local $c (ref null $cont)) + ;; CHECK-NEXT: (resume $cont + ;; CHECK-NEXT: (local.get $c) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $resume.local + (local $c (ref null $cont)) + ;; $c must remain (ref null $cont), not generalize to contref. + (resume $cont + (local.get $c) + ) + ) +) +