diff options
Diffstat (limited to 'deps/v8/src/compiler/js-call-reducer.cc')
-rw-r--r-- | deps/v8/src/compiler/js-call-reducer.cc | 2109 |
1 files changed, 1967 insertions, 142 deletions
diff --git a/deps/v8/src/compiler/js-call-reducer.cc b/deps/v8/src/compiler/js-call-reducer.cc index c595b360d5..1f8e7a2cef 100644 --- a/deps/v8/src/compiler/js-call-reducer.cc +++ b/deps/v8/src/compiler/js-call-reducer.cc @@ -5,6 +5,7 @@ #include "src/compiler/js-call-reducer.h" #include "src/api.h" +#include "src/builtins/builtins-utils.h" #include "src/code-factory.h" #include "src/code-stubs.h" #include "src/compilation-dependencies.h" @@ -17,6 +18,7 @@ #include "src/feedback-vector-inl.h" #include "src/ic/call-optimization.h" #include "src/objects-inl.h" +#include "src/vector-slot-pair.h" namespace v8 { namespace internal { @@ -90,20 +92,6 @@ Reduction JSCallReducer::ReduceBooleanConstructor(Node* node) { return Replace(value); } -// ES6 section 20.1.1 The Number Constructor -Reduction JSCallReducer::ReduceNumberConstructor(Node* node) { - DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); - CallParameters const& p = CallParametersOf(node->op()); - - // Turn the {node} into a {JSToNumber} call. - DCHECK_LE(2u, p.arity()); - Node* value = (p.arity() == 2) ? jsgraph()->ZeroConstant() - : NodeProperties::GetValueInput(node, 2); - NodeProperties::ReplaceValueInputs(node, value); - NodeProperties::ChangeOp(node, javascript()->ToNumber()); - return Changed(node); -} - // ES section #sec-object-constructor Reduction JSCallReducer::ReduceObjectConstructor(Node* node) { DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); @@ -549,7 +537,7 @@ Reduction JSCallReducer::ReduceObjectPrototypeHasOwnProperty(Node* node) { Node* check = graph()->NewNode(simplified()->ReferenceEqual(), receiver_map, cache_type); effect = graph()->NewNode( - simplified()->CheckIf(DeoptimizeReason::kNoReason), check, effect, + simplified()->CheckIf(DeoptimizeReason::kWrongMap), check, effect, control); } Node* value = jsgraph()->TrueConstant(); @@ -804,15 +792,37 @@ bool CanInlineArrayIteratingBuiltin(Handle<Map> receiver_map) { isolate->IsAnyInitialArrayPrototype(receiver_prototype); } +Node* JSCallReducer::WireInLoopStart(Node* k, Node** control, Node** effect) { + Node* loop = *control = + graph()->NewNode(common()->Loop(2), *control, *control); + Node* eloop = *effect = + graph()->NewNode(common()->EffectPhi(2), *effect, *effect, loop); + Node* terminate = graph()->NewNode(common()->Terminate(), eloop, loop); + NodeProperties::MergeControlToEnd(graph(), common(), terminate); + return graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2), k, + k, loop); +} + +void JSCallReducer::WireInLoopEnd(Node* loop, Node* eloop, Node* vloop, Node* k, + Node* control, Node* effect) { + loop->ReplaceInput(1, control); + vloop->ReplaceInput(1, k); + eloop->ReplaceInput(1, effect); +} + Reduction JSCallReducer::ReduceArrayForEach(Handle<JSFunction> function, 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* outer_frame_state = NodeProperties::GetFrameStateInput(node); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); Node* context = NodeProperties::GetContextInput(node); - CallParameters const& p = CallParametersOf(node->op()); // Try to determine the {receiver} map. Node* receiver = NodeProperties::GetValueInput(node, 1); @@ -825,10 +835,193 @@ Reduction JSCallReducer::ReduceArrayForEach(Handle<JSFunction> function, ZoneHandleSet<Map> receiver_maps; NodeProperties::InferReceiverMapsResult result = NodeProperties::InferReceiverMaps(receiver, effect, &receiver_maps); - if (result != NodeProperties::kReliableReceiverMaps) { + if (result == NodeProperties::kNoReceiverMaps) return NoChange(); + + // By ensuring that {kind} is object or double, we can be polymorphic + // on different elements kinds. + ElementsKind kind = receiver_maps[0]->elements_kind(); + if (IsSmiElementsKind(kind)) { + kind = FastSmiToObjectElementsKind(kind); + } + for (Handle<Map> receiver_map : receiver_maps) { + ElementsKind next_kind = receiver_map->elements_kind(); + if (!CanInlineArrayIteratingBuiltin(receiver_map)) { + return NoChange(); + } + if (!IsFastElementsKind(next_kind)) { + return NoChange(); + } + if (IsDoubleElementsKind(kind) != IsDoubleElementsKind(next_kind)) { + return NoChange(); + } + if (IsHoleyElementsKind(next_kind)) { + kind = GetHoleyElementsKind(kind); + } + } + + // Install code dependencies on the {receiver} prototype maps and the + // global array protector cell. + dependencies()->AssumePropertyCell(factory()->no_elements_protector()); + + // If we have unreliable maps, we need a map check. + if (result == NodeProperties::kUnreliableReceiverMaps) { + effect = + graph()->NewNode(simplified()->CheckMaps(CheckMapsFlag::kNone, + receiver_maps, p.feedback()), + receiver, effect, control); + } + + Node* k = jsgraph()->ZeroConstant(); + + Node* original_length = effect = graph()->NewNode( + simplified()->LoadField(AccessBuilder::ForJSArrayLength(kind)), receiver, + effect, control); + + std::vector<Node*> checkpoint_params( + {receiver, fncallback, this_arg, k, original_length}); + const int stack_parameters = static_cast<int>(checkpoint_params.size()); + + // Check whether the given callback function is callable. Note that this has + // to happen outside the loop to make sure we also throw on empty arrays. + Node* check_frame_state = CreateJavaScriptBuiltinContinuationFrameState( + jsgraph(), function, Builtins::kArrayForEachLoopLazyDeoptContinuation, + node->InputAt(0), context, &checkpoint_params[0], stack_parameters, + outer_frame_state, ContinuationFrameStateMode::LAZY); + Node* check_fail = nullptr; + Node* check_throw = nullptr; + WireInCallbackIsCallableCheck(fncallback, context, check_frame_state, effect, + &control, &check_fail, &check_throw); + + // Start the loop. + Node* vloop = k = WireInLoopStart(k, &control, &effect); + Node *loop = control, *eloop = effect; + checkpoint_params[3] = k; + + Node* continue_test = + graph()->NewNode(simplified()->NumberLessThan(), k, original_length); + Node* continue_branch = graph()->NewNode(common()->Branch(BranchHint::kTrue), + continue_test, control); + + Node* if_true = graph()->NewNode(common()->IfTrue(), continue_branch); + Node* if_false = graph()->NewNode(common()->IfFalse(), continue_branch); + control = if_true; + + Node* frame_state = CreateJavaScriptBuiltinContinuationFrameState( + jsgraph(), function, Builtins::kArrayForEachLoopEagerDeoptContinuation, + node->InputAt(0), context, &checkpoint_params[0], stack_parameters, + outer_frame_state, ContinuationFrameStateMode::EAGER); + + effect = + graph()->NewNode(common()->Checkpoint(), frame_state, effect, control); + + // Make sure the map hasn't changed during the iteration + effect = + graph()->NewNode(simplified()->CheckMaps(CheckMapsFlag::kNone, + receiver_maps, p.feedback()), + receiver, effect, control); + + Node* element = + SafeLoadElement(kind, receiver, control, &effect, &k, p.feedback()); + + Node* next_k = + graph()->NewNode(simplified()->NumberAdd(), k, jsgraph()->OneConstant()); + checkpoint_params[3] = next_k; + + Node* hole_true = nullptr; + Node* hole_false = nullptr; + Node* effect_true = effect; + + if (IsHoleyElementsKind(kind)) { + // Holey elements kind require a hole check and skipping of the element in + // the case of a hole. + Node* check; + if (IsDoubleElementsKind(kind)) { + check = graph()->NewNode(simplified()->NumberIsFloat64Hole(), element); + } else { + check = graph()->NewNode(simplified()->ReferenceEqual(), element, + jsgraph()->TheHoleConstant()); + } + Node* branch = + graph()->NewNode(common()->Branch(BranchHint::kFalse), check, control); + hole_true = graph()->NewNode(common()->IfTrue(), branch); + hole_false = graph()->NewNode(common()->IfFalse(), branch); + control = hole_false; + + // The contract is that we don't leak "the hole" into "user JavaScript", + // so we must rename the {element} here to explicitly exclude "the hole" + // from the type of {element}. + element = effect = graph()->NewNode( + common()->TypeGuard(Type::NonInternal()), element, effect, control); + } + + frame_state = CreateJavaScriptBuiltinContinuationFrameState( + jsgraph(), function, Builtins::kArrayForEachLoopLazyDeoptContinuation, + node->InputAt(0), context, &checkpoint_params[0], stack_parameters, + outer_frame_state, ContinuationFrameStateMode::LAZY); + + control = effect = graph()->NewNode( + javascript()->Call(5, p.frequency()), fncallback, this_arg, element, k, + receiver, context, frame_state, effect, control); + + // Rewire potential exception edges. + Node* on_exception = nullptr; + if (NodeProperties::IsExceptionalCall(node, &on_exception)) { + RewirePostCallbackExceptionEdges(check_throw, on_exception, effect, + &check_fail, &control); + } + + if (IsHoleyElementsKind(kind)) { + Node* after_call_control = control; + Node* after_call_effect = effect; + control = hole_true; + effect = effect_true; + + control = graph()->NewNode(common()->Merge(2), control, after_call_control); + effect = graph()->NewNode(common()->EffectPhi(2), effect, after_call_effect, + control); + } + + WireInLoopEnd(loop, eloop, vloop, next_k, control, effect); + + control = if_false; + effect = eloop; + + // Wire up the branch for the case when IsCallable fails for the callback. + // Since {check_throw} is an unconditional throw, it's impossible to + // return a successful completion. Therefore, we simply connect the successful + // completion to the graph end. + Node* throw_node = + graph()->NewNode(common()->Throw(), check_throw, check_fail); + NodeProperties::MergeControlToEnd(graph(), common(), throw_node); + + ReplaceWithValue(node, jsgraph()->UndefinedConstant(), effect, control); + return Replace(jsgraph()->UndefinedConstant()); +} + +Reduction JSCallReducer::ReduceArrayReduce(Handle<JSFunction> function, + Node* node) { + if (!FLAG_turbo_inline_array_builtins) return NoChange(); + DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); + + Node* outer_frame_state = NodeProperties::GetFrameStateInput(node); + Node* effect = NodeProperties::GetEffectInput(node); + Node* control = NodeProperties::GetControlInput(node); + Node* context = NodeProperties::GetContextInput(node); + CallParameters const& p = CallParametersOf(node->op()); + if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) { return NoChange(); } - if (receiver_maps.size() == 0) return NoChange(); + + // Try to determine the {receiver} map. + Node* receiver = NodeProperties::GetValueInput(node, 1); + Node* fncallback = node->op()->ValueInputCount() > 2 + ? NodeProperties::GetValueInput(node, 2) + : jsgraph()->UndefinedConstant(); + + ZoneHandleSet<Map> receiver_maps; + NodeProperties::InferReceiverMapsResult result = + NodeProperties::InferReceiverMaps(receiver, effect, &receiver_maps); + if (result == NodeProperties::kNoReceiverMaps) return NoChange(); ElementsKind kind = IsDoubleElementsKind(receiver_maps[0]->elements_kind()) ? PACKED_DOUBLE_ELEMENTS @@ -838,8 +1031,7 @@ Reduction JSCallReducer::ReduceArrayForEach(Handle<JSFunction> function, if (!CanInlineArrayIteratingBuiltin(receiver_map)) { return NoChange(); } - if (!IsFastElementsKind(next_kind) || - (IsDoubleElementsKind(next_kind) && IsHoleyElementsKind(next_kind))) { + if (!IsFastElementsKind(next_kind) || IsHoleyElementsKind(next_kind)) { return NoChange(); } if (IsDoubleElementsKind(kind) != IsDoubleElementsKind(next_kind)) { @@ -854,36 +1046,73 @@ Reduction JSCallReducer::ReduceArrayForEach(Handle<JSFunction> function, // global array protector cell. dependencies()->AssumePropertyCell(factory()->no_elements_protector()); - Node* k = jsgraph()->ZeroConstant(); + // If we have unreliable maps, we need a map check. + if (result == NodeProperties::kUnreliableReceiverMaps) { + effect = + graph()->NewNode(simplified()->CheckMaps(CheckMapsFlag::kNone, + receiver_maps, p.feedback()), + receiver, effect, control); + } Node* original_length = effect = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForJSArrayLength(PACKED_ELEMENTS)), receiver, effect, control); - std::vector<Node*> checkpoint_params( - {receiver, fncallback, this_arg, k, original_length}); + Node* k = jsgraph()->ZeroConstant(); + + std::vector<Node*> checkpoint_params({receiver, fncallback, k, + original_length, + jsgraph()->UndefinedConstant()}); const int stack_parameters = static_cast<int>(checkpoint_params.size()); // Check whether the given callback function is callable. Note that this has // to happen outside the loop to make sure we also throw on empty arrays. Node* check_frame_state = CreateJavaScriptBuiltinContinuationFrameState( - jsgraph(), function, Builtins::kArrayForEachLoopLazyDeoptContinuation, - node->InputAt(0), context, &checkpoint_params[0], stack_parameters, + jsgraph(), function, Builtins::kArrayReduceLoopLazyDeoptContinuation, + node->InputAt(0), context, &checkpoint_params[0], stack_parameters - 1, 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); + // Set initial accumulator value + Node* cur = jsgraph()->TheHoleConstant(); + + Node* initial_element_check_fail = nullptr; + Node* initial_element_check_throw = nullptr; + if (node->op()->ValueInputCount() > 3) { + cur = NodeProperties::GetValueInput(node, 3); + } else { + Node* check = + graph()->NewNode(simplified()->NumberEqual(), original_length, k); + Node* check_branch = + graph()->NewNode(common()->Branch(BranchHint::kFalse), check, control); + initial_element_check_fail = + graph()->NewNode(common()->IfTrue(), check_branch); + initial_element_check_throw = graph()->NewNode( + javascript()->CallRuntime(Runtime::kThrowTypeError, 2), + jsgraph()->Constant(MessageTemplate::kReduceNoInitial), fncallback, + context, check_frame_state, effect, initial_element_check_fail); + control = graph()->NewNode(common()->IfFalse(), check_branch); + + cur = SafeLoadElement(kind, receiver, control, &effect, &k, p.feedback()); + k = graph()->NewNode(simplified()->NumberAdd(), k, + jsgraph()->OneConstant()); + } + // 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( + Node* kloop = k = graph()->NewNode( common()->Phi(MachineRepresentation::kTagged, 2), k, k, loop); - checkpoint_params[3] = k; + Node* curloop = cur = graph()->NewNode( + common()->Phi(MachineRepresentation::kTagged, 2), cur, cur, loop); + checkpoint_params[2] = k; + checkpoint_params[4] = curloop; control = loop; effect = eloop; @@ -898,7 +1127,7 @@ Reduction JSCallReducer::ReduceArrayForEach(Handle<JSFunction> function, control = if_true; Node* frame_state = CreateJavaScriptBuiltinContinuationFrameState( - jsgraph(), function, Builtins::kArrayForEachLoopEagerDeoptContinuation, + jsgraph(), function, Builtins::kArrayReduceLoopEagerDeoptContinuation, node->InputAt(0), context, &checkpoint_params[0], stack_parameters, outer_frame_state, ContinuationFrameStateMode::EAGER); @@ -910,11 +1139,12 @@ Reduction JSCallReducer::ReduceArrayForEach(Handle<JSFunction> function, simplified()->CheckMaps(CheckMapsFlag::kNone, receiver_maps), receiver, effect, control); - Node* element = SafeLoadElement(kind, receiver, control, &effect, &k); + Node* element = + SafeLoadElement(kind, receiver, control, &effect, &k, p.feedback()); Node* next_k = - graph()->NewNode(simplified()->NumberAdd(), k, jsgraph()->Constant(1)); - checkpoint_params[3] = next_k; + graph()->NewNode(simplified()->NumberAdd(), k, jsgraph()->OneConstant()); + checkpoint_params[2] = next_k; Node* hole_true = nullptr; Node* hole_false = nullptr; @@ -934,18 +1164,19 @@ Reduction JSCallReducer::ReduceArrayForEach(Handle<JSFunction> function, // 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 = graph()->NewNode(common()->TypeGuard(Type::NonInternal()), - element, control); + element = effect = graph()->NewNode( + common()->TypeGuard(Type::NonInternal()), element, effect, control); } frame_state = CreateJavaScriptBuiltinContinuationFrameState( - jsgraph(), function, Builtins::kArrayForEachLoopLazyDeoptContinuation, - node->InputAt(0), context, &checkpoint_params[0], stack_parameters, + jsgraph(), function, Builtins::kArrayReduceLoopLazyDeoptContinuation, + node->InputAt(0), context, &checkpoint_params[0], stack_parameters - 1, 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); + Node* 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; @@ -963,12 +1194,17 @@ Reduction JSCallReducer::ReduceArrayForEach(Handle<JSFunction> function, 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); - vloop->ReplaceInput(1, k); + kloop->ReplaceInput(1, k); + curloop->ReplaceInput(1, cur); eloop->ReplaceInput(1, effect); control = if_false; @@ -982,19 +1218,271 @@ Reduction JSCallReducer::ReduceArrayForEach(Handle<JSFunction> function, graph()->NewNode(common()->Throw(), check_throw, check_fail); NodeProperties::MergeControlToEnd(graph(), common(), throw_node); - ReplaceWithValue(node, jsgraph()->UndefinedConstant(), effect, control); - return Replace(jsgraph()->UndefinedConstant()); -} + if (node->op()->ValueInputCount() <= 3) { + // Wire up the branch for the case when an array is empty. + // 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(), initial_element_check_throw, + initial_element_check_fail); + NodeProperties::MergeControlToEnd(graph(), common(), throw_node); + } + + ReplaceWithValue(node, curloop, effect, control); + return Replace(curloop); +} // namespace compiler + +Reduction JSCallReducer::ReduceArrayReduceRight(Handle<JSFunction> function, + Node* node) { + if (!FLAG_turbo_inline_array_builtins) return NoChange(); + DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); + + Node* outer_frame_state = NodeProperties::GetFrameStateInput(node); + Node* effect = NodeProperties::GetEffectInput(node); + Node* control = NodeProperties::GetControlInput(node); + Node* context = NodeProperties::GetContextInput(node); + CallParameters const& p = CallParametersOf(node->op()); + if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) { + return NoChange(); + } + + // Try to determine the {receiver} map. + Node* receiver = NodeProperties::GetValueInput(node, 1); + Node* fncallback = node->op()->ValueInputCount() > 2 + ? NodeProperties::GetValueInput(node, 2) + : jsgraph()->UndefinedConstant(); + + ZoneHandleSet<Map> receiver_maps; + NodeProperties::InferReceiverMapsResult result = + NodeProperties::InferReceiverMaps(receiver, effect, &receiver_maps); + if (result == NodeProperties::kNoReceiverMaps) return NoChange(); + + ElementsKind kind = IsDoubleElementsKind(receiver_maps[0]->elements_kind()) + ? PACKED_DOUBLE_ELEMENTS + : PACKED_ELEMENTS; + for (Handle<Map> receiver_map : receiver_maps) { + ElementsKind next_kind = receiver_map->elements_kind(); + if (!CanInlineArrayIteratingBuiltin(receiver_map)) { + return NoChange(); + } + if (!IsFastElementsKind(next_kind) || IsHoleyElementsKind(next_kind)) { + return NoChange(); + } + if (IsDoubleElementsKind(kind) != IsDoubleElementsKind(next_kind)) { + return NoChange(); + } + if (IsHoleyElementsKind(next_kind)) { + kind = HOLEY_ELEMENTS; + } + } + + // Install code dependencies on the {receiver} prototype maps and the + // global array protector cell. + dependencies()->AssumePropertyCell(factory()->no_elements_protector()); + + // If we have unreliable maps, we need a map check. + if (result == NodeProperties::kUnreliableReceiverMaps) { + effect = + graph()->NewNode(simplified()->CheckMaps(CheckMapsFlag::kNone, + receiver_maps, p.feedback()), + receiver, effect, control); + } + + Node* original_length = effect = graph()->NewNode( + simplified()->LoadField(AccessBuilder::ForJSArrayLength(PACKED_ELEMENTS)), + receiver, effect, control); + + Node* k = graph()->NewNode(simplified()->NumberSubtract(), original_length, + jsgraph()->OneConstant()); + + std::vector<Node*> checkpoint_params({receiver, fncallback, k, + original_length, + jsgraph()->UndefinedConstant()}); + const int stack_parameters = static_cast<int>(checkpoint_params.size()); + + // Check whether the given callback function is callable. Note that this has + // to happen outside the loop to make sure we also throw on empty arrays. + Node* check_frame_state = CreateJavaScriptBuiltinContinuationFrameState( + jsgraph(), function, Builtins::kArrayReduceRightLoopLazyDeoptContinuation, + node->InputAt(0), context, &checkpoint_params[0], stack_parameters - 1, + 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); + + // Set initial accumulator value + Node* cur = nullptr; + + Node* initial_element_check_fail = nullptr; + Node* initial_element_check_throw = nullptr; + if (node->op()->ValueInputCount() > 3) { + cur = NodeProperties::GetValueInput(node, 3); + } else { + Node* check = graph()->NewNode(simplified()->NumberEqual(), original_length, + jsgraph()->SmiConstant(0)); + Node* check_branch = + graph()->NewNode(common()->Branch(BranchHint::kFalse), check, control); + initial_element_check_fail = + graph()->NewNode(common()->IfTrue(), check_branch); + initial_element_check_throw = graph()->NewNode( + javascript()->CallRuntime(Runtime::kThrowTypeError, 2), + jsgraph()->Constant(MessageTemplate::kReduceNoInitial), fncallback, + context, check_frame_state, effect, initial_element_check_fail); + control = graph()->NewNode(common()->IfFalse(), check_branch); + + cur = SafeLoadElement(kind, receiver, control, &effect, &k, p.feedback()); + k = graph()->NewNode(simplified()->NumberSubtract(), k, + jsgraph()->OneConstant()); + } + + // 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); + checkpoint_params[2] = k; + checkpoint_params[4] = curloop; + + control = loop; + effect = eloop; + + Node* continue_test = graph()->NewNode(simplified()->NumberLessThanOrEqual(), + jsgraph()->ZeroConstant(), k); + Node* continue_branch = graph()->NewNode(common()->Branch(BranchHint::kTrue), + continue_test, control); + + Node* if_true = graph()->NewNode(common()->IfTrue(), continue_branch); + Node* if_false = graph()->NewNode(common()->IfFalse(), continue_branch); + control = if_true; + + Node* frame_state = CreateJavaScriptBuiltinContinuationFrameState( + jsgraph(), function, + Builtins::kArrayReduceRightLoopEagerDeoptContinuation, node->InputAt(0), + context, &checkpoint_params[0], stack_parameters, outer_frame_state, + ContinuationFrameStateMode::EAGER); + + effect = + graph()->NewNode(common()->Checkpoint(), frame_state, effect, control); + + // Make sure the map hasn't changed during the iteration + effect = graph()->NewNode( + simplified()->CheckMaps(CheckMapsFlag::kNone, receiver_maps), receiver, + effect, control); + + Node* element = + SafeLoadElement(kind, receiver, control, &effect, &k, p.feedback()); + + Node* next_k = graph()->NewNode(simplified()->NumberSubtract(), k, + jsgraph()->OneConstant()); + checkpoint_params[2] = 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 = graph()->NewNode(simplified()->ReferenceEqual(), element, + jsgraph()->TheHoleConstant()); + Node* branch = + graph()->NewNode(common()->Branch(BranchHint::kFalse), check, control); + hole_true = graph()->NewNode(common()->IfTrue(), branch); + hole_false = graph()->NewNode(common()->IfFalse(), branch); + control = hole_false; + + // The contract is that we don't leak "the hole" into "user JavaScript", + // so we must rename the {element} here to explicitly exclude "the hole" + // from the type of {element}. + element = effect = graph()->NewNode( + common()->TypeGuard(Type::NonInternal()), element, effect, control); + } + + frame_state = CreateJavaScriptBuiltinContinuationFrameState( + jsgraph(), function, Builtins::kArrayReduceRightLoopLazyDeoptContinuation, + node->InputAt(0), context, &checkpoint_params[0], stack_parameters - 1, + outer_frame_state, ContinuationFrameStateMode::LAZY); + + Node* 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); + + if (node->op()->ValueInputCount() <= 3) { + // Wire up the branch for the case when an array is empty. + // 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(), initial_element_check_throw, + initial_element_check_fail); + NodeProperties::MergeControlToEnd(graph(), common(), throw_node); + } + + ReplaceWithValue(node, curloop, effect, control); + return Replace(curloop); +} // namespace compiler Reduction JSCallReducer::ReduceArrayMap(Handle<JSFunction> function, 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* outer_frame_state = NodeProperties::GetFrameStateInput(node); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); Node* context = NodeProperties::GetContextInput(node); - CallParameters const& p = CallParametersOf(node->op()); // Try to determine the {receiver} map. Node* receiver = NodeProperties::GetValueInput(node, 1); @@ -1007,31 +1495,18 @@ Reduction JSCallReducer::ReduceArrayMap(Handle<JSFunction> function, ZoneHandleSet<Map> receiver_maps; NodeProperties::InferReceiverMapsResult result = NodeProperties::InferReceiverMaps(receiver, effect, &receiver_maps); - if (result != NodeProperties::kReliableReceiverMaps) { - return NoChange(); - } + if (result == NodeProperties::kNoReceiverMaps) return NoChange(); // Ensure that any changes to the Array species constructor cause deopt. if (!isolate()->IsArraySpeciesLookupChainIntact()) return NoChange(); - if (receiver_maps.size() == 0) return NoChange(); - const ElementsKind kind = receiver_maps[0]->elements_kind(); - // TODO(danno): Handle holey elements kinds. - if (!IsFastPackedElementsKind(kind)) { - return NoChange(); - } - for (Handle<Map> receiver_map : receiver_maps) { - if (!CanInlineArrayIteratingBuiltin(receiver_map)) { - return NoChange(); - } + if (!CanInlineArrayIteratingBuiltin(receiver_map)) return NoChange(); // We can handle different maps, as long as their elements kind are the // same. - if (receiver_map->elements_kind() != kind) { - return NoChange(); - } + if (receiver_map->elements_kind() != kind) return NoChange(); } dependencies()->AssumePropertyCell(factory()->species_protector()); @@ -1045,10 +1520,13 @@ Reduction JSCallReducer::ReduceArrayMap(Handle<JSFunction> function, Node* k = jsgraph()->ZeroConstant(); - // Make sure the map hasn't changed before we construct the output array. - effect = graph()->NewNode( - simplified()->CheckMaps(CheckMapsFlag::kNone, receiver_maps), receiver, - effect, control); + // If we have unreliable maps, we need a map check. + if (result == NodeProperties::kUnreliableReceiverMaps) { + effect = + graph()->NewNode(simplified()->CheckMaps(CheckMapsFlag::kNone, + receiver_maps, p.feedback()), + receiver, effect, control); + } Node* original_length = effect = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForJSArrayLength(kind)), receiver, @@ -1078,18 +1556,10 @@ Reduction JSCallReducer::ReduceArrayMap(Handle<JSFunction> function, &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* vloop = k = WireInLoopStart(k, &control, &effect); + Node *loop = control, *eloop = effect; checkpoint_params[4] = k; - control = loop; - effect = eloop; - Node* continue_test = graph()->NewNode(simplified()->NumberLessThan(), k, original_length); Node* continue_branch = graph()->NewNode(common()->Branch(BranchHint::kTrue), @@ -1108,15 +1578,44 @@ Reduction JSCallReducer::ReduceArrayMap(Handle<JSFunction> function, graph()->NewNode(common()->Checkpoint(), frame_state, effect, control); // Make sure the map hasn't changed during the iteration - effect = graph()->NewNode( - simplified()->CheckMaps(CheckMapsFlag::kNone, receiver_maps), receiver, - effect, control); + effect = + graph()->NewNode(simplified()->CheckMaps(CheckMapsFlag::kNone, + receiver_maps, p.feedback()), + receiver, effect, control); - Node* element = SafeLoadElement(kind, receiver, control, &effect, &k); + Node* element = + SafeLoadElement(kind, receiver, control, &effect, &k, p.feedback()); Node* next_k = graph()->NewNode(simplified()->NumberAdd(), k, jsgraph()->OneConstant()); + Node* hole_true = nullptr; + Node* hole_false = nullptr; + Node* effect_true = effect; + + if (IsHoleyElementsKind(kind)) { + // Holey elements kind require a hole check and skipping of the element in + // the case of a hole. + Node* check; + if (IsDoubleElementsKind(kind)) { + check = graph()->NewNode(simplified()->NumberIsFloat64Hole(), element); + } else { + check = graph()->NewNode(simplified()->ReferenceEqual(), element, + jsgraph()->TheHoleConstant()); + } + Node* branch = + graph()->NewNode(common()->Branch(BranchHint::kFalse), check, control); + hole_true = graph()->NewNode(common()->IfTrue(), branch); + hole_false = graph()->NewNode(common()->IfFalse(), branch); + control = hole_false; + + // The contract is that we don't leak "the hole" into "user JavaScript", + // so we must rename the {element} here to explicitly exclude "the hole" + // from the type of {element}. + element = effect = graph()->NewNode( + common()->TypeGuard(Type::NonInternal()), element, effect, control); + } + // This frame state is dealt with by hand in // ArrayMapLoopLazyDeoptContinuation. frame_state = CreateJavaScriptBuiltinContinuationFrameState( @@ -1143,11 +1642,19 @@ Reduction JSCallReducer::ReduceArrayMap(Handle<JSFunction> function, simplified()->TransitionAndStoreElement(double_map, fast_map), a, k, callback_value, effect, control); - k = next_k; + if (IsHoleyElementsKind(kind)) { + Node* after_call_and_store_control = control; + Node* after_call_and_store_effect = effect; + control = hole_true; + effect = effect_true; - loop->ReplaceInput(1, control); - vloop->ReplaceInput(1, k); - eloop->ReplaceInput(1, effect); + 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; @@ -1168,11 +1675,15 @@ Reduction JSCallReducer::ReduceArrayFilter(Handle<JSFunction> function, 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* outer_frame_state = NodeProperties::GetFrameStateInput(node); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); Node* context = NodeProperties::GetContextInput(node); - CallParameters const& p = CallParametersOf(node->op()); // Try to determine the {receiver} map. Node* receiver = NodeProperties::GetValueInput(node, 1); Node* fncallback = node->op()->ValueInputCount() > 2 @@ -1184,21 +1695,14 @@ Reduction JSCallReducer::ReduceArrayFilter(Handle<JSFunction> function, ZoneHandleSet<Map> receiver_maps; NodeProperties::InferReceiverMapsResult result = NodeProperties::InferReceiverMaps(receiver, effect, &receiver_maps); - if (result != NodeProperties::kReliableReceiverMaps) { - return NoChange(); - } + if (result == NodeProperties::kNoReceiverMaps) return NoChange(); // And ensure that any changes to the Array species constructor cause deopt. if (!isolate()->IsArraySpeciesLookupChainIntact()) return NoChange(); - if (receiver_maps.size() == 0) return NoChange(); - const ElementsKind kind = receiver_maps[0]->elements_kind(); - - // TODO(danno): Handle holey elements kinds. - if (!IsFastPackedElementsKind(kind)) { - return NoChange(); - } + // The output array is packed (filter doesn't visit holes). + const ElementsKind packed_kind = GetPackedElementsKind(kind); for (Handle<Map> receiver_map : receiver_maps) { if (!CanInlineArrayIteratingBuiltin(receiver_map)) { @@ -1206,23 +1710,24 @@ Reduction JSCallReducer::ReduceArrayFilter(Handle<JSFunction> function, } // We can handle different maps, as long as their elements kind are the // same. - if (receiver_map->elements_kind() != kind) { - return NoChange(); - } + if (receiver_map->elements_kind() != kind) return NoChange(); } dependencies()->AssumePropertyCell(factory()->species_protector()); Handle<Map> initial_map( - Map::cast(native_context()->GetInitialJSArrayMap(kind))); + Map::cast(native_context()->GetInitialJSArrayMap(packed_kind))); Node* k = jsgraph()->ZeroConstant(); Node* to = jsgraph()->ZeroConstant(); - // Make sure the map hasn't changed before we construct the output array. - effect = graph()->NewNode( - simplified()->CheckMaps(CheckMapsFlag::kNone, receiver_maps), receiver, - effect, control); + // If we have unreliable maps, we need a map check. + if (result == NodeProperties::kUnreliableReceiverMaps) { + effect = + graph()->NewNode(simplified()->CheckMaps(CheckMapsFlag::kNone, + receiver_maps, p.feedback()), + receiver, effect, control); + } Node* a; // Construct the output array. { @@ -1232,7 +1737,8 @@ Reduction JSCallReducer::ReduceArrayFilter(Handle<JSFunction> function, Node* empty_fixed_array = jsgraph()->EmptyFixedArrayConstant(); ab.Store(AccessBuilder::ForJSObjectPropertiesOrHash(), empty_fixed_array); ab.Store(AccessBuilder::ForJSObjectElements(), empty_fixed_array); - ab.Store(AccessBuilder::ForJSArrayLength(kind), jsgraph()->ZeroConstant()); + 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()); @@ -1268,19 +1774,11 @@ Reduction JSCallReducer::ReduceArrayFilter(Handle<JSFunction> function, } // 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* 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); - control = loop; - effect = eloop; - Node* continue_test = graph()->NewNode(simplified()->NumberLessThan(), k, original_length); Node* continue_branch = graph()->NewNode(common()->Branch(BranchHint::kTrue), @@ -1305,15 +1803,45 @@ Reduction JSCallReducer::ReduceArrayFilter(Handle<JSFunction> function, } // Make sure the map hasn't changed during the iteration. - effect = graph()->NewNode( - simplified()->CheckMaps(CheckMapsFlag::kNone, receiver_maps), receiver, - effect, control); + effect = + graph()->NewNode(simplified()->CheckMaps(CheckMapsFlag::kNone, + receiver_maps, p.feedback()), + receiver, effect, control); - Node* element = SafeLoadElement(kind, receiver, control, &effect, &k); + 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 @@ -1363,14 +1891,25 @@ Reduction JSCallReducer::ReduceArrayFilter(Handle<JSFunction> function, // 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(kind, &control, &effect, a, to, element, + to = DoFilterPostCallbackWork(packed_kind, &control, &effect, a, to, element, callback_value); - k = next_k; - loop->ReplaceInput(1, control); - vloop->ReplaceInput(1, k); + 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); - eloop->ReplaceInput(1, effect); control = if_false; effect = eloop; @@ -1387,6 +1926,216 @@ Reduction JSCallReducer::ReduceArrayFilter(Handle<JSFunction> function, return Replace(a); } +Reduction JSCallReducer::ReduceArrayFind(ArrayFindVariant variant, + Handle<JSFunction> function, + 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(); + } + + Builtins::Name eager_continuation_builtin; + Builtins::Name lazy_continuation_builtin; + Builtins::Name after_callback_lazy_continuation_builtin; + if (variant == ArrayFindVariant::kFind) { + eager_continuation_builtin = Builtins::kArrayFindLoopEagerDeoptContinuation; + lazy_continuation_builtin = Builtins::kArrayFindLoopLazyDeoptContinuation; + after_callback_lazy_continuation_builtin = + Builtins::kArrayFindLoopAfterCallbackLazyDeoptContinuation; + } else { + DCHECK_EQ(ArrayFindVariant::kFindIndex, variant); + eager_continuation_builtin = + Builtins::kArrayFindIndexLoopEagerDeoptContinuation; + lazy_continuation_builtin = + Builtins::kArrayFindIndexLoopLazyDeoptContinuation; + after_callback_lazy_continuation_builtin = + Builtins::kArrayFindIndexLoopAfterCallbackLazyDeoptContinuation; + } + + Node* outer_frame_state = NodeProperties::GetFrameStateInput(node); + Node* effect = NodeProperties::GetEffectInput(node); + Node* control = NodeProperties::GetControlInput(node); + Node* context = NodeProperties::GetContextInput(node); + + // Try to determine the {receiver} map. + Node* receiver = NodeProperties::GetValueInput(node, 1); + Node* fncallback = node->op()->ValueInputCount() > 2 + ? NodeProperties::GetValueInput(node, 2) + : jsgraph()->UndefinedConstant(); + Node* this_arg = node->op()->ValueInputCount() > 3 + ? NodeProperties::GetValueInput(node, 3) + : jsgraph()->UndefinedConstant(); + ZoneHandleSet<Map> receiver_maps; + NodeProperties::InferReceiverMapsResult result = + NodeProperties::InferReceiverMaps(receiver, effect, &receiver_maps); + if (result == NodeProperties::kNoReceiverMaps) return NoChange(); + + const ElementsKind kind = receiver_maps[0]->elements_kind(); + + // TODO(pwong): Handle holey double elements kinds. + if (IsDoubleElementsKind(kind) && IsHoleyElementsKind(kind)) { + return NoChange(); + } + + for (Handle<Map> receiver_map : receiver_maps) { + if (!CanInlineArrayIteratingBuiltin(receiver_map)) return NoChange(); + // We can handle different maps, as long as their elements kind are the + // same. + if (receiver_map->elements_kind() != kind) return NoChange(); + } + + // Install code dependencies on the {receiver} prototype maps and the + // global array protector cell. + dependencies()->AssumePropertyCell(factory()->no_elements_protector()); + + // If we have unreliable maps, we need a map check. + if (result == NodeProperties::kUnreliableReceiverMaps) { + effect = + graph()->NewNode(simplified()->CheckMaps(CheckMapsFlag::kNone, + receiver_maps, p.feedback()), + receiver, effect, control); + } + + Node* k = jsgraph()->ZeroConstant(); + + Node* original_length = effect = graph()->NewNode( + simplified()->LoadField(AccessBuilder::ForJSArrayLength(kind)), receiver, + effect, control); + + std::vector<Node*> checkpoint_params( + {receiver, fncallback, this_arg, k, original_length}); + const int stack_parameters = static_cast<int>(checkpoint_params.size()); + + // Check whether the given callback function is callable. Note that this has + // to happen outside the loop to make sure we also throw on empty arrays. + Node* check_fail = nullptr; + Node* check_throw = nullptr; + { + Node* frame_state = CreateJavaScriptBuiltinContinuationFrameState( + jsgraph(), function, lazy_continuation_builtin, node->InputAt(0), + context, &checkpoint_params[0], stack_parameters, outer_frame_state, + ContinuationFrameStateMode::LAZY); + WireInCallbackIsCallableCheck(fncallback, context, frame_state, effect, + &control, &check_fail, &check_throw); + } + + // Start the loop. + Node* vloop = k = WireInLoopStart(k, &control, &effect); + Node *loop = control, *eloop = effect; + checkpoint_params[3] = k; + + // Check if we've iterated past the last element of the array. + Node* if_false = nullptr; + { + Node* continue_test = + graph()->NewNode(simplified()->NumberLessThan(), k, original_length); + Node* continue_branch = graph()->NewNode( + common()->Branch(BranchHint::kTrue), continue_test, control); + control = graph()->NewNode(common()->IfTrue(), continue_branch); + if_false = graph()->NewNode(common()->IfFalse(), continue_branch); + } + + // Check the map hasn't changed during the iteration. + { + Node* frame_state = CreateJavaScriptBuiltinContinuationFrameState( + jsgraph(), function, eager_continuation_builtin, node->InputAt(0), + context, &checkpoint_params[0], stack_parameters, outer_frame_state, + ContinuationFrameStateMode::EAGER); + + effect = + graph()->NewNode(common()->Checkpoint(), frame_state, effect, control); + + effect = + graph()->NewNode(simplified()->CheckMaps(CheckMapsFlag::kNone, + receiver_maps, p.feedback()), + receiver, effect, control); + } + + // Load k-th element from receiver. + Node* element = + SafeLoadElement(kind, receiver, control, &effect, &k, p.feedback()); + + // Increment k for the next iteration. + Node* next_k = checkpoint_params[3] = + graph()->NewNode(simplified()->NumberAdd(), k, jsgraph()->OneConstant()); + + // Replace holes with undefined. + if (IsHoleyElementsKind(kind)) { + element = graph()->NewNode( + common()->Select(MachineRepresentation::kTagged, BranchHint::kFalse), + graph()->NewNode(simplified()->ReferenceEqual(), element, + jsgraph()->TheHoleConstant()), + jsgraph()->UndefinedConstant(), element); + } + + Node* if_found_return_value = + (variant == ArrayFindVariant::kFind) ? element : k; + + // Call the callback. + Node* callback_value = nullptr; + { + std::vector<Node*> call_checkpoint_params({receiver, fncallback, this_arg, + next_k, original_length, + if_found_return_value}); + const int call_stack_parameters = + static_cast<int>(call_checkpoint_params.size()); + + Node* frame_state = CreateJavaScriptBuiltinContinuationFrameState( + jsgraph(), function, after_callback_lazy_continuation_builtin, + node->InputAt(0), context, &call_checkpoint_params[0], + call_stack_parameters, outer_frame_state, + ContinuationFrameStateMode::LAZY); + + callback_value = control = effect = graph()->NewNode( + javascript()->Call(5, p.frequency()), fncallback, this_arg, element, k, + receiver, context, frame_state, effect, control); + } + + // Rewire potential exception edges. + Node* on_exception = nullptr; + if (NodeProperties::IsExceptionalCall(node, &on_exception)) { + RewirePostCallbackExceptionEdges(check_throw, on_exception, effect, + &check_fail, &control); + } + + // Check whether the given callback function returned a truthy value. + Node* boolean_result = + graph()->NewNode(simplified()->ToBoolean(), callback_value); + Node* efound_branch = effect; + Node* found_branch = graph()->NewNode(common()->Branch(BranchHint::kFalse), + boolean_result, control); + Node* if_found = graph()->NewNode(common()->IfTrue(), found_branch); + Node* if_notfound = graph()->NewNode(common()->IfFalse(), found_branch); + control = if_notfound; + + // Close the loop. + WireInLoopEnd(loop, eloop, vloop, next_k, control, effect); + + control = graph()->NewNode(common()->Merge(2), if_found, if_false); + effect = + graph()->NewNode(common()->EffectPhi(2), efound_branch, eloop, control); + + Node* if_not_found_value = (variant == ArrayFindVariant::kFind) + ? jsgraph()->UndefinedConstant() + : jsgraph()->MinusOneConstant(); + Node* return_value = + graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2), + if_found_return_value, if_not_found_value, control); + + // Wire up the branch for the case when IsCallable fails for the callback. + // Since {check_throw} is an unconditional throw, it's impossible to + // return a successful completion. Therefore, we simply connect the successful + // completion to the graph end. + Node* throw_node = + graph()->NewNode(common()->Throw(), check_throw, check_fail); + NodeProperties::MergeControlToEnd(graph(), common(), throw_node); + + ReplaceWithValue(node, return_value, effect, control); + return Replace(return_value); +} + Node* JSCallReducer::DoFilterPostCallbackWork(ElementsKind kind, Node** control, Node** effect, Node* a, Node* to, Node* element, @@ -1411,8 +2160,8 @@ Node* JSCallReducer::DoFilterPostCallbackWork(ElementsKind kind, Node** control, // We know that {to} is in Unsigned31 range here, being smaller than // {original_length} at all times. - Node* checked_to = - graph()->NewNode(common()->TypeGuard(Type::Unsigned31()), to, if_true); + Node* checked_to = etrue = graph()->NewNode( + common()->TypeGuard(Type::Unsigned31()), to, etrue, if_true); Node* elements_length = etrue = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForFixedArrayLength()), elements, etrue, if_true); @@ -1420,9 +2169,9 @@ Node* JSCallReducer::DoFilterPostCallbackWork(ElementsKind kind, Node** control, GrowFastElementsMode mode = IsDoubleElementsKind(kind) ? GrowFastElementsMode::kDoubleElements : GrowFastElementsMode::kSmiOrObjectElements; - elements = etrue = - graph()->NewNode(simplified()->MaybeGrowFastElements(mode), a, elements, - checked_to, elements_length, etrue, if_true); + 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, @@ -1489,14 +2238,15 @@ void JSCallReducer::RewirePostCallbackExceptionEdges(Node* check_throw, } Node* JSCallReducer::SafeLoadElement(ElementsKind kind, Node* receiver, - Node* control, Node** effect, Node** k) { + 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(), *k, length, - *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 @@ -1514,6 +2264,455 @@ Node* JSCallReducer::SafeLoadElement(ElementsKind kind, Node* receiver, return element; } +Reduction JSCallReducer::ReduceArrayEvery(Handle<JSFunction> function, + 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* outer_frame_state = NodeProperties::GetFrameStateInput(node); + Node* effect = NodeProperties::GetEffectInput(node); + Node* control = NodeProperties::GetControlInput(node); + Node* context = NodeProperties::GetContextInput(node); + // Try to determine the {receiver} map. + Node* receiver = NodeProperties::GetValueInput(node, 1); + Node* fncallback = node->op()->ValueInputCount() > 2 + ? NodeProperties::GetValueInput(node, 2) + : jsgraph()->UndefinedConstant(); + Node* this_arg = node->op()->ValueInputCount() > 3 + ? NodeProperties::GetValueInput(node, 3) + : jsgraph()->UndefinedConstant(); + ZoneHandleSet<Map> receiver_maps; + NodeProperties::InferReceiverMapsResult result = + NodeProperties::InferReceiverMaps(receiver, effect, &receiver_maps); + if (result == NodeProperties::kNoReceiverMaps) return NoChange(); + + // And ensure that any changes to the Array species constructor cause deopt. + if (!isolate()->IsArraySpeciesLookupChainIntact()) return NoChange(); + + const ElementsKind kind = receiver_maps[0]->elements_kind(); + + for (Handle<Map> receiver_map : receiver_maps) { + if (!CanInlineArrayIteratingBuiltin(receiver_map)) return NoChange(); + // We can handle different maps, as long as their elements kind are the + // same. + if (receiver_map->elements_kind() != kind) return NoChange(); + } + + dependencies()->AssumePropertyCell(factory()->species_protector()); + + // If we have unreliable maps, we need a map check. + if (result == NodeProperties::kUnreliableReceiverMaps) { + effect = + graph()->NewNode(simplified()->CheckMaps(CheckMapsFlag::kNone, + receiver_maps, p.feedback()), + receiver, effect, control); + } + + Node* k = jsgraph()->ZeroConstant(); + + // Make sure the map hasn't changed before we construct the output array. + effect = graph()->NewNode( + simplified()->CheckMaps(CheckMapsFlag::kNone, receiver_maps), receiver, + effect, control); + + Node* original_length = effect = graph()->NewNode( + simplified()->LoadField(AccessBuilder::ForJSArrayLength(kind)), receiver, + effect, control); + + // Check whether the given callback function is callable. Note that this has + // to happen outside the loop to make sure we also throw on empty arrays. + Node* check_fail = nullptr; + Node* check_throw = nullptr; + { + // This frame state doesn't ever call the deopt continuation, it's only + // necessary to specifiy a continuation in order to handle the exceptional + // case. + std::vector<Node*> checkpoint_params( + {receiver, fncallback, this_arg, k, original_length}); + const int stack_parameters = static_cast<int>(checkpoint_params.size()); + + Node* check_frame_state = CreateJavaScriptBuiltinContinuationFrameState( + jsgraph(), function, Builtins::kArrayEveryLoopLazyDeoptContinuation, + node->InputAt(0), context, &checkpoint_params[0], stack_parameters, + outer_frame_state, ContinuationFrameStateMode::LAZY); + WireInCallbackIsCallableCheck(fncallback, context, check_frame_state, + effect, &control, &check_fail, &check_throw); + } + + // Start the loop. + Node* vloop = k = WireInLoopStart(k, &control, &effect); + Node *loop = control, *eloop = effect; + + Node* continue_test = + graph()->NewNode(simplified()->NumberLessThan(), k, original_length); + Node* continue_branch = graph()->NewNode(common()->Branch(BranchHint::kTrue), + continue_test, control); + + Node* if_true = graph()->NewNode(common()->IfTrue(), continue_branch); + Node* if_false = graph()->NewNode(common()->IfFalse(), continue_branch); + control = if_true; + + { + std::vector<Node*> checkpoint_params( + {receiver, fncallback, this_arg, k, original_length}); + const int stack_parameters = static_cast<int>(checkpoint_params.size()); + + Node* frame_state = CreateJavaScriptBuiltinContinuationFrameState( + jsgraph(), function, Builtins::kArrayEveryLoopEagerDeoptContinuation, + node->InputAt(0), context, &checkpoint_params[0], stack_parameters, + outer_frame_state, ContinuationFrameStateMode::EAGER); + + effect = + graph()->NewNode(common()->Checkpoint(), frame_state, effect, control); + } + + // Make sure the map hasn't changed during the iteration. + effect = + graph()->NewNode(simplified()->CheckMaps(CheckMapsFlag::kNone, + receiver_maps, p.feedback()), + receiver, effect, control); + + Node* element = + SafeLoadElement(kind, receiver, control, &effect, &k, p.feedback()); + + Node* next_k = + graph()->NewNode(simplified()->NumberAdd(), k, jsgraph()->OneConstant()); + + Node* hole_true = nullptr; + Node* hole_false = nullptr; + Node* effect_true = effect; + + if (IsHoleyElementsKind(kind)) { + // Holey elements kind require a hole check and skipping of the element in + // the case of a hole. + Node* check; + if (IsDoubleElementsKind(kind)) { + check = graph()->NewNode(simplified()->NumberIsFloat64Hole(), element); + } else { + check = graph()->NewNode(simplified()->ReferenceEqual(), element, + jsgraph()->TheHoleConstant()); + } + Node* branch = + graph()->NewNode(common()->Branch(BranchHint::kFalse), check, control); + hole_true = graph()->NewNode(common()->IfTrue(), branch); + hole_false = graph()->NewNode(common()->IfFalse(), branch); + control = hole_false; + + // The contract is that we don't leak "the hole" into "user JavaScript", + // so we must rename the {element} here to explicitly exclude "the hole" + // from the type of {element}. + element = effect = graph()->NewNode( + common()->TypeGuard(Type::NonInternal()), element, effect, control); + } + + Node* callback_value = nullptr; + { + // This frame state is dealt with by hand in + // Builtins::kArrayEveryLoopLazyDeoptContinuation. + std::vector<Node*> checkpoint_params( + {receiver, fncallback, this_arg, k, original_length}); + const int stack_parameters = static_cast<int>(checkpoint_params.size()); + + Node* frame_state = CreateJavaScriptBuiltinContinuationFrameState( + jsgraph(), function, 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* check_boolean_result = + graph()->NewNode(simplified()->ReferenceEqual(), boolean_result, + jsgraph()->TrueConstant()); + Node* boolean_branch = graph()->NewNode(common()->Branch(BranchHint::kTrue), + check_boolean_result, control); + if_false_callback = graph()->NewNode(common()->IfFalse(), boolean_branch); + efalse_callback = effect; + + // Nothing to do in the true case. + control = graph()->NewNode(common()->IfTrue(), boolean_branch); + } + + if (IsHoleyElementsKind(kind)) { + Node* after_call_control = control; + Node* after_call_effect = effect; + control = hole_true; + effect = effect_true; + + control = graph()->NewNode(common()->Merge(2), control, after_call_control); + effect = graph()->NewNode(common()->EffectPhi(2), effect, after_call_effect, + control); + } + + WireInLoopEnd(loop, eloop, vloop, next_k, control, effect); + + control = graph()->NewNode(common()->Merge(2), if_false, if_false_callback); + effect = + graph()->NewNode(common()->EffectPhi(2), eloop, efalse_callback, control); + Node* return_value = graph()->NewNode( + common()->Phi(MachineRepresentation::kTagged, 2), + jsgraph()->TrueConstant(), jsgraph()->FalseConstant(), control); + + // Wire up the branch for the case when IsCallable fails for the callback. + // Since {check_throw} is an unconditional throw, it's impossible to + // return a successful completion. Therefore, we simply connect the successful + // completion to the graph end. + Node* throw_node = + graph()->NewNode(common()->Throw(), check_throw, check_fail); + NodeProperties::MergeControlToEnd(graph(), common(), throw_node); + + ReplaceWithValue(node, return_value, effect, control); + return Replace(return_value); +} + +Reduction JSCallReducer::ReduceArraySome(Handle<JSFunction> function, + 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* outer_frame_state = NodeProperties::GetFrameStateInput(node); + Node* effect = NodeProperties::GetEffectInput(node); + Node* control = NodeProperties::GetControlInput(node); + Node* context = NodeProperties::GetContextInput(node); + // Try to determine the {receiver} map. + Node* receiver = NodeProperties::GetValueInput(node, 1); + Node* fncallback = node->op()->ValueInputCount() > 2 + ? NodeProperties::GetValueInput(node, 2) + : jsgraph()->UndefinedConstant(); + Node* this_arg = node->op()->ValueInputCount() > 3 + ? NodeProperties::GetValueInput(node, 3) + : jsgraph()->UndefinedConstant(); + ZoneHandleSet<Map> receiver_maps; + NodeProperties::InferReceiverMapsResult result = + NodeProperties::InferReceiverMaps(receiver, effect, &receiver_maps); + if (result == NodeProperties::kNoReceiverMaps) return NoChange(); + + // And ensure that any changes to the Array species constructor cause deopt. + if (!isolate()->IsArraySpeciesLookupChainIntact()) return NoChange(); + + if (receiver_maps.size() == 0) return NoChange(); + + const ElementsKind kind = receiver_maps[0]->elements_kind(); + + // TODO(pwong): Handle holey double elements kinds. + if (IsDoubleElementsKind(kind) && IsHoleyElementsKind(kind)) { + return NoChange(); + } + + for (Handle<Map> receiver_map : receiver_maps) { + if (!CanInlineArrayIteratingBuiltin(receiver_map)) return NoChange(); + // We can handle different maps, as long as their elements kind are the + // same. + if (receiver_map->elements_kind() != kind) return NoChange(); + } + + dependencies()->AssumePropertyCell(factory()->species_protector()); + + Node* k = jsgraph()->ZeroConstant(); + + // If we have unreliable maps, we need a map check. + if (result == NodeProperties::kUnreliableReceiverMaps) { + effect = + graph()->NewNode(simplified()->CheckMaps(CheckMapsFlag::kNone, + receiver_maps, p.feedback()), + receiver, effect, control); + } + + // Make sure the map hasn't changed before we construct the output array. + effect = graph()->NewNode( + simplified()->CheckMaps(CheckMapsFlag::kNone, receiver_maps), receiver, + effect, control); + + Node* original_length = effect = graph()->NewNode( + simplified()->LoadField(AccessBuilder::ForJSArrayLength(kind)), receiver, + effect, control); + + // Check whether the given callback function is callable. Note that this has + // to happen outside the loop to make sure we also throw on empty arrays. + Node* check_fail = nullptr; + Node* check_throw = nullptr; + { + // This frame state doesn't ever call the deopt continuation, it's only + // necessary to specifiy a continuation in order to handle the exceptional + // case. + std::vector<Node*> checkpoint_params( + {receiver, fncallback, this_arg, k, original_length}); + const int stack_parameters = static_cast<int>(checkpoint_params.size()); + + Node* check_frame_state = CreateJavaScriptBuiltinContinuationFrameState( + jsgraph(), function, Builtins::kArraySomeLoopLazyDeoptContinuation, + node->InputAt(0), context, &checkpoint_params[0], stack_parameters, + outer_frame_state, ContinuationFrameStateMode::LAZY); + WireInCallbackIsCallableCheck(fncallback, context, check_frame_state, + effect, &control, &check_fail, &check_throw); + } + + // Start the loop. + Node* loop = control = graph()->NewNode(common()->Loop(2), control, control); + Node* eloop = effect = + graph()->NewNode(common()->EffectPhi(2), effect, effect, loop); + Node* terminate = graph()->NewNode(common()->Terminate(), eloop, loop); + NodeProperties::MergeControlToEnd(graph(), common(), terminate); + Node* vloop = k = graph()->NewNode( + common()->Phi(MachineRepresentation::kTagged, 2), k, k, loop); + + Node* continue_test = + graph()->NewNode(simplified()->NumberLessThan(), k, original_length); + Node* continue_branch = graph()->NewNode(common()->Branch(BranchHint::kTrue), + continue_test, control); + + Node* if_true = graph()->NewNode(common()->IfTrue(), continue_branch); + Node* if_false = graph()->NewNode(common()->IfFalse(), continue_branch); + control = if_true; + + { + std::vector<Node*> checkpoint_params( + {receiver, fncallback, this_arg, k, original_length}); + const int stack_parameters = static_cast<int>(checkpoint_params.size()); + + Node* frame_state = CreateJavaScriptBuiltinContinuationFrameState( + jsgraph(), function, Builtins::kArraySomeLoopEagerDeoptContinuation, + node->InputAt(0), context, &checkpoint_params[0], stack_parameters, + outer_frame_state, ContinuationFrameStateMode::EAGER); + + effect = + graph()->NewNode(common()->Checkpoint(), frame_state, effect, control); + } + + // Make sure the map hasn't changed during the iteration. + effect = + graph()->NewNode(simplified()->CheckMaps(CheckMapsFlag::kNone, + receiver_maps, p.feedback()), + receiver, effect, control); + + Node* element = + SafeLoadElement(kind, receiver, control, &effect, &k, p.feedback()); + + Node* next_k = + graph()->NewNode(simplified()->NumberAdd(), k, jsgraph()->OneConstant()); + + Node* hole_true = nullptr; + Node* hole_false = nullptr; + Node* effect_true = effect; + + if (IsHoleyElementsKind(kind)) { + // Holey elements kind require a hole check and skipping of the element in + // the case of a hole. + Node* check = graph()->NewNode(simplified()->ReferenceEqual(), element, + jsgraph()->TheHoleConstant()); + Node* branch = + graph()->NewNode(common()->Branch(BranchHint::kFalse), check, control); + hole_true = graph()->NewNode(common()->IfTrue(), branch); + hole_false = graph()->NewNode(common()->IfFalse(), branch); + control = hole_false; + + // The contract is that we don't leak "the hole" into "user JavaScript", + // so we must rename the {element} here to explicitly exclude "the hole" + // from the type of {element}. + element = effect = graph()->NewNode( + common()->TypeGuard(Type::NonInternal()), element, effect, control); + } + + Node* callback_value = nullptr; + { + // This frame state is dealt with by hand in + // Builtins::kArrayEveryLoopLazyDeoptContinuation. + std::vector<Node*> checkpoint_params( + {receiver, fncallback, this_arg, k, original_length}); + const int stack_parameters = static_cast<int>(checkpoint_params.size()); + + Node* frame_state = CreateJavaScriptBuiltinContinuationFrameState( + jsgraph(), function, 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* check_boolean_result = + graph()->NewNode(simplified()->ReferenceEqual(), boolean_result, + jsgraph()->TrueConstant()); + Node* boolean_branch = graph()->NewNode( + common()->Branch(BranchHint::kFalse), check_boolean_result, control); + if_true_callback = graph()->NewNode(common()->IfTrue(), boolean_branch); + etrue_callback = effect; + + // Nothing to do in the false case. + control = graph()->NewNode(common()->IfFalse(), boolean_branch); + } + + if (IsHoleyElementsKind(kind)) { + Node* after_call_control = control; + Node* after_call_effect = effect; + control = hole_true; + effect = effect_true; + + control = graph()->NewNode(common()->Merge(2), control, after_call_control); + effect = graph()->NewNode(common()->EffectPhi(2), effect, after_call_effect, + control); + } + + loop->ReplaceInput(1, control); + vloop->ReplaceInput(1, next_k); + eloop->ReplaceInput(1, effect); + + control = graph()->NewNode(common()->Merge(2), if_false, if_true_callback); + effect = + graph()->NewNode(common()->EffectPhi(2), eloop, etrue_callback, control); + Node* return_value = graph()->NewNode( + common()->Phi(MachineRepresentation::kTagged, 2), + jsgraph()->FalseConstant(), jsgraph()->TrueConstant(), control); + + // Wire up the branch for the case when IsCallable fails for the callback. + // Since {check_throw} is an unconditional throw, it's impossible to + // return a successful completion. Therefore, we simply connect the successful + // completion to the graph end. + Node* throw_node = + graph()->NewNode(common()->Throw(), check_throw, check_fail); + NodeProperties::MergeControlToEnd(graph(), common(), throw_node); + + ReplaceWithValue(node, return_value, effect, control); + return Replace(return_value); +} + Reduction JSCallReducer::ReduceCallApiFunction(Node* node, Handle<JSFunction> function) { DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); @@ -1911,8 +3110,6 @@ Reduction JSCallReducer::ReduceJSCall(Node* node) { return ReduceFunctionPrototypeCall(node); case Builtins::kFunctionPrototypeHasInstance: return ReduceFunctionPrototypeHasInstance(node); - case Builtins::kNumberConstructor: - return ReduceNumberConstructor(node); case Builtins::kObjectConstructor: return ReduceObjectConstructor(node); case Builtins::kObjectGetPrototypeOf: @@ -1941,8 +3138,30 @@ Reduction JSCallReducer::ReduceJSCall(Node* node) { return ReduceArrayMap(function, node); case Builtins::kArrayFilter: return ReduceArrayFilter(function, node); + case Builtins::kArrayReduce: + return ReduceArrayReduce(function, node); + case Builtins::kArrayReduceRight: + return ReduceArrayReduceRight(function, node); + case Builtins::kArrayPrototypeFind: + return ReduceArrayFind(ArrayFindVariant::kFind, function, node); + case Builtins::kArrayPrototypeFindIndex: + return ReduceArrayFind(ArrayFindVariant::kFindIndex, function, node); + case Builtins::kArrayEvery: + return ReduceArrayEvery(function, node); + case Builtins::kArrayPrototypePush: + return ReduceArrayPrototypePush(node); + case Builtins::kArrayPrototypePop: + return ReduceArrayPrototypePop(node); + case Builtins::kArrayPrototypeShift: + return ReduceArrayPrototypeShift(node); case Builtins::kReturnReceiver: return ReduceReturnReceiver(node); + case Builtins::kStringPrototypeIndexOf: + return ReduceStringPrototypeIndexOf(function, node); + case Builtins::kStringPrototypeCharAt: + return ReduceStringPrototypeCharAt(node); + case Builtins::kStringPrototypeCharCodeAt: + return ReduceStringPrototypeCharCodeAt(node); default: break; } @@ -2046,9 +3265,9 @@ Reduction JSCallReducer::ReduceJSCall(Node* node) { // Check that the {target} is still the {target_function}. Node* check = graph()->NewNode(simplified()->ReferenceEqual(), target, target_function); - effect = - graph()->NewNode(simplified()->CheckIf(DeoptimizeReason::kNoReason), - check, effect, control); + effect = graph()->NewNode( + simplified()->CheckIf(DeoptimizeReason::kWrongCallTarget), check, + effect, control); // Specialize the JSCall node to the {target_function}. NodeProperties::ReplaceValueInput(node, target_function, 0); @@ -2119,9 +3338,9 @@ Reduction JSCallReducer::ReduceJSConstruct(Node* node) { // Check that the {target} is still the {array_function}. Node* check = graph()->NewNode(simplified()->ReferenceEqual(), target, array_function); - effect = - graph()->NewNode(simplified()->CheckIf(DeoptimizeReason::kNoReason), - check, effect, control); + effect = graph()->NewNode( + simplified()->CheckIf(DeoptimizeReason::kWrongCallTarget), check, + effect, control); // Turn the {node} into a {JSCreateArray} call. NodeProperties::ReplaceEffectInput(node, effect); @@ -2142,9 +3361,9 @@ Reduction JSCallReducer::ReduceJSConstruct(Node* node) { // 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::kNoReason), - check, effect, control); + 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); @@ -2297,6 +3516,47 @@ Reduction JSCallReducer::ReduceJSConstruct(Node* node) { return NoChange(); } +// ES6 String.prototype.indexOf(searchString [, position]) +// #sec-string.prototype.indexof +Reduction JSCallReducer::ReduceStringPrototypeIndexOf( + Handle<JSFunction> function, 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(); +} + Reduction JSCallReducer::ReduceJSConstructWithArrayLike(Node* node) { DCHECK_EQ(IrOpcode::kJSConstructWithArrayLike, node->opcode()); CallFrequency frequency = CallFrequencyOf(node->op()); @@ -2328,9 +3588,9 @@ Reduction JSCallReducer::ReduceSoftDeoptimize(Node* node, Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); Node* frame_state = NodeProperties::FindFrameStateBefore(node); - Node* deoptimize = - graph()->NewNode(common()->Deoptimize(DeoptimizeKind::kSoft, reason), - frame_state, effect, control); + 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()); @@ -2339,6 +3599,571 @@ Reduction JSCallReducer::ReduceSoftDeoptimize(Node* node, return Changed(node); } +namespace { + +// TODO(turbofan): This was copied from Crankshaft, might be too restrictive. +bool IsReadOnlyLengthDescriptor(Handle<Map> jsarray_map) { + DCHECK(!jsarray_map->is_dictionary_map()); + Isolate* isolate = jsarray_map->GetIsolate(); + Handle<Name> length_string = isolate->factory()->length_string(); + DescriptorArray* descriptors = jsarray_map->instance_descriptors(); + int number = + descriptors->SearchWithCache(isolate, *length_string, *jsarray_map); + DCHECK_NE(DescriptorArray::kNotFound, number); + return descriptors->GetDetails(number).IsReadOnly(); +} + +// TODO(turbofan): This was copied from Crankshaft, might be too restrictive. +bool CanInlineArrayResizeOperation(Handle<Map> receiver_map) { + Isolate* const isolate = receiver_map->GetIsolate(); + if (!receiver_map->prototype()->IsJSArray()) return false; + Handle<JSArray> receiver_prototype(JSArray::cast(receiver_map->prototype()), + isolate); + return receiver_map->instance_type() == JS_ARRAY_TYPE && + IsFastElementsKind(receiver_map->elements_kind()) && + !receiver_map->is_dictionary_map() && receiver_map->is_extensible() && + isolate->IsAnyInitialArrayPrototype(receiver_prototype) && + !IsReadOnlyLengthDescriptor(receiver_map); +} + +} // namespace + +// ES6 section 22.1.3.18 Array.prototype.push ( ) +Reduction JSCallReducer::ReduceArrayPrototypePush(Node* node) { + DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); + CallParameters const& p = CallParametersOf(node->op()); + if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) { + return NoChange(); + } + + if (!isolate()->IsNoElementsProtectorIntact()) return NoChange(); + + int const num_values = node->op()->ValueInputCount() - 2; + Node* receiver = NodeProperties::GetValueInput(node, 1); + Node* effect = NodeProperties::GetEffectInput(node); + Node* control = NodeProperties::GetControlInput(node); + + // Try to determine the {receiver} map(s). + ZoneHandleSet<Map> receiver_maps; + NodeProperties::InferReceiverMapsResult result = + NodeProperties::InferReceiverMaps(receiver, effect, &receiver_maps); + if (result == NodeProperties::kNoReceiverMaps) return NoChange(); + DCHECK_NE(0, receiver_maps.size()); + + ElementsKind kind = receiver_maps[0]->elements_kind(); + + for (Handle<Map> receiver_map : receiver_maps) { + if (!CanInlineArrayResizeOperation(receiver_map)) return NoChange(); + if (!UnionElementsKindUptoPackedness(&kind, receiver_map->elements_kind())) + return NoChange(); + } + + // Install code dependencies on the {receiver} global array protector cell. + dependencies()->AssumePropertyCell(factory()->no_elements_protector()); + + // If the {receiver_maps} information is not reliable, we need + // to check that the {receiver} still has one of these maps. + if (result == NodeProperties::kUnreliableReceiverMaps) { + effect = + graph()->NewNode(simplified()->CheckMaps(CheckMapsFlag::kNone, + receiver_maps, p.feedback()), + receiver, effect, control); + } + + // Collect the value inputs to push. + std::vector<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); + Node* value = length; + + // Check if we have any {values} to push. + if (num_values > 0) { + // Compute the resulting "length" of the {receiver}. + Node* new_length = value = graph()->NewNode( + simplified()->NumberAdd(), length, jsgraph()->Constant(num_values)); + + // Load the elements backing store of the {receiver}. + Node* elements = effect = graph()->NewNode( + simplified()->LoadField(AccessBuilder::ForJSObjectElements()), receiver, + effect, control); + Node* elements_length = effect = graph()->NewNode( + simplified()->LoadField(AccessBuilder::ForFixedArrayLength()), elements, + effect, control); + + GrowFastElementsMode mode = + IsDoubleElementsKind(kind) ? GrowFastElementsMode::kDoubleElements + : GrowFastElementsMode::kSmiOrObjectElements; + elements = effect = graph()->NewNode( + simplified()->MaybeGrowFastElements(mode, p.feedback()), receiver, + elements, + graph()->NewNode(simplified()->NumberAdd(), length, + jsgraph()->Constant(num_values - 1)), + elements_length, effect, control); + + // Update the JSArray::length field. Since this is observable, + // there must be no other check after this. + effect = graph()->NewNode( + simplified()->StoreField(AccessBuilder::ForJSArrayLength(kind)), + receiver, new_length, effect, control); + + // Append the {values} to the {elements}. + for (int i = 0; i < num_values; ++i) { + Node* value = values[i]; + Node* index = graph()->NewNode(simplified()->NumberAdd(), length, + jsgraph()->Constant(i)); + effect = graph()->NewNode( + simplified()->StoreElement(AccessBuilder::ForFixedArrayElement(kind)), + elements, index, value, effect, control); + } + } + + ReplaceWithValue(node, value, effect, control); + return Replace(value); +} + +// ES6 section 22.1.3.17 Array.prototype.pop ( ) +Reduction JSCallReducer::ReduceArrayPrototypePop(Node* node) { + DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); + CallParameters const& p = CallParametersOf(node->op()); + if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) { + return NoChange(); + } + + if (!isolate()->IsNoElementsProtectorIntact()) return NoChange(); + + Node* receiver = NodeProperties::GetValueInput(node, 1); + Node* effect = NodeProperties::GetEffectInput(node); + Node* control = NodeProperties::GetControlInput(node); + + ZoneHandleSet<Map> receiver_maps; + NodeProperties::InferReceiverMapsResult result = + NodeProperties::InferReceiverMaps(receiver, effect, &receiver_maps); + if (result == NodeProperties::kNoReceiverMaps) return NoChange(); + DCHECK_NE(0, receiver_maps.size()); + + ElementsKind kind = receiver_maps[0]->elements_kind(); + for (Handle<Map> receiver_map : receiver_maps) { + if (!CanInlineArrayResizeOperation(receiver_map)) return NoChange(); + // TODO(turbofan): Extend this to also handle fast holey double elements + // once we got the hole NaN mess sorted out in TurboFan/V8. + if (receiver_map->elements_kind() == HOLEY_DOUBLE_ELEMENTS) + return NoChange(); + if (!UnionElementsKindUptoPackedness(&kind, receiver_map->elements_kind())) + return NoChange(); + } + + // Install code dependencies on the {receiver} global array protector cell. + dependencies()->AssumePropertyCell(factory()->no_elements_protector()); + + // If the {receiver_maps} information is not reliable, we need + // to check that the {receiver} still has one of these maps. + if (result == NodeProperties::kUnreliableReceiverMaps) { + effect = + graph()->NewNode(simplified()->CheckMaps(CheckMapsFlag::kNone, + receiver_maps, p.feedback()), + receiver, effect, control); + } + + // Load the "length" property of the {receiver}. + Node* length = effect = graph()->NewNode( + simplified()->LoadField(AccessBuilder::ForJSArrayLength(kind)), receiver, + effect, control); + + // Check if the {receiver} has any elements. + Node* check = graph()->NewNode(simplified()->NumberEqual(), length, + jsgraph()->ZeroConstant()); + Node* branch = + graph()->NewNode(common()->Branch(BranchHint::kFalse), check, control); + + Node* if_true = graph()->NewNode(common()->IfTrue(), branch); + Node* etrue = effect; + Node* vtrue = jsgraph()->UndefinedConstant(); + + Node* if_false = graph()->NewNode(common()->IfFalse(), branch); + Node* efalse = effect; + Node* vfalse; + { + // TODO(tebbi): We should trim the backing store if the capacity is too + // big, as implemented in elements.cc:ElementsAccessorBase::SetLengthImpl. + + // Load the elements backing store from the {receiver}. + Node* elements = efalse = graph()->NewNode( + simplified()->LoadField(AccessBuilder::ForJSObjectElements()), receiver, + efalse, if_false); + + // Ensure that we aren't popping from a copy-on-write backing store. + if (IsSmiOrObjectElementsKind(kind)) { + elements = efalse = + graph()->NewNode(simplified()->EnsureWritableFastElements(), receiver, + elements, efalse, if_false); + } + + // Compute the new {length}. + length = graph()->NewNode(simplified()->NumberSubtract(), length, + jsgraph()->OneConstant()); + + // Store the new {length} to the {receiver}. + efalse = graph()->NewNode( + simplified()->StoreField(AccessBuilder::ForJSArrayLength(kind)), + receiver, length, efalse, if_false); + + // Load the last entry from the {elements}. + vfalse = efalse = graph()->NewNode( + simplified()->LoadElement(AccessBuilder::ForFixedArrayElement(kind)), + elements, length, efalse, if_false); + + // Store a hole to the element we just removed from the {receiver}. + efalse = graph()->NewNode( + simplified()->StoreElement( + AccessBuilder::ForFixedArrayElement(GetHoleyElementsKind(kind))), + elements, length, jsgraph()->TheHoleConstant(), efalse, if_false); + } + + control = graph()->NewNode(common()->Merge(2), if_true, if_false); + effect = graph()->NewNode(common()->EffectPhi(2), etrue, efalse, control); + Node* value = graph()->NewNode( + common()->Phi(MachineRepresentation::kTagged, 2), vtrue, vfalse, control); + + // Convert the hole to undefined. Do this last, so that we can optimize + // conversion operator via some smart strength reduction in many cases. + if (IsHoleyElementsKind(kind)) { + value = + graph()->NewNode(simplified()->ConvertTaggedHoleToUndefined(), value); + } + + ReplaceWithValue(node, value, effect, control); + return Replace(value); +} + +// ES6 section 22.1.3.22 Array.prototype.shift ( ) +Reduction JSCallReducer::ReduceArrayPrototypeShift(Node* node) { + DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); + CallParameters const& p = CallParametersOf(node->op()); + if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) { + return NoChange(); + } + + if (!isolate()->IsNoElementsProtectorIntact()) return NoChange(); + Node* target = NodeProperties::GetValueInput(node, 0); + Node* receiver = NodeProperties::GetValueInput(node, 1); + Node* context = NodeProperties::GetContextInput(node); + Node* frame_state = NodeProperties::GetFrameStateInput(node); + Node* effect = NodeProperties::GetEffectInput(node); + Node* control = NodeProperties::GetControlInput(node); + + ZoneHandleSet<Map> receiver_maps; + NodeProperties::InferReceiverMapsResult result = + NodeProperties::InferReceiverMaps(receiver, effect, &receiver_maps); + if (result == NodeProperties::kNoReceiverMaps) return NoChange(); + DCHECK_NE(0, receiver_maps.size()); + + ElementsKind kind = receiver_maps[0]->elements_kind(); + for (Handle<Map> receiver_map : receiver_maps) { + if (!CanInlineArrayResizeOperation(receiver_map)) return NoChange(); + // TODO(turbofan): Extend this to also handle fast holey double elements + // once we got the hole NaN mess sorted out in TurboFan/V8. + if (receiver_map->elements_kind() == HOLEY_DOUBLE_ELEMENTS) + return NoChange(); + if (!UnionElementsKindUptoPackedness(&kind, receiver_map->elements_kind())) + return NoChange(); + } + + // Install code dependencies on the {receiver} global array protector cell. + dependencies()->AssumePropertyCell(factory()->no_elements_protector()); + + // If the {receiver_maps} information is not reliable, we need + // to check that the {receiver} still has one of these maps. + if (result == NodeProperties::kUnreliableReceiverMaps) { + effect = + graph()->NewNode(simplified()->CheckMaps(CheckMapsFlag::kNone, + receiver_maps, p.feedback()), + receiver, effect, control); + } + + // Load length of the {receiver}. + Node* length = effect = graph()->NewNode( + simplified()->LoadField(AccessBuilder::ForJSArrayLength(kind)), receiver, + effect, control); + + // Return undefined if {receiver} has no elements. + Node* check0 = graph()->NewNode(simplified()->NumberEqual(), length, + jsgraph()->ZeroConstant()); + Node* branch0 = + graph()->NewNode(common()->Branch(BranchHint::kFalse), check0, control); + + Node* if_true0 = graph()->NewNode(common()->IfTrue(), branch0); + Node* etrue0 = effect; + Node* vtrue0 = jsgraph()->UndefinedConstant(); + + Node* if_false0 = graph()->NewNode(common()->IfFalse(), branch0); + Node* efalse0 = effect; + Node* vfalse0; + { + // Check if we should take the fast-path. + Node* check1 = + graph()->NewNode(simplified()->NumberLessThanOrEqual(), length, + jsgraph()->Constant(JSArray::kMaxCopyElements)); + Node* branch1 = graph()->NewNode(common()->Branch(BranchHint::kTrue), + check1, if_false0); + + Node* if_true1 = graph()->NewNode(common()->IfTrue(), branch1); + Node* etrue1 = efalse0; + Node* vtrue1; + { + Node* elements = etrue1 = graph()->NewNode( + simplified()->LoadField(AccessBuilder::ForJSObjectElements()), + receiver, etrue1, if_true1); + + // Load the first element here, which we return below. + vtrue1 = etrue1 = graph()->NewNode( + simplified()->LoadElement(AccessBuilder::ForFixedArrayElement(kind)), + elements, jsgraph()->ZeroConstant(), etrue1, if_true1); + + // Ensure that we aren't shifting a copy-on-write backing store. + if (IsSmiOrObjectElementsKind(kind)) { + elements = etrue1 = + graph()->NewNode(simplified()->EnsureWritableFastElements(), + receiver, elements, etrue1, if_true1); + } + + // Shift the remaining {elements} by one towards the start. + Node* loop = graph()->NewNode(common()->Loop(2), if_true1, if_true1); + Node* eloop = + graph()->NewNode(common()->EffectPhi(2), etrue1, etrue1, loop); + Node* terminate = graph()->NewNode(common()->Terminate(), eloop, loop); + NodeProperties::MergeControlToEnd(graph(), common(), terminate); + Node* index = graph()->NewNode( + common()->Phi(MachineRepresentation::kTagged, 2), + jsgraph()->OneConstant(), + jsgraph()->Constant(JSArray::kMaxCopyElements - 1), loop); + + { + Node* check2 = + graph()->NewNode(simplified()->NumberLessThan(), index, length); + Node* branch2 = graph()->NewNode(common()->Branch(), check2, loop); + + if_true1 = graph()->NewNode(common()->IfFalse(), branch2); + etrue1 = eloop; + + Node* control = graph()->NewNode(common()->IfTrue(), branch2); + Node* effect = etrue1; + + ElementAccess const access = AccessBuilder::ForFixedArrayElement(kind); + Node* value = effect = + graph()->NewNode(simplified()->LoadElement(access), elements, index, + effect, control); + effect = + graph()->NewNode(simplified()->StoreElement(access), elements, + graph()->NewNode(simplified()->NumberSubtract(), + index, jsgraph()->OneConstant()), + value, effect, control); + + loop->ReplaceInput(1, control); + eloop->ReplaceInput(1, effect); + index->ReplaceInput(1, + graph()->NewNode(simplified()->NumberAdd(), index, + jsgraph()->OneConstant())); + } + + // Compute the new {length}. + length = graph()->NewNode(simplified()->NumberSubtract(), length, + jsgraph()->OneConstant()); + + // Store the new {length} to the {receiver}. + etrue1 = graph()->NewNode( + simplified()->StoreField(AccessBuilder::ForJSArrayLength(kind)), + receiver, length, etrue1, if_true1); + + // Store a hole to the element we just removed from the {receiver}. + etrue1 = graph()->NewNode( + simplified()->StoreElement( + AccessBuilder::ForFixedArrayElement(GetHoleyElementsKind(kind))), + elements, length, jsgraph()->TheHoleConstant(), etrue1, if_true1); + } + + Node* if_false1 = graph()->NewNode(common()->IfFalse(), branch1); + Node* efalse1 = efalse0; + Node* vfalse1; + { + // Call the generic C++ implementation. + const int builtin_index = Builtins::kArrayShift; + CallDescriptor const* const desc = 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(builtin_entry, isolate())); + Node* argc = + jsgraph()->Constant(BuiltinArguments::kNumExtraArgsWithReceiver); + if_false1 = efalse1 = vfalse1 = + graph()->NewNode(common()->Call(desc), stub_code, receiver, + jsgraph()->PaddingConstant(), argc, target, + jsgraph()->UndefinedConstant(), entry, argc, context, + frame_state, efalse1, if_false1); + } + + if_false0 = graph()->NewNode(common()->Merge(2), if_true1, if_false1); + efalse0 = + graph()->NewNode(common()->EffectPhi(2), etrue1, efalse1, if_false0); + vfalse0 = graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2), + vtrue1, vfalse1, if_false0); + } + + control = graph()->NewNode(common()->Merge(2), if_true0, if_false0); + effect = graph()->NewNode(common()->EffectPhi(2), etrue0, efalse0, control); + Node* value = + graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2), + vtrue0, vfalse0, control); + + // Convert the hole to undefined. Do this last, so that we can optimize + // conversion operator via some smart strength reduction in many cases. + if (IsHoleyElementsKind(kind)) { + value = + graph()->NewNode(simplified()->ConvertTaggedHoleToUndefined(), value); + } + + ReplaceWithValue(node, value, effect, control); + return Replace(value); +} + +// ES6 section 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 = jsgraph()->ZeroConstant(); + Node* effect = NodeProperties::GetEffectInput(node); + Node* control = NodeProperties::GetControlInput(node); + + receiver = effect = graph()->NewNode(simplified()->CheckString(p.feedback()), + receiver, effect, control); + if (node->op()->ValueInputCount() >= 3) { + index = effect = graph()->NewNode(simplified()->CheckSmi(p.feedback()), + NodeProperties::GetValueInput(node, 2), + effect, control); + // Map -0 and NaN to 0 (as per ToInteger), and the values in + // the [-2^31,-1] range to the [2^31,2^32-1] range, which will + // be considered out-of-bounds as well, because of the maximal + // String length limit in V8. + STATIC_ASSERT(String::kMaxLength <= kMaxInt); + index = graph()->NewNode(simplified()->NumberToUint32(), index); + } + + // Determine the {receiver} length. + Node* receiver_length = + graph()->NewNode(simplified()->StringLength(), receiver); + + // Check if {index} is less than {receiver} length. + Node* check = + graph()->NewNode(simplified()->NumberLessThan(), index, receiver_length); + Node* branch = + graph()->NewNode(common()->Branch(BranchHint::kTrue), check, control); + + // Return the character from the {receiver} as single character string. + Node* if_true = graph()->NewNode(common()->IfTrue(), branch); + + Node* masked_index = graph()->NewNode(simplified()->MaskIndexWithBound(), + index, receiver_length); + + Node* vtrue = graph()->NewNode(simplified()->StringCharAt(), receiver, + masked_index, if_true); + + // Return the empty string otherwise. + Node* if_false = graph()->NewNode(common()->IfFalse(), branch); + Node* vfalse = jsgraph()->EmptyStringConstant(); + + control = graph()->NewNode(common()->Merge(2), if_true, if_false); + Node* value = graph()->NewNode( + common()->Phi(MachineRepresentation::kTagged, 2), vtrue, vfalse, control); + + ReplaceWithValue(node, value, effect, control); + return Replace(value); +} + +// ES6 section 21.1.3.2 String.prototype.charCodeAt ( pos ) +Reduction JSCallReducer::ReduceStringPrototypeCharCodeAt(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 = jsgraph()->ZeroConstant(); + Node* effect = NodeProperties::GetEffectInput(node); + Node* control = NodeProperties::GetControlInput(node); + + receiver = effect = graph()->NewNode(simplified()->CheckString(p.feedback()), + receiver, effect, control); + if (node->op()->ValueInputCount() >= 3) { + index = effect = graph()->NewNode(simplified()->CheckSmi(p.feedback()), + NodeProperties::GetValueInput(node, 2), + effect, control); + + // Map -0 and NaN to 0 (as per ToInteger), and the values in + // the [-2^31,-1] range to the [2^31,2^32-1] range, which will + // be considered out-of-bounds as well, because of the maximal + // String length limit in V8. + STATIC_ASSERT(String::kMaxLength <= kMaxInt); + index = graph()->NewNode(simplified()->NumberToUint32(), index); + } + + // Determine the {receiver} length. + Node* receiver_length = + graph()->NewNode(simplified()->StringLength(), receiver); + + // Check if {index} is less than {receiver} length. + Node* check = + graph()->NewNode(simplified()->NumberLessThan(), index, receiver_length); + Node* branch = + graph()->NewNode(common()->Branch(BranchHint::kTrue), check, control); + + // Load the character from the {receiver}. + Node* if_true = graph()->NewNode(common()->IfTrue(), branch); + + Node* masked_index = graph()->NewNode(simplified()->MaskIndexWithBound(), + index, receiver_length); + + Node* vtrue = graph()->NewNode(simplified()->StringCharCodeAt(), receiver, + masked_index, if_true); + + // Return NaN otherwise. + Node* if_false = graph()->NewNode(common()->IfFalse(), branch); + Node* vfalse = jsgraph()->NaNConstant(); + + control = graph()->NewNode(common()->Merge(2), if_true, if_false); + Node* value = graph()->NewNode( + common()->Phi(MachineRepresentation::kTagged, 2), vtrue, vfalse, control); + + ReplaceWithValue(node, value, effect, control); + return Replace(value); +} + Graph* JSCallReducer::graph() const { return jsgraph()->graph(); } Isolate* JSCallReducer::isolate() const { return jsgraph()->isolate(); } |