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 b1b27def6a9278f53a31811ed8ca6e5b2ce672f3..9936252f45459318690e21112149d4112360c8cc 100644 |
--- a/src/compiler/js-native-context-specialization.cc |
+++ b/src/compiler/js-native-context-specialization.cc |
@@ -55,6 +55,12 @@ bool HasOnlyStringMaps(T const& maps) { |
} // namespace |
+struct JSNativeContextSpecialization::ScriptContextTableLookupResult { |
+ Handle<Context> context; |
+ bool immutable; |
+ int index; |
+}; |
+ |
JSNativeContextSpecialization::JSNativeContextSpecialization( |
Editor* editor, JSGraph* jsgraph, Flags flags, |
Handle<Context> native_context, CompilationDependencies* dependencies, |
@@ -62,6 +68,8 @@ JSNativeContextSpecialization::JSNativeContextSpecialization( |
: AdvancedReducer(editor), |
jsgraph_(jsgraph), |
flags_(flags), |
+ global_object_(native_context->global_object()), |
+ global_proxy_(JSGlobalProxy::cast(native_context->global_proxy())), |
native_context_(native_context), |
dependencies_(dependencies), |
zone_(zone), |
@@ -77,6 +85,10 @@ Reduction JSNativeContextSpecialization::Reduce(Node* node) { |
return ReduceJSOrdinaryHasInstance(node); |
case IrOpcode::kJSLoadContext: |
return ReduceJSLoadContext(node); |
+ case IrOpcode::kJSLoadGlobal: |
+ return ReduceJSLoadGlobal(node); |
+ case IrOpcode::kJSStoreGlobal: |
+ return ReduceJSStoreGlobal(node); |
case IrOpcode::kJSLoadNamed: |
return ReduceJSLoadNamed(node); |
case IrOpcode::kJSStoreNamed: |
@@ -255,6 +267,250 @@ Reduction JSNativeContextSpecialization::ReduceJSLoadContext(Node* node) { |
return NoChange(); |
} |
+namespace { |
+ |
+FieldAccess ForPropertyCellValue(MachineRepresentation representation, |
+ Type* type, MaybeHandle<Map> map, |
+ Handle<Name> name) { |
+ WriteBarrierKind kind = kFullWriteBarrier; |
+ if (representation == MachineRepresentation::kTaggedSigned) { |
+ kind = kNoWriteBarrier; |
+ } else if (representation == MachineRepresentation::kTaggedPointer) { |
+ kind = kPointerWriteBarrier; |
+ } |
+ MachineType r = MachineType::TypeForRepresentation(representation); |
+ FieldAccess access = { |
+ kTaggedBase, PropertyCell::kValueOffset, name, map, type, r, kind}; |
+ return access; |
+} |
+ |
+} // namespace |
+ |
+Reduction JSNativeContextSpecialization::ReduceGlobalAccess( |
+ Node* node, Node* receiver, Node* value, Handle<Name> name, |
+ AccessMode access_mode) { |
+ Node* effect = NodeProperties::GetEffectInput(node); |
+ Node* control = NodeProperties::GetControlInput(node); |
+ |
+ // Lookup on the global object. We only deal with own data properties |
+ // of the global object here (represented as PropertyCell). |
+ LookupIterator it(global_object(), name, LookupIterator::OWN); |
+ it.TryLookupCachedProperty(); |
+ if (it.state() != LookupIterator::DATA) return NoChange(); |
+ if (!it.GetHolder<JSObject>()->IsJSGlobalObject()) return NoChange(); |
+ Handle<PropertyCell> property_cell = it.GetPropertyCell(); |
+ PropertyDetails property_details = property_cell->property_details(); |
+ Handle<Object> property_cell_value(property_cell->value(), isolate()); |
+ PropertyCellType property_cell_type = property_details.cell_type(); |
+ |
+ // We have additional constraints for stores. |
+ if (access_mode == AccessMode::kStore) { |
+ if (property_details.IsReadOnly()) { |
+ // Don't even bother trying to lower stores to read-only data properties. |
+ return NoChange(); |
+ } else if (property_cell_type == PropertyCellType::kUndefined) { |
+ // There's no fast-path for dealing with undefined property cells. |
+ return NoChange(); |
+ } else if (property_cell_type == PropertyCellType::kConstantType) { |
+ // There's also no fast-path to store to a global cell which pretended |
+ // to be stable, but is no longer stable now. |
+ if (property_cell_value->IsHeapObject() && |
+ !Handle<HeapObject>::cast(property_cell_value)->map()->is_stable()) { |
+ return NoChange(); |
+ } |
+ } |
+ } |
+ |
+ // Check if we have a {receiver} to validate. If so, we need to check that |
+ // the {receiver} is actually the JSGlobalProxy for the native context that |
+ // we are specializing to. |
+ if (receiver != nullptr) { |
+ Node* check = graph()->NewNode(simplified()->ReferenceEqual(), receiver, |
+ jsgraph()->HeapConstant(global_proxy())); |
+ effect = graph()->NewNode(simplified()->CheckIf(), check, effect, control); |
+ } |
+ |
+ if (access_mode == AccessMode::kLoad) { |
+ // Load from non-configurable, read-only data property on the global |
+ // object can be constant-folded, even without deoptimization support. |
+ if (!property_details.IsConfigurable() && property_details.IsReadOnly()) { |
+ value = jsgraph()->Constant(property_cell_value); |
+ } else { |
+ // Record a code dependency on the cell if we can benefit from the |
+ // additional feedback, or the global property is configurable (i.e. |
+ // can be deleted or reconfigured to an accessor property). |
+ if (property_details.cell_type() != PropertyCellType::kMutable || |
+ property_details.IsConfigurable()) { |
+ dependencies()->AssumePropertyCell(property_cell); |
+ } |
+ |
+ // Load from constant/undefined global property can be constant-folded. |
+ if (property_details.cell_type() == PropertyCellType::kConstant || |
+ property_details.cell_type() == PropertyCellType::kUndefined) { |
+ value = jsgraph()->Constant(property_cell_value); |
+ } else { |
+ // Load from constant type cell can benefit from type feedback. |
+ MaybeHandle<Map> map; |
+ Type* property_cell_value_type = Type::NonInternal(); |
+ MachineRepresentation representation = MachineRepresentation::kTagged; |
+ if (property_details.cell_type() == PropertyCellType::kConstantType) { |
+ // Compute proper type based on the current value in the cell. |
+ if (property_cell_value->IsSmi()) { |
+ property_cell_value_type = Type::SignedSmall(); |
+ representation = MachineRepresentation::kTaggedSigned; |
+ } else if (property_cell_value->IsNumber()) { |
+ property_cell_value_type = Type::Number(); |
+ representation = MachineRepresentation::kTaggedPointer; |
+ } else { |
+ Handle<Map> property_cell_value_map( |
+ Handle<HeapObject>::cast(property_cell_value)->map(), |
+ isolate()); |
+ property_cell_value_type = Type::For(property_cell_value_map); |
+ representation = MachineRepresentation::kTaggedPointer; |
+ |
+ // We can only use the property cell value map for map check |
+ // elimination if it's stable, i.e. the HeapObject wasn't |
+ // mutated without the cell state being updated. |
+ if (property_cell_value_map->is_stable()) { |
+ dependencies()->AssumeMapStable(property_cell_value_map); |
+ map = property_cell_value_map; |
+ } |
+ } |
+ } |
+ value = effect = graph()->NewNode( |
+ simplified()->LoadField(ForPropertyCellValue( |
+ representation, property_cell_value_type, map, name)), |
+ jsgraph()->HeapConstant(property_cell), effect, control); |
+ } |
+ } |
+ } else { |
+ DCHECK_EQ(AccessMode::kStore, access_mode); |
+ DCHECK(!property_details.IsReadOnly()); |
+ switch (property_details.cell_type()) { |
+ case PropertyCellType::kUndefined: { |
+ UNREACHABLE(); |
+ break; |
+ } |
+ case PropertyCellType::kConstant: { |
+ // Record a code dependency on the cell, and just deoptimize if the new |
+ // value doesn't match the previous value stored inside the cell. |
+ dependencies()->AssumePropertyCell(property_cell); |
+ Node* check = |
+ graph()->NewNode(simplified()->ReferenceEqual(), value, |
+ jsgraph()->Constant(property_cell_value)); |
+ effect = |
+ graph()->NewNode(simplified()->CheckIf(), check, effect, control); |
+ break; |
+ } |
+ case PropertyCellType::kConstantType: { |
+ // Record a code dependency on the cell, and just deoptimize if the new |
+ // values' type doesn't match the type of the previous value in the |
+ // cell. |
+ dependencies()->AssumePropertyCell(property_cell); |
+ Type* property_cell_value_type; |
+ MachineRepresentation representation = MachineRepresentation::kTagged; |
+ if (property_cell_value->IsHeapObject()) { |
+ // We cannot do anything if the {property_cell_value}s map is no |
+ // longer stable. |
+ Handle<Map> property_cell_value_map( |
+ Handle<HeapObject>::cast(property_cell_value)->map(), isolate()); |
+ DCHECK(property_cell_value_map->is_stable()); |
+ dependencies()->AssumeMapStable(property_cell_value_map); |
+ |
+ // Check that the {value} is a HeapObject. |
+ value = effect = graph()->NewNode(simplified()->CheckHeapObject(), |
+ value, effect, control); |
+ |
+ // Check {value} map agains the {property_cell} map. |
+ effect = |
+ graph()->NewNode(simplified()->CheckMaps( |
+ CheckMapsFlag::kNone, |
+ ZoneHandleSet<Map>(property_cell_value_map)), |
+ value, effect, control); |
+ property_cell_value_type = Type::OtherInternal(); |
+ representation = MachineRepresentation::kTaggedPointer; |
+ } else { |
+ // Check that the {value} is a Smi. |
+ value = effect = graph()->NewNode(simplified()->CheckSmi(), value, |
+ effect, control); |
+ property_cell_value_type = Type::SignedSmall(); |
+ representation = MachineRepresentation::kTaggedSigned; |
+ } |
+ effect = graph()->NewNode(simplified()->StoreField(ForPropertyCellValue( |
+ representation, property_cell_value_type, |
+ MaybeHandle<Map>(), name)), |
+ jsgraph()->HeapConstant(property_cell), value, |
+ effect, control); |
+ break; |
+ } |
+ case PropertyCellType::kMutable: { |
+ // Record a code dependency on the cell, and just deoptimize if the |
+ // property ever becomes read-only. |
+ dependencies()->AssumePropertyCell(property_cell); |
+ effect = graph()->NewNode( |
+ simplified()->StoreField(ForPropertyCellValue( |
+ MachineRepresentation::kTagged, Type::NonInternal(), |
+ MaybeHandle<Map>(), name)), |
+ jsgraph()->HeapConstant(property_cell), value, effect, control); |
+ break; |
+ } |
+ } |
+ } |
+ |
+ ReplaceWithValue(node, value, effect, control); |
+ return Replace(value); |
+} |
+ |
+Reduction JSNativeContextSpecialization::ReduceJSLoadGlobal(Node* node) { |
+ DCHECK_EQ(IrOpcode::kJSLoadGlobal, node->opcode()); |
+ Handle<Name> name = LoadGlobalParametersOf(node->op()).name(); |
+ Node* effect = NodeProperties::GetEffectInput(node); |
+ |
+ // Try to lookup the name on the script context table first (lexical scoping). |
+ ScriptContextTableLookupResult result; |
+ if (LookupInScriptContextTable(name, &result)) { |
+ if (result.context->is_the_hole(isolate(), result.index)) return NoChange(); |
+ Node* context = jsgraph()->HeapConstant(result.context); |
+ Node* value = effect = graph()->NewNode( |
+ javascript()->LoadContext(0, result.index, result.immutable), context, |
+ effect); |
+ ReplaceWithValue(node, value, effect); |
+ return Replace(value); |
+ } |
+ |
+ // Not much we can do if deoptimization support is disabled. |
+ if (!(flags() & kDeoptimizationEnabled)) return NoChange(); |
+ |
+ // Lookup the {name} on the global object instead. |
+ return ReduceGlobalAccess(node, nullptr, nullptr, name, AccessMode::kLoad); |
+} |
+ |
+Reduction JSNativeContextSpecialization::ReduceJSStoreGlobal(Node* node) { |
+ DCHECK_EQ(IrOpcode::kJSStoreGlobal, node->opcode()); |
+ Handle<Name> name = StoreGlobalParametersOf(node->op()).name(); |
+ Node* value = NodeProperties::GetValueInput(node, 0); |
+ Node* effect = NodeProperties::GetEffectInput(node); |
+ Node* control = NodeProperties::GetControlInput(node); |
+ |
+ // Try to lookup the name on the script context table first (lexical scoping). |
+ ScriptContextTableLookupResult result; |
+ if (LookupInScriptContextTable(name, &result)) { |
+ if (result.context->is_the_hole(isolate(), result.index)) return NoChange(); |
+ if (result.immutable) return NoChange(); |
+ Node* context = jsgraph()->HeapConstant(result.context); |
+ effect = graph()->NewNode(javascript()->StoreContext(0, result.index), |
+ value, context, effect, control); |
+ ReplaceWithValue(node, value, effect, control); |
+ return Replace(value); |
+ } |
+ |
+ // Not much we can do if deoptimization support is disabled. |
+ if (!(flags() & kDeoptimizationEnabled)) return NoChange(); |
+ |
+ // Lookup the {name} on the global object instead. |
+ return ReduceGlobalAccess(node, nullptr, value, name, AccessMode::kStore); |
+} |
+ |
Reduction JSNativeContextSpecialization::ReduceNamedAccess( |
Node* node, Node* value, MapHandleList const& receiver_maps, |
Handle<Name> name, AccessMode access_mode, LanguageMode language_mode, |
@@ -273,6 +529,20 @@ Reduction JSNativeContextSpecialization::ReduceNamedAccess( |
// Not much we can do if deoptimization support is disabled. |
if (!(flags() & kDeoptimizationEnabled)) return NoChange(); |
+ // Check if we have an access o.x or o.x=v where o is the current |
+ // native contexts' global proxy, and turn that into a direct access |
+ // to the current native contexts' global object instead. |
+ if (receiver_maps.length() == 1) { |
+ Handle<Map> receiver_map = receiver_maps.first(); |
+ if (receiver_map->IsJSGlobalProxyMap()) { |
+ Context* receiver_context = |
+ JSFunction::cast(receiver_map->GetConstructor())->native_context(); |
+ if (receiver_context == *native_context()) { |
+ return ReduceGlobalAccess(node, receiver, value, name, access_mode); |
+ } |
+ } |
+ } |
+ |
// Compute property access infos for the receiver maps. |
AccessInfoFactory access_info_factory(dependencies(), native_context(), |
graph()->zone()); |
@@ -487,6 +757,13 @@ Reduction JSNativeContextSpecialization::ReduceNamedAccessFromNexus( |
Node* const receiver = NodeProperties::GetValueInput(node, 0); |
Node* const effect = NodeProperties::GetEffectInput(node); |
+ // Check if we are accessing the current native contexts' global proxy. |
+ HeapObjectMatcher m(receiver); |
+ if (m.HasValue() && m.Value().is_identical_to(global_proxy())) { |
+ // Optimize accesses to the current native contexts' global proxy. |
+ return ReduceGlobalAccess(node, nullptr, value, name, access_mode); |
+ } |
+ |
// Check if the {nexus} reports type feedback for the IC. |
if (nexus.IsUninitialized()) { |
if ((flags() & kDeoptimizationEnabled) && |
@@ -517,7 +794,6 @@ Reduction JSNativeContextSpecialization::ReduceNamedAccessFromNexus( |
language_mode, nexus.vector_handle(), nexus.slot()); |
} |
- |
Reduction JSNativeContextSpecialization::ReduceJSLoadNamed(Node* node) { |
DCHECK_EQ(IrOpcode::kJSLoadNamed, node->opcode()); |
NamedAccess const& p = NamedAccessOf(node->op()); |
@@ -1942,6 +2218,24 @@ MaybeHandle<Map> JSNativeContextSpecialization::InferReceiverRootMap( |
return MaybeHandle<Map>(); |
} |
+bool JSNativeContextSpecialization::LookupInScriptContextTable( |
+ Handle<Name> name, ScriptContextTableLookupResult* result) { |
+ if (!name->IsString()) return false; |
+ Handle<ScriptContextTable> script_context_table( |
+ global_object()->native_context()->script_context_table(), isolate()); |
+ ScriptContextTable::LookupResult lookup_result; |
+ if (!ScriptContextTable::Lookup(script_context_table, |
+ Handle<String>::cast(name), &lookup_result)) { |
+ return false; |
+ } |
+ Handle<Context> script_context = ScriptContextTable::GetContext( |
+ script_context_table, lookup_result.context_index); |
+ result->context = script_context; |
+ result->immutable = lookup_result.mode == CONST; |
+ result->index = lookup_result.slot_index; |
+ return true; |
+} |
+ |
Graph* JSNativeContextSpecialization::graph() const { |
return jsgraph()->graph(); |
} |