From e4377a6b0c55e9514dd1de4ef4a7c19739aee8ba Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Mon, 20 Jul 2026 07:28:56 -0700 Subject: [PATCH 1/5] Add a MakeSharedObjects pass This pass makes all functions unshared and all structs and arrays shared. It avoids validation errors from storing unshared function references in shared structs and arrays by replacing all function references with i31 references holding indices into a function table. Indirect calls and casts are updated to extract their function reference from the table. This new pass will make it easier to convert unshared Wasm GC programs to run on the shared heap for testing purposes. It will also lower shared functions emitted by toolchains to allow them to run on prototypes that do not support shared functions. --- src/passes/CMakeLists.txt | 1 + src/passes/MakeSharedObjects.cpp | 384 ++++++ src/passes/pass.cpp | 3 + src/passes/passes.h | 1 + src/wasm-traversal.h | 2 +- src/wasm-type.h | 15 +- test/lit/help/wasm-metadce.test | 3 + test/lit/help/wasm-opt.test | 3 + test/lit/help/wasm2js.test | 3 + test/lit/passes/make-shared-objects.wast | 1422 ++++++++++++++++++++++ 10 files changed, 1831 insertions(+), 6 deletions(-) create mode 100644 src/passes/MakeSharedObjects.cpp create mode 100644 test/lit/passes/make-shared-objects.wast diff --git a/src/passes/CMakeLists.txt b/src/passes/CMakeLists.txt index 684077171c8..41ccdef82bd 100644 --- a/src/passes/CMakeLists.txt +++ b/src/passes/CMakeLists.txt @@ -66,6 +66,7 @@ set(passes_SOURCES LocalSubtyping.cpp LogExecution.cpp LoopInvariantCodeMotion.cpp + MakeSharedObjects.cpp MarkJSCalled.cpp Memory64Lowering.cpp MemoryPacking.cpp diff --git a/src/passes/MakeSharedObjects.cpp b/src/passes/MakeSharedObjects.cpp new file mode 100644 index 00000000000..b29b40b296e --- /dev/null +++ b/src/passes/MakeSharedObjects.cpp @@ -0,0 +1,384 @@ +/* + * Copyright 2026 WebAssembly Community Group participants + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ir/drop.h" +#include "ir/module-utils.h" +#include "ir/names.h" +#include "ir/properties.h" +#include "ir/type-updating.h" +#include "ir/utils.h" +#include "literal.h" +#include "pass.h" +#include "support/name.h" +#include "wasm-builder.h" +#include "wasm-features.h" +#include "wasm-traversal.h" +#include "wasm-type.h" +#include "wasm.h" + +#include +#include + +namespace wasm { + +struct MakeSharedObjects + : WalkerPass>> { + Name tableName; + std::vector funcs; + std::unordered_map funcIndices; + + Type funcref = Type(HeapTypes::func, Nullable); + + Name getTable() { + if (!tableName) { + tableName = Names::getValidTableName(*getModule(), "funcs"); + } + return tableName; + } + + Index getIndex(Name func) { + auto [it, inserted] = funcIndices.insert({func, funcs.size()}); + if (inserted) { + funcs.push_back(func); + } + return it->second; + } + + HeapType updatedHeapType(HeapType type) { + if (type.isMaybeShared(HeapType::func) || type.isSignature()) { + return HeapTypes::i31.getBasic(Shared); + } + if (type.isMaybeShared(HeapType::nofunc)) { + return HeapTypes::none.getBasic(Shared); + } + if (type.isBasic()) { + return type.getBasic(Shared); + } + return type; + } + + Type updatedSingleType(Type type) { + if (type.isRef()) { + return type.with(updatedHeapType(type.getHeapType())); + } + return type; + } + + Type updatedType(Type type) { + if (type.isTuple()) { + std::vector elems; + elems.reserve(type.size()); + for (auto t : type) { + elems.push_back(updatedSingleType(t)); + } + return Type(elems); + } + return updatedSingleType(type); + } + + void updateType(Type& type) { type = updatedType(type); } + + void visitRefFunc(RefFunc* curr) { + Builder builder(*getModule()); + replaceCurrent(builder.makeRefI31( + builder.makeConst(Literal(getIndex(curr->func))), Shared)); + } + + std::unordered_map callRefTypes; + + void walkFunction(Function* curr) { + // When we replace call_ref target operands with i31s, we will lose + // information we need about the intended call target type. Collect those + // types up front before we update anything. + struct CallRefCollector : PostWalker { + std::unordered_map& types; + CallRefCollector(std::unordered_map& types) + : types(types) {} + void visitCallRef(CallRef* curr) { + if (curr->target->type.isSignature()) { + types[curr] = curr->target->type.getHeapType(); + } + } + }; + CallRefCollector collector(callRefTypes); + collector.walk(curr->body); + WalkerPass::walkFunction(curr); + ReFinalize().walkFunctionInModule(curr, getModule()); + } + + void visitCallRef(CallRef* curr) { + updateType(curr->type); + Builder builder(*getModule()); + auto it = callRefTypes.find(curr); + if (it == callRefTypes.end()) { + // The target type wasn't a signature, so it must have been null or + // unreachable. + replaceCurrent( + getDroppedChildrenAndAppend(curr, + *getModule(), + getPassOptions(), + builder.makeUnreachable(), + DropMode::IgnoreParentEffects)); + return; + } + auto type = it->second; + callRefTypes.erase(it); + replaceCurrent( + builder.makeCallIndirect(getTable(), + builder.makeI31Get(curr->target, false), + curr->operands, + type, + curr->isReturn)); + } + + void visitCallIndirect(CallIndirect* curr) { + updateType(curr->type); + Builder builder(*getModule()); + Name oldTable = curr->table; + Type oldTableType = updatedType(getModule()->getTable(oldTable)->type); + auto* index = builder.makeTableGet(oldTable, curr->target, oldTableType); + curr->target = builder.makeI31Get(index, false); + curr->table = getTable(); + } + + void visitRefTest(RefTest* curr) { + if (curr->type == Type::unreachable) { + return; + } + auto castHeapType = curr->castType.getHeapType(); + if (castHeapType.isMaybeShared(HeapType::func) || + castHeapType.isMaybeShared(HeapType::nofunc)) { + // This always passes or fails except possibly due to nulls. + updateType(curr->castType); + return; + } + if (!castHeapType.isSignature()) { + return; + } + Builder builder(*getModule()); + if (curr->ref->type.isNonNullable()) { + // (ref.test castType (table.get $t (i31.get_u ref))) + curr->ref = builder.makeTableGet( + getTable(), builder.makeI31Get(curr->ref, false), funcref); + return; + } + // (if (result i32) + // (ref.is_null (local.tee $scratch ref)) + // (then (i32.const 1) OR (i32.const 0)) + // (else (ref.test castType + // (table.get $t (i31.get_u (local.get $scratch)))) + // ) + // ) + auto scratchType = curr->ref->type; + Index scratch = Builder::addVar(getFunction(), scratchType); + auto* cond = builder.makeRefIsNull( + builder.makeLocalTee(scratch, curr->ref, scratchType)); + auto* ifNull = + builder.makeConst(Literal(int32_t(curr->castType.isNullable()))); + curr->ref = builder.makeTableGet( + getTable(), + builder.makeI31Get(builder.makeLocalGet(scratch, scratchType), false), + funcref); + replaceCurrent(builder.makeIf(cond, ifNull, curr, Type::i32)); + } + + void visitRefCast(RefCast* curr) { + if (curr->type == Type::unreachable) { + return; + } + auto castHeapType = curr->type.getHeapType(); + if (castHeapType.isMaybeShared(HeapType::func) || + castHeapType.isMaybeShared(HeapType::nofunc)) { + // This always passes or fails except possibly due to nulls. + updateType(curr->type); + return; + } + if (!castHeapType.isSignature()) { + return; + } + if (getPassOptions().trapsNeverHappen) { + // shared i31 in, shared i31 out, no matter what the cast source and + // target are. + replaceCurrent(curr->ref); + return; + } + // (if (result sharedi31ref) + // (ref.is_null (local.tee $scratch ref)), + // (then (local.get $scratch) OR (unreachable)) + // (else + // (if (result sharedi31ref) + // (ref.test castType + // (table.get $t (i31.get_u (local.get $scratch))) + // (then (ref.as_non_null? (local.get $scratch))) + // (else (unreachable)) + // ) + // ) + // ) + Builder builder(*getModule()); + auto newCastType = updatedType(curr->type); + auto scratchType = curr->ref->type; + Index scratch = Builder::addVar(getFunction(), scratchType); + auto* tee = builder.makeLocalTee(scratch, curr->ref, scratchType); + auto* isNull = builder.makeRefIsNull(tee); + Expression* ifNull = nullptr; + if (curr->type.isNullable()) { + ifNull = builder.makeLocalGet(scratch, scratchType); + } else { + ifNull = builder.makeUnreachable(); + } + auto* getScratch = builder.makeLocalGet(scratch, scratchType); + auto* i31get = builder.makeI31Get(getScratch, false); + auto* tableGet = builder.makeTableGet(getTable(), i31get, funcref); + auto* refTest = builder.makeRefTest(tableGet, curr->type); + Expression* ifPass = builder.makeLocalGet(scratch, scratchType); + if (curr->type.isNonNullable()) { + ifPass = builder.makeRefAs(RefAsNonNull, ifPass); + } + auto* ifFail = builder.makeUnreachable(); + auto* ifNonNull = builder.makeIf(refTest, ifPass, ifFail, newCastType); + replaceCurrent(builder.makeIf(isNull, ifNull, ifNonNull, newCastType)); + } + + void visitBrOn(BrOn* curr) { + if (curr->type == Type::unreachable) { + return; + } + if (curr->op != BrOnCast && curr->op != BrOnCastFail) { + return; + } + auto castHeapType = curr->castType.getHeapType(); + if (castHeapType.isMaybeShared(HeapType::func) || + castHeapType.isMaybeShared(HeapType::nofunc)) { + // This always passes or fails except possibly due to nulls. + updateType(curr->castType); + return; + } + WASM_UNREACHABLE("TODO: br_on"); + } + + void visitExpression(Expression* curr) { + updateType(curr->type); + +#define DELEGATE_ID curr->_id +#define DELEGATE_START(id) [[maybe_unused]] auto* cast = curr->cast(); +#define DELEGATE_GET_FIELD(id, field) cast->field +#define DELEGATE_FIELD_TYPE(id, field) updateType(cast->field); +#define DELEGATE_FIELD_CHILD(id, field) +#define DELEGATE_FIELD_INT(id, field) +#define DELEGATE_FIELD_LITERAL(id, field) +#define DELEGATE_FIELD_NAME(id, field) +#define DELEGATE_FIELD_SCOPE_NAME_DEF(id, field) +#define DELEGATE_FIELD_SCOPE_NAME_USE(id, field) +#define DELEGATE_FIELD_HEAPTYPE(id, field) +#define DELEGATE_FIELD_ADDRESS(id, field) +#include "wasm-delegations-fields.def" + } + + void visitFunction(Function* curr) { + for (auto& type : curr->vars) { + updateType(type); + } + } + + void visitTable(Table* curr) { updateType(curr->type); } + + void visitElementSegment(ElementSegment* curr) { updateType(curr->type); } + + void visitGlobal(Global* curr) { updateType(curr->type); } + + void doWalkModule(Module* wasm) { + wasm->features.setSharedEverything(); + WalkerPass::doWalkModule(wasm); + } + + void visitModule(Module* wasm) { + rewriteTypes(); + addFunctionTable(); + } + + void rewriteTypes() { + auto info = ModuleUtils::getOptimizedIndexedHeapTypes(*getModule()); + TypeBuilder builder(info.types.size()); + + auto map = [&](HeapType type) -> HeapType { + if (auto newType = updatedHeapType(type); newType.isBasic()) { + return newType; + } + return builder[info.indices.at(type)]; + }; + + for (Index i = 0; i < info.types.size(); ++i) { + if (info.types[i].getRecGroupIndex() == 0) { + builder.createRecGroup(i, info.types[i].getRecGroup().size()); + } + builder[i].copy(info.types[i], map); + if (info.types[i].isSignature()) { + builder[i].setShared(Unshared); + // `map` will have mapped any supertype to i31, which means the copy + // will not have its supertype set. Fix it. + if (auto super = info.types[i].getDeclaredSuperType()) { + builder[i].subTypeOf(builder[info.indices.at(*super)]); + } + } + builder[i].setShared(info.types[i].isSignature() ? Unshared : Shared); + } + + auto built = builder.build(); + if (auto* err = built.getError()) { + Fatal() << "Failed to build types: " << err->index << ": " << err->reason; + } + + assert(info.types.size() == built->size()); + std::unordered_map oldToNew; + for (Index i = 0; i < info.types.size(); ++i) { + oldToNew[info.types[i]] = (*built)[i]; + } + + GlobalTypeRewriter rewriter(*getModule(), getPassOptions().worldMode); + rewriter.mapTypes(oldToNew); + rewriter.mapTypeNamesAndIndices(oldToNew); + } + + void addFunctionTable() { + if (!tableName) { + return; + } + Builder builder(*getModule()); + Table* table = getModule()->addTable(Builder::makeTable(tableName)); + table->type = funcref; + table->initial = table->max = funcs.size(); + Name segName = Names::getValidElementSegmentName(*getModule(), "funcs"); + auto* offset = builder.makeConst(Literal(int32_t(0))); + auto* segment = getModule()->addElementSegment( + Builder::makeElementSegment(segName, tableName, offset)); + segment->type = funcref; + + segment->data.reserve(funcs.size()); + for (auto func : funcs) { + segment->data.push_back( + builder.makeRefFunc(func, getModule()->getFunction(func)->type)); + } + } + + std::unique_ptr create() override { + return std::make_unique(); + } +}; + +Pass* createMakeSharedObjectsPass() { return new MakeSharedObjects(); } + +} // namespace wasm diff --git a/src/passes/pass.cpp b/src/passes/pass.cpp index 178b9605cc8..34bc0c7dd40 100644 --- a/src/passes/pass.cpp +++ b/src/passes/pass.cpp @@ -280,6 +280,9 @@ void PassRegistry::registerPasses() { registerPass("limit-segments", "attempt to merge segments to fit within web limits", createLimitSegmentsPass); + registerPass("make-shared-objects", + "Make structs and arrays shared and functions unshared", + createMakeSharedObjectsPass); registerPass("mark-js-called", "mark js called functions (using configureAll) as doing so", createMarkJSCalledPass); diff --git a/src/passes/passes.h b/src/passes/passes.h index 3e289460eec..86e506b44b8 100644 --- a/src/passes/passes.h +++ b/src/passes/passes.h @@ -88,6 +88,7 @@ Pass* createInstrumentLocalsPass(); Pass* createInstrumentMemoryPass(); Pass* createLLVMMemoryCopyFillLoweringPass(); Pass* createLoopInvariantCodeMotionPass(); +Pass* createMakeSharedObjectsPass(); Pass* createMarkJSCalledPass(); Pass* createMemory64LoweringPass(); Pass* createMemoryPackingPass(); diff --git a/src/wasm-traversal.h b/src/wasm-traversal.h index ecb91ac9523..c984af766be 100644 --- a/src/wasm-traversal.h +++ b/src/wasm-traversal.h @@ -175,7 +175,7 @@ struct Walker : public VisitorType { if (segment->isActive()) { walk(segment->offset); } - for (auto* expr : segment->data) { + for (auto*& expr : segment->data) { walk(expr); } static_cast(this)->visitElementSegment(segment); diff --git a/src/wasm-type.h b/src/wasm-type.h index 68f753f00ce..ac94c992e87 100644 --- a/src/wasm-type.h +++ b/src/wasm-type.h @@ -343,10 +343,9 @@ class Type { Nullability nullable, Exactness exact = Inexact) : Type(heapType.getID() | (nullable == Nullable ? NullMask : 0) | - (exact == Exact ? ExactMask : 0)) { + (exact == Exact && !heapType.isBasic() ? ExactMask : 0)) { assert(!(heapType.getID() & (TupleMask | NullMask | (heapType.isBasic() ? 0 : ExactMask)))); - assert(!heapType.isBasic() || exact == Inexact); } // Predicates @@ -809,13 +808,19 @@ struct TypeBuilder { template void copyHeapType(size_t i, HeapType type, F map) { assert(!type.isBasic()); if (auto super = type.getDeclaredSuperType()) { - setSubType(i, map(*super)); + if (auto mapped = map(*super); !mapped.isBasic()) { + setSubType(i, map(*super)); + } } if (auto desc = type.getDescriptorType()) { - setDescriptor(i, map(*desc)); + if (auto mapped = map(*desc); !mapped.isBasic()) { + setDescriptor(i, map(*desc)); + } } if (auto desc = type.getDescribedType()) { - setDescribed(i, map(*desc)); + if (auto mapped = map(*desc); !mapped.isBasic()) { + setDescribed(i, map(*desc)); + } } setOpen(i, type.isOpen()); setShared(i, type.getShared()); diff --git a/test/lit/help/wasm-metadce.test b/test/lit/help/wasm-metadce.test index 43234cf67b4..6f86b33fea5 100644 --- a/test/lit/help/wasm-metadce.test +++ b/test/lit/help/wasm-metadce.test @@ -259,6 +259,9 @@ ;; CHECK-NEXT: --log-execution instrument the build with ;; CHECK-NEXT: logging of where execution goes ;; CHECK-NEXT: +;; CHECK-NEXT: --make-shared-objects Make structs and arrays shared +;; CHECK-NEXT: and functions unshared +;; CHECK-NEXT: ;; CHECK-NEXT: --mark-js-called mark js called functions (using ;; CHECK-NEXT: configureAll) as doing so ;; CHECK-NEXT: diff --git a/test/lit/help/wasm-opt.test b/test/lit/help/wasm-opt.test index 2d712c157c8..763bf958be0 100644 --- a/test/lit/help/wasm-opt.test +++ b/test/lit/help/wasm-opt.test @@ -295,6 +295,9 @@ ;; CHECK-NEXT: --log-execution instrument the build with ;; CHECK-NEXT: logging of where execution goes ;; CHECK-NEXT: +;; CHECK-NEXT: --make-shared-objects Make structs and arrays shared +;; CHECK-NEXT: and functions unshared +;; CHECK-NEXT: ;; CHECK-NEXT: --mark-js-called mark js called functions (using ;; CHECK-NEXT: configureAll) as doing so ;; CHECK-NEXT: diff --git a/test/lit/help/wasm2js.test b/test/lit/help/wasm2js.test index 168347c4d9c..7ead11eea29 100644 --- a/test/lit/help/wasm2js.test +++ b/test/lit/help/wasm2js.test @@ -223,6 +223,9 @@ ;; CHECK-NEXT: --log-execution instrument the build with ;; CHECK-NEXT: logging of where execution goes ;; CHECK-NEXT: +;; CHECK-NEXT: --make-shared-objects Make structs and arrays shared +;; CHECK-NEXT: and functions unshared +;; CHECK-NEXT: ;; CHECK-NEXT: --mark-js-called mark js called functions (using ;; CHECK-NEXT: configureAll) as doing so ;; CHECK-NEXT: diff --git a/test/lit/passes/make-shared-objects.wast b/test/lit/passes/make-shared-objects.wast new file mode 100644 index 00000000000..fe2ee5ebef5 --- /dev/null +++ b/test/lit/passes/make-shared-objects.wast @@ -0,0 +1,1422 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. +;; RUN: foreach %s %t wasm-opt -all --make-shared-objects -S -o - | filecheck %s + +(module + ;; Check lowering of basic heap types. + ;; CHECK: (type $0 (func (param (ref null (shared none))) (result (ref null (shared none))))) + + ;; CHECK: (type $1 (func (param (ref (shared none))) (result (ref (shared none))))) + + ;; CHECK: (type $2 (func (param (ref null (shared i31))) (result (ref null (shared i31))))) + + ;; CHECK: (type $3 (func (param (ref (shared i31))) (result (ref (shared i31))))) + + ;; CHECK: (type $4 (func (param (ref null (shared any))) (result (ref null (shared any))))) + + ;; CHECK: (type $5 (func (param (ref (shared any))) (result (ref (shared any))))) + + ;; CHECK: (type $6 (func (param (ref null (shared eq))) (result (ref null (shared eq))))) + + ;; CHECK: (type $7 (func (param (ref (shared eq))) (result (ref (shared eq))))) + + ;; CHECK: (type $8 (func (param (ref null (shared struct))) (result (ref null (shared struct))))) + + ;; CHECK: (type $9 (func (param (ref (shared struct))) (result (ref (shared struct))))) + + ;; CHECK: (type $10 (func (param (ref null (shared array))) (result (ref null (shared array))))) + + ;; CHECK: (type $11 (func (param (ref (shared array))) (result (ref (shared array))))) + + ;; CHECK: (func $funcref (type $2) (param $0 (ref null (shared i31))) (result (ref null (shared i31))) + ;; CHECK-NEXT: (local $1 (ref null (shared i31))) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + (func $funcref (param funcref) (result funcref) + (local funcref) + (local.get 0) + ) + + ;; CHECK: (func $shared-funcref (type $3) (param $0 (ref (shared i31))) (result (ref (shared i31))) + ;; CHECK-NEXT: (local $1 (ref (shared i31))) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + (func $shared-funcref (param (ref (shared func))) (result (ref (shared func))) + (local (ref (shared func))) + (local.get 0) + ) + + ;; CHECK: (func $nullfuncref (type $0) (param $0 (ref null (shared none))) (result (ref null (shared none))) + ;; CHECK-NEXT: (local $1 (ref null (shared none))) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + (func $nullfuncref (param nullfuncref) (result nullfuncref) + (local nullfuncref) + (local.get 0) + ) + + ;; CHECK: (func $shared-nullfuncref (type $1) (param $0 (ref (shared none))) (result (ref (shared none))) + ;; CHECK-NEXT: (local $1 (ref (shared none))) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + (func $shared-nullfuncref (param (ref (shared nofunc))) (result (ref (shared nofunc))) + (local (ref (shared nofunc))) + (local.get 0) + ) + + ;; CHECK: (func $anyref (type $4) (param $0 (ref null (shared any))) (result (ref null (shared any))) + ;; CHECK-NEXT: (local $1 (ref null (shared any))) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + (func $anyref (param anyref) (result anyref) + (local anyref) + (local.get 0) + ) + + ;; CHECK: (func $shared-anyref (type $5) (param $0 (ref (shared any))) (result (ref (shared any))) + ;; CHECK-NEXT: (local $1 (ref (shared any))) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + (func $shared-anyref (param (ref (shared any))) (result (ref (shared any))) + (local (ref (shared any))) + (local.get 0) + ) + + ;; CHECK: (func $eqref (type $6) (param $0 (ref null (shared eq))) (result (ref null (shared eq))) + ;; CHECK-NEXT: (local $1 (ref null (shared eq))) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + (func $eqref (param eqref) (result eqref) + (local eqref) + (local.get 0) + ) + + ;; CHECK: (func $shared-eqref (type $7) (param $0 (ref (shared eq))) (result (ref (shared eq))) + ;; CHECK-NEXT: (local $1 (ref (shared eq))) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + (func $shared-eqref (param (ref (shared eq))) (result (ref (shared eq))) + (local (ref (shared eq))) + (local.get 0) + ) + + ;; CHECK: (func $structref (type $8) (param $0 (ref null (shared struct))) (result (ref null (shared struct))) + ;; CHECK-NEXT: (local $1 (ref null (shared struct))) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + (func $structref (param structref) (result structref) + (local structref) + (local.get 0) + ) + + ;; CHECK: (func $shared-structref (type $9) (param $0 (ref (shared struct))) (result (ref (shared struct))) + ;; CHECK-NEXT: (local $1 (ref (shared struct))) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + (func $shared-structref (param (ref (shared struct))) (result (ref (shared struct))) + (local (ref (shared struct))) + (local.get 0) + ) + + ;; CHECK: (func $arrayref (type $10) (param $0 (ref null (shared array))) (result (ref null (shared array))) + ;; CHECK-NEXT: (local $1 (ref null (shared array))) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + (func $arrayref (param arrayref) (result arrayref) + (local arrayref) + (local.get 0) + ) + + ;; CHECK: (func $shared-arrayref (type $11) (param $0 (ref (shared array))) (result (ref (shared array))) + ;; CHECK-NEXT: (local $1 (ref (shared array))) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + (func $shared-arrayref (param (ref (shared array))) (result (ref (shared array))) + (local (ref (shared array))) + (local.get 0) + ) + + ;; CHECK: (func $nullref (type $0) (param $0 (ref null (shared none))) (result (ref null (shared none))) + ;; CHECK-NEXT: (local $1 (ref null (shared none))) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + (func $nullref (param nullref) (result nullref) + (local nullref) + (local.get 0) + ) + + ;; CHECK: (func $shared-nullref (type $1) (param $0 (ref (shared none))) (result (ref (shared none))) + ;; CHECK-NEXT: (local $1 (ref (shared none))) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + (func $shared-nullref (param (ref (shared none))) (result (ref (shared none))) + (local (ref (shared none))) + (local.get 0) + ) + + ;; TODO: Handle exnref, contref. +) + +(module + ;; CHECK: (type $func (func (param (ref (shared i31))) (result (ref (shared i31))))) + (type $func (func (param (ref $func)) (result (ref $func)))) + ;; CHECK: (func $f (type $func) (param $0 (ref (shared i31))) (result (ref (shared i31))) + ;; CHECK-NEXT: (local $1 (ref (shared i31))) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + (func $f (type $func) (param (ref $func)) (result (ref $func)) + (local (ref $func)) + (local.get 0) + ) +) + +(module + ;; Struct and array types should become shared. Function references inside + ;; them should become shared i31refs. Shared struct and array types should + ;; remain shared. Function types should become unshared. Distinct shared and + ;; and unshared types can be merged, but that's ok because their values can + ;; never flow into the same locations. + (type $func (func)) + ;; CHECK: (type $array (shared (array (ref (shared i31))))) + + ;; CHECK: (type $shared-func (func)) + + ;; CHECK: (type $struct (shared (struct (field (ref (shared i31))) (field i32)))) + (type $struct (struct (field (ref $func)) (field i32))) + (type $array (array (ref (exact $func)))) + + (type $shared-func (shared (func))) + ;; CHECK: (type $shared-struct (shared (struct (field (ref null (shared i31)))))) + (type $shared-struct (shared (struct (field (ref null (exact $shared-func)))))) + (type $shared-array (shared (array (ref $shared-func)))) + + ;; CHECK: (type $4 (func (param (ref $struct) (ref $array) (ref $shared-struct) (ref $array)))) + + ;; CHECK: (func $use-types (type $4) (param $0 (ref $struct)) (param $1 (ref $array)) (param $2 (ref $shared-struct)) (param $3 (ref $array)) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $use-types (param (ref $struct) (ref $array) (ref $shared-struct) (ref $shared-array)) + (nop) + ) + ;; CHECK: (func $f (type $shared-func) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $f (type $func) + (nop) + ) + ;; CHECK: (func $f-shared (type $shared-func) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $f-shared (type $shared-func) + (nop) + ) +) + +(module + ;; Rec groups containing mutually recursive struct, array, and function types + ;; should be preserved. + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $rec-func (func (param (ref $rec-struct)))) + (type $rec-func (func (param (ref $rec-struct)))) + ;; CHECK: (type $rec-struct (shared (struct (field (ref $rec-array)) (field (ref (shared i31)))))) + (type $rec-struct (struct (field (ref $rec-array)) (field (ref $rec-func)))) + ;; CHECK: (type $rec-array (shared (array (ref $rec-struct)))) + (type $rec-array (array (ref $rec-struct))) + ) + ;; CHECK: (type $3 (func (param (ref $rec-struct) (ref $rec-array) (ref (shared i31))))) + + ;; CHECK: (func $rec-group (type $3) (param $0 (ref $rec-struct)) (param $1 (ref $rec-array)) (param $2 (ref (shared i31))) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $rec-group (param (ref $rec-struct) (ref $rec-array) (ref $rec-func)) + (nop) + ) +) + +(module + ;; Function supertypes are preserved. + ;; CHECK: (type $parent (sub (func))) + (type $parent (sub (shared (func)))) + ;; CHECK: (type $child (sub $parent (func))) + (type $child (sub $parent (shared (func)))) + ;; CHECK: (func $f1 (type $parent) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $f1 (type $parent) + (nop) + ) + ;; CHECK: (func $f2 (type $child) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $f2 (type $child) + (nop) + ) +) + +(module + ;; Struct descriptor and described types become shared correctly. + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $struct (shared (descriptor $desc) (struct (field i32)))) + (type $struct (descriptor $desc) (struct (field i32))) + ;; CHECK: (type $desc (shared (describes $struct) (struct (field i32)))) + (type $desc (describes $struct) (struct (field i32))) + ) + ;; CHECK: (type $2 (func (param (ref $struct) (ref $desc)))) + + ;; CHECK: (func $descriptors (type $2) (param $0 (ref $struct)) (param $1 (ref $desc)) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $descriptors (param (ref $struct) (ref $desc)) + (nop) + ) +) + +(module + ;; CHECK: (type $f (func)) + (type $f (func)) + ;; CHECK: (type $1 (func (result (ref null (shared none))))) + + ;; CHECK: (type $2 (func (result (ref null (shared i31))))) + + ;; CHECK: (type $3 (func (result (ref (shared i31))))) + + ;; CHECK: (func $target1 (type $f) + ;; CHECK-NEXT: ) + (func $target1 (type $f)) + ;; CHECK: (func $target2 (type $f) + ;; CHECK-NEXT: ) + (func $target2 (type $f)) + + ;; CHECK: (func $ref-null-nofunc (type $1) (result (ref null (shared none))) + ;; CHECK-NEXT: (ref.null (shared none)) + ;; CHECK-NEXT: ) + (func $ref-null-nofunc (result (ref null nofunc)) + ;; (ref.null nofunc) becomes (ref.null (shared none)). + (ref.null nofunc) + ) + + ;; CHECK: (func $ref-null-shared-nofunc (type $1) (result (ref null (shared none))) + ;; CHECK-NEXT: (ref.null (shared none)) + ;; CHECK-NEXT: ) + (func $ref-null-shared-nofunc (result (ref null (shared nofunc))) + ;; Same with a shared nofunc. + (ref.null (shared nofunc)) + ) + + ;; CHECK: (func $ref-func (type $2) (result (ref null (shared i31))) + ;; CHECK-NEXT: (ref.i31_shared + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ref-func (result funcref) + ;; ref.func becomes a shared i31. + (ref.func $target1) + ) + + ;; CHECK: (func $other-ref-func (type $3) (result (ref (shared i31))) + ;; CHECK-NEXT: (ref.i31_shared + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $other-ref-func (result (ref (exact $f))) + ;; Different referenced function, different i31 value. + (ref.func $target2) + ) + + ;; CHECK: (func $multiple-ref-func (type $f) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.i31_shared + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.i31_shared + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.i31_shared + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $multiple-ref-func + ;; Mapping of functions to i31 values should be consistent. + (drop (ref.func $target1)) + (drop (ref.func $target1)) + (drop (ref.func $target2)) + ) +) + +(module + (type $sig (func (param i32) (result i32))) + ;; CHECK: (type $0 (func (result i32))) + + ;; CHECK: (type $shared-sig (func (param i32) (result i32))) + (type $shared-sig (shared (func (param i32) (result i32)))) + + ;; CHECK: (type $2 (func)) + + ;; CHECK: (type $struct (shared (struct (field (ref null (shared i31)))))) + (type $struct (struct (field (ref null func)))) + + (func $effect (import "" "") (result i32)) + + ;; CHECK: (type $4 (func (param (ref (shared i31)) i32) (result i32))) + + ;; CHECK: (type $5 (func (param (ref (shared i31))) (result i32))) + + ;; CHECK: (type $6 (func (param i32 i32) (result i32))) + + ;; CHECK: (import "" "" (func $effect (type $0) (result i32))) + + ;; CHECK: (global $g (ref null (shared i31)) (ref.i31_shared + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: )) + (global $g (ref null func) (ref.func $a)) + + ;; CHECK: (global $g-struct (ref null $struct) (struct.new $struct + ;; CHECK-NEXT: (ref.i31_shared + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: )) + (global $g-struct (ref null $struct) (struct.new $struct (ref.func $b))) + + ;; CHECK: (table $t-init 1 1 (ref null (shared i31)) (ref.i31_shared + ;; CHECK-NEXT: (i32.const 3) + ;; CHECK-NEXT: )) + (table $t-init 1 1 (ref null func) (ref.func $c)) + + ;; CHECK: (table $t 1 1 (ref null (shared i31))) + (table $t 1 1 (ref null $sig)) + ;; CHECK: (table $t-shared 1 1 (ref null (shared i31))) + (table $t-shared 1 1 (ref null (shared func))) + + ;; CHECK: (table $funcs 4 4 funcref) + + ;; CHECK: (elem $elem (ref null (shared i31)) (item (ref.i31_shared + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ))) + (elem $elem funcref (ref.func $d)) + + ;; CHECK: (elem $funcs (table $funcs) (i32.const 0) func $a $b $d $c) + + ;; CHECK: (func $a (type $2) + ;; CHECK-NEXT: ) + (func $a) + ;; CHECK: (func $b (type $2) + ;; CHECK-NEXT: ) + (func $b) + ;; CHECK: (func $c (type $2) + ;; CHECK-NEXT: ) + (func $c) + ;; CHECK: (func $d (type $2) + ;; CHECK-NEXT: ) + (func $d) + + ;; CHECK: (func $call-ref (type $4) (param $0 (ref (shared i31))) (param $1 i32) (result i32) + ;; CHECK-NEXT: (call_indirect $funcs (type $shared-sig) + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: (i31.get_u + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $call-ref (param (ref $sig) i32) (result i32) + (call_ref $sig (local.get 1) (local.get 0)) + ) + + ;; CHECK: (func $unreachable-call-ref (type $0) (result i32) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $unreachable-call-ref (result i32) + (call_ref $sig (i32.const 0) (unreachable)) + ) + + ;; CHECK: (func $unreachable-call-ref-effect (type $0) (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $effect) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $unreachable-call-ref-effect (result i32) + (call_ref $sig (call $effect) (unreachable)) + ) + + ;; CHECK: (func $null-call-ref (type $0) (result i32) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $null-call-ref (result i32) + (call_ref $sig (i32.const 0) (ref.null nofunc)) + ) + + ;; CHECK: (func $null-call-ref-effect (type $0) (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $effect) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $null-call-ref-effect (result i32) + (call_ref $sig (call $effect) (ref.null nofunc)) + ) + + ;; CHECK: (func $shared-call-ref (type $5) (param $0 (ref (shared i31))) (result i32) + ;; CHECK-NEXT: (call_indirect $funcs (type $shared-sig) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: (i31.get_u + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $shared-call-ref (param (ref $shared-sig)) (result i32) + (call_ref $shared-sig (i32.const 42) (local.get 0)) + ) + + ;; CHECK: (func $shared-unreachable-call-ref (type $0) (result i32) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $shared-unreachable-call-ref (result i32) + (call_ref $shared-sig (i32.const 0) (unreachable)) + ) + + ;; CHECK: (func $shared-unreachable-call-ref-effect (type $0) (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $effect) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $shared-unreachable-call-ref-effect (result i32) + (call_ref $shared-sig (call $effect) (unreachable)) + ) + + ;; CHECK: (func $shared-null-call-ref (type $0) (result i32) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $shared-null-call-ref (result i32) + (call_ref $shared-sig (i32.const 0) (ref.null (shared nofunc))) + ) + + ;; CHECK: (func $shared-null-call-ref-effect (type $0) (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $effect) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $shared-null-call-ref-effect (result i32) + (call_ref $shared-sig (call $effect) (ref.null (shared nofunc))) + ) + + ;; CHECK: (func $return-call-ref (type $4) (param $0 (ref (shared i31))) (param $1 i32) (result i32) + ;; CHECK-NEXT: (return_call_indirect $funcs (type $shared-sig) + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: (i31.get_u + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $return-call-ref (param (ref $sig) i32) (result i32) + (return_call_ref $sig (local.get 1) (local.get 0)) + ) + + ;; call_indirect lowering + ;; CHECK: (func $call-indirect (type $6) (param $0 i32) (param $1 i32) (result i32) + ;; CHECK-NEXT: (call_indirect $funcs (type $shared-sig) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (i31.get_u + ;; CHECK-NEXT: (table.get $t + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $call-indirect (param i32 i32) (result i32) + (call_indirect $t (type $sig) (local.get 0) (local.get 1)) + ) + + ;; Unreachable call_indirect lowering. + ;; CHECK: (func $unreachable-call-indirect (type $0) (result i32) + ;; CHECK-NEXT: (call_indirect $funcs (type $shared-sig) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i31.get_u + ;; CHECK-NEXT: (table.get $t + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $unreachable-call-indirect (result i32) + (call_indirect $t (type $sig) (i32.const 0) (unreachable)) + ) + + ;; call_indirect lowering of shared function. + ;; CHECK: (func $shared-call-indirect (type $shared-sig) (param $0 i32) (result i32) + ;; CHECK-NEXT: (call_indirect $funcs (type $shared-sig) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: (i31.get_u + ;; CHECK-NEXT: (table.get $t-shared + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $shared-call-indirect (param i32) (result i32) + (call_indirect $t-shared (type $shared-sig) (i32.const 42) (local.get 0)) + ) + + ;; unreachable call_indirect + ;; CHECK: (func $unreachable-param-call-indirect (type $shared-sig) (param $0 i32) (result i32) + ;; CHECK-NEXT: (call_indirect $funcs (type $shared-sig) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: (i31.get_u + ;; CHECK-NEXT: (table.get $t + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $unreachable-param-call-indirect (param i32) (result i32) + (call_indirect $t (type $sig) (unreachable) (local.get 0)) + ) +) + +(module + (type $sig (func (param i32) (result i32))) + ;; CHECK: (type $shared-sig (func (param i32) (result i32))) + (type $shared-sig (shared (func (param i32) (result i32)))) + ;; CHECK: (type $1 (func (param (ref null (shared i31))) (result i32))) + + ;; CHECK: (type $2 (func (param (ref null (shared i31))) (result (ref null (shared i31))))) + + ;; CHECK: (type $struct (shared (struct (field i32)))) + (type $struct (struct (field i32))) + + ;; ref.test of funcref + ;; CHECK: (type $4 (func (param (ref null (shared i31))) (result (ref (shared i31))))) + + ;; CHECK: (type $5 (func (param (ref (shared i31))) (result i32))) + + ;; CHECK: (type $6 (func (result i32))) + + ;; CHECK: (type $7 (func (param (ref null (shared i31))) (result (ref (shared none))))) + + ;; CHECK: (type $8 (func (param (ref null (shared i31))) (result (ref null (shared none))))) + + ;; CHECK: (type $9 (func (param (ref (shared i31))) (result (ref (shared i31))))) + + ;; CHECK: (type $10 (func (result (ref null (shared i31))))) + + ;; CHECK: (type $11 (func (param (ref null $struct)) (result i32))) + + ;; CHECK: (type $12 (func (param (ref null $struct)) (result (ref null $struct)))) + + ;; CHECK: (table $funcs 0 0 funcref) + + ;; CHECK: (elem $funcs (i32.const 0)) + + ;; CHECK: (func $ref-test-func (type $1) (param $0 (ref null (shared i31))) (result i32) + ;; CHECK-NEXT: (ref.test (ref (shared i31)) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ref-test-func (param funcref) (result i32) + (ref.test (ref func) (local.get 0)) + ) + + ;; CHECK: (func $ref-test-funcref (type $1) (param $0 (ref null (shared i31))) (result i32) + ;; CHECK-NEXT: (ref.test (ref null (shared i31)) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ref-test-funcref (param funcref) (result i32) + (ref.test (ref null func) (local.get 0)) + ) + + ;; CHECK: (func $ref-test-nofunc (type $1) (param $0 (ref null (shared i31))) (result i32) + ;; CHECK-NEXT: (ref.test (ref (shared none)) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ref-test-nofunc (param funcref) (result i32) + (ref.test (ref nofunc) (local.get 0)) + ) + + ;; CHECK: (func $ref-test-null-nofunc (type $1) (param $0 (ref null (shared i31))) (result i32) + ;; CHECK-NEXT: (ref.test (ref null (shared none)) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ref-test-null-nofunc (param funcref) (result i32) + (ref.test (ref null nofunc) (local.get 0)) + ) + + ;; CHECK: (func $ref-test-shared-func (type $1) (param $0 (ref null (shared i31))) (result i32) + ;; CHECK-NEXT: (ref.test (ref (shared i31)) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ref-test-shared-func (param (ref null (shared func))) (result i32) + (ref.test (ref (shared func)) (local.get 0)) + ) + + ;; CHECK: (func $ref-test-shared-funcref (type $1) (param $0 (ref null (shared i31))) (result i32) + ;; CHECK-NEXT: (ref.test (ref null (shared i31)) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ref-test-shared-funcref (param (ref null (shared func))) (result i32) + (ref.test (ref null (shared func)) (local.get 0)) + ) + + ;; CHECK: (func $ref-test-shared-nofunc (type $1) (param $0 (ref null (shared i31))) (result i32) + ;; CHECK-NEXT: (ref.test (ref (shared none)) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ref-test-shared-nofunc (param (ref null (shared func))) (result i32) + (ref.test (ref (shared nofunc)) (local.get 0)) + ) + + ;; CHECK: (func $ref-test-null-shared-nofunc (type $1) (param $0 (ref null (shared i31))) (result i32) + ;; CHECK-NEXT: (ref.test (ref null (shared none)) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ref-test-null-shared-nofunc (param (ref null (shared func))) (result i32) + (ref.test (ref null (shared nofunc)) (local.get 0)) + ) + + ;; CHECK: (func $ref-test-no-nulls (type $5) (param $0 (ref (shared i31))) (result i32) + ;; CHECK-NEXT: (ref.test (ref $shared-sig) + ;; CHECK-NEXT: (table.get $funcs + ;; CHECK-NEXT: (i31.get_u + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ref-test-no-nulls (param (ref func)) (result i32) + (ref.test (ref $sig) (local.get 0)) + ) + + ;; CHECK: (func $ref-test-non-nullable (type $1) (param $0 (ref null (shared i31))) (result i32) + ;; CHECK-NEXT: (local $1 (ref null (shared i31))) + ;; CHECK-NEXT: (if (result i32) + ;; CHECK-NEXT: (ref.is_null + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (ref.test (ref $shared-sig) + ;; CHECK-NEXT: (table.get $funcs + ;; CHECK-NEXT: (i31.get_u + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ref-test-non-nullable (param funcref) (result i32) + (ref.test (ref $sig) (local.get 0)) + ) + + ;; CHECK: (func $ref-test-nullable (type $1) (param $0 (ref null (shared i31))) (result i32) + ;; CHECK-NEXT: (local $1 (ref null (shared i31))) + ;; CHECK-NEXT: (if (result i32) + ;; CHECK-NEXT: (ref.is_null + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (ref.test (ref null $shared-sig) + ;; CHECK-NEXT: (table.get $funcs + ;; CHECK-NEXT: (i31.get_u + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ref-test-nullable (param funcref) (result i32) + (ref.test (ref null $sig) (local.get 0)) + ) + + ;; CHECK: (func $ref-test-nullable-exact (type $1) (param $0 (ref null (shared i31))) (result i32) + ;; CHECK-NEXT: (local $1 (ref null (shared i31))) + ;; CHECK-NEXT: (if (result i32) + ;; CHECK-NEXT: (ref.is_null + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (ref.test (ref null (exact $shared-sig)) + ;; CHECK-NEXT: (table.get $funcs + ;; CHECK-NEXT: (i31.get_u + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ref-test-nullable-exact (param funcref) (result i32) + (ref.test (ref null (exact $sig)) (local.get 0)) + ) + + ;; CHECK: (func $ref-test-shared-no-nulls (type $5) (param $0 (ref (shared i31))) (result i32) + ;; CHECK-NEXT: (ref.test (ref $shared-sig) + ;; CHECK-NEXT: (table.get $funcs + ;; CHECK-NEXT: (i31.get_u + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ref-test-shared-no-nulls (param (ref (shared func))) (result i32) + (ref.test (ref $shared-sig) (local.get 0)) + ) + + ;; CHECK: (func $ref-test-shared-non-nullable (type $1) (param $0 (ref null (shared i31))) (result i32) + ;; CHECK-NEXT: (local $1 (ref null (shared i31))) + ;; CHECK-NEXT: (if (result i32) + ;; CHECK-NEXT: (ref.is_null + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (ref.test (ref $shared-sig) + ;; CHECK-NEXT: (table.get $funcs + ;; CHECK-NEXT: (i31.get_u + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ref-test-shared-non-nullable (param (ref null (shared func))) (result i32) + (ref.test (ref $shared-sig) (local.get 0)) + ) + + ;; CHECK: (func $ref-test-shared-nullable (type $1) (param $0 (ref null (shared i31))) (result i32) + ;; CHECK-NEXT: (local $1 (ref null (shared i31))) + ;; CHECK-NEXT: (if (result i32) + ;; CHECK-NEXT: (ref.is_null + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (ref.test (ref null $shared-sig) + ;; CHECK-NEXT: (table.get $funcs + ;; CHECK-NEXT: (i31.get_u + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ref-test-shared-nullable (param (ref null (shared func))) (result i32) + (ref.test (ref null $shared-sig) (local.get 0)) + ) + + ;; CHECK: (func $ref-test-shared-nullable-exact (type $1) (param $0 (ref null (shared i31))) (result i32) + ;; CHECK-NEXT: (local $1 (ref null (shared i31))) + ;; CHECK-NEXT: (if (result i32) + ;; CHECK-NEXT: (ref.is_null + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (ref.test (ref null (exact $shared-sig)) + ;; CHECK-NEXT: (table.get $funcs + ;; CHECK-NEXT: (i31.get_u + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ref-test-shared-nullable-exact (param (ref null (shared func))) (result i32) + (ref.test (ref null (exact $shared-sig)) (local.get 0)) + ) + + + ;; ref.test with null input + ;; CHECK: (func $ref-test-null-input (type $6) (result i32) + ;; CHECK-NEXT: (ref.test (ref null (shared none)) + ;; CHECK-NEXT: (ref.null (shared none)) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ref-test-null-input (result i32) + (ref.test (ref null $sig) (ref.null nofunc)) + ) + + ;; ref.test with unreachable input + ;; CHECK: (func $ref-test-unreachable-input (type $6) (result i32) + ;; CHECK-NEXT: (ref.test (ref null $shared-sig) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ref-test-unreachable-input (result i32) + (ref.test (ref null $sig) (unreachable)) + ) + + ;; ref.test of non-function type. + ;; CHECK: (func $ref-test-non-func (type $11) (param $0 (ref null $struct)) (result i32) + ;; CHECK-NEXT: (ref.test (ref null $struct) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ref-test-non-func (param (ref null $struct)) (result i32) + ;; This should not be changed. + (ref.test (ref null $struct) (local.get 0)) + ) + + ;; CHECK: (func $ref-cast-func (type $4) (param $0 (ref null (shared i31))) (result (ref (shared i31))) + ;; CHECK-NEXT: (ref.cast (ref (shared i31)) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ref-cast-func (param funcref) (result (ref func)) + (ref.cast (ref func) (local.get 0)) + ) + + ;; CHECK: (func $ref-cast-funcref (type $2) (param $0 (ref null (shared i31))) (result (ref null (shared i31))) + ;; CHECK-NEXT: (ref.cast (ref null (shared i31)) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ref-cast-funcref (param funcref) (result (ref null func)) + (ref.cast (ref null func) (local.get 0)) + ) + + ;; CHECK: (func $ref-cast-nofunc (type $7) (param $0 (ref null (shared i31))) (result (ref (shared none))) + ;; CHECK-NEXT: (ref.cast (ref (shared none)) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ref-cast-nofunc (param funcref) (result (ref nofunc)) + (ref.cast (ref nofunc) (local.get 0)) + ) + + ;; CHECK: (func $ref-cast-null-nofunc (type $8) (param $0 (ref null (shared i31))) (result (ref null (shared none))) + ;; CHECK-NEXT: (ref.cast (ref null (shared none)) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ref-cast-null-nofunc (param funcref) (result (ref null nofunc)) + (ref.cast (ref null nofunc) (local.get 0)) + ) + + ;; CHECK: (func $ref-cast-shared-func (type $4) (param $0 (ref null (shared i31))) (result (ref (shared i31))) + ;; CHECK-NEXT: (ref.cast (ref (shared i31)) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ref-cast-shared-func (param (ref null (shared func))) (result (ref (shared func))) + (ref.cast (ref (shared func)) (local.get 0)) + ) + + ;; CHECK: (func $ref-cast-shared-funcref (type $2) (param $0 (ref null (shared i31))) (result (ref null (shared i31))) + ;; CHECK-NEXT: (ref.cast (ref null (shared i31)) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ref-cast-shared-funcref (param (ref null (shared func))) (result (ref null (shared func))) + (ref.cast (ref null (shared func)) (local.get 0)) + ) + + ;; CHECK: (func $ref-cast-shared-nofunc (type $7) (param $0 (ref null (shared i31))) (result (ref (shared none))) + ;; CHECK-NEXT: (ref.cast (ref (shared none)) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ref-cast-shared-nofunc (param (ref null (shared func))) (result (ref (shared nofunc))) + (ref.cast (ref (shared nofunc)) (local.get 0)) + ) + + ;; CHECK: (func $ref-cast-null-shared-nofunc (type $8) (param $0 (ref null (shared i31))) (result (ref null (shared none))) + ;; CHECK-NEXT: (ref.cast (ref null (shared none)) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ref-cast-null-shared-nofunc (param (ref null (shared func))) (result (ref null (shared nofunc))) + (ref.cast (ref null (shared nofunc)) (local.get 0)) + ) + + ;; CHECK: (func $ref-cast-no-nulls (type $9) (param $0 (ref (shared i31))) (result (ref (shared i31))) + ;; CHECK-NEXT: (local $1 (ref (shared i31))) + ;; CHECK-NEXT: (if (result (ref (shared i31))) + ;; CHECK-NEXT: (ref.is_null + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (if (result (ref (shared i31))) + ;; CHECK-NEXT: (ref.test (ref $shared-sig) + ;; CHECK-NEXT: (table.get $funcs + ;; CHECK-NEXT: (i31.get_u + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ref-cast-no-nulls (param (ref func)) (result (ref $sig)) + (ref.cast (ref $sig) (local.get 0)) + ) + + ;; CHECK: (func $ref-cast-non-nullable (type $4) (param $0 (ref null (shared i31))) (result (ref (shared i31))) + ;; CHECK-NEXT: (local $1 (ref null (shared i31))) + ;; CHECK-NEXT: (if (result (ref (shared i31))) + ;; CHECK-NEXT: (ref.is_null + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (if (result (ref (shared i31))) + ;; CHECK-NEXT: (ref.test (ref $shared-sig) + ;; CHECK-NEXT: (table.get $funcs + ;; CHECK-NEXT: (i31.get_u + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ref-cast-non-nullable (param funcref) (result (ref $sig)) + (ref.cast (ref $sig) (local.get 0)) + ) + + ;; CHECK: (func $ref-cast-nullable (type $2) (param $0 (ref null (shared i31))) (result (ref null (shared i31))) + ;; CHECK-NEXT: (local $1 (ref null (shared i31))) + ;; CHECK-NEXT: (if (result (ref null (shared i31))) + ;; CHECK-NEXT: (ref.is_null + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (if (result (ref null (shared i31))) + ;; CHECK-NEXT: (ref.test (ref null $shared-sig) + ;; CHECK-NEXT: (table.get $funcs + ;; CHECK-NEXT: (i31.get_u + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ref-cast-nullable (param funcref) (result (ref null $sig)) + (ref.cast (ref null $sig) (local.get 0)) + ) + + ;; CHECK: (func $ref-cast-nullable-exact (type $2) (param $0 (ref null (shared i31))) (result (ref null (shared i31))) + ;; CHECK-NEXT: (local $1 (ref null (shared i31))) + ;; CHECK-NEXT: (if (result (ref null (shared i31))) + ;; CHECK-NEXT: (ref.is_null + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (if (result (ref null (shared i31))) + ;; CHECK-NEXT: (ref.test (ref null (exact $shared-sig)) + ;; CHECK-NEXT: (table.get $funcs + ;; CHECK-NEXT: (i31.get_u + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ref-cast-nullable-exact (param funcref) (result (ref null (exact $sig))) + (ref.cast (ref null (exact $sig)) (local.get 0)) + ) + + ;; CHECK: (func $ref-cast-shared-no-nulls (type $9) (param $0 (ref (shared i31))) (result (ref (shared i31))) + ;; CHECK-NEXT: (local $1 (ref (shared i31))) + ;; CHECK-NEXT: (if (result (ref (shared i31))) + ;; CHECK-NEXT: (ref.is_null + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (if (result (ref (shared i31))) + ;; CHECK-NEXT: (ref.test (ref $shared-sig) + ;; CHECK-NEXT: (table.get $funcs + ;; CHECK-NEXT: (i31.get_u + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ref-cast-shared-no-nulls (param (ref (shared func))) (result (ref $shared-sig)) + (ref.cast (ref $shared-sig) (local.get 0)) + ) + + ;; CHECK: (func $ref-cast-shared-non-nullable (type $4) (param $0 (ref null (shared i31))) (result (ref (shared i31))) + ;; CHECK-NEXT: (local $1 (ref null (shared i31))) + ;; CHECK-NEXT: (if (result (ref (shared i31))) + ;; CHECK-NEXT: (ref.is_null + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (if (result (ref (shared i31))) + ;; CHECK-NEXT: (ref.test (ref $shared-sig) + ;; CHECK-NEXT: (table.get $funcs + ;; CHECK-NEXT: (i31.get_u + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ref-cast-shared-non-nullable (param (ref null (shared func))) (result (ref $shared-sig)) + (ref.cast (ref $shared-sig) (local.get 0)) + ) + + ;; CHECK: (func $ref-cast-shared-nullable (type $2) (param $0 (ref null (shared i31))) (result (ref null (shared i31))) + ;; CHECK-NEXT: (local $1 (ref null (shared i31))) + ;; CHECK-NEXT: (if (result (ref null (shared i31))) + ;; CHECK-NEXT: (ref.is_null + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (if (result (ref null (shared i31))) + ;; CHECK-NEXT: (ref.test (ref null $shared-sig) + ;; CHECK-NEXT: (table.get $funcs + ;; CHECK-NEXT: (i31.get_u + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ref-cast-shared-nullable (param (ref null (shared func))) (result (ref null $shared-sig)) + (ref.cast (ref null $shared-sig) (local.get 0)) + ) + + ;; CHECK: (func $ref-cast-shared-nullable-exact (type $2) (param $0 (ref null (shared i31))) (result (ref null (shared i31))) + ;; CHECK-NEXT: (local $1 (ref null (shared i31))) + ;; CHECK-NEXT: (if (result (ref null (shared i31))) + ;; CHECK-NEXT: (ref.is_null + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (if (result (ref null (shared i31))) + ;; CHECK-NEXT: (ref.test (ref null (exact $shared-sig)) + ;; CHECK-NEXT: (table.get $funcs + ;; CHECK-NEXT: (i31.get_u + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ref-cast-shared-nullable-exact (param (ref null (shared func))) (result (ref null $shared-sig)) + (ref.cast (ref null (exact $shared-sig)) (local.get 0)) + ) + + ;; CHECK: (func $ref-cast-null-input (type $10) (result (ref null (shared i31))) + ;; CHECK-NEXT: (ref.cast (ref null (shared none)) + ;; CHECK-NEXT: (ref.null (shared none)) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ref-cast-null-input (result (ref null $sig)) + (ref.cast (ref null $sig) (ref.null nofunc)) + ) + + ;; CHECK: (func $ref-cast-unreachable-input (type $10) (result (ref null (shared i31))) + ;; CHECK-NEXT: (block ;; (replaces unreachable RefCast we can't emit) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ref-cast-unreachable-input (result (ref null $sig)) + (ref.cast (ref null $sig) (unreachable)) + ) + + ;; CHECK: (func $ref-cast-non-func (type $12) (param $0 (ref null $struct)) (result (ref null $struct)) + ;; CHECK-NEXT: (ref.cast (ref null $struct) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ref-cast-non-func (param (ref null $struct)) (result (ref null $struct)) + ;; This should not be changed. + (ref.cast (ref null $struct) (local.get 0)) + ) + + ;; CHECK: (func $br-on-cast-funcref (type $2) (param $0 (ref null (shared i31))) (result (ref null (shared i31))) + ;; CHECK-NEXT: (block $label (result (ref null (shared i31))) + ;; CHECK-NEXT: (br_on_cast $label (ref null (shared i31)) (ref null (shared i31)) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $br-on-cast-funcref (param funcref) (result funcref) + (block $label (result funcref) + (br_on_cast $label funcref funcref (local.get 0)) + ) + ) + + ;; CHECK: (func $br-on-cast-fail-funcref (type $2) (param $0 (ref null (shared i31))) (result (ref null (shared i31))) + ;; CHECK-NEXT: (block $label (result (ref null (shared i31))) + ;; CHECK-NEXT: (br_on_cast_fail $label (ref null (shared i31)) (ref null (shared i31)) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $br-on-cast-fail-funcref (param funcref) (result funcref) + (block $label (result funcref) + (br_on_cast_fail $label funcref funcref (local.get 0)) + ) + ) + + ;; CHECK: (func $br-on-cast-nullfuncref (type $2) (param $0 (ref null (shared i31))) (result (ref null (shared i31))) + ;; CHECK-NEXT: (block $label (result (ref null (shared i31))) + ;; CHECK-NEXT: (br_on_cast $label (ref null (shared i31)) (ref null (shared none)) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $br-on-cast-nullfuncref (param funcref) (result funcref) + (block $label (result funcref) + (br_on_cast $label funcref nullfuncref (local.get 0)) + ) + ) + + ;; CHECK: (func $br-on-cast-fail-nullfuncref (type $2) (param $0 (ref null (shared i31))) (result (ref null (shared i31))) + ;; CHECK-NEXT: (block $label (result (ref null (shared i31))) + ;; CHECK-NEXT: (br_on_cast_fail $label (ref null (shared i31)) (ref null (shared none)) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $br-on-cast-fail-nullfuncref (param funcref) (result funcref) + (block $label (result funcref) + (br_on_cast_fail $label funcref nullfuncref (local.get 0)) + ) + ) + + ;; TODO: other br_on_cast{_fail} lowering +) + +(module + (type $sig (func (param i32) (result i32))) + + ;; Test that types in if, loop, result + + ;; Test that function types in globals, tables, element segments, and tags are updated. + ;; CHECK: (type $0 (func (param (ref null (shared i31))))) + + ;; CHECK: (type $1 (func (param i32) (result (ref null (shared i31))))) + + ;; CHECK: (global $g (ref null (shared i31)) (ref.null (shared none))) + (global $g (ref null $sig) (ref.null $sig)) + ;; CHECK: (table $t 1 1 (ref null (shared i31)) (ref.null (shared none))) + (table $t 1 1 (ref null $sig) (ref.null $sig)) + ;; CHECK: (elem $e (ref null (shared i31)) (item (ref.null (shared none)))) + (elem $e (ref null $sig) (ref.null $sig)) + ;; CHECK: (tag $tag (type $0) (param (ref null (shared i31)))) + (tag $tag (param (ref null $sig))) + + ;; CHECK: (func $control-types (type $1) (param $0 i32) (result (ref null (shared i31))) + ;; CHECK-NEXT: (if (result (ref null (shared none))) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (ref.null (shared none)) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (ref.null (shared none)) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $control-types (param i32) (result (ref null $sig)) + (if (result (ref null $sig)) (local.get 0) + (then (ref.null $sig)) + (else (ref.null $sig)) + ) + ) +) + +(module + ;; Name collisions with the "funcs" table and "funcs" elements segment are handled correctly. + ;; CHECK: (type $f (func)) + (type $f (func)) + ;; CHECK: (table $funcs 1 1 (ref null (shared i31))) + (table $funcs 1 1 funcref) + ;; CHECK: (table $funcs_1 1 1 funcref) + + ;; CHECK: (elem $funcs (table $funcs) (i32.const 0) (ref null (shared i31)) (item (ref.i31_shared + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ))) + (elem $funcs (table $funcs) (i32.const 0) func $target) + ;; CHECK: (elem $funcs_1 (table $funcs_1) (i32.const 0) func $target) + + ;; CHECK: (func $target (type $f) + ;; CHECK-NEXT: ) + (func $target (type $f)) + + ;; CHECK: (func $name-collision (type $f) + ;; CHECK-NEXT: (call_indirect $funcs_1 (type $f) + ;; CHECK-NEXT: (i31.get_u + ;; CHECK-NEXT: (ref.i31_shared + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $name-collision + (call_ref $f (ref.func $target)) + ) +) + +(module + ;; We want public types to be updated as well. + (type $sig (func)) + ;; CHECK: (import "" "" (global $g-funcref (ref null (shared i31)))) + (import "" "" (global $g-funcref funcref)) + ;; CHECK: (import "" "" (global $g-sig (ref null (shared i31)))) + (import "" "" (global $g-sig (ref null $sig))) +) From 9cf91e8cab11a33f734b9ef4e421296b3a0411d9 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Wed, 29 Jul 2026 20:18:57 -0700 Subject: [PATCH 2/5] top level comment --- src/passes/MakeSharedObjects.cpp | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/passes/MakeSharedObjects.cpp b/src/passes/MakeSharedObjects.cpp index b29b40b296e..8c82ed92d2d 100644 --- a/src/passes/MakeSharedObjects.cpp +++ b/src/passes/MakeSharedObjects.cpp @@ -14,10 +14,27 @@ * limitations under the License. */ +// Make all structs and arrays shared and makes all functions unshared. This +// serves two purposes: 1) converts unshared Wasm GC programs to use the shared +// heap for testing, and 2) lowers shared Wasm GC programs that use shared +// functions so they can run on experimental implementations that do not support +// shared functions. +// +// Because shared structs and arrays cannot contain unshared function +// references, replace function references in structs and arrays with indices +// into a function table that will be duplicated on each thread. Because +// arbitrary unknown function references may be written into structs and arrays +// and there is no way to look up a table index given a function reference, +// function references cannot be replaced only inside structs and arrays. +// Replace all function references in the module with table indices and fix up +// all instructions that consume function references (e.g. call_ref, casts) +// accordingly. Use i31 references to represent the table indices to avoid +// further complications from mapping function references to non-reference +// values. + #include "ir/drop.h" #include "ir/module-utils.h" #include "ir/names.h" -#include "ir/properties.h" #include "ir/type-updating.h" #include "ir/utils.h" #include "literal.h" From a62890afc4e89a8d0ebb3feff67acaa3ed6a90f5 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Wed, 29 Jul 2026 21:05:40 -0700 Subject: [PATCH 3/5] wasm-type.h improvements --- src/wasm-type.h | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/wasm-type.h b/src/wasm-type.h index ac94c992e87..e46088dee07 100644 --- a/src/wasm-type.h +++ b/src/wasm-type.h @@ -343,9 +343,10 @@ class Type { Nullability nullable, Exactness exact = Inexact) : Type(heapType.getID() | (nullable == Nullable ? NullMask : 0) | - (exact == Exact && !heapType.isBasic() ? ExactMask : 0)) { + (exact == Exact ? ExactMask : 0)) { assert(!(heapType.getID() & (TupleMask | NullMask | (heapType.isBasic() ? 0 : ExactMask)))); + assert(!heapType.isBasic() || exact == Inexact); } // Predicates @@ -807,19 +808,21 @@ struct TypeBuilder { // function. template void copyHeapType(size_t i, HeapType type, F map) { assert(!type.isBasic()); + // Supertypes, descriptor types, and described types cannot be basic heap + // types. Only set them if the mapping takes them to defined types. if (auto super = type.getDeclaredSuperType()) { if (auto mapped = map(*super); !mapped.isBasic()) { - setSubType(i, map(*super)); + setSubType(i, mapped); } } if (auto desc = type.getDescriptorType()) { if (auto mapped = map(*desc); !mapped.isBasic()) { - setDescriptor(i, map(*desc)); + setDescriptor(i, mapped); } } if (auto desc = type.getDescribedType()) { if (auto mapped = map(*desc); !mapped.isBasic()) { - setDescribed(i, map(*desc)); + setDescribed(i, mapped); } } setOpen(i, type.isOpen()); @@ -830,8 +833,10 @@ struct TypeBuilder { return t; } assert(t.isRef()); - return getTempRefType( - map(t.getHeapType()), t.getNullability(), t.getExactness()); + auto mapped = map(t.getHeapType()); + auto null = t.getNullability(); + auto exact = mapped.isBasic() ? Inexact : t.getExactness(); + return getTempRefType(mapped, null, exact); }; auto copyType = [&](Type t) -> Type { if (t.isTuple()) { From 1276a095a6066c6eb78879240f9c03593afdc8d4 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Wed, 29 Jul 2026 21:42:51 -0700 Subject: [PATCH 4/5] determinism fix --- src/ir/type-updating.cpp | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/ir/type-updating.cpp b/src/ir/type-updating.cpp index 069ccf4f9ca..fbf164929ca 100644 --- a/src/ir/type-updating.cpp +++ b/src/ir/type-updating.cpp @@ -423,7 +423,14 @@ void GlobalTypeRewriter::mapTypeNamesAndIndices(const TypeMap& oldToNewTypes) { } if (auto it = wasm.typeNames.find(old); it != wasm.typeNames.end()) { auto& names = it->second; - newTypeNames[new_] = names; + auto [newIt, inserted] = newTypeNames.insert({new_, names}); + if (!inserted) { + // Multiple old type names are being merged into the same new type. + // Deterministically keep the lesser name. + if (names.name.view() < newIt->second.name.view()) { + newIt->second = names; + } + } // Use the existing name in the new type, as usually it completely // replaces the old. Rename the old name in a unique way to avoid // confusion in the case that it remains used. @@ -437,9 +444,12 @@ void GlobalTypeRewriter::mapTypeNamesAndIndices(const TypeMap& oldToNewTypes) { } } if (auto it = wasm.typeIndices.find(old); it != wasm.typeIndices.end()) { - // It's ok if we end up with duplicate indices. Ties will be resolved in - // some arbitrary manner. - newTypeIndices[new_] = it->second; + auto [newIt, inserted] = newTypeIndices.insert({new_, it->second}); + if (!inserted) { + // Multiple old type indices are being merged into the same new type. + // Deterministically keep the lesser index. + newIt->second = std::min(newIt->second, it->second); + } } } newTypeNames.merge(wasm.typeNames); From 540f16206e1a50a6ea30f03907aa0ab107c00834 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Thu, 30 Jul 2026 06:14:22 -0700 Subject: [PATCH 5/5] update tests --- test/lit/passes/dae2.wast | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/lit/passes/dae2.wast b/test/lit/passes/dae2.wast index a74e9709612..18506c88fee 100644 --- a/test/lit/passes/dae2.wast +++ b/test/lit/passes/dae2.wast @@ -1599,7 +1599,7 @@ ;; CHECK: (rec ;; CHECK-NEXT: (type $super (sub (func (param i32 i32)))) (type $super (sub (func (param i32 i32)))) - ;; CHECK: (type $sub2 (sub $super (func (param i32 i32)))) + ;; CHECK: (type $sub1_0 (sub $super (func (param i32 i32)))) ;; CHECK: (type $sub1 (sub $super (func (param i32 i32)))) (type $sub1 (sub $super (func (param i32 i32)))) @@ -1612,7 +1612,7 @@ ;; CHECK: (elem declare func $referenced1 $referenced2) ;; CHECK: (func $referenced1 (type $sub1) (param $0 i32) (param $1 i32) - ;; CHECK-NEXT: (call_ref $sub2 + ;; CHECK-NEXT: (call_ref $sub1_0 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: (ref.func $referenced2) @@ -1634,7 +1634,7 @@ ) ) - ;; CHECK: (func $referenced2 (type $sub2) (param $0 i32) (param $1 i32) + ;; CHECK: (func $referenced2 (type $sub1_0) (param $0 i32) (param $1 i32) ;; CHECK-NEXT: (call_ref $sub1 ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: (i32.const 1)