// Copyright 2023 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef V8_COMPILER_TURBOSHAFT_TYPER_H_
#define V8_COMPILER_TURBOSHAFT_TYPER_H_

#include <limits>

#include "src/base/logging.h"
#include "src/base/vector.h"
#include "src/compiler/turboshaft/operations.h"
#include "src/compiler/turboshaft/representations.h"
#include "src/compiler/turboshaft/types.h"

namespace v8::internal::compiler::turboshaft {

// Returns the array's least element, ignoring NaN.
// There must be at least one non-NaN element.
// Any -0 is converted to 0.
template <typename T, size_t N>
T array_min(const std::array<T, N>& a) {
  DCHECK_NE(0, N);
  T x = +std::numeric_limits<T>::infinity();
  for (size_t i = 0; i < N; ++i) {
    if (!std::isnan(a[i])) {
      x = std::min(a[i], x);
    }
  }
  DCHECK(!std::isnan(x));
  return x == T{0} ? T{0} : x;  // -0 -> 0
}

// Returns the array's greatest element, ignoring NaN.
// There must be at least one non-NaN element.
// Any -0 is converted to 0.
template <typename T, size_t N>
T array_max(const std::array<T, N>& a) {
  DCHECK_NE(0, N);
  T x = -std::numeric_limits<T>::infinity();
  for (size_t i = 0; i < N; ++i) {
    if (!std::isnan(a[i])) {
      x = std::max(a[i], x);
    }
  }
  DCHECK(!std::isnan(x));
  return x == T{0} ? T{0} : x;  // -0 -> 0
}

template <size_t Bits>
struct WordOperationTyper {
  static_assert(Bits == 32 || Bits == 64);
  using word_t = uint_type<Bits>;
  using type_t = WordType<Bits>;
  using ElementsVector = base::SmallVector<word_t, type_t::kMaxSetSize * 2>;
  static constexpr word_t max = std::numeric_limits<word_t>::max();

  static type_t FromElements(ElementsVector elements, Zone* zone) {
    base::sort(elements);
    auto it = std::unique(elements.begin(), elements.end());
    elements.pop_back(std::distance(it, elements.end()));
    DCHECK(!elements.empty());
    if (elements.size() <= type_t::kMaxSetSize) {
      return type_t::Set(elements, zone);
    }

    auto range =
        MakeRange(base::Vector<const word_t>{elements.data(), elements.size()});
    auto result = type_t::Range(range.first, range.second, zone);
    DCHECK(
        base::all_of(elements, [&](word_t e) { return result.Contains(e); }));
    return result;
  }

  static std::pair<word_t, word_t> MakeRange(const type_t& t) {
    if (t.is_range()) return t.range();
    DCHECK(t.is_set());
    return MakeRange(t.set_elements());
  }

  // This function tries to find a somewhat reasonable range for a given set of
  // values. If the elements span no more than half of the range, we just
  // construct the range from min(elements) to max(elements) Otherwise, we
  // consider a wrapping range because it is likely that there is a larger gap
  // in the middle of the elements. For that, we start with a wrapping range
  // from max(elements) to min(elements) and then incrementally add another
  // element either by increasing the 'to' or decreasing the 'from' of the
  // range, whichever leads to a smaller range.
  static std::pair<word_t, word_t> MakeRange(
      const base::Vector<const word_t>& elements) {
    DCHECK(!elements.empty());
    DCHECK(detail::is_unique_and_sorted(elements));
    if (elements[elements.size() - 1] - elements[0] <= max / 2) {
      // Construct a non-wrapping range.
      return {elements[0], elements[elements.size() - 1]};
    }
    // Construct a wrapping range.
    size_t from_index = elements.size() - 1;
    size_t to_index = 0;
    while (to_index + 1 < from_index) {
      if ((elements[to_index + 1] - elements[to_index]) <
          (elements[from_index] - elements[from_index - 1])) {
        ++to_index;
      } else {
        --from_index;
      }
    }
    return {elements[from_index], elements[to_index]};
  }

  static word_t distance(const std::pair<word_t, word_t>& range) {
    return distance(range.first, range.second);
  }
  static word_t distance(word_t from, word_t to) {
    return is_wrapping(from, to) ? (max - from + to) : to - from;
  }

  static bool is_wrapping(const std::pair<word_t, word_t>& range) {
    return is_wrapping(range.first, range.second);
  }
  static bool is_wrapping(word_t from, word_t to) { return from > to; }

  static type_t Add(const type_t& lhs, const type_t& rhs, Zone* zone) {
    if (lhs.is_any() || rhs.is_any()) return type_t::Any();

    // If both sides are decently small sets, we produce the product set (which
    // we convert to a range if it exceeds the set limit).
    if (lhs.is_set() && rhs.is_set()) {
      ElementsVector result_elements;
      for (int i = 0; i < lhs.set_size(); ++i) {
        for (int j = 0; j < rhs.set_size(); ++j) {
          result_elements.push_back(lhs.set_element(i) + rhs.set_element(j));
        }
      }
      return FromElements(std::move(result_elements), zone);
    }

    // Otherwise just construct a range.
    std::pair<word_t, word_t> x = MakeRange(lhs);
    std::pair<word_t, word_t> y = MakeRange(rhs);

    // If the result would not be a complete range, we compute it.
    // Check: (lhs.to + rhs.to + 1) - (lhs.from + rhs.from + 1) < max
    // =====> (lhs.to - lhs.from) + (rhs.to - rhs.from) < max
    // =====> (lhs.to - lhs.from) < max - (rhs.to - rhs.from)
    if (distance(x) < max - distance(y)) {
      return type_t::Range(x.first + y.first, x.second + y.second, zone);
    }

    return type_t::Any();
  }

  static type_t Subtract(const type_t& lhs, const type_t& rhs, Zone* zone) {
    if (lhs.is_any() || rhs.is_any()) return type_t::Any();

    // If both sides are decently small sets, we produce the product set (which
    // we convert to a range if it exceeds the set limit).
    if (lhs.is_set() && rhs.is_set()) {
      ElementsVector result_elements;
      for (int i = 0; i < lhs.set_size(); ++i) {
        for (int j = 0; j < rhs.set_size(); ++j) {
          result_elements.push_back(lhs.set_element(i) - rhs.set_element(j));
        }
      }
      return FromElements(std::move(result_elements), zone);
    }

    // Otherwise just construct a range.
    std::pair<word_t, word_t> x = MakeRange(lhs);
    std::pair<word_t, word_t> y = MakeRange(rhs);

    if (is_wrapping(x) && is_wrapping(y)) {
      return type_t::Range(x.first - y.second, x.second - y.first, zone);
    }

    // TODO(nicohartmann@): Improve the wrapping cases.
    return type_t::Any();
  }

  static Word32Type UnsignedLessThan(const type_t& lhs, const type_t& rhs,
                                     Zone* zone) {
    bool can_be_true = lhs.unsigned_min() < rhs.unsigned_max();
    bool can_be_false = lhs.unsigned_max() >= rhs.unsigned_min();

    if (!can_be_true) return Word32Type::Constant(0);
    if (!can_be_false) return Word32Type::Constant(1);
    return Word32Type::Set({0, 1}, zone);
  }

  static Word32Type UnsignedLessThanOrEqual(const type_t& lhs,
                                            const type_t& rhs, Zone* zone) {
    bool can_be_true = lhs.unsigned_min() <= rhs.unsigned_max();
    bool can_be_false = lhs.unsigned_max() > rhs.unsigned_min();

    if (!can_be_true) return Word32Type::Constant(0);
    if (!can_be_false) return Word32Type::Constant(1);
    return Word32Type::Set({0, 1}, zone);
  }

  // Computes the ranges to which the sides of the unsigned comparison (lhs <
  // rhs) can be restricted when the comparison is true. When the comparison is
  // true, we learn: lhs cannot be >= rhs.max and rhs cannot be <= lhs.min.
  static std::pair<Type, Type> RestrictionForUnsignedLessThan_True(
      const type_t& lhs, const type_t& rhs, Zone* zone) {
    Type restrict_lhs;
    if (rhs.unsigned_max() == 0) {
      // There is no value for lhs that could make (lhs < 0) true.
      restrict_lhs = Type::None();
    } else {
      restrict_lhs = type_t::Range(0, next_smaller(rhs.unsigned_max()), zone);
    }

    Type restrict_rhs;
    if (lhs.unsigned_min() == max) {
      // There is no value for rhs that could make (max < rhs) true.
      restrict_rhs = Type::None();
    } else {
      restrict_rhs = type_t::Range(next_larger(lhs.unsigned_min()), max, zone);
    }

    return {restrict_lhs, restrict_rhs};
  }

