Index: src/compiler/js-create-lowering.cc |
diff --git a/src/compiler/js-create-lowering.cc b/src/compiler/js-create-lowering.cc |
index 27c7604c45910f041ab948ae141eb580fc16960f..b2445455b080ac54624233bfac9e786051893344 100644 |
--- a/src/compiler/js-create-lowering.cc |
+++ b/src/compiler/js-create-lowering.cc |
@@ -4,6 +4,7 @@ |
#include "src/compiler/js-create-lowering.h" |
+#include "src/allocation-site-scopes.h" |
#include "src/code-factory.h" |
#include "src/compilation-dependencies.h" |
#include "src/compiler/access-builder.h" |
@@ -13,6 +14,7 @@ |
#include "src/compiler/linkage.h" |
#include "src/compiler/node.h" |
#include "src/compiler/node-properties.h" |
+#include "src/compiler/operator-properties.h" |
#include "src/compiler/simplified-operator.h" |
#include "src/compiler/state-values-utils.h" |
@@ -127,6 +129,73 @@ const int kElementLoopUnrollLimit = 16; |
const int kFunctionContextAllocationLimit = 16; |
const int kBlockContextAllocationLimit = 16; |
+// 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. |
+bool IsFastLiteral(Handle<JSObject> boilerplate, int max_depth, |
+ int* max_properties) { |
+ DCHECK_GE(max_depth, 0); |
+ DCHECK_GE(*max_properties, 0); |
+ |
+ // Make sure the boilerplate map is not deprecated. |
+ if (!JSObject::TryMigrateInstance(boilerplate)) return false; |
+ |
+ // Check for too deep nesting. |
+ if (max_depth == 0) return false; |
+ |
+ // Check the elements. |
+ Isolate* const isolate = boilerplate->GetIsolate(); |
+ Handle<FixedArrayBase> elements(boilerplate->elements(), isolate); |
+ if (elements->length() > 0 && |
+ elements->map() != isolate->heap()->fixed_cow_array_map()) { |
+ if (boilerplate->HasFastSmiOrObjectElements()) { |
+ Handle<FixedArray> fast_elements = Handle<FixedArray>::cast(elements); |
+ int length = elements->length(); |
+ for (int i = 0; i < length; i++) { |
+ if ((*max_properties)-- == 0) return false; |
+ Handle<Object> value(fast_elements->get(i), isolate); |
+ if (value->IsJSObject()) { |
+ Handle<JSObject> value_object = Handle<JSObject>::cast(value); |
+ if (!IsFastLiteral(value_object, max_depth - 1, max_properties)) { |
+ return false; |
+ } |
+ } |
+ } |
+ } else if (!boilerplate->HasFastDoubleElements()) { |
+ return false; |
+ } |
+ } |
+ |
+ // TODO(turbofan): Do we want to support out-of-object properties? |
+ Handle<FixedArray> properties(boilerplate->properties(), isolate); |
+ if (properties->length() > 0) return false; |
+ |
+ // Check the in-object properties. |
+ Handle<DescriptorArray> descriptors( |
+ boilerplate->map()->instance_descriptors(), isolate); |
+ int limit = boilerplate->map()->NumberOfOwnDescriptors(); |
+ for (int i = 0; i < limit; i++) { |
+ PropertyDetails details = descriptors->GetDetails(i); |
+ if (details.type() != DATA) continue; |
+ if ((*max_properties)-- == 0) return false; |
+ FieldIndex field_index = FieldIndex::ForDescriptor(boilerplate->map(), i); |
+ if (boilerplate->IsUnboxedDoubleField(field_index)) continue; |
+ Handle<Object> value(boilerplate->RawFastPropertyAt(field_index), isolate); |
+ if (value->IsJSObject()) { |
+ Handle<JSObject> value_object = Handle<JSObject>::cast(value); |
+ if (!IsFastLiteral(value_object, max_depth - 1, max_properties)) { |
+ return false; |
+ } |
+ } |
+ } |
+ return true; |
+} |
+ |
+// Maximum depth and total number of elements and properties for literal |
+// graphs to be considered for fast deep-copying. |
+const int kMaxFastLiteralDepth = 3; |
+const int kMaxFastLiteralProperties = 8; |
+ |
} // namespace |
Reduction JSCreateLowering::Reduce(Node* node) { |
@@ -139,6 +208,9 @@ Reduction JSCreateLowering::Reduce(Node* node) { |
return ReduceJSCreateArray(node); |
case IrOpcode::kJSCreateIterResultObject: |
return ReduceJSCreateIterResultObject(node); |
+ case IrOpcode::kJSCreateLiteralArray: |
+ case IrOpcode::kJSCreateLiteralObject: |
+ return ReduceJSCreateLiteral(node); |
case IrOpcode::kJSCreateFunctionContext: |
return ReduceJSCreateFunctionContext(node); |
case IrOpcode::kJSCreateWithContext: |
@@ -504,6 +576,36 @@ Reduction JSCreateLowering::ReduceJSCreateIterResultObject(Node* node) { |
return Changed(node); |
} |
+Reduction JSCreateLowering::ReduceJSCreateLiteral(Node* node) { |
+ DCHECK(node->opcode() == IrOpcode::kJSCreateLiteralArray || |
+ node->opcode() == IrOpcode::kJSCreateLiteralObject); |
+ CreateLiteralParameters const& p = CreateLiteralParametersOf(node->op()); |
+ Node* effect = NodeProperties::GetEffectInput(node); |
+ Node* control = NodeProperties::GetControlInput(node); |
+ |
+ Handle<LiteralsArray> literals_array; |
+ if (GetSpecializationLiterals(node).ToHandle(&literals_array)) { |
+ Handle<Object> literal(literals_array->literal(p.index()), isolate()); |
+ if (literal->IsAllocationSite()) { |
+ Handle<AllocationSite> site = Handle<AllocationSite>::cast(literal); |
+ Handle<JSObject> boilerplate(JSObject::cast(site->transition_info()), |
+ isolate()); |
+ int max_properties = kMaxFastLiteralProperties; |
+ if (IsFastLiteral(boilerplate, kMaxFastLiteralDepth, &max_properties)) { |
+ AllocationSiteUsageContext site_context(isolate(), site, false); |
+ site_context.EnterNewScope(); |
+ Node* value = effect = |
+ AllocateFastLiteral(effect, control, boilerplate, &site_context); |
+ site_context.ExitScope(site, boilerplate); |
+ ReplaceWithValue(node, value, effect, control); |
+ return Replace(value); |
+ } |
+ } |
+ } |
+ |
+ return NoChange(); |
+} |
+ |
Reduction JSCreateLowering::ReduceJSCreateFunctionContext(Node* node) { |
DCHECK_EQ(IrOpcode::kJSCreateFunctionContext, node->opcode()); |
int slot_count = OpParameter<int>(node->op()); |
@@ -754,6 +856,225 @@ Node* JSCreateLowering::AllocateElements(Node* effect, Node* control, |
return a.Finish(); |
} |
+Node* JSCreateLowering::AllocateFastLiteral( |
+ Node* effect, Node* control, Handle<JSObject> boilerplate, |
+ AllocationSiteUsageContext* site_context) { |
+ Handle<AllocationSite> current_site(*site_context->current(), isolate()); |
+ dependencies()->AssumeTransitionStable(current_site); |
+ |
+ PretenureFlag pretenure = NOT_TENURED; |
+ if (FLAG_allocation_site_pretenuring) { |
+ Handle<AllocationSite> top_site(*site_context->top(), isolate()); |
+ pretenure = top_site->GetPretenureMode(); |
+ if (current_site.is_identical_to(top_site)) { |
+ // We install a dependency for pretenuring only on the outermost literal. |
+ dependencies()->AssumeTenuringDecision(top_site); |
+ } |
+ } |
+ |
+ // Setup the properties backing store. |
+ Node* properties = jsgraph()->EmptyFixedArrayConstant(); |
+ |
+ // Setup the elements backing store. |
+ Node* elements = AllocateFastLiteralElements(effect, control, boilerplate, |
+ pretenure, site_context); |
+ if (elements->op()->EffectOutputCount() > 0) effect = elements; |
+ |
+ // Compute the in-object properties to store first (might have effects). |
+ Handle<Map> boilerplate_map(boilerplate->map(), isolate()); |
+ ZoneVector<std::pair<FieldAccess, Node*>> inobject_fields(zone()); |
+ inobject_fields.reserve(boilerplate_map->GetInObjectProperties()); |
+ int const boilerplate_nof = boilerplate_map->NumberOfOwnDescriptors(); |
+ for (int i = 0; i < boilerplate_nof; ++i) { |
+ PropertyDetails const property_details = |
+ boilerplate_map->instance_descriptors()->GetDetails(i); |
+ if (property_details.type() != DATA) continue; |
+ Handle<Name> property_name( |
+ boilerplate_map->instance_descriptors()->GetKey(i), isolate()); |
+ FieldIndex index = FieldIndex::ForDescriptor(*boilerplate_map, i); |
+ FieldAccess access = {kTaggedBase, index.offset(), property_name, |
+ Type::Tagged(), MachineType::AnyTagged()}; |
+ Node* value; |
+ if (boilerplate->IsUnboxedDoubleField(index)) { |
+ access.machine_type = MachineType::Float64(); |
+ access.type = Type::Number(); |
+ value = jsgraph()->Constant(boilerplate->RawFastDoublePropertyAt(index)); |
+ } else { |
+ Handle<Object> boilerplate_value(boilerplate->RawFastPropertyAt(index), |
+ isolate()); |
+ if (boilerplate_value->IsJSObject()) { |
+ Handle<JSObject> boilerplate_object = |
+ Handle<JSObject>::cast(boilerplate_value); |
+ Handle<AllocationSite> current_site = site_context->EnterNewScope(); |
+ value = effect = AllocateFastLiteral(effect, control, |
+ boilerplate_object, site_context); |
+ site_context->ExitScope(current_site, boilerplate_object); |
+ } else if (property_details.representation().IsDouble()) { |
+ // Allocate a mutable HeapNumber box and store the value into it. |
+ value = effect = AllocateMutableHeapNumber( |
+ Handle<HeapNumber>::cast(boilerplate_value)->value(), |
+ effect, control); |
+ } else if (property_details.representation().IsSmi()) { |
+ // Ensure that value is stored as smi. |
+ value = boilerplate_value->IsUninitialized() |
+ ? jsgraph()->ZeroConstant() |
+ : jsgraph()->Constant(boilerplate_value); |
+ } else { |
+ value = jsgraph()->Constant(boilerplate_value); |
+ } |
+ } |
+ inobject_fields.push_back(std::make_pair(access, value)); |
+ } |
+ |
+ // Fill slack at the end of the boilerplate object with filler maps. |
+ int const boilerplate_length = boilerplate_map->GetInObjectProperties(); |
+ for (int index = static_cast<int>(inobject_fields.size()); |
+ index < boilerplate_length; ++index) { |
+ FieldAccess access = |
+ AccessBuilder::ForJSObjectInObjectProperty(boilerplate_map, index); |
+ Node* value = jsgraph()->HeapConstant(factory()->one_pointer_filler_map()); |
+ inobject_fields.push_back(std::make_pair(access, value)); |
+ } |
+ |
+ // Actually allocate and initialize the object. |
+ AllocationBuilder builder(jsgraph(), effect, control); |
+ builder.Allocate(boilerplate_map->instance_size(), pretenure); |
+ builder.Store(AccessBuilder::ForMap(), boilerplate_map); |
+ builder.Store(AccessBuilder::ForJSObjectProperties(), properties); |
+ builder.Store(AccessBuilder::ForJSObjectElements(), elements); |
+ if (boilerplate_map->IsJSArrayMap()) { |
+ Handle<JSArray> boilerplate_array = Handle<JSArray>::cast(boilerplate); |
+ builder.Store( |
+ AccessBuilder::ForJSArrayLength(boilerplate_array->GetElementsKind()), |
+ handle(boilerplate_array->length(), isolate())); |
+ } |
+ for (auto const inobject_field : inobject_fields) { |
+ builder.Store(inobject_field.first, inobject_field.second); |
+ } |
+ return builder.Finish(); |
+} |
+ |
+Node* JSCreateLowering::AllocateFastLiteralElements( |
+ Node* effect, Node* control, Handle<JSObject> boilerplate, |
+ PretenureFlag pretenure, AllocationSiteUsageContext* site_context) { |
+ Handle<FixedArrayBase> boilerplate_elements(boilerplate->elements(), |
+ isolate()); |
+ |
+ // Empty or copy-on-write elements just store a constant. |
+ if (boilerplate_elements->length() == 0 || |
+ boilerplate_elements->map() == isolate()->heap()->fixed_cow_array_map()) { |
+ if (pretenure == TENURED && |
+ isolate()->heap()->InNewSpace(*boilerplate_elements)) { |
+ // If we would like to pretenure a fixed cow array, we must ensure that |
+ // the array is already in old space, otherwise we'll create too many |
+ // old-to-new-space pointers (overflowing the store buffer). |
+ boilerplate_elements = Handle<FixedArrayBase>( |
+ isolate()->factory()->CopyAndTenureFixedCOWArray( |
+ Handle<FixedArray>::cast(boilerplate_elements))); |
+ boilerplate->set_elements(*boilerplate_elements); |
+ } |
+ return jsgraph()->HeapConstant(boilerplate_elements); |
+ } |
+ |
+ // Compute the elements to store first (might have effects). |
+ int const elements_length = boilerplate_elements->length(); |
+ Handle<Map> elements_map(boilerplate_elements->map(), isolate()); |
+ ZoneVector<Node*> elements_values(elements_length, zone()); |
+ if (elements_map->instance_type() == FIXED_DOUBLE_ARRAY_TYPE) { |
+ Handle<FixedDoubleArray> elements = |
+ Handle<FixedDoubleArray>::cast(boilerplate_elements); |
+ for (int i = 0; i < elements_length; ++i) { |
+ if (elements->is_the_hole(i)) { |
+ // TODO(turbofan): We cannot currently safely pass thru the (signaling) |
+ // hole NaN in C++ code, as the C++ compiler on Intel might use FPU |
+ // instructions/registers for doubles and therefore make the NaN quiet. |
+ // We should consider passing doubles in the compiler as raw int64 |
+ // values to prevent this. |
+ elements_values[i] = effect = |
+ graph()->NewNode(simplified()->LoadElement( |
+ AccessBuilder::ForFixedDoubleArrayElement()), |
+ jsgraph()->HeapConstant(elements), |
+ jsgraph()->Constant(i), effect, control); |
+ } else { |
+ elements_values[i] = jsgraph()->Constant(elements->get_scalar(i)); |
+ } |
+ } |
+ } else { |
+ Handle<FixedArray> elements = |
+ Handle<FixedArray>::cast(boilerplate_elements); |
+ for (int i = 0; i < elements_length; ++i) { |
+ if (elements->is_the_hole(i)) { |
+ elements_values[i] = jsgraph()->TheHoleConstant(); |
+ } else { |
+ Handle<Object> element_value(elements->get(i), isolate()); |
+ if (element_value->IsJSObject()) { |
+ Handle<JSObject> boilerplate_object = |
+ Handle<JSObject>::cast(element_value); |
+ Handle<AllocationSite> current_site = site_context->EnterNewScope(); |
+ elements_values[i] = effect = AllocateFastLiteral( |
+ effect, control, boilerplate_object, site_context); |
+ site_context->ExitScope(current_site, boilerplate_object); |
+ } else { |
+ elements_values[i] = jsgraph()->Constant(element_value); |
+ } |
+ } |
+ } |
+ } |
+ |
+ // Allocate the backing store array and store the elements. |
+ AllocationBuilder builder(jsgraph(), effect, control); |
+ builder.AllocateArray(elements_length, elements_map, pretenure); |
+ ElementAccess const access = |
+ (elements_map->instance_type() == FIXED_DOUBLE_ARRAY_TYPE) |
+ ? AccessBuilder::ForFixedDoubleArrayElement() |
+ : AccessBuilder::ForFixedArrayElement(); |
+ for (int i = 0; i < elements_length; ++i) { |
+ builder.Store(access, jsgraph()->Constant(i), elements_values[i]); |
+ } |
+ return builder.Finish(); |
+} |
+ |
+Node* JSCreateLowering::AllocateMutableHeapNumber(double value, Node* effect, |
+ Node* control) { |
+ // TODO(turbofan): Support inline allocation of MutableHeapNumber |
+ // (requires proper alignment on Allocate, and Begin/FinishRegion). |
+ Callable callable = CodeFactory::AllocateMutableHeapNumber(isolate()); |
+ CallDescriptor* desc = Linkage::GetStubCallDescriptor( |
+ isolate(), jsgraph()->zone(), callable.descriptor(), 0, |
+ CallDescriptor::kNoFlags, Operator::kNoThrow); |
+ Node* result = effect = graph()->NewNode( |
+ common()->Call(desc), jsgraph()->HeapConstant(callable.code()), |
+ jsgraph()->NoContextConstant(), effect, control); |
+ effect = graph()->NewNode( |
+ simplified()->StoreField(AccessBuilder::ForHeapNumberValue()), result, |
+ jsgraph()->Constant(value), effect, control); |
+ return result; |
+} |
+ |
+MaybeHandle<LiteralsArray> JSCreateLowering::GetSpecializationLiterals( |
+ Node* node) { |
+ Node* const closure = NodeProperties::GetValueInput(node, 0); |
+ switch (closure->opcode()) { |
+ case IrOpcode::kHeapConstant: { |
+ Handle<HeapObject> object = OpParameter<Handle<HeapObject>>(closure); |
+ return handle(Handle<JSFunction>::cast(object)->literals()); |
+ } |
+ case IrOpcode::kParameter: { |
+ int const index = ParameterIndexOf(closure->op()); |
+ // The closure is always the last parameter to a JavaScript function, and |
+ // {Parameter} indices start at -1, so value outputs of {Start} look like |
+ // this: closure, receiver, param0, ..., paramN, context. |
+ if (index == -1) { |
+ return literals_array_; |
+ } |
+ break; |
+ } |
+ default: |
+ break; |
+ } |
+ return MaybeHandle<LiteralsArray>(); |
+} |
+ |
Factory* JSCreateLowering::factory() const { return isolate()->factory(); } |
Graph* JSCreateLowering::graph() const { return jsgraph()->graph(); } |