// Copyright 2015 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 <stdlib.h>

#include <initializer_list>
#include <utility>

#include "src/base/logging.h"
#include "src/execution/execution.h"
#include "src/heap/factory-inl.h"
#include "src/objects/field-type.h"
#include "src/objects/heap-number-inl.h"
#include "src/objects/internal-index.h"
#include "src/objects/map-updater.h"
#include "src/objects/objects-inl.h"
#include "src/objects/property-details.h"
#include "src/objects/property.h"
#include "src/objects/struct-inl.h"
#include "src/objects/transitions.h"
#include "src/utils/ostreams.h"
#include "test/cctest/cctest.h"

namespace v8 {
namespace internal {
namespace compiler {
namespace test_field_type_tracking {

// TODO(ishell): fix this once TransitionToPrototype stops generalizing
// all field representations (similar to crbug/448711 where elements kind
// and observed transitions caused generalization of all fields).

// TODO(ishell): fix this once TransitionToAccessorProperty is able to always
// keep map in fast mode.
const bool IS_ACCESSOR_FIELD_SUPPORTED = false;

// Number of properties used in the tests.
const int kPropCount = 7;

enum ChangeAlertMechanism { kDeprecation, kFieldOwnerDependency, kNoAlert };

//
// Helper functions.
//

static Handle<AccessorPair> CreateAccessorPair(bool with_getter,
                                               bool with_setter) {
  Isolate* isolate = CcTest::i_isolate();
  Factory* factory = isolate->factory();
  Handle<AccessorPair> pair = factory->NewAccessorPair();
  DirectHandle<String> empty_string = factory->empty_string();
  if (with_getter) {
    DirectHandle<JSFunction> func =
        factory->NewFunctionForTesting(empty_string);
    pair->set_getter(*func);
  }
  if (with_setter) {
    DirectHandle<JSFunction> func =
        factory->NewFunctionForTesting(empty_string);
    pair->set_setter(*func);
  }
  return pair;
}

// Check cached migration target map after Map::Update() and Map::TryUpdate()
static void CheckMigrationTarget(Isolate* isolate, Tagged<Map> old_map,
                                 Tagged<Map> new_map) {
  Tagged<Map> target =
      TransitionsAccessor(isolate, old_map).GetMigrationTarget();
  if (target.is_null()) return;
  CHECK_EQ(new_map, target);
  CHECK_EQ(MapUpdater::TryUpdateNoLock(isolate, old_map,
                                       ConcurrencyMode::kSynchronous),
           target);
}

class Expectations {
  static const int MAX_PROPERTIES = 10;
  Isolate* isolate_;
  ElementsKind elements_kind_;
  PropertyKind kinds_[MAX_PROPERTIES];
  PropertyLocation locations_[MAX_PROPERTIES];
  PropertyConstness constnesses_[MAX_PROPERTIES];
  PropertyAttributes attributes_[MAX_PROPERTIES];
  Representation representations_[MAX_PROPERTIES];
  // FieldType for kField, value for DATA_CONSTANT and getter for
  // ACCESSOR_CONSTANT.
  DirectHandle<Object> values_[MAX_PROPERTIES];
  // Setter for ACCESSOR_CONSTANT.
  DirectHandle<Object> setter_values_[MAX_PROPERTIES];
  int number_of_properties_;

 public:
  explicit Expectations(Isolate* isolate, ElementsKind elements_kind)
      : isolate_(isolate),
        elements_kind_(elements_kind),
        number_of_properties_(0) {}

  explicit Expectations(Isolate* isolate)
      : Expectations(
            isolate,
            isolate->object_function()->initial_map()->elements_kind()) {}

  void Init(int index, PropertyKind kind, PropertyAttributes attributes,
            PropertyConstness constness, PropertyLocation location,
            Representation representation, DirectHandle<Object> value) {
    CHECK(index < MAX_PROPERTIES);
    kinds_[index] = kind;
    locations_[index] = location;
    if (kind == PropertyKind::kData && location == PropertyLocation::kField &&
        IsTransitionableFastElementsKind(elements_kind_)) {
      // Maps with transitionable elements kinds must have the most general
      // field type.
      value = FieldType::Any(isolate_);
      representation = Representation::Tagged();
    }
    constnesses_[index] = constness;
    attributes_[index] = attributes;
    representations_[index] = representation;
    values_[index] = value;
  }

  void Print() const {
    StdoutStream os;
    os << "Expectations: #" << number_of_properties_ << "\n";
    for (int i = 0; i < number_of_properties_; i++) {
      os << " " << i << ": ";
      os << "Descriptor @ ";

      if (kinds_[i] == PropertyKind::kData) {
        FieldType::PrintTo(Cast<FieldType>(*values_[i]), os);
      } else {
        // kAccessor
        os << "(get: " << Brief(*values_[i])
           << ", set: " << Brief(*setter_values_[i]) << ") ";
      }

      os << " (";
      if (constnesses_[i] == PropertyConstness::kConst) os << "const ";
      os << (kinds_[i] == PropertyKind::kData ? "data " : "accessor ");
      if (locations_[i] == PropertyLocation::kField) {
        os << "field"
           << ": " << representations_[i].Mnemonic();
      } else {
        os << "descriptor";
      }
      os << ", attrs: " << attributes_[i] << ")\n";
    }
    os << "\n";
  }

  void SetElementsKind(ElementsKind elements_kind) {
    elements_kind_ = elements_kind;
  }

  DirectHandle<FieldType> GetFieldType(int index) {
    CHECK(index < MAX_PROPERTIES);
    CHECK_EQ(PropertyLocation::kField, locations_[index]);
    return Cast<FieldType>(values_[index]);
  }

  void SetDataField(int index, PropertyAttributes attrs,
                    PropertyConstness constness, Representation representation,
                    DirectHandle<FieldType> field_type) {
    Init(index, PropertyKind::kData, attrs, constness, PropertyLocation::kField,
         representation, field_type);
  }

  void SetDataField(int index, PropertyConstness constness,
                    Representation representation,
                    DirectHandle<FieldType> field_type) {
    SetDataField(index, attributes_[index], constness, representation,
                 field_type);
  }

  void SetAccessorField(int index, PropertyAttributes attrs) {
    Init(index, PropertyKind::kAccessor, attrs, PropertyConstness::kConst,
         PropertyLocation::kDescriptor, Representation::Tagged(),
         FieldType::Any(isolate_));
  }

  void SetAccessorField(int index) {
    SetAccessorField(index, attributes_[index]);
  }

  void SetDataConstant(int index, PropertyAttributes attrs,
                       DirectHandle<JSFunction> value) {
    DirectHandle<FieldType> field_type(FieldType::Class(value->map()),
                                       isolate_);
    Init(index, PropertyKind::kData, attrs, PropertyConstness::kConst,
         PropertyLocation::kField, Representation::HeapObject(), field_type);
  }

  void SetDataConstant(int index, DirectHandle<JSFunction> value) {
    SetDataConstant(index, attributes_[index], value);
  }

  void SetAccessorConstant(int index, PropertyAttributes attrs,
                           DirectHandle<Object> getter,
                           DirectHandle<Object> setter) {
    Init(index, PropertyKind::kAccessor, attrs, PropertyConstness::kConst,
         PropertyLocation::kDescriptor, Representation::Tagged(), getter);
    setter_values_[index] = setter;
  }

  void SetAccessorConstantComponent(int index, PropertyAttributes attrs,
                                    AccessorComponent component,
                                    DirectHandle<Object> accessor) {
    CHECK_EQ(PropertyKind::kAccessor, kinds_[index]);
    CHECK_EQ(PropertyLocation::kDescriptor, locations_[index]);
    CHECK(index < number_of_properties_);
    if (component == ACCESSOR_GETTER) {
      values_[index] = accessor;
    } else {
      setter_values_[index] = accessor;
    }
  }

  void SetAccessorConstant(int index, PropertyAttributes attrs,
                           DirectHandle<AccessorPair> pair) {
    DirectHandle<Object> getter(pair->getter(), isolate_);
    DirectHandle<Object> setter(pair->setter(), isolate_);
    SetAccessorConstant(index, attrs, getter, setter);
  }

  void SetAccessorConstant(int index, DirectHandle<Object> getter,
                           DirectHandle<Object> setter) {
    SetAccessorConstant(index, attributes_[index], getter, setter);
  }

  void SetAccessorConstant(int index, DirectHandle<AccessorPair> pair) {
    DirectHandle<Object> getter(pair->getter(), isolate_);
    DirectHandle<Object> setter(pair->setter(), isolate_);
    SetAccessorConstant(index, getter, setter);
  }

  void GeneralizeField(int index) {
    CHECK(index < number_of_properties_);
    representations_[index] = Representation::Tagged();
    if (locations_[index] == PropertyLocation::kField) {
      values_[index] = FieldType::Any(isolate_);
    }
  }

  bool Check(Tagged<DescriptorArray> descriptors,
             InternalIndex descriptor) const {
    PropertyDetails details = descriptors->GetDetails(descriptor);

    if (details.kind() != kinds_[descriptor.as_int()]) return false;
    if (details.location() != locations_[descriptor.as_int()]) return false;
    if (details.constness() != constnesses_[descriptor.as_int()]) return false;

    PropertyAttributes expected_attributes = attributes_[descriptor.as_int()];
    if (details.attributes() != expected_attributes) return false;

    Representation expected_representation =
        representations_[descriptor.as_int()];

    if (!details.representation().Equals(expected_representation)) return false;

    Tagged<Object> expected_value = *values_[descriptor.as_int()];
    if (details.location() == PropertyLocation::kField) {
      if (details.kind() == PropertyKind::kData) {
        Tagged<FieldType> type = descriptors->GetFieldType(descriptor);
        return Cast<FieldType>(expected_value) == type;
      } else {
        // kAccessor
        UNREACHABLE();
      }
    } else {
      CHECK_EQ(PropertyKind::kAccessor, details.kind());
      Tagged<Object> value = descriptors->GetStrongValue(descriptor);
      if (value == expected_value) return true;
      if (!IsAccessorPair(value)) return false;
      Tagged<AccessorPair> pair = Cast<AccessorPair>(value);
      return pair->Equals(expected_value, *setter_values_[descriptor.as_int()]);
    }
    UNREACHABLE();
  }

  bool Check(Tagged<Map> map, int expected_nof) const {
    CHECK_EQ(elements_kind_, map->elements_kind());
    CHECK(number_of_properties_ <= MAX_PROPERTIES);
    CHECK_EQ(expected_nof, map->NumberOfOwnDescriptors());
    CHECK(!map->is_dictionary_map());

    Tagged<DescriptorArray> descriptors = map->instance_descriptors();
    CHECK(expected_nof <= number_of_properties_);
    for (InternalIndex i : InternalIndex::Range(expected_nof)) {
      if (!Check(descriptors, i)) {
        Print();
#ifdef OBJECT_PRINT
        i::Print(descriptors);
#endif
        return false;
      }
    }
    return true;
  }

  bool Check(Tagged<Map> map) const {
    return Check(map, number_of_properties_);
  }

  bool CheckNormalized(Tagged<Map> map) const {
    CHECK(map->is_dictionary_map());
    CHECK_EQ(elements_kind_, map->elements_kind());
    // TODO(leszeks): Iterate over the key/value pairs of the map and compare
    // them against the expected fields.
    return true;
  }

  //
  // Helper methods for initializing expectations and adding properties to
  // given |map|.
  //

  Handle<Map> AsElementsKind(Handle<Map> map, ElementsKind elements_kind) {
    elements_kind_ = elements_kind;
    map = Map::AsElementsKind(isolate_, map, elements_kind);
    CHECK_EQ(elements_kind_, map->elements_kind());
    return map;
  }

  void ChangeAttributesForAllProperties(PropertyAttributes attributes) {
    for (int i = 0; i < number_of_properties_; i++) {
      attributes_[i] = attributes;
    }
  }

  Handle<Map> AddDataField(DirectHandle<Map> map, PropertyAttributes attributes,
                           PropertyConstness constness,
                           Representation representation,
                           DirectHandle<FieldType> field_type) {
    CHECK_EQ(number_of_properties_, map->NumberOfOwnDescriptors());
    int property_index = number_of_properties_++;
    SetDataField(property_index, attributes, constness, representation,
                 field_type);

    DirectHandle<String> name = CcTest::MakeName("prop", property_index);
    return Map::CopyWithField(isolate_, map, name, field_type, attributes,
                              constness, representation, INSERT_TRANSITION)
        .ToHandleChecked();
  }

  DirectHandle<Map> AddDataConstant(DirectHandle<Map> map,
                                    PropertyAttributes attributes,
                                    DirectHandle<JSFunction> value) {
    CHECK_EQ(number_of_properties_, map->NumberOfOwnDescriptors());
    int property_index = number_of_properties_++;
    SetDataConstant(property_index, attributes, value);

    DirectHandle<String> name = CcTest::MakeName("prop", property_index);
    return Map::CopyWithConstant(isolate_, map, name, value, attributes,
                                 INSERT_TRANSITION)
        .ToHandleChecked();
  }

  DirectHandle<Map> TransitionToDataField(DirectHandle<Map> map,
                                          PropertyAttributes attributes,
                                          PropertyConstness constness,
                                          Representation representation,
                                          DirectHandle<FieldType> heap_type,
                                          DirectHandle<Object> value) {
    CHECK_EQ(number_of_properties_, map->NumberOfOwnDescriptors());
    int property_index = number_of_properties_++;
    SetDataField(property_index, attributes, constness, representation,
                 heap_type);

    DirectHandle<String> name = CcTest::MakeName("prop", property_index);
    return Map::TransitionToDataProperty(isolate_, map, name, value, attributes,
                                         constness, StoreOrigin::kNamed);
  }

  DirectHandle<Map> TransitionToDataConstant(DirectHandle<Map> map,
                                             PropertyAttributes attributes,
                                             DirectHandle<JSFunction> value) {
    CHECK_EQ(number_of_properties_, map->NumberOfOwnDescriptors());
    int property_index = number_of_properties_++;
    SetDataConstant(property_index, attributes, value);

    DirectHandle<String> name = CcTest::MakeName("prop", property_index);
    return Map::TransitionToDataProperty(isolate_, map, name, value, attributes,
                                         PropertyConstness::kConst,
                                         StoreOrigin::kNamed);
  }

  DirectHandle<Map> FollowDataTransition(DirectHandle<Map> map,
                                         PropertyAttributes attributes,
                                         PropertyConstness constness,
                                         Representation representation,
                                         DirectHandle<FieldType> heap_type) {
    CHECK_EQ(number_of_properties_, map->NumberOfOwnDescriptors());
    int property_index = number_of_properties_++;
    SetDataField(property_index, attributes, constness, representation,
                 heap_type);

    DirectHandle<String> name = CcTest::MakeName("prop", property_index);
    MaybeDirectHandle<Map> target = TransitionsAccessor::SearchTransition(
        isolate_, map, *name, PropertyKind::kData, attributes);
    CHECK(!target.is_null());
    return target.ToHandleChecked();
  }

  Handle<Map> AddAccessorConstant(DirectHandle<Map> map,
                                  PropertyAttributes attributes,
                                  DirectHandle<AccessorPair> pair) {
    CHECK_EQ(number_of_properties_, map->NumberOfOwnDescriptors());
    int property_index = number_of_properties_++;
    SetAccessorConstant(property_index, attributes, pair);

    DirectHandle<String> name = CcTest::MakeName("prop", property_index);

    Descriptor d = Descriptor::AccessorConstant(name, pair, attributes);
    return Map::CopyInsertDescriptor(isolate_, map, &d, INSERT_TRANSITION);
  }

  DirectHandle<Map> AddAccessorConstant(DirectHandle<Map> map,
                                        PropertyAttributes attributes,
                                        DirectHandle<Object> getter,
                                        DirectHandle<Object> setter) {
    CHECK_EQ(number_of_properties_, map->NumberOfOwnDescriptors());
    int property_index = number_of_properties_++;
    SetAccessorConstant(property_index, attributes, getter, setter);

    DirectHandle<String> name = CcTest::MakeName("prop", property_index);

    CHECK(!IsNull(*getter) || !IsNull(*setter));
    Factory* factory = isolate_->factory();

    if (!IsNull(*getter)) {
      DirectHandle<AccessorPair> pair = factory->NewAccessorPair();
      pair->SetComponents(*getter, *factory->null_value());
      Descriptor d = Descriptor::AccessorConstant(name, pair, attributes);
      map = Map::CopyInsertDescriptor(isolate_, map, &d, INSERT_TRANSITION);
    }
    if (!IsNull(*setter)) {
      DirectHandle<AccessorPair> pair = factory->NewAccessorPair();
      pair->SetComponents(*getter, *setter);
      Descriptor d = Descriptor::AccessorConstant(name, pair, attributes);
      map = Map::CopyInsertDescriptor(isolate_, map, &d, INSERT_TRANSITION);
    }
    return map;
  }

