Commit 279a83e4 authored by Benedikt Meurer's avatar Benedikt Meurer Committed by Commit Bot

[turbofan] Refactor the JSCreateArray lowering to support more cases.

Array (subclass) constructor calls with 0 parameters are now properly
turned into inline allocations, also Array (subclass) constructor calls
with exactly one parameter, which is either known to not be a Number
or which is a known integer in the valid loop unrolling range.

Also refactor the general JSCreateArray lowering logic to properly
support Array subclasses, i.e. deal with inobject properties and
initial maps correctly.

This boosts performance of those cases significantly (and will allow
us to reduce the complexity of the Array constructor significantly
long-term). For example the simple case

  new Array("a", "b", "c", "d", "e", "f", "g")

is now around 10x faster than before.

Bug: v8:6399
Change-Id: I70661971398524ee0c6a349ee559b98a962a6266
Reviewed-on: https://chromium-review.googlesource.com/703134
Commit-Queue: Benedikt Meurer <bmeurer@chromium.org>
Reviewed-by: 's avatarJaroslav Sevcik <jarin@chromium.org>
Cr-Commit-Position: refs/heads/master@{#48325}
parent 3dcb40c9
......@@ -581,27 +581,20 @@ Reduction JSCreateLowering::ReduceJSCreateGeneratorObject(Node* node) {
Reduction JSCreateLowering::ReduceNewArray(Node* node, Node* length,
int capacity,
Handle<AllocationSite> site) {
Handle<Map> initial_map,
PretenureFlag pretenure) {
DCHECK(node->opcode() == IrOpcode::kJSCreateArray ||
node->opcode() == IrOpcode::kJSCreateEmptyLiteralArray);
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node);
// Extract transition and tenuring feedback from the {site} and add
// appropriate code dependencies on the {site} if deoptimization is
// enabled.
PretenureFlag pretenure = site->GetPretenureMode();
ElementsKind elements_kind = site->GetElementsKind();
DCHECK(IsFastElementsKind(elements_kind));
if (NodeProperties::GetType(length)->Max() > 0) {
// Determine the appropriate elements kind.
ElementsKind elements_kind = initial_map->elements_kind();
if (NodeProperties::GetType(length)->Max() > 0.0) {
elements_kind = GetHoleyElementsKind(elements_kind);
initial_map = Map::AsElementsKind(initial_map, elements_kind);
}
dependencies()->AssumeTenuringDecision(site);
dependencies()->AssumeTransitionStable(site);
// Retrieve the initial map for the array.
Node* js_array_map = jsgraph()->HeapConstant(
handle(native_context()->GetInitialJSArrayMap(elements_kind), isolate()));
DCHECK(IsFastElementsKind(elements_kind));
// Setup elements and properties.
Node* elements;
......@@ -615,11 +608,15 @@ Reduction JSCreateLowering::ReduceNewArray(Node* node, Node* length,
// Perform the allocation of the actual JSArray object.
AllocationBuilder a(jsgraph(), effect, control);
a.Allocate(JSArray::kSize, pretenure);
a.Store(AccessBuilder::ForMap(), js_array_map);
a.Allocate(initial_map->instance_size(), pretenure);
a.Store(AccessBuilder::ForMap(), initial_map);
a.Store(AccessBuilder::ForJSObjectPropertiesOrHash(), properties);
a.Store(AccessBuilder::ForJSObjectElements(), elements);
a.Store(AccessBuilder::ForJSArrayLength(elements_kind), length);
for (int i = 0; i < initial_map->GetInObjectProperties(); ++i) {
a.Store(AccessBuilder::ForJSObjectInObjectProperty(initial_map, i),
jsgraph()->UndefinedConstant());
}
RelaxControls(node);
a.FinishAndChange(node);
return Changed(node);
......@@ -627,19 +624,15 @@ Reduction JSCreateLowering::ReduceNewArray(Node* node, Node* length,
Reduction JSCreateLowering::ReduceNewArray(Node* node,
std::vector<Node*> values,
Handle<AllocationSite> site) {
Handle<Map> initial_map,
PretenureFlag pretenure) {
DCHECK_EQ(IrOpcode::kJSCreateArray, node->opcode());
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node);
// Extract transition and tenuring feedback from the {site} and add
// appropriate code dependencies on the {site} if deoptimization is
// enabled.
PretenureFlag pretenure = site->GetPretenureMode();
ElementsKind elements_kind = site->GetElementsKind();
// Determine the appropriate elements kind.
ElementsKind elements_kind = initial_map->elements_kind();
DCHECK(IsFastElementsKind(elements_kind));
dependencies()->AssumeTenuringDecision(site);
dependencies()->AssumeTransitionStable(site);
// Check {values} based on the {elements_kind}. These checks are guarded
// by the {elements_kind} feedback on the {site}, so it's safe to just
......@@ -662,10 +655,6 @@ Reduction JSCreateLowering::ReduceNewArray(Node* node,
}
}
// Retrieve the initial map for the array.
Node* js_array_map = jsgraph()->HeapConstant(
handle(native_context()->GetInitialJSArrayMap(elements_kind), isolate()));
// Setup elements, properties and length.
Node* elements = effect =
AllocateElements(effect, control, elements_kind, values, pretenure);
......@@ -674,11 +663,15 @@ Reduction JSCreateLowering::ReduceNewArray(Node* node,
// Perform the allocation of the actual JSArray object.
AllocationBuilder a(jsgraph(), effect, control);
a.Allocate(JSArray::kSize, pretenure);
a.Store(AccessBuilder::ForMap(), js_array_map);
a.Allocate(initial_map->instance_size(), pretenure);
a.Store(AccessBuilder::ForMap(), initial_map);
a.Store(AccessBuilder::ForJSObjectPropertiesOrHash(), properties);
a.Store(AccessBuilder::ForJSObjectElements(), elements);
a.Store(AccessBuilder::ForJSArrayLength(elements_kind), length);
for (int i = 0; i < initial_map->GetInObjectProperties(); ++i) {
a.Store(AccessBuilder::ForJSObjectInObjectProperty(initial_map, i),
jsgraph()->UndefinedConstant());
}
RelaxControls(node);
a.FinishAndChange(node);
return Changed(node);
......@@ -749,49 +742,124 @@ Reduction JSCreateLowering::ReduceNewArrayToStubCall(
Reduction JSCreateLowering::ReduceJSCreateArray(Node* node) {
DCHECK_EQ(IrOpcode::kJSCreateArray, node->opcode());
CreateArrayParameters const& p = CreateArrayParametersOf(node->op());
int const arity = static_cast<int>(p.arity());
Handle<AllocationSite> const site = p.site();
PretenureFlag pretenure = NOT_TENURED;
Handle<JSFunction> constructor(native_context()->array_function(), isolate());
Node* target = NodeProperties::GetValueInput(node, 0);
Node* new_target = NodeProperties::GetValueInput(node, 1);
Type* new_target_type = (target == new_target)
? Type::HeapConstant(constructor, zone())
: NodeProperties::GetType(new_target);
// TODO(bmeurer): Optimize the subclassing case.
if (target != new_target) return NoChange();
// Extract original constructor function.
if (new_target_type->IsHeapConstant() &&
new_target_type->AsHeapConstant()->Value()->IsJSFunction()) {
Handle<JSFunction> original_constructor =
Handle<JSFunction>::cast(new_target_type->AsHeapConstant()->Value());
DCHECK(constructor->IsConstructor());
DCHECK(original_constructor->IsConstructor());
// Check if we have a feedback {site} on the {node}.
Handle<AllocationSite> site = p.site();
if (!site.is_null()) {
// Attempt to inline calls to the Array constructor for the relevant cases
// where either no arguments are provided, or exactly one unsigned number
// argument is given.
if (site->CanInlineCall()) {
if (p.arity() == 0) {
// Check if we can inline the allocation.
if (IsAllocationInlineable(constructor, original_constructor)) {
// Force completion of inobject slack tracking before
// generating code to finalize the instance size.
original_constructor->CompleteInobjectSlackTrackingIfActive();
Handle<Map> initial_map(original_constructor->initial_map(), isolate());
// Add a dependency on the {initial_map} to make sure that this code is
// deoptimized whenever the {initial_map} changes.
dependencies()->AssumeInitialMapCantChange(initial_map);
// Check if we have a feedback {site} on the {node}.
if (!site.is_null()) {
ElementsKind elements_kind = site->GetElementsKind();
if (initial_map->elements_kind() != elements_kind) {
initial_map = Map::AsElementsKind(initial_map, elements_kind);
}
pretenure = site->GetPretenureMode();
dependencies()->AssumeTransitionStable(site);
dependencies()->AssumeTenuringDecision(site);
}
if (arity == 0) {
Node* length = jsgraph()->ZeroConstant();
int capacity = JSArray::kPreallocatedArrayElements;
return ReduceNewArray(node, length, capacity, site);
} else if (p.arity() == 1) {
return ReduceNewArray(node, length, capacity, initial_map, pretenure);
} else if (arity == 1) {
Node* length = NodeProperties::GetValueInput(node, 2);
Type* length_type = NodeProperties::GetType(length);
if (!length_type->Maybe(Type::Number())) {
// Handle the single argument case, where we know that the value
// cannot be a valid Array length.
return ReduceNewArray(node, {length}, site);
ElementsKind elements_kind = initial_map->elements_kind();
elements_kind = GetMoreGeneralElementsKind(
elements_kind, IsHoleyElementsKind(elements_kind)
? HOLEY_ELEMENTS
: PACKED_ELEMENTS);
initial_map = Map::AsElementsKind(initial_map, elements_kind);
return ReduceNewArray(node, {length}, initial_map, pretenure);
}
if (length_type->Is(Type::SignedSmall()) && length_type->Min() >= 0 &&
length_type->Max() <= kElementLoopUnrollLimit &&
length_type->Min() == length_type->Max()) {
int capacity = static_cast<int>(length_type->Max());
return ReduceNewArray(node, length, capacity, site);
return ReduceNewArray(node, length, capacity, initial_map, pretenure);
}
} else if (p.arity() <= JSArray::kInitialMaxFastElementArray) {
} else if (arity <= JSArray::kInitialMaxFastElementArray) {
// Gather the values to store into the newly created array.
bool values_all_smis = true, values_all_numbers = true,
values_any_nonnumber = false;
std::vector<Node*> values;
values.reserve(p.arity());
for (size_t i = 0; i < p.arity(); ++i) {
values.push_back(
NodeProperties::GetValueInput(node, static_cast<int>(2 + i)));
for (int i = 0; i < arity; ++i) {
Node* value = NodeProperties::GetValueInput(node, 2 + i);
Type* value_type = NodeProperties::GetType(value);
if (!value_type->Is(Type::SignedSmall())) {
values_all_smis = false;
}
if (!value_type->Is(Type::Number())) {
values_all_numbers = false;
}
if (!value_type->Maybe(Type::Number())) {
values_any_nonnumber = true;
}
values.push_back(value);
}
// Try to figure out the ideal elements kind statically.
ElementsKind elements_kind = initial_map->elements_kind();
if (values_all_smis) {
// Smis can be stored with any elements kind.
} else if (values_all_numbers) {
elements_kind = GetMoreGeneralElementsKind(
elements_kind, IsHoleyElementsKind(elements_kind)
? HOLEY_DOUBLE_ELEMENTS
: PACKED_DOUBLE_ELEMENTS);
} else if (values_any_nonnumber) {
elements_kind = GetMoreGeneralElementsKind(
elements_kind, IsHoleyElementsKind(elements_kind)
? HOLEY_ELEMENTS
: PACKED_ELEMENTS);
} else if (site.is_null() || !site->CanInlineCall()) {
// We have some crazy combination of types for the {values} where
// there's no clear decision on the elements kind statically. And
// we don't have a protection against deoptimization loops for the
// checks that are introduced in the call to ReduceNewArray, so
// we cannot inline this invocation of the Array constructor here.
return NoChange();
}
return ReduceNewArray(node, values, site);
initial_map = Map::AsElementsKind(initial_map, elements_kind);
return ReduceNewArray(node, values, initial_map, pretenure);
}
}
}
// TODO(bmeurer): Optimize the subclassing case.
if (target != new_target) return NoChange();
return ReduceNewArrayToStubCall(node, site);
}
......@@ -931,8 +999,14 @@ Reduction JSCreateLowering::ReduceJSCreateEmptyLiteralArray(Node* node) {
if (feedback->IsAllocationSite()) {
Handle<AllocationSite> site = Handle<AllocationSite>::cast(feedback);
DCHECK(!site->PointsToLiteral());
Handle<Map> const initial_map(
native_context()->GetInitialJSArrayMap(site->GetElementsKind()),
isolate());
PretenureFlag const pretenure = site->GetPretenureMode();
dependencies()->AssumeTransitionStable(site);
dependencies()->AssumeTenuringDecision(site);
Node* length = jsgraph()->ZeroConstant();
return ReduceNewArray(node, length, 0, site);
return ReduceNewArray(node, length, 0, initial_map, pretenure);
}
return NoChange();
}
......
......@@ -63,9 +63,9 @@ class V8_EXPORT_PRIVATE JSCreateLowering final
Reduction ReduceJSCreateBlockContext(Node* node);
Reduction ReduceJSCreateGeneratorObject(Node* node);
Reduction ReduceNewArray(Node* node, Node* length, int capacity,
Handle<AllocationSite> site);
Handle<Map> initial_map, PretenureFlag pretenure);
Reduction ReduceNewArray(Node* node, std::vector<Node*> values,
Handle<AllocationSite> site);
Handle<Map> initial_map, PretenureFlag pretenure);
Node* AllocateArguments(Node* effect, Node* control, Node* frame_state);
Node* AllocateRestArguments(Node* effect, Node* control, Node* frame_state,
......
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