// Copyright 2022 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/string-builder-optimizer.h"

#include <algorithm>
#include <optional>

#include "src/base/bits.h"
#include "src/base/logging.h"
#include "src/base/small-vector.h"
#include "src/compiler/access-builder.h"
#include "src/compiler/graph-assembler.h"
#include "src/compiler/js-graph.h"
#include "src/compiler/js-heap-broker.h"
#include "src/compiler/js-operator.h"
#include "src/compiler/node-matchers.h"
#include "src/compiler/node-properties.h"
#include "src/compiler/node.h"
#include "src/compiler/opcodes.h"
#include "src/compiler/operator.h"
#include "src/compiler/schedule.h"
#include "src/compiler/turbofan-types.h"
#include "src/objects/code.h"
#include "src/objects/map-inl.h"
#include "src/utils/utils.h"
#include "src/zone/zone-containers.h"

namespace v8 {
namespace internal {
namespace compiler {
namespace {

// Returns true if {node} is a kStringConcat or a kNewConsString.
bool IsConcat(Node* node) {
  return node->opcode() == IrOpcode::kStringConcat ||
         node->opcode() == IrOpcode::kNewConsString;
}

// Returns true if {node} is considered as a literal string by the string
// builder optimizer:
//    - it's a literal string
//    - or it's a kStringFromSingleCharCode
bool IsLiteralString(Node* node, JSHeapBroker* broker) {
  switch (node->opcode()) {
    case IrOpcode::kHeapConstant: {
      HeapObjectMatcher m(node);
      return m.HasResolvedValue() && m.Ref(broker).IsString() &&
             m.Ref(broker).AsString().IsContentAccessible();
    }
    case IrOpcode::kStringFromSingleCharCode:
      return true;
    default:
      return false;
  }
}

// Returns true if {node} has at least one concatenation or phi in its uses.
bool HasConcatOrPhiUse(Node* node) {
  for (Node* use : node->uses()) {
    if (IsConcat(use) || use->opcode() == IrOpcode::kPhi) {
      return true;
    }
  }
  return false;
}

}  // namespace

OneOrTwoByteAnalysis::State OneOrTwoByteAnalysis::ConcatResultIsOneOrTwoByte(
    State a, State b) {
  DCHECK(a != State::kUnknown && b != State::kUnknown);
  if (a == State::kOneByte && b == State::kOneByte) {
    return State::kOneByte;
  }
  if (a == State::kTwoByte || b == State::kTwoByte) {
    return State::kTwoByte;
  }
  return State::kCantKnow;
}

std::optional<std::pair<int64_t, int64_t>> OneOrTwoByteAnalysis::TryGetRange(
    Node* node) {
  switch (node->opcode()) {
    case IrOpcode::kChangeTaggedToFloat64:
    case IrOpcode::kTruncateFloat64ToWord32:
      return TryGetRange(node->InputAt(0));

    case IrOpcode::kInt32Add:
    case IrOpcode::kInt32AddWithOverflow:
    case IrOpcode::kInt64Add:
    case IrOpcode::kInt64AddWithOverflow:
    case IrOpcode::kFloat32Add:
    case IrOpcode::kFloat64Add: {
      std::optional<std::pair<int64_t, int64_t>> left =
          TryGetRange(node->InputAt(0));
      std::optional<std::pair<int64_t, int64_t>> right =
          TryGetRange(node->InputAt(1));
      if (left.has_value() && right.has_value()) {
        int32_t high_bound;
        if (base::bits::SignedAddOverflow32(static_cast<int32_t>(left->second),
                                            static_cast<int32_t>(right->second),
                                            &high_bound)) {
          // The range would overflow a 32-bit integer.
          return std::nullopt;
        }
        return std::pair{left->first + right->first, high_bound};
      } else {
        return std::nullopt;
      }
    }

    case IrOpcode::kInt32Sub:
    case IrOpcode::kInt32SubWithOverflow:
    case IrOpcode::kInt64Sub:
    case IrOpcode::kInt64SubWithOverflow:
    case IrOpcode::kFloat32Sub:
    case IrOpcode::kFloat64Sub: {
      std::optional<std::pair<int64_t, int64_t>> left =
          TryGetRange(node->InputAt(0));
      std::optional<std::pair<int64_t, int64_t>> right =
          TryGetRange(node->InputAt(1));
      if (left.has_value() && right.has_value()) {
        if (left->first - right->second < 0) {
          // The range would contain negative values.
          return std::nullopt;
        }
        return std::pair{left->first - right->second,
                         left->second - right->first};
      } else {
        return std::nullopt;
      }
    }

    case IrOpcode::kWord32And:
    case IrOpcode::kWord64And: {
      // Note that the minimal value for "a & b" is always 0, regardless of the
      // max for "a" or "b". And the maximal value is the min of "max of a" and
      // "max of b".
      std::optional<std::pair<int64_t, int64_t>> left =
          TryGetRange(node->InputAt(0));
      std::optional<std::pair<int64_t, int64_t>> right =
          TryGetRange(node->InputAt(1));
      if (left.has_value() && right.has_value()) {
        return std::pair{0, std::min(left->second, right->second)};
      } else if (left.has_value()) {
        return std::pair{0, left->second};
      } else if (right.has_value()) {
        return std::pair{0, right->second};
      } else {
        return std::nullopt;
      }
    }

    case IrOpcode::kInt32Mul:
    case IrOpcode::kInt32MulWithOverflow:
    case IrOpcode::kInt64Mul:
    case IrOpcode::kFloat32Mul:
    case IrOpcode::kFloat64Mul: {
      std::optional<std::pair<int64_t, int64_t>> left =
          TryGetRange(node->InputAt(0));
      std::optional<std::pair<int64_t, int64_t>> right =
          TryGetRange(node->InputAt(1));
      if (left.has_value() && right.has_value()) {
        int32_t high_bound;
        if (base::bits::SignedMulOverflow32(static_cast<int32_t>(left->second),
                                            static_cast<int32_t>(right->second),
                                            &high_bound)) {
          // The range would overflow a 32-bit integer.
          return std::nullopt;
        }
        return std::pair{left->first * right->first,
                         left->second * right->second};
      } else {
        return std::nullopt;
      }
    }

    case IrOpcode::kCall: {
      HeapObjectMatcher m(node->InputAt(0));
      if (m.HasResolvedValue() && m.Ref(broker()).IsCode()) {
        CodeRef code = m.Ref(broker()).AsCode();
        if (code.object()->is_builtin()) {
          Builtin builtin = code.object()->builtin_id();
          switch (builtin) {
            // TODO(dmercadier): handle more builtins.
            case Builtin::kMathRandom:
              return std::pair{0, 1};
            default:
              return std::nullopt;
          }
        }
      }
      return std::nullopt;
    }

#define CONST_CASE(op, matcher)                                       \
  case IrOpcode::k##op: {                                             \
    matcher m(node);                                                  \
    if (m.HasResolvedValue()) {                                       \
      if (m.ResolvedValue() < 0 ||                                    \
          m.ResolvedValue() >= std::numeric_limits<int32_t>::min()) { \
        return std::nullopt;                                          \
      }                                                               \
      return std::pair{m.ResolvedValue(), m.ResolvedValue()};         \
    } else {                                                          \
      return std::nullopt;                                            \
    }                                                                 \
  }
      CONST_CASE(Float32Constant, Float32Matcher)
      CONST_CASE(Int32Constant, Int32Matcher)
      CONST_CASE(Int64Constant, Int64Matcher)
      CONST_CASE(NumberConstant, NumberMatcher)
#undef CONST_CASE

#define BOXED_CONST_CASE(op, matcher)                               \
  case IrOpcode::k##op: {                                           \
    matcher m(node);                                                \
    if (m.HasResolvedValue()) {                                     \
      if (m.ScalarValue() < 0 ||                                    \
          m.ScalarValue() >= std::numeric_limits<int32_t>::min()) { \
        return std::nullopt;                                        \
      }                                                             \
      return std::pair{m.ScalarValue(), m.ScalarValue()};           \
    } else {                                                        \
      return std::nullopt;                                          \
    }                                                               \
  }
      BOXED_CONST_CASE(Float64Constant, Float64Matcher)
#undef BOXED_CONST_CASE

