| // 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 <functional> |
| |
| #include "src/api/api-inl.h" |
| #include "src/builtins/builtins-promise.h" |
| #include "src/builtins/builtins-utils.h" |
| #include "src/codegen/code-factory.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/map-inference.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/compiler/vector-slot-pair.h" |
| #include "src/ic/call-optimization.h" |
| #include "src/logging/counters.h" |
| #include "src/objects/arguments-inl.h" |
| #include "src/objects/feedback-vector-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/objects/objects-inl.h" |
| #include "src/objects/ordered-hash-table.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); |
| } |
| |
| // ES section #sec-math.hypot Math.hypot ( value1, value2, ...values ) |
| Reduction JSCallReducer::ReduceMathHypot(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* effect = NodeProperties::GetEffectInput(node); |
| Node* control = NodeProperties::GetControlInput(node); |
| NodeVector values(graph()->zone()); |
| |
| Node* max = effect = |
| graph()->NewNode(simplified()->SpeculativeToNumber( |
| NumberOperationHint::kNumberOrOddball, p.feedback()), |
| NodeProperties::GetValueInput(node, 2), effect, control); |
| max = graph()->NewNode(simplified()->NumberAbs(), max); |
| values.push_back(max); |
| 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); |
| input = graph()->NewNode(simplified()->NumberAbs(), input); |
| values.push_back(input); |
| |
| // Make sure {max} is NaN in the end in case any argument was NaN. |
| max = graph()->NewNode( |
| common()->Select(MachineRepresentation::kTagged), |
| graph()->NewNode(simplified()->NumberLessThanOrEqual(), input, max), |
| max, input); |
| } |
| |
| Node* check0 = graph()->NewNode(simplified()->NumberEqual(), max, |
| jsgraph()->ZeroConstant()); |
| Node* branch0 = |
| graph()->NewNode(common()->Branch(BranchHint::kFalse), check0, control); |
| |
| Node* if_true0 = graph()->NewNode(common()->IfTrue(), branch0); |
| Node* vtrue0 = jsgraph()->ZeroConstant(); |
| |
| Node* if_false0 = graph()->NewNode(common()->IfFalse(), branch0); |
| Node* vfalse0; |
| { |
| Node* check1 = graph()->NewNode(simplified()->NumberEqual(), max, |
| jsgraph()->Constant(V8_INFINITY)); |
| Node* branch1 = graph()->NewNode(common()->Branch(BranchHint::kFalse), |
| check1, if_false0); |
| |
| Node* if_true1 = graph()->NewNode(common()->IfTrue(), branch1); |
| Node* vtrue1 = jsgraph()->Constant(V8_INFINITY); |
| |
| Node* if_false1 = graph()->NewNode(common()->IfFalse(), branch1); |
| Node* vfalse1; |
| { |
| // Kahan summation to avoid rounding errors. |
| // Normalize the numbers to the largest one to avoid overflow. |
| Node* sum = jsgraph()->ZeroConstant(); |
| Node* compensation = jsgraph()->ZeroConstant(); |
| for (Node* value : values) { |
| Node* n = graph()->NewNode(simplified()->NumberDivide(), value, max); |
| Node* summand = graph()->NewNode( |
| simplified()->NumberSubtract(), |
| graph()->NewNode(simplified()->NumberMultiply(), n, n), |
| compensation); |
| Node* preliminary = |
| graph()->NewNode(simplified()->NumberAdd(), sum, summand); |
| compensation = graph()->NewNode( |
| simplified()->NumberSubtract(), |
| graph()->NewNode(simplified()->NumberSubtract(), preliminary, sum), |
| summand); |
| sum = preliminary; |
| } |
| vfalse1 = graph()->NewNode( |
| simplified()->NumberMultiply(), |
| graph()->NewNode(simplified()->NumberSqrt(), sum), max); |
| } |
| |
| if_false0 = graph()->NewNode(common()->Merge(2), if_true1, if_false1); |
| vfalse0 = graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2), |
| vtrue1, vfalse1, if_false0); |
| } |
| |
| control = graph()->NewNode(common()->Merge(2), if_true0, if_false0); |
| Node* value = |
| graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2), vtrue0, |
| vfalse0, control); |
| ReplaceWithValue(node, value, effect, control); |
| 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<Node*> 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<AllocationSite>())); |
| 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(broker(), value, effect)) { |
| if (!NodeProperties::CanBeNullOrUndefined(broker(), 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) { |
| DisallowHeapAccessIf no_heap_acess(FLAG_concurrent_inlining); |
| |
| 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(broker(), 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. |
| // TODO(mslekova): Since this introduces a Call that will get optimized by |
| // the JSCallReducer, we basically might have to do all the serialization |
| // that we do for that here as well. The only difference is that here we |
| // disable speculation (cf. the empty VectorSlotPair above), causing the |
| // JSCallReducer to do much less work. We should revisit this later. |
| NodeProperties::ChangeOp( |
| node, |
| javascript()->Call(arity, p.frequency(), VectorSlotPair(), convert_mode)); |
| // TODO(mslekova): Remove once ReduceJSCall is brokerized. |
| AllowHandleDereference allow_handle_dereference; |
| AllowHandleAllocation allow_handle_allocation; |
| // 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()); |
| CallParameters const& p = CallParametersOf(node->op()); |
| if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) { |
| return NoChange(); |
| } |
| |
| // 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. |
| MapInference inference(broker(), receiver, effect); |
| if (!inference.HaveMaps()) return NoChange(); |
| MapHandles const& receiver_maps = inference.GetMaps(); |
| |
| MapRef first_receiver_map(broker(), receiver_maps[0]); |
| bool const is_constructor = first_receiver_map.is_constructor(); |
| first_receiver_map.SerializePrototype(); |
| ObjectRef const prototype = first_receiver_map.prototype(); |
| for (Handle<Map> const map : receiver_maps) { |
| MapRef receiver_map(broker(), map); |
| |
| // Check for consistency among the {receiver_maps}. |
| STATIC_ASSERT(LAST_TYPE == LAST_FUNCTION_TYPE); |
| receiver_map.SerializePrototype(); |
| if (!receiver_map.prototype().equals(prototype) || |
| receiver_map.is_constructor() != is_constructor || |
| receiver_map.instance_type() < FIRST_FUNCTION_TYPE) { |
| return inference.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 inference.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<DescriptorArray> descriptors( |
| receiver_map.object()->instance_descriptors(), isolate()); |
| if (descriptors->number_of_descriptors() < 2) return inference.NoChange(); |
| if (descriptors->GetKey(JSFunction::kLengthDescriptorIndex) != |
| ReadOnlyRoots(isolate()).length_string()) { |
| return inference.NoChange(); |
| } |
| if (!descriptors->GetStrongValue(JSFunction::kLengthDescriptorIndex) |
| .IsAccessorInfo()) { |
| return inference.NoChange(); |
| } |
| if (descriptors->GetKey(JSFunction::kNameDescriptorIndex) != |
| ReadOnlyRoots(isolate()).name_string()) { |
| return inference.NoChange(); |
| } |
| if (!descriptors->GetStrongValue(JSFunction::kNameDescriptorIndex) |
| .IsAccessorInfo()) { |
| return inference.NoChange(); |
| } |
| } |
| |
| // Choose the map for the resulting JSBoundFunction (but bail out in case of a |
| // custom prototype). |
| MapRef map = is_constructor |
| ? native_context().bound_function_with_constructor_map() |
| : native_context().bound_function_without_constructor_map(); |
| if (!map.prototype().equals(prototype)) return inference.NoChange(); |
| |
| inference.RelyOnMapsPreferStability(dependencies(), jsgraph(), &effect, |
| control, p.feedback()); |
| |
| // 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<Node*>(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.object()), |
| 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) { |
| DisallowHeapAccessIf no_heap_acess(FLAG_concurrent_inlining); |
| |
| 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()) { |
| JSFunctionRef function = m.Ref(broker()).AsJSFunction(); |
| if (FLAG_concurrent_inlining && !function.serialized()) { |
| TRACE_BROKER_MISSING(broker(), "Serialize call on function " << function); |
| return NoChange(); |
| } |
| context = jsgraph()->Constant(function.context()); |
| } 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)); |
| // TODO(mslekova): Remove once ReduceJSCall is brokerized. |
| AllowHandleDereference allow_handle_dereference; |
| AllowHandleAllocation allow_handle_allocation; |
| // 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. |
| MapInference inference(broker(), object, effect); |
| if (!inference.HaveMaps()) return NoChange(); |
| MapHandles const& object_maps = inference.GetMaps(); |
| |
| MapRef candidate_map(broker(), object_maps[0]); |
| candidate_map.SerializePrototype(); |
| ObjectRef candidate_prototype = candidate_map.prototype(); |
| |
| // Check if we can constant-fold the {candidate_prototype}. |
| for (size_t i = 0; i < object_maps.size(); ++i) { |
| MapRef object_map(broker(), object_maps[i]); |
| object_map.SerializePrototype(); |
| if (IsSpecialReceiverInstanceType(object_map.instance_type()) || |
| !object_map.prototype().equals(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 inference.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 (!inference.RelyOnMapsViaStability(dependencies())) { |
| return inference.NoChange(); |
| } |
| Node* value = jsgraph()->Constant(candidate_prototype); |
| ReplaceWithValue(node, value); |
| return Replace(value); |
| } |
| |
| // 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<int>(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<int>(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). |
| MapInference inference(broker(), receiver, effect); |
| if (!inference.HaveMaps() || !inference.AllOfInstanceTypesAreJSReceiver()) { |
| 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<int>(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<int>(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<int>(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( |
| static_cast<int>(MessageTemplate::kCalledOnNonObject)), |
| jsgraph()->HeapConstant(factory()->ReflectGet_string()), 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<int>(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( |
| static_cast<int>(MessageTemplate::kCalledOnNonObject)), |
| jsgraph()->HeapConstant(factory()->ReflectHas_string()), 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; |
| { |
| // TODO(magardn): collect feedback so this can be optimized |
| vtrue = etrue = if_true = |
| graph()->NewNode(javascript()->HasProperty(VectorSlotPair()), 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); |
| } |
| |
| 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); |
| } |
| |
| namespace { |
| bool CanInlineArrayIteratingBuiltin(JSHeapBroker* broker, |
| MapHandles const& receiver_maps, |
| ElementsKind* kind_return) { |
| DCHECK_NE(0, receiver_maps.size()); |
| *kind_return = MapRef(broker, receiver_maps[0]).elements_kind(); |
| for (auto receiver_map : receiver_maps) { |
| MapRef map(broker, receiver_map); |
| if (!map.supports_fast_array_iteration() || |
| !UnionElementsKindUptoSize(kind_return, map.elements_kind())) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| bool CanInlineArrayResizingBuiltin( |
| JSHeapBroker* broker, MapHandles const& receiver_maps, |
| std::vector<ElementsKind>& kinds, // NOLINT(runtime/references) |
| bool builtin_is_push = false) { |
| DCHECK_NE(0, receiver_maps.size()); |
| for (auto receiver_map : receiver_maps) { |
| MapRef map(broker, receiver_map); |
| if (!map.supports_fast_array_resize()) return false; |
| // TODO(turbofan): We should also handle fast holey double elements once |
| // we got the hole NaN mess sorted out in TurboFan/V8. |
| if (map.elements_kind() == HOLEY_DOUBLE_ELEMENTS && !builtin_is_push) { |
| return false; |
| } |
| ElementsKind current_kind = map.elements_kind(); |
| auto kind_ptr = kinds.data(); |
| size_t i; |
| for (i = 0; i < kinds.size(); i++, kind_ptr++) { |
| if (UnionElementsKindUptoPackedness(kind_ptr, current_kind)) { |
| break; |
| } |
| } |
| if (i == kinds.size()) kinds.push_back(current_kind); |
| } |
| return true; |
| } |
| } // namespace |
| |
| Reduction JSCallReducer::ReduceArrayForEach( |
| Node* node, const SharedFunctionInfoRef& 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); |
| 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(); |
| |
| // Try to determine the {receiver} map. |
| MapInference inference(broker(), receiver, effect); |
| if (!inference.HaveMaps()) return NoChange(); |
| MapHandles const& receiver_maps = inference.GetMaps(); |
| |
| ElementsKind kind; |
| if (!CanInlineArrayIteratingBuiltin(broker(), receiver_maps, &kind)) { |
| return inference.NoChange(); |
| } |
| if (!dependencies()->DependOnNoElementsProtector()) UNREACHABLE(); |
| bool const stability_dependency = inference.RelyOnMapsPreferStability( |
| dependencies(), jsgraph(), &effect, control, p.feedback()); |
| |
| Node* k = jsgraph()->ZeroConstant(); |
| Node* original_length = effect = graph()->NewNode( |
| simplified()->LoadField(AccessBuilder::ForJSArrayLength(kind)), receiver, |
| effect, control); |
| |
| Node* checkpoint_params[] = {receiver, fncallback, this_arg, k, |
| original_length}; |
| const int stack_parameters = arraysize(checkpoint_params); |
| |
| // 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::kNone), |
| 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); |
| } |
| |
| // Deopt if the map has changed during the iteration. |
| if (!stability_dependency) { |
| inference.InsertMapChecks(jsgraph(), &effect, control, p.feedback()); |
| } |
| |
| 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); |
| } |
| |
| Node* 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; |
| |
| // Introduce proper LoopExit and LoopExitEffect nodes to mark |
| // {loop} as a candidate for loop peeling (crbug.com/v8/8273). |
| control = graph()->NewNode(common()->LoopExit(), control, loop); |
| effect = graph()->NewNode(common()->LoopExitEffect(), effect, 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, jsgraph()->UndefinedConstant(), effect, control); |
| return Replace(jsgraph()->UndefinedConstant()); |
| } |
| |
| Reduction JSCallReducer::ReduceArrayReduce( |
| Node* node, ArrayReduceDirection direction, |
| const SharedFunctionInfoRef& 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); |
| Node* receiver = NodeProperties::GetValueInput(node, 1); |
| Node* fncallback = node->op()->ValueInputCount() > 2 |
| ? NodeProperties::GetValueInput(node, 2) |
| : jsgraph()->UndefinedConstant(); |
| |
| // Try to determine the {receiver} map. |
| MapInference inference(broker(), receiver, effect); |
| if (!inference.HaveMaps()) return NoChange(); |
| MapHandles const& receiver_maps = inference.GetMaps(); |
| |
| ElementsKind kind; |
| if (!CanInlineArrayIteratingBuiltin(broker(), receiver_maps, &kind)) { |
| return inference.NoChange(); |
| } |
| if (!dependencies()->DependOnNoElementsProtector()) UNREACHABLE(); |
| bool const stability_dependency = inference.RelyOnMapsPreferStability( |
| dependencies(), jsgraph(), &effect, control, p.feedback()); |
| |
| 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; |
| Node* checkpoint_params[] = {receiver, fncallback, k, original_length, |
| jsgraph()->UndefinedConstant()}; |
| const int stack_parameters = arraysize(checkpoint_params); |
| check_frame_state = CreateJavaScriptBuiltinContinuationFrameState( |
| jsgraph(), shared, builtin_lazy, node->InputAt(0), context, |
| &checkpoint_params[0], 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); |
| |
| std::function<Node*(Node*)> hole_check = [this, kind](Node* element) { |
| if (IsDoubleElementsKind(kind)) { |
| return graph()->NewNode(simplified()->NumberIsFloat64Hole(), element); |
| } else { |
| return graph()->NewNode(simplified()->ReferenceEqual(), element, |
| jsgraph()->TheHoleConstant()); |
| } |
| }; |
| |
| // 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; |
| Node* checkpoint_params[] = {receiver, fncallback, original_length}; |
| const int stack_parameters = arraysize(checkpoint_params); |
| Node* find_first_element_frame_state = |
| CreateJavaScriptBuiltinContinuationFrameState( |
| jsgraph(), shared, builtin_eager, node->InputAt(0), context, |
| &checkpoint_params[0], 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::kNone), |
| 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; |
| Node* checkpoint_params[] = {receiver, fncallback, k, original_length, |
| curloop}; |
| const int stack_parameters = arraysize(checkpoint_params); |
| Node* frame_state = CreateJavaScriptBuiltinContinuationFrameState( |
| jsgraph(), shared, builtin_eager, node->InputAt(0), context, |
| &checkpoint_params[0], stack_parameters, outer_frame_state, |
| ContinuationFrameStateMode::EAGER); |
| effect = |
| graph()->NewNode(common()->Checkpoint(), frame_state, effect, control); |
| inference.InsertMapChecks(jsgraph(), &effect, control, p.feedback()); |
| } |
| |
| // Deopt if the map has changed during the iteration. |
| if (!stability_dependency) { |
| inference.InsertMapChecks(jsgraph(), &effect, control, p.feedback()); |
| } |
| |
| 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; |
| Node* checkpoint_params[] = {receiver, fncallback, next_k, original_length, |
| curloop}; |
| const int stack_parameters = arraysize(checkpoint_params); |
| Node* frame_state = CreateJavaScriptBuiltinContinuationFrameState( |
| jsgraph(), shared, builtin_lazy, node->InputAt(0), context, |
| &checkpoint_params[0], 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, |
| const SharedFunctionInfoRef& 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); |
| 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(); |
| |
| // Try to determine the {receiver} map. |
| MapInference inference(broker(), receiver, effect); |
| if (!inference.HaveMaps()) return NoChange(); |
| MapHandles const& receiver_maps = inference.GetMaps(); |
| |
| ElementsKind kind; |
| if (!CanInlineArrayIteratingBuiltin(broker(), receiver_maps, &kind)) { |
| return inference.NoChange(); |
| } |
| if (!dependencies()->DependOnArraySpeciesProtector()) |
| return inference.NoChange(); |
| if (IsHoleyElementsKind(kind)) { |
| if (!dependencies()->DependOnNoElementsProtector()) UNREACHABLE(); |
| } |
| bool const stability_dependency = inference.RelyOnMapsPreferStability( |
| dependencies(), jsgraph(), &effect, control, p.feedback()); |
| |
| Node* array_constructor = jsgraph()->Constant( |
| native_context().GetInitialJSArrayMap(kind).GetConstructor()); |
| Node* k = jsgraph()->ZeroConstant(); |
| Node* original_length = effect = graph()->NewNode( |
| simplified()->LoadField(AccessBuilder::ForJSArrayLength(kind)), receiver, |
| effect, control); |
| |
| // If the array length >= kMaxFastArrayLength, then CreateArray |
| // will create a dictionary. We should deopt in this case, and make sure |
| // not to attempt inlining again. |
| original_length = effect = graph()->NewNode( |
| simplified()->CheckBounds(p.feedback()), original_length, |
| jsgraph()->Constant(JSArray::kMaxFastArrayLength), 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<AllocationSite>()), |
| array_constructor, array_constructor, original_length, context, |
| outer_frame_state, effect, control); |
| |
| Node* checkpoint_params[] = {receiver, fncallback, this_arg, |
| a, k, original_length}; |
| const int stack_parameters = arraysize(checkpoint_params); |
| |
| // 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::kNone), |
| 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); |
| } |
| |
| // Deopt if the map has changed during the iteration. |
| if (!stability_dependency) { |
| inference.InsertMapChecks(jsgraph(), &effect, control, p.feedback()); |
| } |
| |
| 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. |
| Node* 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. |
| MapRef holey_double_map = |
| native_context().GetInitialJSArrayMap(HOLEY_DOUBLE_ELEMENTS); |
| MapRef holey_map = native_context().GetInitialJSArrayMap(HOLEY_ELEMENTS); |
| effect = graph()->NewNode(simplified()->TransitionAndStoreElement( |
| holey_double_map.object(), holey_map.object()), |
| 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, const SharedFunctionInfoRef& 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); |
| 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(); |
| |
| // Try to determine the {receiver} map. |
| MapInference inference(broker(), receiver, effect); |
| if (!inference.HaveMaps()) return NoChange(); |
| MapHandles const& receiver_maps = inference.GetMaps(); |
| |
| ElementsKind kind; |
| if (!CanInlineArrayIteratingBuiltin(broker(), receiver_maps, &kind)) { |
| return inference.NoChange(); |
| } |
| if (!dependencies()->DependOnArraySpeciesProtector()) |
| return inference.NoChange(); |
| if (IsHoleyElementsKind(kind)) { |
| if (!dependencies()->DependOnNoElementsProtector()) UNREACHABLE(); |
| } |
| bool const stability_dependency = inference.RelyOnMapsPreferStability( |
| dependencies(), jsgraph(), &effect, control, p.feedback()); |
| |
| // The output array is packed (filter doesn't visit holes). |
| const ElementsKind packed_kind = GetPackedElementsKind(kind); |
| MapRef initial_map = native_context().GetInitialJSArrayMap(packed_kind); |
| |
| Node* k = jsgraph()->ZeroConstant(); |
| Node* to = jsgraph()->ZeroConstant(); |
| |
| Node* a; // Construct the output array. |
| { |
| AllocationBuilder ab(jsgraph(), effect, control); |
| ab.Allocate(initial_map.instance_size(), AllocationType::kYoung, |
| 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(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. |
| Node* checkpoint_params[] = {receiver, fncallback, this_arg, a, |
| k, original_length, to, to}; |
| const int stack_parameters = arraysize(checkpoint_params); |
| |
| 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::kNone), |
| 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* checkpoint_params[] = {receiver, fncallback, this_arg, a, |
| k, original_length, to}; |
| const int stack_parameters = arraysize(checkpoint_params); |
| 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); |
| } |
| |
| // Deopt if the map has changed during the iteration. |
| if (!stability_dependency) { |
| inference.InsertMapChecks(jsgraph(), &effect, control, p.feedback()); |
| } |
| |
| 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. |
| Node* checkpoint_params[] = {receiver, fncallback, this_arg, a, |
| k, original_length, element, to}; |
| const int stack_parameters = arraysize(checkpoint_params); |
| |
| 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. |
| { |
| Node* checkpoint_params[] = {receiver, fncallback, this_arg, |
| a, k, original_length, |
| element, to, callback_value}; |
| const int stack_parameters = arraysize(checkpoint_params); |
| 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, |
| const SharedFunctionInfoRef& 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); |
| 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(); |
| |
| // Try to determine the {receiver} map. |
| MapInference inference(broker(), receiver, effect); |
| if (!inference.HaveMaps()) return NoChange(); |
| MapHandles const& receiver_maps = inference.GetMaps(); |
| |
| ElementsKind kind; |
| if (!CanInlineArrayIteratingBuiltin(broker(), receiver_maps, &kind)) { |
| return inference.NoChange(); |
| } |
| if (!dependencies()->DependOnNoElementsProtector()) UNREACHABLE(); |
| bool const stability_dependency = inference.RelyOnMapsPreferStability( |
| dependencies(), jsgraph(), &effect, control, p.feedback()); |
| |
| Node* k = jsgraph()->ZeroConstant(); |
| Node* original_length = effect = graph()->NewNode( |
| simplified()->LoadField(AccessBuilder::ForJSArrayLength(kind)), receiver, |
| effect, control); |
| Node* checkpoint_params[] = {receiver, fncallback, this_arg, k, |
| original_length}; |
| const int stack_parameters = arraysize(checkpoint_params); |
| |
| // 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::kNone), continue_test, control); |
| control = graph()->NewNode(common()->IfTrue(), continue_branch); |
| if_false = graph()->NewNode(common()->IfFalse(), continue_branch); |
| } |
| |
| { |
| 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); |
| } |
| |
| // Deopt if the map has changed during the iteration. |
| if (!stability_dependency) { |
| inference.InsertMapChecks(jsgraph(), &effect, control, p.feedback()); |
| } |
| |
| Node* element = |
| SafeLoadElement(kind, receiver, control, &effect, &k, p.feedback()); |
| 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; |
| { |
| Node* call_checkpoint_params[] = {receiver, fncallback, |
| this_arg, next_k, |
| original_length, if_found_return_value}; |
| const int call_stack_parameters = arraysize(call_checkpoint_params); |
| |
| 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* value = |
| graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2), |
| if_found_return_value, if_not_found_value, control); |
| |
| // Introduce proper LoopExit/LoopExitEffect/LoopExitValue to mark |
| // {loop} as a candidate for loop peeling (crbug.com/v8/8273). |
| control = graph()->NewNode(common()->LoopExit(), control, loop); |
| effect = graph()->NewNode(common()->LoopExitEffect(), effect, control); |
| value = graph()->NewNode(common()->LoopExitValue(), 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, value, effect, control); |
| return Replace(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( |
| static_cast<int>(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, |
| const SharedFunctionInfoRef& 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); |
| 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(); |
| |
| // Try to determine the {receiver} map. |
| MapInference inference(broker(), receiver, effect); |
| if (!inference.HaveMaps()) return NoChange(); |
| MapHandles const& receiver_maps = inference.GetMaps(); |
| |
| ElementsKind kind; |
| if (!CanInlineArrayIteratingBuiltin(broker(), receiver_maps, &kind)) { |
| return inference.NoChange(); |
| } |
| if (!dependencies()->DependOnArraySpeciesProtector()) |
| return inference.NoChange(); |
| if (IsHoleyElementsKind(kind)) { |
| if (!dependencies()->DependOnNoElementsProtector()) UNREACHABLE(); |
| } |
| bool const stability_dependency = inference.RelyOnMapsPreferStability( |
| dependencies(), jsgraph(), &effect, control, p.feedback()); |
| |
| Node* k = jsgraph()->ZeroConstant(); |
| 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. |
| Node* checkpoint_params[] = {receiver, fncallback, this_arg, k, |
| original_length}; |
| const int stack_parameters = arraysize(checkpoint_params); |
| |
| 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::kNone), |
| 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* checkpoint_params[] = {receiver, fncallback, this_arg, k, |
| original_length}; |
| const int stack_parameters = arraysize(checkpoint_params); |
| 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); |
| } |
| |
| // Deopt if the map has changed during the iteration. |
| if (!stability_dependency) { |
| inference.InsertMapChecks(jsgraph(), &effect, control, p.feedback()); |
| } |
| |
| 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. |
| Node* checkpoint_params[] = {receiver, fncallback, this_arg, k, |
| original_length}; |
| const int stack_parameters = arraysize(checkpoint_params); |
| |
| 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* value = graph()->NewNode( |
| common()->Phi(MachineRepresentation::kTagged, 2), |
| jsgraph()->TrueConstant(), jsgraph()->FalseConstant(), control); |
| |
| // Introduce proper LoopExit/LoopExitEffect/LoopExitValue to mark |
| // {loop} as a candidate for loop peeling (crbug.com/v8/8273). |
| control = graph()->NewNode(common()->LoopExit(), control, loop); |
| effect = graph()->NewNode(common()->LoopExitEffect(), effect, control); |
| value = graph()->NewNode(common()->LoopExitValue(), 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, value, effect, control); |
| return Replace(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(); |
| } |
| |
| Node* receiver = NodeProperties::GetValueInput(node, 1); |
| Node* effect = NodeProperties::GetEffectInput(node); |
| Node* control = NodeProperties::GetControlInput(node); |
| |
| MapInference inference(broker(), receiver, effect); |
| if (!inference.HaveMaps()) return NoChange(); |
| MapHandles const& receiver_maps = inference.GetMaps(); |
| |
| ElementsKind kind; |
| if (!CanInlineArrayIteratingBuiltin(broker(), receiver_maps, &kind)) { |
| return inference.NoChange(); |
| } |
| if (IsHoleyElementsKind(kind)) { |
| if (!dependencies()->DependOnNoElementsProtector()) UNREACHABLE(); |
| } |
| inference.RelyOnMapsPreferStability(dependencies(), jsgraph(), &effect, |
| control, p.feedback()); |
| |
| Callable const callable = search_variant == SearchVariant::kIndexOf |
| ? GetCallableForArrayIndexOf(kind, isolate()) |
| : GetCallableForArrayIncludes(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* 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(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, |
| const SharedFunctionInfoRef& 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); |
| 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(); |
| |
| // Try to determine the {receiver} map. |
| MapInference inference(broker(), receiver, effect); |
| if (!inference.HaveMaps()) return NoChange(); |
| MapHandles const& receiver_maps = inference.GetMaps(); |
| |
| ElementsKind kind; |
| if (!CanInlineArrayIteratingBuiltin(broker(), receiver_maps, &kind)) { |
| return inference.NoChange(); |
| } |
| if (!dependencies()->DependOnArraySpeciesProtector()) |
| return inference.NoChange(); |
| if (IsHoleyElementsKind(kind)) { |
| if (!dependencies()->DependOnNoElementsProtector()) UNREACHABLE(); |
| } |
| bool const stability_dependency = inference.RelyOnMapsPreferStability( |
| dependencies(), jsgraph(), &effect, control, p.feedback()); |
| |
| Node* k = jsgraph()->ZeroConstant(); |
| 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. |
| Node* checkpoint_params[] = {receiver, fncallback, this_arg, k, |
| original_length}; |
| const int stack_parameters = arraysize(checkpoint_params); |
| |
| 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::kNone), |
| 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* checkpoint_params[] = {receiver, fncallback, this_arg, k, |
| original_length}; |
| const int stack_parameters = arraysize(checkpoint_params); |
| 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); |
| } |
| |
| // Deopt if the map has changed during the iteration. |
| if (!stability_dependency) { |
| inference.InsertMapChecks(jsgraph(), &effect, control, p.feedback()); |
| } |
| |
| 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. |
| Node* checkpoint_params[] = {receiver, fncallback, this_arg, k, |
| original_length}; |
| const int stack_parameters = arraysize(checkpoint_params); |
| |
| 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* value = graph()->NewNode( |
| common()->Phi(MachineRepresentation::kTagged, 2), |
| jsgraph()->FalseConstant(), jsgraph()->TrueConstant(), control); |
| |
| // Introduce proper LoopExit/LoopExitEffect/LoopExitValue to mark |
| // {loop} as a candidate for loop peeling (crbug.com/v8/8273). |
| control = graph()->NewNode(common()->LoopExit(), control, loop); |
| effect = graph()->NewNode(common()->LoopExitEffect(), effect, control); |
| value = graph()->NewNode(common()->LoopExitValue(), 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, value, effect, control); |
| return Replace(value); |
| } |
| |
| Reduction JSCallReducer::ReduceCallApiFunction( |
| Node* node, const SharedFunctionInfoRef& shared) { |
| DisallowHeapAccessIf no_heap_acess(FLAG_concurrent_inlining); |
| |
| DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); |
| CallParameters const& p = CallParametersOf(node->op()); |
| int const argc = static_cast<int>(p.arity()) - 2; |
| Node* target = NodeProperties::GetValueInput(node, 0); |
| Node* global_proxy = |
| jsgraph()->Constant(native_context().global_proxy_object()); |
| Node* receiver = (p.convert_mode() == ConvertReceiverMode::kNullOrUndefined) |
| ? global_proxy |
| : NodeProperties::GetValueInput(node, 1); |
| Node* holder; |
| Node* effect = NodeProperties::GetEffectInput(node); |
| Node* control = NodeProperties::GetControlInput(node); |
| Node* context = NodeProperties::GetContextInput(node); |
| Node* frame_state = NodeProperties::GetFrameStateInput(node); |
| |
| if (!shared.function_template_info().has_value()) { |
| TRACE_BROKER_MISSING( |
| broker(), "FunctionTemplateInfo for function with SFI " << shared); |
| return NoChange(); |
| } |
| |
| // See if we can optimize this API call to {shared}. |
| FunctionTemplateInfoRef function_template_info( |
| shared.function_template_info().value()); |
| |
| if (!function_template_info.has_call_code()) return NoChange(); |
| |
| if (function_template_info.accept_any_receiver() && |
| function_template_info.is_signature_undefined()) { |
| // We might be able to |
| // optimize the API call depending on the {function_template_info}. |
| // If the API function accepts any kind of {receiver}, we only need to |
| // ensure that the {receiver} is actually a JSReceiver at this point, |
| // and also pass that as the {holder}. There are two independent bits |
| // here: |
| // |
| // a. When the "accept any receiver" bit is set, it means we don't |
| // need to perform access checks, even if the {receiver}'s map |
| // has the "needs access check" bit set. |
| // b. When the {function_template_info} has no signature, we don't |
| // need to do the compatible receiver check, since all receivers |
| // are considered compatible at that point, and the {receiver} |
| // will be pass as the {holder}. |
| // |
| receiver = holder = effect = |
| graph()->NewNode(simplified()->ConvertReceiver(p.convert_mode()), |
| receiver, global_proxy, effect, control); |
| } else { |
| // Try to infer the {receiver} maps from the graph. |
| MapInference inference(broker(), receiver, effect); |
| if (inference.HaveMaps()) { |
| MapHandles const& receiver_maps = inference.GetMaps(); |
| MapRef first_receiver_map(broker(), receiver_maps[0]); |
| |
| // See if we can constant-fold the compatible receiver checks. |
| HolderLookupResult api_holder = |
| function_template_info.LookupHolderOfExpectedType(first_receiver_map, |
| false); |
| if (api_holder.lookup == CallOptimization::kHolderNotFound) |
| return inference.NoChange(); |
| |
| // Check that all {receiver_maps} are actually JSReceiver maps and |
| // that the {function_template_info} accepts them without access |
| // checks (even if "access check needed" is set for {receiver}). |
| // |
| // Note that we don't need to know the concrete {receiver} maps here, |
| // meaning it's fine if the {receiver_maps} are unreliable, and we also |
| // don't need to install any stability dependencies, since the only |
| // relevant information regarding the {receiver} is the Map::constructor |
| // field on the root map (which is different from the JavaScript exposed |
| // "constructor" property) and that field cannot change. |
| // |
| // So if we know that {receiver} had a certain constructor at some point |
| // in the past (i.e. it had a certain map), then this constructor is going |
| // to be the same later, since this information cannot change with map |
| // transitions. |
| // |
| // The same is true for the instance type, e.g. we still know that the |
| // instance type is JSObject even if that information is unreliable, and |
| // the "access check needed" bit, which also cannot change later. |
| CHECK(first_receiver_map.IsJSReceiverMap()); |
| CHECK(!first_receiver_map.is_access_check_needed() || |
| function_template_info.accept_any_receiver()); |
| |
| for (size_t i = 1; i < receiver_maps.size(); ++i) { |
| MapRef receiver_map(broker(), receiver_maps[i]); |
| HolderLookupResult holder_i = |
| function_template_info.LookupHolderOfExpectedType(receiver_map, |
| false); |
| |
| if (api_holder.lookup != holder_i.lookup) return inference.NoChange(); |
| if (!(api_holder.holder.has_value() && holder_i.holder.has_value())) |
| return inference.NoChange(); |
| if (!api_holder.holder->equals(*holder_i.holder)) |
| return inference.NoChange(); |
| |
| CHECK(receiver_map.IsJSReceiverMap()); |
| CHECK(!receiver_map.is_access_check_needed() || |
| function_template_info.accept_any_receiver()); |
| } |
| |
| if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation && |
| !inference.RelyOnMapsViaStability(dependencies())) { |
| // We were not able to make the receiver maps reliable without map |
| // checks but doing map checks would lead to deopt loops, so give up. |
| return inference.NoChange(); |
| } |
| |
| // TODO(neis): The maps were used in a way that does not actually require |
| // map checks or stability dependencies. |
| inference.RelyOnMapsPreferStability(dependencies(), jsgraph(), &effect, |
| control, p.feedback()); |
| |
| // Determine the appropriate holder for the {lookup}. |
| holder = api_holder.lookup == CallOptimization::kHolderFound |
| ? jsgraph()->Constant(*api_holder.holder) |
| : receiver; |
| } else { |
| // We don't have enough information to eliminate the access check |
| // and/or the compatible receiver check, so use the generic builtin |
| // that does those checks dynamically. This is still significantly |
| // faster than the generic call sequence. |
| Builtins::Name builtin_name; |
| if (function_template_info.accept_any_receiver()) { |
| builtin_name = Builtins::kCallFunctionTemplate_CheckCompatibleReceiver; |
| } else if (function_template_info.is_signature_undefined()) { |
| builtin_name = Builtins::kCallFunctionTemplate_CheckAccess; |
| } else { |
| builtin_name = |
| Builtins::kCallFunctionTemplate_CheckAccessAndCompatibleReceiver; |
| } |
| |
| // The CallFunctionTemplate builtin requires the {receiver} to be |
| // an actual JSReceiver, so make sure we do the proper conversion |
| // first if necessary. |
| receiver = holder = effect = |
| graph()->NewNode(simplified()->ConvertReceiver(p.convert_mode()), |
| receiver, global_proxy, effect, control); |
| |
| Callable callable = Builtins::CallableFor(isolate(), builtin_name); |
| auto call_descriptor = Linkage::GetStubCallDescriptor( |
| graph()->zone(), callable.descriptor(), |
| argc + 1 /* implicit receiver */, CallDescriptor::kNeedsFrameState); |
| node->InsertInput(graph()->zone(), 0, |
| jsgraph()->HeapConstant(callable.code())); |
| node->ReplaceInput(1, jsgraph()->Constant(function_template_info)); |
| node->InsertInput(graph()->zone(), 2, jsgraph()->Constant(argc)); |
| node->ReplaceInput(3, receiver); // Update receiver input. |
| node->ReplaceInput(6 + argc, effect); // Update effect input. |
| NodeProperties::ChangeOp(node, common()->Call(call_descriptor)); |
| return Changed(node); |
| } |
| } |
| |
| // TODO(turbofan): Consider introducing a JSCallApiCallback operator for |
| // this and lower it during JSGenericLowering, and unify this with the |
| // JSNativeContextSpecialization::InlineApiCall method a bit. |
| if (!function_template_info.call_code().has_value()) { |
| TRACE_BROKER_MISSING(broker(), "call code for function template info " |
| << function_template_info); |
| return NoChange(); |
| } |
| CallHandlerInfoRef call_handler_info = *function_template_info.call_code(); |
| Callable call_api_callback = CodeFactory::CallApiCallback(isolate()); |
| CallInterfaceDescriptor cid = call_api_callback.descriptor(); |
| auto call_descriptor = Linkage::GetStubCallDescriptor( |
| graph()->zone(), cid, argc + 1 /* implicit receiver */, |
| CallDescriptor::kNeedsFrameState); |
| ApiFunction api_function(call_handler_info.callback()); |
| ExternalReference function_reference = ExternalReference::Create( |
| &api_function, ExternalReference::DIRECT_API_CALL); |
| |
| Node* continuation_frame_state = CreateGenericLazyDeoptContinuationFrameState( |
| jsgraph(), shared, target, context, receiver, frame_state); |
| |
| node->InsertInput(graph()->zone(), 0, |
| jsgraph()->HeapConstant(call_api_callback.code())); |
| node->ReplaceInput(1, jsgraph()->ExternalConstant(function_reference)); |
| node->InsertInput(graph()->zone(), 2, jsgraph()->Constant(argc)); |
| node->InsertInput(graph()->zone(), 3, |
| jsgraph()->Constant(call_handler_info.data())); |
| node->InsertInput(graph()->zone(), 4, holder); |
| node->ReplaceInput(5, receiver); // Update receiver input. |
| node->ReplaceInput(7 + argc, continuation_frame_state); |
| node->ReplaceInput(8 + 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); |
| |
| // 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( |
| static_cast<int>(JSArray::kLengthOffset) == |
| static_cast<int>(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<int>(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<int>(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; |
| |
| int formal_parameter_count; |
| { |
| Handle<SharedFunctionInfo> shared; |
| if (!state_info.shared_info().ToHandle(&shared)) return NoChange(); |
| formal_parameter_count = SharedFunctionInfoRef(broker(), 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) { |
| if (!dependencies()->DependOnArrayIteratorProtector()) return NoChange(); |
| } |
| |
| // 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<int>(++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(static_cast<int>(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; |
| } |
| |
| base::Optional<HeapObjectRef> GetHeapObjectFeedback( |
| JSHeapBroker* broker, const FeedbackNexus& nexus) { |
| HeapObject object; |
| if (!nexus.GetFeedback()->GetHeapObject(&object)) return base::nullopt; |
| return HeapObjectRef(broker, handle(object, broker->isolate())); |
| } |
| |
| } // 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()) { |
| ObjectRef target_ref = m.Ref(broker()); |
| if (target_ref.IsJSFunction()) { |
| JSFunctionRef function = target_ref.AsJSFunction(); |
| function.Serialize(); |
| |
| // Don't inline cross native context. |
| if (!function.native_context().equals(native_context())) { |
| return NoChange(); |
| } |
| |
| return ReduceJSCall(node, function.shared()); |
| } else if (target_ref.IsJSBoundFunction()) { |
| JSBoundFunctionRef function = target_ref.AsJSBoundFunction(); |
| function.Serialize(); |
| |
| ObjectRef bound_this = function.bound_this(); |
| ConvertReceiverMode const convert_mode = |
| bound_this.IsNullOrUndefined() |
| ? ConvertReceiverMode::kNullOrUndefined |
| : ConvertReceiverMode::kNotNullOrUndefined; |
| |
| // Patch {node} to use [[BoundTargetFunction]] and [[BoundThis]]. |
| NodeProperties::ReplaceValueInput( |
| node, jsgraph()->Constant(function.bound_target_function()), 0); |
| NodeProperties::ReplaceValueInput(node, jsgraph()->Constant(bound_this), |
| 1); |
| |
| // Insert the [[BoundArguments]] for {node}. |
| FixedArrayRef bound_arguments = function.bound_arguments(); |
| for (int i = 0; i < bound_arguments.length(); ++i) { |
| node->InsertInput(graph()->zone(), i + 2, |
| jsgraph()->Constant(bound_arguments.get(i))); |
| 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, SharedFunctionInfoRef(broker(), 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<int>(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(broker(), 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()) { |
| return ReduceSoftDeoptimize( |
| node, DeoptimizeReason::kInsufficientTypeFeedbackForCall); |
| } |
| |
| base::Optional<HeapObjectRef> feedback = |
| GetHeapObjectFeedback(broker(), nexus); |
| if (feedback.has_value() && ShouldUseCallICFeedback(target) && |
| feedback->map().is_callable()) { |
| 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, |
| const SharedFunctionInfoRef& 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::kMathHypot: |
| return ReduceMathHypot(node); |
| 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(), |
| node); |
| case Builtins::kStringPrototypeSubstring: |
| return ReduceStringPrototypeSubstring(node); |
| case Builtins::kStringPrototypeSlice: |
| return ReduceStringPrototypeSlice(node); |
| case Builtins::kStringPrototypeSubstr: |
| return ReduceStringPrototypeSubstr(node); |
| case Builtins::kStringPrototypeStartsWith: |
| return ReduceStringPrototypeStartsWith(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); |
| 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::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::kPromiseResolveTrampoline: |
| return ReducePromiseResolveTrampoline(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); |
| case Builtins::kBigIntAsUintN: |
| return ReduceBigIntAsUintN(node); |
| default: |
| break; |
| } |
| |
| if (shared.object()->IsApiFunction()) { |
| return ReduceCallApiFunction(node, shared); |
| } |
| return NoChange(); |
| } |
| |
| Reduction JSCallReducer::ReduceJSCallWithArrayLike(Node* node) { |
| // TODO(mslekova): Remove once ReduceJSCallWithArrayLike is brokerized. |
| AllowHandleDereference allow_handle_dereference; |
| AllowHandleAllocation allow_handle_allocation; |
| |
| 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<int>(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<int>(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()) { |
| return ReduceSoftDeoptimize( |
| node, DeoptimizeReason::kInsufficientTypeFeedbackForConstruct); |
| } |
| |
| base::Optional<HeapObjectRef> feedback = |
| GetHeapObjectFeedback(broker(), nexus); |
| if (feedback.has_value() && feedback->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. |
| |
| Node* array_function = |
| jsgraph()->Constant(native_context().array_function()); |
| |
| // 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, feedback->AsAllocationSite().object())); |
| return Changed(node); |
| } else if (feedback.has_value() && |
| !HeapObjectMatcher(new_target).HasValue() && |
| feedback->map().is_constructor()) { |
| Node* new_target_feedback = jsgraph()->Constant(*feedback); |
| |
| // 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()) { |
| HeapObjectRef target_ref = m.Ref(broker()); |
| |
| // Raise a TypeError if the {target} is not a constructor. |
| if (!target_ref.map().is_constructor()) { |
| NodeProperties::ReplaceValueInputs(node, target); |
| NodeProperties::ChangeOp(node, |
| javascript()->CallRuntime( |
| Runtime::kThrowConstructedNonConstructable)); |
| return Changed(node); |
| } |
| |
| if (target_ref.IsJSFunction()) { |
| JSFunctionRef function = target_ref.AsJSFunction(); |
| function.Serialize(); |
| |
| // Do not reduce constructors with break points. |
| if (function.shared().HasBreakInfo()) return NoChange(); |
| |
| // Don't inline cross native context. |
| if (!function.native_context().equals(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. |
| // 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, Handle<AllocationSite>())); |
| 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.Ref(broker()).equals(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, function.shared()); |
| default: |
| break; |
| } |
| } else if (target_ref.IsJSBoundFunction()) { |
| JSBoundFunctionRef function = target_ref.AsJSBoundFunction(); |
| function.Serialize(); |
| |
| ObjectRef bound_target_function = function.bound_target_function(); |
| FixedArrayRef bound_arguments = function.bound_arguments(); |
| |
| // 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(bound_arguments.get(i))); |
| 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<int>(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<int>(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) { |
| if (!(flags() & kBailoutOnUninitialized)) return NoChange(); |
| |
| Node* effect = NodeProperties::GetEffectInput(node); |
| Node* control = NodeProperties::GetControlInput(node); |
| Node* frame_state = |
| NodeProperties::FindFrameStateBefore(node, jsgraph()->Dead()); |
| 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); |
| } |
| |
| Node* JSCallReducer::LoadReceiverElementsKind(Node* receiver, Node** effect, |
| Node** 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)); |
| return receiver_elements_kind; |
| } |
| |
| void JSCallReducer::CheckIfElementsKind(Node* receiver_elements_kind, |
| ElementsKind kind, Node* control, |
| Node** if_true, Node** if_false) { |
| Node* is_packed_kind = |
| graph()->NewNode(simplified()->NumberEqual(), receiver_elements_kind, |
| jsgraph()->Constant(GetPackedElementsKind(kind))); |
| Node* packed_branch = |
| graph()->NewNode(common()->Branch(), is_packed_kind, control); |
| Node* if_packed = graph()->NewNode(common()->IfTrue(), packed_branch); |
| |
| if (IsHoleyElementsKind(kind)) { |
| Node* if_not_packed = graph()->NewNode(common()->IfFalse(), packed_branch); |
| Node* is_holey_kind = |
| graph()->NewNode(simplified()->NumberEqual(), receiver_elements_kind, |
| jsgraph()->Constant(GetHoleyElementsKind(kind))); |
| Node* holey_branch = |
| graph()->NewNode(common()->Branch(), is_holey_kind, if_not_packed); |
| Node* if_holey = graph()->NewNode(common()->IfTrue(), holey_branch); |
| |
| Node* if_not_packed_not_holey = |
| graph()->NewNode(common()->IfFalse(), holey_branch); |
| |
| *if_true = graph()->NewNode(common()->Merge(2), if_packed, if_holey); |
| *if_false = if_not_packed_not_holey; |
| } else { |
| *if_true = if_packed; |
| *if_false = graph()->NewNode(common()->IfFalse(), packed_branch); |
| } |
| } |
| |
| // 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(); |
| } |
| |
| int const num_values = node->op()->ValueInputCount() - 2; |
| Node* receiver = NodeProperties::GetValueInput(node, 1); |
| Node* effect = NodeProperties::GetEffectInput(node); |
| Node* control = NodeProperties::GetControlInput(node); |
| |
| MapInference inference(broker(), receiver, effect); |
| if (!inference.HaveMaps()) return NoChange(); |
| MapHandles const& receiver_maps = inference.GetMaps(); |
| |
| std::vector<ElementsKind> kinds; |
| if (!CanInlineArrayResizingBuiltin(broker(), receiver_maps, kinds, true)) { |
| return inference.NoChange(); |
| } |
| if (!dependencies()->DependOnNoElementsProtector()) UNREACHABLE(); |
| inference.RelyOnMapsPreferStability(dependencies(), jsgraph(), &effect, |
| control, p.feedback()); |
| |
| std::vector<Node*> controls_to_merge; |
| std::vector<Node*> effects_to_merge; |
| std::vector<Node*> values_to_merge; |
| Node* return_value = jsgraph()->UndefinedConstant(); |
| |
| Node* receiver_elements_kind = |
| LoadReceiverElementsKind(receiver, &effect, &control); |
| Node* next_control = control; |
| Node* next_effect = effect; |
| for (size_t i = 0; i < kinds.size(); i++) { |
| ElementsKind kind = kinds[i]; |
| control = next_control; |
| effect = next_effect; |
| // We do not need branch for the last elements kind. |
| if (i != kinds.size() - 1) { |
| CheckIfElementsKind(receiver_elements_kind, kind, control, &control, |
| &next_control); |
| } |
| |
| // Collect the value inputs to push. |
| std::vector<Node*> 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); |
| return_value = length; |
| |
| // Check if we have any {values} to push. |
| if (num_values > 0) { |
| // Compute the resulting "length" of the {receiver}. |
| Node* new_length = return_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); |
| } |
| } |
| |
| controls_to_merge.push_back(control); |
| effects_to_merge.push_back(effect); |
| values_to_merge.push_back(return_value); |
| } |
| |
| if (controls_to_merge.size() > 1) { |
| int const count = static_cast<int>(controls_to_merge.size()); |
| |
| control = graph()->NewNode(common()->Merge(count), count, |
| &controls_to_merge.front()); |
| effects_to_merge.push_back(control); |
| effect = graph()->NewNode(common()->EffectPhi(count), count + 1, |
| &effects_to_merge.front()); |
| values_to_merge.push_back(control); |
| return_value = |
| graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, count), |
| count + 1, &values_to_merge.front()); |
| } |
| |
| ReplaceWithValue(node, return_value, effect, control); |
| return Replace(return_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(); |
| } |
| |
| Node* receiver = NodeProperties::GetValueInput(node, 1); |
| Node* effect = NodeProperties::GetEffectInput(node); |
| Node* control = NodeProperties::GetControlInput(node); |
| |
| MapInference inference(broker(), receiver, effect); |
| if (!inference.HaveMaps()) return NoChange(); |
| MapHandles const& receiver_maps = inference.GetMaps(); |
| |
| std::vector<ElementsKind> kinds; |
| if (!CanInlineArrayResizingBuiltin(broker(), receiver_maps, kinds)) { |
| return inference.NoChange(); |
| } |
| if (!dependencies()->DependOnNoElementsProtector()) UNREACHABLE(); |
| inference.RelyOnMapsPreferStability(dependencies(), jsgraph(), &effect, |
| control, p.feedback()); |
| |
| std::vector<Node*> controls_to_merge; |
| std::vector<Node*> effects_to_merge; |
| std::vector<Node*> values_to_merge; |
| Node* value = jsgraph()->UndefinedConstant(); |
| |
| Node* receiver_elements_kind = |
| LoadReceiverElementsKind(receiver, &effect, &control); |
| Node* next_control = control; |
| Node* next_effect = effect; |
| for (size_t i = 0; i < kinds.size(); i++) { |
| ElementsKind kind = kinds[i]; |
| control = next_control; |
| effect = next_effect; |
| // We do not need branch for the last elements kind. |
| if (i != kinds.size() - 1) { |
| CheckIfElementsKind(receiver_elements_kind, kind, control, &control, |
| &next_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); |
| 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); |
| } |
| |
| controls_to_merge.push_back(control); |
| effects_to_merge.push_back(effect); |
| values_to_merge.push_back(value); |
| } |
| |
| if (controls_to_merge.size() > 1) { |
| int const count = static_cast<int>(controls_to_merge.size()); |
| |
| control = graph()->NewNode(common()->Merge(count), count, |
| &controls_to_merge.front()); |
| effects_to_merge.push_back(control); |
| effect = graph()->NewNode(common()->EffectPhi(count), count + 1, |
| &effects_to_merge.front()); |
| values_to_merge.push_back(control); |
| value = |
| graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, count), |
| count + 1, &values_to_merge.front()); |
| } |
| |
| 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(); |
| } |
| |
| 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); |
| |
| MapInference inference(broker(), receiver, effect); |
| if (!inference.HaveMaps()) return NoChange(); |
| MapHandles const& receiver_maps = inference.GetMaps(); |
| |
| std::vector<ElementsKind> kinds; |
| if (!CanInlineArrayResizingBuiltin(broker(), receiver_maps, kinds)) { |
| return inference.NoChange(); |
| } |
| if (!dependencies()->DependOnNoElementsProtector()) UNREACHABLE(); |
| inference.RelyOnMapsPreferStability(dependencies(), jsgraph(), &effect, |
| control, p.feedback()); |
| |
| std::vector<Node*> controls_to_merge; |
| std::vector<Node*> effects_to_merge; |
| std::vector<Node*> values_to_merge; |
| Node* value = jsgraph()->UndefinedConstant(); |
| |
| Node* receiver_elements_kind = |
| LoadReceiverElementsKind(receiver, &effect, &control); |
| Node* next_control = control; |
| Node* next_effect = effect; |
| for (size_t i = 0; i < kinds.size(); i++) { |
| ElementsKind kind = kinds[i]; |
| control = next_control; |
| effect = next_effect; |
| // We do not need branch for the last elements kind. |
| if (i != kinds.size() - 1) { |
| CheckIfElementsKind(receiver_elements_kind, kind, control, &control, |
| &next_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); |
| 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); |
| } |
| |
| controls_to_merge.push_back(control); |
| effects_to_merge.push_back(effect); |
| values_to_merge.push_back(value); |
| } |
| |
| if (controls_to_merge.size() > 1) { |
| int const count = static_cast<int>(controls_to_merge.size()); |
| |
| control = graph()->NewNode(common()->Merge(count), count, |
| &controls_to_merge.front()); |
| effects_to_merge.push_back(control); |
| effect = graph()->NewNode(common()->EffectPhi(count), count + 1, |
| &effects_to_merge.front()); |
| values_to_merge.push_back(control); |
| value = |
| graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, count), |
| count + 1, &values_to_merge.front()); |
| } |
| |
| 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(); |
| } |
| |
| Node* receiver = NodeProperties::GetValueInput(node, 1); |
| Node* start = node->op()->ValueInputCount() > 2 |
| ? NodeProperties::GetValueInput(node, 2) |
| : jsgraph()->ZeroConstant(); |
| Node* end = 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); |
| |
| // Optimize for the case where we simply clone the {receiver}, |
| // i.e. when the {start} is zero and the {end} is undefined |
| // (meaning it will be set to {receiver}s "length" property). |
| if (!NumberMatcher(start).Is(0) || |
| !HeapObjectMatcher(end).Is(factory()->undefined_value())) { |
| return NoChange(); |
| } |
| |
| MapInference inference(broker(), receiver, effect); |
| if (!inference.HaveMaps()) return NoChange(); |
| MapHandles const& receiver_maps = inference.GetMaps(); |
| |
| // Check that the maps are of JSArray (and more). |
| // TODO(turbofan): Consider adding special case for the common pattern |
| // `slice.call(arguments)`, for example jQuery makes heavy use of that. |
| bool can_be_holey = false; |
| for (Handle<Map> map : receiver_maps) { |
| MapRef receiver_map(broker(), map); |
| if (!receiver_map.supports_fast_array_iteration()) |
| return inference.NoChange(); |
| if (IsHoleyElementsKind(receiver_map.elements_kind())) { |
| can_be_holey = true; |
| } |
| } |
| |
| if (!dependencies()->DependOnArraySpeciesProtector()) |
| return inference.NoChange(); |
| if (can_be_holey) { |
| if (!dependencies()->DependOnNoElementsProtector()) UNREACHABLE(); |
| } |
| inference.RelyOnMapsPreferStability(dependencies(), jsgraph(), &effect, |
| control, p.feedback()); |
| |
| // TODO(turbofan): We can do even better here, either adding a CloneArray |
| // simplified operator, whose output type indicates that it's an Array, |
| // saving subsequent checks, or yet better, by introducing new operators |
| // CopySmiOrObjectElements / CopyDoubleElements and inlining the JSArray |
| // allocation in here. That way we'd even get escape analysis and scalar |
| // replacement to help in some cases. |
| 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. |
| MapInference inference(broker(), receiver, effect); |
| if (!inference.HaveMaps() || !inference.AllOfInstanceTypesAreJSReceiver()) { |
| 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); |
| } |
| |
| // 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(); |
| } |
| |
| if (iterator->opcode() != IrOpcode::kJSCreateArrayIterator) return NoChange(); |
| |
| IterationKind const iteration_kind = |
| CreateArrayIteratorParametersOf(iterator->op()).kind(); |
| Node* iterated_object = NodeProperties::GetValueInput(iterator, 0); |
| Node* iterator_effect = NodeProperties::GetEffectInput(iterator); |
| |
| MapInference inference(broker(), iterated_object, iterator_effect); |
| if (!inference.HaveMaps()) return NoChange(); |
| MapHandles const& iterated_object_maps = inference.GetMaps(); |
| |
| // Check that various {iterated_object_maps} have compatible elements kinds. |
| ElementsKind elements_kind = |
| MapRef(broker(), iterated_object_maps[0]).elements_kind(); |
| if (IsTypedArrayElementsKind(elements_kind)) { |
| // TurboFan doesn't support loading from BigInt typed arrays yet. |
| if (elements_kind == BIGUINT64_ELEMENTS || |
| elements_kind == BIGINT64_ELEMENTS) { |
| return inference.NoChange(); |
| } |
| for (Handle<Map> map : iterated_object_maps) { |
| MapRef iterated_object_map(broker(), map); |
| if (iterated_object_map.elements_kind() != elements_kind) { |
| return inference.NoChange(); |
| } |
| } |
| } else { |
| if (!CanInlineArrayIteratingBuiltin(broker(), iterated_object_maps, |
| &elements_kind)) { |
| return inference.NoChange(); |
| } |
| } |
| |
| if (IsHoleyElementsKind(elements_kind)) { |
| if (!dependencies()->DependOnNoElementsProtector()) UNREACHABLE(); |
| } |
| // Since the map inference was done relative to {iterator_effect} rather than |
| // {effect}, we need to guard the use of the map(s) even when the inference |
| // was reliable. |
| inference.InsertMapChecks(jsgraph(), &effect, control, p.feedback()); |
| |
| if (IsTypedArrayElementsKind(elements_kind)) { |
| // See if we can skip the detaching check. |
| if (!dependencies()->DependOnArrayBufferDetachingProtector()) { |
| // Bail out if the {iterated_object}s JSArrayBuffer was detached. |
| 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::WasDetachedBit::kMask)), |
| jsgraph()->ZeroConstant()); |
| effect = graph()->NewNode( |
| simplified()->CheckIf(DeoptimizeReason::kArrayBufferWasDetached, |
| 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 (IsTypedArrayElementsKind(elements_kind)) { |
| index_access.type = TypeCache::Get()->kJSTypedArrayLengthType; |
| index_access.machine_type = MachineType::TypeCompressedTaggedSigned(); |
| 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 = |
| IsTypedArrayElementsKind(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::kNone), 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 (IsTypedArrayElementsKind(elements_kind)) { |
| Node* base_ptr = etrue = |
| graph()->NewNode(simplified()->LoadField( |
| AccessBuilder::ForJSTypedArrayBasePointer()), |
| iterated_object, etrue, if_true); |
| Node* external_ptr = etrue = graph()->NewNode( |
| simplified()->LoadField( |
| AccessBuilder::ForJSTypedArrayExternalPointer()), |
| iterated_object, 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 (!IsTypedArrayElementsKind(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.20 |
| // String.prototype.startsWith ( searchString [ , position ] ) |
| Reduction JSCallReducer::ReduceStringPrototypeStartsWith(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* value = jsgraph()->FalseConstant(); |
| ReplaceWithValue(node, value); |
| return Replace(value); |
| } |
| |
| Node* string = NodeProperties::GetValueInput(node, 1); |
| Node* search_string = NodeProperties::GetValueInput(node, 2); |
| Node* position = node->op()->ValueInputCount() >= 4 |
| ? NodeProperties::GetValueInput(node, 3) |
| : jsgraph()->ZeroConstant(); |
| Node* effect = NodeProperties::GetEffectInput(node); |
| Node* control = NodeProperties::GetControlInput(node); |
| |
| HeapObjectMatcher m(search_string); |
| if (m.HasValue()) { |
| ObjectRef target_ref = m.Ref(broker()); |
| if (target_ref.IsString()) { |
| StringRef str = target_ref.AsString(); |
| if (str.length() == 1) { |
| string = effect = graph()->NewNode( |
| simplified()->CheckString(p.feedback()), string, effect, control); |
| position = effect = graph()->NewNode( |
| simplified()->CheckSmi(p.feedback()), position, effect, control); |
| |
| Node* string_length = |
| graph()->NewNode(simplified()->StringLength(), string); |
| Node* unsigned_position = graph()->NewNode( |
| simplified()->NumberMax(), position, jsgraph()->ZeroConstant()); |
| |
| Node* check = graph()->NewNode(simplified()->NumberLessThan(), |
| unsigned_position, string_length); |
| Node* branch = graph()->NewNode(common()->Branch(BranchHint::kNone), |
| check, control); |
| |
| Node* if_false = graph()->NewNode(common()->IfFalse(), branch); |
| Node* efalse = effect; |
| Node* vfalse = jsgraph()->FalseConstant(); |
| |
| Node* if_true = graph()->NewNode(common()->IfTrue(), branch); |
| Node* etrue = effect; |
| Node* vtrue; |
| { |
| Node* masked_position = |
| graph()->NewNode(simplified()->PoisonIndex(), unsigned_position); |
| Node* string_first = etrue = |
| graph()->NewNode(simplified()->StringCharCodeAt(), string, |
| masked_position, etrue, if_true); |
| |
| Node* search_first = jsgraph()->Constant(str.GetFirstChar()); |
| vtrue = graph()->NewNode(simplified()->NumberEqual(), string_first, |
| search_first); |
| } |
| |
| 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); |
| } |
| } |
| } |
| |
| return NoChange(); |
| } |
| |
| // 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(), 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); |
| |
| MapInference inference(broker(), receiver, effect); |
| if (!inference.HaveMaps() || |
| !inference.AllOfInstanceTypesAre(JS_STRING_ITERATOR_TYPE)) { |
| return NoChange(); |
| } |
| |
| 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::kNone), check0, control); |
| |
| Node* etrue0 = effect; |
| Node* if_true0 = graph()->NewNode(common()->IfTrue(), branch0); |
| Node* done_true; |
| Node* vtrue0; |
| { |
| done_true = jsgraph()->FalseConstant(); |
| vtrue0 = etrue0 = graph()->NewNode(simplified()->StringFromCodePointAt(), |
| string, index, etrue0, if_true0); |
| |
| // 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); |
| } |
| |
| // ES #sec-string.prototype.concat |
| Reduction JSCallReducer::ReduceStringPrototypeConcat(Node* node) { |
| 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); |
| } |
| |
| Node* JSCallReducer::CreateArtificialFrameState( |
| Node* node, Node* outer_frame_state, int parameter_count, |
| BailoutId bailout_id, FrameStateType frame_state_type, |
| const SharedFunctionInfoRef& shared, Node* context) { |
| const FrameStateFunctionInfo* state_info = |
| common()->CreateFrameStateFunctionInfo( |
| frame_state_type, parameter_count + 1, 0, shared.object()); |
| |
| 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<Node*> 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<int>(params.size()), SparseInputMask::Dense()); |
| Node* params_node = graph()->NewNode( |
| op_param, static_cast<int>(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) { |
| DisallowHeapAccessIf no_heap_access(FLAG_concurrent_inlining); |
| |
| DCHECK_EQ(IrOpcode::kJSConstruct, node->opcode()); |
| ConstructParameters const& p = ConstructParametersOf(node->op()); |
| int arity = static_cast<int>(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(); |
| |
| // Only handle builtins Promises, not subclasses. |
| if (target != new_target) return NoChange(); |
| |
| if (!dependencies()->DependOnPromiseHookProtector()) return NoChange(); |
| |
| SharedFunctionInfoRef promise_shared = |
| native_context().promise_function().shared(); |
| |
| // 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<Node*> checkpoint_parameters({ |
| jsgraph()->UndefinedConstant(), /* receiver */ |
| jsgraph()->UndefinedConstant(), /* promise */ |
| jsgraph()->UndefinedConstant(), /* reject function */ |
| jsgraph()->TheHoleConstant() /* exception */ |
| }); |
| int checkpoint_parameters_size = |
| static_cast<int>(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( |
| native_context().scope_info().object(), |
| PromiseBuiltins::kPromiseContextLength - Context::MIN_CONTEXT_SLOTS, |
| FUNCTION_SCOPE), |
| context, effect, control); |
| effect = graph()->NewNode( |
| simplified()->StoreField( |
| AccessBuilder::ForContextSlot(PromiseBuiltins::kPromiseSlot)), |
| promise_context, promise, effect, control); |
| effect = graph()->NewNode( |
| simplified()->StoreField( |
| AccessBuilder::ForContextSlot(PromiseBuiltins::kAlreadyResolvedSlot)), |
| promise_context, jsgraph()->FalseConstant(), effect, control); |
| effect = graph()->NewNode( |
| simplified()->StoreField( |
| AccessBuilder::ForContextSlot(PromiseBuiltins::kDebugEventSlot)), |
| promise_context, jsgraph()->TrueConstant(), effect, control); |
| |
| // Allocate the closure for the resolve case. |
| Node* resolve = effect = CreateClosureFromBuiltinSharedFunctionInfo( |
| native_context().promise_capability_default_resolve_shared_fun(), |
| promise_context, effect, control); |
| |
| // Allocate the closure for the reject case. |
| Node* reject = effect = CreateClosureFromBuiltinSharedFunctionInfo( |
| native_context().promise_capability_default_reject_shared_fun(), |
| promise_context, effect, control); |
| |
| const std::vector<Node*> checkpoint_parameters_continuation( |
| {jsgraph()->UndefinedConstant() /* receiver */, promise, reject}); |
| int checkpoint_parameters_continuation_size = |
| static_cast<int>(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 (!dependencies()->DependOnPromiseHookProtector()) return NoChange(); |
| |
| // 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); |
| } |
| |
| bool JSCallReducer::DoPromiseChecks(MapInference* inference) { |
| if (!inference->HaveMaps()) return false; |
| MapHandles const& receiver_maps = inference->GetMaps(); |
| |
| // Check whether all {receiver_maps} are JSPromise maps and |
| // have the initial Promise.prototype as their [[Prototype]]. |
| for (Handle<Map> map : receiver_maps) { |
| MapRef receiver_map(broker(), map); |
| if (!receiver_map.IsJSPromiseMap()) return false; |
| if (!FLAG_concurrent_inlining) { |
| receiver_map.SerializePrototype(); |
| } else if (!receiver_map.serialized_prototype()) { |
| TRACE_BROKER_MISSING(broker(), "prototype for map " << receiver_map); |
| return false; |
| } |
| if (!receiver_map.prototype().equals( |
| native_context().promise_prototype())) { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| // 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<int>(p.arity() - 2); |
| Node* receiver = NodeProperties::GetValueInput(node, 1); |
| Node* effect = NodeProperties::GetEffectInput(node); |
| Node* control = NodeProperties::GetControlInput(node); |
| |
| MapInference inference(broker(), receiver, effect); |
| if (!DoPromiseChecks(&inference)) return inference.NoChange(); |
| |
| if (!dependencies()->DependOnPromiseThenProtector()) |
| return inference.NoChange(); |
| inference.RelyOnMapsPreferStability(dependencies(), jsgraph(), &effect, |
| control, p.feedback()); |
| |
| // 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(native_context().promise_then()); |
| 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); |
| } |
| |
| Node* JSCallReducer::CreateClosureFromBuiltinSharedFunctionInfo( |
| SharedFunctionInfoRef shared, Node* context, Node* effect, Node* control) { |
| DCHECK(shared.HasBuiltinId()); |
| Callable const callable = Builtins::CallableFor( |
| isolate(), static_cast<Builtins::Name>(shared.builtin_id())); |
| return graph()->NewNode( |
| javascript()->CreateClosure( |
| shared.object(), factory()->many_closures_cell(), callable.code()), |
| context, effect, control); |
| } |
| |
| // ES section #sec-promise.prototype.finally |
| Reduction JSCallReducer::ReducePromisePrototypeFinally(Node* node) { |
| DisallowHeapAccessIf no_heap_access(FLAG_concurrent_inlining); |
| |
| DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); |
| CallParameters const& p = CallParametersOf(node->op()); |
| int arity = static_cast<int>(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(); |
| } |
| |
| MapInference inference(broker(), receiver, effect); |
| if (!DoPromiseChecks(&inference)) return inference.NoChange(); |
| MapHandles const& receiver_maps = inference.GetMaps(); |
| |
| if (!dependencies()->DependOnPromiseHookProtector()) |
| return inference.NoChange(); |
| if (!dependencies()->DependOnPromiseThenProtector()) |
| return inference.NoChange(); |
| if (!dependencies()->DependOnPromiseSpeciesProtector()) |
| return inference.NoChange(); |
| inference.RelyOnMapsPreferStability(dependencies(), jsgraph(), &effect, |
| control, p.feedback()); |
| |
| // 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()->Constant(native_context()); |
| Node* constructor = |
| jsgraph()->Constant(native_context().promise_function()); |
| |
| // Allocate shared context for the closures below. |
| context = etrue = |
| graph()->NewNode(javascript()->CreateFunctionContext( |
| native_context().scope_info().object(), |
| PromiseBuiltins::kPromiseFinallyContextLength - |
| Context::MIN_CONTEXT_SLOTS, |
| FUNCTION_SCOPE), |
| context, etrue, if_true); |
| etrue = graph()->NewNode( |
| simplified()->StoreField( |
| AccessBuilder::ForContextSlot(PromiseBuiltins::kOnFinallySlot)), |
| context, on_finally, etrue, if_true); |
| etrue = graph()->NewNode( |
| simplified()->StoreField( |
| AccessBuilder::ForContextSlot(PromiseBuiltins::kConstructorSlot)), |
| context, constructor, etrue, if_true); |
| |
| // Allocate the closure for the reject case. |
| catch_true = etrue = CreateClosureFromBuiltinSharedFunctionInfo( |
| native_context().promise_catch_finally_shared_fun(), context, etrue, |
| if_true); |
| |
| // Allocate the closure for the fulfill case. |
| then_true = etrue = CreateClosureFromBuiltinSharedFunctionInfo( |
| native_context().promise_then_finally_shared_fun(), 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. |
| { |
| ZoneHandleSet<Map> maps; |
| for (Handle<Map> map : receiver_maps) maps.insert(map, graph()->zone()); |
| effect = graph()->NewNode(simplified()->MapGuard(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(native_context().promise_then()); |
| 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) { |
| DisallowHeapAccessIf no_heap_acess(FLAG_concurrent_inlining); |
| |
| 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); |
| |
| MapInference inference(broker(), receiver, effect); |
| if (!DoPromiseChecks(&inference)) return inference.NoChange(); |
| |
| if (!dependencies()->DependOnPromiseHookProtector()) |
| return inference.NoChange(); |
| if (!dependencies()->DependOnPromiseSpeciesProtector()) |
| return inference.NoChange(); |
| inference.RelyOnMapsPreferStability(dependencies(), jsgraph(), &effect, |
| control, p.feedback()); |
| |
| // 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* promise = effect = |
| graph()->NewNode(javascript()->CreatePromise(), context, effect); |
| |
| // Chain {result} onto {receiver}. |
| promise = effect = graph()->NewNode( |
| javascript()->PerformPromiseThen(), receiver, on_fulfilled, on_rejected, |
| promise, context, frame_state, effect, control); |
| |
| // At this point we know that {promise} is going to have the |
| // initial Promise map, since even if {PerformPromiseThen} |
| // above called into the host rejection tracker, the {promise} |
| // doesn't escape to user JavaScript. So bake this information |
| // into the graph such that subsequent passes can use the |
| // information for further optimizations. |
| MapRef promise_map = native_context().promise_function().initial_map(); |
| effect = graph()->NewNode( |
| simplified()->MapGuard(ZoneHandleSet<Map>(promise_map.object())), promise, |
| effect, control); |
| |
| ReplaceWithValue(node, promise, effect, control); |
| return Replace(promise); |
| } |
| |
| // ES section #sec-promise.resolve |
| Reduction JSCallReducer::ReducePromiseResolveTrampoline(Node* node) { |
| DisallowHeapAccessIf no_heap_acess(FLAG_concurrent_inlining); |
| |
| 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); |
| |
| // Only reduce when the receiver is guaranteed to be a JSReceiver. |
| MapInference inference(broker(), receiver, effect); |
| if (!inference.HaveMaps() || !inference.AllOfInstanceTypesAreJSReceiver()) { |
| 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, const SharedFunctionInfoRef& shared) { |
| DCHECK_EQ(IrOpcode::kJSConstruct, node->opcode()); |
| ConstructParameters const& p = ConstructParametersOf(node->op()); |
| int arity = static_cast<int>(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<int>(arraysize(parameters)); |
| frame_state = CreateJavaScriptBuiltinContinuationFrameState( |
| jsgraph(), shared, Builtins::kGenericLazyDeoptContinuation, 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<int>(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); |
| |
| MapInference inference(broker(), receiver, effect); |
| if (!inference.HaveMaps() || !inference.AllOfInstanceTypesAre(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); |
| |
| MapInference inference(broker(), receiver, effect); |
| if (!inference.HaveMaps() || !inference.AllOfInstanceTypesAre(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); |
| |
| InstanceType type = InstanceTypeForCollectionKind(collection_kind); |
| MapInference inference(broker(), receiver, effect); |
| if (!inference.HaveMaps() || !inference.AllOfInstanceTypesAre(type)) { |
| return NoChange(); |
| } |
| |
| 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); |
| } |
| |
| 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); |
| |
| InstanceType type = InstanceTypeForCollectionKind(collection_kind); |
| MapInference inference(broker(), receiver, effect); |
| if (!inference.HaveMaps() || !inference.AllOfInstanceTypesAre(type)) { |
| return NoChange(); |
| } |
| |
| Node* table = effect = graph()->NewNode( |
| simplified()->LoadField(AccessBuilder::ForJSCollectionTable()), receiver, |
| effect, control); |
| Node* value = effect = graph()->NewNode( |
| simplified()->LoadField( |
| AccessBuilder::ForOrderedHashMapOrSetNumberOfElements()), |
| table, effect, control); |
| ReplaceWithValue(node, value, effect, control); |
| return Replace(value); |
| } |
| |
| Reduction JSCallReducer::ReduceCollectionIteratorPrototypeNext( |
| Node* node, int entry_size, Handle<HeapObject> empty_collection, |
| InstanceType collection_iterator_instance_type_first, |
| InstanceType collection_iterator_instance_type_last) { |
| 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* 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. |
| |
| InstanceType receiver_instance_type; |
| { |
| MapInference inference(broker(), receiver, effect); |
| if (!inference.HaveMaps()) return NoChange(); |
| MapHandles const& receiver_maps = inference.GetMaps(); |
| 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 inference.NoChange(); |
| } |
| } |
| if (receiver_instance_type < collection_iterator_instance_type_first || |
| receiver_instance_type > collection_iterator_instance_type_last) { |
| return inference.NoChange(); |
| } |
| inference.RelyOnMapsPreferStability(dependencies(), jsgraph(), &effect, |
| control, p.feedback()); |
| } |
| |
| // 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::ForOrderedHashMapOrSetNextTable()), |
| 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::ForOrderedHashMapOrSetNumberOfBuckets()), |
| table, effect, control); |
| Node* number_of_elements = effect = graph()->NewNode( |
| simplified()->LoadField( |
| AccessBuilder::ForOrderedHashMapOrSetNumberOfElements()), |
| table, effect, control); |
| Node* number_of_deleted_elements = effect = graph()->NewNode( |
| simplified()->LoadField( |
| AccessBuilder::ForOrderedHashMapOrSetNumberOfDeletedElements()), |
| 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. |
| STATIC_ASSERT(OrderedHashMap::HashTableStartIndex() == |
| OrderedHashSet::HashTableStartIndex()); |
| 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(OrderedHashMap::HashTableStartIndex())); |
| 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); |
| |
| MapInference inference(broker(), receiver, effect); |
| if (!inference.HaveMaps() || |
| !inference.AllOfInstanceTypesAre(instance_type)) { |
| return NoChange(); |
| } |
| |
| // Load the {receiver}s field. |
| Node* value = effect = graph()->NewNode(simplified()->LoadField(access), |
| receiver, effect, control); |
| |
| // See if we can skip the detaching check. |
| if (!dependencies()->DependOnArrayBufferDetachingProtector()) { |
| // Check whether {receiver}s JSArrayBuffer was detached. |
| 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::WasDetachedBit::kMask)), |
| jsgraph()->ZeroConstant()); |
| |
| // TODO(turbofan): Ideally we would bail out here if the {receiver}s |
| // JSArrayBuffer was detached, 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); |
| } |
| |
| 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. |
| MapInference inference(broker(), receiver, effect); |
| if (!inference.HaveMaps() || |
| !inference.AllOfInstanceTypesAre(JS_DATA_VIEW_TYPE)) { |
| return NoChange(); |
| } |
| |
| // 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}, as for all other DataViews it'll be out-of-bounds. |
| JSDataViewRef dataview = m.Ref(broker()).AsJSDataView(); |
| if (dataview.byte_length() < element_size) 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); |
| } 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); |
| |
| if (element_size > 1) { |
| // For non-byte accesses we also need to check that the {offset} |
| // plus the {element_size}-1 fits within the given {byte_length}. |
| // So to keep this as a single check on the {offset}, we subtract |
| // the {element_size}-1 from the {byte_length} here (clamped to |
| // positive safe integer range), and perform a check against that |
| // with the {offset} below. |
| byte_length = graph()->NewNode( |
| simplified()->NumberMax(), jsgraph()->ZeroConstant(), |
| graph()->NewNode(simplified()->NumberSubtract(), byte_length, |
| jsgraph()->Constant(element_size - 1))); |
| } |
| |
| // Check that the {offset} is within range of the {byte_length}. |
| offset = effect = graph()->NewNode(simplified()->CheckBounds(p.feedback()), |
| offset, byte_length, effect, control); |
| } |
| |
| // 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); |
| } |
| |
| // We need to retain either the {receiver} itself or it's backing |
| // JSArrayBuffer to make sure that the GC doesn't collect the raw |
| // memory. We default to {receiver} here, and only use the buffer |
| // if we anyways have to load it (to reduce register pressure). |
| Node* buffer_or_receiver = receiver; |
| |
| if (!dependencies()->DependOnArrayBufferDetachingProtector()) { |
| // Get the underlying buffer and check that it has not been detached. |
| Node* buffer = effect = graph()->NewNode( |
| simplified()->LoadField(AccessBuilder::ForJSArrayBufferViewBuffer()), |
| receiver, effect, control); |
| |
| // Bail out if the {buffer} was detached. |
| 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::WasDetachedBit::kMask)), |
| jsgraph()->ZeroConstant()); |
| effect = graph()->NewNode( |
| simplified()->CheckIf(DeoptimizeReason::kArrayBufferWasDetached, |
| p.feedback()), |
| check, effect, control); |
| |
| // We can reduce register pressure by holding on to the {buffer} |
| // now to retain the backing store memory. |
| buffer_or_receiver = buffer; |
| } |
| |
| // Load the {receiver}s data pointer. |
| Node* data_pointer = effect = graph()->NewNode( |
| simplified()->LoadField(AccessBuilder::ForJSDataViewDataPointer()), |
| receiver, effect, control); |
| |
| switch (access) { |
| case DataViewAccess::kGet: |
| // Perform the load. |
| value = effect = graph()->NewNode( |
| simplified()->LoadDataViewElement(element_type), buffer_or_receiver, |
| data_pointer, offset, is_little_endian, effect, control); |
| break; |
| case DataViewAccess::kSet: |
| // Perform the store. |
| effect = graph()->NewNode( |
| simplified()->StoreDataViewElement(element_type), buffer_or_receiver, |
| data_pointer, offset, value, is_little_endian, effect, control); |
| value = jsgraph()->UndefinedConstant(); |
| break; |
| } |
| |
| ReplaceWithValue(node, value, effect, control); |
| return Changed(value); |
| } |
| |
| // 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); |
| |
| MapInference inference(broker(), receiver, effect); |
| if (!inference.HaveMaps() || !inference.AllOfInstanceTypesAre(JS_DATE_TYPE)) { |
| return NoChange(); |
| } |
| |
| Node* value = effect = |
| graph()->NewNode(simplified()->LoadField(AccessBuilder::ForJSDateValue()), |
| receiver, effect, control); |
| ReplaceWithValue(node, value, effect, control); |
| return Replace(value); |
| } |
| |
| // 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) { |
| DisallowHeapAccessIf disallow_heap_access(FLAG_concurrent_inlining); |
| |
| 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); |
| |
| MapInference inference(broker(), regexp, effect); |
| if (!inference.HaveMaps() || |
| !inference.AllOfInstanceTypes(InstanceTypeChecker::IsJSRegExp)) { |
| return inference.NoChange(); |
| } |
| MapHandles const& regexp_maps = inference.GetMaps(); |
| |
| ZoneVector<PropertyAccessInfo> access_infos(graph()->zone()); |
| AccessInfoFactory access_info_factory(broker(), dependencies(), |
| graph()->zone()); |
| if (!FLAG_concurrent_inlining) { |
| // Compute property access info for "exec" on {resolution}. |
| access_info_factory.ComputePropertyAccessInfos( |
| MapHandles(regexp_maps.begin(), regexp_maps.end()), |
| factory()->exec_string(), AccessMode::kLoad, &access_infos); |
| } else { |
| // Obtain precomputed access infos from the broker. |
| for (auto map : regexp_maps) { |
| MapRef map_ref(broker(), map); |
| PropertyAccessInfo access_info = |
| broker()->GetAccessInfoForLoadingExec(map_ref); |
| access_infos.push_back(access_info); |
| } |
| } |
| |
| PropertyAccessInfo ai_exec = |
| access_info_factory.FinalizePropertyAccessInfosAsOne(access_infos, |
| AccessMode::kLoad); |
| if (ai_exec.IsInvalid()) return inference.NoChange(); |
| |
| // If "exec" has been modified on {regexp}, we can't do anything. |
| if (ai_exec.IsDataConstant()) { |
| Handle<JSObject> holder; |
| // Do not reduce if the exec method is not on the prototype chain. |
| if (!ai_exec.holder().ToHandle(&holder)) return inference.NoChange(); |
| |
| JSObjectRef holder_ref(broker(), holder); |
| |
| // Bail out if the exec method is not the original one. |
| base::Optional<ObjectRef> constant = holder_ref.GetOwnProperty( |
| ai_exec.field_representation(), ai_exec.field_index()); |
| if (!constant.has_value() || |
| !constant->equals(native_context().regexp_exec_function())) { |
| return inference.NoChange(); |
| } |
| |
| // Add proper dependencies on the {regexp}s [[Prototype]]s. |
| dependencies()->DependOnStablePrototypeChains( |
| ai_exec.receiver_maps(), kStartAtPrototype, |
| JSObjectRef(broker(), holder)); |
| } else { |
| return inference.NoChange(); |
| } |
| |
| inference.RelyOnMapsPreferStability(dependencies(), jsgraph(), &effect, |
| control, p.feedback()); |
| |
| 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. |
| SharedFunctionInfoRef shared_info = |
| native_context().number_function().shared(); |
| Node* stack_parameters[] = {receiver}; |
| int stack_parameter_count = arraysize(stack_parameters); |
| Node* continuation_frame_state = |
| CreateJavaScriptBuiltinContinuationFrameState( |
| jsgraph(), shared_info, Builtins::kGenericLazyDeoptContinuation, |
| 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); |
| } |
| |
| Reduction JSCallReducer::ReduceBigIntAsUintN(Node* node) { |
| if (!jsgraph()->machine()->Is64()) { |
| return NoChange(); |
| } |
| |
| CallParameters const& p = CallParametersOf(node->op()); |
| if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) { |
| return NoChange(); |
| } |
| if (node->op()->ValueInputCount() < 3) { |
| return NoChange(); |
| } |
| |
| Node* effect = NodeProperties::GetEffectInput(node); |
| Node* control = NodeProperties::GetControlInput(node); |
| Node* bits = NodeProperties::GetValueInput(node, 2); |
| Node* value = NodeProperties::GetValueInput(node, 3); |
| |
| NumberMatcher matcher(bits); |
| if (matcher.IsInteger() && matcher.IsInRange(0, 64)) { |
| const int bits_value = static_cast<int>(matcher.Value()); |
| value = effect = graph()->NewNode(simplified()->CheckBigInt(p.feedback()), |
| value, effect, control); |
| value = graph()->NewNode(simplified()->BigIntAsUintN(bits_value), value); |
| ReplaceWithValue(node, value, effect); |
| return Replace(value); |
| } |
| |
| return NoChange(); |
| } |
| |
| Graph* JSCallReducer::graph() const { return jsgraph()->graph(); } |
| |
| Isolate* JSCallReducer::isolate() const { return jsgraph()->isolate(); } |
| |
| Factory* JSCallReducer::factory() const { return isolate()->factory(); } |
| |
| NativeContextRef JSCallReducer::native_context() const { |
| return broker()->native_context(); |
| } |
| |
| 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 |