Commit 34de39bf authored by Benedikt Meurer's avatar Benedikt Meurer Committed by Commit Bot

[turbofan] Add support to inline new Array(n) calls.

Make calls like

  new Array(n)
  new A(n)

(where A is a subclass of Array) inlinable into TurboFan. We do this by
speculatively checking that n is an unsigned integer that is not greater
than JSArray::kInitialMaxFastElementArray, and then lowering the backing
store allocation to a builtin call. The speculative optimization is
either protected by the AllocationSite for the Array constructor
invocation (if we have one), or by a newly introduced global protector
cell that is used for Array constructor invocations that don't have an
AllocationSite, i.e. the ones from Array#map, Array#filter, or from
subclasses of Array.

Next step will be to implement the backing store allocations inline in
TurboFan, but that requires Loop support in the GraphAssembler, so it's
done as a separate CL. This should further boost the performance.

This boosts the ARES6 ML benchmark by up to 8% on the steady state,
and also improves monomorphic Array#map calls by around 20-25% on the
initial setup.

Bug: v8:6399
Tbr: ulan@chromium.org
Change-Id: I7c8bdecf7c814ce52db6ee3051c3206a4f7d4bb6
Reviewed-on: https://chromium-review.googlesource.com/704639
Commit-Queue: Benedikt Meurer <bmeurer@chromium.org>
Reviewed-by: 's avatarBenedikt Meurer <bmeurer@chromium.org>
Reviewed-by: 's avatarJaroslav Sevcik <jarin@chromium.org>
Reviewed-by: 's avatarMichael Stanton <mvstanton@chromium.org>
Cr-Commit-Position: refs/heads/master@{#48348}
parent b7db31a2
......@@ -167,6 +167,12 @@ namespace internal {
TFC(GrowFastDoubleElements, GrowArrayElements, 1) \
TFC(GrowFastSmiOrObjectElements, GrowArrayElements, 1) \
TFC(NewArgumentsElements, NewArgumentsElements, 1) \
/* TODO(bmeurer): Remove these four builtins when the operations are */ \
/* inlined into TurboFan, which requires Loop support in GraphAssembler. */ \
TFS(NewFastDoubleElements_NotTenured, kLength) \
TFS(NewFastDoubleElements_Tenured, kLength) \
TFS(NewFastSmiOrObjectElements_NotTenured, kLength) \
TFS(NewFastSmiOrObjectElements_Tenured, kLength) \
\
/* Debugger */ \
ASM(FrameDropperTrampoline) \
......
......@@ -94,6 +94,88 @@ TF_BUILTIN(GrowFastSmiOrObjectElements, CodeStubAssembler) {
TailCallRuntime(Runtime::kGrowArrayElements, context, object, key);
}
// TODO(bmeurer): Temporary builtin, will disappear soon.
TF_BUILTIN(NewFastDoubleElements_NotTenured, CodeStubAssembler) {
Node* length = SmiUntag(Parameter(Descriptor::kLength));
Node* const zero = IntPtrConstant(0);
ElementsKind const elements_kind = PACKED_DOUBLE_ELEMENTS;
Label if_empty(this, Label::kDeferred), if_nonempty(this);
Branch(WordEqual(length, zero), &if_empty, &if_nonempty);
BIND(&if_empty);
Return(EmptyFixedArrayConstant());
BIND(&if_nonempty);
Node* const elements = AllocateFixedArray(elements_kind, length);
FillFixedArrayWithValue(elements_kind, elements, zero, length,
Heap::kTheHoleValueRootIndex);
Return(elements);
}
// TODO(bmeurer): Temporary builtin, will disappear soon.
TF_BUILTIN(NewFastDoubleElements_Tenured, CodeStubAssembler) {
Node* length = SmiUntag(Parameter(Descriptor::kLength));
Node* const zero = IntPtrConstant(0);
ElementsKind const elements_kind = PACKED_DOUBLE_ELEMENTS;
Label if_empty(this, Label::kDeferred), if_nonempty(this);
Branch(WordEqual(length, zero), &if_empty, &if_nonempty);
BIND(&if_empty);
Return(EmptyFixedArrayConstant());
BIND(&if_nonempty);
Node* const elements =
AllocateFixedArray(elements_kind, length, INTPTR_PARAMETERS, kPretenured);
FillFixedArrayWithValue(elements_kind, elements, zero, length,
Heap::kTheHoleValueRootIndex);
Return(elements);
}
// TODO(bmeurer): Temporary builtin, will disappear soon.
TF_BUILTIN(NewFastSmiOrObjectElements_NotTenured, CodeStubAssembler) {
Node* length = SmiUntag(Parameter(Descriptor::kLength));
Node* const zero = IntPtrConstant(0);
ElementsKind const elements_kind = PACKED_ELEMENTS;
Label if_empty(this, Label::kDeferred), if_nonempty(this);
Branch(WordEqual(length, zero), &if_empty, &if_nonempty);
BIND(&if_empty);
Return(EmptyFixedArrayConstant());
BIND(&if_nonempty);
Node* const elements = AllocateFixedArray(elements_kind, length);
FillFixedArrayWithValue(elements_kind, elements, zero, length,
Heap::kTheHoleValueRootIndex);
Return(elements);
}
// TODO(bmeurer): Temporary builtin, will disappear soon.
TF_BUILTIN(NewFastSmiOrObjectElements_Tenured, CodeStubAssembler) {
Node* length = SmiUntag(Parameter(Descriptor::kLength));
Node* const zero = IntPtrConstant(0);
ElementsKind const elements_kind = PACKED_ELEMENTS;
Label if_empty(this, Label::kDeferred), if_nonempty(this);
Branch(WordEqual(length, zero), &if_empty, &if_nonempty);
BIND(&if_empty);
Return(EmptyFixedArrayConstant());
BIND(&if_nonempty);
Node* const elements =
AllocateFixedArray(elements_kind, length, INTPTR_PARAMETERS, kPretenured);
FillFixedArrayWithValue(elements_kind, elements, zero, length,
Heap::kTheHoleValueRootIndex);
Return(elements);
}
TF_BUILTIN(NewArgumentsElements, CodeStubAssembler) {
Node* frame = Parameter(Descriptor::kFrame);
Node* length = SmiToWord(Parameter(Descriptor::kLength));
......
......@@ -754,6 +754,12 @@ bool EffectControlLinearizer::TryWireInStateEffect(Node* node,
case IrOpcode::kArgumentsLength:
result = LowerArgumentsLength(node);
break;
case IrOpcode::kNewFastDoubleElements:
result = LowerNewFastDoubleElements(node);
break;
case IrOpcode::kNewFastSmiOrObjectElements:
result = LowerNewFastSmiOrObjectElements(node);
break;
case IrOpcode::kNewArgumentsElements:
result = LowerNewArgumentsElements(node);
break;
......@@ -2215,6 +2221,38 @@ Node* EffectControlLinearizer::LowerArgumentsFrame(Node* node) {
return done.PhiAt(0);
}
Node* EffectControlLinearizer::LowerNewFastDoubleElements(Node* node) {
PretenureFlag const pretenure = PretenureFlagOf(node->op());
Node* length = node->InputAt(0);
Callable const callable = Builtins::CallableFor(
isolate(), pretenure == NOT_TENURED
? Builtins::kNewFastDoubleElements_NotTenured
: Builtins::kNewFastDoubleElements_Tenured);
Operator::Properties const properties = node->op()->properties();
CallDescriptor::Flags const flags = CallDescriptor::kNoFlags;
CallDescriptor* desc = Linkage::GetStubCallDescriptor(
isolate(), graph()->zone(), callable.descriptor(), 0, flags, properties);
return __ Call(desc, __ HeapConstant(callable.code()), length,
__ NoContextConstant());
}
Node* EffectControlLinearizer::LowerNewFastSmiOrObjectElements(Node* node) {
PretenureFlag const pretenure = PretenureFlagOf(node->op());
Node* length = node->InputAt(0);
Callable const callable = Builtins::CallableFor(
isolate(), pretenure == NOT_TENURED
? Builtins::kNewFastSmiOrObjectElements_NotTenured
: Builtins::kNewFastSmiOrObjectElements_Tenured);
Operator::Properties const properties = node->op()->properties();
CallDescriptor::Flags const flags = CallDescriptor::kNoFlags;
CallDescriptor* desc = Linkage::GetStubCallDescriptor(
isolate(), graph()->zone(), callable.descriptor(), 0, flags, properties);
return __ Call(desc, __ HeapConstant(callable.code()), length,
__ NoContextConstant());
}
Node* EffectControlLinearizer::LowerNewArgumentsElements(Node* node) {
Node* frame = NodeProperties::GetValueInput(node, 0);
Node* length = NodeProperties::GetValueInput(node, 1);
......
......@@ -98,6 +98,8 @@ class V8_EXPORT_PRIVATE EffectControlLinearizer {
Node* LowerObjectIsUndetectable(Node* node);
Node* LowerArgumentsFrame(Node* node);
Node* LowerArgumentsLength(Node* node);
Node* LowerNewFastDoubleElements(Node* node);
Node* LowerNewFastSmiOrObjectElements(Node* node);
Node* LowerNewArgumentsElements(Node* node);
Node* LowerArrayBufferWasNeutered(Node* node);
Node* LowerStringCharAt(Node* node);
......
......@@ -579,6 +579,57 @@ Reduction JSCreateLowering::ReduceJSCreateGeneratorObject(Node* node) {
return NoChange();
}
// Constructs an array with a variable {length} when no upper bound
// is known for the capacity.
Reduction JSCreateLowering::ReduceNewArray(Node* node, Node* length,
Handle<Map> initial_map,
PretenureFlag pretenure) {
DCHECK_EQ(IrOpcode::kJSCreateArray, node->opcode());
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node);
// Constructing an Array via new Array(N) where N is an unsigned
// integer, always creates a holey backing store.
if (!IsHoleyElementsKind(initial_map->elements_kind())) {
initial_map = Map::AsElementsKind(
initial_map, GetHoleyElementsKind(initial_map->elements_kind()));
}
// Check that the {limit} is an unsigned integer in the valid range.
// This has to be kept in sync with src/runtime/runtime-array.cc,
// where this limit is protected.
length = effect = graph()->NewNode(
simplified()->CheckBounds(), length,
jsgraph()->Constant(JSArray::kInitialMaxFastElementArray), effect,
control);
// Construct elements and properties for the resulting JSArray.
Node* elements = effect = graph()->NewNode(
IsDoubleElementsKind(initial_map->elements_kind())
? simplified()->NewFastDoubleElements(pretenure)
: simplified()->NewFastSmiOrObjectElements(pretenure),
length, effect, control);
Node* properties = jsgraph()->EmptyFixedArrayConstant();
// Perform the allocation of the actual JSArray object.
AllocationBuilder a(jsgraph(), effect, control);
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(initial_map->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);
}
// Constructs an array with a variable {length} when an actual
// upper bound is known for the {capacity}.
Reduction JSCreateLowering::ReduceNewArray(Node* node, Node* length,
int capacity,
Handle<Map> initial_map,
......@@ -771,16 +822,23 @@ Reduction JSCreateLowering::ReduceJSCreateArray(Node* node) {
// deoptimized whenever the {initial_map} changes.
dependencies()->AssumeInitialMapCantChange(initial_map);
// Tells whether we are protected by either the {site} or a
// protector cell to do certain speculative optimizations.
bool can_inline_call = false;
// 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);
}
can_inline_call = site->CanInlineCall();
pretenure = site->GetPretenureMode();
dependencies()->AssumeTransitionStable(site);
dependencies()->AssumeTenuringDecision(site);
} else {
can_inline_call = isolate()->IsArrayConstructorIntact();
}
if (arity == 0) {
......@@ -799,7 +857,8 @@ Reduction JSCreateLowering::ReduceJSCreateArray(Node* node) {
? HOLEY_ELEMENTS
: PACKED_ELEMENTS);
initial_map = Map::AsElementsKind(initial_map, elements_kind);
return ReduceNewArray(node, {length}, initial_map, pretenure);
return ReduceNewArray(node, std::vector<Node*>{length}, initial_map,
pretenure);
}
if (length_type->Is(Type::SignedSmall()) && length_type->Min() >= 0 &&
length_type->Max() <= kElementLoopUnrollLimit &&
......@@ -807,6 +866,9 @@ Reduction JSCreateLowering::ReduceJSCreateArray(Node* node) {
int capacity = static_cast<int>(length_type->Max());
return ReduceNewArray(node, length, capacity, initial_map, pretenure);
}
if (length_type->Maybe(Type::UnsignedSmall()) && can_inline_call) {
return ReduceNewArray(node, length, initial_map, pretenure);
}
} else if (arity <= JSArray::kInitialMaxFastElementArray) {
// Gather the values to store into the newly created array.
bool values_all_smis = true, values_all_numbers = true,
......@@ -842,7 +904,7 @@ Reduction JSCreateLowering::ReduceJSCreateArray(Node* node) {
elements_kind, IsHoleyElementsKind(elements_kind)
? HOLEY_ELEMENTS
: PACKED_ELEMENTS);
} else if (site.is_null() || !site->CanInlineCall()) {
} else if (!can_inline_call) {
// 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
......
......@@ -62,6 +62,8 @@ class V8_EXPORT_PRIVATE JSCreateLowering final
Reduction ReduceJSCreateCatchContext(Node* node);
Reduction ReduceJSCreateBlockContext(Node* node);
Reduction ReduceJSCreateGeneratorObject(Node* node);
Reduction ReduceNewArray(Node* node, Node* length, Handle<Map> initial_map,
PretenureFlag pretenure);
Reduction ReduceNewArray(Node* node, Node* length, int capacity,
Handle<Map> initial_map, PretenureFlag pretenure);
Reduction ReduceNewArray(Node* node, std::vector<Node*> values,
......
......@@ -366,6 +366,8 @@
V(ObjectIsUndetectable) \
V(ArgumentsFrame) \
V(ArgumentsLength) \
V(NewFastDoubleElements) \
V(NewFastSmiOrObjectElements) \
V(NewArgumentsElements) \
V(ArrayBufferWasNeutered) \
V(EnsureWritableFastElements) \
......
......@@ -2777,6 +2777,12 @@ class RepresentationSelector {
MachineRepresentation::kTaggedSigned);
return;
}
case IrOpcode::kNewFastDoubleElements:
case IrOpcode::kNewFastSmiOrObjectElements: {
VisitUnop(node, UseInfo::TaggedSigned(),
MachineRepresentation::kTaggedPointer);
return;
}
case IrOpcode::kNewArgumentsElements: {
VisitBinop(node, UseInfo::PointerInt(), UseInfo::TaggedSigned(),
MachineRepresentation::kTaggedPointer);
......
......@@ -397,6 +397,10 @@ bool operator!=(AllocateParameters const& lhs, AllocateParameters const& rhs) {
}
PretenureFlag PretenureFlagOf(const Operator* op) {
if (op->opcode() == IrOpcode::kNewFastDoubleElements ||
op->opcode() == IrOpcode::kNewFastSmiOrObjectElements) {
return OpParameter<PretenureFlag>(op);
}
DCHECK_EQ(IrOpcode::kAllocate, op->opcode());
return OpParameter<AllocateParameters>(op).pretenure();
}
......@@ -973,6 +977,26 @@ bool IsRestLengthOf(const Operator* op) {
return OpParameter<ArgumentsLengthParameters>(op).is_rest_length;
}
const Operator* SimplifiedOperatorBuilder::NewFastDoubleElements(
PretenureFlag pretenure) {
return new (zone()) Operator1<PretenureFlag>( // --
IrOpcode::kNewFastDoubleElements, // opcode
Operator::kEliminatable, // flags
"NewFastDoubleElements", // name
1, 1, 1, 1, 1, 0, // counts
pretenure); // parameter
}
const Operator* SimplifiedOperatorBuilder::NewFastSmiOrObjectElements(
PretenureFlag pretenure) {
return new (zone()) Operator1<PretenureFlag>( // --
IrOpcode::kNewFastSmiOrObjectElements, // opcode
Operator::kEliminatable, // flags
"NewFastSmiOrObjectElements", // name
1, 1, 1, 1, 1, 0, // counts
pretenure); // parameter
}
const Operator* SimplifiedOperatorBuilder::NewArgumentsElements(
int mapped_count) {
return new (zone()) Operator1<int>( // --
......
......@@ -452,6 +452,9 @@ class V8_EXPORT_PRIVATE SimplifiedOperatorBuilder final
const Operator* ArgumentsLength(int formal_parameter_count,
bool is_rest_length);
const Operator* NewFastDoubleElements(PretenureFlag);
const Operator* NewFastSmiOrObjectElements(PretenureFlag);
// new-arguments-elements arguments-frame, arguments-length
const Operator* NewArgumentsElements(int mapped_count);
......
......@@ -2034,6 +2034,14 @@ Type* Typer::Visitor::TypeArgumentsFrame(Node* node) {
return Type::ExternalPointer();
}
Type* Typer::Visitor::TypeNewFastDoubleElements(Node* node) {
return Type::OtherInternal();
}
Type* Typer::Visitor::TypeNewFastSmiOrObjectElements(Node* node) {
return Type::OtherInternal();
}
Type* Typer::Visitor::TypeNewArgumentsElements(Node* node) {
return Type::OtherInternal();
}
......
......@@ -1035,6 +1035,12 @@ void Verifier::Visitor::Check(Node* node) {
case IrOpcode::kArgumentsFrame:
CheckTypeIs(node, Type::ExternalPointer());
break;
case IrOpcode::kNewFastDoubleElements:
case IrOpcode::kNewFastSmiOrObjectElements:
CheckValueInputIs(node, 0,
Type::Range(0.0, FixedArray::kMaxLength, zone));
CheckTypeIs(node, Type::OtherInternal());
break;
case IrOpcode::kNewArgumentsElements:
CheckValueInputIs(node, 0, Type::ExternalPointer());
CheckValueInputIs(node, 1, Type::Range(-Code::kMaxArguments,
......
......@@ -186,6 +186,7 @@ using v8::MemoryPressureLevel;
V(WeakCell, empty_weak_cell, EmptyWeakCell) \
V(InterceptorInfo, noop_interceptor_info, NoOpInterceptorInfo) \
/* Protectors */ \
V(Cell, array_constructor_protector, ArrayConstructorProtector) \
V(PropertyCell, array_protector, ArrayProtector) \
V(Cell, is_concat_spreadable_protector, IsConcatSpreadableProtector) \
V(PropertyCell, species_protector, SpeciesProtector) \
......
......@@ -574,6 +574,10 @@ void Heap::CreateInitialObjects() {
script->set_type(Script::TYPE_NATIVE);
set_empty_script(*script);
Handle<Cell> array_constructor_cell = factory->NewCell(
handle(Smi::FromInt(Isolate::kProtectorValid), isolate()));
set_array_constructor_protector(*array_constructor_cell);
Handle<PropertyCell> cell = factory->NewPropertyCell(factory->empty_string());
cell->set_value(Smi::FromInt(Isolate::kProtectorValid));
set_array_protector(*cell);
......
......@@ -126,6 +126,11 @@ Isolate::ExceptionScope::~ExceptionScope() {
NATIVE_CONTEXT_FIELDS(NATIVE_CONTEXT_FIELD_ACCESSOR)
#undef NATIVE_CONTEXT_FIELD_ACCESSOR
bool Isolate::IsArrayConstructorIntact() {
Cell* array_constructor_cell = heap()->array_constructor_protector();
return array_constructor_cell->value() == Smi::FromInt(kProtectorValid);
}
bool Isolate::IsArraySpeciesLookupChainIntact() {
// Note: It would be nice to have debug checks to make sure that the
// species protector is accurate, but this would be hard to do for most of
......
......@@ -3164,6 +3164,14 @@ void Isolate::InvalidateIsConcatSpreadableProtector() {
DCHECK(!IsIsConcatSpreadableLookupChainIntact());
}
void Isolate::InvalidateArrayConstructorProtector() {
DCHECK(factory()->array_constructor_protector()->value()->IsSmi());
DCHECK(IsArrayConstructorIntact());
factory()->array_constructor_protector()->set_value(
Smi::FromInt(kProtectorInvalid));
DCHECK(!IsArrayConstructorIntact());
}
void Isolate::InvalidateArraySpeciesProtector() {
DCHECK(factory()->species_protector()->value()->IsSmi());
DCHECK(IsArraySpeciesLookupChainIntact());
......
......@@ -1069,6 +1069,7 @@ class Isolate {
static const int kProtectorValid = 1;
static const int kProtectorInvalid = 0;
inline bool IsArrayConstructorIntact();
bool IsFastArrayConstructorPrototypeChainIntact();
inline bool IsArraySpeciesLookupChainIntact();
bool IsIsConcatSpreadableLookupChainIntact();
......@@ -1096,6 +1097,7 @@ class Isolate {
void UpdateArrayProtectorOnNormalizeElements(Handle<JSObject> object) {
UpdateArrayProtectorOnSetElement(object);
}
void InvalidateArrayConstructorProtector();
void InvalidateArraySpeciesProtector();
void InvalidateIsConcatSpreadableProtector();
void InvalidateStringLengthOverflowProtector();
......
......@@ -460,13 +460,25 @@ RUNTIME_FUNCTION(Runtime_NewArray) {
ElementsKind old_kind = array->GetElementsKind();
RETURN_FAILURE_ON_EXCEPTION(isolate,
ArrayConstructInitializeElements(array, &argv));
if (!site.is_null() &&
(old_kind != array->GetElementsKind() || !can_use_type_feedback ||
!can_inline_array_constructor)) {
// The arguments passed in caused a transition. This kind of complexity
// can't be dealt with in the inlined hydrogen array constructor case.
// We must mark the allocationsite as un-inlinable.
site->SetDoNotInlineCall();
if (!site.is_null()) {
if ((old_kind != array->GetElementsKind() || !can_use_type_feedback ||
!can_inline_array_constructor)) {
// The arguments passed in caused a transition. This kind of complexity
// can't be dealt with in the inlined hydrogen array constructor case.
// We must mark the allocationsite as un-inlinable.
site->SetDoNotInlineCall();
}
} else {
if (old_kind != array->GetElementsKind() || !can_inline_array_constructor) {
// We don't have an AllocationSite for this Array constructor invocation,
// i.e. it might a call from Array#map or from an Array subclass, so we
// just flip the bit on the global protector cell instead.
// TODO(bmeurer): Find a better way to mark this. Global protectors
// tend to back-fire over time...
if (isolate->IsArrayConstructorIntact()) {
isolate->InvalidateArrayConstructorProtector();
}
}
}
return *array;
......
......@@ -106,14 +106,9 @@ function assertKind(expected, obj, name_opt) {
a = bar(10);
assertKind(elements_kind.fast, a);
assertOptimized(bar);
bar(100000);
bar(10000);
assertOptimized(bar);
// If the argument isn't a smi, things should still work.
a = bar("oops");
assertOptimized(bar);
assertKind(elements_kind.fast, a);
function barn(one, two, three) {
return new Array(one, two, three);
}
......
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