OLD | NEW |
| (Empty) |
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-type-feedback.h" | |
6 | |
7 #include "src/property-details.h" | |
8 | |
9 #include "src/accessors.h" | |
10 #include "src/ast.h" | |
11 #include "src/compiler.h" | |
12 #include "src/type-info.h" | |
13 | |
14 #include "src/compiler/access-builder.h" | |
15 #include "src/compiler/common-operator.h" | |
16 #include "src/compiler/frame-states.h" | |
17 #include "src/compiler/node-aux-data.h" | |
18 #include "src/compiler/node-matchers.h" | |
19 #include "src/compiler/operator-properties.h" | |
20 #include "src/compiler/simplified-operator.h" | |
21 | |
22 namespace v8 { | |
23 namespace internal { | |
24 namespace compiler { | |
25 | |
26 enum LoadOrStore { LOAD, STORE }; | |
27 | |
28 // TODO(turbofan): fix deoptimization problems | |
29 #define ENABLE_FAST_PROPERTY_LOADS false | |
30 #define ENABLE_FAST_PROPERTY_STORES false | |
31 | |
32 JSTypeFeedbackTable::JSTypeFeedbackTable(Zone* zone) | |
33 : type_feedback_id_map_(TypeFeedbackIdMap::key_compare(), | |
34 TypeFeedbackIdMap::allocator_type(zone)), | |
35 feedback_vector_slot_map_(TypeFeedbackIdMap::key_compare(), | |
36 TypeFeedbackIdMap::allocator_type(zone)) {} | |
37 | |
38 | |
39 void JSTypeFeedbackTable::Record(Node* node, TypeFeedbackId id) { | |
40 type_feedback_id_map_.insert(std::make_pair(node->id(), id)); | |
41 } | |
42 | |
43 | |
44 void JSTypeFeedbackTable::Record(Node* node, FeedbackVectorSlot slot) { | |
45 feedback_vector_slot_map_.insert(std::make_pair(node->id(), slot)); | |
46 } | |
47 | |
48 | |
49 Reduction JSTypeFeedbackSpecializer::Reduce(Node* node) { | |
50 switch (node->opcode()) { | |
51 case IrOpcode::kJSLoadProperty: | |
52 return ReduceJSLoadProperty(node); | |
53 case IrOpcode::kJSLoadNamed: | |
54 return ReduceJSLoadNamed(node); | |
55 case IrOpcode::kJSLoadGlobal: | |
56 return ReduceJSLoadGlobal(node); | |
57 case IrOpcode::kJSStoreNamed: | |
58 return ReduceJSStoreNamed(node); | |
59 case IrOpcode::kJSStoreProperty: | |
60 return ReduceJSStoreProperty(node); | |
61 default: | |
62 break; | |
63 } | |
64 return NoChange(); | |
65 } | |
66 | |
67 | |
68 static void AddFieldAccessTypes(FieldAccess* access, | |
69 PropertyDetails property_details) { | |
70 if (property_details.representation().IsSmi()) { | |
71 access->type = Type::SignedSmall(); | |
72 access->machine_type = static_cast<MachineType>(kTypeInt32 | kRepTagged); | |
73 } else if (property_details.representation().IsDouble()) { | |
74 access->type = Type::Number(); | |
75 access->machine_type = kMachFloat64; | |
76 } | |
77 } | |
78 | |
79 | |
80 static bool GetInObjectFieldAccess(LoadOrStore mode, Handle<Map> map, | |
81 Handle<Name> name, FieldAccess* access) { | |
82 access->base_is_tagged = kTaggedBase; | |
83 access->offset = -1; | |
84 access->name = name; | |
85 access->type = Type::Any(); | |
86 access->machine_type = kMachAnyTagged; | |
87 | |
88 // Check for properties that have accessors but are JSObject fields. | |
89 if (Accessors::IsJSObjectFieldAccessor(map, name, &access->offset)) { | |
90 // TODO(turbofan): fill in types for special JSObject field accesses. | |
91 return true; | |
92 } | |
93 | |
94 // Check if the map is a dictionary. | |
95 if (map->is_dictionary_map()) return false; | |
96 | |
97 // Search the descriptor array. | |
98 DescriptorArray* descriptors = map->instance_descriptors(); | |
99 int number = descriptors->SearchWithCache(*name, *map); | |
100 if (number == DescriptorArray::kNotFound) return false; | |
101 PropertyDetails property_details = descriptors->GetDetails(number); | |
102 | |
103 bool is_smi = property_details.representation().IsSmi(); | |
104 bool is_double = property_details.representation().IsDouble(); | |
105 | |
106 if (property_details.type() != DATA) { | |
107 // TODO(turbofan): constant loads and stores. | |
108 return false; | |
109 } | |
110 | |
111 // Transfer known types from property details. | |
112 AddFieldAccessTypes(access, property_details); | |
113 | |
114 if (mode == STORE) { | |
115 if (property_details.IsReadOnly()) { | |
116 // TODO(turbofan): deopt, ignore or throw on readonly stores. | |
117 return false; | |
118 } | |
119 if (is_smi || is_double) { | |
120 // TODO(turbofan): check type and deopt for SMI/double stores. | |
121 return false; | |
122 } | |
123 } | |
124 | |
125 int index = map->instance_descriptors()->GetFieldIndex(number); | |
126 FieldIndex field_index = FieldIndex::ForPropertyIndex(*map, index, is_double); | |
127 | |
128 if (field_index.is_inobject()) { | |
129 if (is_double && !map->IsUnboxedDoubleField(field_index)) { | |
130 // TODO(turbofan): support for out-of-line (MutableHeapNumber) loads. | |
131 return false; | |
132 } | |
133 access->offset = field_index.offset(); | |
134 return true; | |
135 } | |
136 | |
137 // TODO(turbofan): handle out of object properties. | |
138 return false; | |
139 } | |
140 | |
141 | |
142 Reduction JSTypeFeedbackSpecializer::ReduceJSLoadNamed(Node* node) { | |
143 DCHECK(node->opcode() == IrOpcode::kJSLoadNamed); | |
144 if (mode() != kDeoptimizationEnabled) return NoChange(); | |
145 Node* frame_state_before = GetFrameStateBefore(node); | |
146 if (frame_state_before == nullptr) return NoChange(); | |
147 | |
148 NamedAccess const& p = NamedAccessOf(node->op()); | |
149 SmallMapList maps; | |
150 | |
151 FeedbackVectorSlot slot = js_type_feedback_->FindFeedbackVectorSlot(node); | |
152 if (slot.IsInvalid() || | |
153 oracle()->LoadInlineCacheState(slot) == UNINITIALIZED) { | |
154 // No type feedback ids or the load is uninitialized. | |
155 return NoChange(); | |
156 } | |
157 oracle()->PropertyReceiverTypes(slot, p.name(), &maps); | |
158 | |
159 Node* receiver = node->InputAt(0); | |
160 Node* effect = NodeProperties::GetEffectInput(node); | |
161 | |
162 if (maps.length() != 1) return NoChange(); // TODO(turbofan): polymorphism | |
163 if (!ENABLE_FAST_PROPERTY_LOADS) return NoChange(); | |
164 | |
165 Handle<Map> map = maps.first(); | |
166 FieldAccess field_access; | |
167 if (!GetInObjectFieldAccess(LOAD, map, p.name(), &field_access)) { | |
168 return NoChange(); | |
169 } | |
170 | |
171 Node* control = NodeProperties::GetControlInput(node); | |
172 Node* check_success; | |
173 Node* check_failed; | |
174 BuildMapCheck(receiver, map, true, effect, control, &check_success, | |
175 &check_failed); | |
176 | |
177 // Build the actual load. | |
178 Node* load = graph()->NewNode(simplified()->LoadField(field_access), receiver, | |
179 effect, check_success); | |
180 | |
181 // TODO(turbofan): handle slow case instead of deoptimizing. | |
182 Node* deopt = graph()->NewNode(common()->Deoptimize(), frame_state_before, | |
183 effect, check_failed); | |
184 NodeProperties::MergeControlToEnd(graph(), common(), deopt); | |
185 ReplaceWithValue(node, load, load, check_success); | |
186 return Replace(load); | |
187 } | |
188 | |
189 | |
190 Reduction JSTypeFeedbackSpecializer::ReduceJSLoadGlobal(Node* node) { | |
191 DCHECK(node->opcode() == IrOpcode::kJSLoadGlobal); | |
192 Handle<String> name = | |
193 Handle<String>::cast(LoadGlobalParametersOf(node->op()).name()); | |
194 if (global_object_.is_null()) { | |
195 // Nothing else can be done if we don't have a global object. | |
196 return NoChange(); | |
197 } | |
198 | |
199 if (mode() == kDeoptimizationEnabled) { | |
200 // Handle lookups in the script context. | |
201 { | |
202 Handle<ScriptContextTable> script_contexts( | |
203 global_object_->native_context()->script_context_table()); | |
204 ScriptContextTable::LookupResult lookup; | |
205 if (ScriptContextTable::Lookup(script_contexts, name, &lookup)) { | |
206 // TODO(turbofan): introduce a LoadContext here. | |
207 return NoChange(); | |
208 } | |
209 } | |
210 | |
211 // Constant promotion or cell access requires lazy deoptimization support. | |
212 LookupIterator it(global_object_, name, LookupIterator::OWN); | |
213 | |
214 if (it.state() == LookupIterator::DATA) { | |
215 Handle<PropertyCell> cell = it.GetPropertyCell(); | |
216 dependencies_->AssumePropertyCell(cell); | |
217 | |
218 if (it.property_details().cell_type() == PropertyCellType::kConstant) { | |
219 // Constant promote the global's current value. | |
220 Handle<Object> constant_value(cell->value(), jsgraph()->isolate()); | |
221 if (constant_value->IsConsString()) { | |
222 constant_value = | |
223 String::Flatten(Handle<String>::cast(constant_value)); | |
224 } | |
225 Node* constant = jsgraph()->Constant(constant_value); | |
226 ReplaceWithValue(node, constant); | |
227 return Replace(constant); | |
228 } else { | |
229 // Load directly from the property cell. | |
230 FieldAccess access = AccessBuilder::ForPropertyCellValue(); | |
231 Node* control = NodeProperties::GetControlInput(node); | |
232 Node* load_field = graph()->NewNode( | |
233 simplified()->LoadField(access), jsgraph()->Constant(cell), | |
234 NodeProperties::GetEffectInput(node), control); | |
235 ReplaceWithValue(node, load_field, load_field, control); | |
236 return Replace(load_field); | |
237 } | |
238 } | |
239 } else { | |
240 // TODO(turbofan): non-configurable properties on the global object | |
241 // should be loadable through a cell without deoptimization support. | |
242 } | |
243 | |
244 return NoChange(); | |
245 } | |
246 | |
247 | |
248 Reduction JSTypeFeedbackSpecializer::ReduceJSLoadProperty(Node* node) { | |
249 return NoChange(); | |
250 } | |
251 | |
252 | |
253 Reduction JSTypeFeedbackSpecializer::ReduceJSStoreNamed(Node* node) { | |
254 DCHECK(node->opcode() == IrOpcode::kJSStoreNamed); | |
255 Node* frame_state_before = GetFrameStateBefore(node); | |
256 if (frame_state_before == nullptr) return NoChange(); | |
257 | |
258 NamedAccess const& p = NamedAccessOf(node->op()); | |
259 SmallMapList maps; | |
260 TypeFeedbackId id = js_type_feedback_->FindTypeFeedbackId(node); | |
261 if (id.IsNone() || oracle()->StoreIsUninitialized(id) == UNINITIALIZED) { | |
262 // No type feedback ids or the store is uninitialized. | |
263 // TODO(titzer): no feedback from vector ICs from stores. | |
264 return NoChange(); | |
265 } else { | |
266 oracle()->AssignmentReceiverTypes(id, p.name(), &maps); | |
267 } | |
268 | |
269 Node* receiver = node->InputAt(0); | |
270 Node* effect = NodeProperties::GetEffectInput(node); | |
271 | |
272 if (maps.length() != 1) return NoChange(); // TODO(turbofan): polymorphism | |
273 | |
274 if (!ENABLE_FAST_PROPERTY_STORES) return NoChange(); | |
275 | |
276 Handle<Map> map = maps.first(); | |
277 FieldAccess field_access; | |
278 if (!GetInObjectFieldAccess(STORE, map, p.name(), &field_access)) { | |
279 return NoChange(); | |
280 } | |
281 | |
282 Node* control = NodeProperties::GetControlInput(node); | |
283 Node* check_success; | |
284 Node* check_failed; | |
285 BuildMapCheck(receiver, map, true, effect, control, &check_success, | |
286 &check_failed); | |
287 | |
288 // Build the actual load. | |
289 Node* value = node->InputAt(1); | |
290 Node* store = graph()->NewNode(simplified()->StoreField(field_access), | |
291 receiver, value, effect, check_success); | |
292 | |
293 // TODO(turbofan): handle slow case instead of deoptimizing. | |
294 Node* deopt = graph()->NewNode(common()->Deoptimize(), frame_state_before, | |
295 effect, check_failed); | |
296 NodeProperties::MergeControlToEnd(graph(), common(), deopt); | |
297 ReplaceWithValue(node, store, store, check_success); | |
298 return Replace(store); | |
299 } | |
300 | |
301 | |
302 Reduction JSTypeFeedbackSpecializer::ReduceJSStoreProperty(Node* node) { | |
303 return NoChange(); | |
304 } | |
305 | |
306 | |
307 void JSTypeFeedbackSpecializer::BuildMapCheck(Node* receiver, Handle<Map> map, | |
308 bool smi_check, Node* effect, | |
309 Node* control, Node** success, | |
310 Node** fail) { | |
311 Node* if_smi = nullptr; | |
312 if (smi_check) { | |
313 Node* branch_smi = graph()->NewNode( | |
314 common()->Branch(BranchHint::kFalse), | |
315 graph()->NewNode(simplified()->ObjectIsSmi(), receiver), control); | |
316 if_smi = graph()->NewNode(common()->IfTrue(), branch_smi); | |
317 control = graph()->NewNode(common()->IfFalse(), branch_smi); | |
318 } | |
319 | |
320 FieldAccess map_access = AccessBuilder::ForMap(); | |
321 Node* receiver_map = graph()->NewNode(simplified()->LoadField(map_access), | |
322 receiver, effect, control); | |
323 Node* map_const = jsgraph_->Constant(map); | |
324 Node* cmp = graph()->NewNode(simplified()->ReferenceEqual(Type::Internal()), | |
325 receiver_map, map_const); | |
326 Node* branch = | |
327 graph()->NewNode(common()->Branch(BranchHint::kTrue), cmp, control); | |
328 *success = graph()->NewNode(common()->IfTrue(), branch); | |
329 *fail = graph()->NewNode(common()->IfFalse(), branch); | |
330 | |
331 if (if_smi) { | |
332 *fail = graph()->NewNode(common()->Merge(2), *fail, if_smi); | |
333 } | |
334 } | |
335 | |
336 | |
337 // Get the frame state before an operation if it exists and has a valid | |
338 // bailout id. | |
339 Node* JSTypeFeedbackSpecializer::GetFrameStateBefore(Node* node) { | |
340 int count = OperatorProperties::GetFrameStateInputCount(node->op()); | |
341 DCHECK_LE(count, 2); | |
342 if (count == 2) { | |
343 Node* frame_state = NodeProperties::GetFrameStateInput(node, 1); | |
344 if (frame_state->opcode() == IrOpcode::kFrameState) { | |
345 BailoutId id = OpParameter<FrameStateInfo>(node).bailout_id(); | |
346 if (id != BailoutId::None()) return frame_state; | |
347 } | |
348 } | |
349 return nullptr; | |
350 } | |
351 | |
352 } // namespace compiler | |
353 } // namespace internal | |
354 } // namespace v8 | |
OLD | NEW |