// Copyright 2015 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "src/compiler/js-call-reducer.h" #include "src/api-inl.h" #include "src/builtins/builtins-promise-gen.h" #include "src/builtins/builtins-utils.h" #include "src/code-factory.h" #include "src/code-stubs.h" #include "src/compiler/access-builder.h" #include "src/compiler/access-info.h" #include "src/compiler/allocation-builder.h" #include "src/compiler/compilation-dependencies.h" #include "src/compiler/js-graph.h" #include "src/compiler/linkage.h" #include "src/compiler/node-matchers.h" #include "src/compiler/property-access-builder.h" #include "src/compiler/simplified-operator.h" #include "src/compiler/type-cache.h" #include "src/feedback-vector-inl.h" #include "src/ic/call-optimization.h" #include "src/objects-inl.h" #include "src/objects/arguments-inl.h" #include "src/objects/js-array-buffer-inl.h" #include "src/objects/js-array-inl.h" #include "src/objects/js-objects.h" #include "src/vector-slot-pair.h" namespace v8 { namespace internal { namespace compiler { Reduction JSCallReducer::ReduceMathUnary(Node* node, const Operator* op) { CallParameters const& p = CallParametersOf(node->op()); if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) { return NoChange(); } if (node->op()->ValueInputCount() < 3) { Node* value = jsgraph()->NaNConstant(); ReplaceWithValue(node, value); return Replace(value); } Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); Node* input = NodeProperties::GetValueInput(node, 2); input = effect = graph()->NewNode(simplified()->SpeculativeToNumber( NumberOperationHint::kNumberOrOddball, p.feedback()), input, effect, control); Node* value = graph()->NewNode(op, input); ReplaceWithValue(node, value, effect); return Replace(value); } Reduction JSCallReducer::ReduceMathBinary(Node* node, const Operator* op) { CallParameters const& p = CallParametersOf(node->op()); if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) { return NoChange(); } if (node->op()->ValueInputCount() < 3) { Node* value = jsgraph()->NaNConstant(); ReplaceWithValue(node, value); return Replace(value); } Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); Node* left = NodeProperties::GetValueInput(node, 2); Node* right = node->op()->ValueInputCount() > 3 ? NodeProperties::GetValueInput(node, 3) : jsgraph()->NaNConstant(); left = effect = graph()->NewNode(simplified()->SpeculativeToNumber( NumberOperationHint::kNumberOrOddball, p.feedback()), left, effect, control); right = effect = graph()->NewNode(simplified()->SpeculativeToNumber( NumberOperationHint::kNumberOrOddball, p.feedback()), right, effect, control); Node* value = graph()->NewNode(op, left, right); ReplaceWithValue(node, value, effect); return Replace(value); } // ES6 section 20.2.2.19 Math.imul ( x, y ) Reduction JSCallReducer::ReduceMathImul(Node* node) { CallParameters const& p = CallParametersOf(node->op()); if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) { return NoChange(); } if (node->op()->ValueInputCount() < 3) { Node* value = jsgraph()->ZeroConstant(); ReplaceWithValue(node, value); return Replace(value); } Node* left = NodeProperties::GetValueInput(node, 2); Node* right = node->op()->ValueInputCount() > 3 ? NodeProperties::GetValueInput(node, 3) : jsgraph()->ZeroConstant(); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); left = effect = graph()->NewNode(simplified()->SpeculativeToNumber( NumberOperationHint::kNumberOrOddball, p.feedback()), left, effect, control); right = effect = graph()->NewNode(simplified()->SpeculativeToNumber( NumberOperationHint::kNumberOrOddball, p.feedback()), right, effect, control); left = graph()->NewNode(simplified()->NumberToUint32(), left); right = graph()->NewNode(simplified()->NumberToUint32(), right); Node* value = graph()->NewNode(simplified()->NumberImul(), left, right); ReplaceWithValue(node, value, effect); return Replace(value); } // ES6 section 20.2.2.11 Math.clz32 ( x ) Reduction JSCallReducer::ReduceMathClz32(Node* node) { CallParameters const& p = CallParametersOf(node->op()); if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) { return NoChange(); } if (node->op()->ValueInputCount() < 3) { Node* value = jsgraph()->Constant(32); ReplaceWithValue(node, value); return Replace(value); } Node* input = NodeProperties::GetValueInput(node, 2); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); input = effect = graph()->NewNode(simplified()->SpeculativeToNumber( NumberOperationHint::kNumberOrOddball, p.feedback()), input, effect, control); input = graph()->NewNode(simplified()->NumberToUint32(), input); Node* value = graph()->NewNode(simplified()->NumberClz32(), input); ReplaceWithValue(node, value, effect); return Replace(value); } // ES6 section 20.2.2.24 Math.max ( value1, value2, ...values ) // ES6 section 20.2.2.25 Math.min ( value1, value2, ...values ) Reduction JSCallReducer::ReduceMathMinMax(Node* node, const Operator* op, Node* empty_value) { CallParameters const& p = CallParametersOf(node->op()); if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) { return NoChange(); } if (node->op()->ValueInputCount() <= 2) { ReplaceWithValue(node, empty_value); return Replace(empty_value); } Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); Node* value = effect = graph()->NewNode(simplified()->SpeculativeToNumber( NumberOperationHint::kNumberOrOddball, p.feedback()), NodeProperties::GetValueInput(node, 2), effect, control); for (int i = 3; i < node->op()->ValueInputCount(); i++) { Node* input = effect = graph()->NewNode( simplified()->SpeculativeToNumber(NumberOperationHint::kNumberOrOddball, p.feedback()), NodeProperties::GetValueInput(node, i), effect, control); value = graph()->NewNode(op, value, input); } ReplaceWithValue(node, value, effect); return Replace(value); } Reduction JSCallReducer::Reduce(Node* node) { switch (node->opcode()) { case IrOpcode::kJSConstruct: return ReduceJSConstruct(node); case IrOpcode::kJSConstructWithArrayLike: return ReduceJSConstructWithArrayLike(node); case IrOpcode::kJSConstructWithSpread: return ReduceJSConstructWithSpread(node); case IrOpcode::kJSCall: return ReduceJSCall(node); case IrOpcode::kJSCallWithArrayLike: return ReduceJSCallWithArrayLike(node); case IrOpcode::kJSCallWithSpread: return ReduceJSCallWithSpread(node); default: break; } return NoChange(); } void JSCallReducer::Finalize() { // TODO(turbofan): This is not the best solution; ideally we would be able // to teach the GraphReducer about arbitrary dependencies between different // nodes, even if they don't show up in the use list of the other node. std::set const waitlist = std::move(waitlist_); for (Node* node : waitlist) { if (!node->IsDead()) { Reduction const reduction = Reduce(node); if (reduction.Changed()) { Node* replacement = reduction.replacement(); if (replacement != node) { Replace(node, replacement); } } } } } // ES6 section 22.1.1 The Array Constructor Reduction JSCallReducer::ReduceArrayConstructor(Node* node) { DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); Node* target = NodeProperties::GetValueInput(node, 0); CallParameters const& p = CallParametersOf(node->op()); // Turn the {node} into a {JSCreateArray} call. DCHECK_LE(2u, p.arity()); size_t const arity = p.arity() - 2; NodeProperties::ReplaceValueInput(node, target, 0); NodeProperties::ReplaceValueInput(node, target, 1); NodeProperties::ChangeOp( node, javascript()->CreateArray(arity, MaybeHandle())); return Changed(node); } // ES6 section 19.3.1.1 Boolean ( value ) Reduction JSCallReducer::ReduceBooleanConstructor(Node* node) { DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); CallParameters const& p = CallParametersOf(node->op()); // Replace the {node} with a proper {ToBoolean} operator. DCHECK_LE(2u, p.arity()); Node* value = (p.arity() == 2) ? jsgraph()->UndefinedConstant() : NodeProperties::GetValueInput(node, 2); value = graph()->NewNode(simplified()->ToBoolean(), value); ReplaceWithValue(node, value); return Replace(value); } // ES section #sec-object-constructor Reduction JSCallReducer::ReduceObjectConstructor(Node* node) { DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); CallParameters const& p = CallParametersOf(node->op()); if (p.arity() < 3) return NoChange(); Node* value = (p.arity() >= 3) ? NodeProperties::GetValueInput(node, 2) : jsgraph()->UndefinedConstant(); Node* effect = NodeProperties::GetEffectInput(node); // We can fold away the Object(x) call if |x| is definitely not a primitive. if (NodeProperties::CanBePrimitive(isolate(), value, effect)) { if (!NodeProperties::CanBeNullOrUndefined(isolate(), value, effect)) { // Turn the {node} into a {JSToObject} call if we know that // the {value} cannot be null or undefined. NodeProperties::ReplaceValueInputs(node, value); NodeProperties::ChangeOp(node, javascript()->ToObject()); return Changed(node); } } else { ReplaceWithValue(node, value); return Replace(value); } return NoChange(); } // ES6 section 19.2.3.1 Function.prototype.apply ( thisArg, argArray ) Reduction JSCallReducer::ReduceFunctionPrototypeApply(Node* node) { DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); CallParameters const& p = CallParametersOf(node->op()); size_t arity = p.arity(); DCHECK_LE(2u, arity); ConvertReceiverMode convert_mode = ConvertReceiverMode::kAny; if (arity == 2) { // Neither thisArg nor argArray was provided. convert_mode = ConvertReceiverMode::kNullOrUndefined; node->ReplaceInput(0, node->InputAt(1)); node->ReplaceInput(1, jsgraph()->UndefinedConstant()); } else if (arity == 3) { // The argArray was not provided, just remove the {target}. node->RemoveInput(0); --arity; } else { Node* target = NodeProperties::GetValueInput(node, 1); Node* this_argument = NodeProperties::GetValueInput(node, 2); Node* arguments_list = NodeProperties::GetValueInput(node, 3); Node* context = NodeProperties::GetContextInput(node); Node* frame_state = NodeProperties::GetFrameStateInput(node); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); // If {arguments_list} cannot be null or undefined, we don't need // to expand this {node} to control-flow. if (!NodeProperties::CanBeNullOrUndefined(isolate(), arguments_list, effect)) { // Massage the value inputs appropriately. node->ReplaceInput(0, target); node->ReplaceInput(1, this_argument); node->ReplaceInput(2, arguments_list); while (arity-- > 3) node->RemoveInput(3); // Morph the {node} to a {JSCallWithArrayLike}. NodeProperties::ChangeOp(node, javascript()->CallWithArrayLike(p.frequency())); Reduction const reduction = ReduceJSCallWithArrayLike(node); return reduction.Changed() ? reduction : Changed(node); } else { // Check whether {arguments_list} is null. Node* check_null = graph()->NewNode(simplified()->ReferenceEqual(), arguments_list, jsgraph()->NullConstant()); control = graph()->NewNode(common()->Branch(BranchHint::kFalse), check_null, control); Node* if_null = graph()->NewNode(common()->IfTrue(), control); control = graph()->NewNode(common()->IfFalse(), control); // Check whether {arguments_list} is undefined. Node* check_undefined = graph()->NewNode(simplified()->ReferenceEqual(), arguments_list, jsgraph()->UndefinedConstant()); control = graph()->NewNode(common()->Branch(BranchHint::kFalse), check_undefined, control); Node* if_undefined = graph()->NewNode(common()->IfTrue(), control); control = graph()->NewNode(common()->IfFalse(), control); // Lower to {JSCallWithArrayLike} if {arguments_list} is neither null // nor undefined. Node* effect0 = effect; Node* control0 = control; Node* value0 = effect0 = control0 = graph()->NewNode( javascript()->CallWithArrayLike(p.frequency()), target, this_argument, arguments_list, context, frame_state, effect0, control0); // Lower to {JSCall} if {arguments_list} is either null or undefined. Node* effect1 = effect; Node* control1 = graph()->NewNode(common()->Merge(2), if_null, if_undefined); Node* value1 = effect1 = control1 = graph()->NewNode(javascript()->Call(2), target, this_argument, context, frame_state, effect1, control1); // Rewire potential exception edges. Node* if_exception = nullptr; if (NodeProperties::IsExceptionalCall(node, &if_exception)) { // Create appropriate {IfException} and {IfSuccess} nodes. Node* if_exception0 = graph()->NewNode(common()->IfException(), control0, effect0); control0 = graph()->NewNode(common()->IfSuccess(), control0); Node* if_exception1 = graph()->NewNode(common()->IfException(), control1, effect1); control1 = graph()->NewNode(common()->IfSuccess(), control1); // Join the exception edges. Node* merge = graph()->NewNode(common()->Merge(2), if_exception0, if_exception1); Node* ephi = graph()->NewNode(common()->EffectPhi(2), if_exception0, if_exception1, merge); Node* phi = graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2), if_exception0, if_exception1, merge); ReplaceWithValue(if_exception, phi, ephi, merge); } // Join control paths. control = graph()->NewNode(common()->Merge(2), control0, control1); effect = graph()->NewNode(common()->EffectPhi(2), effect0, effect1, control); Node* value = graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2), value0, value1, control); ReplaceWithValue(node, value, effect, control); return Replace(value); } } // Change {node} to the new {JSCall} operator. NodeProperties::ChangeOp( node, javascript()->Call(arity, p.frequency(), VectorSlotPair(), convert_mode)); // Try to further reduce the JSCall {node}. Reduction const reduction = ReduceJSCall(node); return reduction.Changed() ? reduction : Changed(node); } // ES section #sec-function.prototype.bind Reduction JSCallReducer::ReduceFunctionPrototypeBind(Node* node) { DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); // Value inputs to the {node} are as follows: // // - target, which is Function.prototype.bind JSFunction // - receiver, which is the [[BoundTargetFunction]] // - bound_this (optional), which is the [[BoundThis]] // - and all the remaining value inouts are [[BoundArguments]] Node* receiver = NodeProperties::GetValueInput(node, 1); Node* bound_this = (node->op()->ValueInputCount() < 3) ? jsgraph()->UndefinedConstant() : NodeProperties::GetValueInput(node, 2); Node* context = NodeProperties::GetContextInput(node); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); // Ensure that the {receiver} is known to be a JSBoundFunction or // a JSFunction with the same [[Prototype]], and all maps we've // seen for the {receiver} so far indicate that {receiver} is // definitely a constructor or not a constructor. ZoneHandleSet receiver_maps; NodeProperties::InferReceiverMapsResult result = NodeProperties::InferReceiverMaps(isolate(), receiver, effect, &receiver_maps); if (result == NodeProperties::kNoReceiverMaps) return NoChange(); DCHECK_NE(0, receiver_maps.size()); bool const is_constructor = receiver_maps[0]->is_constructor(); Handle const prototype(receiver_maps[0]->prototype(), isolate()); for (Handle const receiver_map : receiver_maps) { // Check for consistency among the {receiver_maps}. STATIC_ASSERT(LAST_TYPE == LAST_FUNCTION_TYPE); if (receiver_map->prototype() != *prototype) return NoChange(); if (receiver_map->is_constructor() != is_constructor) return NoChange(); if (receiver_map->instance_type() < FIRST_FUNCTION_TYPE) return NoChange(); // Disallow binding of slow-mode functions. We need to figure out // whether the length and name property are in the original state. if (receiver_map->is_dictionary_map()) return NoChange(); // Check whether the length and name properties are still present // as AccessorInfo objects. In that case, their values can be // recomputed even if the actual value of the object changes. // This mirrors the checks done in builtins-function-gen.cc at // runtime otherwise. Handle descriptors(receiver_map->instance_descriptors(), isolate()); if (descriptors->number_of_descriptors() < 2) return NoChange(); if (descriptors->GetKey(JSFunction::kLengthDescriptorIndex) != ReadOnlyRoots(isolate()).length_string()) { return NoChange(); } if (!descriptors->GetStrongValue(JSFunction::kLengthDescriptorIndex) ->IsAccessorInfo()) { return NoChange(); } if (descriptors->GetKey(JSFunction::kNameDescriptorIndex) != ReadOnlyRoots(isolate()).name_string()) { return NoChange(); } if (!descriptors->GetStrongValue(JSFunction::kNameDescriptorIndex) ->IsAccessorInfo()) { return NoChange(); } } // Setup the map for the resulting JSBoundFunction with the // correct instance {prototype}. Handle map( is_constructor ? native_context()->bound_function_with_constructor_map() : native_context()->bound_function_without_constructor_map(), isolate()); if (map->prototype() != *prototype) { map = Map::TransitionToPrototype(isolate(), map, prototype); } // Make sure we can rely on the {receiver_maps}. if (result == NodeProperties::kUnreliableReceiverMaps) { effect = graph()->NewNode( simplified()->CheckMaps(CheckMapsFlag::kNone, receiver_maps), receiver, effect, control); } // Replace the {node} with a JSCreateBoundFunction. int const arity = std::max(0, node->op()->ValueInputCount() - 3); int const input_count = 2 + arity + 3; Node** inputs = graph()->zone()->NewArray(input_count); inputs[0] = receiver; inputs[1] = bound_this; for (int i = 0; i < arity; ++i) { inputs[2 + i] = NodeProperties::GetValueInput(node, 3 + i); } inputs[2 + arity + 0] = context; inputs[2 + arity + 1] = effect; inputs[2 + arity + 2] = control; Node* value = effect = graph()->NewNode( javascript()->CreateBoundFunction(arity, map), input_count, inputs); ReplaceWithValue(node, value, effect, control); return Replace(value); } // ES6 section 19.2.3.3 Function.prototype.call (thisArg, ...args) Reduction JSCallReducer::ReduceFunctionPrototypeCall(Node* node) { DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); CallParameters const& p = CallParametersOf(node->op()); Node* target = NodeProperties::GetValueInput(node, 0); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); // Change context of {node} to the Function.prototype.call context, // to ensure any exception is thrown in the correct context. Node* context; HeapObjectMatcher m(target); if (m.HasValue()) { Handle function = Handle::cast(m.Value()); context = jsgraph()->HeapConstant(handle(function->context(), isolate())); } else { context = effect = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForJSFunctionContext()), target, effect, control); } NodeProperties::ReplaceContextInput(node, context); NodeProperties::ReplaceEffectInput(node, effect); // Remove the target from {node} and use the receiver as target instead, and // the thisArg becomes the new target. If thisArg was not provided, insert // undefined instead. size_t arity = p.arity(); DCHECK_LE(2u, arity); ConvertReceiverMode convert_mode; if (arity == 2) { // The thisArg was not provided, use undefined as receiver. convert_mode = ConvertReceiverMode::kNullOrUndefined; node->ReplaceInput(0, node->InputAt(1)); node->ReplaceInput(1, jsgraph()->UndefinedConstant()); } else { // Just remove the target, which is the first value input. convert_mode = ConvertReceiverMode::kAny; node->RemoveInput(0); --arity; } NodeProperties::ChangeOp( node, javascript()->Call(arity, p.frequency(), VectorSlotPair(), convert_mode)); // Try to further reduce the JSCall {node}. Reduction const reduction = ReduceJSCall(node); return reduction.Changed() ? reduction : Changed(node); } // ES6 section 19.2.3.6 Function.prototype [ @@hasInstance ] (V) Reduction JSCallReducer::ReduceFunctionPrototypeHasInstance(Node* node) { DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); Node* receiver = NodeProperties::GetValueInput(node, 1); Node* object = (node->op()->ValueInputCount() >= 3) ? NodeProperties::GetValueInput(node, 2) : jsgraph()->UndefinedConstant(); Node* context = NodeProperties::GetContextInput(node); Node* frame_state = NodeProperties::GetFrameStateInput(node); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); // TODO(turbofan): If JSOrdinaryToInstance raises an exception, the // stack trace doesn't contain the @@hasInstance call; we have the // corresponding bug in the baseline case. Some massaging of the frame // state would be necessary here. // Morph this {node} into a JSOrdinaryHasInstance node. node->ReplaceInput(0, receiver); node->ReplaceInput(1, object); node->ReplaceInput(2, context); node->ReplaceInput(3, frame_state); node->ReplaceInput(4, effect); node->ReplaceInput(5, control); node->TrimInputCount(6); NodeProperties::ChangeOp(node, javascript()->OrdinaryHasInstance()); return Changed(node); } Reduction JSCallReducer::ReduceObjectGetPrototype(Node* node, Node* object) { Node* effect = NodeProperties::GetEffectInput(node); // Try to determine the {object} map. ZoneHandleSet object_maps; NodeProperties::InferReceiverMapsResult result = NodeProperties::InferReceiverMaps(isolate(), object, effect, &object_maps); if (result != NodeProperties::kNoReceiverMaps) { Handle candidate_map = object_maps[0]; Handle candidate_prototype(candidate_map->prototype(), isolate()); // Check if we can constant-fold the {candidate_prototype}. for (size_t i = 0; i < object_maps.size(); ++i) { Handle object_map = object_maps[i]; if (object_map->IsSpecialReceiverMap() || object_map->has_hidden_prototype() || object_map->prototype() != *candidate_prototype) { // We exclude special receivers, like JSProxy or API objects that // might require access checks here; we also don't want to deal // with hidden prototypes at this point. return NoChange(); } // The above check also excludes maps for primitive values, which is // important because we are not applying [[ToObject]] here as expected. DCHECK(!object_map->IsPrimitiveMap() && object_map->IsJSReceiverMap()); if (result == NodeProperties::kUnreliableReceiverMaps && !object_map->is_stable()) { return NoChange(); } } if (result == NodeProperties::kUnreliableReceiverMaps) { for (size_t i = 0; i < object_maps.size(); ++i) { dependencies()->DependOnStableMap( MapRef(js_heap_broker(), object_maps[i])); } } Node* value = jsgraph()->Constant(candidate_prototype); ReplaceWithValue(node, value); return Replace(value); } return NoChange(); } // ES6 section 19.1.2.11 Object.getPrototypeOf ( O ) Reduction JSCallReducer::ReduceObjectGetPrototypeOf(Node* node) { DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); Node* object = (node->op()->ValueInputCount() >= 3) ? NodeProperties::GetValueInput(node, 2) : jsgraph()->UndefinedConstant(); return ReduceObjectGetPrototype(node, object); } // ES section #sec-object.is Reduction JSCallReducer::ReduceObjectIs(Node* node) { DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); CallParameters const& params = CallParametersOf(node->op()); int const argc = static_cast(params.arity() - 2); Node* lhs = (argc >= 1) ? NodeProperties::GetValueInput(node, 2) : jsgraph()->UndefinedConstant(); Node* rhs = (argc >= 2) ? NodeProperties::GetValueInput(node, 3) : jsgraph()->UndefinedConstant(); Node* value = graph()->NewNode(simplified()->SameValue(), lhs, rhs); ReplaceWithValue(node, value); return Replace(value); } // ES6 section B.2.2.1.1 get Object.prototype.__proto__ Reduction JSCallReducer::ReduceObjectPrototypeGetProto(Node* node) { DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); Node* receiver = NodeProperties::GetValueInput(node, 1); return ReduceObjectGetPrototype(node, receiver); } // ES #sec-object.prototype.hasownproperty Reduction JSCallReducer::ReduceObjectPrototypeHasOwnProperty(Node* node) { DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); CallParameters const& params = CallParametersOf(node->op()); int const argc = static_cast(params.arity() - 2); Node* receiver = NodeProperties::GetValueInput(node, 1); Node* name = (argc >= 1) ? NodeProperties::GetValueInput(node, 2) : jsgraph()->UndefinedConstant(); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); // We can optimize a call to Object.prototype.hasOwnProperty if it's being // used inside a fast-mode for..in, so for code like this: // // for (name in receiver) { // if (receiver.hasOwnProperty(name)) { // ... // } // } // // If the for..in is in fast-mode, we know that the {receiver} has {name} // as own property, otherwise the enumeration wouldn't include it. The graph // constructed by the BytecodeGraphBuilder in this case looks like this: // receiver // ^ ^ // | | // | +-+ // | | // | JSToObject // | ^ // | | // | JSForInNext // | ^ // +----+ | // | | // JSCall[hasOwnProperty] // We can constant-fold the {node} to True in this case, and insert // a (potentially redundant) map check to guard the fact that the // {receiver} map didn't change since the dominating JSForInNext. This // map check is only necessary when TurboFan cannot prove that there // is no observable side effect between the {JSForInNext} and the // {JSCall} to Object.prototype.hasOwnProperty. // // Also note that it's safe to look through the {JSToObject}, since the // Object.prototype.hasOwnProperty does an implicit ToObject anyway, and // these operations are not observable. if (name->opcode() == IrOpcode::kJSForInNext) { ForInMode const mode = ForInModeOf(name->op()); if (mode != ForInMode::kGeneric) { Node* object = NodeProperties::GetValueInput(name, 0); Node* cache_type = NodeProperties::GetValueInput(name, 2); if (object->opcode() == IrOpcode::kJSToObject) { object = NodeProperties::GetValueInput(object, 0); } if (object == receiver) { // No need to repeat the map check if we can prove that there's no // observable side effect between {effect} and {name]. if (!NodeProperties::NoObservableSideEffectBetween(effect, name)) { Node* receiver_map = effect = graph()->NewNode(simplified()->LoadField(AccessBuilder::ForMap()), receiver, effect, control); Node* check = graph()->NewNode(simplified()->ReferenceEqual(), receiver_map, cache_type); effect = graph()->NewNode( simplified()->CheckIf(DeoptimizeReason::kWrongMap), check, effect, control); } Node* value = jsgraph()->TrueConstant(); ReplaceWithValue(node, value, effect, control); return Replace(value); } } } return NoChange(); } // ES #sec-object.prototype.isprototypeof Reduction JSCallReducer::ReduceObjectPrototypeIsPrototypeOf(Node* node) { DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); Node* receiver = NodeProperties::GetValueInput(node, 1); Node* value = node->op()->ValueInputCount() > 2 ? NodeProperties::GetValueInput(node, 2) : jsgraph()->UndefinedConstant(); Node* effect = NodeProperties::GetEffectInput(node); // Ensure that the {receiver} is known to be a JSReceiver (so that // the ToObject step of Object.prototype.isPrototypeOf is a no-op). ZoneHandleSet receiver_maps; NodeProperties::InferReceiverMapsResult result = NodeProperties::InferReceiverMaps(isolate(), receiver, effect, &receiver_maps); if (result == NodeProperties::kNoReceiverMaps) return NoChange(); for (size_t i = 0; i < receiver_maps.size(); ++i) { if (!receiver_maps[i]->IsJSReceiverMap()) return NoChange(); } // We don't check whether {value} is a proper JSReceiver here explicitly, // and don't explicitly rule out Primitive {value}s, since all of them // have null as their prototype, so the prototype chain walk inside the // JSHasInPrototypeChain operator immediately aborts and yields false. NodeProperties::ReplaceValueInput(node, value, 0); NodeProperties::ReplaceValueInput(node, receiver, 1); for (int i = node->op()->ValueInputCount(); i-- > 2;) { node->RemoveInput(i); } NodeProperties::ChangeOp(node, javascript()->HasInPrototypeChain()); return Changed(node); } // ES6 section 26.1.1 Reflect.apply ( target, thisArgument, argumentsList ) Reduction JSCallReducer::ReduceReflectApply(Node* node) { DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); CallParameters const& p = CallParametersOf(node->op()); int arity = static_cast(p.arity() - 2); DCHECK_LE(0, arity); // Massage value inputs appropriately. node->RemoveInput(0); node->RemoveInput(0); while (arity < 3) { node->InsertInput(graph()->zone(), arity++, jsgraph()->UndefinedConstant()); } while (arity-- > 3) { node->RemoveInput(arity); } NodeProperties::ChangeOp(node, javascript()->CallWithArrayLike(p.frequency())); Reduction const reduction = ReduceJSCallWithArrayLike(node); return reduction.Changed() ? reduction : Changed(node); } // ES6 section 26.1.2 Reflect.construct ( target, argumentsList [, newTarget] ) Reduction JSCallReducer::ReduceReflectConstruct(Node* node) { DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); CallParameters const& p = CallParametersOf(node->op()); int arity = static_cast(p.arity() - 2); DCHECK_LE(0, arity); // Massage value inputs appropriately. node->RemoveInput(0); node->RemoveInput(0); while (arity < 2) { node->InsertInput(graph()->zone(), arity++, jsgraph()->UndefinedConstant()); } if (arity < 3) { node->InsertInput(graph()->zone(), arity++, node->InputAt(0)); } while (arity-- > 3) { node->RemoveInput(arity); } NodeProperties::ChangeOp(node, javascript()->ConstructWithArrayLike(p.frequency())); Reduction const reduction = ReduceJSConstructWithArrayLike(node); return reduction.Changed() ? reduction : Changed(node); } // ES6 section 26.1.7 Reflect.getPrototypeOf ( target ) Reduction JSCallReducer::ReduceReflectGetPrototypeOf(Node* node) { DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); Node* target = (node->op()->ValueInputCount() >= 3) ? NodeProperties::GetValueInput(node, 2) : jsgraph()->UndefinedConstant(); return ReduceObjectGetPrototype(node, target); } // ES6 section #sec-object.create Object.create(proto, properties) Reduction JSCallReducer::ReduceObjectCreate(Node* node) { int arg_count = node->op()->ValueInputCount(); Node* properties = arg_count >= 4 ? NodeProperties::GetValueInput(node, 3) : jsgraph()->UndefinedConstant(); if (properties != jsgraph()->UndefinedConstant()) return NoChange(); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); Node* context = NodeProperties::GetContextInput(node); Node* frame_state = NodeProperties::GetFrameStateInput(node); Node* prototype = arg_count >= 3 ? NodeProperties::GetValueInput(node, 2) : jsgraph()->UndefinedConstant(); node->ReplaceInput(0, prototype); node->ReplaceInput(1, context); node->ReplaceInput(2, frame_state); node->ReplaceInput(3, effect); node->ReplaceInput(4, control); node->TrimInputCount(5); NodeProperties::ChangeOp(node, javascript()->CreateObject()); return Changed(node); } // ES section #sec-reflect.get Reduction JSCallReducer::ReduceReflectGet(Node* node) { DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); CallParameters const& p = CallParametersOf(node->op()); int arity = static_cast(p.arity() - 2); if (arity != 2) return NoChange(); Node* target = NodeProperties::GetValueInput(node, 2); Node* key = NodeProperties::GetValueInput(node, 3); Node* context = NodeProperties::GetContextInput(node); Node* frame_state = NodeProperties::GetFrameStateInput(node); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); // Check whether {target} is a JSReceiver. Node* check = graph()->NewNode(simplified()->ObjectIsReceiver(), target); Node* branch = graph()->NewNode(common()->Branch(BranchHint::kTrue), check, control); // Throw an appropriate TypeError if the {target} is not a JSReceiver. Node* if_false = graph()->NewNode(common()->IfFalse(), branch); Node* efalse = effect; { if_false = efalse = graph()->NewNode( javascript()->CallRuntime(Runtime::kThrowTypeError, 2), jsgraph()->Constant(MessageTemplate::kCalledOnNonObject), jsgraph()->HeapConstant( factory()->NewStringFromAsciiChecked("Reflect.get")), context, frame_state, efalse, if_false); } // Otherwise just use the existing GetPropertyStub. Node* if_true = graph()->NewNode(common()->IfTrue(), branch); Node* etrue = effect; Node* vtrue; { Callable callable = Builtins::CallableFor(isolate(), Builtins::kGetProperty); auto call_descriptor = Linkage::GetStubCallDescriptor( graph()->zone(), callable.descriptor(), callable.descriptor().GetStackParameterCount(), CallDescriptor::kNeedsFrameState, Operator::kNoProperties); Node* stub_code = jsgraph()->HeapConstant(callable.code()); vtrue = etrue = if_true = graph()->NewNode(common()->Call(call_descriptor), stub_code, target, key, context, frame_state, etrue, if_true); } // Rewire potential exception edges. Node* on_exception = nullptr; if (NodeProperties::IsExceptionalCall(node, &on_exception)) { // Create appropriate {IfException} and {IfSuccess} nodes. Node* extrue = graph()->NewNode(common()->IfException(), etrue, if_true); if_true = graph()->NewNode(common()->IfSuccess(), if_true); Node* exfalse = graph()->NewNode(common()->IfException(), efalse, if_false); if_false = graph()->NewNode(common()->IfSuccess(), if_false); // Join the exception edges. Node* merge = graph()->NewNode(common()->Merge(2), extrue, exfalse); Node* ephi = graph()->NewNode(common()->EffectPhi(2), extrue, exfalse, merge); Node* phi = graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2), extrue, exfalse, merge); ReplaceWithValue(on_exception, phi, ephi, merge); } // Connect the throwing path to end. if_false = graph()->NewNode(common()->Throw(), efalse, if_false); NodeProperties::MergeControlToEnd(graph(), common(), if_false); // Continue on the regular path. ReplaceWithValue(node, vtrue, etrue, if_true); return Changed(vtrue); } // ES section #sec-reflect.has Reduction JSCallReducer::ReduceReflectHas(Node* node) { DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); CallParameters const& p = CallParametersOf(node->op()); int arity = static_cast(p.arity() - 2); DCHECK_LE(0, arity); Node* target = (arity >= 1) ? NodeProperties::GetValueInput(node, 2) : jsgraph()->UndefinedConstant(); Node* key = (arity >= 2) ? NodeProperties::GetValueInput(node, 3) : jsgraph()->UndefinedConstant(); Node* context = NodeProperties::GetContextInput(node); Node* frame_state = NodeProperties::GetFrameStateInput(node); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); // Check whether {target} is a JSReceiver. Node* check = graph()->NewNode(simplified()->ObjectIsReceiver(), target); Node* branch = graph()->NewNode(common()->Branch(BranchHint::kTrue), check, control); // Throw an appropriate TypeError if the {target} is not a JSReceiver. Node* if_false = graph()->NewNode(common()->IfFalse(), branch); Node* efalse = effect; { if_false = efalse = graph()->NewNode( javascript()->CallRuntime(Runtime::kThrowTypeError, 2), jsgraph()->Constant(MessageTemplate::kCalledOnNonObject), jsgraph()->HeapConstant( factory()->NewStringFromAsciiChecked("Reflect.has")), context, frame_state, efalse, if_false); } // Otherwise just use the existing {JSHasProperty} logic. Node* if_true = graph()->NewNode(common()->IfTrue(), branch); Node* etrue = effect; Node* vtrue; { vtrue = etrue = if_true = graph()->NewNode(javascript()->HasProperty(), target, key, context, frame_state, etrue, if_true); } // Rewire potential exception edges. Node* on_exception = nullptr; if (NodeProperties::IsExceptionalCall(node, &on_exception)) { // Create appropriate {IfException} and {IfSuccess} nodes. Node* extrue = graph()->NewNode(common()->IfException(), etrue, if_true); if_true = graph()->NewNode(common()->IfSuccess(), if_true); Node* exfalse = graph()->NewNode(common()->IfException(), efalse, if_false); if_false = graph()->NewNode(common()->IfSuccess(), if_false); // Join the exception edges. Node* merge = graph()->NewNode(common()->Merge(2), extrue, exfalse); Node* ephi = graph()->NewNode(common()->EffectPhi(2), extrue, exfalse, merge); Node* phi = graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2), extrue, exfalse, merge); ReplaceWithValue(on_exception, phi, ephi, merge); } // Connect the throwing path to end. if_false = graph()->NewNode(common()->Throw(), efalse, if_false); NodeProperties::MergeControlToEnd(graph(), common(), if_false); // Continue on the regular path. ReplaceWithValue(node, vtrue, etrue, if_true); return Changed(vtrue); } bool CanInlineArrayIteratingBuiltin(Isolate* isolate, Handle receiver_map) { if (!receiver_map->prototype()->IsJSArray()) return false; Handle receiver_prototype(JSArray::cast(receiver_map->prototype()), isolate); return receiver_map->instance_type() == JS_ARRAY_TYPE && IsFastElementsKind(receiver_map->elements_kind()) && isolate->IsNoElementsProtectorIntact() && isolate->IsAnyInitialArrayPrototype(receiver_prototype); } Node* JSCallReducer::WireInLoopStart(Node* k, Node** control, Node** effect) { Node* loop = *control = graph()->NewNode(common()->Loop(2), *control, *control); Node* eloop = *effect = graph()->NewNode(common()->EffectPhi(2), *effect, *effect, loop); Node* terminate = graph()->NewNode(common()->Terminate(), eloop, loop); NodeProperties::MergeControlToEnd(graph(), common(), terminate); return graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2), k, k, loop); } void JSCallReducer::WireInLoopEnd(Node* loop, Node* eloop, Node* vloop, Node* k, Node* control, Node* effect) { loop->ReplaceInput(1, control); vloop->ReplaceInput(1, k); eloop->ReplaceInput(1, effect); } Reduction JSCallReducer::ReduceArrayForEach(Node* node, Handle shared) { if (!FLAG_turbo_inline_array_builtins) return NoChange(); DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); CallParameters const& p = CallParametersOf(node->op()); if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) { return NoChange(); } Node* outer_frame_state = NodeProperties::GetFrameStateInput(node); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); Node* context = NodeProperties::GetContextInput(node); // Try to determine the {receiver} map. Node* receiver = NodeProperties::GetValueInput(node, 1); Node* fncallback = node->op()->ValueInputCount() > 2 ? NodeProperties::GetValueInput(node, 2) : jsgraph()->UndefinedConstant(); Node* this_arg = node->op()->ValueInputCount() > 3 ? NodeProperties::GetValueInput(node, 3) : jsgraph()->UndefinedConstant(); ZoneHandleSet receiver_maps; NodeProperties::InferReceiverMapsResult result = NodeProperties::InferReceiverMaps(isolate(), receiver, effect, &receiver_maps); if (result == NodeProperties::kNoReceiverMaps) return NoChange(); // By ensuring that {kind} is object or double, we can be polymorphic // on different elements kinds. ElementsKind kind = receiver_maps[0]->elements_kind(); if (IsSmiElementsKind(kind)) { kind = FastSmiToObjectElementsKind(kind); } for (Handle receiver_map : receiver_maps) { ElementsKind next_kind = receiver_map->elements_kind(); if (!CanInlineArrayIteratingBuiltin(isolate(), receiver_map)) { return NoChange(); } if (!IsFastElementsKind(next_kind)) { return NoChange(); } if (IsDoubleElementsKind(kind) != IsDoubleElementsKind(next_kind)) { return NoChange(); } if (IsHoleyElementsKind(next_kind)) { kind = GetHoleyElementsKind(kind); } } // Install code dependencies on the {receiver} prototype maps and the // global array protector cell. dependencies()->DependOnProtector( PropertyCellRef(js_heap_broker(), factory()->no_elements_protector())); // If we have unreliable maps, we need a map check. if (result == NodeProperties::kUnreliableReceiverMaps) { effect = graph()->NewNode(simplified()->CheckMaps(CheckMapsFlag::kNone, receiver_maps, p.feedback()), receiver, effect, control); } Node* k = jsgraph()->ZeroConstant(); Node* original_length = effect = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForJSArrayLength(kind)), receiver, effect, control); std::vector checkpoint_params( {receiver, fncallback, this_arg, k, original_length}); const int stack_parameters = static_cast(checkpoint_params.size()); // Check whether the given callback function is callable. Note that this has // to happen outside the loop to make sure we also throw on empty arrays. Node* check_frame_state = CreateJavaScriptBuiltinContinuationFrameState( jsgraph(), shared, Builtins::kArrayForEachLoopLazyDeoptContinuation, node->InputAt(0), context, &checkpoint_params[0], stack_parameters, outer_frame_state, ContinuationFrameStateMode::LAZY); Node* check_fail = nullptr; Node* check_throw = nullptr; WireInCallbackIsCallableCheck(fncallback, context, check_frame_state, effect, &control, &check_fail, &check_throw); // Start the loop. Node* vloop = k = WireInLoopStart(k, &control, &effect); Node *loop = control, *eloop = effect; checkpoint_params[3] = k; Node* continue_test = graph()->NewNode(simplified()->NumberLessThan(), k, original_length); Node* continue_branch = graph()->NewNode(common()->Branch(BranchHint::kTrue), continue_test, control); Node* if_true = graph()->NewNode(common()->IfTrue(), continue_branch); Node* if_false = graph()->NewNode(common()->IfFalse(), continue_branch); control = if_true; Node* frame_state = CreateJavaScriptBuiltinContinuationFrameState( jsgraph(), shared, Builtins::kArrayForEachLoopEagerDeoptContinuation, node->InputAt(0), context, &checkpoint_params[0], stack_parameters, outer_frame_state, ContinuationFrameStateMode::EAGER); effect = graph()->NewNode(common()->Checkpoint(), frame_state, effect, control); // Make sure the map hasn't changed during the iteration effect = graph()->NewNode(simplified()->CheckMaps(CheckMapsFlag::kNone, receiver_maps, p.feedback()), receiver, effect, control); Node* element = SafeLoadElement(kind, receiver, control, &effect, &k, p.feedback()); Node* next_k = graph()->NewNode(simplified()->NumberAdd(), k, jsgraph()->OneConstant()); checkpoint_params[3] = next_k; Node* hole_true = nullptr; Node* hole_false = nullptr; Node* effect_true = effect; if (IsHoleyElementsKind(kind)) { // Holey elements kind require a hole check and skipping of the element in // the case of a hole. Node* check; if (IsDoubleElementsKind(kind)) { check = graph()->NewNode(simplified()->NumberIsFloat64Hole(), element); } else { check = graph()->NewNode(simplified()->ReferenceEqual(), element, jsgraph()->TheHoleConstant()); } Node* branch = graph()->NewNode(common()->Branch(BranchHint::kFalse), check, control); hole_true = graph()->NewNode(common()->IfTrue(), branch); hole_false = graph()->NewNode(common()->IfFalse(), branch); control = hole_false; // The contract is that we don't leak "the hole" into "user JavaScript", // so we must rename the {element} here to explicitly exclude "the hole" // from the type of {element}. element = effect = graph()->NewNode( common()->TypeGuard(Type::NonInternal()), element, effect, control); } frame_state = CreateJavaScriptBuiltinContinuationFrameState( jsgraph(), shared, Builtins::kArrayForEachLoopLazyDeoptContinuation, node->InputAt(0), context, &checkpoint_params[0], stack_parameters, outer_frame_state, ContinuationFrameStateMode::LAZY); control = effect = graph()->NewNode( javascript()->Call(5, p.frequency()), fncallback, this_arg, element, k, receiver, context, frame_state, effect, control); // Rewire potential exception edges. Node* on_exception = nullptr; if (NodeProperties::IsExceptionalCall(node, &on_exception)) { RewirePostCallbackExceptionEdges(check_throw, on_exception, effect, &check_fail, &control); } if (IsHoleyElementsKind(kind)) { Node* after_call_control = control; Node* after_call_effect = effect; control = hole_true; effect = effect_true; control = graph()->NewNode(common()->Merge(2), control, after_call_control); effect = graph()->NewNode(common()->EffectPhi(2), effect, after_call_effect, control); } WireInLoopEnd(loop, eloop, vloop, next_k, control, effect); control = if_false; effect = eloop; // Wire up the branch for the case when IsCallable fails for the callback. // Since {check_throw} is an unconditional throw, it's impossible to // return a successful completion. Therefore, we simply connect the successful // completion to the graph end. Node* throw_node = graph()->NewNode(common()->Throw(), check_throw, check_fail); NodeProperties::MergeControlToEnd(graph(), common(), throw_node); ReplaceWithValue(node, jsgraph()->UndefinedConstant(), effect, control); return Replace(jsgraph()->UndefinedConstant()); } Reduction JSCallReducer::ReduceArrayReduce(Node* node, ArrayReduceDirection direction, Handle shared) { if (!FLAG_turbo_inline_array_builtins) return NoChange(); DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); CallParameters const& p = CallParametersOf(node->op()); if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) { return NoChange(); } bool left = direction == ArrayReduceDirection::kLeft; Node* outer_frame_state = NodeProperties::GetFrameStateInput(node); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); Node* context = NodeProperties::GetContextInput(node); // Try to determine the {receiver} map. Node* receiver = NodeProperties::GetValueInput(node, 1); Node* fncallback = node->op()->ValueInputCount() > 2 ? NodeProperties::GetValueInput(node, 2) : jsgraph()->UndefinedConstant(); ZoneHandleSet receiver_maps; NodeProperties::InferReceiverMapsResult result = NodeProperties::InferReceiverMaps(isolate(), receiver, effect, &receiver_maps); if (result == NodeProperties::kNoReceiverMaps) return NoChange(); ElementsKind kind = receiver_maps[0]->elements_kind(); for (Handle receiver_map : receiver_maps) { if (!CanInlineArrayIteratingBuiltin(isolate(), receiver_map)) return NoChange(); if (!UnionElementsKindUptoSize(&kind, receiver_map->elements_kind())) return NoChange(); } std::function hole_check = [this, kind](Node* element) { if (IsDoubleElementsKind(kind)) { return graph()->NewNode(simplified()->NumberIsFloat64Hole(), element); } else { return graph()->NewNode(simplified()->ReferenceEqual(), element, jsgraph()->TheHoleConstant()); } }; // Install code dependencies on the {receiver} prototype maps and the // global array protector cell. dependencies()->DependOnProtector( PropertyCellRef(js_heap_broker(), factory()->no_elements_protector())); // If we have unreliable maps, we need a map check. if (result == NodeProperties::kUnreliableReceiverMaps) { effect = graph()->NewNode(simplified()->CheckMaps(CheckMapsFlag::kNone, receiver_maps, p.feedback()), receiver, effect, control); } Node* original_length = effect = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForJSArrayLength(PACKED_ELEMENTS)), receiver, effect, control); Node* initial_index = left ? jsgraph()->ZeroConstant() : graph()->NewNode(simplified()->NumberSubtract(), original_length, jsgraph()->OneConstant()); const Operator* next_op = left ? simplified()->NumberAdd() : simplified()->NumberSubtract(); Node* k = initial_index; Node* check_frame_state; { Builtins::Name builtin_lazy = left ? Builtins::kArrayReduceLoopLazyDeoptContinuation : Builtins::kArrayReduceRightLoopLazyDeoptContinuation; const std::vector checkpoint_params( {receiver, fncallback, k, original_length, jsgraph()->UndefinedConstant()}); const int stack_parameters = static_cast(checkpoint_params.size()); check_frame_state = CreateJavaScriptBuiltinContinuationFrameState( jsgraph(), shared, builtin_lazy, node->InputAt(0), context, checkpoint_params.data(), stack_parameters - 1, outer_frame_state, ContinuationFrameStateMode::LAZY); } Node* check_fail = nullptr; Node* check_throw = nullptr; // Check whether the given callback function is callable. Note that // this has to happen outside the loop to make sure we also throw on // empty arrays. WireInCallbackIsCallableCheck(fncallback, context, check_frame_state, effect, &control, &check_fail, &check_throw); // Set initial accumulator value Node* cur = jsgraph()->TheHoleConstant(); if (node->op()->ValueInputCount() > 3) { cur = NodeProperties::GetValueInput(node, 3); } else { // Find first/last non holey element. In case the search fails, we need a // deopt continuation. Builtins::Name builtin_eager = left ? Builtins::kArrayReducePreLoopEagerDeoptContinuation : Builtins::kArrayReduceRightPreLoopEagerDeoptContinuation; const std::vector checkpoint_params( {receiver, fncallback, original_length}); const int stack_parameters = static_cast(checkpoint_params.size()); Node* find_first_element_frame_state = CreateJavaScriptBuiltinContinuationFrameState( jsgraph(), shared, builtin_eager, node->InputAt(0), context, checkpoint_params.data(), stack_parameters, outer_frame_state, ContinuationFrameStateMode::EAGER); Node* vloop = k = WireInLoopStart(k, &control, &effect); Node* loop = control; Node* eloop = effect; effect = graph()->NewNode(common()->Checkpoint(), find_first_element_frame_state, effect, control); Node* continue_test = left ? graph()->NewNode(simplified()->NumberLessThan(), k, original_length) : graph()->NewNode(simplified()->NumberLessThanOrEqual(), jsgraph()->ZeroConstant(), k); effect = graph()->NewNode( simplified()->CheckIf(DeoptimizeReason::kNoInitialElement), continue_test, effect, control); cur = SafeLoadElement(kind, receiver, control, &effect, &k, p.feedback()); Node* next_k = graph()->NewNode(next_op, k, jsgraph()->OneConstant()); Node* hole_branch = graph()->NewNode(common()->Branch(BranchHint::kTrue), hole_check(cur), control); Node* found_el = graph()->NewNode(common()->IfFalse(), hole_branch); control = found_el; Node* is_hole = graph()->NewNode(common()->IfTrue(), hole_branch); WireInLoopEnd(loop, eloop, vloop, next_k, is_hole, effect); // We did the hole-check, so exclude hole from the type. cur = effect = graph()->NewNode(common()->TypeGuard(Type::NonInternal()), cur, effect, control); k = next_k; } // Start the loop. Node* loop = control = graph()->NewNode(common()->Loop(2), control, control); Node* eloop = effect = graph()->NewNode(common()->EffectPhi(2), effect, effect, loop); Node* terminate = graph()->NewNode(common()->Terminate(), eloop, loop); NodeProperties::MergeControlToEnd(graph(), common(), terminate); Node* kloop = k = graph()->NewNode( common()->Phi(MachineRepresentation::kTagged, 2), k, k, loop); Node* curloop = cur = graph()->NewNode( common()->Phi(MachineRepresentation::kTagged, 2), cur, cur, loop); control = loop; effect = eloop; Node* continue_test = left ? graph()->NewNode(simplified()->NumberLessThan(), k, original_length) : graph()->NewNode(simplified()->NumberLessThanOrEqual(), jsgraph()->ZeroConstant(), k); Node* continue_branch = graph()->NewNode(common()->Branch(BranchHint::kTrue), continue_test, control); Node* if_true = graph()->NewNode(common()->IfTrue(), continue_branch); Node* if_false = graph()->NewNode(common()->IfFalse(), continue_branch); control = if_true; { Builtins::Name builtin_eager = left ? Builtins::kArrayReduceLoopEagerDeoptContinuation : Builtins::kArrayReduceRightLoopEagerDeoptContinuation; const std::vector checkpoint_params( {receiver, fncallback, k, original_length, curloop}); const int stack_parameters = static_cast(checkpoint_params.size()); Node* frame_state = CreateJavaScriptBuiltinContinuationFrameState( jsgraph(), shared, builtin_eager, node->InputAt(0), context, checkpoint_params.data(), stack_parameters, outer_frame_state, ContinuationFrameStateMode::EAGER); effect = graph()->NewNode(common()->Checkpoint(), frame_state, effect, control); } // Make sure the map hasn't changed during the iteration effect = graph()->NewNode( simplified()->CheckMaps(CheckMapsFlag::kNone, receiver_maps), receiver, effect, control); Node* element = SafeLoadElement(kind, receiver, control, &effect, &k, p.feedback()); Node* next_k = graph()->NewNode(next_op, k, jsgraph()->OneConstant()); Node* hole_true = nullptr; Node* hole_false = nullptr; Node* effect_true = effect; if (IsHoleyElementsKind(kind)) { // Holey elements kind require a hole check and skipping of the element in // the case of a hole. Node* branch = graph()->NewNode(common()->Branch(BranchHint::kFalse), hole_check(element), control); hole_true = graph()->NewNode(common()->IfTrue(), branch); hole_false = graph()->NewNode(common()->IfFalse(), branch); control = hole_false; // The contract is that we don't leak "the hole" into "user JavaScript", // so we must rename the {element} here to explicitly exclude "the hole" // from the type of {element}. element = effect = graph()->NewNode( common()->TypeGuard(Type::NonInternal()), element, effect, control); } Node* next_cur; { Builtins::Name builtin_lazy = left ? Builtins::kArrayReduceLoopLazyDeoptContinuation : Builtins::kArrayReduceRightLoopLazyDeoptContinuation; const std::vector checkpoint_params( {receiver, fncallback, next_k, original_length, curloop}); const int stack_parameters = static_cast(checkpoint_params.size()); Node* frame_state = CreateJavaScriptBuiltinContinuationFrameState( jsgraph(), shared, builtin_lazy, node->InputAt(0), context, checkpoint_params.data(), stack_parameters - 1, outer_frame_state, ContinuationFrameStateMode::LAZY); next_cur = control = effect = graph()->NewNode(javascript()->Call(6, p.frequency()), fncallback, jsgraph()->UndefinedConstant(), cur, element, k, receiver, context, frame_state, effect, control); } // Rewire potential exception edges. Node* on_exception = nullptr; if (NodeProperties::IsExceptionalCall(node, &on_exception)) { RewirePostCallbackExceptionEdges(check_throw, on_exception, effect, &check_fail, &control); } if (IsHoleyElementsKind(kind)) { Node* after_call_control = control; Node* after_call_effect = effect; control = hole_true; effect = effect_true; control = graph()->NewNode(common()->Merge(2), control, after_call_control); effect = graph()->NewNode(common()->EffectPhi(2), effect, after_call_effect, control); next_cur = graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2), cur, next_cur, control); } k = next_k; cur = next_cur; loop->ReplaceInput(1, control); kloop->ReplaceInput(1, k); curloop->ReplaceInput(1, cur); eloop->ReplaceInput(1, effect); control = if_false; effect = eloop; // Wire up the branch for the case when IsCallable fails for the callback. // Since {check_throw} is an unconditional throw, it's impossible to // return a successful completion. Therefore, we simply connect the successful // completion to the graph end. Node* throw_node = graph()->NewNode(common()->Throw(), check_throw, check_fail); NodeProperties::MergeControlToEnd(graph(), common(), throw_node); ReplaceWithValue(node, curloop, effect, control); return Replace(curloop); } Reduction JSCallReducer::ReduceArrayMap(Node* node, Handle shared) { if (!FLAG_turbo_inline_array_builtins) return NoChange(); DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); CallParameters const& p = CallParametersOf(node->op()); if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) { return NoChange(); } Node* outer_frame_state = NodeProperties::GetFrameStateInput(node); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); Node* context = NodeProperties::GetContextInput(node); // Try to determine the {receiver} map. Node* receiver = NodeProperties::GetValueInput(node, 1); Node* fncallback = node->op()->ValueInputCount() > 2 ? NodeProperties::GetValueInput(node, 2) : jsgraph()->UndefinedConstant(); Node* this_arg = node->op()->ValueInputCount() > 3 ? NodeProperties::GetValueInput(node, 3) : jsgraph()->UndefinedConstant(); ZoneHandleSet receiver_maps; NodeProperties::InferReceiverMapsResult result = NodeProperties::InferReceiverMaps(isolate(), receiver, effect, &receiver_maps); if (result == NodeProperties::kNoReceiverMaps) return NoChange(); // Ensure that any changes to the Array species constructor cause deopt. if (!isolate()->IsArraySpeciesLookupChainIntact()) return NoChange(); const ElementsKind kind = receiver_maps[0]->elements_kind(); for (Handle receiver_map : receiver_maps) { if (!CanInlineArrayIteratingBuiltin(isolate(), receiver_map)) return NoChange(); // We can handle different maps, as long as their elements kind are the // same. if (receiver_map->elements_kind() != kind) return NoChange(); } if (IsHoleyElementsKind(kind)) { dependencies()->DependOnProtector( PropertyCellRef(js_heap_broker(), factory()->no_elements_protector())); } dependencies()->DependOnProtector( PropertyCellRef(js_heap_broker(), factory()->array_species_protector())); Handle handle_constructor( JSFunction::cast( native_context()->GetInitialJSArrayMap(kind)->GetConstructor()), isolate()); Node* array_constructor = jsgraph()->HeapConstant(handle_constructor); Node* k = jsgraph()->ZeroConstant(); // If we have unreliable maps, we need a map check. if (result == NodeProperties::kUnreliableReceiverMaps) { effect = graph()->NewNode(simplified()->CheckMaps(CheckMapsFlag::kNone, receiver_maps, p.feedback()), receiver, effect, control); } Node* original_length = effect = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForJSArrayLength(kind)), receiver, effect, control); // Even though {JSCreateArray} is not marked as {kNoThrow}, we can elide the // exceptional projections because it cannot throw with the given parameters. Node* a = control = effect = graph()->NewNode( javascript()->CreateArray(1, MaybeHandle()), array_constructor, array_constructor, original_length, context, outer_frame_state, effect, control); std::vector checkpoint_params( {receiver, fncallback, this_arg, a, k, original_length}); const int stack_parameters = static_cast(checkpoint_params.size()); // Check whether the given callback function is callable. Note that this has // to happen outside the loop to make sure we also throw on empty arrays. Node* check_frame_state = CreateJavaScriptBuiltinContinuationFrameState( jsgraph(), shared, Builtins::kArrayMapLoopLazyDeoptContinuation, node->InputAt(0), context, &checkpoint_params[0], stack_parameters, outer_frame_state, ContinuationFrameStateMode::LAZY); Node* check_fail = nullptr; Node* check_throw = nullptr; WireInCallbackIsCallableCheck(fncallback, context, check_frame_state, effect, &control, &check_fail, &check_throw); // Start the loop. Node* vloop = k = WireInLoopStart(k, &control, &effect); Node *loop = control, *eloop = effect; checkpoint_params[4] = k; Node* continue_test = graph()->NewNode(simplified()->NumberLessThan(), k, original_length); Node* continue_branch = graph()->NewNode(common()->Branch(BranchHint::kTrue), continue_test, control); Node* if_true = graph()->NewNode(common()->IfTrue(), continue_branch); Node* if_false = graph()->NewNode(common()->IfFalse(), continue_branch); control = if_true; Node* frame_state = CreateJavaScriptBuiltinContinuationFrameState( jsgraph(), shared, Builtins::kArrayMapLoopEagerDeoptContinuation, node->InputAt(0), context, &checkpoint_params[0], stack_parameters, outer_frame_state, ContinuationFrameStateMode::EAGER); effect = graph()->NewNode(common()->Checkpoint(), frame_state, effect, control); // Make sure the map hasn't changed during the iteration effect = graph()->NewNode(simplified()->CheckMaps(CheckMapsFlag::kNone, receiver_maps, p.feedback()), receiver, effect, control); Node* element = SafeLoadElement(kind, receiver, control, &effect, &k, p.feedback()); Node* next_k = graph()->NewNode(simplified()->NumberAdd(), k, jsgraph()->OneConstant()); Node* hole_true = nullptr; Node* hole_false = nullptr; Node* effect_true = effect; if (IsHoleyElementsKind(kind)) { // Holey elements kind require a hole check and skipping of the element in // the case of a hole. Node* check; if (IsDoubleElementsKind(kind)) { check = graph()->NewNode(simplified()->NumberIsFloat64Hole(), element); } else { check = graph()->NewNode(simplified()->ReferenceEqual(), element, jsgraph()->TheHoleConstant()); } Node* branch = graph()->NewNode(common()->Branch(BranchHint::kFalse), check, control); hole_true = graph()->NewNode(common()->IfTrue(), branch); hole_false = graph()->NewNode(common()->IfFalse(), branch); control = hole_false; // The contract is that we don't leak "the hole" into "user JavaScript", // so we must rename the {element} here to explicitly exclude "the hole" // from the type of {element}. element = effect = graph()->NewNode( common()->TypeGuard(Type::NonInternal()), element, effect, control); } // This frame state is dealt with by hand in // ArrayMapLoopLazyDeoptContinuation. frame_state = CreateJavaScriptBuiltinContinuationFrameState( jsgraph(), shared, Builtins::kArrayMapLoopLazyDeoptContinuation, node->InputAt(0), context, &checkpoint_params[0], stack_parameters, outer_frame_state, ContinuationFrameStateMode::LAZY); Node* callback_value = control = effect = graph()->NewNode( javascript()->Call(5, p.frequency()), fncallback, this_arg, element, k, receiver, context, frame_state, effect, control); // Rewire potential exception edges. Node* on_exception = nullptr; if (NodeProperties::IsExceptionalCall(node, &on_exception)) { RewirePostCallbackExceptionEdges(check_throw, on_exception, effect, &check_fail, &control); } // The array {a} should be HOLEY_SMI_ELEMENTS because we'd only come into this // loop if the input array length is non-zero, and "new Array({x > 0})" always // produces a HOLEY array. Handle double_map(Map::cast(native_context()->get( Context::ArrayMapIndex(HOLEY_DOUBLE_ELEMENTS))), isolate()); Handle fast_map( Map::cast(native_context()->get(Context::ArrayMapIndex(HOLEY_ELEMENTS))), isolate()); effect = graph()->NewNode( simplified()->TransitionAndStoreElement(double_map, fast_map), a, k, callback_value, effect, control); if (IsHoleyElementsKind(kind)) { Node* after_call_and_store_control = control; Node* after_call_and_store_effect = effect; control = hole_true; effect = effect_true; control = graph()->NewNode(common()->Merge(2), control, after_call_and_store_control); effect = graph()->NewNode(common()->EffectPhi(2), effect, after_call_and_store_effect, control); } WireInLoopEnd(loop, eloop, vloop, next_k, control, effect); control = if_false; effect = eloop; // Wire up the branch for the case when IsCallable fails for the callback. // Since {check_throw} is an unconditional throw, it's impossible to // return a successful completion. Therefore, we simply connect the successful // completion to the graph end. Node* throw_node = graph()->NewNode(common()->Throw(), check_throw, check_fail); NodeProperties::MergeControlToEnd(graph(), common(), throw_node); ReplaceWithValue(node, a, effect, control); return Replace(a); } Reduction JSCallReducer::ReduceArrayFilter(Node* node, Handle shared) { if (!FLAG_turbo_inline_array_builtins) return NoChange(); DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); CallParameters const& p = CallParametersOf(node->op()); if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) { return NoChange(); } Node* outer_frame_state = NodeProperties::GetFrameStateInput(node); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); Node* context = NodeProperties::GetContextInput(node); // Try to determine the {receiver} map. Node* receiver = NodeProperties::GetValueInput(node, 1); Node* fncallback = node->op()->ValueInputCount() > 2 ? NodeProperties::GetValueInput(node, 2) : jsgraph()->UndefinedConstant(); Node* this_arg = node->op()->ValueInputCount() > 3 ? NodeProperties::GetValueInput(node, 3) : jsgraph()->UndefinedConstant(); ZoneHandleSet receiver_maps; NodeProperties::InferReceiverMapsResult result = NodeProperties::InferReceiverMaps(isolate(), receiver, effect, &receiver_maps); if (result == NodeProperties::kNoReceiverMaps) return NoChange(); // And ensure that any changes to the Array species constructor cause deopt. if (!isolate()->IsArraySpeciesLookupChainIntact()) return NoChange(); const ElementsKind kind = receiver_maps[0]->elements_kind(); // The output array is packed (filter doesn't visit holes). const ElementsKind packed_kind = GetPackedElementsKind(kind); for (Handle receiver_map : receiver_maps) { if (!CanInlineArrayIteratingBuiltin(isolate(), receiver_map)) { return NoChange(); } // We can handle different maps, as long as their elements kind are the // same. if (receiver_map->elements_kind() != kind) return NoChange(); } if (IsHoleyElementsKind(kind)) { dependencies()->DependOnProtector( PropertyCellRef(js_heap_broker(), factory()->no_elements_protector())); } dependencies()->DependOnProtector( PropertyCellRef(js_heap_broker(), factory()->array_species_protector())); Handle initial_map( Map::cast(native_context()->GetInitialJSArrayMap(packed_kind)), isolate()); Node* k = jsgraph()->ZeroConstant(); Node* to = jsgraph()->ZeroConstant(); // If we have unreliable maps, we need a map check. if (result == NodeProperties::kUnreliableReceiverMaps) { effect = graph()->NewNode(simplified()->CheckMaps(CheckMapsFlag::kNone, receiver_maps, p.feedback()), receiver, effect, control); } Node* a; // Construct the output array. { AllocationBuilder ab(jsgraph(), effect, control); ab.Allocate(initial_map->instance_size(), NOT_TENURED, Type::Array()); ab.Store(AccessBuilder::ForMap(), initial_map); Node* empty_fixed_array = jsgraph()->EmptyFixedArrayConstant(); ab.Store(AccessBuilder::ForJSObjectPropertiesOrHash(), empty_fixed_array); ab.Store(AccessBuilder::ForJSObjectElements(), empty_fixed_array); ab.Store(AccessBuilder::ForJSArrayLength(packed_kind), jsgraph()->ZeroConstant()); for (int i = 0; i < initial_map->GetInObjectProperties(); ++i) { ab.Store(AccessBuilder::ForJSObjectInObjectProperty( MapRef(js_heap_broker(), initial_map), i), jsgraph()->UndefinedConstant()); } a = effect = ab.Finish(); } Node* original_length = effect = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForJSArrayLength(kind)), receiver, effect, control); // Check whether the given callback function is callable. Note that this has // to happen outside the loop to make sure we also throw on empty arrays. Node* check_fail = nullptr; Node* check_throw = nullptr; { // This frame state doesn't ever call the deopt continuation, it's only // necessary to specifiy a continuation in order to handle the exceptional // case. We don't have all the values available to completely fill out // checkpoint_params yet, but that's okay because it'll never be called. // Therefore, "to" is mentioned twice, once standing in for the k_value // value. std::vector checkpoint_params( {receiver, fncallback, this_arg, a, k, original_length, to, to}); const int stack_parameters = static_cast(checkpoint_params.size()); Node* check_frame_state = CreateJavaScriptBuiltinContinuationFrameState( jsgraph(), shared, Builtins::kArrayFilterLoopLazyDeoptContinuation, node->InputAt(0), context, &checkpoint_params[0], stack_parameters, outer_frame_state, ContinuationFrameStateMode::LAZY); WireInCallbackIsCallableCheck(fncallback, context, check_frame_state, effect, &control, &check_fail, &check_throw); } // Start the loop. Node* vloop = k = WireInLoopStart(k, &control, &effect); Node *loop = control, *eloop = effect; Node* v_to_loop = to = graph()->NewNode( common()->Phi(MachineRepresentation::kTaggedSigned, 2), to, to, loop); Node* continue_test = graph()->NewNode(simplified()->NumberLessThan(), k, original_length); Node* continue_branch = graph()->NewNode(common()->Branch(BranchHint::kTrue), continue_test, control); Node* if_true = graph()->NewNode(common()->IfTrue(), continue_branch); Node* if_false = graph()->NewNode(common()->IfFalse(), continue_branch); control = if_true; { std::vector checkpoint_params( {receiver, fncallback, this_arg, a, k, original_length, to}); const int stack_parameters = static_cast(checkpoint_params.size()); Node* frame_state = CreateJavaScriptBuiltinContinuationFrameState( jsgraph(), shared, Builtins::kArrayFilterLoopEagerDeoptContinuation, node->InputAt(0), context, &checkpoint_params[0], stack_parameters, outer_frame_state, ContinuationFrameStateMode::EAGER); effect = graph()->NewNode(common()->Checkpoint(), frame_state, effect, control); } // Make sure the map hasn't changed during the iteration. effect = graph()->NewNode(simplified()->CheckMaps(CheckMapsFlag::kNone, receiver_maps, p.feedback()), receiver, effect, control); Node* element = SafeLoadElement(kind, receiver, control, &effect, &k, p.feedback()); Node* next_k = graph()->NewNode(simplified()->NumberAdd(), k, jsgraph()->OneConstant()); Node* hole_true = nullptr; Node* hole_false = nullptr; Node* effect_true = effect; Node* hole_true_vto = to; if (IsHoleyElementsKind(kind)) { // Holey elements kind require a hole check and skipping of the element in // the case of a hole. Node* check; if (IsDoubleElementsKind(kind)) { check = graph()->NewNode(simplified()->NumberIsFloat64Hole(), element); } else { check = graph()->NewNode(simplified()->ReferenceEqual(), element, jsgraph()->TheHoleConstant()); } Node* branch = graph()->NewNode(common()->Branch(BranchHint::kFalse), check, control); hole_true = graph()->NewNode(common()->IfTrue(), branch); hole_false = graph()->NewNode(common()->IfFalse(), branch); control = hole_false; // The contract is that we don't leak "the hole" into "user JavaScript", // so we must rename the {element} here to explicitly exclude "the hole" // from the type of {element}. element = effect = graph()->NewNode( common()->TypeGuard(Type::NonInternal()), element, effect, control); } Node* callback_value = nullptr; { // This frame state is dealt with by hand in // Builtins::kArrayFilterLoopLazyDeoptContinuation. std::vector checkpoint_params( {receiver, fncallback, this_arg, a, k, original_length, element, to}); const int stack_parameters = static_cast(checkpoint_params.size()); Node* frame_state = CreateJavaScriptBuiltinContinuationFrameState( jsgraph(), shared, Builtins::kArrayFilterLoopLazyDeoptContinuation, node->InputAt(0), context, &checkpoint_params[0], stack_parameters, outer_frame_state, ContinuationFrameStateMode::LAZY); callback_value = control = effect = graph()->NewNode( javascript()->Call(5, p.frequency()), fncallback, this_arg, element, k, receiver, context, frame_state, effect, control); } // Rewire potential exception edges. Node* on_exception = nullptr; if (NodeProperties::IsExceptionalCall(node, &on_exception)) { RewirePostCallbackExceptionEdges(check_throw, on_exception, effect, &check_fail, &control); } // We need an eager frame state for right after the callback function // returned, just in case an attempt to grow the output array fails. // // Note that we are intentionally reusing the // Builtins::kArrayFilterLoopLazyDeoptContinuation as an *eager* entry // point in this case. This is safe, because re-evaluating a [ToBoolean] // coercion is safe. { std::vector checkpoint_params({receiver, fncallback, this_arg, a, k, original_length, element, to, callback_value}); const int stack_parameters = static_cast(checkpoint_params.size()); Node* frame_state = CreateJavaScriptBuiltinContinuationFrameState( jsgraph(), shared, Builtins::kArrayFilterLoopLazyDeoptContinuation, node->InputAt(0), context, &checkpoint_params[0], stack_parameters, outer_frame_state, ContinuationFrameStateMode::EAGER); effect = graph()->NewNode(common()->Checkpoint(), frame_state, effect, control); } // We have to coerce callback_value to boolean, and only store the element in // a if it's true. The checkpoint above protects against the case that // growing {a} fails. to = DoFilterPostCallbackWork(packed_kind, &control, &effect, a, to, element, callback_value); if (IsHoleyElementsKind(kind)) { Node* after_call_control = control; Node* after_call_effect = effect; control = hole_true; effect = effect_true; control = graph()->NewNode(common()->Merge(2), control, after_call_control); effect = graph()->NewNode(common()->EffectPhi(2), effect, after_call_effect, control); to = graph()->NewNode(common()->Phi(MachineRepresentation::kTaggedSigned, 2), hole_true_vto, to, control); } WireInLoopEnd(loop, eloop, vloop, next_k, control, effect); v_to_loop->ReplaceInput(1, to); control = if_false; effect = eloop; // Wire up the branch for the case when IsCallable fails for the callback. // Since {check_throw} is an unconditional throw, it's impossible to // return a successful completion. Therefore, we simply connect the successful // completion to the graph end. Node* throw_node = graph()->NewNode(common()->Throw(), check_throw, check_fail); NodeProperties::MergeControlToEnd(graph(), common(), throw_node); ReplaceWithValue(node, a, effect, control); return Replace(a); } Reduction JSCallReducer::ReduceArrayFind(Node* node, ArrayFindVariant variant, Handle shared) { if (!FLAG_turbo_inline_array_builtins) return NoChange(); DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); CallParameters const& p = CallParametersOf(node->op()); if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) { return NoChange(); } Builtins::Name eager_continuation_builtin; Builtins::Name lazy_continuation_builtin; Builtins::Name after_callback_lazy_continuation_builtin; if (variant == ArrayFindVariant::kFind) { eager_continuation_builtin = Builtins::kArrayFindLoopEagerDeoptContinuation; lazy_continuation_builtin = Builtins::kArrayFindLoopLazyDeoptContinuation; after_callback_lazy_continuation_builtin = Builtins::kArrayFindLoopAfterCallbackLazyDeoptContinuation; } else { DCHECK_EQ(ArrayFindVariant::kFindIndex, variant); eager_continuation_builtin = Builtins::kArrayFindIndexLoopEagerDeoptContinuation; lazy_continuation_builtin = Builtins::kArrayFindIndexLoopLazyDeoptContinuation; after_callback_lazy_continuation_builtin = Builtins::kArrayFindIndexLoopAfterCallbackLazyDeoptContinuation; } Node* outer_frame_state = NodeProperties::GetFrameStateInput(node); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); Node* context = NodeProperties::GetContextInput(node); // Try to determine the {receiver} map. Node* receiver = NodeProperties::GetValueInput(node, 1); Node* fncallback = node->op()->ValueInputCount() > 2 ? NodeProperties::GetValueInput(node, 2) : jsgraph()->UndefinedConstant(); Node* this_arg = node->op()->ValueInputCount() > 3 ? NodeProperties::GetValueInput(node, 3) : jsgraph()->UndefinedConstant(); ZoneHandleSet receiver_maps; NodeProperties::InferReceiverMapsResult result = NodeProperties::InferReceiverMaps(isolate(), receiver, effect, &receiver_maps); if (result == NodeProperties::kNoReceiverMaps) return NoChange(); const ElementsKind kind = receiver_maps[0]->elements_kind(); for (Handle receiver_map : receiver_maps) { if (!CanInlineArrayIteratingBuiltin(isolate(), receiver_map)) return NoChange(); // We can handle different maps, as long as their elements kind are the // same. if (receiver_map->elements_kind() != kind) return NoChange(); } // Install code dependencies on the {receiver} prototype maps and the // global array protector cell. dependencies()->DependOnProtector( PropertyCellRef(js_heap_broker(), factory()->no_elements_protector())); // If we have unreliable maps, we need a map check. if (result == NodeProperties::kUnreliableReceiverMaps) { effect = graph()->NewNode(simplified()->CheckMaps(CheckMapsFlag::kNone, receiver_maps, p.feedback()), receiver, effect, control); } Node* k = jsgraph()->ZeroConstant(); Node* original_length = effect = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForJSArrayLength(kind)), receiver, effect, control); std::vector checkpoint_params( {receiver, fncallback, this_arg, k, original_length}); const int stack_parameters = static_cast(checkpoint_params.size()); // Check whether the given callback function is callable. Note that this has // to happen outside the loop to make sure we also throw on empty arrays. Node* check_fail = nullptr; Node* check_throw = nullptr; { Node* frame_state = CreateJavaScriptBuiltinContinuationFrameState( jsgraph(), shared, lazy_continuation_builtin, node->InputAt(0), context, &checkpoint_params[0], stack_parameters, outer_frame_state, ContinuationFrameStateMode::LAZY); WireInCallbackIsCallableCheck(fncallback, context, frame_state, effect, &control, &check_fail, &check_throw); } // Start the loop. Node* vloop = k = WireInLoopStart(k, &control, &effect); Node *loop = control, *eloop = effect; checkpoint_params[3] = k; // Check if we've iterated past the last element of the array. Node* if_false = nullptr; { Node* continue_test = graph()->NewNode(simplified()->NumberLessThan(), k, original_length); Node* continue_branch = graph()->NewNode( common()->Branch(BranchHint::kTrue), continue_test, control); control = graph()->NewNode(common()->IfTrue(), continue_branch); if_false = graph()->NewNode(common()->IfFalse(), continue_branch); } // Check the map hasn't changed during the iteration. { Node* frame_state = CreateJavaScriptBuiltinContinuationFrameState( jsgraph(), shared, eager_continuation_builtin, node->InputAt(0), context, &checkpoint_params[0], stack_parameters, outer_frame_state, ContinuationFrameStateMode::EAGER); effect = graph()->NewNode(common()->Checkpoint(), frame_state, effect, control); effect = graph()->NewNode(simplified()->CheckMaps(CheckMapsFlag::kNone, receiver_maps, p.feedback()), receiver, effect, control); } // Load k-th element from receiver. Node* element = SafeLoadElement(kind, receiver, control, &effect, &k, p.feedback()); // Increment k for the next iteration. Node* next_k = checkpoint_params[3] = graph()->NewNode(simplified()->NumberAdd(), k, jsgraph()->OneConstant()); // Replace holes with undefined. if (kind == HOLEY_DOUBLE_ELEMENTS) { // TODO(7409): avoid deopt if not all uses of value are truncated. CheckFloat64HoleMode mode = CheckFloat64HoleMode::kAllowReturnHole; element = effect = graph()->NewNode(simplified()->CheckFloat64Hole(mode, p.feedback()), element, effect, control); } else if (IsHoleyElementsKind(kind)) { element = graph()->NewNode(simplified()->ConvertTaggedHoleToUndefined(), element); } Node* if_found_return_value = (variant == ArrayFindVariant::kFind) ? element : k; // Call the callback. Node* callback_value = nullptr; { std::vector call_checkpoint_params({receiver, fncallback, this_arg, next_k, original_length, if_found_return_value}); const int call_stack_parameters = static_cast(call_checkpoint_params.size()); Node* frame_state = CreateJavaScriptBuiltinContinuationFrameState( jsgraph(), shared, after_callback_lazy_continuation_builtin, node->InputAt(0), context, &call_checkpoint_params[0], call_stack_parameters, outer_frame_state, ContinuationFrameStateMode::LAZY); callback_value = control = effect = graph()->NewNode( javascript()->Call(5, p.frequency()), fncallback, this_arg, element, k, receiver, context, frame_state, effect, control); } // Rewire potential exception edges. Node* on_exception = nullptr; if (NodeProperties::IsExceptionalCall(node, &on_exception)) { RewirePostCallbackExceptionEdges(check_throw, on_exception, effect, &check_fail, &control); } // Check whether the given callback function returned a truthy value. Node* boolean_result = graph()->NewNode(simplified()->ToBoolean(), callback_value); Node* efound_branch = effect; Node* found_branch = graph()->NewNode(common()->Branch(BranchHint::kFalse), boolean_result, control); Node* if_found = graph()->NewNode(common()->IfTrue(), found_branch); Node* if_notfound = graph()->NewNode(common()->IfFalse(), found_branch); control = if_notfound; // Close the loop. WireInLoopEnd(loop, eloop, vloop, next_k, control, effect); control = graph()->NewNode(common()->Merge(2), if_found, if_false); effect = graph()->NewNode(common()->EffectPhi(2), efound_branch, eloop, control); Node* if_not_found_value = (variant == ArrayFindVariant::kFind) ? jsgraph()->UndefinedConstant() : jsgraph()->MinusOneConstant(); Node* return_value = graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2), if_found_return_value, if_not_found_value, control); // Wire up the branch for the case when IsCallable fails for the callback. // Since {check_throw} is an unconditional throw, it's impossible to // return a successful completion. Therefore, we simply connect the successful // completion to the graph end. Node* throw_node = graph()->NewNode(common()->Throw(), check_throw, check_fail); NodeProperties::MergeControlToEnd(graph(), common(), throw_node); ReplaceWithValue(node, return_value, effect, control); return Replace(return_value); } Node* JSCallReducer::DoFilterPostCallbackWork(ElementsKind kind, Node** control, Node** effect, Node* a, Node* to, Node* element, Node* callback_value) { Node* boolean_result = graph()->NewNode(simplified()->ToBoolean(), callback_value); Node* boolean_branch = graph()->NewNode(common()->Branch(BranchHint::kTrue), boolean_result, *control); Node* if_true = graph()->NewNode(common()->IfTrue(), boolean_branch); Node* etrue = *effect; Node* vtrue; { // Load the elements backing store of the {receiver}. Node* elements = etrue = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForJSObjectElements()), a, etrue, if_true); DCHECK(TypeCache::Get().kFixedDoubleArrayLengthType.Is( TypeCache::Get().kFixedArrayLengthType)); Node* checked_to = etrue = graph()->NewNode( common()->TypeGuard(TypeCache::Get().kFixedArrayLengthType), to, etrue, if_true); Node* elements_length = etrue = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForFixedArrayLength()), elements, etrue, if_true); GrowFastElementsMode mode = IsDoubleElementsKind(kind) ? GrowFastElementsMode::kDoubleElements : GrowFastElementsMode::kSmiOrObjectElements; elements = etrue = graph()->NewNode( simplified()->MaybeGrowFastElements(mode, VectorSlotPair()), a, elements, checked_to, elements_length, etrue, if_true); // Update the length of {a}. Node* new_length_a = graph()->NewNode(simplified()->NumberAdd(), checked_to, jsgraph()->OneConstant()); etrue = graph()->NewNode( simplified()->StoreField(AccessBuilder::ForJSArrayLength(kind)), a, new_length_a, etrue, if_true); // Append the value to the {elements}. etrue = graph()->NewNode( simplified()->StoreElement(AccessBuilder::ForFixedArrayElement(kind)), elements, checked_to, element, etrue, if_true); vtrue = new_length_a; } Node* if_false = graph()->NewNode(common()->IfFalse(), boolean_branch); Node* efalse = *effect; Node* vfalse = to; *control = graph()->NewNode(common()->Merge(2), if_true, if_false); *effect = graph()->NewNode(common()->EffectPhi(2), etrue, efalse, *control); to = graph()->NewNode(common()->Phi(MachineRepresentation::kTaggedSigned, 2), vtrue, vfalse, *control); return to; } void JSCallReducer::WireInCallbackIsCallableCheck( Node* fncallback, Node* context, Node* check_frame_state, Node* effect, Node** control, Node** check_fail, Node** check_throw) { Node* check = graph()->NewNode(simplified()->ObjectIsCallable(), fncallback); Node* check_branch = graph()->NewNode(common()->Branch(BranchHint::kTrue), check, *control); *check_fail = graph()->NewNode(common()->IfFalse(), check_branch); *check_throw = *check_fail = graph()->NewNode( javascript()->CallRuntime(Runtime::kThrowTypeError, 2), jsgraph()->Constant(MessageTemplate::kCalledNonCallable), fncallback, context, check_frame_state, effect, *check_fail); *control = graph()->NewNode(common()->IfTrue(), check_branch); } void JSCallReducer::RewirePostCallbackExceptionEdges(Node* check_throw, Node* on_exception, Node* effect, Node** check_fail, Node** control) { // Create appropriate {IfException} and {IfSuccess} nodes. Node* if_exception0 = graph()->NewNode(common()->IfException(), check_throw, *check_fail); *check_fail = graph()->NewNode(common()->IfSuccess(), *check_fail); Node* if_exception1 = graph()->NewNode(common()->IfException(), effect, *control); *control = graph()->NewNode(common()->IfSuccess(), *control); // Join the exception edges. Node* merge = graph()->NewNode(common()->Merge(2), if_exception0, if_exception1); Node* ephi = graph()->NewNode(common()->EffectPhi(2), if_exception0, if_exception1, merge); Node* phi = graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2), if_exception0, if_exception1, merge); ReplaceWithValue(on_exception, phi, ephi, merge); } Node* JSCallReducer::SafeLoadElement(ElementsKind kind, Node* receiver, Node* control, Node** effect, Node** k, const VectorSlotPair& feedback) { // Make sure that the access is still in bounds, since the callback could have // changed the array's size. Node* length = *effect = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForJSArrayLength(kind)), receiver, *effect, control); *k = *effect = graph()->NewNode(simplified()->CheckBounds(feedback), *k, length, *effect, control); // Reload the elements pointer before calling the callback, since the previous // callback might have resized the array causing the elements buffer to be // re-allocated. Node* elements = *effect = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForJSObjectElements()), receiver, *effect, control); Node* element = *effect = graph()->NewNode( simplified()->LoadElement(AccessBuilder::ForFixedArrayElement( kind, LoadSensitivity::kCritical)), elements, *k, *effect, control); return element; } Reduction JSCallReducer::ReduceArrayEvery(Node* node, Handle shared) { if (!FLAG_turbo_inline_array_builtins) return NoChange(); DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); CallParameters const& p = CallParametersOf(node->op()); if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) { return NoChange(); } Node* outer_frame_state = NodeProperties::GetFrameStateInput(node); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); Node* context = NodeProperties::GetContextInput(node); // Try to determine the {receiver} map. Node* receiver = NodeProperties::GetValueInput(node, 1); Node* fncallback = node->op()->ValueInputCount() > 2 ? NodeProperties::GetValueInput(node, 2) : jsgraph()->UndefinedConstant(); Node* this_arg = node->op()->ValueInputCount() > 3 ? NodeProperties::GetValueInput(node, 3) : jsgraph()->UndefinedConstant(); ZoneHandleSet receiver_maps; NodeProperties::InferReceiverMapsResult result = NodeProperties::InferReceiverMaps(isolate(), receiver, effect, &receiver_maps); if (result == NodeProperties::kNoReceiverMaps) return NoChange(); // And ensure that any changes to the Array species constructor cause deopt. if (!isolate()->IsArraySpeciesLookupChainIntact()) return NoChange(); const ElementsKind kind = receiver_maps[0]->elements_kind(); for (Handle receiver_map : receiver_maps) { if (!CanInlineArrayIteratingBuiltin(isolate(), receiver_map)) return NoChange(); // We can handle different maps, as long as their elements kind are the // same. if (receiver_map->elements_kind() != kind) return NoChange(); } if (IsHoleyElementsKind(kind)) { dependencies()->DependOnProtector( PropertyCellRef(js_heap_broker(), factory()->no_elements_protector())); } dependencies()->DependOnProtector( PropertyCellRef(js_heap_broker(), factory()->array_species_protector())); // If we have unreliable maps, we need a map check. if (result == NodeProperties::kUnreliableReceiverMaps) { effect = graph()->NewNode(simplified()->CheckMaps(CheckMapsFlag::kNone, receiver_maps, p.feedback()), receiver, effect, control); } Node* k = jsgraph()->ZeroConstant(); // Make sure the map hasn't changed before we construct the output array. effect = graph()->NewNode( simplified()->CheckMaps(CheckMapsFlag::kNone, receiver_maps), receiver, effect, control); Node* original_length = effect = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForJSArrayLength(kind)), receiver, effect, control); // Check whether the given callback function is callable. Note that this has // to happen outside the loop to make sure we also throw on empty arrays. Node* check_fail = nullptr; Node* check_throw = nullptr; { // This frame state doesn't ever call the deopt continuation, it's only // necessary to specifiy a continuation in order to handle the exceptional // case. std::vector checkpoint_params( {receiver, fncallback, this_arg, k, original_length}); const int stack_parameters = static_cast(checkpoint_params.size()); Node* check_frame_state = CreateJavaScriptBuiltinContinuationFrameState( jsgraph(), shared, Builtins::kArrayEveryLoopLazyDeoptContinuation, node->InputAt(0), context, &checkpoint_params[0], stack_parameters, outer_frame_state, ContinuationFrameStateMode::LAZY); WireInCallbackIsCallableCheck(fncallback, context, check_frame_state, effect, &control, &check_fail, &check_throw); } // Start the loop. Node* vloop = k = WireInLoopStart(k, &control, &effect); Node *loop = control, *eloop = effect; Node* continue_test = graph()->NewNode(simplified()->NumberLessThan(), k, original_length); Node* continue_branch = graph()->NewNode(common()->Branch(BranchHint::kTrue), continue_test, control); Node* if_true = graph()->NewNode(common()->IfTrue(), continue_branch); Node* if_false = graph()->NewNode(common()->IfFalse(), continue_branch); control = if_true; { std::vector checkpoint_params( {receiver, fncallback, this_arg, k, original_length}); const int stack_parameters = static_cast(checkpoint_params.size()); Node* frame_state = CreateJavaScriptBuiltinContinuationFrameState( jsgraph(), shared, Builtins::kArrayEveryLoopEagerDeoptContinuation, node->InputAt(0), context, &checkpoint_params[0], stack_parameters, outer_frame_state, ContinuationFrameStateMode::EAGER); effect = graph()->NewNode(common()->Checkpoint(), frame_state, effect, control); } // Make sure the map hasn't changed during the iteration. effect = graph()->NewNode(simplified()->CheckMaps(CheckMapsFlag::kNone, receiver_maps, p.feedback()), receiver, effect, control); Node* element = SafeLoadElement(kind, receiver, control, &effect, &k, p.feedback()); Node* next_k = graph()->NewNode(simplified()->NumberAdd(), k, jsgraph()->OneConstant()); Node* hole_true = nullptr; Node* hole_false = nullptr; Node* effect_true = effect; if (IsHoleyElementsKind(kind)) { // Holey elements kind require a hole check and skipping of the element in // the case of a hole. Node* check; if (IsDoubleElementsKind(kind)) { check = graph()->NewNode(simplified()->NumberIsFloat64Hole(), element); } else { check = graph()->NewNode(simplified()->ReferenceEqual(), element, jsgraph()->TheHoleConstant()); } Node* branch = graph()->NewNode(common()->Branch(BranchHint::kFalse), check, control); hole_true = graph()->NewNode(common()->IfTrue(), branch); hole_false = graph()->NewNode(common()->IfFalse(), branch); control = hole_false; // The contract is that we don't leak "the hole" into "user JavaScript", // so we must rename the {element} here to explicitly exclude "the hole" // from the type of {element}. element = effect = graph()->NewNode( common()->TypeGuard(Type::NonInternal()), element, effect, control); } Node* callback_value = nullptr; { // This frame state is dealt with by hand in // Builtins::kArrayEveryLoopLazyDeoptContinuation. std::vector checkpoint_params( {receiver, fncallback, this_arg, k, original_length}); const int stack_parameters = static_cast(checkpoint_params.size()); Node* frame_state = CreateJavaScriptBuiltinContinuationFrameState( jsgraph(), shared, Builtins::kArrayEveryLoopLazyDeoptContinuation, node->InputAt(0), context, &checkpoint_params[0], stack_parameters, outer_frame_state, ContinuationFrameStateMode::LAZY); callback_value = control = effect = graph()->NewNode( javascript()->Call(5, p.frequency()), fncallback, this_arg, element, k, receiver, context, frame_state, effect, control); } // Rewire potential exception edges. Node* on_exception = nullptr; if (NodeProperties::IsExceptionalCall(node, &on_exception)) { RewirePostCallbackExceptionEdges(check_throw, on_exception, effect, &check_fail, &control); } // We have to coerce callback_value to boolean. Node* if_false_callback; Node* efalse_callback; { Node* boolean_result = graph()->NewNode(simplified()->ToBoolean(), callback_value); Node* boolean_branch = graph()->NewNode(common()->Branch(BranchHint::kTrue), boolean_result, control); if_false_callback = graph()->NewNode(common()->IfFalse(), boolean_branch); efalse_callback = effect; // Nothing to do in the true case. control = graph()->NewNode(common()->IfTrue(), boolean_branch); } if (IsHoleyElementsKind(kind)) { Node* after_call_control = control; Node* after_call_effect = effect; control = hole_true; effect = effect_true; control = graph()->NewNode(common()->Merge(2), control, after_call_control); effect = graph()->NewNode(common()->EffectPhi(2), effect, after_call_effect, control); } WireInLoopEnd(loop, eloop, vloop, next_k, control, effect); control = graph()->NewNode(common()->Merge(2), if_false, if_false_callback); effect = graph()->NewNode(common()->EffectPhi(2), eloop, efalse_callback, control); Node* return_value = graph()->NewNode( common()->Phi(MachineRepresentation::kTagged, 2), jsgraph()->TrueConstant(), jsgraph()->FalseConstant(), control); // Wire up the branch for the case when IsCallable fails for the callback. // Since {check_throw} is an unconditional throw, it's impossible to // return a successful completion. Therefore, we simply connect the successful // completion to the graph end. Node* throw_node = graph()->NewNode(common()->Throw(), check_throw, check_fail); NodeProperties::MergeControlToEnd(graph(), common(), throw_node); ReplaceWithValue(node, return_value, effect, control); return Replace(return_value); } namespace { // Returns the correct Callable for Array's indexOf based on the receiver's // |elements_kind| and |isolate|. Assumes that |elements_kind| is a fast one. Callable GetCallableForArrayIndexOf(ElementsKind elements_kind, Isolate* isolate) { switch (elements_kind) { case PACKED_SMI_ELEMENTS: case HOLEY_SMI_ELEMENTS: case PACKED_ELEMENTS: case HOLEY_ELEMENTS: return Builtins::CallableFor(isolate, Builtins::kArrayIndexOfSmiOrObject); case PACKED_DOUBLE_ELEMENTS: return Builtins::CallableFor(isolate, Builtins::kArrayIndexOfPackedDoubles); default: DCHECK_EQ(HOLEY_DOUBLE_ELEMENTS, elements_kind); return Builtins::CallableFor(isolate, Builtins::kArrayIndexOfHoleyDoubles); } } // Returns the correct Callable for Array's includes based on the receiver's // |elements_kind| and |isolate|. Assumes that |elements_kind| is a fast one. Callable GetCallableForArrayIncludes(ElementsKind elements_kind, Isolate* isolate) { switch (elements_kind) { case PACKED_SMI_ELEMENTS: case HOLEY_SMI_ELEMENTS: case PACKED_ELEMENTS: case HOLEY_ELEMENTS: return Builtins::CallableFor(isolate, Builtins::kArrayIncludesSmiOrObject); case PACKED_DOUBLE_ELEMENTS: return Builtins::CallableFor(isolate, Builtins::kArrayIncludesPackedDoubles); default: DCHECK_EQ(HOLEY_DOUBLE_ELEMENTS, elements_kind); return Builtins::CallableFor(isolate, Builtins::kArrayIncludesHoleyDoubles); } } } // namespace // For search_variant == kIndexOf: // ES6 Array.prototype.indexOf(searchElement[, fromIndex]) // #sec-array.prototype.indexof // For search_variant == kIncludes: // ES7 Array.prototype.inludes(searchElement[, fromIndex]) // #sec-array.prototype.includes Reduction JSCallReducer::ReduceArrayIndexOfIncludes( SearchVariant search_variant, Node* node) { CallParameters const& p = CallParametersOf(node->op()); if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) { return NoChange(); } Handle receiver_map; if (!NodeProperties::GetMapWitness(isolate(), node).ToHandle(&receiver_map)) return NoChange(); if (!CanInlineArrayIteratingBuiltin(isolate(), receiver_map)) return NoChange(); if (IsHoleyElementsKind(receiver_map->elements_kind())) { dependencies()->DependOnProtector( PropertyCellRef(js_heap_broker(), factory()->no_elements_protector())); } Callable const callable = search_variant == SearchVariant::kIndexOf ? GetCallableForArrayIndexOf(receiver_map->elements_kind(), isolate()) : GetCallableForArrayIncludes(receiver_map->elements_kind(), isolate()); CallDescriptor const* const desc = Linkage::GetStubCallDescriptor( graph()->zone(), callable.descriptor(), callable.descriptor().GetStackParameterCount(), CallDescriptor::kNoFlags, Operator::kEliminatable); // The stub expects the following arguments: the receiver array, its elements, // the search_element, the array length, and the index to start searching // from. Node* receiver = NodeProperties::GetValueInput(node, 1); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); Node* elements = effect = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForJSObjectElements()), receiver, effect, control); Node* search_element = (node->op()->ValueInputCount() >= 3) ? NodeProperties::GetValueInput(node, 2) : jsgraph()->UndefinedConstant(); Node* length = effect = graph()->NewNode( simplified()->LoadField( AccessBuilder::ForJSArrayLength(receiver_map->elements_kind())), receiver, effect, control); Node* new_from_index = jsgraph()->ZeroConstant(); if (node->op()->ValueInputCount() >= 4) { Node* from_index = NodeProperties::GetValueInput(node, 3); from_index = effect = graph()->NewNode(simplified()->CheckSmi(p.feedback()), from_index, effect, control); // If the index is negative, it means the offset from the end and therefore // needs to be added to the length. If the result is still negative, it // needs to be clamped to 0. new_from_index = graph()->NewNode( common()->Select(MachineRepresentation::kTagged, BranchHint::kFalse), graph()->NewNode(simplified()->NumberLessThan(), from_index, jsgraph()->ZeroConstant()), graph()->NewNode( simplified()->NumberMax(), graph()->NewNode(simplified()->NumberAdd(), length, from_index), jsgraph()->ZeroConstant()), from_index); } Node* context = NodeProperties::GetContextInput(node); Node* replacement_node = effect = graph()->NewNode( common()->Call(desc), jsgraph()->HeapConstant(callable.code()), elements, search_element, length, new_from_index, context, effect); ReplaceWithValue(node, replacement_node, effect); return Replace(replacement_node); } Reduction JSCallReducer::ReduceArraySome(Node* node, Handle shared) { if (!FLAG_turbo_inline_array_builtins) return NoChange(); DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); CallParameters const& p = CallParametersOf(node->op()); if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) { return NoChange(); } Node* outer_frame_state = NodeProperties::GetFrameStateInput(node); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); Node* context = NodeProperties::GetContextInput(node); // Try to determine the {receiver} map. Node* receiver = NodeProperties::GetValueInput(node, 1); Node* fncallback = node->op()->ValueInputCount() > 2 ? NodeProperties::GetValueInput(node, 2) : jsgraph()->UndefinedConstant(); Node* this_arg = node->op()->ValueInputCount() > 3 ? NodeProperties::GetValueInput(node, 3) : jsgraph()->UndefinedConstant(); ZoneHandleSet receiver_maps; NodeProperties::InferReceiverMapsResult result = NodeProperties::InferReceiverMaps(isolate(), receiver, effect, &receiver_maps); if (result == NodeProperties::kNoReceiverMaps) return NoChange(); // And ensure that any changes to the Array species constructor cause deopt. if (!isolate()->IsArraySpeciesLookupChainIntact()) return NoChange(); if (receiver_maps.size() == 0) return NoChange(); const ElementsKind kind = receiver_maps[0]->elements_kind(); for (Handle receiver_map : receiver_maps) { if (!CanInlineArrayIteratingBuiltin(isolate(), receiver_map)) return NoChange(); // We can handle different maps, as long as their elements kind are the // same. if (receiver_map->elements_kind() != kind) return NoChange(); } if (IsHoleyElementsKind(kind)) { dependencies()->DependOnProtector( PropertyCellRef(js_heap_broker(), factory()->no_elements_protector())); } dependencies()->DependOnProtector( PropertyCellRef(js_heap_broker(), factory()->array_species_protector())); Node* k = jsgraph()->ZeroConstant(); // If we have unreliable maps, we need a map check. if (result == NodeProperties::kUnreliableReceiverMaps) { effect = graph()->NewNode(simplified()->CheckMaps(CheckMapsFlag::kNone, receiver_maps, p.feedback()), receiver, effect, control); } // Make sure the map hasn't changed before we construct the output array. effect = graph()->NewNode( simplified()->CheckMaps(CheckMapsFlag::kNone, receiver_maps), receiver, effect, control); Node* original_length = effect = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForJSArrayLength(kind)), receiver, effect, control); // Check whether the given callback function is callable. Note that this has // to happen outside the loop to make sure we also throw on empty arrays. Node* check_fail = nullptr; Node* check_throw = nullptr; { // This frame state doesn't ever call the deopt continuation, it's only // necessary to specifiy a continuation in order to handle the exceptional // case. std::vector checkpoint_params( {receiver, fncallback, this_arg, k, original_length}); const int stack_parameters = static_cast(checkpoint_params.size()); Node* check_frame_state = CreateJavaScriptBuiltinContinuationFrameState( jsgraph(), shared, Builtins::kArraySomeLoopLazyDeoptContinuation, node->InputAt(0), context, &checkpoint_params[0], stack_parameters, outer_frame_state, ContinuationFrameStateMode::LAZY); WireInCallbackIsCallableCheck(fncallback, context, check_frame_state, effect, &control, &check_fail, &check_throw); } // Start the loop. Node* loop = control = graph()->NewNode(common()->Loop(2), control, control); Node* eloop = effect = graph()->NewNode(common()->EffectPhi(2), effect, effect, loop); Node* terminate = graph()->NewNode(common()->Terminate(), eloop, loop); NodeProperties::MergeControlToEnd(graph(), common(), terminate); Node* vloop = k = graph()->NewNode( common()->Phi(MachineRepresentation::kTagged, 2), k, k, loop); Node* continue_test = graph()->NewNode(simplified()->NumberLessThan(), k, original_length); Node* continue_branch = graph()->NewNode(common()->Branch(BranchHint::kTrue), continue_test, control); Node* if_true = graph()->NewNode(common()->IfTrue(), continue_branch); Node* if_false = graph()->NewNode(common()->IfFalse(), continue_branch); control = if_true; { std::vector checkpoint_params( {receiver, fncallback, this_arg, k, original_length}); const int stack_parameters = static_cast(checkpoint_params.size()); Node* frame_state = CreateJavaScriptBuiltinContinuationFrameState( jsgraph(), shared, Builtins::kArraySomeLoopEagerDeoptContinuation, node->InputAt(0), context, &checkpoint_params[0], stack_parameters, outer_frame_state, ContinuationFrameStateMode::EAGER); effect = graph()->NewNode(common()->Checkpoint(), frame_state, effect, control); } // Make sure the map hasn't changed during the iteration. effect = graph()->NewNode(simplified()->CheckMaps(CheckMapsFlag::kNone, receiver_maps, p.feedback()), receiver, effect, control); Node* element = SafeLoadElement(kind, receiver, control, &effect, &k, p.feedback()); Node* next_k = graph()->NewNode(simplified()->NumberAdd(), k, jsgraph()->OneConstant()); Node* hole_true = nullptr; Node* hole_false = nullptr; Node* effect_true = effect; if (IsHoleyElementsKind(kind)) { // Holey elements kind require a hole check and skipping of the element in // the case of a hole. Node* check; if (IsDoubleElementsKind(kind)) { check = graph()->NewNode(simplified()->NumberIsFloat64Hole(), element); } else { check = graph()->NewNode(simplified()->ReferenceEqual(), element, jsgraph()->TheHoleConstant()); } Node* branch = graph()->NewNode(common()->Branch(BranchHint::kFalse), check, control); hole_true = graph()->NewNode(common()->IfTrue(), branch); hole_false = graph()->NewNode(common()->IfFalse(), branch); control = hole_false; // The contract is that we don't leak "the hole" into "user JavaScript", // so we must rename the {element} here to explicitly exclude "the hole" // from the type of {element}. element = effect = graph()->NewNode( common()->TypeGuard(Type::NonInternal()), element, effect, control); } Node* callback_value = nullptr; { // This frame state is dealt with by hand in // Builtins::kArrayEveryLoopLazyDeoptContinuation. std::vector checkpoint_params( {receiver, fncallback, this_arg, k, original_length}); const int stack_parameters = static_cast(checkpoint_params.size()); Node* frame_state = CreateJavaScriptBuiltinContinuationFrameState( jsgraph(), shared, Builtins::kArraySomeLoopLazyDeoptContinuation, node->InputAt(0), context, &checkpoint_params[0], stack_parameters, outer_frame_state, ContinuationFrameStateMode::LAZY); callback_value = control = effect = graph()->NewNode( javascript()->Call(5, p.frequency()), fncallback, this_arg, element, k, receiver, context, frame_state, effect, control); } // Rewire potential exception edges. Node* on_exception = nullptr; if (NodeProperties::IsExceptionalCall(node, &on_exception)) { RewirePostCallbackExceptionEdges(check_throw, on_exception, effect, &check_fail, &control); } // We have to coerce callback_value to boolean. Node* if_true_callback; Node* etrue_callback; { Node* boolean_result = graph()->NewNode(simplified()->ToBoolean(), callback_value); Node* boolean_branch = graph()->NewNode( common()->Branch(BranchHint::kFalse), boolean_result, control); if_true_callback = graph()->NewNode(common()->IfTrue(), boolean_branch); etrue_callback = effect; // Nothing to do in the false case. control = graph()->NewNode(common()->IfFalse(), boolean_branch); } if (IsHoleyElementsKind(kind)) { Node* after_call_control = control; Node* after_call_effect = effect; control = hole_true; effect = effect_true; control = graph()->NewNode(common()->Merge(2), control, after_call_control); effect = graph()->NewNode(common()->EffectPhi(2), effect, after_call_effect, control); } loop->ReplaceInput(1, control); vloop->ReplaceInput(1, next_k); eloop->ReplaceInput(1, effect); control = graph()->NewNode(common()->Merge(2), if_false, if_true_callback); effect = graph()->NewNode(common()->EffectPhi(2), eloop, etrue_callback, control); Node* return_value = graph()->NewNode( common()->Phi(MachineRepresentation::kTagged, 2), jsgraph()->FalseConstant(), jsgraph()->TrueConstant(), control); // Wire up the branch for the case when IsCallable fails for the callback. // Since {check_throw} is an unconditional throw, it's impossible to // return a successful completion. Therefore, we simply connect the successful // completion to the graph end. Node* throw_node = graph()->NewNode(common()->Throw(), check_throw, check_fail); NodeProperties::MergeControlToEnd(graph(), common(), throw_node); ReplaceWithValue(node, return_value, effect, control); return Replace(return_value); } Reduction JSCallReducer::ReduceCallApiFunction( Node* node, Handle shared) { DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); CallParameters const& p = CallParametersOf(node->op()); int const argc = static_cast(p.arity()) - 2; Node* target = NodeProperties::GetValueInput(node, 0); Node* receiver = (p.convert_mode() == ConvertReceiverMode::kNullOrUndefined) ? jsgraph()->HeapConstant(global_proxy()) : NodeProperties::GetValueInput(node, 1); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); Handle function_template_info( FunctionTemplateInfo::cast(shared->function_data()), isolate()); // CallApiCallbackStub expects the target in a register, so we count it out, // and counts the receiver as an implicit argument, so we count the receiver // out too. if (argc > CallApiCallbackStub::kArgMax) return NoChange(); // Infer the {receiver} maps, and check if we can inline the API function // callback based on those. ZoneHandleSet receiver_maps; NodeProperties::InferReceiverMapsResult result = NodeProperties::InferReceiverMaps(isolate(), receiver, effect, &receiver_maps); if (result == NodeProperties::kNoReceiverMaps) return NoChange(); for (size_t i = 0; i < receiver_maps.size(); ++i) { Handle receiver_map = receiver_maps[i]; if (!receiver_map->IsJSObjectMap() || (!function_template_info->accept_any_receiver() && receiver_map->is_access_check_needed())) { return NoChange(); } // In case of unreliable {receiver} information, the {receiver_maps} // must all be stable in order to consume the information. if (result == NodeProperties::kUnreliableReceiverMaps) { if (!receiver_map->is_stable()) return NoChange(); } } // See if we can constant-fold the compatible receiver checks. CallOptimization call_optimization(isolate(), function_template_info); if (!call_optimization.is_simple_api_call()) return NoChange(); CallOptimization::HolderLookup lookup; Handle api_holder = call_optimization.LookupHolderOfExpectedType(receiver_maps[0], &lookup); if (lookup == CallOptimization::kHolderNotFound) return NoChange(); for (size_t i = 1; i < receiver_maps.size(); ++i) { CallOptimization::HolderLookup lookupi; Handle holder = call_optimization.LookupHolderOfExpectedType( receiver_maps[i], &lookupi); if (lookup != lookupi) return NoChange(); if (!api_holder.is_identical_to(holder)) return NoChange(); } // Install stability dependencies for unreliable {receiver_maps}. if (result == NodeProperties::kUnreliableReceiverMaps) { for (size_t i = 0; i < receiver_maps.size(); ++i) { dependencies()->DependOnStableMap( MapRef(js_heap_broker(), receiver_maps[i])); } } // Load the {target}s context. Node* context = effect = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForJSFunctionContext()), target, effect, control); // CallApiCallbackStub's register arguments: code, target, call data, holder, // function address. // TODO(turbofan): Consider introducing a JSCallApiCallback operator for // this and lower it during JSGenericLowering, and unify this with the // JSNativeContextSpecialization::InlineApiCall method a bit. Handle call_handler_info( CallHandlerInfo::cast(function_template_info->call_code()), isolate()); Handle data(call_handler_info->data(), isolate()); Callable call_api_callback = CodeFactory::CallApiCallback(isolate(), argc); CallInterfaceDescriptor cid = call_api_callback.descriptor(); auto call_descriptor = Linkage::GetStubCallDescriptor( graph()->zone(), cid, cid.GetStackParameterCount() + argc + 1 /* implicit receiver */, CallDescriptor::kNeedsFrameState); ApiFunction api_function(v8::ToCData
(call_handler_info->callback())); Node* holder = lookup == CallOptimization::kHolderFound ? jsgraph()->HeapConstant(api_holder) : receiver; ExternalReference function_reference = ExternalReference::Create( &api_function, ExternalReference::DIRECT_API_CALL); node->InsertInput(graph()->zone(), 0, jsgraph()->HeapConstant(call_api_callback.code())); node->ReplaceInput(1, context); node->InsertInput(graph()->zone(), 2, jsgraph()->Constant(data)); node->InsertInput(graph()->zone(), 3, holder); node->InsertInput(graph()->zone(), 4, jsgraph()->ExternalConstant(function_reference)); node->ReplaceInput(5, receiver); node->RemoveInput(6 + argc); // Remove context input. node->ReplaceInput(7 + argc, effect); // Update effect input. NodeProperties::ChangeOp(node, common()->Call(call_descriptor)); return Changed(node); } namespace { // Check whether elements aren't mutated; we play it extremely safe here by // explicitly checking that {node} is only used by {LoadField} or {LoadElement}. bool IsSafeArgumentsElements(Node* node) { for (Edge const edge : node->use_edges()) { if (!NodeProperties::IsValueEdge(edge)) continue; if (edge.from()->opcode() != IrOpcode::kLoadField && edge.from()->opcode() != IrOpcode::kLoadElement) { return false; } } return true; } } // namespace Reduction JSCallReducer::ReduceCallOrConstructWithArrayLikeOrSpread( Node* node, int arity, CallFrequency const& frequency, VectorSlotPair const& feedback) { DCHECK(node->opcode() == IrOpcode::kJSCallWithArrayLike || node->opcode() == IrOpcode::kJSCallWithSpread || node->opcode() == IrOpcode::kJSConstructWithArrayLike || node->opcode() == IrOpcode::kJSConstructWithSpread); // In case of a call/construct with spread, we need to // ensure that it's safe to avoid the actual iteration. if ((node->opcode() == IrOpcode::kJSCallWithSpread || node->opcode() == IrOpcode::kJSConstructWithSpread) && !isolate()->IsArrayIteratorLookupChainIntact()) { return NoChange(); } // Check if {arguments_list} is an arguments object, and {node} is the only // value user of {arguments_list} (except for value uses in frame states). Node* arguments_list = NodeProperties::GetValueInput(node, arity); if (arguments_list->opcode() != IrOpcode::kJSCreateArguments) { return NoChange(); } for (Edge edge : arguments_list->use_edges()) { if (!NodeProperties::IsValueEdge(edge)) continue; Node* const user = edge.from(); switch (user->opcode()) { case IrOpcode::kCheckMaps: case IrOpcode::kFrameState: case IrOpcode::kStateValues: case IrOpcode::kReferenceEqual: case IrOpcode::kReturn: // Ignore safe uses that definitely don't mess with the arguments. continue; case IrOpcode::kLoadField: { DCHECK_EQ(arguments_list, user->InputAt(0)); FieldAccess const& access = FieldAccessOf(user->op()); if (access.offset == JSArray::kLengthOffset) { // Ignore uses for arguments#length. STATIC_ASSERT(JSArray::kLengthOffset == JSArgumentsObjectWithLength::kLengthOffset); continue; } else if (access.offset == JSObject::kElementsOffset) { // Ignore safe uses for arguments#elements. if (IsSafeArgumentsElements(user)) continue; } break; } case IrOpcode::kJSCallWithArrayLike: // Ignore uses as argumentsList input to calls with array like. if (user->InputAt(2) == arguments_list) continue; break; case IrOpcode::kJSConstructWithArrayLike: // Ignore uses as argumentsList input to calls with array like. if (user->InputAt(1) == arguments_list) continue; break; case IrOpcode::kJSCallWithSpread: { // Ignore uses as spread input to calls with spread. CallParameters p = CallParametersOf(user->op()); int const arity = static_cast(p.arity() - 1); if (user->InputAt(arity) == arguments_list) continue; break; } case IrOpcode::kJSConstructWithSpread: { // Ignore uses as spread input to construct with spread. ConstructParameters p = ConstructParametersOf(user->op()); int const arity = static_cast(p.arity() - 2); if (user->InputAt(arity) == arguments_list) continue; break; } default: break; } // We cannot currently reduce the {node} to something better than what // it already is, but we might be able to do something about the {node} // later, so put it on the waitlist and try again during finalization. waitlist_.insert(node); return NoChange(); } // Get to the actual frame state from which to extract the arguments; // we can only optimize this in case the {node} was already inlined into // some other function (and same for the {arguments_list}). CreateArgumentsType const type = CreateArgumentsTypeOf(arguments_list->op()); Node* frame_state = NodeProperties::GetFrameStateInput(arguments_list); FrameStateInfo state_info = FrameStateInfoOf(frame_state->op()); int start_index = 0; // Determine the formal parameter count; Handle shared; if (!state_info.shared_info().ToHandle(&shared)) return NoChange(); int formal_parameter_count = shared->internal_formal_parameter_count(); if (type == CreateArgumentsType::kMappedArguments) { // Mapped arguments (sloppy mode) that are aliased can only be handled // here if there's no side-effect between the {node} and the {arg_array}. // TODO(turbofan): Further relax this constraint. if (formal_parameter_count != 0) { Node* effect = NodeProperties::GetEffectInput(node); if (!NodeProperties::NoObservableSideEffectBetween(effect, arguments_list)) { return NoChange(); } } } else if (type == CreateArgumentsType::kRestParameter) { start_index = formal_parameter_count; } // For call/construct with spread, we need to also install a code // dependency on the array iterator lookup protector cell to ensure // that no one messed with the %ArrayIteratorPrototype%.next method. if (node->opcode() == IrOpcode::kJSCallWithSpread || node->opcode() == IrOpcode::kJSConstructWithSpread) { dependencies()->DependOnProtector(PropertyCellRef( js_heap_broker(), factory()->array_iterator_protector())); } // Remove the {arguments_list} input from the {node}. node->RemoveInput(arity--); // Check if are spreading to inlined arguments or to the arguments of // the outermost function. Node* outer_state = frame_state->InputAt(kFrameStateOuterStateInput); if (outer_state->opcode() != IrOpcode::kFrameState) { Operator const* op = (node->opcode() == IrOpcode::kJSCallWithArrayLike || node->opcode() == IrOpcode::kJSCallWithSpread) ? javascript()->CallForwardVarargs(arity + 1, start_index) : javascript()->ConstructForwardVarargs(arity + 2, start_index); NodeProperties::ChangeOp(node, op); return Changed(node); } // Get to the actual frame state from which to extract the arguments; // we can only optimize this in case the {node} was already inlined into // some other function (and same for the {arg_array}). FrameStateInfo outer_info = FrameStateInfoOf(outer_state->op()); if (outer_info.type() == FrameStateType::kArgumentsAdaptor) { // Need to take the parameters from the arguments adaptor. frame_state = outer_state; } // Add the actual parameters to the {node}, skipping the receiver. Node* const parameters = frame_state->InputAt(kFrameStateParametersInput); for (int i = start_index + 1; i < parameters->InputCount(); ++i) { node->InsertInput(graph()->zone(), static_cast(++arity), parameters->InputAt(i)); } if (node->opcode() == IrOpcode::kJSCallWithArrayLike || node->opcode() == IrOpcode::kJSCallWithSpread) { NodeProperties::ChangeOp( node, javascript()->Call(arity + 1, frequency, feedback)); Reduction const reduction = ReduceJSCall(node); return reduction.Changed() ? reduction : Changed(node); } else { NodeProperties::ChangeOp( node, javascript()->Construct(arity + 2, frequency, feedback)); Node* new_target = NodeProperties::GetValueInput(node, arity + 1); Node* frame_state = NodeProperties::GetFrameStateInput(node); Node* context = NodeProperties::GetContextInput(node); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); // Check whether the given new target value is a constructor function. The // replacement {JSConstruct} operator only checks the passed target value // but relies on the new target value to be implicitly valid. Node* check = graph()->NewNode(simplified()->ObjectIsConstructor(), new_target); Node* check_branch = graph()->NewNode(common()->Branch(BranchHint::kTrue), check, control); Node* check_fail = graph()->NewNode(common()->IfFalse(), check_branch); Node* check_throw = check_fail = graph()->NewNode(javascript()->CallRuntime(Runtime::kThrowTypeError, 2), jsgraph()->Constant(MessageTemplate::kNotConstructor), new_target, context, frame_state, effect, check_fail); control = graph()->NewNode(common()->IfTrue(), check_branch); NodeProperties::ReplaceControlInput(node, control); // Rewire potential exception edges. Node* on_exception = nullptr; if (NodeProperties::IsExceptionalCall(node, &on_exception)) { // Create appropriate {IfException} and {IfSuccess} nodes. Node* if_exception = graph()->NewNode(common()->IfException(), check_throw, check_fail); check_fail = graph()->NewNode(common()->IfSuccess(), check_fail); // Join the exception edges. Node* merge = graph()->NewNode(common()->Merge(2), if_exception, on_exception); Node* ephi = graph()->NewNode(common()->EffectPhi(2), if_exception, on_exception, merge); Node* phi = graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2), if_exception, on_exception, merge); ReplaceWithValue(on_exception, phi, ephi, merge); merge->ReplaceInput(1, on_exception); ephi->ReplaceInput(1, on_exception); phi->ReplaceInput(1, on_exception); } // The above %ThrowTypeError runtime call is an unconditional throw, making // it impossible to return a successful completion in this case. We simply // connect the successful completion to the graph end. Node* throw_node = graph()->NewNode(common()->Throw(), check_throw, check_fail); NodeProperties::MergeControlToEnd(graph(), common(), throw_node); Reduction const reduction = ReduceJSConstruct(node); return reduction.Changed() ? reduction : Changed(node); } } namespace { bool ShouldUseCallICFeedback(Node* node) { HeapObjectMatcher m(node); if (m.HasValue() || m.IsJSCreateClosure()) { // Don't use CallIC feedback when we know the function // being called, i.e. either know the closure itself or // at least the SharedFunctionInfo. return false; } else if (m.IsPhi()) { // Protect against endless loops here. Node* control = NodeProperties::GetControlInput(node); if (control->opcode() == IrOpcode::kLoop) return false; // Check if {node} is a Phi of nodes which shouldn't // use CallIC feedback (not looking through loops). int const value_input_count = m.node()->op()->ValueInputCount(); for (int n = 0; n < value_input_count; ++n) { if (ShouldUseCallICFeedback(node->InputAt(n))) return true; } return false; } return true; } } // namespace Reduction JSCallReducer::ReduceJSCall(Node* node) { DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); CallParameters const& p = CallParametersOf(node->op()); Node* target = NodeProperties::GetValueInput(node, 0); Node* control = NodeProperties::GetControlInput(node); Node* effect = NodeProperties::GetEffectInput(node); size_t arity = p.arity(); DCHECK_LE(2u, arity); // Try to specialize JSCall {node}s with constant {target}s. HeapObjectMatcher m(target); if (m.HasValue()) { if (m.Value()->IsJSFunction()) { Handle function = Handle::cast(m.Value()); // Don't inline cross native context. if (function->native_context() != *native_context()) return NoChange(); return ReduceJSCall(node, handle(function->shared(), isolate())); } else if (m.Value()->IsJSBoundFunction()) { Handle function = Handle::cast(m.Value()); Handle bound_target_function( function->bound_target_function(), isolate()); Handle bound_this(function->bound_this(), isolate()); Handle bound_arguments(function->bound_arguments(), isolate()); ConvertReceiverMode const convert_mode = (bound_this->IsNullOrUndefined(isolate())) ? ConvertReceiverMode::kNullOrUndefined : ConvertReceiverMode::kNotNullOrUndefined; // Patch {node} to use [[BoundTargetFunction]] and [[BoundThis]]. NodeProperties::ReplaceValueInput( node, jsgraph()->Constant(bound_target_function), 0); NodeProperties::ReplaceValueInput(node, jsgraph()->Constant(bound_this), 1); // Insert the [[BoundArguments]] for {node}. for (int i = 0; i < bound_arguments->length(); ++i) { node->InsertInput( graph()->zone(), i + 2, jsgraph()->Constant(handle(bound_arguments->get(i), isolate()))); arity++; } NodeProperties::ChangeOp( node, javascript()->Call(arity, p.frequency(), VectorSlotPair(), convert_mode)); // Try to further reduce the JSCall {node}. Reduction const reduction = ReduceJSCall(node); return reduction.Changed() ? reduction : Changed(node); } // Don't mess with other {node}s that have a constant {target}. // TODO(bmeurer): Also support proxies here. return NoChange(); } // If {target} is the result of a JSCreateClosure operation, we can // just immediately try to inline based on the SharedFunctionInfo, // since TurboFan generally doesn't inline cross-context, and hence // the {target} must have the same native context as the call site. if (target->opcode() == IrOpcode::kJSCreateClosure) { CreateClosureParameters const& p = CreateClosureParametersOf(target->op()); return ReduceJSCall(node, p.shared_info()); } // If {target} is the result of a JSCreateBoundFunction operation, // we can just fold the construction and call the bound target // function directly instead. if (target->opcode() == IrOpcode::kJSCreateBoundFunction) { Node* bound_target_function = NodeProperties::GetValueInput(target, 0); Node* bound_this = NodeProperties::GetValueInput(target, 1); int const bound_arguments_length = static_cast(CreateBoundFunctionParametersOf(target->op()).arity()); // Patch the {node} to use [[BoundTargetFunction]] and [[BoundThis]]. NodeProperties::ReplaceValueInput(node, bound_target_function, 0); NodeProperties::ReplaceValueInput(node, bound_this, 1); // Insert the [[BoundArguments]] for {node}. for (int i = 0; i < bound_arguments_length; ++i) { Node* value = NodeProperties::GetValueInput(target, 2 + i); node->InsertInput(graph()->zone(), 2 + i, value); arity++; } // Update the JSCall operator on {node}. ConvertReceiverMode const convert_mode = NodeProperties::CanBeNullOrUndefined(isolate(), bound_this, effect) ? ConvertReceiverMode::kAny : ConvertReceiverMode::kNotNullOrUndefined; NodeProperties::ChangeOp( node, javascript()->Call(arity, p.frequency(), VectorSlotPair(), convert_mode)); // Try to further reduce the JSCall {node}. Reduction const reduction = ReduceJSCall(node); return reduction.Changed() ? reduction : Changed(node); } // Extract feedback from the {node} using the FeedbackNexus. if (!p.feedback().IsValid()) return NoChange(); FeedbackNexus nexus(p.feedback().vector(), p.feedback().slot()); if (nexus.IsUninitialized()) { if (flags() & kBailoutOnUninitialized) { // Introduce a SOFT deopt if the call {node} wasn't executed so far. return ReduceSoftDeoptimize( node, DeoptimizeReason::kInsufficientTypeFeedbackForCall); } return NoChange(); } HeapObject* heap_object; if (nexus.GetFeedback()->GetHeapObjectIfWeak(&heap_object)) { Handle feedback(heap_object, isolate()); // Check if we want to use CallIC feedback here. if (!ShouldUseCallICFeedback(target)) return NoChange(); if (feedback->IsCallable()) { Node* target_function = jsgraph()->Constant(feedback); // Check that the {target} is still the {target_function}. Node* check = graph()->NewNode(simplified()->ReferenceEqual(), target, target_function); effect = graph()->NewNode( simplified()->CheckIf(DeoptimizeReason::kWrongCallTarget), check, effect, control); // Specialize the JSCall node to the {target_function}. NodeProperties::ReplaceValueInput(node, target_function, 0); NodeProperties::ReplaceEffectInput(node, effect); // Try to further reduce the JSCall {node}. Reduction const reduction = ReduceJSCall(node); return reduction.Changed() ? reduction : Changed(node); } } return NoChange(); } Reduction JSCallReducer::ReduceJSCall(Node* node, Handle shared) { DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); Node* target = NodeProperties::GetValueInput(node, 0); // Do not reduce calls to functions with break points. if (shared->HasBreakInfo()) return NoChange(); // Raise a TypeError if the {target} is a "classConstructor". if (IsClassConstructor(shared->kind())) { NodeProperties::ReplaceValueInputs(node, target); NodeProperties::ChangeOp( node, javascript()->CallRuntime( Runtime::kThrowConstructorNonCallableError, 1)); return Changed(node); } // Check for known builtin functions. int builtin_id = shared->HasBuiltinId() ? shared->builtin_id() : Builtins::kNoBuiltinId; switch (builtin_id) { case Builtins::kArrayConstructor: return ReduceArrayConstructor(node); case Builtins::kBooleanConstructor: return ReduceBooleanConstructor(node); case Builtins::kFunctionPrototypeApply: return ReduceFunctionPrototypeApply(node); case Builtins::kFastFunctionPrototypeBind: return ReduceFunctionPrototypeBind(node); case Builtins::kFunctionPrototypeCall: return ReduceFunctionPrototypeCall(node); case Builtins::kFunctionPrototypeHasInstance: return ReduceFunctionPrototypeHasInstance(node); case Builtins::kObjectConstructor: return ReduceObjectConstructor(node); case Builtins::kObjectCreate: return ReduceObjectCreate(node); case Builtins::kObjectGetPrototypeOf: return ReduceObjectGetPrototypeOf(node); case Builtins::kObjectIs: return ReduceObjectIs(node); case Builtins::kObjectPrototypeGetProto: return ReduceObjectPrototypeGetProto(node); case Builtins::kObjectPrototypeHasOwnProperty: return ReduceObjectPrototypeHasOwnProperty(node); case Builtins::kObjectPrototypeIsPrototypeOf: return ReduceObjectPrototypeIsPrototypeOf(node); case Builtins::kReflectApply: return ReduceReflectApply(node); case Builtins::kReflectConstruct: return ReduceReflectConstruct(node); case Builtins::kReflectGet: return ReduceReflectGet(node); case Builtins::kReflectGetPrototypeOf: return ReduceReflectGetPrototypeOf(node); case Builtins::kReflectHas: return ReduceReflectHas(node); case Builtins::kArrayForEach: return ReduceArrayForEach(node, shared); case Builtins::kArrayMap: return ReduceArrayMap(node, shared); case Builtins::kArrayFilter: return ReduceArrayFilter(node, shared); case Builtins::kArrayReduce: return ReduceArrayReduce(node, ArrayReduceDirection::kLeft, shared); case Builtins::kArrayReduceRight: return ReduceArrayReduce(node, ArrayReduceDirection::kRight, shared); case Builtins::kArrayPrototypeFind: return ReduceArrayFind(node, ArrayFindVariant::kFind, shared); case Builtins::kArrayPrototypeFindIndex: return ReduceArrayFind(node, ArrayFindVariant::kFindIndex, shared); case Builtins::kArrayEvery: return ReduceArrayEvery(node, shared); case Builtins::kArrayIndexOf: return ReduceArrayIndexOfIncludes(SearchVariant::kIndexOf, node); case Builtins::kArrayIncludes: return ReduceArrayIndexOfIncludes(SearchVariant::kIncludes, node); case Builtins::kArraySome: return ReduceArraySome(node, shared); case Builtins::kArrayPrototypePush: return ReduceArrayPrototypePush(node); case Builtins::kArrayPrototypePop: return ReduceArrayPrototypePop(node); case Builtins::kArrayPrototypeShift: return ReduceArrayPrototypeShift(node); case Builtins::kArrayPrototypeSlice: return ReduceArrayPrototypeSlice(node); case Builtins::kArrayPrototypeEntries: return ReduceArrayIterator(node, IterationKind::kEntries); case Builtins::kArrayPrototypeKeys: return ReduceArrayIterator(node, IterationKind::kKeys); case Builtins::kArrayPrototypeValues: return ReduceArrayIterator(node, IterationKind::kValues); case Builtins::kArrayIteratorPrototypeNext: return ReduceArrayIteratorPrototypeNext(node); case Builtins::kArrayIsArray: return ReduceArrayIsArray(node); case Builtins::kArrayBufferIsView: return ReduceArrayBufferIsView(node); case Builtins::kDataViewPrototypeGetByteLength: return ReduceArrayBufferViewAccessor( node, JS_DATA_VIEW_TYPE, AccessBuilder::ForJSArrayBufferViewByteLength()); case Builtins::kDataViewPrototypeGetByteOffset: return ReduceArrayBufferViewAccessor( node, JS_DATA_VIEW_TYPE, AccessBuilder::ForJSArrayBufferViewByteOffset()); case Builtins::kDataViewPrototypeGetUint8: return ReduceDataViewAccess(node, DataViewAccess::kGet, ExternalArrayType::kExternalUint8Array); case Builtins::kDataViewPrototypeGetInt8: return ReduceDataViewAccess(node, DataViewAccess::kGet, ExternalArrayType::kExternalInt8Array); case Builtins::kDataViewPrototypeGetUint16: return ReduceDataViewAccess(node, DataViewAccess::kGet, ExternalArrayType::kExternalUint16Array); case Builtins::kDataViewPrototypeGetInt16: return ReduceDataViewAccess(node, DataViewAccess::kGet, ExternalArrayType::kExternalInt16Array); case Builtins::kDataViewPrototypeGetUint32: return ReduceDataViewAccess(node, DataViewAccess::kGet, ExternalArrayType::kExternalUint32Array); case Builtins::kDataViewPrototypeGetInt32: return ReduceDataViewAccess(node, DataViewAccess::kGet, ExternalArrayType::kExternalInt32Array); case Builtins::kDataViewPrototypeGetFloat32: return ReduceDataViewAccess(node, DataViewAccess::kGet, ExternalArrayType::kExternalFloat32Array); case Builtins::kDataViewPrototypeGetFloat64: return ReduceDataViewAccess(node, DataViewAccess::kGet, ExternalArrayType::kExternalFloat64Array); case Builtins::kDataViewPrototypeSetUint8: return ReduceDataViewAccess(node, DataViewAccess::kSet, ExternalArrayType::kExternalUint8Array); case Builtins::kDataViewPrototypeSetInt8: return ReduceDataViewAccess(node, DataViewAccess::kSet, ExternalArrayType::kExternalInt8Array); case Builtins::kDataViewPrototypeSetUint16: return ReduceDataViewAccess(node, DataViewAccess::kSet, ExternalArrayType::kExternalUint16Array); case Builtins::kDataViewPrototypeSetInt16: return ReduceDataViewAccess(node, DataViewAccess::kSet, ExternalArrayType::kExternalInt16Array); case Builtins::kDataViewPrototypeSetUint32: return ReduceDataViewAccess(node, DataViewAccess::kSet, ExternalArrayType::kExternalUint32Array); case Builtins::kDataViewPrototypeSetInt32: return ReduceDataViewAccess(node, DataViewAccess::kSet, ExternalArrayType::kExternalInt32Array); case Builtins::kDataViewPrototypeSetFloat32: return ReduceDataViewAccess(node, DataViewAccess::kSet, ExternalArrayType::kExternalFloat32Array); case Builtins::kDataViewPrototypeSetFloat64: return ReduceDataViewAccess(node, DataViewAccess::kSet, ExternalArrayType::kExternalFloat64Array); case Builtins::kTypedArrayPrototypeByteLength: return ReduceArrayBufferViewAccessor( node, JS_TYPED_ARRAY_TYPE, AccessBuilder::ForJSArrayBufferViewByteLength()); case Builtins::kTypedArrayPrototypeByteOffset: return ReduceArrayBufferViewAccessor( node, JS_TYPED_ARRAY_TYPE, AccessBuilder::ForJSArrayBufferViewByteOffset()); case Builtins::kTypedArrayPrototypeLength: return ReduceArrayBufferViewAccessor( node, JS_TYPED_ARRAY_TYPE, AccessBuilder::ForJSTypedArrayLength()); case Builtins::kTypedArrayPrototypeToStringTag: return ReduceTypedArrayPrototypeToStringTag(node); case Builtins::kMathAbs: return ReduceMathUnary(node, simplified()->NumberAbs()); case Builtins::kMathAcos: return ReduceMathUnary(node, simplified()->NumberAcos()); case Builtins::kMathAcosh: return ReduceMathUnary(node, simplified()->NumberAcosh()); case Builtins::kMathAsin: return ReduceMathUnary(node, simplified()->NumberAsin()); case Builtins::kMathAsinh: return ReduceMathUnary(node, simplified()->NumberAsinh()); case Builtins::kMathAtan: return ReduceMathUnary(node, simplified()->NumberAtan()); case Builtins::kMathAtanh: return ReduceMathUnary(node, simplified()->NumberAtanh()); case Builtins::kMathCbrt: return ReduceMathUnary(node, simplified()->NumberCbrt()); case Builtins::kMathCeil: return ReduceMathUnary(node, simplified()->NumberCeil()); case Builtins::kMathCos: return ReduceMathUnary(node, simplified()->NumberCos()); case Builtins::kMathCosh: return ReduceMathUnary(node, simplified()->NumberCosh()); case Builtins::kMathExp: return ReduceMathUnary(node, simplified()->NumberExp()); case Builtins::kMathExpm1: return ReduceMathUnary(node, simplified()->NumberExpm1()); case Builtins::kMathFloor: return ReduceMathUnary(node, simplified()->NumberFloor()); case Builtins::kMathFround: return ReduceMathUnary(node, simplified()->NumberFround()); case Builtins::kMathLog: return ReduceMathUnary(node, simplified()->NumberLog()); case Builtins::kMathLog1p: return ReduceMathUnary(node, simplified()->NumberLog1p()); case Builtins::kMathLog10: return ReduceMathUnary(node, simplified()->NumberLog10()); case Builtins::kMathLog2: return ReduceMathUnary(node, simplified()->NumberLog2()); case Builtins::kMathRound: return ReduceMathUnary(node, simplified()->NumberRound()); case Builtins::kMathSign: return ReduceMathUnary(node, simplified()->NumberSign()); case Builtins::kMathSin: return ReduceMathUnary(node, simplified()->NumberSin()); case Builtins::kMathSinh: return ReduceMathUnary(node, simplified()->NumberSinh()); case Builtins::kMathSqrt: return ReduceMathUnary(node, simplified()->NumberSqrt()); case Builtins::kMathTan: return ReduceMathUnary(node, simplified()->NumberTan()); case Builtins::kMathTanh: return ReduceMathUnary(node, simplified()->NumberTanh()); case Builtins::kMathTrunc: return ReduceMathUnary(node, simplified()->NumberTrunc()); case Builtins::kMathAtan2: return ReduceMathBinary(node, simplified()->NumberAtan2()); case Builtins::kMathPow: return ReduceMathBinary(node, simplified()->NumberPow()); case Builtins::kMathClz32: return ReduceMathClz32(node); case Builtins::kMathImul: return ReduceMathImul(node); case Builtins::kMathMax: return ReduceMathMinMax(node, simplified()->NumberMax(), jsgraph()->Constant(-V8_INFINITY)); case Builtins::kMathMin: return ReduceMathMinMax(node, simplified()->NumberMin(), jsgraph()->Constant(V8_INFINITY)); case Builtins::kNumberIsFinite: return ReduceNumberIsFinite(node); case Builtins::kNumberIsInteger: return ReduceNumberIsInteger(node); case Builtins::kNumberIsSafeInteger: return ReduceNumberIsSafeInteger(node); case Builtins::kNumberIsNaN: return ReduceNumberIsNaN(node); case Builtins::kNumberParseInt: return ReduceNumberParseInt(node); case Builtins::kGlobalIsFinite: return ReduceGlobalIsFinite(node); case Builtins::kGlobalIsNaN: return ReduceGlobalIsNaN(node); case Builtins::kMapPrototypeGet: return ReduceMapPrototypeGet(node); case Builtins::kMapPrototypeHas: return ReduceMapPrototypeHas(node); case Builtins::kRegExpPrototypeTest: return ReduceRegExpPrototypeTest(node); case Builtins::kReturnReceiver: return ReduceReturnReceiver(node); case Builtins::kStringPrototypeIndexOf: return ReduceStringPrototypeIndexOf(node); case Builtins::kStringPrototypeCharAt: return ReduceStringPrototypeCharAt(node); case Builtins::kStringPrototypeCharCodeAt: return ReduceStringPrototypeStringAt(simplified()->StringCharCodeAt(), node); case Builtins::kStringPrototypeCodePointAt: return ReduceStringPrototypeStringAt( simplified()->StringCodePointAt(UnicodeEncoding::UTF32), node); case Builtins::kStringPrototypeSubstring: return ReduceStringPrototypeSubstring(node); case Builtins::kStringPrototypeSlice: return ReduceStringPrototypeSlice(node); case Builtins::kStringPrototypeSubstr: return ReduceStringPrototypeSubstr(node); #ifdef V8_INTL_SUPPORT case Builtins::kStringPrototypeToLowerCaseIntl: return ReduceStringPrototypeToLowerCaseIntl(node); case Builtins::kStringPrototypeToUpperCaseIntl: return ReduceStringPrototypeToUpperCaseIntl(node); #endif // V8_INTL_SUPPORT case Builtins::kStringFromCharCode: return ReduceStringFromCharCode(node); case Builtins::kStringFromCodePoint: return ReduceStringFromCodePoint(node); case Builtins::kStringPrototypeIterator: return ReduceStringPrototypeIterator(node); case Builtins::kStringIteratorPrototypeNext: return ReduceStringIteratorPrototypeNext(node); case Builtins::kStringPrototypeConcat: return ReduceStringPrototypeConcat(node, shared); case Builtins::kTypedArrayPrototypeEntries: return ReduceArrayIterator(node, IterationKind::kEntries); case Builtins::kTypedArrayPrototypeKeys: return ReduceArrayIterator(node, IterationKind::kKeys); case Builtins::kTypedArrayPrototypeValues: return ReduceArrayIterator(node, IterationKind::kValues); case Builtins::kAsyncFunctionPromiseCreate: return ReduceAsyncFunctionPromiseCreate(node); case Builtins::kAsyncFunctionPromiseRelease: return ReduceAsyncFunctionPromiseRelease(node); case Builtins::kPromiseInternalConstructor: return ReducePromiseInternalConstructor(node); case Builtins::kPromiseInternalReject: return ReducePromiseInternalReject(node); case Builtins::kPromiseInternalResolve: return ReducePromiseInternalResolve(node); case Builtins::kPromisePrototypeCatch: return ReducePromisePrototypeCatch(node); case Builtins::kPromisePrototypeFinally: return ReducePromisePrototypeFinally(node); case Builtins::kPromisePrototypeThen: return ReducePromisePrototypeThen(node); case Builtins::kMapPrototypeEntries: return ReduceCollectionIteration(node, CollectionKind::kMap, IterationKind::kEntries); case Builtins::kMapPrototypeKeys: return ReduceCollectionIteration(node, CollectionKind::kMap, IterationKind::kKeys); case Builtins::kMapPrototypeGetSize: return ReduceCollectionPrototypeSize(node, CollectionKind::kMap); case Builtins::kMapPrototypeValues: return ReduceCollectionIteration(node, CollectionKind::kMap, IterationKind::kValues); case Builtins::kMapIteratorPrototypeNext: return ReduceCollectionIteratorPrototypeNext( node, OrderedHashMap::kEntrySize, factory()->empty_ordered_hash_map(), FIRST_MAP_ITERATOR_TYPE, LAST_MAP_ITERATOR_TYPE); case Builtins::kSetPrototypeEntries: return ReduceCollectionIteration(node, CollectionKind::kSet, IterationKind::kEntries); case Builtins::kSetPrototypeGetSize: return ReduceCollectionPrototypeSize(node, CollectionKind::kSet); case Builtins::kSetPrototypeValues: return ReduceCollectionIteration(node, CollectionKind::kSet, IterationKind::kValues); case Builtins::kSetIteratorPrototypeNext: return ReduceCollectionIteratorPrototypeNext( node, OrderedHashSet::kEntrySize, factory()->empty_ordered_hash_set(), FIRST_SET_ITERATOR_TYPE, LAST_SET_ITERATOR_TYPE); case Builtins::kDatePrototypeGetTime: return ReduceDatePrototypeGetTime(node); case Builtins::kDateNow: return ReduceDateNow(node); case Builtins::kNumberConstructor: return ReduceNumberConstructor(node); default: break; } if (!FLAG_runtime_stats && shared->IsApiFunction()) { return ReduceCallApiFunction(node, shared); } return NoChange(); } Reduction JSCallReducer::ReduceJSCallWithArrayLike(Node* node) { DCHECK_EQ(IrOpcode::kJSCallWithArrayLike, node->opcode()); CallFrequency frequency = CallFrequencyOf(node->op()); VectorSlotPair feedback; return ReduceCallOrConstructWithArrayLikeOrSpread(node, 2, frequency, feedback); } Reduction JSCallReducer::ReduceJSCallWithSpread(Node* node) { DCHECK_EQ(IrOpcode::kJSCallWithSpread, node->opcode()); CallParameters const& p = CallParametersOf(node->op()); DCHECK_LE(3u, p.arity()); int arity = static_cast(p.arity() - 1); CallFrequency frequency = p.frequency(); VectorSlotPair feedback = p.feedback(); return ReduceCallOrConstructWithArrayLikeOrSpread(node, arity, frequency, feedback); } Reduction JSCallReducer::ReduceJSConstruct(Node* node) { DCHECK_EQ(IrOpcode::kJSConstruct, node->opcode()); ConstructParameters const& p = ConstructParametersOf(node->op()); DCHECK_LE(2u, p.arity()); int arity = static_cast(p.arity() - 2); Node* target = NodeProperties::GetValueInput(node, 0); Node* new_target = NodeProperties::GetValueInput(node, arity + 1); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); // Extract feedback from the {node} using the FeedbackNexus. if (p.feedback().IsValid()) { FeedbackNexus nexus(p.feedback().vector(), p.feedback().slot()); if (nexus.IsUninitialized()) { if (flags() & kBailoutOnUninitialized) { // Introduce a SOFT deopt if the construct {node} wasn't executed so // far. return ReduceSoftDeoptimize( node, DeoptimizeReason::kInsufficientTypeFeedbackForConstruct); } return NoChange(); } HeapObject* feedback_object; if (nexus.GetFeedback()->GetHeapObjectIfStrong(&feedback_object) && feedback_object->IsAllocationSite()) { // The feedback is an AllocationSite, which means we have called the // Array function and collected transition (and pretenuring) feedback // for the resulting arrays. This has to be kept in sync with the // implementation in Ignition. Handle site(AllocationSite::cast(feedback_object), isolate()); // Retrieve the Array function from the {node}. Node* array_function = jsgraph()->HeapConstant( handle(native_context()->array_function(), isolate())); // Check that the {target} is still the {array_function}. Node* check = graph()->NewNode(simplified()->ReferenceEqual(), target, array_function); effect = graph()->NewNode( simplified()->CheckIf(DeoptimizeReason::kWrongCallTarget), check, effect, control); // Turn the {node} into a {JSCreateArray} call. NodeProperties::ReplaceEffectInput(node, effect); for (int i = arity; i > 0; --i) { NodeProperties::ReplaceValueInput( node, NodeProperties::GetValueInput(node, i), i + 1); } NodeProperties::ReplaceValueInput(node, array_function, 1); NodeProperties::ChangeOp(node, javascript()->CreateArray(arity, site)); return Changed(node); } else if (nexus.GetFeedback()->GetHeapObjectIfWeak(&feedback_object) && !HeapObjectMatcher(new_target).HasValue()) { Handle object(feedback_object, isolate()); if (object->IsConstructor()) { Node* new_target_feedback = jsgraph()->Constant(object); // Check that the {new_target} is still the {new_target_feedback}. Node* check = graph()->NewNode(simplified()->ReferenceEqual(), new_target, new_target_feedback); effect = graph()->NewNode( simplified()->CheckIf(DeoptimizeReason::kWrongCallTarget), check, effect, control); // Specialize the JSConstruct node to the {new_target_feedback}. NodeProperties::ReplaceValueInput(node, new_target_feedback, arity + 1); NodeProperties::ReplaceEffectInput(node, effect); if (target == new_target) { NodeProperties::ReplaceValueInput(node, new_target_feedback, 0); } // Try to further reduce the JSConstruct {node}. Reduction const reduction = ReduceJSConstruct(node); return reduction.Changed() ? reduction : Changed(node); } } } // Try to specialize JSConstruct {node}s with constant {target}s. HeapObjectMatcher m(target); if (m.HasValue()) { // Raise a TypeError if the {target} is not a constructor. if (!m.Value()->IsConstructor()) { NodeProperties::ReplaceValueInputs(node, target); NodeProperties::ChangeOp(node, javascript()->CallRuntime( Runtime::kThrowConstructedNonConstructable)); return Changed(node); } if (m.Value()->IsJSFunction()) { Handle function = Handle::cast(m.Value()); // Do not reduce constructors with break points. if (function->shared()->HasBreakInfo()) return NoChange(); // Don't inline cross native context. if (function->native_context() != *native_context()) return NoChange(); // Check for known builtin functions. int builtin_id = function->shared()->HasBuiltinId() ? function->shared()->builtin_id() : Builtins::kNoBuiltinId; switch (builtin_id) { case Builtins::kArrayConstructor: { // TODO(bmeurer): Deal with Array subclasses here. Handle site; // Turn the {node} into a {JSCreateArray} call. for (int i = arity; i > 0; --i) { NodeProperties::ReplaceValueInput( node, NodeProperties::GetValueInput(node, i), i + 1); } NodeProperties::ReplaceValueInput(node, new_target, 1); NodeProperties::ChangeOp(node, javascript()->CreateArray(arity, site)); return Changed(node); } case Builtins::kObjectConstructor: { // If no value is passed, we can immediately lower to a simple // JSCreate and don't need to do any massaging of the {node}. if (arity == 0) { NodeProperties::ChangeOp(node, javascript()->Create()); return Changed(node); } // Otherwise we can only lower to JSCreate if we know that // the value parameter is ignored, which is only the case if // the {new_target} and {target} are definitely not identical. HeapObjectMatcher mnew_target(new_target); if (mnew_target.HasValue() && *mnew_target.Value() != *function) { // Drop the value inputs. for (int i = arity; i > 0; --i) node->RemoveInput(i); NodeProperties::ChangeOp(node, javascript()->Create()); return Changed(node); } break; } case Builtins::kPromiseConstructor: return ReducePromiseConstructor(node); case Builtins::kTypedArrayConstructor: return ReduceTypedArrayConstructor( node, handle(function->shared(), isolate())); default: break; } } else if (m.Value()->IsJSBoundFunction()) { Handle function = Handle::cast(m.Value()); Handle bound_target_function( function->bound_target_function(), isolate()); Handle bound_arguments(function->bound_arguments(), isolate()); // Patch {node} to use [[BoundTargetFunction]]. NodeProperties::ReplaceValueInput( node, jsgraph()->Constant(bound_target_function), 0); // Patch {node} to use [[BoundTargetFunction]] // as new.target if {new_target} equals {target}. NodeProperties::ReplaceValueInput( node, graph()->NewNode(common()->Select(MachineRepresentation::kTagged), graph()->NewNode(simplified()->ReferenceEqual(), target, new_target), jsgraph()->Constant(bound_target_function), new_target), arity + 1); // Insert the [[BoundArguments]] for {node}. for (int i = 0; i < bound_arguments->length(); ++i) { node->InsertInput( graph()->zone(), i + 1, jsgraph()->Constant(handle(bound_arguments->get(i), isolate()))); arity++; } // Update the JSConstruct operator on {node}. NodeProperties::ChangeOp( node, javascript()->Construct(arity + 2, p.frequency(), VectorSlotPair())); // Try to further reduce the JSConstruct {node}. Reduction const reduction = ReduceJSConstruct(node); return reduction.Changed() ? reduction : Changed(node); } // TODO(bmeurer): Also support optimizing proxies here. } // If {target} is the result of a JSCreateBoundFunction operation, // we can just fold the construction and construct the bound target // function directly instead. if (target->opcode() == IrOpcode::kJSCreateBoundFunction) { Node* bound_target_function = NodeProperties::GetValueInput(target, 0); int const bound_arguments_length = static_cast(CreateBoundFunctionParametersOf(target->op()).arity()); // Patch the {node} to use [[BoundTargetFunction]]. NodeProperties::ReplaceValueInput(node, bound_target_function, 0); // Patch {node} to use [[BoundTargetFunction]] // as new.target if {new_target} equals {target}. NodeProperties::ReplaceValueInput( node, graph()->NewNode(common()->Select(MachineRepresentation::kTagged), graph()->NewNode(simplified()->ReferenceEqual(), target, new_target), bound_target_function, new_target), arity + 1); // Insert the [[BoundArguments]] for {node}. for (int i = 0; i < bound_arguments_length; ++i) { Node* value = NodeProperties::GetValueInput(target, 2 + i); node->InsertInput(graph()->zone(), 1 + i, value); arity++; } // Update the JSConstruct operator on {node}. NodeProperties::ChangeOp( node, javascript()->Construct(arity + 2, p.frequency(), VectorSlotPair())); // Try to further reduce the JSConstruct {node}. Reduction const reduction = ReduceJSConstruct(node); return reduction.Changed() ? reduction : Changed(node); } return NoChange(); } // ES #sec-string.prototype.indexof Reduction JSCallReducer::ReduceStringPrototypeIndexOf(Node* node) { DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); CallParameters const& p = CallParametersOf(node->op()); if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) { return NoChange(); } Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); if (node->op()->ValueInputCount() >= 3) { Node* receiver = NodeProperties::GetValueInput(node, 1); Node* new_receiver = effect = graph()->NewNode( simplified()->CheckString(p.feedback()), receiver, effect, control); Node* search_string = NodeProperties::GetValueInput(node, 2); Node* new_search_string = effect = graph()->NewNode(simplified()->CheckString(p.feedback()), search_string, effect, control); Node* new_position = jsgraph()->ZeroConstant(); if (node->op()->ValueInputCount() >= 4) { Node* position = NodeProperties::GetValueInput(node, 3); new_position = effect = graph()->NewNode( simplified()->CheckSmi(p.feedback()), position, effect, control); } NodeProperties::ReplaceEffectInput(node, effect); RelaxEffectsAndControls(node); node->ReplaceInput(0, new_receiver); node->ReplaceInput(1, new_search_string); node->ReplaceInput(2, new_position); node->TrimInputCount(3); NodeProperties::ChangeOp(node, simplified()->StringIndexOf()); return Changed(node); } return NoChange(); } // ES #sec-string.prototype.substring Reduction JSCallReducer::ReduceStringPrototypeSubstring(Node* node) { if (node->op()->ValueInputCount() < 3) return NoChange(); CallParameters const& p = CallParametersOf(node->op()); if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) { return NoChange(); } Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); Node* receiver = NodeProperties::GetValueInput(node, 1); Node* start = NodeProperties::GetValueInput(node, 2); Node* end = node->op()->ValueInputCount() > 3 ? NodeProperties::GetValueInput(node, 3) : jsgraph()->UndefinedConstant(); receiver = effect = graph()->NewNode(simplified()->CheckString(p.feedback()), receiver, effect, control); start = effect = graph()->NewNode(simplified()->CheckSmi(p.feedback()), start, effect, control); Node* length = graph()->NewNode(simplified()->StringLength(), receiver); Node* check = graph()->NewNode(simplified()->ReferenceEqual(), end, jsgraph()->UndefinedConstant()); Node* branch = graph()->NewNode(common()->Branch(BranchHint::kFalse), check, control); Node* if_true = graph()->NewNode(common()->IfTrue(), branch); Node* etrue = effect; Node* vtrue = length; Node* if_false = graph()->NewNode(common()->IfFalse(), branch); Node* efalse = effect; Node* vfalse = efalse = graph()->NewNode(simplified()->CheckSmi(p.feedback()), end, efalse, if_false); control = graph()->NewNode(common()->Merge(2), if_true, if_false); effect = graph()->NewNode(common()->EffectPhi(2), etrue, efalse, control); end = graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2), vtrue, vfalse, control); Node* finalStart = graph()->NewNode(simplified()->NumberMin(), graph()->NewNode(simplified()->NumberMax(), start, jsgraph()->ZeroConstant()), length); Node* finalEnd = graph()->NewNode(simplified()->NumberMin(), graph()->NewNode(simplified()->NumberMax(), end, jsgraph()->ZeroConstant()), length); Node* from = graph()->NewNode(simplified()->NumberMin(), finalStart, finalEnd); Node* to = graph()->NewNode(simplified()->NumberMax(), finalStart, finalEnd); Node* value = effect = graph()->NewNode(simplified()->StringSubstring(), receiver, from, to, effect, control); ReplaceWithValue(node, value, effect, control); return Replace(value); } // ES #sec-string.prototype.slice Reduction JSCallReducer::ReduceStringPrototypeSlice(Node* node) { if (node->op()->ValueInputCount() < 3) return NoChange(); CallParameters const& p = CallParametersOf(node->op()); if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) { return NoChange(); } Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); Node* receiver = NodeProperties::GetValueInput(node, 1); Node* start = NodeProperties::GetValueInput(node, 2); Node* end = node->op()->ValueInputCount() > 3 ? NodeProperties::GetValueInput(node, 3) : jsgraph()->UndefinedConstant(); receiver = effect = graph()->NewNode(simplified()->CheckString(p.feedback()), receiver, effect, control); start = effect = graph()->NewNode(simplified()->CheckSmi(p.feedback()), start, effect, control); Node* length = graph()->NewNode(simplified()->StringLength(), receiver); // Replace {end} argument with {length} if it is undefined. { Node* check = graph()->NewNode(simplified()->ReferenceEqual(), end, jsgraph()->UndefinedConstant()); Node* branch = graph()->NewNode(common()->Branch(BranchHint::kFalse), check, control); Node* if_true = graph()->NewNode(common()->IfTrue(), branch); Node* etrue = effect; Node* vtrue = length; Node* if_false = graph()->NewNode(common()->IfFalse(), branch); Node* efalse = effect; Node* vfalse = efalse = graph()->NewNode( simplified()->CheckSmi(p.feedback()), end, efalse, if_false); control = graph()->NewNode(common()->Merge(2), if_true, if_false); effect = graph()->NewNode(common()->EffectPhi(2), etrue, efalse, control); end = graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2), vtrue, vfalse, control); } Node* from = graph()->NewNode( common()->Select(MachineRepresentation::kTagged, BranchHint::kFalse), graph()->NewNode(simplified()->NumberLessThan(), start, jsgraph()->ZeroConstant()), graph()->NewNode( simplified()->NumberMax(), graph()->NewNode(simplified()->NumberAdd(), length, start), jsgraph()->ZeroConstant()), graph()->NewNode(simplified()->NumberMin(), start, length)); // {from} is always in non-negative Smi range, but our typer cannot // figure that out yet. from = effect = graph()->NewNode(common()->TypeGuard(Type::UnsignedSmall()), from, effect, control); Node* to = graph()->NewNode( common()->Select(MachineRepresentation::kTagged, BranchHint::kFalse), graph()->NewNode(simplified()->NumberLessThan(), end, jsgraph()->ZeroConstant()), graph()->NewNode(simplified()->NumberMax(), graph()->NewNode(simplified()->NumberAdd(), length, end), jsgraph()->ZeroConstant()), graph()->NewNode(simplified()->NumberMin(), end, length)); // {to} is always in non-negative Smi range, but our typer cannot // figure that out yet. to = effect = graph()->NewNode(common()->TypeGuard(Type::UnsignedSmall()), to, effect, control); Node* result_string = nullptr; // Return empty string if {from} is smaller than {to}. { Node* check = graph()->NewNode(simplified()->NumberLessThan(), from, to); Node* branch = graph()->NewNode(common()->Branch(BranchHint::kTrue), check, control); Node* if_true = graph()->NewNode(common()->IfTrue(), branch); Node* etrue = effect; Node* vtrue = etrue = graph()->NewNode(simplified()->StringSubstring(), receiver, from, to, etrue, if_true); Node* if_false = graph()->NewNode(common()->IfFalse(), branch); Node* efalse = effect; Node* vfalse = jsgraph()->EmptyStringConstant(); control = graph()->NewNode(common()->Merge(2), if_true, if_false); effect = graph()->NewNode(common()->EffectPhi(2), etrue, efalse, control); result_string = graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2), vtrue, vfalse, control); } ReplaceWithValue(node, result_string, effect, control); return Replace(result_string); } // ES #sec-string.prototype.substr Reduction JSCallReducer::ReduceStringPrototypeSubstr(Node* node) { if (node->op()->ValueInputCount() < 3) return NoChange(); CallParameters const& p = CallParametersOf(node->op()); if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) { return NoChange(); } Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); Node* receiver = NodeProperties::GetValueInput(node, 1); Node* start = NodeProperties::GetValueInput(node, 2); Node* end = node->op()->ValueInputCount() > 3 ? NodeProperties::GetValueInput(node, 3) : jsgraph()->UndefinedConstant(); receiver = effect = graph()->NewNode(simplified()->CheckString(p.feedback()), receiver, effect, control); start = effect = graph()->NewNode(simplified()->CheckSmi(p.feedback()), start, effect, control); Node* length = graph()->NewNode(simplified()->StringLength(), receiver); // Replace {end} argument with {length} if it is undefined. { Node* check = graph()->NewNode(simplified()->ReferenceEqual(), end, jsgraph()->UndefinedConstant()); Node* branch = graph()->NewNode(common()->Branch(BranchHint::kFalse), check, control); Node* if_true = graph()->NewNode(common()->IfTrue(), branch); Node* etrue = effect; Node* vtrue = length; Node* if_false = graph()->NewNode(common()->IfFalse(), branch); Node* efalse = effect; Node* vfalse = efalse = graph()->NewNode( simplified()->CheckSmi(p.feedback()), end, efalse, if_false); control = graph()->NewNode(common()->Merge(2), if_true, if_false); effect = graph()->NewNode(common()->EffectPhi(2), etrue, efalse, control); end = graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2), vtrue, vfalse, control); } Node* initStart = graph()->NewNode( common()->Select(MachineRepresentation::kTagged, BranchHint::kFalse), graph()->NewNode(simplified()->NumberLessThan(), start, jsgraph()->ZeroConstant()), graph()->NewNode( simplified()->NumberMax(), graph()->NewNode(simplified()->NumberAdd(), length, start), jsgraph()->ZeroConstant()), start); // The select above guarantees that initStart is non-negative, but // our typer can't figure that out yet. initStart = effect = graph()->NewNode( common()->TypeGuard(Type::UnsignedSmall()), initStart, effect, control); Node* resultLength = graph()->NewNode( simplified()->NumberMin(), graph()->NewNode(simplified()->NumberMax(), end, jsgraph()->ZeroConstant()), graph()->NewNode(simplified()->NumberSubtract(), length, initStart)); // The the select below uses {resultLength} only if {resultLength > 0}, // but our typer can't figure that out yet. Node* to = effect = graph()->NewNode( common()->TypeGuard(Type::UnsignedSmall()), graph()->NewNode(simplified()->NumberAdd(), initStart, resultLength), effect, control); Node* result_string = nullptr; // Return empty string if {from} is smaller than {to}. { Node* check = graph()->NewNode(simplified()->NumberLessThan(), jsgraph()->ZeroConstant(), resultLength); Node* branch = graph()->NewNode(common()->Branch(BranchHint::kTrue), check, control); Node* if_true = graph()->NewNode(common()->IfTrue(), branch); Node* etrue = effect; Node* vtrue = etrue = graph()->NewNode(simplified()->StringSubstring(), receiver, initStart, to, etrue, if_true); Node* if_false = graph()->NewNode(common()->IfFalse(), branch); Node* efalse = effect; Node* vfalse = jsgraph()->EmptyStringConstant(); control = graph()->NewNode(common()->Merge(2), if_true, if_false); effect = graph()->NewNode(common()->EffectPhi(2), etrue, efalse, control); result_string = graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2), vtrue, vfalse, control); } ReplaceWithValue(node, result_string, effect, control); return Replace(result_string); } Reduction JSCallReducer::ReduceJSConstructWithArrayLike(Node* node) { DCHECK_EQ(IrOpcode::kJSConstructWithArrayLike, node->opcode()); CallFrequency frequency = CallFrequencyOf(node->op()); VectorSlotPair feedback; return ReduceCallOrConstructWithArrayLikeOrSpread(node, 1, frequency, feedback); } Reduction JSCallReducer::ReduceJSConstructWithSpread(Node* node) { DCHECK_EQ(IrOpcode::kJSConstructWithSpread, node->opcode()); ConstructParameters const& p = ConstructParametersOf(node->op()); DCHECK_LE(3u, p.arity()); int arity = static_cast(p.arity() - 2); CallFrequency frequency = p.frequency(); VectorSlotPair feedback = p.feedback(); return ReduceCallOrConstructWithArrayLikeOrSpread(node, arity, frequency, feedback); } Reduction JSCallReducer::ReduceReturnReceiver(Node* node) { DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); Node* receiver = NodeProperties::GetValueInput(node, 1); ReplaceWithValue(node, receiver); return Replace(receiver); } Reduction JSCallReducer::ReduceSoftDeoptimize(Node* node, DeoptimizeReason reason) { Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); Node* frame_state = NodeProperties::FindFrameStateBefore(node); Node* deoptimize = graph()->NewNode( common()->Deoptimize(DeoptimizeKind::kSoft, reason, VectorSlotPair()), frame_state, effect, control); // TODO(bmeurer): This should be on the AdvancedReducer somehow. NodeProperties::MergeControlToEnd(graph(), common(), deoptimize); Revisit(graph()->end()); node->TrimInputCount(0); NodeProperties::ChangeOp(node, common()->Dead()); return Changed(node); } namespace { // TODO(turbofan): This was copied from old compiler, might be too restrictive. bool IsReadOnlyLengthDescriptor(Isolate* isolate, Handle jsarray_map) { DCHECK(!jsarray_map->is_dictionary_map()); Handle length_string = isolate->factory()->length_string(); DescriptorArray* descriptors = jsarray_map->instance_descriptors(); int number = descriptors->Search(*length_string, *jsarray_map); DCHECK_NE(DescriptorArray::kNotFound, number); return descriptors->GetDetails(number).IsReadOnly(); } // TODO(turbofan): This was copied from old compiler, might be too restrictive. bool CanInlineArrayResizeOperation(Isolate* isolate, Handle receiver_map) { if (!receiver_map->prototype()->IsJSArray()) return false; Handle receiver_prototype(JSArray::cast(receiver_map->prototype()), isolate); return receiver_map->instance_type() == JS_ARRAY_TYPE && IsFastElementsKind(receiver_map->elements_kind()) && !receiver_map->is_dictionary_map() && receiver_map->is_extensible() && isolate->IsAnyInitialArrayPrototype(receiver_prototype) && !IsReadOnlyLengthDescriptor(isolate, receiver_map); } } // namespace // ES6 section 22.1.3.18 Array.prototype.push ( ) Reduction JSCallReducer::ReduceArrayPrototypePush(Node* node) { DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); CallParameters const& p = CallParametersOf(node->op()); if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) { return NoChange(); } if (!isolate()->IsNoElementsProtectorIntact()) return NoChange(); int const num_values = node->op()->ValueInputCount() - 2; Node* receiver = NodeProperties::GetValueInput(node, 1); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); // Try to determine the {receiver} map(s). ZoneHandleSet receiver_maps; NodeProperties::InferReceiverMapsResult result = NodeProperties::InferReceiverMaps(isolate(), receiver, effect, &receiver_maps); if (result == NodeProperties::kNoReceiverMaps) return NoChange(); DCHECK_NE(0, receiver_maps.size()); ElementsKind kind = receiver_maps[0]->elements_kind(); for (Handle receiver_map : receiver_maps) { if (!CanInlineArrayResizeOperation(isolate(), receiver_map)) return NoChange(); if (!UnionElementsKindUptoPackedness(&kind, receiver_map->elements_kind())) return NoChange(); } // Install code dependencies on the {receiver} global array protector cell. dependencies()->DependOnProtector( PropertyCellRef(js_heap_broker(), factory()->no_elements_protector())); // If the {receiver_maps} information is not reliable, we need // to check that the {receiver} still has one of these maps. if (result == NodeProperties::kUnreliableReceiverMaps) { effect = graph()->NewNode(simplified()->CheckMaps(CheckMapsFlag::kNone, receiver_maps, p.feedback()), receiver, effect, control); } // Collect the value inputs to push. std::vector values(num_values); for (int i = 0; i < num_values; ++i) { values[i] = NodeProperties::GetValueInput(node, 2 + i); } for (auto& value : values) { if (IsSmiElementsKind(kind)) { value = effect = graph()->NewNode(simplified()->CheckSmi(p.feedback()), value, effect, control); } else if (IsDoubleElementsKind(kind)) { value = effect = graph()->NewNode(simplified()->CheckNumber(p.feedback()), value, effect, control); // Make sure we do not store signaling NaNs into double arrays. value = graph()->NewNode(simplified()->NumberSilenceNaN(), value); } } // Load the "length" property of the {receiver}. Node* length = effect = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForJSArrayLength(kind)), receiver, effect, control); Node* value = length; // Check if we have any {values} to push. if (num_values > 0) { // Compute the resulting "length" of the {receiver}. Node* new_length = value = graph()->NewNode( simplified()->NumberAdd(), length, jsgraph()->Constant(num_values)); // Load the elements backing store of the {receiver}. Node* elements = effect = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForJSObjectElements()), receiver, effect, control); Node* elements_length = effect = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForFixedArrayLength()), elements, effect, control); GrowFastElementsMode mode = IsDoubleElementsKind(kind) ? GrowFastElementsMode::kDoubleElements : GrowFastElementsMode::kSmiOrObjectElements; elements = effect = graph()->NewNode( simplified()->MaybeGrowFastElements(mode, p.feedback()), receiver, elements, graph()->NewNode(simplified()->NumberAdd(), length, jsgraph()->Constant(num_values - 1)), elements_length, effect, control); // Update the JSArray::length field. Since this is observable, // there must be no other check after this. effect = graph()->NewNode( simplified()->StoreField(AccessBuilder::ForJSArrayLength(kind)), receiver, new_length, effect, control); // Append the {values} to the {elements}. for (int i = 0; i < num_values; ++i) { Node* value = values[i]; Node* index = graph()->NewNode(simplified()->NumberAdd(), length, jsgraph()->Constant(i)); effect = graph()->NewNode( simplified()->StoreElement(AccessBuilder::ForFixedArrayElement(kind)), elements, index, value, effect, control); } } ReplaceWithValue(node, value, effect, control); return Replace(value); } // ES6 section 22.1.3.17 Array.prototype.pop ( ) Reduction JSCallReducer::ReduceArrayPrototypePop(Node* node) { DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); CallParameters const& p = CallParametersOf(node->op()); if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) { return NoChange(); } if (!isolate()->IsNoElementsProtectorIntact()) return NoChange(); Node* receiver = NodeProperties::GetValueInput(node, 1); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); ZoneHandleSet receiver_maps; NodeProperties::InferReceiverMapsResult result = NodeProperties::InferReceiverMaps(isolate(), receiver, effect, &receiver_maps); if (result == NodeProperties::kNoReceiverMaps) return NoChange(); DCHECK_NE(0, receiver_maps.size()); ElementsKind kind = receiver_maps[0]->elements_kind(); for (Handle receiver_map : receiver_maps) { if (!CanInlineArrayResizeOperation(isolate(), receiver_map)) return NoChange(); // TODO(turbofan): Extend this to also handle fast holey double elements // once we got the hole NaN mess sorted out in TurboFan/V8. if (receiver_map->elements_kind() == HOLEY_DOUBLE_ELEMENTS) return NoChange(); if (!UnionElementsKindUptoSize(&kind, receiver_map->elements_kind())) return NoChange(); } // Install code dependencies on the {receiver} global array protector cell. dependencies()->DependOnProtector( PropertyCellRef(js_heap_broker(), factory()->no_elements_protector())); // If the {receiver_maps} information is not reliable, we need // to check that the {receiver} still has one of these maps. if (result == NodeProperties::kUnreliableReceiverMaps) { effect = graph()->NewNode(simplified()->CheckMaps(CheckMapsFlag::kNone, receiver_maps, p.feedback()), receiver, effect, control); } // Load the "length" property of the {receiver}. Node* length = effect = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForJSArrayLength(kind)), receiver, effect, control); // Check if the {receiver} has any elements. Node* check = graph()->NewNode(simplified()->NumberEqual(), length, jsgraph()->ZeroConstant()); Node* branch = graph()->NewNode(common()->Branch(BranchHint::kFalse), check, control); Node* if_true = graph()->NewNode(common()->IfTrue(), branch); Node* etrue = effect; Node* vtrue = jsgraph()->UndefinedConstant(); Node* if_false = graph()->NewNode(common()->IfFalse(), branch); Node* efalse = effect; Node* vfalse; { // TODO(tebbi): We should trim the backing store if the capacity is too // big, as implemented in elements.cc:ElementsAccessorBase::SetLengthImpl. // Load the elements backing store from the {receiver}. Node* elements = efalse = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForJSObjectElements()), receiver, efalse, if_false); // Ensure that we aren't popping from a copy-on-write backing store. if (IsSmiOrObjectElementsKind(kind)) { elements = efalse = graph()->NewNode(simplified()->EnsureWritableFastElements(), receiver, elements, efalse, if_false); } // Compute the new {length}. length = graph()->NewNode(simplified()->NumberSubtract(), length, jsgraph()->OneConstant()); // Store the new {length} to the {receiver}. efalse = graph()->NewNode( simplified()->StoreField(AccessBuilder::ForJSArrayLength(kind)), receiver, length, efalse, if_false); // Load the last entry from the {elements}. vfalse = efalse = graph()->NewNode( simplified()->LoadElement(AccessBuilder::ForFixedArrayElement(kind)), elements, length, efalse, if_false); // Store a hole to the element we just removed from the {receiver}. efalse = graph()->NewNode( simplified()->StoreElement( AccessBuilder::ForFixedArrayElement(GetHoleyElementsKind(kind))), elements, length, jsgraph()->TheHoleConstant(), efalse, if_false); } control = graph()->NewNode(common()->Merge(2), if_true, if_false); effect = graph()->NewNode(common()->EffectPhi(2), etrue, efalse, control); Node* value = graph()->NewNode( common()->Phi(MachineRepresentation::kTagged, 2), vtrue, vfalse, control); // Convert the hole to undefined. Do this last, so that we can optimize // conversion operator via some smart strength reduction in many cases. if (IsHoleyElementsKind(kind)) { value = graph()->NewNode(simplified()->ConvertTaggedHoleToUndefined(), value); } ReplaceWithValue(node, value, effect, control); return Replace(value); } // ES6 section 22.1.3.22 Array.prototype.shift ( ) Reduction JSCallReducer::ReduceArrayPrototypeShift(Node* node) { DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); CallParameters const& p = CallParametersOf(node->op()); if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) { return NoChange(); } if (!isolate()->IsNoElementsProtectorIntact()) return NoChange(); Node* target = NodeProperties::GetValueInput(node, 0); Node* receiver = NodeProperties::GetValueInput(node, 1); Node* context = NodeProperties::GetContextInput(node); Node* frame_state = NodeProperties::GetFrameStateInput(node); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); ZoneHandleSet receiver_maps; NodeProperties::InferReceiverMapsResult result = NodeProperties::InferReceiverMaps(isolate(), receiver, effect, &receiver_maps); if (result == NodeProperties::kNoReceiverMaps) return NoChange(); DCHECK_NE(0, receiver_maps.size()); ElementsKind kind = receiver_maps[0]->elements_kind(); for (Handle receiver_map : receiver_maps) { if (!CanInlineArrayResizeOperation(isolate(), receiver_map)) return NoChange(); // TODO(turbofan): Extend this to also handle fast holey double elements // once we got the hole NaN mess sorted out in TurboFan/V8. if (receiver_map->elements_kind() == HOLEY_DOUBLE_ELEMENTS) return NoChange(); if (!UnionElementsKindUptoSize(&kind, receiver_map->elements_kind())) return NoChange(); } // Install code dependencies on the {receiver} global array protector cell. dependencies()->DependOnProtector( PropertyCellRef(js_heap_broker(), factory()->no_elements_protector())); // If the {receiver_maps} information is not reliable, we need // to check that the {receiver} still has one of these maps. if (result == NodeProperties::kUnreliableReceiverMaps) { effect = graph()->NewNode(simplified()->CheckMaps(CheckMapsFlag::kNone, receiver_maps, p.feedback()), receiver, effect, control); } // Load length of the {receiver}. Node* length = effect = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForJSArrayLength(kind)), receiver, effect, control); // Return undefined if {receiver} has no elements. Node* check0 = graph()->NewNode(simplified()->NumberEqual(), length, jsgraph()->ZeroConstant()); Node* branch0 = graph()->NewNode(common()->Branch(BranchHint::kFalse), check0, control); Node* if_true0 = graph()->NewNode(common()->IfTrue(), branch0); Node* etrue0 = effect; Node* vtrue0 = jsgraph()->UndefinedConstant(); Node* if_false0 = graph()->NewNode(common()->IfFalse(), branch0); Node* efalse0 = effect; Node* vfalse0; { // Check if we should take the fast-path. Node* check1 = graph()->NewNode(simplified()->NumberLessThanOrEqual(), length, jsgraph()->Constant(JSArray::kMaxCopyElements)); Node* branch1 = graph()->NewNode(common()->Branch(BranchHint::kTrue), check1, if_false0); Node* if_true1 = graph()->NewNode(common()->IfTrue(), branch1); Node* etrue1 = efalse0; Node* vtrue1; { Node* elements = etrue1 = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForJSObjectElements()), receiver, etrue1, if_true1); // Load the first element here, which we return below. vtrue1 = etrue1 = graph()->NewNode( simplified()->LoadElement(AccessBuilder::ForFixedArrayElement(kind)), elements, jsgraph()->ZeroConstant(), etrue1, if_true1); // Ensure that we aren't shifting a copy-on-write backing store. if (IsSmiOrObjectElementsKind(kind)) { elements = etrue1 = graph()->NewNode(simplified()->EnsureWritableFastElements(), receiver, elements, etrue1, if_true1); } // Shift the remaining {elements} by one towards the start. Node* loop = graph()->NewNode(common()->Loop(2), if_true1, if_true1); Node* eloop = graph()->NewNode(common()->EffectPhi(2), etrue1, etrue1, loop); Node* terminate = graph()->NewNode(common()->Terminate(), eloop, loop); NodeProperties::MergeControlToEnd(graph(), common(), terminate); Node* index = graph()->NewNode( common()->Phi(MachineRepresentation::kTagged, 2), jsgraph()->OneConstant(), jsgraph()->Constant(JSArray::kMaxCopyElements - 1), loop); { Node* check2 = graph()->NewNode(simplified()->NumberLessThan(), index, length); Node* branch2 = graph()->NewNode(common()->Branch(), check2, loop); if_true1 = graph()->NewNode(common()->IfFalse(), branch2); etrue1 = eloop; Node* control = graph()->NewNode(common()->IfTrue(), branch2); Node* effect = etrue1; ElementAccess const access = AccessBuilder::ForFixedArrayElement(kind); Node* value = effect = graph()->NewNode(simplified()->LoadElement(access), elements, index, effect, control); effect = graph()->NewNode(simplified()->StoreElement(access), elements, graph()->NewNode(simplified()->NumberSubtract(), index, jsgraph()->OneConstant()), value, effect, control); loop->ReplaceInput(1, control); eloop->ReplaceInput(1, effect); index->ReplaceInput(1, graph()->NewNode(simplified()->NumberAdd(), index, jsgraph()->OneConstant())); } // Compute the new {length}. length = graph()->NewNode(simplified()->NumberSubtract(), length, jsgraph()->OneConstant()); // Store the new {length} to the {receiver}. etrue1 = graph()->NewNode( simplified()->StoreField(AccessBuilder::ForJSArrayLength(kind)), receiver, length, etrue1, if_true1); // Store a hole to the element we just removed from the {receiver}. etrue1 = graph()->NewNode( simplified()->StoreElement( AccessBuilder::ForFixedArrayElement(GetHoleyElementsKind(kind))), elements, length, jsgraph()->TheHoleConstant(), etrue1, if_true1); } Node* if_false1 = graph()->NewNode(common()->IfFalse(), branch1); Node* efalse1 = efalse0; Node* vfalse1; { // Call the generic C++ implementation. const int builtin_index = Builtins::kArrayShift; auto call_descriptor = Linkage::GetCEntryStubCallDescriptor( graph()->zone(), 1, BuiltinArguments::kNumExtraArgsWithReceiver, Builtins::name(builtin_index), node->op()->properties(), CallDescriptor::kNeedsFrameState); Node* stub_code = jsgraph()->CEntryStubConstant(1, kDontSaveFPRegs, kArgvOnStack, true); Address builtin_entry = Builtins::CppEntryOf(builtin_index); Node* entry = jsgraph()->ExternalConstant(ExternalReference::Create(builtin_entry)); Node* argc = jsgraph()->Constant(BuiltinArguments::kNumExtraArgsWithReceiver); if_false1 = efalse1 = vfalse1 = graph()->NewNode(common()->Call(call_descriptor), stub_code, receiver, jsgraph()->PaddingConstant(), argc, target, jsgraph()->UndefinedConstant(), entry, argc, context, frame_state, efalse1, if_false1); } if_false0 = graph()->NewNode(common()->Merge(2), if_true1, if_false1); efalse0 = graph()->NewNode(common()->EffectPhi(2), etrue1, efalse1, if_false0); vfalse0 = graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2), vtrue1, vfalse1, if_false0); } control = graph()->NewNode(common()->Merge(2), if_true0, if_false0); effect = graph()->NewNode(common()->EffectPhi(2), etrue0, efalse0, control); Node* value = graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2), vtrue0, vfalse0, control); // Convert the hole to undefined. Do this last, so that we can optimize // conversion operator via some smart strength reduction in many cases. if (IsHoleyElementsKind(kind)) { value = graph()->NewNode(simplified()->ConvertTaggedHoleToUndefined(), value); } ReplaceWithValue(node, value, effect, control); return Replace(value); } // ES6 section 22.1.3.23 Array.prototype.slice ( ) Reduction JSCallReducer::ReduceArrayPrototypeSlice(Node* node) { if (!FLAG_turbo_inline_array_builtins) return NoChange(); DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); CallParameters const& p = CallParametersOf(node->op()); if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) { return NoChange(); } int arity = static_cast(p.arity() - 2); // Here we only optimize for cloning, that is when slice is called // without arguments, or with a single argument that is the constant 0. if (arity >= 2) return NoChange(); if (arity == 1) { NumberMatcher m(NodeProperties::GetValueInput(node, 2)); if (!m.HasValue()) return NoChange(); if (m.Value() != 0) return NoChange(); } Node* receiver = NodeProperties::GetValueInput(node, 1); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); // Try to determine the {receiver} map. ZoneHandleSet receiver_maps; NodeProperties::InferReceiverMapsResult result = NodeProperties::InferReceiverMaps(isolate(), receiver, effect, &receiver_maps); if (result == NodeProperties::kNoReceiverMaps) return NoChange(); // Ensure that any changes to the Array species constructor cause deopt. if (!isolate()->IsArraySpeciesLookupChainIntact()) return NoChange(); dependencies()->DependOnProtector( PropertyCellRef(js_heap_broker(), factory()->array_species_protector())); bool can_be_holey = false; // Check that the maps are of JSArray (and more) for (Handle receiver_map : receiver_maps) { if (!CanInlineArrayIteratingBuiltin(isolate(), receiver_map)) return NoChange(); if (IsHoleyElementsKind(receiver_map->elements_kind())) can_be_holey = true; } // Install code dependency on the array protector for holey arrays. if (can_be_holey) { dependencies()->DependOnProtector( PropertyCellRef(js_heap_broker(), factory()->no_elements_protector())); } // If we have unreliable maps, we need a map check. // This is actually redundant due to how JSNativeContextSpecialization // reduces the load of slice, but we do it here nevertheless for consistency // and robustness. if (result == NodeProperties::kUnreliableReceiverMaps) { effect = graph()->NewNode(simplified()->CheckMaps(CheckMapsFlag::kNone, receiver_maps, p.feedback()), receiver, effect, control); } Node* context = NodeProperties::GetContextInput(node); Callable callable = Builtins::CallableFor(isolate(), Builtins::kCloneFastJSArray); auto call_descriptor = Linkage::GetStubCallDescriptor( graph()->zone(), callable.descriptor(), callable.descriptor().GetStackParameterCount(), CallDescriptor::kNoFlags, Operator::kNoThrow | Operator::kNoDeopt); // Calls to Builtins::kCloneFastJSArray produce COW arrays // if the original array is COW Node* clone = effect = graph()->NewNode( common()->Call(call_descriptor), jsgraph()->HeapConstant(callable.code()), receiver, context, effect, control); ReplaceWithValue(node, clone, effect, control); return Replace(clone); } // ES6 section 22.1.2.2 Array.isArray ( arg ) Reduction JSCallReducer::ReduceArrayIsArray(Node* node) { // We certainly know that undefined is not an array. if (node->op()->ValueInputCount() < 3) { Node* value = jsgraph()->FalseConstant(); ReplaceWithValue(node, value); return Replace(value); } Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); Node* context = NodeProperties::GetContextInput(node); Node* frame_state = NodeProperties::GetFrameStateInput(node); Node* object = NodeProperties::GetValueInput(node, 2); node->ReplaceInput(0, object); node->ReplaceInput(1, context); node->ReplaceInput(2, frame_state); node->ReplaceInput(3, effect); node->ReplaceInput(4, control); node->TrimInputCount(5); NodeProperties::ChangeOp(node, javascript()->ObjectIsArray()); return Changed(node); } Reduction JSCallReducer::ReduceArrayIterator(Node* node, IterationKind kind) { DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); Node* receiver = NodeProperties::GetValueInput(node, 1); Node* context = NodeProperties::GetContextInput(node); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); // Check if we know that {receiver} is a valid JSReceiver. ZoneHandleSet receiver_maps; NodeProperties::InferReceiverMapsResult result = NodeProperties::InferReceiverMaps(isolate(), receiver, effect, &receiver_maps); if (result == NodeProperties::kNoReceiverMaps) return NoChange(); DCHECK_NE(0, receiver_maps.size()); for (Handle receiver_map : receiver_maps) { if (!receiver_map->IsJSReceiverMap()) return NoChange(); } // Morph the {node} into a JSCreateArrayIterator with the given {kind}. RelaxControls(node); node->ReplaceInput(0, receiver); node->ReplaceInput(1, context); node->ReplaceInput(2, effect); node->ReplaceInput(3, control); node->TrimInputCount(4); NodeProperties::ChangeOp(node, javascript()->CreateArrayIterator(kind)); return Changed(node); } namespace { bool InferIteratedObjectMaps(Isolate* isolate, Node* iterator, ZoneHandleSet* iterated_object_maps) { DCHECK_EQ(IrOpcode::kJSCreateArrayIterator, iterator->opcode()); Node* iterated_object = NodeProperties::GetValueInput(iterator, 0); Node* effect = NodeProperties::GetEffectInput(iterator); NodeProperties::InferReceiverMapsResult result = NodeProperties::InferReceiverMaps(isolate, iterated_object, effect, iterated_object_maps); return result != NodeProperties::kNoReceiverMaps; } } // namespace // ES #sec-%arrayiteratorprototype%.next Reduction JSCallReducer::ReduceArrayIteratorPrototypeNext(Node* node) { DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); CallParameters const& p = CallParametersOf(node->op()); Node* iterator = NodeProperties::GetValueInput(node, 1); Node* context = NodeProperties::GetContextInput(node); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) { return NoChange(); } // Check if the {iterator} is a JSCreateArrayIterator. if (iterator->opcode() != IrOpcode::kJSCreateArrayIterator) return NoChange(); IterationKind const iteration_kind = CreateArrayIteratorParametersOf(iterator->op()).kind(); // Try to infer the [[IteratedObject]] maps from the {iterator}. ZoneHandleSet iterated_object_maps; if (!InferIteratedObjectMaps(isolate(), iterator, &iterated_object_maps)) { return NoChange(); } DCHECK_NE(0, iterated_object_maps.size()); // Check that various {iterated_object_maps} have compatible elements kinds. ElementsKind elements_kind = iterated_object_maps[0]->elements_kind(); if (IsFixedTypedArrayElementsKind(elements_kind)) { // TurboFan doesn't support loading from BigInt typed arrays yet. if (elements_kind == BIGUINT64_ELEMENTS || elements_kind == BIGINT64_ELEMENTS) { return NoChange(); } for (Handle iterated_object_map : iterated_object_maps) { if (iterated_object_map->elements_kind() != elements_kind) { return NoChange(); } } } else { for (Handle iterated_object_map : iterated_object_maps) { if (!CanInlineArrayIteratingBuiltin(isolate(), iterated_object_map)) { return NoChange(); } if (!UnionElementsKindUptoSize(&elements_kind, iterated_object_map->elements_kind())) { return NoChange(); } } } // Install code dependency on the array protector for holey arrays. if (IsHoleyElementsKind(elements_kind)) { dependencies()->DependOnProtector( PropertyCellRef(js_heap_broker(), factory()->no_elements_protector())); } // Load the (current) {iterated_object} from the {iterator}. Node* iterated_object = effect = graph()->NewNode(simplified()->LoadField( AccessBuilder::ForJSArrayIteratorIteratedObject()), iterator, effect, control); // Ensure that the {iterated_object} map didn't change. effect = graph()->NewNode( simplified()->CheckMaps(CheckMapsFlag::kNone, iterated_object_maps, p.feedback()), iterated_object, effect, control); if (IsFixedTypedArrayElementsKind(elements_kind)) { // See if we can skip the neutering check. if (isolate()->IsArrayBufferNeuteringIntact()) { // Add a code dependency so we are deoptimized in case an ArrayBuffer // gets neutered. dependencies()->DependOnProtector(PropertyCellRef( js_heap_broker(), factory()->array_buffer_neutering_protector())); } else { // Bail out if the {iterated_object}s JSArrayBuffer was neutered. Node* buffer = effect = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForJSArrayBufferViewBuffer()), iterated_object, effect, control); Node* buffer_bit_field = effect = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForJSArrayBufferBitField()), buffer, effect, control); Node* check = graph()->NewNode( simplified()->NumberEqual(), graph()->NewNode( simplified()->NumberBitwiseAnd(), buffer_bit_field, jsgraph()->Constant(JSArrayBuffer::WasNeuteredBit::kMask)), jsgraph()->ZeroConstant()); effect = graph()->NewNode( simplified()->CheckIf(DeoptimizeReason::kArrayBufferWasNeutered, p.feedback()), check, effect, control); } } // Load the [[NextIndex]] from the {iterator} and leverage the fact // that we definitely know that it's in Unsigned32 range since the // {iterated_object} is either a JSArray or a JSTypedArray. For the // latter case we even know that it's a Smi in UnsignedSmall range. FieldAccess index_access = AccessBuilder::ForJSArrayIteratorNextIndex(); if (IsFixedTypedArrayElementsKind(elements_kind)) { index_access.type = TypeCache::Get().kJSTypedArrayLengthType; index_access.machine_type = MachineType::TaggedSigned(); index_access.write_barrier_kind = kNoWriteBarrier; } else { index_access.type = TypeCache::Get().kJSArrayLengthType; } Node* index = effect = graph()->NewNode(simplified()->LoadField(index_access), iterator, effect, control); // Load the elements of the {iterated_object}. While it feels // counter-intuitive to place the elements pointer load before // the condition below, as it might not be needed (if the {index} // is out of bounds for the {iterated_object}), it's better this // way as it allows the LoadElimination to eliminate redundant // reloads of the elements pointer. Node* elements = effect = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForJSObjectElements()), iterated_object, effect, control); // Load the length of the {iterated_object}. Due to the map checks we // already know something about the length here, which we can leverage // to generate Word32 operations below without additional checking. FieldAccess length_access = IsFixedTypedArrayElementsKind(elements_kind) ? AccessBuilder::ForJSTypedArrayLength() : AccessBuilder::ForJSArrayLength(elements_kind); Node* length = effect = graph()->NewNode( simplified()->LoadField(length_access), iterated_object, effect, control); // Check whether {index} is within the valid range for the {iterated_object}. Node* check = graph()->NewNode(simplified()->NumberLessThan(), index, length); Node* branch = graph()->NewNode(common()->Branch(BranchHint::kTrue), check, control); Node* done_true; Node* value_true; Node* etrue = effect; Node* if_true = graph()->NewNode(common()->IfTrue(), branch); { // We know that the {index} is range of the {length} now. index = etrue = graph()->NewNode( common()->TypeGuard( Type::Range(0.0, length_access.type.Max() - 1.0, graph()->zone())), index, etrue, if_true); done_true = jsgraph()->FalseConstant(); if (iteration_kind == IterationKind::kKeys) { // Just return the {index}. value_true = index; } else { DCHECK(iteration_kind == IterationKind::kEntries || iteration_kind == IterationKind::kValues); if (IsFixedTypedArrayElementsKind(elements_kind)) { Node* base_ptr = etrue = graph()->NewNode( simplified()->LoadField( AccessBuilder::ForFixedTypedArrayBaseBasePointer()), elements, etrue, if_true); Node* external_ptr = etrue = graph()->NewNode( simplified()->LoadField( AccessBuilder::ForFixedTypedArrayBaseExternalPointer()), elements, etrue, if_true); ExternalArrayType array_type = kExternalInt8Array; switch (elements_kind) { #define TYPED_ARRAY_CASE(Type, type, TYPE, ctype) \ case TYPE##_ELEMENTS: \ array_type = kExternal##Type##Array; \ break; TYPED_ARRAYS(TYPED_ARRAY_CASE) default: UNREACHABLE(); #undef TYPED_ARRAY_CASE } Node* buffer = etrue = graph()->NewNode(simplified()->LoadField( AccessBuilder::ForJSArrayBufferViewBuffer()), iterated_object, etrue, if_true); value_true = etrue = graph()->NewNode(simplified()->LoadTypedElement(array_type), buffer, base_ptr, external_ptr, index, etrue, if_true); } else { value_true = etrue = graph()->NewNode( simplified()->LoadElement( AccessBuilder::ForFixedArrayElement(elements_kind)), elements, index, etrue, if_true); // Convert hole to undefined if needed. if (elements_kind == HOLEY_ELEMENTS || elements_kind == HOLEY_SMI_ELEMENTS) { value_true = graph()->NewNode( simplified()->ConvertTaggedHoleToUndefined(), value_true); } else if (elements_kind == HOLEY_DOUBLE_ELEMENTS) { // TODO(6587): avoid deopt if not all uses of value are truncated. CheckFloat64HoleMode mode = CheckFloat64HoleMode::kAllowReturnHole; value_true = etrue = graph()->NewNode( simplified()->CheckFloat64Hole(mode, p.feedback()), value_true, etrue, if_true); } } if (iteration_kind == IterationKind::kEntries) { // Allocate elements for key/value pair value_true = etrue = graph()->NewNode(javascript()->CreateKeyValueArray(), index, value_true, context, etrue); } else { DCHECK_EQ(IterationKind::kValues, iteration_kind); } } // Increment the [[NextIndex]] field in the {iterator}. The TypeGuards // above guarantee that the {next_index} is in the UnsignedSmall range. Node* next_index = graph()->NewNode(simplified()->NumberAdd(), index, jsgraph()->OneConstant()); etrue = graph()->NewNode(simplified()->StoreField(index_access), iterator, next_index, etrue, if_true); } Node* done_false; Node* value_false; Node* efalse = effect; Node* if_false = graph()->NewNode(common()->IfFalse(), branch); { // iterator.[[NextIndex]] >= array.length, stop iterating. done_false = jsgraph()->TrueConstant(); value_false = jsgraph()->UndefinedConstant(); if (!IsFixedTypedArrayElementsKind(elements_kind)) { // Mark the {iterator} as exhausted by setting the [[NextIndex]] to a // value that will never pass the length check again (aka the maximum // value possible for the specific iterated object). Note that this is // different from what the specification says, which is changing the // [[IteratedObject]] field to undefined, but that makes it difficult // to eliminate the map checks and "length" accesses in for..of loops. // // This is not necessary for JSTypedArray's, since the length of those // cannot change later and so if we were ever out of bounds for them // we will stay out-of-bounds forever. Node* end_index = jsgraph()->Constant(index_access.type.Max()); efalse = graph()->NewNode(simplified()->StoreField(index_access), iterator, end_index, efalse, if_false); } } control = graph()->NewNode(common()->Merge(2), if_true, if_false); effect = graph()->NewNode(common()->EffectPhi(2), etrue, efalse, control); Node* value = graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2), value_true, value_false, control); Node* done = graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2), done_true, done_false, control); // Create IteratorResult object. value = effect = graph()->NewNode(javascript()->CreateIterResultObject(), value, done, context, effect); ReplaceWithValue(node, value, effect, control); return Replace(value); } // ES6 section 21.1.3.2 String.prototype.charCodeAt ( pos ) // ES6 section 21.1.3.3 String.prototype.codePointAt ( pos ) Reduction JSCallReducer::ReduceStringPrototypeStringAt( const Operator* string_access_operator, Node* node) { DCHECK(string_access_operator->opcode() == IrOpcode::kStringCharCodeAt || string_access_operator->opcode() == IrOpcode::kStringCodePointAt); DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); CallParameters const& p = CallParametersOf(node->op()); if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) { return NoChange(); } Node* receiver = NodeProperties::GetValueInput(node, 1); Node* index = node->op()->ValueInputCount() >= 3 ? NodeProperties::GetValueInput(node, 2) : jsgraph()->ZeroConstant(); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); // Ensure that the {receiver} is actually a String. receiver = effect = graph()->NewNode(simplified()->CheckString(p.feedback()), receiver, effect, control); // Determine the {receiver} length. Node* receiver_length = graph()->NewNode(simplified()->StringLength(), receiver); // Check that the {index} is within range. index = effect = graph()->NewNode(simplified()->CheckBounds(p.feedback()), index, receiver_length, effect, control); // Return the character from the {receiver} as single character string. Node* masked_index = graph()->NewNode(simplified()->PoisonIndex(), index); Node* value = effect = graph()->NewNode(string_access_operator, receiver, masked_index, effect, control); ReplaceWithValue(node, value, effect, control); return Replace(value); } // ES section 21.1.3.1 String.prototype.charAt ( pos ) Reduction JSCallReducer::ReduceStringPrototypeCharAt(Node* node) { DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); CallParameters const& p = CallParametersOf(node->op()); if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) { return NoChange(); } Node* receiver = NodeProperties::GetValueInput(node, 1); Node* index = node->op()->ValueInputCount() >= 3 ? NodeProperties::GetValueInput(node, 2) : jsgraph()->ZeroConstant(); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); // Ensure that the {receiver} is actually a String. receiver = effect = graph()->NewNode(simplified()->CheckString(p.feedback()), receiver, effect, control); // Determine the {receiver} length. Node* receiver_length = graph()->NewNode(simplified()->StringLength(), receiver); // Check that the {index} is within range. index = effect = graph()->NewNode(simplified()->CheckBounds(p.feedback()), index, receiver_length, effect, control); // Return the character from the {receiver} as single character string. Node* masked_index = graph()->NewNode(simplified()->PoisonIndex(), index); Node* value = effect = graph()->NewNode(simplified()->StringCharCodeAt(), receiver, masked_index, effect, control); value = graph()->NewNode(simplified()->StringFromSingleCharCode(), value); ReplaceWithValue(node, value, effect, control); return Replace(value); } #ifdef V8_INTL_SUPPORT Reduction JSCallReducer::ReduceStringPrototypeToLowerCaseIntl(Node* node) { DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); CallParameters const& p = CallParametersOf(node->op()); if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) { return NoChange(); } Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); Node* receiver = effect = graph()->NewNode(simplified()->CheckString(p.feedback()), NodeProperties::GetValueInput(node, 1), effect, control); NodeProperties::ReplaceEffectInput(node, effect); RelaxEffectsAndControls(node); node->ReplaceInput(0, receiver); node->TrimInputCount(1); NodeProperties::ChangeOp(node, simplified()->StringToLowerCaseIntl()); NodeProperties::SetType(node, Type::String()); return Changed(node); } Reduction JSCallReducer::ReduceStringPrototypeToUpperCaseIntl(Node* node) { DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); CallParameters const& p = CallParametersOf(node->op()); if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) { return NoChange(); } Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); Node* receiver = effect = graph()->NewNode(simplified()->CheckString(p.feedback()), NodeProperties::GetValueInput(node, 1), effect, control); NodeProperties::ReplaceEffectInput(node, effect); RelaxEffectsAndControls(node); node->ReplaceInput(0, receiver); node->TrimInputCount(1); NodeProperties::ChangeOp(node, simplified()->StringToUpperCaseIntl()); NodeProperties::SetType(node, Type::String()); return Changed(node); } #endif // V8_INTL_SUPPORT // ES #sec-string.fromcharcode Reduction JSCallReducer::ReduceStringFromCharCode(Node* node) { DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); CallParameters const& p = CallParametersOf(node->op()); if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) { return NoChange(); } if (node->op()->ValueInputCount() == 3) { Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); Node* input = NodeProperties::GetValueInput(node, 2); input = effect = graph()->NewNode( simplified()->SpeculativeToNumber(NumberOperationHint::kNumberOrOddball, p.feedback()), input, effect, control); Node* value = graph()->NewNode(simplified()->StringFromSingleCharCode(), input); ReplaceWithValue(node, value, effect); return Replace(value); } return NoChange(); } // ES #sec-string.fromcodepoint Reduction JSCallReducer::ReduceStringFromCodePoint(Node* node) { DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); CallParameters const& p = CallParametersOf(node->op()); if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) { return NoChange(); } if (node->op()->ValueInputCount() == 3) { Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); Node* input = NodeProperties::GetValueInput(node, 2); input = effect = graph()->NewNode(simplified()->CheckBounds(p.feedback()), input, jsgraph()->Constant(0x10FFFF + 1), effect, control); Node* value = graph()->NewNode( simplified()->StringFromSingleCodePoint(UnicodeEncoding::UTF32), input); ReplaceWithValue(node, value, effect); return Replace(value); } return NoChange(); } Reduction JSCallReducer::ReduceStringPrototypeIterator(Node* node) { CallParameters const& p = CallParametersOf(node->op()); if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) { return NoChange(); } Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); Node* receiver = effect = graph()->NewNode(simplified()->CheckString(p.feedback()), NodeProperties::GetValueInput(node, 1), effect, control); Node* iterator = effect = graph()->NewNode(javascript()->CreateStringIterator(), receiver, jsgraph()->NoContextConstant(), effect); ReplaceWithValue(node, iterator, effect, control); return Replace(iterator); } Reduction JSCallReducer::ReduceStringIteratorPrototypeNext(Node* node) { Node* receiver = NodeProperties::GetValueInput(node, 1); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); Node* context = NodeProperties::GetContextInput(node); if (NodeProperties::HasInstanceTypeWitness(isolate(), receiver, effect, JS_STRING_ITERATOR_TYPE)) { Node* string = effect = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForJSStringIteratorString()), receiver, effect, control); Node* index = effect = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForJSStringIteratorIndex()), receiver, effect, control); Node* length = graph()->NewNode(simplified()->StringLength(), string); // branch0: if (index < length) Node* check0 = graph()->NewNode(simplified()->NumberLessThan(), index, length); Node* branch0 = graph()->NewNode(common()->Branch(BranchHint::kTrue), check0, control); Node* etrue0 = effect; Node* if_true0 = graph()->NewNode(common()->IfTrue(), branch0); Node* done_true; Node* vtrue0; { done_true = jsgraph()->FalseConstant(); Node* codepoint = etrue0 = graph()->NewNode( simplified()->StringCodePointAt(UnicodeEncoding::UTF16), string, index, etrue0, if_true0); vtrue0 = graph()->NewNode( simplified()->StringFromSingleCodePoint(UnicodeEncoding::UTF16), codepoint); // Update iterator.[[NextIndex]] Node* char_length = graph()->NewNode(simplified()->StringLength(), vtrue0); index = graph()->NewNode(simplified()->NumberAdd(), index, char_length); etrue0 = graph()->NewNode( simplified()->StoreField(AccessBuilder::ForJSStringIteratorIndex()), receiver, index, etrue0, if_true0); } Node* if_false0 = graph()->NewNode(common()->IfFalse(), branch0); Node* done_false; Node* vfalse0; { vfalse0 = jsgraph()->UndefinedConstant(); done_false = jsgraph()->TrueConstant(); } control = graph()->NewNode(common()->Merge(2), if_true0, if_false0); effect = graph()->NewNode(common()->EffectPhi(2), etrue0, effect, control); Node* value = graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2), vtrue0, vfalse0, control); Node* done = graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2), done_true, done_false, control); value = effect = graph()->NewNode(javascript()->CreateIterResultObject(), value, done, context, effect); ReplaceWithValue(node, value, effect, control); return Replace(value); } return NoChange(); } // ES #sec-string.prototype.concat Reduction JSCallReducer::ReduceStringPrototypeConcat( Node* node, Handle shared) { if (node->op()->ValueInputCount() < 2 || node->op()->ValueInputCount() > 3) { return NoChange(); } CallParameters const& p = CallParametersOf(node->op()); if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) { return NoChange(); } Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); Node* receiver = effect = graph()->NewNode(simplified()->CheckString(p.feedback()), NodeProperties::GetValueInput(node, 1), effect, control); if (node->op()->ValueInputCount() < 3) { ReplaceWithValue(node, receiver, effect, control); return Replace(receiver); } Node* argument = effect = graph()->NewNode(simplified()->CheckString(p.feedback()), NodeProperties::GetValueInput(node, 2), effect, control); Node* receiver_length = graph()->NewNode(simplified()->StringLength(), receiver); Node* argument_length = graph()->NewNode(simplified()->StringLength(), argument); Node* length = graph()->NewNode(simplified()->NumberAdd(), receiver_length, argument_length); length = effect = graph()->NewNode( simplified()->CheckBounds(p.feedback()), length, jsgraph()->Constant(String::kMaxLength + 1), effect, control); Node* value = graph()->NewNode(simplified()->StringConcat(), length, receiver, argument); ReplaceWithValue(node, value, effect, control); return Replace(value); } Reduction JSCallReducer::ReduceAsyncFunctionPromiseCreate(Node* node) { DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); Node* context = NodeProperties::GetContextInput(node); Node* effect = NodeProperties::GetEffectInput(node); if (!isolate()->IsPromiseHookProtectorIntact()) return NoChange(); // Install a code dependency on the promise hook protector cell. dependencies()->DependOnProtector( PropertyCellRef(js_heap_broker(), factory()->promise_hook_protector())); // Morph this {node} into a JSCreatePromise node. RelaxControls(node); node->ReplaceInput(0, context); node->ReplaceInput(1, effect); node->TrimInputCount(2); NodeProperties::ChangeOp(node, javascript()->CreatePromise()); return Changed(node); } Reduction JSCallReducer::ReduceAsyncFunctionPromiseRelease(Node* node) { DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); if (!isolate()->IsPromiseHookProtectorIntact()) return NoChange(); dependencies()->DependOnProtector( PropertyCellRef(js_heap_broker(), factory()->promise_hook_protector())); // The AsyncFunctionPromiseRelease builtin is a no-op as long as neither // the debugger is active nor any promise hook has been installed (ever). Node* value = jsgraph()->UndefinedConstant(); ReplaceWithValue(node, value); return Replace(value); } Node* JSCallReducer::CreateArtificialFrameState( Node* node, Node* outer_frame_state, int parameter_count, BailoutId bailout_id, FrameStateType frame_state_type, Handle shared, Node* context) { const FrameStateFunctionInfo* state_info = common()->CreateFrameStateFunctionInfo(frame_state_type, parameter_count + 1, 0, shared); const Operator* op = common()->FrameState( bailout_id, OutputFrameStateCombine::Ignore(), state_info); const Operator* op0 = common()->StateValues(0, SparseInputMask::Dense()); Node* node0 = graph()->NewNode(op0); std::vector params; params.reserve(parameter_count + 1); for (int parameter = 0; parameter < parameter_count + 1; ++parameter) { params.push_back(node->InputAt(1 + parameter)); } const Operator* op_param = common()->StateValues( static_cast(params.size()), SparseInputMask::Dense()); Node* params_node = graph()->NewNode( op_param, static_cast(params.size()), ¶ms.front()); if (!context) { context = jsgraph()->UndefinedConstant(); } return graph()->NewNode(op, params_node, node0, node0, context, node->InputAt(0), outer_frame_state); } Reduction JSCallReducer::ReducePromiseConstructor(Node* node) { DCHECK_EQ(IrOpcode::kJSConstruct, node->opcode()); ConstructParameters const& p = ConstructParametersOf(node->op()); int arity = static_cast(p.arity() - 2); // We only inline when we have the executor. if (arity < 1) return NoChange(); Node* target = NodeProperties::GetValueInput(node, 0); Node* executor = NodeProperties::GetValueInput(node, 1); Node* new_target = NodeProperties::GetValueInput(node, arity + 1); Node* context = NodeProperties::GetContextInput(node); Node* outer_frame_state = NodeProperties::GetFrameStateInput(node); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); if (!FLAG_experimental_inline_promise_constructor) return NoChange(); if (!isolate()->IsPromiseHookProtectorIntact()) return NoChange(); // Only handle builtins Promises, not subclasses. if (target != new_target) return NoChange(); dependencies()->DependOnProtector( PropertyCellRef(js_heap_broker(), factory()->promise_hook_protector())); Handle promise_shared( handle(native_context()->promise_function()->shared(), isolate())); // Insert a construct stub frame into the chain of frame states. This will // reconstruct the proper frame when deoptimizing within the constructor. // For the frame state, we only provide the executor parameter, even if more // arugments were passed. This is not observable from JS. DCHECK_EQ(1, promise_shared->internal_formal_parameter_count()); Node* constructor_frame_state = CreateArtificialFrameState( node, outer_frame_state, 1, BailoutId::ConstructStubInvoke(), FrameStateType::kConstructStub, promise_shared, context); // The deopt continuation of this frame state is never called; the frame state // is only necessary to obtain the right stack trace. const std::vector checkpoint_parameters({ jsgraph()->UndefinedConstant(), /* receiver */ jsgraph()->UndefinedConstant(), /* promise */ jsgraph()->UndefinedConstant(), /* reject function */ jsgraph()->TheHoleConstant() /* exception */ }); int checkpoint_parameters_size = static_cast(checkpoint_parameters.size()); Node* frame_state = CreateJavaScriptBuiltinContinuationFrameState( jsgraph(), promise_shared, Builtins::kPromiseConstructorLazyDeoptContinuation, target, context, checkpoint_parameters.data(), checkpoint_parameters_size, constructor_frame_state, ContinuationFrameStateMode::LAZY); // Check if executor is callable Node* check_fail = nullptr; Node* check_throw = nullptr; WireInCallbackIsCallableCheck(executor, context, frame_state, effect, &control, &check_fail, &check_throw); // Create the resulting JSPromise. Node* promise = effect = graph()->NewNode(javascript()->CreatePromise(), context, effect); // 8. CreatePromiseResolvingFunctions // Allocate a promise context for the closures below. Node* promise_context = effect = graph()->NewNode(javascript()->CreateFunctionContext( handle(native_context()->scope_info(), isolate()), PromiseBuiltinsAssembler::kPromiseContextLength - Context::MIN_CONTEXT_SLOTS, FUNCTION_SCOPE), context, effect, control); effect = graph()->NewNode(simplified()->StoreField(AccessBuilder::ForContextSlot( PromiseBuiltinsAssembler::kPromiseSlot)), promise_context, promise, effect, control); effect = graph()->NewNode( simplified()->StoreField(AccessBuilder::ForContextSlot( PromiseBuiltinsAssembler::kAlreadyResolvedSlot)), promise_context, jsgraph()->FalseConstant(), effect, control); effect = graph()->NewNode( simplified()->StoreField(AccessBuilder::ForContextSlot( PromiseBuiltinsAssembler::kDebugEventSlot)), promise_context, jsgraph()->TrueConstant(), effect, control); // Allocate the closure for the resolve case. Handle resolve_shared( native_context()->promise_capability_default_resolve_shared_fun(), isolate()); Node* resolve = effect = graph()->NewNode(javascript()->CreateClosure( resolve_shared, factory()->many_closures_cell(), handle(resolve_shared->GetCode(), isolate())), promise_context, effect, control); // Allocate the closure for the reject case. Handle reject_shared( native_context()->promise_capability_default_reject_shared_fun(), isolate()); Node* reject = effect = graph()->NewNode(javascript()->CreateClosure( reject_shared, factory()->many_closures_cell(), handle(reject_shared->GetCode(), isolate())), promise_context, effect, control); const std::vector checkpoint_parameters_continuation( {jsgraph()->UndefinedConstant() /* receiver */, promise, reject}); int checkpoint_parameters_continuation_size = static_cast(checkpoint_parameters_continuation.size()); // This continuation just returns the created promise and takes care of // exceptions thrown by the executor. frame_state = CreateJavaScriptBuiltinContinuationFrameState( jsgraph(), promise_shared, Builtins::kPromiseConstructorLazyDeoptContinuation, target, context, checkpoint_parameters_continuation.data(), checkpoint_parameters_continuation_size, constructor_frame_state, ContinuationFrameStateMode::LAZY_WITH_CATCH); // 9. Call executor with both resolving functions effect = control = graph()->NewNode( javascript()->Call(4, p.frequency(), VectorSlotPair(), ConvertReceiverMode::kNullOrUndefined, SpeculationMode::kDisallowSpeculation), executor, jsgraph()->UndefinedConstant(), resolve, reject, context, frame_state, effect, control); Node* exception_effect = effect; Node* exception_control = control; { Node* reason = exception_effect = exception_control = graph()->NewNode( common()->IfException(), exception_control, exception_effect); // 10a. Call reject if the call to executor threw. exception_effect = exception_control = graph()->NewNode( javascript()->Call(3, p.frequency(), VectorSlotPair(), ConvertReceiverMode::kNullOrUndefined, SpeculationMode::kDisallowSpeculation), reject, jsgraph()->UndefinedConstant(), reason, context, frame_state, exception_effect, exception_control); // Rewire potential exception edges. Node* on_exception = nullptr; if (NodeProperties::IsExceptionalCall(node, &on_exception)) { RewirePostCallbackExceptionEdges(check_throw, on_exception, exception_effect, &check_fail, &exception_control); } } Node* success_effect = effect; Node* success_control = control; { success_control = graph()->NewNode(common()->IfSuccess(), success_control); } control = graph()->NewNode(common()->Merge(2), success_control, exception_control); effect = graph()->NewNode(common()->EffectPhi(2), success_effect, exception_effect, control); // Wire up the branch for the case when IsCallable fails for the executor. // Since {check_throw} is an unconditional throw, it's impossible to // return a successful completion. Therefore, we simply connect the successful // completion to the graph end. Node* throw_node = graph()->NewNode(common()->Throw(), check_throw, check_fail); NodeProperties::MergeControlToEnd(graph(), common(), throw_node); ReplaceWithValue(node, promise, effect, control); return Replace(promise); } // V8 Extras: v8.createPromise(parent) Reduction JSCallReducer::ReducePromiseInternalConstructor(Node* node) { DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); Node* context = NodeProperties::GetContextInput(node); Node* effect = NodeProperties::GetEffectInput(node); // Check that promises aren't being observed through (debug) hooks. if (!isolate()->IsPromiseHookProtectorIntact()) return NoChange(); dependencies()->DependOnProtector( PropertyCellRef(js_heap_broker(), factory()->promise_hook_protector())); // Create a new pending promise. Node* value = effect = graph()->NewNode(javascript()->CreatePromise(), context, effect); ReplaceWithValue(node, value, effect); return Replace(value); } // V8 Extras: v8.rejectPromise(promise, reason) Reduction JSCallReducer::ReducePromiseInternalReject(Node* node) { DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); Node* promise = node->op()->ValueInputCount() >= 2 ? NodeProperties::GetValueInput(node, 2) : jsgraph()->UndefinedConstant(); Node* reason = node->op()->ValueInputCount() >= 3 ? NodeProperties::GetValueInput(node, 3) : jsgraph()->UndefinedConstant(); Node* debug_event = jsgraph()->TrueConstant(); Node* frame_state = NodeProperties::GetFrameStateInput(node); Node* context = NodeProperties::GetContextInput(node); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); // Reject the {promise} using the given {reason}, and trigger debug logic. Node* value = effect = graph()->NewNode(javascript()->RejectPromise(), promise, reason, debug_event, context, frame_state, effect, control); ReplaceWithValue(node, value, effect, control); return Replace(value); } // V8 Extras: v8.resolvePromise(promise, resolution) Reduction JSCallReducer::ReducePromiseInternalResolve(Node* node) { DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); Node* promise = node->op()->ValueInputCount() >= 2 ? NodeProperties::GetValueInput(node, 2) : jsgraph()->UndefinedConstant(); Node* resolution = node->op()->ValueInputCount() >= 3 ? NodeProperties::GetValueInput(node, 3) : jsgraph()->UndefinedConstant(); Node* frame_state = NodeProperties::GetFrameStateInput(node); Node* context = NodeProperties::GetContextInput(node); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); // Resolve the {promise} using the given {resolution}. Node* value = effect = graph()->NewNode(javascript()->ResolvePromise(), promise, resolution, context, frame_state, effect, control); ReplaceWithValue(node, value, effect, control); return Replace(value); } // ES section #sec-promise.prototype.catch Reduction JSCallReducer::ReducePromisePrototypeCatch(Node* node) { DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); CallParameters const& p = CallParametersOf(node->op()); if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) { return NoChange(); } int arity = static_cast(p.arity() - 2); Node* receiver = NodeProperties::GetValueInput(node, 1); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); // Check that the Promise.then protector is intact. This protector guards // that all JSPromise instances whose [[Prototype]] is the initial // %PromisePrototype% yield the initial %PromisePrototype%.then method // when looking up "then". if (!isolate()->IsPromiseThenLookupChainIntact()) return NoChange(); // Check if we know something about {receiver} already. ZoneHandleSet receiver_maps; NodeProperties::InferReceiverMapsResult result = NodeProperties::InferReceiverMaps(isolate(), receiver, effect, &receiver_maps); if (result == NodeProperties::kNoReceiverMaps) return NoChange(); DCHECK_NE(0, receiver_maps.size()); // Check whether all {receiver_maps} are JSPromise maps and // have the initial Promise.prototype as their [[Prototype]]. for (Handle receiver_map : receiver_maps) { if (!receiver_map->IsJSPromiseMap()) return NoChange(); if (receiver_map->prototype() != native_context()->promise_prototype()) { return NoChange(); } } dependencies()->DependOnProtector( PropertyCellRef(js_heap_broker(), factory()->promise_then_protector())); // If the {receiver_maps} aren't reliable, we need to repeat the // map check here, guarded by the CALL_IC. if (result == NodeProperties::kUnreliableReceiverMaps) { effect = graph()->NewNode(simplified()->CheckMaps(CheckMapsFlag::kNone, receiver_maps, p.feedback()), receiver, effect, control); } // Massage the {node} to call "then" instead by first removing all inputs // following the onRejected parameter, and then filling up the parameters // to two inputs from the left with undefined. Node* target = jsgraph()->Constant(handle(native_context()->promise_then(), isolate())); NodeProperties::ReplaceValueInput(node, target, 0); NodeProperties::ReplaceEffectInput(node, effect); for (; arity > 1; --arity) node->RemoveInput(3); for (; arity < 2; ++arity) { node->InsertInput(graph()->zone(), 2, jsgraph()->UndefinedConstant()); } NodeProperties::ChangeOp( node, javascript()->Call(2 + arity, p.frequency(), p.feedback(), ConvertReceiverMode::kNotNullOrUndefined, p.speculation_mode())); Reduction const reduction = ReducePromisePrototypeThen(node); return reduction.Changed() ? reduction : Changed(node); } // ES section #sec-promise.prototype.finally Reduction JSCallReducer::ReducePromisePrototypeFinally(Node* node) { DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); CallParameters const& p = CallParametersOf(node->op()); int arity = static_cast(p.arity() - 2); Node* receiver = NodeProperties::GetValueInput(node, 1); Node* on_finally = arity >= 1 ? NodeProperties::GetValueInput(node, 2) : jsgraph()->UndefinedConstant(); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) { return NoChange(); } // Check that promises aren't being observed through (debug) hooks. if (!isolate()->IsPromiseHookProtectorIntact()) return NoChange(); // Check that the Promise#then protector is intact. This protector guards // that all JSPromise instances whose [[Prototype]] is the initial // %PromisePrototype% yield the initial %PromisePrototype%.then method // when looking up "then". if (!isolate()->IsPromiseThenLookupChainIntact()) return NoChange(); // Also check that the @@species protector is intact, which guards the // lookup of "constructor" on JSPromise instances, whoch [[Prototype]] is // the initial %PromisePrototype%, and the Symbol.species lookup on the // %PromisePrototype%. if (!isolate()->IsPromiseSpeciesLookupChainIntact()) return NoChange(); // Check if we know something about {receiver} already. ZoneHandleSet receiver_maps; NodeProperties::InferReceiverMapsResult result = NodeProperties::InferReceiverMaps(isolate(), receiver, effect, &receiver_maps); if (result == NodeProperties::kNoReceiverMaps) return NoChange(); DCHECK_NE(0, receiver_maps.size()); // Check whether all {receiver_maps} are JSPromise maps and // have the initial Promise.prototype as their [[Prototype]]. for (Handle receiver_map : receiver_maps) { if (!receiver_map->IsJSPromiseMap()) return NoChange(); if (receiver_map->prototype() != native_context()->promise_prototype()) { return NoChange(); } } dependencies()->DependOnProtector( PropertyCellRef(js_heap_broker(), factory()->promise_hook_protector())); dependencies()->DependOnProtector( PropertyCellRef(js_heap_broker(), factory()->promise_then_protector())); dependencies()->DependOnProtector(PropertyCellRef( js_heap_broker(), factory()->promise_species_protector())); // If the {receiver_maps} aren't reliable, we need to repeat the // map check here, guarded by the CALL_IC. if (result == NodeProperties::kUnreliableReceiverMaps) { effect = graph()->NewNode(simplified()->CheckMaps(CheckMapsFlag::kNone, receiver_maps, p.feedback()), receiver, effect, control); } // Check if {on_finally} is callable, and if so wrap it into appropriate // closures that perform the finalization. Node* check = graph()->NewNode(simplified()->ObjectIsCallable(), on_finally); Node* branch = graph()->NewNode(common()->Branch(BranchHint::kTrue), check, control); Node* if_true = graph()->NewNode(common()->IfTrue(), branch); Node* etrue = effect; Node* catch_true; Node* then_true; { Node* context = jsgraph()->HeapConstant(native_context()); Node* constructor = jsgraph()->HeapConstant( handle(native_context()->promise_function(), isolate())); // Allocate shared context for the closures below. context = etrue = graph()->NewNode( javascript()->CreateFunctionContext( handle(native_context()->scope_info(), isolate()), PromiseBuiltinsAssembler::kPromiseFinallyContextLength - Context::MIN_CONTEXT_SLOTS, FUNCTION_SCOPE), context, etrue, if_true); etrue = graph()->NewNode(simplified()->StoreField(AccessBuilder::ForContextSlot( PromiseBuiltinsAssembler::kOnFinallySlot)), context, on_finally, etrue, if_true); etrue = graph()->NewNode(simplified()->StoreField(AccessBuilder::ForContextSlot( PromiseBuiltinsAssembler::kConstructorSlot)), context, constructor, etrue, if_true); // Allocate the closure for the reject case. Handle catch_finally( native_context()->promise_catch_finally_shared_fun(), isolate()); catch_true = etrue = graph()->NewNode(javascript()->CreateClosure( catch_finally, factory()->many_closures_cell(), handle(catch_finally->GetCode(), isolate())), context, etrue, if_true); // Allocate the closure for the fulfill case. Handle then_finally( native_context()->promise_then_finally_shared_fun(), isolate()); then_true = etrue = graph()->NewNode(javascript()->CreateClosure( then_finally, factory()->many_closures_cell(), handle(then_finally->GetCode(), isolate())), context, etrue, if_true); } Node* if_false = graph()->NewNode(common()->IfFalse(), branch); Node* efalse = effect; Node* catch_false = on_finally; Node* then_false = on_finally; control = graph()->NewNode(common()->Merge(2), if_true, if_false); effect = graph()->NewNode(common()->EffectPhi(2), etrue, efalse, control); Node* catch_finally = graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2), catch_true, catch_false, control); Node* then_finally = graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2), then_true, then_false, control); // At this point we definitely know that {receiver} has one of the // {receiver_maps}, so insert a MapGuard as a hint for the lowering // of the call to "then" below. effect = graph()->NewNode(simplified()->MapGuard(receiver_maps), receiver, effect, control); // Massage the {node} to call "then" instead by first removing all inputs // following the onFinally parameter, and then replacing the only parameter // input with the {on_finally} value. Node* target = jsgraph()->Constant(handle(native_context()->promise_then(), isolate())); NodeProperties::ReplaceValueInput(node, target, 0); NodeProperties::ReplaceEffectInput(node, effect); NodeProperties::ReplaceControlInput(node, control); for (; arity > 2; --arity) node->RemoveInput(2); for (; arity < 2; ++arity) node->InsertInput(graph()->zone(), 2, then_finally); node->ReplaceInput(2, then_finally); node->ReplaceInput(3, catch_finally); NodeProperties::ChangeOp( node, javascript()->Call(2 + arity, p.frequency(), p.feedback(), ConvertReceiverMode::kNotNullOrUndefined, p.speculation_mode())); Reduction const reduction = ReducePromisePrototypeThen(node); return reduction.Changed() ? reduction : Changed(node); } Reduction JSCallReducer::ReducePromisePrototypeThen(Node* node) { DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); CallParameters const& p = CallParametersOf(node->op()); if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) { return NoChange(); } Node* receiver = NodeProperties::GetValueInput(node, 1); Node* on_fulfilled = node->op()->ValueInputCount() > 2 ? NodeProperties::GetValueInput(node, 2) : jsgraph()->UndefinedConstant(); Node* on_rejected = node->op()->ValueInputCount() > 3 ? NodeProperties::GetValueInput(node, 3) : jsgraph()->UndefinedConstant(); Node* context = NodeProperties::GetContextInput(node); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); Node* frame_state = NodeProperties::GetFrameStateInput(node); // Check that promises aren't being observed through (debug) hooks. if (!isolate()->IsPromiseHookProtectorIntact()) return NoChange(); // Check if the @@species protector is intact. The @@species protector // guards the "constructor" lookup on all JSPromise instances and the // initial Promise.prototype, as well as the Symbol.species lookup on // the Promise constructor. if (!isolate()->IsPromiseSpeciesLookupChainIntact()) return NoChange(); // Check if we know something about {receiver} already. ZoneHandleSet receiver_maps; NodeProperties::InferReceiverMapsResult infer_receiver_maps_result = NodeProperties::InferReceiverMaps(isolate(), receiver, effect, &receiver_maps); if (infer_receiver_maps_result == NodeProperties::kNoReceiverMaps) { return NoChange(); } DCHECK_NE(0, receiver_maps.size()); // Check whether all {receiver_maps} are JSPromise maps and // have the initial Promise.prototype as their [[Prototype]]. for (Handle receiver_map : receiver_maps) { if (!receiver_map->IsJSPromiseMap()) return NoChange(); if (receiver_map->prototype() != native_context()->promise_prototype()) { return NoChange(); } } dependencies()->DependOnProtector( PropertyCellRef(js_heap_broker(), factory()->promise_hook_protector())); dependencies()->DependOnProtector(PropertyCellRef( js_heap_broker(), factory()->promise_species_protector())); // If the {receiver_maps} aren't reliable, we need to repeat the // map check here, guarded by the CALL_IC. if (infer_receiver_maps_result == NodeProperties::kUnreliableReceiverMaps) { effect = graph()->NewNode(simplified()->CheckMaps(CheckMapsFlag::kNone, receiver_maps, p.feedback()), receiver, effect, control); } // Check that {on_fulfilled} is callable. on_fulfilled = graph()->NewNode( common()->Select(MachineRepresentation::kTagged, BranchHint::kTrue), graph()->NewNode(simplified()->ObjectIsCallable(), on_fulfilled), on_fulfilled, jsgraph()->UndefinedConstant()); // Check that {on_rejected} is callable. on_rejected = graph()->NewNode( common()->Select(MachineRepresentation::kTagged, BranchHint::kTrue), graph()->NewNode(simplified()->ObjectIsCallable(), on_rejected), on_rejected, jsgraph()->UndefinedConstant()); // Create the resulting JSPromise. Node* result = effect = graph()->NewNode(javascript()->CreatePromise(), context, effect); // Chain {result} onto {receiver}. result = effect = graph()->NewNode( javascript()->PerformPromiseThen(), receiver, on_fulfilled, on_rejected, result, context, frame_state, effect, control); ReplaceWithValue(node, result, effect, control); return Replace(result); } // ES section #sec-promise.resolve Reduction JSCallReducer::ReducePromiseResolveTrampoline(Node* node) { DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); Node* receiver = NodeProperties::GetValueInput(node, 1); Node* value = node->op()->ValueInputCount() > 2 ? NodeProperties::GetValueInput(node, 2) : jsgraph()->UndefinedConstant(); Node* context = NodeProperties::GetContextInput(node); Node* frame_state = NodeProperties::GetFrameStateInput(node); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); // Check if we know something about {receiver} already. ZoneHandleSet receiver_maps; NodeProperties::InferReceiverMapsResult infer_receiver_maps_result = NodeProperties::InferReceiverMaps(isolate(), receiver, effect, &receiver_maps); if (infer_receiver_maps_result == NodeProperties::kNoReceiverMaps) { return NoChange(); } DCHECK_NE(0, receiver_maps.size()); // Only reduce when all {receiver_maps} are JSReceiver maps. for (Handle receiver_map : receiver_maps) { if (!receiver_map->IsJSReceiverMap()) return NoChange(); } // Morph the {node} into a JSPromiseResolve operation. node->ReplaceInput(0, receiver); node->ReplaceInput(1, value); node->ReplaceInput(2, context); node->ReplaceInput(3, frame_state); node->ReplaceInput(4, effect); node->ReplaceInput(5, control); node->TrimInputCount(6); NodeProperties::ChangeOp(node, javascript()->PromiseResolve()); return Changed(node); } // ES #sec-typedarray-constructors Reduction JSCallReducer::ReduceTypedArrayConstructor( Node* node, Handle shared) { DCHECK_EQ(IrOpcode::kJSConstruct, node->opcode()); ConstructParameters const& p = ConstructParametersOf(node->op()); int arity = static_cast(p.arity() - 2); Node* target = NodeProperties::GetValueInput(node, 0); Node* arg1 = (arity >= 1) ? NodeProperties::GetValueInput(node, 1) : jsgraph()->UndefinedConstant(); Node* arg2 = (arity >= 2) ? NodeProperties::GetValueInput(node, 2) : jsgraph()->UndefinedConstant(); Node* arg3 = (arity >= 3) ? NodeProperties::GetValueInput(node, 3) : jsgraph()->UndefinedConstant(); Node* new_target = NodeProperties::GetValueInput(node, arity + 1); Node* context = NodeProperties::GetContextInput(node); Node* frame_state = NodeProperties::GetFrameStateInput(node); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); // Insert a construct stub frame into the chain of frame states. This will // reconstruct the proper frame when deoptimizing within the constructor. frame_state = CreateArtificialFrameState( node, frame_state, arity, BailoutId::ConstructStubInvoke(), FrameStateType::kConstructStub, shared, context); // This continuation just returns the newly created JSTypedArray. We // pass the_hole as the receiver, just like the builtin construct stub // does in this case. Node* const parameters[] = {jsgraph()->TheHoleConstant()}; int const num_parameters = static_cast(arraysize(parameters)); frame_state = CreateJavaScriptBuiltinContinuationFrameState( jsgraph(), shared, Builtins::kGenericConstructorLazyDeoptContinuation, target, context, parameters, num_parameters, frame_state, ContinuationFrameStateMode::LAZY); Node* result = graph()->NewNode(javascript()->CreateTypedArray(), target, new_target, arg1, arg2, arg3, context, frame_state, effect, control); return Replace(result); } // ES #sec-get-%typedarray%.prototype-@@tostringtag Reduction JSCallReducer::ReduceTypedArrayPrototypeToStringTag(Node* node) { Node* receiver = NodeProperties::GetValueInput(node, 1); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); NodeVector values(graph()->zone()); NodeVector effects(graph()->zone()); NodeVector controls(graph()->zone()); Node* check = graph()->NewNode(simplified()->ObjectIsSmi(), receiver); control = graph()->NewNode(common()->Branch(BranchHint::kFalse), check, control); values.push_back(jsgraph()->UndefinedConstant()); effects.push_back(effect); controls.push_back(graph()->NewNode(common()->IfTrue(), control)); control = graph()->NewNode(common()->IfFalse(), control); Node* receiver_map = effect = graph()->NewNode(simplified()->LoadField(AccessBuilder::ForMap()), receiver, effect, control); Node* receiver_bit_field2 = effect = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForMapBitField2()), receiver_map, effect, control); Node* receiver_elements_kind = graph()->NewNode( simplified()->NumberShiftRightLogical(), graph()->NewNode(simplified()->NumberBitwiseAnd(), receiver_bit_field2, jsgraph()->Constant(Map::ElementsKindBits::kMask)), jsgraph()->Constant(Map::ElementsKindBits::kShift)); // Offset the elements kind by FIRST_FIXED_TYPED_ARRAY_ELEMENTS_KIND, // so that the branch cascade below is turned into a simple table // switch by the ControlFlowOptimizer later. receiver_elements_kind = graph()->NewNode( simplified()->NumberSubtract(), receiver_elements_kind, jsgraph()->Constant(FIRST_FIXED_TYPED_ARRAY_ELEMENTS_KIND)); #define TYPED_ARRAY_CASE(Type, type, TYPE, ctype) \ do { \ Node* check = graph()->NewNode( \ simplified()->NumberEqual(), receiver_elements_kind, \ jsgraph()->Constant(TYPE##_ELEMENTS - \ FIRST_FIXED_TYPED_ARRAY_ELEMENTS_KIND)); \ control = graph()->NewNode(common()->Branch(), check, control); \ values.push_back(jsgraph()->HeapConstant( \ factory()->InternalizeUtf8String(#Type "Array"))); \ effects.push_back(effect); \ controls.push_back(graph()->NewNode(common()->IfTrue(), control)); \ control = graph()->NewNode(common()->IfFalse(), control); \ } while (false); TYPED_ARRAYS(TYPED_ARRAY_CASE) #undef TYPED_ARRAY_CASE values.push_back(jsgraph()->UndefinedConstant()); effects.push_back(effect); controls.push_back(control); int const count = static_cast(controls.size()); control = graph()->NewNode(common()->Merge(count), count, &controls.front()); effects.push_back(control); effect = graph()->NewNode(common()->EffectPhi(count), count + 1, &effects.front()); values.push_back(control); Node* value = graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, count), count + 1, &values.front()); ReplaceWithValue(node, value, effect, control); return Replace(value); } // ES #sec-number.isfinite Reduction JSCallReducer::ReduceNumberIsFinite(Node* node) { if (node->op()->ValueInputCount() < 3) { Node* value = jsgraph()->FalseConstant(); ReplaceWithValue(node, value); return Replace(value); } Node* input = NodeProperties::GetValueInput(node, 2); Node* value = graph()->NewNode(simplified()->ObjectIsFiniteNumber(), input); ReplaceWithValue(node, value); return Replace(value); } // ES #sec-number.isfinite Reduction JSCallReducer::ReduceNumberIsInteger(Node* node) { if (node->op()->ValueInputCount() < 3) { Node* value = jsgraph()->FalseConstant(); ReplaceWithValue(node, value); return Replace(value); } Node* input = NodeProperties::GetValueInput(node, 2); Node* value = graph()->NewNode(simplified()->ObjectIsInteger(), input); ReplaceWithValue(node, value); return Replace(value); } // ES #sec-number.issafeinteger Reduction JSCallReducer::ReduceNumberIsSafeInteger(Node* node) { if (node->op()->ValueInputCount() < 3) { Node* value = jsgraph()->FalseConstant(); ReplaceWithValue(node, value); return Replace(value); } Node* input = NodeProperties::GetValueInput(node, 2); Node* value = graph()->NewNode(simplified()->ObjectIsSafeInteger(), input); ReplaceWithValue(node, value); return Replace(value); } // ES #sec-number.isnan Reduction JSCallReducer::ReduceNumberIsNaN(Node* node) { if (node->op()->ValueInputCount() < 3) { Node* value = jsgraph()->FalseConstant(); ReplaceWithValue(node, value); return Replace(value); } Node* input = NodeProperties::GetValueInput(node, 2); Node* value = graph()->NewNode(simplified()->ObjectIsNaN(), input); ReplaceWithValue(node, value); return Replace(value); } Reduction JSCallReducer::ReduceMapPrototypeGet(Node* node) { // We only optimize if we have target, receiver and key parameters. if (node->op()->ValueInputCount() != 3) return NoChange(); Node* receiver = NodeProperties::GetValueInput(node, 1); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); Node* key = NodeProperties::GetValueInput(node, 2); if (!NodeProperties::HasInstanceTypeWitness(isolate(), receiver, effect, JS_MAP_TYPE)) return NoChange(); Node* table = effect = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForJSCollectionTable()), receiver, effect, control); Node* entry = effect = graph()->NewNode( simplified()->FindOrderedHashMapEntry(), table, key, effect, control); Node* check = graph()->NewNode(simplified()->NumberEqual(), entry, jsgraph()->MinusOneConstant()); Node* branch = graph()->NewNode(common()->Branch(), check, control); // Key not found. Node* if_true = graph()->NewNode(common()->IfTrue(), branch); Node* etrue = effect; Node* vtrue = jsgraph()->UndefinedConstant(); // Key found. Node* if_false = graph()->NewNode(common()->IfFalse(), branch); Node* efalse = effect; Node* vfalse = efalse = graph()->NewNode( simplified()->LoadElement(AccessBuilder::ForOrderedHashMapEntryValue()), table, entry, efalse, if_false); control = graph()->NewNode(common()->Merge(2), if_true, if_false); Node* value = graph()->NewNode( common()->Phi(MachineRepresentation::kTagged, 2), vtrue, vfalse, control); effect = graph()->NewNode(common()->EffectPhi(2), etrue, efalse, control); ReplaceWithValue(node, value, effect, control); return Replace(value); } Reduction JSCallReducer::ReduceMapPrototypeHas(Node* node) { // We only optimize if we have target, receiver and key parameters. if (node->op()->ValueInputCount() != 3) return NoChange(); Node* receiver = NodeProperties::GetValueInput(node, 1); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); Node* key = NodeProperties::GetValueInput(node, 2); if (!NodeProperties::HasInstanceTypeWitness(isolate(), receiver, effect, JS_MAP_TYPE)) return NoChange(); Node* table = effect = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForJSCollectionTable()), receiver, effect, control); Node* index = effect = graph()->NewNode( simplified()->FindOrderedHashMapEntry(), table, key, effect, control); Node* value = graph()->NewNode(simplified()->NumberEqual(), index, jsgraph()->MinusOneConstant()); value = graph()->NewNode(simplified()->BooleanNot(), value); ReplaceWithValue(node, value, effect, control); return Replace(value); } namespace { InstanceType InstanceTypeForCollectionKind(CollectionKind kind) { switch (kind) { case CollectionKind::kMap: return JS_MAP_TYPE; case CollectionKind::kSet: return JS_SET_TYPE; } UNREACHABLE(); } } // namespace Reduction JSCallReducer::ReduceCollectionIteration( Node* node, CollectionKind collection_kind, IterationKind iteration_kind) { DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); Node* receiver = NodeProperties::GetValueInput(node, 1); Node* context = NodeProperties::GetContextInput(node); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); if (NodeProperties::HasInstanceTypeWitness( isolate(), receiver, effect, InstanceTypeForCollectionKind(collection_kind))) { Node* js_create_iterator = effect = graph()->NewNode( javascript()->CreateCollectionIterator(collection_kind, iteration_kind), receiver, context, effect, control); ReplaceWithValue(node, js_create_iterator, effect); return Replace(js_create_iterator); } return NoChange(); } Reduction JSCallReducer::ReduceCollectionPrototypeSize( Node* node, CollectionKind collection_kind) { DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); Node* receiver = NodeProperties::GetValueInput(node, 1); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); if (NodeProperties::HasInstanceTypeWitness( isolate(), receiver, effect, InstanceTypeForCollectionKind(collection_kind))) { Node* table = effect = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForJSCollectionTable()), receiver, effect, control); Node* value = effect = graph()->NewNode( simplified()->LoadField( AccessBuilder::ForOrderedHashTableBaseNumberOfElements()), table, effect, control); ReplaceWithValue(node, value, effect, control); return Replace(value); } return NoChange(); } Reduction JSCallReducer::ReduceCollectionIteratorPrototypeNext( Node* node, int entry_size, Handle empty_collection, InstanceType collection_iterator_instance_type_first, InstanceType collection_iterator_instance_type_last) { DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); Node* receiver = NodeProperties::GetValueInput(node, 1); Node* context = NodeProperties::GetContextInput(node); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); // A word of warning to begin with: This whole method might look a bit // strange at times, but that's mostly because it was carefully handcrafted // to allow for full escape analysis and scalar replacement of both the // collection iterator object and the iterator results, including the // key-value arrays in case of Set/Map entry iteration. // // TODO(turbofan): Currently the escape analysis (and the store-load // forwarding) is unable to eliminate the allocations for the key-value // arrays in case of Set/Map entry iteration, and we should investigate // how to update the escape analysis / arrange the graph in a way that // this becomes possible. // Infer the {receiver} instance type. InstanceType receiver_instance_type; ZoneHandleSet receiver_maps; NodeProperties::InferReceiverMapsResult result = NodeProperties::InferReceiverMaps(isolate(), receiver, effect, &receiver_maps); if (result == NodeProperties::kNoReceiverMaps) return NoChange(); DCHECK_NE(0, receiver_maps.size()); receiver_instance_type = receiver_maps[0]->instance_type(); for (size_t i = 1; i < receiver_maps.size(); ++i) { if (receiver_maps[i]->instance_type() != receiver_instance_type) { return NoChange(); } } if (receiver_instance_type < collection_iterator_instance_type_first || receiver_instance_type > collection_iterator_instance_type_last) { return NoChange(); } // Transition the JSCollectionIterator {receiver} if necessary // (i.e. there were certain mutations while we're iterating). { Node* done_loop; Node* done_eloop; Node* loop = control = graph()->NewNode(common()->Loop(2), control, control); Node* eloop = effect = graph()->NewNode(common()->EffectPhi(2), effect, effect, loop); Node* terminate = graph()->NewNode(common()->Terminate(), eloop, loop); NodeProperties::MergeControlToEnd(graph(), common(), terminate); // Check if reached the final table of the {receiver}. Node* table = effect = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForJSCollectionIteratorTable()), receiver, effect, control); Node* next_table = effect = graph()->NewNode(simplified()->LoadField( AccessBuilder::ForOrderedHashTableBaseNextTable()), table, effect, control); Node* check = graph()->NewNode(simplified()->ObjectIsSmi(), next_table); control = graph()->NewNode(common()->Branch(BranchHint::kTrue), check, control); // Abort the {loop} when we reach the final table. done_loop = graph()->NewNode(common()->IfTrue(), control); done_eloop = effect; // Migrate to the {next_table} otherwise. control = graph()->NewNode(common()->IfFalse(), control); // Self-heal the {receiver}s index. Node* index = effect = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForJSCollectionIteratorIndex()), receiver, effect, control); Callable const callable = Builtins::CallableFor(isolate(), Builtins::kOrderedHashTableHealIndex); auto call_descriptor = Linkage::GetStubCallDescriptor( graph()->zone(), callable.descriptor(), callable.descriptor().GetStackParameterCount(), CallDescriptor::kNoFlags, Operator::kEliminatable); index = effect = graph()->NewNode(common()->Call(call_descriptor), jsgraph()->HeapConstant(callable.code()), table, index, jsgraph()->NoContextConstant(), effect); index = effect = graph()->NewNode( common()->TypeGuard(TypeCache::Get().kFixedArrayLengthType), index, effect, control); // Update the {index} and {table} on the {receiver}. effect = graph()->NewNode( simplified()->StoreField(AccessBuilder::ForJSCollectionIteratorIndex()), receiver, index, effect, control); effect = graph()->NewNode( simplified()->StoreField(AccessBuilder::ForJSCollectionIteratorTable()), receiver, next_table, effect, control); // Tie the knot. loop->ReplaceInput(1, control); eloop->ReplaceInput(1, effect); control = done_loop; effect = done_eloop; } // Get current index and table from the JSCollectionIterator {receiver}. Node* index = effect = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForJSCollectionIteratorIndex()), receiver, effect, control); Node* table = effect = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForJSCollectionIteratorTable()), receiver, effect, control); // Create the {JSIteratorResult} first to ensure that we always have // a dominating Allocate node for the allocation folding phase. Node* iterator_result = effect = graph()->NewNode( javascript()->CreateIterResultObject(), jsgraph()->UndefinedConstant(), jsgraph()->TrueConstant(), context, effect); // Look for the next non-holey key, starting from {index} in the {table}. Node* controls[2]; Node* effects[3]; { // Compute the currently used capacity. Node* number_of_buckets = effect = graph()->NewNode( simplified()->LoadField( AccessBuilder::ForOrderedHashTableBaseNumberOfBuckets()), table, effect, control); Node* number_of_elements = effect = graph()->NewNode( simplified()->LoadField( AccessBuilder::ForOrderedHashTableBaseNumberOfElements()), table, effect, control); Node* number_of_deleted_elements = effect = graph()->NewNode( simplified()->LoadField( AccessBuilder::ForOrderedHashTableBaseNumberOfDeletedElements()), table, effect, control); Node* used_capacity = graph()->NewNode(simplified()->NumberAdd(), number_of_elements, number_of_deleted_elements); // Skip holes and update the {index}. Node* loop = graph()->NewNode(common()->Loop(2), control, control); Node* eloop = graph()->NewNode(common()->EffectPhi(2), effect, effect, loop); Node* terminate = graph()->NewNode(common()->Terminate(), eloop, loop); NodeProperties::MergeControlToEnd(graph(), common(), terminate); Node* iloop = graph()->NewNode( common()->Phi(MachineRepresentation::kTagged, 2), index, index, loop); Node* index = effect = graph()->NewNode( common()->TypeGuard(TypeCache::Get().kFixedArrayLengthType), iloop, eloop, control); { Node* check0 = graph()->NewNode(simplified()->NumberLessThan(), index, used_capacity); Node* branch0 = graph()->NewNode(common()->Branch(BranchHint::kTrue), check0, loop); Node* if_false0 = graph()->NewNode(common()->IfFalse(), branch0); Node* efalse0 = effect; { // Mark the {receiver} as exhausted. efalse0 = graph()->NewNode( simplified()->StoreField( AccessBuilder::ForJSCollectionIteratorTable()), receiver, jsgraph()->HeapConstant(empty_collection), efalse0, if_false0); controls[0] = if_false0; effects[0] = efalse0; } Node* if_true0 = graph()->NewNode(common()->IfTrue(), branch0); Node* etrue0 = effect; { // Load the key of the entry. Node* entry_start_position = graph()->NewNode( simplified()->NumberAdd(), graph()->NewNode( simplified()->NumberAdd(), graph()->NewNode(simplified()->NumberMultiply(), index, jsgraph()->Constant(entry_size)), number_of_buckets), jsgraph()->Constant(OrderedHashTableBase::kHashTableStartIndex)); Node* entry_key = etrue0 = graph()->NewNode( simplified()->LoadElement(AccessBuilder::ForFixedArrayElement()), table, entry_start_position, etrue0, if_true0); // Advance the index. index = graph()->NewNode(simplified()->NumberAdd(), index, jsgraph()->OneConstant()); Node* check1 = graph()->NewNode(simplified()->ReferenceEqual(), entry_key, jsgraph()->TheHoleConstant()); Node* branch1 = graph()->NewNode(common()->Branch(BranchHint::kFalse), check1, if_true0); { // Abort loop with resulting value. Node* control = graph()->NewNode(common()->IfFalse(), branch1); Node* effect = etrue0; Node* value = effect = graph()->NewNode(common()->TypeGuard(Type::NonInternal()), entry_key, effect, control); Node* done = jsgraph()->FalseConstant(); // Advance the index on the {receiver}. effect = graph()->NewNode( simplified()->StoreField( AccessBuilder::ForJSCollectionIteratorIndex()), receiver, index, effect, control); // The actual {value} depends on the {receiver} iteration type. switch (receiver_instance_type) { case JS_MAP_KEY_ITERATOR_TYPE: case JS_SET_VALUE_ITERATOR_TYPE: break; case JS_SET_KEY_VALUE_ITERATOR_TYPE: value = effect = graph()->NewNode(javascript()->CreateKeyValueArray(), value, value, context, effect); break; case JS_MAP_VALUE_ITERATOR_TYPE: value = effect = graph()->NewNode( simplified()->LoadElement( AccessBuilder::ForFixedArrayElement()), table, graph()->NewNode( simplified()->NumberAdd(), entry_start_position, jsgraph()->Constant(OrderedHashMap::kValueOffset)), effect, control); break; case JS_MAP_KEY_VALUE_ITERATOR_TYPE: value = effect = graph()->NewNode( simplified()->LoadElement( AccessBuilder::ForFixedArrayElement()), table, graph()->NewNode( simplified()->NumberAdd(), entry_start_position, jsgraph()->Constant(OrderedHashMap::kValueOffset)), effect, control); value = effect = graph()->NewNode(javascript()->CreateKeyValueArray(), entry_key, value, context, effect); break; default: UNREACHABLE(); break; } // Store final {value} and {done} into the {iterator_result}. effect = graph()->NewNode(simplified()->StoreField( AccessBuilder::ForJSIteratorResultValue()), iterator_result, value, effect, control); effect = graph()->NewNode(simplified()->StoreField( AccessBuilder::ForJSIteratorResultDone()), iterator_result, done, effect, control); controls[1] = control; effects[1] = effect; } // Continue with next loop index. loop->ReplaceInput(1, graph()->NewNode(common()->IfTrue(), branch1)); eloop->ReplaceInput(1, etrue0); iloop->ReplaceInput(1, index); } } control = effects[2] = graph()->NewNode(common()->Merge(2), 2, controls); effect = graph()->NewNode(common()->EffectPhi(2), 3, effects); } // Yield the final {iterator_result}. ReplaceWithValue(node, iterator_result, effect, control); return Replace(iterator_result); } Reduction JSCallReducer::ReduceArrayBufferIsView(Node* node) { Node* value = node->op()->ValueInputCount() >= 3 ? NodeProperties::GetValueInput(node, 2) : jsgraph()->UndefinedConstant(); RelaxEffectsAndControls(node); node->ReplaceInput(0, value); node->TrimInputCount(1); NodeProperties::ChangeOp(node, simplified()->ObjectIsArrayBufferView()); return Changed(node); } Reduction JSCallReducer::ReduceArrayBufferViewAccessor( Node* node, InstanceType instance_type, FieldAccess const& access) { Node* receiver = NodeProperties::GetValueInput(node, 1); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); if (NodeProperties::HasInstanceTypeWitness(isolate(), receiver, effect, instance_type)) { // Load the {receiver}s field. Node* value = effect = graph()->NewNode(simplified()->LoadField(access), receiver, effect, control); // See if we can skip the neutering check. if (isolate()->IsArrayBufferNeuteringIntact()) { // Add a code dependency so we are deoptimized in case an ArrayBuffer // gets neutered. dependencies()->DependOnProtector(PropertyCellRef( js_heap_broker(), factory()->array_buffer_neutering_protector())); } else { // Check whether {receiver}s JSArrayBuffer was neutered. Node* buffer = effect = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForJSArrayBufferViewBuffer()), receiver, effect, control); Node* buffer_bit_field = effect = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForJSArrayBufferBitField()), buffer, effect, control); Node* check = graph()->NewNode( simplified()->NumberEqual(), graph()->NewNode( simplified()->NumberBitwiseAnd(), buffer_bit_field, jsgraph()->Constant(JSArrayBuffer::WasNeuteredBit::kMask)), jsgraph()->ZeroConstant()); // TODO(turbofan): Ideally we would bail out here if the {receiver}s // JSArrayBuffer was neutered, but there's no way to guard against // deoptimization loops right now, since the JSCall {node} is usually // created from a LOAD_IC inlining, and so there's no CALL_IC slot // from which we could use the speculation bit. value = graph()->NewNode( common()->Select(MachineRepresentation::kTagged, BranchHint::kTrue), check, value, jsgraph()->ZeroConstant()); } ReplaceWithValue(node, value, effect, control); return Replace(value); } return NoChange(); } namespace { uint32_t ExternalArrayElementSize(const ExternalArrayType element_type) { switch (element_type) { #define TYPED_ARRAY_CASE(Type, type, TYPE, ctype) \ case kExternal##Type##Array: \ DCHECK_LE(sizeof(ctype), 8); \ return sizeof(ctype); TYPED_ARRAYS(TYPED_ARRAY_CASE) default: UNREACHABLE(); #undef TYPED_ARRAY_CASE } } } // namespace Reduction JSCallReducer::ReduceDataViewAccess(Node* node, DataViewAccess access, ExternalArrayType element_type) { size_t const element_size = ExternalArrayElementSize(element_type); CallParameters const& p = CallParametersOf(node->op()); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); Node* receiver = NodeProperties::GetValueInput(node, 1); Node* offset = node->op()->ValueInputCount() > 2 ? NodeProperties::GetValueInput(node, 2) : jsgraph()->ZeroConstant(); Node* value = (access == DataViewAccess::kGet) ? nullptr : (node->op()->ValueInputCount() > 3 ? NodeProperties::GetValueInput(node, 3) : jsgraph()->ZeroConstant()); Node* is_little_endian = (access == DataViewAccess::kGet) ? (node->op()->ValueInputCount() > 3 ? NodeProperties::GetValueInput(node, 3) : jsgraph()->FalseConstant()) : (node->op()->ValueInputCount() > 4 ? NodeProperties::GetValueInput(node, 4) : jsgraph()->FalseConstant()); if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) { return NoChange(); } // Only do stuff if the {receiver} is really a DataView. if (NodeProperties::HasInstanceTypeWitness(isolate(), receiver, effect, JS_DATA_VIEW_TYPE)) { // Check that the {offset} is within range for the {receiver}. HeapObjectMatcher m(receiver); if (m.HasValue()) { // We only deal with DataViews here whose [[ByteLength]] is at least // {element_size} and less than 2^31-{element_size}. Handle dataview = Handle::cast(m.Value()); if (dataview->byte_length() < element_size || dataview->byte_length() - element_size > kMaxInt) { return NoChange(); } // The {receiver}s [[ByteOffset]] must be within Unsigned31 range. if (dataview->byte_offset() > kMaxInt) { return NoChange(); } // Check that the {offset} is within range of the {byte_length}. Node* byte_length = jsgraph()->Constant(dataview->byte_length() - (element_size - 1)); offset = effect = graph()->NewNode(simplified()->CheckBounds(p.feedback()), offset, byte_length, effect, control); // Add the [[ByteOffset]] to compute the effective offset. Node* byte_offset = jsgraph()->Constant(dataview->byte_offset()); offset = graph()->NewNode(simplified()->NumberAdd(), offset, byte_offset); } else { // We only deal with DataViews here that have Smi [[ByteLength]]s. Node* byte_length = effect = graph()->NewNode(simplified()->LoadField( AccessBuilder::ForJSArrayBufferViewByteLength()), receiver, effect, control); byte_length = effect = graph()->NewNode( simplified()->CheckSmi(p.feedback()), byte_length, effect, control); // Check that the {offset} is within range of the {byte_length}. offset = effect = graph()->NewNode(simplified()->CheckBounds(p.feedback()), offset, byte_length, effect, control); if (element_size > 0) { // For non-byte accesses we also need to check that the {offset} // plus the {element_size}-1 fits within the given {byte_length}. Node* end_offset = graph()->NewNode(simplified()->NumberAdd(), offset, jsgraph()->Constant(element_size - 1)); effect = graph()->NewNode(simplified()->CheckBounds(p.feedback()), end_offset, byte_length, effect, control); } // The {receiver}s [[ByteOffset]] also needs to be a (positive) Smi. Node* byte_offset = effect = graph()->NewNode(simplified()->LoadField( AccessBuilder::ForJSArrayBufferViewByteOffset()), receiver, effect, control); byte_offset = effect = graph()->NewNode( simplified()->CheckSmi(p.feedback()), byte_offset, effect, control); // Compute the buffer index at which we'll read. offset = graph()->NewNode(simplified()->NumberAdd(), offset, byte_offset); } // Coerce {is_little_endian} to boolean. is_little_endian = graph()->NewNode(simplified()->ToBoolean(), is_little_endian); // Coerce {value} to Number. if (access == DataViewAccess::kSet) { value = effect = graph()->NewNode( simplified()->SpeculativeToNumber( NumberOperationHint::kNumberOrOddball, p.feedback()), value, effect, control); } // Get the underlying buffer and check that it has not been neutered. Node* buffer = effect = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForJSArrayBufferViewBuffer()), receiver, effect, control); if (isolate()->IsArrayBufferNeuteringIntact()) { // Add a code dependency so we are deoptimized in case an ArrayBuffer // gets neutered. dependencies()->DependOnProtector(PropertyCellRef( js_heap_broker(), factory()->array_buffer_neutering_protector())); } else { // Bail out if the {buffer} was neutered. Node* buffer_bit_field = effect = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForJSArrayBufferBitField()), buffer, effect, control); Node* check = graph()->NewNode( simplified()->NumberEqual(), graph()->NewNode( simplified()->NumberBitwiseAnd(), buffer_bit_field, jsgraph()->Constant(JSArrayBuffer::WasNeuteredBit::kMask)), jsgraph()->ZeroConstant()); effect = graph()->NewNode( simplified()->CheckIf(DeoptimizeReason::kArrayBufferWasNeutered, p.feedback()), check, effect, control); } // Get the buffer's backing store. Node* backing_store = effect = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForJSArrayBufferBackingStore()), buffer, effect, control); switch (access) { case DataViewAccess::kGet: // Perform the load. value = effect = graph()->NewNode( simplified()->LoadDataViewElement(element_type), buffer, backing_store, offset, is_little_endian, effect, control); break; case DataViewAccess::kSet: // Perform the store. effect = graph()->NewNode( simplified()->StoreDataViewElement(element_type), buffer, backing_store, offset, value, is_little_endian, effect, control); value = jsgraph()->UndefinedConstant(); break; } // Continue on the regular path. ReplaceWithValue(node, value, effect, control); return Changed(value); } return NoChange(); } // ES6 section 18.2.2 isFinite ( number ) Reduction JSCallReducer::ReduceGlobalIsFinite(Node* node) { CallParameters const& p = CallParametersOf(node->op()); if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) { return NoChange(); } if (node->op()->ValueInputCount() < 3) { Node* value = jsgraph()->FalseConstant(); ReplaceWithValue(node, value); return Replace(value); } Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); Node* input = NodeProperties::GetValueInput(node, 2); input = effect = graph()->NewNode(simplified()->SpeculativeToNumber( NumberOperationHint::kNumberOrOddball, p.feedback()), input, effect, control); Node* value = graph()->NewNode(simplified()->NumberIsFinite(), input); ReplaceWithValue(node, value, effect); return Replace(value); } // ES6 section 18.2.3 isNaN ( number ) Reduction JSCallReducer::ReduceGlobalIsNaN(Node* node) { CallParameters const& p = CallParametersOf(node->op()); if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) { return NoChange(); } if (node->op()->ValueInputCount() < 3) { Node* value = jsgraph()->TrueConstant(); ReplaceWithValue(node, value); return Replace(value); } Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); Node* input = NodeProperties::GetValueInput(node, 2); input = effect = graph()->NewNode(simplified()->SpeculativeToNumber( NumberOperationHint::kNumberOrOddball, p.feedback()), input, effect, control); Node* value = graph()->NewNode(simplified()->NumberIsNaN(), input); ReplaceWithValue(node, value, effect); return Replace(value); } // ES6 section 20.3.4.10 Date.prototype.getTime ( ) Reduction JSCallReducer::ReduceDatePrototypeGetTime(Node* node) { Node* receiver = NodeProperties::GetValueInput(node, 1); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); if (NodeProperties::HasInstanceTypeWitness(isolate(), receiver, effect, JS_DATE_TYPE)) { Node* value = effect = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForJSDateValue()), receiver, effect, control); ReplaceWithValue(node, value, effect, control); return Replace(value); } return NoChange(); } // ES6 section 20.3.3.1 Date.now ( ) Reduction JSCallReducer::ReduceDateNow(Node* node) { Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); Node* value = effect = graph()->NewNode(simplified()->DateNow(), effect, control); ReplaceWithValue(node, value, effect, control); return Replace(value); } // ES6 section 20.1.2.13 Number.parseInt ( string, radix ) Reduction JSCallReducer::ReduceNumberParseInt(Node* node) { // We certainly know that undefined is not an array. if (node->op()->ValueInputCount() < 3) { Node* value = jsgraph()->NaNConstant(); ReplaceWithValue(node, value); return Replace(value); } int arg_count = node->op()->ValueInputCount(); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); Node* context = NodeProperties::GetContextInput(node); Node* frame_state = NodeProperties::GetFrameStateInput(node); Node* object = NodeProperties::GetValueInput(node, 2); Node* radix = arg_count >= 4 ? NodeProperties::GetValueInput(node, 3) : jsgraph()->UndefinedConstant(); node->ReplaceInput(0, object); node->ReplaceInput(1, radix); node->ReplaceInput(2, context); node->ReplaceInput(3, frame_state); node->ReplaceInput(4, effect); node->ReplaceInput(5, control); node->TrimInputCount(6); NodeProperties::ChangeOp(node, javascript()->ParseInt()); return Changed(node); } Reduction JSCallReducer::ReduceRegExpPrototypeTest(Node* node) { if (FLAG_force_slow_path) return NoChange(); if (node->op()->ValueInputCount() < 3) return NoChange(); CallParameters const& p = CallParametersOf(node->op()); if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) { return NoChange(); } Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); Node* regexp = NodeProperties::GetValueInput(node, 1); // Check if we know something about the {regexp}. ZoneHandleSet regexp_maps; NodeProperties::InferReceiverMapsResult result = NodeProperties::InferReceiverMaps(isolate(), regexp, effect, ®exp_maps); bool need_map_check = false; switch (result) { case NodeProperties::kNoReceiverMaps: return NoChange(); case NodeProperties::kUnreliableReceiverMaps: need_map_check = true; break; case NodeProperties::kReliableReceiverMaps: break; } for (auto map : regexp_maps) { if (map->instance_type() != JS_REGEXP_TYPE) return NoChange(); } // Compute property access info for "exec" on {resolution}. PropertyAccessInfo ai_exec; AccessInfoFactory access_info_factory(js_heap_broker(), dependencies(), native_context(), graph()->zone()); if (!access_info_factory.ComputePropertyAccessInfo( MapHandles(regexp_maps.begin(), regexp_maps.end()), factory()->exec_string(), AccessMode::kLoad, &ai_exec)) { return NoChange(); } // If "exec" has been modified on {regexp}, we can't do anything. if (!ai_exec.IsDataConstant()) return NoChange(); Handle exec_on_proto = ai_exec.constant(); if (*exec_on_proto != *isolate()->regexp_exec_function()) return NoChange(); PropertyAccessBuilder access_builder(jsgraph(), js_heap_broker(), dependencies()); // Add proper dependencies on the {regexp}s [[Prototype]]s. Handle holder; if (ai_exec.holder().ToHandle(&holder)) { dependencies()->DependOnStablePrototypeChains( js_heap_broker(), native_context(), ai_exec.receiver_maps(), holder); } if (need_map_check) { effect = graph()->NewNode(simplified()->CheckMaps(CheckMapsFlag::kNone, regexp_maps, p.feedback()), regexp, effect, control); } Node* context = NodeProperties::GetContextInput(node); Node* frame_state = NodeProperties::GetFrameStateInput(node); Node* search = NodeProperties::GetValueInput(node, 2); Node* search_string = effect = graph()->NewNode( simplified()->CheckString(p.feedback()), search, effect, control); Node* lastIndex = effect = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForJSRegExpLastIndex()), regexp, effect, control); Node* lastIndexSmi = effect = graph()->NewNode( simplified()->CheckSmi(p.feedback()), lastIndex, effect, control); Node* is_positive = graph()->NewNode(simplified()->NumberLessThanOrEqual(), jsgraph()->ZeroConstant(), lastIndexSmi); effect = graph()->NewNode( simplified()->CheckIf(DeoptimizeReason::kNotASmi, p.feedback()), is_positive, effect, control); node->ReplaceInput(0, regexp); node->ReplaceInput(1, search_string); node->ReplaceInput(2, context); node->ReplaceInput(3, frame_state); node->ReplaceInput(4, effect); node->ReplaceInput(5, control); node->TrimInputCount(6); NodeProperties::ChangeOp(node, javascript()->RegExpTest()); return Changed(node); } // ES section #sec-number-constructor Reduction JSCallReducer::ReduceNumberConstructor(Node* node) { DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); CallParameters const& p = CallParametersOf(node->op()); Node* target = NodeProperties::GetValueInput(node, 0); Node* receiver = NodeProperties::GetValueInput(node, 1); Node* value = p.arity() < 3 ? jsgraph()->ZeroConstant() : NodeProperties::GetValueInput(node, 2); Node* context = NodeProperties::GetContextInput(node); Node* frame_state = NodeProperties::GetFrameStateInput(node); // Create the artificial frame state in the middle of the Number constructor. Handle shared_info( handle(native_context()->number_function()->shared(), isolate())); Node* stack_parameters[] = {receiver}; int stack_parameter_count = arraysize(stack_parameters); Node* continuation_frame_state = CreateJavaScriptBuiltinContinuationFrameState( jsgraph(), shared_info, Builtins::kGenericConstructorLazyDeoptContinuation, target, context, stack_parameters, stack_parameter_count, frame_state, ContinuationFrameStateMode::LAZY); // Convert the {value} to a Number. NodeProperties::ReplaceValueInputs(node, value); NodeProperties::ChangeOp(node, javascript()->ToNumberConvertBigInt()); NodeProperties::ReplaceFrameStateInput(node, continuation_frame_state); return Changed(node); } Graph* JSCallReducer::graph() const { return jsgraph()->graph(); } Isolate* JSCallReducer::isolate() const { return jsgraph()->isolate(); } Factory* JSCallReducer::factory() const { return isolate()->factory(); } Handle JSCallReducer::global_proxy() const { return handle(JSGlobalProxy::cast(native_context()->global_proxy()), isolate()); } CommonOperatorBuilder* JSCallReducer::common() const { return jsgraph()->common(); } JSOperatorBuilder* JSCallReducer::javascript() const { return jsgraph()->javascript(); } SimplifiedOperatorBuilder* JSCallReducer::simplified() const { return jsgraph()->simplified(); } } // namespace compiler } // namespace internal } // namespace v8