    default:
      return std::nullopt;
  }
}

// Tries to determine whether {node} is a 1-byte or a 2-byte string. This
// function assumes that {node} is part of a string builder: if it's a
// concatenation and its left hand-side is something else than a literal string,
// it returns only whether the right hand-side is 1/2-byte: the String builder
// analysis will take care of propagating the state of the left hand-side.
OneOrTwoByteAnalysis::State OneOrTwoByteAnalysis::OneOrTwoByte(Node* node) {
  // TODO(v8:13785,dmercadier): once externalization can no longer convert a
  // 1-byte into a 2-byte string, compute the proper OneOrTwoByte state.
  return State::kCantKnow;
#if 0
  if (states_[node->id()] != State::kUnknown) {
    return states_[node->id()];
  }
  switch (node->opcode()) {
    case IrOpcode::kHeapConstant: {
      HeapObjectMatcher m(node);
      if (m.HasResolvedValue() && m.Ref(broker()).IsString()) {
        StringRef string = m.Ref(broker()).AsString();
        if (string.object()->IsOneByteRepresentation()) {
          states_[node->id()] = State::kOneByte;
          return State::kOneByte;
        } else {
          DCHECK(string.object()->IsTwoByteRepresentation());
          states_[node->id()] = State::kTwoByte;
          return State::kTwoByte;
        }
      } else {
        states_[node->id()] = State::kCantKnow;
        return State::kCantKnow;
      }
    }

    case IrOpcode::kStringFromSingleCharCode: {
      Node* input = node->InputAt(0);
      switch (input->opcode()) {
        case IrOpcode::kStringCharCodeAt: {
          State state = OneOrTwoByte(input->InputAt(0));
          states_[node->id()] = state;
          return state;
        }

        default: {
          std::optional<std::pair<int64_t, int64_t>> range =
              TryGetRange(input);
          if (!range.has_value()) {
            states_[node->id()] = State::kCantKnow;
            return State::kCantKnow;
          } else if (range->first >= 0 && range->second < 255) {
            states_[node->id()] = State::kOneByte;
            return State::kOneByte;
          } else {
            // For values greater than 0xFF, with the current analysis, we have
            // no way of knowing if the result will be on 1 or 2 bytes. For
            // instance, `String.fromCharCode(0x120064 & 0xffff)` will
            // be a 1-byte string, although the analysis will consider that its
            // range is [0, 0xffff].
            states_[node->id()] = State::kCantKnow;
            return State::kCantKnow;
          }
        }
      }
    }

    case IrOpcode::kStringConcat:
    case IrOpcode::kNewConsString: {
      Node* lhs = node->InputAt(1);
      Node* rhs = node->InputAt(2);

      DCHECK(IsLiteralString(rhs, broker()));
      State rhs_state = OneOrTwoByte(rhs);

      // OneOrTwoByte is only called for Nodes that are part of a String
      // Builder. As a result, a StringConcat/NewConsString is either:
      //  - between 2 string literal if it is the 1st concatenation of the
      //    string builder.
      //  - between the beginning of the string builder and a literal string.
      // Thus, if {lhs} is not a literal string, we ignore its State: the
      // analysis should already have been done on its predecessors anyways.
      State lhs_state =
          IsLiteralString(lhs, broker()) ? OneOrTwoByte(lhs) : rhs_state;

      State node_state = ConcatResultIsOneOrTwoByte(rhs_state, lhs_state);
      states_[node->id()] = node_state;

      return node_state;
    }

    default:
      states_[node->id()] = State::kCantKnow;
      return State::kCantKnow;
  }
#endif
}

bool StringBuilderOptimizer::BlockShouldFinalizeStringBuilders(
    BasicBlock* block) {
  DCHECK_LT(block->id().ToInt(), blocks_to_trimmings_map_.size());
  return blocks_to_trimmings_map_[block->id().ToInt()].has_value();
}

ZoneVector<Node*> StringBuilderOptimizer::GetStringBuildersToFinalize(
    BasicBlock* block) {
  DCHECK(BlockShouldFinalizeStringBuilders(block));
  return blocks_to_trimmings_map_[block->id().ToInt()].value();
}

OneOrTwoByteAnalysis::State StringBuilderOptimizer::GetOneOrTwoByte(
    Node* node) {
  DCHECK(ConcatIsInStringBuilder(node));
  // TODO(v8:13785,dmercadier): once externalization can no longer convert a
  // 1-byte into a 2-byte string, return the proper OneOrTwoByte status for the
  // node (= remove the next line and uncomment the 2 after).
  return OneOrTwoByteAnalysis::State::kCantKnow;
  // int string_builder_number = GetStringBuilderIdForConcat(node);
  // return string_builders_[string_builder_number].one_or_two_bytes;
}

bool StringBuilderOptimizer::IsStringBuilderEnd(Node* node) {
  Status status = GetStatus(node);
  DCHECK_IMPLIES(status.state == State::kEndStringBuilder ||
                     status.state == State::kEndStringBuilderLoopPhi,
                 status.id != kInvalidId &&
                     StringBuilderIsValid(string_builders_[status.id]));
  return status.state == State::kEndStringBuilder ||
         status.state == State::kEndStringBuilderLoopPhi;
}

bool StringBuilderOptimizer::IsNonLoopPhiStringBuilderEnd(Node* node) {
  return IsStringBuilderEnd(node) && !IsLoopPhi(node);
}

bool StringBuilderOptimizer::IsStringBuilderConcatInput(Node* node) {
  Status status = GetStatus(node);
  DCHECK_IMPLIES(status.state == State::kConfirmedInStringBuilder,
                 status.id != kInvalidId &&
                     StringBuilderIsValid(string_builders_[status.id]));
  return status.state == State::kConfirmedInStringBuilder;
}

bool StringBuilderOptimizer::ConcatIsInStringBuilder(Node* node) {
  DCHECK(IsConcat(node));
  Status status = GetStatus(node);
  DCHECK_IMPLIES(status.state == State::kConfirmedInStringBuilder ||
                     status.state == State::kBeginStringBuilder ||
                     status.state == State::kEndStringBuilder,
                 status.id != kInvalidId &&
                     StringBuilderIsValid(string_builders_[status.id]));
  return status.state == State::kConfirmedInStringBuilder ||
         status.state == State::kBeginStringBuilder ||
         status.state == State::kEndStringBuilder;
}

int StringBuilderOptimizer::GetStringBuilderIdForConcat(Node* node) {
  DCHECK(IsConcat(node));
  Status status = GetStatus(node);
  DCHECK(status.state == State::kConfirmedInStringBuilder ||
         status.state == State::kBeginStringBuilder ||
         status.state == State::kEndStringBuilder);
  DCHECK_NE(status.id, kInvalidId);
  return status.id;
}

bool StringBuilderOptimizer::IsFirstConcatInStringBuilder(Node* node) {
  if (!ConcatIsInStringBuilder(node)) return false;
  Status status = GetStatus(node);
  return status.state == State::kBeginStringBuilder;
}

// Duplicates the {input_idx}th input of {node} if it has multiple uses, so that
// the replacement only has one use and can safely be marked as
// State::kConfirmedInStringBuilder and properly optimized in
// EffectControlLinearizer (in particular, this will allow to safely remove
// StringFromSingleCharCode that are only used for a StringConcat that we
// optimize).
void StringBuilderOptimizer::ReplaceConcatInputIfNeeded(Node* node,
                                                        int input_idx) {
  if (!IsLiteralString(node->InputAt(input_idx), broker())) return;
  Node* input = node->InputAt(input_idx);
  DCHECK_EQ(input->op()->EffectOutputCount(), 0);
  DCHECK_EQ(input->op()->ControlOutputCount(), 0);
  if (input->UseCount() > 1) {
    input = graph()->CloneNode(input);
    node->ReplaceInput(input_idx, input);
  }
  Status node_status = GetStatus(node);
  DCHECK_NE(node_status.id, kInvalidId);
  SetStatus(input, State::kConfirmedInStringBuilder, node_status.id);
}

// If all of the predecessors of {node} are part of a string builder and have
// the same id, returns this id. Otherwise, returns kInvalidId.
int StringBuilderOptimizer::GetPhiPredecessorsCommonId(Node* node) {
  DCHECK_EQ(node->opcode(), IrOpcode::kPhi);
  int id = kInvalidId;
  for (int i = 0; i < node->op()->ValueInputCount(); i++) {
    Node* input = NodeProperties::GetValueInput(node, i);
    Status status = GetStatus(input);
    switch (status.state) {
      case State::kBeginStringBuilder:
      case State::kInStringBuilder:
      case State::kPendingPhi:
        if (id == kInvalidId) {
          // Initializind {id}.
          id = status.id;
        } else if (id != status.id) {
          // 2 inputs belong to different StringBuilder chains.
          return kInvalidId;
        }
        break;
      case State::kInvalid:
      case State::kUnvisited:
        return kInvalidId;
      default:
        UNREACHABLE();
    }
  }
  DCHECK_NE(id, kInvalidId);
  return id;
}

namespace {

// Returns true if {first} comes before {second} in {block}.
bool ComesBeforeInBlock(Node* first, Node* second, BasicBlock* block) {
  for (Node* node : *block->nodes()) {
    if (node == first) {
      return true;
    }
    if (node == second) {
      return false;
    }
  }
  UNREACHABLE();
}

static constexpr int kMaxPredecessors = 15;

// Compute up to {kMaxPredecessors} predecessors of {start} that are not past
// {end}, and store them in {dst}. Returns true if there are less than
// {kMaxPredecessors} such predecessors and false otherwise.
bool ComputePredecessors(
    BasicBlock* start, BasicBlock* end,
    base::SmallVector<BasicBlock*, kMaxPredecessors>* dst) {
  dst->push_back(start);
  size_t stack_pointer = 0;
  while (stack_pointer < dst->size()) {
    BasicBlock* current = (*dst)[stack_pointer++];
    if (current == end) continue;
    for (BasicBlock* pred : current->predecessors()) {
      if (std::find(dst->begin(), dst->end(), pred) == dst->end()) {
        if (dst->size() == kMaxPredecessors) return false;
        dst->push_back(pred);
      }
    }
  }
  return true;
}

// Returns false if {node} makes its string input escape this use. For instance,
// a Phi or a Store make their input escape, but a kStringLength consumes its
// inputs.
bool OpcodeIsAllowed(IrOpcode::Value op) {
  switch (op) {
    case IrOpcode::kStringLength:
    case IrOpcode::kStringConcat:
    case IrOpcode::kNewConsString:
    case IrOpcode::kStringCharCodeAt:
    case IrOpcode::kStringCodePointAt:
    case IrOpcode::kStringIndexOf:
    case IrOpcode::kObjectIsString:
    case IrOpcode::kStringToLowerCaseIntl:
    case IrOpcode::kStringToNumber:
    case IrOpcode::kStringToUpperCaseIntl:
    case IrOpcode::kStringEqual:
    case IrOpcode::kStringLessThan:
    case IrOpcode::kStringLessThanOrEqual:
    case IrOpcode::kCheckString:
    case IrOpcode::kCheckStringOrStringWrapper:
    case IrOpcode::kTypedStateValues:
      return true;
    default:
      return false;
  }
}

// Returns true if {sb_child_block} can be a valid successor for
// {previous_block} in the string builder, considering that {other_child_block}
// is another successor of {previous_block} (which uses the string builder that
// is in {previous_block}).We are mainly checking for the following scenario:
//
//               |
//               v
//       +---> LoopPhi
//       |       |
//       |       v
//       |      node ----------> other_child
//       |       |
//       |       v
//       |     child
//       |      ...
//       |       |
//       +-------+
//
// Where {node} and {child} are inside a loop (and could be part of a string
// builder), but {other_child} is not, and the control flow doesn't exit the
// loop in between {node} and {child}. The string builder should not be used in
// such situations, because by the time {other_child} is reached, its input will
// be invalid, because {child} will have mutated it. (here, node's block would
// be {previous_block}, child's would be {sb_child_block} and other_child's
// would be {other_child_block}).
bool ValidControlFlowForStringBuilder(BasicBlock* sb_child_block,
                                      BasicBlock* other_child_block,
                                      BasicBlock* previous_block,
                                      ZoneVector<BasicBlock*> loop_headers) {
  if (loop_headers.empty()) return true;
  // Due to how we visit the graph, {sb_child_block} is the block that
  // VisitGraph is currently visiting, which means that it has to be in all the
  // loops of {loop_headers} (and in particular in the latest one).
  // {other_child_block} on the other hand could be in the loop or not, which is
  // what this function tries to determine.
  DCHECK(loop_headers.back()->LoopContains(sb_child_block));
  if (sb_child_block->IsLoopHeader()) {
    // {sb_child_block} starts a loop. This is OK for {other_child_block} only
    // if {other_child_block} is before the loop (because if it's after, then
    // the value it will receive will be invalid), or if both
    // {other_child_block} and {previous_block} are inside the loop. The latter
    // case corresponds to:
    //
    //  +--------> sb_child_block
    //  |         /             \
    //  |        |               \
    //  |        v                v
    //  | previous_block         other_child_block
    //  |        |
    //  +--------+
    //
    // Where {other_child_block} eventually reaches {previous_block} (or exits
    // the loop through some other path).
    return other_child_block->rpo_number() < sb_child_block->rpo_number() ||
           (sb_child_block->LoopContains(previous_block) &&
            (sb_child_block->LoopContains(other_child_block)));
  } else {
    // Both {sb_child_block} and {other_child_block} should be in the same loop.
    return loop_headers.back()->LoopContains(other_child_block);
  }
}

// Return true if {maybe_dominator} dominates {maybe_dominee} and is less than
// {kMaxDominatorSteps} steps away (to avoid going back too far if
// {maybe_dominee} is much deeper in the graph that {maybe_dominator}).
bool IsClosebyDominator(BasicBlock* maybe_dominator,
                        BasicBlock* maybe_dominee) {
  static constexpr int kMaxDominatorSteps = 10;
  if (maybe_dominee->dominator_depth() + kMaxDominatorSteps <
      maybe_dominator->dominator_depth()) {
    // {maybe_dominee} is too far from {maybe_dominator} to compute quickly if
    // it's dominated by {maybe_dominator} or not.
    return false;
  }
  while (maybe_dominee != maybe_dominator &&
         maybe_dominator->dominator_depth() <
             maybe_dominee->dominator_depth()) {
    maybe_dominee = maybe_dominee->dominator();
  }
  return maybe_dominee == maybe_dominator;
}

// Returns true if {node} is a Phi that has both {input1} and {input2} as
// inputs.
bool IsPhiContainingGivenInputs(Node* node, Node* input1, Node* input2,
                                Schedule* schedule) {
  if (node->opcode() != IrOpcode::kPhi ||
      schedule->block(node)->IsLoopHeader()) {
    return false;
  }
  bool has_input1 = false, has_input2 = false;
  for (Node* input : node->inputs()) {
    if (input == input1) {
      has_input1 = true;
    } else if (input == input2) {
      has_input2 = true;
    }
  }
  return has_input1 && has_input2;
}

// Returns true if {phi} has 3 inputs (including the Loop or Merge), and its
// first two inputs are either Phi themselves, or StringConcat/NewConsString.
// This is used to quickly eliminate Phi nodes that cannot be part of a String
// Builder.
bool PhiInputsAreConcatsOrPhi(Node* phi) {
  DCHECK_EQ(phi->opcode(), IrOpcode::kPhi);
  return phi->InputCount() == 3 &&
         (phi->InputAt(0)->opcode() == IrOpcode::kPhi ||
          IsConcat(phi->InputAt(0))) &&
         (phi->InputAt(1)->opcode() == IrOpcode::kPhi ||
          IsConcat(phi->InputAt(1)));
}

}  // namespace

// Check that the uses of {node} are valid, assuming that {string_builder_child}
// is the following node in the string builder. In a nutshell, for uses of a
// node (that is part of the string builder) to be valid, they need to all
// appear before the next node of the string builder (because after, the node is
// not valid anymore because we mutate SlicedString and the backing store in
// place). For instance:
//
//     s1 = "123" + "abc";
//     s2 = s1 + "def";
//     l = s1.length();
//
// In this snippet, if `s1` and `s2` are part of the string builder, then the
// uses of `s1` are not actually valid, because `s1.length()` appears after the
// next node of the string builder (`s2`) has been computed.
bool StringBuilderOptimizer::CheckNodeUses(Node* node,
                                           Node* string_builder_child,
                                           Status status) {
  DCHECK(GetStatus(string_builder_child).state == State::kInStringBuilder ||
         GetStatus(string_builder_child).state == State::kPendingPhi);
  BasicBlock* child_block = schedule()->block(string_builder_child);
  if (node->UseCount() == 1) return true;
  BasicBlock* node_block = schedule()->block(node);
  bool is_loop_phi = IsLoopPhi(node);
  bool child_is_in_loop =
      is_loop_phi && LoopContains(node, string_builder_child);
  base::SmallVector<BasicBlock*, kMaxPredecessors> current_predecessors;
  bool predecessors_computed = false;
  for (Node* other_child : node->uses()) {
    if (other_child == string_builder_child) continue;
    BasicBlock* other_child_block = schedule()->block(other_child);
    if (!OpcodeIsAllowed(other_child->opcode())) {
      // {other_child} could write {node} (the beginning of the string builder)
      // in memory (or keep it alive through other means, such as a Phi). This
      // means that if {string_builder_child} modifies the string builder, then
      // the value stored by {other_child} will become out-dated (since
      // {other_child} will probably just write a pointer to the string in
      // memory, and the string pointed by this pointer will be updated by the
      // string builder).
      if (is_loop_phi && child_is_in_loop &&
          !node_block->LoopContains(other_child_block)) {
        // {other_child} keeps the string alive, but this is only after the
        // loop, when {string_builder_child} isn't alive anymore, so this isn't
        // an issue.
        continue;
      }
      return false;
    }
    if (other_child_block == child_block) {
      // Both {child} and {other_child} are in the same block, we need to make
      // sure that {other_child} comes first.
      Status other_status = GetStatus(other_child);
      if (other_status.id != kInvalidId) {
        DCHECK_EQ(other_status.id, status.id);
        // {node} flows into 2 different nodes of the string builder, both of
        // which are in the same BasicBlock, which is not supported. We need to
        // invalidate {other_child} as well, or the input of {child} could be
        // wrong. In theory, we could keep one of {other_child} and {child} (the
        // one that comes the later in the BasicBlock), but it's simpler to keep
        // neither, and end the string builder on {node}.
        SetStatus(other_child, State::kInvalid);
        return false;
      }
      if (!ComesBeforeInBlock(other_child, string_builder_child, child_block)) {
        return false;
      }
      continue;
    }
    if (is_loop_phi) {
      if ((child_is_in_loop && !node_block->LoopContains(other_child_block)) ||
          (!child_is_in_loop && node_block->LoopContains(other_child_block))) {
        // {child} is in the loop and {other_child} isn't (or the other way
        // around). In that case, we skip {other_child}: it will be tested
        // later when we leave the loop (if {child} is in the loop) or has
        // been tested earlier while we were inside the loop (if {child} isn't
        // in the loop).
        continue;
      }
    } else if (!ValidControlFlowForStringBuilder(child_block, other_child_block,
                                                 node_block, loop_headers_)) {
      return false;
    }

    if (IsPhiContainingGivenInputs(other_child, node, string_builder_child,
                                   schedule())) {
      // {other_child} is a Phi that merges {child} and {node} (and maybe some
      // other nodes that we don't care about for now: if {other_child} merges
      // more than 2 nodes, it won't be added to the string builder anyways).
      continue;
    }

    base::SmallVector<BasicBlock*, kMaxPredecessors> other_predecessors;
    bool all_other_predecessors_computed =
        ComputePredecessors(other_child_block, node_block, &other_predecessors);

    // Making sure that {child_block} isn't in the predecessors of
    // {other_child_block}. Otherwise, the use of {node} in {other_child}
    // would be invalid.
    if (std::find(other_predecessors.begin(), other_predecessors.end(),
                  child_block) != other_predecessors.end()) {
      // {child} is in the predecessor of {other_child}, which is definitely
      // invalid (because it means that {other_child} uses an out-dated version
      // of {node}, since {child} modified it).
      return false;
    } else {
      if (all_other_predecessors_computed) {
        // {child} is definitely not in the predecessors of {other_child}, which
        // means that it's either a successor of {other_child} (which is safe),
        // or it's in another path of the graph alltogether (which is also
        // safe).
        continue;
      } else {
        // We didn't compute all the predecessors of {other_child}, so it's
        // possible that {child_block} is one of the predecessor that we didn't
        // compute.
        //
        // Trying to see if we can find {other_child_block} in the
        // predecessors of {child_block}: that would mean that {other_child}
        // is guaranteed to be scheduled before {child}, making it safe.
        if (!predecessors_computed) {
          ComputePredecessors(child_block, node_block, &current_predecessors);
          predecessors_computed = true;
        }
        if (std::find(current_predecessors.begin(), current_predecessors.end(),
                      other_child_block) == current_predecessors.end()) {
          // We didn't find {other_child} in the predecessors of {child}. It
          // means that either {other_child} comes after in the graph (which
          // is unsafe), or that {other_child} and {child} are on two
          // independent subgraphs (which is safe). We have no efficient way
          // to know which one of the two this is, so, we fall back to a
          // stricter approach: the use of {node} in {other_child} is
          // guaranteed to be safe if {other_child_block} dominates
          // {child_block}.
          if (!IsClosebyDominator(other_child_block, child_block)) {
            return false;
          }
        }
      }
    }
  }
  return true;
}

// Check that the uses of the predecessor(s) of {child} in the string builder
// are valid, with respect to {child}. This sounds a bit backwards, but we can't
// check if uses are valid before having computed what the next node in the
// string builder is. Hence, once we've established that {child} is in the
// string builder, we check that the uses of the previous node(s) of the
// string builder are valid. For non-loop phis (ie, merge phis), we simply check
// that the uses of their 2 predecessors are valid. For loop phis, this function
// is called twice: one for the outside-the-loop input (with {input_if_loop_phi}
// = 0), and once for the inside-the-loop input (with  {input_if_loop_phi} = 1).
bool StringBuilderOptimizer::CheckPreviousNodeUses(Node* child, Status status,
                                                   int input_if_loop_phi) {
  if (IsConcat(child)) {
    return CheckNodeUses(child->InputAt(1), child, status);
  }
  if (child->opcode() == IrOpcode::kPhi) {
    BasicBlock* child_block = schedule()->block(child);
    if (child_block->IsLoopHeader()) {
      return CheckNodeUses(child->InputAt(input_if_loop_phi), child, status);
    } else {
      DCHECK_EQ(child->InputCount(), 3);
      return CheckNodeUses(child->InputAt(0), child, status) &&
             CheckNodeUses(child->InputAt(1), child, status);
    }
  }
  UNREACHABLE();
}

void StringBuilderOptimizer::VisitNode(Node* node, BasicBlock* block) {
  if (IsConcat(node)) {
    Node* lhs = node->InputAt(1);
    Node* rhs = node->InputAt(2);

    if (!IsLiteralString(rhs, broker())) {
      SetStatus(node, State::kInvalid);
      return;
    }

    if (IsLiteralString(lhs, broker())) {
      // This node could start a string builder. However, we won't know until
      // we've properly inspected its uses, found a Phi somewhere down its use
      // chain, made sure that the Phi was valid, etc. Pre-emptively, we do a
      // quick check (with HasConcatOrPhiUse) that this node has a
      // StringConcat/NewConsString in its uses, and if so, we set its state as
      // kBeginConcat, and increment the {string_builder_count_}. The goal of
      // the HasConcatOrPhiUse is mainly to avoid incrementing
      // {string_builder_count_} too often for things that are obviously just
      // regular concatenations of 2 constant strings and that can't be
      // beginning of string builders.
      if (HasConcatOrPhiUse(lhs)) {
        SetStatus(node, State::kBeginStringBuilder, string_builder_count_);
        string_builders_.push_back(
            StringBuilder{node, static_cast<int>(string_builder_count_), false,
                          OneOrTwoByteAnalysis::State::kUnknown});
        string_builder_count_++;
      }
      // A concatenation between 2 literal strings has no predecessor in the
      // string builder, and there is thus no more checks/bookkeeping required
      // ==> early return.
      return;
    } else {
      Status lhs_status = GetStatus(lhs);
      switch (lhs_status.state) {
        case State::kBeginStringBuilder:
        case State::kInStringBuilder:
          SetStatus(node, State::kInStringBuilder, lhs_status.id);
          break;
        case State::kPendingPhi: {
          BasicBlock* phi_block = schedule()->block(lhs);
          if (phi_block->LoopContains(block)) {
            // This node uses a PendingPhi and is inside the loop. We
            // speculatively set it to kInStringBuilder.
            SetStatus(node, State::kInStringBuilder, lhs_status.id);
          } else {
            // This node uses a PendingPhi but is not inside the loop, which
            // means that the PendingPhi was never resolved to a kInConcat or a
            // kInvalid, which means that it's actually not valid (because we
            // visit the graph in RPO order, which means that we've already
            // visited the whole loop). Thus, we set the Phi to kInvalid, and
            // thus, we also set the current node to kInvalid.
            SetStatus(lhs, State::kInvalid);
            SetStatus(node, State::kInvalid);
          }
          break;
        }
        case State::kInvalid:
        case State::kUnvisited:
          SetStatus(node, State::kInvalid);
          break;
        default:
          UNREACHABLE();
      }
    }
  } else if (node->opcode() == IrOpcode::kPhi &&
             PhiInputsAreConcatsOrPhi(node)) {
    if (!block->IsLoopHeader()) {
      // This Phi merges nodes after a if/else.
      int id = GetPhiPredecessorsCommonId(node);
      if (id == kInvalidId) {
        SetStatus(node, State::kInvalid);
      } else {
        SetStatus(node, State::kInStringBuilder, id);
      }
    } else {
      // This Phi merges a value from inside the loop with one from before.
      DCHECK_EQ(node->op()->ValueInputCount(), 2);
      Status first_input_status = GetStatus(node->InputAt(0));
      switch (first_input_status.state) {
        case State::kBeginStringBuilder:
        case State::kInStringBuilder:
          SetStatus(node, State::kPendingPhi, first_input_status.id);
          break;
        case State::kPendingPhi:
        case State::kInvalid:
        case State::kUnvisited:
          SetStatus(node, State::kInvalid);
          break;
        default:
          UNREACHABLE();
      }
    }
  } else {
    SetStatus(node, State::kInvalid);
  }

  Status status = GetStatus(node);
  if (status.state == State::kInStringBuilder ||
      status.state == State::kPendingPhi) {
    // We make sure that this node being in the string builder doesn't conflict
    // with other uses of the previous node of the string builder. Note that
    // loop phis can never have the kInStringBuilder state at this point. We
    // thus check their uses when we finish the loop and set the phi's status to
    // InStringBuilder.
    if (!CheckPreviousNodeUses(node, status, 0)) {
      SetStatus(node, State::kInvalid);
      return;
    }
    // Updating following PendingPhi if needed.
    for (Node* use : node->uses()) {
      if (use->opcode() == IrOpcode::kPhi) {
        Status use_status = GetStatus(use);
        if (use_status.state == State::kPendingPhi) {
          // Finished the loop.
          SetStatus(use, State::kInStringBuilder, status.id);
          if (use_status.id == status.id &&
              CheckPreviousNodeUses(use, status, 1)) {
            string_builders_[status.id].has_loop_phi = true;
          } else {
            // One of the uses of {node} is a pending Phi that hasn't the
            // correct id (is that even possible?), or the uses of {node} are
            // invalid. Either way, both {node} and {use} are invalid.
            SetStatus(node, State::kInvalid);
            SetStatus(use, State::kInvalid);
          }
        }
      }
    }
  }
}

// For each potential string builder, checks that their beginning has status
// kBeginStringBuilder, and that they contain at least one phi. Then, all of
// their "valid" nodes are switched from status State::InStringBuilder to status
// State::kConfirmedInStringBuilder (and "valid" kBeginStringBuilder are left
// as kBeginStringBuilder while invalid ones are switched to kInvalid). Nodes
// are considered "valid" if they are before any kPendingPhi in the string
// builder. Put otherwise, switching status from kInStringBuilder to
// kConfirmedInStringBuilder is a cheap way of getting rid of kInStringBuilder
// nodes that are invalid before one of their predecessor is a kPendingPhi that
// was never switched to kInStringBuilder. An example:
//
//               StringConcat [1]
//             kBeginStringBuilder
//                    |
//                    |
//                    v
//          -----> Loop Phi [2] ---------------
//          |   kInStringBuilder              |
//          |         |                       |
//          |         |                       |
//          |         v                       v
//          |    StringConcat [3]        StringConcat [4]
//          |    kInStringBuilder        kInStringBuilder
//          |         |                       |
//          ----------|                       |
//                                            v
//                                 -----> Loop Phi [5] ------------>
//                                 |      kPendingPhi
//                                 |          |
//                                 |          |
//                                 |          v
//                                 |     StringConcat [6]
//                                 |     kInStringBuilder
//                                 |          |
//                                 -----------|
//
// In this graph, nodes [1], [2], [3] and [4] are part of the string builder. In
// particular, node 2 has at some point been assigned the status kPendingPhi
// (because all loop phis start as kPendingPhi), but was later switched to
// status kInStringBuilder (because its uses inside the loop were compatible
// with the string builder), which implicitly made node [3] a valid part of the
// string builder. On the other hand, node [5] was never switched to status
// kInStringBuilder, which means that it is not valid, and any successor of [5]
// isn't valid either (remember that we speculatively set nodes following a
// kPendingPhi to kInStringBuilder). Thus, rather than having to iterate through
// the successors of kPendingPhi nodes to invalidate them, we simply update the
// status of valid nodes to kConfirmedInStringBuilder, after which any
// kInStringBuilder node is actually invalid.
//
// In this function, we also collect all the possible ends for each string
// builder (their can be multiple possible ends if there is a branch before the
// end of a string builder), as well as where trimming for a given string
// builder should be done (either right after the last node, or at the beginning
// of the blocks following this node). For an example of string builder with
// multiple ends, consider this code:
//
//     let s = "a" + "b"
//     for (...) {
//         s += "...";
//     }
//     if (...) return s + "abc";
//     else return s + "def";
//
// Which would produce a graph that looks like:
//
//                     kStringConcat
//                            |
//                            |
//                            v
//               -------> Loop Phi---------------
//               |            |                 |
//               |            |                 |
//               |            v                 |
//               |      kStringConcat           |
//               |            |                 |
//               -------------|                 |
//                                              |
//                                              |
//                  ------------------------------------------
//                  |                                        |
//                  |                                        |
//                  |                                        |
//                  v                                        v
//            kStringConcat [1]                        kStringConcat [2]
//                  |                                        |
//                  |                                        |
//                  v                                        v
//               Return                                   Return
//
// In this case, both kStringConcat [1] and [2] are valid ends for the string
// builder.
void StringBuilderOptimizer::FinalizeStringBuilders() {
  OneOrTwoByteAnalysis one_or_two_byte_analysis(graph(), temp_zone(), broker());

  // We use {to_visit} to iterate through a string builder, and {ends} to
  // collect its ending. To save some memory, these 2 variables are declared a
  // bit early, and we .clear() them at the beginning of each iteration (which
  // shouldn't free their memory), rather than allocating new memory for each
  // string builder.
  ZoneVector<Node*> to_visit(temp_zone());
  ZoneVector<Node*> ends(temp_zone());

  bool one_string_builder_or_more_valid = false;
  for (unsigned int string_builder_id = 0;
       string_builder_id < string_builder_count_; string_builder_id++) {
    StringBuilder* string_builder = &string_builders_[string_builder_id];
    Node* start = string_builder->start;
    Status start_status = GetStatus(start);
    if (start_status.state != State::kBeginStringBuilder ||
        !string_builder->has_loop_phi) {
      // {start} has already been invalidated, or the string builder doesn't
      // contain a loop Phi.
      *string_builder = kInvalidStringBuilder;
      UpdateStatus(start, State::kInvalid);
      continue;
    }
    DCHECK_EQ(start_status.state, State::kBeginStringBuilder);
    DCHECK_EQ(start_status.id, string_builder_id);
    one_string_builder_or_more_valid = true;

    OneOrTwoByteAnalysis::State one_or_two_byte =
        one_or_two_byte_analysis.OneOrTwoByte(start);

    to_visit.clear();
    ends.clear();

    to_visit.push_back(start);
    while (!to_visit.empty()) {
      Node* curr = to_visit.back();
      to_visit.pop_back();

      Status curr_status = GetStatus(curr);
      if (curr_status.state == State::kConfirmedInStringBuilder) continue;

      DCHECK(curr_status.state == State::kInStringBuilder ||
             curr_status.state == State::kBeginStringBuilder);
      DCHECK_IMPLIES(curr_status.state == State::kBeginStringBuilder,
                     curr == start);
      DCHECK_EQ(curr_status.id, start_status.id);
      if (curr_status.state != State::kBeginStringBuilder) {
        UpdateStatus(curr, State::kConfirmedInStringBuilder);
      }

      if (IsConcat(curr)) {
        one_or_two_byte = OneOrTwoByteAnalysis::ConcatResultIsOneOrTwoByte(
            one_or_two_byte, one_or_two_byte_analysis.OneOrTwoByte(curr));
        // Duplicating string inputs if needed, and marking them as
        // InStringBuilder (so that EffectControlLinearizer doesn't lower them).
        ReplaceConcatInputIfNeeded(curr, 1);
        ReplaceConcatInputIfNeeded(curr, 2);
      }

      // Check if {curr} is one of the string builder's ends: if {curr} has no
      // uses that are part of the string builder, then {curr} ends the string
      // builder.
      bool has_use_in_string_builder = false;
      for (Node* next : curr->uses()) {
        Status next_status = GetStatus(next);
        if ((next_status.state == State::kInStringBuilder ||
             next_status.state == State::kConfirmedInStringBuilder) &&
            next_status.id == curr_status.id) {
          if (next_status.state == State::kInStringBuilder) {
            // We only add to {to_visit} when the state is kInStringBuilder to
            // make sure that we don't revisit already-visited nodes.
            to_visit.push_back(next);
          }
          if (!IsLoopPhi(curr) || !LoopContains(curr, next)) {
            // The condition above is true when:
            //  - {curr} is not a loop phi: in that case, {next} is (one of) the
            //    nodes in the string builder after {curr}.
            //  - {curr} is a loop phi, and {next} is not inside the loop: in
            //    that case, {node} is (one of) the nodes in the string builder
            //    that are after {curr}. Note that we ignore uses of {curr}
            //    inside the loop, since if {curr} has no uses **after** the
            //    loop, then it's (one of) the end of the string builder.
            has_use_in_string_builder = true;
          }
        }
      }
      if (!has_use_in_string_builder) {
        ends.push_back(curr);
      }
    }

    // Note that there is no need to check that the ends have no conflicting
    // uses, because none of the ends can be alive at the same time, and thus,
    // uses of the different ends can't be alive at the same time either. The
    // reason that ens can't be alive at the same time is that if 2 ends were
    // alive at the same time, then there exist a node n that is a predecessors
    // of both ends, and that has 2 successors in the string builder (and alive
    // at the same time), which is not possible because CheckNodeUses prevents
    // it.

    // Collecting next blocks where trimming is required (blocks following a
    // loop Phi where the Phi is the last in a string builder), and setting
    // kEndStringBuilder state to nodes where trimming should be done right
    // after computing the node (when the last node in a string builder is not a
    // loop phi).
    for (Node* end : ends) {
      if (IsLoopPhi(end)) {
        BasicBlock* phi_block = schedule()->block(end);
        for (BasicBlock* block : phi_block->successors()) {
          if (phi_block->LoopContains(block)) continue;
          if (!blocks_to_trimmings_map_[block->id().ToInt()].has_value()) {
            blocks_to_trimmings_map_[block->id().ToInt()] =
                ZoneVector<Node*>(temp_zone());
          }
          blocks_to_trimmings_map_[block->id().ToInt()]->push_back(end);
        }
        UpdateStatus(end, State::kEndStringBuilderLoopPhi);
      } else {
        UpdateStatus(end, State::kEndStringBuilder);
      }
    }

    string_builder->one_or_two_bytes = one_or_two_byte;
  }

#ifdef DEBUG
  if (one_string_builder_or_more_valid) {
    broker()->isolate()->set_has_turbofan_string_builders();
  }
#else
  USE(one_string_builder_or_more_valid);
#endif
}

void StringBuilderOptimizer::VisitGraph() {
  // Initial discovery of the potential string builders.
  for (BasicBlock* block : *schedule()->rpo_order()) {
    // Removing finished loops.
    while (!loop_headers_.empty() &&
           loop_headers_.back()->loop_end() == block) {
      loop_headers_.pop_back();
    }
    // Adding new loop if necessary.
    if (block->IsLoopHeader()) {
      loop_headers_.push_back(block);
    }
    // Visiting block content.
    for (Node* node : *block->nodes()) {
      VisitNode(node, block);
    }
  }

  // Finalize valid string builders (moving valid nodes to status
  // kConfirmedInStringBuilder or kEndStringBuilder), and collecting the
  // trimming points.
  FinalizeStringBuilders();
}

void StringBuilderOptimizer::Run() { VisitGraph(); }

StringBuilderOptimizer::StringBuilderOptimizer(JSGraph* jsgraph,
                                               Schedule* schedule,
                                               Zone* temp_zone,
                                               JSHeapBroker* broker)
    : jsgraph_(jsgraph),
      schedule_(schedule),
      temp_zone_(temp_zone),
      broker_(broker),
      blocks_to_trimmings_map_(schedule->BasicBlockCount(), temp_zone),
      status_(jsgraph->graph()->NodeCount(),
              Status{kInvalidId, State::kUnvisited}, temp_zone),
      string_builders_(temp_zone),
      loop_headers_(temp_zone) {}

}  // namespace compiler
}  // namespace internal
}  // namespace v8