@ 261106.8
current
/*
 * 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.isNonNullableRef()) {
      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.isNonNullableRef()) {
      // (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.isNullableRef())));
    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.isNullableRef()) {
      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.isNonNullableRef()) {
      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.isNonNullableRef()) {
        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