/*
 * Copyright 2020 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.
 */

// The process of module splitting involves these steps:
//
//   1. Create the new secondary modules.
//
//   2. Move the deferred functions from the primary to each of the secondary
//      modules.
//
//   3. For any secondary function exported from the primary module, export in
//      its place a trampoline function that makes an indirect call to its
//      placeholder function (and eventually to the original secondary
//      function), allocating a new table slot for the placeholder if necessary.
//
//   4. Export globals, tags, tables, and memories from the primary module and
//      import them in the secondary modules. If possible, move those module
//      items instead to the secondary modules.
//
//   5. Replace all references to each secondary module's functions in the
//      primary module's and each other secondary module's table segments with
//      references to imported placeholder functions.
//
//   6. Rewrite direct calls from primary functions to secondary functions to be
//      indirect calls to their placeholder functions (and eventually to their
//      original secondary functions), allocating new table slots for the
//      placeholders if necessary.
//
//   7. For each primary function directly called from a secondary function,
//      export the primary function if it is not already exported and import it
//      into each secondary module using it.
//
//   8. For each secondary module, create new dispatch table segments in the
//      module that will replace all the placeholder function references in the
//      table with references to their corresponding secondary functions upon
//      instantiation.
//
// Functions can be used or referenced three ways in a WebAssembly module: they
// can be exported, called, or referenced with ref.func. The above procedure
// introduces a layer of indirection to each of those mechanisms that removes
// all references to secondary functions from the primary module but restores
// the original program's semantics once the secondary modules are instantiated.
//
// The code as currently written makes a couple assumptions about the module
// that is being split:
//
//   1. It assumes that mutable-globals is allowed. This could be worked around
//      by introducing wrapper functions for globals and rewriting secondary
//      code that accesses them, but now that mutable-globals is shipped on all
//      browsers, hopefully that extra complexity won't be necessary.
//
//   2. It assumes that either all table segment offsets are constants or there
//      is exactly one segment that may have a non-constant offset. It also
//      assumes that all segments are dispatch segments.
//
//   3. It assumes that if exact function references are required for validity
//      (because they are stored in a local with an exact function type, for
//      example), then custom descriptors are allowed so primary functions can
//      be imported exactly. This could be worked around by removing exactness
//      from the IR before splitting.
//
#include "ir/module-splitting.h"
#include "ir/effects.h"
#include "ir/find_all.h"
#include "ir/module-utils.h"
#include "ir/names.h"
#include "support/stdckdint.h"
#include "wasm-builder.h"
#include "wasm.h"

namespace wasm::ModuleSplitting {

namespace {

template<class F> void forEachElement(Module& module, F f) {
  ModuleUtils::iterActiveElementSegments(module, [&](ElementSegment* segment) {
    Name base;
    Index offset = 0;
    if (auto* c = segment->offset->dynCast<Const>()) {
      offset = c->value.getInteger();
    } else if (auto* g = segment->offset->dynCast<GlobalGet>()) {
      base = g->name;
    }
    for (Index i = 0; i < segment->data.size(); ++i) {
      f(segment->table, base, offset + i, segment->data[i]);
    }
  });
}

struct TableSlotManager {
  struct Slot {
    Name tableName;

    // If `global` is empty, then this slot is at a statically known index.
    Name global;
    Index index = 0;

    // Generate code to compute the index of this table slot
    Expression* makeExpr(Module& module);
  };
  Module& module;
  const std::vector<std::unique_ptr<Module>>& secondaries;
  Table* dispatchTable = nullptr;
  ElementSegment* dispatchSegment = nullptr;
  Slot dispatchBase;
  std::map<Name, Slot> funcIndices;
  std::vector<ElementSegment*> dispatchTableSegments;

  TableSlotManager(Module& module,
                   const std::vector<std::unique_ptr<Module>>& secondaries);

  Table* makeTable();
  ElementSegment* makeElementSegment();

