| /* |
| * 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. |
| */ |
| |
| // 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. |
| // |
| // Although the shared objects prototype supports shared externrefs in general, |
| // it is not the case that arbitrary unshared externrefs can be made shared. To |
| // work around this, also lower unshared externrefs to i31ref table indices. |
| // Unlike function references, which we assume form a closed set whose table |
| // indices are meaningful across threads, there can be an arbitrary number of |
| // externrefs at runtime and only those that are imported as globals can be |
| // assumed to be meaningful across different threads. As a result, the externref |
| // table supports growing over time and externrefs rather than their table |
| // indices are still passed at the module boundary, unlike for function |
| // references. |
| |
| #include "ir/drop.h" |
| #include "ir/module-utils.h" |
| #include "ir/names.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 <unordered_map> |
| #include <vector> |
| |
| namespace wasm { |
| |
| // Track several components used to store references in a table: |
| // - The table itself |
| // - Runtime functions for converting between references and table indices |
| // - Utilities for inserting calls to these runtime functions as necessary. |
| // The table and runtime functions are only added to the module if they are |
| // used. |
| struct LazyTable { |
| Module* wasm = nullptr; |
| // The desired table name. |
| Name base; |
| // The non-conflicting version of `base`, if ever accessed. |
| Name name; |
| // The table type. |
| Type type; |
| // The names of conversion functions, if ever used. |
| Name refToIndexName; |
| Name indexToRefName; |
| |
| LazyTable(Name base, Type type) : base(base), type(type) {} |
| |
| Name getName() { |
| assert(wasm); |
| if (!name) { |
| name = Names::getValidTableName(*wasm, base); |
| } |
| return name; |
| } |
| |
| Name getRefToIndexName() { |
| assert(wasm); |
| if (!refToIndexName) { |
| std::string funcName = type.getHeapType().toString() + "_to_index"; |
| refToIndexName = Names::getValidFunctionName(*wasm, funcName); |
| getName(); |
| } |
| return refToIndexName; |
| } |
| |
| Name getIndexToRefName() { |
| assert(wasm); |
| if (!indexToRefName) { |
| std::string funcName = "index_to_" + type.getHeapType().toString(); |
| indexToRefName = Names::getValidFunctionName(*wasm, funcName); |
| getName(); |
| } |
| return indexToRefName; |
| } |
| |
| void addRefToIndexFunction() { |
| // (func $<t>_to_index (param $ref <t>) (result (ref null (shared i31))) |
| // (local $idx i32) |
| // (if (result (ref null (shared i31))) |
| // (ref.is_null (local.get $ref)) |
| // (then |
| // (ref.null (shared none)) |
| // ) |
| // (else |
| // (if (result (ref (shared i31))) |
| // (i32.ge_s |
| // (local.tee $idx |
| // (table.grow $<table> (local.get $ref) (i32.const 1)) |
| // ) |
| // (i32.const 0) |
| // ) |
| // (then |
| // (ref.i31_shared (local.get $idx)) |
| // ) |
| // (else |
| // (unreachable) |
| // ) |
| // ) |
| // ) |
| // ) |
| // ) |
| Builder builder(*wasm); |
| |
| Type sharedI31Nullable = Type(HeapTypes::i31.getBasic(Shared), Nullable); |
| Type sharedI31NonNull = Type(HeapTypes::i31.getBasic(Shared), NonNullable); |
| auto* isNull = builder.makeRefIsNull(builder.makeLocalGet(0, type)); |
| auto* retNull = builder.makeRefNull(HeapTypes::none.getBasic(Shared)); |
| auto* grow = builder.makeTableGrow(getName(), |
| builder.makeLocalGet(0, type), |
| builder.makeConst(Literal(int32_t(1)))); |
| auto* tee = builder.makeLocalTee(1, grow, Type::i32); |
| auto* geZero = |
| builder.makeBinary(GeSInt32, tee, builder.makeConst(Literal(int32_t(0)))); |
| auto* retIndex = |
| builder.makeRefI31(builder.makeLocalGet(1, Type::i32), Shared); |
| auto* checkGrow = builder.makeIf( |
| geZero, retIndex, builder.makeUnreachable(), sharedI31NonNull); |
| |
| auto* body = builder.makeIf(isNull, retNull, checkGrow, sharedI31Nullable); |
| auto func = Builder::makeFunction( |
| refToIndexName, Signature(type, sharedI31Nullable), {Type::i32}, body); |
| func->hasExplicitName = true; |
| wasm->addFunction(std::move(func)); |
| } |
| |
| void addIndexToRefFunction() { |
| // (func $index_to_<t> (param $idx (ref null (shared i31))) (result <t>) |
| // (if (result <type>) |
| // (ref.is_null (local.get $idx)) |
| // (then |
| // (ref.null <bottom>) |
| // ) |
| // (else |
| // (table.get $<table> (i31.get_u (local.get $idx))) |
| // ) |
| // ) |
| // ) |
| Builder builder(*wasm); |
| |
| Type sharedI31Nullable = Type(HeapTypes::i31.getBasic(Shared), Nullable); |
| auto* isNull = |
| builder.makeRefIsNull(builder.makeLocalGet(0, sharedI31Nullable)); |
| auto* retNull = builder.makeRefNull(type.getHeapType().getBottom()); |
| auto* getRef = builder.makeTableGet( |
| getName(), |
| builder.makeI31Get(builder.makeLocalGet(0, sharedI31Nullable), false), |
| type); |
| |
| auto* body = builder.makeIf(isNull, retNull, getRef, type); |
| auto func = Builder::makeFunction( |
| indexToRefName, Signature(sharedI31Nullable, type), {}, body); |
| func->hasExplicitName = true; |
| wasm->addFunction(std::move(func)); |
| } |
| |
| // Returns true if the table was added. (If it is never used, it will not be |
| // added.) |
| bool maybeAdd(Address initial, Address max) { |
| assert(wasm); |
| if (!name) { |
| return false; |
| } |
| Table* table = wasm->addTable(Builder::makeTable(name)); |
| table->type = type; |
| table->initial = initial; |
| table->max = max; |
| if (refToIndexName) { |
| addRefToIndexFunction(); |
| } |
| if (indexToRefName) { |
| addIndexToRefFunction(); |
| } |
| return true; |
| } |
| |
| bool isTableType(Type t) const { |
| return t.isRef() && Type::isSubType(t, type); |
| } |
| |
| bool hasTableType(Type t) const { |
| if (t.isTuple()) { |
| for (Type elem : t) { |
| if (hasTableType(elem)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| return isTableType(t); |
| } |
| |
| bool funcHasTableType(Function* func) const { |
| Signature sig = func->type.getHeapType().getSignature(); |
| return hasTableType(sig.params) || hasTableType(sig.results); |
| } |
| |
| Expression* convertToRef(Expression* arg, Type origType) { |
| if (!isTableType(origType)) { |
| return arg; |
| } |
| Builder builder(*wasm); |
| Expression* res = builder.makeCall(getIndexToRefName(), {arg}, type); |
| if (origType != type) { |
| res = builder.makeRefCast(res, origType); |
| } |
| return res; |
| } |
| |
| Expression* convertToIndex(Expression* arg, Type origType, Type targetType) { |
| if (!isTableType(origType)) { |
| return arg; |
| } |
| Builder builder(*wasm); |
| Type sharedI31Nullable = Type(HeapTypes::i31.getBasic(Shared), Nullable); |
| Expression* res = |
| builder.makeCall(getRefToIndexName(), {arg}, sharedI31Nullable); |
| if (targetType.isNonNullable()) { |
| res = builder.makeRefAs(RefAsNonNull, res); |
| } |
| return res; |
| } |
| }; |
| |
| struct MakeSharedObjects |
| : WalkerPass<PostWalker<MakeSharedObjects, |
| UnifiedExpressionVisitor<MakeSharedObjects>>> { |
| Type funcref = Type(HeapTypes::func, Nullable); |
| Type externref = Type(HeapTypes::ext, Nullable); |
| |
| LazyTable funcTable{"funcs", funcref}; |
| LazyTable externTable{"externs", externref}; |
| std::vector<Name> funcs; |
| std::unordered_map<Name, Index> funcIndices; |
| Name anyToExternName; |
| Name externToAnyName; |
| |
| Name getAnyToExternName() { |
| if (!anyToExternName) { |
| anyToExternName = |
| Names::getValidFunctionName(*getModule(), "any_to_extern"); |
| } |
| return anyToExternName; |
| } |
| |
| Name getExternToAnyName() { |
| if (!externToAnyName) { |
| externToAnyName = |
| Names::getValidFunctionName(*getModule(), "extern_to_any"); |
| } |
| return externToAnyName; |
| } |
| |
| 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 == HeapType::ext || type == HeapType::string) { |
| return HeapTypes::i31.getBasic(Shared); |
| } |
| if (type == HeapType::noext) { |
| 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<Type> 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); } |
| |
| Type getBoundaryType(Type origType, Type rewrittenType) { |
| Signature origSig = origType.getHeapType().getSignature(); |
| Signature rewrittenSig = rewrittenType.getHeapType().getSignature(); |
| |
| std::vector<Type> params; |
| Index i = 0; |
| for (Type param : origSig.params) { |
| if (externTable.isTableType(param)) { |
| params.push_back(param); |
| } else { |
| params.push_back(rewrittenSig.params[i]); |
| } |
| ++i; |
| } |
| |
| std::vector<Type> results; |
| Index j = 0; |
| for (Type result : origSig.results) { |
| if (externTable.isTableType(result)) { |
| results.push_back(result); |
| } else { |
| results.push_back(rewrittenSig.results[j]); |
| } |
| ++j; |
| } |
| |
| return Type(Signature(Type(params), Type(results)), |
| NonNullable, |
| origType.getExactness()); |
| } |
| |
| void wrapImport(Function* func, Type origType) { |
| // Create a new imported function with the boundary type. The original |
| // function (which has lowered param and result types) is no longer an |
| // import and is given a body that calls the new import and converts the |
| // externrefs to and from indices. |
| Builder builder(*getModule()); |
| Name origName = func->name; |
| Name importName = Names::getValidFunctionName( |
| *getModule(), origName.toString() + "$import"); |
| |
| Type boundaryType = getBoundaryType(origType, func->type); |
| |
| auto importFunc = std::make_unique<Function>(); |
| importFunc->name = importName; |
| importFunc->module = func->module; |
| importFunc->base = func->base; |
| importFunc->type = boundaryType; |
| importFunc->hasExplicitName = true; |
| |
| func->module = Name(); |
| func->base = Name(); |
| func->type = Type(func->type.getHeapType(), NonNullable, Exact); |
| |
| // Convert indices passed as params to externrefs. |
| Signature boundarySig = boundaryType.getHeapType().getSignature(); |
| std::vector<Expression*> callArgs; |
| Index i = 0; |
| for (Type param : boundarySig.params) { |
| Type localType = func->getParams()[i]; |
| Expression* get = builder.makeLocalGet(i, localType); |
| callArgs.push_back(externTable.convertToRef(get, param)); |
| ++i; |
| } |
| |
| Type extResults = boundarySig.results; |
| auto* call = builder.makeCall(importName, callArgs, extResults); |
| |
| // Convert externrefs received as results to indices. |
| if (!externTable.hasTableType(extResults)) { |
| func->body = call; |
| } else if (extResults.isSingle()) { |
| Type targetType = func->getResults(); |
| func->body = externTable.convertToIndex(call, extResults, targetType); |
| } else { |
| Index scratch = Builder::addVar(func, extResults); |
| auto* set = builder.makeLocalSet(scratch, call); |
| std::vector<Expression*> tupleElems; |
| Index j = 0; |
| for (Type t : extResults) { |
| auto* extract = builder.makeTupleExtract( |
| builder.makeLocalGet(scratch, extResults), j); |
| tupleElems.push_back( |
| externTable.convertToIndex(extract, t, func->getResults()[j])); |
| ++j; |
| } |
| auto* tupleMake = builder.makeTupleMake(tupleElems); |
| func->body = builder.makeBlock({set, tupleMake}); |
| } |
| |
| getModule()->addFunction(std::move(importFunc)); |
| } |
| |
| Name wrapExport(Export* ex, Type origType) { |
| // Wrap the exported function with a new function that calls the original |
| // exported function, converting externref params into indices and index |
| // results into externrefs. |
| Builder builder(*getModule()); |
| auto* internalFunc = getModule()->getFunction(*ex->getInternalName()); |
| Name origName = internalFunc->name; |
| Name exportWrapperName = Names::getValidFunctionName( |
| *getModule(), origName.toString() + "$export"); |
| |
| Type boundaryType = getBoundaryType(origType, internalFunc->type); |
| |
| auto exportWrapper = std::make_unique<Function>(); |
| exportWrapper->name = exportWrapperName; |
| exportWrapper->type = Type(boundaryType.getHeapType(), NonNullable, Exact); |
| exportWrapper->hasExplicitName = true; |
| |
| // Forward params, converting externrefs to indices. |
| Signature boundarySig = boundaryType.getHeapType().getSignature(); |
| std::vector<Expression*> callArgs; |
| Index i = 0; |
| for (Type param : boundarySig.params) { |
| Type localType = exportWrapper->getParams()[i]; |
| Expression* get = builder.makeLocalGet(i, localType); |
| Type targetType = internalFunc->getParams()[i]; |
| callArgs.push_back(externTable.convertToIndex(get, param, targetType)); |
| ++i; |
| } |
| |
| Type internalResults = internalFunc->getResults(); |
| auto* call = builder.makeCall(origName, callArgs, internalResults); |
| |
| // Forward results, converting indices to externrefs. |
| Type extResults = boundarySig.results; |
| if (!externTable.hasTableType(extResults)) { |
| exportWrapper->body = call; |
| } else if (extResults.isSingle()) { |
| exportWrapper->body = externTable.convertToRef(call, extResults); |
| } else { |
| Index scratch = Builder::addVar(exportWrapper.get(), internalResults); |
| auto* set = builder.makeLocalSet(scratch, call); |
| std::vector<Expression*> tupleElems; |
| Index j = 0; |
| for (Type t : extResults) { |
| auto* extract = builder.makeTupleExtract( |
| builder.makeLocalGet(scratch, internalResults), j); |
| tupleElems.push_back(externTable.convertToRef(extract, t)); |
| ++j; |
| } |
| auto* tupleMake = builder.makeTupleMake(tupleElems); |
| exportWrapper->body = builder.makeBlock({set, tupleMake}); |
| } |
| |
| getModule()->addFunction(std::move(exportWrapper)); |
| ex->setInternalName(exportWrapperName); |
| return exportWrapperName; |
| } |
| |
| void visitRefFunc(RefFunc* curr) { |
| Builder builder(*getModule()); |
| replaceCurrent(builder.makeRefI31( |
| builder.makeConst(Literal(getIndex(curr->func))), Shared)); |
| } |
| |
| std::unordered_map<CallRef*, HeapType> 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<CallRefCollector> { |
| std::unordered_map<CallRef*, HeapType>& types; |
| CallRefCollector(std::unordered_map<CallRef*, HeapType>& 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); |
| } |
| |
| 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(funcTable.getName(), |
| 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 = funcTable.getName(); |
| } |
| |
| 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()) { |
| updateType(curr->castType); |
| return; |
| } |
| Builder builder(*getModule()); |
| if (curr->ref->type.isNonNullable()) { |
| // (ref.test castType (table.get $t (i31.get_u ref))) |
| curr->ref = builder.makeTableGet( |
| funcTable.getName(), 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( |
| funcTable.getName(), |
| 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()) { |
| updateType(curr->type); |
| 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(funcTable.getName(), 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 visitRefAs(RefAs* curr) { |
| if (curr->type == Type::unreachable) { |
| return; |
| } |
| if (curr->op == AnyConvertExtern) { |
| Type extType = Type(HeapTypes::ext, curr->type.getNullability()); |
| Expression* ext = externTable.convertToRef(curr->value, extType); |
| Builder builder(*getModule()); |
| Type sharedAnyNullable = Type(HeapTypes::any.getBasic(Shared), Nullable); |
| Expression* call = |
| builder.makeCall(getExternToAnyName(), {ext}, sharedAnyNullable); |
| if (curr->type.isNonNullable()) { |
| call = builder.makeRefAs(RefAsNonNull, call); |
| } |
| replaceCurrent(call); |
| } else if (curr->op == ExternConvertAny) { |
| Builder builder(*getModule()); |
| Expression* call = |
| builder.makeCall(getAnyToExternName(), {curr->value}, externref); |
| Type extType = Type(HeapTypes::ext, curr->type.getNullability()); |
| Type targetType = updatedType(curr->type); |
| replaceCurrent(externTable.convertToIndex(call, extType, targetType)); |
| } |
| } |
| |
| void visitExpression(Expression* curr) { |
| updateType(curr->type); |
| |
| #define DELEGATE_ID curr->_id |
| #define DELEGATE_START(id) [[maybe_unused]] auto* cast = curr->cast<id>(); |
| #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) { |
| funcTable.wasm = wasm; |
| externTable.wasm = wasm; |
| wasm->features.setSharedEverything(); |
| WalkerPass::doWalkModule(wasm); |
| } |
| |
| struct ImportToWrap { |
| Function* func; |
| Type origType; |
| }; |
| |
| struct ExportToWrap { |
| Export* ex; |
| Type origType; |
| }; |
| |
| void visitModule(Module* wasm) { |
| std::vector<ImportToWrap> importsToWrap; |
| for (auto& func : wasm->functions) { |
| if (func->imported() && externTable.funcHasTableType(func.get())) { |
| importsToWrap.push_back({func.get(), func->type}); |
| } |
| } |
| |
| std::vector<ExportToWrap> exportsToWrap; |
| for (auto& ex : wasm->exports) { |
| if (ex->kind == ExternalKind::Function) { |
| if (auto* name = ex->getInternalName()) { |
| auto* func = wasm->getFunction(*name); |
| if (externTable.funcHasTableType(func)) { |
| exportsToWrap.push_back(ExportToWrap{ex.get(), func->type}); |
| } |
| } |
| } |
| } |
| |
| rewriteTypes(); |
| |
| for (auto& info : importsToWrap) { |
| wrapImport(info.func, info.origType); |
| } |
| std::unordered_map<Function*, Name> wrappedExports; |
| for (auto& info : exportsToWrap) { |
| auto* internalFunc = wasm->getFunction(*info.ex->getInternalName()); |
| auto [it, inserted] = wrappedExports.insert({internalFunc, Name()}); |
| if (inserted) { |
| it->second = wrapExport(info.ex, info.origType); |
| } else { |
| info.ex->setInternalName(it->second); |
| } |
| } |
| |
| addFunctionTable(); |
| addExternTable(); |
| ReFinalize().run(getPassRunner(), wasm); |
| } |
| |
| 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)]); |
| } |
| } else { |
| builder[i].setShared(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<HeapType, HeapType> 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 (!funcTable.maybeAdd(funcs.size(), funcs.size())) { |
| return; |
| } |
| if (funcs.empty()) { |
| return; |
| } |
| Builder builder(*getModule()); |
| Name segName = Names::getValidElementSegmentName(*getModule(), "funcs"); |
| auto* offset = builder.makeConst(Literal(int32_t(0))); |
| auto* segment = getModule()->addElementSegment( |
| Builder::makeElementSegment(segName, funcTable.getName(), offset)); |
| segment->type = funcTable.type; |
| |
| segment->data.reserve(funcs.size()); |
| for (auto func : funcs) { |
| segment->data.push_back( |
| builder.makeRefFunc(func, getModule()->getFunction(func)->type)); |
| } |
| } |
| |
| void addAnyToExternFunction() { |
| Type sharedAnyNullable = Type(HeapTypes::any.getBasic(Shared), Nullable); |
| Type externrefNullable = Type(HeapTypes::ext, Nullable); |
| auto importFunc = Builder::makeFunction( |
| anyToExternName, Signature(sharedAnyNullable, externrefNullable), {}); |
| importFunc->module = "env"; |
| importFunc->base = "any_to_extern"; |
| importFunc->hasExplicitName = true; |
| getModule()->addFunction(std::move(importFunc)); |
| } |
| |
| void addExternToAnyFunction() { |
| Type externrefNullable = Type(HeapTypes::ext, Nullable); |
| Type sharedAnyNullable = Type(HeapTypes::any.getBasic(Shared), Nullable); |
| auto importFunc = Builder::makeFunction( |
| externToAnyName, Signature(externrefNullable, sharedAnyNullable), {}); |
| importFunc->module = "env"; |
| importFunc->base = "extern_to_any"; |
| importFunc->hasExplicitName = true; |
| getModule()->addFunction(std::move(importFunc)); |
| } |
| |
| void addExternTable() { |
| externTable.maybeAdd(0, Table::kUnlimitedSize); |
| if (anyToExternName) { |
| addAnyToExternFunction(); |
| } |
| if (externToAnyName) { |
| addExternToAnyFunction(); |
| } |
| } |
| |
| std::unique_ptr<Pass> create() override { |
| return std::make_unique<MakeSharedObjects>(); |
| } |
| }; |
| |
| Pass* createMakeSharedObjectsPass() { return new MakeSharedObjects(); } |
| |
| } // namespace wasm |