  DirectHandle<Map> TransitionToAccessorConstant(
      DirectHandle<Map> map, PropertyAttributes attributes,
      DirectHandle<AccessorPair> pair) {
    CHECK_EQ(number_of_properties_, map->NumberOfOwnDescriptors());
    int property_index = number_of_properties_++;
    SetAccessorConstant(property_index, attributes, pair);

    DirectHandle<String> name = CcTest::MakeName("prop", property_index);

    Isolate* isolate = CcTest::i_isolate();
    DirectHandle<Object> getter(pair->getter(), isolate);
    DirectHandle<Object> setter(pair->setter(), isolate);

    InternalIndex descriptor =
        map->instance_descriptors()->SearchWithCache(isolate, *name, *map);
    map = Map::TransitionToAccessorProperty(isolate, map, name, descriptor,
                                            getter, setter, attributes);
    CHECK(!map->is_deprecated());
    CHECK(!map->is_dictionary_map());
    return map;
  }
};


////////////////////////////////////////////////////////////////////////////////
// A set of tests for property reconfiguration that makes new transition tree
// branch.
//

namespace {

Handle<Map> ReconfigureProperty(Isolate* isolate, DirectHandle<Map> map,
                                InternalIndex modify_index,
                                PropertyKind new_kind,
                                PropertyAttributes new_attributes,
                                Representation new_representation,
                                DirectHandle<FieldType> new_field_type) {
  DCHECK_EQ(PropertyKind::kData, new_kind);  // Only kData case is supported.
  MapUpdater mu(isolate, map);
  return mu.ReconfigureToDataField(modify_index, new_attributes,
                                   PropertyConstness::kConst,
                                   new_representation, new_field_type);
}

}  // namespace

TEST(ReconfigureAccessorToNonExistingDataField) {
  CcTest::InitializeVM();
  v8::HandleScope scope(CcTest::isolate());
  Isolate* isolate = CcTest::i_isolate();

  DirectHandle<FieldType> any_type = FieldType::Any(isolate);
  DirectHandle<FieldType> none_type = FieldType::None(isolate);
  DirectHandle<AccessorPair> pair = CreateAccessorPair(true, true);

  Expectations expectations(isolate);

  // Create a map, add required properties to it and initialize expectations.
  DirectHandle<Map> initial_map = Map::Create(isolate, 0);
  DirectHandle<Map> map = initial_map;
  map = expectations.AddAccessorConstant(map, NONE, pair);

  CHECK(!map->is_deprecated());
  CHECK(map->is_stable());
  CHECK(expectations.Check(*map));

  InternalIndex first(0);
  DirectHandle<Map> new_map =
      ReconfigureProperty(isolate, map, first, PropertyKind::kData, NONE,
                          Representation::None(), none_type);
  // |map| did not change except marked unstable.
  CHECK(!map->is_deprecated());
  CHECK(!map->is_stable());
  CHECK(expectations.Check(*map));

  // Property kind reconfiguration always makes the field mutable.
  expectations.SetDataField(0, NONE, PropertyConstness::kMutable,
                            Representation::None(), none_type);

  CHECK(!new_map->is_deprecated());
  CHECK(new_map->is_stable());
  CHECK(expectations.Check(*new_map));

  DirectHandle<Map> new_map2 =
      ReconfigureProperty(isolate, map, first, PropertyKind::kData, NONE,
                          Representation::None(), none_type);
  CHECK_EQ(*new_map, *new_map2);

  DirectHandle<Object> value(Smi::zero(), isolate);
  DirectHandle<Map> prepared_map = Map::PrepareForDataProperty(
      isolate, new_map, first, PropertyConstness::kConst, value);
  // None to Smi generalization is trivial, map does not change.
  CHECK_EQ(*new_map, *prepared_map);

  expectations.SetDataField(0, NONE, PropertyConstness::kMutable,
                            Representation::Smi(), any_type);
  CHECK(prepared_map->is_stable());
  CHECK(expectations.Check(*prepared_map));

  // Now create an object with |map|, migrate it to |prepared_map| and ensure
  // that the data property is uninitialized.
  Factory* factory = isolate->factory();
  DirectHandle<JSObject> obj = factory->NewJSObjectFromMap(map);
  JSObject::MigrateToMap(isolate, obj, prepared_map);
  FieldIndex index = FieldIndex::ForDescriptor(*prepared_map, first);
  CHECK(IsUninitializedHole(obj->RawFastPropertyAt(index)));
#ifdef VERIFY_HEAP
  Object::ObjectVerify(*obj, isolate);
#endif
}


// This test checks that the LookupIterator machinery involved in
// JSObject::SetOwnPropertyIgnoreAttributes() does not try to migrate object
// to a map with a property with None representation.
TEST(ReconfigureAccessorToNonExistingDataFieldHeavy) {
  CcTest::InitializeVM();
  v8::HandleScope scope(CcTest::isolate());
  Isolate* isolate = CcTest::i_isolate();
  Factory* factory = isolate->factory();

  CompileRun(
      "function getter() { return 1; };"
      "function setter() {};"
      "var o = {};"
      "Object.defineProperty(o, 'foo', "
      "                      { get: getter, set: setter, "
      "                        configurable: true, enumerable: true});");

  DirectHandle<String> foo_str = factory->InternalizeUtf8String("foo");
  DirectHandle<String> obj_name = factory->InternalizeUtf8String("o");

  DirectHandle<Object> obj_value =
      Object::GetProperty(isolate, isolate->global_object(), obj_name)
          .ToHandleChecked();
  CHECK(IsJSObject(*obj_value));
  DirectHandle<JSObject> obj = Cast<JSObject>(obj_value);

  CHECK_EQ(1, obj->map()->NumberOfOwnDescriptors());
  InternalIndex first(0);
  CHECK(IsAccessorPair(
      obj->map()->instance_descriptors()->GetStrongValue(first)));

  DirectHandle<Object> value(Smi::FromInt(42), isolate);
  JSObject::SetOwnPropertyIgnoreAttributes(obj, foo_str, value, NONE).Check();

  // Check that the property contains |value|.
  CHECK_EQ(1, obj->map()->NumberOfOwnDescriptors());
  FieldIndex index = FieldIndex::ForDescriptor(obj->map(), first);
  Tagged<Object> the_value = obj->RawFastPropertyAt(index);
  CHECK(IsSmi(the_value));
  CHECK_EQ(42, Smi::ToInt(the_value));
}


////////////////////////////////////////////////////////////////////////////////
// A set of tests for field generalization case.
//

namespace {

// <Constness, Representation, FieldType> data.
struct CRFTData {
  PropertyConstness constness;
  Representation representation;
  DirectHandle<FieldType> type;
};

Handle<Code> CreateDummyOptimizedCode(Isolate* isolate) {
  uint8_t buffer[1];
  CodeDesc desc;
  desc.buffer = buffer;
  desc.buffer_size = arraysize(buffer);
  desc.instr_size = arraysize(buffer);
  return Factory::CodeBuilder(isolate, desc, CodeKind::TURBOFAN_JS)
      .set_is_turbofanned()
      .set_empty_source_position_table()
      .set_deoptimization_data(DeoptimizationData::Empty(isolate))
      .Build();
}

static void CheckCodeObjectForDeopt(const CRFTData& from,
                                    const CRFTData& expected,
                                    DirectHandle<Code> code_field_type,
                                    DirectHandle<Code> code_field_repr,
                                    DirectHandle<Code> code_field_const,
                                    bool expected_deopt) {
  if (!FieldType::Equals(*from.type, *expected.type)) {
    CHECK_EQ(expected_deopt, code_field_type->marked_for_deoptimization());
  } else {
    CHECK(!code_field_type->marked_for_deoptimization());
  }

  if (!from.representation.Equals(expected.representation)) {
    CHECK_EQ(expected_deopt, code_field_repr->marked_for_deoptimization());
  } else {
    CHECK(!code_field_repr->marked_for_deoptimization());
  }

  if (!code_field_const.is_null()) {
    if (from.constness != expected.constness) {
      CHECK_EQ(expected_deopt, code_field_const->marked_for_deoptimization());
    } else {
      CHECK(!code_field_const->marked_for_deoptimization());
    }
  }
}

// This test ensures that field generalization at |property_index| is done
// correctly independently of the fact that the |map| is detached from
// transition tree or not.
//
//  {} - p0 - p1 - p2: |detach_point_map|
//                  |
//                  X - detached at |detach_property_at_index|
//                  |
//                  + - p3 - p4: |map|
//
// Detaching does not happen if |detach_property_at_index| is -1.
//
void TestGeneralizeField(int detach_property_at_index, int property_index,
                         const CRFTData& from, const CRFTData& to,
                         const CRFTData& expected,
                         ChangeAlertMechanism expected_alert) {
  Isolate* isolate = CcTest::i_isolate();
  DirectHandle<FieldType> any_type = FieldType::Any(isolate);

  CHECK(detach_property_at_index >= -1 &&
        detach_property_at_index < kPropCount);
  CHECK_LT(property_index, kPropCount);
  CHECK_NE(detach_property_at_index, property_index);

  const bool is_detached_map = detach_property_at_index >= 0;

  Expectations expectations(isolate);

  // Create a map, add required properties to it and initialize expectations.
  DirectHandle<Map> initial_map = Map::Create(isolate, 0);
  DirectHandle<Map> map = initial_map;
  DirectHandle<Map> detach_point_map;
  for (int i = 0; i < kPropCount; i++) {
    if (i == property_index) {
      map = expectations.AddDataField(map, NONE, from.constness,
                                      from.representation, from.type);
    } else {
      map = expectations.AddDataField(map, NONE, PropertyConstness::kConst,
                                      Representation::Smi(), any_type);
      if (i == detach_property_at_index) {
        detach_point_map = map;
      }
    }
  }
  CHECK(!map->is_deprecated());
  CHECK(map->is_stable());
  CHECK(expectations.Check(*map));

  if (is_detached_map) {
    detach_point_map = ReconfigureProperty(
        isolate, detach_point_map, InternalIndex(detach_property_at_index),
        PropertyKind::kData, NONE, Representation::Double(), any_type);
    expectations.SetDataField(detach_property_at_index,
                              PropertyConstness::kConst,
                              Representation::Double(), any_type);
    CHECK(map->is_deprecated());
    CHECK(expectations.Check(*detach_point_map,
                             detach_point_map->NumberOfOwnDescriptors()));
  }

  // Create dummy optimized code object to test correct dependencies
  // on the field owner.
  Handle<Code> code_field_type = CreateDummyOptimizedCode(isolate);
  Handle<Code> code_field_repr = CreateDummyOptimizedCode(isolate);
  Handle<Code> code_field_const = CreateDummyOptimizedCode(isolate);
  Handle<Map> field_owner(map->FindFieldOwner(InternalIndex(property_index)),
                          isolate);
  DependentCode::InstallDependency(isolate, code_field_type, field_owner,
                                   DependentCode::kFieldTypeGroup);
  DependentCode::InstallDependency(isolate, code_field_repr, field_owner,
                                   DependentCode::kFieldRepresentationGroup);
  DependentCode::InstallDependency(isolate, code_field_const, field_owner,
                                   DependentCode::kFieldConstGroup);
  CHECK(!code_field_type->marked_for_deoptimization());
  CHECK(!code_field_repr->marked_for_deoptimization());
  CHECK(!code_field_const->marked_for_deoptimization());

  // Create new maps by generalizing representation of propX field.
  DirectHandle<Map> new_map = ReconfigureProperty(
      isolate, map, InternalIndex(property_index), PropertyKind::kData, NONE,
      to.representation, to.type);

  expectations.SetDataField(property_index, expected.constness,
                            expected.representation, expected.type);

  CHECK(!new_map->is_deprecated());
  CHECK(expectations.Check(*new_map));

  bool should_deopt = false;
  if (is_detached_map) {
    CHECK(!map->is_stable());
    CHECK(map->is_deprecated());
    CHECK_NE(*map, *new_map);
    should_deopt = (expected_alert == kFieldOwnerDependency) &&
                   !field_owner->is_deprecated();
  } else if (expected_alert == kDeprecation) {
    CHECK(!map->is_stable());
    CHECK(map->is_deprecated());
    CHECK(field_owner->is_deprecated());
    should_deopt = false;
  } else {
    CHECK(!field_owner->is_deprecated());
    CHECK(map->is_stable());  // Map did not change, must be left stable.
    CHECK_EQ(*map, *new_map);
    should_deopt = (expected_alert == kFieldOwnerDependency);
  }

  CheckCodeObjectForDeopt(from, expected, code_field_type, code_field_repr,
                          code_field_const, should_deopt);

  {
    // Check that all previous maps are not stable.
    Tagged<Map> tmp = *new_map;
    while (true) {
      Tagged<Object> back = tmp->GetBackPointer();
      if (IsUndefined(back)) break;
      tmp = Cast<Map>(back);
      CHECK(!tmp->is_stable());
    }
  }

  // Update all deprecated maps and check that they are now the same.
  DirectHandle<Map> updated_map = Map::Update(isolate, map);
  CHECK_EQ(*new_map, *updated_map);
  CheckMigrationTarget(isolate, *map, *updated_map);
}

void TestGeneralizeField(const CRFTData& from, const CRFTData& to,
                         const CRFTData& expected,
                         ChangeAlertMechanism expected_alert) {
  // Check the cases when the map being reconfigured is a part of the
  // transition tree.
  static_assert(kPropCount > 4);
  int indices[] = {0, 2, kPropCount - 1};
  for (int i = 0; i < static_cast<int>(arraysize(indices)); i++) {
    TestGeneralizeField(-1, indices[i], from, to, expected, expected_alert);
  }

  if (!from.representation.IsNone()) {
    // Check the cases when the map being reconfigured is NOT a part of the
    // transition tree. "None -> anything" representation changes make sense
    // only for "attached" maps.
    int indices2[] = {0, kPropCount - 1};
    for (int i = 0; i < static_cast<int>(arraysize(indices2)); i++) {
      TestGeneralizeField(indices2[i], 2, from, to, expected, expected_alert);
    }

    // Check that reconfiguration to the very same field works correctly.
    CRFTData data = from;
    TestGeneralizeField(-1, 2, data, data, data, kNoAlert);
  }
}

}  // namespace

TEST(GeneralizeSmiFieldToDouble) {
  CcTest::InitializeVM();
  v8::HandleScope scope(CcTest::isolate());
  Isolate* isolate = CcTest::i_isolate();

  DirectHandle<FieldType> any_type = FieldType::Any(isolate);

  TestGeneralizeField(
      {PropertyConstness::kMutable, Representation::Smi(), any_type},
      {PropertyConstness::kMutable, Representation::Double(), any_type},
      {PropertyConstness::kMutable, Representation::Double(), any_type},
      kDeprecation);
}

TEST(GeneralizeSmiFieldToTagged) {
  CcTest::InitializeVM();
  v8::HandleScope scope(CcTest::isolate());
  Isolate* isolate = CcTest::i_isolate();

  DirectHandle<FieldType> any_type = FieldType::Any(isolate);
  DirectHandle<FieldType> value_type =
      FieldType::Class(Map::Create(isolate, 0), isolate);

  TestGeneralizeField(
      {PropertyConstness::kMutable, Representation::Smi(), any_type},
      {PropertyConstness::kMutable, Representation::HeapObject(), value_type},
      {PropertyConstness::kMutable, Representation::Tagged(), any_type},
      kFieldOwnerDependency);
}

TEST(GeneralizeDoubleFieldToTagged) {
  CcTest::InitializeVM();
  v8::HandleScope scope(CcTest::isolate());
  Isolate* isolate = CcTest::i_isolate();

  DirectHandle<FieldType> any_type = FieldType::Any(isolate);
  DirectHandle<FieldType> value_type =
      FieldType::Class(Map::Create(isolate, 0), isolate);

  TestGeneralizeField(
      {PropertyConstness::kMutable, Representation::Double(), any_type},
      {PropertyConstness::kMutable, Representation::HeapObject(), value_type},
      {PropertyConstness::kMutable, Representation::Tagged(), any_type},
      kFieldOwnerDependency);
}

TEST(GeneralizeHeapObjectFieldToTagged) {
  CcTest::InitializeVM();
  v8::HandleScope scope(CcTest::isolate());
  Isolate* isolate = CcTest::i_isolate();

  DirectHandle<FieldType> any_type = FieldType::Any(isolate);
  DirectHandle<FieldType> value_type =
      FieldType::Class(Map::Create(isolate, 0), isolate);

  TestGeneralizeField(
      {PropertyConstness::kMutable, Representation::HeapObject(), value_type},
      {PropertyConstness::kMutable, Representation::Smi(), any_type},
      {PropertyConstness::kMutable, Representation::Tagged(), any_type},
      kFieldOwnerDependency);
}

TEST(GeneralizeHeapObjectFieldToHeapObject) {
  CcTest::InitializeVM();
  v8::HandleScope scope(CcTest::isolate());
  Isolate* isolate = CcTest::i_isolate();

  DirectHandle<FieldType> any_type = FieldType::Any(isolate);

  DirectHandle<FieldType> current_type =
      FieldType::Class(Map::Create(isolate, 0), isolate);

  DirectHandle<FieldType> new_type =
      FieldType::Class(Map::Create(isolate, 0), isolate);

  DirectHandle<FieldType> expected_type = any_type;

  TestGeneralizeField(
      {PropertyConstness::kMutable, Representation::HeapObject(), current_type},
      {PropertyConstness::kMutable, Representation::HeapObject(), new_type},
      {PropertyConstness::kMutable, Representation::HeapObject(),
       expected_type},
      kFieldOwnerDependency);
  current_type = expected_type;

  new_type = FieldType::Class(Map::Create(isolate, 0), isolate);

  TestGeneralizeField(
      {PropertyConstness::kMutable, Representation::HeapObject(), any_type},
      {PropertyConstness::kMutable, Representation::HeapObject(), new_type},
      {PropertyConstness::kMutable, Representation::HeapObject(), any_type},
      kNoAlert);
}

TEST(GeneralizeNoneFieldToSmi) {
  CcTest::InitializeVM();
  v8::HandleScope scope(CcTest::isolate());
  Isolate* isolate = CcTest::i_isolate();

  DirectHandle<FieldType> none_type = FieldType::None(isolate);
  DirectHandle<FieldType> any_type = FieldType::Any(isolate);

  // None -> Smi representation change is trivial.
  TestGeneralizeField(
      {PropertyConstness::kMutable, Representation::None(), none_type},
      {PropertyConstness::kMutable, Representation::Smi(), any_type},
      {PropertyConstness::kMutable, Representation::Smi(), any_type},
      kFieldOwnerDependency);
}

TEST(GeneralizeNoneFieldToDouble) {
  CcTest::InitializeVM();
  v8::HandleScope scope(CcTest::isolate());
  Isolate* isolate = CcTest::i_isolate();

  DirectHandle<FieldType> none_type = FieldType::None(isolate);
  DirectHandle<FieldType> any_type = FieldType::Any(isolate);

  // None -> Double representation change is NOT trivial.
  TestGeneralizeField(
      {PropertyConstness::kMutable, Representation::None(), none_type},
      {PropertyConstness::kMutable, Representation::Double(), any_type},
      {PropertyConstness::kMutable, Representation::Double(), any_type},
      kDeprecation);
}

TEST(GeneralizeNoneFieldToHeapObject) {
  CcTest::InitializeVM();
  v8::HandleScope scope(CcTest::isolate());
  Isolate* isolate = CcTest::i_isolate();

  DirectHandle<FieldType> none_type = FieldType::None(isolate);
  DirectHandle<FieldType> value_type =
      FieldType::Class(Map::Create(isolate, 0), isolate);

  // None -> HeapObject representation change is trivial.
  TestGeneralizeField(
      {PropertyConstness::kMutable, Representation::None(), none_type},
      {PropertyConstness::kMutable, Representation::HeapObject(), value_type},
      {PropertyConstness::kMutable, Representation::HeapObject(), value_type},
      kFieldOwnerDependency);
}

TEST(GeneralizeNoneFieldToTagged) {
  CcTest::InitializeVM();
  v8::HandleScope scope(CcTest::isolate());
  Isolate* isolate = CcTest::i_isolate();

  DirectHandle<FieldType> none_type = FieldType::None(isolate);
  DirectHandle<FieldType> any_type = FieldType::Any(isolate);

  // None -> HeapObject representation change is trivial.
  TestGeneralizeField(
      {PropertyConstness::kMutable, Representation::None(), none_type},
      {PropertyConstness::kMutable, Representation::Tagged(), any_type},
      {PropertyConstness::kMutable, Representation::Tagged(), any_type},
      kFieldOwnerDependency);
}


////////////////////////////////////////////////////////////////////////////////
// A set of tests for field generalization case with kAccessor properties.
//

TEST(GeneralizeFieldWithAccessorProperties) {
  CcTest::InitializeVM();
  v8::HandleScope scope(CcTest::isolate());
  Isolate* isolate = CcTest::i_isolate();

  DirectHandle<FieldType> any_type = FieldType::Any(isolate);
  DirectHandle<AccessorPair> pair = CreateAccessorPair(true, true);

  const int kAccessorProp = kPropCount / 2;
  Expectations expectations(isolate);

  // Create a map, add required properties to it and initialize expectations.
  DirectHandle<Map> initial_map = Map::Create(isolate, 0);
  DirectHandle<Map> map = initial_map;
  for (int i = 0; i < kPropCount; i++) {
    if (i == kAccessorProp) {
      map = expectations.AddAccessorConstant(map, NONE, pair);
    } else {
      map = expectations.AddDataField(map, NONE, PropertyConstness::kMutable,
                                      Representation::Smi(), any_type);
    }
  }
  CHECK(!map->is_deprecated());
  CHECK(map->is_stable());
  CHECK(expectations.Check(*map));

  // Create new maps by generalizing representation of propX field.
  std::array<DirectHandle<Map>, kPropCount> maps;
  for (int i = 0; i < kPropCount; i++) {
    if (i == kAccessorProp) {
      // Skip accessor property reconfiguration.
      maps[i] = maps[i - 1];
      continue;
    }
    DirectHandle<Map> new_map =
        ReconfigureProperty(isolate, map, InternalIndex(i), PropertyKind::kData,
                            NONE, Representation::Double(), any_type);
    maps[i] = new_map;

    expectations.SetDataField(i, PropertyConstness::kMutable,
                              Representation::Double(), any_type);

    CHECK(!map->is_stable());
    CHECK(map->is_deprecated());
    CHECK_NE(*map, *new_map);
    CHECK(i == 0 || maps[i - 1]->is_deprecated());

    CHECK(!new_map->is_deprecated());
    CHECK(expectations.Check(*new_map));
  }

  DirectHandle<Map> active_map = maps[kPropCount - 1];
  CHECK(!active_map->is_deprecated());

  // Update all deprecated maps and check that they are now the same.
  DirectHandle<Map> updated_map = Map::Update(isolate, map);
  CHECK_EQ(*active_map, *updated_map);
  CheckMigrationTarget(isolate, *map, *updated_map);
  for (int i = 0; i < kPropCount; i++) {
    updated_map = Map::Update(isolate, maps[i]);
    CHECK_EQ(*active_map, *updated_map);
    CheckMigrationTarget(isolate, *maps[i], *updated_map);
  }
}

////////////////////////////////////////////////////////////////////////////////
// A set of tests for attribute reconfiguration case.
//

namespace {

// This test ensures that field generalization is correctly propagated from one
// branch of transition tree (|map2|) to another (|map|).
//
//             + - p2B - p3 - p4: |map2|
//             |
//  {} - p0 - p1 - p2A - p3 - p4: |map|
//
// where "p2A" and "p2B" differ only in the attributes.
//
void TestReconfigureDataFieldAttribute_GeneralizeField(
    const CRFTData& from, const CRFTData& to, const CRFTData& expected,
    ChangeAlertMechanism expected_alert) {
  Isolate* isolate = CcTest::i_isolate();

  Expectations expectations(isolate);

  // Create a map, add required properties to it and initialize expectations.
  DirectHandle<Map> initial_map = Map::Create(isolate, 0);
  DirectHandle<Map> map = initial_map;
  for (int i = 0; i < kPropCount; i++) {
    map = expectations.AddDataField(map, NONE, from.constness,
                                    from.representation, from.type);
  }
  CHECK(!map->is_deprecated());
  CHECK(map->is_stable());
  CHECK(expectations.Check(*map));

  // Create another branch in transition tree (property at index |kSplitProp|
  // has different attributes), initialize expectations.
  const int kSplitProp = kPropCount / 2;
  Expectations expectations2(isolate);

  DirectHandle<Map> map2 = initial_map;
  for (int i = 0; i < kSplitProp; i++) {
    map2 = expectations2.FollowDataTransition(map2, NONE, from.constness,
                                              from.representation, from.type);
  }
  map2 = expectations2.AddDataField(map2, READ_ONLY, to.constness,
                                    to.representation, to.type);

  for (int i = kSplitProp + 1; i < kPropCount; i++) {
    map2 = expectations2.AddDataField(map2, NONE, to.constness,
                                      to.representation, to.type);
  }
  CHECK(!map2->is_deprecated());
  CHECK(map2->is_stable());
  CHECK(expectations2.Check(*map2));

  // Create dummy optimized code object to test correct dependencies
  // on the field owner.
  Handle<Code> code_field_type = CreateDummyOptimizedCode(isolate);
  Handle<Code> code_field_repr = CreateDummyOptimizedCode(isolate);
  Handle<Code> code_field_const = CreateDummyOptimizedCode(isolate);
  Handle<Code> code_src_field_const = CreateDummyOptimizedCode(isolate);
  {
    Handle<Map> field_owner(map->FindFieldOwner(InternalIndex(kSplitProp)),
                            isolate);
    DependentCode::InstallDependency(isolate, code_field_type, field_owner,
                                     DependentCode::kFieldTypeGroup);
    DependentCode::InstallDependency(isolate, code_field_repr, field_owner,
                                     DependentCode::kFieldRepresentationGroup);
    DependentCode::InstallDependency(isolate, code_field_const, field_owner,
                                     DependentCode::kFieldConstGroup);
  }
  {
    Handle<Map> field_owner(map2->FindFieldOwner(InternalIndex(kSplitProp)),
                            isolate);
    DependentCode::InstallDependency(isolate, code_src_field_const, field_owner,
                                     DependentCode::kFieldConstGroup);
  }
  CHECK(!code_field_type->marked_for_deoptimization());
  CHECK(!code_field_repr->marked_for_deoptimization());
  CHECK(!code_field_const->marked_for_deoptimization());
  CHECK(!code_src_field_const->marked_for_deoptimization());

  // Reconfigure attributes of property |kSplitProp| of |map2| to NONE, which
  // should generalize representations in |map1|.
  DirectHandle<Map> new_map = MapUpdater::ReconfigureExistingProperty(
      isolate, map2, InternalIndex(kSplitProp), PropertyKind::kData, NONE,
      PropertyConstness::kConst);

  // |map2| should be mosly left unchanged but marked unstable and if the
  // source property was constant it should also be transitioned to kMutable.
  CHECK(!map2->is_stable());
  CHECK(!map2->is_deprecated());
  CHECK_NE(*map2, *new_map);
  CHECK(!code_src_field_const->marked_for_deoptimization());
  CHECK(expectations2.Check(*map2));

  for (int i = kSplitProp; i < kPropCount; i++) {
    expectations.SetDataField(i, expected.constness, expected.representation,
                              expected.type);
  }

  if (expected_alert == kDeprecation) {
    // |map| should be deprecated and |new_map| should match new expectations.
    CHECK(map->is_deprecated());
    CHECK(!code_field_type->marked_for_deoptimization());
    CHECK(!code_field_repr->marked_for_deoptimization());
    CHECK(!code_field_const->marked_for_deoptimization());
    CHECK_NE(*map, *new_map);

    CHECK(!new_map->is_deprecated());
    CHECK(expectations.Check(*new_map));

    // Update deprecated |map|, it should become |new_map|.
    DirectHandle<Map> updated_map = Map::Update(isolate, map);
    CHECK_EQ(*new_map, *updated_map);
    CheckMigrationTarget(isolate, *map, *updated_map);
  } else {
    CHECK(expected_alert == kFieldOwnerDependency ||
          expected_alert == kNoAlert);
    // In case of in-place generalization |map| should be returned as a result
    // of the property reconfiguration, respective field types should be
    // generalized and respective code dependencies should be invalidated.
    // |map| should be NOT deprecated and it should match new expectations.
    CHECK(!map->is_deprecated());
    CHECK_EQ(*map, *new_map);
    bool expect_deopt = expected_alert == kFieldOwnerDependency;
    CheckCodeObjectForDeopt(from, expected, code_field_type, code_field_repr,
                            code_field_const, expect_deopt);

    CHECK(!new_map->is_deprecated());
    CHECK(expectations.Check(*new_map));

    DirectHandle<Map> updated_map = Map::Update(isolate, map);
    CHECK_EQ(*new_map, *updated_map);
  }
}

}  // namespace

TEST(ReconfigureDataFieldAttribute_GeneralizeSmiFieldToDouble) {
  CcTest::InitializeVM();
  v8::HandleScope scope(CcTest::isolate());
  Isolate* isolate = CcTest::i_isolate();

  DirectHandle<FieldType> any_type = FieldType::Any(isolate);

  TestReconfigureDataFieldAttribute_GeneralizeField(
      {PropertyConstness::kConst, Representation::Smi(), any_type},
      {PropertyConstness::kConst, Representation::Double(), any_type},
      {PropertyConstness::kConst, Representation::Double(), any_type},
      kDeprecation);

  TestReconfigureDataFieldAttribute_GeneralizeField(
      {PropertyConstness::kConst, Representation::Smi(), any_type},
      {PropertyConstness::kMutable, Representation::Double(), any_type},
      {PropertyConstness::kMutable, Representation::Double(), any_type},
      kDeprecation);

  TestReconfigureDataFieldAttribute_GeneralizeField(
      {PropertyConstness::kMutable, Representation::Smi(), any_type},
      {PropertyConstness::kConst, Representation::Double(), any_type},
      {PropertyConstness::kMutable, Representation::Double(), any_type},
      kDeprecation);

  TestReconfigureDataFieldAttribute_GeneralizeField(
      {PropertyConstness::kMutable, Representation::Smi(), any_type},
      {PropertyConstness::kMutable, Representation::Double(), any_type},
      {PropertyConstness::kMutable, Representation::Double(), any_type},
      kDeprecation);
}

TEST(ReconfigureDataFieldAttribute_GeneralizeSmiFieldToTagged) {
  CcTest::InitializeVM();
  v8::HandleScope scope(CcTest::isolate());
  Isolate* isolate = CcTest::i_isolate();

  DirectHandle<FieldType> any_type = FieldType::Any(isolate);
  DirectHandle<FieldType> value_type =
      FieldType::Class(Map::Create(isolate, 0), isolate);

  TestReconfigureDataFieldAttribute_GeneralizeField(
      {PropertyConstness::kConst, Representation::Smi(), any_type},
      {PropertyConstness::kConst, Representation::HeapObject(), value_type},
      {PropertyConstness::kConst, Representation::Tagged(), any_type},
      kFieldOwnerDependency);

  TestReconfigureDataFieldAttribute_GeneralizeField(
      {PropertyConstness::kConst, Representation::Smi(), any_type},
      {PropertyConstness::kMutable, Representation::HeapObject(), value_type},
      {PropertyConstness::kMutable, Representation::Tagged(), any_type},
      kFieldOwnerDependency);

  TestReconfigureDataFieldAttribute_GeneralizeField(
      {PropertyConstness::kMutable, Representation::Smi(), any_type},
      {PropertyConstness::kConst, Representation::HeapObject(), value_type},
      {PropertyConstness::kMutable, Representation::Tagged(), any_type},
      kFieldOwnerDependency);

  TestReconfigureDataFieldAttribute_GeneralizeField(
      {PropertyConstness::kMutable, Representation::Smi(), any_type},
      {PropertyConstness::kMutable, Representation::HeapObject(), value_type},
      {PropertyConstness::kMutable, Representation::Tagged(), any_type},
      kFieldOwnerDependency);
}

TEST(ReconfigureDataFieldAttribute_GeneralizeDoubleFieldToTagged) {
  CcTest::InitializeVM();
  v8::HandleScope scope(CcTest::isolate());
  Isolate* isolate = CcTest::i_isolate();

  DirectHandle<FieldType> any_type = FieldType::Any(isolate);
  DirectHandle<FieldType> value_type =
      FieldType::Class(Map::Create(isolate, 0), isolate);

  TestReconfigureDataFieldAttribute_GeneralizeField(
      {PropertyConstness::kConst, Representation::Double(), any_type},
      {PropertyConstness::kConst, Representation::HeapObject(), value_type},
      {PropertyConstness::kConst, Representation::Tagged(), any_type},
      kFieldOwnerDependency);

  TestReconfigureDataFieldAttribute_GeneralizeField(
      {PropertyConstness::kConst, Representation::Double(), any_type},
      {PropertyConstness::kMutable, Representation::HeapObject(), value_type},
      {PropertyConstness::kMutable, Representation::Tagged(), any_type},
      kFieldOwnerDependency);

  TestReconfigureDataFieldAttribute_GeneralizeField(
      {PropertyConstness::kMutable, Representation::Double(), any_type},
      {PropertyConstness::kConst, Representation::HeapObject(), value_type},
      {PropertyConstness::kMutable, Representation::Tagged(), any_type},
      kFieldOwnerDependency);

  TestReconfigureDataFieldAttribute_GeneralizeField(
      {PropertyConstness::kMutable, Representation::Double(), any_type},
      {PropertyConstness::kMutable, Representation::HeapObject(), value_type},
      {PropertyConstness::kMutable, Representation::Tagged(), any_type},
      kFieldOwnerDependency);
}

TEST(ReconfigureDataFieldAttribute_GeneralizeHeapObjFieldToHeapObj) {
  CcTest::InitializeVM();
  v8::HandleScope scope(CcTest::isolate());
  Isolate* isolate = CcTest::i_isolate();

  DirectHandle<FieldType> any_type = FieldType::Any(isolate);

  DirectHandle<FieldType> current_type =
      FieldType::Class(Map::Create(isolate, 0), isolate);

  DirectHandle<FieldType> new_type =
      FieldType::Class(Map::Create(isolate, 0), isolate);

  DirectHandle<FieldType> expected_type = any_type;

  // Check generalizations that trigger deopts.
  TestReconfigureDataFieldAttribute_GeneralizeField(
      {PropertyConstness::kConst, Representation::HeapObject(), current_type},
      {PropertyConstness::kConst, Representation::HeapObject(), new_type},
      {PropertyConstness::kConst, Representation::HeapObject(), expected_type},
      kFieldOwnerDependency);

  // PropertyConstness::kConst to PropertyConstness::kMutable migration does
  // not create a new map, therefore trivial generalization.
  TestReconfigureDataFieldAttribute_GeneralizeField(
      {PropertyConstness::kConst, Representation::HeapObject(), current_type},
      {PropertyConstness::kMutable, Representation::HeapObject(), new_type},
      {PropertyConstness::kMutable, Representation::HeapObject(),
       expected_type},
      kFieldOwnerDependency);

  TestReconfigureDataFieldAttribute_GeneralizeField(
      {PropertyConstness::kMutable, Representation::HeapObject(), current_type},
      {PropertyConstness::kConst, Representation::HeapObject(), new_type},
      {PropertyConstness::kMutable, Representation::HeapObject(),
       expected_type},
      kFieldOwnerDependency);

  TestReconfigureDataFieldAttribute_GeneralizeField(
      {PropertyConstness::kMutable, Representation::HeapObject(), current_type},
      {PropertyConstness::kMutable, Representation::HeapObject(), new_type},
      {PropertyConstness::kMutable, Representation::HeapObject(),
       expected_type},
      kFieldOwnerDependency);
  current_type = expected_type;

  // Check generalizations that do not trigger deopts.
  new_type = FieldType::Class(Map::Create(isolate, 0), isolate);

  TestReconfigureDataFieldAttribute_GeneralizeField(
      {PropertyConstness::kConst, Representation::HeapObject(), any_type},
      {PropertyConstness::kConst, Representation::HeapObject(), new_type},
      {PropertyConstness::kConst, Representation::HeapObject(), any_type},
      kNoAlert);

  // PropertyConstness::kConst to PropertyConstness::kMutable migration does
  // not create a new map, therefore trivial generalization.
  TestReconfigureDataFieldAttribute_GeneralizeField(
      {PropertyConstness::kConst, Representation::HeapObject(), any_type},
      {PropertyConstness::kMutable, Representation::HeapObject(), new_type},
      {PropertyConstness::kMutable, Representation::HeapObject(), any_type},
      kFieldOwnerDependency);

  TestReconfigureDataFieldAttribute_GeneralizeField(
      {PropertyConstness::kMutable, Representation::HeapObject(), any_type},
      {PropertyConstness::kConst, Representation::HeapObject(), new_type},
      {PropertyConstness::kMutable, Representation::HeapObject(), any_type},
      kNoAlert);

  TestReconfigureDataFieldAttribute_GeneralizeField(
      {PropertyConstness::kMutable, Representation::HeapObject(), any_type},
      {PropertyConstness::kMutable, Representation::HeapObject(), new_type},
      {PropertyConstness::kMutable, Representation::HeapObject(), any_type},
      kNoAlert);
}

TEST(ReconfigureDataFieldAttribute_GeneralizeHeapObjectFieldToTagged) {
  CcTest::InitializeVM();
  v8::HandleScope scope(CcTest::isolate());
  Isolate* isolate = CcTest::i_isolate();

  DirectHandle<FieldType> any_type = FieldType::Any(isolate);
  DirectHandle<FieldType> value_type =
      FieldType::Class(Map::Create(isolate, 0), isolate);

  TestReconfigureDataFieldAttribute_GeneralizeField(
      {PropertyConstness::kMutable, Representation::HeapObject(), value_type},
      {PropertyConstness::kMutable, Representation::Smi(), any_type},
      {PropertyConstness::kMutable, Representation::Tagged(), any_type},
      kFieldOwnerDependency);
}

// Checks that given |map| is deprecated and that it updates to given |new_map|
// which in turn should match expectations.
struct CheckDeprecated {
  void Check(Isolate* isolate, DirectHandle<Map> map, DirectHandle<Map> new_map,
             const Expectations& expectations) {
    CHECK(map->is_deprecated());
    CHECK_NE(*map, *new_map);

    CHECK(!new_map->is_deprecated());
    CHECK(expectations.Check(*new_map));

    // Update deprecated |map|, it should become |new_map|.
    DirectHandle<Map> updated_map = Map::Update(isolate, map);
    CHECK_EQ(*new_map, *updated_map);
    CheckMigrationTarget(isolate, *map, *updated_map);
  }
};

// Checks that given |map| is NOT deprecated, equals to given |new_map| and
// matches expectations.
struct CheckSameMap {
  void Check(Isolate* isolate, DirectHandle<Map> map, DirectHandle<Map> new_map,
             const Expectations& expectations) {
    // |map| was not reconfigured, therefore it should stay stable.
    CHECK(map->is_stable());
    CHECK(!map->is_deprecated());
    CHECK_EQ(*map, *new_map);

    CHECK(!new_map->is_deprecated());
    CHECK(expectations.Check(*new_map));

    // Update deprecated |map|, it should become |new_map|.
    DirectHandle<Map> updated_map = Map::Update(isolate, map);
    CHECK_EQ(*new_map, *updated_map);
  }
};

// Checks that given |map| is NOT deprecated and matches expectations.
// |new_map| is unrelated to |map|.
struct CheckUnrelated {
  void Check(Isolate* isolate, DirectHandle<Map> map, DirectHandle<Map> new_map,
             const Expectations& expectations) {
    CHECK(!map->is_deprecated());
    CHECK_NE(*map, *new_map);
    CHECK(expectations.Check(*map));

    CHECK(new_map->is_stable());
    CHECK(!new_map->is_deprecated());
  }
};

// Checks that given |map| is NOT deprecated, and |new_map| is a result of going
// dictionary mode.
struct CheckNormalize {
  void Check(Isolate* isolate, DirectHandle<Map> map, DirectHandle<Map> new_map,
             const Expectations& expectations) {
    CHECK(!map->is_deprecated());
    CHECK_NE(*map, *new_map);

    CHECK(IsUndefined(new_map->GetBackPointer()));
    CHECK(!new_map->is_deprecated());
    CHECK(expectations.CheckNormalized(*new_map));
  }
};

// This test ensures that field generalization is correctly propagated from one
// branch of transition tree (|map2|) to another (|map1|).
//
//             + - p2B - p3 - p4: |map2|
//             |
//  {} - p0 - p1: |map|
//             |
//             + - p2A - p3 - p4: |map1|
//                        |
//                        + - the property customized by the TestConfig provided
//
// where "p2A" and "p2B" differ only in the attributes.
//
template <typename TestConfig, typename Checker>
static void TestReconfigureProperty_CustomPropertyAfterTargetMap(
    TestConfig* config, Checker* checker) {
  Isolate* isolate = CcTest::i_isolate();
  DirectHandle<FieldType> any_type = FieldType::Any(isolate);

  const int kCustomPropIndex = kPropCount - 2;
  Expectations expectations(isolate);

  const int kSplitProp = 2;
  CHECK_LT(kSplitProp, kCustomPropIndex);

  const PropertyConstness constness = PropertyConstness::kMutable;
  const Representation representation = Representation::Smi();

  // Create common part of transition tree.
  DirectHandle<Map> initial_map = Map::Create(isolate, 0);
  DirectHandle<Map> map = initial_map;
  for (int i = 0; i < kSplitProp; i++) {
    map = expectations.AddDataField(map, NONE, constness, representation,
                                    any_type);
  }
  CHECK(!map->is_deprecated());
  CHECK(map->is_stable());
  CHECK(expectations.Check(*map));

  // Create branch to |map1|.
  DirectHandle<Map> map1 = map;
  Expectations expectations1 = expectations;
  for (int i = kSplitProp; i < kCustomPropIndex; i++) {
    map1 = expectations1.AddDataField(map1, NONE, constness, representation,
                                      any_type);
  }
  map1 = config->AddPropertyAtBranch(1, &expectations1, map1);
  for (int i = kCustomPropIndex + 1; i < kPropCount; i++) {
    map1 = expectations1.AddDataField(map1, NONE, constness, representation,
                                      any_type);
  }
  CHECK(!map1->is_deprecated());
  CHECK(map1->is_stable());
  CHECK(expectations1.Check(*map1));

  // Create another branch in transition tree (property at index |kSplitProp|
  // has different attributes), initialize expectations.
  DirectHandle<Map> map2 = map;
  Expectations expectations2 = expectations;
  map2 = expectations2.AddDataField(map2, READ_ONLY, constness, representation,
                                    any_type);
  for (int i = kSplitProp + 1; i < kCustomPropIndex; i++) {
    map2 = expectations2.AddDataField(map2, NONE, constness, representation,
                                      any_type);
  }
  map2 = config->AddPropertyAtBranch(2, &expectations2, map2);
  for (int i = kCustomPropIndex + 1; i < kPropCount; i++) {
    map2 = expectations2.AddDataField(map2, NONE, constness, representation,
                                      any_type);
  }
  CHECK(!map2->is_deprecated());
  CHECK(map2->is_stable());
  CHECK(expectations2.Check(*map2));

  // Reconfigure attributes of property |kSplitProp| of |map2| to NONE, which
  // should generalize representations in |map1|.
  DirectHandle<Map> new_map = MapUpdater::ReconfigureExistingProperty(
      isolate, map2, InternalIndex(kSplitProp), PropertyKind::kData, NONE,
      PropertyConstness::kConst);

  // |map2| should be left unchanged but marked unstable.
  CHECK(!map2->is_stable());
  CHECK(!map2->is_deprecated());
  CHECK_NE(*map2, *new_map);
  CHECK(expectations2.Check(*map2));

  config->UpdateExpectations(kCustomPropIndex, &expectations1);
  checker->Check(isolate, map1, new_map, expectations1);
}

TEST(ReconfigureDataFieldAttribute_SameDataConstantAfterTargetMap) {
  CcTest::InitializeVM();
  v8::HandleScope scope(CcTest::isolate());

  struct TestConfig {
    Handle<JSFunction> js_func_;
    TestConfig() {
      Isolate* isolate = CcTest::i_isolate();
      Factory* factory = isolate->factory();
      js_func_ = factory->NewFunctionForTesting(factory->empty_string());
    }

    DirectHandle<Map> AddPropertyAtBranch(int branch_id,
                                          Expectations* expectations,
                                          DirectHandle<Map> map) {
      CHECK(branch_id == 1 || branch_id == 2);
      // Add the same data constant property at both transition tree branches.
      return expectations->AddDataConstant(map, NONE, js_func_);
    }

    void UpdateExpectations(int property_index, Expectations* expectations) {
      // Expectations stay the same.
    }
  };

  TestConfig config;
  // Two branches are "compatible" so the |map1| should NOT be deprecated.
  CheckSameMap checker;
  TestReconfigureProperty_CustomPropertyAfterTargetMap(&config, &checker);
}

TEST(ReconfigureDataFieldAttribute_DataConstantToDataFieldAfterTargetMap) {
  CcTest::InitializeVM();
  v8::HandleScope scope(CcTest::isolate());

  struct TestConfig {
    Handle<JSFunction> js_func1_;
    Handle<JSFunction> js_func2_;
    DirectHandle<FieldType> function_type_;
    TestConfig() {
      Isolate* isolate = CcTest::i_isolate();
      Factory* factory = isolate->factory();
      DirectHandle<String> name = factory->empty_string();
      DirectHandle<Map> sloppy_map =
          Map::CopyInitialMap(isolate, isolate->sloppy_function_map());
      DirectHandle<SharedFunctionInfo> info =
          factory->NewSharedFunctionInfoForBuiltin(name, Builtin::kIllegal, 0,
                                                   kDontAdapt);
      function_type_ = FieldType::Class(sloppy_map, isolate);
      CHECK(sloppy_map->is_stable());

      js_func1_ =
          Factory::JSFunctionBuilder{isolate, info, isolate->native_context()}
              .set_map(sloppy_map)
              .Build();

      js_func2_ =
          Factory::JSFunctionBuilder{isolate, info, isolate->native_context()}
              .set_map(sloppy_map)
              .Build();
    }

    DirectHandle<Map> AddPropertyAtBranch(int branch_id,
                                          Expectations* expectations,
                                          DirectHandle<Map> map) {
      CHECK(branch_id == 1 || branch_id == 2);
      DirectHandle<JSFunction> js_func = branch_id == 1 ? js_func1_ : js_func2_;
      return expectations->AddDataConstant(map, NONE, js_func);
    }

    void UpdateExpectations(int property_index, Expectations* expectations) {
      expectations->SetDataField(property_index, PropertyConstness::kConst,
                                 Representation::HeapObject(), function_type_);
    }
  };

  TestConfig config;
  CheckSameMap checker;
  TestReconfigureProperty_CustomPropertyAfterTargetMap(&config, &checker);
}

TEST(ReconfigureDataFieldAttribute_DataConstantToAccConstantAfterTargetMap) {
  CcTest::InitializeVM();
  v8::HandleScope scope(CcTest::isolate());

  struct TestConfig {
    Handle<JSFunction> js_func_;
    Handle<AccessorPair> pair_;
    TestConfig() {
      Isolate* isolate = CcTest::i_isolate();
      Factory* factory = isolate->factory();
      js_func_ = factory->NewFunctionForTesting(factory->empty_string());
      pair_ = CreateAccessorPair(true, true);
    }

    DirectHandle<Map> AddPropertyAtBranch(int branch_id,
                                          Expectations* expectations,
                                          DirectHandle<Map> map) {
      CHECK(branch_id == 1 || branch_id == 2);
      if (branch_id == 1) {
        return expectations->AddDataConstant(map, NONE, js_func_);
      } else {
        return expectations->AddAccessorConstant(map, NONE, pair_);
      }
    }

    void UpdateExpectations(int property_index, Expectations* expectations) {}
  };

  TestConfig config;
  // These are completely separate branches in transition tree.
  CheckUnrelated checker;
  TestReconfigureProperty_CustomPropertyAfterTargetMap(&config, &checker);
}


TEST(ReconfigureDataFieldAttribute_SameAccessorConstantAfterTargetMap) {
  CcTest::InitializeVM();
  v8::HandleScope scope(CcTest::isolate());

  struct TestConfig {
    Handle<AccessorPair> pair_;
    TestConfig() { pair_ = CreateAccessorPair(true, true); }

    DirectHandle<Map> AddPropertyAtBranch(int branch_id,
                                          Expectations* expectations,
                                          DirectHandle<Map> map) {
      CHECK(branch_id == 1 || branch_id == 2);
      // Add the same accessor constant property at both transition tree
      // branches.
      return expectations->AddAccessorConstant(map, NONE, pair_);
    }

    void UpdateExpectations(int property_index, Expectations* expectations) {
      // Two branches are "compatible" so the |map1| should NOT be deprecated.
    }
  };

  TestConfig config;
  CheckSameMap checker;
  TestReconfigureProperty_CustomPropertyAfterTargetMap(&config, &checker);
}


TEST(ReconfigureDataFieldAttribute_AccConstantToAccFieldAfterTargetMap) {
  CcTest::InitializeVM();
  v8::HandleScope scope(CcTest::isolate());

  struct TestConfig {
    Handle<AccessorPair> pair1_;
    Handle<AccessorPair> pair2_;
    TestConfig() {
      pair1_ = CreateAccessorPair(true, true);
      pair2_ = CreateAccessorPair(true, true);
    }

    DirectHandle<Map> AddPropertyAtBranch(int branch_id,
                                          Expectations* expectations,
                                          DirectHandle<Map> map) {
      CHECK(branch_id == 1 || branch_id == 2);
      DirectHandle<AccessorPair> pair = branch_id == 1 ? pair1_ : pair2_;
      return expectations->AddAccessorConstant(map, NONE, pair);
    }

    void UpdateExpectations(int property_index, Expectations* expectations) {
      if (IS_ACCESSOR_FIELD_SUPPORTED) {
        expectations->SetAccessorField(property_index);
      } else {
        // Currently we have a normalize case and ACCESSOR property becomes
        // ACCESSOR_CONSTANT.
        expectations->SetAccessorConstant(property_index, pair2_);
      }
    }
  };

  TestConfig config;
  if (IS_ACCESSOR_FIELD_SUPPORTED) {
    CheckSameMap checker;
    TestReconfigureProperty_CustomPropertyAfterTargetMap(&config, &checker);
  } else {
    // Currently we have a normalize case.
    CheckNormalize checker;
    TestReconfigureProperty_CustomPropertyAfterTargetMap(&config, &checker);
  }
}


TEST(ReconfigureDataFieldAttribute_AccConstantToDataFieldAfterTargetMap) {
  CcTest::InitializeVM();
  v8::HandleScope scope(CcTest::isolate());

  struct TestConfig {
    Handle<AccessorPair> pair_;
    TestConfig() { pair_ = CreateAccessorPair(true, true); }

    DirectHandle<Map> AddPropertyAtBranch(int branch_id,
                                          Expectations* expectations,
                                          DirectHandle<Map> map) {
      CHECK(branch_id == 1 || branch_id == 2);
      if (branch_id == 1) {
        return expectations->AddAccessorConstant(map, NONE, pair_);
      } else {
        Isolate* isolate = CcTest::i_isolate();
        DirectHandle<FieldType> any_type = FieldType::Any(isolate);
        return expectations->AddDataField(map, NONE, PropertyConstness::kConst,
                                          Representation::Smi(), any_type);
      }
    }

    void UpdateExpectations(int property_index, Expectations* expectations) {}
  };

  TestConfig config;
  // These are completely separate branches in transition tree.
  CheckUnrelated checker;
  TestReconfigureProperty_CustomPropertyAfterTargetMap(&config, &checker);
}


////////////////////////////////////////////////////////////////////////////////
// A set of tests for elements kind reconfiguration case.
//

namespace {

// This test ensures that in-place field generalization is correctly propagated
// from one branch of transition tree (|map2|) to another (|map|).
//
//   + - p0 - p1 - p2A - p3 - p4: |map|
//   |
//  ek
//   |
//  {} - p0 - p1 - p2B - p3 - p4: |map2|
//
// where "p2A" and "p2B" differ only in the representation/field type.
//
static void TestReconfigureElementsKind_GeneralizeFieldInPlace(
    const CRFTData& from, const CRFTData& to, const CRFTData& expected) {
  Isolate* isolate = CcTest::i_isolate();

  Expectations expectations(isolate, PACKED_SMI_ELEMENTS);

  // Create a map, add required properties to it and initialize expectations.
  Handle<Map> initial_map =
      isolate->factory()->NewContextfulMapForCurrentContext(
          JS_ARRAY_TYPE, JSArray::kHeaderSize, PACKED_SMI_ELEMENTS);
  initial_map->SetConstructor(*isolate->object_function());

  Handle<Map> map = initial_map;
  map = expectations.AsElementsKind(map, PACKED_ELEMENTS);
  for (int i = 0; i < kPropCount; i++) {
    map = expectations.AddDataField(map, NONE, from.constness,
                                    from.representation, from.type);
  }
  CHECK(!map->is_deprecated());
  CHECK(map->is_stable());
  CHECK(expectations.Check(*map));

  // Create another branch in transition tree (property at index |kDiffProp|
  // has different attributes), initialize expectations.
  const int kDiffProp = kPropCount / 2;
  Expectations expectations2(isolate, PACKED_SMI_ELEMENTS);

  DirectHandle<Map> map2 = initial_map;
  for (int i = 0; i < kPropCount; i++) {
    if (i == kDiffProp) {
      map2 = expectations2.AddDataField(map2, NONE, to.constness,
                                        to.representation, to.type);
    } else {
      map2 = expectations2.AddDataField(map2, NONE, from.constness,
                                        from.representation, from.type);
    }
  }
  CHECK(!map2->is_deprecated());
  CHECK(map2->is_stable());
  CHECK(expectations2.Check(*map2));

  // Create dummy optimized code object to test correct dependencies
  // on the field owner.
  Handle<Code> code_field_type = CreateDummyOptimizedCode(isolate);
  Handle<Code> code_field_repr = CreateDummyOptimizedCode(isolate);
  Handle<Code> code_field_const = CreateDummyOptimizedCode(isolate);
  Handle<Map> field_owner(map->FindFieldOwner(InternalIndex(kDiffProp)),
                          isolate);
  DependentCode::InstallDependency(isolate, code_field_type, field_owner,
                                   DependentCode::kFieldTypeGroup);
  DependentCode::InstallDependency(isolate, code_field_repr, field_owner,
                                   DependentCode::kFieldRepresentationGroup);
  DependentCode::InstallDependency(isolate, code_field_const, field_owner,
                                   DependentCode::kFieldConstGroup);
  CHECK(!code_field_type->marked_for_deoptimization());
  CHECK(!code_field_repr->marked_for_deoptimization());
  CHECK(!code_field_const->marked_for_deoptimization());

  // Reconfigure elements kinds of |map2|, which should generalize
  // representations in |map|.
  DirectHandle<Map> new_map =
      MapUpdater{isolate, map2}.ReconfigureElementsKind(PACKED_ELEMENTS);

  // |map2| should be left unchanged but marked unstable.
  CHECK(!map2->is_stable());
  CHECK(!map2->is_deprecated());
  CHECK_NE(*map2, *new_map);
  CHECK(expectations2.Check(*map2));

  // In case of in-place generalization |map| should be returned as a result of
  // the elements kind reconfiguration, respective field types should be
  // generalized and respective code dependencies should be invalidated.
  // |map| should be NOT deprecated and it should match new expectations.
  expectations.SetDataField(kDiffProp, expected.constness,
                            expected.representation, expected.type);
  CHECK(!map->is_deprecated());
  CHECK_EQ(*map, *new_map);
  CHECK_EQ(IsGeneralizableTo(to.constness, from.constness),
           !code_field_const->marked_for_deoptimization());
  CheckCodeObjectForDeopt(from, expected, code_field_type, code_field_repr,
                          DirectHandle<Code>(), false);

  CHECK(!new_map->is_deprecated());
  CHECK(expectations.Check(*new_map));

  DirectHandle<Map> updated_map = Map::Update(isolate, map);
  CHECK_EQ(*new_map, *updated_map);

  // Ensure Map::FindElementsKindTransitionedMap() is able to find the
  // transitioned map.
  {
    DirectHandle<Map> map_list[1]{updated_map};
    Tagged<Map> transitioned_map = map2->FindElementsKindTransitionedMap(
        isolate, map_list, ConcurrencyMode::kSynchronous);
    CHECK_EQ(*updated_map, transitioned_map);
  }
}

}  // namespace

TEST(ReconfigureElementsKind_GeneralizeSmiFieldToDouble) {
  CcTest::InitializeVM();
  v8::HandleScope scope(CcTest::isolate());
  Isolate* isolate = CcTest::i_isolate();

  DirectHandle<FieldType> any_type = FieldType::Any(isolate);

  TestReconfigureElementsKind_GeneralizeFieldInPlace(
      {PropertyConstness::kConst, Representation::Smi(), any_type},
      {PropertyConstness::kConst, Representation::Double(), any_type},
      {PropertyConstness::kConst, Representation::Double(), any_type});

  TestReconfigureElementsKind_GeneralizeFieldInPlace(
      {PropertyConstness::kConst, Representation::Smi(), any_type},
      {PropertyConstness::kMutable, Representation::Double(), any_type},
      {PropertyConstness::kMutable, Representation::Double(), any_type});

  TestReconfigureElementsKind_GeneralizeFieldInPlace(
      {PropertyConstness::kMutable, Representation::Smi(), any_type},
      {PropertyConstness::kConst, Representation::Double(), any_type},
      {PropertyConstness::kMutable, Representation::Double(), any_type});

  TestReconfigureElementsKind_GeneralizeFieldInPlace(
      {PropertyConstness::kMutable, Representation::Smi(), any_type},
      {PropertyConstness::kMutable, Representation::Double(), any_type},
      {PropertyConstness::kMutable, Representation::Double(), any_type});
}

TEST(ReconfigureElementsKind_GeneralizeSmiFieldToTagged) {
  CcTest::InitializeVM();
  v8::HandleScope scope(CcTest::isolate());
  Isolate* isolate = CcTest::i_isolate();

  DirectHandle<FieldType> any_type = FieldType::Any(isolate);
  DirectHandle<FieldType> value_type =
      FieldType::Class(Map::Create(isolate, 0), isolate);

  TestReconfigureElementsKind_GeneralizeFieldInPlace(
      {PropertyConstness::kConst, Representation::Smi(), any_type},
      {PropertyConstness::kConst, Representation::HeapObject(), value_type},
      {PropertyConstness::kConst, Representation::Tagged(), any_type});

  TestReconfigureElementsKind_GeneralizeFieldInPlace(
      {PropertyConstness::kConst, Representation::Smi(), any_type},
      {PropertyConstness::kMutable, Representation::HeapObject(), value_type},
      {PropertyConstness::kMutable, Representation::Tagged(), any_type});

  TestReconfigureElementsKind_GeneralizeFieldInPlace(
      {PropertyConstness::kMutable, Representation::Smi(), any_type},
      {PropertyConstness::kConst, Representation::HeapObject(), value_type},
      {PropertyConstness::kMutable, Representation::Tagged(), any_type});

  TestReconfigureElementsKind_GeneralizeFieldInPlace(
      {PropertyConstness::kMutable, Representation::Smi(), any_type},
      {PropertyConstness::kMutable, Representation::HeapObject(), value_type},
      {PropertyConstness::kMutable, Representation::Tagged(), any_type});
}

TEST(ReconfigureElementsKind_GeneralizeDoubleFieldToTagged) {
  CcTest::InitializeVM();
  v8::HandleScope scope(CcTest::isolate());
  Isolate* isolate = CcTest::i_isolate();

  DirectHandle<FieldType> any_type = FieldType::Any(isolate);
  DirectHandle<FieldType> value_type =
      FieldType::Class(Map::Create(isolate, 0), isolate);

  TestReconfigureElementsKind_GeneralizeFieldInPlace(
      {PropertyConstness::kConst, Representation::Double(), any_type},
      {PropertyConstness::kConst, Representation::HeapObject(), value_type},
      {PropertyConstness::kConst, Representation::Tagged(), any_type});

  TestReconfigureElementsKind_GeneralizeFieldInPlace(
      {PropertyConstness::kConst, Representation::Double(), any_type},
      {PropertyConstness::kMutable, Representation::HeapObject(), value_type},
      {PropertyConstness::kMutable, Representation::Tagged(), any_type});

  TestReconfigureElementsKind_GeneralizeFieldInPlace(
      {PropertyConstness::kMutable, Representation::Double(), any_type},
      {PropertyConstness::kConst, Representation::HeapObject(), value_type},
      {PropertyConstness::kMutable, Representation::Tagged(), any_type});

  TestReconfigureElementsKind_GeneralizeFieldInPlace(
      {PropertyConstness::kMutable, Representation::Double(), any_type},
      {PropertyConstness::kMutable, Representation::HeapObject(), value_type},
      {PropertyConstness::kMutable, Representation::Tagged(), any_type});
}

TEST(ReconfigureElementsKind_GeneralizeHeapObjFieldToHeapObj) {
  CcTest::InitializeVM();
  v8::HandleScope scope(CcTest::isolate());
  Isolate* isolate = CcTest::i_isolate();

  DirectHandle<FieldType> any_type = FieldType::Any(isolate);

  DirectHandle<FieldType> current_type =
      FieldType::Class(Map::Create(isolate, 0), isolate);

  DirectHandle<FieldType> new_type =
      FieldType::Class(Map::Create(isolate, 0), isolate);

  DirectHandle<FieldType> expected_type = any_type;

  // Check generalizations that trigger deopts.
  TestReconfigureElementsKind_GeneralizeFieldInPlace(
      {PropertyConstness::kConst, Representation::HeapObject(), current_type},
      {PropertyConstness::kConst, Representation::HeapObject(), new_type},
      {PropertyConstness::kConst, Representation::HeapObject(), expected_type});

  // PropertyConstness::kConst to PropertyConstness::kMutable migration does
  // not create a new map, therefore trivial generalization.
  TestReconfigureElementsKind_GeneralizeFieldInPlace(
      {PropertyConstness::kConst, Representation::HeapObject(), current_type},
      {PropertyConstness::kMutable, Representation::HeapObject(), new_type},
      {PropertyConstness::kMutable, Representation::HeapObject(),
       expected_type});

  TestReconfigureElementsKind_GeneralizeFieldInPlace(
      {PropertyConstness::kMutable, Representation::HeapObject(), current_type},
      {PropertyConstness::kConst, Representation::HeapObject(), new_type},
      {PropertyConstness::kMutable, Representation::HeapObject(),
       expected_type});

  TestReconfigureElementsKind_GeneralizeFieldInPlace(
      {PropertyConstness::kMutable, Representation::HeapObject(), current_type},
      {PropertyConstness::kMutable, Representation::HeapObject(), new_type},
      {PropertyConstness::kMutable, Representation::HeapObject(),
       expected_type});
  current_type = expected_type;

  // Check generalizations that do not trigger deopts.
  new_type = FieldType::Class(Map::Create(isolate, 0), isolate);

  TestReconfigureElementsKind_GeneralizeFieldInPlace(
      {PropertyConstness::kConst, Representation::HeapObject(), any_type},
      {PropertyConstness::kConst, Representation::HeapObject(), new_type},
      {PropertyConstness::kConst, Representation::HeapObject(), any_type});

  // PropertyConstness::kConst to PropertyConstness::kMutable migration does
  // not create a new map, therefore trivial generalization.
  TestReconfigureElementsKind_GeneralizeFieldInPlace(
      {PropertyConstness::kConst, Representation::HeapObject(), any_type},
      {PropertyConstness::kMutable, Representation::HeapObject(), new_type},
      {PropertyConstness::kMutable, Representation::HeapObject(), any_type});

  TestReconfigureElementsKind_GeneralizeFieldInPlace(
      {PropertyConstness::kMutable, Representation::HeapObject(), any_type},
      {PropertyConstness::kConst, Representation::HeapObject(), new_type},
      {PropertyConstness::kMutable, Representation::HeapObject(), any_type});

  TestReconfigureElementsKind_GeneralizeFieldInPlace(
      {PropertyConstness::kMutable, Representation::HeapObject(), any_type},
      {PropertyConstness::kMutable, Representation::HeapObject(), new_type},
      {PropertyConstness::kMutable, Representation::HeapObject(), any_type});
}

TEST(ReconfigureElementsKind_GeneralizeHeapObjectFieldToTagged) {
  CcTest::InitializeVM();
  v8::HandleScope scope(CcTest::isolate());
  Isolate* isolate = CcTest::i_isolate();

  DirectHandle<FieldType> any_type = FieldType::Any(isolate);
  DirectHandle<FieldType> value_type =
      FieldType::Class(Map::Create(isolate, 0), isolate);

  TestReconfigureElementsKind_GeneralizeFieldInPlace(
      {PropertyConstness::kConst, Representation::HeapObject(), value_type},
      {PropertyConstness::kConst, Representation::Smi(), any_type},
      {PropertyConstness::kConst, Representation::Tagged(), any_type});

  TestReconfigureElementsKind_GeneralizeFieldInPlace(
      {PropertyConstness::kConst, Representation::HeapObject(), value_type},
      {PropertyConstness::kMutable, Representation::Smi(), any_type},
      {PropertyConstness::kMutable, Representation::Tagged(), any_type});

  TestReconfigureElementsKind_GeneralizeFieldInPlace(
      {PropertyConstness::kMutable, Representation::HeapObject(), value_type},
      {PropertyConstness::kConst, Representation::Smi(), any_type},
      {PropertyConstness::kMutable, Representation::Tagged(), any_type});

  TestReconfigureElementsKind_GeneralizeFieldInPlace(
      {PropertyConstness::kMutable, Representation::HeapObject(), value_type},
      {PropertyConstness::kMutable, Representation::Smi(), any_type},
      {PropertyConstness::kMutable, Representation::Tagged(), any_type});
}

////////////////////////////////////////////////////////////////////////////////
// A set of tests checking split map deprecation.
//

TEST(ReconfigurePropertySplitMapTransitionsOverflow) {
  CcTest::InitializeVM();
  v8::HandleScope scope(CcTest::isolate());
  Isolate* isolate = CcTest::i_isolate();

  DirectHandle<FieldType> any_type = FieldType::Any(isolate);

  Expectations expectations(isolate);

  // Create a map, add required properties to it and initialize expectations.
  DirectHandle<Map> initial_map = Map::Create(isolate, 0);
  DirectHandle<Map> map = initial_map;
  for (int i = 0; i < kPropCount; i++) {
    map = expectations.AddDataField(map, NONE, PropertyConstness::kMutable,
                                    Representation::Smi(), any_type);
  }
  CHECK(!map->is_deprecated());
  CHECK(map->is_stable());

  // Generalize representation of property at index |kSplitProp|.
  const int kSplitProp = kPropCount / 2;
  DirectHandle<Map> split_map;
  DirectHandle<Map> map2 = initial_map;
  {
    for (int i = 0; i < kSplitProp + 1; i++) {
      if (i == kSplitProp) {
        split_map = map2;
      }

      DirectHandle<String> name = CcTest::MakeName("prop", i);
      MaybeDirectHandle<Map> target = TransitionsAccessor::SearchTransition(
          isolate, map2, *name, PropertyKind::kData, NONE);
      CHECK(!target.is_null());
      map2 = target.ToHandleChecked();
    }

    map2 = ReconfigureProperty(isolate, map2, InternalIndex(kSplitProp),
                               PropertyKind::kData, NONE,
                               Representation::Double(), any_type);
    expectations.SetDataField(kSplitProp, PropertyConstness::kMutable,
                              Representation::Double(), any_type);

    CHECK(expectations.Check(*split_map, kSplitProp));
    CHECK(expectations.Check(*map2, kSplitProp + 1));
  }

  // At this point |map| should be deprecated and disconnected from the
  // transition tree.
  CHECK(map->is_deprecated());
  CHECK(!split_map->is_deprecated());
  CHECK(map2->is_stable());
  CHECK(!map2->is_deprecated());

  // Fill in transition tree of |map2| so that it can't have more transitions.
  for (int i = 0; i < TransitionsAccessor::kMaxNumberOfTransitions; i++) {
    CHECK(TransitionsAccessor::CanHaveMoreTransitions(isolate, map2));
    DirectHandle<String> name = CcTest::MakeName("foo", i);
    Map::CopyWithField(isolate, map2, name, any_type, NONE,
                       PropertyConstness::kMutable, Representation::Smi(),
                       INSERT_TRANSITION)
        .ToHandleChecked();
  }
  CHECK(!TransitionsAccessor::CanHaveMoreTransitions(isolate, map2));

  // Try to update |map|, since there is no place for propX transition at |map2|
  // |map| should become normalized.
  DirectHandle<Map> updated_map = Map::Update(isolate, map);

  CheckNormalize checker;
  checker.Check(isolate, map2, updated_map, expectations);
}

////////////////////////////////////////////////////////////////////////////////
// A set of tests involving special transitions (such as elements kind
// transition, observed transition or prototype transition).
//
// This test ensures that field generalization is correctly propagated from one
// branch of transition tree (|map2|) to another (|map|).
//
//                            p4B: |map_b|
//                             ^
//                             |
//                             * - special transition
//                             |
//  {} - p0 - p1 - p2A - p3 - p4A: |map_a|
//
// where "p4A" and "p4B" are exactly the same properties.
//
// UpdateDirectionCheck::kFwd checks if updates to map_a propagate to map_b,
// whereas UpdateDirectionCheck::kBwd checks if updates to map_b propagate back
// to map_a.
//
enum class UpdateDirectionCheck { kFwd, kBwd };
template <typename TestConfig>
static void TestGeneralizeFieldWithSpecialTransition(
    TestConfig* config, const CRFTData& from, const CRFTData& to,
    const CRFTData& expected, ChangeAlertMechanism expected_alert,
    UpdateDirectionCheck direction = UpdateDirectionCheck::kFwd) {
  Isolate* isolate = CcTest::i_isolate();

  Expectations expectations_a(isolate);

  // Create a map, add required properties to it and initialize expectations.
  Handle<Map> map_a = Map::Create(isolate, 0);
  for (int i = 0; i < kPropCount; i++) {
    map_a = expectations_a.AddDataField(map_a, NONE, from.constness,
                                        from.representation, from.type);
  }
  CHECK(!map_a->is_deprecated());
  CHECK(map_a->is_stable());
  CHECK(expectations_a.Check(*map_a));

  Expectations expectations_b = expectations_a;

  // Apply some special transition to |map|.
  CHECK(map_a->owns_descriptors());
  Handle<Map> map_b = config->Transition(map_a, &expectations_b);

  // |map| should still match expectations.
  CHECK(!map_a->is_deprecated());
  CHECK(expectations_a.Check(*map_a));

  CHECK(!map_b->is_deprecated());
  CHECK(map_b->is_stable());
  CHECK(expectations_b.Check(*map_b));

  // Create dummy optimized code object to test correct dependencies
  // on the field owner.
  Handle<Code> code_field_type = CreateDummyOptimizedCode(isolate);
  Handle<Code> code_field_repr = CreateDummyOptimizedCode(isolate);
  Handle<Code> code_field_const = CreateDummyOptimizedCode(isolate);
  Handle<Map> field_owner(
      (direction == UpdateDirectionCheck::kFwd ? map_b : map_a)
          ->FindFieldOwner(InternalIndex(0)),
      isolate);
  DependentCode::InstallDependency(isolate, code_field_type, field_owner,
                                   DependentCode::kFieldTypeGroup);
  DependentCode::InstallDependency(isolate, code_field_repr, field_owner,
                                   DependentCode::kFieldRepresentationGroup);
  DependentCode::InstallDependency(isolate, code_field_const, field_owner,
                                   DependentCode::kFieldConstGroup);
  CHECK(!code_field_type->marked_for_deoptimization());
  CHECK(!code_field_repr->marked_for_deoptimization());
  CHECK(!code_field_const->marked_for_deoptimization());

  // Create new maps by generalizing representation of propX field.
  DirectHandle<Map> updated_maps[kPropCount];
  for (int i = 0; i < kPropCount; i++) {
    DirectHandle<Map> new_map_a = map_a;
    DirectHandle<Map> new_map_b = map_b;
    DirectHandle<Map> map_to_change =
        direction == UpdateDirectionCheck::kFwd ? map_a : map_b;
    DirectHandle<Map> changed_map = ReconfigureProperty(
        isolate, map_to_change, InternalIndex(i), PropertyKind::kData, NONE,
        to.representation, to.type);
    updated_maps[i] = changed_map;

    expectations_a.SetDataField(i, expected.constness, expected.representation,
                                expected.type);
    expectations_b.SetDataField(i, expected.constness, expected.representation,
                                expected.type);

    if (direction == UpdateDirectionCheck::kFwd) {
      new_map_a = changed_map;
      CHECK(expectations_a.Check(*new_map_a));
    } else {
      new_map_b = changed_map;
      CHECK(expectations_b.Check(*new_map_b));
    }

    // Prototype transitions are always moved to the front. Thus both
    // branches are independent since we have two independent property
    // owners in each branch. However on UpdatePrototype we do propagate
    // field types between the branches. Thus we need to call the MapUpdater
    // once more for the changes to propagate.
    if (new_map_a->prototype() != new_map_b->prototype()) {
      Expectations tmp = expectations_a;
      config->Transition(new_map_a, &tmp);
      // TODO(olivf) Prototype transitions do not propagate any changes back to
      // their "true" root map.
      DCHECK_EQ(direction, UpdateDirectionCheck::kFwd);
    }

    switch (expected_alert) {
      case kDeprecation: {
        CHECK(map_to_change->is_deprecated());

        CHECK_NE(*map_to_change, *changed_map);
        CHECK(i == 0 || updated_maps[i - 1]->is_deprecated());

        DirectHandle<Map> changed_map2 = Map::Update(isolate, map_to_change);
        CHECK_EQ(*changed_map, *changed_map2);

        new_map_a = Map::Update(isolate, new_map_a);
        new_map_b = Map::Update(isolate, new_map_b);

        CHECK(!new_map_a->is_deprecated());
        CHECK(!new_map_a->is_dictionary_map());
        CHECK(!new_map_b->is_deprecated());
        CHECK(!new_map_b->is_dictionary_map());

        // If Map::TryUpdate() manages to succeed the result must match the
        // result of Map::Update().
        DirectHandle<Map> tmp_map;
        CHECK(Map::TryUpdate(isolate, map_a).ToHandle(&tmp_map));
        CHECK_EQ(*new_map_a, *tmp_map);
        CHECK(Map::TryUpdate(isolate, map_b).ToHandle(&tmp_map));
        CHECK_EQ(*new_map_b, *tmp_map);

        CHECK(expectations_a.Check(*new_map_a));
        CHECK(expectations_b.Check(*new_map_b));
        CHECK(!IsUndefined(new_map_b->GetBackPointer()));
        break;
      }
      case kFieldOwnerDependency: {
        CHECK(!map_a->is_deprecated());
        CHECK_EQ(*map_a, *new_map_a);
        CHECK_NE(*map_a, *new_map_b);

        CHECK(!map_b->is_deprecated());
        CHECK_EQ(*map_b, *new_map_b);
        CHECK_NE(*map_b, *new_map_a);

        CHECK(expectations_b.Check(*new_map_b));
        CHECK(expectations_a.Check(*new_map_a));
        break;
      }
      case kNoAlert:
        UNREACHABLE();
        break;
    }
  }

  CheckCodeObjectForDeopt(from, expected, code_field_type, code_field_repr,
                          code_field_const,
                          expected_alert == kFieldOwnerDependency);

  DirectHandle<Map> active_map = updated_maps[kPropCount - 1];
  DirectHandle<Map> old_map =
      direction == UpdateDirectionCheck::kFwd ? map_a : map_b;
  CHECK(!active_map->is_deprecated());
  // Update all deprecated maps and check that they are now the same.
  DirectHandle<Map> updated_map = Map::Update(isolate, old_map);
  CHECK_EQ(*active_map, *updated_map);
  CheckMigrationTarget(isolate, *map_a, *updated_map);
  for (int i = 0; i < kPropCount; i++) {
    updated_map = Map::Update(isolate, updated_maps[i]);
    CHECK_EQ(*active_map, *updated_map);
    CheckMigrationTarget(isolate, *updated_maps[i], *updated_map);
  }
}

template <typename TestConfig>
void TestMultipleElementsKindTransitions(Isolate* isolate, TestConfig* config,
                                         UpdateDirectionCheck direction) {
  DirectHandle<FieldType> value_type(
      FieldType::Class(Map::Create(isolate, 0), isolate));
  DirectHandle<FieldType> any_type(FieldType::Any(isolate));

  TestGeneralizeFieldWithSpecialTransition(
      config, {PropertyConstness::kMutable, Representation::Smi(), any_type},
      {PropertyConstness::kMutable, Representation::HeapObject(), value_type},
      {PropertyConstness::kMutable, Representation::Tagged(), any_type},
      kFieldOwnerDependency, direction);

  TestGeneralizeFieldWithSpecialTransition(
      config, {PropertyConstness::kMutable, Representation::Double(), any_type},
      {PropertyConstness::kMutable, Representation::HeapObject(), value_type},
      {PropertyConstness::kMutable, Representation::Tagged(), any_type},
      kFieldOwnerDependency, direction);

  TestGeneralizeFieldWithSpecialTransition(
      config, {PropertyConstness::kMutable, Representation::Smi(), any_type},
      {PropertyConstness::kMutable, Representation::Double(), value_type},
      {PropertyConstness::kMutable, Representation::Double(), any_type},
      kDeprecation, direction);
}

TEST(ElementsKindTransitionFromMapOwningDescriptor) {
  CcTest::InitializeVM();
  v8::HandleScope scope(CcTest::isolate());
  Isolate* isolate = CcTest::i_isolate();

  struct TestConfig {
    TestConfig(PropertyAttributes attributes, Handle<Symbol> symbol,
               ElementsKind kind)
        : attributes(attributes), symbol(symbol), elements_kind(kind) {}

    Handle<Map> Transition(DirectHandle<Map> map, Expectations* expectations) {
      expectations->SetElementsKind(elements_kind);
      expectations->ChangeAttributesForAllProperties(attributes);
      return Map::CopyForPreventExtensions(CcTest::i_isolate(), map, attributes,
                                           symbol, "CopyForPreventExtensions");
    }

    PropertyAttributes attributes;
    Handle<Symbol> symbol;
    ElementsKind elements_kind;
  };
  Factory* factory = isolate->factory();
  TestConfig configs[] = {
      {FROZEN, factory->frozen_symbol(), HOLEY_FROZEN_ELEMENTS},
      {SEALED, factory->sealed_symbol(), HOLEY_SEALED_ELEMENTS},
      {NONE, factory->nonextensible_symbol(), HOLEY_NONEXTENSIBLE_ELEMENTS}};

  for (auto& direction :
       {UpdateDirectionCheck::kFwd, UpdateDirectionCheck::kBwd}) {
    for (size_t i = 0; i < arraysize(configs); i++) {
      TestMultipleElementsKindTransitions(isolate, &configs[i], direction);
    }
  }
}

TEST(ElementsKindTransitionFromMapNotOwningDescriptor) {
  CcTest::InitializeVM();
  v8::HandleScope scope(CcTest::isolate());
  Isolate* isolate = CcTest::i_isolate();

  struct TestConfig {
    TestConfig(PropertyAttributes attributes, Handle<Symbol> symbol,
               ElementsKind kind)
        : attributes(attributes), symbol(symbol), elements_kind(kind) {}

    Handle<Map> Transition(DirectHandle<Map> map, Expectations* expectations) {
      Isolate* isolate = CcTest::i_isolate();
      DirectHandle<FieldType> any_type = FieldType::Any(isolate);

      // Add one more transition to |map| in order to prevent descriptors
      // ownership.
      CHECK(map->owns_descriptors());
      Map::CopyWithField(isolate, map, CcTest::MakeString("foo"), any_type,
                         NONE, PropertyConstness::kMutable,
                         Representation::Smi(), INSERT_TRANSITION)
          .ToHandleChecked();
      CHECK(!map->owns_descriptors());

      expectations->SetElementsKind(elements_kind);
      expectations->ChangeAttributesForAllProperties(attributes);
      return Map::CopyForPreventExtensions(isolate, map, attributes, symbol,
                                           "CopyForPreventExtensions");
    }

    PropertyAttributes attributes;
    Handle<Symbol> symbol;
    ElementsKind elements_kind;
  };
  Factory* factory = isolate->factory();
  TestConfig configs[] = {
      {FROZEN, factory->frozen_symbol(), HOLEY_FROZEN_ELEMENTS},
      {SEALED, factory->sealed_symbol(), HOLEY_SEALED_ELEMENTS},
      {NONE, factory->nonextensible_symbol(), HOLEY_NONEXTENSIBLE_ELEMENTS}};

  for (auto& direction :
       {UpdateDirectionCheck::kFwd, UpdateDirectionCheck::kBwd}) {
    for (size_t i = 0; i < arraysize(configs); i++) {
      TestMultipleElementsKindTransitions(isolate, &configs[i], direction);
    }
  }
}

////////////////////////////////////////////////////////////////////////////////
// A set of tests for the prototype transition case.
//
// This test ensures that field generalization is correctly propagated across an
// UpdatePrototype transition.
//
// In the case of prototype transitions the transition tree is actually
// reshaped as:
//
//  {} - p0B - p1B - p2B - p3B - p4B: |map_b|
//  ^
//  |
//  * - prototype transition
//  |
//  {} - p0A - p1A - p2A - p3A - p4A: |map_a|
//
//  And the updates go via the MapUpdater. Thus generalizations from map_a to
//  map_b happen during UpdatePrototype, (i.e., on the transition of the next
//  object).
//
// By design updates currently only happen in forward direction, i.e., changes
// to map_a are propagated to map_b, but not the inverse.

template <typename TestConfig>
void TestMultiplePrototypeTransitions(Isolate* isolate, TestConfig* config) {
  DirectHandle<FieldType> value_type(
      FieldType::Class(Map::Create(isolate, 0), isolate));
  DirectHandle<FieldType> any_type(FieldType::Any(isolate));

  // Smi + HeapObject -> Tagged

  TestGeneralizeFieldWithSpecialTransition(
      config, {PropertyConstness::kConst, Representation::Smi(), any_type},
      {PropertyConstness::kConst, Representation::HeapObject(), value_type},
      {PropertyConstness::kConst, Representation::Tagged(), any_type},
      kFieldOwnerDependency);
  TestGeneralizeFieldWithSpecialTransition(
      config, {PropertyConstness::kMutable, Representation::Smi(), any_type},
      {PropertyConstness::kConst, Representation::HeapObject(), value_type},
      {PropertyConstness::kMutable, Representation::Tagged(), any_type},
      kFieldOwnerDependency);
  TestGeneralizeFieldWithSpecialTransition(
      config, {PropertyConstness::kMutable, Representation::Smi(), any_type},
      {PropertyConstness::kMutable, Representation::HeapObject(), value_type},
      {PropertyConstness::kMutable, Representation::Tagged(), any_type},
      kFieldOwnerDependency);

  TestGeneralizeFieldWithSpecialTransition(
      config, {PropertyConstness::kConst, Representation::Smi(), any_type},
      {PropertyConstness::kConst, Representation::HeapObject(), any_type},
      {PropertyConstness::kConst, Representation::Tagged(), any_type},
      kFieldOwnerDependency);

  // HeapObject + HeapObject -> Tagged

  TestGeneralizeFieldWithSpecialTransition(
      config,
      {PropertyConstness::kConst, Representation::HeapObject(), value_type},
      {PropertyConstness::kConst, Representation::HeapObject(), value_type},
      {PropertyConstness::kConst, Representation::HeapObject(), value_type},
      kFieldOwnerDependency);
  TestGeneralizeFieldWithSpecialTransition(
      config,
      {PropertyConstness::kMutable, Representation::HeapObject(), value_type},
      {PropertyConstness::kConst, Representation::HeapObject(), value_type},
      {PropertyConstness::kMutable, Representation::HeapObject(), value_type},
      kFieldOwnerDependency);
  TestGeneralizeFieldWithSpecialTransition(
      config,
      {PropertyConstness::kMutable, Representation::HeapObject(), value_type},
      {PropertyConstness::kMutable, Representation::HeapObject(), value_type},
      {PropertyConstness::kMutable, Representation::HeapObject(), value_type},
      kFieldOwnerDependency);

  TestGeneralizeFieldWithSpecialTransition(
      config,
      {PropertyConstness::kConst, Representation::HeapObject(), any_type},
      {PropertyConstness::kConst, Representation::HeapObject(), value_type},
      {PropertyConstness::kConst, Representation::HeapObject(), any_type},
      kFieldOwnerDependency);
  TestGeneralizeFieldWithSpecialTransition(
      config,
      {PropertyConstness::kMutable, Representation::HeapObject(), any_type},
      {PropertyConstness::kConst, Representation::HeapObject(), value_type},
      {PropertyConstness::kMutable, Representation::HeapObject(), any_type},
      kFieldOwnerDependency);
  TestGeneralizeFieldWithSpecialTransition(
      config,
      {PropertyConstness::kMutable, Representation::HeapObject(), any_type},
      {PropertyConstness::kMutable, Representation::HeapObject(), value_type},
      {PropertyConstness::kMutable, Representation::HeapObject(), any_type},
      kFieldOwnerDependency);

  // Double + HeapObject -> Tagged

  TestGeneralizeFieldWithSpecialTransition(
      config, {PropertyConstness::kConst, Representation::Double(), any_type},
      {PropertyConstness::kConst, Representation::HeapObject(), any_type},
      {PropertyConstness::kConst, Representation::Tagged(), any_type},
      kFieldOwnerDependency);

  TestGeneralizeFieldWithSpecialTransition(
      config, {PropertyConstness::kConst, Representation::Double(), any_type},
      {PropertyConstness::kConst, Representation::HeapObject(), value_type},
      {PropertyConstness::kConst, Representation::Tagged(), any_type},
      kFieldOwnerDependency);
  TestGeneralizeFieldWithSpecialTransition(
      config, {PropertyConstness::kMutable, Representation::Double(), any_type},
      {PropertyConstness::kConst, Representation::HeapObject(), value_type},
      {PropertyConstness::kMutable, Representation::Tagged(), any_type},
      kFieldOwnerDependency);
  TestGeneralizeFieldWithSpecialTransition(
      config, {PropertyConstness::kMutable, Representation::Double(), any_type},
      {PropertyConstness::kMutable, Representation::HeapObject(), value_type},
      {PropertyConstness::kMutable, Representation::Tagged(), any_type},
      kFieldOwnerDependency);

  // Smi + Double -> Double

  TestGeneralizeFieldWithSpecialTransition(
      config, {PropertyConstness::kConst, Representation::Smi(), any_type},
      {PropertyConstness::kConst, Representation::Double(), any_type},
      {PropertyConstness::kConst, Representation::Double(), any_type},
      kDeprecation);
  TestGeneralizeFieldWithSpecialTransition(
      config, {PropertyConstness::kMutable, Representation::Smi(), any_type},
      {PropertyConstness::kConst, Representation::Double(), any_type},
      {PropertyConstness::kMutable, Representation::Double(), any_type},
      kDeprecation);
  TestGeneralizeFieldWithSpecialTransition(
      config, {PropertyConstness::kMutable, Representation::Smi(), any_type},
      {PropertyConstness::kMutable, Representation::Double(), any_type},
      {PropertyConstness::kMutable, Representation::Double(), any_type},
      kDeprecation);
}

TEST(PrototypeTransitionFromMapOwningDescriptor) {
  CcTest::InitializeVM();
  v8::HandleScope scope(CcTest::isolate());
  Isolate* isolate = CcTest::i_isolate();

  struct TestConfig {
    Handle<JSObject> prototype_;

    TestConfig() {
      Isolate* isolate = CcTest::i_isolate();
      Factory* factory = isolate->factory();
      prototype_ = factory->NewJSObjectFromMap(Map::Create(isolate, 0));
    }

    Handle<Map> Transition(DirectHandle<Map> map, Expectations* expectations) {
      MapUpdater update(CcTest::i_isolate(), map);
      return update.ApplyPrototypeTransition(prototype_);
    }
  } config;

  TestMultiplePrototypeTransitions(isolate, &config);
}

TEST(PrototypeTransitionFromMapNotOwningDescriptor) {
  CcTest::InitializeVM();
  v8::HandleScope scope(CcTest::isolate());
  Isolate* isolate = CcTest::i_isolate();

  struct TestConfig {
    Handle<JSObject> prototype_;

    TestConfig() {
      Isolate* isolate = CcTest::i_isolate();
      Factory* factory = isolate->factory();
      prototype_ = factory->NewJSObjectFromMap(Map::Create(isolate, 0));
    }

    Handle<Map> Transition(DirectHandle<Map> map, Expectations* expectations) {
      Isolate* isolate = CcTest::i_isolate();
      DirectHandle<FieldType> any_type = FieldType::Any(isolate);

      // Add one more transition to |map| in order to prevent descriptors
      // ownership.
      if (map->owns_descriptors()) {
        Map::CopyWithField(isolate, map, CcTest::MakeString("foo"), any_type,
                           NONE, PropertyConstness::kMutable,
                           Representation::Smi(), INSERT_TRANSITION)
            .ToHandleChecked();
      }
      CHECK(!map->owns_descriptors());

      MapUpdater update(isolate, map);
      return update.ApplyPrototypeTransition(prototype_);
    }
  } config;

  TestMultiplePrototypeTransitions(isolate, &config);
}

////////////////////////////////////////////////////////////////////////////////
// A set of tests for higher level transitioning mechanics.
//

struct TransitionToDataFieldOperator {
  PropertyConstness constness_;
  Representation representation_;
  PropertyAttributes attributes_;
  DirectHandle<FieldType> heap_type_;
  DirectHandle<Object> value_;

