// Copyright 2025 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.

#include "src/compiler/turboshaft/wasm-shuffle-reducer.h"

#include <span>

#include "src/base/iterator.h"
#include "src/compiler/backend/simd-shuffle.h"
#include "src/flags/flags.h"

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

#define TRACE(...)                                             \
  do {                                                         \
    if (v8_flags.trace_wasm_simd_shuffle) PrintF(__VA_ARGS__); \
  } while (false)

#define TRACE_SHUFFLE(s)                                                      \
  do {                                                                        \
    if (v8_flags.trace_wasm_simd_shuffle)                                     \
      TRACE(                                                                  \
          "%d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d\n", \
          s[0], s[1], s[2], s[3], s[4], s[5], s[6], s[7], s[8], s[9], s[10],  \
          s[11], s[12], s[13], s[14], s[15]);                                 \
  } while (false)

void DemandedByteAnalysis::Add(OpIndex node, DemandedBytes demanded) {
  const Operation& op = input_graph().Get(node);
  // We're trying to find out which SIMD bytes of op are used. If op has more
  // than a single user we will have to visit it multiple times. Both `Record`
  // methods will attempt to recursively add more operations.
  if (op.saturated_use_count.Is(1)) {
    TRACE("Found Op %d, demanded bytes: %#04x\n", input_graph().Index(op).id(),
          static_cast<uint32_t>(demanded.bytes()));
    RecordOp(op, demanded);
  } else {
    RecordPartialOp(op, demanded);
  }
}

void DemandedByteAnalysis::AddOp(const Simd128UnaryOp& unop,
                                 DemandedBytes demanded) {
  if (Visited(&unop)) return;
  visited_.insert(&unop);

  if (IsUnaryLowHalfOp(unop.kind)) {
    demanded.HalveWithLimit(GetInputElementSizeInBytes(unop.kind));
    Add(unop.input(), demanded);
  }
}

void DemandedByteAnalysis::AddOp(const Simd128BinopOp& binop,
                                 DemandedBytes demanded) {
  if (Visited(&binop)) return;
  visited_.insert(&binop);

  if (IsBinaryLowHalfOp(binop.kind)) {
    demanded.HalveWithLimit(GetInputElementSizeInBytes(binop.kind));
    Add(binop.left(), demanded);
    Add(binop.right(), demanded);
  }
}

void DemandedByteAnalysis::AddOp(const Simd128ExtractLaneOp& extract_op,
                                 DemandedBytes demanded) {
  if (Visited(&extract_op)) return;
  visited_.insert(&extract_op);

  // F16 isn't supported yet because the element rep is Float32.
  if (extract_op.kind == Simd128ExtractLaneOp::Kind::kF16x8) return;

  uint8_t elem_size =
      ElementSizeInBytes(Simd128ExtractLaneOp::element_rep(extract_op.kind));
  demanded = DemandedBytes::LowFromLane(elem_size, extract_op.lane);
  TRACE("ExtractOp %d extracts lane %d, of size %d, so demands: %#04x\n",
        input_graph().Index(extract_op).id(), extract_op.lane, elem_size,
        static_cast<uint32_t>(demanded.bytes()));
  Add(extract_op.input(), demanded);
}

void DemandedByteAnalysis::AddOp(const Simd128LaneMemoryOp& lane_op,
                                 DemandedBytes demanded) {
  if (Visited(&lane_op)) return;
  visited_.insert(&lane_op);

  if (lane_op.mode == Simd128LaneMemoryOp::Mode::kStore) {
    demanded = DemandedBytes::LowFromLane(lane_op.lane_size(), lane_op.lane);
    TRACE("LaneMemoryOp %d stores lane %d, of size %d, so demands: %#04x\n",
          input_graph().Index(lane_op).id(), lane_op.lane, lane_op.lane_size(),
          static_cast<uint32_t>(demanded.bytes()));
    Add(lane_op.value(), demanded);
  }
}

