// 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/common-utils.h"

#include "src/compiler/js-heap-broker.h"
#include "src/heap/local-heap.h"
#include "src/objects/string-inl.h"

namespace v8::internal::compiler::utils {

// Concatenates {left} and {right}. The result is fairly similar to creating a
// new ConsString with {left} and {right} and then flattening it, which we don't
// do because String::Flatten does not support background threads. Rather than
// implementing a full String::Flatten for background threads, we preferred to
// implement this Concatenate function, which, unlike String::Flatten, doesn't
// need to replace ConsStrings by ThinStrings.
MaybeHandle<String> ConcatenateStrings(Handle<String> left,
                                       Handle<String> right,
                                       JSHeapBroker* broker) {
  if (left->length() == 0) return right;
  if (right->length() == 0) return left;

  if (left->length() + right->length() > String::kMaxLength) {
    return {};
  }

  // Repeated concatenations have a quadratic cost (eg, "s+=a;s+=b;s+=c;...").
  // Rather than doing static analysis to determine how many concatenations we
  // there are and how many uses the result of each concatenation have, we
  // generate ConsString when the result of the concatenation would have more
  // than {kConstantStringFlattenMaxSize} characters, and flattened SeqString
  // otherwise.
  // TODO(dmercadier): ideally, we would like to get rid of this constant, and
  // always flatten. This requires some care to avoid the quadratic worst-case.
  constexpr int32_t kConstantStringFlattenMaxSize = 100;

  int32_t length = left->length() + right->length();
  if (length > kConstantStringFlattenMaxSize) {
    return broker->local_isolate_or_isolate()
        ->factory()
        ->NewConsString(left, right, AllocationType::kOld)
        .ToHandleChecked();
  }

  // If one of the string is not in readonly space, then we need a
  // SharedStringAccessGuardIfNeeded before accessing its content.
  bool require_guard = SharedStringAccessGuardIfNeeded::IsNeeded(
                           *left, broker->local_isolate_or_isolate()) ||
                       SharedStringAccessGuardIfNeeded::IsNeeded(
                           *right, broker->local_isolate_or_isolate());

  // Check string representation of both strings. This does not require the
  // SharedStringAccessGuardIfNeeded as the representation is stable.
  const bool result_is_one_byte_string =
      left->IsOneByteRepresentation() && right->IsOneByteRepresentation();

  if (result_is_one_byte_string) {
    // {left} and {right} are 1-byte ==> the result will be 1-byte.
    // Note that we need a canonical handle, because some callers
    // (JSNativeContextSpecialization) use the handle's address,
    //  which is meaningless if the handle isn't canonical.
    Handle<SeqOneByteString> flat = broker->CanonicalPersistentHandle(
        broker->local_isolate_or_isolate()
            ->factory()
            ->NewRawOneByteString(length, AllocationType::kOld)
            .ToHandleChecked());
    DisallowGarbageCollection no_gc;
    SharedStringAccessGuardIfNeeded access_guard(
        require_guard ? broker->local_isolate_or_isolate() : nullptr);
    String::WriteToFlat(*left, flat->GetChars(no_gc, access_guard), 0,
                        left->length(), access_guard);
    String::WriteToFlat(*right,
                        flat->GetChars(no_gc, access_guard) + left->length(), 0,
                        right->length(), access_guard);
    return flat;
  }

  // One (or both) of {left} and {right} is 2-byte ==> the result will be
  // 2-byte.
  Handle<SeqTwoByteString> flat = broker->CanonicalPersistentHandle(
      broker->local_isolate_or_isolate()
          ->factory()
          ->NewRawTwoByteString(length, AllocationType::kOld)
          .ToHandleChecked());
  DisallowGarbageCollection no_gc;
  SharedStringAccessGuardIfNeeded access_guard(
      require_guard ? broker->local_isolate_or_isolate() : nullptr);
  String::WriteToFlat(*left, flat->GetChars(no_gc, access_guard), 0,
                      left->length(), access_guard);
  String::WriteToFlat(*right,
                      flat->GetChars(no_gc, access_guard) + left->length(), 0,
                      right->length(), access_guard);
  return flat;
}

}  // namespace v8::internal::compiler::utils