  TransitionToDataFieldOperator(PropertyConstness constness,
                                Representation representation,
                                DirectHandle<FieldType> heap_type,
                                DirectHandle<Object> value,
                                PropertyAttributes attributes = NONE)
      : constness_(constness),
        representation_(representation),
        attributes_(attributes),
        heap_type_(heap_type),
        value_(value) {}

  DirectHandle<Map> DoTransition(Expectations* expectations,
                                 DirectHandle<Map> map) {
    return expectations->TransitionToDataField(
        map, attributes_, constness_, representation_, heap_type_, value_);
  }
};


struct TransitionToDataConstantOperator {
  PropertyAttributes attributes_;
  Handle<JSFunction> value_;

  TransitionToDataConstantOperator(Handle<JSFunction> value,
                                   PropertyAttributes attributes = NONE)
      : attributes_(attributes), value_(value) {}

  DirectHandle<Map> DoTransition(Expectations* expectations,
                                 DirectHandle<Map> map) {
    return expectations->TransitionToDataConstant(map, attributes_, value_);
  }
};


struct TransitionToAccessorConstantOperator {
  PropertyAttributes attributes_;
  Handle<AccessorPair> pair_;

  TransitionToAccessorConstantOperator(Handle<AccessorPair> pair,
                                       PropertyAttributes attributes = NONE)
      : attributes_(attributes), pair_(pair) {}

