blob: 312f5ddcf9042085422649765a8fafaca1914be6 [file]
// Copyright 2014 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/ast/ast.h"
#include "src/base/logging.h"
#include "src/common/globals.h"
#include "src/execution/arguments-inl.h"
#include "src/execution/isolate-inl.h"
#include "src/handles/maybe-handles.h"
#include "src/objects/allocation-site-scopes-inl.h"
#include "src/objects/casting.h"
#include "src/objects/dictionary-inl.h"
#include "src/objects/field-type.h"
#include "src/objects/hash-table-inl.h"
#include "src/objects/heap-number-inl.h"
#include "src/objects/heap-object.h"
#include "src/objects/js-data-object-builder-inl.h"
#include "src/objects/js-regexp-inl.h"
#include "src/objects/literal-objects-inl.h"
#include "src/objects/lookup.h"
#include "src/objects/map-updater.h"
#include "src/objects/object-conversions-inl.h"
#include "src/objects/objects.h"
#include "src/objects/property-descriptor-object.h"
#include "src/objects/property-descriptor.h"
#include "src/objects/property-details.h"
#include "src/objects/transitions-inl.h"
#include "src/runtime/runtime.h"
namespace v8 {
namespace internal {
namespace {
bool IsUninitializedLiteralSite(Tagged<Object> literal_site) {
return literal_site == Smi::zero();
}
bool HasBoilerplate(DirectHandle<Object> literal_site) {
return !IsSmi(*literal_site);
}
void PreInitializeLiteralSite(DirectHandle<FeedbackVector> vector,
FeedbackSlot slot) {
vector->SynchronizedSet(slot, Smi::FromInt(1));
}
template <class ContextObject>
class JSObjectWalkVisitor {
public:
explicit JSObjectWalkVisitor(ContextObject* site_context)
: site_context_(site_context) {}
V8_WARN_UNUSED_RESULT MaybeHandle<JSObject> StructureWalk(
Handle<JSObject> object);
protected:
V8_WARN_UNUSED_RESULT inline MaybeHandle<JSObject> VisitElementOrProperty(
DirectHandle<JSObject> object, Handle<JSObject> value) {
// Dont create allocation sites for nested object literals
if (!IsJSArray(*value)) {
return StructureWalk(value);
}
DirectHandle<AllocationSite> current_site = site_context()->EnterNewScope();
MaybeHandle<JSObject> copy_of_value = StructureWalk(value);
site_context()->ExitScope(current_site, value);
return copy_of_value;
}
inline ContextObject* site_context() { return site_context_; }
inline Isolate* isolate() { return site_context()->isolate(); }
private:
ContextObject* site_context_;
};
template <class ContextObject>
MaybeHandle<JSObject> JSObjectWalkVisitor<ContextObject>::StructureWalk(
Handle<JSObject> object) {
Isolate* isolate = this->isolate();
bool copying = ContextObject::kCopying;
{
StackLimitCheck check(isolate);
if (check.HasOverflowed()) {
isolate->StackOverflow();
return MaybeHandle<JSObject>();
}
}
if (object->map()->is_deprecated()) {
base::MutexGuard mutex_guard(isolate->boilerplate_migration_access());
JSObject::MigrateInstance(isolate, object);
}
Handle<JSObject> copy;
if (copying) {
// JSFunction objects are not allowed to be in normal boilerplates at all.
DCHECK(!IsJSFunction(*object));
DirectHandle<AllocationSite> site_to_pass;
if (site_context()->ShouldCreateMemento(object)) {
site_to_pass = site_context()->current();
}
copy = isolate->factory()->CopyJSObjectWithAllocationSite(object,
site_to_pass);
} else {
copy = object;
}
DCHECK(copying || copy.is_identical_to(object));
HandleScope scope(isolate);
// Deep copy own properties. Arrays only have 1 property "length".
if (!IsJSArray(*copy)) {
if (copy->HasFastProperties()) {
DirectHandle<DescriptorArray> descriptors(
copy->map()->instance_descriptors(), isolate);
for (InternalIndex i : copy->map()->IterateOwnDescriptors()) {
PropertyDetails details = descriptors->GetDetails(i);
DCHECK_EQ(PropertyLocation::kField, details.location());
DCHECK_EQ(PropertyKind::kData, details.kind());
FieldIndex index = FieldIndex::ForDetails(copy->map(), details);
Tagged<Object> raw = copy->RawFastPropertyAt(index);
if (IsJSObject(raw)) {
Handle<JSObject> value(Cast<JSObject>(raw), isolate);
ASSIGN_RETURN_ON_EXCEPTION(isolate, value,
VisitElementOrProperty(copy, value));
if (copying) copy->FastPropertyAtPut(index, *value);
} else if (copying && details.representation().IsDouble()) {
uint64_t double_value = Cast<HeapNumber>(raw)->value_as_bits();
auto value = isolate->factory()->NewHeapNumberFromBits(double_value);
copy->FastPropertyAtPut(index, *value);
}
}
} else {
if (V8_ENABLE_SWISS_NAME_DICTIONARY_BOOL) {
DirectHandle<SwissNameDictionary> dict(
copy->property_dictionary_swiss(), isolate);
for (InternalIndex i : dict->IterateEntries()) {
Tagged<Object> raw = dict->ValueAt(i);
if (!IsJSObject(raw)) continue;
DCHECK(IsName(dict->KeyAt(i)));
Handle<JSObject> value(Cast<JSObject>(raw), isolate);
ASSIGN_RETURN_ON_EXCEPTION(isolate, value,
VisitElementOrProperty(copy, value));
if (copying) dict->ValueAtPut(i, *value);
}
} else {
DirectHandle<NameDictionary> dict(copy->property_dictionary(), isolate);
for (InternalIndex i : dict->IterateEntries()) {
Tagged<Object> raw = dict->ValueAt(i);
if (!IsJSObject(raw)) continue;
DCHECK(IsName(dict->KeyAt(i)));
Handle<JSObject> value(Cast<JSObject>(raw), isolate);
ASSIGN_RETURN_ON_EXCEPTION(isolate, value,
VisitElementOrProperty(copy, value));
if (copying) dict->ValueAtPut(i, *value);
}
}
}
// Assume non-arrays don't end up having elements.
if (copy->elements()->ulength().value() == 0) return copy;
}
// Deep copy own elements.
switch (copy->GetElementsKind()) {
case PACKED_ELEMENTS:
case PACKED_FROZEN_ELEMENTS:
case PACKED_SEALED_ELEMENTS:
case PACKED_NONEXTENSIBLE_ELEMENTS:
case HOLEY_FROZEN_ELEMENTS:
case HOLEY_SEALED_ELEMENTS:
case HOLEY_NONEXTENSIBLE_ELEMENTS:
case HOLEY_ELEMENTS:
case SHARED_ARRAY_ELEMENTS: {
DirectHandle<FixedArray> elements(Cast<FixedArray>(copy->elements()),
isolate);
uint32_t elements_len = elements->ulength().value();
if (elements->map() == ReadOnlyRoots(isolate).fixed_cow_array_map()) {
#ifdef DEBUG
for (uint32_t i = 0; i < elements_len; i++) {
DCHECK(!IsJSObject(elements->get(i)));
}
#endif
} else {
for (uint32_t i = 0; i < elements_len; i++) {
Tagged<Object> raw = elements->get(i);
if (!IsJSObject(raw)) continue;
Handle<JSObject> value(Cast<JSObject>(raw), isolate);
ASSIGN_RETURN_ON_EXCEPTION(isolate, value,
VisitElementOrProperty(copy, value));
if (copying) elements->set(i, *value);
}
}
break;
}
case DICTIONARY_ELEMENTS: {
DirectHandle<NumberDictionary> element_dictionary(
copy->element_dictionary(), isolate);
for (InternalIndex i : element_dictionary->IterateEntries()) {
Tagged<Object> raw = element_dictionary->ValueAt(i);
if (!IsJSObject(raw)) continue;
Handle<JSObject> value(Cast<JSObject>(raw), isolate);
ASSIGN_RETURN_ON_EXCEPTION(isolate, value,
VisitElementOrProperty(copy, value));
if (copying) element_dictionary->ValueAtPut(i, *value);
}
break;
}
case FAST_SLOPPY_ARGUMENTS_ELEMENTS:
case SLOW_SLOPPY_ARGUMENTS_ELEMENTS:
UNIMPLEMENTED();
break;
case FAST_STRING_WRAPPER_ELEMENTS:
case SLOW_STRING_WRAPPER_ELEMENTS:
case WASM_ARRAY_ELEMENTS:
UNREACHABLE();
#define TYPED_ARRAY_CASE(Type, type, TYPE, ctype) case TYPE##_ELEMENTS:
TYPED_ARRAYS(TYPED_ARRAY_CASE)
RAB_GSAB_TYPED_ARRAYS(TYPED_ARRAY_CASE)
#undef TYPED_ARRAY_CASE
// Typed elements cannot be created using an object literal.
UNREACHABLE();
case PACKED_SMI_ELEMENTS:
case HOLEY_SMI_ELEMENTS:
case PACKED_DOUBLE_ELEMENTS:
case HOLEY_DOUBLE_ELEMENTS:
case NO_ELEMENTS:
// No contained objects, nothing to do.
break;
}
return copy;
}
// AllocationSiteCreationContext aids in the creation of AllocationSites to
// accompany object literals.
class AllocationSiteCreationContext : public AllocationSiteContext {
public:
explicit AllocationSiteCreationContext(Isolate* isolate)
: AllocationSiteContext(isolate) {}
Handle<AllocationSite> EnterNewScope() {
Handle<AllocationSite> scope_site;
if (top().is_null()) {
// We are creating the top level AllocationSite as opposed to a nested
// AllocationSite.
InitializeTraversal(isolate()->factory()->NewAllocationSite(true));
scope_site = Handle<AllocationSite>(*top(), isolate());
if (v8_flags.trace_creation_allocation_sites) {
PrintF("*** Creating top level %s AllocationSite %p\n", "Fat",
reinterpret_cast<void*>(scope_site->ptr()));
}
} else {
DCHECK(!current().is_null());
scope_site = isolate()->factory()->NewAllocationSite(false);
if (v8_flags.trace_creation_allocation_sites) {
PrintF(
"*** Creating nested %s AllocationSite (top, current, new) (%p, "
"%p, "
"%p)\n",
"Slim", reinterpret_cast<void*>(top()->ptr()),
reinterpret_cast<void*>(current()->ptr()),
reinterpret_cast<void*>(scope_site->ptr()));
}
current()->set_nested_site(*scope_site);
update_current_site(*scope_site);
}
DCHECK(!scope_site.is_null());
return scope_site;
}
void ExitScope(DirectHandle<AllocationSite> scope_site,
DirectHandle<JSObject> object) {
if (object.is_null()) return;
scope_site->set_boilerplate(*object, kReleaseStore);
if (v8_flags.trace_creation_allocation_sites) {
bool top_level =
!scope_site.is_null() && top().is_identical_to(scope_site);
if (top_level) {
PrintF("*** Setting AllocationSite %p transition_info %p\n",
reinterpret_cast<void*>(scope_site->ptr()),
reinterpret_cast<void*>(object->ptr()));
} else {
PrintF("*** Setting AllocationSite (%p, %p) transition_info %p\n",
reinterpret_cast<void*>(top()->ptr()),
reinterpret_cast<void*>(scope_site->ptr()),
reinterpret_cast<void*>(object->ptr()));
}
}
}
static const bool kCopying = false;
};
MaybeDirectHandle<JSObject> DeepWalk(
Handle<JSObject> object, AllocationSiteCreationContext* site_context) {
JSObjectWalkVisitor<AllocationSiteCreationContext> v(site_context);
MaybeDirectHandle<JSObject> result = v.StructureWalk(object);
DirectHandle<JSObject> for_assert;
DCHECK(!result.ToHandle(&for_assert) || for_assert.is_identical_to(object));
return result;
}
MaybeDirectHandle<JSObject> DeepCopy(Handle<JSObject> object,
AllocationSiteUsageContext* site_context) {
JSObjectWalkVisitor<AllocationSiteUsageContext> v(site_context);
MaybeDirectHandle<JSObject> copy = v.StructureWalk(object);
DirectHandle<JSObject> for_assert;
DCHECK(!copy.ToHandle(&for_assert) || !for_assert.is_identical_to(object));
return copy;
}
Handle<JSObject> CreateObjectLiteral(
Isolate* isolate,
DirectHandle<ObjectBoilerplateDescription> object_boilerplate_description,
int flags, AllocationType allocation);
Handle<JSObject> CreateArrayLiteral(
Isolate* isolate,
DirectHandle<ArrayBoilerplateDescription> array_boilerplate_description,
AllocationType allocation);
struct ObjectLiteralHelper {
static inline Handle<JSObject> Create(Isolate* isolate,
Handle<HeapObject> description,
int flags, AllocationType allocation) {
auto object_boilerplate_description =
Cast<ObjectBoilerplateDescription>(description);
return CreateObjectLiteral(isolate, object_boilerplate_description, flags,
allocation);
}
};
struct ArrayLiteralHelper {
static inline Handle<JSObject> Create(Isolate* isolate,
Handle<HeapObject> description,
int flags_not_used,
AllocationType allocation) {
auto array_boilerplate_description =
Cast<ArrayBoilerplateDescription>(description);
return CreateArrayLiteral(isolate, array_boilerplate_description,
allocation);
}
};
Handle<JSObject> CreateObjectLiteralWithNullProto(
Isolate* isolate,
DirectHandle<ObjectBoilerplateDescription> object_boilerplate_description,
int flags, AllocationType allocation) {
DirectHandle<NativeContext> native_context = isolate->native_context();
bool use_fast_elements = (flags & ObjectLiteral::kFastElements) != 0;
int number_of_properties =
object_boilerplate_description->backing_store_size();
// Ignoring number_of_properties for force dictionary map with
// __proto__:null.
DirectHandle<Map> map = direct_handle(
native_context->slow_object_with_null_prototype_map(), isolate);
Handle<JSObject> boilerplate =
isolate->factory()->NewFastOrSlowJSObjectFromMap(
map, number_of_properties, allocation);
// Normalize the elements of the boilerplate to save space if needed.
if (!use_fast_elements) JSObject::NormalizeElements(isolate, boilerplate);
// TODO(leszeks): This path could be faster, e.g. we could populate the
// property dictionary directly.
int length = object_boilerplate_description->boilerplate_properties_count();
for (int index = 0; index < length; index++) {
DirectHandle<ObjectBoilerplateDescription::KeyT> key(
object_boilerplate_description->name(index), isolate);
DirectHandle<Object> value(object_boilerplate_description->value(index),
isolate);
if (DirectHandle<HeapObject> ho_value; TryCast(value, &ho_value)) {
if (DirectHandle<ArrayBoilerplateDescription> array_desc;
TryCast(ho_value, &array_desc)) {
value = CreateArrayLiteral(isolate, array_desc, allocation);
} else if (DirectHandle<ObjectBoilerplateDescription> obj_desc;
TryCast(ho_value, &obj_desc)) {
value = CreateObjectLiteral(isolate, obj_desc, obj_desc->flags(),
allocation);
}
}
uint32_t element_index = 0;
if (Object::ToArrayIndex(*key, &element_index)) {
// Array index (uint32).
JSObject::SetOwnElementIgnoreAttributes(boilerplate, element_index, value,
NONE)
.Check();
} else {
DirectHandle<String> name = Cast<InternalizedString>(key);
DCHECK(!name->AsArrayIndex(&element_index));
JSObject::SetOwnPropertyIgnoreAttributes(boilerplate, name, value, NONE)
.Check();
}
}
return boilerplate;
}
// Helper class for iterating an ObjectBoilerplateDescription for use with
// JSDataObjectBuilder.
class ObjectBoilerplateDescriptionIterator {
public:
static constexpr bool kSupportsRawKeys = false;
static constexpr bool kMayHaveDuplicateKeys = false;
ObjectBoilerplateDescriptionIterator(
DirectHandle<ObjectBoilerplateDescription> object_boilerplate_description,
Isolate* isolate, AllocationType allocation)
: object_boilerplate_description_(object_boilerplate_description),
isolate_(isolate),
allocation_(allocation) {
for (; i_ < length_; ++i_) {
if (IsInternalizedString(object_boilerplate_description->name(i_))) {
break;
}
}
start_ = i_;
}
Handle<InternalizedString> GetKey() {
return handle(
Cast<InternalizedString>(object_boilerplate_description_->name(i_)),
isolate_);
}
Handle<Object> GetValue(bool will_revisit) {
Handle<Object> value(object_boilerplate_description_->value(i_), isolate_);
if (Handle<HeapObject> ho_value; TryCast(value, &ho_value)) {
if (Handle<ArrayBoilerplateDescription> array_desc;
TryCast(ho_value, &array_desc)) {
value = CreateArrayLiteral(isolate_, array_desc, allocation_);
if (will_revisit) {
materialized_values_.push_back(value);
}
} else if (Handle<ObjectBoilerplateDescription> obj_desc;
TryCast(ho_value, &obj_desc)) {
value = CreateObjectLiteral(isolate_, obj_desc, obj_desc->flags(),
allocation_);
if (will_revisit) {
materialized_values_.push_back(value);
}
}
}
return value;
}
void Advance() {
if (i_ >= length_) return;
for (++i_; i_ < length_; ++i_) {
if (IsInternalizedString(object_boilerplate_description_->name(i_))) {
break;
}
}
}
bool Done() { return i_ >= length_; }
// Helper class for revisiting values already iterated by this iterator, in
// particular avoiding re-materialising nested values.
struct RevisitValueIterator {
DirectHandle<ObjectBoilerplateDescription> object_boilerplate_description;
base::SmallVector<Handle<Object>, 4>::iterator materialized_values_it;
base::SmallVector<Handle<Object>, 4>::iterator materialized_values_end;
Isolate* isolate;
int i;
int length = object_boilerplate_description->boilerplate_properties_count();
Tagged<Object> GetNext() {
DCHECK(IsInternalizedString(object_boilerplate_description->name(i)));
Tagged<Object> value = object_boilerplate_description->value(i);
// Potentially re-use an already materialized value.
if (IsArrayBoilerplateDescription(value) ||
IsObjectBoilerplateDescription(value)) {
SBXCHECK_LT(materialized_values_it, materialized_values_end);
value = **materialized_values_it++;
}
// Advance to the next string name.
for (++i; i < length; ++i) {
if (IsInternalizedString(object_boilerplate_description->name(i))) {
break;
}
}
return value;
}
};
RevisitValueIterator RevisitValues() {
return RevisitValueIterator{object_boilerplate_description_,
materialized_values_.begin(),
materialized_values_.end(), isolate_, start_};
}
private:
DirectHandle<ObjectBoilerplateDescription> object_boilerplate_description_;
Isolate* isolate_;
AllocationType allocation_;
int i_ = 0;
int start_ = 0;
int length_ = object_boilerplate_description_->boilerplate_properties_count();
base::SmallVector<Handle<Object>, 4> materialized_values_ = {};
};
Handle<JSObject> CreateObjectLiteral(
Isolate* isolate,
DirectHandle<ObjectBoilerplateDescription> object_boilerplate_description,
int flags, AllocationType allocation) {
DirectHandle<NativeContext> native_context = isolate->native_context();
bool has_null_prototype = (flags & ObjectLiteral::kHasNullPrototype) != 0;
if (has_null_prototype) {
return CreateObjectLiteralWithNullProto(
isolate, object_boilerplate_description, flags, allocation);
}
// Fast path using manual elements initialisation and JSDataObjectBuilder.
bool use_fast_elements = (flags & ObjectLiteral::kFastElements) != 0;
int number_of_properties =
object_boilerplate_description->backing_store_size();
int length = object_boilerplate_description->boilerplate_properties_count();
// First iterate to count the elements and find the max element index.
// TODO(leszeks): We could already at parse-time figure out if there are any
// elements and avoid this step.
int element_count = 0;
uint32_t max_element_index = 0;
for (int i = 0; i < length; i++) {
uint32_t element_index = 0;
if (Object::ToArrayIndex(object_boilerplate_description->name(i),
&element_index)) {
element_count++;
if (element_index > max_element_index) max_element_index = element_index;
}
}
// Then allocate the appropriate element container.
DirectHandle<FixedArray> elements;
ElementsKind elements_kind;
if (use_fast_elements) {
elements_kind = HOLEY_ELEMENTS;
if (element_count > 0) {
elements =
isolate->factory()->NewFixedArrayWithHoles(max_element_index + 1);
} else {
elements = isolate->factory()->empty_fixed_array();
}
} else {
elements_kind = DICTIONARY_ELEMENTS;
elements = NumberDictionary::New(isolate, element_count);
}
// Fill the element container with values.
for (int i = 0; i < length; i++) {
uint32_t element_index = 0;
if (!Object::ToArrayIndex(object_boilerplate_description->name(i),
&element_index)) {
continue;
}
DirectHandle<Object> value =
direct_handle(object_boilerplate_description->value(i), isolate);
if (DirectHandle<HeapObject> ho_value; TryCast(value, &ho_value)) {
if (DirectHandle<ArrayBoilerplateDescription> array_desc;
TryCast(ho_value, &array_desc)) {
value = CreateArrayLiteral(isolate, array_desc, allocation);
} else if (DirectHandle<ObjectBoilerplateDescription> obj_desc;
TryCast(ho_value, &obj_desc)) {
value = CreateObjectLiteral(isolate, obj_desc, obj_desc->flags(),
allocation);
}
}
if (use_fast_elements) {
elements->set(element_index, *value);
} else {
DirectHandle<NumberDictionary> dict = Cast<NumberDictionary>(elements);
PropertyDetails details(PropertyKind::kData, NONE,
PropertyConstness::kConst, 0);
NumberDictionary::UncheckedAdd(isolate, dict, element_index, value,
details);
}
}
if (!use_fast_elements) {
DirectHandle<NumberDictionary> dict = Cast<NumberDictionary>(elements);
dict->SetInitialNumberOfElements(element_count);
dict->UpdateMaxNumberKey(max_element_index, Handle<JSObject>::null());
}
// Finally, use JSDataObjectBuilder to build the object itself.
JSDataObjectBuilder builder(isolate, elements_kind, number_of_properties,
DirectHandle<Map>(),
JSDataObjectBuilder::kNormalHeapNumbers);
return builder.BuildFromIterator(
ObjectBoilerplateDescriptionIterator{object_boilerplate_description,
isolate, allocation},
elements);
}
Handle<JSObject> CreateArrayLiteral(
Isolate* isolate,
DirectHandle<ArrayBoilerplateDescription> array_boilerplate_description,
AllocationType allocation) {
ElementsKind constant_elements_kind =
array_boilerplate_description->elements_kind();
Handle<FixedArrayBase> constant_elements_values(
array_boilerplate_description->constant_elements(), isolate);
// Create the JSArray.
Handle<FixedArrayBase> copied_elements_values;
if (IsDoubleElementsKind(constant_elements_kind)) {
copied_elements_values = isolate->factory()->CopyFixedDoubleArray(
Cast<FixedDoubleArray>(constant_elements_values));
} else {
DCHECK(IsSmiOrObjectElementsKind(constant_elements_kind));
const bool is_cow = (constant_elements_values->map() ==
ReadOnlyRoots(isolate).fixed_cow_array_map());
if (is_cow) {
copied_elements_values = constant_elements_values;
if (DEBUG_BOOL) {
auto fixed_array_values = Cast<FixedArray>(copied_elements_values);
uint32_t fixed_array_values_len = fixed_array_values->ulength().value();
for (uint32_t i = 0; i < fixed_array_values_len; i++) {
DCHECK(!IsFixedArray(fixed_array_values->get(i)));
}
}
} else {
Handle<FixedArray> fixed_array_values =
Cast<FixedArray>(constant_elements_values);
Handle<FixedArray> fixed_array_values_copy =
isolate->factory()->CopyFixedArray(fixed_array_values);
copied_elements_values = fixed_array_values_copy;
uint32_t fixed_array_values_len = fixed_array_values->ulength().value();
for (uint32_t i = 0; i < fixed_array_values_len; i++) {
Tagged<Object> value = fixed_array_values_copy->get(i);
Tagged<HeapObject> value_heap_object;
if (!value.GetHeapObject(isolate, &value_heap_object)) continue;
if (IsAnyHole(value_heap_object)) continue;
if (IsArrayBoilerplateDescription(value_heap_object)) {
HandleScope sub_scope(isolate);
DirectHandle<ArrayBoilerplateDescription> boilerplate(
Cast<ArrayBoilerplateDescription>(value_heap_object), isolate);
DirectHandle<JSObject> result =
CreateArrayLiteral(isolate, boilerplate, allocation);
fixed_array_values_copy->set(i, *result);
} else if (IsObjectBoilerplateDescription(value_heap_object)) {
HandleScope sub_scope(isolate);
DirectHandle<ObjectBoilerplateDescription> boilerplate(
Cast<ObjectBoilerplateDescription>(value_heap_object), isolate);
DirectHandle<JSObject> result = CreateObjectLiteral(
isolate, boilerplate, boilerplate->flags(), allocation);
fixed_array_values_copy->set(i, *result);
}
}
}
}
return isolate->factory()->NewJSArrayWithElements(
copied_elements_values, constant_elements_kind,
copied_elements_values->ulength().value(), allocation);
}
template <typename LiteralHelper>
MaybeDirectHandle<JSObject> CreateLiteralWithoutAllocationSite(
Isolate* isolate, Handle<HeapObject> description, int flags) {
return LiteralHelper::Create(isolate, description, flags,
AllocationType::kYoung);
}
template <typename LiteralHelper>
MaybeDirectHandle<JSObject> CreateLiteral(Isolate* isolate,
Handle<HeapObject> maybe_vector,
int literals_index,
Handle<HeapObject> description,
int flags) {
if (!IsFeedbackVector(*maybe_vector)) {
DCHECK(IsUndefined(*maybe_vector));
return CreateLiteralWithoutAllocationSite<LiteralHelper>(
isolate, description, flags);
}
auto vector = Cast<FeedbackVector>(maybe_vector);
FeedbackSlot literals_slot(FeedbackVector::ToSlot(literals_index));
CHECK_LT(literals_slot.ToInt(), vector->length().value());
Handle<Object> literal_site(Cast<Object>(vector->Get(literals_slot)),
isolate);
Handle<AllocationSite> site;
Handle<JSObject> boilerplate;
if (HasBoilerplate(literal_site)) {
site = Cast<AllocationSite>(literal_site);
boilerplate = Handle<JSObject>(site->boilerplate(), isolate);
} else {
// Eagerly create AllocationSites for literals that contain an Array.
bool needs_initial_allocation_site =
(flags & AggregateLiteral::kNeedsInitialAllocationSite) != 0;
if (!needs_initial_allocation_site &&
IsUninitializedLiteralSite(*literal_site)) {
PreInitializeLiteralSite(vector, literals_slot);
return CreateLiteralWithoutAllocationSite<LiteralHelper>(
isolate, description, flags);
} else {
boilerplate = LiteralHelper::Create(isolate, description, flags,
AllocationType::kOld);
}
// Install AllocationSite objects.
AllocationSiteCreationContext creation_context(isolate);
site = creation_context.EnterNewScope();
RETURN_ON_EXCEPTION(isolate, DeepWalk(boilerplate, &creation_context));
creation_context.ExitScope(site, boilerplate);
vector->SynchronizedSet(literals_slot, *site);
}
static_assert(static_cast<int>(ObjectLiteral::kDisableMementos) ==
static_cast<int>(ArrayLiteral::kDisableMementos));
bool enable_mementos = (flags & ObjectLiteral::kDisableMementos) == 0;
// Copy the existing boilerplate.
AllocationSiteUsageContext usage_context(isolate, site, enable_mementos);
usage_context.EnterNewScope();
MaybeDirectHandle<JSObject> copy = DeepCopy(boilerplate, &usage_context);
usage_context.ExitScope(site, boilerplate);
return copy;
}
DirectHandle<Object> InstantiateIfSharedFunctionInfo(
DirectHandle<Context> context, Isolate* isolate,
DirectHandle<JSObject> js_proto, DirectHandle<Object> value,
DirectHandle<ClosureFeedbackCellArray> feedback_cell_array,
Handle<ObjectBoilerplateDescription> object_boilerplate_description,
int start_slot, int& current_slot) {
DirectHandle<SharedFunctionInfo> shared;
if (!TryCast<SharedFunctionInfo>(value, &shared)) {
return value;
}
if (!v8_flags.proto_assign_seq_lazy_func_opt ||
!base::IsInRange(current_slot, 0, kMaxUInt16)) {
DirectHandle<FeedbackCell> feedback_cell(
feedback_cell_array->get(current_slot), isolate);
value = Factory::JSFunctionBuilder{isolate, shared, context}
.set_feedback_cell(feedback_cell)
.set_allocation_type(AllocationType::kYoung)
.Build();
++current_slot;
return value;
}
DirectHandle<Map> proto_map = direct_handle(js_proto->map(), isolate);
if (Tagged<PrototypeSharedClosureInfo> closure_info;
proto_map->TryGetPrototypeSharedClosureInfo(&closure_info)) {
// We already have closure infos on this prototype, this means we
// already called SetPrototypeProperties on it and some closures were
// set up. We can only take the lazy closure path if the context
// is the same.
if (closure_info->context() == *context &&
*object_boilerplate_description ==
closure_info->boilerplate_description()) {
// fast path
shared->set_feedback_slot(current_slot);
} else {
// not lazy allocation
DirectHandle<FeedbackCell> feedback_cell(
feedback_cell_array->get(current_slot), isolate);
value = Factory::JSFunctionBuilder{isolate, shared, context}
.set_feedback_cell(feedback_cell)
.set_allocation_type(AllocationType::kYoung)
.Build();
}
} else {
// We do not have closure_info
auto val = *isolate->factory()->NewPrototypeSharedClosureInfo(
object_boilerplate_description, context, feedback_cell_array);
proto_map->SetPrototypeSharedClosureInfo(val);
shared->set_feedback_slot(current_slot);
}
++current_slot;
return value;
}
} // namespace
RUNTIME_FUNCTION(Runtime_CreateObjectLiteral) {
HandleScope scope(isolate);
DCHECK_EQ(4, args.length());
Handle<HeapObject> maybe_vector = args.at<HeapObject>(0);
int literals_index = args.tagged_index_value_at(1);
Handle<ObjectBoilerplateDescription> description =
args.at<ObjectBoilerplateDescription>(2);
int flags = args.smi_value_at(3);
RETURN_RESULT_OR_FAILURE(
isolate, CreateLiteral<ObjectLiteralHelper>(
isolate, maybe_vector, literals_index, description, flags));
}
static MaybeDirectHandle<Object> SetPrototypePropertiesSlow(
Isolate* isolate, DirectHandle<Context> context, DirectHandle<JSAny> obj,
Handle<ObjectBoilerplateDescription> object_boilerplate_description,
DirectHandle<ClosureFeedbackCellArray> feedback_cell_array,
int& current_slot, int start_index = 0) {
MaybeDirectHandle<Object> result;
int length = object_boilerplate_description->boilerplate_properties_count();
for (int index = start_index; index < length; index++) {
DirectHandle<Object> proto;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, proto,
Runtime::GetObjectProperty(isolate, obj,
isolate->factory()->prototype_string()));
DirectHandle<Object> key(object_boilerplate_description->name(index),
isolate);
DirectHandle<Object> value(object_boilerplate_description->value(index),
isolate);
if (DirectHandle<SharedFunctionInfo> shared;
TryCast<SharedFunctionInfo>(value, &shared)) {
DirectHandle<FeedbackCell> feedback_cell(
feedback_cell_array->get(current_slot++), isolate);
value = Factory::JSFunctionBuilder{isolate, shared, context}
.set_feedback_cell(feedback_cell)
.set_allocation_type(AllocationType::kYoung)
.Build();
}
RETURN_ON_EXCEPTION(
isolate, Runtime::SetObjectProperty(isolate, Cast<JSAny>(proto), key,
value, StoreOrigin::kNamed));
result = value;
}
return result.ToHandleChecked();
}
static bool IsDefaultFunctionPrototype(DirectHandle<JSObject> js_proto,
Isolate* isolate) {
// Object function prototype's map.
Tagged<Map> proto_map = js_proto->map();
// Check that given function.prototype object has a default initial state:
// it's extensible.
if (!proto_map->is_extensible()) {
return false;
}
// it's in dictionary mode.
if (!proto_map->is_dictionary_map()) {
return false;
}
// it has exactly one "constructor" property installed.
if (js_proto->property_dictionary()->NumberOfElements() != 1) {
return false;
}
if (js_proto->property_dictionary()
->FindEntry(isolate, isolate->factory()->constructor_string())
.is_not_found()) {
return false;
}
// its prototype is the original and unmodified Object.prototype object.
if (proto_map->prototype()->map() !=
*isolate->object_function_prototype_map()) {
return false;
}
return true;
}
RUNTIME_FUNCTION(Runtime_SetPrototypeProperties) {
HandleScope scope(isolate);
DCHECK_EQ(4, args.length());
DirectHandle<Context> context(isolate->context(), isolate);
DirectHandle<JSAny> obj = args.at<JSAny>(0); // acc JS Object
Handle<ObjectBoilerplateDescription> object_boilerplate_description =
args.at<ObjectBoilerplateDescription>(1);
DirectHandle<ClosureFeedbackCellArray> feedback_cell_array =
args.at<ClosureFeedbackCellArray>(2);
int current_slot = args.smi_value_at(3);
int start_slot = current_slot;
// Proxy and any non-function not welcome
if (!IsJSFunction(*obj)) {
RETURN_RESULT_OR_FAILURE(
isolate, SetPrototypePropertiesSlow(isolate, context, obj,
object_boilerplate_description,
feedback_cell_array, current_slot));
}
DirectHandle<JSFunction> acc_fun = Cast<JSFunction>(obj);
if (!acc_fun->has_prototype_slot()) {
RETURN_RESULT_OR_FAILURE(
isolate, SetPrototypePropertiesSlow(isolate, context, obj,
object_boilerplate_description,
feedback_cell_array, current_slot));
}
DirectHandle<Object> prototype =
JSFunction::GetFunctionPrototype(isolate, acc_fun);
DCHECK_EQ(*prototype,
*Runtime::GetObjectProperty(isolate, obj,
isolate->factory()->prototype_string())
.ToHandleChecked());
if (IsNull(*prototype)) {
RETURN_RESULT_OR_FAILURE(
isolate, SetPrototypePropertiesSlow(isolate, context, obj,
object_boilerplate_description,
feedback_cell_array, current_slot));
}
DirectHandle<JSObject> js_proto;
if (!TryCast<JSObject>(prototype, &js_proto)) {
RETURN_RESULT_OR_FAILURE(
isolate, SetPrototypePropertiesSlow(isolate, context, obj,
object_boilerplate_description,
feedback_cell_array, current_slot));
}
if (IsSpecialReceiverMap(js_proto->map())) {
RETURN_RESULT_OR_FAILURE(
isolate, SetPrototypePropertiesSlow(isolate, context, obj,
object_boilerplate_description,
feedback_cell_array, current_slot));
}
if (!JSObject::IsExtensible(isolate, js_proto)) {
RETURN_RESULT_OR_FAILURE(
isolate, SetPrototypePropertiesSlow(isolate, context, obj,
object_boilerplate_description,
feedback_cell_array, current_slot));
}
bool is_default_func_prototype =
IsDefaultFunctionPrototype(js_proto, isolate);
// It should now be safe to perform a fast merge
MaybeDirectHandle<Object> result;
int length = object_boilerplate_description->boilerplate_properties_count();
if (is_default_func_prototype) {
for (int index = 0; index < length; index++) {
DirectHandle<Object> key(object_boilerplate_description->name(index),
isolate);
DirectHandle<Object> value(object_boilerplate_description->value(index),
isolate);
value = InstantiateIfSharedFunctionInfo(
context, isolate, js_proto, value, feedback_cell_array,
object_boilerplate_description, start_slot, current_slot);
DirectHandle<String> name = Cast<String>(key);
DCHECK(!name->IsArrayIndex());
DCHECK(!IsTheHole(*value));
LookupIterator it(isolate, js_proto, name, LookupIterator::OWN);
if (IsSharedFunctionInfo(*value)) {
DirectHandle<AccessorInfo> accessor_info =
isolate->factory()->lazy_closure_accessor();
JSObject::SetAccessor(js_proto, name, accessor_info,
PropertyAttributes::NONE)
.Check();
} else {
Object::TransitionAndWriteDataProperty(
&it, value, NONE, Just(kDontThrow), StoreOrigin::kNamed)
.Check();
}
result = value;
}
} else {
// Make sure None of the keys we are writing to are setters/getters
// TODO(rherouart): if prototype is empty we can skip these checks
for (int index = 0; index < length; index++) {
PropertyDescriptor desc;
DirectHandle<Object> key(object_boilerplate_description->name(index),
isolate);
DirectHandle<Object> value(object_boilerplate_description->value(index),
isolate);
CHECK(IsName(*key));
PropertyKey lookup_key(isolate, key);
LookupIterator it(isolate, js_proto, lookup_key,
LookupIterator::PROTOTYPE_CHAIN);
LookupIterator::State it_state = it.state();
if (it_state != LookupIterator::NOT_FOUND &&
(it_state != LookupIterator::DATA || it.IsReadOnly())) {
RETURN_RESULT_OR_FAILURE(
isolate, SetPrototypePropertiesSlow(
isolate, context, obj, object_boilerplate_description,
feedback_cell_array, current_slot, index));
}
DCHECK(!IsTheHole(*value));
if (it_state == LookupIterator::DATA &&
it.HolderIsReceiverOrHiddenPrototype()) {
DirectHandle<SharedFunctionInfo> shared;
if (TryCast<SharedFunctionInfo>(value, &shared)) {
// If we were to set an existing property to a SharedFunctionInfo,
// there would be the risk of it being returned from IC without being
// instantiated.
DirectHandle<FeedbackCell> feedback_cell(
feedback_cell_array->get(current_slot), isolate);
value = Factory::JSFunctionBuilder{isolate, shared, context}
.set_feedback_cell(feedback_cell)
.set_allocation_type(AllocationType::kYoung)
.Build();
current_slot++;
}
it.UpdateProtector();
Object::SetDataProperty(&it, value).Check();
} else {
value = InstantiateIfSharedFunctionInfo(
context, isolate, js_proto, value, feedback_cell_array,
object_boilerplate_description, start_slot, current_slot);
if (IsSharedFunctionInfo(*value)) {
DirectHandle<AccessorInfo> accessor_info =
isolate->factory()->lazy_closure_accessor();
// Unlike SetDataProperty/TransitionAndWriteDataProperty, SetAccessor
// doesn't invalidate protectors, so do it here for e.g. `then` or
// `next` added to a watched prototype.
it.UpdateProtector();
JSObject::SetAccessor(js_proto, Cast<Name>(key), accessor_info,
PropertyAttributes::NONE)
.Check();
} else {
Object::TransitionAndWriteDataProperty(
&it, value, NONE, Just(kDontThrow), StoreOrigin::kNamed)
.Check();
}
}
result = value;
}
}
return *result.ToHandleChecked();
}
RUNTIME_FUNCTION(Runtime_CreateArrayLiteral) {
HandleScope scope(isolate);
DCHECK_EQ(4, args.length());
Handle<HeapObject> maybe_vector = args.at<HeapObject>(0);
int literals_index = args.tagged_index_value_at(1);
Handle<ArrayBoilerplateDescription> elements =
args.at<ArrayBoilerplateDescription>(2);
int flags = args.smi_value_at(3);
RETURN_RESULT_OR_FAILURE(
isolate, CreateLiteral<ArrayLiteralHelper>(
isolate, maybe_vector, literals_index, elements, flags));
}
RUNTIME_FUNCTION(Runtime_CreateRegExpLiteral) {
HandleScope scope(isolate);
DCHECK_EQ(4, args.length());
Handle<HeapObject> maybe_vector = args.at<HeapObject>(0);
int index = args.tagged_index_value_at(1);
DirectHandle<String> pattern = args.at<String>(2);
int flags = args.smi_value_at(3);
if (IsUndefined(*maybe_vector)) {
// We don't have a vector; don't create a boilerplate, simply construct a
// plain JSRegExp instance and return it.
RETURN_RESULT_OR_FAILURE(
isolate, JSRegExp::New(isolate, pattern, JSRegExp::Flags(flags)));
}
auto vector = Cast<FeedbackVector>(maybe_vector);
FeedbackSlot literal_slot(FeedbackVector::ToSlot(index));
DirectHandle<Object> literal_site(Cast<Object>(vector->Get(literal_slot)),
isolate);
// This function must not be called when a boilerplate already exists (if it
// exists, callers should instead copy the boilerplate into a new JSRegExp
// instance).
CHECK(!HasBoilerplate(literal_site));
DirectHandle<JSRegExp> regexp_instance;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, regexp_instance,
JSRegExp::New(isolate, pattern, JSRegExp::Flags(flags)));
// JSRegExp literal sites are initialized in a two-step process:
// Uninitialized-Preinitialized, and Preinitialized-Initialized.
if (IsUninitializedLiteralSite(*literal_site)) {
PreInitializeLiteralSite(vector, literal_slot);
return *regexp_instance;
}
DirectHandle<RegExpData> data(regexp_instance->data(isolate), isolate);
DirectHandle<RegExpBoilerplateDescription> boilerplate =
isolate->factory()->NewRegExpBoilerplateDescription(
data, Smi::FromInt(static_cast<int>(regexp_instance->flags())));
vector->SynchronizedSet(literal_slot, *boilerplate);
DCHECK(HasBoilerplate(
direct_handle(Cast<Object>(vector->Get(literal_slot)), isolate)));
return *regexp_instance;
}
} // namespace internal
} // namespace v8