  // Returns the table index for `func`, allocating a new index if necessary.
  Slot getSlot(Name func, HeapType type);
  void addSlot(Name func, Slot slot);
};

Expression* TableSlotManager::Slot::makeExpr(Module& module) {
  Builder builder(module);
  auto* table = module.getTable(tableName);
  auto makeIndex = [&]() {
    return builder.makeConst(Literal::makeFromInt32(index, table->addressType));
  };
  if (global) {
    Expression* getBase = builder.makeGlobalGet(global, table->addressType);
    auto addOp = table->is64() ? AddInt64 : AddInt32;
    return index == 0 ? getBase
                      : builder.makeBinary(addOp, getBase, makeIndex());
  } else {
    return makeIndex();
  }
}

void TableSlotManager::addSlot(Name func, Slot slot) {
  // Ignore functions that already have slots.
  funcIndices.insert({func, slot});
}

TableSlotManager::TableSlotManager(
  Module& module, const std::vector<std::unique_ptr<Module>>& secondaries)
  : module(module), secondaries(secondaries) {
  // If possible, just create a new table to manage all primary-to-secondary
  // calls lazily. Do not re-use slots for functions that will already be in
  // existing tables, since that is not correct in the face of table mutations.
  // However, do not do this for emscripten; its loader code (and dynamic
  // loading in particular) do not support this yet.
  // TODO: Reduce overhead by creating a separate table for each function type
  // if WasmGC is enabled.
  Export* emscriptenTableExport =
    module.getExportOrNull("__indirect_function_table");
  Table* singletonTable =
    module.tables.size() == 1 ? module.tables[0].get() : nullptr;
  bool emscriptenTableImport =
    singletonTable && singletonTable->imported() &&
    singletonTable->module == "env" &&
    singletonTable->base == "__indirect_function_table";

  if (module.features.hasReferenceTypes() && !emscriptenTableExport &&
      !emscriptenTableImport) {
    return;
  }

  // TODO: Reject or handle passive element segments
  auto funcref = Type(HeapType::func, Nullable);
  auto it = std::find_if(
    module.tables.begin(),
    module.tables.end(),
    [&](std::unique_ptr<Table>& table) { return table->type == funcref; });
  if (it == module.tables.end()) {
    // There is no indirect function table, so we will create one lazily.
    return;
  }

  dispatchTable = it->get();
  ModuleUtils::iterTableSegments(
    module, dispatchTable->name, [&](ElementSegment* segment) {
      dispatchTableSegments.push_back(segment);
    });

  if (dispatchTableSegments.empty()) {
    // There are no dispatch segments, so we will lazily create one and start
    // filling it at index 0.
    dispatchBase = {dispatchTable->name, Name(), 0};
  } else if (dispatchTableSegments.size() == 1 &&
             dispatchTableSegments[0]->type == funcref &&
             !dispatchTableSegments[0]->offset->is<Const>()) {
    // If there is exactly one table segment and that segment has a non-constant
    // offset, append new items to the end of that segment. In all other cases,
    // append new items at constant offsets after all existing items at constant
    // offsets.
    assert(dispatchTableSegments[0]->offset->is<GlobalGet>() &&
           "Unexpected initializer instruction");
    dispatchSegment = dispatchTableSegments[0];
    dispatchBase = {dispatchTable->name,
                    dispatchTableSegments[0]->offset->cast<GlobalGet>()->name,
                    0};
  } else {
    // Finds the segment with the highest occupied table slot so that new items
    // can be inserted contiguously at the end of it without accidentally
    // overwriting any other items. TODO: be more clever about filling gaps in
    // the table, if that is ever useful.
    Index maxIndex = 0;
    for (auto& segment : dispatchTableSegments) {
      assert(segment->offset->is<Const>() &&
             "Unexpected non-const segment offset with multiple segments");
      Index segmentBase = segment->offset->cast<Const>()->value.getInteger();
      if (segmentBase + segment->data.size() >= maxIndex) {
        maxIndex = segmentBase + segment->data.size();
        dispatchSegment = segment;
        dispatchBase = {dispatchTable->name, Name(), segmentBase};
      }
    }
  }

  // Initialize funcIndices with the functions already in the table.
  forEachElement(module,
                 [&](Name table, Name base, Index offset, Expression* elem) {
                   if (auto* func = elem->dynCast<RefFunc>()) {
                     addSlot(func->func, {table, base, offset});
                   }
                 });
}

Table* TableSlotManager::makeTable() {
  // Because the dispatch table will be imported in secondary modules, its name
  // should not collide with any existing tables in primary and secondary
  // modules.
  std::unordered_set<Name> secondaryTableNames;
  for (auto& secondary : secondaries) {
    for (auto& table : secondary->tables) {
      secondaryTableNames.insert(table->name);
    }
  }
  Name name = Names::getValidName("0", [&](Name test) {
    if (module.getTableOrNull(test)) {
      return false;
    }
    if (secondaryTableNames.contains(test)) {
      return false;
    }
    return true;
  });
  return module.addTable(Builder::makeTable(name));
}

ElementSegment* TableSlotManager::makeElementSegment() {
  Builder builder(module);
  Expression* offset =
    builder.makeConst(Literal::makeFromInt32(0, dispatchTable->addressType));
  return module.addElementSegment(Builder::makeElementSegment(
    Names::getValidElementSegmentName(module, Name::fromInt(0)),
    dispatchTable->name,
    offset));
}

TableSlotManager::Slot TableSlotManager::getSlot(Name func, HeapType type) {
  auto slotIt = funcIndices.find(func);
  if (slotIt != funcIndices.end()) {
    return slotIt->second;
  }

  // If there are no segments yet, allocate one.
  if (dispatchSegment == nullptr) {
    if (dispatchTable == nullptr) {
      dispatchTable = makeTable();
      dispatchBase = {dispatchTable->name, Name(), 0};
    }

    // None of the existing segments should refer to the dispatch table
    assert(std::all_of(module.elementSegments.begin(),
                       module.elementSegments.end(),
                       [&](std::unique_ptr<ElementSegment>& segment) {
                         return segment->table != dispatchTable->name;
                       }));

    dispatchSegment = makeElementSegment();
  }

  Slot newSlot = {dispatchBase.tableName,
                  dispatchBase.global,
                  dispatchBase.index + Index(dispatchSegment->data.size())};

  Builder builder(module);
  auto funcType = Type(type, NonNullable, Inexact);
  dispatchSegment->data.push_back(builder.makeRefFunc(func, funcType));

  addSlot(func, newSlot);
  if (dispatchTable->initial <= newSlot.index) {
    dispatchTable->initial = newSlot.index + 1;
    // TODO: handle the dispatch table not being the dylink table (#3823)
    if (module.dylinkSection) {
      module.dylinkSection->tableSize = dispatchTable->initial;
    }
  }
  if (dispatchTable->max <= newSlot.index) {
    dispatchTable->max = newSlot.index + 1;
  }
  return newSlot;
}

struct ModuleSplitter {
  const Config& config;
  std::vector<std::unique_ptr<Module>> secondaries;

  Module& primary;

  std::unordered_set<Name> primaryFuncs;
  std::unordered_set<Name> allSecondaryFuncs;
  std::unordered_map<Name, Index> funcToSecondaryIndex;

  TableSlotManager tableManager;

  Names::MinifiedNameGenerator minified;

  // Map from internal function names to (one of) their corresponding export
  // names.
  std::unordered_map<Name, Name> exportedPrimaryFuncs;

  // Map from <Kind, InternalName> to their corresponding export names for
  // non-function items.
  std::unordered_map<std::pair<ExternalKind, Name>, Name> exportedPrimaryItems;

  // For each table, map placeholder indices to the names of the functions they
  // replace.
  std::unordered_map<Name, std::map<size_t, Name>> placeholderMap;

  // Map from original secondary function name to its trampoline
  std::unordered_map<Name, Name> trampolineMap;

  void shareDispatchTable(Module* secondary);

  // Initialization helpers
  static std::unique_ptr<Module> initSecondary(const Module& primary);
  static std::unordered_map<Name, Name>
  initExportedPrimaryFuncs(const Module& primary);
  static std::unordered_map<std::pair<ExternalKind, Name>, Name>
  initExportedPrimaryItems(const Module& primary);

  // Other helpers
  void exportImportFunction(Name func, const std::set<Module*>& modules);
  void makeImportExport(Importable& primaryItem,
                        Importable& secondaryItem,
                        const std::string& genericExportName,
                        ExternalKind kind);
  Name getTrampoline(Name funcName);

  struct UsedNames {
    std::unordered_set<Name> globals;
    std::unordered_set<Name> memories;
    std::unordered_set<Name> tables;
    std::unordered_set<Name> tags;
    std::unordered_set<Name> dataSegments;
    std::unordered_set<Name> elementSegments;
  };
  using PrimarySecondaryUsedNames =
    std::pair<UsedNames, std::vector<UsedNames>>;
  PrimarySecondaryUsedNames computeUsedNames();

  // Main splitting steps
  void classifyFunctions();
  void moveSecondaryFunctions();
  void thunkExportedSecondaryFunctions();
  void shareImportableItems();
  void indirectReferencesToSecondaryFunctions();
  void indirectCallsToSecondaryFunctions();
  void exportImportCalledPrimaryFunctions();
  void setupTablePatching();

