Commit 9a23caf0 authored by Georg Neis's avatar Georg Neis Committed by V8 LUCI CQ

[compiler] Make AllocationSite never-serialized

To get there, also:

- Refactor AllocationSite serialization as necessary.

- Make some accessors on AllocationSite atomic.

- Add JSObjectRef::raw_properties_or_hash().

- Eliminate use of IsFastLiteral in JSCallReducer. It isn't really
  needed there and we want to have only a single piece of code
  traversing boilerplates. (We still have a separate traversal in the
  serializer but that will be removed soon.)

- Merge IsFastLiteral checks into JSCreateLowering's
  TryAllocateFastLiteral.
  Note: TryAllocateFastLiteral doesn't explicitly look at the
  boilerplate's elements kind beyond bailing out for
  DICTIONARY_ELEMENTS in the beginning. After that it looks only at
  the backing store instance type. There is no room for confusion
  because, while elements kind transitions can generally happen
  concurrently to TryAllocateFastLiteral, boilerplates can never
  transition to DICTIONARY_ELEMENTS (added a CHECK for that).

- Slightly adapt CompilationDependencies and remove obsolete comments.

- Fix JSHeapBroker::ClearReconstructibleData (clearing of Refs in
  stress mode) to exclude JSObjectRefs with extra data.