void DemandedByteAnalysis::RecordOp(const Operation& op,
                                    DemandedBytes demanded) {
  if (auto* unop = op.TryCast<Simd128UnaryOp>()) {
    AddOp(*unop, demanded);
  } else if (auto* binop = op.TryCast<Simd128BinopOp>()) {
    AddOp(*binop, demanded);
  } else if (auto* extract_op = op.TryCast<Simd128ExtractLaneOp>()) {
    AddOp(*extract_op, demanded);
  } else if (auto* lane_op = op.TryCast<Simd128LaneMemoryOp>()) {
    AddOp(*lane_op, demanded);
  } else if (auto* shuffle = op.TryCast<Simd128ShuffleOp>()) {
    if (!demanded.IsAll()) {
      if (demanded_bytes_.size() < kMaxNumOperations) {
        demanded_bytes_.emplace_back(shuffle, demanded);
      } else if (!demanded_limit_reached_) {
        TRACE("Demanded elements limit reached in RecordOp\n");
        demanded_limit_reached_ = true;
      }
    }
  }
}

std::optional<DemandedBytes> DemandedByteAnalysis::AddUserAndCheckFoundAll(
    const Operation& op, const DemandedBytes& demanded) {
  for (MultiUserBits& multi_use : to_revisit_) {
    if (multi_use.op() == &op) {
      multi_use.Add(demanded);
      if (multi_use.FoundAllUsers()) {
        // Ensure we don't visit this again from the top-level search.
        visited_.insert(&op);
        TRACE("Found all users (%d) of Op %d, demanded bytes: %#04x\n",
              op.saturated_use_count.GetMaybeSaturated(),
              input_graph().Index(op).id(),
              static_cast<uint32_t>(multi_use.demanded().bytes()));
        return multi_use.demanded();
      }
      return {};
    }
  }
  if (to_revisit_.size() < kMaxNumOperations) {
    to_revisit_.emplace_back(&op, demanded, 1);
  } else if (!revisit_limit_reached_) {
    TRACE("Revisit limit reached\n");
    revisit_limit_reached_ = true;
  }
  return {};
}

void DemandedByteAnalysis::RecordPartialOp(const Operation& op,
                                           DemandedBytes demanded) {
  TRACE("Recording partial Op %d, demanded bytes: %#04x\n",
        input_graph().Index(op).id(), static_cast<uint32_t>(demanded.bytes()));
  if (auto* unop = op.TryCast<Simd128UnaryOp>()) {
    RecordPartialOp(*unop, demanded);
  } else if (auto* binop = op.TryCast<Simd128BinopOp>()) {
    RecordPartialOp(*binop, demanded);
  } else if (auto* shuffle = op.TryCast<Simd128ShuffleOp>()) {
    (void)AddUserAndCheckFoundAll(*shuffle, demanded);
  }
}

void DemandedByteAnalysis::RecordPartialOp(const Simd128UnaryOp& unop,
                                           DemandedBytes demanded) {
  if (IsUnaryLowHalfOp(unop.kind)) {
    // If we've now visited this unop from all of its users, visit its
    // operand.
    if (auto maybe_demanded = AddUserAndCheckFoundAll(unop, demanded)) {
      demanded = maybe_demanded.value();
      demanded.HalveWithLimit(GetInputElementSizeInBytes(unop.kind));
      Add(unop.input(), demanded);
    }
  }
}

void DemandedByteAnalysis::RecordPartialOp(const Simd128BinopOp& binop,
                                           DemandedBytes demanded) {
  if (IsBinaryLowHalfOp(binop.kind)) {
    // If we've now visited this binop from all of its users, visit its
    // operands.
    if (auto maybe_demanded = AddUserAndCheckFoundAll(binop, demanded)) {
      demanded = maybe_demanded.value();
      demanded.HalveWithLimit(GetInputElementSizeInBytes(binop.kind));
      Add(binop.left(), demanded);
      Add(binop.right(), demanded);
    }
  }
}

void DemandedByteAnalysis::RevisitShuffle(const Simd128ShuffleOp& shuffle,
                                          DemandedBytes demanded) {
  // The shuffle may have been recorded previously because it had a single user,
  // but that user may have been used by multiple others. Once all those users
  // have been visited, we may now know that fewer bytes are demanded.
  for (unsigned i = 0; i < demanded_bytes_.size(); ++i) {
    if (demanded_bytes_[i].first == &shuffle) {
      DCHECK(demanded.IsLessThanOrEqual(demanded_bytes_[i].second));
      demanded_bytes_[i].second = demanded;
      return;
    }
  }
  if (demanded_bytes_.size() < kMaxNumOperations) {
    demanded_bytes_.emplace_back(&shuffle, demanded);
  } else if (!demanded_limit_reached_) {
    TRACE("Demanded elements limit reached in RevisitShuffle\n");
    demanded_limit_reached_ = true;
  }
}

