Chromium Code Reviews| Index: src/compiler/js-native-context-specialization.cc |
| diff --git a/src/compiler/js-native-context-specialization.cc b/src/compiler/js-native-context-specialization.cc |
| index 1bebb4cce128df7b1de57fbc715b114568dfd9e1..78453a3ff864555f6efb2453a2b4580793ae58dc 100644 |
| --- a/src/compiler/js-native-context-specialization.cc |
| +++ b/src/compiler/js-native-context-specialization.cc |
| @@ -248,18 +248,26 @@ Reduction JSNativeContextSpecialization::ReduceJSStoreGlobal(Node* node) { |
| // object property, either on the object itself or on the prototype chain. |
| class JSNativeContextSpecialization::PropertyAccessInfo final { |
| public: |
| - enum Kind { kInvalid, kDataConstant, kDataField }; |
| + enum Kind { kInvalid, kDataConstant, kDataField, kTransitionToField }; |
| static PropertyAccessInfo DataConstant(Type* receiver_type, |
| Handle<Object> constant, |
| MaybeHandle<JSObject> holder) { |
| return PropertyAccessInfo(holder, constant, receiver_type); |
| } |
| - static PropertyAccessInfo DataField(Type* receiver_type, |
| - FieldIndex field_index, Type* field_type, |
| - MaybeHandle<JSObject> holder) { |
| + static PropertyAccessInfo DataField( |
| + Type* receiver_type, FieldIndex field_index, Type* field_type, |
| + MaybeHandle<JSObject> holder = MaybeHandle<JSObject>()) { |
| return PropertyAccessInfo(holder, field_index, field_type, receiver_type); |
| } |
| + static PropertyAccessInfo TransitionToField(Type* receiver_type, |
| + FieldIndex field_index, |
| + Type* field_type, |
| + Handle<Map> transition_map, |
| + MaybeHandle<JSObject> holder) { |
| + return PropertyAccessInfo(holder, transition_map, field_index, field_type, |
| + receiver_type); |
| + } |
| PropertyAccessInfo() : kind_(kInvalid) {} |
| PropertyAccessInfo(MaybeHandle<JSObject> holder, Handle<Object> constant, |
| @@ -275,13 +283,24 @@ class JSNativeContextSpecialization::PropertyAccessInfo final { |
| holder_(holder), |
| field_index_(field_index), |
| field_type_(field_type) {} |
| + PropertyAccessInfo(MaybeHandle<JSObject> holder, Handle<Map> transition_map, |
| + FieldIndex field_index, Type* field_type, |
| + Type* receiver_type) |
| + : kind_(kTransitionToField), |
| + receiver_type_(receiver_type), |
| + transition_map_(transition_map), |
| + holder_(holder), |
| + field_index_(field_index), |
| + field_type_(field_type) {} |
| bool IsDataConstant() const { return kind() == kDataConstant; } |
| bool IsDataField() const { return kind() == kDataField; } |
| + bool IsTransitionToField() const { return kind() == kTransitionToField; } |
| Kind kind() const { return kind_; } |
| MaybeHandle<JSObject> holder() const { return holder_; } |
| Handle<Object> constant() const { return constant_; } |
| + Handle<Object> transition_map() const { return transition_map_; } |
| FieldIndex field_index() const { return field_index_; } |
| Type* field_type() const { return field_type_; } |
| Type* receiver_type() const { return receiver_type_; } |
| @@ -290,6 +309,7 @@ class JSNativeContextSpecialization::PropertyAccessInfo final { |
| Kind kind_; |
| Type* receiver_type_; |
| Handle<Object> constant_; |
| + Handle<Map> transition_map_; |
| MaybeHandle<JSObject> holder_; |
| FieldIndex field_index_; |
| Type* field_type_ = Type::Any(); |
| @@ -314,17 +334,18 @@ bool CanInlinePropertyAccess(Handle<Map> map) { |
| bool JSNativeContextSpecialization::ComputePropertyAccessInfo( |
| Handle<Map> map, Handle<Name> name, PropertyAccessMode access_mode, |
| PropertyAccessInfo* access_info) { |
| - MaybeHandle<JSObject> holder; |
| + // Check if it is safe to inline property access for the {map}. |
| + if (!CanInlinePropertyAccess(map)) return false; |
| + |
| + // Compute the receiver type. |
| Handle<Map> receiver_map = map; |
| Type* receiver_type = Type::Class(receiver_map, graph()->zone()); |
| - while (CanInlinePropertyAccess(map)) { |
| + |
| + // We support fast inline cases for certain JSObject getters. |
| + if (access_mode == kLoad) { |
| // Check for special JSObject field accessors. |
| int offset; |
| if (Accessors::IsJSObjectFieldAccessor(map, name, &offset)) { |
| - // Don't bother optimizing stores to special JSObject field accessors. |
| - if (access_mode == kStore) { |
| - break; |
| - } |
| FieldIndex field_index = FieldIndex::ForInObjectOffset(offset); |
| Type* field_type = Type::Tagged(); |
| if (map->IsStringMap()) { |
| @@ -347,29 +368,38 @@ bool JSNativeContextSpecialization::ComputePropertyAccessInfo( |
| field_type = type_cache_.kJSArrayLengthType; |
| } |
| } |
| - *access_info = PropertyAccessInfo::DataField(receiver_type, field_index, |
| - field_type, holder); |
| + *access_info = |
| + PropertyAccessInfo::DataField(receiver_type, field_index, field_type); |
| return true; |
| } |
| + } |
| + MaybeHandle<JSObject> holder; |
| + while (true) { |
| // Lookup the named property on the {map}. |
| Handle<DescriptorArray> descriptors(map->instance_descriptors(), isolate()); |
| int const number = descriptors->SearchWithCache(*name, *map); |
| if (number != DescriptorArray::kNotFound) { |
| - if (access_mode == kStore && !map.is_identical_to(receiver_map)) { |
| - return false; |
| - } |
| PropertyDetails const details = descriptors->GetDetails(number); |
| + if (access_mode == kStore) { |
| + // Don't bother optimizing stores to read-only properties. |
| + if (details.IsReadOnly()) { |
| + return false; |
| + } |
| + // Check for store to data property on a prototype. |
| + if (details.kind() == kData && !holder.is_null()) { |
| + // We need to add the data field to the receiver. Leave the loop |
| + // and check whether we already have a transition for this field. |
| + // Implemented according to ES6 section 9.1.9 [[Set]] (P, V, Receiver) |
| + break; |
| + } |
| + } |
| if (details.type() == DATA_CONSTANT) { |
| *access_info = PropertyAccessInfo::DataConstant( |
| receiver_type, handle(descriptors->GetValue(number), isolate()), |
| holder); |
| return true; |
| } else if (details.type() == DATA) { |
| - // Don't bother optimizing stores to read-only properties. |
| - if (access_mode == kStore && details.IsReadOnly()) { |
| - break; |
| - } |
| int index = descriptors->GetFieldIndex(number); |
| Representation field_representation = details.representation(); |
| FieldIndex field_index = FieldIndex::ForPropertyIndex( |
| @@ -380,7 +410,7 @@ bool JSNativeContextSpecialization::ComputePropertyAccessInfo( |
| } else if (field_representation.IsDouble()) { |
| if (access_mode == kStore) { |
| // TODO(bmeurer): Add support for storing to double fields. |
| - break; |
| + return false; |
| } |
| field_type = type_cache_.kFloat64; |
| } else if (field_representation.IsHeapObject()) { |
| @@ -392,10 +422,8 @@ bool JSNativeContextSpecialization::ComputePropertyAccessInfo( |
| graph()->zone()), |
| Type::TaggedPointer(), graph()->zone()); |
| if (field_type->Is(Type::None())) { |
| - if (access_mode == kStore) { |
| - // Store is not safe if the field type was cleared. |
| - break; |
| - } |
| + // Store is not safe if the field type was cleared. |
| + if (access_mode == kStore) return false; |
| // The field type was cleared by the GC, so we don't know anything |
| // about the contents now. |
| @@ -414,7 +442,7 @@ bool JSNativeContextSpecialization::ComputePropertyAccessInfo( |
| return true; |
| } else { |
| // TODO(bmeurer): Add support for accessors. |
| - break; |
| + return false; |
| } |
| } |
| @@ -422,7 +450,7 @@ bool JSNativeContextSpecialization::ComputePropertyAccessInfo( |
| // integer indexed exotic objects (see ES6 section 9.4.5). |
| if (map->IsJSTypedArrayMap() && name->IsString() && |
| IsSpecialIndex(isolate()->unicode_cache(), String::cast(*name))) { |
| - break; |
| + return false; |
| } |
| // Walk up the prototype chain. |
| @@ -434,9 +462,17 @@ bool JSNativeContextSpecialization::ComputePropertyAccessInfo( |
| .ToHandle(&constructor)) { |
| map = handle(constructor->initial_map(), isolate()); |
| DCHECK(map->prototype()->IsJSObject()); |
| - } else { |
| + } else if (map->prototype()->IsNull()) { |
| + // Store to property not found on the receiver or any prototype, we need |
| + // to transition to a new data property. |
| + // Implemented according to ES6 section 9.1.9 [[Set]] (P, V, Receiver) |
| + if (access_mode == kStore) { |
| + break; |
| + } |
| // TODO(bmeurer): Handle the not found case if the prototype is null. |
| - break; |
| + return false; |
| + } else { |
| + return false; |
| } |
| } |
| Handle<JSObject> map_prototype(JSObject::cast(map->prototype()), isolate()); |
| @@ -447,6 +483,59 @@ bool JSNativeContextSpecialization::ComputePropertyAccessInfo( |
| } |
| map = handle(map_prototype->map(), isolate()); |
| holder = map_prototype; |
| + |
| + // Check if it is safe to inline property access for the {map}. |
| + if (!CanInlinePropertyAccess(map)) return false; |
| + } |
| + DCHECK_EQ(kStore, access_mode); |
| + |
| + // Check if the {receiver_map} has a data transition with the given {name}. |
|
Jarin
2015/10/29 09:02:33
The structure of this code is strange. This whole
Benedikt Meurer
2015/10/29 09:03:47
Yes, but currently we have two "break" points. I w
|
| + if (receiver_map->unused_property_fields() == 0) return false; |
| + if (Map* transition = TransitionArray::SearchTransition(*receiver_map, kData, |
| + *name, NONE)) { |
| + Handle<Map> transition_map(transition, isolate()); |
| + int const number = transition_map->LastAdded(); |
| + PropertyDetails const details = |
| + transition_map->instance_descriptors()->GetDetails(number); |
| + // Don't bother optimizing stores to read-only properties. |
| + if (details.IsReadOnly()) return false; |
| + // TODO(bmeurer): Handle transition to data constant? |
| + if (details.type() != DATA) return false; |
| + int const index = details.field_index(); |
| + Representation field_representation = details.representation(); |
| + FieldIndex field_index = FieldIndex::ForPropertyIndex( |
| + *transition_map, index, field_representation.IsDouble()); |
| + Type* field_type = Type::Tagged(); |
| + if (field_representation.IsSmi()) { |
| + field_type = type_cache_.kSmi; |
| + } else if (field_representation.IsDouble()) { |
| + // TODO(bmeurer): Add support for storing to double fields. |
| + return false; |
| + } else if (field_representation.IsHeapObject()) { |
| + // Extract the field type from the property details (make sure its |
| + // representation is TaggedPointer to reflect the heap object case). |
| + field_type = Type::Intersect( |
| + Type::Convert<HeapType>( |
| + handle( |
| + transition_map->instance_descriptors()->GetFieldType(number), |
| + isolate()), |
| + graph()->zone()), |
| + Type::TaggedPointer(), graph()->zone()); |
| + if (field_type->Is(Type::None())) { |
| + // Store is not safe if the field type was cleared. |
| + return false; |
| + } else if (!Type::Any()->Is(field_type)) { |
| + // Add proper code dependencies in case of stable field map(s). |
| + Handle<Map> field_owner_map(transition_map->FindFieldOwner(number), |
| + isolate()); |
| + dependencies()->AssumeFieldType(field_owner_map); |
| + } |
| + DCHECK(field_type->Is(Type::TaggedPointer())); |
| + } |
| + dependencies()->AssumeMapNotDeprecated(transition_map); |
| + *access_info = PropertyAccessInfo::TransitionToField( |
| + receiver_type, field_index, field_type, transition_map, holder); |
| + return true; |
| } |
| return false; |
| } |
| @@ -564,9 +653,6 @@ Reduction JSNativeContextSpecialization::ReduceNamedAccess( |
| Handle<JSObject> holder; |
| if (access_info.holder().ToHandle(&holder)) { |
| AssumePrototypesStable(receiver_type, holder); |
| - if (access_mode == kLoad) { |
| - this_receiver = jsgraph()->Constant(holder); |
| - } |
| } |
| // Generate the actual property access. |
| @@ -581,13 +667,17 @@ Reduction JSNativeContextSpecialization::ReduceNamedAccess( |
| this_control = graph()->NewNode(common()->IfTrue(), branch); |
| } |
| } else { |
| - DCHECK(access_info.IsDataField()); |
| + DCHECK(access_info.IsDataField() || access_info.IsTransitionToField()); |
| FieldIndex const field_index = access_info.field_index(); |
| Type* const field_type = access_info.field_type(); |
| + if (access_mode == kLoad && access_info.holder().ToHandle(&holder)) { |
| + this_receiver = jsgraph()->Constant(holder); |
| + } |
| + Node* this_storage = this_receiver; |
| if (!field_index.is_inobject()) { |
| - this_receiver = this_effect = graph()->NewNode( |
| + this_storage = this_effect = graph()->NewNode( |
| simplified()->LoadField(AccessBuilder::ForJSObjectProperties()), |
| - this_receiver, this_effect, this_control); |
| + this_storage, this_effect, this_control); |
| } |
| FieldAccess field_access = {kTaggedBase, field_index.offset(), name, |
| field_type, kMachAnyTagged}; |
| @@ -595,9 +685,9 @@ Reduction JSNativeContextSpecialization::ReduceNamedAccess( |
| if (field_type->Is(Type::UntaggedFloat64())) { |
| if (!field_index.is_inobject() || field_index.is_hidden_field() || |
| !FLAG_unbox_double_fields) { |
| - this_receiver = this_effect = |
| + this_storage = this_effect = |
| graph()->NewNode(simplified()->LoadField(field_access), |
| - this_receiver, this_effect, this_control); |
| + this_storage, this_effect, this_control); |
| field_access.offset = HeapNumber::kValueOffset; |
| field_access.name = MaybeHandle<Name>(); |
| } |
| @@ -605,7 +695,7 @@ Reduction JSNativeContextSpecialization::ReduceNamedAccess( |
| } |
| this_value = this_effect = |
| graph()->NewNode(simplified()->LoadField(field_access), |
| - this_receiver, this_effect, this_control); |
| + this_storage, this_effect, this_control); |
| } else { |
| DCHECK_EQ(kStore, access_mode); |
| if (field_type->Is(Type::TaggedSigned())) { |
| @@ -651,9 +741,21 @@ Reduction JSNativeContextSpecialization::ReduceNamedAccess( |
| } else { |
| DCHECK(field_type->Is(Type::Tagged())); |
| } |
| + if (access_info.IsTransitionToField()) { |
| + this_effect = graph()->NewNode(common()->BeginRegion(), this_effect); |
| + this_effect = graph()->NewNode( |
| + simplified()->StoreField(AccessBuilder::ForMap()), this_receiver, |
| + jsgraph()->Constant(access_info.transition_map()), this_effect, |
| + this_control); |
| + } |
| this_effect = graph()->NewNode(simplified()->StoreField(field_access), |
| - this_receiver, this_value, this_effect, |
| + this_storage, this_value, this_effect, |
| this_control); |
| + if (access_info.IsTransitionToField()) { |
| + this_effect = |
| + graph()->NewNode(common()->FinishRegion(), |
| + jsgraph()->UndefinedConstant(), this_effect); |
| + } |
| } |
| } |