blob: edefb0bea034e583b598586acf8408dbc555253f [file] [log] [blame]
// Copyright 2015 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/compiler/js-call-reducer.h"
#include "src/api-inl.h"
#include "src/builtins/builtins-promise-gen.h"
#include "src/builtins/builtins-utils.h"
#include "src/code-factory.h"
#include "src/code-stubs.h"
#include "src/compiler/access-builder.h"
#include "src/compiler/access-info.h"
#include "src/compiler/allocation-builder.h"
#include "src/compiler/compilation-dependencies.h"
#include "src/compiler/js-graph.h"
#include "src/compiler/linkage.h"
#include "src/compiler/node-matchers.h"
#include "src/compiler/property-access-builder.h"
#include "src/compiler/simplified-operator.h"
#include "src/compiler/type-cache.h"
#include "src/feedback-vector-inl.h"
#include "src/ic/call-optimization.h"
#include "src/objects-inl.h"
#include "src/objects/arguments-inl.h"
#include "src/objects/js-array-inl.h"
#include "src/vector-slot-pair.h"
namespace v8 {
namespace internal {
namespace compiler {
Reduction JSCallReducer::ReduceMathUnary(Node* node, const Operator* op) {
CallParameters const& p = CallParametersOf(node->op());
if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) {
return NoChange();
}
if (node->op()->ValueInputCount() < 3) {
Node* value = jsgraph()->NaNConstant();
ReplaceWithValue(node, value);
return Replace(value);
}
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node);
Node* input = NodeProperties::GetValueInput(node, 2);
input = effect =
graph()->NewNode(simplified()->SpeculativeToNumber(
NumberOperationHint::kNumberOrOddball, p.feedback()),
input, effect, control);
Node* value = graph()->NewNode(op, input);
ReplaceWithValue(node, value, effect);
return Replace(value);
}
Reduction JSCallReducer::ReduceMathBinary(Node* node, const Operator* op) {
CallParameters const& p = CallParametersOf(node->op());
if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) {
return NoChange();
}
if (node->op()->ValueInputCount() < 3) {
Node* value = jsgraph()->NaNConstant();
ReplaceWithValue(node, value);
return Replace(value);
}
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node);
Node* left = NodeProperties::GetValueInput(node, 2);
Node* right = node->op()->ValueInputCount() > 3
? NodeProperties::GetValueInput(node, 3)
: jsgraph()->NaNConstant();
left = effect =
graph()->NewNode(simplified()->SpeculativeToNumber(
NumberOperationHint::kNumberOrOddball, p.feedback()),
left, effect, control);
right = effect =
graph()->NewNode(simplified()->SpeculativeToNumber(
NumberOperationHint::kNumberOrOddball, p.feedback()),
right, effect, control);
Node* value = graph()->NewNode(op, left, right);
ReplaceWithValue(node, value, effect);
return Replace(value);
}
// ES6 section 20.2.2.19 Math.imul ( x, y )
Reduction JSCallReducer::ReduceMathImul(Node* node) {
CallParameters const& p = CallParametersOf(node->op());
if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) {
return NoChange();
}
if (node->op()->ValueInputCount() < 3) {
Node* value = jsgraph()->ZeroConstant();
ReplaceWithValue(node, value);
return Replace(value);
}
Node* left = NodeProperties::GetValueInput(node, 2);
Node* right = node->op()->ValueInputCount() > 3
? NodeProperties::GetValueInput(node, 3)
: jsgraph()->ZeroConstant();
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node);
left = effect =
graph()->NewNode(simplified()->SpeculativeToNumber(
NumberOperationHint::kNumberOrOddball, p.feedback()),
left, effect, control);
right = effect =
graph()->NewNode(simplified()->SpeculativeToNumber(
NumberOperationHint::kNumberOrOddball, p.feedback()),
right, effect, control);
left = graph()->NewNode(simplified()->NumberToUint32(), left);
right = graph()->NewNode(simplified()->NumberToUint32(), right);
Node* value = graph()->NewNode(simplified()->NumberImul(), left, right);
ReplaceWithValue(node, value, effect);
return Replace(value);
}
// ES6 section 20.2.2.11 Math.clz32 ( x )
Reduction JSCallReducer::ReduceMathClz32(Node* node) {
CallParameters const& p = CallParametersOf(node->op());
if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) {
return NoChange();
}
if (node->op()->ValueInputCount() < 3) {
Node* value = jsgraph()->Constant(32);
ReplaceWithValue(node, value);
return Replace(value);
}
Node* input = NodeProperties::GetValueInput(node, 2);
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node);
input = effect =
graph()->NewNode(simplified()->SpeculativeToNumber(
NumberOperationHint::kNumberOrOddball, p.feedback()),
input, effect, control);
input = graph()->NewNode(simplified()->NumberToUint32(), input);
Node* value = graph()->NewNode(simplified()->NumberClz32(), input);
ReplaceWithValue(node, value, effect);
return Replace(value);
}
// ES6 section 20.2.2.24 Math.max ( value1, value2, ...values )
// ES6 section 20.2.2.25 Math.min ( value1, value2, ...values )
Reduction JSCallReducer::ReduceMathMinMax(Node* node, const Operator* op,
Node* empty_value) {
CallParameters const& p = CallParametersOf(node->op());
if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) {
return NoChange();
}
if (node->op()->ValueInputCount() <= 2) {
ReplaceWithValue(node, empty_value);
return Replace(empty_value);
}
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node);
Node* value = effect =
graph()->NewNode(simplified()->SpeculativeToNumber(
NumberOperationHint::kNumberOrOddball, p.feedback()),
NodeProperties::GetValueInput(node, 2), effect, control);
for (int i = 3; i < node->op()->ValueInputCount(); i++) {
Node* input = effect = graph()->NewNode(
simplified()->SpeculativeToNumber(NumberOperationHint::kNumberOrOddball,
p.feedback()),
NodeProperties::GetValueInput(node, i), effect, control);
value = graph()->NewNode(op, value, input);
}
ReplaceWithValue(node, value, effect);
return Replace(value);
}
Reduction JSCallReducer::Reduce(Node* node) {
switch (node->opcode()) {
case IrOpcode::kJSConstruct:
return ReduceJSConstruct(node);
case IrOpcode::kJSConstructWithArrayLike:
return ReduceJSConstructWithArrayLike(node);
case IrOpcode::kJSConstructWithSpread:
return ReduceJSConstructWithSpread(node);
case IrOpcode::kJSCall:
return ReduceJSCall(node);
case IrOpcode::kJSCallWithArrayLike:
return ReduceJSCallWithArrayLike(node);
case IrOpcode::kJSCallWithSpread:
return ReduceJSCallWithSpread(node);
default:
break;
}
return NoChange();
}
void JSCallReducer::Finalize() {
// TODO(turbofan): This is not the best solution; ideally we would be able
// to teach the GraphReducer about arbitrary dependencies between different
// nodes, even if they don't show up in the use list of the other node.
std::set<Node*> const waitlist = std::move(waitlist_);
for (Node* node : waitlist) {
if (!node->IsDead()) {
Reduction const reduction = Reduce(node);
if (reduction.Changed()) {
Node* replacement = reduction.replacement();
if (replacement != node) {
Replace(node, replacement);
}
}
}
}
}
// ES6 section 22.1.1 The Array Constructor
Reduction JSCallReducer::ReduceArrayConstructor(Node* node) {
DCHECK_EQ(IrOpcode::kJSCall, node->opcode());
Node* target = NodeProperties::GetValueInput(node, 0);
CallParameters const& p = CallParametersOf(node->op());
// Turn the {node} into a {JSCreateArray} call.
DCHECK_LE(2u, p.arity());
size_t const arity = p.arity() - 2;
NodeProperties::ReplaceValueInput(node, target, 0);
NodeProperties::ReplaceValueInput(node, target, 1);
NodeProperties::ChangeOp(
node, javascript()->CreateArray(arity, MaybeHandle<AllocationSite>()));
return Changed(node);
}
// ES6 section 19.3.1.1 Boolean ( value )
Reduction JSCallReducer::ReduceBooleanConstructor(Node* node) {
DCHECK_EQ(IrOpcode::kJSCall, node->opcode());
CallParameters const& p = CallParametersOf(node->op());
// Replace the {node} with a proper {ToBoolean} operator.
DCHECK_LE(2u, p.arity());
Node* value = (p.arity() == 2) ? jsgraph()->UndefinedConstant()
: NodeProperties::GetValueInput(node, 2);
value = graph()->NewNode(simplified()->ToBoolean(), value);
ReplaceWithValue(node, value);
return Replace(value);
}
// ES section #sec-object-constructor
Reduction JSCallReducer::ReduceObjectConstructor(Node* node) {
DCHECK_EQ(IrOpcode::kJSCall, node->opcode());
CallParameters const& p = CallParametersOf(node->op());
if (p.arity() < 3) return NoChange();
Node* value = (p.arity() >= 3) ? NodeProperties::GetValueInput(node, 2)
: jsgraph()->UndefinedConstant();
Node* effect = NodeProperties::GetEffectInput(node);
// We can fold away the Object(x) call if |x| is definitely not a primitive.
if (NodeProperties::CanBePrimitive(isolate(), value, effect)) {
if (!NodeProperties::CanBeNullOrUndefined(isolate(), value, effect)) {
// Turn the {node} into a {JSToObject} call if we know that
// the {value} cannot be null or undefined.
NodeProperties::ReplaceValueInputs(node, value);
NodeProperties::ChangeOp(node, javascript()->ToObject());
return Changed(node);
}
} else {
ReplaceWithValue(node, value);
return Replace(value);
}
return NoChange();
}
// ES6 section 19.2.3.1 Function.prototype.apply ( thisArg, argArray )
Reduction JSCallReducer::ReduceFunctionPrototypeApply(Node* node) {
DCHECK_EQ(IrOpcode::kJSCall, node->opcode());
CallParameters const& p = CallParametersOf(node->op());
size_t arity = p.arity();
DCHECK_LE(2u, arity);
ConvertReceiverMode convert_mode = ConvertReceiverMode::kAny;
if (arity == 2) {
// Neither thisArg nor argArray was provided.
convert_mode = ConvertReceiverMode::kNullOrUndefined;
node->ReplaceInput(0, node->InputAt(1));
node->ReplaceInput(1, jsgraph()->UndefinedConstant());
} else if (arity == 3) {
// The argArray was not provided, just remove the {target}.
node->RemoveInput(0);
--arity;
} else {
Node* target = NodeProperties::GetValueInput(node, 1);
Node* this_argument = NodeProperties::GetValueInput(node, 2);
Node* arguments_list = NodeProperties::GetValueInput(node, 3);
Node* context = NodeProperties::GetContextInput(node);
Node* frame_state = NodeProperties::GetFrameStateInput(node);
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node);
// If {arguments_list} cannot be null or undefined, we don't need
// to expand this {node} to control-flow.
if (!NodeProperties::CanBeNullOrUndefined(isolate(), arguments_list,
effect)) {
// Massage the value inputs appropriately.
node->ReplaceInput(0, target);
node->ReplaceInput(1, this_argument);
node->ReplaceInput(2, arguments_list);
while (arity-- > 3) node->RemoveInput(3);
// Morph the {node} to a {JSCallWithArrayLike}.
NodeProperties::ChangeOp(node,
javascript()->CallWithArrayLike(p.frequency()));
Reduction const reduction = ReduceJSCallWithArrayLike(node);
return reduction.Changed() ? reduction : Changed(node);
} else {
// Check whether {arguments_list} is null.
Node* check_null =
graph()->NewNode(simplified()->ReferenceEqual(), arguments_list,
jsgraph()->NullConstant());
control = graph()->NewNode(common()->Branch(BranchHint::kFalse),
check_null, control);
Node* if_null = graph()->NewNode(common()->IfTrue(), control);
control = graph()->NewNode(common()->IfFalse(), control);
// Check whether {arguments_list} is undefined.
Node* check_undefined =
graph()->NewNode(simplified()->ReferenceEqual(), arguments_list,
jsgraph()->UndefinedConstant());
control = graph()->NewNode(common()->Branch(BranchHint::kFalse),
check_undefined, control);
Node* if_undefined = graph()->NewNode(common()->IfTrue(), control);
control = graph()->NewNode(common()->IfFalse(), control);
// Lower to {JSCallWithArrayLike} if {arguments_list} is neither null
// nor undefined.
Node* effect0 = effect;
Node* control0 = control;
Node* value0 = effect0 = control0 = graph()->NewNode(
javascript()->CallWithArrayLike(p.frequency()), target, this_argument,
arguments_list, context, frame_state, effect0, control0);
// Lower to {JSCall} if {arguments_list} is either null or undefined.
Node* effect1 = effect;
Node* control1 =
graph()->NewNode(common()->Merge(2), if_null, if_undefined);
Node* value1 = effect1 = control1 =
graph()->NewNode(javascript()->Call(2), target, this_argument,
context, frame_state, effect1, control1);
// Rewire potential exception edges.
Node* if_exception = nullptr;
if (NodeProperties::IsExceptionalCall(node, &if_exception)) {
// Create appropriate {IfException} and {IfSuccess} nodes.
Node* if_exception0 =
graph()->NewNode(common()->IfException(), control0, effect0);
control0 = graph()->NewNode(common()->IfSuccess(), control0);
Node* if_exception1 =
graph()->NewNode(common()->IfException(), control1, effect1);
control1 = graph()->NewNode(common()->IfSuccess(), control1);
// Join the exception edges.
Node* merge =
graph()->NewNode(common()->Merge(2), if_exception0, if_exception1);
Node* ephi = graph()->NewNode(common()->EffectPhi(2), if_exception0,
if_exception1, merge);
Node* phi =
graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2),
if_exception0, if_exception1, merge);
ReplaceWithValue(if_exception, phi, ephi, merge);
}
// Join control paths.
control = graph()->NewNode(common()->Merge(2), control0, control1);
effect =
graph()->NewNode(common()->EffectPhi(2), effect0, effect1, control);
Node* value =
graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2),
value0, value1, control);
ReplaceWithValue(node, value, effect, control);
return Replace(value);
}
}
// Change {node} to the new {JSCall} operator.
NodeProperties::ChangeOp(
node,
javascript()->Call(arity, p.frequency(), VectorSlotPair(), convert_mode));
// Try to further reduce the JSCall {node}.
Reduction const reduction = ReduceJSCall(node);
return reduction.Changed() ? reduction : Changed(node);
}
// ES section #sec-function.prototype.bind
Reduction JSCallReducer::ReduceFunctionPrototypeBind(Node* node) {
DCHECK_EQ(IrOpcode::kJSCall, node->opcode());
// Value inputs to the {node} are as follows:
//
// - target, which is Function.prototype.bind JSFunction
// - receiver, which is the [[BoundTargetFunction]]
// - bound_this (optional), which is the [[BoundThis]]
// - and all the remaining value inouts are [[BoundArguments]]
Node* receiver = NodeProperties::GetValueInput(node, 1);
Node* bound_this = (node->op()->ValueInputCount() < 3)
? jsgraph()->UndefinedConstant()
: NodeProperties::GetValueInput(node, 2);
Node* context = NodeProperties::GetContextInput(node);
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node);
// Ensure that the {receiver} is known to be a JSBoundFunction or
// a JSFunction with the same [[Prototype]], and all maps we've
// seen for the {receiver} so far indicate that {receiver} is
// definitely a constructor or not a constructor.
ZoneHandleSet<Map> receiver_maps;
NodeProperties::InferReceiverMapsResult result =
NodeProperties::InferReceiverMaps(isolate(), receiver, effect,
&receiver_maps);
if (result == NodeProperties::kNoReceiverMaps) return NoChange();
DCHECK_NE(0, receiver_maps.size());
bool const is_constructor = receiver_maps[0]->is_constructor();
Handle<Object> const prototype(receiver_maps[0]->prototype(), isolate());
for (Handle<Map> const receiver_map : receiver_maps) {
// Check for consistency among the {receiver_maps}.
STATIC_ASSERT(LAST_TYPE == LAST_FUNCTION_TYPE);
if (receiver_map->prototype() != *prototype) return NoChange();
if (receiver_map->is_constructor() != is_constructor) return NoChange();
if (receiver_map->instance_type() < FIRST_FUNCTION_TYPE) return NoChange();
// Disallow binding of slow-mode functions. We need to figure out
// whether the length and name property are in the original state.
if (receiver_map->is_dictionary_map()) return NoChange();
// Check whether the length and name properties are still present
// as AccessorInfo objects. In that case, their values can be
// recomputed even if the actual value of the object changes.
// This mirrors the checks done in builtins-function-gen.cc at
// runtime otherwise.
Handle<DescriptorArray> descriptors(receiver_map->instance_descriptors(),
isolate());
if (descriptors->number_of_descriptors() < 2) return NoChange();
if (descriptors->GetKey(JSFunction::kLengthDescriptorIndex) !=
ReadOnlyRoots(isolate()).length_string()) {
return NoChange();
}
if (!descriptors->GetStrongValue(JSFunction::kLengthDescriptorIndex)
->IsAccessorInfo()) {
return NoChange();
}
if (descriptors->GetKey(JSFunction::kNameDescriptorIndex) !=
ReadOnlyRoots(isolate()).name_string()) {
return NoChange();
}
if (!descriptors->GetStrongValue(JSFunction::kNameDescriptorIndex)
->IsAccessorInfo()) {
return NoChange();
}
}
// Setup the map for the resulting JSBoundFunction with the
// correct instance {prototype}.
Handle<Map> map(
is_constructor
? native_context()->bound_function_with_constructor_map()
: native_context()->bound_function_without_constructor_map(),
isolate());
if (map->prototype() != *prototype) {
map = Map::TransitionToPrototype(isolate(), map, prototype);
}
// Make sure we can rely on the {receiver_maps}.
if (result == NodeProperties::kUnreliableReceiverMaps) {
effect = graph()->NewNode(
simplified()->CheckMaps(CheckMapsFlag::kNone, receiver_maps), receiver,
effect, control);
}
// Replace the {node} with a JSCreateBoundFunction.
int const arity = std::max(0, node->op()->ValueInputCount() - 3);
int const input_count = 2 + arity + 3;
Node** inputs = graph()->zone()->NewArray<Node*>(input_count);
inputs[0] = receiver;
inputs[1] = bound_this;
for (int i = 0; i < arity; ++i) {
inputs[2 + i] = NodeProperties::GetValueInput(node, 3 + i);
}
inputs[2 + arity + 0] = context;
inputs[2 + arity + 1] = effect;
inputs[2 + arity + 2] = control;
Node* value = effect = graph()->NewNode(
javascript()->CreateBoundFunction(arity, map), input_count, inputs);
ReplaceWithValue(node, value, effect, control);
return Replace(value);
}
// ES6 section 19.2.3.3 Function.prototype.call (thisArg, ...args)
Reduction JSCallReducer::ReduceFunctionPrototypeCall(Node* node) {
DCHECK_EQ(IrOpcode::kJSCall, node->opcode());
CallParameters const& p = CallParametersOf(node->op());
Node* target = NodeProperties::GetValueInput(node, 0);
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node);
// Change context of {node} to the Function.prototype.call context,
// to ensure any exception is thrown in the correct context.
Node* context;
HeapObjectMatcher m(target);
if (m.HasValue()) {
Handle<JSFunction> function = Handle<JSFunction>::cast(m.Value());
context = jsgraph()->HeapConstant(handle(function->context(), isolate()));
} else {
context = effect = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForJSFunctionContext()), target,
effect, control);
}
NodeProperties::ReplaceContextInput(node, context);
NodeProperties::ReplaceEffectInput(node, effect);
// Remove the target from {node} and use the receiver as target instead, and
// the thisArg becomes the new target. If thisArg was not provided, insert
// undefined instead.
size_t arity = p.arity();
DCHECK_LE(2u, arity);
ConvertReceiverMode convert_mode;
if (arity == 2) {
// The thisArg was not provided, use undefined as receiver.
convert_mode = ConvertReceiverMode::kNullOrUndefined;
node->ReplaceInput(0, node->InputAt(1));
node->ReplaceInput(1, jsgraph()->UndefinedConstant());
} else {
// Just remove the target, which is the first value input.
convert_mode = ConvertReceiverMode::kAny;
node->RemoveInput(0);
--arity;
}
NodeProperties::ChangeOp(
node,
javascript()->Call(arity, p.frequency(), VectorSlotPair(), convert_mode));
// Try to further reduce the JSCall {node}.
Reduction const reduction = ReduceJSCall(node);
return reduction.Changed() ? reduction : Changed(node);
}
// ES6 section 19.2.3.6 Function.prototype [ @@hasInstance ] (V)
Reduction JSCallReducer::ReduceFunctionPrototypeHasInstance(Node* node) {
DCHECK_EQ(IrOpcode::kJSCall, node->opcode());
Node* receiver = NodeProperties::GetValueInput(node, 1);
Node* object = (node->op()->ValueInputCount() >= 3)
? NodeProperties::GetValueInput(node, 2)
: jsgraph()->UndefinedConstant();
Node* context = NodeProperties::GetContextInput(node);
Node* frame_state = NodeProperties::GetFrameStateInput(node);
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node);
// TODO(turbofan): If JSOrdinaryToInstance raises an exception, the
// stack trace doesn't contain the @@hasInstance call; we have the
// corresponding bug in the baseline case. Some massaging of the frame
// state would be necessary here.
// Morph this {node} into a JSOrdinaryHasInstance node.
node->ReplaceInput(0, receiver);
node->ReplaceInput(1, object);
node->ReplaceInput(2, context);
node->ReplaceInput(3, frame_state);
node->ReplaceInput(4, effect);
node->ReplaceInput(5, control);
node->TrimInputCount(6);
NodeProperties::ChangeOp(node, javascript()->OrdinaryHasInstance());
return Changed(node);
}
Reduction JSCallReducer::ReduceObjectGetPrototype(Node* node, Node* object) {
Node* effect = NodeProperties::GetEffectInput(node);
// Try to determine the {object} map.
ZoneHandleSet<Map> object_maps;
NodeProperties::InferReceiverMapsResult result =
NodeProperties::InferReceiverMaps(isolate(), object, effect,
&object_maps);
if (result != NodeProperties::kNoReceiverMaps) {
Handle<Map> candidate_map = object_maps[0];
Handle<Object> candidate_prototype(candidate_map->prototype(), isolate());
// Check if we can constant-fold the {candidate_prototype}.
for (size_t i = 0; i < object_maps.size(); ++i) {
Handle<Map> object_map = object_maps[i];
if (object_map->IsSpecialReceiverMap() ||
object_map->has_hidden_prototype() ||
object_map->prototype() != *candidate_prototype) {
// We exclude special receivers, like JSProxy or API objects that
// might require access checks here; we also don't want to deal
// with hidden prototypes at this point.
return NoChange();
}
// The above check also excludes maps for primitive values, which is
// important because we are not applying [[ToObject]] here as expected.
DCHECK(!object_map->IsPrimitiveMap() && object_map->IsJSReceiverMap());
if (result == NodeProperties::kUnreliableReceiverMaps &&
!object_map->is_stable()) {
return NoChange();
}
}
if (result == NodeProperties::kUnreliableReceiverMaps) {
for (size_t i = 0; i < object_maps.size(); ++i) {
dependencies()->DependOnStableMap(
MapRef(js_heap_broker(), object_maps[i]));
}
}
Node* value = jsgraph()->Constant(candidate_prototype);
ReplaceWithValue(node, value);
return Replace(value);
}
return NoChange();
}
// ES6 section 19.1.2.11 Object.getPrototypeOf ( O )
Reduction JSCallReducer::ReduceObjectGetPrototypeOf(Node* node) {
DCHECK_EQ(IrOpcode::kJSCall, node->opcode());
Node* object = (node->op()->ValueInputCount() >= 3)
? NodeProperties::GetValueInput(node, 2)
: jsgraph()->UndefinedConstant();
return ReduceObjectGetPrototype(node, object);
}
// ES section #sec-object.is
Reduction JSCallReducer::ReduceObjectIs(Node* node) {
DCHECK_EQ(IrOpcode::kJSCall, node->opcode());
CallParameters const& params = CallParametersOf(node->op());
int const argc = static_cast<int>(params.arity() - 2);
Node* lhs = (argc >= 1) ? NodeProperties::GetValueInput(node, 2)
: jsgraph()->UndefinedConstant();
Node* rhs = (argc >= 2) ? NodeProperties::GetValueInput(node, 3)
: jsgraph()->UndefinedConstant();
Node* value = graph()->NewNode(simplified()->SameValue(), lhs, rhs);
ReplaceWithValue(node, value);
return Replace(value);
}
// ES6 section B.2.2.1.1 get Object.prototype.__proto__
Reduction JSCallReducer::ReduceObjectPrototypeGetProto(Node* node) {
DCHECK_EQ(IrOpcode::kJSCall, node->opcode());
Node* receiver = NodeProperties::GetValueInput(node, 1);
return ReduceObjectGetPrototype(node, receiver);
}
// ES #sec-object.prototype.hasownproperty
Reduction JSCallReducer::ReduceObjectPrototypeHasOwnProperty(Node* node) {
DCHECK_EQ(IrOpcode::kJSCall, node->opcode());
CallParameters const& params = CallParametersOf(node->op());
int const argc = static_cast<int>(params.arity() - 2);
Node* receiver = NodeProperties::GetValueInput(node, 1);
Node* name = (argc >= 1) ? NodeProperties::GetValueInput(node, 2)
: jsgraph()->UndefinedConstant();
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node);
// We can optimize a call to Object.prototype.hasOwnProperty if it's being
// used inside a fast-mode for..in, so for code like this:
//
// for (name in receiver) {
// if (receiver.hasOwnProperty(name)) {
// ...
// }
// }
//
// If the for..in is in fast-mode, we know that the {receiver} has {name}
// as own property, otherwise the enumeration wouldn't include it. The graph
// constructed by the BytecodeGraphBuilder in this case looks like this:
// receiver
// ^ ^
// | |
// | +-+
// | |
// | JSToObject
// | ^
// | |
// | JSForInNext
// | ^
// +----+ |
// | |
// JSCall[hasOwnProperty]
// We can constant-fold the {node} to True in this case, and insert
// a (potentially redundant) map check to guard the fact that the
// {receiver} map didn't change since the dominating JSForInNext. This
// map check is only necessary when TurboFan cannot prove that there
// is no observable side effect between the {JSForInNext} and the
// {JSCall} to Object.prototype.hasOwnProperty.
//
// Also note that it's safe to look through the {JSToObject}, since the
// Object.prototype.hasOwnProperty does an implicit ToObject anyway, and
// these operations are not observable.
if (name->opcode() == IrOpcode::kJSForInNext) {
ForInMode const mode = ForInModeOf(name->op());
if (mode != ForInMode::kGeneric) {
Node* object = NodeProperties::GetValueInput(name, 0);
Node* cache_type = NodeProperties::GetValueInput(name, 2);
if (object->opcode() == IrOpcode::kJSToObject) {
object = NodeProperties::GetValueInput(object, 0);
}
if (object == receiver) {
// No need to repeat the map check if we can prove that there's no
// observable side effect between {effect} and {name].
if (!NodeProperties::NoObservableSideEffectBetween(effect, name)) {
Node* receiver_map = effect =
graph()->NewNode(simplified()->LoadField(AccessBuilder::ForMap()),
receiver, effect, control);
Node* check = graph()->NewNode(simplified()->ReferenceEqual(),
receiver_map, cache_type);
effect = graph()->NewNode(
simplified()->CheckIf(DeoptimizeReason::kWrongMap), check, effect,
control);
}
Node* value = jsgraph()->TrueConstant();
ReplaceWithValue(node, value, effect, control);
return Replace(value);
}
}
}
return NoChange();
}
// ES #sec-object.prototype.isprototypeof
Reduction JSCallReducer::ReduceObjectPrototypeIsPrototypeOf(Node* node) {
DCHECK_EQ(IrOpcode::kJSCall, node->opcode());
Node* receiver = NodeProperties::GetValueInput(node, 1);
Node* value = node->op()->ValueInputCount() > 2
? NodeProperties::GetValueInput(node, 2)
: jsgraph()->UndefinedConstant();
Node* effect = NodeProperties::GetEffectInput(node);
// Ensure that the {receiver} is known to be a JSReceiver (so that
// the ToObject step of Object.prototype.isPrototypeOf is a no-op).
ZoneHandleSet<Map> receiver_maps;
NodeProperties::InferReceiverMapsResult result =
NodeProperties::InferReceiverMaps(isolate(), receiver, effect,
&receiver_maps);
if (result == NodeProperties::kNoReceiverMaps) return NoChange();
for (size_t i = 0; i < receiver_maps.size(); ++i) {
if (!receiver_maps[i]->IsJSReceiverMap()) return NoChange();
}
// We don't check whether {value} is a proper JSReceiver here explicitly,
// and don't explicitly rule out Primitive {value}s, since all of them
// have null as their prototype, so the prototype chain walk inside the
// JSHasInPrototypeChain operator immediately aborts and yields false.
NodeProperties::ReplaceValueInput(node, value, 0);
NodeProperties::ReplaceValueInput(node, receiver, 1);
for (int i = node->op()->ValueInputCount(); i-- > 2;) {
node->RemoveInput(i);
}
NodeProperties::ChangeOp(node, javascript()->HasInPrototypeChain());
return Changed(node);
}
// ES6 section 26.1.1 Reflect.apply ( target, thisArgument, argumentsList )
Reduction JSCallReducer::ReduceReflectApply(Node* node) {
DCHECK_EQ(IrOpcode::kJSCall, node->opcode());
CallParameters const& p = CallParametersOf(node->op());
int arity = static_cast<int>(p.arity() - 2);
DCHECK_LE(0, arity);
// Massage value inputs appropriately.
node->RemoveInput(0);
node->RemoveInput(0);
while (arity < 3) {
node->InsertInput(graph()->zone(), arity++, jsgraph()->UndefinedConstant());
}
while (arity-- > 3) {
node->RemoveInput(arity);
}
NodeProperties::ChangeOp(node,
javascript()->CallWithArrayLike(p.frequency()));
Reduction const reduction = ReduceJSCallWithArrayLike(node);
return reduction.Changed() ? reduction : Changed(node);
}
// ES6 section 26.1.2 Reflect.construct ( target, argumentsList [, newTarget] )
Reduction JSCallReducer::ReduceReflectConstruct(Node* node) {
DCHECK_EQ(IrOpcode::kJSCall, node->opcode());
CallParameters const& p = CallParametersOf(node->op());
int arity = static_cast<int>(p.arity() - 2);
DCHECK_LE(0, arity);
// Massage value inputs appropriately.
node->RemoveInput(0);
node->RemoveInput(0);
while (arity < 2) {
node->InsertInput(graph()->zone(), arity++, jsgraph()->UndefinedConstant());
}
if (arity < 3) {
node->InsertInput(graph()->zone(), arity++, node->InputAt(0));
}
while (arity-- > 3) {
node->RemoveInput(arity);
}
NodeProperties::ChangeOp(node,
javascript()->ConstructWithArrayLike(p.frequency()));
Reduction const reduction = ReduceJSConstructWithArrayLike(node);
return reduction.Changed() ? reduction : Changed(node);
}
// ES6 section 26.1.7 Reflect.getPrototypeOf ( target )
Reduction JSCallReducer::ReduceReflectGetPrototypeOf(Node* node) {
DCHECK_EQ(IrOpcode::kJSCall, node->opcode());
Node* target = (node->op()->ValueInputCount() >= 3)
? NodeProperties::GetValueInput(node, 2)
: jsgraph()->UndefinedConstant();
return ReduceObjectGetPrototype(node, target);
}
// ES6 section #sec-object.create Object.create(proto, properties)
Reduction JSCallReducer::ReduceObjectCreate(Node* node) {
int arg_count = node->op()->ValueInputCount();
Node* properties = arg_count >= 4 ? NodeProperties::GetValueInput(node, 3)
: jsgraph()->UndefinedConstant();
if (properties != jsgraph()->UndefinedConstant()) return NoChange();
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node);
Node* context = NodeProperties::GetContextInput(node);
Node* frame_state = NodeProperties::GetFrameStateInput(node);
Node* prototype = arg_count >= 3 ? NodeProperties::GetValueInput(node, 2)
: jsgraph()->UndefinedConstant();
node->ReplaceInput(0, prototype);
node->ReplaceInput(1, context);
node->ReplaceInput(2, frame_state);
node->ReplaceInput(3, effect);
node->ReplaceInput(4, control);
node->TrimInputCount(5);
NodeProperties::ChangeOp(node, javascript()->CreateObject());
return Changed(node);
}
// ES section #sec-reflect.get
Reduction JSCallReducer::ReduceReflectGet(Node* node) {
DCHECK_EQ(IrOpcode::kJSCall, node->opcode());
CallParameters const& p = CallParametersOf(node->op());
int arity = static_cast<int>(p.arity() - 2);
if (arity != 2) return NoChange();
Node* target = NodeProperties::GetValueInput(node, 2);
Node* key = NodeProperties::GetValueInput(node, 3);
Node* context = NodeProperties::GetContextInput(node);
Node* frame_state = NodeProperties::GetFrameStateInput(node);
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node);
// Check whether {target} is a JSReceiver.
Node* check = graph()->NewNode(simplified()->ObjectIsReceiver(), target);
Node* branch =
graph()->NewNode(common()->Branch(BranchHint::kTrue), check, control);
// Throw an appropriate TypeError if the {target} is not a JSReceiver.
Node* if_false = graph()->NewNode(common()->IfFalse(), branch);
Node* efalse = effect;
{
if_false = efalse = graph()->NewNode(
javascript()->CallRuntime(Runtime::kThrowTypeError, 2),
jsgraph()->Constant(MessageTemplate::kCalledOnNonObject),
jsgraph()->HeapConstant(
factory()->NewStringFromAsciiChecked("Reflect.get")),
context, frame_state, efalse, if_false);
}
// Otherwise just use the existing GetPropertyStub.
Node* if_true = graph()->NewNode(common()->IfTrue(), branch);
Node* etrue = effect;
Node* vtrue;
{
Callable callable =
Builtins::CallableFor(isolate(), Builtins::kGetProperty);
auto call_descriptor = Linkage::GetStubCallDescriptor(
graph()->zone(), callable.descriptor(), 0,
CallDescriptor::kNeedsFrameState, Operator::kNoProperties);
Node* stub_code = jsgraph()->HeapConstant(callable.code());
vtrue = etrue = if_true =
graph()->NewNode(common()->Call(call_descriptor), stub_code, target,
key, context, frame_state, etrue, if_true);
}
// Rewire potential exception edges.
Node* on_exception = nullptr;
if (NodeProperties::IsExceptionalCall(node, &on_exception)) {
// Create appropriate {IfException} and {IfSuccess} nodes.
Node* extrue = graph()->NewNode(common()->IfException(), etrue, if_true);
if_true = graph()->NewNode(common()->IfSuccess(), if_true);
Node* exfalse = graph()->NewNode(common()->IfException(), efalse, if_false);
if_false = graph()->NewNode(common()->IfSuccess(), if_false);
// Join the exception edges.
Node* merge = graph()->NewNode(common()->Merge(2), extrue, exfalse);
Node* ephi =
graph()->NewNode(common()->EffectPhi(2), extrue, exfalse, merge);
Node* phi =
graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2),
extrue, exfalse, merge);
ReplaceWithValue(on_exception, phi, ephi, merge);
}
// Connect the throwing path to end.
if_false = graph()->NewNode(common()->Throw(), efalse, if_false);
NodeProperties::MergeControlToEnd(graph(), common(), if_false);
// Continue on the regular path.
ReplaceWithValue(node, vtrue, etrue, if_true);
return Changed(vtrue);
}
// ES section #sec-reflect.has
Reduction JSCallReducer::ReduceReflectHas(Node* node) {
DCHECK_EQ(IrOpcode::kJSCall, node->opcode());
CallParameters const& p = CallParametersOf(node->op());
int arity = static_cast<int>(p.arity() - 2);
DCHECK_LE(0, arity);
Node* target = (arity >= 1) ? NodeProperties::GetValueInput(node, 2)
: jsgraph()->UndefinedConstant();
Node* key = (arity >= 2) ? NodeProperties::GetValueInput(node, 3)
: jsgraph()->UndefinedConstant();
Node* context = NodeProperties::GetContextInput(node);
Node* frame_state = NodeProperties::GetFrameStateInput(node);
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node);
// Check whether {target} is a JSReceiver.
Node* check = graph()->NewNode(simplified()->ObjectIsReceiver(), target);
Node* branch =
graph()->NewNode(common()->Branch(BranchHint::kTrue), check, control);
// Throw an appropriate TypeError if the {target} is not a JSReceiver.
Node* if_false = graph()->NewNode(common()->IfFalse(), branch);
Node* efalse = effect;
{
if_false = efalse = graph()->NewNode(
javascript()->CallRuntime(Runtime::kThrowTypeError, 2),
jsgraph()->Constant(MessageTemplate::kCalledOnNonObject),
jsgraph()->HeapConstant(
factory()->NewStringFromAsciiChecked("Reflect.has")),
context, frame_state, efalse, if_false);
}
// Otherwise just use the existing {JSHasProperty} logic.
Node* if_true = graph()->NewNode(common()->IfTrue(), branch);
Node* etrue = effect;
Node* vtrue;
{
vtrue = etrue = if_true =
graph()->NewNode(javascript()->HasProperty(), target, key, context,
frame_state, etrue, if_true);
}
// Rewire potential exception edges.
Node* on_exception = nullptr;
if (NodeProperties::IsExceptionalCall(node, &on_exception)) {
// Create appropriate {IfException} and {IfSuccess} nodes.
Node* extrue = graph()->NewNode(common()->IfException(), etrue, if_true);
if_true = graph()->NewNode(common()->IfSuccess(), if_true);
Node* exfalse = graph()->NewNode(common()->IfException(), efalse, if_false);
if_false = graph()->NewNode(common()->IfSuccess(), if_false);
// Join the exception edges.
Node* merge = graph()->NewNode(common()->Merge(2), extrue, exfalse);
Node* ephi =
graph()->NewNode(common()->EffectPhi(2), extrue, exfalse, merge);
Node* phi =
graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2),
extrue, exfalse, merge);
ReplaceWithValue(on_exception, phi, ephi, merge);
}
// Connect the throwing path to end.
if_false = graph()->NewNode(common()->Throw(), efalse, if_false);
NodeProperties::MergeControlToEnd(graph(), common(), if_false);
// Continue on the regular path.
ReplaceWithValue(node, vtrue, etrue, if_true);
return Changed(vtrue);
}
bool CanInlineArrayIteratingBuiltin(Isolate* isolate,
Handle<Map> receiver_map) {
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_prototype_map() || receiver_map->is_stable()) &&
isolate->IsNoElementsProtectorIntact() &&
isolate->IsAnyInitialArrayPrototype(receiver_prototype);
}
Node* JSCallReducer::WireInLoopStart(Node* k, Node** control, Node** effect) {
Node* loop = *control =
graph()->NewNode(common()->Loop(2), *control, *control);
Node* eloop = *effect =
graph()->NewNode(common()->EffectPhi(2), *effect, *effect, loop);
Node* terminate = graph()->NewNode(common()->Terminate(), eloop, loop);
NodeProperties::MergeControlToEnd(graph(), common(), terminate);
return graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2), k,
k, loop);
}
void JSCallReducer::WireInLoopEnd(Node* loop, Node* eloop, Node* vloop, Node* k,
Node* control, Node* effect) {
loop->ReplaceInput(1, control);
vloop->ReplaceInput(1, k);
eloop->ReplaceInput(1, effect);
}
Reduction JSCallReducer::ReduceArrayForEach(Node* node,
Handle<SharedFunctionInfo> shared) {
if (!FLAG_turbo_inline_array_builtins) return NoChange();
DCHECK_EQ(IrOpcode::kJSCall, node->opcode());
CallParameters const& p = CallParametersOf(node->op());
if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) {
return NoChange();
}
Node* outer_frame_state = NodeProperties::GetFrameStateInput(node);
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node);
Node* context = NodeProperties::GetContextInput(node);
// Try to determine the {receiver} map.
Node* receiver = NodeProperties::GetValueInput(node, 1);
Node* fncallback = node->op()->ValueInputCount() > 2
? NodeProperties::GetValueInput(node, 2)
: jsgraph()->UndefinedConstant();
Node* this_arg = node->op()->ValueInputCount() > 3
? NodeProperties::GetValueInput(node, 3)
: jsgraph()->UndefinedConstant();
ZoneHandleSet<Map> receiver_maps;
NodeProperties::InferReceiverMapsResult result =
NodeProperties::InferReceiverMaps(isolate(), receiver, effect,
&receiver_maps);
if (result == NodeProperties::kNoReceiverMaps) return NoChange();
// By ensuring that {kind} is object or double, we can be polymorphic
// on different elements kinds.
ElementsKind kind = receiver_maps[0]->elements_kind();
if (IsSmiElementsKind(kind)) {
kind = FastSmiToObjectElementsKind(kind);
}
for (Handle<Map> receiver_map : receiver_maps) {
ElementsKind next_kind = receiver_map->elements_kind();
if (!CanInlineArrayIteratingBuiltin(isolate(), receiver_map)) {
return NoChange();
}
if (!IsFastElementsKind(next_kind)) {
return NoChange();
}
if (IsDoubleElementsKind(kind) != IsDoubleElementsKind(next_kind)) {
return NoChange();
}
if (IsHoleyElementsKind(next_kind)) {
kind = GetHoleyElementsKind(kind);
}
}
// Install code dependencies on the {receiver} prototype maps and the
// global array protector cell.
dependencies()->DependOnProtector(
PropertyCellRef(js_heap_broker(), factory()->no_elements_protector()));
// If we have unreliable maps, we need a map check.
if (result == NodeProperties::kUnreliableReceiverMaps) {
effect =
graph()->NewNode(simplified()->CheckMaps(CheckMapsFlag::kNone,
receiver_maps, p.feedback()),
receiver, effect, control);
}
Node* k = jsgraph()->ZeroConstant();
Node* original_length = effect = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForJSArrayLength(kind)), receiver,
effect, control);
std::vector<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(), shared, Builtins::kArrayForEachLoopLazyDeoptContinuation,
node->InputAt(0), context, &checkpoint_params[0], stack_parameters,
outer_frame_state, ContinuationFrameStateMode::LAZY);
Node* check_fail = nullptr;
Node* check_throw = nullptr;
WireInCallbackIsCallableCheck(fncallback, context, check_frame_state, effect,
&control, &check_fail, &check_throw);
// Start the loop.
Node* vloop = k = WireInLoopStart(k, &control, &effect);
Node *loop = control, *eloop = effect;
checkpoint_params[3] = k;
Node* continue_test =
graph()->NewNode(simplified()->NumberLessThan(), k, original_length);
Node* continue_branch = graph()->NewNode(common()->Branch(BranchHint::kTrue),
continue_test, control);
Node* if_true = graph()->NewNode(common()->IfTrue(), continue_branch);
Node* if_false = graph()->NewNode(common()->IfFalse(), continue_branch);
control = if_true;
Node* frame_state = CreateJavaScriptBuiltinContinuationFrameState(
jsgraph(), shared, Builtins::kArrayForEachLoopEagerDeoptContinuation,
node->InputAt(0), context, &checkpoint_params[0], stack_parameters,
outer_frame_state, ContinuationFrameStateMode::EAGER);
effect =
graph()->NewNode(common()->Checkpoint(), frame_state, effect, control);
// Make sure the map hasn't changed during the iteration
effect =
graph()->NewNode(simplified()->CheckMaps(CheckMapsFlag::kNone,
receiver_maps, p.feedback()),
receiver, effect, control);
Node* element =
SafeLoadElement(kind, receiver, control, &effect, &k, p.feedback());
Node* next_k =
graph()->NewNode(simplified()->NumberAdd(), k, jsgraph()->OneConstant());
checkpoint_params[3] = next_k;
Node* hole_true = nullptr;
Node* hole_false = nullptr;
Node* effect_true = effect;
if (IsHoleyElementsKind(kind)) {
// Holey elements kind require a hole check and skipping of the element in
// the case of a hole.
Node* check;
if (IsDoubleElementsKind(kind)) {
check = graph()->NewNode(simplified()->NumberIsFloat64Hole(), element);
} else {
check = graph()->NewNode(simplified()->ReferenceEqual(), element,
jsgraph()->TheHoleConstant());
}
Node* branch =
graph()->NewNode(common()->Branch(BranchHint::kFalse), check, control);
hole_true = graph()->NewNode(common()->IfTrue(), branch);
hole_false = graph()->NewNode(common()->IfFalse(), branch);
control = hole_false;
// The contract is that we don't leak "the hole" into "user JavaScript",
// so we must rename the {element} here to explicitly exclude "the hole"
// from the type of {element}.
element = effect = graph()->NewNode(
common()->TypeGuard(Type::NonInternal()), element, effect, control);
}
frame_state = CreateJavaScriptBuiltinContinuationFrameState(
jsgraph(), shared, Builtins::kArrayForEachLoopLazyDeoptContinuation,
node->InputAt(0), context, &checkpoint_params[0], stack_parameters,
outer_frame_state, ContinuationFrameStateMode::LAZY);
control = effect = graph()->NewNode(
javascript()->Call(5, p.frequency()), fncallback, this_arg, element, k,
receiver, context, frame_state, effect, control);
// Rewire potential exception edges.
Node* on_exception = nullptr;
if (NodeProperties::IsExceptionalCall(node, &on_exception)) {
RewirePostCallbackExceptionEdges(check_throw, on_exception, effect,
&check_fail, &control);
}
if (IsHoleyElementsKind(kind)) {
Node* after_call_control = control;
Node* after_call_effect = effect;
control = hole_true;
effect = effect_true;
control = graph()->NewNode(common()->Merge(2), control, after_call_control);
effect = graph()->NewNode(common()->EffectPhi(2), effect, after_call_effect,
control);
}
WireInLoopEnd(loop, eloop, vloop, next_k, control, effect);
control = if_false;
effect = eloop;
// Wire up the branch for the case when IsCallable fails for the callback.
// Since {check_throw} is an unconditional throw, it's impossible to
// return a successful completion. Therefore, we simply connect the successful
// completion to the graph end.
Node* throw_node =
graph()->NewNode(common()->Throw(), check_throw, check_fail);
NodeProperties::MergeControlToEnd(graph(), common(), throw_node);
ReplaceWithValue(node, jsgraph()->UndefinedConstant(), effect, control);
return Replace(jsgraph()->UndefinedConstant());
}
Reduction JSCallReducer::ReduceArrayReduce(Node* node,
ArrayReduceDirection direction,
Handle<SharedFunctionInfo> shared) {
if (!FLAG_turbo_inline_array_builtins) return NoChange();
DCHECK_EQ(IrOpcode::kJSCall, node->opcode());
CallParameters const& p = CallParametersOf(node->op());
if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) {
return NoChange();
}
bool left = direction == ArrayReduceDirection::kLeft;
Node* outer_frame_state = NodeProperties::GetFrameStateInput(node);
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node);
Node* context = NodeProperties::GetContextInput(node);
// Try to determine the {receiver} map.
Node* receiver = NodeProperties::GetValueInput(node, 1);
Node* fncallback = node->op()->ValueInputCount() > 2
? NodeProperties::GetValueInput(node, 2)
: jsgraph()->UndefinedConstant();
ZoneHandleSet<Map> receiver_maps;
NodeProperties::InferReceiverMapsResult result =
NodeProperties::InferReceiverMaps(isolate(), receiver, effect,
&receiver_maps);
if (result == NodeProperties::kNoReceiverMaps) return NoChange();
ElementsKind kind = receiver_maps[0]->elements_kind();
for (Handle<Map> receiver_map : receiver_maps) {
if (!CanInlineArrayIteratingBuiltin(isolate(), receiver_map))
return NoChange();
if (!UnionElementsKindUptoSize(&kind, receiver_map->elements_kind()))
return NoChange();
}
std::function<Node*(Node*)> hole_check = [this, kind](Node* element) {
if (IsDoubleElementsKind(kind)) {
return graph()->NewNode(simplified()->NumberIsFloat64Hole(), element);
} else {
return graph()->NewNode(simplified()->ReferenceEqual(), element,
jsgraph()->TheHoleConstant());
}
};
// Install code dependencies on the {receiver} prototype maps and the
// global array protector cell.
dependencies()->DependOnProtector(
PropertyCellRef(js_heap_broker(), factory()->no_elements_protector()));
// If we have unreliable maps, we need a map check.
if (result == NodeProperties::kUnreliableReceiverMaps) {
effect =
graph()->NewNode(simplified()->CheckMaps(CheckMapsFlag::kNone,
receiver_maps, p.feedback()),
receiver, effect, control);
}
Node* original_length = effect = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForJSArrayLength(PACKED_ELEMENTS)),
receiver, effect, control);
Node* initial_index =
left ? jsgraph()->ZeroConstant()
: graph()->NewNode(simplified()->NumberSubtract(), original_length,
jsgraph()->OneConstant());
const Operator* next_op =
left ? simplified()->NumberAdd() : simplified()->NumberSubtract();
Node* k = initial_index;
Node* check_frame_state;
{
Builtins::Name builtin_lazy =
left ? Builtins::kArrayReduceLoopLazyDeoptContinuation
: Builtins::kArrayReduceRightLoopLazyDeoptContinuation;
const std::vector<Node*> checkpoint_params(
{receiver, fncallback, k, original_length,
jsgraph()->UndefinedConstant()});
const int stack_parameters = static_cast<int>(checkpoint_params.size());
check_frame_state = CreateJavaScriptBuiltinContinuationFrameState(
jsgraph(), shared, builtin_lazy, node->InputAt(0), context,
checkpoint_params.data(), stack_parameters - 1, outer_frame_state,
ContinuationFrameStateMode::LAZY);
}
Node* check_fail = nullptr;
Node* check_throw = nullptr;
// Check whether the given callback function is callable. Note that
// this has to happen outside the loop to make sure we also throw on
// empty arrays.
WireInCallbackIsCallableCheck(fncallback, context, check_frame_state, effect,
&control, &check_fail, &check_throw);
// Set initial accumulator value
Node* cur = jsgraph()->TheHoleConstant();
if (node->op()->ValueInputCount() > 3) {
cur = NodeProperties::GetValueInput(node, 3);
} else {
// Find first/last non holey element. In case the search fails, we need a
// deopt continuation.
Builtins::Name builtin_eager =
left ? Builtins::kArrayReducePreLoopEagerDeoptContinuation
: Builtins::kArrayReduceRightPreLoopEagerDeoptContinuation;
const std::vector<Node*> checkpoint_params(
{receiver, fncallback, original_length});
const int stack_parameters = static_cast<int>(checkpoint_params.size());
Node* find_first_element_frame_state =
CreateJavaScriptBuiltinContinuationFrameState(
jsgraph(), shared, builtin_eager, node->InputAt(0), context,
checkpoint_params.data(), stack_parameters, outer_frame_state,
ContinuationFrameStateMode::EAGER);
Node* vloop = k = WireInLoopStart(k, &control, &effect);
Node* loop = control;
Node* eloop = effect;
effect = graph()->NewNode(common()->Checkpoint(),
find_first_element_frame_state, effect, control);
Node* continue_test =
left ? graph()->NewNode(simplified()->NumberLessThan(), k,
original_length)
: graph()->NewNode(simplified()->NumberLessThanOrEqual(),
jsgraph()->ZeroConstant(), k);
effect = graph()->NewNode(
simplified()->CheckIf(DeoptimizeReason::kNoInitialElement),
continue_test, effect, control);
cur = SafeLoadElement(kind, receiver, control, &effect, &k, p.feedback());
Node* next_k = graph()->NewNode(next_op, k, jsgraph()->OneConstant());
Node* hole_branch = graph()->NewNode(common()->Branch(BranchHint::kTrue),
hole_check(cur), control);
Node* found_el = graph()->NewNode(common()->IfFalse(), hole_branch);
control = found_el;
Node* is_hole = graph()->NewNode(common()->IfTrue(), hole_branch);
WireInLoopEnd(loop, eloop, vloop, next_k, is_hole, effect);
// We did the hole-check, so exclude hole from the type.
cur = effect = graph()->NewNode(common()->TypeGuard(Type::NonInternal()),
cur, effect, control);
k = next_k;
}
// Start the loop.
Node* loop = control = graph()->NewNode(common()->Loop(2), control, control);
Node* eloop = effect =
graph()->NewNode(common()->EffectPhi(2), effect, effect, loop);
Node* terminate = graph()->NewNode(common()->Terminate(), eloop, loop);
NodeProperties::MergeControlToEnd(graph(), common(), terminate);
Node* kloop = k = graph()->NewNode(
common()->Phi(MachineRepresentation::kTagged, 2), k, k, loop);
Node* curloop = cur = graph()->NewNode(
common()->Phi(MachineRepresentation::kTagged, 2), cur, cur, loop);
control = loop;
effect = eloop;
Node* continue_test =
left
? graph()->NewNode(simplified()->NumberLessThan(), k, original_length)
: graph()->NewNode(simplified()->NumberLessThanOrEqual(),
jsgraph()->ZeroConstant(), k);
Node* continue_branch = graph()->NewNode(common()->Branch(BranchHint::kTrue),
continue_test, control);
Node* if_true = graph()->NewNode(common()->IfTrue(), continue_branch);
Node* if_false = graph()->NewNode(common()->IfFalse(), continue_branch);
control = if_true;
{
Builtins::Name builtin_eager =
left ? Builtins::kArrayReduceLoopEagerDeoptContinuation
: Builtins::kArrayReduceRightLoopEagerDeoptContinuation;
const std::vector<Node*> checkpoint_params(
{receiver, fncallback, k, original_length, curloop});
const int stack_parameters = static_cast<int>(checkpoint_params.size());
Node* frame_state = CreateJavaScriptBuiltinContinuationFrameState(
jsgraph(), shared, builtin_eager, node->InputAt(0), context,
checkpoint_params.data(), stack_parameters, outer_frame_state,
ContinuationFrameStateMode::EAGER);
effect =
graph()->NewNode(common()->Checkpoint(), frame_state, effect, control);
}
// Make sure the map hasn't changed during the iteration
effect = graph()->NewNode(
simplified()->CheckMaps(CheckMapsFlag::kNone, receiver_maps), receiver,
effect, control);
Node* element =
SafeLoadElement(kind, receiver, control, &effect, &k, p.feedback());
Node* next_k = graph()->NewNode(next_op, k, jsgraph()->OneConstant());
Node* hole_true = nullptr;
Node* hole_false = nullptr;
Node* effect_true = effect;
if (IsHoleyElementsKind(kind)) {
// Holey elements kind require a hole check and skipping of the element in
// the case of a hole.
Node* branch = graph()->NewNode(common()->Branch(BranchHint::kFalse),
hole_check(element), control);
hole_true = graph()->NewNode(common()->IfTrue(), branch);
hole_false = graph()->NewNode(common()->IfFalse(), branch);
control = hole_false;
// The contract is that we don't leak "the hole" into "user JavaScript",
// so we must rename the {element} here to explicitly exclude "the hole"
// from the type of {element}.
element = effect = graph()->NewNode(
common()->TypeGuard(Type::NonInternal()), element, effect, control);
}
Node* next_cur;
{
Builtins::Name builtin_lazy =
left ? Builtins::kArrayReduceLoopLazyDeoptContinuation
: Builtins::kArrayReduceRightLoopLazyDeoptContinuation;
const std::vector<Node*> checkpoint_params(
{receiver, fncallback, next_k, original_length, curloop});
const int stack_parameters = static_cast<int>(checkpoint_params.size());
Node* frame_state = CreateJavaScriptBuiltinContinuationFrameState(
jsgraph(), shared, builtin_lazy, node->InputAt(0), context,
checkpoint_params.data(), stack_parameters - 1, outer_frame_state,
ContinuationFrameStateMode::LAZY);
next_cur = control = effect =
graph()->NewNode(javascript()->Call(6, p.frequency()), fncallback,
jsgraph()->UndefinedConstant(), cur, element, k,
receiver, context, frame_state, effect, control);
}
// Rewire potential exception edges.
Node* on_exception = nullptr;
if (NodeProperties::IsExceptionalCall(node, &on_exception)) {
RewirePostCallbackExceptionEdges(check_throw, on_exception, effect,
&check_fail, &control);
}
if (IsHoleyElementsKind(kind)) {
Node* after_call_control = control;
Node* after_call_effect = effect;
control = hole_true;
effect = effect_true;
control = graph()->NewNode(common()->Merge(2), control, after_call_control);
effect = graph()->NewNode(common()->EffectPhi(2), effect, after_call_effect,
control);
next_cur =
graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2), cur,
next_cur, control);
}
k = next_k;
cur = next_cur;
loop->ReplaceInput(1, control);
kloop->ReplaceInput(1, k);
curloop->ReplaceInput(1, cur);
eloop->ReplaceInput(1, effect);
control = if_false;
effect = eloop;
// Wire up the branch for the case when IsCallable fails for the callback.
// Since {check_throw} is an unconditional throw, it's impossible to
// return a successful completion. Therefore, we simply connect the successful
// completion to the graph end.
Node* throw_node =
graph()->NewNode(common()->Throw(), check_throw, check_fail);
NodeProperties::MergeControlToEnd(graph(), common(), throw_node);
ReplaceWithValue(node, curloop, effect, control);
return Replace(curloop);
}
Reduction JSCallReducer::ReduceArrayMap(Node* node,
Handle<SharedFunctionInfo> shared) {
if (!FLAG_turbo_inline_array_builtins) return NoChange();
DCHECK_EQ(IrOpcode::kJSCall, node->opcode());
CallParameters const& p = CallParametersOf(node->op());
if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) {
return NoChange();
}
Node* outer_frame_state = NodeProperties::GetFrameStateInput(node);
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node);
Node* context = NodeProperties::GetContextInput(node);
// Try to determine the {receiver} map.
Node* receiver = NodeProperties::GetValueInput(node, 1);
Node* fncallback = node->op()->ValueInputCount() > 2
? NodeProperties::GetValueInput(node, 2)
: jsgraph()->UndefinedConstant();
Node* this_arg = node->op()->ValueInputCount() > 3
? NodeProperties::GetValueInput(node, 3)
: jsgraph()->UndefinedConstant();
ZoneHandleSet<Map> receiver_maps;
NodeProperties::InferReceiverMapsResult result =
NodeProperties::InferReceiverMaps(isolate(), receiver, effect,
&receiver_maps);
if (result == NodeProperties::kNoReceiverMaps) return NoChange();
// Ensure that any changes to the Array species constructor cause deopt.
if (!isolate()->IsArraySpeciesLookupChainIntact()) return NoChange();
const ElementsKind kind = receiver_maps[0]->elements_kind();
for (Handle<Map> receiver_map : receiver_maps) {
if (!CanInlineArrayIteratingBuiltin(isolate(), receiver_map))
return NoChange();
// We can handle different maps, as long as their elements kind are the
// same.
if (receiver_map->elements_kind() != kind) return NoChange();
}
if (IsHoleyElementsKind(kind)) {
dependencies()->DependOnProtector(
PropertyCellRef(js_heap_broker(), factory()->no_elements_protector()));
}
dependencies()->DependOnProtector(
PropertyCellRef(js_heap_broker(), factory()->array_species_protector()));
Handle<JSFunction> handle_constructor(
JSFunction::cast(
native_context()->GetInitialJSArrayMap(kind)->GetConstructor()),
isolate());
Node* array_constructor = jsgraph()->HeapConstant(handle_constructor);
Node* k = jsgraph()->ZeroConstant();
// If we have unreliable maps, we need a map check.
if (result == NodeProperties::kUnreliableReceiverMaps) {
effect =
graph()->NewNode(simplified()->CheckMaps(CheckMapsFlag::kNone,
receiver_maps, p.feedback()),
receiver, effect, control);
}
Node* original_length = effect = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForJSArrayLength(kind)), receiver,
effect, control);
// Even though {JSCreateArray} is not marked as {kNoThrow}, we can elide the
// exceptional projections because it cannot throw with the given parameters.
Node* a = control = effect = graph()->NewNode(
javascript()->CreateArray(1, MaybeHandle<AllocationSite>()),
array_constructor, array_constructor, original_length, context,
outer_frame_state, effect, control);
std::vector<Node*> checkpoint_params(
{receiver, fncallback, this_arg, a, 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(), shared, Builtins::kArrayMapLoopLazyDeoptContinuation,
node->InputAt(0), context, &checkpoint_params[0], stack_parameters,
outer_frame_state, ContinuationFrameStateMode::LAZY);
Node* check_fail = nullptr;
Node* check_throw = nullptr;
WireInCallbackIsCallableCheck(fncallback, context, check_frame_state, effect,
&control, &check_fail, &check_throw);
// Start the loop.
Node* vloop = k = WireInLoopStart(k, &control, &effect);
Node *loop = control, *eloop = effect;
checkpoint_params[4] = k;
Node* continue_test =
graph()->NewNode(simplified()->NumberLessThan(), k, original_length);
Node* continue_branch = graph()->NewNode(common()->Branch(BranchHint::kTrue),
continue_test, control);
Node* if_true = graph()->NewNode(common()->IfTrue(), continue_branch);
Node* if_false = graph()->NewNode(common()->IfFalse(), continue_branch);
control = if_true;
Node* frame_state = CreateJavaScriptBuiltinContinuationFrameState(
jsgraph(), shared, Builtins::kArrayMapLoopEagerDeoptContinuation,
node->InputAt(0), context, &checkpoint_params[0], stack_parameters,
outer_frame_state, ContinuationFrameStateMode::EAGER);
effect =
graph()->NewNode(common()->Checkpoint(), frame_state, effect, control);
// Make sure the map hasn't changed during the iteration
effect =
graph()->NewNode(simplified()->CheckMaps(CheckMapsFlag::kNone,
receiver_maps, p.feedback()),
receiver, effect, control);
Node* element =
SafeLoadElement(kind, receiver, control, &effect, &k, p.feedback());
Node* next_k =
graph()->NewNode(simplified()->NumberAdd(), k, jsgraph()->OneConstant());
Node* hole_true = nullptr;
Node* hole_false = nullptr;
Node* effect_true = effect;
if (IsHoleyElementsKind(kind)) {
// Holey elements kind require a hole check and skipping of the element in
// the case of a hole.
Node* check;
if (IsDoubleElementsKind(kind)) {
check = graph()->NewNode(simplified()->NumberIsFloat64Hole(), element);
} else {
check = graph()->NewNode(simplified()->ReferenceEqual(), element,
jsgraph()->TheHoleConstant());
}
Node* branch =
graph()->NewNode(common()->Branch(BranchHint::kFalse), check, control);
hole_true = graph()->NewNode(common()->IfTrue(), branch);
hole_false = graph()->NewNode(common()->IfFalse(), branch);
control = hole_false;
// The contract is that we don't leak "the hole" into "user JavaScript",
// so we must rename the {element} here to explicitly exclude "the hole"
// from the type of {element}.
element = effect = graph()->NewNode(
common()->TypeGuard(Type::NonInternal()), element, effect, control);
}
// This frame state is dealt with by hand in
// ArrayMapLoopLazyDeoptContinuation.
frame_state = CreateJavaScriptBuiltinContinuationFrameState(
jsgraph(), shared, Builtins::kArrayMapLoopLazyDeoptContinuation,
node->InputAt(0), context, &checkpoint_params[0], stack_parameters,
outer_frame_state, ContinuationFrameStateMode::LAZY);
Node* callback_value = control = effect = graph()->NewNode(
javascript()->Call(5, p.frequency()), fncallback, this_arg, element, k,
receiver, context, frame_state, effect, control);
// Rewire potential exception edges.
Node* on_exception = nullptr;
if (NodeProperties::IsExceptionalCall(node, &on_exception)) {
RewirePostCallbackExceptionEdges(check_throw, on_exception, effect,
&check_fail, &control);
}
// The array {a} should be HOLEY_SMI_ELEMENTS because we'd only come into this
// loop if the input array length is non-zero, and "new Array({x > 0})" always
// produces a HOLEY array.
Handle<Map> double_map(Map::cast(native_context()->get(
Context::ArrayMapIndex(HOLEY_DOUBLE_ELEMENTS))),
isolate());
Handle<Map> fast_map(
Map::cast(native_context()->get(Context::ArrayMapIndex(HOLEY_ELEMENTS))),
isolate());
effect = graph()->NewNode(
simplified()->TransitionAndStoreElement(double_map, fast_map), a, k,
callback_value, effect, control);
if (IsHoleyElementsKind(kind)) {
Node* after_call_and_store_control = control;
Node* after_call_and_store_effect = effect;
control = hole_true;
effect = effect_true;
control = graph()->NewNode(common()->Merge(2), control,
after_call_and_store_control);
effect = graph()->NewNode(common()->EffectPhi(2), effect,
after_call_and_store_effect, control);
}
WireInLoopEnd(loop, eloop, vloop, next_k, control, effect);
control = if_false;
effect = eloop;
// Wire up the branch for the case when IsCallable fails for the callback.
// Since {check_throw} is an unconditional throw, it's impossible to
// return a successful completion. Therefore, we simply connect the successful
// completion to the graph end.
Node* throw_node =
graph()->NewNode(common()->Throw(), check_throw, check_fail);
NodeProperties::MergeControlToEnd(graph(), common(), throw_node);
ReplaceWithValue(node, a, effect, control);
return Replace(a);
}
Reduction JSCallReducer::ReduceArrayFilter(Node* node,
Handle<SharedFunctionInfo> shared) {
if (!FLAG_turbo_inline_array_builtins) return NoChange();
DCHECK_EQ(IrOpcode::kJSCall, node->opcode());
CallParameters const& p = CallParametersOf(node->op());
if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) {
return NoChange();
}
Node* outer_frame_state = NodeProperties::GetFrameStateInput(node);
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node);
Node* context = NodeProperties::GetContextInput(node);
// Try to determine the {receiver} map.
Node* receiver = NodeProperties::GetValueInput(node, 1);
Node* fncallback = node->op()->ValueInputCount() > 2
? NodeProperties::GetValueInput(node, 2)
: jsgraph()->UndefinedConstant();
Node* this_arg = node->op()->ValueInputCount() > 3
? NodeProperties::GetValueInput(node, 3)
: jsgraph()->UndefinedConstant();
ZoneHandleSet<Map> receiver_maps;
NodeProperties::InferReceiverMapsResult result =
NodeProperties::InferReceiverMaps(isolate(), receiver, effect,
&receiver_maps);
if (result == NodeProperties::kNoReceiverMaps) return NoChange();
// And ensure that any changes to the Array species constructor cause deopt.
if (!isolate()->IsArraySpeciesLookupChainIntact()) return NoChange();
const ElementsKind kind = receiver_maps[0]->elements_kind();
// The output array is packed (filter doesn't visit holes).
const ElementsKind packed_kind = GetPackedElementsKind(kind);
for (Handle<Map> receiver_map : receiver_maps) {
if (!CanInlineArrayIteratingBuiltin(isolate(), receiver_map)) {
return NoChange();
}
// We can handle different maps, as long as their elements kind are the
// same.
if (receiver_map->elements_kind() != kind) return NoChange();
}
if (IsHoleyElementsKind(kind)) {
dependencies()->DependOnProtector(
PropertyCellRef(js_heap_broker(), factory()->no_elements_protector()));
}
dependencies()->DependOnProtector(
PropertyCellRef(js_heap_broker(), factory()->array_species_protector()));
Handle<Map> initial_map(
Map::cast(native_context()->GetInitialJSArrayMap(packed_kind)),
isolate());
Node* k = jsgraph()->ZeroConstant();
Node* to = jsgraph()->ZeroConstant();
// If we have unreliable maps, we need a map check.
if (result == NodeProperties::kUnreliableReceiverMaps) {
effect =
graph()->NewNode(simplified()->CheckMaps(CheckMapsFlag::kNone,
receiver_maps, p.feedback()),
receiver, effect, control);
}
Node* a; // Construct the output array.
{
AllocationBuilder ab(jsgraph(), effect, control);
ab.Allocate(initial_map->instance_size(), NOT_TENURED, Type::Array());
ab.Store(AccessBuilder::ForMap(), initial_map);
Node* empty_fixed_array = jsgraph()->EmptyFixedArrayConstant();
ab.Store(AccessBuilder::ForJSObjectPropertiesOrHash(), empty_fixed_array);
ab.Store(AccessBuilder::ForJSObjectElements(), empty_fixed_array);
ab.Store(AccessBuilder::ForJSArrayLength(packed_kind),
jsgraph()->ZeroConstant());
for (int i = 0; i < initial_map->GetInObjectProperties(); ++i) {
ab.Store(AccessBuilder::ForJSObjectInObjectProperty(
MapRef(js_heap_broker(), initial_map), i),
jsgraph()->UndefinedConstant());
}
a = effect = ab.Finish();
}
Node* original_length = effect = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForJSArrayLength(kind)), receiver,
effect, control);
// Check whether the given callback function is callable. Note that this has
// to happen outside the loop to make sure we also throw on empty arrays.
Node* check_fail = nullptr;
Node* check_throw = nullptr;
{
// This frame state doesn't ever call the deopt continuation, it's only
// necessary to specifiy a continuation in order to handle the exceptional
// case. We don't have all the values available to completely fill out
// checkpoint_params yet, but that's okay because it'll never be called.
// Therefore, "to" is mentioned twice, once standing in for the k_value
// value.
std::vector<Node*> checkpoint_params(
{receiver, fncallback, this_arg, a, k, original_length, to, to});
const int stack_parameters = static_cast<int>(checkpoint_params.size());
Node* check_frame_state = CreateJavaScriptBuiltinContinuationFrameState(
jsgraph(), shared, Builtins::kArrayFilterLoopLazyDeoptContinuation,
node->InputAt(0), context, &checkpoint_params[0], stack_parameters,
outer_frame_state, ContinuationFrameStateMode::LAZY);
WireInCallbackIsCallableCheck(fncallback, context, check_frame_state,
effect, &control, &check_fail, &check_throw);
}
// Start the loop.
Node* vloop = k = WireInLoopStart(k, &control, &effect);
Node *loop = control, *eloop = effect;
Node* v_to_loop = to = graph()->NewNode(
common()->Phi(MachineRepresentation::kTaggedSigned, 2), to, to, loop);
Node* continue_test =
graph()->NewNode(simplified()->NumberLessThan(), k, original_length);
Node* continue_branch = graph()->NewNode(common()->Branch(BranchHint::kTrue),
continue_test, control);
Node* if_true = graph()->NewNode(common()->IfTrue(), continue_branch);
Node* if_false = graph()->NewNode(common()->IfFalse(), continue_branch);
control = if_true;
{
std::vector<Node*> checkpoint_params(
{receiver, fncallback, this_arg, a, k, original_length, to});
const int stack_parameters = static_cast<int>(checkpoint_params.size());
Node* frame_state = CreateJavaScriptBuiltinContinuationFrameState(
jsgraph(), shared, Builtins::kArrayFilterLoopEagerDeoptContinuation,
node->InputAt(0), context, &checkpoint_params[0], stack_parameters,
outer_frame_state, ContinuationFrameStateMode::EAGER);
effect =
graph()->NewNode(common()->Checkpoint(), frame_state, effect, control);
}
// Make sure the map hasn't changed during the iteration.
effect =
graph()->NewNode(simplified()->CheckMaps(CheckMapsFlag::kNone,
receiver_maps, p.feedback()),
receiver, effect, control);
Node* element =
SafeLoadElement(kind, receiver, control, &effect, &k, p.feedback());
Node* next_k =
graph()->NewNode(simplified()->NumberAdd(), k, jsgraph()->OneConstant());
Node* hole_true = nullptr;
Node* hole_false = nullptr;
Node* effect_true = effect;
Node* hole_true_vto = to;
if (IsHoleyElementsKind(kind)) {
// Holey elements kind require a hole check and skipping of the element in
// the case of a hole.
Node* check;
if (IsDoubleElementsKind(kind)) {
check = graph()->NewNode(simplified()->NumberIsFloat64Hole(), element);
} else {
check = graph()->NewNode(simplified()->ReferenceEqual(), element,
jsgraph()->TheHoleConstant());
}
Node* branch =
graph()->NewNode(common()->Branch(BranchHint::kFalse), check, control);
hole_true = graph()->NewNode(common()->IfTrue(), branch);
hole_false = graph()->NewNode(common()->IfFalse(), branch);
control = hole_false;
// The contract is that we don't leak "the hole" into "user JavaScript",
// so we must rename the {element} here to explicitly exclude "the hole"
// from the type of {element}.
element = effect = graph()->NewNode(
common()->TypeGuard(Type::NonInternal()), element, effect, control);
}
Node* callback_value = nullptr;
{
// This frame state is dealt with by hand in
// Builtins::kArrayFilterLoopLazyDeoptContinuation.
std::vector<Node*> checkpoint_params(
{receiver, fncallback, this_arg, a, k, original_length, element, to});
const int stack_parameters = static_cast<int>(checkpoint_params.size());
Node* frame_state = CreateJavaScriptBuiltinContinuationFrameState(
jsgraph(), shared, Builtins::kArrayFilterLoopLazyDeoptContinuation,
node->InputAt(0), context, &checkpoint_params[0], stack_parameters,
outer_frame_state, ContinuationFrameStateMode::LAZY);
callback_value = control = effect = graph()->NewNode(
javascript()->Call(5, p.frequency()), fncallback, this_arg, element, k,
receiver, context, frame_state, effect, control);
}
// Rewire potential exception edges.
Node* on_exception = nullptr;
if (NodeProperties::IsExceptionalCall(node, &on_exception)) {
RewirePostCallbackExceptionEdges(check_throw, on_exception, effect,
&check_fail, &control);
}
// We need an eager frame state for right after the callback function
// returned, just in case an attempt to grow the output array fails.
//
// Note that we are intentionally reusing the
// Builtins::kArrayFilterLoopLazyDeoptContinuation as an *eager* entry
// point in this case. This is safe, because re-evaluating a [ToBoolean]
// coercion is safe.
{
std::vector<Node*> checkpoint_params({receiver, fncallback, this_arg, a, k,
original_length, element, to,
callback_value});
const int stack_parameters = static_cast<int>(checkpoint_params.size());
Node* frame_state = CreateJavaScriptBuiltinContinuationFrameState(
jsgraph(), shared, Builtins::kArrayFilterLoopLazyDeoptContinuation,
node->InputAt(0), context, &checkpoint_params[0], stack_parameters,
outer_frame_state, ContinuationFrameStateMode::EAGER);
effect =
graph()->NewNode(common()->Checkpoint(), frame_state, effect, control);
}
// We have to coerce callback_value to boolean, and only store the element in
// a if it's true. The checkpoint above protects against the case that
// growing {a} fails.
to = DoFilterPostCallbackWork(packed_kind, &control, &effect, a, to, element,
callback_value);
if (IsHoleyElementsKind(kind)) {
Node* after_call_control = control;
Node* after_call_effect = effect;
control = hole_true;
effect = effect_true;
control = graph()->NewNode(common()->Merge(2), control, after_call_control);
effect = graph()->NewNode(common()->EffectPhi(2), effect, after_call_effect,
control);
to =
graph()->NewNode(common()->Phi(MachineRepresentation::kTaggedSigned, 2),
hole_true_vto, to, control);
}
WireInLoopEnd(loop, eloop, vloop, next_k, control, effect);
v_to_loop->ReplaceInput(1, to);
control = if_false;
effect = eloop;
// Wire up the branch for the case when IsCallable fails for the callback.
// Since {check_throw} is an unconditional throw, it's impossible to
// return a successful completion. Therefore, we simply connect the successful
// completion to the graph end.
Node* throw_node =
graph()->NewNode(common()->Throw(), check_throw, check_fail);
NodeProperties::MergeControlToEnd(graph(), common(), throw_node);
ReplaceWithValue(node, a, effect, control);
return Replace(a);
}
Reduction JSCallReducer::ReduceArrayFind(Node* node, ArrayFindVariant variant,
Handle<SharedFunctionInfo> shared) {
if (!FLAG_turbo_inline_array_builtins) return NoChange();
DCHECK_EQ(IrOpcode::kJSCall, node->opcode());
CallParameters const& p = CallParametersOf(node->op());
if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) {
return NoChange();
}
Builtins::Name eager_continuation_builtin;
Builtins::Name lazy_continuation_builtin;
Builtins::Name after_callback_lazy_continuation_builtin;
if (variant == ArrayFindVariant::kFind) {
eager_continuation_builtin = Builtins::kArrayFindLoopEagerDeoptContinuation;
lazy_continuation_builtin = Builtins::kArrayFindLoopLazyDeoptContinuation;
after_callback_lazy_continuation_builtin =
Builtins::kArrayFindLoopAfterCallbackLazyDeoptContinuation;
} else {
DCHECK_EQ(ArrayFindVariant::kFindIndex, variant);
eager_continuation_builtin =
Builtins::kArrayFindIndexLoopEagerDeoptContinuation;
lazy_continuation_builtin =
Builtins::kArrayFindIndexLoopLazyDeoptContinuation;
after_callback_lazy_continuation_builtin =
Builtins::kArrayFindIndexLoopAfterCallbackLazyDeoptContinuation;
}
Node* outer_frame_state = NodeProperties::GetFrameStateInput(node);
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node);
Node* context = NodeProperties::GetContextInput(node);
// Try to determine the {receiver} map.
Node* receiver = NodeProperties::GetValueInput(node, 1);
Node* fncallback = node->op()->ValueInputCount() > 2
? NodeProperties::GetValueInput(node, 2)
: jsgraph()->UndefinedConstant();
Node* this_arg = node->op()->ValueInputCount() > 3
? NodeProperties::GetValueInput(node, 3)
: jsgraph()->UndefinedConstant();
ZoneHandleSet<Map> receiver_maps;
NodeProperties::InferReceiverMapsResult result =
NodeProperties::InferReceiverMaps(isolate(), receiver, effect,
&receiver_maps);
if (result == NodeProperties::kNoReceiverMaps) return NoChange();
const ElementsKind kind = receiver_maps[0]->elements_kind();
// TODO(pwong): Handle holey double elements kinds.
if (IsDoubleElementsKind(kind) && IsHoleyElementsKind(kind)) {
return NoChange();
}
for (Handle<Map> receiver_map : receiver_maps) {
if (!CanInlineArrayIteratingBuiltin(isolate(), receiver_map))
return NoChange();
// We can handle different maps, as long as their elements kind are the
// same.
if (receiver_map->elements_kind() != kind) return NoChange();
}
// Install code dependencies on the {receiver} prototype maps and the
// global array protector cell.
dependencies()->DependOnProtector(
PropertyCellRef(js_heap_broker(), factory()->no_elements_protector()));
// If we have unreliable maps, we need a map check.
if (result == NodeProperties::kUnreliableReceiverMaps) {
effect =
graph()->NewNode(simplified()->CheckMaps(CheckMapsFlag::kNone,
receiver_maps, p.feedback()),
receiver, effect, control);
}
Node* k = jsgraph()->ZeroConstant();
Node* original_length = effect = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForJSArrayLength(kind)), receiver,
effect, control);
std::vector<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(), shared, lazy_continuation_builtin, node->InputAt(0), context,
&checkpoint_params[0], stack_parameters, outer_frame_state,
ContinuationFrameStateMode::LAZY);
WireInCallbackIsCallableCheck(fncallback, context, frame_state, effect,
&control, &check_fail, &check_throw);
}
// Start the loop.
Node* vloop = k = WireInLoopStart(k, &control, &effect);
Node *loop = control, *eloop = effect;
checkpoint_params[3] = k;
// Check if we've iterated past the last element of the array.
Node* if_false = nullptr;
{
Node* continue_test =
graph()->NewNode(simplified()->NumberLessThan(), k, original_length);
Node* continue_branch = graph()->NewNode(
common()->Branch(BranchHint::kTrue), continue_test, control);
control = graph()->NewNode(common()->IfTrue(), continue_branch);
if_false = graph()->NewNode(common()->IfFalse(), continue_branch);
}
// Check the map hasn't changed during the iteration.
{
Node* frame_state = CreateJavaScriptBuiltinContinuationFrameState(
jsgraph(), shared, eager_continuation_builtin, node->InputAt(0),
context, &checkpoint_params[0], stack_parameters, outer_frame_state,
ContinuationFrameStateMode::EAGER);
effect =
graph()->NewNode(common()->Checkpoint(), frame_state, effect, control);
effect =
graph()->NewNode(simplified()->CheckMaps(CheckMapsFlag::kNone,
receiver_maps, p.feedback()),
receiver, effect, control);
}
// Load k-th element from receiver.
Node* element =
SafeLoadElement(kind, receiver, control, &effect, &k, p.feedback());
// Increment k for the next iteration.
Node* next_k = checkpoint_params[3] =
graph()->NewNode(simplified()->NumberAdd(), k, jsgraph()->OneConstant());
// Replace holes with undefined.
if (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(), shared, after_callback_lazy_continuation_builtin,
node->InputAt(0), context, &call_checkpoint_params[0],
call_stack_parameters, outer_frame_state,
ContinuationFrameStateMode::LAZY);
callback_value = control = effect = graph()->NewNode(
javascript()->Call(5, p.frequency()), fncallback, this_arg, element, k,
receiver, context, frame_state, effect, control);
}
// Rewire potential exception edges.
Node* on_exception = nullptr;
if (NodeProperties::IsExceptionalCall(node, &on_exception)) {
RewirePostCallbackExceptionEdges(check_throw, on_exception, effect,
&check_fail, &control);
}
// Check whether the given callback function returned a truthy value.
Node* boolean_result =
graph()->NewNode(simplified()->ToBoolean(), callback_value);
Node* efound_branch = effect;
Node* found_branch = graph()->NewNode(common()->Branch(BranchHint::kFalse),
boolean_result, control);
Node* if_found = graph()->NewNode(common()->IfTrue(), found_branch);
Node* if_notfound = graph()->NewNode(common()->IfFalse(), found_branch);
control = if_notfound;
// Close the loop.
WireInLoopEnd(loop, eloop, vloop, next_k, control, effect);
control = graph()->NewNode(common()->Merge(2), if_found, if_false);
effect =
graph()->NewNode(common()->EffectPhi(2), efound_branch, eloop, control);
Node* if_not_found_value = (variant == ArrayFindVariant::kFind)
? jsgraph()->UndefinedConstant()
: jsgraph()->MinusOneConstant();
Node* return_value =
graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2),
if_found_return_value, if_not_found_value, control);
// Wire up the branch for the case when IsCallable fails for the callback.
// Since {check_throw} is an unconditional throw, it's impossible to
// return a successful completion. Therefore, we simply connect the successful
// completion to the graph end.
Node* throw_node =
graph()->NewNode(common()->Throw(), check_throw, check_fail);
NodeProperties::MergeControlToEnd(graph(), common(), throw_node);
ReplaceWithValue(node, return_value, effect, control);
return Replace(return_value);
}
Node* JSCallReducer::DoFilterPostCallbackWork(ElementsKind kind, Node** control,
Node** effect, Node* a, Node* to,
Node* element,
Node* callback_value) {
Node* boolean_result =
graph()->NewNode(simplified()->ToBoolean(), callback_value);
Node* check_boolean_result =
graph()->NewNode(simplified()->ReferenceEqual(), boolean_result,
jsgraph()->TrueConstant());
Node* boolean_branch = graph()->NewNode(common()->Branch(BranchHint::kTrue),
check_boolean_result, *control);
Node* if_true = graph()->NewNode(common()->IfTrue(), boolean_branch);
Node* etrue = *effect;
Node* vtrue;
{
// Load the elements backing store of the {receiver}.
Node* elements = etrue = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForJSObjectElements()), a, etrue,
if_true);
DCHECK(TypeCache::Get().kFixedDoubleArrayLengthType.Is(
TypeCache::Get().kFixedArrayLengthType));
Node* checked_to = etrue = graph()->NewNode(
common()->TypeGuard(TypeCache::Get().kFixedArrayLengthType), to, etrue,
if_true);
Node* elements_length = etrue = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForFixedArrayLength()), elements,
etrue, if_true);
GrowFastElementsMode mode =
IsDoubleElementsKind(kind) ? GrowFastElementsMode::kDoubleElements
: GrowFastElementsMode::kSmiOrObjectElements;
elements = etrue = graph()->NewNode(
simplified()->MaybeGrowFastElements(mode, VectorSlotPair()), a,
elements, checked_to, elements_length, etrue, if_true);
// Update the length of {a}.
Node* new_length_a = graph()->NewNode(simplified()->NumberAdd(), checked_to,
jsgraph()->OneConstant());
etrue = graph()->NewNode(
simplified()->StoreField(AccessBuilder::ForJSArrayLength(kind)), a,
new_length_a, etrue, if_true);
// Append the value to the {elements}.
etrue = graph()->NewNode(
simplified()->StoreElement(AccessBuilder::ForFixedArrayElement(kind)),
elements, checked_to, element, etrue, if_true);
vtrue = new_length_a;
}
Node* if_false = graph()->NewNode(common()->IfFalse(), boolean_branch);
Node* efalse = *effect;
Node* vfalse = to;
*control = graph()->NewNode(common()->Merge(2), if_true, if_false);
*effect = graph()->NewNode(common()->EffectPhi(2), etrue, efalse, *control);
to = graph()->NewNode(common()->Phi(MachineRepresentation::kTaggedSigned, 2),
vtrue, vfalse, *control);
return to;
}
void JSCallReducer::WireInCallbackIsCallableCheck(
Node* fncallback, Node* context, Node* check_frame_state, Node* effect,
Node** control, Node** check_fail, Node** check_throw) {
Node* check = graph()->NewNode(simplified()->ObjectIsCallable(), fncallback);
Node* check_branch =
graph()->NewNode(common()->Branch(BranchHint::kTrue), check, *control);
*check_fail = graph()->NewNode(common()->IfFalse(), check_branch);
*check_throw = *check_fail = graph()->NewNode(
javascript()->CallRuntime(Runtime::kThrowTypeError, 2),
jsgraph()->Constant(MessageTemplate::kCalledNonCallable), fncallback,
context, check_frame_state, effect, *check_fail);
*control = graph()->NewNode(common()->IfTrue(), check_branch);
}
void JSCallReducer::RewirePostCallbackExceptionEdges(Node* check_throw,
Node* on_exception,
Node* effect,
Node** check_fail,
Node** control) {
// Create appropriate {IfException} and {IfSuccess} nodes.
Node* if_exception0 =
graph()->NewNode(common()->IfException(), check_throw, *check_fail);
*check_fail = graph()->NewNode(common()->IfSuccess(), *check_fail);
Node* if_exception1 =
graph()->NewNode(common()->IfException(), effect, *control);
*control = graph()->NewNode(common()->IfSuccess(), *control);
// Join the exception edges.
Node* merge =
graph()->NewNode(common()->Merge(2), if_exception0, if_exception1);
Node* ephi = graph()->NewNode(common()->EffectPhi(2), if_exception0,
if_exception1, merge);
Node* phi = graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2),
if_exception0, if_exception1, merge);
ReplaceWithValue(on_exception, phi, ephi, merge);
}
Node* JSCallReducer::SafeLoadElement(ElementsKind kind, Node* receiver,
Node* control, Node** effect, Node** k,
const VectorSlotPair& feedback) {
// Make sure that the access is still in bounds, since the callback could have
// changed the array's size.
Node* length = *effect = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForJSArrayLength(kind)), receiver,
*effect, control);
*k = *effect = graph()->NewNode(simplified()->CheckBounds(feedback), *k,
length, *effect, control);
// Reload the elements pointer before calling the callback, since the previous
// callback might have resized the array causing the elements buffer to be
// re-allocated.
Node* elements = *effect = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForJSObjectElements()), receiver,
*effect, control);
Node* element = *effect = graph()->NewNode(
simplified()->LoadElement(AccessBuilder::ForFixedArrayElement(
kind, LoadSensitivity::kCritical)),
elements, *k, *effect, control);
return element;
}
Reduction JSCallReducer::ReduceArrayEvery(Node* node,
Handle<SharedFunctionInfo> shared) {
if (!FLAG_turbo_inline_array_builtins) return NoChange();
DCHECK_EQ(IrOpcode::kJSCall, node->opcode());
CallParameters const& p = CallParametersOf(node->op());
if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) {
return NoChange();
}
Node* outer_frame_state = NodeProperties::GetFrameStateInput(node);
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node);
Node* context = NodeProperties::GetContextInput(node);
// Try to determine the {receiver} map.
Node* receiver = NodeProperties::GetValueInput(node, 1);
Node* fncallback = node->op()->ValueInputCount() > 2
? NodeProperties::GetValueInput(node, 2)
: jsgraph()->UndefinedConstant();
Node* this_arg = node->op()->ValueInputCount() > 3
? NodeProperties::GetValueInput(node, 3)
: jsgraph()->UndefinedConstant();
ZoneHandleSet<Map> receiver_maps;
NodeProperties::InferReceiverMapsResult result =
NodeProperties::InferReceiverMaps(isolate(), receiver, effect,
&receiver_maps);
if (result == NodeProperties::kNoReceiverMaps) return NoChange();
// And ensure that any changes to the Array species constructor cause deopt.
if (!isolate()->IsArraySpeciesLookupChainIntact()) return NoChange();
const ElementsKind kind = receiver_maps[0]->elements_kind();
for (Handle<Map> receiver_map : receiver_maps) {
if (!CanInlineArrayIteratingBuiltin(isolate(), receiver_map))
return NoChange();
// We can handle different maps, as long as their elements kind are the
// same.
if (receiver_map->elements_kind() != kind) return NoChange();
}
if (IsHoleyElementsKind(kind)) {
dependencies()->DependOnProtector(
PropertyCellRef(js_heap_broker(), factory()->no_elements_protector()));
}
dependencies()->DependOnProtector(
PropertyCellRef(js_heap_broker(), factory()->array_species_protector()));
// If we have unreliable maps, we need a map check.
if (result == NodeProperties::kUnreliableReceiverMaps) {
effect =
graph()->NewNode(simplified()->CheckMaps(CheckMapsFlag::kNone,
receiver_maps, p.feedback()),
receiver, effect, control);
}