void DemandedByteAnalysis::Revisit() {
  for (const MultiUserBits& multi_user : to_revisit_) {
    if (multi_user.FoundAllUsers()) {
      const Operation* op = multi_user.op();
      if (auto* shuffle = op->TryCast<Simd128ShuffleOp>()) {
        RevisitShuffle(*shuffle, multi_user.demanded());
      }
    }
  }
  to_revisit_.clear();
  revisit_limit_reached_ = false;
}

void WasmShuffleAnalyzer::Run() {
  TRACE("Running WasmShuffleAnalyzer\n");
  for (uint32_t processed = input_graph().block_count(); processed > 0;
       --processed) {
    BlockIndex block_index = static_cast<BlockIndex>(processed - 1);
    TRACE("BLOCK %d\n", block_index.id());
    const Block& block = input_graph().Get(block_index);
    auto idx_range = input_graph().OperationIndices(block);
    for (OpIndex op_idx : base::Reversed(idx_range)) {
      const Operation& op = input_graph().Get(op_idx);
      Process(op);
    }
    demanded_byte_analysis_.Revisit();
    // TODO(sparker): Reset the state after each block? Currently our testing
    // requires being able to look at demanded_bytes.
  }
}

void WasmShuffleAnalyzer::Process(const Operation& op) {
  if (ShouldSkipOperation(op)) {
    return;
  }

  if (auto* unop = op.TryCast<Simd128UnaryOp>()) {
    ProcessUnary(*unop);
    return;
  }

  if (auto* binop = op.TryCast<Simd128BinopOp>()) {
    ProcessBinary(*binop);
    return;
  }

  if (auto* shuffle_op = op.TryCast<Simd128ShuffleOp>()) {
    ProcessShuffle(*shuffle_op);
    return;
  }

  if (auto* replace_op = op.TryCast<Simd128ReplaceLaneOp>()) {
    ProcessReplaceLane(*replace_op);
    return;
  }

  if (auto* extract_op = op.TryCast<Simd128ExtractLaneOp>()) {
    ProcessExtractLane(*extract_op);
    return;
  }

  if (auto* lane_op = op.TryCast<Simd128LaneMemoryOp>()) {
    ProcessLaneMemory(*lane_op);
    return;
  }
}

void WasmShuffleAnalyzer::ProcessUnary(const Simd128UnaryOp& unop) {
  demanded_byte_analysis_.AddOp(unop, DemandedBytes::All());
}

void WasmShuffleAnalyzer::ProcessBinary(const Simd128BinopOp& binop) {
  demanded_byte_analysis_.AddOp(binop, DemandedBytes::All());
}

void WasmShuffleAnalyzer::ProcessReplaceLane(
    const Simd128ReplaceLaneOp& replace_op) {
  // FP16 isn't supported.
  if (replace_op.kind == Simd128ReplaceLaneOp::Kind::kF16x8) return;

  if (const LoadOp* load =
          input_graph().Get(replace_op.new_lane()).TryCast<LoadOp>()) {
    int replace_bytes =
        ElementSizeInBytes(Simd128ReplaceLaneOp::element_rep(replace_op.kind));
    if (load->saturated_use_count.Is(1) &&
        load->loaded_rep.SizeInBytes() == replace_bytes &&
        !load->kind.tagged_base && !load->kind.is_atomic) {
      AddLoadLaneCandidate(load, replace_op);
    }
  }
}

void WasmShuffleAnalyzer::ProcessExtractLane(
    const Simd128ExtractLaneOp& extract_op) {
  demanded_byte_analysis_.AddOp(extract_op, DemandedBytes::All());
}

void WasmShuffleAnalyzer::ProcessLaneMemory(
    const Simd128LaneMemoryOp& lane_op) {
  demanded_byte_analysis_.AddOp(lane_op, DemandedBytes::All());
}