  ModuleSplitter(Module& primary, const Config& config)
    : config(config), primary(primary), tableManager(primary, secondaries),
      exportedPrimaryFuncs(initExportedPrimaryFuncs(primary)),
      exportedPrimaryItems(initExportedPrimaryItems(primary)) {
    classifyFunctions();
    moveSecondaryFunctions();
    thunkExportedSecondaryFunctions();
    shareImportableItems();
    indirectReferencesToSecondaryFunctions();
    indirectCallsToSecondaryFunctions();
    exportImportCalledPrimaryFunctions();
    setupTablePatching();
  }
};

std::unique_ptr<Module> ModuleSplitter::initSecondary(const Module& primary) {
  // Create the secondary module and copy trivial properties.
  auto secondary = std::make_unique<Module>();
  secondary->features = primary.features;
  secondary->hasFeaturesSection = primary.hasFeaturesSection;
  return secondary;
}

void ModuleSplitter::classifyFunctions() {
  // Find functions that refer to data or element segments. These functions must
  // remain in the primary module because segments cannot be exported to be
  // accessed from the secondary module.
  //
  // TODO: Investigate other options, such as moving the segments to the
  // secondary module or replacing the segment-using instructions in the
  // secondary module with calls to imports.
  ModuleUtils::ParallelFunctionAnalysis<std::vector<Name>>
    segmentReferrerCollector(
      primary, [&](Function* func, std::vector<Name>& segmentReferrers) {
        if (func->imported()) {
          return;
        }

        struct SegmentReferrerCollector
          : PostWalker<SegmentReferrerCollector,
                       UnifiedExpressionVisitor<SegmentReferrerCollector>> {
          bool hasSegmentReference = false;

          void visitExpression(Expression* curr) {

#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)
#define DELEGATE_FIELD_HEAPTYPE(id, field)
#define DELEGATE_FIELD_CHILD(id, field)
#define DELEGATE_FIELD_OPTIONAL_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_ADDRESS(id, field)

#define DELEGATE_FIELD_NAME_KIND(id, field, kind)                              \
  if (kind == ModuleItemKind::DataSegment ||                                   \
      kind == ModuleItemKind::ElementSegment) {                                \
    hasSegmentReference = true;                                                \
  }

#include "wasm-delegations-fields.def"
          }
        };
        SegmentReferrerCollector collector;
        collector.walkFunction(func);
        if (collector.hasSegmentReference) {
          segmentReferrers.push_back(func->name);
        }
      });

  std::unordered_set<Name> segmentReferrers;
  for (auto& [_, referrers] : segmentReferrerCollector.map) {
    segmentReferrers.insert(referrers.begin(), referrers.end());
  }

  std::unordered_set<Name> configSecondaryFuncs;
  for (auto& funcs : config.secondaryFuncs) {
    configSecondaryFuncs.insert(funcs.begin(), funcs.end());
  }
  for (auto& func : primary.functions) {
    if (func->imported() || !configSecondaryFuncs.contains(func->name) ||
        segmentReferrers.contains(func->name)) {
      primaryFuncs.insert(func->name);
    } else {
      assert(func->name != primary.start && "The start function must be kept");
      allSecondaryFuncs.insert(func->name);
    }
  }
}

std::unordered_map<Name, Name>
ModuleSplitter::initExportedPrimaryFuncs(const Module& primary) {
  std::unordered_map<Name, Name> functionExportNames;
  for (auto& ex : primary.exports) {
    if (ex->kind == ExternalKind::Function) {
      functionExportNames[*ex->getInternalName()] = ex->name;
    }
  }
  return functionExportNames;
}

std::unordered_map<std::pair<ExternalKind, Name>, Name>
ModuleSplitter::initExportedPrimaryItems(const Module& primary) {
  std::unordered_map<std::pair<ExternalKind, Name>, Name> exports;
  for (auto& ex : primary.exports) {
    if (ex->kind != ExternalKind::Function) {
      if (auto* name = ex->getInternalName()) {
        exports[std::make_pair(ex->kind, *name)] = ex->name;
      }
    }
  }
  return exports;
}

void ModuleSplitter::makeImportExport(Importable& primaryItem,
                                      Importable& secondaryItem,
                                      const std::string& genericExportName,
                                      ExternalKind kind) {
  secondaryItem.name = primaryItem.name;
  secondaryItem.hasExplicitName = primaryItem.hasExplicitName;
  secondaryItem.module = config.importNamespace;
  auto exportIt = exportedPrimaryItems.find({kind, primaryItem.name});
  if (exportIt != exportedPrimaryItems.end()) {
    secondaryItem.base = exportIt->second;
  } else {
    std::string baseName =
      config.newExportPrefix +
      (config.minimizeNewExportNames ? minified.getName() : genericExportName);
    Name exportName = Names::getValidExportName(primary, baseName);
    primary.addExport(
      std::make_unique<Export>(exportName, kind, primaryItem.name));
    secondaryItem.base = exportName;
    exportedPrimaryItems[{kind, primaryItem.name}] = exportName;
  }
}

void ModuleSplitter::exportImportFunction(Name funcName,
                                          const std::set<Module*>& modules) {
  Name exportName;
  // If the function is already exported, use the existing export name.
  // Otherwise, create a new export for it.
  auto exportIt = exportedPrimaryFuncs.find(funcName);
  if (exportIt != exportedPrimaryFuncs.end()) {
    exportName = exportIt->second;
  } else {
    if (config.minimizeNewExportNames) {
      do {
        exportName = config.newExportPrefix + minified.getName();
      } while (primary.getExportOrNull(exportName) != nullptr);
    } else {
      exportName = Names::getValidExportName(
        primary, config.newExportPrefix + funcName.toString());
    }
    primary.addExport(
      Builder::makeExport(exportName, funcName, ExternalKind::Function));
    exportedPrimaryFuncs[funcName] = exportName;
  }
  // Import the function if it is not already imported into the secondary
  // module.
  for (auto* secondary : modules) {
    if (secondary->getFunctionOrNull(funcName) == nullptr) {
      auto primaryFunc = primary.getFunction(funcName);
      auto func = Builder::makeFunction(funcName, primaryFunc->type, {});
      func->hasExplicitName = primaryFunc->hasExplicitName;
      func->module = config.importNamespace;
      func->base = exportName;
      func->type = func->type.withInexactIfNoCustomDescs(secondary->features);
      secondary->addFunction(std::move(func));
    }
  }
}

void ModuleSplitter::moveSecondaryFunctions() {
  // Move the specified functions from the primary to the secondary modules.
  for (auto& funcNames : config.secondaryFuncs) {
    auto secondary = initSecondary(primary);
    for (auto funcName : funcNames) {
      if (allSecondaryFuncs.contains(funcName)) {
        auto* func = primary.getFunction(funcName);
        ModuleUtils::copyFunction(func, *secondary);
        primary.removeFunction(funcName);
        funcToSecondaryIndex[funcName] = secondaries.size();
      }
    }
    secondaries.push_back(std::move(secondary));
  }
}

Name ModuleSplitter::getTrampoline(Name funcName) {
  auto [it, inserted] = trampolineMap.insert({funcName, Name()});
  if (!inserted) {
    return it->second;
  }

  Builder builder(primary);
  Module& secondary = *secondaries.at(funcToSecondaryIndex.at(funcName));
  auto* oldFunc = secondary.getFunction(funcName);
  auto trampoline = Names::getValidFunctionName(
    primary, std::string("trampoline_") + funcName.toString());
  it->second = trampoline;

  // Generate the call and the function. We generate a direct call here, but
  // this will be converted to a call_indirect in
  // indirectCallsToSecondaryFunctions.
  std::vector<Expression*> args;
  for (Index i = 0; i < oldFunc->getNumParams(); i++) {
    args.push_back(builder.makeLocalGet(i, oldFunc->getLocalType(i)));
  }
  auto* call = builder.makeCall(funcName, args, oldFunc->getResults());

  auto func = builder.makeFunction(trampoline, oldFunc->type, {}, call);
  func->hasExplicitName = oldFunc->hasExplicitName;
  primary.addFunction(std::move(func));
  primaryFuncs.insert(trampoline);
  return trampoline;
}

void ModuleSplitter::shareDispatchTable(Module* secondary) {
  assert(tableManager.dispatchTable);
  auto secondaryTable =
    secondary->getTableOrNull(tableManager.dispatchTable->name);
  if (secondaryTable) {
    // In case it's already in the secondary module, sync the initial/max
    secondaryTable->initial = tableManager.dispatchTable->initial;
    secondaryTable->max = tableManager.dispatchTable->max;
  } else {
    secondaryTable =
      ModuleUtils::copyTable(tableManager.dispatchTable, *secondary);
    makeImportExport(*tableManager.dispatchTable,
                     *secondaryTable,
                     "table",
                     ExternalKind::Table);
  }
  if (tableManager.dispatchBase.global) {
    auto* primaryGlobal = primary.getGlobal(tableManager.dispatchBase.global);
    auto* secondaryGlobal =
      secondary->getGlobalOrNull(tableManager.dispatchBase.global);
    if (!secondaryGlobal) {
      secondaryGlobal = ModuleUtils::copyGlobal(primaryGlobal, *secondary);
      makeImportExport(
        *primaryGlobal, *secondaryGlobal, "global", ExternalKind::Global);
    }
  }
}

void ModuleSplitter::thunkExportedSecondaryFunctions() {
  // Update exports of secondary functions in the primary module to export
  // wrapper functions that indirectly call the secondary functions. We are
  // adding secondary function names to the primary table here, but they will be
  // replaced with placeholder functions later along with any references to
  // secondary functions that were already in the table.
  Builder builder(primary);
  for (auto& ex : primary.exports) {
    if (ex->kind != ExternalKind::Function ||
        !allSecondaryFuncs.contains(*ex->getInternalName())) {
      continue;
    }
    Name trampoline = getTrampoline(*ex->getInternalName());
    ex->setInternalName(trampoline);
  }
}

ModuleSplitter::PrimarySecondaryUsedNames ModuleSplitter::computeUsedNames() {
  struct NameCollector
    : public PostWalker<NameCollector,
                        UnifiedExpressionVisitor<NameCollector>> {
    UsedNames& used;
    NameCollector(UsedNames& used) : used(used) {}

    void visitExpression(Expression* curr) {
#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)
#define DELEGATE_FIELD_HEAPTYPE(id, 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_ADDRESS(id, field)

#define DELEGATE_FIELD_NAME_KIND(id, field, kind)                              \
  if (cast->field.is()) {                                                      \
    switch (kind) {                                                            \
      case ModuleItemKind::Table:                                              \
        used.tables.insert(cast->field);                                       \
        break;                                                                 \
      case ModuleItemKind::Memory:                                             \
        used.memories.insert(cast->field);                                     \
        break;                                                                 \
      case ModuleItemKind::Global:                                             \
        used.globals.insert(cast->field);                                      \
        break;                                                                 \
      case ModuleItemKind::Tag:                                                \
        used.tags.insert(cast->field);                                         \
        break;                                                                 \
      case ModuleItemKind::DataSegment:                                        \
        used.dataSegments.insert(cast->field);                                 \
        break;                                                                 \
      case ModuleItemKind::ElementSegment:                                     \
        used.elementSegments.insert(cast->field);                              \
        break;                                                                 \
      case ModuleItemKind::Function:                                           \
      case ModuleItemKind::Invalid:                                            \
        break;                                                                 \
    }                                                                          \
  }

#include "wasm-delegations-fields.def"
    }
  };

  // Given a module, collect names used in the module
  auto scanModule = [&](Module& module) {
    UsedNames used;
    ModuleUtils::ParallelFunctionAnalysis<UsedNames> nameCollector(
      module, [&](Function* func, UsedNames& used) {
        if (!func->imported()) {
          NameCollector(used).walk(func->body);
        }
      });

    for (auto& [_, funcUsed] : nameCollector.map) {
      used.globals.insert(funcUsed.globals.begin(), funcUsed.globals.end());
      used.memories.insert(funcUsed.memories.begin(), funcUsed.memories.end());
      used.tables.insert(funcUsed.tables.begin(), funcUsed.tables.end());
      used.tags.insert(funcUsed.tags.begin(), funcUsed.tags.end());
      used.dataSegments.insert(funcUsed.dataSegments.begin(),
                               funcUsed.dataSegments.end());
      used.elementSegments.insert(funcUsed.elementSegments.begin(),
                                  funcUsed.elementSegments.end());
    }

    NameCollector collector(used);

    return used;
  };

  UsedNames primaryUsed = scanModule(primary);
  std::vector<UsedNames> secondaryUsed;
  for (auto& secondaryPtr : secondaries) {
    secondaryUsed.push_back(scanModule(*secondaryPtr));
  }

  // If primary module has exports, they are "used" in it. Secondary modules
  // don't have exports, so this only applies to the primary module.
  for (auto& ex : primary.exports) {
    switch (ex->kind) {
      case ExternalKind::Global:
        primaryUsed.globals.insert(*ex->getInternalName());
        break;
      case ExternalKind::Memory:
        primaryUsed.memories.insert(*ex->getInternalName());
        break;
      case ExternalKind::Table:
        primaryUsed.tables.insert(*ex->getInternalName());
        break;
      case ExternalKind::Tag:
        primaryUsed.tags.insert(*ex->getInternalName());
        break;
      default:
        break;
    }
  }

  // We need to assume the dispatch table and its base global are used in the
  // primary module, because we will create segments there later.
  if (tableManager.dispatchTable) {
    primaryUsed.tables.insert(tableManager.dispatchTable->name);
  }
  if (tableManager.dispatchBase.global) {
    primaryUsed.globals.insert(tableManager.dispatchBase.global);
  }

  // If custom-descirptors is enabled, global and table initializers can trap.
  // Trapping globals should stay in the primary module to preserve the trapping
  // behavior upon instantiation.
  if (primary.features.hasCustomDescriptors()) {
    for (auto& global : primary.globals) {
      if (global->init &&
          EffectAnalyzer(config.passOptions, primary, global->init)
            .hasUnremovableSideEffects()) {
        primaryUsed.globals.insert(global->name);
      }
    }
    for (auto& table : primary.tables) {
      if (table->init &&
          EffectAnalyzer(config.passOptions, primary, table->init)
            .hasUnremovableSideEffects()) {
        primaryUsed.tables.insert(table->name);
      }
    }
  }

  // Given a name and a module item kind (field pointer), find which module
  // "owns" it. If it is used by exactly one secondary module, that secondary
  // module is the owner. If it is used by the primary module or multiple
  // secondary modules, the primary module is the owner. If it is not used,
  // returns nullptr.
  auto getOwner = [&](Name name, auto UsedNames::* field) -> UsedNames* {
    UsedNames* owner = nullptr;
    if ((primaryUsed.*field).contains(name)) {
      owner = &primaryUsed;
    }
    for (auto& sec : secondaryUsed) {
      if ((sec.*field).contains(name)) {
        if (owner) {
          owner = &primaryUsed;
          break;
        }
        owner = &sec;
      }
    }
    return owner;
  };

  // Scan table initializers into their owning modules. If a table is used by a
  // single secondary module, its initializer dependencies are marked as "used"
  // in that secondary module. Otherwise, they are marked as used in the primary
  // module.
  if (primary.features.hasGC()) {
    for (auto& table : primary.tables) {
      if (!table->init) {
        continue;
      }
      if (UsedNames* owner = getOwner(table->name, &UsedNames::tables)) {
        NameCollector(*owner).walk(table->init);
      }
    }
  }

  auto mayTrap = [&](auto* segment) {
    if constexpr (std::is_same_v<decltype(segment), ElementSegment*>) {
      if (primary.features.hasCustomDescriptors()) {
        for (auto* item : segment->data) {
          if (EffectAnalyzer(config.passOptions, primary, item)
                .hasUnremovableSideEffects()) {
            return true;
          }
        }
      }
    }

    // Check for out-of-bounds offset. This is adapted from maybeRootSegment
    // function in RemoveUnusedModuleElements pass.
    if (!config.passOptions.trapsNeverHappen) {
      Index segmentSize;
      Index parentSize;
      if constexpr (std::is_same_v<decltype(segment), DataSegment*>) {
        segmentSize = segment->data.size();
        auto* memory = primary.getMemory(segment->memory);
        parentSize = memory->initial << memory->pageSizeLog2;
      } else {
        segmentSize = segment->data.size();
        auto* table = primary.getTable(segment->table);
        parentSize = table->initial;
      }

      // Check if this might trap. If it is obviously in bounds then it cannot.
      auto* c = segment->offset->template dynCast<Const>();
      // Check for overflow in the largest possible space of addresses.
      uint64_t maxWritten;
      // If there is no integer, or if there is and the addition overflows, or
      // if the addition leads to a too-large value, then we may trap.
      if (!c ||
          std::ckd_add(&maxWritten,
                       (uint64_t)segmentSize,
                       (uint64_t)c->value.getInteger()) ||
          maxWritten > parentSize) {
        return true;
      }
    }
    return false;
  };

  // Iterate on active data and element segments. If its table or memory is
  // used by a single secondary module, mark it "used" there. Only scan its
  // 'offset' or 'data'(in case of ElementSegment) and add it to that module's
  // used only when it is a sole secondary owner. If not assign it to the
  // primary module and scan it there.
  ModuleUtils::iterActiveDataSegments(primary, [&](DataSegment* segment) {
    UsedNames* owner = getOwner(segment->memory, &UsedNames::memories);
    // Trapping segments should be kept in the primary module because they are
    // evaluated at the instantiation time.
    if (mayTrap(segment)) {
      owner = &primaryUsed;
    }
    if (!owner) {
      return;
    }
    owner->dataSegments.insert(segment->name);
    owner->memories.insert(segment->memory);
    if (segment->offset) {
      NameCollector(*owner).walk(segment->offset);
    }
  });

  ModuleUtils::iterActiveElementSegments(primary, [&](ElementSegment* segment) {
    UsedNames* owner = getOwner(segment->table, &UsedNames::tables);

    // If placeholders are NOT used, and if all functions in an element segment
    // belong to a single secondary module, we can move the segment to that
    // secondary module, because those functions aren't available until the
    // secondary module is loaded anyway.
    if (!config.usePlaceholders && segment->type.isFunction() &&
        owner == &primaryUsed) {
      bool foundSecondary = false;
      Index secondaryIndex = 0;
      bool keepInPrimary = false;
      for (auto* item : segment->data) {
        if (item->is<GlobalGet>()) {
          // If we encounter a global.get, we just keep the segment in the
          // primary module. TODO Consider moving this if this global is in a
          // secondary module.
          keepInPrimary = true;
          break;
        } else if (auto* ref = item->dynCast<RefFunc>()) {
          // If this ref.func is in the primary module, keep the segment there.
          auto it = funcToSecondaryIndex.find(ref->func);
          if (it == funcToSecondaryIndex.end()) {
            keepInPrimary = true;
            break;
          }
          // If the segment contains ref.funcs from more than one secondary
          // modules, keep it in the primary.
          if (foundSecondary && secondaryIndex != it->second) {
            keepInPrimary = true;
            break;
          }
          // This segment contains ref.funcs from a single secondary module.
          foundSecondary = true;
          secondaryIndex = it->second;
        }
      }
      if (!keepInPrimary && foundSecondary) {
        owner = &secondaryUsed[secondaryIndex];
      }
    }

    if (mayTrap(segment)) {
      owner = &primaryUsed;
    }
    if (!owner) {
      return;
    }
    owner->elementSegments.insert(segment->name);
    owner->tables.insert(segment->table);
    if (segment->offset) {
      NameCollector(*owner).walk(segment->offset);
    }
    for (auto* item : segment->data) {
      NameCollector(*owner).walk(item);
    }
  });

  // Passive element segments contain expressions (e.g. global.get) in their
  // data arrays that must be scanned. Since currently functions referring to
  // segments are forced into the primary module, passive segments always belong
  // to the primary module.
  for (auto& segment : primary.elementSegments) {
    if (segment->isPassive() &&
        primaryUsed.elementSegments.contains(segment->name)) {
      for (auto* item : segment->data) {
        NameCollector(primaryUsed).walk(item);
      }
    }
  }

  // Compute the transitive closure of globals referenced in other globals'
  // initializers. WebAssembly validation requires that global initializers only
  // refer to previously defined globals. Therefore, `primary.globals` is
  // guaranteed to be topologically sorted with respect to its internal
  // dependencies. By iterating in reverse, we are guaranteed to process
  // dependent globals before the globals they depend on, allowing us to
  // propagate ownership in a single pass.
  for (auto it = primary.globals.rbegin(); it != primary.globals.rend(); ++it) {
    auto& global = *it;
    if (!global->init) {
      continue;
    }
    if (UsedNames* owner = getOwner(global->name, &UsedNames::globals)) {
      for (auto* get : FindAll<GlobalGet>(global->init).list) {
        owner->globals.insert(get->name);
      }
    }
  }

  return std::make_pair(primaryUsed, secondaryUsed);
}

void ModuleSplitter::shareImportableItems() {
  auto usedNames = computeUsedNames();
  auto& primaryUsed = usedNames.first;
  auto& secondaryUsed = usedNames.second;

  // Given a name and module item kind, returns the list of secondary modules
  // using that name
  auto getUsingSecondaries = [&](const Name& name, auto UsedNames::* field) {
    std::vector<Module*> usingModules;
    for (size_t i = 0; i < secondaries.size(); ++i) {
      if ((secondaryUsed[i].*field).contains(name)) {
        usingModules.push_back(secondaries[i].get());
      }
    }
    return usingModules;
  };

  // Share module items with secondary modules.
  // 1. Only share an item with the modules that use it
  // 2. If an item is used by only a single secondary module, move the item to
  //    that secondary module. If an item is used by multiple modules (including
  //    the primary and secondary modules), export the item from the primary and
  //    import it from the using secondary modules.

  std::vector<Name> memoriesToRemove;
  for (auto& memory : primary.memories) {
    auto usingSecondaries =
      getUsingSecondaries(memory->name, &UsedNames::memories);
    bool inPrimary = primaryUsed.memories.contains(memory->name);

    if (!inPrimary && usingSecondaries.empty()) {
      memoriesToRemove.push_back(memory->name);
    } else if (!inPrimary && usingSecondaries.size() == 1) {
      auto* secondary = usingSecondaries[0];
      ModuleUtils::copyMemory(memory.get(), *secondary);
      memoriesToRemove.push_back(memory->name);
    } else {
      for (auto* secondary : usingSecondaries) {
        auto* secondaryMemory =
          ModuleUtils::copyMemory(memory.get(), *secondary);
        makeImportExport(
          *memory, *secondaryMemory, "memory", ExternalKind::Memory);
      }
    }
  }
  for (auto& name : memoriesToRemove) {
    primary.removeMemory(name);
  }

  std::vector<Name> tablesToRemove;
  for (auto& table : primary.tables) {
    auto usingSecondaries =
      getUsingSecondaries(table->name, &UsedNames::tables);
    bool inPrimary = primaryUsed.tables.contains(table->name);

    if (!inPrimary && usingSecondaries.empty()) {
      tablesToRemove.push_back(table->name);
    } else if (!inPrimary && usingSecondaries.size() == 1) {
      auto* secondary = usingSecondaries[0];
      assert(!secondary->getTableOrNull(table->name));
      ModuleUtils::copyTable(table.get(), *secondary);
      tablesToRemove.push_back(table->name);
    } else {
      for (auto* secondary : usingSecondaries) {
        auto* secondaryTable = ModuleUtils::copyTable(table.get(), *secondary);
        makeImportExport(*table, *secondaryTable, "table", ExternalKind::Table);
      }
    }
  }
  for (auto& name : tablesToRemove) {
    primary.removeTable(name);
  }

  std::vector<Name> globalsToRemove;
  for (auto& global : primary.globals) {
    if (global->mutable_) {
      assert(primary.features.hasMutableGlobals() &&
             "TODO: add wrapper functions for disallowed mutable globals");
    }

    auto usingSecondaries =
      getUsingSecondaries(global->name, &UsedNames::globals);
    bool inPrimary = primaryUsed.globals.contains(global->name);

    if (!inPrimary && usingSecondaries.empty()) {
      globalsToRemove.push_back(global->name);
    } else if (!inPrimary && usingSecondaries.size() == 1) {
      auto* secondary = usingSecondaries[0];
      ModuleUtils::copyGlobal(global.get(), *secondary);
      globalsToRemove.push_back(global->name);
    } else {
      for (auto* secondary : usingSecondaries) {
        auto* secondaryGlobal =
          ModuleUtils::copyGlobal(global.get(), *secondary);
        makeImportExport(
          *global, *secondaryGlobal, "global", ExternalKind::Global);
      }
    }
  }
  for (auto& name : globalsToRemove) {
    primary.removeGlobal(name);
  }

  std::vector<Name> tagsToRemove;
  for (auto& tag : primary.tags) {
    auto usingSecondaries = getUsingSecondaries(tag->name, &UsedNames::tags);
    bool inPrimary = primaryUsed.tags.contains(tag->name);

    if (!inPrimary && usingSecondaries.empty()) {
      tagsToRemove.push_back(tag->name);
    } else if (!inPrimary && usingSecondaries.size() == 1) {
      auto* secondary = usingSecondaries[0];
      ModuleUtils::copyTag(tag.get(), *secondary);
      tagsToRemove.push_back(tag->name);
    } else {
      for (auto* secondary : usingSecondaries) {
        auto* secondaryTag = ModuleUtils::copyTag(tag.get(), *secondary);
        makeImportExport(*tag, *secondaryTag, "tag", ExternalKind::Tag);
      }
    }
  }
  for (auto& name : tagsToRemove) {
    primary.removeTag(name);
  }

  // Move segments that are exclusively used in a secondary module. If not, do
  // nothing. (Segments cannot be imported / exported. They will be handled in
  // indirectReferencesToSecondaryFunctions.)

  std::vector<Name> dataSegmentsToRemove;
  for (auto& dataSegment : primary.dataSegments) {
    auto usingSecondaries =
      getUsingSecondaries(dataSegment->name, &UsedNames::dataSegments);
    bool inPrimary = primaryUsed.dataSegments.contains(dataSegment->name);

    if (!inPrimary && usingSecondaries.empty()) {
      dataSegmentsToRemove.push_back(dataSegment->name);
    } else if (!inPrimary && usingSecondaries.size() == 1) {
      auto* secondary = usingSecondaries[0];
      ModuleUtils::copyDataSegment(dataSegment.get(), *secondary);
      dataSegmentsToRemove.push_back(dataSegment->name);
    }
  }
  for (auto& name : dataSegmentsToRemove) {
    primary.removeDataSegment(name);
  }

  std::vector<Name> elementSegmentsToRemove;
  for (auto& elementSegment : primary.elementSegments) {
    auto usingSecondaries =
      getUsingSecondaries(elementSegment->name, &UsedNames::elementSegments);
    bool inPrimary = primaryUsed.elementSegments.contains(elementSegment->name);

    if (!inPrimary && usingSecondaries.empty()) {
      elementSegmentsToRemove.push_back(elementSegment->name);
    } else if (!inPrimary && usingSecondaries.size() == 1) {
      auto* secondary = usingSecondaries[0];
      ModuleUtils::copyElementSegment(elementSegment.get(), *secondary);
      elementSegmentsToRemove.push_back(elementSegment->name);
    }
  }
  for (auto& name : elementSegmentsToRemove) {
    primary.removeElementSegment(name);
  }
}

void ModuleSplitter::indirectReferencesToSecondaryFunctions() {
  // Turn references to secondary functions into references to thunks that
  // perform a direct call to the original referent. The direct calls in the
  // thunks will be handled like all other cross-module calls later, in
  // |indirectCallsToSecondaryFunctions|.
  struct Gatherer : public PostWalker<Gatherer> {
    ModuleSplitter& parent;

    Gatherer(ModuleSplitter& parent) : parent(parent) {}

    // Collect RefFuncs in a map from the function name to all RefFuncs that
    // refer to it. We only collect this for secondary funcs.
    InsertOrderedMap<Name, std::vector<RefFunc*>> map;

    void visitRefFunc(RefFunc* curr) {
      Module* currModule = getModule();
      // Add ref.func to the map when
      // 1. ref.func's target func is in one of the secondary modules and
      // 2. the current module is a different module (either the primary module
      //    or a different secondary module)
      if (parent.allSecondaryFuncs.contains(curr->func) &&
          (currModule == &parent.primary ||
           parent.secondaries.at(parent.funcToSecondaryIndex.at(curr->func))
               .get() != currModule)) {
        map[curr->func].push_back(curr);
      }
    }
  } gatherer(*this);
  gatherer.walkModule(&primary);
  for (auto& secondaryPtr : secondaries) {
    gatherer.walkModule(secondaryPtr.get());
  }

  // Ignore references to secondary functions that occur in the dispatch
  // segments that will contain the imported placeholders. Indirect calls to
  // table slots initialized by those segments will already go to the right
  // place once the secondary module has been loaded and the table has been
  // patched.
  std::unordered_set<RefFunc*> ignore;
  for (auto* segment : tableManager.dispatchTableSegments) {
    for (auto* expr : segment->data) {
      if (auto* ref = expr->dynCast<RefFunc>()) {
        ignore.insert(ref);
      }
    }
  }

  // Fix up what we found: Generate trampolines as described earlier, and apply
  // them.
  Builder builder(primary);
  // Generate the new trampoline function and add it to the module.
  for (auto& [name, refFuncs] : gatherer.map) {
    // Find the relevant (non-ignored) RefFuncs. If there are none, we can skip
    // creating a thunk entirely.
    std::vector<RefFunc*> relevantRefFuncs;
    for (auto* refFunc : refFuncs) {
      assert(refFunc->func == name);
      if (!ignore.contains(refFunc)) {
        relevantRefFuncs.push_back(refFunc);
      }
    }
    if (relevantRefFuncs.empty()) {
      continue;
    }

    Name trampoline = getTrampoline(name);
    // Update RefFuncs to refer to it.
    for (auto* refFunc : relevantRefFuncs) {
      refFunc->func = trampoline;
    }
  }
}

void ModuleSplitter::indirectCallsToSecondaryFunctions() {
  // Update direct calls of secondary functions to be indirect calls of their
  // corresponding table indices instead.
  struct CallIndirector : public PostWalker<CallIndirector> {
    ModuleSplitter& parent;
    std::unordered_set<Module*> dispatchTableUsingSecondaries;
    CallIndirector(ModuleSplitter& parent) : parent(parent) {}
    void visitCall(Call* curr) {
      // Return if the call's target is not in one of the secondary module.
      if (!parent.allSecondaryFuncs.contains(curr->target)) {
        return;
      }
      // Return if the current module is the same module as the call's target,
      // because we don't need a call_indirect within the same module.
      Module* currModule = getModule();
      Module* calleeModule =
        parent.secondaries.at(parent.funcToSecondaryIndex.at(curr->target))
          .get();
      if (currModule == calleeModule) {
        return;
      }

      Builder builder(*currModule);
      auto* func = calleeModule->getFunction(curr->target);
      auto tableSlot =
        parent.tableManager.getSlot(curr->target, func->type.getHeapType());
      replaceCurrent(
        builder.makeCallIndirect(tableSlot.tableName,
                                 tableSlot.makeExpr(parent.primary),
                                 curr->operands,
                                 func->type.getHeapType(),
                                 curr->isReturn));

      // Share the dispatch table with the current module (caller). We share the
      // dispatch table with with calleeModule later in setupTablePathing.
      if (currModule != &parent.primary) {
        dispatchTableUsingSecondaries.insert(currModule);
      }
    }
  };
  CallIndirector callIndirector(*this);
  callIndirector.walkModule(&primary);
  for (auto& secondaryPtr : secondaries) {
    callIndirector.walkModule(secondaryPtr.get());
  }

  for (auto* secondary : callIndirector.dispatchTableUsingSecondaries) {
    shareDispatchTable(secondary);
  }
}

void ModuleSplitter::exportImportCalledPrimaryFunctions() {
  // Find primary functions called/referred to from the secondary modules.
  using CalledPrimaryToModules = std::map<Name, std::set<Module*>>;
  struct CallCollector : PostWalker<CallCollector> {
    const std::unordered_set<Name>& primaryFuncs;
    CalledPrimaryToModules& calledPrimaryToModules;
    CallCollector(const std::unordered_set<Name>& primaryFuncs,
                  CalledPrimaryToModules& calledPrimaryToModules)
      : primaryFuncs(primaryFuncs),
        calledPrimaryToModules(calledPrimaryToModules) {}
    void visitCall(Call* curr) {
      if (primaryFuncs.contains(curr->target)) {
        calledPrimaryToModules[curr->target].insert(getModule());
      }
    }
    void visitRefFunc(RefFunc* curr) {
      if (primaryFuncs.contains(curr->func)) {
        calledPrimaryToModules[curr->func].insert(getModule());
      }
    }
  };

  for (auto& secondaryPtr : secondaries) {
    Module* secondary = secondaryPtr.get();
    ModuleUtils::ParallelFunctionAnalysis<CalledPrimaryToModules> callCollector(
      *secondary,
      [&](Function* func, CalledPrimaryToModules& calledPrimaryToModules) {
        CallCollector(primaryFuncs, calledPrimaryToModules)
          .walkFunctionInModule(func, secondary);
      });

    CalledPrimaryToModules calledPrimaryToModules;
    for (auto& [_, map] : callCollector.map) {
      calledPrimaryToModules.merge(map);
    }

    CallCollector collector(primaryFuncs, calledPrimaryToModules);
    collector.walkModuleCode(secondary);

    // Ensure each called primary function is exported and imported
    for (auto& [func, modules] : calledPrimaryToModules) {
      exportImportFunction(func, modules);
    }
  }
}

void ModuleSplitter::setupTablePatching() {
  if (!tableManager.dispatchTable) {
    return;
  }

  std::map<Module*, std::map<Index, Function*>> moduleToReplacedElems;
  Name fillerName;
  Type fillerType = Type(Signature(Type::none, Type::none), NonNullable, Exact);
  // Replace table references to secondary functions with an imported
  // placeholder that encodes the table index in its name:
  // `importNamespace`.`index`.
  forEachElement(
    primary, [&](Name table, Name, Index index, Expression*& elem) {
      auto* ref = elem->dynCast<RefFunc>();
      if (!ref) {
        return;
      }
      if (!allSecondaryFuncs.contains(ref->func)) {
        return;
      }
      assert(table == tableManager.dispatchTable->name);

      placeholderMap[table][index] = ref->func;
      Index secondaryIndex = funcToSecondaryIndex.at(ref->func);
      Module& secondary = *secondaries.at(secondaryIndex);
      Name secondaryName = config.secondaryNames.at(secondaryIndex);
      auto* secondaryFunc = secondary.getFunction(ref->func);
      moduleToReplacedElems[&secondary][index] = secondaryFunc;

      if (config.usePlaceholders) {
        auto placeholder = std::make_unique<Function>();
        placeholder->module = config.placeholderNamespacePrefix.toString() +
                              "." + secondaryName.toString();
        placeholder->base = std::to_string(index);
        placeholder->name = Names::getValidFunctionName(
          primary, std::string("placeholder_") + placeholder->base.toString());
        placeholder->hasExplicitName = true;
        placeholder->type = secondaryFunc->type.with(Inexact);
        elem =
          Builder(primary).makeRefFunc(placeholder->name, placeholder->type);
        primary.addFunction(std::move(placeholder));

      } else { // !config.usePlaceholders
        if (primary.features.hasReferenceTypes()) {
          // TODO: This can create dispatch element segments with lots of nulls.
          // We should optimize them like we do data segments with zeros.
          elem = Builder(primary).makeRefNull(HeapType::nofunc);
          return;
        }
        // When reference-types is not enabled, we can't use a ref.null. Put a
        // filler function that contains an unreachable.
        if (!fillerName) {
          fillerName = Names::getValidFunctionName(primary, "filler");
          auto filler = Builder::makeFunction(
            fillerName, fillerType, {}, Builder(primary).makeUnreachable());
          filler->hasExplicitName = true;
          primary.addFunction(std::move(filler));
        }
        elem = Builder(primary).makeRefFunc(fillerName, fillerType);
      }
    });

  if (moduleToReplacedElems.size() == 0) {
    // No placeholders to patch out of the table
    return;
  }

  for (auto& [secondaryPtr, replacedElems] : moduleToReplacedElems) {
    Module& secondary = *secondaryPtr;
    shareDispatchTable(&secondary);
    auto* secondaryTable = secondary.getTable(tableManager.dispatchTable->name);

    if (tableManager.dispatchBase.global) {
      assert(tableManager.dispatchTableSegments.size() == 1 &&
             "Unexpected number of segments with non-const base");
      assert(secondary.tables.size() == 1 && secondary.elementSegments.empty());
      // Since addition is not currently allowed in initializer expressions, we
      // need to start the new secondary segment where the primary segment
      // starts. The secondary segment will contain the same primary functions
      // as the primary module except in positions where it needs to overwrite a
      // placeholder function. All primary functions in the table therefore need
      // to be imported into the second module. TODO: use better strategies
      // here, such as using ref.func in the start function or standardizing
      // addition in initializer expressions.
      ElementSegment* primarySeg = tableManager.dispatchTableSegments.front();
      std::vector<Expression*> secondaryElems;
      secondaryElems.reserve(primarySeg->data.size());

      // Copy functions from the primary segment to the secondary segment,
      // replacing placeholders and creating new exports and imports as
      // necessary.
      auto replacement = replacedElems.begin();
      for (Index i = 0;
           i < primarySeg->data.size() && replacement != replacedElems.end();
           ++i) {
        if (replacement->first == i) {
          // primarySeg->data[i] is a placeholder, so use the secondary
          // function.
          auto* func = replacement->second;
          auto* ref = Builder(secondary).makeRefFunc(func->name, func->type);
          secondaryElems.push_back(ref);
          ++replacement;
        } else if (auto* get = primarySeg->data[i]->dynCast<RefFunc>()) {
          exportImportFunction(get->func, {&secondary});
          auto* copied =
            ExpressionManipulator::copy(primarySeg->data[i], secondary);
          secondaryElems.push_back(copied);
        }
      }

      auto offset = ExpressionManipulator::copy(primarySeg->offset, secondary);
      auto secondarySeg = std::make_unique<ElementSegment>(
        secondaryTable->name, offset, secondaryTable->type, secondaryElems);
      secondarySeg->setName(primarySeg->name, primarySeg->hasExplicitName);
      secondary.addElementSegment(std::move(secondarySeg));
      return;
    }

    // Create dispatch table segments in the secondary module to patch in the
    // original functions when it is instantiated.
    Index currBase = replacedElems.begin()->first;
    std::vector<Expression*> currData;
    auto finishSegment = [&]() {
      auto* offset = Builder(secondary).makeConst(
        Literal::makeFromInt32(currBase, secondaryTable->addressType));
      auto secondarySeg = std::make_unique<ElementSegment>(
        secondaryTable->name, offset, secondaryTable->type, currData);
      Name name = Names::getValidElementSegmentName(
        secondary, Name::fromInt(secondary.elementSegments.size()));
      secondarySeg->setName(name, false);
      secondary.addElementSegment(std::move(secondarySeg));
    };
    for (auto curr = replacedElems.begin(); curr != replacedElems.end();
         ++curr) {
      if (curr->first != currBase + currData.size()) {
        finishSegment();
        currBase = curr->first;
        currData.clear();
      }
      auto* func = curr->second;
      currData.push_back(
        Builder(secondary).makeRefFunc(func->name, func->type));
    }
    if (currData.size()) {
      finishSegment();
    }
  }
}

} // anonymous namespace

Results splitFunctions(Module& primary, const Config& config) {
  ModuleSplitter split(primary, config);
  return {std::move(split.secondaries), std::move(split.placeholderMap)};
}

} // namespace wasm::ModuleSplitting