  DirectHandle<Map> DoTransition(Expectations* expectations,
                                 DirectHandle<Map> map) {
    return expectations->TransitionToAccessorConstant(map, attributes_, pair_);
  }
};


struct ReconfigureAsDataPropertyOperator {
  InternalIndex descriptor_;
  Representation representation_;
  PropertyAttributes attributes_;
  DirectHandle<FieldType> heap_type_;

  ReconfigureAsDataPropertyOperator(int descriptor,
                                    Representation representation,
                                    DirectHandle<FieldType> heap_type,
                                    PropertyAttributes attributes = NONE)
      : descriptor_(descriptor),
        representation_(representation),
        attributes_(attributes),
        heap_type_(heap_type) {}

  DirectHandle<Map> DoTransition(Isolate* isolate, Expectations* expectations,
                                 DirectHandle<Map> map) {
    expectations->SetDataField(descriptor_.as_int(),
                               PropertyConstness::kMutable, representation_,
                               heap_type_);
    return MapUpdater::ReconfigureExistingProperty(
        isolate, map, descriptor_, PropertyKind::kData, attributes_,
        PropertyConstness::kConst);
  }
};


struct ReconfigureAsAccessorPropertyOperator {
  InternalIndex descriptor_;
  PropertyAttributes attributes_;