namespace {

struct ShuffleWindows {
  WasmShuffleAnalyzer::ShuffleWindow shuffle_out_window;
  WasmShuffleAnalyzer::ShuffleWindow shuffle_in_window;
};

// Searches for a contiguous window of size `shuffle_in_demanded` within
// `shuffle` such that all elements in the window are sourced from `side`, and
// all elements outside of the window are sourced from the other side. Returns
// both the matching output window and the corresponding input window if one
// exists. Examples accepted for the left side are: [ 9 ] [ 8, 9 ]
// [ 2, 3, 4, 5 ] [ 5, 4, 3, 2 ] [ 3, 2, 5, 4 ] [ 3, 3, 3, 3 ]
// [ 0, 2, 4, 6, 8, 10, 12, 14 ]. In these cases, we also have to ensure
// that there's not another instance of a value 0-15 in the shuffle.
std::optional<ShuffleWindows> GetWindows(const Simd128ShuffleOp& shuffle_in,
                                         const Simd128ShuffleOp& shuffle_out,
                                         std::span<const uint8_t> shuffle,
                                         const ShuffleSide side,
                                         DemandedBytes shuffle_in_demanded) {
  DCHECK_LE(shuffle_in_demanded.bytes(), kSimd128HalfSize);
  DCHECK_LE(shuffle.size(), kSimd128Size);
  DCHECK_EQ(shuffle.data(), &shuffle_out.shuffle[0]);

  const size_t window_size = shuffle_in_demanded.bytes();
  if (window_size > shuffle.size()) return {};
  auto in_range = [side](uint8_t i) { return InRange(i, side); };

  // Sliding window over the demanded shuffle output bytes. Stop once a full
  // window would exceed bounds.
  for (size_t i = 0; i + window_size <= shuffle.size(); ++i) {
    std::span<const uint8_t> before = shuffle.first(i);
    std::span<const uint8_t> window = shuffle.subspan(i, window_size);
    std::span<const uint8_t> after = shuffle.subspan(i + window_size);
    // Test for a valid range in the window, with no other instances of
    // in_range.
    if (std::all_of(window.begin(), window.end(), in_range) &&
        std::none_of(after.begin(), after.end(), in_range) &&
        std::none_of(before.begin(), before.end(), in_range)) {
      WasmShuffleAnalyzer::ShuffleWindow shuffle_out_window(
          shuffle_out, static_cast<uint8_t>(i), shuffle_in_demanded);
      DemandedBytes input_demanded = shuffle_out_window.InputDemanded();
      uint8_t input_begin = shuffle_out_window.LowestInputByte() % kSimd128Size;

      // InputDemanded will round up to the nearest power of two, so ensure that
      // the range is still in-bounds.
      if (input_begin + input_demanded.bytes() > kSimd128Size) continue;

      WasmShuffleAnalyzer::ShuffleWindow shuffle_in_window(
          shuffle_in, input_begin, input_demanded);
      return ShuffleWindows{shuffle_out_window, shuffle_in_window};
    }
  }
  return {};
}

}  // namespace

bool WasmShuffleAnalyzer::ProcessShuffleOfShuffle(
    const Simd128ShuffleOp& shuffle_in, const Simd128ShuffleOp& shuffle_out,
    const ShuffleSide side) {
  DCHECK(shuffle_in.saturated_use_count.Is(1));
  // Suppose we have two 16-byte shuffles:
  // |---a1---|---b3---|--------|--------|  shuffle_in = (a, b)
  //
  // |---a1---|---b3---|---c?---|---c?---|  shuffle_out = (shuffle_in, c)
  //
  // As only half of the shuffle_in is used, it means that half the work of
  // shuffle_in is wasted so, here, we try to reduce shuffle_in to a more narrow
  // kind. In the case above we can simply truncate shuffle_in.shuffle but
  // there are other situations which involve more work:
  //
  // In the following case, shuffle_in.shuffle needs to be shifted left so that
  // it writes the required bytes to the low half of the result. This then means
  // that shuffle_out.shuffle needs to be updated to read from the low half.
  //
  // |--------|--------|---a1---|---b3---|  shuffle_in = (a, b)
  //
  // |---a1---|---b3---|---c?---|---c?---|  shuffle_out = (shuffle_in, c)
  //
  // And we're not restricted to half vectors. If `GetWindows` returns a
  // value, it marks both the exclusive window, in shuffle_out, that is defined
  // by shuffle_in and the corresponding input window in shuffle_in. So, for the
  // below shuffle we'd find that shuffle_out.left() could be reduced to
  // DemandedBytes::Low(8) because there's a single 8-byte window, in
  // shuffle_out, that reads from its left input.
  //
  // shuffle_out = [ 19, 18, 17, 16, 2, 3, 2, 3, 2, 3, 2, 3, 20, 21, 22, 23 ]
  //                                 ^
  //                                 |
  //      GetWindows().shuffle_out_window.begin_index() = 4
  //
  // Once we create a ShuffleWindow, from shuffle_out, it can be deduced that
  // only two bytes of shuffle_in are used. So:
  // shuffle_out_window.begin_index() == 4.
  // shuffle_out_window.LowestInputByte() == 2
  // shuffle_out_window.InputDemanded() == 2.
  //
  // shuffle_in_window.begin_index() == 2
  // shuffle_in_window.demanded_bytes() == 2
  //
  // shuffle_in will be added to the shuffle_to_shift list, with
  // DemandedBytes::Low(2), and its begin_index set to 2. When this shuffle
  // is reduced, it will now write bytes 2 and 3 into bytes 0 and 1.
  // shuffle_out will be added to the shuffles_to_read_shifted list which will
  // update its shuffle array to look at indices 0 and 1, instead of 2 and 3.

  DemandedBytes shuffle_out_demanded = GetDemandedBytes(&shuffle_out);
  std::span<const uint8_t> shuffle_out_bytes(shuffle_out.shuffle,
                                             shuffle_out_demanded.bytes());
  for (uint8_t bytes : {8, 4, 2, 1}) {
    auto shuffle_in_demanded = DemandedBytes::Low(bytes);
    if (std::optional<ShuffleWindows> maybe_windows =
            GetWindows(shuffle_in, shuffle_out, shuffle_out_bytes, side,
                       shuffle_in_demanded)) {
      ShuffleWindow shuffle_out_window = maybe_windows->shuffle_out_window;
      ShuffleWindow shuffle_in_window = maybe_windows->shuffle_in_window;
      TRACE("Found a window of size %d starting at index %d.\n",
            shuffle_out_window.OutputDemanded().bytes(),
            shuffle_out_window.begin_index());
      TRACE_SHUFFLE(shuffle_out.shuffle);
      // The incoming shuffle can be reduced because, at least a part of, it
      // defines a consecutive part of shuffle_out.

      if (shuffle_in_window.OutputDemanded().IsAll()) continue;

      // Use shuffle_out_window to obtain the begin index of shuffle_in. And we
      // look at the indices used by shuffle_out to recalculate DemandedBytes
      // for shuffle_in too.
      TRACE("Incoming ShuffleOp can be reduced to %d bytes, from byte %d\n",
            shuffle_in_window.OutputDemanded().bytes(),
            shuffle_in_window.begin_index());

      if (shuffle_out_window.InputRequiresShift()) {
        // shuffle_in needs to write the selected shuffle_in_demanded_bytes of
        // bytes into its LSBs.
        shuffles_to_shift_.emplace_back(shuffle_in_window);
        // shuffle_out.shuffle will need to be updated to read the modified
        // shuffle_in.
        shuffles_to_read_shifted_.emplace_back(shuffle_out_window);
      }
      // shuffle_in will be reduced.
      demanded_byte_analysis_.RecordOp(shuffle_in,
                                       shuffle_in_window.OutputDemanded());
      return true;
    }
  }
  return false;
}

bool WasmShuffleAnalyzer::CouldLoadPair(const LoadOp& load0,
                                        const LoadOp& load1) const {
  DCHECK_NE(load0, load1);
  // Before adding a candidate, ensure that the loads can be scheduled together
  // and that the accesses are consecutive.
  if (load0.kind != load1.kind) {
    return false;
  }

  // Only try this for loads in the same block.
  if (input_graph().BlockIndexOf(load0) != input_graph().BlockIndexOf(load1)) {
    return false;
  }

  if (load0.base() != load1.base()) {
    return false;
  }
  if (load0.index() != load1.index()) {
    return false;
  }

  // TODO(sparker): Can we use absolute diff..? I guess this would require
  // some permutation after the load.
  if (load1.offset - load0.offset != kSimd128Size) {
    return false;
  }

  // Calculate whether second can be moved next to first by comparing the
  // effects through the sequence of operations in between.
  auto CanReschedule = [this](OpIndex first_idx, OpIndex second_idx) {
    const Operation& second = input_graph().Get(second_idx);
    OpEffects second_effects = second.Effects();
    OpIndex prev_idx = input_graph().PreviousIndex(second_idx);
    bool second_is_trapping_load = second.IsTrappingLoad();

    while (prev_idx != first_idx) {
      const Operation& prev_op = input_graph().Get(prev_idx);
      OpEffects prev_effects = prev_op.Effects();
      bool both_protected = second_is_trapping_load && prev_op.IsTrappingLoad();
      if (both_protected &&
          CannotSwapTrappingLoads(prev_effects, second_effects)) {
        return false;
      } else if (CannotSwapOperations(prev_effects, second_effects)) {
        return false;
      }
      prev_idx = input_graph().PreviousIndex(prev_idx);
    }
    return true;
  };

  OpIndex first_idx = input_graph().Index(load0);
  OpIndex second_idx = input_graph().Index(load1);
  if (first_idx.offset() > second_idx.offset()) {
    std::swap(first_idx, second_idx);
  }

  return CanReschedule(first_idx, second_idx);
}

