File: | out/../deps/v8/src/compiler/js-native-context-specialization.cc |
Warning: | line 1379, column 53 Called C++ object pointer is null |
Press '?' to see keyboard shortcuts
Keyboard shortcuts:
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-native-context-specialization.h" | |||
6 | ||||
7 | #include "src/api/api-inl.h" | |||
8 | #include "src/base/optional.h" | |||
9 | #include "src/builtins/accessors.h" | |||
10 | #include "src/codegen/code-factory.h" | |||
11 | #include "src/codegen/string-constants.h" | |||
12 | #include "src/compiler/access-builder.h" | |||
13 | #include "src/compiler/access-info.h" | |||
14 | #include "src/compiler/allocation-builder-inl.h" | |||
15 | #include "src/compiler/allocation-builder.h" | |||
16 | #include "src/compiler/compilation-dependencies.h" | |||
17 | #include "src/compiler/js-graph.h" | |||
18 | #include "src/compiler/js-operator.h" | |||
19 | #include "src/compiler/linkage.h" | |||
20 | #include "src/compiler/map-inference.h" | |||
21 | #include "src/compiler/node-matchers.h" | |||
22 | #include "src/compiler/property-access-builder.h" | |||
23 | #include "src/compiler/type-cache.h" | |||
24 | #include "src/execution/isolate-inl.h" | |||
25 | #include "src/objects/feedback-vector.h" | |||
26 | #include "src/objects/field-index-inl.h" | |||
27 | #include "src/objects/heap-number.h" | |||
28 | #include "src/objects/js-array-buffer-inl.h" | |||
29 | #include "src/objects/js-array-inl.h" | |||
30 | #include "src/objects/templates.h" | |||
31 | ||||
32 | namespace v8 { | |||
33 | namespace internal { | |||
34 | namespace compiler { | |||
35 | ||||
36 | namespace { | |||
37 | ||||
38 | bool HasNumberMaps(JSHeapBroker* broker, ZoneVector<MapRef> const& maps) { | |||
39 | for (MapRef map : maps) { | |||
40 | if (map.IsHeapNumberMap()) return true; | |||
41 | } | |||
42 | return false; | |||
43 | } | |||
44 | ||||
45 | bool HasOnlyJSArrayMaps(JSHeapBroker* broker, ZoneVector<MapRef> const& maps) { | |||
46 | for (MapRef map : maps) { | |||
47 | if (!map.IsJSArrayMap()) return false; | |||
48 | } | |||
49 | return true; | |||
50 | } | |||
51 | ||||
52 | } // namespace | |||
53 | ||||
54 | JSNativeContextSpecialization::JSNativeContextSpecialization( | |||
55 | Editor* editor, JSGraph* jsgraph, JSHeapBroker* broker, Flags flags, | |||
56 | CompilationDependencies* dependencies, Zone* zone, Zone* shared_zone) | |||
57 | : AdvancedReducer(editor), | |||
58 | jsgraph_(jsgraph), | |||
59 | broker_(broker), | |||
60 | flags_(flags), | |||
61 | global_object_(broker->target_native_context().global_object().object()), | |||
62 | global_proxy_( | |||
63 | broker->target_native_context().global_proxy_object().object()), | |||
64 | dependencies_(dependencies), | |||
65 | zone_(zone), | |||
66 | shared_zone_(shared_zone), | |||
67 | type_cache_(TypeCache::Get()) {} | |||
68 | ||||
69 | Reduction JSNativeContextSpecialization::Reduce(Node* node) { | |||
70 | switch (node->opcode()) { | |||
| ||||
71 | case IrOpcode::kJSAdd: | |||
72 | return ReduceJSAdd(node); | |||
73 | case IrOpcode::kJSAsyncFunctionEnter: | |||
74 | return ReduceJSAsyncFunctionEnter(node); | |||
75 | case IrOpcode::kJSAsyncFunctionReject: | |||
76 | return ReduceJSAsyncFunctionReject(node); | |||
77 | case IrOpcode::kJSAsyncFunctionResolve: | |||
78 | return ReduceJSAsyncFunctionResolve(node); | |||
79 | case IrOpcode::kJSGetSuperConstructor: | |||
80 | return ReduceJSGetSuperConstructor(node); | |||
81 | case IrOpcode::kJSInstanceOf: | |||
82 | return ReduceJSInstanceOf(node); | |||
83 | case IrOpcode::kJSHasInPrototypeChain: | |||
84 | return ReduceJSHasInPrototypeChain(node); | |||
85 | case IrOpcode::kJSOrdinaryHasInstance: | |||
86 | return ReduceJSOrdinaryHasInstance(node); | |||
87 | case IrOpcode::kJSPromiseResolve: | |||
88 | return ReduceJSPromiseResolve(node); | |||
89 | case IrOpcode::kJSResolvePromise: | |||
90 | return ReduceJSResolvePromise(node); | |||
91 | case IrOpcode::kJSLoadGlobal: | |||
92 | return ReduceJSLoadGlobal(node); | |||
93 | case IrOpcode::kJSStoreGlobal: | |||
94 | return ReduceJSStoreGlobal(node); | |||
95 | case IrOpcode::kJSLoadNamed: | |||
96 | return ReduceJSLoadNamed(node); | |||
97 | case IrOpcode::kJSLoadNamedFromSuper: | |||
98 | return ReduceJSLoadNamedFromSuper(node); | |||
99 | case IrOpcode::kJSSetNamedProperty: | |||
100 | return ReduceJSSetNamedProperty(node); | |||
101 | case IrOpcode::kJSHasProperty: | |||
102 | return ReduceJSHasProperty(node); | |||
103 | case IrOpcode::kJSLoadProperty: | |||
104 | return ReduceJSLoadProperty(node); | |||
105 | case IrOpcode::kJSSetKeyedProperty: | |||
106 | return ReduceJSSetKeyedProperty(node); | |||
107 | case IrOpcode::kJSDefineKeyedOwnProperty: | |||
108 | return ReduceJSDefineKeyedOwnProperty(node); | |||
109 | case IrOpcode::kJSDefineNamedOwnProperty: | |||
110 | return ReduceJSDefineNamedOwnProperty(node); | |||
111 | case IrOpcode::kJSDefineKeyedOwnPropertyInLiteral: | |||
112 | return ReduceJSDefineKeyedOwnPropertyInLiteral(node); | |||
113 | case IrOpcode::kJSStoreInArrayLiteral: | |||
114 | return ReduceJSStoreInArrayLiteral(node); | |||
115 | case IrOpcode::kJSToObject: | |||
116 | return ReduceJSToObject(node); | |||
117 | case IrOpcode::kJSToString: | |||
118 | return ReduceJSToString(node); | |||
119 | case IrOpcode::kJSGetIterator: | |||
120 | return ReduceJSGetIterator(node); | |||
121 | default: | |||
122 | break; | |||
123 | } | |||
124 | return NoChange(); | |||
125 | } | |||
126 | ||||
127 | // static | |||
128 | base::Optional<size_t> JSNativeContextSpecialization::GetMaxStringLength( | |||
129 | JSHeapBroker* broker, Node* node) { | |||
130 | if (node->opcode() == IrOpcode::kDelayedStringConstant) { | |||
131 | return StringConstantBaseOf(node->op())->GetMaxStringConstantLength(); | |||
132 | } | |||
133 | ||||
134 | HeapObjectMatcher matcher(node); | |||
135 | if (matcher.HasResolvedValue() && matcher.Ref(broker).IsString()) { | |||
136 | StringRef input = matcher.Ref(broker).AsString(); | |||
137 | return input.length(); | |||
138 | } | |||
139 | ||||
140 | NumberMatcher number_matcher(node); | |||
141 | if (number_matcher.HasResolvedValue()) { | |||
142 | return kMaxDoubleStringLength; | |||
143 | } | |||
144 | ||||
145 | // We don't support objects with possibly monkey-patched prototype.toString | |||
146 | // as it might have side-effects, so we shouldn't attempt lowering them. | |||
147 | return base::nullopt; | |||
148 | } | |||
149 | ||||
150 | Reduction JSNativeContextSpecialization::ReduceJSToString(Node* node) { | |||
151 | DCHECK_EQ(IrOpcode::kJSToString, node->opcode())((void) 0); | |||
152 | Node* const input = node->InputAt(0); | |||
153 | Reduction reduction; | |||
154 | ||||
155 | HeapObjectMatcher matcher(input); | |||
156 | if (matcher.HasResolvedValue() && matcher.Ref(broker()).IsString()) { | |||
157 | reduction = Changed(input); // JSToString(x:string) => x | |||
158 | ReplaceWithValue(node, reduction.replacement()); | |||
159 | return reduction; | |||
160 | } | |||
161 | ||||
162 | // TODO(turbofan): This optimization is weaker than what we used to have | |||
163 | // in js-typed-lowering for OrderedNumbers. We don't have types here though, | |||
164 | // so alternative approach should be designed if this causes performance | |||
165 | // regressions and the stronger optimization should be re-implemented. | |||
166 | NumberMatcher number_matcher(input); | |||
167 | if (number_matcher.HasResolvedValue()) { | |||
168 | const StringConstantBase* base = shared_zone()->New<NumberToStringConstant>( | |||
169 | number_matcher.ResolvedValue()); | |||
170 | reduction = | |||
171 | Replace(graph()->NewNode(common()->DelayedStringConstant(base))); | |||
172 | ReplaceWithValue(node, reduction.replacement()); | |||
173 | return reduction; | |||
174 | } | |||
175 | ||||
176 | return NoChange(); | |||
177 | } | |||
178 | ||||
179 | base::Optional<const StringConstantBase*> | |||
180 | JSNativeContextSpecialization::CreateDelayedStringConstant(Node* node) { | |||
181 | if (node->opcode() == IrOpcode::kDelayedStringConstant) { | |||
182 | return StringConstantBaseOf(node->op()); | |||
183 | } else { | |||
184 | NumberMatcher number_matcher(node); | |||
185 | if (number_matcher.HasResolvedValue()) { | |||
186 | return shared_zone()->New<NumberToStringConstant>( | |||
187 | number_matcher.ResolvedValue()); | |||
188 | } else { | |||
189 | HeapObjectMatcher matcher(node); | |||
190 | if (matcher.HasResolvedValue() && matcher.Ref(broker()).IsString()) { | |||
191 | StringRef s = matcher.Ref(broker()).AsString(); | |||
192 | if (!s.length().has_value()) return base::nullopt; | |||
193 | return shared_zone()->New<StringLiteral>( | |||
194 | s.object(), static_cast<size_t>(s.length().value())); | |||
195 | } else { | |||
196 | UNREACHABLE()V8_Fatal("unreachable code"); | |||
197 | } | |||
198 | } | |||
199 | } | |||
200 | } | |||
201 | ||||
202 | namespace { | |||
203 | bool IsStringConstant(JSHeapBroker* broker, Node* node) { | |||
204 | if (node->opcode() == IrOpcode::kDelayedStringConstant) { | |||
205 | return true; | |||
206 | } | |||
207 | ||||
208 | HeapObjectMatcher matcher(node); | |||
209 | return matcher.HasResolvedValue() && matcher.Ref(broker).IsString(); | |||
210 | } | |||
211 | } // namespace | |||
212 | ||||
213 | Reduction JSNativeContextSpecialization::ReduceJSAsyncFunctionEnter( | |||
214 | Node* node) { | |||
215 | DCHECK_EQ(IrOpcode::kJSAsyncFunctionEnter, node->opcode())((void) 0); | |||
216 | Node* closure = NodeProperties::GetValueInput(node, 0); | |||
217 | Node* receiver = NodeProperties::GetValueInput(node, 1); | |||
218 | Node* context = NodeProperties::GetContextInput(node); | |||
219 | Node* frame_state = NodeProperties::GetFrameStateInput(node); | |||
220 | Node* effect = NodeProperties::GetEffectInput(node); | |||
221 | Node* control = NodeProperties::GetControlInput(node); | |||
222 | ||||
223 | if (!dependencies()->DependOnPromiseHookProtector()) return NoChange(); | |||
224 | ||||
225 | // Create the promise for the async function. | |||
226 | Node* promise = effect = | |||
227 | graph()->NewNode(javascript()->CreatePromise(), context, effect); | |||
228 | ||||
229 | // Create the JSAsyncFunctionObject based on the SharedFunctionInfo | |||
230 | // extracted from the top-most frame in {frame_state}. | |||
231 | SharedFunctionInfoRef shared = MakeRef( | |||
232 | broker(), | |||
233 | FrameStateInfoOf(frame_state->op()).shared_info().ToHandleChecked()); | |||
234 | DCHECK(shared.is_compiled())((void) 0); | |||
235 | int register_count = | |||
236 | shared.internal_formal_parameter_count_without_receiver() + | |||
237 | shared.GetBytecodeArray().register_count(); | |||
238 | MapRef fixed_array_map = MakeRef(broker(), factory()->fixed_array_map()); | |||
239 | AllocationBuilder ab(jsgraph(), effect, control); | |||
240 | if (!ab.CanAllocateArray(register_count, fixed_array_map)) { | |||
241 | return NoChange(); | |||
242 | } | |||
243 | Node* value = effect = | |||
244 | graph()->NewNode(javascript()->CreateAsyncFunctionObject(register_count), | |||
245 | closure, receiver, promise, context, effect, control); | |||
246 | ReplaceWithValue(node, value, effect, control); | |||
247 | return Replace(value); | |||
248 | } | |||
249 | ||||
250 | Reduction JSNativeContextSpecialization::ReduceJSAsyncFunctionReject( | |||
251 | Node* node) { | |||
252 | DCHECK_EQ(IrOpcode::kJSAsyncFunctionReject, node->opcode())((void) 0); | |||
253 | Node* async_function_object = NodeProperties::GetValueInput(node, 0); | |||
254 | Node* reason = NodeProperties::GetValueInput(node, 1); | |||
255 | Node* context = NodeProperties::GetContextInput(node); | |||
256 | Node* frame_state = NodeProperties::GetFrameStateInput(node); | |||
257 | Node* effect = NodeProperties::GetEffectInput(node); | |||
258 | Node* control = NodeProperties::GetControlInput(node); | |||
259 | ||||
260 | if (!dependencies()->DependOnPromiseHookProtector()) return NoChange(); | |||
261 | ||||
262 | // Load the promise from the {async_function_object}. | |||
263 | Node* promise = effect = graph()->NewNode( | |||
264 | simplified()->LoadField(AccessBuilder::ForJSAsyncFunctionObjectPromise()), | |||
265 | async_function_object, effect, control); | |||
266 | ||||
267 | // Create a nested frame state inside the current method's most-recent | |||
268 | // {frame_state} that will ensure that lazy deoptimizations at this | |||
269 | // point will still return the {promise} instead of the result of the | |||
270 | // JSRejectPromise operation (which yields undefined). | |||
271 | Node* parameters[] = {promise}; | |||
272 | frame_state = CreateStubBuiltinContinuationFrameState( | |||
273 | jsgraph(), Builtin::kAsyncFunctionLazyDeoptContinuation, context, | |||
274 | parameters, arraysize(parameters)(sizeof(ArraySizeHelper(parameters))), frame_state, | |||
275 | ContinuationFrameStateMode::LAZY); | |||
276 | ||||
277 | // Disable the additional debug event for the rejection since a | |||
278 | // debug event already happend for the exception that got us here. | |||
279 | Node* debug_event = jsgraph()->FalseConstant(); | |||
280 | effect = graph()->NewNode(javascript()->RejectPromise(), promise, reason, | |||
281 | debug_event, context, frame_state, effect, control); | |||
282 | ReplaceWithValue(node, promise, effect, control); | |||
283 | return Replace(promise); | |||
284 | } | |||
285 | ||||
286 | Reduction JSNativeContextSpecialization::ReduceJSAsyncFunctionResolve( | |||
287 | Node* node) { | |||
288 | DCHECK_EQ(IrOpcode::kJSAsyncFunctionResolve, node->opcode())((void) 0); | |||
289 | Node* async_function_object = NodeProperties::GetValueInput(node, 0); | |||
290 | Node* value = NodeProperties::GetValueInput(node, 1); | |||
291 | Node* context = NodeProperties::GetContextInput(node); | |||
292 | Node* frame_state = NodeProperties::GetFrameStateInput(node); | |||
293 | Node* effect = NodeProperties::GetEffectInput(node); | |||
294 | Node* control = NodeProperties::GetControlInput(node); | |||
295 | ||||
296 | if (!dependencies()->DependOnPromiseHookProtector()) return NoChange(); | |||
297 | ||||
298 | // Load the promise from the {async_function_object}. | |||
299 | Node* promise = effect = graph()->NewNode( | |||
300 | simplified()->LoadField(AccessBuilder::ForJSAsyncFunctionObjectPromise()), | |||
301 | async_function_object, effect, control); | |||
302 | ||||
303 | // Create a nested frame state inside the current method's most-recent | |||
304 | // {frame_state} that will ensure that lazy deoptimizations at this | |||
305 | // point will still return the {promise} instead of the result of the | |||
306 | // JSResolvePromise operation (which yields undefined). | |||
307 | Node* parameters[] = {promise}; | |||
308 | frame_state = CreateStubBuiltinContinuationFrameState( | |||
309 | jsgraph(), Builtin::kAsyncFunctionLazyDeoptContinuation, context, | |||
310 | parameters, arraysize(parameters)(sizeof(ArraySizeHelper(parameters))), frame_state, | |||
311 | ContinuationFrameStateMode::LAZY); | |||
312 | ||||
313 | effect = graph()->NewNode(javascript()->ResolvePromise(), promise, value, | |||
314 | context, frame_state, effect, control); | |||
315 | ReplaceWithValue(node, promise, effect, control); | |||
316 | return Replace(promise); | |||
317 | } | |||
318 | ||||
319 | Reduction JSNativeContextSpecialization::ReduceJSAdd(Node* node) { | |||
320 | // TODO(turbofan): This has to run together with the inlining and | |||
321 | // native context specialization to be able to leverage the string | |||
322 | // constant-folding for optimizing property access, but we should | |||
323 | // nevertheless find a better home for this at some point. | |||
324 | DCHECK_EQ(IrOpcode::kJSAdd, node->opcode())((void) 0); | |||
325 | ||||
326 | Node* const lhs = node->InputAt(0); | |||
327 | Node* const rhs = node->InputAt(1); | |||
328 | ||||
329 | base::Optional<size_t> lhs_len = GetMaxStringLength(broker(), lhs); | |||
330 | base::Optional<size_t> rhs_len = GetMaxStringLength(broker(), rhs); | |||
331 | if (!lhs_len || !rhs_len) { | |||
332 | return NoChange(); | |||
333 | } | |||
334 | ||||
335 | // Fold into DelayedStringConstant if at least one of the parameters is a | |||
336 | // string constant and the addition won't throw due to too long result. | |||
337 | if (*lhs_len + *rhs_len <= String::kMaxLength && | |||
338 | (IsStringConstant(broker(), lhs) || IsStringConstant(broker(), rhs))) { | |||
339 | base::Optional<const StringConstantBase*> left = | |||
340 | CreateDelayedStringConstant(lhs); | |||
341 | if (!left.has_value()) return NoChange(); | |||
342 | base::Optional<const StringConstantBase*> right = | |||
343 | CreateDelayedStringConstant(rhs); | |||
344 | if (!right.has_value()) return NoChange(); | |||
345 | const StringConstantBase* cons = | |||
346 | shared_zone()->New<StringCons>(left.value(), right.value()); | |||
347 | ||||
348 | Node* reduced = graph()->NewNode(common()->DelayedStringConstant(cons)); | |||
349 | ReplaceWithValue(node, reduced); | |||
350 | return Replace(reduced); | |||
351 | } | |||
352 | ||||
353 | return NoChange(); | |||
354 | } | |||
355 | ||||
356 | Reduction JSNativeContextSpecialization::ReduceJSGetSuperConstructor( | |||
357 | Node* node) { | |||
358 | DCHECK_EQ(IrOpcode::kJSGetSuperConstructor, node->opcode())((void) 0); | |||
359 | Node* constructor = NodeProperties::GetValueInput(node, 0); | |||
360 | ||||
361 | // Check if the input is a known JSFunction. | |||
362 | HeapObjectMatcher m(constructor); | |||
363 | if (!m.HasResolvedValue() || !m.Ref(broker()).IsJSFunction()) { | |||
364 | return NoChange(); | |||
365 | } | |||
366 | JSFunctionRef function = m.Ref(broker()).AsJSFunction(); | |||
367 | MapRef function_map = function.map(); | |||
368 | HeapObjectRef function_prototype = function_map.prototype(); | |||
369 | ||||
370 | // We can constant-fold the super constructor access if the | |||
371 | // {function}s map is stable, i.e. we can use a code dependency | |||
372 | // to guard against [[Prototype]] changes of {function}. | |||
373 | if (function_map.is_stable()) { | |||
374 | dependencies()->DependOnStableMap(function_map); | |||
375 | Node* value = jsgraph()->Constant(function_prototype); | |||
376 | ReplaceWithValue(node, value); | |||
377 | return Replace(value); | |||
378 | } | |||
379 | ||||
380 | return NoChange(); | |||
381 | } | |||
382 | ||||
383 | Reduction JSNativeContextSpecialization::ReduceJSInstanceOf(Node* node) { | |||
384 | JSInstanceOfNode n(node); | |||
385 | FeedbackParameter const& p = n.Parameters(); | |||
386 | Node* object = n.left(); | |||
387 | Node* constructor = n.right(); | |||
388 | TNode<Object> context = n.context(); | |||
389 | FrameState frame_state = n.frame_state(); | |||
390 | Effect effect = n.effect(); | |||
391 | Control control = n.control(); | |||
392 | ||||
393 | // Check if the right hand side is a known {receiver}, or | |||
394 | // we have feedback from the InstanceOfIC. | |||
395 | base::Optional<JSObjectRef> receiver; | |||
396 | HeapObjectMatcher m(constructor); | |||
397 | if (m.HasResolvedValue() && m.Ref(broker()).IsJSObject()) { | |||
398 | receiver = m.Ref(broker()).AsJSObject(); | |||
399 | } else if (p.feedback().IsValid()) { | |||
400 | ProcessedFeedback const& feedback = | |||
401 | broker()->GetFeedbackForInstanceOf(FeedbackSource(p.feedback())); | |||
402 | if (feedback.IsInsufficient()) return NoChange(); | |||
403 | receiver = feedback.AsInstanceOf().value(); | |||
404 | } else { | |||
405 | return NoChange(); | |||
406 | } | |||
407 | ||||
408 | if (!receiver.has_value()) return NoChange(); | |||
409 | ||||
410 | MapRef receiver_map = receiver->map(); | |||
411 | NameRef name = MakeRef(broker(), isolate()->factory()->has_instance_symbol()); | |||
412 | PropertyAccessInfo access_info = broker()->GetPropertyAccessInfo( | |||
413 | receiver_map, name, AccessMode::kLoad, dependencies()); | |||
414 | ||||
415 | // TODO(v8:11457) Support dictionary mode holders here. | |||
416 | if (access_info.IsInvalid() || access_info.HasDictionaryHolder()) { | |||
417 | return NoChange(); | |||
418 | } | |||
419 | access_info.RecordDependencies(dependencies()); | |||
420 | ||||
421 | PropertyAccessBuilder access_builder(jsgraph(), broker(), dependencies()); | |||
422 | ||||
423 | if (access_info.IsNotFound()) { | |||
424 | // If there's no @@hasInstance handler, the OrdinaryHasInstance operation | |||
425 | // takes over, but that requires the constructor to be callable. | |||
426 | if (!receiver_map.is_callable()) return NoChange(); | |||
427 | ||||
428 | dependencies()->DependOnStablePrototypeChains( | |||
429 | access_info.lookup_start_object_maps(), kStartAtPrototype); | |||
430 | ||||
431 | // Monomorphic property access. | |||
432 | access_builder.BuildCheckMaps(constructor, &effect, control, | |||
433 | access_info.lookup_start_object_maps()); | |||
434 | ||||
435 | // Lower to OrdinaryHasInstance(C, O). | |||
436 | NodeProperties::ReplaceValueInput(node, constructor, 0); | |||
437 | NodeProperties::ReplaceValueInput(node, object, 1); | |||
438 | NodeProperties::ReplaceEffectInput(node, effect); | |||
439 | STATIC_ASSERT(n.FeedbackVectorIndex() == 2)static_assert(n.FeedbackVectorIndex() == 2, "n.FeedbackVectorIndex() == 2" ); | |||
440 | node->RemoveInput(n.FeedbackVectorIndex()); | |||
441 | NodeProperties::ChangeOp(node, javascript()->OrdinaryHasInstance()); | |||
442 | return Changed(node).FollowedBy(ReduceJSOrdinaryHasInstance(node)); | |||
443 | } | |||
444 | ||||
445 | if (access_info.IsFastDataConstant()) { | |||
446 | base::Optional<JSObjectRef> holder = access_info.holder(); | |||
447 | bool found_on_proto = holder.has_value(); | |||
448 | JSObjectRef holder_ref = found_on_proto ? holder.value() : receiver.value(); | |||
449 | base::Optional<ObjectRef> constant = holder_ref.GetOwnFastDataProperty( | |||
450 | access_info.field_representation(), access_info.field_index(), | |||
451 | dependencies()); | |||
452 | if (!constant.has_value() || !constant->IsHeapObject() || | |||
453 | !constant->AsHeapObject().map().is_callable()) { | |||
454 | return NoChange(); | |||
455 | } | |||
456 | ||||
457 | if (found_on_proto) { | |||
458 | dependencies()->DependOnStablePrototypeChains( | |||
459 | access_info.lookup_start_object_maps(), kStartAtPrototype, | |||
460 | holder.value()); | |||
461 | } | |||
462 | ||||
463 | // Check that {constructor} is actually {receiver}. | |||
464 | constructor = access_builder.BuildCheckValue(constructor, &effect, control, | |||
465 | receiver->object()); | |||
466 | ||||
467 | // Monomorphic property access. | |||
468 | access_builder.BuildCheckMaps(constructor, &effect, control, | |||
469 | access_info.lookup_start_object_maps()); | |||
470 | ||||
471 | // Create a nested frame state inside the current method's most-recent frame | |||
472 | // state that will ensure that deopts that happen after this point will not | |||
473 | // fallback to the last Checkpoint--which would completely re-execute the | |||
474 | // instanceof logic--but rather create an activation of a version of the | |||
475 | // ToBoolean stub that finishes the remaining work of instanceof and returns | |||
476 | // to the caller without duplicating side-effects upon a lazy deopt. | |||
477 | Node* continuation_frame_state = CreateStubBuiltinContinuationFrameState( | |||
478 | jsgraph(), Builtin::kToBooleanLazyDeoptContinuation, context, nullptr, | |||
479 | 0, frame_state, ContinuationFrameStateMode::LAZY); | |||
480 | ||||
481 | // Call the @@hasInstance handler. | |||
482 | Node* target = jsgraph()->Constant(*constant); | |||
483 | Node* feedback = jsgraph()->UndefinedConstant(); | |||
484 | // Value inputs plus context, frame state, effect, control. | |||
485 | STATIC_ASSERT(JSCallNode::ArityForArgc(1) + 4 == 8)static_assert(JSCallNode::ArityForArgc(1) + 4 == 8, "JSCallNode::ArityForArgc(1) + 4 == 8" ); | |||
486 | node->EnsureInputCount(graph()->zone(), 8); | |||
487 | node->ReplaceInput(JSCallNode::TargetIndex(), target); | |||
488 | node->ReplaceInput(JSCallNode::ReceiverIndex(), constructor); | |||
489 | node->ReplaceInput(JSCallNode::ArgumentIndex(0), object); | |||
490 | node->ReplaceInput(3, feedback); | |||
491 | node->ReplaceInput(4, context); | |||
492 | node->ReplaceInput(5, continuation_frame_state); | |||
493 | node->ReplaceInput(6, effect); | |||
494 | node->ReplaceInput(7, control); | |||
495 | NodeProperties::ChangeOp( | |||
496 | node, javascript()->Call(JSCallNode::ArityForArgc(1), CallFrequency(), | |||
497 | FeedbackSource(), | |||
498 | ConvertReceiverMode::kNotNullOrUndefined)); | |||
499 | ||||
500 | // Rewire the value uses of {node} to ToBoolean conversion of the result. | |||
501 | Node* value = graph()->NewNode(simplified()->ToBoolean(), node); | |||
502 | for (Edge edge : node->use_edges()) { | |||
503 | if (NodeProperties::IsValueEdge(edge) && edge.from() != value) { | |||
504 | edge.UpdateTo(value); | |||
505 | Revisit(edge.from()); | |||
506 | } | |||
507 | } | |||
508 | return Changed(node); | |||
509 | } | |||
510 | ||||
511 | return NoChange(); | |||
512 | } | |||
513 | ||||
514 | JSNativeContextSpecialization::InferHasInPrototypeChainResult | |||
515 | JSNativeContextSpecialization::InferHasInPrototypeChain( | |||
516 | Node* receiver, Effect effect, HeapObjectRef const& prototype) { | |||
517 | ZoneRefUnorderedSet<MapRef> receiver_maps(zone()); | |||
518 | NodeProperties::InferMapsResult result = NodeProperties::InferMapsUnsafe( | |||
519 | broker(), receiver, effect, &receiver_maps); | |||
520 | if (result == NodeProperties::kNoMaps) return kMayBeInPrototypeChain; | |||
521 | ||||
522 | ZoneVector<MapRef> receiver_map_refs(zone()); | |||
523 | ||||
524 | // Try to determine either that all of the {receiver_maps} have the given | |||
525 | // {prototype} in their chain, or that none do. If we can't tell, return | |||
526 | // kMayBeInPrototypeChain. | |||
527 | bool all = true; | |||
528 | bool none = true; | |||
529 | for (MapRef map : receiver_maps) { | |||
530 | receiver_map_refs.push_back(map); | |||
531 | if (result == NodeProperties::kUnreliableMaps && !map.is_stable()) { | |||
532 | return kMayBeInPrototypeChain; | |||
533 | } | |||
534 | while (true) { | |||
535 | if (IsSpecialReceiverInstanceType(map.instance_type())) { | |||
536 | return kMayBeInPrototypeChain; | |||
537 | } | |||
538 | if (!map.IsJSObjectMap()) { | |||
539 | all = false; | |||
540 | break; | |||
541 | } | |||
542 | HeapObjectRef map_prototype = map.prototype(); | |||
543 | if (map_prototype.equals(prototype)) { | |||
544 | none = false; | |||
545 | break; | |||
546 | } | |||
547 | map = map_prototype.map(); | |||
548 | // TODO(v8:11457) Support dictionary mode protoypes here. | |||
549 | if (!map.is_stable() || map.is_dictionary_map()) { | |||
550 | return kMayBeInPrototypeChain; | |||
551 | } | |||
552 | if (map.oddball_type() == OddballType::kNull) { | |||
553 | all = false; | |||
554 | break; | |||
555 | } | |||
556 | } | |||
557 | } | |||
558 | DCHECK_IMPLIES(all, !none)((void) 0); | |||
559 | if (!all && !none) return kMayBeInPrototypeChain; | |||
560 | ||||
561 | { | |||
562 | base::Optional<JSObjectRef> last_prototype; | |||
563 | if (all) { | |||
564 | // We don't need to protect the full chain if we found the prototype, we | |||
565 | // can stop at {prototype}. In fact we could stop at the one before | |||
566 | // {prototype} but since we're dealing with multiple receiver maps this | |||
567 | // might be a different object each time, so it's much simpler to include | |||
568 | // {prototype}. That does, however, mean that we must check {prototype}'s | |||
569 | // map stability. | |||
570 | if (!prototype.map().is_stable()) return kMayBeInPrototypeChain; | |||
571 | last_prototype = prototype.AsJSObject(); | |||
572 | } | |||
573 | WhereToStart start = result == NodeProperties::kUnreliableMaps | |||
574 | ? kStartAtReceiver | |||
575 | : kStartAtPrototype; | |||
576 | dependencies()->DependOnStablePrototypeChains(receiver_map_refs, start, | |||
577 | last_prototype); | |||
578 | } | |||
579 | ||||
580 | DCHECK_EQ(all, !none)((void) 0); | |||
581 | return all ? kIsInPrototypeChain : kIsNotInPrototypeChain; | |||
582 | } | |||
583 | ||||
584 | Reduction JSNativeContextSpecialization::ReduceJSHasInPrototypeChain( | |||
585 | Node* node) { | |||
586 | DCHECK_EQ(IrOpcode::kJSHasInPrototypeChain, node->opcode())((void) 0); | |||
587 | Node* value = NodeProperties::GetValueInput(node, 0); | |||
588 | Node* prototype = NodeProperties::GetValueInput(node, 1); | |||
589 | Effect effect{NodeProperties::GetEffectInput(node)}; | |||
590 | ||||
591 | // Check if we can constant-fold the prototype chain walk | |||
592 | // for the given {value} and the {prototype}. | |||
593 | HeapObjectMatcher m(prototype); | |||
594 | if (m.HasResolvedValue()) { | |||
595 | InferHasInPrototypeChainResult result = | |||
596 | InferHasInPrototypeChain(value, effect, m.Ref(broker())); | |||
597 | if (result != kMayBeInPrototypeChain) { | |||
598 | Node* result_in_chain = | |||
599 | jsgraph()->BooleanConstant(result == kIsInPrototypeChain); | |||
600 | ReplaceWithValue(node, result_in_chain); | |||
601 | return Replace(result_in_chain); | |||
602 | } | |||
603 | } | |||
604 | ||||
605 | return NoChange(); | |||
606 | } | |||
607 | ||||
608 | Reduction JSNativeContextSpecialization::ReduceJSOrdinaryHasInstance( | |||
609 | Node* node) { | |||
610 | DCHECK_EQ(IrOpcode::kJSOrdinaryHasInstance, node->opcode())((void) 0); | |||
611 | Node* constructor = NodeProperties::GetValueInput(node, 0); | |||
612 | Node* object = NodeProperties::GetValueInput(node, 1); | |||
613 | ||||
614 | // Check if the {constructor} is known at compile time. | |||
615 | HeapObjectMatcher m(constructor); | |||
616 | if (!m.HasResolvedValue()) return NoChange(); | |||
617 | ||||
618 | if (m.Ref(broker()).IsJSBoundFunction()) { | |||
619 | // OrdinaryHasInstance on bound functions turns into a recursive invocation | |||
620 | // of the instanceof operator again. | |||
621 | JSBoundFunctionRef function = m.Ref(broker()).AsJSBoundFunction(); | |||
622 | Node* feedback = jsgraph()->UndefinedConstant(); | |||
623 | NodeProperties::ReplaceValueInput(node, object, | |||
624 | JSInstanceOfNode::LeftIndex()); | |||
625 | NodeProperties::ReplaceValueInput( | |||
626 | node, jsgraph()->Constant(function.bound_target_function()), | |||
627 | JSInstanceOfNode::RightIndex()); | |||
628 | node->InsertInput(zone(), JSInstanceOfNode::FeedbackVectorIndex(), | |||
629 | feedback); | |||
630 | NodeProperties::ChangeOp(node, javascript()->InstanceOf(FeedbackSource())); | |||
631 | return Changed(node).FollowedBy(ReduceJSInstanceOf(node)); | |||
632 | } | |||
633 | ||||
634 | if (m.Ref(broker()).IsJSFunction()) { | |||
635 | // Optimize if we currently know the "prototype" property. | |||
636 | ||||
637 | JSFunctionRef function = m.Ref(broker()).AsJSFunction(); | |||
638 | ||||
639 | // TODO(neis): Remove the has_prototype_slot condition once the broker is | |||
640 | // always enabled. | |||
641 | if (!function.map().has_prototype_slot() || | |||
642 | !function.has_instance_prototype(dependencies()) || | |||
643 | function.PrototypeRequiresRuntimeLookup(dependencies())) { | |||
644 | return NoChange(); | |||
645 | } | |||
646 | ||||
647 | ObjectRef prototype = dependencies()->DependOnPrototypeProperty(function); | |||
648 | Node* prototype_constant = jsgraph()->Constant(prototype); | |||
649 | ||||
650 | // Lower the {node} to JSHasInPrototypeChain. | |||
651 | NodeProperties::ReplaceValueInput(node, object, 0); | |||
652 | NodeProperties::ReplaceValueInput(node, prototype_constant, 1); | |||
653 | NodeProperties::ChangeOp(node, javascript()->HasInPrototypeChain()); | |||
654 | return Changed(node).FollowedBy(ReduceJSHasInPrototypeChain(node)); | |||
655 | } | |||
656 | ||||
657 | return NoChange(); | |||
658 | } | |||
659 | ||||
660 | // ES section #sec-promise-resolve | |||
661 | Reduction JSNativeContextSpecialization::ReduceJSPromiseResolve(Node* node) { | |||
662 | DCHECK_EQ(IrOpcode::kJSPromiseResolve, node->opcode())((void) 0); | |||
663 | Node* constructor = NodeProperties::GetValueInput(node, 0); | |||
664 | Node* value = NodeProperties::GetValueInput(node, 1); | |||
665 | Node* context = NodeProperties::GetContextInput(node); | |||
666 | FrameState frame_state{NodeProperties::GetFrameStateInput(node)}; | |||
667 | Effect effect{NodeProperties::GetEffectInput(node)}; | |||
668 | Control control{NodeProperties::GetControlInput(node)}; | |||
669 | ||||
670 | // Check if the {constructor} is the %Promise% function. | |||
671 | HeapObjectMatcher m(constructor); | |||
672 | if (!m.HasResolvedValue() || | |||
673 | !m.Ref(broker()).equals(native_context().promise_function())) { | |||
674 | return NoChange(); | |||
675 | } | |||
676 | ||||
677 | // Only optimize if {value} cannot be a JSPromise. | |||
678 | MapInference inference(broker(), value, effect); | |||
679 | if (!inference.HaveMaps() || | |||
680 | inference.AnyOfInstanceTypesAre(JS_PROMISE_TYPE)) { | |||
681 | return NoChange(); | |||
682 | } | |||
683 | ||||
684 | if (!dependencies()->DependOnPromiseHookProtector()) return NoChange(); | |||
685 | ||||
686 | // Create a %Promise% instance and resolve it with {value}. | |||
687 | Node* promise = effect = | |||
688 | graph()->NewNode(javascript()->CreatePromise(), context, effect); | |||
689 | effect = graph()->NewNode(javascript()->ResolvePromise(), promise, value, | |||
690 | context, frame_state, effect, control); | |||
691 | ReplaceWithValue(node, promise, effect, control); | |||
692 | return Replace(promise); | |||
693 | } | |||
694 | ||||
695 | // ES section #sec-promise-resolve-functions | |||
696 | Reduction JSNativeContextSpecialization::ReduceJSResolvePromise(Node* node) { | |||
697 | DCHECK_EQ(IrOpcode::kJSResolvePromise, node->opcode())((void) 0); | |||
698 | Node* promise = NodeProperties::GetValueInput(node, 0); | |||
699 | Node* resolution = NodeProperties::GetValueInput(node, 1); | |||
700 | Node* context = NodeProperties::GetContextInput(node); | |||
701 | Effect effect{NodeProperties::GetEffectInput(node)}; | |||
702 | Control control{NodeProperties::GetControlInput(node)}; | |||
703 | ||||
704 | // Check if we know something about the {resolution}. | |||
705 | MapInference inference(broker(), resolution, effect); | |||
706 | if (!inference.HaveMaps()) return NoChange(); | |||
707 | ZoneVector<MapRef> const& resolution_maps = inference.GetMaps(); | |||
708 | ||||
709 | // Compute property access info for "then" on {resolution}. | |||
710 | ZoneVector<PropertyAccessInfo> access_infos(graph()->zone()); | |||
711 | AccessInfoFactory access_info_factory(broker(), dependencies(), | |||
712 | graph()->zone()); | |||
713 | ||||
714 | for (const MapRef& map : resolution_maps) { | |||
715 | access_infos.push_back(broker()->GetPropertyAccessInfo( | |||
716 | map, MakeRef(broker(), isolate()->factory()->then_string()), | |||
717 | AccessMode::kLoad, dependencies())); | |||
718 | } | |||
719 | PropertyAccessInfo access_info = | |||
720 | access_info_factory.FinalizePropertyAccessInfosAsOne(access_infos, | |||
721 | AccessMode::kLoad); | |||
722 | ||||
723 | // TODO(v8:11457) Support dictionary mode prototypes here. | |||
724 | if (access_info.IsInvalid() || access_info.HasDictionaryHolder()) { | |||
725 | return inference.NoChange(); | |||
726 | } | |||
727 | ||||
728 | // Only optimize when {resolution} definitely doesn't have a "then" property. | |||
729 | if (!access_info.IsNotFound()) return inference.NoChange(); | |||
730 | ||||
731 | if (!inference.RelyOnMapsViaStability(dependencies())) { | |||
732 | return inference.NoChange(); | |||
733 | } | |||
734 | ||||
735 | dependencies()->DependOnStablePrototypeChains( | |||
736 | access_info.lookup_start_object_maps(), kStartAtPrototype); | |||
737 | ||||
738 | // Simply fulfill the {promise} with the {resolution}. | |||
739 | Node* value = effect = | |||
740 | graph()->NewNode(javascript()->FulfillPromise(), promise, resolution, | |||
741 | context, effect, control); | |||
742 | ReplaceWithValue(node, value, effect, control); | |||
743 | return Replace(value); | |||
744 | } | |||
745 | ||||
746 | namespace { | |||
747 | ||||
748 | FieldAccess ForPropertyCellValue(MachineRepresentation representation, | |||
749 | Type type, MaybeHandle<Map> map, | |||
750 | NameRef const& name) { | |||
751 | WriteBarrierKind kind = kFullWriteBarrier; | |||
752 | if (representation == MachineRepresentation::kTaggedSigned) { | |||
753 | kind = kNoWriteBarrier; | |||
754 | } else if (representation == MachineRepresentation::kTaggedPointer) { | |||
755 | kind = kPointerWriteBarrier; | |||
756 | } | |||
757 | MachineType r = MachineType::TypeForRepresentation(representation); | |||
758 | FieldAccess access = { | |||
759 | kTaggedBase, PropertyCell::kValueOffset, name.object(), map, type, r, | |||
760 | kind}; | |||
761 | return access; | |||
762 | } | |||
763 | ||||
764 | } // namespace | |||
765 | ||||
766 | // TODO(neis): Try to merge this with ReduceNamedAccess by introducing a new | |||
767 | // PropertyAccessInfo kind for global accesses and using the existing mechanism | |||
768 | // for building loads/stores. | |||
769 | // Note: The "receiver" parameter is only used for DCHECKS, but that's on | |||
770 | // purpose. This way we can assert the super property access cases won't hit the | |||
771 | // code which hasn't been modified to support super property access. | |||
772 | Reduction JSNativeContextSpecialization::ReduceGlobalAccess( | |||
773 | Node* node, Node* lookup_start_object, Node* receiver, Node* value, | |||
774 | NameRef const& name, AccessMode access_mode, Node* key, | |||
775 | PropertyCellRef const& property_cell, Node* effect) { | |||
776 | if (!property_cell.Cache()) { | |||
777 | TRACE_BROKER_MISSING(broker(), "usable data for " << property_cell)do { if (broker()->tracing_enabled()) StdoutStream{} << broker()->Trace() << "Missing " << "usable data for " << property_cell << " (" << "../deps/v8/src/compiler/js-native-context-specialization.cc" << ":" << 777 << ")" << std::endl; } while (false); | |||
778 | return NoChange(); | |||
779 | } | |||
780 | ||||
781 | ObjectRef property_cell_value = property_cell.value(); | |||
782 | if (property_cell_value.IsHeapObject() && | |||
783 | property_cell_value.AsHeapObject().map().oddball_type() == | |||
784 | OddballType::kHole) { | |||
785 | // The property cell is no longer valid. | |||
786 | return NoChange(); | |||
787 | } | |||
788 | ||||
789 | PropertyDetails property_details = property_cell.property_details(); | |||
790 | PropertyCellType property_cell_type = property_details.cell_type(); | |||
791 | DCHECK_EQ(PropertyKind::kData, property_details.kind())((void) 0); | |||
792 | ||||
793 | Node* control = NodeProperties::GetControlInput(node); | |||
794 | if (effect == nullptr) { | |||
795 | effect = NodeProperties::GetEffectInput(node); | |||
796 | } | |||
797 | ||||
798 | // We have additional constraints for stores. | |||
799 | if (access_mode == AccessMode::kStore) { | |||
800 | DCHECK_EQ(receiver, lookup_start_object)((void) 0); | |||
801 | if (property_details.IsReadOnly()) { | |||
802 | // Don't even bother trying to lower stores to read-only data properties. | |||
803 | // TODO(neis): We could generate code that checks if the new value equals | |||
804 | // the old one and then does nothing or deopts, respectively. | |||
805 | return NoChange(); | |||
806 | } else if (property_cell_type == PropertyCellType::kUndefined) { | |||
807 | return NoChange(); | |||
808 | } else if (property_cell_type == PropertyCellType::kConstantType) { | |||
809 | // We rely on stability further below. | |||
810 | if (property_cell_value.IsHeapObject() && | |||
811 | !property_cell_value.AsHeapObject().map().is_stable()) { | |||
812 | return NoChange(); | |||
813 | } | |||
814 | } | |||
815 | } else if (access_mode == AccessMode::kHas) { | |||
816 | DCHECK_EQ(receiver, lookup_start_object)((void) 0); | |||
817 | // has checks cannot follow the fast-path used by loads when these | |||
818 | // conditions hold. | |||
819 | if ((property_details.IsConfigurable() || !property_details.IsReadOnly()) && | |||
820 | property_details.cell_type() != PropertyCellType::kConstant && | |||
821 | property_details.cell_type() != PropertyCellType::kUndefined) | |||
822 | return NoChange(); | |||
823 | } | |||
824 | ||||
825 | // Ensure that {key} matches the specified {name} (if {key} is given). | |||
826 | if (key != nullptr) { | |||
827 | effect = BuildCheckEqualsName(name, key, effect, control); | |||
828 | } | |||
829 | ||||
830 | // If we have a {lookup_start_object} to validate, we do so by checking that | |||
831 | // its map is the (target) global proxy's map. This guarantees that in fact | |||
832 | // the lookup start object is the global proxy. | |||
833 | // Note: we rely on the map constant below being the same as what is used in | |||
834 | // NativeContextRef::GlobalIsDetached(). | |||
835 | if (lookup_start_object != nullptr) { | |||
836 | effect = graph()->NewNode( | |||
837 | simplified()->CheckMaps( | |||
838 | CheckMapsFlag::kNone, | |||
839 | ZoneHandleSet<Map>( | |||
840 | native_context().global_proxy_object().map().object())), | |||
841 | lookup_start_object, effect, control); | |||
842 | } | |||
843 | ||||
844 | if (access_mode == AccessMode::kLoad || access_mode == AccessMode::kHas) { | |||
845 | // Load from non-configurable, read-only data property on the global | |||
846 | // object can be constant-folded, even without deoptimization support. | |||
847 | if (!property_details.IsConfigurable() && property_details.IsReadOnly()) { | |||
848 | value = access_mode == AccessMode::kHas | |||
849 | ? jsgraph()->TrueConstant() | |||
850 | : jsgraph()->Constant(property_cell_value); | |||
851 | } else { | |||
852 | // Record a code dependency on the cell if we can benefit from the | |||
853 | // additional feedback, or the global property is configurable (i.e. | |||
854 | // can be deleted or reconfigured to an accessor property). | |||
855 | if (property_details.cell_type() != PropertyCellType::kMutable || | |||
856 | property_details.IsConfigurable()) { | |||
857 | dependencies()->DependOnGlobalProperty(property_cell); | |||
858 | } | |||
859 | ||||
860 | // Load from constant/undefined global property can be constant-folded. | |||
861 | if (property_details.cell_type() == PropertyCellType::kConstant || | |||
862 | property_details.cell_type() == PropertyCellType::kUndefined) { | |||
863 | value = access_mode == AccessMode::kHas | |||
864 | ? jsgraph()->TrueConstant() | |||
865 | : jsgraph()->Constant(property_cell_value); | |||
866 | DCHECK(!property_cell_value.IsHeapObject() ||((void) 0) | |||
867 | property_cell_value.AsHeapObject().map().oddball_type() !=((void) 0) | |||
868 | OddballType::kHole)((void) 0); | |||
869 | } else { | |||
870 | DCHECK_NE(AccessMode::kHas, access_mode)((void) 0); | |||
871 | ||||
872 | // Load from constant type cell can benefit from type feedback. | |||
873 | MaybeHandle<Map> map; | |||
874 | Type property_cell_value_type = Type::NonInternal(); | |||
875 | MachineRepresentation representation = MachineRepresentation::kTagged; | |||
876 | if (property_details.cell_type() == PropertyCellType::kConstantType) { | |||
877 | // Compute proper type based on the current value in the cell. | |||
878 | if (property_cell_value.IsSmi()) { | |||
879 | property_cell_value_type = Type::SignedSmall(); | |||
880 | representation = MachineRepresentation::kTaggedSigned; | |||
881 | } else if (property_cell_value.IsHeapNumber()) { | |||
882 | property_cell_value_type = Type::Number(); | |||
883 | representation = MachineRepresentation::kTaggedPointer; | |||
884 | } else { | |||
885 | MapRef property_cell_value_map = | |||
886 | property_cell_value.AsHeapObject().map(); | |||
887 | property_cell_value_type = Type::For(property_cell_value_map); | |||
888 | representation = MachineRepresentation::kTaggedPointer; | |||
889 | ||||
890 | // We can only use the property cell value map for map check | |||
891 | // elimination if it's stable, i.e. the HeapObject wasn't | |||
892 | // mutated without the cell state being updated. | |||
893 | if (property_cell_value_map.is_stable()) { | |||
894 | dependencies()->DependOnStableMap(property_cell_value_map); | |||
895 | map = property_cell_value_map.object(); | |||
896 | } | |||
897 | } | |||
898 | } | |||
899 | value = effect = graph()->NewNode( | |||
900 | simplified()->LoadField(ForPropertyCellValue( | |||
901 | representation, property_cell_value_type, map, name)), | |||
902 | jsgraph()->Constant(property_cell), effect, control); | |||
903 | } | |||
904 | } | |||
905 | } else { | |||
906 | DCHECK_EQ(AccessMode::kStore, access_mode)((void) 0); | |||
907 | DCHECK_EQ(receiver, lookup_start_object)((void) 0); | |||
908 | DCHECK(!property_details.IsReadOnly())((void) 0); | |||
909 | switch (property_details.cell_type()) { | |||
910 | case PropertyCellType::kConstant: { | |||
911 | // Record a code dependency on the cell, and just deoptimize if the new | |||
912 | // value doesn't match the previous value stored inside the cell. | |||
913 | dependencies()->DependOnGlobalProperty(property_cell); | |||
914 | Node* check = | |||
915 | graph()->NewNode(simplified()->ReferenceEqual(), value, | |||
916 | jsgraph()->Constant(property_cell_value)); | |||
917 | effect = graph()->NewNode( | |||
918 | simplified()->CheckIf(DeoptimizeReason::kValueMismatch), check, | |||
919 | effect, control); | |||
920 | break; | |||
921 | } | |||
922 | case PropertyCellType::kConstantType: { | |||
923 | // Record a code dependency on the cell, and just deoptimize if the new | |||
924 | // value's type doesn't match the type of the previous value in the | |||
925 | // cell. | |||
926 | dependencies()->DependOnGlobalProperty(property_cell); | |||
927 | Type property_cell_value_type; | |||
928 | MachineRepresentation representation = MachineRepresentation::kTagged; | |||
929 | if (property_cell_value.IsHeapObject()) { | |||
930 | MapRef property_cell_value_map = | |||
931 | property_cell_value.AsHeapObject().map(); | |||
932 | dependencies()->DependOnStableMap(property_cell_value_map); | |||
933 | ||||
934 | // Check that the {value} is a HeapObject. | |||
935 | value = effect = graph()->NewNode(simplified()->CheckHeapObject(), | |||
936 | value, effect, control); | |||
937 | // Check {value} map against the {property_cell_value} map. | |||
938 | effect = graph()->NewNode( | |||
939 | simplified()->CheckMaps( | |||
940 | CheckMapsFlag::kNone, | |||
941 | ZoneHandleSet<Map>(property_cell_value_map.object())), | |||
942 | value, effect, control); | |||
943 | property_cell_value_type = Type::OtherInternal(); | |||
944 | representation = MachineRepresentation::kTaggedPointer; | |||
945 | } else { | |||
946 | // Check that the {value} is a Smi. | |||
947 | value = effect = graph()->NewNode( | |||
948 | simplified()->CheckSmi(FeedbackSource()), value, effect, control); | |||
949 | property_cell_value_type = Type::SignedSmall(); | |||
950 | representation = MachineRepresentation::kTaggedSigned; | |||
951 | } | |||
952 | effect = graph()->NewNode(simplified()->StoreField(ForPropertyCellValue( | |||
953 | representation, property_cell_value_type, | |||
954 | MaybeHandle<Map>(), name)), | |||
955 | jsgraph()->Constant(property_cell), value, | |||
956 | effect, control); | |||
957 | break; | |||
958 | } | |||
959 | case PropertyCellType::kMutable: { | |||
960 | // Record a code dependency on the cell, and just deoptimize if the | |||
961 | // property ever becomes read-only. | |||
962 | dependencies()->DependOnGlobalProperty(property_cell); | |||
963 | effect = graph()->NewNode( | |||
964 | simplified()->StoreField(ForPropertyCellValue( | |||
965 | MachineRepresentation::kTagged, Type::NonInternal(), | |||
966 | MaybeHandle<Map>(), name)), | |||
967 | jsgraph()->Constant(property_cell), value, effect, control); | |||
968 | break; | |||
969 | } | |||
970 | case PropertyCellType::kUndefined: | |||
971 | case PropertyCellType::kInTransition: | |||
972 | UNREACHABLE()V8_Fatal("unreachable code"); | |||
973 | } | |||
974 | } | |||
975 | ||||
976 | ReplaceWithValue(node, value, effect, control); | |||
977 | return Replace(value); | |||
978 | } | |||
979 | ||||
980 | Reduction JSNativeContextSpecialization::ReduceJSLoadGlobal(Node* node) { | |||
981 | JSLoadGlobalNode n(node); | |||
982 | LoadGlobalParameters const& p = n.Parameters(); | |||
983 | if (!p.feedback().IsValid()) return NoChange(); | |||
984 | ||||
985 | ProcessedFeedback const& processed = | |||
986 | broker()->GetFeedbackForGlobalAccess(FeedbackSource(p.feedback())); | |||
987 | if (processed.IsInsufficient()) return NoChange(); | |||
988 | ||||
989 | GlobalAccessFeedback const& feedback = processed.AsGlobalAccess(); | |||
990 | if (feedback.IsScriptContextSlot()) { | |||
991 | Effect effect = n.effect(); | |||
992 | Node* script_context = jsgraph()->Constant(feedback.script_context()); | |||
993 | Node* value = effect = | |||
994 | graph()->NewNode(javascript()->LoadContext(0, feedback.slot_index(), | |||
995 | feedback.immutable()), | |||
996 | script_context, effect); | |||
997 | ReplaceWithValue(node, value, effect); | |||
998 | return Replace(value); | |||
999 | } else if (feedback.IsPropertyCell()) { | |||
1000 | return ReduceGlobalAccess(node, nullptr, nullptr, nullptr, p.name(broker()), | |||
1001 | AccessMode::kLoad, nullptr, | |||
1002 | feedback.property_cell()); | |||
1003 | } else { | |||
1004 | DCHECK(feedback.IsMegamorphic())((void) 0); | |||
1005 | return NoChange(); | |||
1006 | } | |||
1007 | } | |||
1008 | ||||
1009 | Reduction JSNativeContextSpecialization::ReduceJSStoreGlobal(Node* node) { | |||
1010 | JSStoreGlobalNode n(node); | |||
1011 | StoreGlobalParameters const& p = n.Parameters(); | |||
1012 | Node* value = n.value(); | |||
1013 | if (!p.feedback().IsValid()) return NoChange(); | |||
1014 | ||||
1015 | ProcessedFeedback const& processed = | |||
1016 | broker()->GetFeedbackForGlobalAccess(FeedbackSource(p.feedback())); | |||
1017 | if (processed.IsInsufficient()) return NoChange(); | |||
1018 | ||||
1019 | GlobalAccessFeedback const& feedback = processed.AsGlobalAccess(); | |||
1020 | if (feedback.IsScriptContextSlot()) { | |||
1021 | if (feedback.immutable()) return NoChange(); | |||
1022 | Effect effect = n.effect(); | |||
1023 | Control control = n.control(); | |||
1024 | Node* script_context = jsgraph()->Constant(feedback.script_context()); | |||
1025 | effect = | |||
1026 | graph()->NewNode(javascript()->StoreContext(0, feedback.slot_index()), | |||
1027 | value, script_context, effect, control); | |||
1028 | ReplaceWithValue(node, value, effect, control); | |||
1029 | return Replace(value); | |||
1030 | } else if (feedback.IsPropertyCell()) { | |||
1031 | return ReduceGlobalAccess(node, nullptr, nullptr, value, p.name(broker()), | |||
1032 | AccessMode::kStore, nullptr, | |||
1033 | feedback.property_cell()); | |||
1034 | } else { | |||
1035 | DCHECK(feedback.IsMegamorphic())((void) 0); | |||
1036 | return NoChange(); | |||
1037 | } | |||
1038 | } | |||
1039 | ||||
1040 | Reduction JSNativeContextSpecialization::ReduceNamedAccess( | |||
1041 | Node* node, Node* value, NamedAccessFeedback const& feedback, | |||
1042 | AccessMode access_mode, Node* key) { | |||
1043 | DCHECK(node->opcode() == IrOpcode::kJSLoadNamed ||((void) 0) | |||
1044 | node->opcode() == IrOpcode::kJSSetNamedProperty ||((void) 0) | |||
1045 | node->opcode() == IrOpcode::kJSLoadProperty ||((void) 0) | |||
1046 | node->opcode() == IrOpcode::kJSSetKeyedProperty ||((void) 0) | |||
1047 | node->opcode() == IrOpcode::kJSDefineNamedOwnProperty ||((void) 0) | |||
1048 | node->opcode() == IrOpcode::kJSDefineKeyedOwnPropertyInLiteral ||((void) 0) | |||
1049 | node->opcode() == IrOpcode::kJSHasProperty ||((void) 0) | |||
1050 | node->opcode() == IrOpcode::kJSLoadNamedFromSuper ||((void) 0) | |||
1051 | node->opcode() == IrOpcode::kJSDefineKeyedOwnProperty)((void) 0); | |||
1052 | STATIC_ASSERT(JSLoadNamedNode::ObjectIndex() == 0 &&static_assert(JSLoadNamedNode::ObjectIndex() == 0 && JSSetNamedPropertyNode ::ObjectIndex() == 0 && JSLoadPropertyNode::ObjectIndex () == 0 && JSSetKeyedPropertyNode::ObjectIndex() == 0 && JSDefineNamedOwnPropertyNode::ObjectIndex() == 0 && JSSetNamedPropertyNode::ObjectIndex() == 0 && JSDefineKeyedOwnPropertyInLiteralNode ::ObjectIndex() == 0 && JSHasPropertyNode::ObjectIndex () == 0 && JSDefineKeyedOwnPropertyNode::ObjectIndex( ) == 0, "JSLoadNamedNode::ObjectIndex() == 0 && JSSetNamedPropertyNode::ObjectIndex() == 0 && JSLoadPropertyNode::ObjectIndex() == 0 && JSSetKeyedPropertyNode::ObjectIndex() == 0 && JSDefineNamedOwnPropertyNode::ObjectIndex() == 0 && JSSetNamedPropertyNode::ObjectIndex() == 0 && JSDefineKeyedOwnPropertyInLiteralNode::ObjectIndex() == 0 && JSHasPropertyNode::ObjectIndex() == 0 && JSDefineKeyedOwnPropertyNode::ObjectIndex() == 0" ) | |||
1053 | JSSetNamedPropertyNode::ObjectIndex() == 0 &&static_assert(JSLoadNamedNode::ObjectIndex() == 0 && JSSetNamedPropertyNode ::ObjectIndex() == 0 && JSLoadPropertyNode::ObjectIndex () == 0 && JSSetKeyedPropertyNode::ObjectIndex() == 0 && JSDefineNamedOwnPropertyNode::ObjectIndex() == 0 && JSSetNamedPropertyNode::ObjectIndex() == 0 && JSDefineKeyedOwnPropertyInLiteralNode ::ObjectIndex() == 0 && JSHasPropertyNode::ObjectIndex () == 0 && JSDefineKeyedOwnPropertyNode::ObjectIndex( ) == 0, "JSLoadNamedNode::ObjectIndex() == 0 && JSSetNamedPropertyNode::ObjectIndex() == 0 && JSLoadPropertyNode::ObjectIndex() == 0 && JSSetKeyedPropertyNode::ObjectIndex() == 0 && JSDefineNamedOwnPropertyNode::ObjectIndex() == 0 && JSSetNamedPropertyNode::ObjectIndex() == 0 && JSDefineKeyedOwnPropertyInLiteralNode::ObjectIndex() == 0 && JSHasPropertyNode::ObjectIndex() == 0 && JSDefineKeyedOwnPropertyNode::ObjectIndex() == 0" ) | |||
1054 | JSLoadPropertyNode::ObjectIndex() == 0 &&static_assert(JSLoadNamedNode::ObjectIndex() == 0 && JSSetNamedPropertyNode ::ObjectIndex() == 0 && JSLoadPropertyNode::ObjectIndex () == 0 && JSSetKeyedPropertyNode::ObjectIndex() == 0 && JSDefineNamedOwnPropertyNode::ObjectIndex() == 0 && JSSetNamedPropertyNode::ObjectIndex() == 0 && JSDefineKeyedOwnPropertyInLiteralNode ::ObjectIndex() == 0 && JSHasPropertyNode::ObjectIndex () == 0 && JSDefineKeyedOwnPropertyNode::ObjectIndex( ) == 0, "JSLoadNamedNode::ObjectIndex() == 0 && JSSetNamedPropertyNode::ObjectIndex() == 0 && JSLoadPropertyNode::ObjectIndex() == 0 && JSSetKeyedPropertyNode::ObjectIndex() == 0 && JSDefineNamedOwnPropertyNode::ObjectIndex() == 0 && JSSetNamedPropertyNode::ObjectIndex() == 0 && JSDefineKeyedOwnPropertyInLiteralNode::ObjectIndex() == 0 && JSHasPropertyNode::ObjectIndex() == 0 && JSDefineKeyedOwnPropertyNode::ObjectIndex() == 0" ) | |||
1055 | JSSetKeyedPropertyNode::ObjectIndex() == 0 &&static_assert(JSLoadNamedNode::ObjectIndex() == 0 && JSSetNamedPropertyNode ::ObjectIndex() == 0 && JSLoadPropertyNode::ObjectIndex () == 0 && JSSetKeyedPropertyNode::ObjectIndex() == 0 && JSDefineNamedOwnPropertyNode::ObjectIndex() == 0 && JSSetNamedPropertyNode::ObjectIndex() == 0 && JSDefineKeyedOwnPropertyInLiteralNode ::ObjectIndex() == 0 && JSHasPropertyNode::ObjectIndex () == 0 && JSDefineKeyedOwnPropertyNode::ObjectIndex( ) == 0, "JSLoadNamedNode::ObjectIndex() == 0 && JSSetNamedPropertyNode::ObjectIndex() == 0 && JSLoadPropertyNode::ObjectIndex() == 0 && JSSetKeyedPropertyNode::ObjectIndex() == 0 && JSDefineNamedOwnPropertyNode::ObjectIndex() == 0 && JSSetNamedPropertyNode::ObjectIndex() == 0 && JSDefineKeyedOwnPropertyInLiteralNode::ObjectIndex() == 0 && JSHasPropertyNode::ObjectIndex() == 0 && JSDefineKeyedOwnPropertyNode::ObjectIndex() == 0" ) | |||
1056 | JSDefineNamedOwnPropertyNode::ObjectIndex() == 0 &&static_assert(JSLoadNamedNode::ObjectIndex() == 0 && JSSetNamedPropertyNode ::ObjectIndex() == 0 && JSLoadPropertyNode::ObjectIndex () == 0 && JSSetKeyedPropertyNode::ObjectIndex() == 0 && JSDefineNamedOwnPropertyNode::ObjectIndex() == 0 && JSSetNamedPropertyNode::ObjectIndex() == 0 && JSDefineKeyedOwnPropertyInLiteralNode ::ObjectIndex() == 0 && JSHasPropertyNode::ObjectIndex () == 0 && JSDefineKeyedOwnPropertyNode::ObjectIndex( ) == 0, "JSLoadNamedNode::ObjectIndex() == 0 && JSSetNamedPropertyNode::ObjectIndex() == 0 && JSLoadPropertyNode::ObjectIndex() == 0 && JSSetKeyedPropertyNode::ObjectIndex() == 0 && JSDefineNamedOwnPropertyNode::ObjectIndex() == 0 && JSSetNamedPropertyNode::ObjectIndex() == 0 && JSDefineKeyedOwnPropertyInLiteralNode::ObjectIndex() == 0 && JSHasPropertyNode::ObjectIndex() == 0 && JSDefineKeyedOwnPropertyNode::ObjectIndex() == 0" ) | |||
1057 | JSSetNamedPropertyNode::ObjectIndex() == 0 &&static_assert(JSLoadNamedNode::ObjectIndex() == 0 && JSSetNamedPropertyNode ::ObjectIndex() == 0 && JSLoadPropertyNode::ObjectIndex () == 0 && JSSetKeyedPropertyNode::ObjectIndex() == 0 && JSDefineNamedOwnPropertyNode::ObjectIndex() == 0 && JSSetNamedPropertyNode::ObjectIndex() == 0 && JSDefineKeyedOwnPropertyInLiteralNode ::ObjectIndex() == 0 && JSHasPropertyNode::ObjectIndex () == 0 && JSDefineKeyedOwnPropertyNode::ObjectIndex( ) == 0, "JSLoadNamedNode::ObjectIndex() == 0 && JSSetNamedPropertyNode::ObjectIndex() == 0 && JSLoadPropertyNode::ObjectIndex() == 0 && JSSetKeyedPropertyNode::ObjectIndex() == 0 && JSDefineNamedOwnPropertyNode::ObjectIndex() == 0 && JSSetNamedPropertyNode::ObjectIndex() == 0 && JSDefineKeyedOwnPropertyInLiteralNode::ObjectIndex() == 0 && JSHasPropertyNode::ObjectIndex() == 0 && JSDefineKeyedOwnPropertyNode::ObjectIndex() == 0" ) | |||
1058 | JSDefineKeyedOwnPropertyInLiteralNode::ObjectIndex() == 0 &&static_assert(JSLoadNamedNode::ObjectIndex() == 0 && JSSetNamedPropertyNode ::ObjectIndex() == 0 && JSLoadPropertyNode::ObjectIndex () == 0 && JSSetKeyedPropertyNode::ObjectIndex() == 0 && JSDefineNamedOwnPropertyNode::ObjectIndex() == 0 && JSSetNamedPropertyNode::ObjectIndex() == 0 && JSDefineKeyedOwnPropertyInLiteralNode ::ObjectIndex() == 0 && JSHasPropertyNode::ObjectIndex () == 0 && JSDefineKeyedOwnPropertyNode::ObjectIndex( ) == 0, "JSLoadNamedNode::ObjectIndex() == 0 && JSSetNamedPropertyNode::ObjectIndex() == 0 && JSLoadPropertyNode::ObjectIndex() == 0 && JSSetKeyedPropertyNode::ObjectIndex() == 0 && JSDefineNamedOwnPropertyNode::ObjectIndex() == 0 && JSSetNamedPropertyNode::ObjectIndex() == 0 && JSDefineKeyedOwnPropertyInLiteralNode::ObjectIndex() == 0 && JSHasPropertyNode::ObjectIndex() == 0 && JSDefineKeyedOwnPropertyNode::ObjectIndex() == 0" ) | |||
1059 | JSHasPropertyNode::ObjectIndex() == 0 &&static_assert(JSLoadNamedNode::ObjectIndex() == 0 && JSSetNamedPropertyNode ::ObjectIndex() == 0 && JSLoadPropertyNode::ObjectIndex () == 0 && JSSetKeyedPropertyNode::ObjectIndex() == 0 && JSDefineNamedOwnPropertyNode::ObjectIndex() == 0 && JSSetNamedPropertyNode::ObjectIndex() == 0 && JSDefineKeyedOwnPropertyInLiteralNode ::ObjectIndex() == 0 && JSHasPropertyNode::ObjectIndex () == 0 && JSDefineKeyedOwnPropertyNode::ObjectIndex( ) == 0, "JSLoadNamedNode::ObjectIndex() == 0 && JSSetNamedPropertyNode::ObjectIndex() == 0 && JSLoadPropertyNode::ObjectIndex() == 0 && JSSetKeyedPropertyNode::ObjectIndex() == 0 && JSDefineNamedOwnPropertyNode::ObjectIndex() == 0 && JSSetNamedPropertyNode::ObjectIndex() == 0 && JSDefineKeyedOwnPropertyInLiteralNode::ObjectIndex() == 0 && JSHasPropertyNode::ObjectIndex() == 0 && JSDefineKeyedOwnPropertyNode::ObjectIndex() == 0" ) | |||
1060 | JSDefineKeyedOwnPropertyNode::ObjectIndex() == 0)static_assert(JSLoadNamedNode::ObjectIndex() == 0 && JSSetNamedPropertyNode ::ObjectIndex() == 0 && JSLoadPropertyNode::ObjectIndex () == 0 && JSSetKeyedPropertyNode::ObjectIndex() == 0 && JSDefineNamedOwnPropertyNode::ObjectIndex() == 0 && JSSetNamedPropertyNode::ObjectIndex() == 0 && JSDefineKeyedOwnPropertyInLiteralNode ::ObjectIndex() == 0 && JSHasPropertyNode::ObjectIndex () == 0 && JSDefineKeyedOwnPropertyNode::ObjectIndex( ) == 0, "JSLoadNamedNode::ObjectIndex() == 0 && JSSetNamedPropertyNode::ObjectIndex() == 0 && JSLoadPropertyNode::ObjectIndex() == 0 && JSSetKeyedPropertyNode::ObjectIndex() == 0 && JSDefineNamedOwnPropertyNode::ObjectIndex() == 0 && JSSetNamedPropertyNode::ObjectIndex() == 0 && JSDefineKeyedOwnPropertyInLiteralNode::ObjectIndex() == 0 && JSHasPropertyNode::ObjectIndex() == 0 && JSDefineKeyedOwnPropertyNode::ObjectIndex() == 0" ); | |||
1061 | STATIC_ASSERT(JSLoadNamedFromSuperNode::ReceiverIndex() == 0)static_assert(JSLoadNamedFromSuperNode::ReceiverIndex() == 0, "JSLoadNamedFromSuperNode::ReceiverIndex() == 0"); | |||
1062 | ||||
1063 | Node* context = NodeProperties::GetContextInput(node); | |||
1064 | FrameState frame_state{NodeProperties::GetFrameStateInput(node)}; | |||
1065 | Effect effect{NodeProperties::GetEffectInput(node)}; | |||
1066 | Control control{NodeProperties::GetControlInput(node)}; | |||
1067 | ||||
1068 | // receiver = the object we pass to the accessor (if any) as the "this" value. | |||
1069 | Node* receiver = NodeProperties::GetValueInput(node, 0); | |||
1070 | // lookup_start_object = the object where we start looking for the property. | |||
1071 | Node* lookup_start_object; | |||
1072 | if (node->opcode() == IrOpcode::kJSLoadNamedFromSuper) { | |||
1073 | DCHECK(FLAG_super_ic)((void) 0); | |||
1074 | JSLoadNamedFromSuperNode n(node); | |||
1075 | // Lookup start object is the __proto__ of the home object. | |||
1076 | lookup_start_object = effect = | |||
1077 | BuildLoadPrototypeFromObject(n.home_object(), effect, control); | |||
1078 | } else { | |||
1079 | lookup_start_object = receiver; | |||
1080 | } | |||
1081 | ||||
1082 | // Either infer maps from the graph or use the feedback. | |||
1083 | ZoneVector<MapRef> inferred_maps(zone()); | |||
1084 | if (!InferMaps(lookup_start_object, effect, &inferred_maps)) { | |||
1085 | for (const MapRef& map : feedback.maps()) { | |||
1086 | inferred_maps.push_back(map); | |||
1087 | } | |||
1088 | } | |||
1089 | RemoveImpossibleMaps(lookup_start_object, &inferred_maps); | |||
1090 | ||||
1091 | // Check if we have an access o.x or o.x=v where o is the target native | |||
1092 | // contexts' global proxy, and turn that into a direct access to the | |||
1093 | // corresponding global object instead. | |||
1094 | if (inferred_maps.size() == 1) { | |||
1095 | MapRef lookup_start_object_map = inferred_maps[0]; | |||
1096 | if (lookup_start_object_map.equals( | |||
1097 | native_context().global_proxy_object().map())) { | |||
1098 | if (!native_context().GlobalIsDetached()) { | |||
1099 | base::Optional<PropertyCellRef> cell = | |||
1100 | native_context().global_object().GetPropertyCell(feedback.name()); | |||
1101 | if (!cell.has_value()) return NoChange(); | |||
1102 | // Note: The map check generated by ReduceGlobalAccesses ensures that we | |||
1103 | // will deopt when/if GlobalIsDetached becomes true. | |||
1104 | return ReduceGlobalAccess(node, lookup_start_object, receiver, value, | |||
1105 | feedback.name(), access_mode, key, *cell, | |||
1106 | effect); | |||
1107 | } | |||
1108 | } | |||
1109 | } | |||
1110 | ||||
1111 | ZoneVector<PropertyAccessInfo> access_infos(zone()); | |||
1112 | { | |||
1113 | ZoneVector<PropertyAccessInfo> access_infos_for_feedback(zone()); | |||
1114 | for (const MapRef& map : inferred_maps) { | |||
1115 | if (map.is_deprecated()) continue; | |||
1116 | ||||
1117 | // TODO(v8:12547): Support writing to shared structs, which needs a write | |||
1118 | // barrier that calls Object::Share to ensure the RHS is shared. | |||
1119 | if (InstanceTypeChecker::IsJSSharedStruct(map.instance_type()) && | |||
1120 | access_mode == AccessMode::kStore) { | |||
1121 | return NoChange(); | |||
1122 | } | |||
1123 | ||||
1124 | PropertyAccessInfo access_info = broker()->GetPropertyAccessInfo( | |||
1125 | map, feedback.name(), access_mode, dependencies()); | |||
1126 | access_infos_for_feedback.push_back(access_info); | |||
1127 | } | |||
1128 | ||||
1129 | AccessInfoFactory access_info_factory(broker(), dependencies(), | |||
1130 | graph()->zone()); | |||
1131 | if (!access_info_factory.FinalizePropertyAccessInfos( | |||
1132 | access_infos_for_feedback, access_mode, &access_infos)) { | |||
1133 | return NoChange(); | |||
1134 | } | |||
1135 | } | |||
1136 | ||||
1137 | // Ensure that {key} matches the specified name (if {key} is given). | |||
1138 | if (key != nullptr) { | |||
1139 | effect = BuildCheckEqualsName(feedback.name(), key, effect, control); | |||
1140 | } | |||
1141 | ||||
1142 | // Collect call nodes to rewire exception edges. | |||
1143 | ZoneVector<Node*> if_exception_nodes(zone()); | |||
1144 | ZoneVector<Node*>* if_exceptions = nullptr; | |||
1145 | Node* if_exception = nullptr; | |||
1146 | if (NodeProperties::IsExceptionalCall(node, &if_exception)) { | |||
1147 | if_exceptions = &if_exception_nodes; | |||
1148 | } | |||
1149 | ||||
1150 | PropertyAccessBuilder access_builder(jsgraph(), broker(), dependencies()); | |||
1151 | ||||
1152 | // Check for the monomorphic cases. | |||
1153 | if (access_infos.size() == 1) { | |||
1154 | PropertyAccessInfo access_info = access_infos.front(); | |||
1155 | if (receiver != lookup_start_object) { | |||
1156 | // Super property access. lookup_start_object is a JSReceiver or | |||
1157 | // null. It can't be a number, a string etc. So trying to build the | |||
1158 | // checks in the "else if" branch doesn't make sense. | |||
1159 | access_builder.BuildCheckMaps(lookup_start_object, &effect, control, | |||
1160 | access_info.lookup_start_object_maps()); | |||
1161 | ||||
1162 | } else if (!access_builder.TryBuildStringCheck( | |||
1163 | broker(), access_info.lookup_start_object_maps(), &receiver, | |||
1164 | &effect, control) && | |||
1165 | !access_builder.TryBuildNumberCheck( | |||
1166 | broker(), access_info.lookup_start_object_maps(), &receiver, | |||
1167 | &effect, control)) { | |||
1168 | // Try to build string check or number check if possible. Otherwise build | |||
1169 | // a map check. | |||
1170 | ||||
1171 | // TryBuildStringCheck and TryBuildNumberCheck don't update the receiver | |||
1172 | // if they fail. | |||
1173 | DCHECK_EQ(receiver, lookup_start_object)((void) 0); | |||
1174 | if (HasNumberMaps(broker(), access_info.lookup_start_object_maps())) { | |||
1175 | // We need to also let Smi {receiver}s through in this case, so | |||
1176 | // we construct a diamond, guarded by the Sminess of the {receiver} | |||
1177 | // and if {receiver} is not a Smi just emit a sequence of map checks. | |||
1178 | Node* check = graph()->NewNode(simplified()->ObjectIsSmi(), receiver); | |||
1179 | Node* branch = graph()->NewNode(common()->Branch(), check, control); | |||
1180 | ||||
1181 | Node* if_true = graph()->NewNode(common()->IfTrue(), branch); | |||
1182 | Node* etrue = effect; | |||
1183 | ||||
1184 | Control if_false{graph()->NewNode(common()->IfFalse(), branch)}; | |||
1185 | Effect efalse = effect; | |||
1186 | access_builder.BuildCheckMaps(receiver, &efalse, if_false, | |||
1187 | access_info.lookup_start_object_maps()); | |||
1188 | ||||
1189 | control = graph()->NewNode(common()->Merge(2), if_true, if_false); | |||
1190 | effect = | |||
1191 | graph()->NewNode(common()->EffectPhi(2), etrue, efalse, control); | |||
1192 | } else { | |||
1193 | access_builder.BuildCheckMaps(receiver, &effect, control, | |||
1194 | access_info.lookup_start_object_maps()); | |||
1195 | } | |||
1196 | } else { | |||
1197 | // At least one of TryBuildStringCheck & TryBuildNumberCheck succeeded | |||
1198 | // and updated the receiver. Update lookup_start_object to match (they | |||
1199 | // should be the same). | |||
1200 | lookup_start_object = receiver; | |||
1201 | } | |||
1202 | ||||
1203 | // Generate the actual property access. | |||
1204 | base::Optional<ValueEffectControl> continuation = BuildPropertyAccess( | |||
1205 | lookup_start_object, receiver, value, context, frame_state, effect, | |||
1206 | control, feedback.name(), if_exceptions, access_info, access_mode); | |||
1207 | if (!continuation) { | |||
1208 | // At this point we maybe have added nodes into the graph (e.g. via | |||
1209 | // NewNode or BuildCheckMaps) in some cases but we haven't connected them | |||
1210 | // to End since we haven't called ReplaceWithValue. Since they are nodes | |||
1211 | // which are not connected with End, they will be removed by graph | |||
1212 | // trimming. | |||
1213 | return NoChange(); | |||
1214 | } | |||
1215 | value = continuation->value(); | |||
1216 | effect = continuation->effect(); | |||
1217 | control = continuation->control(); | |||
1218 | } else { | |||
1219 | // The final states for every polymorphic branch. We join them with | |||
1220 | // Merge+Phi+EffectPhi at the bottom. | |||
1221 | ZoneVector<Node*> values(zone()); | |||
1222 | ZoneVector<Node*> effects(zone()); | |||
1223 | ZoneVector<Node*> controls(zone()); | |||
1224 | ||||
1225 | Node* receiverissmi_control = nullptr; | |||
1226 | Node* receiverissmi_effect = effect; | |||
1227 | ||||
1228 | if (receiver
| |||
1229 | // Check if {receiver} may be a number. | |||
1230 | bool receiverissmi_possible = false; | |||
1231 | for (PropertyAccessInfo const& access_info : access_infos) { | |||
1232 | if (HasNumberMaps(broker(), access_info.lookup_start_object_maps())) { | |||
1233 | receiverissmi_possible = true; | |||
1234 | break; | |||
1235 | } | |||
1236 | } | |||
1237 | ||||
1238 | // Handle the case that {receiver} may be a number. | |||
1239 | if (receiverissmi_possible
| |||
1240 | Node* check = graph()->NewNode(simplified()->ObjectIsSmi(), receiver); | |||
1241 | Node* branch = graph()->NewNode(common()->Branch(), check, control); | |||
1242 | control = graph()->NewNode(common()->IfFalse(), branch); | |||
1243 | receiverissmi_control = graph()->NewNode(common()->IfTrue(), branch); | |||
1244 | receiverissmi_effect = effect; | |||
1245 | } | |||
1246 | } | |||
1247 | ||||
1248 | // Generate code for the various different property access patterns. | |||
1249 | Node* fallthrough_control = control; | |||
1250 | for (size_t j = 0; j < access_infos.size(); ++j) { | |||
1251 | PropertyAccessInfo const& access_info = access_infos[j]; | |||
1252 | Node* this_value = value; | |||
1253 | Node* this_lookup_start_object = lookup_start_object; | |||
1254 | Node* this_receiver = receiver; | |||
1255 | Effect this_effect = effect; | |||
1256 | Control this_control{fallthrough_control}; | |||
1257 | ||||
1258 | // Perform map check on {lookup_start_object}. | |||
1259 | ZoneVector<MapRef> const& lookup_start_object_maps = | |||
1260 | access_info.lookup_start_object_maps(); | |||
1261 | { | |||
1262 | // Whether to insert a dedicated MapGuard node into the | |||
1263 | // effect to be able to learn from the control flow. | |||
1264 | bool insert_map_guard = true; | |||
1265 | ||||
1266 | // Check maps for the {lookup_start_object}s. | |||
1267 | if (j == access_infos.size() - 1) { | |||
1268 | // Last map check on the fallthrough control path, do a | |||
1269 | // conditional eager deoptimization exit here. | |||
1270 | access_builder.BuildCheckMaps(lookup_start_object, &this_effect, | |||
1271 | this_control, lookup_start_object_maps); | |||
1272 | fallthrough_control = nullptr; | |||
1273 | ||||
1274 | // Don't insert a MapGuard in this case, as the CheckMaps | |||
1275 | // node already gives you all the information you need | |||
1276 | // along the effect chain. | |||
1277 | insert_map_guard = false; | |||
1278 | } else { | |||
1279 | // Explicitly branch on the {lookup_start_object_maps}. | |||
1280 | ZoneHandleSet<Map> maps; | |||
1281 | for (MapRef map : lookup_start_object_maps) { | |||
1282 | maps.insert(map.object(), graph()->zone()); | |||
1283 | } | |||
1284 | Node* check = this_effect = | |||
1285 | graph()->NewNode(simplified()->CompareMaps(maps), | |||
1286 | lookup_start_object, this_effect, this_control); | |||
1287 | Node* branch = | |||
1288 | graph()->NewNode(common()->Branch(), check, this_control); | |||
1289 | fallthrough_control = graph()->NewNode(common()->IfFalse(), branch); | |||
1290 | this_control = graph()->NewNode(common()->IfTrue(), branch); | |||
1291 | } | |||
1292 | ||||
1293 | // The Number case requires special treatment to also deal with Smis. | |||
1294 | if (HasNumberMaps(broker(), lookup_start_object_maps)) { | |||
1295 | // Join this check with the "receiver is smi" check above. | |||
1296 | DCHECK_EQ(receiver, lookup_start_object)((void) 0); | |||
1297 | DCHECK_NOT_NULL(receiverissmi_effect)((void) 0); | |||
1298 | DCHECK_NOT_NULL(receiverissmi_control)((void) 0); | |||
1299 | this_control = graph()->NewNode(common()->Merge(2), this_control, | |||
1300 | receiverissmi_control); | |||
1301 | this_effect = graph()->NewNode(common()->EffectPhi(2), this_effect, | |||
1302 | receiverissmi_effect, this_control); | |||
1303 | receiverissmi_effect = receiverissmi_control = nullptr; | |||
1304 | ||||
1305 | // The {lookup_start_object} can also be a Smi in this case, so | |||
1306 | // a MapGuard doesn't make sense for this at all. | |||
1307 | insert_map_guard = false; | |||
1308 | } | |||
1309 | ||||
1310 | // Introduce a MapGuard to learn from this on the effect chain. | |||
1311 | if (insert_map_guard) { | |||
1312 | ZoneHandleSet<Map> maps; | |||
1313 | for (MapRef map : lookup_start_object_maps) { | |||
1314 | maps.insert(map.object(), graph()->zone()); | |||
1315 | } | |||
1316 | this_effect = | |||
1317 | graph()->NewNode(simplified()->MapGuard(maps), | |||
1318 | lookup_start_object, this_effect, this_control); | |||
1319 | } | |||
1320 | ||||
1321 | // If all {lookup_start_object_maps} are Strings we also need to rename | |||
1322 | // the {lookup_start_object} here to make sure that TurboFan knows that | |||
1323 | // along this path the {this_lookup_start_object} is a String. This is | |||
1324 | // because we want strict checking of types, for example for | |||
1325 | // StringLength operators. | |||
1326 | if (HasOnlyStringMaps(broker(), lookup_start_object_maps)) { | |||
1327 | DCHECK_EQ(receiver, lookup_start_object)((void) 0); | |||
1328 | this_lookup_start_object = this_receiver = this_effect = | |||
1329 | graph()->NewNode(common()->TypeGuard(Type::String()), | |||
1330 | lookup_start_object, this_effect, this_control); | |||
1331 | } | |||
1332 | } | |||
1333 | ||||
1334 | // Generate the actual property access. | |||
1335 | base::Optional<ValueEffectControl> continuation = BuildPropertyAccess( | |||
1336 | this_lookup_start_object, this_receiver, this_value, context, | |||
1337 | frame_state, this_effect, this_control, feedback.name(), | |||
1338 | if_exceptions, access_info, access_mode); | |||
1339 | if (!continuation) { | |||
1340 | // At this point we maybe have added nodes into the graph (e.g. via | |||
1341 | // NewNode or BuildCheckMaps) in some cases but we haven't connected | |||
1342 | // them to End since we haven't called ReplaceWithValue. Since they are | |||
1343 | // nodes which are not connected with End, they will be removed by graph | |||
1344 | // trimming. | |||
1345 | return NoChange(); | |||
1346 | } | |||
1347 | values.push_back(continuation->value()); | |||
1348 | effects.push_back(continuation->effect()); | |||
1349 | controls.push_back(continuation->control()); | |||
1350 | } | |||
1351 | ||||
1352 | DCHECK_NULL(fallthrough_control)((void) 0); | |||
1353 | ||||
1354 | // Generate the final merge point for all (polymorphic) branches. | |||
1355 | int const control_count = static_cast<int>(controls.size()); | |||
1356 | if (control_count == 0) { | |||
1357 | value = effect = control = jsgraph()->Dead(); | |||
1358 | } else if (control_count == 1) { | |||
1359 | value = values.front(); | |||
1360 | effect = effects.front(); | |||
1361 | control = controls.front(); | |||
1362 | } else { | |||
1363 | control = graph()->NewNode(common()->Merge(control_count), control_count, | |||
1364 | &controls.front()); | |||
1365 | values.push_back(control); | |||
1366 | value = graph()->NewNode( | |||
1367 | common()->Phi(MachineRepresentation::kTagged, control_count), | |||
1368 | control_count + 1, &values.front()); | |||
1369 | effects.push_back(control); | |||
1370 | effect = graph()->NewNode(common()->EffectPhi(control_count), | |||
1371 | control_count + 1, &effects.front()); | |||
1372 | } | |||
1373 | } | |||
1374 | ||||
1375 | // Properly rewire IfException edges if {node} is inside a try-block. | |||
1376 | if (!if_exception_nodes.empty()) { | |||
1377 | DCHECK_NOT_NULL(if_exception)((void) 0); | |||
1378 | DCHECK_EQ(if_exceptions, &if_exception_nodes)((void) 0); | |||
1379 | int const if_exception_count = static_cast<int>(if_exceptions->size()); | |||
| ||||
1380 | Node* merge = graph()->NewNode(common()->Merge(if_exception_count), | |||
1381 | if_exception_count, &if_exceptions->front()); | |||
1382 | if_exceptions->push_back(merge); | |||
1383 | Node* ephi = | |||
1384 | graph()->NewNode(common()->EffectPhi(if_exception_count), | |||
1385 | if_exception_count + 1, &if_exceptions->front()); | |||
1386 | Node* phi = graph()->NewNode( | |||
1387 | common()->Phi(MachineRepresentation::kTagged, if_exception_count), | |||
1388 | if_exception_count + 1, &if_exceptions->front()); | |||
1389 | ReplaceWithValue(if_exception, phi, ephi, merge); | |||
1390 | } | |||
1391 | ||||
1392 | ReplaceWithValue(node, value, effect, control); | |||
1393 | return Replace(value); | |||
1394 | } | |||
1395 | ||||
1396 | Reduction JSNativeContextSpecialization::ReduceJSLoadNamed(Node* node) { | |||
1397 | JSLoadNamedNode n(node); | |||
1398 | NamedAccess const& p = n.Parameters(); | |||
1399 | Node* const receiver = n.object(); | |||
1400 | NameRef name = p.name(broker()); | |||
1401 | ||||
1402 | // Check if we have a constant receiver. | |||
1403 | HeapObjectMatcher m(receiver); | |||
1404 | if (m.HasResolvedValue()) { | |||
1405 | ObjectRef object = m.Ref(broker()); | |||
1406 | if (object.IsJSFunction() && | |||
1407 | name.equals(MakeRef(broker(), factory()->prototype_string()))) { | |||
1408 | // Optimize "prototype" property of functions. | |||
1409 | JSFunctionRef function = object.AsJSFunction(); | |||
1410 | // TODO(neis): Remove the has_prototype_slot condition once the broker is | |||
1411 | // always enabled. | |||
1412 | if (!function.map().has_prototype_slot() || | |||
1413 | !function.has_instance_prototype(dependencies()) || | |||
1414 | function.PrototypeRequiresRuntimeLookup(dependencies())) { | |||
1415 | return NoChange(); | |||
1416 | } | |||
1417 | ObjectRef prototype = dependencies()->DependOnPrototypeProperty(function); | |||
1418 | Node* value = jsgraph()->Constant(prototype); | |||
1419 | ReplaceWithValue(node, value); | |||
1420 | return Replace(value); | |||
1421 | } else if (object.IsString() && | |||
1422 | name.equals(MakeRef(broker(), factory()->length_string()))) { | |||
1423 | // Constant-fold "length" property on constant strings. | |||
1424 | if (!object.AsString().length().has_value()) return NoChange(); | |||
1425 | Node* value = jsgraph()->Constant(object.AsString().length().value()); | |||
1426 | ReplaceWithValue(node, value); | |||
1427 | return Replace(value); | |||
1428 | } | |||
1429 | } | |||
1430 | ||||
1431 | if (!p.feedback().IsValid()) return NoChange(); | |||
1432 | return ReducePropertyAccess(node, nullptr, name, jsgraph()->Dead(), | |||
1433 | FeedbackSource(p.feedback()), AccessMode::kLoad); | |||
1434 | } | |||
1435 | ||||
1436 | Reduction JSNativeContextSpecialization::ReduceJSLoadNamedFromSuper( | |||
1437 | Node* node) { | |||
1438 | JSLoadNamedFromSuperNode n(node); | |||
1439 | NamedAccess const& p = n.Parameters(); | |||
1440 | NameRef name = p.name(broker()); | |||
1441 | ||||
1442 | if (!p.feedback().IsValid()) return NoChange(); | |||
1443 | return ReducePropertyAccess(node, nullptr, name, jsgraph()->Dead(), | |||
1444 | FeedbackSource(p.feedback()), AccessMode::kLoad); | |||
1445 | } | |||
1446 | ||||
1447 | Reduction JSNativeContextSpecialization::ReduceJSGetIterator(Node* node) { | |||
1448 | JSGetIteratorNode n(node); | |||
1449 | GetIteratorParameters const& p = n.Parameters(); | |||
1450 | ||||
1451 | TNode<Object> receiver = n.receiver(); | |||
1452 | TNode<Object> context = n.context(); | |||
1453 | FrameState frame_state = n.frame_state(); | |||
1454 | Effect effect = n.effect(); | |||
1455 | Control control = n.control(); | |||
1456 | ||||
1457 | // Load iterator property operator | |||
1458 | NameRef iterator_symbol = MakeRef(broker(), factory()->iterator_symbol()); | |||
1459 | const Operator* load_op = | |||
1460 | javascript()->LoadNamed(iterator_symbol, p.loadFeedback()); | |||
1461 | ||||
1462 | // Lazy deopt of the load iterator property | |||
1463 | // TODO(v8:10047): Use TaggedIndexConstant here once deoptimizer supports it. | |||
1464 | Node* call_slot = jsgraph()->SmiConstant(p.callFeedback().slot.ToInt()); | |||
1465 | Node* call_feedback = jsgraph()->HeapConstant(p.callFeedback().vector); | |||
1466 | Node* lazy_deopt_parameters[] = {receiver, call_slot, call_feedback}; | |||
1467 | Node* lazy_deopt_frame_state = CreateStubBuiltinContinuationFrameState( | |||
1468 | jsgraph(), Builtin::kGetIteratorWithFeedbackLazyDeoptContinuation, | |||
1469 | context, lazy_deopt_parameters, arraysize(lazy_deopt_parameters)(sizeof(ArraySizeHelper(lazy_deopt_parameters))), | |||
1470 | frame_state, ContinuationFrameStateMode::LAZY); | |||
1471 | Node* load_property = | |||
1472 | graph()->NewNode(load_op, receiver, n.feedback_vector(), context, | |||
1473 | lazy_deopt_frame_state, effect, control); | |||
1474 | effect = load_property; | |||
1475 | control = load_property; | |||
1476 | ||||
1477 | // Handle exception path for the load named property | |||
1478 | Node* iterator_exception_node = nullptr; | |||
1479 | if (NodeProperties::IsExceptionalCall(node, &iterator_exception_node)) { | |||
1480 | // If there exists an exception node for the given iterator_node, create a | |||
1481 | // pair of IfException/IfSuccess nodes on the current control path. The uses | |||
1482 | // of new exception node are merged with the original exception node. The | |||
1483 | // IfSuccess node is returned as a control path for further reduction. | |||
1484 | Node* exception_node = | |||
1485 | graph()->NewNode(common()->IfException(), effect, control); | |||
1486 | Node* if_success = graph()->NewNode(common()->IfSuccess(), control); | |||
1487 | ||||
1488 | // Use dead_node as a placeholder for the original exception node until | |||
1489 | // its uses are rewired to the nodes merging the exceptions | |||
1490 | Node* dead_node = jsgraph()->Dead(); | |||
1491 | Node* merge_node = | |||
1492 | graph()->NewNode(common()->Merge(2), dead_node, exception_node); | |||
1493 | Node* effect_phi = graph()->NewNode(common()->EffectPhi(2), dead_node, | |||
1494 | exception_node, merge_node); | |||
1495 | Node* phi = | |||
1496 | graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2), | |||
1497 | dead_node, exception_node, merge_node); | |||
1498 | ReplaceWithValue(iterator_exception_node, phi, effect_phi, merge_node); | |||
1499 | phi->ReplaceInput(0, iterator_exception_node); | |||
1500 | effect_phi->ReplaceInput(0, iterator_exception_node); | |||
1501 | merge_node->ReplaceInput(0, iterator_exception_node); | |||
1502 | control = if_success; | |||
1503 | } | |||
1504 | ||||
1505 | // Eager deopt of call iterator property | |||
1506 | Node* parameters[] = {receiver, load_property, call_slot, call_feedback}; | |||
1507 | Node* eager_deopt_frame_state = CreateStubBuiltinContinuationFrameState( | |||
1508 | jsgraph(), Builtin::kCallIteratorWithFeedback, context, parameters, | |||
1509 | arraysize(parameters)(sizeof(ArraySizeHelper(parameters))), frame_state, ContinuationFrameStateMode::EAGER); | |||
1510 | Node* deopt_checkpoint = graph()->NewNode( | |||
1511 | common()->Checkpoint(), eager_deopt_frame_state, effect, control); | |||
1512 | effect = deopt_checkpoint; | |||
1513 | ||||
1514 | // Call iterator property operator | |||
1515 | ProcessedFeedback const& feedback = | |||
1516 | broker()->GetFeedbackForCall(p.callFeedback()); | |||
1517 | SpeculationMode mode = feedback.IsInsufficient() | |||
1518 | ? SpeculationMode::kDisallowSpeculation | |||
1519 | : feedback.AsCall().speculation_mode(); | |||
1520 | const Operator* call_op = javascript()->Call( | |||
1521 | JSCallNode::ArityForArgc(0), CallFrequency(), p.callFeedback(), | |||
1522 | ConvertReceiverMode::kNotNullOrUndefined, mode, | |||
1523 | CallFeedbackRelation::kTarget); | |||
1524 | Node* call_property = | |||
1525 | graph()->NewNode(call_op, load_property, receiver, n.feedback_vector(), | |||
1526 | context, frame_state, effect, control); | |||
1527 | ||||
1528 | return Replace(call_property); | |||
1529 | } | |||
1530 | ||||
1531 | Reduction JSNativeContextSpecialization::ReduceJSSetNamedProperty(Node* node) { | |||
1532 | JSSetNamedPropertyNode n(node); | |||
1533 | NamedAccess const& p = n.Parameters(); | |||
1534 | if (!p.feedback().IsValid()) return NoChange(); | |||
1535 | return ReducePropertyAccess(node, nullptr, p.name(broker()), n.value(), | |||
1536 | FeedbackSource(p.feedback()), AccessMode::kStore); | |||
1537 | } | |||
1538 | ||||
1539 | Reduction JSNativeContextSpecialization::ReduceJSDefineNamedOwnProperty( | |||
1540 | Node* node) { | |||
1541 | JSDefineNamedOwnPropertyNode n(node); | |||
1542 | DefineNamedOwnPropertyParameters const& p = n.Parameters(); | |||
1543 | if (!p.feedback().IsValid()) return NoChange(); | |||
1544 | return ReducePropertyAccess(node, nullptr, p.name(broker()), n.value(), | |||
1545 | FeedbackSource(p.feedback()), | |||
1546 | AccessMode::kStoreInLiteral); | |||
1547 | } | |||
1548 | ||||
1549 | Reduction JSNativeContextSpecialization::ReduceElementAccessOnString( | |||
1550 | Node* node, Node* index, Node* value, KeyedAccessMode const& keyed_mode) { | |||
1551 | Node* receiver = NodeProperties::GetValueInput(node, 0); | |||
1552 | Node* effect = NodeProperties::GetEffectInput(node); | |||
1553 | Node* control = NodeProperties::GetControlInput(node); | |||
1554 | ||||
1555 | // Strings are immutable in JavaScript. | |||
1556 | if (keyed_mode.access_mode() == AccessMode::kStore) return NoChange(); | |||
1557 | ||||
1558 | // `in` cannot be used on strings. | |||
1559 | if (keyed_mode.access_mode() == AccessMode::kHas) return NoChange(); | |||
1560 | ||||
1561 | // Ensure that the {receiver} is actually a String. | |||
1562 | receiver = effect = graph()->NewNode( | |||
1563 | simplified()->CheckString(FeedbackSource()), receiver, effect, control); | |||
1564 | ||||
1565 | // Determine the {receiver} length. | |||
1566 | Node* length = graph()->NewNode(simplified()->StringLength(), receiver); | |||
1567 | ||||
1568 | // Load the single character string from {receiver} or yield undefined | |||
1569 | // if the {index} is out of bounds (depending on the {load_mode}). | |||
1570 | value = BuildIndexedStringLoad(receiver, index, length, &effect, &control, | |||
1571 | keyed_mode.load_mode()); | |||
1572 | ||||
1573 | ReplaceWithValue(node, value, effect, control); | |||
1574 | return Replace(value); | |||
1575 | } | |||
1576 | ||||
1577 | namespace { | |||
1578 | ||||
1579 | base::Optional<JSTypedArrayRef> GetTypedArrayConstant(JSHeapBroker* broker, | |||
1580 | Node* receiver) { | |||
1581 | HeapObjectMatcher m(receiver); | |||
1582 | if (!m.HasResolvedValue()) return base::nullopt; | |||
1583 | ObjectRef object = m.Ref(broker); | |||
1584 | if (!object.IsJSTypedArray()) return base::nullopt; | |||
1585 | JSTypedArrayRef typed_array = object.AsJSTypedArray(); | |||
1586 | if (typed_array.is_on_heap()) return base::nullopt; | |||
1587 | return typed_array; | |||
1588 | } | |||
1589 | ||||
1590 | } // namespace | |||
1591 | ||||
1592 | void JSNativeContextSpecialization::RemoveImpossibleMaps( | |||
1593 | Node* object, ZoneVector<MapRef>* maps) const { | |||
1594 | base::Optional<MapRef> root_map = InferRootMap(object); | |||
1595 | if (root_map.has_value() && !root_map->is_abandoned_prototype_map()) { | |||
1596 | maps->erase(std::remove_if(maps->begin(), maps->end(), | |||
1597 | [root_map](const MapRef& map) { | |||
1598 | return map.is_abandoned_prototype_map() || | |||
1599 | !map.FindRootMap().equals(*root_map); | |||
1600 | }), | |||
1601 | maps->end()); | |||
1602 | } | |||
1603 | } | |||
1604 | ||||
1605 | // Possibly refine the feedback using inferred map information from the graph. | |||
1606 | ElementAccessFeedback const& | |||
1607 | JSNativeContextSpecialization::TryRefineElementAccessFeedback( | |||
1608 | ElementAccessFeedback const& feedback, Node* receiver, | |||
1609 | Effect effect) const { | |||
1610 | AccessMode access_mode = feedback.keyed_mode().access_mode(); | |||
1611 | bool use_inference = | |||
1612 | access_mode == AccessMode::kLoad || access_mode == AccessMode::kHas; | |||
1613 | if (!use_inference) return feedback; | |||
1614 | ||||
1615 | ZoneVector<MapRef> inferred_maps(zone()); | |||
1616 | if (!InferMaps(receiver, effect, &inferred_maps)) return feedback; | |||
1617 | ||||
1618 | RemoveImpossibleMaps(receiver, &inferred_maps); | |||
1619 | // TODO(neis): After Refine, the resulting feedback can still contain | |||
1620 | // impossible maps when a target is kept only because more than one of its | |||
1621 | // sources was inferred. Think of a way to completely rule out impossible | |||
1622 | // maps. | |||
1623 | return feedback.Refine(broker(), inferred_maps); | |||
1624 | } | |||
1625 | ||||
1626 | Reduction JSNativeContextSpecialization::ReduceElementAccess( | |||
1627 | Node* node, Node* index, Node* value, | |||
1628 | ElementAccessFeedback const& feedback) { | |||
1629 | DCHECK(node->opcode() == IrOpcode::kJSLoadProperty ||((void) 0) | |||
1630 | node->opcode() == IrOpcode::kJSSetKeyedProperty ||((void) 0) | |||
1631 | node->opcode() == IrOpcode::kJSStoreInArrayLiteral ||((void) 0) | |||
1632 | node->opcode() == IrOpcode::kJSDefineKeyedOwnPropertyInLiteral ||((void) 0) | |||
1633 | node->opcode() == IrOpcode::kJSHasProperty ||((void) 0) | |||
1634 | node->opcode() == IrOpcode::kJSDefineKeyedOwnProperty)((void) 0); | |||
1635 | STATIC_ASSERT(JSLoadPropertyNode::ObjectIndex() == 0 &&static_assert(JSLoadPropertyNode::ObjectIndex() == 0 && JSSetKeyedPropertyNode::ObjectIndex() == 0 && JSStoreInArrayLiteralNode ::ArrayIndex() == 0 && JSDefineKeyedOwnPropertyInLiteralNode ::ObjectIndex() == 0 && JSHasPropertyNode::ObjectIndex () == 0, "JSLoadPropertyNode::ObjectIndex() == 0 && JSSetKeyedPropertyNode::ObjectIndex() == 0 && JSStoreInArrayLiteralNode::ArrayIndex() == 0 && JSDefineKeyedOwnPropertyInLiteralNode::ObjectIndex() == 0 && JSHasPropertyNode::ObjectIndex() == 0" ) | |||
1636 | JSSetKeyedPropertyNode::ObjectIndex() == 0 &&static_assert(JSLoadPropertyNode::ObjectIndex() == 0 && JSSetKeyedPropertyNode::ObjectIndex() == 0 && JSStoreInArrayLiteralNode ::ArrayIndex() == 0 && JSDefineKeyedOwnPropertyInLiteralNode ::ObjectIndex() == 0 && JSHasPropertyNode::ObjectIndex () == 0, "JSLoadPropertyNode::ObjectIndex() == 0 && JSSetKeyedPropertyNode::ObjectIndex() == 0 && JSStoreInArrayLiteralNode::ArrayIndex() == 0 && JSDefineKeyedOwnPropertyInLiteralNode::ObjectIndex() == 0 && JSHasPropertyNode::ObjectIndex() == 0" ) | |||
1637 | JSStoreInArrayLiteralNode::ArrayIndex() == 0 &&static_assert(JSLoadPropertyNode::ObjectIndex() == 0 && JSSetKeyedPropertyNode::ObjectIndex() == 0 && JSStoreInArrayLiteralNode ::ArrayIndex() == 0 && JSDefineKeyedOwnPropertyInLiteralNode ::ObjectIndex() == 0 && JSHasPropertyNode::ObjectIndex () == 0, "JSLoadPropertyNode::ObjectIndex() == 0 && JSSetKeyedPropertyNode::ObjectIndex() == 0 && JSStoreInArrayLiteralNode::ArrayIndex() == 0 && JSDefineKeyedOwnPropertyInLiteralNode::ObjectIndex() == 0 && JSHasPropertyNode::ObjectIndex() == 0" ) | |||
1638 | JSDefineKeyedOwnPropertyInLiteralNode::ObjectIndex() == 0 &&static_assert(JSLoadPropertyNode::ObjectIndex() == 0 && JSSetKeyedPropertyNode::ObjectIndex() == 0 && JSStoreInArrayLiteralNode ::ArrayIndex() == 0 && JSDefineKeyedOwnPropertyInLiteralNode ::ObjectIndex() == 0 && JSHasPropertyNode::ObjectIndex () == 0, "JSLoadPropertyNode::ObjectIndex() == 0 && JSSetKeyedPropertyNode::ObjectIndex() == 0 && JSStoreInArrayLiteralNode::ArrayIndex() == 0 && JSDefineKeyedOwnPropertyInLiteralNode::ObjectIndex() == 0 && JSHasPropertyNode::ObjectIndex() == 0" ) | |||
1639 | JSHasPropertyNode::ObjectIndex() == 0)static_assert(JSLoadPropertyNode::ObjectIndex() == 0 && JSSetKeyedPropertyNode::ObjectIndex() == 0 && JSStoreInArrayLiteralNode ::ArrayIndex() == 0 && JSDefineKeyedOwnPropertyInLiteralNode ::ObjectIndex() == 0 && JSHasPropertyNode::ObjectIndex () == 0, "JSLoadPropertyNode::ObjectIndex() == 0 && JSSetKeyedPropertyNode::ObjectIndex() == 0 && JSStoreInArrayLiteralNode::ArrayIndex() == 0 && JSDefineKeyedOwnPropertyInLiteralNode::ObjectIndex() == 0 && JSHasPropertyNode::ObjectIndex() == 0" ); | |||
1640 | ||||
1641 | Node* receiver = NodeProperties::GetValueInput(node, 0); | |||
1642 | Effect effect{NodeProperties::GetEffectInput(node)}; | |||
1643 | Control control{NodeProperties::GetControlInput(node)}; | |||
1644 | ||||
1645 | // TODO(neis): It's odd that we do optimizations below that don't really care | |||
1646 | // about the feedback, but we don't do them when the feedback is megamorphic. | |||
1647 | if (feedback.transition_groups().empty()) return NoChange(); | |||
1648 | ||||
1649 | ElementAccessFeedback const& refined_feedback = | |||
1650 | TryRefineElementAccessFeedback(feedback, receiver, effect); | |||
1651 | ||||
1652 | AccessMode access_mode = refined_feedback.keyed_mode().access_mode(); | |||
1653 | if ((access_mode == AccessMode::kLoad || access_mode == AccessMode::kHas) && | |||
1654 | receiver->opcode() == IrOpcode::kHeapConstant) { | |||
1655 | Reduction reduction = ReduceElementLoadFromHeapConstant( | |||
1656 | node, index, access_mode, refined_feedback.keyed_mode().load_mode()); | |||
1657 | if (reduction.Changed()) return reduction; | |||
1658 | } | |||
1659 | ||||
1660 | if (!refined_feedback.transition_groups().empty() && | |||
1661 | refined_feedback.HasOnlyStringMaps(broker())) { | |||
1662 | return ReduceElementAccessOnString(node, index, value, | |||
1663 | refined_feedback.keyed_mode()); | |||
1664 | } | |||
1665 | ||||
1666 | AccessInfoFactory access_info_factory(broker(), dependencies(), | |||
1667 | graph()->zone()); | |||
1668 | ZoneVector<ElementAccessInfo> access_infos(zone()); | |||
1669 | if (!access_info_factory.ComputeElementAccessInfos(refined_feedback, | |||
1670 | &access_infos) || | |||
1671 | access_infos.empty()) { | |||
1672 | return NoChange(); | |||
1673 | } | |||
1674 | ||||
1675 | // For holey stores or growing stores, we need to check that the prototype | |||
1676 | // chain contains no setters for elements, and we need to guard those checks | |||
1677 | // via code dependencies on the relevant prototype maps. | |||
1678 | if (access_mode == AccessMode::kStore) { | |||
1679 | // TODO(turbofan): We could have a fast path here, that checks for the | |||
1680 | // common case of Array or Object prototype only and therefore avoids | |||
1681 | // the zone allocation of this vector. | |||
1682 | ZoneVector<MapRef> prototype_maps(zone()); | |||
1683 | for (ElementAccessInfo const& access_info : access_infos) { | |||
1684 | for (MapRef receiver_map : access_info.lookup_start_object_maps()) { | |||
1685 | // If the {receiver_map} has a prototype and its elements backing | |||
1686 | // store is either holey, or we have a potentially growing store, | |||
1687 | // then we need to check that all prototypes have stable maps with | |||
1688 | // fast elements (and we need to guard against changes to that below). | |||
1689 | if ((IsHoleyOrDictionaryElementsKind(receiver_map.elements_kind()) || | |||
1690 | IsGrowStoreMode(feedback.keyed_mode().store_mode())) && | |||
1691 | !receiver_map.HasOnlyStablePrototypesWithFastElements( | |||
1692 | &prototype_maps)) { | |||
1693 | return NoChange(); | |||
1694 | } | |||
1695 | ||||
1696 | // TODO(v8:12547): Support writing to shared structs, which needs a | |||
1697 | // write barrier that calls Object::Share to ensure the RHS is shared. | |||
1698 | if (InstanceTypeChecker::IsJSSharedStruct( | |||
1699 | receiver_map.instance_type())) { | |||
1700 | return NoChange(); | |||
1701 | } | |||
1702 | } | |||
1703 | } | |||
1704 | for (MapRef const& prototype_map : prototype_maps) { | |||
1705 | dependencies()->DependOnStableMap(prototype_map); | |||
1706 | } | |||
1707 | } else if (access_mode == AccessMode::kHas) { | |||
1708 | // If we have any fast arrays, we need to check and depend on | |||
1709 | // NoElementsProtector. | |||
1710 | for (ElementAccessInfo const& access_info : access_infos) { | |||
1711 | if (IsFastElementsKind(access_info.elements_kind())) { | |||
1712 | if (!dependencies()->DependOnNoElementsProtector()) return NoChange(); | |||
1713 | break; | |||
1714 | } | |||
1715 | } | |||
1716 | } | |||
1717 | ||||
1718 | // Check for the monomorphic case. | |||
1719 | PropertyAccessBuilder access_builder(jsgraph(), broker(), dependencies()); | |||
1720 | if (access_infos.size() == 1) { | |||
1721 | ElementAccessInfo access_info = access_infos.front(); | |||
1722 | ||||
1723 | // Perform possible elements kind transitions. | |||
1724 | MapRef transition_target = access_info.lookup_start_object_maps().front(); | |||
1725 | for (MapRef transition_source : access_info.transition_sources()) { | |||
1726 | DCHECK_EQ(access_info.lookup_start_object_maps().size(), 1)((void) 0); | |||
1727 | effect = graph()->NewNode( | |||
1728 | simplified()->TransitionElementsKind(ElementsTransition( | |||
1729 | IsSimpleMapChangeTransition(transition_source.elements_kind(), | |||
1730 | transition_target.elements_kind()) | |||
1731 | ? ElementsTransition::kFastTransition | |||
1732 | : ElementsTransition::kSlowTransition, | |||
1733 | transition_source.object(), transition_target.object())), | |||
1734 | receiver, effect, control); | |||
1735 | } | |||
1736 | ||||
1737 | // TODO(turbofan): The effect/control linearization will not find a | |||
1738 | // FrameState after the StoreField or Call that is generated for the | |||
1739 | // elements kind transition above. This is because those operators | |||
1740 | // don't have the kNoWrite flag on it, even though they are not | |||
1741 | // observable by JavaScript. | |||
1742 | Node* frame_state = | |||
1743 | NodeProperties::FindFrameStateBefore(node, jsgraph()->Dead()); | |||
1744 | effect = | |||
1745 | graph()->NewNode(common()->Checkpoint(), frame_state, effect, control); | |||
1746 | ||||
1747 | // Perform map check on the {receiver}. | |||
1748 | access_builder.BuildCheckMaps(receiver, &effect, control, | |||
1749 | access_info.lookup_start_object_maps()); | |||
1750 | ||||
1751 | // Access the actual element. | |||
1752 | ValueEffectControl continuation = | |||
1753 | BuildElementAccess(receiver, index, value, effect, control, access_info, | |||
1754 | feedback.keyed_mode()); | |||
1755 | value = continuation.value(); | |||
1756 | effect = continuation.effect(); | |||
1757 | control = continuation.control(); | |||
1758 | } else { | |||
1759 | // The final states for every polymorphic branch. We join them with | |||
1760 | // Merge+Phi+EffectPhi at the bottom. | |||
1761 | ZoneVector<Node*> values(zone()); | |||
1762 | ZoneVector<Node*> effects(zone()); | |||
1763 | ZoneVector<Node*> controls(zone()); | |||
1764 | ||||
1765 | // Generate code for the various different element access patterns. | |||
1766 | Node* fallthrough_control = control; | |||
1767 | for (size_t j = 0; j < access_infos.size(); ++j) { | |||
1768 | ElementAccessInfo const& access_info = access_infos[j]; | |||
1769 | Node* this_receiver = receiver; | |||
1770 | Node* this_value = value; | |||
1771 | Node* this_index = index; | |||
1772 | Effect this_effect = effect; | |||
1773 | Control this_control{fallthrough_control}; | |||
1774 | ||||
1775 | // Perform possible elements kind transitions. | |||
1776 | MapRef transition_target = access_info.lookup_start_object_maps().front(); | |||
1777 | for (MapRef transition_source : access_info.transition_sources()) { | |||
1778 | DCHECK_EQ(access_info.lookup_start_object_maps().size(), 1)((void) 0); | |||
1779 | this_effect = graph()->NewNode( | |||
1780 | simplified()->TransitionElementsKind(ElementsTransition( | |||
1781 | IsSimpleMapChangeTransition(transition_source.elements_kind(), | |||
1782 | transition_target.elements_kind()) | |||
1783 | ? ElementsTransition::kFastTransition | |||
1784 | : ElementsTransition::kSlowTransition, | |||
1785 | transition_source.object(), transition_target.object())), | |||
1786 | receiver, this_effect, this_control); | |||
1787 | } | |||
1788 | ||||
1789 | // Perform map check(s) on {receiver}. | |||
1790 | ZoneVector<MapRef> const& receiver_maps = | |||
1791 | access_info.lookup_start_object_maps(); | |||
1792 | if (j == access_infos.size() - 1) { | |||
1793 | // Last map check on the fallthrough control path, do a | |||
1794 | // conditional eager deoptimization exit here. | |||
1795 | access_builder.BuildCheckMaps(receiver, &this_effect, this_control, | |||
1796 | receiver_maps); | |||
1797 | fallthrough_control = nullptr; | |||
1798 | } else { | |||
1799 | // Explicitly branch on the {receiver_maps}. | |||
1800 | ZoneHandleSet<Map> maps; | |||
1801 | for (MapRef map : receiver_maps) { | |||
1802 | maps.insert(map.object(), graph()->zone()); | |||
1803 | } | |||
1804 | Node* check = this_effect = | |||
1805 | graph()->NewNode(simplified()->CompareMaps(maps), receiver, | |||
1806 | this_effect, fallthrough_control); | |||
1807 | Node* branch = | |||
1808 | graph()->NewNode(common()->Branch(), check, fallthrough_control); | |||
1809 | fallthrough_control = graph()->NewNode(common()->IfFalse(), branch); | |||
1810 | this_control = graph()->NewNode(common()->IfTrue(), branch); | |||
1811 | ||||
1812 | // Introduce a MapGuard to learn from this on the effect chain. | |||
1813 | this_effect = graph()->NewNode(simplified()->MapGuard(maps), receiver, | |||
1814 | this_effect, this_control); | |||
1815 | } | |||
1816 | ||||
1817 | // Access the actual element. | |||
1818 | ValueEffectControl continuation = | |||
1819 | BuildElementAccess(this_receiver, this_index, this_value, this_effect, | |||
1820 | this_control, access_info, feedback.keyed_mode()); | |||
1821 | values.push_back(continuation.value()); | |||
1822 | effects.push_back(continuation.effect()); | |||
1823 | controls.push_back(continuation.control()); | |||
1824 | } | |||
1825 | ||||
1826 | DCHECK_NULL(fallthrough_control)((void) 0); | |||
1827 | ||||
1828 | // Generate the final merge point for all (polymorphic) branches. | |||
1829 | int const control_count = static_cast<int>(controls.size()); | |||
1830 | if (control_count == 0) { | |||
1831 | value = effect = control = jsgraph()->Dead(); | |||
1832 | } else if (control_count == 1) { | |||
1833 | value = values.front(); | |||
1834 | effect = effects.front(); | |||
1835 | control = controls.front(); | |||
1836 | } else { | |||
1837 | control = graph()->NewNode(common()->Merge(control_count), control_count, | |||
1838 | &controls.front()); | |||
1839 | values.push_back(control); | |||
1840 | value = graph()->NewNode( | |||
1841 | common()->Phi(MachineRepresentation::kTagged, control_count), | |||
1842 | control_count + 1, &values.front()); | |||
1843 | effects.push_back(control); | |||
1844 | effect = graph()->NewNode(common()->EffectPhi(control_count), | |||
1845 | control_count + 1, &effects.front()); | |||
1846 | } | |||
1847 | } | |||
1848 | ||||
1849 | ReplaceWithValue(node, value, effect, control); | |||
1850 | return Replace(value); | |||
1851 | } | |||
1852 | ||||
1853 | Reduction JSNativeContextSpecialization::ReduceElementLoadFromHeapConstant( | |||
1854 | Node* node, Node* key, AccessMode access_mode, | |||
1855 | KeyedAccessLoadMode load_mode) { | |||
1856 | DCHECK(node->opcode() == IrOpcode::kJSLoadProperty ||((void) 0) | |||
1857 | node->opcode() == IrOpcode::kJSHasProperty)((void) 0); | |||
1858 | Node* receiver = NodeProperties::GetValueInput(node, 0); | |||
1859 | Node* effect = NodeProperties::GetEffectInput(node); | |||
1860 | Node* control = NodeProperties::GetControlInput(node); | |||
1861 | ||||
1862 | HeapObjectMatcher mreceiver(receiver); | |||
1863 | HeapObjectRef receiver_ref = mreceiver.Ref(broker()); | |||
1864 | if (receiver_ref.map().oddball_type() == OddballType::kHole || | |||
1865 | receiver_ref.map().oddball_type() == OddballType::kNull || | |||
1866 | receiver_ref.map().oddball_type() == OddballType::kUndefined || | |||
1867 | // The 'in' operator throws a TypeError on primitive values. | |||
1868 | (receiver_ref.IsString() && access_mode == AccessMode::kHas)) { | |||
1869 | return NoChange(); | |||
1870 | } | |||
1871 | ||||
1872 | // Check whether we're accessing a known element on the {receiver} and can | |||
1873 | // constant-fold the load. | |||
1874 | NumberMatcher mkey(key); | |||
1875 | if (mkey.IsInteger() && | |||
1876 | mkey.IsInRange(0.0, static_cast<double>(JSObject::kMaxElementIndex))) { | |||
1877 | STATIC_ASSERT(JSObject::kMaxElementIndex <= kMaxUInt32)static_assert(JSObject::kMaxElementIndex <= kMaxUInt32, "JSObject::kMaxElementIndex <= kMaxUInt32" ); | |||
1878 | const uint32_t index = static_cast<uint32_t>(mkey.ResolvedValue()); | |||
1879 | base::Optional<ObjectRef> element; | |||
1880 | ||||
1881 | if (receiver_ref.IsJSObject()) { | |||
1882 | JSObjectRef jsobject_ref = receiver_ref.AsJSObject(); | |||
1883 | base::Optional<FixedArrayBaseRef> elements = | |||
1884 | jsobject_ref.elements(kRelaxedLoad); | |||
1885 | if (elements.has_value()) { | |||
1886 | element = jsobject_ref.GetOwnConstantElement(*elements, index, | |||
1887 | dependencies()); | |||
1888 | if (!element.has_value() && receiver_ref.IsJSArray()) { | |||
1889 | // We didn't find a constant element, but if the receiver is a | |||
1890 | // cow-array we can exploit the fact that any future write to the | |||
1891 | // element will replace the whole elements storage. | |||
1892 | element = receiver_ref.AsJSArray().GetOwnCowElement(*elements, index); | |||
1893 | if (element.has_value()) { | |||
1894 | Node* actual_elements = effect = graph()->NewNode( | |||
1895 | simplified()->LoadField(AccessBuilder::ForJSObjectElements()), | |||
1896 | receiver, effect, control); | |||
1897 | Node* check = graph()->NewNode(simplified()->ReferenceEqual(), | |||
1898 | actual_elements, | |||
1899 | jsgraph()->Constant(*elements)); | |||
1900 | effect = graph()->NewNode( | |||
1901 | simplified()->CheckIf( | |||
1902 | DeoptimizeReason::kCowArrayElementsChanged), | |||
1903 | check, effect, control); | |||
1904 | } | |||
1905 | } | |||
1906 | } | |||
1907 | } else if (receiver_ref.IsString()) { | |||
1908 | element = receiver_ref.AsString().GetCharAsStringOrUndefined(index); | |||
1909 | } | |||
1910 | ||||
1911 | if (element.has_value()) { | |||
1912 | Node* value = access_mode == AccessMode::kHas | |||
1913 | ? jsgraph()->TrueConstant() | |||
1914 | : jsgraph()->Constant(*element); | |||
1915 | ReplaceWithValue(node, value, effect, control); | |||
1916 | return Replace(value); | |||
1917 | } | |||
1918 | } | |||
1919 | ||||
1920 | // For constant Strings we can eagerly strength-reduce the keyed | |||
1921 | // accesses using the known length, which doesn't change. | |||
1922 | if (receiver_ref.IsString()) { | |||
1923 | DCHECK_NE(access_mode, AccessMode::kHas)((void) 0); | |||
1924 | // Ensure that {key} is less than {receiver} length. | |||
1925 | if (!receiver_ref.AsString().length().has_value()) return NoChange(); | |||
1926 | Node* length = | |||
1927 | jsgraph()->Constant(receiver_ref.AsString().length().value()); | |||
1928 | ||||
1929 | // Load the single character string from {receiver} or yield | |||
1930 | // undefined if the {key} is out of bounds (depending on the | |||
1931 | // {load_mode}). | |||
1932 | Node* value = BuildIndexedStringLoad(receiver, key, length, &effect, | |||
1933 | &control, load_mode); | |||
1934 | ReplaceWithValue(node, value, effect, control); | |||
1935 | return Replace(value); | |||
1936 | } | |||
1937 | ||||
1938 | return NoChange(); | |||
1939 | } | |||
1940 | ||||
1941 | Reduction JSNativeContextSpecialization::ReducePropertyAccess( | |||
1942 | Node* node, Node* key, base::Optional<NameRef> static_name, Node* value, | |||
1943 | FeedbackSource const& source, AccessMode access_mode) { | |||
1944 | DCHECK_EQ(key == nullptr, static_name.has_value())((void) 0); | |||
1945 | DCHECK(node->opcode() == IrOpcode::kJSLoadProperty ||((void) 0) | |||
1946 | node->opcode() == IrOpcode::kJSSetKeyedProperty ||((void) 0) | |||
1947 | node->opcode() == IrOpcode::kJSStoreInArrayLiteral ||((void) 0) | |||
1948 | node->opcode() == IrOpcode::kJSDefineKeyedOwnPropertyInLiteral ||((void) 0) | |||
1949 | node->opcode() == IrOpcode::kJSHasProperty ||((void) 0) | |||
1950 | node->opcode() == IrOpcode::kJSLoadNamed ||((void) 0) | |||
1951 | node->opcode() == IrOpcode::kJSSetNamedProperty ||((void) 0) | |||
1952 | node->opcode() == IrOpcode::kJSDefineNamedOwnProperty ||((void) 0) | |||
1953 | node->opcode() == IrOpcode::kJSLoadNamedFromSuper ||((void) 0) | |||
1954 | node->opcode() == IrOpcode::kJSDefineKeyedOwnProperty)((void) 0); | |||
1955 | DCHECK_GE(node->op()->ControlOutputCount(), 1)((void) 0); | |||
1956 | ||||
1957 | ProcessedFeedback const& feedback = | |||
1958 | broker()->GetFeedbackForPropertyAccess(source, access_mode, static_name); | |||
1959 | switch (feedback.kind()) { | |||
1960 | case ProcessedFeedback::kInsufficient: | |||
1961 | return ReduceEagerDeoptimize( | |||
1962 | node, | |||
1963 | DeoptimizeReason::kInsufficientTypeFeedbackForGenericNamedAccess); | |||
1964 | case ProcessedFeedback::kNamedAccess: | |||
1965 | return ReduceNamedAccess(node, value, feedback.AsNamedAccess(), | |||
1966 | access_mode, key); | |||
1967 | case ProcessedFeedback::kElementAccess: | |||
1968 | DCHECK_EQ(feedback.AsElementAccess().keyed_mode().access_mode(),((void) 0) | |||
1969 | access_mode)((void) 0); | |||
1970 | DCHECK_NE(node->opcode(), IrOpcode::kJSLoadNamedFromSuper)((void) 0); | |||
1971 | return ReduceElementAccess(node, key, value, feedback.AsElementAccess()); | |||
1972 | default: | |||
1973 | UNREACHABLE()V8_Fatal("unreachable code"); | |||
1974 | } | |||
1975 | } | |||
1976 | ||||
1977 | Reduction JSNativeContextSpecialization::ReduceEagerDeoptimize( | |||
1978 | Node* node, DeoptimizeReason reason) { | |||
1979 | if (!(flags() & kBailoutOnUninitialized)) return NoChange(); | |||
1980 | ||||
1981 | Node* effect = NodeProperties::GetEffectInput(node); | |||
1982 | Node* control = NodeProperties::GetControlInput(node); | |||
1983 | Node* frame_state = | |||
1984 | NodeProperties::FindFrameStateBefore(node, jsgraph()->Dead()); | |||
1985 | Node* deoptimize = | |||
1986 | graph()->NewNode(common()->Deoptimize(reason, FeedbackSource()), | |||
1987 | frame_state, effect, control); | |||
1988 | // TODO(bmeurer): This should be on the AdvancedReducer somehow. | |||
1989 | NodeProperties::MergeControlToEnd(graph(), common(), deoptimize); | |||
1990 | Revisit(graph()->end()); | |||
1991 | node->TrimInputCount(0); | |||
1992 | NodeProperties::ChangeOp(node, common()->Dead()); | |||
1993 | return Changed(node); | |||
1994 | } | |||
1995 | ||||
1996 | Reduction JSNativeContextSpecialization::ReduceJSHasProperty(Node* node) { | |||
1997 | JSHasPropertyNode n(node); | |||
1998 | PropertyAccess const& p = n.Parameters(); | |||
1999 | Node* value = jsgraph()->Dead(); | |||
2000 | if (!p.feedback().IsValid()) return NoChange(); | |||
2001 | return ReducePropertyAccess(node, n.key(), base::nullopt, value, | |||
2002 | FeedbackSource(p.feedback()), AccessMode::kHas); | |||
2003 | } | |||
2004 | ||||
2005 | Reduction JSNativeContextSpecialization::ReduceJSLoadPropertyWithEnumeratedKey( | |||
2006 | Node* node) { | |||
2007 | // We can optimize a property load if it's being used inside a for..in: | |||
2008 | // for (name in receiver) { | |||
2009 | // value = receiver[name]; | |||
2010 | // ... | |||
2011 | // } | |||
2012 | // | |||
2013 | // If the for..in is in fast-mode, we know that the {receiver} has {name} | |||
2014 | // as own property, otherwise the enumeration wouldn't include it. The graph | |||
2015 | // constructed by the BytecodeGraphBuilder in this case looks like this: | |||
2016 | ||||
2017 | // receiver | |||
2018 | // ^ ^ | |||
2019 | // | | | |||
2020 | // | +-+ | |||
2021 | // | | | |||
2022 | // | JSToObject | |||
2023 | // | ^ | |||
2024 | // | | | |||
2025 | // | | | |||
2026 | // | JSForInNext | |||
2027 | // | ^ | |||
2028 | // | | | |||
2029 | // +----+ | | |||
2030 | // | | | |||
2031 | // | | | |||
2032 | // JSLoadProperty | |||
2033 | ||||
2034 | // If the for..in has only seen maps with enum cache consisting of keys | |||
2035 | // and indices so far, we can turn the {JSLoadProperty} into a map check | |||
2036 | // on the {receiver} and then just load the field value dynamically via | |||
2037 | // the {LoadFieldByIndex} operator. The map check is only necessary when | |||
2038 | // TurboFan cannot prove that there is no observable side effect between | |||
2039 | // the {JSForInNext} and the {JSLoadProperty} node. | |||
2040 | // | |||
2041 | // Also note that it's safe to look through the {JSToObject}, since the | |||
2042 | // [[Get]] operation does an implicit ToObject anyway, and these operations | |||
2043 | // are not observable. | |||
2044 | ||||
2045 | DCHECK_EQ(IrOpcode::kJSLoadProperty, node->opcode())((void) 0); | |||
2046 | Node* receiver = NodeProperties::GetValueInput(node, 0); | |||
2047 | JSForInNextNode name(NodeProperties::GetValueInput(node, 1)); | |||
2048 | Node* effect = NodeProperties::GetEffectInput(node); | |||
2049 | Node* control = NodeProperties::GetControlInput(node); | |||
2050 | ||||
2051 | if (name.Parameters().mode() != ForInMode::kUseEnumCacheKeysAndIndices) { | |||
2052 | return NoChange(); | |||
2053 | } | |||
2054 | ||||
2055 | Node* object = name.receiver(); | |||
2056 | Node* cache_type = name.cache_type(); | |||
2057 | Node* index = name.index(); | |||
2058 | if (object->opcode() == IrOpcode::kJSToObject) { | |||
2059 | object = NodeProperties::GetValueInput(object, 0); | |||
2060 | } | |||
2061 | if (object != receiver) return NoChange(); | |||
2062 | ||||
2063 | // No need to repeat the map check if we can prove that there's no | |||
2064 | // observable side effect between {effect} and {name]. | |||
2065 | if (!NodeProperties::NoObservableSideEffectBetween(effect, name)) { | |||
2066 | // Check that the {receiver} map is still valid. | |||
2067 | Node* receiver_map = effect = | |||
2068 | graph()->NewNode(simplified()->LoadField(AccessBuilder::ForMap()), | |||
2069 | receiver, effect, control); | |||
2070 | Node* check = graph()->NewNode(simplified()->ReferenceEqual(), receiver_map, | |||
2071 | cache_type); | |||
2072 | effect = | |||
2073 | graph()->NewNode(simplified()->CheckIf(DeoptimizeReason::kWrongMap), | |||
2074 | check, effect, control); | |||
2075 | } | |||
2076 | ||||
2077 | // Load the enum cache indices from the {cache_type}. | |||
2078 | Node* descriptor_array = effect = graph()->NewNode( | |||
2079 | simplified()->LoadField(AccessBuilder::ForMapDescriptors()), cache_type, | |||
2080 | effect, control); | |||
2081 | Node* enum_cache = effect = graph()->NewNode( | |||
2082 | simplified()->LoadField(AccessBuilder::ForDescriptorArrayEnumCache()), | |||
2083 | descriptor_array, effect, control); | |||
2084 | Node* enum_indices = effect = graph()->NewNode( | |||
2085 | simplified()->LoadField(AccessBuilder::ForEnumCacheIndices()), enum_cache, | |||
2086 | effect, control); | |||
2087 | ||||
2088 | // Ensure that the {enum_indices} are valid. | |||
2089 | Node* check = graph()->NewNode( | |||
2090 | simplified()->BooleanNot(), | |||
2091 | graph()->NewNode(simplified()->ReferenceEqual(), enum_indices, | |||
2092 | jsgraph()->EmptyFixedArrayConstant())); | |||
2093 | effect = graph()->NewNode( | |||
2094 | simplified()->CheckIf(DeoptimizeReason::kWrongEnumIndices), check, effect, | |||
2095 | control); | |||
2096 | ||||
2097 | // Determine the key from the {enum_indices}. | |||
2098 | Node* key = effect = graph()->NewNode( | |||
2099 | simplified()->LoadElement( | |||
2100 | AccessBuilder::ForFixedArrayElement(PACKED_SMI_ELEMENTS)), | |||
2101 | enum_indices, index, effect, control); | |||
2102 | ||||
2103 | // Load the actual field value. | |||
2104 | Node* value = effect = graph()->NewNode(simplified()->LoadFieldByIndex(), | |||
2105 | receiver, key, effect, control); | |||
2106 | ReplaceWithValue(node, value, effect, control); | |||
2107 | return Replace(value); | |||
2108 | } | |||
2109 | ||||
2110 | Reduction JSNativeContextSpecialization::ReduceJSLoadProperty(Node* node) { | |||
2111 | JSLoadPropertyNode n(node); | |||
2112 | PropertyAccess const& p = n.Parameters(); | |||
2113 | Node* name = n.key(); | |||
2114 | ||||
2115 | if (name->opcode() == IrOpcode::kJSForInNext) { | |||
2116 | Reduction reduction = ReduceJSLoadPropertyWithEnumeratedKey(node); | |||
2117 | if (reduction.Changed()) return reduction; | |||
2118 | } | |||
2119 | ||||
2120 | if (!p.feedback().IsValid()) return NoChange(); | |||
2121 | Node* value = jsgraph()->Dead(); | |||
2122 | return ReducePropertyAccess(node, name, base::nullopt, value, | |||
2123 | FeedbackSource(p.feedback()), AccessMode::kLoad); | |||
2124 | } | |||
2125 | ||||
2126 | Reduction JSNativeContextSpecialization::ReduceJSSetKeyedProperty(Node* node) { | |||
2127 | JSSetKeyedPropertyNode n(node); | |||
2128 | PropertyAccess const& p = n.Parameters(); | |||
2129 | if (!p.feedback().IsValid()) return NoChange(); | |||
2130 | return ReducePropertyAccess(node, n.key(), base::nullopt, n.value(), | |||
2131 | FeedbackSource(p.feedback()), AccessMode::kStore); | |||
2132 | } | |||
2133 | ||||
2134 | Reduction JSNativeContextSpecialization::ReduceJSDefineKeyedOwnProperty( | |||
2135 | Node* node) { | |||
2136 | JSDefineKeyedOwnPropertyNode n(node); | |||
2137 | PropertyAccess const& p = n.Parameters(); | |||
2138 | if (!p.feedback().IsValid()) return NoChange(); | |||
2139 | return ReducePropertyAccess(node, n.key(), base::nullopt, n.value(), | |||
2140 | FeedbackSource(p.feedback()), | |||
2141 | AccessMode::kDefine); | |||
2142 | } | |||
2143 | ||||
2144 | Node* JSNativeContextSpecialization::InlinePropertyGetterCall( | |||
2145 | Node* receiver, ConvertReceiverMode receiver_mode, | |||
2146 | Node* lookup_start_object, Node* context, Node* frame_state, Node** effect, | |||
2147 | Node** control, ZoneVector<Node*>* if_exceptions, | |||
2148 | PropertyAccessInfo const& access_info) { | |||
2149 | ObjectRef constant = access_info.constant().value(); | |||
2150 | ||||
2151 | if (access_info.IsDictionaryProtoAccessorConstant()) { | |||
2152 | // For fast mode holders we recorded dependencies in BuildPropertyLoad. | |||
2153 | for (const MapRef map : access_info.lookup_start_object_maps()) { | |||
2154 | dependencies()->DependOnConstantInDictionaryPrototypeChain( | |||
2155 | map, access_info.name(), constant, PropertyKind::kAccessor); | |||
2156 | } | |||
2157 | } | |||
2158 | ||||
2159 | Node* target = jsgraph()->Constant(constant); | |||
2160 | // Introduce the call to the getter function. | |||
2161 | Node* value; | |||
2162 | if (constant.IsJSFunction()) { | |||
2163 | Node* feedback = jsgraph()->UndefinedConstant(); | |||
2164 | value = *effect = *control = graph()->NewNode( | |||
2165 | jsgraph()->javascript()->Call(JSCallNode::ArityForArgc(0), | |||
2166 | CallFrequency(), FeedbackSource(), | |||
2167 | receiver_mode), | |||
2168 | target, receiver, feedback, context, frame_state, *effect, *control); | |||
2169 | } else { | |||
2170 | // Disable optimizations for super ICs using API getters, so that we get | |||
2171 | // the correct receiver checks. | |||
2172 | if (receiver != lookup_start_object) { | |||
2173 | return nullptr; | |||
2174 | } | |||
2175 | Node* holder = access_info.holder().has_value() | |||
2176 | ? jsgraph()->Constant(access_info.holder().value()) | |||
2177 | : receiver; | |||
2178 | value = InlineApiCall(receiver, holder, frame_state, nullptr, effect, | |||
2179 | control, constant.AsFunctionTemplateInfo()); | |||
2180 | } | |||
2181 | // Remember to rewire the IfException edge if this is inside a try-block. | |||
2182 | if (if_exceptions != nullptr) { | |||
2183 | // Create the appropriate IfException/IfSuccess projections. | |||
2184 | Node* const if_exception = | |||
2185 | graph()->NewNode(common()->IfException(), *control, *effect); | |||
2186 | Node* const if_success = graph()->NewNode(common()->IfSuccess(), *control); | |||
2187 | if_exceptions->push_back(if_exception); | |||
2188 | *control = if_success; | |||
2189 | } | |||
2190 | return value; | |||
2191 | } | |||
2192 | ||||
2193 | void JSNativeContextSpecialization::InlinePropertySetterCall( | |||
2194 | Node* receiver, Node* value, Node* context, Node* frame_state, | |||
2195 | Node** effect, Node** control, ZoneVector<Node*>* if_exceptions, | |||
2196 | PropertyAccessInfo const& access_info) { | |||
2197 | ObjectRef constant = access_info.constant().value(); | |||
2198 | Node* target = jsgraph()->Constant(constant); | |||
2199 | // Introduce the call to the setter function. | |||
2200 | if (constant.IsJSFunction()) { | |||
2201 | Node* feedback = jsgraph()->UndefinedConstant(); | |||
2202 | *effect = *control = graph()->NewNode( | |||
2203 | jsgraph()->javascript()->Call(JSCallNode::ArityForArgc(1), | |||
2204 | CallFrequency(), FeedbackSource(), | |||
2205 | ConvertReceiverMode::kNotNullOrUndefined), | |||
2206 | target, receiver, value, feedback, context, frame_state, *effect, | |||
2207 | *control); | |||
2208 | } else { | |||
2209 | Node* holder = access_info.holder().has_value() | |||
2210 | ? jsgraph()->Constant(access_info.holder().value()) | |||
2211 | : receiver; | |||
2212 | InlineApiCall(receiver, holder, frame_state, value, effect, control, | |||
2213 | constant.AsFunctionTemplateInfo()); | |||
2214 | } | |||
2215 | // Remember to rewire the IfException edge if this is inside a try-block. | |||
2216 | if (if_exceptions != nullptr) { | |||
2217 | // Create the appropriate IfException/IfSuccess projections. | |||
2218 | Node* const if_exception = | |||
2219 | graph()->NewNode(common()->IfException(), *control, *effect); | |||
2220 | Node* const if_success = graph()->NewNode(common()->IfSuccess(), *control); | |||
2221 | if_exceptions->push_back(if_exception); | |||
2222 | *control = if_success; | |||
2223 | } | |||
2224 | } | |||
2225 | ||||
2226 | Node* JSNativeContextSpecialization::InlineApiCall( | |||
2227 | Node* receiver, Node* holder, Node* frame_state, Node* value, Node** effect, | |||
2228 | Node** control, FunctionTemplateInfoRef const& function_template_info) { | |||
2229 | if (!function_template_info.call_code().has_value()) { | |||
2230 | TRACE_BROKER_MISSING(broker(), "call code for function template info "do { if (broker()->tracing_enabled()) StdoutStream{} << broker()->Trace() << "Missing " << "call code for function template info " << function_template_info << " (" << "../deps/v8/src/compiler/js-native-context-specialization.cc" << ":" << 2231 << ")" << std::endl; } while (false) | |||
2231 | << function_template_info)do { if (broker()->tracing_enabled()) StdoutStream{} << broker()->Trace() << "Missing " << "call code for function template info " << function_template_info << " (" << "../deps/v8/src/compiler/js-native-context-specialization.cc" << ":" << 2231 << ")" << std::endl; } while (false); | |||
2232 | return nullptr; | |||
2233 | } | |||
2234 | CallHandlerInfoRef call_handler_info = *function_template_info.call_code(); | |||
2235 | ||||
2236 | // Only setters have a value. | |||
2237 | int const argc = value == nullptr ? 0 : 1; | |||
2238 | // The stub always expects the receiver as the first param on the stack. | |||
2239 | Callable call_api_callback = CodeFactory::CallApiCallback(isolate()); | |||
2240 | CallInterfaceDescriptor call_interface_descriptor = | |||
2241 | call_api_callback.descriptor(); | |||
2242 | auto call_descriptor = Linkage::GetStubCallDescriptor( | |||
2243 | graph()->zone(), call_interface_descriptor, | |||
2244 | call_interface_descriptor.GetStackParameterCount() + argc + | |||
2245 | 1 /* implicit receiver */, | |||
2246 | CallDescriptor::kNeedsFrameState); | |||
2247 | ||||
2248 | Node* data = jsgraph()->Constant(call_handler_info.data()); | |||
2249 | ApiFunction function(call_handler_info.callback()); | |||
2250 | Node* function_reference = | |||
2251 | graph()->NewNode(common()->ExternalConstant(ExternalReference::Create( | |||
2252 | &function, ExternalReference::DIRECT_API_CALL))); | |||
2253 | Node* code = jsgraph()->HeapConstant(call_api_callback.code()); | |||
2254 | ||||
2255 | // Add CallApiCallbackStub's register argument as well. | |||
2256 | Node* context = jsgraph()->Constant(native_context()); | |||
2257 | Node* inputs[11] = { | |||
2258 | code, function_reference, jsgraph()->Constant(argc), data, holder, | |||
2259 | receiver}; | |||
2260 | int index = 6 + argc; | |||
2261 | inputs[index++] = context; | |||
2262 | inputs[index++] = frame_state; | |||
2263 | inputs[index++] = *effect; | |||
2264 | inputs[index++] = *control; | |||
2265 | // This needs to stay here because of the edge case described in | |||
2266 | // http://crbug.com/675648. | |||
2267 | if (value != nullptr) { | |||
2268 | inputs[6] = value; | |||
2269 | } | |||
2270 | ||||
2271 | return *effect = *control = | |||
2272 | graph()->NewNode(common()->Call(call_descriptor), index, inputs); | |||
2273 | } | |||
2274 | ||||
2275 | base::Optional<JSNativeContextSpecialization::ValueEffectControl> | |||
2276 | JSNativeContextSpecialization::BuildPropertyLoad( | |||
2277 | Node* lookup_start_object, Node* receiver, Node* context, Node* frame_state, | |||
2278 | Node* effect, Node* control, NameRef const& name, | |||
2279 | ZoneVector<Node*>* if_exceptions, PropertyAccessInfo const& access_info) { | |||
2280 | // Determine actual holder and perform prototype chain checks. | |||
2281 | base::Optional<JSObjectRef> holder = access_info.holder(); | |||
2282 | if (holder.has_value() && !access_info.HasDictionaryHolder()) { | |||
2283 | dependencies()->DependOnStablePrototypeChains( | |||
2284 | access_info.lookup_start_object_maps(), kStartAtPrototype, | |||
2285 | holder.value()); | |||
2286 | } | |||
2287 | ||||
2288 | // Generate the actual property access. | |||
2289 | Node* value; | |||
2290 | if (access_info.IsNotFound()) { | |||
2291 | value = jsgraph()->UndefinedConstant(); | |||
2292 | } else if (access_info.IsFastAccessorConstant() || | |||
2293 | access_info.IsDictionaryProtoAccessorConstant()) { | |||
2294 | ConvertReceiverMode receiver_mode = | |||
2295 | receiver == lookup_start_object | |||
2296 | ? ConvertReceiverMode::kNotNullOrUndefined | |||
2297 | : ConvertReceiverMode::kAny; | |||
2298 | value = InlinePropertyGetterCall( | |||
2299 | receiver, receiver_mode, lookup_start_object, context, frame_state, | |||
2300 | &effect, &control, if_exceptions, access_info); | |||
2301 | } else if (access_info.IsModuleExport()) { | |||
2302 | Node* cell = jsgraph()->Constant(access_info.constant().value().AsCell()); | |||
2303 | value = effect = | |||
2304 | graph()->NewNode(simplified()->LoadField(AccessBuilder::ForCellValue()), | |||
2305 | cell, effect, control); | |||
2306 | } else if (access_info.IsStringLength()) { | |||
2307 | DCHECK_EQ(receiver, lookup_start_object)((void) 0); | |||
2308 | value = graph()->NewNode(simplified()->StringLength(), receiver); | |||
2309 | } else { | |||
2310 | DCHECK(access_info.IsDataField() || access_info.IsFastDataConstant() ||((void) 0) | |||
2311 | access_info.IsDictionaryProtoDataConstant())((void) 0); | |||
2312 | PropertyAccessBuilder access_builder(jsgraph(), broker(), dependencies()); | |||
2313 | if (access_info.IsDictionaryProtoDataConstant()) { | |||
2314 | auto maybe_value = | |||
2315 | access_builder.FoldLoadDictPrototypeConstant(access_info); | |||
2316 | if (!maybe_value) return {}; | |||
2317 | value = maybe_value.value(); | |||
2318 | } else { | |||
2319 | value = access_builder.BuildLoadDataField( | |||
2320 | name, access_info, lookup_start_object, &effect, &control); | |||
2321 | } | |||
2322 | } | |||
2323 | if (value != nullptr) { | |||
2324 | return ValueEffectControl(value, effect, control); | |||
2325 | } | |||
2326 | return base::Optional<ValueEffectControl>(); | |||
2327 | } | |||
2328 | ||||
2329 | JSNativeContextSpecialization::ValueEffectControl | |||
2330 | JSNativeContextSpecialization::BuildPropertyTest( | |||
2331 | Node* effect, Node* control, PropertyAccessInfo const& access_info) { | |||
2332 | // TODO(v8:11457) Support property tests for dictionary mode protoypes. | |||
2333 | DCHECK(!access_info.HasDictionaryHolder())((void) 0); | |||
2334 | ||||
2335 | // Determine actual holder and perform prototype chain checks. | |||
2336 | base::Optional<JSObjectRef> holder = access_info.holder(); | |||
2337 | if (holder.has_value()) { | |||
2338 | dependencies()->DependOnStablePrototypeChains( | |||
2339 | access_info.lookup_start_object_maps(), kStartAtPrototype, | |||
2340 | holder.value()); | |||
2341 | } | |||
2342 | ||||
2343 | Node* value = access_info.IsNotFound() ? jsgraph()->FalseConstant() | |||
2344 | : jsgraph()->TrueConstant(); | |||
2345 | return ValueEffectControl(value, effect, control); | |||
2346 | } | |||
2347 | ||||
2348 | base::Optional<JSNativeContextSpecialization::ValueEffectControl> | |||
2349 | JSNativeContextSpecialization::BuildPropertyAccess( | |||
2350 | Node* lookup_start_object, Node* receiver, Node* value, Node* context, | |||
2351 | Node* frame_state, Node* effect, Node* control, NameRef const& name, | |||
2352 | ZoneVector<Node*>* if_exceptions, PropertyAccessInfo const& access_info, | |||
2353 | AccessMode access_mode) { | |||
2354 | switch (access_mode) { | |||
2355 | case AccessMode::kLoad: | |||
2356 | return BuildPropertyLoad(lookup_start_object, receiver, context, | |||
2357 | frame_state, effect, control, name, | |||
2358 | if_exceptions, access_info); | |||
2359 | case AccessMode::kStore: | |||
2360 | case AccessMode::kStoreInLiteral: | |||
2361 | case AccessMode::kDefine: | |||
2362 | DCHECK_EQ(receiver, lookup_start_object)((void) 0); | |||
2363 | return BuildPropertyStore(receiver, value, context, frame_state, effect, | |||
2364 | control, name, if_exceptions, access_info, | |||
2365 | access_mode); | |||
2366 | case AccessMode::kHas: | |||
2367 | DCHECK_EQ(receiver, lookup_start_object)((void) 0); | |||
2368 | return BuildPropertyTest(effect, control, access_info); | |||
2369 | } | |||
2370 | UNREACHABLE()V8_Fatal("unreachable code"); | |||
2371 | } | |||
2372 | ||||
2373 | JSNativeContextSpecialization::ValueEffectControl | |||
2374 | JSNativeContextSpecialization::BuildPropertyStore( | |||
2375 | Node* receiver, Node* value, Node* context, Node* frame_state, Node* effect, | |||
2376 | Node* control, NameRef const& name, ZoneVector<Node*>* if_exceptions, | |||
2377 | PropertyAccessInfo const& access_info, AccessMode access_mode) { | |||
2378 | // Determine actual holder and perform prototype chain checks. | |||
2379 | PropertyAccessBuilder access_builder(jsgraph(), broker(), dependencies()); | |||
2380 | base::Optional<JSObjectRef> holder = access_info.holder(); | |||
2381 | if (holder.has_value()) { | |||
2382 | DCHECK_NE(AccessMode::kStoreInLiteral, access_mode)((void) 0); | |||
2383 | DCHECK_NE(AccessMode::kDefine, access_mode)((void) 0); | |||
2384 | dependencies()->DependOnStablePrototypeChains( | |||
2385 | access_info.lookup_start_object_maps(), kStartAtPrototype, | |||
2386 | holder.value()); | |||
2387 | } | |||
2388 | ||||
2389 | DCHECK(!access_info.IsNotFound())((void) 0); | |||
2390 | ||||
2391 | // Generate the actual property access. | |||
2392 | if (access_info.IsFastAccessorConstant()) { | |||
2393 | InlinePropertySetterCall(receiver, value, context, frame_state, &effect, | |||
2394 | &control, if_exceptions, access_info); | |||
2395 | } else { | |||
2396 | DCHECK(access_info.IsDataField() || access_info.IsFastDataConstant())((void) 0); | |||
2397 | DCHECK(access_mode == AccessMode::kStore ||((void) 0) | |||
2398 | access_mode == AccessMode::kStoreInLiteral ||((void) 0) | |||
2399 | access_mode == AccessMode::kDefine)((void) 0); | |||
2400 | FieldIndex const field_index = access_info.field_index(); | |||
2401 | Type const field_type = access_info.field_type(); | |||
2402 | MachineRepresentation const field_representation = | |||
2403 | PropertyAccessBuilder::ConvertRepresentation( | |||
2404 | access_info.field_representation()); | |||
2405 | Node* storage = receiver; | |||
2406 | if (!field_index.is_inobject()) { | |||
2407 | storage = effect = graph()->NewNode( | |||
2408 | simplified()->LoadField( | |||
2409 | AccessBuilder::ForJSObjectPropertiesOrHashKnownPointer()), | |||
2410 | storage, effect, control); | |||
2411 | } | |||
2412 | bool store_to_existing_constant_field = access_info.IsFastDataConstant() && | |||
2413 | access_mode == AccessMode::kStore && | |||
2414 | !access_info.HasTransitionMap(); | |||
2415 | FieldAccess field_access = { | |||
2416 | kTaggedBase, | |||
2417 | field_index.offset(), | |||
2418 | name.object(), | |||
2419 | MaybeHandle<Map>(), | |||
2420 | field_type, | |||
2421 | MachineType::TypeForRepresentation(field_representation), | |||
2422 | kFullWriteBarrier, | |||
2423 | access_info.GetConstFieldInfo(), | |||
2424 | access_mode == AccessMode::kStoreInLiteral}; | |||
2425 | ||||
2426 | switch (field_representation) { | |||
2427 | case MachineRepresentation::kFloat64: { | |||
2428 | value = effect = | |||
2429 | graph()->NewNode(simplified()->CheckNumber(FeedbackSource()), value, | |||
2430 | effect, control); | |||
2431 | if (access_info.HasTransitionMap()) { | |||
2432 | // Allocate a HeapNumber for the new property. | |||
2433 | AllocationBuilder a(jsgraph(), effect, control); | |||
2434 | a.Allocate(HeapNumber::kSize, AllocationType::kYoung, | |||
2435 | Type::OtherInternal()); | |||
2436 | a.Store(AccessBuilder::ForMap(), | |||
2437 | MakeRef(broker(), factory()->heap_number_map())); | |||
2438 | FieldAccess value_field_access = AccessBuilder::ForHeapNumberValue(); | |||
2439 | value_field_access.const_field_info = field_access.const_field_info; | |||
2440 | a.Store(value_field_access, value); | |||
2441 | value = effect = a.Finish(); | |||
2442 | ||||
2443 | field_access.type = Type::Any(); | |||
2444 | field_access.machine_type = MachineType::TaggedPointer(); | |||
2445 | field_access.write_barrier_kind = kPointerWriteBarrier; | |||
2446 | } else { | |||
2447 | // We just store directly to the HeapNumber. | |||
2448 | FieldAccess const storage_access = { | |||
2449 | kTaggedBase, | |||
2450 | field_index.offset(), | |||
2451 | name.object(), | |||
2452 | MaybeHandle<Map>(), | |||
2453 | Type::OtherInternal(), | |||
2454 | MachineType::TaggedPointer(), | |||
2455 | kPointerWriteBarrier, | |||
2456 | access_info.GetConstFieldInfo(), | |||
2457 | access_mode == AccessMode::kStoreInLiteral}; | |||
2458 | storage = effect = | |||
2459 | graph()->NewNode(simplified()->LoadField(storage_access), storage, | |||
2460 | effect, control); | |||
2461 | field_access.offset = HeapNumber::kValueOffset; | |||
2462 | field_access.name = MaybeHandle<Name>(); | |||
2463 | field_access.machine_type = MachineType::Float64(); | |||
2464 | } | |||
2465 | if (store_to_existing_constant_field) { | |||
2466 | DCHECK(!access_info.HasTransitionMap())((void) 0); | |||
2467 | // If the field is constant check that the value we are going | |||
2468 | // to store matches current value. | |||
2469 | Node* current_value = effect = graph()->NewNode( | |||
2470 | simplified()->LoadField(field_access), storage, effect, control); | |||
2471 | ||||
2472 | Node* check = | |||
2473 | graph()->NewNode(simplified()->SameValue(), current_value, value); | |||
2474 | effect = graph()->NewNode( | |||
2475 | simplified()->CheckIf(DeoptimizeReason::kWrongValue), check, | |||
2476 | effect, control); | |||
2477 | return ValueEffectControl(value, effect, control); | |||
2478 | } | |||
2479 | break; | |||
2480 | } | |||
2481 | case MachineRepresentation::kTaggedSigned: | |||
2482 | case MachineRepresentation::kTaggedPointer: | |||
2483 | case MachineRepresentation::kTagged: | |||
2484 | if (store_to_existing_constant_field) { | |||
2485 | DCHECK(!access_info.HasTransitionMap())((void) 0); | |||
2486 | // If the field is constant check that the value we are going | |||
2487 | // to store matches current value. | |||
2488 | Node* current_value = effect = graph()->NewNode( | |||
2489 | simplified()->LoadField(field_access), storage, effect, control); | |||
2490 | ||||
2491 | Node* check = graph()->NewNode(simplified()->SameValueNumbersOnly(), | |||
2492 | current_value, value); | |||
2493 | effect = graph()->NewNode( | |||
2494 | simplified()->CheckIf(DeoptimizeReason::kWrongValue), check, | |||
2495 | effect, control); | |||
2496 | return ValueEffectControl(value, effect, control); | |||
2497 | } | |||
2498 | ||||
2499 | if (field_representation == MachineRepresentation::kTaggedSigned) { | |||
2500 | value = effect = graph()->NewNode( | |||
2501 | simplified()->CheckSmi(FeedbackSource()), value, effect, control); | |||
2502 | field_access.write_barrier_kind = kNoWriteBarrier; | |||
2503 | ||||
2504 | } else if (field_representation == | |||
2505 | MachineRepresentation::kTaggedPointer) { | |||
2506 | base::Optional<MapRef> field_map = access_info.field_map(); | |||
2507 | if (field_map.has_value()) { | |||
2508 | // Emit a map check for the value. | |||
2509 | effect = | |||
2510 | graph()->NewNode(simplified()->CheckMaps( | |||
2511 | CheckMapsFlag::kNone, | |||
2512 | ZoneHandleSet<Map>(field_map->object())), | |||
2513 | value, effect, control); | |||
2514 | } else { | |||
2515 | // Ensure that {value} is a HeapObject. | |||
2516 | value = effect = graph()->NewNode(simplified()->CheckHeapObject(), | |||
2517 | value, effect, control); | |||
2518 | } | |||
2519 | field_access.write_barrier_kind = kPointerWriteBarrier; | |||
2520 | ||||
2521 | } else { | |||
2522 | DCHECK(field_representation == MachineRepresentation::kTagged)((void) 0); | |||
2523 | } | |||
2524 | break; | |||
2525 | case MachineRepresentation::kNone: | |||
2526 | case MachineRepresentation::kBit: | |||
2527 | case MachineRepresentation::kCompressedPointer: | |||
2528 | case MachineRepresentation::kCompressed: | |||
2529 | case MachineRepresentation::kSandboxedPointer: | |||
2530 | case MachineRepresentation::kWord8: | |||
2531 | case MachineRepresentation::kWord16: | |||
2532 | case MachineRepresentation::kWord32: | |||
2533 | case MachineRepresentation::kWord64: | |||
2534 | case MachineRepresentation::kFloat32: | |||
2535 | case MachineRepresentation::kSimd128: | |||
2536 | case MachineRepresentation::kMapWord: | |||
2537 | UNREACHABLE()V8_Fatal("unreachable code"); | |||
2538 | } | |||
2539 | // Check if we need to perform a transitioning store. | |||
2540 | base::Optional<MapRef> transition_map = access_info.transition_map(); | |||
2541 | if (transition_map.has_value()) { | |||
2542 | // Check if we need to grow the properties backing store | |||
2543 | // with this transitioning store. | |||
2544 | MapRef transition_map_ref = transition_map.value(); | |||
2545 | MapRef original_map = transition_map_ref.GetBackPointer().AsMap(); | |||
2546 | if (original_map.UnusedPropertyFields() == 0) { | |||
2547 | DCHECK(!field_index.is_inobject())((void) 0); | |||
2548 | ||||
2549 | // Reallocate the properties {storage}. | |||
2550 | storage = effect = BuildExtendPropertiesBackingStore( | |||
2551 | original_map, storage, effect, control); | |||
2552 | ||||
2553 | // Perform the actual store. | |||
2554 | effect = graph()->NewNode(simplified()->StoreField(field_access), | |||
2555 | storage, value, effect, control); | |||
2556 | ||||
2557 | // Atomically switch to the new properties below. | |||
2558 | field_access = AccessBuilder::ForJSObjectPropertiesOrHashKnownPointer(); | |||
2559 | value = storage; | |||
2560 | storage = receiver; | |||
2561 | } | |||
2562 | effect = graph()->NewNode( | |||
2563 | common()->BeginRegion(RegionObservability::kObservable), effect); | |||
2564 | effect = graph()->NewNode( | |||
2565 | simplified()->StoreField(AccessBuilder::ForMap()), receiver, | |||
2566 | jsgraph()->Constant(transition_map_ref), effect, control); | |||
2567 | effect = graph()->NewNode(simplified()->StoreField(field_access), storage, | |||
2568 | value, effect, control); | |||
2569 | effect = graph()->NewNode(common()->FinishRegion(), | |||
2570 | jsgraph()->UndefinedConstant(), effect); | |||
2571 | } else { | |||
2572 | // Regular non-transitioning field store. | |||
2573 | effect = graph()->NewNode(simplified()->StoreField(field_access), storage, | |||
2574 | value, effect, control); | |||
2575 | } | |||
2576 | } | |||
2577 | ||||
2578 | return ValueEffectControl(value, effect, control); | |||
2579 | } | |||
2580 | ||||
2581 | Reduction | |||
2582 | JSNativeContextSpecialization::ReduceJSDefineKeyedOwnPropertyInLiteral( | |||
2583 | Node* node) { | |||
2584 | JSDefineKeyedOwnPropertyInLiteralNode n(node); | |||
2585 | FeedbackParameter const& p = n.Parameters(); | |||
2586 | if (!p.feedback().IsValid()) return NoChange(); | |||
2587 | ||||
2588 | NumberMatcher mflags(n.flags()); | |||
2589 | CHECK(mflags.HasResolvedValue())do { if ((__builtin_expect(!!(!(mflags.HasResolvedValue())), 0 ))) { V8_Fatal("Check failed: %s.", "mflags.HasResolvedValue()" ); } } while (false); | |||
2590 | DefineKeyedOwnPropertyInLiteralFlags cflags(mflags.ResolvedValue()); | |||
2591 | DCHECK(!(cflags & DefineKeyedOwnPropertyInLiteralFlag::kDontEnum))((void) 0); | |||
2592 | if (cflags & DefineKeyedOwnPropertyInLiteralFlag::kSetFunctionName) | |||
2593 | return NoChange(); | |||
2594 | ||||
2595 | return ReducePropertyAccess(node, n.name(), base::nullopt, n.value(), | |||
2596 | FeedbackSource(p.feedback()), | |||
2597 | AccessMode::kStoreInLiteral); | |||
2598 | } | |||
2599 | ||||
2600 | Reduction JSNativeContextSpecialization::ReduceJSStoreInArrayLiteral( | |||
2601 | Node* node) { | |||
2602 | JSStoreInArrayLiteralNode n(node); | |||
2603 | FeedbackParameter const& p = n.Parameters(); | |||
2604 | if (!p.feedback().IsValid()) return NoChange(); | |||
2605 | return ReducePropertyAccess(node, n.index(), base::nullopt, n.value(), | |||
2606 | FeedbackSource(p.feedback()), | |||
2607 | AccessMode::kStoreInLiteral); | |||
2608 | } | |||
2609 | ||||
2610 | Reduction JSNativeContextSpecialization::ReduceJSToObject(Node* node) { | |||
2611 | DCHECK_EQ(IrOpcode::kJSToObject, node->opcode())((void) 0); | |||
2612 | Node* receiver = NodeProperties::GetValueInput(node, 0); | |||
2613 | Effect effect{NodeProperties::GetEffectInput(node)}; | |||
2614 | ||||
2615 | MapInference inference(broker(), receiver, effect); | |||
2616 | if (!inference.HaveMaps() || !inference.AllOfInstanceTypesAreJSReceiver()) { | |||
2617 | return NoChange(); | |||
2618 | } | |||
2619 | ||||
2620 | ReplaceWithValue(node, receiver, effect); | |||
2621 | return Replace(receiver); | |||
2622 | } | |||
2623 | ||||
2624 | namespace { | |||
2625 | ||||
2626 | ExternalArrayType GetArrayTypeFromElementsKind(ElementsKind kind) { | |||
2627 | switch (kind) { | |||
2628 | #define TYPED_ARRAY_CASE(Type, type, TYPE, ctype) \ | |||
2629 | case TYPE##_ELEMENTS: \ | |||
2630 | return kExternal##Type##Array; | |||
2631 | TYPED_ARRAYS(TYPED_ARRAY_CASE)TYPED_ARRAY_CASE(Uint8, uint8, UINT8, uint8_t) TYPED_ARRAY_CASE (Int8, int8, INT8, int8_t) TYPED_ARRAY_CASE(Uint16, uint16, UINT16 , uint16_t) TYPED_ARRAY_CASE(Int16, int16, INT16, int16_t) TYPED_ARRAY_CASE (Uint32, uint32, UINT32, uint32_t) TYPED_ARRAY_CASE(Int32, int32 , INT32, int32_t) TYPED_ARRAY_CASE(Float32, float32, FLOAT32, float) TYPED_ARRAY_CASE(Float64, float64, FLOAT64, double) TYPED_ARRAY_CASE (Uint8Clamped, uint8_clamped, UINT8_CLAMPED, uint8_t) TYPED_ARRAY_CASE (BigUint64, biguint64, BIGUINT64, uint64_t) TYPED_ARRAY_CASE( BigInt64, bigint64, BIGINT64, int64_t) | |||
2632 | #undef TYPED_ARRAY_CASE | |||
2633 | default: | |||
2634 | break; | |||
2635 | } | |||
2636 | UNREACHABLE()V8_Fatal("unreachable code"); | |||
2637 | } | |||
2638 | ||||
2639 | } // namespace | |||
2640 | ||||
2641 | JSNativeContextSpecialization::ValueEffectControl | |||
2642 | JSNativeContextSpecialization::BuildElementAccess( | |||
2643 | Node* receiver, Node* index, Node* value, Node* effect, Node* control, | |||
2644 | ElementAccessInfo const& access_info, KeyedAccessMode const& keyed_mode) { | |||
2645 | // TODO(bmeurer): We currently specialize based on elements kind. We should | |||
2646 | // also be able to properly support strings and other JSObjects here. | |||
2647 | ElementsKind elements_kind = access_info.elements_kind(); | |||
2648 | ZoneVector<MapRef> const& receiver_maps = | |||
2649 | access_info.lookup_start_object_maps(); | |||
2650 | ||||
2651 | if (IsTypedArrayElementsKind(elements_kind)) { | |||
2652 | Node* buffer_or_receiver = receiver; | |||
2653 | Node* length; | |||
2654 | Node* base_pointer; | |||
2655 | Node* external_pointer; | |||
2656 | ||||
2657 | // Check if we can constant-fold information about the {receiver} (e.g. | |||
2658 | // for asm.js-like code patterns). | |||
2659 | base::Optional<JSTypedArrayRef> typed_array = | |||
2660 | GetTypedArrayConstant(broker(), receiver); | |||
2661 | if (typed_array.has_value()) { | |||
2662 | length = jsgraph()->Constant(static_cast<double>(typed_array->length())); | |||
2663 | ||||
2664 | DCHECK(!typed_array->is_on_heap())((void) 0); | |||
2665 | // Load the (known) data pointer for the {receiver} and set {base_pointer} | |||
2666 | // and {external_pointer} to the values that will allow to generate typed | |||
2667 | // element accesses using the known data pointer. | |||
2668 | // The data pointer might be invalid if the {buffer} was detached, | |||
2669 | // so we need to make sure that any access is properly guarded. | |||
2670 | base_pointer = jsgraph()->ZeroConstant(); | |||
2671 | external_pointer = jsgraph()->PointerConstant(typed_array->data_ptr()); | |||
2672 | } else { | |||
2673 | // Load the {receiver}s length. | |||
2674 | length = effect = graph()->NewNode( | |||
2675 | simplified()->LoadField(AccessBuilder::ForJSTypedArrayLength()), | |||
2676 | receiver, effect, control); | |||
2677 | ||||
2678 | // Load the base pointer for the {receiver}. This will always be Smi | |||
2679 | // zero unless we allow on-heap TypedArrays, which is only the case | |||
2680 | // for Chrome. Node and Electron both set this limit to 0. Setting | |||
2681 | // the base to Smi zero here allows the EffectControlLinearizer to | |||
2682 | // optimize away the tricky part of the access later. | |||
2683 | if (JSTypedArray::kMaxSizeInHeap == 0) { | |||
2684 | base_pointer = jsgraph()->ZeroConstant(); | |||
2685 | } else { | |||
2686 | base_pointer = effect = | |||
2687 | graph()->NewNode(simplified()->LoadField( | |||
2688 | AccessBuilder::ForJSTypedArrayBasePointer()), | |||
2689 | receiver, effect, control); | |||
2690 | } | |||
2691 | ||||
2692 | // Load the external pointer for the {receiver}. | |||
2693 | external_pointer = effect = | |||
2694 | graph()->NewNode(simplified()->LoadField( | |||
2695 | AccessBuilder::ForJSTypedArrayExternalPointer()), | |||
2696 | receiver, effect, control); | |||
2697 | } | |||
2698 | ||||
2699 | // See if we can skip the detaching check. | |||
2700 | if (!dependencies()->DependOnArrayBufferDetachingProtector()) { | |||
2701 | // Load the buffer for the {receiver}. | |||
2702 | Node* buffer = | |||
2703 | typed_array.has_value() | |||
2704 | ? jsgraph()->Constant(typed_array->buffer()) | |||
2705 | : (effect = graph()->NewNode( | |||
2706 | simplified()->LoadField( | |||
2707 | AccessBuilder::ForJSArrayBufferViewBuffer()), | |||
2708 | receiver, effect, control)); | |||
2709 | ||||
2710 | // Deopt if the {buffer} was detached. | |||
2711 | // Note: A detached buffer leads to megamorphic feedback. | |||
2712 | Node* buffer_bit_field = effect = graph()->NewNode( | |||
2713 | simplified()->LoadField(AccessBuilder::ForJSArrayBufferBitField()), | |||
2714 | buffer, effect, control); | |||
2715 | Node* check = graph()->NewNode( | |||
2716 | simplified()->NumberEqual(), | |||
2717 | graph()->NewNode( | |||
2718 | simplified()->NumberBitwiseAnd(), buffer_bit_field, | |||
2719 | jsgraph()->Constant(JSArrayBuffer::WasDetachedBit::kMask)), | |||
2720 | jsgraph()->ZeroConstant()); | |||
2721 | effect = graph()->NewNode( | |||
2722 | simplified()->CheckIf(DeoptimizeReason::kArrayBufferWasDetached), | |||
2723 | check, effect, control); | |||
2724 | ||||
2725 | // Retain the {buffer} instead of {receiver} to reduce live ranges. | |||
2726 | buffer_or_receiver = buffer; | |||
2727 | } | |||
2728 | ||||
2729 | enum Situation { kBoundsCheckDone, kHandleOOB_SmiCheckDone }; | |||
2730 | Situation situation; | |||
2731 | if ((keyed_mode.IsLoad() && | |||
2732 | keyed_mode.load_mode() == LOAD_IGNORE_OUT_OF_BOUNDS) || | |||
2733 | (keyed_mode.IsStore() && | |||
2734 | keyed_mode.store_mode() == STORE_IGNORE_OUT_OF_BOUNDS)) { | |||
2735 | // Only check that the {index} is in SignedSmall range. We do the actual | |||
2736 | // bounds check below and just skip the property access if it's out of | |||
2737 | // bounds for the {receiver}. | |||
2738 | index = effect = graph()->NewNode( | |||
2739 | simplified()->CheckSmi(FeedbackSource()), index, effect, control); | |||
2740 | ||||
2741 | // Cast the {index} to Unsigned32 range, so that the bounds checks | |||
2742 | // below are performed on unsigned values, which means that all the | |||
2743 | // Negative32 values are treated as out-of-bounds. | |||
2744 | index = graph()->NewNode(simplified()->NumberToUint32(), index); | |||
2745 | situation = kHandleOOB_SmiCheckDone; | |||
2746 | } else { | |||
2747 | // Check that the {index} is in the valid range for the {receiver}. | |||
2748 | index = effect = graph()->NewNode( | |||
2749 | simplified()->CheckBounds( | |||
2750 | FeedbackSource(), CheckBoundsFlag::kConvertStringAndMinusZero), | |||
2751 | index, length, effect, control); | |||
2752 | situation = kBoundsCheckDone; | |||
2753 | } | |||
2754 | ||||
2755 | // Access the actual element. | |||
2756 | ExternalArrayType external_array_type = | |||
2757 | GetArrayTypeFromElementsKind(elements_kind); | |||
2758 | switch (keyed_mode.access_mode()) { | |||
2759 | case AccessMode::kLoad: { | |||
2760 | // Check if we can return undefined for out-of-bounds loads. | |||
2761 | if (situation == kHandleOOB_SmiCheckDone) { | |||
2762 | Node* check = | |||
2763 | graph()->NewNode(simplified()->NumberLessThan(), index, length); | |||
2764 | Node* branch = graph()->NewNode(common()->Branch(BranchHint::kTrue), | |||
2765 | check, control); | |||
2766 | ||||
2767 | Node* if_true = graph()->NewNode(common()->IfTrue(), branch); | |||
2768 | Node* etrue = effect; | |||
2769 | Node* vtrue; | |||
2770 | { | |||
2771 | // Do a real bounds check against {length}. This is in order to | |||
2772 | // protect against a potential typer bug leading to the elimination | |||
2773 | // of the NumberLessThan above. | |||
2774 | index = etrue = graph()->NewNode( | |||
2775 | simplified()->CheckBounds( | |||
2776 | FeedbackSource(), | |||
2777 | CheckBoundsFlag::kConvertStringAndMinusZero | | |||
2778 | CheckBoundsFlag::kAbortOnOutOfBounds), | |||
2779 | index, length, etrue, if_true); | |||
2780 | ||||
2781 | // Perform the actual load | |||
2782 | vtrue = etrue = graph()->NewNode( | |||
2783 | simplified()->LoadTypedElement(external_array_type), | |||
2784 | buffer_or_receiver, base_pointer, external_pointer, index, | |||
2785 | etrue, if_true); | |||
2786 | } | |||
2787 | ||||
2788 | Node* if_false = graph()->NewNode(common()->IfFalse(), branch); | |||
2789 | Node* efalse = effect; | |||
2790 | Node* vfalse; | |||
2791 | { | |||
2792 | // Materialize undefined for out-of-bounds loads. | |||
2793 | vfalse = jsgraph()->UndefinedConstant(); | |||
2794 | } | |||
2795 | ||||
2796 | control = graph()->NewNode(common()->Merge(2), if_true, if_false); | |||
2797 | effect = | |||
2798 | graph()->NewNode(common()->EffectPhi(2), etrue, efalse, control); | |||
2799 | value = | |||
2800 | graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2), | |||
2801 | vtrue, vfalse, control); | |||
2802 | } else { | |||
2803 | // Perform the actual load. | |||
2804 | DCHECK_EQ(kBoundsCheckDone, situation)((void) 0); | |||
2805 | value = effect = graph()->NewNode( | |||
2806 | simplified()->LoadTypedElement(external_array_type), | |||
2807 | buffer_or_receiver, base_pointer, external_pointer, index, effect, | |||
2808 | control); | |||
2809 | } | |||
2810 | break; | |||
2811 | } | |||
2812 | case AccessMode::kStoreInLiteral: | |||
2813 | case AccessMode::kDefine: | |||
2814 | UNREACHABLE()V8_Fatal("unreachable code"); | |||
2815 | case AccessMode::kStore: { | |||
2816 | // Ensure that the {value} is actually a Number or an Oddball, | |||
2817 | // and truncate it to a Number appropriately. | |||
2818 | value = effect = graph()->NewNode( | |||
2819 | simplified()->SpeculativeToNumber( | |||
2820 | NumberOperationHint::kNumberOrOddball, FeedbackSource()), | |||
2821 | value, effect, control); | |||
2822 | ||||
2823 | // Introduce the appropriate truncation for {value}. Currently we | |||
2824 | // only need to do this for ClamedUint8Array {receiver}s, as the | |||
2825 | // other truncations are implicit in the StoreTypedElement, but we | |||
2826 | // might want to change that at some point. | |||
2827 | if (external_array_type == kExternalUint8ClampedArray) { | |||
2828 | value = graph()->NewNode(simplified()->NumberToUint8Clamped(), value); | |||
2829 | } | |||
2830 | ||||
2831 | if (situation == kHandleOOB_SmiCheckDone) { | |||
2832 | // We have to detect OOB stores and handle them without deopt (by | |||
2833 | // simply not performing them). | |||
2834 | Node* check = | |||
2835 | graph()->NewNode(simplified()->NumberLessThan(), index, length); | |||
2836 | Node* branch = graph()->NewNode(common()->Branch(BranchHint::kTrue), | |||
2837 | check, control); | |||
2838 | ||||
2839 | Node* if_true = graph()->NewNode(common()->IfTrue(), branch); | |||
2840 | Node* etrue = effect; | |||
2841 | { | |||
2842 | // Do a real bounds check against {length}. This is in order to | |||
2843 | // protect against a potential typer bug leading to the elimination | |||
2844 | // of the NumberLessThan above. | |||
2845 | index = etrue = graph()->NewNode( | |||
2846 | simplified()->CheckBounds( | |||
2847 | FeedbackSource(), | |||
2848 | CheckBoundsFlag::kConvertStringAndMinusZero | | |||
2849 | CheckBoundsFlag::kAbortOnOutOfBounds), | |||
2850 | index, length, etrue, if_true); | |||
2851 | ||||
2852 | // Perform the actual store. | |||
2853 | etrue = graph()->NewNode( | |||
2854 | simplified()->StoreTypedElement(external_array_type), | |||
2855 | buffer_or_receiver, base_pointer, external_pointer, index, | |||
2856 | value, etrue, if_true); | |||
2857 | } | |||
2858 | ||||
2859 | Node* if_false = graph()->NewNode(common()->IfFalse(), branch); | |||
2860 | Node* efalse = effect; | |||
2861 | { | |||
2862 | // Just ignore the out-of-bounds write. | |||
2863 | } | |||
2864 | ||||
2865 | control = graph()->NewNode(common()->Merge(2), if_true, if_false); | |||
2866 | effect = | |||
2867 | graph()->NewNode(common()->EffectPhi(2), etrue, efalse, control); | |||
2868 | } else { | |||
2869 | // Perform the actual store | |||
2870 | DCHECK_EQ(kBoundsCheckDone, situation)((void) 0); | |||
2871 | effect = graph()->NewNode( | |||
2872 | simplified()->StoreTypedElement(external_array_type), | |||
2873 | buffer_or_receiver, base_pointer, external_pointer, index, value, | |||
2874 | effect, control); | |||
2875 | } | |||
2876 | break; | |||
2877 | } | |||
2878 | case AccessMode::kHas: | |||
2879 | if (situation == kHandleOOB_SmiCheckDone) { | |||
2880 | value = effect = | |||
2881 | graph()->NewNode(simplified()->SpeculativeNumberLessThan( | |||
2882 | NumberOperationHint::kSignedSmall), | |||
2883 | index, length, effect, control); | |||
2884 | } else { | |||
2885 | DCHECK_EQ(kBoundsCheckDone, situation)((void) 0); | |||
2886 | // For has-property on a typed array, all we need is a bounds check. | |||
2887 | value = jsgraph()->TrueConstant(); | |||
2888 | } | |||
2889 | break; | |||
2890 | } | |||
2891 | } else { | |||
2892 | // Load the elements for the {receiver}. | |||
2893 | Node* elements = effect = graph()->NewNode( | |||
2894 | simplified()->LoadField(AccessBuilder::ForJSObjectElements()), receiver, | |||
2895 | effect, control); | |||
2896 | ||||
2897 | // Don't try to store to a copy-on-write backing store (unless supported by | |||
2898 | // the store mode). | |||
2899 | if (keyed_mode.access_mode() == AccessMode::kStore && | |||
2900 | IsSmiOrObjectElementsKind(elements_kind) && | |||
2901 | !IsCOWHandlingStoreMode(keyed_mode.store_mode())) { | |||
2902 | effect = graph()->NewNode( | |||
2903 | simplified()->CheckMaps( | |||
2904 | CheckMapsFlag::kNone, | |||
2905 | ZoneHandleSet<Map>(factory()->fixed_array_map())), | |||
2906 | elements, effect, control); | |||
2907 | } | |||
2908 | ||||
2909 | // Check if the {receiver} is a JSArray. | |||
2910 | bool receiver_is_jsarray = HasOnlyJSArrayMaps(broker(), receiver_maps); | |||
2911 | ||||
2912 | // Load the length of the {receiver}. | |||
2913 | Node* length = effect = | |||
2914 | receiver_is_jsarray | |||
2915 | ? graph()->NewNode( | |||
2916 | simplified()->LoadField( | |||
2917 | AccessBuilder::ForJSArrayLength(elements_kind)), | |||
2918 | receiver, effect, control) | |||
2919 | : graph()->NewNode( | |||
2920 | simplified()->LoadField(AccessBuilder::ForFixedArrayLength()), | |||
2921 | elements, effect, control); | |||
2922 | ||||
2923 | // Check if we might need to grow the {elements} backing store. | |||
2924 | if (keyed_mode.IsStore() && IsGrowStoreMode(keyed_mode.store_mode())) { | |||
2925 | // For growing stores we validate the {index} below. | |||
2926 | } else if (keyed_mode.IsLoad() && | |||
2927 | keyed_mode.load_mode() == LOAD_IGNORE_OUT_OF_BOUNDS && | |||
2928 | CanTreatHoleAsUndefined(receiver_maps)) { | |||
2929 | // Check that the {index} is a valid array index, we do the actual | |||
2930 | // bounds check below and just skip the store below if it's out of | |||
2931 | // bounds for the {receiver}. | |||
2932 | index = effect = graph()->NewNode( | |||
2933 | simplified()->CheckBounds( | |||
2934 | FeedbackSource(), CheckBoundsFlag::kConvertStringAndMinusZero), | |||
2935 | index, jsgraph()->Constant(Smi::kMaxValue), effect, control); | |||
2936 | } else { | |||
2937 | // Check that the {index} is in the valid range for the {receiver}. | |||
2938 | index = effect = graph()->NewNode( | |||
2939 | simplified()->CheckBounds( | |||
2940 | FeedbackSource(), CheckBoundsFlag::kConvertStringAndMinusZero), | |||
2941 | index, length, effect, control); | |||
2942 | } | |||
2943 | ||||
2944 | // Compute the element access. | |||
2945 | Type element_type = Type::NonInternal(); | |||
2946 | MachineType element_machine_type = MachineType::AnyTagged(); | |||
2947 | if (IsDoubleElementsKind(elements_kind)) { | |||
2948 | element_type = Type::Number(); | |||
2949 | element_machine_type = MachineType::Float64(); | |||
2950 | } else if (IsSmiElementsKind(elements_kind)) { | |||
2951 | element_type = Type::SignedSmall(); | |||
2952 | element_machine_type = MachineType::TaggedSigned(); | |||
2953 | } | |||
2954 | ElementAccess element_access = {kTaggedBase, FixedArray::kHeaderSize, | |||
2955 | element_type, element_machine_type, | |||
2956 | kFullWriteBarrier}; | |||
2957 | ||||
2958 | // Access the actual element. | |||
2959 | if (keyed_mode.access_mode() == AccessMode::kLoad) { | |||
2960 | // Compute the real element access type, which includes the hole in case | |||
2961 | // of holey backing stores. | |||
2962 | if (IsHoleyElementsKind(elements_kind)) { | |||
2963 | element_access.type = | |||
2964 | Type::Union(element_type, Type::Hole(), graph()->zone()); | |||
2965 | } | |||
2966 | if (elements_kind == HOLEY_ELEMENTS || | |||
2967 | elements_kind == HOLEY_SMI_ELEMENTS) { | |||
2968 | element_access.machine_type = MachineType::AnyTagged(); | |||
2969 | } | |||
2970 | ||||
2971 | // Check if we can return undefined for out-of-bounds loads. | |||
2972 | if (keyed_mode.load_mode() == LOAD_IGNORE_OUT_OF_BOUNDS && | |||
2973 | CanTreatHoleAsUndefined(receiver_maps)) { | |||
2974 | Node* check = | |||
2975 | graph()->NewNode(simplified()->NumberLessThan(), index, length); | |||
2976 | Node* branch = graph()->NewNode(common()->Branch(BranchHint::kTrue), | |||
2977 | check, control); | |||
2978 | ||||
2979 | Node* if_true = graph()->NewNode(common()->IfTrue(), branch); | |||
2980 | Node* etrue = effect; | |||
2981 | Node* vtrue; | |||
2982 | { | |||
2983 | // Do a real bounds check against {length}. This is in order to | |||
2984 | // protect against a potential typer bug leading to the elimination of | |||
2985 | // the NumberLessThan above. | |||
2986 | index = etrue = | |||
2987 | graph()->NewNode(simplified()->CheckBounds( | |||
2988 | FeedbackSource(), | |||
2989 | CheckBoundsFlag::kConvertStringAndMinusZero | | |||
2990 | CheckBoundsFlag::kAbortOnOutOfBounds), | |||
2991 | index, length, etrue, if_true); | |||
2992 | ||||
2993 | // Perform the actual load | |||
2994 | vtrue = etrue = | |||
2995 | graph()->NewNode(simplified()->LoadElement(element_access), | |||
2996 | elements, index, etrue, if_true); | |||
2997 | ||||
2998 | // Handle loading from holey backing stores correctly, by either | |||
2999 | // mapping the hole to undefined if possible, or deoptimizing | |||
3000 | // otherwise. | |||
3001 | if (elements_kind == HOLEY_ELEMENTS || | |||
3002 | elements_kind == HOLEY_SMI_ELEMENTS) { | |||
3003 | // Turn the hole into undefined. | |||
3004 | vtrue = graph()->NewNode( | |||
3005 | simplified()->ConvertTaggedHoleToUndefined(), vtrue); | |||
3006 | } else if (elements_kind == HOLEY_DOUBLE_ELEMENTS) { | |||
3007 | // Return the signaling NaN hole directly if all uses are | |||
3008 | // truncating. | |||
3009 | vtrue = etrue = graph()->NewNode( | |||
3010 | simplified()->CheckFloat64Hole( | |||
3011 | CheckFloat64HoleMode::kAllowReturnHole, FeedbackSource()), | |||
3012 | vtrue, etrue, if_true); | |||
3013 | } | |||
3014 | } | |||
3015 | ||||
3016 | Node* if_false = graph()->NewNode(common()->IfFalse(), branch); | |||
3017 | Node* efalse = effect; | |||
3018 | Node* vfalse; | |||
3019 | { | |||
3020 | // Materialize undefined for out-of-bounds loads. | |||
3021 | vfalse = jsgraph()->UndefinedConstant(); | |||
3022 | } | |||
3023 | ||||
3024 | control = graph()->NewNode(common()->Merge(2), if_true, if_false); | |||
3025 | effect = | |||
3026 | graph()->NewNode(common()->EffectPhi(2), etrue, efalse, control); | |||
3027 | value = | |||
3028 | graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2), | |||
3029 | vtrue, vfalse, control); | |||
3030 | } else { | |||
3031 | // Perform the actual load. | |||
3032 | value = effect = | |||
3033 | graph()->NewNode(simplified()->LoadElement(element_access), | |||
3034 | elements, index, effect, control); | |||
3035 | ||||
3036 | // Handle loading from holey backing stores correctly, by either mapping | |||
3037 | // the hole to undefined if possible, or deoptimizing otherwise. | |||
3038 | if (elements_kind == HOLEY_ELEMENTS || | |||
3039 | elements_kind == HOLEY_SMI_ELEMENTS) { | |||
3040 | // Check if we are allowed to turn the hole into undefined. | |||
3041 | if (CanTreatHoleAsUndefined(receiver_maps)) { | |||
3042 | // Turn the hole into undefined. | |||
3043 | value = graph()->NewNode( | |||
3044 | simplified()->ConvertTaggedHoleToUndefined(), value); | |||
3045 | } else { | |||
3046 | // Bailout if we see the hole. | |||
3047 | value = effect = graph()->NewNode( | |||
3048 | simplified()->CheckNotTaggedHole(), value, effect, control); | |||
3049 | } | |||
3050 | } else if (elements_kind == HOLEY_DOUBLE_ELEMENTS) { | |||
3051 | // Perform the hole check on the result. | |||
3052 | CheckFloat64HoleMode mode = CheckFloat64HoleMode::kNeverReturnHole; | |||
3053 | // Check if we are allowed to return the hole directly. | |||
3054 | if (CanTreatHoleAsUndefined(receiver_maps)) { | |||
3055 | // Return the signaling NaN hole directly if all uses are | |||
3056 | // truncating. | |||
3057 | mode = CheckFloat64HoleMode::kAllowReturnHole; | |||
3058 | } | |||
3059 | value = effect = graph()->NewNode( | |||
3060 | simplified()->CheckFloat64Hole(mode, FeedbackSource()), value, | |||
3061 | effect, control); | |||
3062 | } | |||
3063 | } | |||
3064 | } else if (keyed_mode.access_mode() == AccessMode::kHas) { | |||
3065 | // For packed arrays with NoElementsProctector valid, a bound check | |||
3066 | // is equivalent to HasProperty. | |||
3067 | value = effect = graph()->NewNode(simplified()->SpeculativeNumberLessThan( | |||
3068 | NumberOperationHint::kSignedSmall), | |||
3069 | index, length, effect, control); | |||
3070 | if (IsHoleyElementsKind(elements_kind)) { | |||
3071 | // If the index is in bounds, do a load and hole check. | |||
3072 | ||||
3073 | Node* branch = graph()->NewNode(common()->Branch(), value, control); | |||
3074 | ||||
3075 | Node* if_false = graph()->NewNode(common()->IfFalse(), branch); | |||
3076 | Node* efalse = effect; | |||
3077 | Node* vfalse = jsgraph()->FalseConstant(); | |||
3078 | ||||
3079 | element_access.type = | |||
3080 | Type::Union(element_type, Type::Hole(), graph()->zone()); | |||
3081 | ||||
3082 | if (elements_kind == HOLEY_ELEMENTS || | |||
3083 | elements_kind == HOLEY_SMI_ELEMENTS) { | |||
3084 | element_access.machine_type = MachineType::AnyTagged(); | |||
3085 | } | |||
3086 | ||||
3087 | Node* if_true = graph()->NewNode(common()->IfTrue(), branch); | |||
3088 | Node* etrue = effect; | |||
3089 | ||||
3090 | Node* checked = etrue = graph()->NewNode( | |||
3091 | simplified()->CheckBounds( | |||
3092 | FeedbackSource(), CheckBoundsFlag::kConvertStringAndMinusZero), | |||
3093 | index, length, etrue, if_true); | |||
3094 | ||||
3095 | Node* element = etrue = | |||
3096 | graph()->NewNode(simplified()->LoadElement(element_access), | |||
3097 | elements, checked, etrue, if_true); | |||
3098 | ||||
3099 | Node* vtrue; | |||
3100 | if (CanTreatHoleAsUndefined(receiver_maps)) { | |||
3101 | if (elements_kind == HOLEY_ELEMENTS || | |||
3102 | elements_kind == HOLEY_SMI_ELEMENTS) { | |||
3103 | // Check if we are allowed to turn the hole into undefined. | |||
3104 | // Turn the hole into undefined. | |||
3105 | vtrue = graph()->NewNode(simplified()->ReferenceEqual(), element, | |||
3106 | jsgraph()->TheHoleConstant()); | |||
3107 | } else { | |||
3108 | vtrue = | |||
3109 | graph()->NewNode(simplified()->NumberIsFloat64Hole(), element); | |||
3110 | } | |||
3111 | ||||
3112 | // has == !IsHole | |||
3113 | vtrue = graph()->NewNode(simplified()->BooleanNot(), vtrue); | |||
3114 | } else { | |||
3115 | if (elements_kind == HOLEY_ELEMENTS || | |||
3116 | elements_kind == HOLEY_SMI_ELEMENTS) { | |||
3117 | // Bailout if we see the hole. | |||
3118 | etrue = graph()->NewNode(simplified()->CheckNotTaggedHole(), | |||
3119 | element, etrue, if_true); | |||
3120 | } else { | |||
3121 | etrue = graph()->NewNode( | |||
3122 | simplified()->CheckFloat64Hole( | |||
3123 | CheckFloat64HoleMode::kNeverReturnHole, FeedbackSource()), | |||
3124 | element, etrue, if_true); | |||
3125 | } | |||
3126 | ||||
3127 | vtrue = jsgraph()->TrueConstant(); | |||
3128 | } | |||
3129 | ||||
3130 | control = graph()->NewNode(common()->Merge(2), if_true, if_false); | |||
3131 | effect = | |||
3132 | graph()->NewNode(common()->EffectPhi(2), etrue, efalse, control); | |||
3133 | value = | |||
3134 | graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2), | |||
3135 | vtrue, vfalse, control); | |||
3136 | } | |||
3137 | } else { | |||
3138 | DCHECK(keyed_mode.access_mode() == AccessMode::kStore ||((void) 0) | |||
3139 | keyed_mode.access_mode() == AccessMode::kStoreInLiteral ||((void) 0) | |||
3140 | keyed_mode.access_mode() == AccessMode::kDefine)((void) 0); | |||
3141 | ||||
3142 | if (IsSmiElementsKind(elements_kind)) { | |||
3143 | value = effect = graph()->NewNode( | |||
3144 | simplified()->CheckSmi(FeedbackSource()), value, effect, control); | |||
3145 | } else if (IsDoubleElementsKind(elements_kind)) { | |||
3146 | value = effect = | |||
3147 | graph()->NewNode(simplified()->CheckNumber(FeedbackSource()), value, | |||
3148 | effect, control); | |||
3149 | // Make sure we do not store signalling NaNs into double arrays. | |||
3150 | value = graph()->NewNode(simplified()->NumberSilenceNaN(), value); | |||
3151 | } | |||
3152 | ||||
3153 | // Ensure that copy-on-write backing store is writable. | |||
3154 | if (IsSmiOrObjectElementsKind(elements_kind) && | |||
3155 | keyed_mode.store_mode() == STORE_HANDLE_COW) { | |||
3156 | elements = effect = | |||
3157 | graph()->NewNode(simplified()->EnsureWritableFastElements(), | |||
3158 | receiver, elements, effect, control); | |||
3159 | } else if (IsGrowStoreMode(keyed_mode.store_mode())) { | |||
3160 | // Determine the length of the {elements} backing store. | |||
3161 | Node* elements_length = effect = graph()->NewNode( | |||
3162 | simplified()->LoadField(AccessBuilder::ForFixedArrayLength()), | |||
3163 | elements, effect, control); | |||
3164 | ||||
3165 | // Validate the {index} depending on holeyness: | |||
3166 | // | |||
3167 | // For HOLEY_*_ELEMENTS the {index} must not exceed the {elements} | |||
3168 | // backing store capacity plus the maximum allowed gap, as otherwise | |||
3169 | // the (potential) backing store growth would normalize and thus | |||
3170 | // the elements kind of the {receiver} would change to slow mode. | |||
3171 | // | |||
3172 | // For PACKED_*_ELEMENTS the {index} must be within the range | |||
3173 | // [0,length+1[ to be valid. In case {index} equals {length}, | |||
3174 | // the {receiver} will be extended, but kept packed. | |||
3175 | Node* limit = | |||
3176 | IsHoleyElementsKind(elements_kind) | |||
3177 | ? graph()->NewNode(simplified()->NumberAdd(), elements_length, | |||
3178 | jsgraph()->Constant(JSObject::kMaxGap)) | |||
3179 | : graph()->NewNode(simplified()->NumberAdd(), length, | |||
3180 | jsgraph()->OneConstant()); | |||
3181 | index = effect = graph()->NewNode( | |||
3182 | simplified()->CheckBounds( | |||
3183 | FeedbackSource(), CheckBoundsFlag::kConvertStringAndMinusZero), | |||
3184 | index, limit, effect, control); | |||
3185 | ||||
3186 | // Grow {elements} backing store if necessary. | |||
3187 | GrowFastElementsMode mode = | |||
3188 | IsDoubleElementsKind(elements_kind) | |||
3189 | ? GrowFastElementsMode::kDoubleElements | |||
3190 | : GrowFastElementsMode::kSmiOrObjectElements; | |||
3191 | elements = effect = graph()->NewNode( | |||
3192 | simplified()->MaybeGrowFastElements(mode, FeedbackSource()), | |||
3193 | receiver, elements, index, elements_length, effect, control); | |||
3194 | ||||
3195 | // If we didn't grow {elements}, it might still be COW, in which case we | |||
3196 | // copy it now. | |||
3197 | if (IsSmiOrObjectElementsKind(elements_kind) && | |||
3198 | keyed_mode.store_mode() == STORE_AND_GROW_HANDLE_COW) { | |||
3199 | elements = effect = | |||
3200 | graph()->NewNode(simplified()->EnsureWritableFastElements(), | |||
3201 | receiver, elements, effect, control); | |||
3202 | } | |||
3203 | ||||
3204 | // Also update the "length" property if {receiver} is a JSArray. | |||
3205 | if (receiver_is_jsarray) { | |||
3206 | Node* check = | |||
3207 | graph()->NewNode(simplified()->NumberLessThan(), index, length); | |||
3208 | Node* branch = graph()->NewNode(common()->Branch(), check, control); | |||
3209 | ||||
3210 | Node* if_true = graph()->NewNode(common()->IfTrue(), branch); | |||
3211 | Node* etrue = effect; | |||
3212 | { | |||
3213 | // We don't need to do anything, the {index} is within | |||
3214 | // the valid bounds for the JSArray {receiver}. | |||
3215 | } | |||
3216 | ||||
3217 | Node* if_false = graph()->NewNode(common()->IfFalse(), branch); | |||
3218 | Node* efalse = effect; | |||
3219 | { | |||
3220 | // Update the JSArray::length field. Since this is observable, | |||
3221 | // there must be no other check after this. | |||
3222 | Node* new_length = graph()->NewNode( | |||
3223 | simplified()->NumberAdd(), index, jsgraph()->OneConstant()); | |||
3224 | efalse = graph()->NewNode( | |||
3225 | simplified()->StoreField( | |||
3226 | AccessBuilder::ForJSArrayLength(elements_kind)), | |||
3227 | receiver, new_length, efalse, if_false); | |||
3228 | } | |||
3229 | ||||
3230 | control = graph()->NewNode(common()->Merge(2), if_true, if_false); | |||
3231 | effect = | |||
3232 | graph()->NewNode(common()->EffectPhi(2), etrue, efalse, control); | |||
3233 | } | |||
3234 | } | |||
3235 | ||||
3236 | // Perform the actual element access. | |||
3237 | effect = graph()->NewNode(simplified()->StoreElement(element_access), | |||
3238 | elements, index, value, effect, control); | |||
3239 | } | |||
3240 | } | |||
3241 | ||||
3242 | return ValueEffectControl(value, effect, control); | |||
3243 | } | |||
3244 | ||||
3245 | Node* JSNativeContextSpecialization::BuildIndexedStringLoad( | |||
3246 | Node* receiver, Node* index, Node* length, Node** effect, Node** control, | |||
3247 | KeyedAccessLoadMode load_mode) { | |||
3248 | if (load_mode == LOAD_IGNORE_OUT_OF_BOUNDS && | |||
3249 | dependencies()->DependOnNoElementsProtector()) { | |||
3250 | // Ensure that the {index} is a valid String length. | |||
3251 | index = *effect = graph()->NewNode( | |||
3252 | simplified()->CheckBounds(FeedbackSource(), | |||
3253 | CheckBoundsFlag::kConvertStringAndMinusZero), | |||
3254 | index, jsgraph()->Constant(String::kMaxLength), *effect, *control); | |||
3255 | ||||
3256 | // Load the single character string from {receiver} or yield | |||
3257 | // undefined if the {index} is not within the valid bounds. | |||
3258 | Node* check = | |||
3259 | graph()->NewNode(simplified()->NumberLessThan(), index, length); | |||
3260 | Node* branch = | |||
3261 | graph()->NewNode(common()->Branch(BranchHint::kTrue), check, *control); | |||
3262 | ||||
3263 | Node* if_true = graph()->NewNode(common()->IfTrue(), branch); | |||
3264 | // Do a real bounds check against {length}. This is in order to protect | |||
3265 | // against a potential typer bug leading to the elimination of the | |||
3266 | // NumberLessThan above. | |||
3267 | Node* etrue = index = graph()->NewNode( | |||
3268 | simplified()->CheckBounds(FeedbackSource(), | |||
3269 | CheckBoundsFlag::kConvertStringAndMinusZero | | |||
3270 | CheckBoundsFlag::kAbortOnOutOfBounds), | |||
3271 | index, length, *effect, if_true); | |||
3272 | Node* vtrue = etrue = graph()->NewNode(simplified()->StringCharCodeAt(), | |||
3273 | receiver, index, etrue, if_true); | |||
3274 | vtrue = graph()->NewNode(simplified()->StringFromSingleCharCode(), vtrue); | |||
3275 | ||||
3276 | Node* if_false = graph()->NewNode(common()->IfFalse(), branch); | |||
3277 | Node* vfalse = jsgraph()->UndefinedConstant(); | |||
3278 | ||||
3279 | *control = graph()->NewNode(common()->Merge(2), if_true, if_false); | |||
3280 | *effect = | |||
3281 | graph()->NewNode(common()->EffectPhi(2), etrue, *effect, *control); | |||
3282 | return graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2), | |||
3283 | vtrue, vfalse, *control); | |||
3284 | } else { | |||
3285 | // Ensure that {index} is less than {receiver} length. | |||
3286 | index = *effect = graph()->NewNode( | |||
3287 | simplified()->CheckBounds(FeedbackSource(), | |||
3288 | CheckBoundsFlag::kConvertStringAndMinusZero), | |||
3289 | index, length, *effect, *control); | |||
3290 | ||||
3291 | // Return the character from the {receiver} as single character string. | |||
3292 | Node* value = *effect = graph()->NewNode( | |||
3293 | simplified()->StringCharCodeAt(), receiver, index, *effect, *control); | |||
3294 | value = graph()->NewNode(simplified()->StringFromSingleCharCode(), value); | |||
3295 | return value; | |||
3296 | } | |||
3297 | } | |||
3298 | ||||
3299 | Node* JSNativeContextSpecialization::BuildExtendPropertiesBackingStore( | |||
3300 | const MapRef& map, Node* properties, Node* effect, Node* control) { | |||
3301 | // TODO(bmeurer/jkummerow): Property deletions can undo map transitions | |||
3302 | // while keeping the backing store around, meaning that even though the | |||
3303 | // map might believe that objects have no unused property fields, there | |||
3304 | // might actually be some. It would be nice to not create a new backing | |||
3305 | // store in that case (i.e. when properties->length() >= new_length). | |||
3306 | // However, introducing branches and Phi nodes here would make it more | |||
3307 | // difficult for escape analysis to get rid of the backing stores used | |||
3308 | // for intermediate states of chains of property additions. That makes | |||
3309 | // it unclear what the best approach is here. | |||
3310 | DCHECK_EQ(0, map.UnusedPropertyFields())((void) 0); | |||
3311 | // Compute the length of the old {properties} and the new properties. | |||
3312 | int length = map.NextFreePropertyIndex() - map.GetInObjectProperties(); | |||
3313 | int new_length = length + JSObject::kFieldsAdded; | |||
3314 | // Collect the field values from the {properties}. | |||
3315 | ZoneVector<Node*> values(zone()); | |||
3316 | values.reserve(new_length); | |||
3317 | for (int i = 0; i < length; ++i) { | |||
3318 | Node* value = effect = graph()->NewNode( | |||
3319 | simplified()->LoadField(AccessBuilder::ForFixedArraySlot(i)), | |||
3320 | properties, effect, control); | |||
3321 | values.push_back(value); | |||
3322 | } | |||
3323 | // Initialize the new fields to undefined. | |||
3324 | for (int i = 0; i < JSObject::kFieldsAdded; ++i) { | |||
3325 | values.push_back(jsgraph()->UndefinedConstant()); | |||
3326 | } | |||
3327 | ||||
3328 | // Compute new length and hash. | |||
3329 | Node* hash; | |||
3330 | if (length == 0) { | |||
3331 | hash = graph()->NewNode( | |||
3332 | common()->Select(MachineRepresentation::kTaggedSigned), | |||
3333 | graph()->NewNode(simplified()->ObjectIsSmi(), properties), properties, | |||
3334 | jsgraph()->SmiConstant(PropertyArray::kNoHashSentinel)); | |||
3335 | hash = effect = graph()->NewNode(common()->TypeGuard(Type::SignedSmall()), | |||
3336 | hash, effect, control); | |||
3337 | hash = | |||
3338 | graph()->NewNode(simplified()->NumberShiftLeft(), hash, | |||
3339 | jsgraph()->Constant(PropertyArray::HashField::kShift)); | |||
3340 | } else { | |||
3341 | hash = effect = graph()->NewNode( | |||
3342 | simplified()->LoadField(AccessBuilder::ForPropertyArrayLengthAndHash()), | |||
3343 | properties, effect, control); | |||
3344 | hash = | |||
3345 | graph()->NewNode(simplified()->NumberBitwiseAnd(), hash, | |||
3346 | jsgraph()->Constant(PropertyArray::HashField::kMask)); | |||
3347 | } | |||
3348 | Node* new_length_and_hash = graph()->NewNode( | |||
3349 | simplified()->NumberBitwiseOr(), jsgraph()->Constant(new_length), hash); | |||
3350 | // TDOO(jarin): Fix the typer to infer tighter bound for NumberBitwiseOr. | |||
3351 | new_length_and_hash = effect = | |||
3352 | graph()->NewNode(common()->TypeGuard(Type::SignedSmall()), | |||
3353 | new_length_and_hash, effect, control); | |||
3354 | ||||
3355 | // Allocate and initialize the new properties. | |||
3356 | AllocationBuilder a(jsgraph(), effect, control); | |||
3357 | a.Allocate(PropertyArray::SizeFor(new_length), AllocationType::kYoung, | |||
3358 | Type::OtherInternal()); | |||
3359 | a.Store(AccessBuilder::ForMap(), jsgraph()->PropertyArrayMapConstant()); | |||
3360 | a.Store(AccessBuilder::ForPropertyArrayLengthAndHash(), new_length_and_hash); | |||
3361 | for (int i = 0; i < new_length; ++i) { | |||
3362 | a.Store(AccessBuilder::ForFixedArraySlot(i), values[i]); | |||
3363 | } | |||
3364 | return a.Finish(); | |||
3365 | } | |||
3366 | ||||
3367 | Node* JSNativeContextSpecialization::BuildCheckEqualsName(NameRef const& name, | |||
3368 | Node* value, | |||
3369 | Node* effect, | |||
3370 | Node* control) { | |||
3371 | DCHECK(name.IsUniqueName())((void) 0); | |||
3372 | Operator const* const op = | |||
3373 | name.IsSymbol() ? simplified()->CheckEqualsSymbol() | |||
3374 | : simplified()->CheckEqualsInternalizedString(); | |||
3375 | return graph()->NewNode(op, jsgraph()->Constant(name), value, effect, | |||
3376 | control); | |||
3377 | } | |||
3378 | ||||
3379 | bool JSNativeContextSpecialization::CanTreatHoleAsUndefined( | |||
3380 | ZoneVector<MapRef> const& receiver_maps) { | |||
3381 | // Check if all {receiver_maps} have one of the initial Array.prototype | |||
3382 | // or Object.prototype objects as their prototype (in any of the current | |||
3383 | // native contexts, as the global Array protector works isolate-wide). | |||
3384 | for (MapRef receiver_map : receiver_maps) { | |||
3385 | ObjectRef receiver_prototype = receiver_map.prototype(); | |||
3386 | if (!receiver_prototype.IsJSObject() || | |||
3387 | !broker()->IsArrayOrObjectPrototype(receiver_prototype.AsJSObject())) { | |||
3388 | return false; | |||
3389 | } | |||
3390 | } | |||
3391 | ||||
3392 | // Check if the array prototype chain is intact. | |||
3393 | return dependencies()->DependOnNoElementsProtector(); | |||
3394 | } | |||
3395 | ||||
3396 | bool JSNativeContextSpecialization::InferMaps(Node* object, Effect effect, | |||
3397 | ZoneVector<MapRef>* maps) const { | |||
3398 | ZoneRefUnorderedSet<MapRef> map_set(broker()->zone()); | |||
3399 | NodeProperties::InferMapsResult result = | |||
3400 | NodeProperties::InferMapsUnsafe(broker(), object, effect, &map_set); | |||
3401 | if (result == NodeProperties::kReliableMaps) { | |||
3402 | for (const MapRef& map : map_set) { | |||
3403 | maps->push_back(map); | |||
3404 | } | |||
3405 | return true; | |||
3406 | } else if (result == NodeProperties::kUnreliableMaps) { | |||
3407 | // For untrusted maps, we can still use the information | |||
3408 | // if the maps are stable. | |||
3409 | for (const MapRef& map : map_set) { | |||
3410 | if (!map.is_stable()) return false; | |||
3411 | } | |||
3412 | for (const MapRef& map : map_set) { | |||
3413 | maps->push_back(map); | |||
3414 | } | |||
3415 | return true; | |||
3416 | } | |||
3417 | return false; | |||
3418 | } | |||
3419 | ||||
3420 | base::Optional<MapRef> JSNativeContextSpecialization::InferRootMap( | |||
3421 | Node* object) const { | |||
3422 | HeapObjectMatcher m(object); | |||
3423 | if (m.HasResolvedValue()) { | |||
3424 | MapRef map = m.Ref(broker()).map(); | |||
3425 | return map.FindRootMap(); | |||
3426 | } else if (m.IsJSCreate()) { | |||
3427 | base::Optional<MapRef> initial_map = | |||
3428 | NodeProperties::GetJSCreateMap(broker(), object); | |||
3429 | if (initial_map.has_value()) { | |||
3430 | DCHECK(initial_map->equals(initial_map->FindRootMap()))((void) 0); | |||
3431 | return *initial_map; | |||
3432 | } | |||
3433 | } | |||
3434 | return base::nullopt; | |||
3435 | } | |||
3436 | ||||
3437 | Node* JSNativeContextSpecialization::BuildLoadPrototypeFromObject( | |||
3438 | Node* object, Node* effect, Node* control) { | |||
3439 | Node* map = effect = | |||
3440 | graph()->NewNode(simplified()->LoadField(AccessBuilder::ForMap()), object, | |||
3441 | effect, control); | |||
3442 | return graph()->NewNode( | |||
3443 | simplified()->LoadField(AccessBuilder::ForMapPrototype()), map, effect, | |||
3444 | control); | |||
3445 | } | |||
3446 | ||||
3447 | Graph* JSNativeContextSpecialization::graph() const { | |||
3448 | return jsgraph()->graph(); | |||
3449 | } | |||
3450 | ||||
3451 | Isolate* JSNativeContextSpecialization::isolate() const { | |||
3452 | return jsgraph()->isolate(); | |||
3453 | } | |||
3454 | ||||
3455 | Factory* JSNativeContextSpecialization::factory() const { | |||
3456 | return isolate()->factory(); | |||
3457 | } | |||
3458 | ||||
3459 | CommonOperatorBuilder* JSNativeContextSpecialization::common() const { | |||
3460 | return jsgraph()->common(); | |||
3461 | } | |||
3462 | ||||
3463 | JSOperatorBuilder* JSNativeContextSpecialization::javascript() const { | |||
3464 | return jsgraph()->javascript(); | |||
3465 | } | |||
3466 | ||||
3467 | SimplifiedOperatorBuilder* JSNativeContextSpecialization::simplified() const { | |||
3468 | return jsgraph()->simplified(); | |||
3469 | } | |||
3470 | ||||
3471 | } // namespace compiler | |||
3472 | } // namespace internal | |||
3473 | } // namespace v8 |