// Copyright 2017 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/ic/handler-configuration.h"

#include "src/codegen/code-factory.h"
#include "src/ic/handler-configuration-inl.h"
#include "src/objects/data-handler-inl.h"
#include "src/objects/maybe-object.h"
#include "src/objects/transitions.h"

namespace v8 {
namespace internal {

namespace {

template <typename BitField>
Tagged<Smi> SetBitFieldValue(Tagged<Smi> smi_handler,
                             typename BitField::FieldType value) {
  int config = smi_handler.value();
  config = BitField::update(config, true);
  return Smi::FromInt(config);
}

// TODO(ishell): Remove templatezation once we move common bits from
// Load/StoreHandler to the base class.
template <typename ICHandler, bool fill_handler = true>
int InitPrototypeChecksImpl(Isolate* isolate, DirectHandle<ICHandler> handler,
                            Tagged<Smi>* smi_handler,
                            DirectHandle<Map> lookup_start_object_map,
                            MaybeObjectDirectHandle data1,
                            MaybeObjectDirectHandle maybe_data2) {
  int data_size = 1;
  // Holder-is-receiver case itself does not add entries unless there is an
  // optional data2 value provided.

  DCHECK_IMPLIES(IsJSGlobalObjectMap(*lookup_start_object_map),
                 lookup_start_object_map->is_prototype_map());

  if (IsPrimitiveMap(*lookup_start_object_map) ||
      lookup_start_object_map->is_access_check_needed()) {
    DCHECK(!IsJSGlobalObjectMap(*lookup_start_object_map));
    // The validity cell check for primitive and global proxy receivers does
    // not guarantee that certain native context ever had access to other
    // native context. However, a handler created for one native context could
    // be used in another native context through the megamorphic stub cache.
    // So we record the original native context to which this handler
    // corresponds in the validity cell.
    if (fill_handler) {
      if (DEBUG_BOOL) {
        // Make sure validity cell contains native context.
        Tagged<Cell> validity_cell = Cast<Cell>(handler->validity_cell());
        Tagged<HeapObject> heap_object;
        CHECK(validity_cell->maybe_value().GetHeapObject(&heap_object));
        CHECK(IsNativeContext(heap_object));
      }
    } else {
      // Enable access checks on the lookup start object.
      *smi_handler = SetBitFieldValue<
          typename ICHandler::DoAccessCheckOnLookupStartObjectBits>(
          *smi_handler, true);
    }
  } else if (lookup_start_object_map->is_dictionary_map() &&
             !IsJSGlobalObjectMap(*lookup_start_object_map)) {
    if (!fill_handler) {
      // Enable lookup on lookup start object.
      *smi_handler =
          SetBitFieldValue<typename ICHandler::LookupOnLookupStartObjectBits>(
              *smi_handler, true);
    }
  }
  if (fill_handler) {
    handler->set_data1(*data1);
  }
  if (!maybe_data2.is_null()) {
    if (fill_handler) {
      // This value will go either to data2 or data3 slot depending on whether
      // data2 slot is already occupied by native context.
      if (data_size == 1) {
        handler->set_data2(*maybe_data2);
      } else {
        DCHECK_EQ(2, data_size);
        handler->set_data3(*maybe_data2);
      }
    }
    data_size++;
  }
  return data_size;
}

// Returns 0 if the validity cell check is enough to ensure that the
// prototype chain from |lookup_start_object_map| till |holder| did not change.
// If the |holder| is an empty handle then the full prototype chain is
// checked.
template <typename ICHandler>
int GetHandlerDataSize(
    Isolate* isolate, Tagged<Smi>* smi_handler,
    DirectHandle<Map> lookup_start_object_map, MaybeObjectDirectHandle data1,
    MaybeObjectDirectHandle maybe_data2 = MaybeObjectDirectHandle()) {
  DCHECK_NOT_NULL(smi_handler);
  return InitPrototypeChecksImpl<ICHandler, false>(
      isolate, DirectHandle<ICHandler>(), smi_handler, lookup_start_object_map,
      data1, maybe_data2);
}

template <typename ICHandler>
void InitPrototypeChecks(
    Isolate* isolate, DirectHandle<ICHandler> handler,
    DirectHandle<Map> lookup_start_object_map, MaybeObjectDirectHandle data1,
    MaybeObjectDirectHandle maybe_data2 = MaybeObjectDirectHandle()) {
  InitPrototypeChecksImpl<ICHandler, true>(
      isolate, handler, nullptr, lookup_start_object_map, data1, maybe_data2);
}

}  // namespace

// static
Handle<Object> LoadHandler::LoadFromPrototype(
    Isolate* isolate, DirectHandle<Map> lookup_start_object_map,
    DirectHandle<JSReceiver> holder, Tagged<Smi> smi_handler,
    MaybeObjectDirectHandle maybe_data1, MaybeObjectDirectHandle maybe_data2) {
  MaybeObjectDirectHandle data1;
  if (maybe_data1.is_null()) {
    data1 = MaybeObjectDirectHandle::Weak(holder);
  } else {
    data1 = maybe_data1;
  }

  int data_size = GetHandlerDataSize<LoadHandler>(
      isolate, &smi_handler, lookup_start_object_map, data1, maybe_data2);

  DirectHandle<UnionOf<Smi, Cell>> validity_cell =
      Map::GetOrCreatePrototypeChainValidityCell(lookup_start_object_map,
                                                 isolate);

  // There must be a validity cell in case access check is needed.
  // ICs support access checks only for JSGlobalProxy objects and they are
  // guaranteed to have validity cell.
  DCHECK_IMPLIES(
      *validity_cell == Map::kNoValidityCellSentinel,
      !DoAccessCheckOnLookupStartObjectBits::decode(smi_handler.value()));

  Handle<LoadHandler> handler = isolate->factory()->NewLoadHandler(data_size);

  handler->set_smi_handler(smi_handler);
  handler->set_validity_cell(*validity_cell);
  InitPrototypeChecks(isolate, direct_handle(handler), lookup_start_object_map,
                      data1, maybe_data2);
  return handler;
}

// static
Handle<Object> LoadHandler::LoadFullChain(
    Isolate* isolate, DirectHandle<Map> lookup_start_object_map,
    const MaybeObjectDirectHandle& holder, Handle<Smi> smi_handler_handle) {
  Tagged<Smi> smi_handler = *smi_handler_handle;
  MaybeObjectDirectHandle data1 = holder;
  int data_size = GetHandlerDataSize<LoadHandler>(
      isolate, &smi_handler, lookup_start_object_map, data1);

  DirectHandle<UnionOf<Smi, Cell>> validity_cell =
      Map::GetOrCreatePrototypeChainValidityCell(lookup_start_object_map,
                                                 isolate);

  // There must be a validity cell in case access check is needed.
  // ICs support access checks only for JSGlobalProxy objects and they are
  // guaranteed to have validity cell.
  DCHECK_IMPLIES(
      *validity_cell == Map::kNoValidityCellSentinel,
      !DoAccessCheckOnLookupStartObjectBits::decode(smi_handler.value()));

  if (*validity_cell == Map::kNoValidityCellSentinel) {
    DCHECK_EQ(1, data_size);
    // Lookup on lookup start object isn't supported in case of a simple smi
    // handler.
    if (!LookupOnLookupStartObjectBits::decode(smi_handler.value())) {
      return smi_handler_handle;
    }
  }

  Handle<LoadHandler> handler = isolate->factory()->NewLoadHandler(data_size);

  handler->set_smi_handler(smi_handler);
  handler->set_validity_cell(*validity_cell);
  InitPrototypeChecks(isolate, direct_handle(handler), lookup_start_object_map,
                      data1);
  return handler;
}

Handle<Object> LoadHandler::LoadNonExistent(
    Isolate* isolate, DirectHandle<Map> lookup_start_object_map) {
  Tagged<Smi> smi_handler = Smi::FromInt(KindBits::encode(Kind::kNonExistent));
  MaybeObjectDirectHandle data1 =
      MaybeObjectDirectHandle(isolate->factory()->null_value());
  int data_size = GetHandlerDataSize<LoadHandler>(
      isolate, &smi_handler, lookup_start_object_map, data1);

  DirectHandle<PrototypeInfo> prototype_info;
  DirectHandle<UnionOf<Smi, Cell>> validity_cell =
      Map::GetOrCreatePrototypeChainValidityCell(lookup_start_object_map,
                                                 isolate, &prototype_info);

  // There must be a validity cell in case access check is needed.
  // ICs support access checks only for JSGlobalProxy objects and they are
  // guaranteed to have validity cell.
  DCHECK_IMPLIES(
      *validity_cell == Map::kNoValidityCellSentinel,
      !DoAccessCheckOnLookupStartObjectBits::decode(smi_handler.value()));

  // Try to fetch cached handler if it was already created.
  int handler_index;

  if (*validity_cell == Map::kNoValidityCellSentinel) {
    DCHECK_EQ(1, data_size);
    // Lookup on lookup start object isn't supported in case of a simple smi
    // handler.
    if (!LookupOnLookupStartObjectBits::decode(smi_handler.value())) {
      return handle(smi_handler, isolate);
    }
    // Handlers with Smi validity cells are not cacheable.
    handler_index = -1;

  } else {
    // |smi_handler| already has do-access-check-on-lookup or lookup-on-receiver
    // bits set if necessary, so just use them in order to figure out the index
    // of the cached non-existent handler.
    int config = smi_handler.value();
    const uint32_t non_default_mask =
        DoAccessCheckOnLookupStartObjectBits::kMask |
        LookupOnLookupStartObjectBits::kMask;
    if (config & non_default_mask) {
      if (config & LookupOnLookupStartObjectBits::kMask) {
        handler_index =
            PrototypeInfo::kLoadNonExistentHandlerWithLookupOnReceiver;
      } else {
        DCHECK(config & DoAccessCheckOnLookupStartObjectBits::kMask);
        // This is a less often used version, we don't cache it.
        handler_index = -1;
      }
    } else {
      handler_index = PrototypeInfo::kLoadNonExistentHandlerDefault;
    }

    if (handler_index >= 0) {
      auto maybe_handler = prototype_info->cached_handler(handler_index);
      if (!IsSmi(maybe_handler)) {
        Tagged<LoadHandler> handler = Cast<LoadHandler>(maybe_handler);
        // Handler's validity cell must be the up-to-date one.
        DCHECK_EQ(handler->validity_cell(), *validity_cell);
        return handle(handler, isolate);
      }
      // Proceed with allocating a handler and caching it in prototype info.
    }
  }

  Handle<LoadHandler> handler = isolate->factory()->NewLoadHandler(data_size);

  handler->set_smi_handler(smi_handler);
  handler->set_validity_cell(*validity_cell);
  InitPrototypeChecks(isolate, direct_handle(handler), lookup_start_object_map,
                      data1);

  if (handler_index >= 0) {
    prototype_info->set_cached_handler(handler_index, *handler);
  }
  return handler;
}

// static
KeyedAccessLoadMode LoadHandler::GetKeyedAccessLoadMode(
    Tagged<MaybeObject> handler) {
  DisallowGarbageCollection no_gc;
  if (IsSmi(handler)) {
    int const raw_handler = handler.ToSmi().value();
    Kind const kind = KindBits::decode(raw_handler);
    if (kind == Kind::kElement || kind == Kind::kIndexedString) {
      bool handle_oob = AllowOutOfBoundsBits::decode(raw_handler);
      bool handle_holes = AllowHandlingHole::decode(raw_handler);
      return CreateKeyedAccessLoadMode(handle_oob, handle_holes);
    }
  }
  return KeyedAccessLoadMode::kInBounds;
}

// static
KeyedAccessStoreMode StoreHandler::GetKeyedAccessStoreMode(
    Tagged<MaybeObject> handler) {
  DisallowGarbageCollection no_gc;
  if (IsSmi(handler)) {
    int const raw_handler = handler.ToSmi().value();
    Kind const kind = KindBits::decode(raw_handler);
    // All the handlers except the Slow Handler that use tshe
    // KeyedAccessStoreMode, compute it using KeyedAccessStoreModeForBuiltin
    // method. Hence if any other Handler get to this path, just return
    // KeyedAccessStoreMode::kInBounds.
    if (kind != Kind::kSlow) {
      return KeyedAccessStoreMode::kInBounds;
    }
    KeyedAccessStoreMode store_mode =
        KeyedAccessStoreModeBits::decode(raw_handler);
    return store_mode;
  }
  return KeyedAccessStoreMode::kInBounds;
}

// static
Handle<Object> StoreHandler::StoreElementTransition(
    Isolate* isolate, DirectHandle<Map> receiver_map,
    DirectHandle<Map> transition, KeyedAccessStoreMode store_mode,
    MaybeDirectHandle<UnionOf<Smi, Cell>> prev_validity_cell) {
  DirectHandle<Code> code =
      ElementsTransitionAndStoreBuiltin(isolate, store_mode);
  DirectHandle<UnionOf<Smi, Cell>> validity_cell;
  if (!prev_validity_cell.ToHandle(&validity_cell)) {
    validity_cell =
        Map::GetOrCreatePrototypeChainValidityCell(receiver_map, isolate);
  }
  Handle<StoreHandler> handler = isolate->factory()->NewStoreHandler(1);
  handler->set_smi_handler(*code);
  handler->set_validity_cell(*validity_cell);
  handler->set_data1(MakeWeak(*transition));
  return handler;
}

// static
MaybeObjectHandle StoreHandler::StoreOwnTransition(Isolate* isolate,
                                                   Handle<Map> transition_map) {
  bool is_dictionary_map = transition_map->is_dictionary_map();
#ifdef DEBUG
  if (!is_dictionary_map) {
    InternalIndex descriptor = transition_map->LastAdded();
    DirectHandle<DescriptorArray> descriptors(
        transition_map->instance_descriptors(isolate), isolate);
    PropertyDetails details = descriptors->GetDetails(descriptor);
    if (descriptors->GetKey(descriptor)->IsPrivate()) {
      DCHECK_EQ(DONT_ENUM, details.attributes());
    } else {
      DCHECK_EQ(NONE, details.attributes());
    }
    Representation representation = details.representation();
    DCHECK(!representation.IsNone());
  }
#endif
  // Declarative handlers don't support access checks.
  DCHECK(!transition_map->is_access_check_needed());

  // StoreOwnTransition does not involve any prototype checks.
  if (is_dictionary_map) {
    DCHECK(!IsJSGlobalObjectMap(*transition_map));
    int config = KindBits::encode(Kind::kNormal);
    return MaybeObjectHandle(Tagged<Object>(Smi::FromInt(config)), isolate);

  } else {
    return MaybeObjectHandle::Weak(transition_map);
  }
}

// static
MaybeObjectHandle StoreHandler::StoreTransition(Isolate* isolate,
                                                Handle<Map> transition_map) {
  bool is_dictionary_map = transition_map->is_dictionary_map();
  // We should not create transition handlers for fast prototype maps
  // because they are not reusable anyway.
  DCHECK_IMPLIES(transition_map->is_prototype_map(), is_dictionary_map);
#ifdef DEBUG
  if (!is_dictionary_map) {
    InternalIndex descriptor = transition_map->LastAdded();
    DirectHandle<DescriptorArray> descriptors(
        transition_map->instance_descriptors(isolate), isolate);
    // Private fields must be added via StoreOwnTransition handler.
    DCHECK(!descriptors->GetKey(descriptor)->IsPrivateName());
    PropertyDetails details = descriptors->GetDetails(descriptor);
    if (descriptors->GetKey(descriptor)->IsPrivate()) {
      DCHECK_EQ(DONT_ENUM, details.attributes());
    } else {
      DCHECK_EQ(NONE, details.attributes());
    }
    Representation representation = details.representation();
    DCHECK(!representation.IsNone());
  }
#endif
  // Declarative handlers don't support access checks.
  DCHECK(!transition_map->is_access_check_needed());

  // Get validity cell value if it is necessary for the handler.
  DirectHandle<UnionOf<Smi, Cell>> validity_cell;
  if (is_dictionary_map || !transition_map->IsPrototypeValidityCellValid()) {
    validity_cell =
        Map::GetOrCreatePrototypeChainValidityCell(transition_map, isolate);
  }

  if (is_dictionary_map) {
    DCHECK(!IsJSGlobalObjectMap(*transition_map));
    Handle<StoreHandler> handler = isolate->factory()->NewStoreHandler(0);
    // Store normal with enabled lookup on receiver.
    int config = KindBits::encode(Kind::kNormal) |
                 LookupOnLookupStartObjectBits::encode(true);
    handler->set_smi_handler(Smi::FromInt(config));
    handler->set_validity_cell(*validity_cell);
    return MaybeObjectHandle(handler);

  } else {
    // Ensure the transition map contains a valid prototype validity cell.
    if (!validity_cell.is_null()) {
      transition_map->set_prototype_validity_cell(*validity_cell,
                                                  kRelaxedStore);
    }
    return MaybeObjectHandle::Weak(transition_map);
  }
}

// static
Handle<Object> StoreHandler::StoreThroughPrototype(
    Isolate* isolate, DirectHandle<Map> receiver_map,
    DirectHandle<JSReceiver> holder, Tagged<Smi> smi_handler,
    MaybeObjectDirectHandle maybe_data1, MaybeObjectDirectHandle maybe_data2) {
  MaybeObjectDirectHandle data1;
  if (maybe_data1.is_null()) {
    data1 = MaybeObjectDirectHandle::Weak(holder);
  } else {
    data1 = maybe_data1;
  }

  int data_size = GetHandlerDataSize<StoreHandler>(
      isolate, &smi_handler, receiver_map, data1, maybe_data2);

  DirectHandle<UnionOf<Smi, Cell>> validity_cell =
      Map::GetOrCreatePrototypeChainValidityCell(receiver_map, isolate);

  // There must be a validity cell in case access check is needed.
  // ICs support access checks only for JSGlobalProxy objects and they are
  // guaranteed to have validity cell.
  DCHECK_IMPLIES(
      *validity_cell == Map::kNoValidityCellSentinel,
      !DoAccessCheckOnLookupStartObjectBits::decode(smi_handler.value()));

  Handle<StoreHandler> handler = isolate->factory()->NewStoreHandler(data_size);

  handler->set_smi_handler(smi_handler);
  handler->set_validity_cell(*validity_cell);
  InitPrototypeChecks(isolate, direct_handle(handler), receiver_map, data1,
                      maybe_data2);
  return handler;
}

// static
MaybeObjectHandle StoreHandler::StoreGlobal(Handle<PropertyCell> cell) {
  return MaybeObjectHandle::Weak(cell);
}

// static
Handle<Object> StoreHandler::StoreProxy(Isolate* isolate,
                                        DirectHandle<Map> receiver_map,
                                        Handle<JSProxy> proxy,
                                        DirectHandle<JSReceiver> receiver) {
  Handle<Smi> smi_handler = StoreProxy(isolate);
  if (receiver.is_identical_to(proxy)) return smi_handler;
  return StoreThroughPrototype(isolate, receiver_map, proxy, *smi_handler,
                               MaybeObjectDirectHandle::Weak(proxy));
}

bool LoadHandler::CanHandleHolderNotLookupStart(Tagged<Object> handler) {
  if (IsSmi(handler)) {
    auto kind = LoadHandler::KindBits::decode(handler.ToSmi().value());
    return kind == LoadHandler::Kind::kSlow ||
           kind == LoadHandler::Kind::kNonExistent;
  }
  return IsLoadHandler(handler);
}

#if defined(OBJECT_PRINT)
namespace {
void PrintSmiLoadHandler(int raw_handler, std::ostream& os) {
  LoadHandler::Kind kind = LoadHandler::KindBits::decode(raw_handler);
  os << "kind = ";
  switch (kind) {
    case LoadHandler::Kind::kElement:
      os << "kElement, ";
      if (LoadHandler::IsWasmArrayBits::decode(raw_handler)) {
        os << "WasmArray, "
           << LoadHandler::WasmArrayTypeBits::decode(raw_handler);

      } else {
        os << "allow out of bounds = "
           << LoadHandler::AllowOutOfBoundsBits::decode(raw_handler)
           << ", is JSArray = "
           << LoadHandler::IsJsArrayBits::decode(raw_handler)
           << ", alow reading holes = "
           << LoadHandler::AllowHandlingHole::decode(raw_handler)
           << ", elements kind = "
           << ElementsKindToString(
                  LoadHandler::ElementsKindBits::decode(raw_handler));
      }
      break;
    case LoadHandler::Kind::kElementWithTransition:
      os << "kElementWithTransition, ";
      os << "allow out of bounds = "
         << LoadHandler::AllowOutOfBoundsBits::decode(raw_handler)
         << ", is JSArray = " << LoadHandler::IsJsArrayBits::decode(raw_handler)
         << ", alow reading holes = "
         << LoadHandler::AllowHandlingHole::decode(raw_handler)
         << ", elements kind = "
         << ElementsKindToString(
                LoadHandler::ElementsKindBits::decode(raw_handler));
      break;
    case LoadHandler::Kind::kIndexedString:
      os << "kIndexedString, allow out of bounds = "
         << LoadHandler::AllowOutOfBoundsBits::decode(raw_handler);
      break;
    case LoadHandler::Kind::kNormal:
      os << "kNormal";
      break;
    case LoadHandler::Kind::kGlobal:
      os << "kGlobal";
      break;
    case LoadHandler::Kind::kField: {
      if (LoadHandler::IsWasmStructBits::decode(raw_handler)) {
        os << "kField, WasmStruct, type = "
           << LoadHandler::WasmFieldTypeBits::decode(raw_handler)
           << ", field offset = "
           << LoadHandler::WasmFieldOffsetBits::decode(raw_handler);
      } else {
        os << "kField, is in object = "
           << LoadHandler::IsInobjectBits::decode(raw_handler)
           << ", is double = " << LoadHandler::IsDoubleBits::decode(raw_handler)
           << ", field index = "
           << LoadHandler::FieldIndexBits::decode(raw_handler);
      }
      break;
    }
    case LoadHandler::Kind::kConstantFromPrototype:
      os << "kConstantFromPrototype";
      break;
    case LoadHandler::Kind::kAccessorFromPrototype:
      os << "kAccessorFromPrototype";
      break;
    case LoadHandler::Kind::kNativeDataProperty:
      os << "kNativeDataProperty, descriptor = "
         << LoadHandler::DescriptorBits::decode(raw_handler);
      break;
    case LoadHandler::Kind::kApiGetter:
      os << "kApiGetter";
      break;
    case LoadHandler::Kind::kInterceptor:
      os << "kInterceptor";
      break;
    case LoadHandler::Kind::kSlow:
      os << "kSlow";
      break;
    case LoadHandler::Kind::kProxy:
      os << "kProxy";
      break;
    case LoadHandler::Kind::kNonExistent:
      os << "kNonExistent";
      break;
    case LoadHandler::Kind::kModuleExport:
      os << "kModuleExport, exports index = "
         << LoadHandler::ExportsIndexBits::decode(raw_handler);
      break;
    default:
      os << "<invalid value " << static_cast<int>(kind) << ">";
      break;
  }
}

void PrintSmiStoreHandler(int raw_handler, std::ostream& os) {
  StoreHandler::Kind kind = StoreHandler::KindBits::decode(raw_handler);
  os << "kind = ";
  switch (kind) {
    case StoreHandler::Kind::kField:
    case StoreHandler::Kind::kConstField: {
      os << "k";
      if (kind == StoreHandler::Kind::kConstField) {
        os << "Const";
      }
      Representation representation = Representation::FromKind(
          StoreHandler::RepresentationBits::decode(raw_handler));
      os << "Field, descriptor = "
         << StoreHandler::DescriptorBits::decode(raw_handler)
         << ", is in object = "
         << StoreHandler::IsInobjectBits::decode(raw_handler)
         << ", representation = " << representation.Mnemonic()
         << ", field index = "
         << StoreHandler::FieldIndexBits::decode(raw_handler);
      break;
    }
    case StoreHandler::Kind::kAccessorFromPrototype:
      os << "kAccessorFromPrototype";
      break;
    case StoreHandler::Kind::kNativeDataProperty:
      os << "kNativeDataProperty, descriptor = "
         << StoreHandler::DescriptorBits::decode(raw_handler);
      break;
    case StoreHandler::Kind::kApiSetter:
      os << "kApiSetter";
      break;
    case StoreHandler::Kind::kGlobalProxy:
      os << "kGlobalProxy";
      break;
    case StoreHandler::Kind::kNormal:
      os << "kNormal";
      break;
    case StoreHandler::Kind::kInterceptor:
      os << "kInterceptor";
      break;
    case StoreHandler::Kind::kSlow: {
      KeyedAccessStoreMode keyed_access_store_mode =
          StoreHandler::KeyedAccessStoreModeBits::decode(raw_handler);
      os << "kSlow, keyed access store mode = " << keyed_access_store_mode;
      break;
    }
    case StoreHandler::Kind::kGeneric:
      os << "kGeneric";
      break;
    case StoreHandler::Kind::kProxy:
      os << "kProxy";
      break;
    case StoreHandler::Kind::kSharedStructField:
      os << "kSharedStructField";
      break;
    case StoreHandler::Kind::kKindsNumber:
      UNREACHABLE();
  }
}

}  // namespace

// static
void LoadHandler::PrintHandler(Tagged<Object> handler, std::ostream& os) {
  DisallowGarbageCollection no_gc;
  if (IsSmi(handler)) {
    int raw_handler = handler.ToSmi().value();
    os << "LoadHandler(Smi)(";
    PrintSmiLoadHandler(raw_handler, os);
    os << ")";
  } else if (Tagged<Code> code; TryCast(handler, &code)) {
    os << "LoadHandler(Code)(" << Builtins::name(code->builtin_id()) << ")";
  } else if (IsSymbol(handler)) {
    os << "LoadHandler(Symbol)(" << Brief(Cast<Symbol>(handler)) << ")";
  } else if (IsLoadHandler(handler)) {
    Tagged<LoadHandler> load_handler = Cast<LoadHandler>(handler);
    int raw_handler = Cast<Smi>(load_handler->smi_handler()).value();
    os << "LoadHandler(do access check on lookup start object = "
       << DoAccessCheckOnLookupStartObjectBits::decode(raw_handler)
       << ", lookup on lookup start object = "
       << LookupOnLookupStartObjectBits::decode(raw_handler) << ", ";
    PrintSmiLoadHandler(raw_handler, os);
    if (load_handler->data_field_count() >= 1) {
      os << ", data1 = ";
      ShortPrint(load_handler->data1(), os);
    }
    if (load_handler->data_field_count() >= 2) {
      os << ", data2 = ";
      ShortPrint(load_handler->data2(), os);
    }
    if (load_handler->data_field_count() >= 3) {
      os << ", data3 = ";
      ShortPrint(load_handler->data3(), os);
    }
    os << ", validity cell = ";
    ShortPrint(load_handler->validity_cell(), os);
    os << ")";
  } else {
    os << "LoadHandler(<unexpected>)(" << Brief(handler) << ")";
  }
}

void StoreHandler::PrintHandler(Tagged<Object> handler, std::ostream& os) {
  DisallowGarbageCollection no_gc;
  if (IsSmi(handler)) {
    int raw_handler = handler.ToSmi().value();
    os << "StoreHandler(Smi)(";
    PrintSmiStoreHandler(raw_handler, os);
    os << ")" << std::endl;
  } else if (IsStoreHandler(handler)) {
    os << "StoreHandler(";
    Tagged<StoreHandler> store_handler = Cast<StoreHandler>(handler);
    if (Tagged<Code> code; TryCast(store_handler->smi_handler(), &code)) {
      os << "builtin = ";
      ShortPrint(code, os);
    } else {
      int raw_handler = Cast<Smi>(store_handler->smi_handler()).value();
      os << "do access check on lookup start object = "
         << DoAccessCheckOnLookupStartObjectBits::decode(raw_handler)
         << ", lookup on lookup start object = "
         << LookupOnLookupStartObjectBits::decode(raw_handler) << ", ";
      PrintSmiStoreHandler(raw_handler, os);
    }
    if (store_handler->data_field_count() >= 1) {
      os << ", data1 = ";
      ShortPrint(store_handler->data1(), os);
    }
    if (store_handler->data_field_count() >= 2) {
      os << ", data2 = ";
      ShortPrint(store_handler->data2(), os);
    }
    if (store_handler->data_field_count() >= 3) {
      os << ", data3 = ";
      ShortPrint(store_handler->data3(), os);
    }
    os << ", validity cell = ";
    ShortPrint(store_handler->validity_cell(), os);
    os << ")" << std::endl;
  } else if (IsMap(handler)) {
    os << "StoreHandler(field transition to " << Brief(handler) << ")"
       << std::endl;
  } else if (Tagged<Code> code; TryCast(handler, &code)) {
    os << "StoreHandler(builtin = ";
    ShortPrint(code, os);
    os << ")" << std::endl;
  } else {
    os << "StoreHandler(<unexpected>)(" << Brief(handler) << ")" << std::endl;
  }
}

std::ostream& operator<<(std::ostream& os, WasmValueType type) {
  return os << WasmValueType2String(type);
}

#endif  // defined(OBJECT_PRINT)

}  // namespace internal
}  // namespace v8