  // Computes the ranges to which the sides of the unsigned comparison (lhs <
  // rhs) can be restricted when the comparison is false. When the comparison is
  // false, we learn: lhs cannot be < rhs.min and rhs cannot be > lhs.max.
  static std::pair<Type, Type> RestrictionForUnsignedLessThan_False(
      const type_t& lhs, const type_t& rhs, Zone* zone) {
    return {type_t::Range(rhs.unsigned_min(), max, zone),
            type_t::Range(0, lhs.unsigned_max(), zone)};
  }

  // Computes the ranges to which the sides of the unsigned comparison (lhs <=
  // rhs) can be restricted when the comparison is true. When the comparison is
  // true, we learn: lhs cannot be > rhs.max and rhs cannot be < lhs.min.
  static std::pair<Type, Type> RestrictionForUnsignedLessThanOrEqual_True(
      const type_t& lhs, const type_t& rhs, Zone* zone) {
    return {type_t::Range(0, rhs.unsigned_max(), zone),
            type_t::Range(lhs.unsigned_min(), max, zone)};
  }

  // Computes the ranges to which the sides of the unsigned comparison (lhs <=
  // rhs) can be restricted when the comparison is false. When the comparison is
  // false, we learn: lhs cannot be <= rhs.min and rhs cannot be >= lhs.max.
  static std::pair<Type, Type> RestrictionForUnsignedLessThanOrEqual_False(
      const type_t& lhs, const type_t& rhs, Zone* zone) {
    Type restrict_lhs;
    if (rhs.unsigned_min() == max) {
      // There is no value for lhs that could make (lhs <= max) false.
      restrict_lhs = Type::None();
    } else {
      restrict_lhs = type_t::Range(next_larger(rhs.unsigned_min()), max, zone);
    }

    Type restrict_rhs;
    if (lhs.unsigned_max() == 0) {
      // There is no value for rhs that could make (0 <= rhs) false.
      restrict_rhs = Type::None();
    } else {
      restrict_rhs = type_t::Range(0, next_smaller(lhs.unsigned_max()), zone);
    }

    return {restrict_lhs, restrict_rhs};
  }

  // WidenMaximal widens one of the boundary to the extreme immediately.
  static type_t WidenMaximal(const type_t& old_type, const type_t& new_type,
                             Zone* zone) {
    if (new_type.is_any()) return new_type;

    if (old_type.is_wrapping()) {
      DCHECK(new_type.is_wrapping());
      return type_t::Any();
    } else if (new_type.is_wrapping()) {
      return type_t::Any();
    } else {
      word_t result_from = new_type.unsigned_min();
      if (result_from < old_type.unsigned_min()) result_from = 0;
      word_t result_to = new_type.unsigned_max();
      if (result_to > old_type.unsigned_max()) {
        result_to = std::numeric_limits<word_t>::max();
      }
      return type_t::Range(result_from, result_to, zone);
    }
  }

  // Performs exponential widening, which means that the number of values
  // described by the resulting type is at least doubled with respect to the
  // {old_type}. If {new_type} is already twice the size of {old_type},
  // {new_type} may be returned directly.
  static type_t WidenExponential(const type_t& old_type, type_t new_type,
                                 Zone* zone) {
    if (new_type.is_any()) return new_type;
    word_t old_from, old_to, new_from, new_to;
    if (old_type.is_set()) {
      const word_t old_size = old_type.set_size();
      if (new_type.is_set()) {
        const word_t new_size = new_type.set_size();
        if (new_size >= 2 * old_size) return new_type;
        std::tie(new_from, new_to) = MakeRange(new_type);
      } else {
        DCHECK(new_type.is_range());
        std::tie(new_from, new_to) = new_type.range();
      }
      if (distance(new_from, new_to) >= 2 * old_size) {
        return type_t::Range(new_from, new_to, zone);
      }
      std::tie(old_from, old_to) = MakeRange(old_type);
    } else {
      DCHECK(old_type.is_range());
      std::tie(old_from, old_to) = old_type.range();
      if (new_type.is_set()) {
        std::tie(new_from, new_to) = MakeRange(new_type);
      } else {
        DCHECK(new_type.is_range());
        std::tie(new_from, new_to) = new_type.range();
      }
    }

    // If the old type is already quite large, we go to full range.
    if (distance(old_from, old_to) >= std::numeric_limits<word_t>::max() / 4) {
      return type_t::Any();
    }

    const word_t min_size = 2 * (distance(old_from, old_to) + 1);
    if (distance(new_from, new_to) >= min_size) {
      return type_t::Range(new_from, new_to, zone);
    }

    // If old is wrapping (and so is new).
    if (is_wrapping(old_from, old_to)) {
      DCHECK(is_wrapping(new_from, new_to));
      if (new_from < old_from) {
        DCHECK_LE(old_to, new_to);
        // We widen the `from` (although `to` might have grown, too).
        DCHECK_LT(new_to, min_size);
        word_t result_from =
            std::numeric_limits<word_t>::max() - (min_size - new_to);
        DCHECK_LT(result_from, new_from);
        DCHECK_LE(min_size, distance(result_from, new_to));
        return type_t::Range(result_from, new_to, zone);
      } else {
        DCHECK_EQ(old_from, new_from);
        // We widen the `to`.
        DCHECK_LT(std::numeric_limits<word_t>::max() - new_from, min_size);
        word_t result_to =
            min_size - (std::numeric_limits<word_t>::max() - new_from);
        DCHECK_GT(result_to, new_to);
        DCHECK_LE(min_size, distance(new_from, result_to));
        return type_t::Range(new_from, result_to, zone);
      }
    }

    // If old is not wrapping, but new is.
    if (is_wrapping(new_from, new_to)) {
      if (new_to < old_to) {
        // If wrapping was caused by to growing over max, grow `to` further
        // (although `from` might have grown, too).
        DCHECK_LT(std::numeric_limits<word_t>::max() - new_from, min_size);
        word_t result_to =
            min_size - (std::numeric_limits<word_t>::max() - new_from);
        DCHECK_LT(new_to, result_to);
        return type_t::Range(new_from, result_to, zone);
      } else {
        DCHECK_LT(old_from, new_from);
        // If wrapping was caused by `from` growing below 0, grow `from`
        // further.
        DCHECK_LT(new_to, min_size);
        word_t result_from =
            std::numeric_limits<word_t>::max() - (min_size - new_to);
        DCHECK_LT(result_from, new_from);
        return type_t::Range(result_from, new_to, zone);
      }
    }

    // Neither old nor new is wrapping.
    if (new_from < old_from) {
      DCHECK_LE(old_to, new_to);
      // Check if we can widen the `from`.
      if (new_to >= min_size) {
        // We can decrease `from` without going below 0.
        word_t result_from = new_to - min_size;
        DCHECK_LT(result_from, new_from);
        return type_t::Range(result_from, new_to, zone);
      } else {
        // We cannot grow `from` enough, so we also have to grow `to`.
        return type_t::Range(0, min_size, zone);
      }
    } else {
      DCHECK_EQ(old_from, new_from);
      // Check if we can widen the `to`.
      if (new_from <= std::numeric_limits<word_t>::max() - min_size) {
        // We can increase `to` without going above max.
        word_t result_to = new_from + min_size;
        DCHECK_GT(result_to, new_to);
        return type_t::Range(new_from, result_to, zone);
      } else {
        // We cannot grow `to` enough, so we also have to grow `from`.
        return type_t::Range(std::numeric_limits<word_t>::max() - min_size,
                             std::numeric_limits<word_t>::max(), zone);
      }
    }
  }
};

template <size_t Bits>
struct FloatOperationTyper {
  static_assert(Bits == 32 || Bits == 64);
  using float_t = std::conditional_t<Bits == 32, float, double>;
  using type_t = FloatType<Bits>;
  static constexpr float_t inf = std::numeric_limits<float_t>::infinity();
  static constexpr int kSetThreshold = type_t::kMaxSetSize;

  static type_t Range(float_t min, float_t max, uint32_t special_values,
                      Zone* zone) {
    DCHECK_LE(min, max);
    if (min == max) return Set({min}, special_values, zone);
    return type_t::Range(min, max, special_values, zone);
  }

  static type_t Set(std::vector<float_t> elements, uint32_t special_values,
                    Zone* zone) {
    base::sort(elements);
    elements.erase(std::unique(elements.begin(), elements.end()),
                   elements.end());
    if (base::erase_if(elements, [](float_t v) { return std::isnan(v); }) > 0) {
      special_values |= type_t::kNaN;
    }
    if (base::erase_if(elements, [](float_t v) { return IsMinusZero(v); }) >
        0) {
      special_values |= type_t::kMinusZero;
    }
    return type_t::Set(elements, special_values, zone);
  }

  // Check if the elements in the set are all integers. This ignores special
  // values (NaN, -0)!
  static bool IsIntegerSet(const type_t& t) {
    if (!t.is_set()) return false;
    int size = t.set_size();
    DCHECK_LT(0, size);

    float_t unused_ipart;
    float_t min = t.set_element(0);
    if (std::modf(min, &unused_ipart) != 0.0) return false;
    if (min == -inf) return false;
    float_t max = t.set_element(size - 1);
    if (std::modf(max, &unused_ipart) != 0.0) return false;
    if (max == inf) return false;

    for (int i = 1; i < size - 1; ++i) {
      if (std::modf(t.set_element(i), &unused_ipart) != 0.0) return false;
    }
    return true;
  }

  static bool IsZeroish(const type_t& l) {
    return l.has_nan() || l.has_minus_zero() || l.Contains(0);
  }

  // Tries to construct the product of two sets where values are generated using
  // {combine}. Returns Type::Invalid() if a set cannot be constructed (e.g.
  // because the result exceeds the maximal number of set elements).
  static Type ProductSet(const type_t& l, const type_t& r,
                         uint32_t special_values, Zone* zone,
                         std::function<float_t(float_t, float_t)> combine) {
    DCHECK(l.is_set());
    DCHECK(r.is_set());

    std::vector<float_t> results;
    auto CombineWithLeft = [&](float_t left) {
      for (int j = 0; j < r.set_size(); ++j) {
        results.push_back(combine(left, r.set_element(j)));
      }
      if (r.has_minus_zero()) results.push_back(combine(left, -0.0));
      if (r.has_nan()) results.push_back(combine(left, nan_v<Bits>));
    };

    for (int i = 0; i < l.set_size(); ++i) {
      CombineWithLeft(l.set_element(i));
    }
    if (l.has_minus_zero()) CombineWithLeft(-0.0);
    if (l.has_nan()) CombineWithLeft(nan_v<Bits>);

    if (base::erase_if(results, [](float_t v) { return std::isnan(v); }) > 0) {
      special_values |= type_t::kNaN;
    }
    if (base::erase_if(results, [](float_t v) { return IsMinusZero(v); }) > 0) {
      special_values |= type_t::kMinusZero;
    }
    base::sort(results);
    auto it = std::unique(results.begin(), results.end());
    if (std::distance(results.begin(), it) > kSetThreshold)
      return Type::Invalid();
    results.erase(it, results.end());
    if (results.empty()) return type_t::OnlySpecialValues(special_values);
    return Set(std::move(results), special_values, zone);
  }

  static Type Add(type_t l, type_t r, Zone* zone) {
    // Addition can return NaN if either input can be NaN or we try to compute
    // the sum of two infinities of opposite sign.
    if (l.is_only_nan() || r.is_only_nan()) return type_t::NaN();
    bool maybe_nan = l.has_nan() || r.has_nan();

    // Addition can yield minus zero only if both inputs can be minus zero.
    bool maybe_minuszero = true;
    if (l.has_minus_zero()) {
      l = type_t::LeastUpperBound(l, type_t::Constant(0), zone);
    } else {
      maybe_minuszero = false;
    }
    if (r.has_minus_zero()) {
      r = type_t::LeastUpperBound(r, type_t::Constant(0), zone);
    } else {
      maybe_minuszero = false;
    }

    uint32_t special_values = (maybe_nan ? type_t::kNaN : 0) |
                              (maybe_minuszero ? type_t::kMinusZero : 0);
    // If both sides are decently small sets, we produce the product set.
    auto combine = [](float_t a, float_t b) { return a + b; };
    if (l.is_set() && r.is_set()) {
      auto result = ProductSet(l, r, special_values, zone, combine);
      if (!result.IsInvalid()) return result;
    }

    // Otherwise just construct a range.
    auto [l_min, l_max] = l.minmax();
    auto [r_min, r_max] = r.minmax();

    std::array<float_t, 4> results;
    results[0] = l_min + r_min;
    results[1] = l_min + r_max;
    results[2] = l_max + r_min;
    results[3] = l_max + r_max;

    int nans = 0;
    for (int i = 0; i < 4; ++i) {
      if (std::isnan(results[i])) ++nans;
    }
    if (nans > 0) {
      special_values |= type_t::kNaN;
      if (nans >= 4) {
        // All combinations of inputs produce NaN.
        return type_t::OnlySpecialValues(special_values);
      }
    }
    const float_t result_min = array_min(results);
    const float_t result_max = array_max(results);
    return Range(result_min, result_max, special_values, zone);
  }

  static Type Subtract(type_t l, type_t r, Zone* zone) {
    // Subtraction can return NaN if either input can be NaN or we try to
    // compute the sum of two infinities of opposite sign.
    if (l.is_only_nan() || r.is_only_nan()) return type_t::NaN();
    bool maybe_nan = l.has_nan() || r.has_nan();

    // Subtraction can yield minus zero if {lhs} can be minus zero and {rhs}
    // can be zero.
    bool maybe_minuszero = false;
    if (l.has_minus_zero()) {
      l = type_t::LeastUpperBound(l, type_t::Constant(0), zone);
      maybe_minuszero = r.Contains(0);
    }
    if (r.has_minus_zero()) {
      r = type_t::LeastUpperBound(r, type_t::Constant(0), zone);
    }

    uint32_t special_values = (maybe_nan ? type_t::kNaN : 0) |
                              (maybe_minuszero ? type_t::kMinusZero : 0);
    // If both sides are decently small sets, we produce the product set.
    auto combine = [](float_t a, float_t b) { return a - b; };
    if (l.is_set() && r.is_set()) {
      auto result = ProductSet(l, r, special_values, zone, combine);
      if (!result.IsInvalid()) return result;
    }

    // Otherwise just construct a range.
    auto [l_min, l_max] = l.minmax();
    auto [r_min, r_max] = r.minmax();

    std::array<float_t, 4> results;
    results[0] = l_min - r_min;
    results[1] = l_min - r_max;
    results[2] = l_max - r_min;
    results[3] = l_max - r_max;

    int nans = 0;
    for (int i = 0; i < 4; ++i) {
      if (std::isnan(results[i])) ++nans;
    }
    if (nans > 0) {
      special_values |= type_t::kNaN;
      if (nans >= 4) {
        // All combinations of inputs produce NaN.
        return type_t::NaN();
      }
    }
    const float_t result_min = array_min(results);
    const float_t result_max = array_max(results);
    return Range(result_min, result_max, special_values, zone);
  }