Bug: v8:7790
Change-Id: Iee1232d01e04bcd00db04d48f6e82064fce6ff62
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3008894
Commit-Queue: Georg Neis <neis@chromium.org>
Auto-Submit: Georg Neis <neis@chromium.org>
Reviewed-by: 's avatarSantiago Aboy Solanes <solanes@chromium.org>
Reviewed-by: 's avatarMichael Stanton <mvstanton@chromium.org>
Cr-Commit-Position: refs/heads/master@{#75656}
parent 433ff6b9
......@@ -25,8 +25,6 @@ CompilationDependencies::CompilationDependencies(JSHeapBroker* broker,
class InitialMapDependency final : public CompilationDependency {
public:
// TODO(neis): Once the concurrent compiler frontend is always-on, we no
// longer need to explicitly store the initial map.
InitialMapDependency(const JSFunctionRef& function, const MapRef& initial_map)
: function_(function), initial_map_(initial_map) {
DCHECK(function_.has_initial_map());
......@@ -53,8 +51,6 @@ class InitialMapDependency final : public CompilationDependency {
class PrototypePropertyDependency final : public CompilationDependency {
public:
// TODO(neis): Once the concurrent compiler frontend is always-on, we no
// longer need to explicitly store the prototype.
PrototypePropertyDependency(const JSFunctionRef& function,
const ObjectRef& prototype)
: function_(function), prototype_(prototype) {
......@@ -362,13 +358,9 @@ class TransitionDependency final : public CompilationDependency {
class PretenureModeDependency final : public CompilationDependency {
public:
// TODO(neis): Once the concurrent compiler frontend is always-on, we no
// longer need to explicitly store the mode.
PretenureModeDependency(const AllocationSiteRef& site,
AllocationType allocation)
: site_(site), allocation_(allocation) {
DCHECK_EQ(allocation, site_.GetAllocationType());
}
: site_(site), allocation_(allocation) {}
bool IsValid() const override {
return allocation_ == site_.object()->GetAllocationType();
......@@ -392,8 +384,6 @@ class PretenureModeDependency final : public CompilationDependency {
class FieldRepresentationDependency final : public CompilationDependency {
public:
// TODO(neis): Once the concurrent compiler frontend is always-on, we no
// longer need to explicitly store the representation.
FieldRepresentationDependency(const MapRef& owner, InternalIndex descriptor,
Representation representation)
: owner_(owner),
......@@ -437,8 +427,6 @@ class FieldRepresentationDependency final : public CompilationDependency {
class FieldTypeDependency final : public CompilationDependency {
public:
// TODO(neis): Once the concurrent compiler frontend is always-on, we no
// longer need to explicitly store the type.
FieldTypeDependency(const MapRef& owner, InternalIndex descriptor,
const ObjectRef& type)
: owner_(owner), descriptor_(descriptor), type_(type) {}
......@@ -556,14 +544,9 @@ class ProtectorDependency final : public CompilationDependency {
class ElementsKindDependency final : public CompilationDependency {
public:
// TODO(neis): Once the concurrent compiler frontend is always-on, we no
// longer need to explicitly store the elements kind.
ElementsKindDependency(const AllocationSiteRef& site, ElementsKind kind)
: site_(site), kind_(kind) {
DCHECK(AllocationSite::ShouldTrack(kind_));
DCHECK_EQ(kind_, site_.PointsToLiteral()
? site_.boilerplate().value().map().elements_kind()
: site_.GetElementsKind());
}
bool IsValid() const override {
......@@ -686,7 +669,7 @@ void CompilationDependencies::DependOnConstantInDictionaryPrototypeChain(
AllocationType CompilationDependencies::DependOnPretenureMode(
const AllocationSiteRef& site) {
DCHECK(!site.IsNeverSerializedHeapObject());
if (!FLAG_allocation_site_pretenuring) return AllocationType::kYoung;
AllocationType allocation = site.GetAllocationType();
RecordDependency(zone_->New<PretenureModeDependency>(site, allocation));
return allocation;
......@@ -769,8 +752,6 @@ bool CompilationDependencies::DependOnPromiseThenProtector() {
void CompilationDependencies::DependOnElementsKind(
const AllocationSiteRef& site) {
DCHECK(!site.IsNeverSerializedHeapObject());
// Do nothing if the object doesn't have any useful element transitions left.
ElementsKind kind = site.PointsToLiteral()
? site.boilerplate().value().map().elements_kind()
: site.GetElementsKind();
......
......@@ -7,6 +7,7 @@
#include "src/common/globals.h"
#include "src/flags/flags.h"
#include "src/objects/js-objects.h"
namespace v8 {
namespace internal {
......@@ -74,6 +75,14 @@ inline std::ostream& operator<<(std::ostream& os,
return os;
}
// Maximum depth and total number of elements and properties for literal
// graphs to be considered for fast deep-copying. The limit is chosen to
// match the maximum number of inobject properties, to ensure that the
// performance of using object literals is not worse than using constructor
// functions, see crbug.com/v8/6211 for details.
const int kMaxFastLiteralDepth = 3;
const int kMaxFastLiteralProperties = JSObject::kMaxInObjectProperties;
} // namespace compiler
} // namespace internal
} // namespace v8
......
This diff is collapsed.
......@@ -105,7 +105,7 @@ enum class RefSerializationKind {
V(JSObject, RefSerializationKind::kBackgroundSerialized) \
/* Subtypes of HeapObject */ \
V(AccessorInfo, RefSerializationKind::kNeverSerialized) \
V(AllocationSite, RefSerializationKind::kSerialized) \
V(AllocationSite, RefSerializationKind::kNeverSerialized) \
V(ArrayBoilerplateDescription, RefSerializationKind::kNeverSerialized) \
V(BigInt, RefSerializationKind::kBackgroundSerialized) \
V(CallHandlerInfo, RefSerializationKind::kNeverSerialized) \
......@@ -319,6 +319,8 @@ class JSObjectRef : public JSReceiverRef {
Handle<JSObject> object() const;
base::Optional<ObjectRef> raw_properties_or_hash() const;
// Usable only for in-object properties. Only use this if the underlying
// value can be an uninitialized-sentinel, or if HeapNumber construction must
// be avoided for some reason. Otherwise, use the higher-level
......@@ -368,6 +370,8 @@ class JSObjectRef : public JSReceiverRef {
void SerializeObjectCreateMap();
base::Optional<MapRef> GetObjectCreateMap() const;
void SerializeAsBoilerplateRecursive();
};
class JSDataViewRef : public JSObjectRef {
......@@ -617,18 +621,9 @@ class AllocationSiteRef : public HeapObjectRef {
AllocationType GetAllocationType() const;
ObjectRef nested_site() const;
// {IsFastLiteral} determines whether the given array or object literal
// boilerplate satisfies all limits to be considered for fast deep-copying
// and computes the total size of all objects that are part of the graph.
//
// If PointsToLiteral() is false, then IsFastLiteral() is also false.
bool IsFastLiteral() const;
void SerializeRecursive();
void SerializeBoilerplate();
// We only serialize boilerplate if IsFastLiteral is true.
base::Optional<JSObjectRef> boilerplate() const;
ElementsKind GetElementsKind() const;
bool CanInlineCall() const;
};
......
......@@ -4198,7 +4198,7 @@ Reduction JSCallReducer::ReduceCallOrConstructWithArrayLikeOrSpread(
int new_argument_count;
// Find array length and elements' kind from the feedback's allocation
// site's boilerplate JSArray..
// site's boilerplate JSArray.
JSCreateLiteralOpNode args_node(arguments_list);
CreateLiteralParameters const& args_params = args_node.Parameters();
const FeedbackSource& array_feedback = args_params.feedback();
......@@ -4207,8 +4207,6 @@ Reduction JSCallReducer::ReduceCallOrConstructWithArrayLikeOrSpread(
if (feedback.IsInsufficient()) return NoChange();
AllocationSiteRef site = feedback.AsLiteral().value();
if (!site.IsFastLiteral()) return NoChange();
base::Optional<JSArrayRef> boilerplate_array =
site.boilerplate()->AsJSArray();
int const array_length = boilerplate_array->GetBoilerplateLength().AsSmi();
......
......@@ -1095,27 +1095,18 @@ Reduction JSCreateLowering::ReduceJSCreateLiteralArrayOrObject(Node* node) {
broker()->GetFeedbackForArrayOrObjectLiteral(p.feedback());
if (!feedback.IsInsufficient()) {
AllocationSiteRef site = feedback.AsLiteral().value();
if (site.IsFastLiteral()) {
AllocationType allocation = FLAG_allocation_site_pretenuring
? site.GetAllocationType()
: AllocationType::kYoung;
JSObjectRef boilerplate = site.boilerplate().value();
base::Optional<Node*> maybe_value =
TryAllocateFastLiteral(effect, control, boilerplate, allocation);
if (!maybe_value.has_value()) {
TRACE_BROKER_MISSING(broker(), "bound argument");
return NoChange();
}
if (FLAG_allocation_site_pretenuring) {
CHECK_EQ(dependencies()->DependOnPretenureMode(site), allocation);
}
dependencies()->DependOnElementsKinds(site);
Node* value = effect = maybe_value.value();
ReplaceWithValue(node, value, effect, control);
return Replace(value);
}
if (!site.boilerplate().has_value()) return NoChange();
AllocationType allocation = dependencies()->DependOnPretenureMode(site);
int max_properties = kMaxFastLiteralProperties;
base::Optional<Node*> maybe_value =
TryAllocateFastLiteral(effect, control, *site.boilerplate(), allocation,
kMaxFastLiteralDepth, &max_properties);
if (!maybe_value.has_value()) return NoChange();
dependencies()->DependOnElementsKinds(site);
Node* value = effect = maybe_value.value();
ReplaceWithValue(node, value, effect, control);
return Replace(value);
}
return NoChange();
}
......@@ -1660,20 +1651,48 @@ Node* JSCreateLowering::AllocateElements(Node* effect, Node* control,
base::Optional<Node*> JSCreateLowering::TryAllocateFastLiteral(
Node* effect, Node* control, JSObjectRef boilerplate,
AllocationType allocation) {
AllocationType allocation, int max_depth, int* max_properties) {
DCHECK_GE(max_depth, 0);
DCHECK_GE(*max_properties, 0);
if (max_depth == 0) return {};
// Prevent concurrent migrations of boilerplate objects.
JSHeapBroker::BoilerplateMigrationGuardIfNeeded boilerplate_access_guard(
broker());
// Now that we hold the migration lock, get the current map.
MapRef boilerplate_map = boilerplate.map();
base::Optional<MapRef> current_boilerplate_map =
boilerplate.map_direct_read();
{
base::Optional<MapRef> current_boilerplate_map =
boilerplate.map_direct_read();
if (!current_boilerplate_map.has_value() ||
!current_boilerplate_map->equals(boilerplate_map)) {
return {};
}
}
// Bail out if the boilerplate map has been deprecated. The map could of
// course be deprecated at some point after the line below, but it's not a
// correctness issue -- it only means the literal won't be created with the
// most up to date map(s).
if (boilerplate_map.is_deprecated()) return {};
if (!current_boilerplate_map.has_value() ||
!current_boilerplate_map.value().equals(boilerplate_map)) {
// We currently only support in-object properties.
if (boilerplate.map().elements_kind() == DICTIONARY_ELEMENTS ||
boilerplate.map().is_dictionary_map() ||
!boilerplate.raw_properties_or_hash().has_value()) {
return {};
}
{
ObjectRef properties = *boilerplate.raw_properties_or_hash();
bool const empty = properties.IsSmi() ||
properties.equals(MakeRef<Object>(
broker(), factory()->empty_fixed_array())) ||
properties.equals(MakeRef<Object>(
broker(), factory()->empty_property_array()));
if (!empty) return {};
}
// Compute the in-object properties to store first (might have effects).
ZoneVector<std::pair<FieldAccess, Node*>> inobject_fields(zone());
......@@ -1684,6 +1703,8 @@ base::Optional<Node*> JSCreateLowering::TryAllocateFastLiteral(
boilerplate_map.GetPropertyDetails(i);
if (property_details.location() != kField) continue;
DCHECK_EQ(kData, property_details.kind());
if ((*max_properties)-- == 0) return {};
NameRef property_name = boilerplate_map.GetPropertyKey(i);
FieldIndex index = boilerplate_map.GetFieldIndexFor(i);
ConstFieldInfo const_field_info(boilerplate_map.object());
......@@ -1730,8 +1751,9 @@ base::Optional<Node*> JSCreateLowering::TryAllocateFastLiteral(
Node* value;
if (boilerplate_value.IsJSObject()) {
JSObjectRef boilerplate_object = boilerplate_value.AsJSObject();
base::Optional<Node*> maybe_value = TryAllocateFastLiteral(
effect, control, boilerplate_object, allocation);
base::Optional<Node*> maybe_value =
TryAllocateFastLiteral(effect, control, boilerplate_object,
allocation, max_depth - 1, max_properties);
if (!maybe_value.has_value()) return {};
value = effect = maybe_value.value();
} else if (property_details.representation().IsDouble()) {
......@@ -1770,8 +1792,8 @@ base::Optional<Node*> JSCreateLowering::TryAllocateFastLiteral(
}
// Setup the elements backing store.
base::Optional<Node*> maybe_elements =
TryAllocateFastLiteralElements(effect, control, boilerplate, allocation);
base::Optional<Node*> maybe_elements = TryAllocateFastLiteralElements(
effect, control, boilerplate, allocation, max_depth, max_properties);
if (!maybe_elements.has_value()) return {};
Node* elements = maybe_elements.value();
if (elements->op()->EffectOutputCount() > 0) effect = elements;
......@@ -1798,7 +1820,10 @@ base::Optional<Node*> JSCreateLowering::TryAllocateFastLiteral(
base::Optional<Node*> JSCreateLowering::TryAllocateFastLiteralElements(
Node* effect, Node* control, JSObjectRef boilerplate,
AllocationType allocation) {
AllocationType allocation, int max_depth, int* max_properties) {
DCHECK_GT(max_depth, 0);
DCHECK_GE(*max_properties, 0);
base::Optional<FixedArrayBaseRef> maybe_boilerplate_elements =
boilerplate.elements(kRelaxedLoad);
if (!maybe_boilerplate_elements.has_value()) return {};
......@@ -1812,34 +1837,36 @@ base::Optional<Node*> JSCreateLowering::TryAllocateFastLiteralElements(
!boilerplate.IsElementsTenured(boilerplate_elements)) {
return {};
}
return jsgraph()->HeapConstant(boilerplate_elements.object());
return jsgraph()->Constant(boilerplate_elements);
}
// Compute the elements to store first (might have effects).
ZoneVector<Node*> elements_values(elements_length, zone());
if (elements_map.instance_type() == FIXED_DOUBLE_ARRAY_TYPE) {
if (boilerplate_elements.IsFixedDoubleArray()) {
int const size = FixedDoubleArray::SizeFor(boilerplate_elements.length());
if (size > kMaxRegularHeapObjectSize) return {};
FixedDoubleArrayRef elements = boilerplate_elements.AsFixedDoubleArray();
for (int i = 0; i < elements_length; ++i) {
Float64 value = elements.GetFromImmutableFixedDoubleArray(i);
if (value.is_hole_nan()) {
elements_values[i] = jsgraph()->TheHoleConstant();
} else {
elements_values[i] = jsgraph()->Constant(value.get_scalar());
}
elements_values[i] = value.is_hole_nan()
? jsgraph()->TheHoleConstant()
: jsgraph()->Constant(value.get_scalar());
}
} else {
FixedArrayRef elements = boilerplate_elements.AsFixedArray();
for (int i = 0; i < elements_length; ++i) {
base::Optional<ObjectRef> maybe_element_value = elements.TryGet(i);
if (!maybe_element_value.has_value()) return {};
ObjectRef element_value = maybe_element_value.value();
if (element_value.IsJSObject()) {
base::Optional<Node*> maybe_value = TryAllocateFastLiteral(
effect, control, element_value.AsJSObject(), allocation);
if (!maybe_value.has_value()) return {};
elements_values[i] = effect = maybe_value.value();
if ((*max_properties)-- == 0) return {};
base::Optional<ObjectRef> element_value = elements.TryGet(i);
if (!element_value.has_value()) return {};
if (element_value->IsJSObject()) {
base::Optional<Node*> object =
TryAllocateFastLiteral(effect, control, element_value->AsJSObject(),
allocation, max_depth - 1, max_properties);
if (!object.has_value()) return {};
elements_values[i] = effect = *object;
} else {
elements_values[i] = jsgraph()->Constant(element_value);
elements_values[i] = jsgraph()->Constant(*element_value);
}
}
}
......@@ -1848,10 +1875,9 @@ base::Optional<Node*> JSCreateLowering::TryAllocateFastLiteralElements(
AllocationBuilder ab(jsgraph(), effect, control);
CHECK(ab.CanAllocateArray(elements_length, elements_map, allocation));
ab.AllocateArray(elements_length, elements_map, allocation);
ElementAccess const access =
(elements_map.instance_type() == FIXED_DOUBLE_ARRAY_TYPE)
? AccessBuilder::ForFixedDoubleArrayElement()
: AccessBuilder::ForFixedArrayElement();
ElementAccess const access = boilerplate_elements.IsFixedDoubleArray()
? AccessBuilder::ForFixedDoubleArrayElement()
: AccessBuilder::ForFixedArrayElement();
for (int i = 0; i < elements_length; ++i) {
ab.Store(access, jsgraph()->Constant(i), elements_values[i]);
}
......
......@@ -99,10 +99,12 @@ class V8_EXPORT_PRIVATE JSCreateLowering final
bool* has_aliased_arguments);
base::Optional<Node*> TryAllocateFastLiteral(Node* effect, Node* control,
JSObjectRef boilerplate,
AllocationType allocation);
AllocationType allocation,
int max_depth,
int* max_properties);
base::Optional<Node*> TryAllocateFastLiteralElements(
Node* effect, Node* control, JSObjectRef boilerplate,
AllocationType allocation);
AllocationType allocation, int max_depth, int* max_properties);
Node* AllocateElements(Node* effect, Node* control,
ElementsKind elements_kind, int capacity,
......
......@@ -762,12 +762,8 @@ ProcessedFeedback const& JSHeapBroker::ReadFeedbackForArrayOrObjectLiteral(
return NewInsufficientFeedback(nexus.kind());
}
AllocationSiteRef site =
MakeRef(this, handle(AllocationSite::cast(object), isolate()));
if (site.IsFastLiteral()) {
site.SerializeBoilerplate();
}
AllocationSiteRef site = MakeRef(this, AllocationSite::cast(object));
if (site.PointsToLiteral()) site.SerializeRecursive();
return *zone()->New<LiteralFeedback>(site, nexus.kind());
}
......
......@@ -30,7 +30,7 @@ ACCESSORS(AllocationSite, transition_info_or_boilerplate, Object,
RELEASE_ACQUIRE_ACCESSORS(AllocationSite, transition_info_or_boilerplate,
Object, kTransitionInfoOrBoilerplateOffset)
ACCESSORS(AllocationSite, nested_site, Object, kNestedSiteOffset)
INT32_ACCESSORS(AllocationSite, pretenure_data, kPretenureDataOffset)
RELAXED_INT32_ACCESSORS(AllocationSite, pretenure_data, kPretenureDataOffset)
INT32_ACCESSORS(AllocationSite, pretenure_create_count,
kPretenureCreateCountOffset)
ACCESSORS(AllocationSite, dependent_code, DependentCode, kDependentCodeOffset)
......@@ -55,12 +55,13 @@ void AllocationSite::set_boilerplate(JSObject value, ReleaseStoreTag tag,
int AllocationSite::transition_info() const {
DCHECK(!PointsToLiteral());
return Smi::cast(transition_info_or_boilerplate()).value();
return Smi::cast(transition_info_or_boilerplate(kAcquireLoad)).value();
}
void AllocationSite::set_transition_info(int value) {
DCHECK(!PointsToLiteral());
set_transition_info_or_boilerplate(Smi::FromInt(value), SKIP_WRITE_BARRIER);
set_transition_info_or_boilerplate(Smi::FromInt(value), kReleaseStore,
SKIP_WRITE_BARRIER);
}
bool AllocationSite::HasWeakNext() const {
......@@ -113,7 +114,7 @@ void AllocationSite::SetDoNotInlineCall() {
}
bool AllocationSite::PointsToLiteral() const {
Object raw_value = transition_info_or_boilerplate();
Object raw_value = transition_info_or_boilerplate(kAcquireLoad);
DCHECK_EQ(!raw_value.IsSmi(),
raw_value.IsJSArray() || raw_value.IsJSObject());
return !raw_value.IsSmi();
......@@ -234,6 +235,7 @@ bool AllocationSite::DigestTransitionFeedback(Handle<AllocationSite> site,
is_nested ? "(nested)" : " ", ElementsKindToString(kind),
ElementsKindToString(to_kind));
}
CHECK_NE(to_kind, DICTIONARY_ELEMENTS);
JSObject::TransitionElementsKind(boilerplate, to_kind);
site->dependent_code().DeoptimizeDependentCodeGroup(
DependentCode::kAllocationSiteTransitionChangedGroup);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment