blob: 8a52fb7994830dafa792048041da4bf56cdcb68f [file]
/*
* 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