  static Type Multiply(type_t l, type_t r, Zone* zone) {
    // Multiplication propagates NaN:
    //   NaN * x = NaN         (regardless of sign of x)
    //   0 * Infinity = NaN    (regardless of signs)
    if (l.is_only_nan() || r.is_only_nan()) return type_t::NaN();
    bool maybe_nan = l.has_nan() || r.has_nan() ||
                     (IsZeroish(l) && (r.min() == -inf || r.max() == inf)) ||
                     (IsZeroish(r) && (l.min() == -inf || r.max() == inf));

    // Try to rule out -0.
    bool maybe_minuszero = l.has_minus_zero() || r.has_minus_zero() ||
                           (IsZeroish(l) && r.min() < 0.0) ||
                           (IsZeroish(r) && l.min() < 0.0);
    if (l.has_minus_zero()) {
      l = type_t::LeastUpperBound(l, type_t::Constant(0), zone);
    }
    if (r.has_minus_zero()) {
      r = type_t::LeastUpperBound(r, type_t::Constant(0), zone);
    }

    uint32_t special_values = (maybe_nan ? type_t::kNaN : 0) |
                              (maybe_minuszero ? type_t::kMinusZero : 0);
    // If both sides are decently small sets, we produce the product set.
    auto combine = [](float_t a, float_t b) { return a * b; };
    if (l.is_set() && r.is_set()) {
      auto result = ProductSet(l, r, special_values, zone, combine);
      if (!result.IsInvalid()) return result;
    }

    // Otherwise just construct a range.
    auto [l_min, l_max] = l.minmax();
    auto [r_min, r_max] = r.minmax();

    std::array<float_t, 4> results;
    results[0] = l_min * r_min;
    results[1] = l_min * r_max;
    results[2] = l_max * r_min;
    results[3] = l_max * r_max;

    for (int i = 0; i < 4; ++i) {
      if (std::isnan(results[i])) {
        return type_t::Any();
      }
    }

    float_t result_min = array_min(results);
    float_t result_max = array_max(results);
    if (result_min <= 0.0 && 0.0 <= result_max &&
        (l_min < 0.0 || r_min < 0.0)) {
      special_values |= type_t::kMinusZero;
      // Remove -0.
      result_min += 0.0;
      result_max += 0.0;
    }
    // 0 * V8_INFINITY is NaN, regardless of sign
    if (((l_min == -inf || l_max == inf) && (r_min <= 0.0 && 0.0 <= r_max)) ||
        ((r_min == -inf || r_max == inf) && (l_min <= 0.0 && 0.0 <= l_max))) {
      special_values |= type_t::kNaN;
    }

    type_t type = Range(result_min, result_max, special_values, zone);
    return type;
  }

  static Type Divide(const type_t& l, const type_t& r, Zone* zone) {
    // Division is tricky, so all we do is try ruling out -0 and NaN.
    if (l.is_only_nan() || r.is_only_nan()) return type_t::NaN();

    // If both sides are decently small sets, we produce the product set.
    auto combine = [](float_t a, float_t b) {
      if V8_UNLIKELY (!std::isfinite(a) && !std::isfinite(b)) {
        return nan_v<Bits>;
      }
      if V8_UNLIKELY (IsMinusZero(b)) {
        // +-0 / -0 ==> NaN
        if (a == 0) return nan_v<Bits>;
        return a > 0 ? -inf : inf;
      }
      if V8_UNLIKELY (b == 0) {
        // +-0 / 0 ==> NaN
        if (a == 0) return nan_v<Bits>;
        return a > 0 ? inf : -inf;
      }
      return a / b;
    };
    if (l.is_set() && r.is_set()) {
      auto result = ProductSet(l, r, 0, zone, combine);
      if (!result.IsInvalid()) return result;
    }

    auto [l_min, l_max] = l.minmax();
    auto [r_min, r_max] = r.minmax();

    bool maybe_nan =
        l.has_nan() || IsZeroish(r) ||
        ((l_min == -inf || l_max == inf) && (r_min == -inf || r_max == inf));

    // Try to rule out -0.
    // -0 / r (r > 0)
    bool maybe_minuszero =
        (l.has_minus_zero() && r_max > 0)
        // 0 / r (r < 0 || r == -0)
        || (l.Contains(0) && (r_min < 0 || r.has_minus_zero()))
        // l / inf (l < 0 || l == -0)
        || (r_max == inf && (l_min < 0 || l.has_minus_zero()))
        // l / -inf (l >= 0)
        || (r_min == -inf && l_max >= 0);

    uint32_t special_values = (maybe_nan ? type_t::kNaN : 0) |
                              (maybe_minuszero ? type_t::kMinusZero : 0);

    const bool r_all_positive = r_min >= 0 && !r.has_minus_zero();
    const bool r_all_negative = r_max < 0;

    // If r doesn't span 0, we can try to compute a more precise type.
    if (r_all_positive || r_all_negative) {
      // If r does not contain 0 or -0, we can compute a range.
      if (r_min > 0 && !r.has_minus_zero()) {
        std::array<float_t, 4> results;
        results[0] = l_min / r_min;
        results[1] = l_min / r_max;
        results[2] = l_max / r_min;
        results[3] = l_max / r_max;

        for (float_t r : results) {
          if (std::isnan(r)) return type_t::Any();
        }

        const float_t result_min = array_min(results);
        const float_t result_max = array_max(results);
        return Range(result_min, result_max, special_values, zone);
      }

      // Otherwise we try to check for the sign of the result.
      if (l_max < 0) {
        if (r_all_positive) {
          // All values are negative.
          return Range(-inf, next_smaller(float_t{0}), special_values, zone);
        } else {
          DCHECK(r_all_negative);
          // All values are positive.
          return Range(0, inf, special_values, zone);
        }
      } else if (l_min >= 0 && !l.has_minus_zero()) {
        if (r_all_positive) {
          // All values are positive.
          DCHECK_EQ(special_values & type_t::kMinusZero, 0);
          return Range(0, inf, special_values, zone);
        } else {
          DCHECK(r_all_negative);
          // All values are negative.
          return Range(-inf, next_smaller(float_t{0}), special_values, zone);
        }
      }
    }

    // Otherwise we give up on a precise type.
    return type_t::Any(special_values);
  }

  static Type Modulus(type_t l, type_t r, Zone* zone) {
    // Modulus can yield NaN if either {lhs} or {rhs} are NaN, or
    // {lhs} is not finite, or the {rhs} is a zero value.
    if (l.is_only_nan() || r.is_only_nan()) return type_t::NaN();
    bool maybe_nan =
        l.has_nan() || IsZeroish(r) || l.min() == -inf || l.max() == inf;

    // Deal with -0 inputs, only the signbit of {lhs} matters for the result.
    bool maybe_minuszero = false;
    if (l.has_minus_zero()) {
      maybe_minuszero = true;
      l = type_t::LeastUpperBound(l, type_t::Constant(0), zone);
    }
    if (r.has_minus_zero()) {
      r = type_t::LeastUpperBound(r, type_t::Constant(0), zone);
    }

    uint32_t special_values = (maybe_nan ? type_t::kNaN : 0) |
                              (maybe_minuszero ? type_t::kMinusZero : 0);
    // For integer inputs {l} and {r} we can infer a precise type.
    if (IsIntegerSet(l) && IsIntegerSet(r)) {
      auto [l_min, l_max] = l.minmax();
      auto [r_min, r_max] = r.minmax();
      // l % r is:
      // - never greater than abs(l)
      // - never greater than abs(r) - 1
      auto l_abs = std::max(std::abs(l_min), std::abs(l_max));
      auto r_abs = std::max(std::abs(r_min), std::abs(r_max));
      // If rhs is 0, we can only produce NaN.
      if (r_abs == 0) return type_t::NaN();
      r_abs -= 1;
      auto abs = std::min(l_abs, r_abs);
      float_t min = 0.0, max = 0.0;
      if (l_min >= 0.0) {
        // {l} positive.
        max = abs;
      } else if (l_max <= 0.0) {
        // {l} negative.
        min = 0.0 - abs;
      } else {
        // {l} positive or negative.
        min = 0.0 - abs;
        max = abs;
      }
      if (min == max) return Set({min}, special_values, zone);
      return Range(min, max, special_values, zone);
    }

    // Otherwise, we give up.
    return type_t::Any(special_values);
  }

  static Type Min(type_t l, type_t r, Zone* zone) {
    if (l.is_only_nan() || r.is_only_nan()) return type_t::NaN();
    bool maybe_nan = l.has_nan() || r.has_nan();

    // In order to ensure monotonicity of the computation below, we additionally
    // pretend +0 is present (for simplicity on both sides).
    bool maybe_minuszero = false;
    if (l.has_minus_zero() && !(r.max() < 0.0)) {
      maybe_minuszero = true;
      l = type_t::LeastUpperBound(l, type_t::Constant(0), zone);
    }
    if (r.has_minus_zero() && !(l.max() < 0.0)) {
      maybe_minuszero = true;
      r = type_t::LeastUpperBound(r, type_t::Constant(0), zone);
    }

    uint32_t special_values = (maybe_nan ? type_t::kNaN : 0) |
                              (maybe_minuszero ? type_t::kMinusZero : 0);
    // If both sides are decently small sets, we produce the product set.
    auto combine = [](float_t a, float_t b) { return std::min(a, b); };
    if (l.is_set() && r.is_set()) {
      // TODO(nicohartmann@): There is a faster way to compute this set.
      auto result = ProductSet(l, r, special_values, zone, combine);
      if (!result.IsInvalid()) return result;
    }

    // Otherwise just construct a range.
    auto [l_min, l_max] = l.minmax();
    auto [r_min, r_max] = r.minmax();

    auto min = std::min(l_min, r_min);
    auto max = std::min(l_max, r_max);
    return Range(min, max, special_values, zone);
  }

