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