void WasmShuffleAnalyzer::AddLoadMultipleCandidate(
    const Simd128ShuffleOp& even_shuffle, const Simd128ShuffleOp& odd_shuffle,
    const LoadOp& left, const LoadOp& right,
    Simd128LoadPairDeinterleaveOp::Kind kind) {
  DCHECK_NE(even_shuffle, odd_shuffle);

  if (CouldLoadPair(left, right)) {
    deinterleave_load_candidates_.emplace_back(kind, left, right, even_shuffle,
                                               odd_shuffle);
  }
}

void WasmShuffleAnalyzer::ProcessShuffleOfLoads(const Simd128ShuffleOp& shuffle,
                                                const LoadOp& left,
                                                const LoadOp& right) {
  DCHECK_EQ(shuffle.left(), input_graph().Index(left));
  DCHECK_EQ(shuffle.right(), input_graph().Index(right));
  // We're looking for two shuffle users of these two loads, so ensure the
  // number of users doesn't exceed two.
  if (!left.saturated_use_count.Is(2) || !right.saturated_use_count.Is(2)) {
    return;
  }

  // Look for even and odd shuffles. If we find one, then also check if we've
  // already found another shuffle, performing the opposite action, on the same
  // two loads. And if this is true, then we add a candidate including both
  // shuffles and both loads.
  auto AddShuffle = [this, &shuffle, &left, &right](
                        SmallShuffleVector& shuffles,
                        const SmallShuffleVector& other_shuffles, bool is_even,
                        Simd128LoadPairDeinterleaveOp::Kind kind) {
    shuffles.push_back(&shuffle);
    if (auto other_shuffle = GetOtherShuffleUser(left, right, other_shuffles)) {
      if (is_even) {
        AddLoadMultipleCandidate(shuffle, *other_shuffle.value(), left, right,
                                 kind);
      } else {
        AddLoadMultipleCandidate(*other_shuffle.value(), shuffle, left, right,
                                 kind);
      }
    }
  };

  if (GetDemandedBytes(&shuffle).IsAll()) {
    // Full width shuffles.
    SimdShuffle::ShuffleArray shuffle_bytes;
    std::copy_n(shuffle.shuffle, kSimd128Size, shuffle_bytes.begin());
    auto canonical = SimdShuffle::TryMatchCanonical(shuffle_bytes);
    switch (canonical) {
      default:
        return;
      case SimdShuffle::CanonicalShuffle::kS64x2Even:
        AddShuffle(even_64x2_shuffles_, odd_64x2_shuffles_, true,
                   Simd128LoadPairDeinterleaveOp::Kind::k64x4);
        break;
      case SimdShuffle::CanonicalShuffle::kS64x2Odd:
        AddShuffle(odd_64x2_shuffles_, even_64x2_shuffles_, false,
                   Simd128LoadPairDeinterleaveOp::Kind::k64x4);
        break;
      case SimdShuffle::CanonicalShuffle::kS32x4Even:
        AddShuffle(even_32x4_shuffles_, odd_32x4_shuffles_, true,
                   Simd128LoadPairDeinterleaveOp::Kind::k32x8);
        break;
      case SimdShuffle::CanonicalShuffle::kS32x4Odd:
        AddShuffle(odd_32x4_shuffles_, even_32x4_shuffles_, false,
                   Simd128LoadPairDeinterleaveOp::Kind::k32x8);
        break;
      case SimdShuffle::CanonicalShuffle::kS16x8Even:
        AddShuffle(even_16x8_shuffles_, odd_16x8_shuffles_, true,
                   Simd128LoadPairDeinterleaveOp::Kind::k16x16);
        break;
      case SimdShuffle::CanonicalShuffle::kS16x8Odd:
        AddShuffle(odd_16x8_shuffles_, even_16x8_shuffles_, false,
                   Simd128LoadPairDeinterleaveOp::Kind::k16x16);
        break;
      case SimdShuffle::CanonicalShuffle::kS8x16Even:
        AddShuffle(even_8x16_shuffles_, odd_8x16_shuffles_, true,
                   Simd128LoadPairDeinterleaveOp::Kind::k8x32);
        break;
      case SimdShuffle::CanonicalShuffle::kS8x16Odd:
        AddShuffle(odd_8x16_shuffles_, even_8x16_shuffles_, false,
                   Simd128LoadPairDeinterleaveOp::Kind::k8x32);
        break;
    }
  }
}