  static Type Max(type_t l, type_t r, Zone* zone) {
    if (l.is_only_nan() || r.is_only_nan()) return type_t::NaN();
    bool maybe_nan = l.has_nan() || r.has_nan();

    // In order to ensure monotonicity of the computation below, we additionally
    // pretend +0 is present (for simplicity on both sides).
    bool maybe_minuszero = false;
    if (l.has_minus_zero() && !(r.min() > 0.0)) {
      maybe_minuszero = true;
      l = type_t::LeastUpperBound(l, type_t::Constant(0), zone);
    }
    if (r.has_minus_zero() && !(l.min() > 0.0)) {
      maybe_minuszero = true;
      r = type_t::LeastUpperBound(r, type_t::Constant(0), zone);
    }

    uint32_t special_values = (maybe_nan ? type_t::kNaN : 0) |
                              (maybe_minuszero ? type_t::kMinusZero : 0);
    // If both sides are decently small sets, we produce the product set.
    auto combine = [](float_t a, float_t b) { return std::max(a, b); };
    if (l.is_set() && r.is_set()) {
      // TODO(nicohartmann@): There is a faster way to compute this set.
      auto result = ProductSet(l, r, special_values, zone, combine);
      if (!result.IsInvalid()) return result;
    }

    // Otherwise just construct a range.
    auto [l_min, l_max] = l.minmax();
    auto [r_min, r_max] = r.minmax();

    auto min = std::max(l_min, r_min);
    auto max = std::max(l_max, r_max);
    return Range(min, max, special_values, zone);
  }

  static Type Power(const type_t& l, const type_t& r, Zone* zone) {
    // x ** NaN => Nan.
    if (r.is_only_nan()) return type_t::NaN();
    // x ** +-0 => 1.
    if (r.is_constant(0) || r.is_only_minus_zero()) return type_t::Constant(1);
    if (l.is_only_nan()) {
      // NaN ** 0 => 1.
      if (r.Contains(0) || r.has_minus_zero()) {
        return type_t::Set({1}, type_t::kNaN, zone);
      }
      // NaN ** x => NaN (x != +-0).
      return type_t::NaN();
    }
    bool maybe_nan = l.has_nan() || r.has_nan();

    // a ** b produces NaN if a < 0 && b is fraction.
    if (l.min() < 0.0 && !IsIntegerSet(r)) maybe_nan = true;

    // a ** b produces -0 iff a == -0 and b is odd. Checking for all the cases
    // where b does only contain odd integer values seems not worth the
    // additional information we get here. We accept this over-approximation for
    // now. We could refine this whenever we see a benefit.
    uint32_t special_values =
        (maybe_nan ? type_t::kNaN : 0) | l.special_values();

    // If both sides are decently small sets, we produce the product set.
    auto combine = [](float_t a, float_t b) { return std::pow(a, b); };
    if (l.is_set() && r.is_set()) {
      auto result = ProductSet(l, r, special_values, zone, combine);
      if (!result.IsInvalid()) return result;
    }

    // TODO(nicohartmann@): Maybe we can produce a more precise range here.
    return type_t::Any(special_values);
  }

  static Type Atan2(const type_t& l, const type_t& r, Zone* zone) {
    // TODO(nicohartmann@): Maybe we can produce a more precise range here.
    return type_t::Any();
  }

  static Type LessThan(const type_t& lhs, const type_t& rhs, Zone* zone) {
    bool can_be_true = false;
    bool can_be_false = false;
    if (lhs.is_only_special_values()) {
      if (lhs.has_minus_zero()) {
        can_be_true = !rhs.is_only_special_values() && rhs.max() > 0.0;
        can_be_false = rhs.min() <= 0.0;
      } else {
        DCHECK(lhs.is_only_nan());
      }
    } else if (rhs.is_only_special_values()) {
      if (rhs.has_minus_zero()) {
        can_be_true = lhs.min() < 0.0;
        can_be_false = lhs.max() >= 0.0;
      } else {
        DCHECK(rhs.is_only_nan());
      }
    } else {
      // Both sides have at least one non-special value. We don't have to treat
      // special values here, because nan has been taken care of already and
      // -0.0 is included in min/max.
      can_be_true = lhs.min() < rhs.max();
      can_be_false = lhs.max() >= rhs.min();
    }

    // Consider NaN.
    can_be_false = can_be_false || lhs.has_nan() || rhs.has_nan();

    if (!can_be_true) return Word32Type::Constant(0);
    if (!can_be_false) return Word32Type::Constant(1);
    return Word32Type::Set({0, 1}, zone);
  }

  static Type LessThanOrEqual(const type_t& lhs, const type_t& rhs,
                              Zone* zone) {
    bool can_be_true = false;
    bool can_be_false = false;
    if (lhs.is_only_special_values()) {
      if (lhs.has_minus_zero()) {
        can_be_true = (!rhs.is_only_special_values() && rhs.max() >= 0.0) ||
                      rhs.has_minus_zero();
        can_be_false = rhs.min() < 0.0;
      } else {
        DCHECK(lhs.is_only_nan());
      }
    } else if (rhs.is_only_special_values()) {
      if (rhs.has_minus_zero()) {
        can_be_true = (!lhs.is_only_special_values() && lhs.min() <= 0.0) ||
                      lhs.has_minus_zero();
        can_be_false = lhs.max() > 0.0;
      } else {
        DCHECK(rhs.is_only_nan());
      }
    } else {
      // Both sides have at least one non-special value. We don't have to treat
      // special values here, because nan has been taken care of already and
      // -0.0 is included in min/max.
      can_be_true = can_be_true || lhs.min() <= rhs.max();
      can_be_false = can_be_false || lhs.max() > rhs.min();
    }

    // Consider NaN.
    can_be_false = can_be_false || lhs.has_nan() || rhs.has_nan();

    if (!can_be_true) return Word32Type::Constant(0);
    if (!can_be_false) return Word32Type::Constant(1);
    return Word32Type::Set({0, 1}, zone);
  }

  static Word32Type UnsignedLessThanOrEqual(const type_t& lhs,
                                            const type_t& rhs, Zone* zone) {
    bool can_be_true = lhs.unsigned_min() <= rhs.unsigned_max();
    bool can_be_false = lhs.unsigned_max() > rhs.unsigned_min();

    if (!can_be_true) return Word32Type::Constant(0);
    if (!can_be_false) return Word32Type::Constant(1);
    return Word32Type::Set({0, 1}, zone);
  }

  // Computes the ranges to which the sides of the comparison (lhs < rhs) can be
  // restricted when the comparison is true. When the comparison is true, we
  // learn: lhs cannot be >= rhs.max and rhs cannot be <= lhs.min and neither
  // can be NaN.
  static std::pair<Type, Type> RestrictionForLessThan_True(const type_t& lhs,
                                                           const type_t& rhs,
                                                           Zone* zone) {
    // If either side is only NaN, this comparison can never be true.
    if (lhs.is_only_nan() || rhs.is_only_nan()) {
      return {Type::None(), Type::None()};
    }

    Type restrict_lhs;
    if (rhs.max() == -inf) {
      // There is no value for lhs that could make (lhs < -inf) true.
      restrict_lhs = Type::None();
    } else {
      const auto max = next_smaller(rhs.max());
      uint32_t sv = max >= 0 ? type_t::kMinusZero : type_t::kNoSpecialValues;
      restrict_lhs = type_t::Range(-inf, max, sv, zone);
    }

    Type restrict_rhs;
    if (lhs.min() == inf) {
      // There is no value for rhs that could make (inf < rhs) true.
      restrict_rhs = Type::None();
    } else {
      const auto min = next_larger(lhs.min());
      uint32_t sv = min <= 0 ? type_t::kMinusZero : type_t::kNoSpecialValues;
      restrict_rhs = type_t::Range(min, inf, sv, zone);
    }

    return {restrict_lhs, restrict_rhs};
  }