  ReconfigureAsAccessorPropertyOperator(int descriptor,
                                        PropertyAttributes attributes = NONE)
      : descriptor_(descriptor), attributes_(attributes) {}

  DirectHandle<Map> DoTransition(Isolate* isolate, Expectations* expectations,
                                 DirectHandle<Map> map) {
    expectations->SetAccessorField(descriptor_.as_int());
    return MapUpdater::ReconfigureExistingProperty(
        isolate, map, descriptor_, PropertyKind::kAccessor, attributes_,
        PropertyConstness::kConst);
  }
};

// Checks that field generalization happened.
struct FieldGeneralizationChecker {
  int descriptor_;
  PropertyConstness constness_;
  Representation representation_;
  PropertyAttributes attributes_;
  DirectHandle<FieldType> heap_type_;

  FieldGeneralizationChecker(int descriptor, PropertyConstness constness,
                             Representation representation,
                             DirectHandle<FieldType> heap_type,
                             PropertyAttributes attributes = NONE)
      : descriptor_(descriptor),
        constness_(constness),
        representation_(representation),
        attributes_(attributes),
        heap_type_(heap_type) {}

  void Check(Isolate* isolate, Expectations* expectations,
             DirectHandle<Map> map1, DirectHandle<Map> map2) {
    CHECK(!map2->is_deprecated());

    CHECK(map1->is_deprecated());
    CHECK_NE(*map1, *map2);
    DirectHandle<Map> updated_map = Map::Update(isolate, map1);
    CHECK_EQ(*map2, *updated_map);
    CheckMigrationTarget(isolate, *map1, *updated_map);

    expectations->SetDataField(descriptor_, attributes_, constness_,
                               representation_, heap_type_);
    CHECK(expectations->Check(*map2));
  }
};


// Checks that existing transition was taken as is.
struct SameMapChecker {
  void Check(Isolate* isolate, Expectations* expectations,
             DirectHandle<Map> map1, DirectHandle<Map> map2) {
    CHECK(!map2->is_deprecated());
    CHECK_EQ(*map1, *map2);
    CHECK(expectations->Check(*map2));
  }
};


// Checks that both |map1| and |map2| should stays non-deprecated, this is
// the case when property kind is change.
struct PropertyKindReconfigurationChecker {
  void Check(Expectations* expectations, DirectHandle<Map> map1,
             DirectHandle<Map> map2) {
    CHECK(!map1->is_deprecated());
    CHECK(!map2->is_deprecated());
    CHECK_NE(*map1, *map2);
    CHECK(expectations->Check(*map2));
  }
};


// This test transitions to various property types under different
// circumstances.
// Plan:
// 1) create a |map| with p0..p3 properties.
// 2) create |map1| by adding "p4" to |map0|.
// 3) create |map2| by transition to "p4" from |map0|.
//
//                       + - p4B: |map2|
//                       |
//  {} - p0 - p1 - pA - p3: |map|
//                       |
//                       + - p4A: |map1|
//
// where "p4A" and "p4B" differ only in the attributes.
//
template <typename TransitionOp1, typename TransitionOp2, typename Checker>
static void TestTransitionTo(TransitionOp1* transition_op1,
                             TransitionOp2* transition_op2, Checker* checker) {
  Isolate* isolate = CcTest::i_isolate();
  DirectHandle<FieldType> any_type = FieldType::Any(isolate);

  Expectations expectations(isolate);

  // Create a map, add required properties to it and initialize expectations.
  DirectHandle<Map> initial_map = Map::Create(isolate, 0);
  DirectHandle<Map> map = initial_map;
  for (int i = 0; i < kPropCount - 1; i++) {
    map = expectations.AddDataField(map, NONE, PropertyConstness::kMutable,
                                    Representation::Smi(), any_type);
  }
  CHECK(expectations.Check(*map));

  Expectations expectations1 = expectations;
  DirectHandle<Map> map1 = transition_op1->DoTransition(&expectations1, map);
  CHECK(expectations1.Check(*map1));

  Expectations expectations2 = expectations;
  DirectHandle<Map> map2 = transition_op2->DoTransition(&expectations2, map);

  // Let the test customization do the check.
  checker->Check(isolate, &expectations2, map1, map2);
}

TEST(TransitionDataFieldToDataField) {
  CcTest::InitializeVM();
  v8::HandleScope scope(CcTest::isolate());
  Isolate* isolate = CcTest::i_isolate();

  DirectHandle<FieldType> any_type = FieldType::Any(isolate);

  DirectHandle<Object> value1(Smi::zero(), isolate);
  TransitionToDataFieldOperator transition_op1(
      PropertyConstness::kMutable, Representation::Smi(), any_type, value1);

  DirectHandle<Object> value2 = isolate->factory()->NewHeapNumber(0);
  TransitionToDataFieldOperator transition_op2(
      PropertyConstness::kMutable, Representation::Double(), any_type, value2);

  FieldGeneralizationChecker checker(kPropCount - 1,
                                     PropertyConstness::kMutable,
                                     Representation::Double(), any_type);
  TestTransitionTo(&transition_op1, &transition_op2, &checker);
}

TEST(TransitionDataConstantToSameDataConstant) {
  CcTest::InitializeVM();
  v8::HandleScope scope(CcTest::isolate());
  Isolate* isolate = CcTest::i_isolate();
  Factory* factory = isolate->factory();

  Handle<JSFunction> js_func =
      factory->NewFunctionForTesting(factory->empty_string());
  TransitionToDataConstantOperator transition_op(js_func);

  SameMapChecker checker;
  TestTransitionTo(&transition_op, &transition_op, &checker);
}


TEST(TransitionDataConstantToAnotherDataConstant) {
  CcTest::InitializeVM();
  v8::HandleScope scope(CcTest::isolate());
  Isolate* isolate = CcTest::i_isolate();
  Factory* factory = isolate->factory();

  DirectHandle<String> name = factory->empty_string();
  DirectHandle<Map> sloppy_map =
      Map::CopyInitialMap(isolate, isolate->sloppy_function_map());
  DirectHandle<SharedFunctionInfo> info =
      factory->NewSharedFunctionInfoForBuiltin(name, Builtin::kIllegal, 0,
                                               kDontAdapt);
  CHECK(sloppy_map->is_stable());

  Handle<JSFunction> js_func1 =
      Factory::JSFunctionBuilder{isolate, info, isolate->native_context()}
          .set_map(sloppy_map)
          .Build();
  TransitionToDataConstantOperator transition_op1(js_func1);

  Handle<JSFunction> js_func2 =
      Factory::JSFunctionBuilder{isolate, info, isolate->native_context()}
          .set_map(sloppy_map)
          .Build();
  TransitionToDataConstantOperator transition_op2(js_func2);

  SameMapChecker checker;
  TestTransitionTo(&transition_op1, &transition_op2, &checker);
}


TEST(TransitionDataConstantToDataField) {
  CcTest::InitializeVM();
  v8::HandleScope scope(CcTest::isolate());
  Isolate* isolate = CcTest::i_isolate();
  Factory* factory = isolate->factory();

  DirectHandle<FieldType> any_type = FieldType::Any(isolate);

  Handle<JSFunction> js_func1 =
      factory->NewFunctionForTesting(factory->empty_string());
  TransitionToDataConstantOperator transition_op1(js_func1);

  DirectHandle<Object> value2 = isolate->factory()->NewHeapNumber(0);
  TransitionToDataFieldOperator transition_op2(
      PropertyConstness::kMutable, Representation::Tagged(), any_type, value2);

  SameMapChecker checker;
  TestTransitionTo(&transition_op1, &transition_op2, &checker);
}


TEST(TransitionAccessorConstantToSameAccessorConstant) {
  CcTest::InitializeVM();
  v8::HandleScope scope(CcTest::isolate());

  Handle<AccessorPair> pair = CreateAccessorPair(true, true);
  TransitionToAccessorConstantOperator transition_op(pair);

  SameMapChecker checker;
  TestTransitionTo(&transition_op, &transition_op, &checker);
}

// TODO(ishell): add this test once IS_ACCESSOR_FIELD_SUPPORTED is supported.
// TEST(TransitionAccessorConstantToAnotherAccessorConstant)

TEST(HoleyHeapNumber) {
  CcTest::InitializeVM();
  v8::HandleScope scope(CcTest::isolate());
  Isolate* isolate = CcTest::i_isolate();

  auto mhn = isolate->factory()->NewHeapNumberWithHoleNaN();
  CHECK_EQ(kHoleNanInt64, mhn->value_as_bits());

  mhn = isolate->factory()->NewHeapNumber(0.0);
  CHECK_EQ(uint64_t{0}, mhn->value_as_bits());

  mhn->set_value_as_bits(kHoleNanInt64);
  CHECK_EQ(kHoleNanInt64, mhn->value_as_bits());

  // Ensure that new storage for uninitialized value or mutable heap number
  // with uninitialized sentinel (kHoleNanInt64) is a mutable heap number
  // with uninitialized sentinel.
  DirectHandle<Object> obj =
      Object::NewStorageFor(isolate, isolate->factory()->uninitialized_value(),
                            Representation::Double());
  CHECK(IsHeapNumber(*obj));
  CHECK_EQ(kHoleNanInt64, Cast<HeapNumber>(*obj)->value_as_bits());

  obj = Object::NewStorageFor(isolate, mhn, Representation::Double());
  CHECK(IsHeapNumber(*obj));
  CHECK_EQ(kHoleNanInt64, Cast<HeapNumber>(*obj)->value_as_bits());
}

namespace {

template <class... Args>
MaybeDirectHandle<Object> Call(Isolate* isolate,
                               DirectHandle<JSFunction> function,
                               Args... args) {
  DirectHandle<Object> arguments[] = {args...};
  return Execution::Call(isolate, function,
                         isolate->factory()->undefined_value(),
                         base::VectorOf(arguments));
}

void TestStoreToConstantField(const char* store_func_source,
                              Handle<Object> value1, Handle<Object> value2,
                              Representation expected_rep,
                              PropertyConstness expected_constness,
                              int store_repetitions) {
  Isolate* isolate = CcTest::i_isolate();
  CompileRun(store_func_source);

  DirectHandle<JSFunction> store_func = GetGlobal<JSFunction>("store");

  DirectHandle<Map> initial_map = Map::Create(isolate, 4);

  // Store value1 to obj1 and check that it got property with expected
  // representation and constness.
  Handle<JSObject> obj1 = isolate->factory()->NewJSObjectFromMap(initial_map);
  for (int i = 0; i < store_repetitions; i++) {
    Call(isolate, store_func, obj1, value1).Check();
  }

  DirectHandle<Map> map(obj1->map(), isolate);
  CHECK(!map->is_dictionary_map());
  CHECK(!map->is_deprecated());
  CHECK_EQ(1, map->NumberOfOwnDescriptors());
  InternalIndex first(0);
  CHECK(map->instance_descriptors()->GetDetails(first).representation().Equals(
      expected_rep));
  CHECK_EQ(PropertyConstness::kConst,
           map->instance_descriptors()->GetDetails(first).constness());

  // Store value2 to obj2 and check that it got same map and property details
  // did not change.
  Handle<JSObject> obj2 = isolate->factory()->NewJSObjectFromMap(initial_map);
  Call(isolate, store_func, obj2, value2).Check();

  CHECK_EQ(*map, obj2->map());
  CHECK(!map->is_dictionary_map());
  CHECK(!map->is_deprecated());
  CHECK_EQ(1, map->NumberOfOwnDescriptors());

  CHECK(map->instance_descriptors()->GetDetails(first).representation().Equals(
      expected_rep));
  CHECK_EQ(PropertyConstness::kConst,
           map->instance_descriptors()->GetDetails(first).constness());

  // Store value2 to obj1 and check that property became mutable.
  Call(isolate, store_func, obj1, value2).Check();

  CHECK_EQ(*map, obj1->map());
  CHECK(!map->is_dictionary_map());
  CHECK(!map->is_deprecated());
  CHECK_EQ(1, map->NumberOfOwnDescriptors());

  CHECK(map->instance_descriptors()->GetDetails(first).representation().Equals(
      expected_rep));
  CHECK_EQ(expected_constness,
           map->instance_descriptors()->GetDetails(first).constness());
}

void TestStoreToConstantField_PlusMinusZero(const char* store_func_source,
                                            int store_repetitions) {
  Isolate* isolate = CcTest::i_isolate();
  CompileRun(store_func_source);

  Handle<Object> minus_zero = isolate->factory()->NewNumber(-0.0);
  Handle<Object> plus_zero = isolate->factory()->NewNumber(0.0);

  // +0 and -0 are treated as not equal upon stores.
  const PropertyConstness kExpectedFieldConstness = PropertyConstness::kMutable;

  TestStoreToConstantField(store_func_source, minus_zero, plus_zero,
                           Representation::Double(), kExpectedFieldConstness,
                           store_repetitions);
}

void TestStoreToConstantField_NaN(const char* store_func_source,
                                  int store_repetitions) {
  Isolate* isolate = CcTest::i_isolate();
  CompileRun(store_func_source);

  uint64_t nan_bits = uint64_t{0x7FF8000000000001};
  double nan_double1 = base::bit_cast<double>(nan_bits);
  double nan_double2 = base::bit_cast<double>(nan_bits | 0x12300);
  CHECK(std::isnan(nan_double1));
  CHECK(std::isnan(nan_double2));
  CHECK_NE(nan_double1, nan_double2);
  CHECK_NE(base::bit_cast<uint64_t>(nan_double1),
           base::bit_cast<uint64_t>(nan_double2));

  Handle<Object> nan1 = isolate->factory()->NewNumber(nan_double1);
  Handle<Object> nan2 = isolate->factory()->NewNumber(nan_double2);

  // NaNs with different bit patters are not treated as equal upon stores.
  TestStoreToConstantField(store_func_source, nan1, nan2,
                           Representation::Double(),
                           PropertyConstness::kMutable, store_repetitions);
}

}  // namespace

TEST(StoreToConstantField_PlusMinusZero) {
  v8_flags.allow_natives_syntax = true;
  CcTest::InitializeVM();
  v8::HandleScope scope(CcTest::isolate());

  const char* store_func_source =
      "function store(o, v) {"
      "  %SetNamedProperty(o, 'v', v);"
      "}";

  TestStoreToConstantField_PlusMinusZero(store_func_source, 1);

  TestStoreToConstantField_NaN(store_func_source, 1);
}

TEST(StoreToConstantField_ObjectDefineProperty) {
  CcTest::InitializeVM();
  v8::HandleScope scope(CcTest::isolate());

  const char* store_func_source =
      "function store(o, v) {"
      "  Object.defineProperty(o, 'v', "
      "                        {value: v, "
      "                         writable: true, "
      "                         configurable: true, "
      "                         enumerable: true});"
      "}";

  TestStoreToConstantField_PlusMinusZero(store_func_source, 1);

  TestStoreToConstantField_NaN(store_func_source, 1);
}

TEST(StoreToConstantField_ReflectSet) {
  CcTest::InitializeVM();
  v8::HandleScope scope(CcTest::isolate());

  const char* store_func_source =
      "function store(o, v) {"
      "  Reflect.set(o, 'v', v);"
      "}";

  TestStoreToConstantField_PlusMinusZero(store_func_source, 1);

  TestStoreToConstantField_NaN(store_func_source, 1);
}

TEST(StoreToConstantField_StoreIC) {
  CcTest::InitializeVM();
  v8::HandleScope scope(CcTest::isolate());

  const char* store_func_source =
      "function store(o, v) {"
      "  o.v = v;"
      "}";

  TestStoreToConstantField_PlusMinusZero(store_func_source, 1);

  TestStoreToConstantField_NaN(store_func_source, 1);
}

TEST(NormalizeToMigrationTarget) {
  CcTest::InitializeVM();
  v8::HandleScope scope(CcTest::isolate());
  Isolate* isolate = CcTest::i_isolate();

  CHECK(
      IsNormalizedMapCache(isolate->native_context()->normalized_map_cache()));

  DirectHandle<Map> base_map = Map::Create(isolate, 4);

  DirectHandle<Map> existing_normalized_map = Map::Normalize(
      isolate, base_map, PropertyNormalizationMode::CLEAR_INOBJECT_PROPERTIES,
      "Test_NormalizeToMigrationTarget_ExistingMap");
  existing_normalized_map->set_is_migration_target(true);

  // Normalizing a second map should hit the normalized map cache, including it
  // being OK for the new map to be a migration target.
  CHECK(!base_map->is_migration_target());
  DirectHandle<Map> new_normalized_map = Map::Normalize(
      isolate, base_map, PropertyNormalizationMode::CLEAR_INOBJECT_PROPERTIES,
      "Test_NormalizeToMigrationTarget_NewMap");
  CHECK_EQ(*existing_normalized_map, *new_normalized_map);
  CHECK(new_normalized_map->is_migration_target());
}

TEST(RepresentationPredicatesAreInSync) {
  static_assert(Representation::kNumRepresentations == 6);
  static Representation reps[] = {
      Representation::None(),   Representation::Smi(),
      Representation::Double(), Representation::HeapObject(),
      Representation::Tagged(), Representation::WasmValue()};

  for (Representation from : reps) {
    Representation most_generic_rep = from.MostGenericInPlaceChange();
    CHECK(from.CanBeInPlaceChangedTo(most_generic_rep));

    bool might_be_deprecated = false;

    for (Representation to : reps) {
      // Skip representation narrowing cases.
      if (!from.fits_into(to)) continue;

      if (!from.CanBeInPlaceChangedTo(to)) {
        might_be_deprecated = true;
      }
    }
    CHECK_EQ(from.MightCauseMapDeprecation(), might_be_deprecated);
  }
}

#define CHECK_SAME(object, rep, expected)                    \
  CHECK_EQ(Object::FitsRepresentation(*object, rep, true),   \
           Object::FitsRepresentation(*object, rep, false)); \
  CHECK_EQ(Object::FitsRepresentation(*object, rep, true), expected)

TEST(CheckFitsRepresentationPredicate) {
  CcTest::InitializeVM();
  v8::HandleScope scope(CcTest::isolate());
  i::Factory* factory = CcTest::i_isolate()->factory();

  DirectHandle<Smi> smi_value = factory->last_script_id();
  DirectHandle<HeapNumber> double_value = factory->nan_value();
  DirectHandle<OrderedHashMap> heapobject_value =
      factory->empty_ordered_hash_map();

  Representation rep_smi = Representation::Smi();
  Representation rep_double = Representation::Double();
  Representation rep_heapobject = Representation::HeapObject();
  Representation rep_tagged = Representation::Tagged();

  // Verify the behavior of Object::FitsRepresentation() with and
  // without coercion. A Smi can be "coerced" into a Double
  // representation by converting it to a HeapNumber. If coercion is
  // disallowed, that query should fail.
  CHECK_SAME(smi_value, rep_smi, true);
  CHECK_EQ(Object::FitsRepresentation(*smi_value, rep_double, true), true);
  CHECK_EQ(Object::FitsRepresentation(*smi_value, rep_double, false), false);
  CHECK_SAME(smi_value, rep_heapobject, false);
  CHECK_SAME(smi_value, rep_tagged, true);

  CHECK_SAME(double_value, rep_smi, false);
  CHECK_SAME(double_value, rep_double, true);
  CHECK_SAME(double_value, rep_heapobject, true);
  CHECK_SAME(double_value, rep_tagged, true);

  CHECK_SAME(heapobject_value, rep_smi, false);
  CHECK_SAME(heapobject_value, rep_double, false);
  CHECK_SAME(heapobject_value, rep_heapobject, true);
  CHECK_SAME(heapobject_value, rep_tagged, true);
}

#undef CHECK_SAME

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