/*
 * Copyright 2023 WebAssembly Community Group participants
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <cstddef>
#include <iterator>
#include <memory>

#include "ir/effects.h"
#include "ir/js-utils.h"
#include "ir/localize.h"
#include "ir/module-utils.h"
#include "ir/names.h"
#include "ir/struct-utils.h"
#include "ir/subtype-exprs.h"
#include "ir/type-updating.h"
#include "ir/utils.h"
#include "pass.h"
#include "support/index.h"
#include "wasm-traversal.h"
#include "wasm-type.h"
#include "wasm.h"

#ifndef UNSUBTYPING_DEBUG
#define UNSUBTYPING_DEBUG 0
#endif

#if UNSUBTYPING_DEBUG
#include "support/insert_ordered.h"
#else
#include <unordered_map>
#include <unordered_set>
#endif

#if UNSUBTYPING_DEBUG
#define DBG(x) x
#else
#define DBG(x)
#endif

// Compute and use the minimal subtype (and descriptor) relations required to
// maintain module validity and behavior. This minimal relation will be a subset
// of the original subtype (and descriptor) relations. Start by walking the IR
// and collecting pairs of types that need to be in the subtype relation for
// each expression to validate (or require a type to have a descriptor). For
// example, a local.set requires that the type of its operand be a subtype of
// the local's type. Casts do not generate subtypings at this point because it
// is not necessary for the cast target to be a subtype of the cast source for
// the cast to validate.
//
// From that initial subtype relation, we then start finding new subtypings (and
// descriptors) that are required by the subtypings we have found already. These
// transitively required subtypings (and descriptors) come from three sources.
//
// The first source is type definitions. Consider these type definitions:
//
//   (type $A (sub (struct (ref $X))))
//   (type $B (sub $A (struct (ref $Y))))
//
// If we have determined that $B must remain a subtype of $A, then we know that
// $Y must remain a subtype of $X as well, since the type definitions would not
// be valid otherwise. Similarly, knowing that $X must remain a subtype of $Y
// may transitively require other subtypings as well based on their type
// definitions.
//
// The second source of transitive subtyping requirements is casts. Although
// casting from one type to another does not necessarily require that those
// types are related, we do need to make sure that we do not change the
// behavior of casts by removing subtype relationships they might observe. For
// example, consider this module:
//
// (module
//  ;; original subtyping: $bot <: $mid <: $top
//  (type $top (sub (struct)))
//  (type $mid (sub $top (struct)))
//  (type $bot (sub $mid (struct)))
//
//  (func $f
//   (local $top (ref $top))
//   (local $mid (ref $mid))
//
//   ;; Requires $bot <: $top
//   (local.set $top (struct.new $bot))
//
//   ;; Cast $top to $mid
//   (local.set $mid (ref.cast (ref $mid) (local.get $top)))
//  )
// )
//
// The only subtype relation directly required by the IR for this module is $bot
// <: $top. However, if we optimized the module so that $bot <: $top was the
// only subtype relation, we would change the behavior of the cast. In the
// original module, a value of type (ref $bot) is cast to (ref $mid). The cast
// succeeds because in the original module, $bot <: $mid. If we optimize so that
// we have $bot <: $top and no other subtypings, though, the cast will fail
// because the value of type (ref $bot) no longer inhabits (ref $mid). To
// prevent the cast's behavior from changing, we need to ensure that $bot <:
// $mid.
//
// The set of subtyping requirements generated by a cast from $src to $dest is
// that for every known remaining subtype $v of $src, if $v <: $dest in the
// original module, then $v <: $dest in the optimized module. In other words,
// for every type $v of values we know can flow into the cast, if the cast would
// have succeeded for values of type $v before, then we know the cast must
// continue to succeed for values of type $v. These requirements arising from
// casts can also generate transitive requirements because we learn about new
// types of values that can flow into casts as we learn about new subtypes of
// cast sources.
//
// The third source of transitive subtyping requirements is the discovery of
// required descriptors (and vice versa). Subtyping and descriptors combine to
// form this diagram, where rightward arrows mean "described by":
//
//   A -> A.desc
//   ^    ^
//   |    |
//   B -> B.desc
//
// If any three of these types exist in these relations with the others, then
// the validation rules require that the fourth type also exist and be in these
// relations. The only exception is that A.desc is allowed to be missing. This
// complex and recursive relationship between subtyping and descriptor relations
// is why we optimize out unneeded descriptors in this pass rather than e.g.
// GlobalTypeOptimization.
//
// Starting with the initial subtype and descriptor relations determined by
// walking the IR, repeatedly search for new subtypings and descriptors by
// analyzing type definitions and casts until we reach a fixed point. This is
// the minimal subtype/descriptor relation that preserves module validity and
// behavior that can be found without a more precise analysis of types that
// might flow into each cast.

namespace wasm {

namespace {

#if UNSUBTYPING_DEBUG
template<typename K, typename V> using Map = InsertOrderedMap<K, V>;
template<typename T> using Set = InsertOrderedSet<T>;
#else
template<typename K, typename V> using Map = std::unordered_map<K, V>;
template<typename T> using Set = std::unordered_set<T>;
#endif

// A tree (or rather a forest) of types with the ability to query and set
// supertypes in constant time and efficiently iterate over supertypes and
// subtypes.
struct TypeTree {
  struct Node {
    // The type represented by this node.
    HeapType type;
    // The index of the parent (supertype) in the list of nodes. Set to the
    // index of this node if there is no parent.
    Index parent;
    // The index of this node in the parent's list of children, if any, enabling
    // O(1) updates.
    Index indexInParent = 0;
    // The indices of the children (subtypes) in the list of nodes.
    std::vector<Index> children;
    // The index of the described and descriptor types, if they are necessary.
    std::optional<Index> described;
    std::optional<Index> descriptor;
    // Whether subtypes of this type might flow out to JS from a JS-called
    // function or via extern.convert_any.
    bool subtypesExposedToJS = false;

    Node(HeapType type, Index index) : type(type), parent(index) {}
  };

  std::vector<Node> nodes;
  Map<HeapType, Index> indices;

  void setSupertype(HeapType sub, HeapType super) {
    auto childIndex = getIndex(sub);
    auto parentIndex = getIndex(super);
    auto& childNode = nodes[childIndex];
    auto& parentNode = nodes[parentIndex];
    // Remove sub from its old supertype if necessary.
    if (auto oldParentIndex = childNode.parent; oldParentIndex != childIndex) {
      auto& oldParentNode = nodes[oldParentIndex];
      // Move sub to the back of its parent's children and then pop it.
      auto& children = oldParentNode.children;
      assert(children[childNode.indexInParent] == childIndex);
      auto& swappedNode = nodes[children.back()];
      assert(swappedNode.indexInParent == children.size() - 1);
      // Swap the indices in the parent's child vector.
      std::swap(children[childNode.indexInParent], children.back());
      // Swap the index in the kept child.
      swappedNode.indexInParent = childNode.indexInParent;
      children.pop_back();
    }
    childNode.parent = parentIndex;
    childNode.indexInParent = parentNode.children.size();
    parentNode.children.push_back(childIndex);
  }

  std::optional<HeapType> getSupertype(HeapType type) const {
    auto index = maybeGetIndex(type);
    if (!index) {
      return std::nullopt;
    }
    auto parentIndex = nodes[*index].parent;
    if (parentIndex == *index) {
      return std::nullopt;
    }
    return nodes[parentIndex].type;
  }

  void setDescriptor(HeapType described, HeapType descriptor) {
    auto describedIndex = getIndex(described);
    auto descriptorIndex = getIndex(descriptor);
    auto& describedNode = nodes[describedIndex];
    auto& descriptorNode = nodes[descriptorIndex];
    // We only ever set the descriptor once.
    assert(!describedNode.descriptor);
    assert(!descriptorNode.described);
    describedNode.descriptor = descriptorIndex;
    descriptorNode.described = describedIndex;
  }

  std::optional<HeapType> getDescriptor(HeapType type) const {
    auto index = maybeGetIndex(type);
    if (!index) {
      return std::nullopt;
    }
    if (auto descIndex = nodes[*index].descriptor) {
      return nodes[*descIndex].type;
    }
    return std::nullopt;
  }

  std::optional<HeapType> getDescribed(HeapType type) const {
    auto index = maybeGetIndex(type);
    if (!index) {
      return std::nullopt;
    }
    if (auto descIndex = nodes[*index].described) {
      return nodes[*descIndex].type;
    }
    return std::nullopt;
  }

  void setSubtypesExposedToJS(HeapType type) {
    auto index = getIndex(type);
    nodes[index].subtypesExposedToJS = true;
  }

  bool areSubtypesExposedToJS(HeapType type) const {
    auto index = maybeGetIndex(type);
    if (!index) {
      return false;
    }
    return nodes[*index].subtypesExposedToJS;
  }

  struct SupertypeIterator {
    using value_type = const HeapType;
    using difference_type = std::ptrdiff_t;
    using reference = const HeapType&;
    using pointer = const HeapType*;
    using iterator_category = std::input_iterator_tag;

    TypeTree* parent;
    std::optional<Index> index;

    bool operator==(const SupertypeIterator& other) const {
      return index == other.index;
    }
    bool operator!=(const SupertypeIterator& other) const {
      return !(*this == other);
    }
    const HeapType& operator*() const { return parent->nodes[*index].type; }
    const HeapType* operator->() const { return &*(*this); }
    SupertypeIterator& operator++() {
      auto parentIndex = parent->nodes[*index].parent;
      if (parentIndex == *index) {
        index = std::nullopt;
      } else {
        index = parentIndex;
      }
      return *this;
    }
    SupertypeIterator operator++(int) {
      auto it = *this;
      ++(*this);
      return it;
    }
  };

  struct Supertypes {
    TypeTree* parent;
    Index index;
    SupertypeIterator begin() { return {parent, index}; }
    SupertypeIterator end() { return {parent, std::nullopt}; }
  };

  Supertypes supertypes(HeapType type) { return {this, getIndex(type)}; }

  struct ImmediateSubtypeIterator {
    using value_type = const HeapType;
    using difference_type = std::ptrdiff_t;
    using reference = const HeapType&;
    using pointer = const HeapType*;
    using iterator_category = std::input_iterator_tag;

    TypeTree* parent;
    std::vector<Index>::const_iterator child;

    bool operator==(const ImmediateSubtypeIterator& other) const {
      return child == other.child;
    }
    bool operator!=(const ImmediateSubtypeIterator& other) const {
      return !(*this == other);
    }
    const HeapType& operator*() const { return parent->nodes[*child].type; }
    const HeapType* operator->() const { return &*(*this); }
    ImmediateSubtypeIterator& operator++() {
      ++child;
      return *this;
    }
    ImmediateSubtypeIterator operator++(int) {
      auto it = *this;
      ++(*this);
      return it;
    }
  };

  struct ImmediateSubtypes {
    TypeTree* parent;
    Index index;
    ImmediateSubtypeIterator begin() {
      return {parent, parent->nodes[index].children.begin()};
    }
    ImmediateSubtypeIterator end() {
      return {parent, parent->nodes[index].children.end()};
    }
  };

  ImmediateSubtypes immediateSubtypes(HeapType type) {
    return {this, getIndex(type)};
  }

  struct SubtypeIterator {
    using value_type = const HeapType;
    using difference_type = std::ptrdiff_t;
    using reference = const HeapType&;
    using pointer = const HeapType*;
    using iterator_category = std::input_iterator_tag;

    TypeTree* parent;

    // DFS stack of (node index, child index) pairs.
    std::vector<std::pair<Index, Index>> stack;

    bool operator==(const SubtypeIterator& other) {
      return stack == other.stack;
    }
    bool operator!=(const SubtypeIterator& other) { return !(*this == other); }
    const HeapType& operator*() const {
      return parent->nodes[stack.back().first].type;
    }
    const HeapType* operator->() const { return &*(*this); }
    SubtypeIterator& operator++() {
      while (true) {
        if (stack.empty()) {
          return *this;
        }
        auto& [index, childIndex] = stack.back();
        auto& children = parent->nodes[index].children;
        if (childIndex == children.size()) {
          stack.pop_back();
        } else {
          auto child = children[childIndex++];
          stack.push_back({child, 0u});
          return *this;
        }
      }
    }
    SubtypeIterator operator++(int) {
      auto it = *this;
      ++(*this);
      return it;
    }
  };

  struct Subtypes {
    TypeTree* parent;
    Index index;
    SubtypeIterator begin() { return {parent, {std::make_pair(index, 0u)}}; }
    SubtypeIterator end() { return {parent, {}}; }
  };

  Subtypes subtypes(HeapType type) { return {this, getIndex(type)}; }

#if UNSUBTYPING_DEBUG
  void dump(Module& wasm) {
    for (auto& node : nodes) {
      std::cerr << ModuleHeapType(wasm, node.type);
      if (auto super = getSupertype(node.type)) {
        std::cerr << " <: " << ModuleHeapType(wasm, *super);
      }
      if (auto desc = getDescribed(node.type)) {
        std::cerr << ", describes " << ModuleHeapType(wasm, *desc);
      }
      if (auto desc = getDescriptor(node.type)) {
        std::cerr << ", descriptor " << ModuleHeapType(wasm, *desc);
      }
      std::cerr << ", children:";
      for (auto child : node.children) {
        std::cerr << " " << ModuleHeapType(wasm, nodes[child].type);
      }
      if (node.subtypesExposedToJS) {
        std::cerr << ", subtypes exposed to JS";
      }
      std::cerr << '\n';
    }
  }
#endif

private:
  Index getIndex(HeapType type) {
    auto [it, inserted] = indices.insert({type, nodes.size()});
    if (inserted) {
      nodes.emplace_back(type, nodes.size());
    }
    return it->second;
  }

  std::optional<Index> maybeGetIndex(HeapType type) const {
    if (auto it = indices.find(type); it != indices.end()) {
      return it->second;
    }
    return std::nullopt;
  }
};

// There are two contexts where we have to note subtypings and casts: in the
// initial parallel analysis of the module and in the follow-on fixed point
// analysis over the type tree. Most of the logic is the same in both cases, but
// the final update of data structures is different. This CRTP utility
// deduplicates the shared logic.
template<typename Self> struct Noter {
  DBG(Module* wasm = nullptr);

  Self& self() { return *static_cast<Self*>(this); }

  void noteSubtype(HeapType sub, HeapType super) {
    // Bottom types are uninteresting, but other basic heap types can be
    // interesting because of their interactions with casts.
    if (sub == super || sub.isBottom()) {
      return;
    }
    DBG(std::cerr << "noting " << ModuleHeapType(*wasm, sub)
                  << " <: " << ModuleHeapType(*wasm, super) << '\n');
    self().doNoteSubtype(sub, super);
  }

  void noteSubtype(Type sub, Type super) {
    if (sub.isTuple()) {
      assert(super.isTuple() && sub.size() == super.size());
      for (size_t i = 0, size = sub.size(); i < size; ++i) {
        noteSubtype(sub[i], super[i]);
      }
      return;
    }
    if (!sub.isRef() || !super.isRef()) {
      return;
    }
    noteSubtype(sub.getHeapType(), super.getHeapType());
  }

  void noteSubtype(Type sub, Expression* super) {
    noteSubtype(sub, super->type);
  }

  void noteSubtype(Expression* sub, Type super) {
    noteSubtype(sub->type, super);
  }

  void noteSubtype(Expression* sub, Expression* super) {
    noteSubtype(sub->type, super->type);
  }

  void noteDescriptor(HeapType described, HeapType descriptor) {
    DBG(std::cerr << "noting " << ModuleHeapType(*wasm, described) << " -> "
                  << ModuleHeapType(*wasm, descriptor) << '\n');
    self().doNoteDescriptor(described, descriptor);
  }

  void noteDescribed(HeapType type) {
    auto desc = type.getDescriptorType();
    assert(desc);
    noteDescriptor(type, *desc);
  }

  void noteDescriptor(HeapType type) {
    auto desc = type.getDescribedType();
    assert(desc);
    noteDescriptor(*desc, type);
  }

  void noteCast(HeapType src, Type dstType) {
    auto dst = dstType.getHeapType();
    // Casts to self and casts that must fail because they have incompatible
    // types are uninteresting.
    if (dst == src) {
      return;
    }
    if (HeapType::isSubType(dst, src)) {
      if (dstType.isExact()) {
        // This cast only tests that the exact destination type is a subtype
        // of the source type and does not impose additional requirements on
        // subtypes of the destination type like a normal cast does.
        noteSubtype(dst, src);
        return;
      }
      self().doNoteCast(src, dst);
      return;
    }
    if (HeapType::isSubType(src, dst)) {
      // This is an upcast that will always succeed, but only if we ensure
      // src <: dst.
      noteSubtype(src, dst);
    }
  }

  void noteCast(Expression* src, Type dst) {
    if (src->type.isRef() && dst.isRef()) {
      noteCast(src->type.getHeapType(), dst);
    }
  }

  void noteCast(Expression* src, Expression* dst) {
    if (src->type.isRef() && dst->type.isRef()) {
      noteCast(src->type.getHeapType(), dst->type);
    }
  }
};

struct Unsubtyping : Pass, Noter<Unsubtyping> {
  // The kind of work to process.
  enum class Kind { Subtype, Descriptor };
  // (sub, super) pairs that we have discovered but not yet processed.
  std::vector<std::tuple<Kind, HeapType, HeapType>> work;

  // Record the type tree with supertype and subtype relations in such a way
  // that we can add new supertype relationships in constant time.
  TypeTree types;

  // Map from cast source types to their destinations.
  Map<HeapType, std::vector<HeapType>> casts;

  void run(Module* wasm) override {
    DBG(this->wasm = wasm);
    if (!wasm->features.hasGC()) {
      return;
    }

    // Initialize the subtype relation based on what is immediately required to
    // keep the code and public types valid.
    analyzePublicTypes(*wasm);
    analyzeJSInterface(*wasm);
    analyzeModule(*wasm);

    // Find further subtypings and iterate to a fixed point.
    while (!work.empty()) {
      auto [kind, a, b] = work.back();
      work.pop_back();
      switch (kind) {
        case Kind::Subtype:
          processSubtype(a, b);
          break;
        case Kind::Descriptor:
          processDescriptor(a, b);
          break;
      }
    }

    DBG(types.dump(*wasm));
    // If we removed a descriptor from a type, we may need to update its
    // allocation sites accordingly.
    fixupAllocations(*wasm);

    rewriteTypes(*wasm);

    // Cast types may be refinable if their source and target types are no
    // longer related. TODO: Experiment with running this only after checking
    // whether it is necessary.
    ReFinalize().run(getPassRunner(), wasm);
  }

  void doNoteSubtype(HeapType sub, HeapType super) {
    work.push_back({Kind::Subtype, sub, super});
  }

  void doNoteCast(HeapType src, HeapType dst) { casts[src].push_back(dst); }

  void doNoteDescriptor(HeapType described, HeapType descriptor) {
    work.push_back({Kind::Descriptor, described, descriptor});
  }

  void noteExposedToJS(Type type) {
    if (!type.isRef()) {
      return;
    }
    noteExposedToJS(type.getHeapType(), type.getExactness());
  }

  void noteExposedToJS(HeapType type, Exactness exact = Inexact) {
    // Keep any descriptor that may configure a prototype.
    if (auto desc = type.getDescriptorType();
        desc && JSUtils::hasPossibleJSPrototypeField(*desc)) {
      noteDescriptor(type, *desc);
    }
    if (exact == Inexact) {
      types.setSubtypesExposedToJS(type);
    }
  }

  void analyzePublicTypes(Module& wasm) {
    // We cannot change supertypes for anything public.
    for (auto type :
         ModuleUtils::getPublicHeapTypes(wasm, getPassOptions().worldMode)) {
      if (auto super = type.getDeclaredSuperType()) {
        noteSubtype(type, *super);
      }
      if (auto desc = type.getDescriptorType()) {
        noteDescriptor(type, *desc);
      }
    }
  }

  void analyzeJSInterface(const Module& wasm) {
    if (!wasm.features.hasCustomDescriptors()) {
      return;
    }
    Type anyref(HeapType::any, Nullable);

    // Values flowing in from JS are implicitly cast from any.
    auto flowIn = [&](Type type) {
      if (Type::isSubType(type, anyref)) {
        noteCast(HeapType::any, type);
      }
    };

    // Values flowing out to JS are converted to extern and might come back in
    // as anyrefs. Their descriptors may need to be kept to configure JS
    // prototypes.
    auto flowOut = [&](Type type) {
      if (Type::isSubType(type, anyref)) {
        noteSubtype(type.getHeapType(), HeapType::any);
        noteExposedToJS(type);
      }
    };

    JSUtils::iterJSInterface(wasm, flowIn, flowOut);
  }

  void analyzeModule(Module& wasm) {
    struct Info {
      // (source, target) pairs for casts.
      Set<std::pair<HeapType, HeapType>> casts;

      // Observed (sub, super) subtype constraints.
      Set<std::pair<HeapType, HeapType>> subtypings;

      // Observed (described, descriptor) requirements.
      Set<std::pair<HeapType, HeapType>> descriptors;

      // Observed externalized types.
      Set<Type> exposedToJS;
    };

    struct Collector
      : ControlFlowWalker<Collector, SubtypingDiscoverer<Collector>>,
        Noter<Collector> {
      using Super =
        ControlFlowWalker<Collector, SubtypingDiscoverer<Collector>>;

      Info& info;
      bool trapsNeverHappen;

      Collector(Info& info, bool trapsNeverHappen, Module* wasm)
        : info(info), trapsNeverHappen(trapsNeverHappen) {
        DBG(this->wasm = wasm);
      }

      void doNoteSubtype(HeapType sub, HeapType super) {
        info.subtypings.insert({sub, super});
      }

      void noteNonFlowSubtype(Expression* sub, Type super) {
        // This expression's type must be a subtype of |super|, but the value
        // does not flow anywhere - this is a static constraint. As the value
        // does not flow, it cannot reach anywhere else, which means we need
        // this in order to validate but it does not interact with casts. Given
        // that, if super is a basic type then we can simply ignore this: we
        // only remove subtyping between user types, so subtyping wrt basic
        // types is unchanged, and so this constraint will never be a problem.
        //
        // This is sort of a hack because in general to be precise we should not
        // just consider basic types here - in general, we should note for each
        // constraint whether it is a flow-based one or not, and only take the
        // flow-based ones into account when looking at the impact of casts.
        // However, in practice this is enough as the only non-trivial case of
        // |noteNonFlowSubtype| is for RefEq, which uses a basic type (eqref).
        // Other cases of non-flow subtyping end up trivial, e.g., the target of
        // a CallRef is compared to itself (and we ignore constraints of A :>
        // A). However, if we change how |noteNonFlowSubtype| is used in
        // SubtypingDiscoverer then we may need to generalize this.
        if (super.isRef() && super.getHeapType().isBasic()) {
          return;
        }

        // Otherwise, we must take this into account.
        noteSubtype(sub, super);
      }

      void doNoteCast(HeapType src, HeapType dst) {
        info.casts.insert({src, dst});
      }

      void doNoteDescriptor(HeapType described, HeapType descriptor) {
        info.descriptors.insert({described, descriptor});
      }

      void visitRefGetDesc(RefGetDesc* curr) {
        Super::visitRefGetDesc(curr);
        if (!curr->ref->type.isStruct()) {
          return;
        }
        noteDescribed(curr->ref->type.getHeapType());
      }
      void visitRefCast(RefCast* curr) {
        Super::visitRefCast(curr);
        if (!curr->desc || !curr->desc->type.isStruct()) {
          return;
        }
        noteDescriptor(curr->desc->type.getHeapType());
      }
      void visitBrOn(BrOn* curr) {
        Super::visitBrOn(curr);
        if (!curr->desc || !curr->desc->type.isStruct()) {
          return;
        }
        noteDescriptor(curr->desc->type.getHeapType());
      }
      void visitStructNew(StructNew* curr) {
        Super::visitStructNew(curr);
        if (curr->type == Type::unreachable || !curr->desc) {
          return;
        }
        // Normally we do not treat struct.new as requiring a descriptor, even
        // if it has one. We are happy to optimize out descriptors that are set
        // in allocations and then never used. But if the descriptor is nullable
        // and outside a function context and we assume it may be null and cause
        // a trap, then we have no way to preserve that trap without keeping the
        // descriptor around.
        if (trapsNeverHappen || getFunction() ||
            curr->desc->type.isNonNullable()) {
          return;
        }
        // We must preserve the potential trap. When we update the instructions
        // later we will move this allocation to a new global if necessary to
        // preserve the potential trap even if a parent of the current
        // expression is removed.
        noteDescribed(curr->type.getHeapType());
      }
      void visitRefAs(RefAs* curr) {
        Super::visitRefAs(curr);
        // extern.convert_any makes its operand type visible to JS, which may
        // require us to keep descriptors that configure prototypes.
        if (curr->op == ExternConvertAny && curr->value->type.isRef()) {
          info.exposedToJS.insert(curr->value->type);
        }
      }
    };

    bool trapsNeverHappen = getPassOptions().trapsNeverHappen;

    // Collect subtyping constraints and casts from functions in parallel.
    ModuleUtils::ParallelFunctionAnalysis<Info> analysis(
      wasm, [&](Function* func, Info& info) {
        if (!func->imported()) {
          Collector(info, trapsNeverHappen, &wasm)
            .walkFunctionInModule(func, &wasm);
        }
      });

    Info collectedInfo;
    for (auto& [_, info] : analysis.map) {
      collectedInfo.casts.insert(info.casts.begin(), info.casts.end());
      collectedInfo.subtypings.insert(info.subtypings.begin(),
                                      info.subtypings.end());
      collectedInfo.descriptors.insert(info.descriptors.begin(),
                                       info.descriptors.end());
      collectedInfo.exposedToJS.insert(info.exposedToJS.begin(),
                                       info.exposedToJS.end());
    }

    // Collect constraints from module-level code as well.
    Collector collector(collectedInfo, trapsNeverHappen, &wasm);
    collector.walkModuleCode(&wasm);
    collector.setModule(&wasm);
    for (auto& global : wasm.globals) {
      collector.visitGlobal(global.get());
    }
    for (auto& table : wasm.tables) {
      collector.visitTable(table.get());
    }
    for (auto& segment : wasm.elementSegments) {
      collector.visitElementSegment(segment.get());
    }

    // Prepare the collected information for the upcoming processing loop.
    for (auto type : collectedInfo.exposedToJS) {
      noteExposedToJS(type);
    }
    for (auto& [sub, super] : collectedInfo.subtypings) {
      noteSubtype(sub, super);
    }
    // Combine casts we have already noted into the newly gathered casts.
    for (auto& [src, dsts] : casts) {
      for (auto dst : dsts) {
        collectedInfo.casts.insert({src, dst});
      }
      dsts.clear();
    }
    // Record the deduplicated cast info.
    for (auto [src, dst] : collectedInfo.casts) {
      casts[src].push_back(dst);
    }
    for (auto [described, descriptor] : collectedInfo.descriptors) {
      noteDescriptor(described, descriptor);
    }
  }

  void processSubtype(HeapType sub, HeapType super) {
    DBG(std::cerr << "processing " << ModuleHeapType(*wasm, sub)
                  << " <: " << ModuleHeapType(*wasm, super) << '\n');
    assert(HeapType::isSubType(sub, super));
    auto oldSuper = types.getSupertype(sub);
    if (oldSuper) {
      // We already had a recorded supertype. The new supertype might be
      // deeper,shallower, or equal to the old supertype. We must recursively
      // note the relationship between the old and new supertypes.
      if (super == *oldSuper) {
        // Nothing new to do here.
        return;
      }
      if (HeapType::isSubType(*oldSuper, super)) {
        // sub <: oldSuper <: super
        noteSubtype(*oldSuper, super);
        // We already handled sub <: oldSuper, so we're done.
        return;
      }
      // sub <: super <: oldSuper
      // Eagerly process super <: oldSuper first. This ensures that sub and
      // super will already be in the same tree when we process them below, so
      // when we process casts we will know that we only need to process up to
      // oldSuper.
      processSubtype(super, *oldSuper);
    }

    types.setSupertype(sub, super);

    // If the supertype is exposed to JS, the subtype potentially is as well.
    // `sub` may be the root of some existing subtype tree, and we have to
    // propagate the exposure to JS to all those existing subtypes. We could
    // just iterate over subtypes(), but manually traverse using
    // immediateSubtypes() so we can avoid visiting subtrees that have already
    // been marked.
    if (types.areSubtypesExposedToJS(super)) {
      std::vector<HeapType> work{{sub}};
      while (!work.empty()) {
        auto curr = work.back();
        work.pop_back();
        if (!types.areSubtypesExposedToJS(curr)) {
          noteExposedToJS(curr);
          auto subtypes = types.immediateSubtypes(curr);
          work.insert(work.end(), subtypes.begin(), subtypes.end());
        }
      }
    }

    // Complete the descriptor squares to the left and right of the new
    // subtyping edge if those squares can possibly exist based on the original
    // types.
    if (super.getDescribedType()) {
      completeDescriptorSquare(
        types.getDescribed(super), super, types.getDescribed(sub), sub);
    }
    if (super.getDescriptorType()) {
      completeDescriptorSquare(
        super, types.getDescriptor(super), sub, types.getDescriptor(sub));
    }

    // Find the implied subtypings from the type definitions and casts.
    processDefinitions(sub, super);
    processCasts(sub, super, oldSuper);
  }

  void processDescriptor(HeapType described, HeapType descriptor) {
    DBG(std::cerr << "processing " << ModuleHeapType(*wasm, described) << " -> "
                  << ModuleHeapType(*wasm, descriptor) << '\n');
    assert(described.getDescriptorType() &&
           *described.getDescriptorType() == descriptor);
    if (auto oldDesc = types.getDescriptor(described)) {
      // We already know about this descriptor.
      assert(*oldDesc == descriptor);
      return;
    }

    types.setDescriptor(described, descriptor);

    // Complete the descriptor squares above and below the new descriptor edge.
    completeDescriptorSquare(
      std::nullopt, types.getSupertype(descriptor), described, descriptor);
    for (auto sub : types.immediateSubtypes(described)) {
      completeDescriptorSquare(
        described, descriptor, sub, types.getDescriptor(sub));
    }
    for (auto subDesc : types.immediateSubtypes(descriptor)) {
      completeDescriptorSquare(
        described, descriptor, types.getDescribed(subDesc), subDesc);
    }
  }

  void processDefinitions(HeapType sub, HeapType super) {
    if (super.isBasic()) {
      return;
    }
    switch (sub.getKind()) {
      case HeapTypeKind::Func: {
        auto sig = sub.getSignature();
        auto superSig = super.getSignature();
        noteSubtype(superSig.params, sig.params);
        noteSubtype(sig.results, superSig.results);
        break;
      }
      case HeapTypeKind::Struct: {
        const auto& fields = sub.getStruct().fields;
        const auto& superFields = super.getStruct().fields;
        for (size_t i = 0, size = superFields.size(); i < size; ++i) {
          noteSubtype(fields[i].type, superFields[i].type);
        }
        break;
      }
      case HeapTypeKind::Array: {
        auto elem = sub.getArray().element;
        noteSubtype(elem.type, super.getArray().element.type);
        break;
      }
      case HeapTypeKind::Cont: {
        noteSubtype(sub.getContinuation().type, super.getContinuation().type);
        break;
      }
      case HeapTypeKind::Basic:
        WASM_UNREACHABLE("unexpected kind");
    }
  }

  void
  processCasts(HeapType sub, HeapType super, std::optional<HeapType> oldSuper) {
    // We are either attaching the one tree rooted at `sub` under a new
    // supertype in another tree, or we are reparenting `sub` below a
    // descendent of `oldSuper` in the same tree. In the former case, we must
    // evaluate `sub` and all its subtypes against all its new supertypes and
    // their cast destinations. In the latter case, `sub` and all its subtypes
    // must have already been evaluated against `oldSuper` and its supertypes,
    // so we only need to additionally evaluate them against supertypes up to
    // `oldSuper`.
    for (auto type : types.subtypes(sub)) {
      for (auto src : types.supertypes(super)) {
        if (oldSuper && src == *oldSuper) {
          break;
        }
        for (auto dst : casts[src]) {
          if (HeapType::isSubType(type, dst)) {
            noteSubtype(type, dst);
          }
        }
      }
    }
  }

  void completeDescriptorSquare(std::optional<HeapType> super,
                                std::optional<HeapType> superDesc,
                                std::optional<HeapType> sub,
                                std::optional<HeapType> subDesc) {
    if ((super && super->isBasic()) || (superDesc && superDesc->isBasic())) {
      // Basic types do not have descriptors or described types, so do not form
      // descriptor squares.
      return;
    }
    if (bool(super) + bool(superDesc) + bool(sub) + bool(subDesc) < 3) {
      // We must have two adjacent edges (involving at least 3 types) for there
      // to be any further requirements.
      return;
    }
    // There may be up to one missing type. Look it up using its original
    // descriptor relation with the present types and add the missing edges.
    if (!super) {
      super = superDesc->getDescribedType();
    } else if (!sub) {
      sub = subDesc->getDescribedType();
    } else if (!subDesc) {
      subDesc = sub->getDescriptorType();
    } else if (!superDesc) {
      // This is the only type that is allowed to be missing.
      return;
    }
    // Add all the edges. Don't worry about duplicating existing edges because
    // checking whether they're necessary now would be about as expensive as
    // discarding them later.
    noteSubtype(*sub, *super);
    noteSubtype(*subDesc, *superDesc);
    noteDescriptor(*super, *superDesc);
    noteDescriptor(*sub, *subDesc);
  }

  void rewriteTypes(Module& wasm) {
    struct Rewriter : GlobalTypeRewriter {
      Unsubtyping& parent;
      Rewriter(Unsubtyping& parent, Module& wasm)
        : GlobalTypeRewriter(wasm, parent.getPassOptions().worldMode),
          parent(parent) {}
      std::optional<HeapType> getDeclaredSuperType(HeapType type) override {
        if (auto super = parent.types.getSupertype(type);
            super && !super->isBasic()) {
          return *super;
        }
        return std::nullopt;
      }
      void modifyTypeBuilderEntry(TypeBuilder& typeBuilder,
                                  Index i,
                                  HeapType oldType) override {
        if (!parent.types.getDescribed(oldType)) {
          typeBuilder[i].describes(std::nullopt);
        }
        if (!parent.types.getDescriptor(oldType)) {
          typeBuilder[i].descriptor(std::nullopt);
        }
      }
    };
    Rewriter(*this, wasm).update();
  }

  void fixupAllocations(Module& wasm) {
    if (!wasm.features.hasCustomDescriptors()) {
      return;
    }
    // TODO: Consider running the fixup only if we are actually removing any
    // descriptors. This would require a better way of detecting this than
    // collecting and iterating over all the types, though.
    struct Rewriter : WalkerPass<PostWalker<Rewriter>> {
      const TypeTree& types;

      // Allocations that might trap that have been removed from module-level
      // initializers. These need to be placed in new globals to preserve any
      // instantiation-time traps.
      std::vector<Expression*> removedTrappingInits;

      Rewriter(const TypeTree& types) : types(types) {}

      bool isFunctionParallel() override { return true; }
      // Only introduces locals that are set immediately before they are used.
      bool requiresNonNullableLocalFixups() override { return false; }
      std::unique_ptr<Pass> create() override {
        return std::make_unique<Rewriter>(types);
      }

      void visitStructNew(StructNew* curr) {
        if (curr->type == Type::unreachable) {
          return;
        }
        if (!curr->desc) {
          return;
        }
        if (types.getDescriptor(curr->type.getHeapType())) {
          return;
        }
        // We need to drop the descriptor argument. In a function context, use
        // ChildLocalizer. Outside a function context just drop the operand
        // because there can be no side effects anyway.
        if (auto* func = getFunction()) {
          // Preserve a trap from a null descriptor if necessary.
          if (!getPassOptions().trapsNeverHappen &&
              curr->desc->type.isNullable()) {
            curr->desc =
              Builder(*getModule()).makeRefAs(RefAsNonNull, curr->desc);
          }
          auto* block =
            ChildLocalizer(curr, func, *getModule(), getPassOptions())
              .getChildrenReplacement();
          block->list.push_back(curr);
          block->type = curr->type;
          replaceCurrent(block);
        } else {
          // We are dropping this descriptor, but it might have a potential trap
          // nested inside it. In that case we need to preserve the trap by
          // moving this descriptor to a new global.
          if (curr->desc->is<StructNew>() &&
              EffectAnalyzer(getPassOptions(), *getModule(), curr->desc).trap) {
            removedTrappingInits.push_back(curr->desc);
          }
        }
        curr->desc = nullptr;
      }
    };

    Rewriter rewriter(types);
    rewriter.run(getPassRunner(), &wasm);
    rewriter.runOnModuleCode(getPassRunner(), &wasm);

    // Insert globals necessary to preserve instantiation-time trapping of
    // removed allocations.
    for (Index i = 0; i < rewriter.removedTrappingInits.size(); ++i) {
      auto* curr = rewriter.removedTrappingInits[i];
      auto name = Names::getValidGlobalName(
        wasm, std::string("unsubtyping-removed-") + std::to_string(i));
      wasm.addGlobal(
        Builder::makeGlobal(name, curr->type, curr, Builder::Immutable));
    }
  }
};

} // anonymous namespace

Pass* createUnsubtypingPass() { return new Unsubtyping(); }

} // namespace wasm