  // Computes the ranges to which the sides of the comparison (lhs < rhs) can be
  // restricted when the comparison is false. When the comparison is false, we
  // learn: lhs cannot be < rhs.min and rhs cannot be > lhs.max.
  static std::pair<Type, Type> RestrictionForLessThan_False(const type_t& lhs,
                                                            const type_t& rhs,
                                                            Zone* zone) {
    Type restrict_lhs;
    if (rhs.has_nan()) {
      restrict_lhs = type_t::Any();
    } else {
      uint32_t lhs_sv =
          type_t::kNaN |
          (rhs.min() <= 0 ? type_t::kMinusZero : type_t::kNoSpecialValues);
      restrict_lhs = type_t::Range(rhs.min(), inf, lhs_sv, zone);
    }

    Type restrict_rhs;
    if (lhs.has_nan()) {
      restrict_rhs = type_t::Any();
    } else {
      uint32_t rhs_sv =
          type_t::kNaN |
          (lhs.max() >= 0 ? type_t::kMinusZero : type_t::kNoSpecialValues);
      restrict_rhs = type_t::Range(-inf, lhs.max(), rhs_sv, zone);
    }

    return {restrict_lhs, restrict_rhs};
  }

  // Computes the ranges to which the sides of the comparison (lhs <= rhs) can
  // be restricted when the comparison is true. When the comparison is true, we
  // learn: lhs cannot be > rhs.max and rhs cannot be < lhs.min and neither can
  // be NaN.
  static std::pair<Type, Type> RestrictionForLessThanOrEqual_True(
      const type_t& lhs, const type_t& rhs, Zone* zone) {
    // If either side is only NaN, this comparison can never be true.
    if (lhs.is_only_nan() || rhs.is_only_nan()) {
      return {Type::None(), Type::None()};
    }

    uint32_t lhs_sv =
        rhs.max() >= 0 ? type_t::kMinusZero : type_t::kNoSpecialValues;
    uint32_t rhs_sv =
        lhs.min() <= 0 ? type_t::kMinusZero : type_t::kNoSpecialValues;
    return {type_t::Range(-inf, rhs.max(), lhs_sv, zone),
            type_t::Range(lhs.min(), inf, rhs_sv, zone)};
  }

  // Computes the ranges to which the sides of the comparison (lhs <= rhs) can
  // be restricted when the comparison is false. When the comparison is false,
  // we learn: lhs cannot be <= rhs.min and rhs cannot be >= lhs.max.
  static std::pair<Type, Type> RestrictionForLessThanOrEqual_False(
      const type_t& lhs, const type_t& rhs, Zone* zone) {
    Type restrict_lhs;
    if (rhs.has_nan()) {
      restrict_lhs = type_t::Any();
    } else if (rhs.min() == inf) {
      // The only value for lhs that could make (lhs <= inf) false is NaN.
      restrict_lhs = type_t::NaN();
    } else {
      const auto min = next_larger(rhs.min());
      uint32_t sv = type_t::kNaN |
                    (min <= 0 ? type_t::kMinusZero : type_t::kNoSpecialValues);
      restrict_lhs = type_t::Range(min, inf, sv, zone);
    }

    Type restrict_rhs;
    if (lhs.has_nan()) {
      restrict_rhs = type_t::Any();
    } else if (lhs.max() == -inf) {
      // The only value for rhs that could make (-inf <= rhs) false is NaN.
      restrict_rhs = type_t::NaN();
    } else {
      const auto max = next_smaller(lhs.max());
      uint32_t sv = type_t::kNaN |
                    (max >= 0 ? type_t::kMinusZero : type_t::kNoSpecialValues);
      restrict_rhs = type_t::Range(-inf, max, sv, zone);
    }

    return {restrict_lhs, restrict_rhs};
  }
};

class Typer {
 public:
  static Type TypeForRepresentation(RegisterRepresentation rep) {
    switch (rep.value()) {
      case RegisterRepresentation::Word32():
        return Word32Type::Any();
      case RegisterRepresentation::Word64():
        return Word64Type::Any();
      case RegisterRepresentation::Float32():
        return Float32Type::Any();
      case RegisterRepresentation::Float64():
        return Float64Type::Any();

      case RegisterRepresentation::Tagged():
      case RegisterRepresentation::Compressed():
        // TODO(nicohartmann@): Support these representations.
        return Type::Any();
    }
  }

  static Type TypeForRepresentation(
      base::Vector<const RegisterRepresentation> reps, Zone* zone) {
    DCHECK_LT(0, reps.size());
    if (reps.size() == 1) return TypeForRepresentation(reps[0]);
    base::SmallVector<Type, 4> tuple_types;
    for (auto rep : reps) tuple_types.push_back(TypeForRepresentation(rep));
    return TupleType::Tuple(base::VectorOf(tuple_types), zone);
  }

  static Type TypeConstant(ConstantOp::Kind kind, ConstantOp::Storage value) {
    switch (kind) {
      case ConstantOp::Kind::kFloat32:
        if (std::isnan(value.float32)) return Float32Type::NaN();
        if (IsMinusZero(value.float32)) return Float32Type::MinusZero();
        return Float32Type::Constant(value.float32);
      case ConstantOp::Kind::kFloat64:
        if (std::isnan(value.float64)) return Float64Type::NaN();
        if (IsMinusZero(value.float64)) return Float64Type::MinusZero();
        return Float64Type::Constant(value.float64);
      case ConstantOp::Kind::kWord32:
        return Word32Type::Constant(static_cast<uint32_t>(value.integral));
      case ConstantOp::Kind::kWord64:
        return Word64Type::Constant(static_cast<uint64_t>(value.integral));
      default:
        // TODO(nicohartmann@): Support remaining {kind}s.
        return Type::Any();
    }
  }

  static Type TypeProjection(const Type& input, uint16_t idx) {
    if (input.IsNone()) return Type::None();
    if (!input.IsTuple()) return Type::Any();
    const TupleType& tuple = input.AsTuple();
    DCHECK_LT(idx, tuple.size());
    return tuple.element(idx);
  }

  static Type TypeWordBinop(Type left_type, Type right_type,
                            WordBinopOp::Kind kind, WordRepresentation rep,
                            Zone* zone) {
    DCHECK(!left_type.IsInvalid());
    DCHECK(!right_type.IsInvalid());

    if (rep == WordRepresentation::Word32()) {
      switch (kind) {
        case WordBinopOp::Kind::kAdd:
          return TypeWord32Add(left_type, right_type, zone);
        case WordBinopOp::Kind::kSub:
          return TypeWord32Sub(left_type, right_type, zone);
        default:
          // TODO(nicohartmann@): Support remaining {kind}s.
          return Word32Type::Any();
      }
    } else {
      DCHECK_EQ(rep, WordRepresentation::Word64());
      switch (kind) {
        case WordBinopOp::Kind::kAdd:
          return TypeWord64Add(left_type, right_type, zone);
        case WordBinopOp::Kind::kSub:
          return TypeWord64Sub(left_type, right_type, zone);
        default:
          // TODO(nicohartmann@): Support remaining {kind}s.
          return Word64Type::Any();
      }
    }
  }

  static Type TypeWord32Add(const Type& lhs, const Type& rhs, Zone* zone) {
    if (lhs.IsNone() || rhs.IsNone()) return Type::None();
    auto l = TruncateWord32Input(lhs, true, zone);
    auto r = TruncateWord32Input(rhs, true, zone);
    return WordOperationTyper<32>::Add(l, r, zone);
  }

  static Type TypeWord32Sub(const Type& lhs, const Type& rhs, Zone* zone) {
    if (lhs.IsNone() || rhs.IsNone()) return Type::None();
    auto l = TruncateWord32Input(lhs, true, zone);
    auto r = TruncateWord32Input(rhs, true, zone);
    return WordOperationTyper<32>::Subtract(l, r, zone);
  }