// If 'input' is only used by 'shuffle' we may be able to reduce input
// based upon the most-significant byte used by shuffle.
void WasmShuffleAnalyzer::TryReduceFromMSB(OpIndex input,
                                           const Simd128ShuffleOp& shuffle,
                                           const ShuffleSide side) {
  DemandedBytes demanded = GetDemandedBytes(&shuffle);
  std::optional<uint8_t> max = {};

  for (unsigned i = 0; i < demanded.bytes(); ++i) {
    uint8_t index = shuffle.shuffle[i];
    if (InRange(index, side)) {
      max = std::max(static_cast<uint8_t>(index % kSimd128Size),
                     max.value_or(uint8_t{0}));
    }
  }
  if (max) {
    // input can be reduced.
    TRACE("Can reduce Op %d based upon max used index: %d\n", input.id(),
          max.value());
    demanded_byte_analysis_.Add(
        input, DemandedBytes::LowFromMaxShuffleIndex(max.value()));
  }
}

void WasmShuffleAnalyzer::ProcessShuffle(const Simd128ShuffleOp& shuffle) {
  bool reduced_left = false;
  bool reduced_right = false;
  const Operation& left = input_graph().Get(shuffle.left());
  const Operation& right = input_graph().Get(shuffle.right());

#if V8_TARGET_ARCH_ARM64

  if (shuffle.kind != Simd128ShuffleOp::Kind::kI8x16) {
    return;
  }

  // Experimental flags controls the generation of deinterleaving loads.
  if (v8_flags.wasm_deinterleave_loads) {
    auto* load_left = left.TryCast<LoadOp>();
    auto* load_right = right.TryCast<LoadOp>();

    // TODO(sparker): Handle I8x8 shuffles, which will likely mean that the
    // input to the shuffles are Simd128LoadTransformOp::Load64Zero.
    if (load_left && load_right) {
      ProcessShuffleOfLoads(shuffle, *load_left, *load_right);
      return;
    }
  }

  // Experimental flags controls reducing shuffles depending on specific bit
  // patterns.
  if (v8_flags.future_wasm_simd_opt) {
    auto* shuffle_left = left.TryCast<Simd128ShuffleOp>();
    auto* shuffle_right = right.TryCast<Simd128ShuffleOp>();
    if (shuffle_left && shuffle_left->kind == Simd128ShuffleOp::Kind::kI8x16 &&
        shuffle_left->saturated_use_count.Is(1)) {
      reduced_left =
          ProcessShuffleOfShuffle(*shuffle_left, shuffle, ShuffleSide::kLeft);
    }
    if (shuffle_right &&
        shuffle_right->kind == Simd128ShuffleOp::Kind::kI8x16 &&
        shuffle_right->saturated_use_count.Is(1)) {
      reduced_right =
          ProcessShuffleOfShuffle(*shuffle_right, shuffle, ShuffleSide::kRight);
    }
  }

#endif  // V8_TARGET_ARCH_ARM64

  uint8_t max_uses = shuffle.left() == shuffle.right() ? 2 : 1;
  if (!reduced_left && left.saturated_use_count.Is(max_uses)) {
    TryReduceFromMSB(shuffle.left(), shuffle, ShuffleSide::kLeft);
  }
  if (!reduced_right && right.saturated_use_count.Is(max_uses)) {
    TryReduceFromMSB(shuffle.right(), shuffle, ShuffleSide::kRight);
  }
}

#undef TRACE

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