1 // Copyright 2015 the V8 project authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "src/compiler/js-global-object-specialization.h"
6 
7 #include "src/compilation-dependencies.h"
8 #include "src/compiler/access-builder.h"
9 #include "src/compiler/common-operator.h"
10 #include "src/compiler/js-graph.h"
11 #include "src/compiler/js-operator.h"
12 #include "src/compiler/node-properties.h"
13 #include "src/compiler/simplified-operator.h"
14 #include "src/lookup.h"
15 #include "src/objects-inl.h"  // TODO(mstarzinger): Temporary cycle breaker!
16 #include "src/type-cache.h"
17 
18 namespace v8 {
19 namespace internal {
20 namespace compiler {
21 
22 struct JSGlobalObjectSpecialization::ScriptContextTableLookupResult {
23   Handle<Context> context;
24   bool immutable;
25   int index;
26 };
27 
28 
JSGlobalObjectSpecialization(Editor * editor,JSGraph * jsgraph,Flags flags,MaybeHandle<Context> native_context,CompilationDependencies * dependencies)29 JSGlobalObjectSpecialization::JSGlobalObjectSpecialization(
30     Editor* editor, JSGraph* jsgraph, Flags flags,
31     MaybeHandle<Context> native_context, CompilationDependencies* dependencies)
32     : AdvancedReducer(editor),
33       jsgraph_(jsgraph),
34       flags_(flags),
35       native_context_(native_context),
36       dependencies_(dependencies),
37       type_cache_(TypeCache::Get()) {}
38 
39 
Reduce(Node * node)40 Reduction JSGlobalObjectSpecialization::Reduce(Node* node) {
41   switch (node->opcode()) {
42     case IrOpcode::kJSLoadGlobal:
43       return ReduceJSLoadGlobal(node);
44     case IrOpcode::kJSStoreGlobal:
45       return ReduceJSStoreGlobal(node);
46     default:
47       break;
48   }
49   return NoChange();
50 }
51 
52 
ReduceJSLoadGlobal(Node * node)53 Reduction JSGlobalObjectSpecialization::ReduceJSLoadGlobal(Node* node) {
54   DCHECK_EQ(IrOpcode::kJSLoadGlobal, node->opcode());
55   Handle<Name> name = LoadGlobalParametersOf(node->op()).name();
56   Node* effect = NodeProperties::GetEffectInput(node);
57   Node* control = NodeProperties::GetControlInput(node);
58 
59   // Retrieve the global object from the given {node}.
60   Handle<JSGlobalObject> global_object;
61   if (!GetGlobalObject(node).ToHandle(&global_object)) return NoChange();
62 
63   // Try to lookup the name on the script context table first (lexical scoping).
64   ScriptContextTableLookupResult result;
65   if (LookupInScriptContextTable(global_object, name, &result)) {
66     if (result.context->is_the_hole(result.index)) return NoChange();
67     Node* context = jsgraph()->HeapConstant(result.context);
68     Node* value = effect = graph()->NewNode(
69         javascript()->LoadContext(0, result.index, result.immutable), context,
70         context, effect);
71     ReplaceWithValue(node, value, effect);
72     return Replace(value);
73   }
74 
75   // Lookup on the global object instead.  We only deal with own data
76   // properties of the global object here (represented as PropertyCell).
77   LookupIterator it(global_object, name, LookupIterator::OWN);
78   if (it.state() != LookupIterator::DATA) return NoChange();
79   Handle<PropertyCell> property_cell = it.GetPropertyCell();
80   PropertyDetails property_details = property_cell->property_details();
81   Handle<Object> property_cell_value(property_cell->value(), isolate());
82 
83   // Load from non-configurable, read-only data property on the global
84   // object can be constant-folded, even without deoptimization support.
85   if (!property_details.IsConfigurable() && property_details.IsReadOnly()) {
86     Node* value = jsgraph()->Constant(property_cell_value);
87     ReplaceWithValue(node, value);
88     return Replace(value);
89   }
90 
91   // Load from non-configurable, data property on the global can be lowered to
92   // a field load, even without deoptimization, because the property cannot be
93   // deleted or reconfigured to an accessor/interceptor property.  Yet, if
94   // deoptimization support is available, we can constant-fold certain global
95   // properties or at least lower them to field loads annotated with more
96   // precise type feedback.
97   Type* property_cell_value_type = Type::Tagged();
98   if (flags() & kDeoptimizationEnabled) {
99     // Record a code dependency on the cell if we can benefit from the
100     // additional feedback, or the global property is configurable (i.e.
101     // can be deleted or reconfigured to an accessor property).
102     if (property_details.cell_type() != PropertyCellType::kMutable ||
103         property_details.IsConfigurable()) {
104       dependencies()->AssumePropertyCell(property_cell);
105     }
106 
107     // Load from constant/undefined global property can be constant-folded.
108     if ((property_details.cell_type() == PropertyCellType::kConstant ||
109          property_details.cell_type() == PropertyCellType::kUndefined)) {
110       Node* value = jsgraph()->Constant(property_cell_value);
111       ReplaceWithValue(node, value);
112       return Replace(value);
113     }
114 
115     // Load from constant type cell can benefit from type feedback.
116     if (property_details.cell_type() == PropertyCellType::kConstantType) {
117       // Compute proper type based on the current value in the cell.
118       if (property_cell_value->IsSmi()) {
119         property_cell_value_type = type_cache_.kSmi;
120       } else if (property_cell_value->IsNumber()) {
121         property_cell_value_type = type_cache_.kHeapNumber;
122       } else {
123         Handle<Map> property_cell_value_map(
124             Handle<HeapObject>::cast(property_cell_value)->map(), isolate());
125         property_cell_value_type =
126             Type::Class(property_cell_value_map, graph()->zone());
127       }
128     }
129   } else if (property_details.IsConfigurable()) {
130     // Access to configurable global properties requires deoptimization support.
131     return NoChange();
132   }
133   Node* value = effect = graph()->NewNode(
134       simplified()->LoadField(
135           AccessBuilder::ForPropertyCellValue(property_cell_value_type)),
136       jsgraph()->HeapConstant(property_cell), effect, control);
137   ReplaceWithValue(node, value, effect, control);
138   return Replace(value);
139 }
140 
141 
ReduceJSStoreGlobal(Node * node)142 Reduction JSGlobalObjectSpecialization::ReduceJSStoreGlobal(Node* node) {
143   DCHECK_EQ(IrOpcode::kJSStoreGlobal, node->opcode());
144   Handle<Name> name = StoreGlobalParametersOf(node->op()).name();
145   Node* value = NodeProperties::GetValueInput(node, 0);
146   Node* frame_state = NodeProperties::GetFrameStateInput(node, 1);
147   Node* effect = NodeProperties::GetEffectInput(node);
148   Node* control = NodeProperties::GetControlInput(node);
149 
150   // Retrieve the global object from the given {node}.
151   Handle<JSGlobalObject> global_object;
152   if (!GetGlobalObject(node).ToHandle(&global_object)) return NoChange();
153 
154   // Try to lookup the name on the script context table first (lexical scoping).
155   ScriptContextTableLookupResult result;
156   if (LookupInScriptContextTable(global_object, name, &result)) {
157     if (result.context->is_the_hole(result.index)) return NoChange();
158     if (result.immutable) return NoChange();
159     Node* context = jsgraph()->HeapConstant(result.context);
160     effect = graph()->NewNode(javascript()->StoreContext(0, result.index),
161                               context, value, context, effect, control);
162     ReplaceWithValue(node, value, effect, control);
163     return Replace(value);
164   }
165 
166   // Lookup on the global object instead.  We only deal with own data
167   // properties of the global object here (represented as PropertyCell).
168   LookupIterator it(global_object, name, LookupIterator::OWN);
169   if (it.state() != LookupIterator::DATA) return NoChange();
170   Handle<PropertyCell> property_cell = it.GetPropertyCell();
171   PropertyDetails property_details = property_cell->property_details();
172   Handle<Object> property_cell_value(property_cell->value(), isolate());
173 
174   // Don't even bother trying to lower stores to read-only data properties.
175   if (property_details.IsReadOnly()) return NoChange();
176   switch (property_details.cell_type()) {
177     case PropertyCellType::kUndefined: {
178       return NoChange();
179     }
180     case PropertyCellType::kConstant: {
181       // Store to constant property cell requires deoptimization support,
182       // because we might even need to eager deoptimize for mismatch.
183       if (!(flags() & kDeoptimizationEnabled)) return NoChange();
184       dependencies()->AssumePropertyCell(property_cell);
185       Node* check =
186           graph()->NewNode(simplified()->ReferenceEqual(Type::Tagged()), value,
187                            jsgraph()->Constant(property_cell_value));
188       Node* branch =
189           graph()->NewNode(common()->Branch(BranchHint::kTrue), check, control);
190       Node* if_false = graph()->NewNode(common()->IfFalse(), branch);
191       Node* deoptimize =
192           graph()->NewNode(common()->Deoptimize(DeoptimizeKind::kEager),
193                            frame_state, effect, if_false);
194       // TODO(bmeurer): This should be on the AdvancedReducer somehow.
195       NodeProperties::MergeControlToEnd(graph(), common(), deoptimize);
196       control = graph()->NewNode(common()->IfTrue(), branch);
197       break;
198     }
199     case PropertyCellType::kConstantType: {
200       // Store to constant-type property cell requires deoptimization support,
201       // because we might even need to eager deoptimize for mismatch.
202       if (!(flags() & kDeoptimizationEnabled)) return NoChange();
203       dependencies()->AssumePropertyCell(property_cell);
204       Node* check = graph()->NewNode(simplified()->ObjectIsSmi(), value);
205       Type* property_cell_value_type = Type::TaggedSigned();
206       if (property_cell_value->IsHeapObject()) {
207         // Deoptimize if the {value} is a Smi.
208         Node* branch = graph()->NewNode(common()->Branch(BranchHint::kFalse),
209                                         check, control);
210         Node* if_true = graph()->NewNode(common()->IfTrue(), branch);
211         Node* deoptimize =
212             graph()->NewNode(common()->Deoptimize(DeoptimizeKind::kEager),
213                              frame_state, effect, if_true);
214         // TODO(bmeurer): This should be on the AdvancedReducer somehow.
215         NodeProperties::MergeControlToEnd(graph(), common(), deoptimize);
216         control = graph()->NewNode(common()->IfFalse(), branch);
217 
218         // Load the {value} map check against the {property_cell} map.
219         Node* value_map = effect =
220             graph()->NewNode(simplified()->LoadField(AccessBuilder::ForMap()),
221                              value, effect, control);
222         Handle<Map> property_cell_value_map(
223             Handle<HeapObject>::cast(property_cell_value)->map(), isolate());
224         check = graph()->NewNode(
225             simplified()->ReferenceEqual(Type::Any()), value_map,
226             jsgraph()->HeapConstant(property_cell_value_map));
227         property_cell_value_type = Type::TaggedPointer();
228       }
229       Node* branch =
230           graph()->NewNode(common()->Branch(BranchHint::kTrue), check, control);
231       Node* if_false = graph()->NewNode(common()->IfFalse(), branch);
232       Node* deoptimize =
233           graph()->NewNode(common()->Deoptimize(DeoptimizeKind::kEager),
234                            frame_state, effect, if_false);
235       // TODO(bmeurer): This should be on the AdvancedReducer somehow.
236       NodeProperties::MergeControlToEnd(graph(), common(), deoptimize);
237       control = graph()->NewNode(common()->IfTrue(), branch);
238       effect = graph()->NewNode(
239           simplified()->StoreField(
240               AccessBuilder::ForPropertyCellValue(property_cell_value_type)),
241           jsgraph()->HeapConstant(property_cell), value, effect, control);
242       break;
243     }
244     case PropertyCellType::kMutable: {
245       // Store to non-configurable, data property on the global can be lowered
246       // to a field store, even without deoptimization, because the property
247       // cannot be deleted or reconfigured to an accessor/interceptor property.
248       if (property_details.IsConfigurable()) {
249         // With deoptimization support, we can lower stores even to configurable
250         // data properties on the global object, by adding a code dependency on
251         // the cell.
252         if (!(flags() & kDeoptimizationEnabled)) return NoChange();
253         dependencies()->AssumePropertyCell(property_cell);
254       }
255       effect = graph()->NewNode(
256           simplified()->StoreField(AccessBuilder::ForPropertyCellValue()),
257           jsgraph()->HeapConstant(property_cell), value, effect, control);
258       break;
259     }
260   }
261   ReplaceWithValue(node, value, effect, control);
262   return Replace(value);
263 }
264 
265 
GetGlobalObject(Node * node)266 MaybeHandle<JSGlobalObject> JSGlobalObjectSpecialization::GetGlobalObject(
267     Node* node) {
268   Node* const context = NodeProperties::GetContextInput(node);
269   return NodeProperties::GetSpecializationGlobalObject(context,
270                                                        native_context());
271 }
272 
273 
LookupInScriptContextTable(Handle<JSGlobalObject> global_object,Handle<Name> name,ScriptContextTableLookupResult * result)274 bool JSGlobalObjectSpecialization::LookupInScriptContextTable(
275     Handle<JSGlobalObject> global_object, Handle<Name> name,
276     ScriptContextTableLookupResult* result) {
277   if (!name->IsString()) return false;
278   Handle<ScriptContextTable> script_context_table(
279       global_object->native_context()->script_context_table(), isolate());
280   ScriptContextTable::LookupResult lookup_result;
281   if (!ScriptContextTable::Lookup(script_context_table,
282                                   Handle<String>::cast(name), &lookup_result)) {
283     return false;
284   }
285   Handle<Context> script_context = ScriptContextTable::GetContext(
286       script_context_table, lookup_result.context_index);
287   result->context = script_context;
288   result->immutable = IsImmutableVariableMode(lookup_result.mode);
289   result->index = lookup_result.slot_index;
290   return true;
291 }
292 
293 
graph() const294 Graph* JSGlobalObjectSpecialization::graph() const {
295   return jsgraph()->graph();
296 }
297 
298 
isolate() const299 Isolate* JSGlobalObjectSpecialization::isolate() const {
300   return jsgraph()->isolate();
301 }
302 
303 
common() const304 CommonOperatorBuilder* JSGlobalObjectSpecialization::common() const {
305   return jsgraph()->common();
306 }
307 
308 
javascript() const309 JSOperatorBuilder* JSGlobalObjectSpecialization::javascript() const {
310   return jsgraph()->javascript();
311 }
312 
313 
simplified() const314 SimplifiedOperatorBuilder* JSGlobalObjectSpecialization::simplified() const {
315   return jsgraph()->simplified();
316 }
317 
318 }  // namespace compiler
319 }  // namespace internal
320 }  // namespace v8
321