  static Type TypeWord64Add(const Type& lhs, const Type& rhs, Zone* zone) {
    if (lhs.IsNone() || rhs.IsNone()) return Type::None();
    if (!InputIs(lhs, Type::Kind::kWord64) ||
        !InputIs(rhs, Type::Kind::kWord64)) {
      return Word64Type::Any();
    }
    const auto& l = lhs.AsWord64();
    const auto& r = rhs.AsWord64();

    return WordOperationTyper<64>::Add(l, r, zone);
  }

  static Type TypeWord64Sub(const Type& lhs, const Type& rhs, Zone* zone) {
    if (lhs.IsNone() || rhs.IsNone()) return Type::None();
    if (!InputIs(lhs, Type::Kind::kWord64) ||
        !InputIs(rhs, Type::Kind::kWord64)) {
      return Word64Type::Any();
    }

    const auto& l = lhs.AsWord64();
    const auto& r = rhs.AsWord64();

    return WordOperationTyper<64>::Subtract(l, r, zone);
  }

  static Type TypeFloatBinop(Type left_type, Type right_type,
                             FloatBinopOp::Kind kind, FloatRepresentation rep,
                             Zone* zone) {
    DCHECK(!left_type.IsInvalid());
    DCHECK(!right_type.IsInvalid());

#define FLOAT_BINOP(op, bits)     \
  case FloatBinopOp::Kind::k##op: \
    return TypeFloat##bits##op(left_type, right_type, zone);

    if (rep == FloatRepresentation::Float32()) {
      switch (kind) {
        FLOAT_BINOP(Add, 32)
        FLOAT_BINOP(Sub, 32)
        FLOAT_BINOP(Mul, 32)
        FLOAT_BINOP(Div, 32)
        FLOAT_BINOP(Mod, 32)
        FLOAT_BINOP(Min, 32)
        FLOAT_BINOP(Max, 32)
        FLOAT_BINOP(Power, 32)
        FLOAT_BINOP(Atan2, 32)
      }
    } else {
      DCHECK_EQ(rep, FloatRepresentation::Float64());
      switch (kind) {
        FLOAT_BINOP(Add, 64)
        FLOAT_BINOP(Sub, 64)
        FLOAT_BINOP(Mul, 64)
        FLOAT_BINOP(Div, 64)
        FLOAT_BINOP(Mod, 64)
        FLOAT_BINOP(Min, 64)
        FLOAT_BINOP(Max, 64)
        FLOAT_BINOP(Power, 64)
        FLOAT_BINOP(Atan2, 64)
      }
    }

#undef FLOAT_BINOP
  }

#define FLOAT_BINOP(op, bits, float_typer_handler)                     \
  static Type TypeFloat##bits##op(const Type& lhs, const Type& rhs,    \
                                  Zone* zone) {                        \
    if (lhs.IsNone() || rhs.IsNone()) return Type::None();             \
    if (!InputIs(lhs, Type::Kind::kFloat##bits) ||                     \
        !InputIs(rhs, Type::Kind::kFloat##bits)) {                     \
      return Float##bits##Type::Any();                                 \
    }                                                                  \
    const auto& l = lhs.AsFloat##bits();                               \
    const auto& r = rhs.AsFloat##bits();                               \
    return FloatOperationTyper<bits>::float_typer_handler(l, r, zone); \
  }

  // Float32 operations
  FLOAT_BINOP(Add, 32, Add)
  FLOAT_BINOP(Sub, 32, Subtract)
  FLOAT_BINOP(Mul, 32, Multiply)
  FLOAT_BINOP(Div, 32, Divide)
  FLOAT_BINOP(Mod, 32, Modulus)
  FLOAT_BINOP(Min, 32, Min)
  FLOAT_BINOP(Max, 32, Max)
  FLOAT_BINOP(Power, 32, Power)
  FLOAT_BINOP(Atan2, 32, Atan2)
  // Float64 operations
  FLOAT_BINOP(Add, 64, Add)
  FLOAT_BINOP(Sub, 64, Subtract)
  FLOAT_BINOP(Mul, 64, Multiply)
  FLOAT_BINOP(Div, 64, Divide)
  FLOAT_BINOP(Mod, 64, Modulus)
  FLOAT_BINOP(Min, 64, Min)
  FLOAT_BINOP(Max, 64, Max)
  FLOAT_BINOP(Power, 64, Power)
  FLOAT_BINOP(Atan2, 64, Atan2)
#undef FLOAT_BINOP

  static Type TypeOverflowCheckedBinop(const Type& left_type,
                                       const Type& right_type,
                                       OverflowCheckedBinopOp::Kind kind,
                                       WordRepresentation rep, Zone* zone) {
    DCHECK(!left_type.IsInvalid());
    DCHECK(!right_type.IsInvalid());

    if (rep == WordRepresentation::Word32()) {
      switch (kind) {
        case OverflowCheckedBinopOp::Kind::kSignedAdd:
          return TypeWord32OverflowCheckedAdd(left_type, right_type, zone);
        case OverflowCheckedBinopOp::Kind::kSignedSub:
        case OverflowCheckedBinopOp::Kind::kSignedMul:
          // TODO(nicohartmann@): Support these.
          return TupleType::Tuple(Word32Type::Any(),
                                  Word32Type::Set({0, 1}, zone), zone);
      }
    } else {
      DCHECK_EQ(rep, WordRepresentation::Word64());
      switch (kind) {
        case OverflowCheckedBinopOp::Kind::kSignedAdd:
        case OverflowCheckedBinopOp::Kind::kSignedSub:
        case OverflowCheckedBinopOp::Kind::kSignedMul:
          // TODO(nicohartmann@): Support these.
          return TupleType::Tuple(Word64Type::Any(),
                                  Word32Type::Set({0, 1}, zone), zone);
      }
    }
  }

  static Type TypeWord32OverflowCheckedAdd(const Type& lhs, const Type& rhs,
                                           Zone* zone) {
    if (lhs.IsNone() || rhs.IsNone()) return Type::None();
    auto l = TruncateWord32Input(lhs, true, zone);
    auto r = TruncateWord32Input(rhs, true, zone);

    auto value = WordOperationTyper<32>::Add(l, r, zone);
    // We check for signed overflow and if the topmost bits of both opperands
    // are 0, we know that the result cannot overflow.
    if ((0xC0000000 & l.unsigned_max()) == 0 &&
        (0xC0000000 & r.unsigned_max()) == 0) {
      // Cannot overflow.
      return TupleType::Tuple(value, Word32Type::Constant(0), zone);
    }
    // Special case for two constant inputs to figure out the overflow.
    if (l.is_constant() && r.is_constant()) {
      constexpr uint32_t msb_mask = 0x80000000;
      DCHECK(value.is_constant());
      uint32_t l_msb = (*l.try_get_constant()) & msb_mask;
      uint32_t r_msb = (*r.try_get_constant()) & msb_mask;
      if (l_msb != r_msb) {
        // Different sign bits can never lead to an overflow.
        return TupleType::Tuple(value, Word32Type::Constant(0), zone);
      }
      uint32_t value_msb = (*value.try_get_constant()) & msb_mask;
      const uint32_t overflow = value_msb == l_msb ? 0 : 1;
      return TupleType::Tuple(value, Word32Type::Constant(overflow), zone);
    }
    // Otherwise we accept some imprecision.
    return TupleType::Tuple(value, Word32Type::Set({0, 1}, zone), zone);
  }

  static Type TypeComparison(const Type& lhs, const Type& rhs,
                             RegisterRepresentation rep,
                             ComparisonOp::Kind kind, Zone* zone) {
    switch (rep.value()) {
      case RegisterRepresentation::Word32():
        return TypeWord32Comparison(lhs, rhs, kind, zone);
      case RegisterRepresentation::Word64():
        return TypeWord64Comparison(lhs, rhs, kind, zone);
      case RegisterRepresentation::Float32():
        return TypeFloat32Comparison(lhs, rhs, kind, zone);
      case RegisterRepresentation::Float64():
        return TypeFloat64Comparison(lhs, rhs, kind, zone);
      case RegisterRepresentation::Tagged():
      case RegisterRepresentation::Compressed():
        if (lhs.IsNone() || rhs.IsNone()) return Type::None();
        // TODO(nicohartmann@): Support those cases.
        return Word32Type::Set({0, 1}, zone);
    }
  }

  static Type TypeWord32Comparison(const Type& lhs, const Type& rhs,
                                   ComparisonOp::Kind kind, Zone* zone) {
    if (lhs.IsNone() || rhs.IsNone()) return Type::None();
    auto l = TruncateWord32Input(lhs, true, zone);
    auto r = TruncateWord32Input(rhs, true, zone);
    switch (kind) {
      case ComparisonOp::Kind::kSignedLessThan:
      case ComparisonOp::Kind::kSignedLessThanOrEqual:
        // TODO(nicohartmann@): Support this.
        return Word32Type::Set({0, 1}, zone);
      case ComparisonOp::Kind::kUnsignedLessThan:
        return WordOperationTyper<32>::UnsignedLessThan(l, r, zone);
      case ComparisonOp::Kind::kUnsignedLessThanOrEqual:
        return WordOperationTyper<32>::UnsignedLessThanOrEqual(l, r, zone);
    }
    UNREACHABLE();
  }

  static Type TypeWord64Comparison(const Type& lhs, const Type& rhs,
                                   ComparisonOp::Kind kind, Zone* zone) {
    if (lhs.IsNone() || rhs.IsNone()) return Type::None();
    switch (kind) {
      case ComparisonOp::Kind::kSignedLessThan:
      case ComparisonOp::Kind::kSignedLessThanOrEqual:
        // TODO(nicohartmann@): Support this.
        return Word32Type::Set({0, 1}, zone);
      case ComparisonOp::Kind::kUnsignedLessThan:
        return WordOperationTyper<64>::UnsignedLessThan(lhs.AsWord64(),
                                                        rhs.AsWord64(), zone);
      case ComparisonOp::Kind::kUnsignedLessThanOrEqual:
        return WordOperationTyper<64>::UnsignedLessThanOrEqual(
            lhs.AsWord64(), rhs.AsWord64(), zone);
    }
    UNREACHABLE();
  }

  static Type TypeFloat32Comparison(const Type& lhs, const Type& rhs,
                                    ComparisonOp::Kind kind, Zone* zone) {
    if (lhs.IsNone() || rhs.IsNone()) return Type::None();
    switch (kind) {
      case ComparisonOp::Kind::kSignedLessThan:
        return FloatOperationTyper<32>::LessThan(lhs.AsFloat32(),
                                                 rhs.AsFloat32(), zone);
      case ComparisonOp::Kind::kSignedLessThanOrEqual:
        return FloatOperationTyper<32>::LessThanOrEqual(lhs.AsFloat32(),
                                                        rhs.AsFloat32(), zone);
      case ComparisonOp::Kind::kUnsignedLessThan:
      case ComparisonOp::Kind::kUnsignedLessThanOrEqual:
        UNREACHABLE();
    }
  }

  static Type TypeFloat64Comparison(const Type& lhs, const Type& rhs,
                                    ComparisonOp::Kind kind, Zone* zone) {
    if (lhs.IsNone() || rhs.IsNone()) return Type::None();
    switch (kind) {
      case ComparisonOp::Kind::kSignedLessThan:
        return FloatOperationTyper<64>::LessThan(lhs.AsFloat64(),
                                                 rhs.AsFloat64(), zone);
      case ComparisonOp::Kind::kSignedLessThanOrEqual:
        return FloatOperationTyper<64>::LessThanOrEqual(lhs.AsFloat64(),
                                                        rhs.AsFloat64(), zone);
      case ComparisonOp::Kind::kUnsignedLessThan:
      case ComparisonOp::Kind::kUnsignedLessThanOrEqual:
        UNREACHABLE();
    }
  }

  static Word64Type ExtendWord32ToWord64(const Word32Type& t, Zone* zone) {
    // We cannot infer much, but the lower bound of the word32 is also the lower
    // bound of the word64 type.
    if (t.is_wrapping()) return Word64Type::Any();
    return Word64Type::Range(static_cast<uint64_t>(t.unsigned_min()),
                             std::numeric_limits<uint64_t>::max(), zone);
  }

  static Word32Type TruncateWord32Input(const Type& input,
                                        bool implicit_word64_narrowing,
                                        Zone* zone) {
    DCHECK(!input.IsInvalid());
    DCHECK(!input.IsNone());

    if (input.IsAny()) {
      if (allow_invalid_inputs()) return Word32Type::Any();
    } else if (input.IsWord32()) {
      return input.AsWord32();
    } else if (input.IsWord64() && implicit_word64_narrowing) {
      // The input is implicitly converted to word32.
      const auto& w64 = input.AsWord64();
      if (w64.is_set()) {
        WordOperationTyper<32>::ElementsVector elements;
        for (uint64_t e : w64.set_elements()) {
          elements.push_back(static_cast<uint32_t>(e));
        }
        return WordOperationTyper<32>::FromElements(std::move(elements), zone);
      }

      if (w64.is_any() || w64.is_wrapping()) return Word32Type::Any();

      if (w64.range_to() <= std::numeric_limits<uint32_t>::max()) {
        DCHECK_LE(w64.range_from(), std::numeric_limits<uint32_t>::max());
        return Word32Type::Range(static_cast<uint32_t>(w64.range_from()),
                                 static_cast<uint32_t>(w64.range_to()), zone);
      }

      // TODO(nicohartmann@): Might compute a more precise range here.
      return Word32Type::Any();
    }

    FATAL("Missing proper type for TruncateWord32Input. Type is: %s",
          input.ToString().c_str());
  }

  class BranchRefinements {
   public:
    // type_getter_t has to provide the type for a given input index.
    using type_getter_t = std::function<Type(OpIndex)>;
    // type_refiner_t is called with those arguments:
    //  - OpIndex: index of the operation whose type is refined by the branch.
    //  - Type: the refined type of the operation (after refinement, guaranteed
    //  to be a subtype of the original type).
    using type_refiner_t = std::function<void(OpIndex, const Type&)>;

    BranchRefinements(type_getter_t type_getter, type_refiner_t type_refiner)
        : type_getter_(type_getter), type_refiner_(type_refiner) {
      DCHECK(type_getter_);
      DCHECK(type_refiner_);
    }

    void RefineTypes(const Operation& condition, bool then_branch, Zone* zone);

   private:
    template <bool allow_implicit_word64_truncation>
    Type RefineWord32Type(const Type& type, const Type& refinement,
                          Zone* zone) {
      // If refinement is Type::None(), the operation/branch is unreachable.
      if (refinement.IsNone()) return Type::None();
      DCHECK(refinement.IsWord32());
      if constexpr (allow_implicit_word64_truncation) {
        // Turboshaft allows implicit trunction of Word64 values to Word32. When
        // an operation on Word32 representation computes a refinement type,
        // this is going to be a Type::Word32() even if the actual {type} was
        // Word64 before truncation. To correctly refine this type, we need to
        // extend the {refinement} to Word64 such that it reflects the
        // corresponding values in the original type (before truncation) before
        // we intersect.
        if (type.IsWord64()) {
          return Word64Type::Intersect(
              type.AsWord64(),
              Typer::ExtendWord32ToWord64(refinement.AsWord32(), zone),
              Type::ResolutionMode::kOverApproximate, zone);
        }
      }
      // We limit the values of {type} to those in {refinement}.
      return Word32Type::Intersect(type.AsWord32(), refinement.AsWord32(),
                                   Type::ResolutionMode::kOverApproximate,
                                   zone);
    }

    type_getter_t type_getter_;
    type_refiner_t type_refiner_;
  };

  static bool InputIs(const Type& input, Type::Kind expected) {
    if (input.IsInvalid()) {
      if (allow_invalid_inputs()) return false;
    } else if (input.kind() == expected) {
      return true;
    } else if (input.IsAny()) {
      if (allow_invalid_inputs()) return false;
    }

    std::stringstream s;
    s << expected;
    FATAL("Missing proper type (%s). Type is: %s", s.str().c_str(),
          input.ToString().c_str());
  }

  // For now we allow invalid inputs (which will then just lead to very generic
  // typing). Once all operations are implemented, we are going to disable this.
  static bool allow_invalid_inputs() { return true; }
};

}  // namespace v8::internal::compiler::turboshaft

#endif  // V8_COMPILER_TURBOSHAFT_TYPER_H_
