blob: e8e3c586cd3d0ed0a223869c7ea5274bcda9989f [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 <functional>
#include <optional>
#include "src/base/container-utils.h"
#include "src/base/small-vector.h"
#include "src/builtins/builtins-promise.h"
#include "src/builtins/builtins-utils.h"
#include "src/codegen/code-factory.h"
#include "src/codegen/tnode.h"
#include "src/compiler/access-builder.h"
#include "src/compiler/access-info.h"
#include "src/compiler/allocation-builder-inl.h"
#include "src/compiler/allocation-builder.h"
#include "src/compiler/common-operator.h"
#include "src/compiler/compilation-dependencies.h"
#include "src/compiler/fast-api-calls.h"
#include "src/compiler/feedback-source.h"
#include "src/compiler/graph-assembler.h"
#include "src/compiler/heap-refs.h"
#include "src/compiler/js-graph.h"
#include "src/compiler/js-operator.h"
#include "src/compiler/linkage.h"
#include "src/compiler/map-inference.h"
#include "src/compiler/node-matchers.h"
#include "src/compiler/opcodes.h"
#include "src/compiler/simplified-operator.h"
#include "src/compiler/state-values-utils.h"
#include "src/compiler/type-cache.h"
#include "src/compiler/use-info.h"
#include "src/flags/flags.h"
#include "src/ic/call-optimization.h"
#include "src/objects/elements-kind.h"
#include "src/objects/instance-type.h"
#include "src/objects/js-function.h"
#include "src/objects/objects-inl.h"
#include "src/objects/ordered-hash-table.h"
#include "src/utils/utils.h"
#ifdef V8_INTL_SUPPORT
#include "src/objects/intl-objects.h"
#endif
namespace v8 {
namespace internal {
namespace compiler {
// Shorter lambda declarations with less visual clutter.
#define _ [&]()
class JSCallReducerAssembler : public JSGraphAssembler {
static constexpr bool kMarkLoopExits = true;
public:
JSCallReducerAssembler(JSCallReducer* reducer, Node* node,
Node* effect = nullptr, Node* control = nullptr)
: JSGraphAssembler(
reducer->broker(), reducer->JSGraphForGraphAssembler(),
reducer->ZoneForGraphAssembler(), BranchSemantics::kJS,
[reducer](Node* n) { reducer->RevisitForGraphAssembler(n); },
kMarkLoopExits),
dependencies_(reducer->dependencies()),
node_(node) {
InitializeEffectControl(
effect ? effect : NodeProperties::GetEffectInput(node),
control ? control : NodeProperties::GetControlInput(node));
// Finish initializing the outermost catch scope.
bool has_handler =
NodeProperties::IsExceptionalCall(node, &outermost_handler_);
outermost_catch_scope_.set_has_handler(has_handler);
}
TNode<Object> ReduceJSCallWithArrayLikeOrSpreadOfEmpty(
std::unordered_set<Node*>* generated_calls_with_array_like_or_spread);
TNode<Object> ReduceMathUnary(const Operator* op);
TNode<Object> ReduceMathBinary(const Operator* op);
TNode<String> ReduceStringPrototypeSubstring();
TNode<Boolean> ReduceStringPrototypeStartsWith();
TNode<Boolean> ReduceStringPrototypeStartsWith(
StringRef search_element_string);
TNode<Boolean> ReduceStringPrototypeEndsWith();
TNode<Boolean> ReduceStringPrototypeEndsWith(StringRef search_element_string);
TNode<String> ReduceStringPrototypeCharAt(SpeculationMode speculation_mode);
TNode<String> ReduceStringPrototypeCharAt(StringRef s, uint32_t index);
TNode<Number> ReduceStringPrototypeCharCodeAt(
SpeculationMode speculation_mode);
TNode<String> ReduceStringPrototypeSlice();
TNode<Object> ReduceJSCallMathMinMaxWithArrayLike(Builtin builtin);
TNode<Object> TargetInput() const { return JSCallNode{node_ptr()}.target(); }
template <typename T>
TNode<T> ReceiverInputAs() const {
return TNode<T>::UncheckedCast(JSCallNode{node_ptr()}.receiver());
}
TNode<Object> ReceiverInput() const { return ReceiverInputAs<Object>(); }
Node* node_ptr() const { return node_; }
// Simplified operators.
TNode<Number> SpeculativeToNumber(
TNode<Object> value,
NumberOperationHint hint = NumberOperationHint::kNumberOrOddball);
TNode<Smi> CheckSmi(TNode<Object> value);
TNode<Number> CheckNumber(TNode<Object> value);
TNode<String> CheckString(TNode<Object> value);
TNode<Number> CheckBounds(TNode<Object> value, TNode<Number> limit,
CheckBoundsFlags flags = {});
// Common operators.
TNode<Smi> TypeGuardUnsignedSmall(TNode<Object> value);
TNode<Object> TypeGuardNonInternal(TNode<Object> value);
TNode<Number> TypeGuardFixedArrayLength(TNode<Object> value);
TNode<Object> Call4(const Callable& callable, TNode<Context> context,
TNode<Object> arg0, TNode<Object> arg1,
TNode<Object> arg2, TNode<Object> arg3);
// Javascript operators.
TNode<Object> JSCall3(TNode<Object> function, TNode<Object> this_arg,
TNode<Object> arg0, TNode<Object> arg1,
TNode<Object> arg2, FrameState frame_state);
TNode<Object> JSCall4(TNode<Object> function, TNode<Object> this_arg,
TNode<Object> arg0, TNode<Object> arg1,
TNode<Object> arg2, TNode<Object> arg3,
FrameState frame_state);
// Emplace a copy of the call node into the graph at current effect/control.
TNode<Object> CopyNode();
// Used in special cases in which we are certain CreateArray does not throw.
TNode<JSArray> CreateArrayNoThrow(TNode<Object> ctor, TNode<Number> size,
FrameState frame_state);
TNode<JSArray> AllocateEmptyJSArray(ElementsKind kind,
NativeContextRef native_context);
TNode<Number> NumberInc(TNode<Number> value) {
return NumberAdd(value, OneConstant());
}
TNode<Number> LoadMapElementsKind(TNode<Map> map);
template <typename T, typename U>
TNode<T> EnterMachineGraph(TNode<U> input, UseInfo use_info) {
return AddNode<T>(
graph()->NewNode(common()->EnterMachineGraph(use_info), input));
}
template <typename T, typename U>
TNode<T> ExitMachineGraph(TNode<U> input,
MachineRepresentation output_representation,
Type output_type) {
return AddNode<T>(graph()->NewNode(
common()->ExitMachineGraph(output_representation, output_type), input));
}
void MaybeInsertMapChecks(MapInference* inference,
bool has_stability_dependency) {
// TODO(jgruber): Implement MapInference::InsertMapChecks in graph
// assembler.
if (!has_stability_dependency) {
Effect e = effect();
inference->InsertMapChecks(jsgraph(), &e, Control{control()}, feedback());
InitializeEffectControl(e, control());
}
}
TNode<Object> ConvertHoleToUndefined(TNode<Object> value, ElementsKind kind) {
DCHECK(IsHoleyElementsKind(kind));
if (kind == HOLEY_DOUBLE_ELEMENTS) {
#ifdef V8_ENABLE_UNDEFINED_DOUBLE
return AddNode<Number>(graph()->NewNode(
simplified()->ChangeFloat64OrUndefinedOrHoleToTagged(), value));
#else
return AddNode<Number>(
graph()->NewNode(simplified()->ChangeFloat64HoleToTagged(), value));
#endif // V8_ENABLE_UNDEFINED_DOUBLE
}
return ConvertTaggedHoleToUndefined(value);
}
class TryCatchBuilder0 {
public:
using TryFunction = VoidGenerator0;
using CatchFunction = std::function<void(TNode<Object>)>;
TryCatchBuilder0(JSCallReducerAssembler* gasm, const TryFunction& try_body)
: gasm_(gasm), try_body_(try_body) {}
void Catch(const CatchFunction& catch_body) {
TNode<Object> handler_exception;
Effect handler_effect{nullptr};
Control handler_control{nullptr};
auto continuation = gasm_->MakeLabel();
// Try.
{
CatchScope catch_scope = CatchScope::Inner(gasm_->temp_zone(), gasm_);
try_body_();
gasm_->Goto(&continuation);
catch_scope.MergeExceptionalPaths(&handler_exception, &handler_effect,
&handler_control);
}
// Catch.
{
gasm_->InitializeEffectControl(handler_effect, handler_control);
catch_body(handler_exception);
gasm_->Goto(&continuation);
}
gasm_->Bind(&continuation);
}
private:
JSCallReducerAssembler* const gasm_;
const VoidGenerator0 try_body_;
};
TryCatchBuilder0 Try(const VoidGenerator0& try_body) {
return {this, try_body};
}
using ConditionFunction1 = std::function<TNode<Boolean>(TNode<Number>)>;
using StepFunction1 = std::function<TNode<Number>(TNode<Number>)>;
class ForBuilder0 {
using For0BodyFunction = std::function<void(TNode<Number>)>;
public:
ForBuilder0(JSGraphAssembler* gasm, TNode<Number> initial_value,
const ConditionFunction1& cond, const StepFunction1& step)
: gasm_(gasm),
initial_value_(initial_value),
cond_(cond),
step_(step) {}
void Do(const For0BodyFunction& body) {
auto loop_exit = gasm_->MakeLabel();
{
GraphAssembler::LoopScope<kPhiRepresentation> loop_scope(gasm_);
auto loop_header = loop_scope.loop_header_label();
auto loop_body = gasm_->MakeLabel();
gasm_->Goto(loop_header, initial_value_);
gasm_->Bind(loop_header);
TNode<Number> i = loop_header->PhiAt<Number>(0);
gasm_->BranchWithHint(cond_(i), &loop_body, &loop_exit,
BranchHint::kTrue);
gasm_->Bind(&loop_body);
body(i);
gasm_->Goto(loop_header, step_(i));
}
gasm_->Bind(&loop_exit);
}
private:
static constexpr MachineRepresentation kPhiRepresentation =
MachineRepresentation::kTagged;
JSGraphAssembler* const gasm_;
const TNode<Number> initial_value_;
const ConditionFunction1 cond_;
const StepFunction1 step_;
};
ForBuilder0 ForZeroUntil(TNode<Number> excluded_limit) {
TNode<Number> initial_value = ZeroConstant();
auto cond = [=, this](TNode<Number> i) {
return NumberLessThan(i, excluded_limit);
};
auto step = [=, this](TNode<Number> i) {
return NumberAdd(i, OneConstant());
};
return {this, initial_value, cond, step};
}
ForBuilder0 Forever(TNode<Number> initial_value, const StepFunction1& step) {
return {this, initial_value,
[=, this](TNode<Number>) { return TrueConstant(); }, step};
}
using For1BodyFunction = std::function<void(TNode<Number>, TNode<Object>*)>;
class ForBuilder1 {
public:
ForBuilder1(JSGraphAssembler* gasm, TNode<Number> initial_value,
const ConditionFunction1& cond, const StepFunction1& step,
TNode<Object> initial_arg0)
: gasm_(gasm),
initial_value_(initial_value),
cond_(cond),
step_(step),
initial_arg0_(initial_arg0) {}
V8_WARN_UNUSED_RESULT ForBuilder1& Do(const For1BodyFunction& body) {
body_ = body;
return *this;
}
V8_WARN_UNUSED_RESULT TNode<Object> Value() {
DCHECK(body_);
TNode<Object> arg0 = initial_arg0_;
auto loop_exit = gasm_->MakeDeferredLabel(kPhiRepresentation);
{
GraphAssembler::LoopScope<kPhiRepresentation, kPhiRepresentation>
loop_scope(gasm_);
auto loop_header = loop_scope.loop_header_label();
auto loop_body = gasm_->MakeDeferredLabel(kPhiRepresentation);
gasm_->Goto(loop_header, initial_value_, initial_arg0_);
gasm_->Bind(loop_header);
TNode<Number> i = loop_header->PhiAt<Number>(0);
arg0 = loop_header->PhiAt<Object>(1);
gasm_->BranchWithHint(cond_(i), &loop_body, &loop_exit,
BranchHint::kTrue, arg0);
gasm_->Bind(&loop_body);
body_(i, &arg0);
gasm_->Goto(loop_header, step_(i), arg0);
}
gasm_->Bind(&loop_exit);
return TNode<Object>::UncheckedCast(loop_exit.PhiAt<Object>(0));
}
void ValueIsUnused() { USE(Value()); }
private:
static constexpr MachineRepresentation kPhiRepresentation =
MachineRepresentation::kTagged;
JSGraphAssembler* const gasm_;
const TNode<Number> initial_value_;
const ConditionFunction1 cond_;
const StepFunction1 step_;
For1BodyFunction body_;
const TNode<Object> initial_arg0_;
};
ForBuilder1 For1(TNode<Number> initial_value, const ConditionFunction1& cond,
const StepFunction1& step, TNode<Object> initial_arg0) {
return {this, initial_value, cond, step, initial_arg0};
}
ForBuilder1 For1ZeroUntil(TNode<Number> excluded_limit,
TNode<Object> initial_arg0) {
TNode<Number> initial_value = ZeroConstant();
auto cond = [=, this](TNode<Number> i) {
return NumberLessThan(i, excluded_limit);
};
auto step = [=, this](TNode<Number> i) {
return NumberAdd(i, OneConstant());
};
return {this, initial_value, cond, step, initial_arg0};
}
void ThrowIfNotCallable(TNode<Object> maybe_callable,
FrameState frame_state) {
IfNot(ObjectIsCallable(maybe_callable))
.Then(_ {
JSCallRuntime1(Runtime::kThrowCalledNonCallable, maybe_callable,
ContextInput(), frame_state);
Unreachable(); // The runtime call throws unconditionally.
})
.ExpectTrue();
}
const FeedbackSource& feedback() const {
CallParameters const& p = CallParametersOf(node_ptr()->op());
return p.feedback();
}
int ArgumentCount() const { return JSCallNode{node_ptr()}.ArgumentCount(); }
TNode<Object> Argument(int index) const {
return TNode<Object>::UncheckedCast(JSCallNode{node_ptr()}.Argument(index));
}
template <typename T>
TNode<T> ArgumentAs(int index) const {
return TNode<T>::UncheckedCast(Argument(index));
}
TNode<Object> ArgumentOrNaN(int index) {
return TNode<Object>::UncheckedCast(
ArgumentCount() > index ? Argument(index) : NaNConstant());
}
TNode<Object> ArgumentOrUndefined(int index) {
return TNode<Object>::UncheckedCast(
ArgumentCount() > index ? Argument(index) : UndefinedConstant());
}
TNode<Number> ArgumentOrZero(int index) {
return TNode<Number>::UncheckedCast(
ArgumentCount() > index ? Argument(index) : ZeroConstant());
}
TNode<Context> ContextInput() const {
return TNode<Context>::UncheckedCast(
NodeProperties::GetContextInput(node_));
}
FrameState FrameStateInput() const {
return FrameState(NodeProperties::GetFrameStateInput(node_));
}
CompilationDependencies* dependencies() const { return dependencies_; }
private:
CompilationDependencies* const dependencies_;
Node* const node_;
};
enum class ArrayReduceDirection { kLeft, kRight };
enum class ArrayFindVariant { kFind, kFindIndex };
enum class ArrayEverySomeVariant { kEvery, kSome };
enum class ArrayIndexOfIncludesVariant { kIncludes, kIndexOf };
// This subclass bundles functionality specific to reducing iterating array
// builtins.
class IteratingArrayBuiltinReducerAssembler : public JSCallReducerAssembler {
public:
IteratingArrayBuiltinReducerAssembler(JSCallReducer* reducer, Node* node)
: JSCallReducerAssembler(reducer, node) {
DCHECK(v8_flags.turbo_inline_array_builtins);
}
TNode<Object> ReduceArrayPrototypeForEach(MapInference* inference,
const bool has_stability_dependency,
ElementsKind kind,
SharedFunctionInfoRef shared);
TNode<Object> ReduceArrayPrototypeReduce(MapInference* inference,
const bool has_stability_dependency,
ElementsKind kind,
ArrayReduceDirection direction,
SharedFunctionInfoRef shared);
TNode<JSArray> ReduceArrayPrototypeMap(MapInference* inference,
const bool has_stability_dependency,
ElementsKind kind,
SharedFunctionInfoRef shared,
NativeContextRef native_context);
TNode<JSArray> ReduceArrayPrototypeFilter(MapInference* inference,
const bool has_stability_dependency,
ElementsKind kind,
SharedFunctionInfoRef shared,
NativeContextRef native_context);
TNode<Object> ReduceArrayPrototypeFind(MapInference* inference,
const bool has_stability_dependency,
ElementsKind kind,
SharedFunctionInfoRef shared,
NativeContextRef native_context,
ArrayFindVariant variant);
TNode<Boolean> ReduceArrayPrototypeEverySome(
MapInference* inference, const bool has_stability_dependency,
ElementsKind kind, SharedFunctionInfoRef shared,
NativeContextRef native_context, ArrayEverySomeVariant variant);
TNode<Object> ReduceArrayPrototypeAt(ZoneVector<MapRef> kinds,
bool needs_fallback_builtin_call);
TNode<Object> ReduceArrayPrototypeIndexOfIncludes(
ElementsKind kind, ArrayIndexOfIncludesVariant variant);
TNode<Number> ReduceArrayPrototypePush(MapInference* inference);
private:
// Returns {index,value}. Assumes that the map has not changed, but possibly
// the length and backing store.
std::pair<TNode<Number>, TNode<Object>> SafeLoadElement(ElementsKind kind,
TNode<JSArray> o,
TNode<Number> index) {
// Make sure that the access is still in bounds, since the callback could
// have changed the array's size.
TNode<Number> length = LoadJSArrayLength(o, kind);
index = CheckBounds(index, length);
// 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.
TNode<HeapObject> elements =
LoadField<HeapObject>(AccessBuilder::ForJSObjectElements(), o);
TNode<Object> value = LoadElement<Object>(
AccessBuilder::ForFixedArrayElement(kind), elements, index);
return std::make_pair(index, value);
}
template <typename... Vars>
TNode<Object> MaybeSkipHole(
TNode<Object> o, ElementsKind kind,
GraphAssemblerLabel<sizeof...(Vars)>* continue_label,
TNode<Vars>... vars) {
if (!IsHoleyElementsKind(kind)) return o;
auto if_not_hole = MakeLabel(MachineRepresentationOf<Vars>::value...);
BranchWithHint(HoleCheck(kind, o), continue_label, &if_not_hole,
BranchHint::kFalse, vars...);
// 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}.
Bind(&if_not_hole);
return TypeGuardNonInternal(o);
}
TNode<Smi> LoadJSArrayLength(TNode<JSArray> array, ElementsKind kind) {
return LoadField<Smi>(AccessBuilder::ForJSArrayLength(kind), array);
}
void StoreJSArrayLength(TNode<JSArray> array, TNode<Number> value,
ElementsKind kind) {
StoreField(AccessBuilder::ForJSArrayLength(kind), array, value);
}
void StoreFixedArrayBaseElement(TNode<FixedArrayBase> o, TNode<Number> index,
TNode<Object> v, ElementsKind kind) {
StoreElement(AccessBuilder::ForFixedArrayElement(kind), o, index, v);
}
TNode<FixedArrayBase> LoadElements(TNode<JSObject> o) {
return LoadField<FixedArrayBase>(AccessBuilder::ForJSObjectElements(), o);
}
TNode<Smi> LoadFixedArrayBaseLength(TNode<FixedArrayBase> o) {
return LoadField<Smi>(AccessBuilder::ForFixedArrayLength(), o);
}
TNode<Boolean> HoleCheck(ElementsKind kind, TNode<Object> v) {
return IsDoubleElementsKind(kind)
? NumberIsFloat64Hole(TNode<Number>::UncheckedCast(v))
: IsTheHole(v);
}
};
class PromiseBuiltinReducerAssembler : public JSCallReducerAssembler {
public:
PromiseBuiltinReducerAssembler(JSCallReducer* reducer, Node* node)
: JSCallReducerAssembler(reducer, node) {
DCHECK_EQ(IrOpcode::kJSConstruct, node->opcode());
}
TNode<Object> ReducePromiseConstructor(NativeContextRef native_context);
int ConstructArity() const {
return JSConstructNode{node_ptr()}.ArgumentCount();
}
TNode<Object> TargetInput() const {
return JSConstructNode{node_ptr()}.target();
}
TNode<Object> NewTargetInput() const {
return JSConstructNode{node_ptr()}.new_target();
}
private:
TNode<JSPromise> CreatePromise(TNode<Context> context) {
return AddNode<JSPromise>(
graph()->NewNode(javascript()->CreatePromise(), context, effect()));
}
TNode<Context> CreateFunctionContext(NativeContextRef native_context,
TNode<Context> outer_context,
int slot_count) {
return AddNode<Context>(graph()->NewNode(
javascript()->CreateFunctionContext(
native_context.scope_info(broker()),
slot_count - Context::MIN_CONTEXT_SLOTS, FUNCTION_SCOPE),
outer_context, effect(), control()));
}
void StoreContextNoCellSlot(TNode<Context> context, size_t slot_index,
TNode<Object> value) {
StoreField(AccessBuilder::ForContextSlot(slot_index), context, value);
}
TNode<JSFunction> CreateClosureFromBuiltinSharedFunctionInfo(
SharedFunctionInfoRef shared, TNode<Context> context) {
DCHECK(shared.HasBuiltinId());
Handle<FeedbackCell> feedback_cell =
isolate()->factory()->many_closures_cell();
Callable const callable =
Builtins::CallableFor(isolate(), shared.builtin_id());
CodeRef code = MakeRef(broker(), *callable.code());
return AddNode<JSFunction>(graph()->NewNode(
javascript()->CreateClosure(shared, code), HeapConstant(feedback_cell),
context, effect(), control()));
}
void CallPromiseExecutor(TNode<Object> executor, TNode<JSFunction> resolve,
TNode<JSFunction> reject, FrameState frame_state) {
JSConstructNode n(node_ptr());
const ConstructParameters& p = n.Parameters();
FeedbackSource no_feedback_source{};
Node* no_feedback = UndefinedConstant();
MayThrow(_ {
return AddNode<Object>(graph()->NewNode(
javascript()->Call(JSCallNode::ArityForArgc(2), p.frequency(),
no_feedback_source,
ConvertReceiverMode::kNullOrUndefined),
executor, UndefinedConstant(), resolve, reject, no_feedback,
n.context(), frame_state, effect(), control()));
});
}
void CallPromiseReject(TNode<JSFunction> reject, TNode<Object> exception,
FrameState frame_state) {
JSConstructNode n(node_ptr());
const ConstructParameters& p = n.Parameters();
FeedbackSource no_feedback_source{};
Node* no_feedback = UndefinedConstant();
MayThrow(_ {
return AddNode<Object>(graph()->NewNode(
javascript()->Call(JSCallNode::ArityForArgc(1), p.frequency(),
no_feedback_source,
ConvertReceiverMode::kNullOrUndefined),
reject, UndefinedConstant(), exception, no_feedback, n.context(),
frame_state, effect(), control()));
});
}
};
class FastApiCallReducerAssembler : public JSCallReducerAssembler {
public:
FastApiCallReducerAssembler(
JSCallReducer* reducer, Node* node,
const FunctionTemplateInfoRef function_template_info,
FastApiCallFunction c_function, Node* receiver,
const SharedFunctionInfoRef shared, Node* target, const int arity,
Node* effect)
: JSCallReducerAssembler(reducer, node),
c_function_(c_function),
function_template_info_(function_template_info),
receiver_(receiver),
shared_(shared),
target_(target),
arity_(arity) {
DCHECK_EQ(IrOpcode::kJSCall, node->opcode());
InitializeEffectControl(effect, NodeProperties::GetControlInput(node));
}
TNode<Object> ReduceFastApiCall() {
JSCallNode n(node_ptr());
// C arguments include the receiver at index 0. Thus C index 1 corresponds
// to the JS argument 0, etc.
// All functions in c_candidate_functions_ have the same number of
// arguments, so extract c_argument_count from the first function.
const int c_argument_count =
static_cast<int>(c_function_.signature->ArgumentCount());
CHECK_GE(c_argument_count, kReceiver);
const int slow_arg_count =
// Arguments for CallApiCallbackOptimizedXXX builtin including
// context, see CallApiCallbackOptimizedDescriptor.
kSlowBuiltinParams +
// JS arguments.
kReceiver + arity_;
const int value_input_count =
FastApiCallNode::ArityForArgc(c_argument_count, slow_arg_count);
base::SmallVector<Node*, kInlineSize> inputs(value_input_count +
kEffectAndControl);
int cursor = 0;
inputs[cursor++] = n.receiver();
// TODO(turbofan): Consider refactoring CFunctionInfo to distinguish
// between receiver and arguments, simplifying this (and related) spots.
int js_args_count = c_argument_count - kReceiver;
for (int i = 0; i < js_args_count; ++i) {
if (i < n.ArgumentCount()) {
inputs[cursor++] = n.Argument(i);
} else {
inputs[cursor++] = UndefinedConstant();
}
}
// Here we add the arguments for the slow call, which will be
// reconstructed at a later phase. Those are effectively the same
// arguments as for the fast call, but we want to have them as
// separate inputs, so that SimplifiedLowering can provide the best
// possible UseInfos for each of them. The inputs to FastApiCall
// look like:
// [receiver, ... C arguments, callback data,
// slow call code, external constant for function, argc,
// FunctionTemplateInfo, holder, receiver, ... JS arguments,
// context, new frame state].
bool no_profiling =
broker()->dependencies()->DependOnNoProfilingProtector();
Callable call_api_callback = Builtins::CallableFor(
isolate(), no_profiling ? Builtin::kCallApiCallbackOptimizedNoProfiling
: Builtin::kCallApiCallbackOptimized);
CallInterfaceDescriptor cid = call_api_callback.descriptor();
DCHECK_EQ(cid.GetParameterCount() + (cid.HasContextParameter() ? 1 : 0),
kSlowBuiltinParams);
CallDescriptor* call_descriptor =
Linkage::GetStubCallDescriptor(graph()->zone(), cid, arity_ + kReceiver,
CallDescriptor::kNeedsFrameState);
ApiFunction api_function(function_template_info_.callback(broker()));
ExternalReference function_reference = ExternalReference::Create(
isolate(), &api_function, ExternalReference::DIRECT_API_CALL,
function_template_info_.c_functions(broker()).data(),
function_template_info_.c_signatures(broker()).data(),
static_cast<unsigned>(
function_template_info_.c_functions(broker()).size()));
// LINT.IfChange
// TODO(crbug.com/418936518): Support deopt for functions with return value.
Node* error_message = jsgraph()->SmiConstant(
static_cast<int>(AbortReason::kUnsupportedDeopt));
Node* continuation_frame_state =
c_function_.signature->ReturnInfo().GetType() == CTypeInfo::Type::kVoid
? CreateInlinedApiFunctionFrameState(jsgraph(), shared_, target_,
ContextInput(), receiver_,
FrameStateInput())
: CreateStubBuiltinContinuationFrameState(
jsgraph(), Builtin::kAbort, ContextInput(), &error_message, 1,
FrameStateInput(), ContinuationFrameStateMode::LAZY);
// The `DeoptimizationEntry_LazyAfterFastCall` builtin currently sets the
// return value unconditionally to `undefined`. If a continuation builtin is
// set up here, then the entry builtin should not overwrite the return
// value.
// LINT.ThenChange(/src/builtins/x64/builtins-x64.cc:DeoptAfterFastCallSetReturnValue)
// Callback data value for fast Api calls. Unlike slow Api calls, the fast
// variant passes callback data directly.
inputs[cursor++] =
Constant(function_template_info_.callback_data(broker()).value());
inputs[cursor++] = HeapConstant(call_api_callback.code());
inputs[cursor++] = ExternalConstant(function_reference);
inputs[cursor++] = NumberConstant(arity_);
inputs[cursor++] = HeapConstant(function_template_info_.object());
inputs[cursor++] = receiver_;
for (int i = 0; i < arity_; ++i) {
inputs[cursor++] = Argument(i);
}
inputs[cursor++] = ContextInput();
inputs[cursor++] = continuation_frame_state;
inputs[cursor++] = effect();
inputs[cursor++] = control();
DCHECK_EQ(cursor, value_input_count + kEffectAndControl);
return FastApiCall(call_descriptor, inputs.begin(), inputs.size());
}
private:
static constexpr int kEffectAndControl = 2;
// Api function address, argc, FunctionTemplateInfo, context.
// See CallApiCallbackOptimizedDescriptor.
static constexpr int kSlowBuiltinParams = 4;
static constexpr int kReceiver = 1;
// Enough for creating FastApiCall node with two JS arguments.
static constexpr int kInlineSize = 16;
TNode<Object> FastApiCall(CallDescriptor* descriptor, Node** inputs,
size_t inputs_size) {
return AddNode<Object>(graph()->NewNode(
simplified()->FastApiCall(c_function_, feedback(), descriptor),
static_cast<int>(inputs_size), inputs));
}
FastApiCallFunction c_function_;
const FunctionTemplateInfoRef function_template_info_;
Node* const receiver_;
const SharedFunctionInfoRef shared_;
Node* const target_;
const int arity_;
};
TNode<Number> JSCallReducerAssembler::SpeculativeToNumber(
TNode<Object> value, NumberOperationHint hint) {
return AddNode<Number>(
graph()->NewNode(simplified()->SpeculativeToNumber(hint, feedback()),
value, effect(), control()));
}
TNode<Smi> JSCallReducerAssembler::CheckSmi(TNode<Object> value) {
return AddNode<Smi>(graph()->NewNode(simplified()->CheckSmi(feedback()),
value, effect(), control()));
}
TNode<Number> JSCallReducerAssembler::CheckNumber(TNode<Object> value) {
return AddNode<Number>(graph()->NewNode(simplified()->CheckNumber(feedback()),
value, effect(), control()));
}
TNode<String> JSCallReducerAssembler::CheckString(TNode<Object> value) {
return AddNode<String>(graph()->NewNode(simplified()->CheckString(feedback()),
value, effect(), control()));
}
TNode<Number> JSCallReducerAssembler::CheckBounds(TNode<Object> value,
TNode<Number> limit,
CheckBoundsFlags flags) {
return AddNode<Number>(
graph()->NewNode(simplified()->CheckBounds(feedback(), flags), value,
limit, effect(), control()));
}
TNode<Smi> JSCallReducerAssembler::TypeGuardUnsignedSmall(TNode<Object> value) {
return TNode<Smi>::UncheckedCast(TypeGuard(Type::UnsignedSmall(), value));
}
TNode<Object> JSCallReducerAssembler::TypeGuardNonInternal(
TNode<Object> value) {
return TNode<Object>::UncheckedCast(TypeGuard(Type::NonInternal(), value));
}
TNode<Number> JSCallReducerAssembler::TypeGuardFixedArrayLength(
TNode<Object> value) {
DCHECK(TypeCache::Get()->kFixedDoubleArrayLengthType.Is(
TypeCache::Get()->kFixedArrayLengthType));
return TNode<Number>::UncheckedCast(
TypeGuard(TypeCache::Get()->kFixedArrayLengthType, value));
}
TNode<Object> JSCallReducerAssembler::Call4(
const Callable& callable, TNode<Context> context, TNode<Object> arg0,
TNode<Object> arg1, TNode<Object> arg2, TNode<Object> arg3) {
// TODO(jgruber): Make this more generic. Currently it's fitted to its single
// callsite.
CallDescriptor* desc = Linkage::GetStubCallDescriptor(
graph()->zone(), callable.descriptor(),
callable.descriptor().GetStackParameterCount(), CallDescriptor::kNoFlags,
Operator::kEliminatable);
return TNode<Object>::UncheckedCast(Call(desc, HeapConstant(callable.code()),
arg0, arg1, arg2, arg3, context));
}
TNode<Object> JSCallReducerAssembler::JSCall3(
TNode<Object> function, TNode<Object> this_arg, TNode<Object> arg0,
TNode<Object> arg1, TNode<Object> arg2, FrameState frame_state) {
JSCallNode n(node_ptr());
CallParameters const& p = n.Parameters();
return MayThrow(_ {
return AddNode<Object>(graph()->NewNode(
javascript()->Call(JSCallNode::ArityForArgc(3), p.frequency(),
p.feedback(), ConvertReceiverMode::kAny,
p.speculation_mode(),
CallFeedbackRelation::kUnrelated),
function, this_arg, arg0, arg1, arg2, n.feedback_vector(),
ContextInput(), frame_state, effect(), control()));
});
}
TNode<Object> JSCallReducerAssembler::JSCall4(
TNode<Object> function, TNode<Object> this_arg, TNode<Object> arg0,
TNode<Object> arg1, TNode<Object> arg2, TNode<Object> arg3,
FrameState frame_state) {
JSCallNode n(node_ptr());
CallParameters const& p = n.Parameters();
return MayThrow(_ {
return AddNode<Object>(graph()->NewNode(
javascript()->Call(JSCallNode::ArityForArgc(4), p.frequency(),
p.feedback(), ConvertReceiverMode::kAny,
p.speculation_mode(),
CallFeedbackRelation::kUnrelated),
function, this_arg, arg0, arg1, arg2, arg3, n.feedback_vector(),
ContextInput(), frame_state, effect(), control()));
});
}
TNode<Object> JSCallReducerAssembler::CopyNode() {
return MayThrow(_ {
Node* copy = graph()->CloneNode(node_ptr());
NodeProperties::ReplaceEffectInput(copy, effect());
NodeProperties::ReplaceControlInput(copy, control());
return AddNode<Object>(copy);
});
}
TNode<JSArray> JSCallReducerAssembler::CreateArrayNoThrow(
TNode<Object> ctor, TNode<Number> size, FrameState frame_state) {
return AddNode<JSArray>(graph()->NewNode(
javascript()->CreateArray(1, std::nullopt, feedback()), ctor, ctor, size,
ContextInput(), frame_state, effect(), control()));
}
TNode<JSArray> JSCallReducerAssembler::AllocateEmptyJSArray(
ElementsKind kind, NativeContextRef native_context) {
// TODO(jgruber): Port AllocationBuilder to JSGraphAssembler.
MapRef map = native_context.GetInitialJSArrayMap(broker(), kind);
AllocationBuilder ab(jsgraph(), broker(), effect(), control());
ab.Allocate(map.instance_size(), AllocationType::kYoung, Type::Array());
ab.Store(AccessBuilder::ForMap(), map);
Node* empty_fixed_array = jsgraph()->EmptyFixedArrayConstant();
ab.Store(AccessBuilder::ForJSObjectPropertiesOrHashKnownPointer(),
empty_fixed_array);
ab.Store(AccessBuilder::ForJSObjectElements(), empty_fixed_array);
ab.Store(AccessBuilder::ForJSArrayLength(kind), jsgraph()->ZeroConstant());
for (int i = 0; i < map.GetInObjectProperties(); ++i) {
ab.Store(AccessBuilder::ForJSObjectInObjectProperty(map, i),
jsgraph()->UndefinedConstant());
}
Node* result = ab.Finish();
InitializeEffectControl(result, control());
return TNode<JSArray>::UncheckedCast(result);
}
TNode<Number> JSCallReducerAssembler::LoadMapElementsKind(TNode<Map> map) {
TNode<Number> bit_field2 =
LoadField<Number>(AccessBuilder::ForMapBitField2(), map);
return NumberShiftRightLogical(
NumberBitwiseAnd(bit_field2,
NumberConstant(Map::Bits2::ElementsKindBits::kMask)),
NumberConstant(Map::Bits2::ElementsKindBits::kShift));
}
TNode<Object> JSCallReducerAssembler::ReduceMathUnary(const Operator* op) {
TNode<Object> input = Argument(0);
TNode<Number> input_as_number = SpeculativeToNumber(input);
return TNode<Object>::UncheckedCast(graph()->NewNode(op, input_as_number));
}
TNode<Object> JSCallReducerAssembler::ReduceMathBinary(const Operator* op) {
TNode<Object> left = Argument(0);
TNode<Object> right = ArgumentOrNaN(1);
TNode<Number> left_number = SpeculativeToNumber(left);
TNode<Number> right_number = SpeculativeToNumber(right);
return TNode<Object>::UncheckedCast(
graph()->NewNode(op, left_number, right_number));
}
TNode<String> JSCallReducerAssembler::ReduceStringPrototypeSubstring() {
TNode<Object> receiver = ReceiverInput();
TNode<Object> start = Argument(0);
TNode<Object> end = ArgumentOrUndefined(1);
TNode<String> receiver_string = CheckString(receiver);
TNode<Number> start_smi = CheckSmi(start);
TNode<Number> length = StringLength(receiver_string);
TNode<Number> end_smi = SelectIf<Number>(IsUndefined(end))
.Then(_ { return length; })
.Else(_ { return CheckSmi(end); })
.ExpectFalse()
.Value();
TNode<Number> zero = TNode<Number>::UncheckedCast(ZeroConstant());
TNode<Number> finalStart = NumberMin(NumberMax(start_smi, zero), length);
TNode<Number> finalEnd = NumberMin(NumberMax(end_smi, zero), length);
TNode<Number> from = NumberMin(finalStart, finalEnd);
TNode<Number> to = NumberMax(finalStart, finalEnd);
return StringSubstring(receiver_string, from, to);
}
TNode<Boolean> JSCallReducerAssembler::ReduceStringPrototypeStartsWith(
StringRef search_element_string) {
DCHECK(search_element_string.IsContentAccessible());
TNode<Object> receiver = ReceiverInput();
TNode<Object> start = ArgumentOrZero(1);
TNode<String> receiver_string = CheckString(receiver);
TNode<Smi> start_smi = CheckSmi(start);
TNode<Number> length = StringLength(receiver_string);
TNode<Number> zero = ZeroConstant();
TNode<Number> clamped_start = NumberMin(NumberMax(start_smi, zero), length);
int search_string_length = search_element_string.length();
DCHECK(search_string_length <= JSCallReducer::kMaxInlineMatchSequence);
auto out = MakeLabel(MachineRepresentation::kTagged);
auto search_string_too_long =
NumberLessThan(NumberSubtract(length, clamped_start),
NumberConstant(search_string_length));
GotoIf(search_string_too_long, &out, BranchHint::kFalse, FalseConstant());
static_assert(String::kMaxLength <= kSmiMaxValue);
for (int i = 0; i < search_string_length; i++) {
TNode<Number> k = NumberConstant(i);
TNode<Number> receiver_string_position = TNode<Number>::UncheckedCast(
TypeGuard(Type::UnsignedSmall(), NumberAdd(k, clamped_start)));
Node* receiver_string_char =
StringCharCodeAt(receiver_string, receiver_string_position);
Node* search_string_char = jsgraph()->ConstantNoHole(
search_element_string.GetChar(broker(), i).value());
auto is_equal = graph()->NewNode(simplified()->NumberEqual(),
search_string_char, receiver_string_char);
GotoIfNot(is_equal, &out, FalseConstant());
}
Goto(&out, TrueConstant());
Bind(&out);
return out.PhiAt<Boolean>(0);
}
TNode<Boolean> JSCallReducerAssembler::ReduceStringPrototypeStartsWith() {
TNode<Object> receiver = ReceiverInput();
TNode<Object> search_element = ArgumentOrUndefined(0);
TNode<Object> start = ArgumentOrZero(1);
TNode<String> receiver_string = CheckString(receiver);
TNode<String> search_string = CheckString(search_element);
TNode<Smi> start_smi = CheckSmi(start);
TNode<Number> length = StringLength(receiver_string);
TNode<Number> zero = ZeroConstant();
TNode<Number> clamped_start = NumberMin(NumberMax(start_smi, zero), length);
TNode<Number> search_string_length = StringLength(search_string);
auto out = MakeLabel(MachineRepresentation::kTagged);
auto search_string_too_long = NumberLessThan(
NumberSubtract(length, clamped_start), search_string_length);
GotoIf(search_string_too_long, &out, BranchHint::kFalse, FalseConstant());
static_assert(String::kMaxLength <= kSmiMaxValue);
ForZeroUntil(search_string_length).Do([&](TNode<Number> k) {
TNode<Number> receiver_string_position = TNode<Number>::UncheckedCast(
TypeGuard(Type::UnsignedSmall(), NumberAdd(k, clamped_start)));
Node* receiver_string_char =
StringCharCodeAt(receiver_string, receiver_string_position);
if (!v8_flags.turbo_loop_variable) {
// Without loop variable analysis, Turbofan's typer is unable to derive a
// sufficiently precise type here. This is not a soundness problem, but
// triggers graph verification errors. So we only insert the TypeGuard if
// necessary.
k = TypeGuard(Type::Unsigned32(), k);
}
Node* search_string_char = StringCharCodeAt(search_string, k);
auto is_equal = graph()->NewNode(simplified()->NumberEqual(),
receiver_string_char, search_string_char);
GotoIfNot(is_equal, &out, FalseConstant());
});
Goto(&out, TrueConstant());
Bind(&out);
return out.PhiAt<Boolean>(0);
}
TNode<Boolean> JSCallReducerAssembler::ReduceStringPrototypeEndsWith(
StringRef search_element_string) {
DCHECK(search_element_string.IsContentAccessible());
TNode<Object> receiver = ReceiverInput();
TNode<Object> end_position = ArgumentOrUndefined(1);
TNode<Number> zero = ZeroConstant();
TNode<String> receiver_string = CheckString(receiver);
TNode<Number> length = StringLength(receiver_string);
int search_string_length = search_element_string.length();
DCHECK_LE(search_string_length, JSCallReducer::kMaxInlineMatchSequence);
TNode<Number> clamped_end =
SelectIf<Number>(IsUndefined(end_position))
.Then(_ { return length; })
.Else(_ {
return NumberMin(NumberMax(CheckSmi(end_position), zero), length);
})
.ExpectTrue()
.Value();
TNode<Number> start =
NumberSubtract(clamped_end, NumberConstant(search_string_length));
auto out = MakeLabel(MachineRepresentation::kTagged);
TNode<Boolean> search_string_too_long = NumberLessThan(start, zero);
GotoIf(search_string_too_long, &out, BranchHint::kFalse, FalseConstant());
for (int i = 0; i < search_string_length; i++) {
TNode<Number> k = NumberConstant(i);
TNode<Number> receiver_string_position = TNode<Number>::UncheckedCast(
TypeGuard(Type::UnsignedSmall(), NumberAdd(k, start)));
Node* receiver_string_char =
StringCharCodeAt(receiver_string, receiver_string_position);
Node* search_string_char = jsgraph()->ConstantNoHole(
search_element_string.GetChar(broker(), i).value());
auto is_equal = graph()->NewNode(simplified()->NumberEqual(),
receiver_string_char, search_string_char);
GotoIfNot(is_equal, &out, FalseConstant());
}
Goto(&out, TrueConstant());
Bind(&out);
return out.PhiAt<Boolean>(0);
}
TNode<Boolean> JSCallReducerAssembler::ReduceStringPrototypeEndsWith() {
TNode<Object> receiver = ReceiverInput();
TNode<Object> search_string = ArgumentOrUndefined(0);
TNode<Object> end_position = ArgumentOrUndefined(1);
TNode<Number> zero = ZeroConstant();
TNode<String> receiver_string = CheckString(receiver);
TNode<Number> length = StringLength(receiver_string);
TNode<String> search_element_string = CheckString(search_string);
TNode<Number> search_string_length = StringLength(search_element_string);
TNode<Number> clamped_end =
SelectIf<Number>(IsUndefined(end_position))
.Then(_ { return length; })
.Else(_ {
return NumberMin(NumberMax(CheckSmi(end_position), zero), length);
})
.ExpectTrue()
.Value();
TNode<Number> start = NumberSubtract(clamped_end, search_string_length);
auto out = MakeLabel(MachineRepresentation::kTagged);
TNode<Boolean> search_string_too_long = NumberLessThan(start, zero);
GotoIf(search_string_too_long, &out, BranchHint::kFalse, FalseConstant());
ForZeroUntil(search_string_length).Do([&](TNode<Number> k) {
TNode<Number> receiver_string_position = TNode<Number>::UncheckedCast(
TypeGuard(Type::UnsignedSmall(), NumberAdd(k, start)));
Node* receiver_string_char =
StringCharCodeAt(receiver_string, receiver_string_position);
if (!v8_flags.turbo_loop_variable) {
// Without loop variable analysis, Turbofan's typer is unable to derive a
// sufficiently precise type here. This is not a soundness problem, but
// triggers graph verification errors. So we only insert the TypeGuard if
// necessary.
k = TypeGuard(Type::Unsigned32(), k);
}
Node* search_string_char = StringCharCodeAt(search_element_string, k);
auto is_equal = graph()->NewNode(simplified()->NumberEqual(),
receiver_string_char, search_string_char);
GotoIfNot(is_equal, &out, FalseConstant());
});
Goto(&out, TrueConstant());
Bind(&out);
return out.PhiAt<Boolean>(0);
}
TNode<String> JSCallReducerAssembler::ReduceStringPrototypeCharAt(
StringRef s, uint32_t index) {
DCHECK(s.IsContentAccessible());
if (s.IsOneByteRepresentation()) {
OptionalObjectRef elem = s.GetCharAsStringOrUndefined(broker(), index);
TNode<String> elem_string =
elem.has_value()
? TNode<String>::UncheckedCast(
jsgraph()->ConstantNoHole(elem.value(), broker()))
: EmptyStringConstant();
return elem_string;
} else {
const uint32_t length = static_cast<uint32_t>(s.length());
if (index >= length) return EmptyStringConstant();
Handle<SeqTwoByteString> flat = broker()->CanonicalPersistentHandle(
broker()
->local_isolate_or_isolate()
->factory()
->NewRawTwoByteString(1, AllocationType::kOld)
.ToHandleChecked());
flat->SeqTwoByteStringSet(0, s.GetChar(broker(), index).value());
TNode<String> two_byte_elem =
TNode<String>::UncheckedCast(jsgraph()->HeapConstantNoHole(flat));
return two_byte_elem;
}
}
TNode<String> JSCallReducerAssembler::ReduceStringPrototypeCharAt(
SpeculationMode speculation_mode) {
TNode<Object> receiver = ReceiverInput();
TNode<Object> index = ArgumentOrZero(0);
TNode<String> receiver_string = CheckString(receiver);
TNode<Number> length = StringLength(receiver_string);
if (speculation_mode == SpeculationMode::kDisallowBoundsCheckSpeculation) {
TNode<Number> index_smi = CheckSmi(index);
return SelectIf<String>(NumberLessThan(index_smi, ZeroConstant()))
.Then(_ { return EmptyStringConstant(); })
.Else(_ {
return SelectIf<String>(NumberLessThan(index_smi, length))
.Then(_ {
return StringFromSingleCharCode(
TNode<Number>::UncheckedCast(StringCharCodeAt(
receiver_string,
TypeGuard(Type::Unsigned32(), index_smi))));
})
.Else(_ { return EmptyStringConstant(); })
.ExpectTrue()
.Value();
})
.ExpectFalse()
.Value();
}
DCHECK_EQ(speculation_mode, SpeculationMode::kAllowSpeculation);
TNode<Number> bounded_index = CheckBounds(index, length);
return StringFromSingleCharCode(TNode<Number>::UncheckedCast(
StringCharCodeAt(receiver_string, bounded_index)));
}
TNode<Number> JSCallReducerAssembler::ReduceStringPrototypeCharCodeAt(
SpeculationMode speculation_mode) {
TNode<Object> receiver = ReceiverInput();
TNode<Object> index = ArgumentOrZero(0);
TNode<String> receiver_string = CheckString(receiver);
TNode<Number> length = StringLength(receiver_string);
if (speculation_mode == SpeculationMode::kDisallowBoundsCheckSpeculation) {
TNode<Number> index_smi = CheckSmi(index);
return SelectIf<Number>(NumberLessThan(index_smi, ZeroConstant()))
.Then(_ { return NaNConstant(); })
.Else(_ {
return SelectIf<Number>(NumberLessThan(index_smi, length))
.Then(_ {
return TNode<Number>::UncheckedCast(StringCharCodeAt(
receiver_string, TypeGuard(Type::Unsigned32(), index_smi)));
})
.Else(_ { return NaNConstant(); })
.ExpectTrue()
.Value();
})
.ExpectFalse()
.Value();
}
DCHECK_EQ(speculation_mode, SpeculationMode::kAllowSpeculation);
TNode<Number> bounded_index = CheckBounds(index, length);
return TNode<Number>::UncheckedCast(
(StringCharCodeAt(receiver_string, bounded_index)));
}
TNode<String> JSCallReducerAssembler::ReduceStringPrototypeSlice() {
TNode<Object> receiver = ReceiverInput();
TNode<String> receiver_string = CheckString(receiver);
TNode<Number> length = StringLength(receiver_string);
TNode<Object> start = Argument(0);
TNode<Object> end = ArgumentOrUndefined(1);
// Special case str.slice(-1) to str.charAt(str.length - 1).
// This will not hit if the start offset is not known to be the -1 constant
// during this reduction, nor if the end argument is explicitly rather than
// implicitly undefined; hopefully in common cases the code will explicitly
// use the -1 literal.
if (ArgumentCount() == 1) {
NumberMatcher m(start);
if (m.Is(-1)) {
return SelectIf<String>(
ReferenceEqual(receiver_string, EmptyStringConstant()))
.Then(_ { return EmptyStringConstant(); })
.Else(_ {
return StringFromSingleCharCode(
TNode<Number>::UncheckedCast(StringCharCodeAt(
receiver_string,
TypeGuard(Type::Unsigned32(),
NumberAdd(length, TNode<Number>::UncheckedCast(
m.node()))))));
})
.Value();
}
}
TNode<Number> start_smi = CheckSmi(start);
TNode<Number> end_smi = SelectIf<Number>(IsUndefined(end))
.Then(_ { return length; })
.Else(_ { return CheckSmi(end); })
.ExpectFalse()
.Value();
TNode<Number> zero = ZeroConstant();
TNode<Number> from_untyped =
SelectIf<Number>(NumberLessThan(start_smi, zero))
.Then(_ { return NumberMax(NumberAdd(length, start_smi), zero); })
.Else(_ { return NumberMin(start_smi, length); })
.ExpectFalse()
.Value();
// {from} is always in non-negative Smi range, but our typer cannot figure
// that out yet.
TNode<Smi> from = TypeGuardUnsignedSmall(from_untyped);
TNode<Number> to_untyped =
SelectIf<Number>(NumberLessThan(end_smi, zero))
.Then(_ { return NumberMax(NumberAdd(length, end_smi), zero); })
.Else(_ { return NumberMin(end_smi, length); })
.ExpectFalse()
.Value();
// {to} is always in non-negative Smi range, but our typer cannot figure that
// out yet.
TNode<Smi> to = TypeGuardUnsignedSmall(to_untyped);
return SelectIf<String>(NumberLessThan(from, to))
.Then(_ { return StringSubstring(receiver_string, from, to); })
.Else(_ { return EmptyStringConstant(); })
.ExpectTrue()
.Value();
}
TNode<Object> JSCallReducerAssembler::ReduceJSCallMathMinMaxWithArrayLike(
Builtin builtin) {
JSCallWithArrayLikeNode n(node_ptr());
TNode<Object> arguments_list = n.Argument(0);
auto call_builtin = MakeLabel();
auto done = MakeLabel(MachineRepresentation::kTagged);
// Check if {arguments_list} is a JSArray.
GotoIf(ObjectIsSmi(arguments_list), &call_builtin);
TNode<Map> arguments_list_map =
LoadField<Map>(AccessBuilder::ForMap(),
TNode<HeapObject>::UncheckedCast(arguments_list));
TNode<Number> arguments_list_instance_type = LoadField<Number>(
AccessBuilder::ForMapInstanceType(), arguments_list_map);
auto check_instance_type =
NumberEqual(arguments_list_instance_type, NumberConstant(JS_ARRAY_TYPE));
GotoIfNot(check_instance_type, &call_builtin);
// Check if {arguments_list} has PACKED_DOUBLE_ELEMENTS.
TNode<Number> arguments_list_elements_kind =
LoadMapElementsKind(arguments_list_map);
auto check_element_kind = NumberEqual(arguments_list_elements_kind,
NumberConstant(PACKED_DOUBLE_ELEMENTS));
GotoIfNot(check_element_kind, &call_builtin);
// If {arguments_list} is a JSArray with PACKED_DOUBLE_ELEMENTS, calculate the
// result with inlined loop.
TNode<JSArray> array_arguments_list =
TNode<JSArray>::UncheckedCast(arguments_list);
Goto(&done, builtin == Builtin::kMathMax
? DoubleArrayMax(array_arguments_list)
: DoubleArrayMin(array_arguments_list));
// Otherwise, call BuiltinMathMin/Max as usual.
Bind(&call_builtin);
TNode<Object> call = CopyNode();
CallParameters const& p = n.Parameters();
// Set SpeculationMode to kDisallowSpeculation to avoid infinite
// recursion.
NodeProperties::ChangeOp(
call, javascript()->CallWithArrayLike(
p.frequency(), p.feedback(),
SpeculationMode::kDisallowSpeculation, p.feedback_relation()));
Goto(&done, call);
Bind(&done);
return done.PhiAt<Object>(0);
}
TNode<Object> IteratingArrayBuiltinReducerAssembler::ReduceArrayPrototypeAt(
ZoneVector<MapRef> maps, bool needs_fallback_builtin_call) {
TNode<JSArray> receiver = ReceiverInputAs<JSArray>();
TNode<Object> index = ArgumentOrZero(0);
TNode<Number> index_num = CheckSmi(index);
TNode<FixedArrayBase> elements = LoadElements(receiver);
TNode<Map> receiver_map =
TNode<Map>::UncheckedCast(LoadField(AccessBuilder::ForMap(), receiver));
auto out = MakeLabel(MachineRepresentation::kTagged);
for (MapRef map : maps) {
DCHECK(map.supports_fast_array_iteration(broker()));
auto correct_map_label = MakeLabel(), wrong_map_label = MakeLabel();
TNode<Boolean> is_map_equal = ReferenceEqual(receiver_map, Constant(map));
Branch(is_map_equal, &correct_map_label, &wrong_map_label);
Bind(&correct_map_label);
TNode<Number> length = LoadJSArrayLength(receiver, map.elements_kind());
// If index is less than 0, then subtract from length.
TNode<Boolean> cond = NumberLessThan(index_num, ZeroConstant());
TNode<Number> real_index_num =
SelectIf<Number>(cond)
.Then(_ { return NumberAdd(length, index_num); })
.Else(_ { return index_num; })
.ExpectTrue() // Most common usage should be .at(-1)
.Value();
// Bound checking.
GotoIf(NumberLessThan(real_index_num, ZeroConstant()), &out,
UndefinedConstant());
GotoIfNot(NumberLessThan(real_index_num, length), &out,
UndefinedConstant());
if (v8_flags.turbo_typer_hardening) {
real_index_num = CheckBounds(real_index_num, length,
CheckBoundsFlag::kAbortOnOutOfBounds);
}
// Retrieving element at index.
TNode<Object> element = LoadElement<Object>(
AccessBuilder::ForFixedArrayElement(map.elements_kind()), elements,
real_index_num);
if (IsHoleyElementsKind(map.elements_kind())) {
// This case is needed in particular for HOLEY_DOUBLE_ELEMENTS: raw
// doubles are stored in the FixedDoubleArray, and need to be converted to
// HeapNumber or to Smi so that this function can return an Object. The
// automatic converstion performed by
// RepresentationChanger::GetTaggedRepresentationFor does not handle
// holes, so we convert manually a potential hole here.
element = ConvertHoleToUndefined(element, map.elements_kind());
}
Goto(&out, element);
Bind(&wrong_map_label);
}
if (needs_fallback_builtin_call) {
JSCallNode n(node_ptr());
CallParameters const& p = n.Parameters();
// We set SpeculationMode to kDisallowSpeculation to avoid infinite
// recursion on the node we're creating (since, after all, it's calling
// Array.Prototype.at).
const Operator* op = javascript()->Call(
JSCallNode::ArityForArgc(1), p.frequency(), p.feedback(),
ConvertReceiverMode::kNotNullOrUndefined,
SpeculationMode::kDisallowSpeculation, CallFeedbackRelation::kTarget);
Node* fallback_builtin = node_ptr()->InputAt(0);
TNode<Object> res = MayThrow(_ {
return AddNode<Object>(graph()->NewNode(
op, fallback_builtin, receiver, index, n.feedback_vector(),
ContextInput(), n.frame_state(), effect(), control()));
});
Goto(&out, res);
} else {
Goto(&out, UndefinedConstant());
}
Bind(&out);
return out.PhiAt<Object>(0);
}
TNode<Number> IteratingArrayBuiltinReducerAssembler::ReduceArrayPrototypePush(
MapInference* inference) {
int const num_push_arguments = ArgumentCount();
ZoneRefSet<Map> const& receiver_maps = inference->GetMaps();
base::SmallVector<MachineRepresentation, 4> argument_reps;
base::SmallVector<Node*, 4> argument_nodes;
for (int i = 0; i < num_push_arguments; ++i) {
argument_reps.push_back(MachineRepresentation::kTagged);
argument_nodes.push_back(Argument(i));
}
TNode<JSArray> receiver = ReceiverInputAs<JSArray>();
TNode<Map> receiver_map = LoadMap(receiver);
auto double_label = MakeLabel(argument_reps);
auto smi_label = MakeLabel(argument_reps);
auto object_label = MakeLabel(argument_reps);
for (size_t i = 0; i < receiver_maps.size(); i++) {
MapRef map = receiver_maps[i];
ElementsKind kind = map.elements_kind();
if (i < receiver_maps.size() - 1) {
TNode<Boolean> is_map_equal = ReferenceEqual(receiver_map, Constant(map));
if (IsDoubleElementsKind(kind)) {
GotoIf(is_map_equal, &double_label, argument_nodes);
} else if (IsSmiElementsKind(kind)) {
GotoIf(is_map_equal, &smi_label, argument_nodes);
} else {
GotoIf(is_map_equal, &object_label, argument_nodes);
}
} else {
if (IsDoubleElementsKind(kind)) {
Goto(&double_label, argument_nodes);
} else if (IsSmiElementsKind(kind)) {
Goto(&smi_label, argument_nodes);
} else {
Goto(&object_label, argument_nodes);
}
}
}
auto return_label = MakeLabel(MachineRepresentation::kTagged);
auto build_array_push = [&](ElementsKind kind,
base::SmallVector<Node*, 1>& push_arguments) {
// Only support PACKED_ELEMENTS and PACKED_DOUBLE_ELEMENTS, as "markers" of
// what the elements array is (a FixedArray or FixedDoubleArray).
DCHECK(kind == PACKED_ELEMENTS || kind == PACKED_DOUBLE_ELEMENTS);
// Load the "length" property of the {receiver}.
TNode<Smi> length = LoadJSArrayLength(receiver, kind);
TNode<Number> return_value = length;
// Check if we have any {values} to push.
if (num_push_arguments > 0) {
// Compute the resulting "length" of the {receiver}.
TNode<Number> new_length = return_value =
NumberAdd(length, NumberConstant(num_push_arguments));
// Load the elements backing store of the {receiver}.
TNode<FixedArrayBase> elements = LoadElements(receiver);
TNode<Smi> elements_length = LoadFixedArrayBaseLength(elements);
elements = MaybeGrowFastElements(
kind, feedback(), receiver, elements,
NumberAdd(length, NumberConstant(num_push_arguments - 1)),
elements_length);
// Update the JSArray::length field. Since this is observable,
// there must be no other check after this.
StoreJSArrayLength(receiver, new_length, kind);
// Append the {values} to the {elements}.
for (int i = 0; i < num_push_arguments; ++i) {
StoreFixedArrayBaseElement(
elements, NumberAdd(length, NumberConstant(i)),
TNode<Object>::UncheckedCast(push_arguments[i]), kind);
}
}
Goto(&return_label, return_value);
};
if (double_label.IsUsed()) {
Bind(&double_label);
base::SmallVector<Node*, 1> push_arguments(num_push_arguments);
for (int i = 0; i < num_push_arguments; ++i) {
Node* value =
CheckNumber(TNode<Object>::UncheckedCast(double_label.PhiAt(i)));
// Make sure we do not store signaling NaNs into double arrays.
value = AddNode<Number>(
graph()->NewNode(simplified()->NumberSilenceNaN(), value));
push_arguments[i] = value;
}
build_array_push(PACKED_DOUBLE_ELEMENTS, push_arguments);
}
if (smi_label.IsUsed()) {
Bind(&smi_label);
base::SmallVector<Node*, 4> push_arguments(num_push_arguments);
for (int i = 0; i < num_push_arguments; ++i) {
Node* value = CheckSmi(TNode<Object>::UncheckedCast(smi_label.PhiAt(i)));
push_arguments[i] = value;
}
Goto(&object_label, push_arguments);
}
if (object_label.IsUsed()) {
Bind(&object_label);
base::SmallVector<Node*, 1> push_arguments(num_push_arguments);
for (int i = 0; i < num_push_arguments; ++i) {
push_arguments[i] = object_label.PhiAt(i);
}
build_array_push(PACKED_ELEMENTS, push_arguments);
}
Bind(&return_label);
return TNode<Number>::UncheckedCast(return_label.PhiAt(0));
}
namespace {
struct ForEachFrameStateParams {
JSGraph* jsgraph;
SharedFunctionInfoRef shared;
TNode<Context> context;
TNode<Object> target;
FrameState outer_frame_state;
TNode<Object> receiver;
TNode<Object> callback;
TNode<Object> this_arg;
TNode<Object> original_length;
};
FrameState ForEachLoopLazyFrameState(const ForEachFrameStateParams& params,
TNode<Object> k) {
Builtin builtin = Builtin::kArrayForEachLoopLazyDeoptContinuation;
Node* checkpoint_params[] = {params.receiver, params.callback,
params.this_arg, k, params.original_length};
return CreateJavaScriptBuiltinContinuationFrameState(
params.jsgraph, params.shared, builtin, params.target, params.context,
checkpoint_params, arraysize(checkpoint_params), params.outer_frame_state,
ContinuationFrameStateMode::LAZY);
}
FrameState ForEachLoopEagerFrameState(const ForEachFrameStateParams& params,
TNode<Object> k) {
Builtin builtin = Builtin::kArrayForEachLoopEagerDeoptContinuation;
Node* checkpoint_params[] = {params.receiver, params.callback,
params.this_arg, k, params.original_length};
return CreateJavaScriptBuiltinContinuationFrameState(
params.jsgraph, params.shared, builtin, params.target, params.context,
checkpoint_params, arraysize(checkpoint_params), params.outer_frame_state,
ContinuationFrameStateMode::EAGER);
}
} // namespace
TNode<Object>
IteratingArrayBuiltinReducerAssembler::ReduceArrayPrototypeForEach(
MapInference* inference, const bool has_stability_dependency,
ElementsKind kind, SharedFunctionInfoRef shared) {
FrameState outer_frame_state = FrameStateInput();
TNode<Context> context = ContextInput();
TNode<Object> target = TargetInput();
TNode<JSArray> receiver = ReceiverInputAs<JSArray>();
TNode<Object> fncallback = ArgumentOrUndefined(0);
TNode<Object> this_arg = ArgumentOrUndefined(1);
TNode<Number> original_length = LoadJSArrayLength(receiver, kind);
ForEachFrameStateParams frame_state_params{
jsgraph(), shared, context, target, outer_frame_state,
receiver, fncallback, this_arg, original_length};
ThrowIfNotCallable(fncallback, ForEachLoopLazyFrameState(frame_state_params,
ZeroConstant()));
ForZeroUntil(original_length).Do([&](TNode<Number> k) {
Checkpoint(ForEachLoopEagerFrameState(frame_state_params, k));
// Deopt if the map has changed during the iteration.
MaybeInsertMapChecks(inference, has_stability_dependency);
TNode<Object> element;
std::tie(k, element) = SafeLoadElement(kind, receiver, k);
auto continue_label = MakeLabel();
element = MaybeSkipHole(element, kind, &continue_label);
TNode<Number> next_k = NumberAdd(k, OneConstant());
JSCall3(fncallback, this_arg, element, k, receiver,
ForEachLoopLazyFrameState(frame_state_params, next_k));
Goto(&continue_label);
Bind(&continue_label);
});
return UndefinedConstant();
}
namespace {
struct ReduceFrameStateParams {
JSGraph* jsgraph;
SharedFunctionInfoRef shared;
ArrayReduceDirection direction;
TNode<Context> context;
TNode<Object> target;
FrameState outer_frame_state;
};
FrameState ReducePreLoopLazyFrameState(const ReduceFrameStateParams& params,
TNode<Object> receiver,
TNode<Object> callback, TNode<Object> k,
TNode<Number> original_length) {
Builtin builtin = (params.direction == ArrayReduceDirection::kLeft)
? Builtin::kArrayReduceLoopLazyDeoptContinuation
: Builtin::kArrayReduceRightLoopLazyDeoptContinuation;
Node* checkpoint_params[] = {receiver, callback, k, original_length};
return CreateJavaScriptBuiltinContinuationFrameState(
params.jsgraph, params.shared, builtin, params.target, params.context,
checkpoint_params, arraysize(checkpoint_params), params.outer_frame_state,
ContinuationFrameStateMode::LAZY);
}
FrameState ReducePreLoopEagerFrameState(const ReduceFrameStateParams& params,
TNode<Object> receiver,
TNode<Object> callback,
TNode<Number> original_length) {
Builtin builtin =
(params.direction == ArrayReduceDirection::kLeft)
? Builtin::kArrayReducePreLoopEagerDeoptContinuation
: Builtin::kArrayReduceRightPreLoopEagerDeoptContinuation;
Node* checkpoint_params[] = {receiver, callback, original_length};
return CreateJavaScriptBuiltinContinuationFrameState(
params.jsgraph, params.shared, builtin, params.target, params.context,
checkpoint_params, arraysize(checkpoint_params), params.outer_frame_state,
ContinuationFrameStateMode::EAGER);
}
FrameState ReduceLoopLazyFrameState(const ReduceFrameStateParams& params,
TNode<Object> receiver,
TNode<Object> callback, TNode<Object> k,
TNode<Number> original_length) {
Builtin builtin = (params.direction == ArrayReduceDirection::kLeft)
? Builtin::kArrayReduceLoopLazyDeoptContinuation
: Builtin::kArrayReduceRightLoopLazyDeoptContinuation;
Node* checkpoint_params[] = {receiver, callback, k, original_length};
return CreateJavaScriptBuiltinContinuationFrameState(
params.jsgraph, params.shared, builtin, params.target, params.context,
checkpoint_params, arraysize(checkpoint_params), params.outer_frame_state,
ContinuationFrameStateMode::LAZY);
}
FrameState ReduceLoopEagerFrameState(const ReduceFrameStateParams& params,
TNode<Object> receiver,
TNode<Object> callback, TNode<Object> k,
TNode<Number> original_length,
TNode<Object> accumulator) {
Builtin builtin = (params.direction == ArrayReduceDirection::kLeft)
? Builtin::kArrayReduceLoopEagerDeoptContinuation
: Builtin::kArrayReduceRightLoopEagerDeoptContinuation;
Node* checkpoint_params[] = {receiver, callback, k, original_length,
accumulator};
return CreateJavaScriptBuiltinContinuationFrameState(
params.jsgraph, params.shared, builtin, params.target, params.context,
checkpoint_params, arraysize(checkpoint_params), params.outer_frame_state,
ContinuationFrameStateMode::EAGER);
}
} // namespace
TNode<Object> IteratingArrayBuiltinReducerAssembler::ReduceArrayPrototypeReduce(
MapInference* inference, const bool has_stability_dependency,
ElementsKind kind, ArrayReduceDirection direction,
SharedFunctionInfoRef shared) {
FrameState outer_frame_state = FrameStateInput();
TNode<Context> context = ContextInput();
TNode<Object> target = TargetInput();
TNode<JSArray> receiver = ReceiverInputAs<JSArray>();
TNode<Object> fncallback = ArgumentOrUndefined(0);
ReduceFrameStateParams frame_state_params{
jsgraph(), shared, direction, context, target, outer_frame_state};
TNode<Number> original_length = LoadJSArrayLength(receiver, kind);
// Set up variable behavior depending on the reduction kind (left/right).
TNode<Number> k;
StepFunction1 step;
ConditionFunction1 cond;
TNode<Number> zero = ZeroConstant();
TNode<Number> one = OneConstant();
if (direction == ArrayReduceDirection::kLeft) {
k = zero;
step = [&](TNode<Number> i) { return NumberAdd(i, one); };
cond = [&](TNode<Number> i) { return NumberLessThan(i, original_length); };
} else {
k = NumberSubtract(original_length, one);
step = [&](TNode<Number> i) { return NumberSubtract(i, one); };
cond = [&](TNode<Number> i) { return NumberLessThanOrEqual(zero, i); };
}
ThrowIfNotCallable(
fncallback, ReducePreLoopLazyFrameState(frame_state_params, receiver,
fncallback, k, original_length));
// Set initial accumulator value.
TNode<Object> accumulator;
if (ArgumentCount() > 1) {
accumulator = Argument(1); // Initial value specified by the user.
} else {
// The initial value was not specified by the user. In this case, the first
// (or last in the case of reduceRight) non-holey value of the array is
// used. Loop until we find it. If not found, trigger a deopt.
// TODO(jgruber): The deopt does not seem necessary. Instead we could simply
// throw the TypeError here from optimized code.
auto found_initial_element = MakeLabel(MachineRepresentation::kTagged,
MachineRepresentation::kTagged);
Forever(k, step).Do([&](TNode<Number> k) {
Checkpoint(ReducePreLoopEagerFrameState(frame_state_params, receiver,
fncallback, original_length));
CheckIf(cond(k), DeoptimizeReason::kNoInitialElement);
TNode<Object> element;
std::tie(k, element) = SafeLoadElement(kind, receiver, k);
auto continue_label = MakeLabel();
GotoIf(HoleCheck(kind, element), &continue_label);
Goto(&found_initial_element, k, TypeGuardNonInternal(element));
Bind(&continue_label);
});
Unreachable(); // The loop is exited either by deopt or a jump to below.
// TODO(jgruber): This manual fiddling with blocks could be avoided by
// implementing a `break` mechanic for loop builders.
Bind(&found_initial_element);
k = step(found_initial_element.PhiAt<Number>(0));
accumulator = found_initial_element.PhiAt<Object>(1);
}
TNode<Object> result =
For1(k, cond, step, accumulator)
.Do([&](TNode<Number> k, TNode<Object>* accumulator) {
Checkpoint(ReduceLoopEagerFrameState(frame_state_params, receiver,
fncallback, k, original_length,
*accumulator));
// Deopt if the map has changed during the iteration.
MaybeInsertMapChecks(inference, has_stability_dependency);
TNode<Object> element;
std::tie(k, element) = SafeLoadElement(kind, receiver, k);
auto continue_label = MakeLabel(MachineRepresentation::kTagged);
element =
MaybeSkipHole(element, kind, &continue_label, *accumulator);
TNode<Number> next_k = step(k);
TNode<Object> next_accumulator = JSCall4(
fncallback, UndefinedConstant(), *accumulator, element, k,
receiver,
ReduceLoopLazyFrameState(frame_state_params, receiver,
fncallback, next_k, original_length));
Goto(&continue_label, next_accumulator);
Bind(&continue_label);
*accumulator = continue_label.PhiAt<Object>(0);
})
.Value();
return result;
}
namespace {
struct MapFrameStateParams {
JSGraph* jsgraph;
SharedFunctionInfoRef shared;
TNode<Context> context;
TNode<Object> target;
FrameState outer_frame_state;
TNode<Object> receiver;
TNode<Object> callback;
TNode<Object> this_arg;
std::optional<TNode<JSArray>> a;
TNode<Object> original_length;
};
FrameState MapPreLoopLazyFrameState(const MapFrameStateParams& params) {
DCHECK(!params.a);
Node* checkpoint_params[] = {params.receiver, params.callback,
params.this_arg, params.original_length};
return CreateJavaScriptBuiltinContinuationFrameState(
params.jsgraph, params.shared,
Builtin::kArrayMapPreLoopLazyDeoptContinuation, params.target,
params.context, checkpoint_params, arraysize(checkpoint_params),
params.outer_frame_state, ContinuationFrameStateMode::LAZY);
}
FrameState MapLoopLazyFrameState(const MapFrameStateParams& params,
TNode<Number> k) {
Node* checkpoint_params[] = {
params.receiver, params.callback, params.this_arg, *params.a, k,
params.original_length};
return CreateJavaScriptBuiltinContinuationFrameState(
params.jsgraph, params.shared,
Builtin::kArrayMapLoopLazyDeoptContinuation, params.target,
params.context, checkpoint_params, arraysize(checkpoint_params),
params.outer_frame_state, ContinuationFrameStateMode::LAZY);
}
FrameState MapLoopEagerFrameState(const MapFrameStateParams& params,
TNode<Number> k) {
Node* checkpoint_params[] = {
params.receiver, params.callback, params.this_arg, *params.a, k,
params.original_length};
return CreateJavaScriptBuiltinContinuationFrameState(
params.jsgraph, params.shared,
Builtin::kArrayMapLoopEagerDeoptContinuation, params.target,
params.context, checkpoint_params, arraysize(checkpoint_params),
params.outer_frame_state, ContinuationFrameStateMode::EAGER);
}
} // namespace
TNode<JSArray> IteratingArrayBuiltinReducerAssembler::ReduceArrayPrototypeMap(
MapInference* inference, const bool has_stability_dependency,
ElementsKind kind, SharedFunctionInfoRef shared,
NativeContextRef native_context) {
FrameState outer_frame_state = FrameStateInput();
TNode<Context> context = ContextInput();
TNode<Object> target = TargetInput();
TNode<JSArray> receiver = ReceiverInputAs<JSArray>();
TNode<Object> fncallback = ArgumentOrUndefined(0);
TNode<Object> this_arg = ArgumentOrUndefined(1);
TNode<Number> original_length = LoadJSArrayLength(receiver, kind);
// If the array length >= kMaxFastArrayLength, then CreateArray
// will create a dictionary. We should deopt in this case, and make sure
// not to attempt inlining again.
original_length = CheckBounds(original_length,
NumberConstant(JSArray::kMaxFastArrayLength));
// Even though {JSCreateArray} is not marked as {kNoThrow}, we can elide the
// exceptional projections because it cannot throw with the given
// parameters.
TNode<Object> array_ctor =
Constant(native_context.GetInitialJSArrayMap(broker(), kind)
.GetConstructor(broker()));
MapFrameStateParams frame_state_params{
jsgraph(), shared, context, target, outer_frame_state,
receiver, fncallback, this_arg, {} /* TBD */, original_length};
TNode<JSArray> a =
CreateArrayNoThrow(array_ctor, original_length,
MapPreLoopLazyFrameState(frame_state_params));
frame_state_params.a = a;
ThrowIfNotCallable(fncallback,
MapLoopLazyFrameState(frame_state_params, ZeroConstant()));
ForZeroUntil(original_length).Do([&](TNode<Number> k) {
Checkpoint(MapLoopEagerFrameState(frame_state_params, k));
MaybeInsertMapChecks(inference, has_stability_dependency);
TNode<Object> element;
std::tie(k, element) = SafeLoadElement(kind, receiver, k);
auto continue_label = MakeLabel();
element = MaybeSkipHole(element, kind, &continue_label);
TNode<Object> v = JSCall3(fncallback, this_arg, element, k, receiver,
MapLoopLazyFrameState(frame_state_params, k));
// The array {a} should be HOLEY_SMI_ELEMENTS because we'd only come into
// this loop if the input array length is non-zero, and "new Array({x > 0})"
// always produces a HOLEY array.
MapRef holey_double_map =
native_context.GetInitialJSArrayMap(broker(), HOLEY_DOUBLE_ELEMENTS);
MapRef holey_map =
native_context.GetInitialJSArrayMap(broker(), HOLEY_ELEMENTS);
TransitionAndStoreElement(holey_double_map, holey_map, a, k, v);
Goto(&continue_label);
Bind(&continue_label);
});
return a;
}
namespace {
struct FilterFrameStateParams {
JSGraph* jsgraph;
SharedFunctionInfoRef shared;
TNode<Context> context;
TNode<Object> target;
FrameState outer_frame_state;
TNode<Object> receiver;
TNode<Object> callback;
TNode<Object> this_arg;
TNode<JSArray> a;
TNode<Object> original_length;
};
FrameState FilterLoopLazyFrameState(const FilterFrameStateParams& params,
TNode<Number> k, TNode<Number> to,
TNode<Object> element) {
Node* checkpoint_params[] = {params.receiver,
params.callback,
params.this_arg,
params.a,
k,
params.original_length,
element,
to};
return CreateJavaScriptBuiltinContinuationFrameState(
params.jsgraph, params.shared,
Builtin::kArrayFilterLoopLazyDeoptContinuation, params.target,
params.context, checkpoint_params, arraysize(checkpoint_params),
params.outer_frame_state, ContinuationFrameStateMode::LAZY);
}
FrameState FilterLoopEagerPostCallbackFrameState(
const FilterFrameStateParams& params, TNode<Number> k, TNode<Number> to,
TNode<Object> element, TNode<Object> callback_value) {
// Note that we are intentionally reusing the
// Builtin::kArrayFilterLoopLazyDeoptContinuation as an *eager* entry
// point in this case. This is safe, because re-evaluating a [ToBoolean]
// coercion is safe.
Node* checkpoint_params[] = {params.receiver,
params.callback,
params.this_arg,
params.a,
k,
params.original_length,
element,
to,
callback_value};
return CreateJavaScriptBuiltinContinuationFrameState(
params.jsgraph, params.shared,
Builtin::kArrayFilterLoopLazyDeoptContinuation, params.target,
params.context, checkpoint_params, arraysize(checkpoint_params),
params.outer_frame_state, ContinuationFrameStateMode::EAGER);
}
FrameState FilterLoopEagerFrameState(const FilterFrameStateParams& params,
TNode<Number> k, TNode<Number> to) {
Node* checkpoint_params[] = {params.receiver,
params.callback,
params.this_arg,
params.a,
k,
params.original_length,
to};
return CreateJavaScriptBuiltinContinuationFrameState(
params.jsgraph, params.shared,
Builtin::kArrayFilterLoopEagerDeoptContinuation, params.target,
params.context, checkpoint_params, arraysize(checkpoint_params),
params.outer_frame_state, ContinuationFrameStateMode::EAGER);
}
} // namespace
TNode<JSArray>
IteratingArrayBuiltinReducerAssembler::ReduceArrayPrototypeFilter(
MapInference* inference, const bool has_stability_dependency,
ElementsKind kind, SharedFunctionInfoRef shared,
NativeContextRef native_context) {
FrameState outer_frame_state = FrameStateInput();
TNode<Context> context = ContextInput();
TNode<Object> target = TargetInput();
TNode<JSArray> receiver = ReceiverInputAs<JSArray>();
TNode<Object> fncallback = ArgumentOrUndefined(0);
TNode<Object> this_arg = ArgumentOrUndefined(1);
// The output array is packed (filter doesn't visit holes).
ElementsKind filtered_kind = GetPackedElementsKind(kind);
#ifdef V8_ENABLE_UNDEFINED_DOUBLE
if (kind == ElementsKind::HOLEY_DOUBLE_ELEMENTS) {
// Array may contain double-encoded undefineds that may be preserved by
// the filter operation, so the output has to be holey, too.
// TODO(nicohartmann, 385155404): We can consider starting with a
// PACKED_DOUBLE_ELEMENTS and only transition to holey, when we actually
// encounter an undefined.
filtered_kind = ElementsKind::HOLEY_DOUBLE_ELEMENTS;
}
#endif // V8_ENABLE_UNDEFINED_DOUBLE
TNode<JSArray> a = AllocateEmptyJSArray(filtered_kind, native_context);
TNode<Number> original_length = LoadJSArrayLength(receiver, kind);
FilterFrameStateParams frame_state_params{
jsgraph(), shared, context, target, outer_frame_state,
receiver, fncallback, this_arg, a, original_length};
// This frame state doesn't ever call the deopt continuation, it's only
// necessary to specify a continuation in order to handle the exceptional
// case. We don't have all the values available to completely fill out
// the checkpoint parameters yet, but that's okay because it'll never be
// called.
TNode<Number> zero = ZeroConstant();
ThrowIfNotCallable(fncallback, FilterLoopLazyFrameState(frame_state_params,
zero, zero, zero));
TNode<Number> initial_a_length = zero;
For1ZeroUntil(original_length, initial_a_length)
.Do([&](TNode<Number> k, TNode<Object>* a_length_object) {
TNode<Number> a_length = TNode<Number>::UncheckedCast(*a_length_object);
Checkpoint(FilterLoopEagerFrameState(frame_state_params, k, a_length));
MaybeInsertMapChecks(inference, has_stability_dependency);
TNode<Object> element;
std::tie(k, element) = SafeLoadElement(kind, receiver, k);
auto continue_label = MakeLabel(MachineRepresentation::kTaggedSigned);
element = MaybeSkipHole(element, kind, &continue_label, a_length);
TNode<Object> v = JSCall3(
fncallback, this_arg, element, k, receiver,
FilterLoopLazyFrameState(frame_state_params, k, a_length, element));
// We need an eager frame state for right after the callback function
// returned, just in case an attempt to grow the output array fails.
Checkpoint(FilterLoopEagerPostCallbackFrameState(frame_state_params, k,
a_length, element, v));
GotoIfNot(ToBoolean(v), &continue_label, a_length);
// Since the callback returned a trueish value, store the element in a.
{
TNode<Number> a_length1 = TypeGuardFixedArrayLength(a_length);
TNode<FixedArrayBase> elements = LoadElements(a);
elements = MaybeGrowFastElements(kind, FeedbackSource{}, a, elements,
a_length1,
LoadFixedArrayBaseLength(elements));
TNode<Number> new_a_length = NumberInc(a_length1);
StoreJSArrayLength(a, new_a_length, kind);
StoreFixedArrayBaseElement(elements, a_length1, element, kind);
Goto(&continue_label, new_a_length);
}
Bind(&continue_label);
*a_length_object =
TNode<Object>::UncheckedCast(continue_label.PhiAt(0));
})
.ValueIsUnused();
return a;
}
namespace {
struct FindFrameStateParams {
JSGraph* jsgraph;
SharedFunctionInfoRef shared;
TNode<Context> context;
TNode<Object> target;
FrameState outer_frame_state;
TNode<Object> receiver;
TNode<Object> callback;
TNode<Object> this_arg;
TNode<Object> original_length;
};
FrameState FindLoopLazyFrameState(const FindFrameStateParams& params,
TNode<Number> k, ArrayFindVariant variant) {
Builtin builtin = (variant == ArrayFindVariant::kFind)
? Builtin::kArrayFindLoopLazyDeoptContinuation
: Builtin::kArrayFindIndexLoopLazyDeoptContinuation;
Node* checkpoint_params[] = {params.receiver, params.callback,
params.this_arg, k, params.original_length};
return CreateJavaScriptBuiltinContinuationFrameState(
params.jsgraph, params.shared, builtin, params.target, params.context,
checkpoint_params, arraysize(checkpoint_params), params.outer_frame_state,
ContinuationFrameStateMode::LAZY);
}
FrameState FindLoopEagerFrameState(const FindFrameStateParams& params,
TNode<Number> k, ArrayFindVariant variant) {
Builtin builtin = (variant == ArrayFindVariant::kFind)
? Builtin::kArrayFindLoopEagerDeoptContinuation
: Builtin::kArrayFindIndexLoopEagerDeoptContinuation;
Node* checkpoint_params[] = {params.receiver, params.callback,
params.this_arg, k, params.original_length};
return CreateJavaScriptBuiltinContinuationFrameState(
params.jsgraph, params.shared, builtin, params.target, params.context,
checkpoint_params, arraysize(checkpoint_params), params.outer_frame_state,
ContinuationFrameStateMode::EAGER);
}
FrameState FindLoopAfterCallbackLazyFrameState(
const FindFrameStateParams& params, TNode<Number> next_k,
TNode<Object> if_found_value, ArrayFindVariant variant) {
Builtin builtin =
(variant == ArrayFindVariant::kFind)
? Builtin::kArrayFindLoopAfterCallbackLazyDeoptContinuation
: Builtin::kArrayFindIndexLoopAfterCallbackLazyDeoptContinuation;
Node* checkpoint_params[] = {params.receiver, params.callback,
params.this_arg, next_k,
params.original_length, if_found_value};
return CreateJavaScriptBuiltinContinuationFrameState(
params.jsgraph, params.shared, builtin, params.target, params.context,
checkpoint_params, arraysize(checkpoint_params), params.outer_frame_state,
ContinuationFrameStateMode::LAZY);
}
} // namespace
TNode<Object> IteratingArrayBuiltinReducerAssembler::ReduceArrayPrototypeFind(
MapInference* inference, const bool has_stability_dependency,
ElementsKind kind, SharedFunctionInfoRef shared,
NativeContextRef native_context, ArrayFindVariant variant) {
FrameState outer_frame_state = FrameStateInput();
TNode<Context> context = ContextInput();
TNode<Object> target = TargetInput();
TNode<JSArray> receiver = ReceiverInputAs<JSArray>();
TNode<Object> fncallback = ArgumentOrUndefined(0);
TNode<Object> this_arg = ArgumentOrUndefined(1);
TNode<Number> original_length = LoadJSArrayLength(receiver, kind);
FindFrameStateParams frame_state_params{
jsgraph(), shared, context, target, outer_frame_state,
receiver, fncallback, this_arg, original_length};
ThrowIfNotCallable(
fncallback,
FindLoopLazyFrameState(frame_state_params, ZeroConstant(), variant));
const bool is_find_variant = (variant == ArrayFindVariant::kFind);
auto out = MakeLabel(MachineRepresentation::kTagged);
ForZeroUntil(original_length).Do([&](TNode<Number> k) {
Checkpoint(FindLoopEagerFrameState(frame_state_params, k, variant));
MaybeInsertMapChecks(inference, has_stability_dependency);
TNode<Object> element;
std::tie(k, element) = SafeLoadElement(kind, receiver, k);
if (IsHoleyElementsKind(kind)) {
element = ConvertHoleToUndefined(element, kind);
}
TNode<Object> if_found_value = is_find_variant ? element : k;
TNode<Number> next_k = NumberInc(k);
// The callback result states whether the desired element was found.
TNode<Object> v =
JSCall3(fncallback, this_arg, element, k, receiver,
FindLoopAfterCallbackLazyFrameState(frame_state_params, next_k,
if_found_value, variant));
GotoIf(ToBoolean(v), &out, if_found_value);
});
// If the loop completed, the element was not found.
TNode<Object> if_not_found_value =
is_find_variant ? TNode<Object>::UncheckedCast(UndefinedConstant())
: TNode<Object>::UncheckedCast(MinusOneConstant());
Goto(&out, if_not_found_value);
Bind(&out);
return out.PhiAt<Object>(0);
}
namespace {
struct EverySomeFrameStateParams {
JSGraph* jsgraph;
SharedFunctionInfoRef shared;
TNode<Context> context;
TNode<Object> target;
FrameState outer_frame_state;
TNode<Object> receiver;
TNode<Object> callback;
TNode<Object> this_arg;
TNode<Object> original_length;
};
FrameState EverySomeLoopLazyFrameState(const EverySomeFrameStateParams& params,
TNode<Number> k,
ArrayEverySomeVariant variant) {
Builtin builtin = (variant == ArrayEverySomeVariant::kEvery)
? Builtin::kArrayEveryLoopLazyDeoptContinuation
: Builtin::kArraySomeLoopLazyDeoptContinuation;
Node* checkpoint_params[] = {params.receiver, params.callback,
params.this_arg, k, params.original_length};
return CreateJavaScriptBuiltinContinuationFrameState(
params.jsgraph, params.shared, builtin, params.target, params.context,
checkpoint_params, arraysize(checkpoint_params), params.outer_frame_state,
ContinuationFrameStateMode::LAZY);
}
FrameState EverySomeLoopEagerFrameState(const EverySomeFrameStateParams& params,
TNode<Number> k,
ArrayEverySomeVariant variant) {
Builtin builtin = (variant == ArrayEverySomeVariant::kEvery)
? Builtin::kArrayEveryLoopEagerDeoptContinuation
: Builtin::kArraySomeLoopEagerDeoptContinuation;
Node* checkpoint_params[] = {params.receiver, params.callback,
params.this_arg, k, params.original_length};
return CreateJavaScriptBuiltinContinuationFrameState(
params.jsgraph, params.shared, builtin, params.target, params.context,
checkpoint_params, arraysize(checkpoint_params), params.outer_frame_state,
ContinuationFrameStateMode::EAGER);
}
} // namespace
TNode<Boolean>
IteratingArrayBuiltinReducerAssembler::ReduceArrayPrototypeEverySome(
MapInference* inference, const bool has_stability_dependency,
ElementsKind kind, SharedFunctionInfoRef shared,
NativeContextRef native_context, ArrayEverySomeVariant variant) {
FrameState outer_frame_state = FrameStateInput();
TNode<Context> context = ContextInput();
TNode<Object> target = TargetInput();
TNode<JSArray> receiver = ReceiverInputAs<JSArray>();
TNode<Object> fncallback = ArgumentOrUndefined(0);
TNode<Object> this_arg = ArgumentOrUndefined(1);
TNode<Number> original_length = LoadJSArrayLength(receiver, kind);
EverySomeFrameStateParams frame_state_params{
jsgraph(), shared, context, target, outer_frame_state,
receiver, fncallback, this_arg, original_length};
ThrowIfNotCallable(
fncallback,
EverySomeLoopLazyFrameState(frame_state_params, ZeroConstant(), variant));
auto out = MakeLabel(MachineRepresentation::kTagged);
ForZeroUntil(original_length).Do([&](TNode<Number> k) {
Checkpoint(EverySomeLoopEagerFrameState(frame_state_params, k, variant));
MaybeInsertMapChecks(inference, has_stability_dependency);
TNode<Object> element;
std::tie(k, element) = SafeLoadElement(kind, receiver, k);
auto continue_label = MakeLabel();
element = MaybeSkipHole(element, kind, &continue_label);
TNode<Object> v =
JSCall3(fncallback, this_arg, element, k, receiver,
EverySomeLoopLazyFrameState(frame_state_params, k, variant));
if (variant == ArrayEverySomeVariant::kEvery) {
GotoIfNot(ToBoolean(v), &out, FalseConstant());
} else {
DCHECK_EQ(variant, ArrayEverySomeVariant::kSome);
GotoIf(ToBoolean(v), &out, TrueConstant());
}
Goto(&continue_label);
Bind(&continue_label);
});
Goto(&out, (variant == ArrayEverySomeVariant::kEvery) ? TrueConstant()
: FalseConstant());
Bind(&out);
return out.PhiAt<Boolean>(0);
}
namespace {
Callable GetCallableForArrayIndexOfIncludes(ArrayIndexOfIncludesVariant variant,
ElementsKind elements_kind,
Isolate* isolate) {
if (variant == ArrayIndexOfIncludesVariant::kIndexOf) {
switch (elements_kind) {
case PACKED_SMI_ELEMENTS:
case HOLEY_SMI_ELEMENTS:
return Builtins::CallableFor(isolate, Builtin::kArrayIndexOfSmi);
case PACKED_ELEMENTS:
case HOLEY_ELEMENTS:
return Builtins::CallableFor(isolate,
Builtin::kArrayIndexOfSmiOrObject);
case PACKED_DOUBLE_ELEMENTS:
return Builtins::CallableFor(isolate,
Builtin::kArrayIndexOfPackedDoubles);
default:
DCHECK_EQ(HOLEY_DOUBLE_ELEMENTS, elements_kind);
return Builtins::CallableFor(isolate,
Builtin::kArrayIndexOfHoleyDoubles);
}
} else {
DCHECK_EQ(variant, ArrayIndexOfIncludesVariant::kIncludes);
switch (elements_kind) {
case PACKED_SMI_ELEMENTS:
case HOLEY_SMI_ELEMENTS:
return Builtins::CallableFor(isolate, Builtin::kArrayIncludesSmi);
case PACKED_ELEMENTS:
case HOLEY_ELEMENTS:
return Builtins::CallableFor(isolate,
Builtin::kArrayIncludesSmiOrObject);
case PACKED_DOUBLE_ELEMENTS:
return Builtins::CallableFor(isolate,
Builtin::kArrayIncludesPackedDoubles);
default:
DCHECK_EQ(HOLEY_DOUBLE_ELEMENTS, elements_kind);
return Builtins::CallableFor(isolate,
Builtin::kArrayIncludesHoleyDoubles);
}
}
UNREACHABLE();
}
} // namespace
TNode<Object>
IteratingArrayBuiltinReducerAssembler::ReduceArrayPrototypeIndexOfIncludes(
ElementsKind kind, ArrayIndexOfIncludesVariant variant) {
TNode<Context> context = ContextInput();
TNode<JSArray> receiver = ReceiverInputAs<JSArray>();
TNode<Object> search_element = ArgumentOrUndefined(0);
TNode<Object> from_index = ArgumentOrZero(1);
// TODO(jgruber): This currently only reduces to a stub call. Create a full
// reduction (similar to other higher-order array builtins) instead of
// lowering to a builtin call. E.g. Array.p.every and Array.p.some have almost
// identical functionality.
TNode<Number> length = LoadJSArrayLength(receiver, kind);
TNode<FixedArrayBase> elements = LoadElements(receiver);
const bool have_from_index = ArgumentCount() > 1;
if (have_from_index) {
TNode<Smi> from_index_smi = CheckSmi(from_index);
// If the index is negative, it means the offset from the end and
// therefore needs to be added to the length. If the result is still
// negative, it needs to be clamped to 0.
TNode<Boolean> cond = NumberLessThan(from_index_smi, ZeroConstant());
from_index = SelectIf<Number>(cond)
.Then(_ {
return NumberMax(NumberAdd(length, from_index_smi),
ZeroConstant());
})
.Else(_ { return from_index_smi; })
.ExpectFalse()
.Value();
}
return Call4(GetCallableForArrayIndexOfIncludes(variant, kind, isolate()),
context, elements, search_element, length, from_index);
}
namespace {
struct PromiseCtorFrameStateParams {
JSGraph* jsgraph;
SharedFunctionInfoRef shared;
Node* node_ptr;
TNode<Context> context;
TNode<Object> target;
FrameState outer_frame_state;
};
// Remnant of old-style JSCallReducer code. Could be ported to graph assembler,
// but probably not worth the effort.
FrameState CreateConstructInvokeStubFrameState(
Node* node, Node* outer_frame_state, SharedFunctionInfoRef shared,
Node* context, CommonOperatorBuilder* common, TFGraph* graph) {
const FrameStateFunctionInfo* state_info =
common->CreateFrameStateFunctionInfo(FrameStateType::kConstructInvokeStub,
1, 0, 0, shared.object(), {});
const Operator* op = common->FrameState(
BytecodeOffset::None(), OutputFrameStateCombine::Ignore(), state_info);
const Operator* op0 = common->StateValues(0, SparseInputMask::Dense());
Node* node0 = graph->NewNode(op0);
static constexpr int kTargetInputIndex = 0;
static constexpr int kReceiverInputIndex = 1;
std::vector<Node*> params;
params.push_back(node->InputAt(kReceiverInputIndex));
const Operator* op_param = common->StateValues(
static_cast<int>(params.size()), SparseInputMask::Dense());
Node* params_node = graph->NewNode(op_param, static_cast<int>(params.size()),
&params.front());
DCHECK(context);
return FrameState(graph->NewNode(op, params_node, node0, node0, context,
node->InputAt(kTargetInputIndex),
outer_frame_state));
}
FrameState PromiseConstructorFrameState(
const PromiseCtorFrameStateParams& params, CommonOperatorBuilder* common,
TFGraph* graph) {
DCHECK_EQ(1,
params.shared
.internal_formal_parameter_count_without_receiver_deprecated());
return CreateConstructInvokeStubFrameState(
params.node_ptr, params.outer_frame_state, params.shared, params.context,
common, graph);
}
FrameState PromiseConstructorLazyFrameState(
const PromiseCtorFrameStateParams& params,
FrameState constructor_frame_state) {
// The deopt continuation of this frame state is never called; the frame state
// is only necessary to obtain the right stack trace.
JSGraph* jsgraph = params.jsgraph;
Node* checkpoint_params[] = {
jsgraph->UndefinedConstant(), /* receiver */
jsgraph->UndefinedConstant(), /* promise */
jsgraph->UndefinedConstant(), /* reject function */
jsgraph->TheHoleConstant() /* exception */
};
return CreateJavaScriptBuiltinContinuationFrameState(
jsgraph, params.shared, Builtin::kPromiseConstructorLazyDeoptContinuation,
params.target, params.context, checkpoint_params,
arraysize(checkpoint_params), constructor_frame_state,
ContinuationFrameStateMode::LAZY);
}
FrameState PromiseConstructorLazyWithCatchFrameState(
const PromiseCtorFrameStateParams& params,
FrameState constructor_frame_state, TNode<JSPromise> promise,
TNode<JSFunction> reject) {
// This continuation just returns the created promise and takes care of
// exceptions thrown by the executor.
Node* checkpoint_params[] = {
params.jsgraph->UndefinedConstant(), /* receiver */
promise, reject};
return CreateJavaScriptBuiltinContinuationFrameState(
params.jsgraph, params.shared,
Builtin::kPromiseConstructorLazyDeoptContinuation, params.target,
params.context, checkpoint_params, arraysize(checkpoint_params),
constructor_frame_state, ContinuationFrameStateMode::LAZY_WITH_CATCH);
}
} // namespace
TNode<Object> PromiseBuiltinReducerAssembler::ReducePromiseConstructor(
NativeContextRef native_context) {
DCHECK_GE(ConstructArity(), 1);
JSConstructNode n(node_ptr());
FrameState outer_frame_state = FrameStateInput();
TNode<Context> context = ContextInput();
TNode<Object> target = TargetInput();
TNode<Object> executor = n.Argument(0);
DCHECK_EQ(target, NewTargetInput());
SharedFunctionInfoRef promise_shared =
native_context.promise_function(broker()).shared(broker());
PromiseCtorFrameStateParams frame_state_params{jsgraph(), promise_shared,
node_ptr(), context,
target, outer_frame_state};
// Insert a construct stub frame into the chain of frame states. This will
// reconstruct the proper frame when deoptimizing within the constructor.
// For the frame state, we only provide the executor parameter, even if more
// arguments were passed. This is not observable from JS.
FrameState constructor_frame_state =
PromiseConstructorFrameState(frame_state_params, common(), graph());
IfNot(ObjectIsCallable(executor))
.Then((_ {
JSCallRuntime2(
Runtime::kThrowTypeError,
SmiConstant(
Smi::FromEnum(MessageTemplate::kResolverNotAFunction).value()),
executor, context,
PromiseConstructorLazyFrameState(frame_state_params,
constructor_frame_state));
}))
.ExpectTrue();
TNode<JSPromise> promise = CreatePromise(context);
// 8. CreatePromiseResolvingFunctions
// Allocate a promise context for the closures below.
TNode<Context> promise_context = CreateFunctionContext(
native_context, context, PromiseBuiltins::kPromiseContextLength);
StoreContextNoCellSlot(promise_context, PromiseBuiltins::kPromiseSlot,
promise);
StoreContextNoCellSlot(promise_context, PromiseBuiltins::kAlreadyResolvedSlot,
FalseConstant());
StoreContextNoCellSlot(promise_context, PromiseBuiltins::kDebugEventSlot,
TrueConstant());
// Allocate closures for the resolve and reject cases.
SharedFunctionInfoRef resolve_sfi =
MakeRef(broker(), broker()
->isolate()
->factory()
->promise_capability_default_resolve_shared_fun());
TNode<JSFunction> resolve =
CreateClosureFromBuiltinSharedFunctionInfo(resolve_sfi, promise_context);
SharedFunctionInfoRef reject_sfi =
MakeRef(broker(), broker()
->isolate()
->factory()
->promise_capability_default_reject_shared_fun());
TNode<JSFunction> reject =
CreateClosureFromBuiltinSharedFunctionInfo(reject_sfi, promise_context);
FrameState lazy_with_catch_frame_state =
PromiseConstructorLazyWithCatchFrameState(
frame_state_params, constructor_frame_state, promise, reject);
// 9. Call executor with both resolving functions.
// 10a. Call reject if the call to executor threw.
Try(_ {
CallPromiseExecutor(executor, resolve, reject, lazy_with_catch_frame_state);
}).Catch([&](TNode<Object> exception) {
// Clear pending message since the exception is not going to be rethrown.
ClearPendingMessage();
CallPromiseReject(reject, exception, lazy_with_catch_frame_state);
});
return promise;
}
#undef _
std::pair<Node*, Node*> JSCallReducer::ReleaseEffectAndControlFromAssembler(
JSCallReducerAssembler* gasm) {
auto catch_scope = gasm->catch_scope();
DCHECK(catch_scope->is_outermost());
if (catch_scope->has_handler() &&
catch_scope->has_exceptional_control_flow()) {
TNode<Object> handler_exception;
Effect handler_effect{nullptr};
Control handler_control{nullptr};
gasm->catch_scope()->MergeExceptionalPaths(
&handler_exception, &handler_effect, &handler_control);
ReplaceWithValue(gasm->outermost_handler(), handler_exception,
handler_effect, handler_control);
}
return {gasm->effect(), gasm->control()};
}
Reduction JSCallReducer::ReplaceWithSubgraph(JSCallReducerAssembler* gasm,
Node* subgraph) {
// TODO(jgruber): Consider a less fiddly way of integrating the new subgraph
// into the outer graph. For instance, the subgraph could be created in
// complete isolation, and then plugged into the outer graph in one go.
// Instead of manually tracking IfException nodes, we could iterate the
// subgraph.
// Replace the Call node with the newly-produced subgraph.
ReplaceWithValue(gasm->node_ptr(), subgraph, gasm->effect(), gasm->control());
// Wire exception edges contained in the newly-produced subgraph into the
// outer graph.
auto catch_scope = gasm->catch_scope();
DCHECK(catch_scope->is_outermost());
if (catch_scope->has_handler() &&
catch_scope->has_exceptional_control_flow()) {
TNode<Object> handler_exception;
Effect handler_effect{nullptr};
Control handler_control{nullptr};
gasm->catch_scope()->MergeExceptionalPaths(
&handler_exception, &handler_effect, &handler_control);
ReplaceWithValue(gasm->outermost_handler(), handler_exception,
handler_effect, handler_control);
}
return Replace(subgraph);
}
Reduction JSCallReducer::ReduceMathUnary(Node* node, const Operator* op) {
JSCallNode n(node);
CallParameters const& p = n.Parameters();
if (p.speculation_mode() != SpeculationMode::kAllowSpeculation) {
return NoChange();
}
if (n.ArgumentCount() < 1) {
Node* value = jsgraph()->NaNConstant();
ReplaceWithValue(node, value);
return Replace(value);
}
JSCallReducerAssembler a(this, node);
Node* subgraph = a.ReduceMathUnary(op);
return ReplaceWithSubgraph(&a, subgraph);
}
Reduction JSCallReducer::ReduceMathBinary(Node* node, const Operator* op) {
JSCallNode n(node);
CallParameters const& p = n.Parameters();
if (p.speculation_mode() != SpeculationMode::kAllowSpeculation) {
return NoChange();
}
if (n.ArgumentCount() < 1) {
Node* value = jsgraph()->NaNConstant();
ReplaceWithValue(node, value);
return Replace(value);
}
JSCallReducerAssembler a(this, node);
Node* subgraph = a.ReduceMathBinary(op);
return ReplaceWithSubgraph(&a, subgraph);
}
// ES6 section 20.2.2.19 Math.imul ( x, y )
Reduction JSCallReducer::ReduceMathImul(Node* node) {
JSCallNode n(node);
CallParameters const& p = n.Parameters();
if (p.speculation_mode() != SpeculationMode::kAllowSpeculation) {
return NoChange();
}
if (n.ArgumentCount() < 1) {
Node* value = jsgraph()->ZeroConstant();
ReplaceWithValue(node, value);
return Replace(value);
}
Node* left = n.Argument(0);
Node* right = n.ArgumentOr(1, jsgraph()->ZeroConstant());
Effect effect = n.effect();
Control control = n.control();
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) {
JSCallNode n(node);
CallParameters const& p = n.Parameters();
if (p.speculation_mode() != SpeculationMode::kAllowSpeculation) {
return NoChange();
}
if (n.ArgumentCount() < 1) {
Node* value = jsgraph()->ConstantNoHole(32);
ReplaceWithValue(node, value);
return Replace(value);
}
Node* input = n.Argument(0);
Effect effect = n.effect();
Control control = n.control();
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) {
JSCallNode n(node);
CallParameters const& p = n.Parameters();
if (p.speculation_mode() != SpeculationMode::kAllowSpeculation) {
return NoChange();
}
if (n.ArgumentCount() < 1) {
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()),
n.Argument(0), effect, control);
for (int i = 1; i < n.ArgumentCount(); i++) {
Node* input = effect = graph()->NewNode(
simplified()->SpeculativeToNumber(NumberOperationHint::kNumberOrOddball,
p.feedback()),
n.Argument(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::kJSConstructForwardAllArgs:
return ReduceJSConstructForwardAllArgs(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()) {
// Remember the max node id before reduction.
NodeId const max_id = static_cast<NodeId>(graph()->NodeCount() - 1);
Reduction const reduction = Reduce(node);
if (reduction.Changed()) {
Node* replacement = reduction.replacement();
if (replacement != node) {
Replace(node, replacement, max_id);
}
}
}
}
}
// ES6 section 22.1.1 The Array Constructor
Reduction JSCallReducer::ReduceArrayConstructor(Node* node) {
JSCallNode n(node);
CallParameters const& p = n.Parameters();
if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) {
return NoChange();
}
// Turn the {node} into a {JSCreateArray} call.
Node* target = n.target();
size_t const arity = p.arity_without_implicit_args();
node->RemoveInput(n.FeedbackVectorIndex());
NodeProperties::ReplaceValueInput(node, target, 0);
NodeProperties::ReplaceValueInput(node, target, 1);
NodeProperties::ChangeOp(
node, javascript()->CreateArray(arity, std::nullopt,
n.Parameters().feedback()));
return Changed(node);
}
// ES6 section 19.3.1.1 Boolean ( value )
Reduction JSCallReducer::ReduceBooleanConstructor(Node* node) {
// Replace the {node} with a proper {ToBoolean} operator.
JSCallNode n(node);
Node* value = n.ArgumentOrUndefined(0, jsgraph());
value = graph()->NewNode(simplified()->ToBoolean(), value);
ReplaceWithValue(node, value);
return Replace(value);
}
// ES section #sec-object-constructor
Reduction JSCallReducer::ReduceObjectConstructor(Node* node) {
JSCallNode n(node);
if (n.ArgumentCount() < 1) return NoChange();
Node* value = n.Argument(0);
Effect effect = n.effect();
// We can fold away the Tagged<Object>(x) call if |x| is definitely not a
// primitive.
if (NodeProperties::CanBePrimitive(broker(), value, effect)) {
if (!NodeProperties::CanBeNullOrUndefined(broker(), value, effect)) {
// Turn the {node} into a {JSToObject} call if we know that
// the {value} cannot be null or undefined.
NodeProperties::ReplaceValueInputs(node, value);
NodeProperties::ChangeOp(node, javascript()->ToObject());
return Changed(node);
}
} else {
ReplaceWithValue(node, value);
return Replace(value);
}
return NoChange();
}
// ES6 section 19.2.3.1 Function.prototype.apply ( thisArg, argArray )
Reduction JSCallReducer::ReduceFunctionPrototypeApply(Node* node) {
JSCallNode n(node);
CallParameters const& p = n.Parameters();
CallFeedbackRelation new_feedback_relation =
p.feedback_relation() == CallFeedbackRelation::kReceiver
? CallFeedbackRelation::kTarget
: CallFeedbackRelation::kUnrelated;
int arity = p.arity_without_implicit_args();
if (arity < 2) {
// Degenerate cases.
ConvertReceiverMode convert_mode;
if (arity == 0) {
// Neither thisArg nor argArray was provided.
convert_mode = ConvertReceiverMode::kNullOrUndefined;
node->ReplaceInput(n.TargetIndex(), n.receiver());
node->ReplaceInput(n.ReceiverIndex(), jsgraph()->UndefinedConstant());
} else {
DCHECK_EQ(arity, 1);
// The argArray was not provided, just remove the {target}.
convert_mode = ConvertReceiverMode::kAny;
node->RemoveInput(n.TargetIndex());
--arity;
}
// Change {node} to a {JSCall} and try to reduce further.
NodeProperties::ChangeOp(
node, javascript()->Call(JSCallNode::ArityForArgc(arity), p.frequency(),
p.feedback(), convert_mode,
p.speculation_mode(), new_feedback_relation));
return Changed(node).FollowedBy(ReduceJSCall(node));
}
// Turn the JSCall into a JSCallWithArrayLike.
// If {argArray} can be null or undefined, we have to generate branches since
// JSCallWithArrayLike would throw for null or undefined.
Node* target = n.receiver();
Node* this_argument = n.Argument(0);
Node* arguments_list = n.Argument(1);
Node* context = n.context();
FrameState frame_state = n.frame_state();
Effect effect = n.effect();
Control control = n.control();
// If {arguments_list} cannot be null or undefined, we don't need
// to expand this {node} to control-flow.
if (!NodeProperties::CanBeNullOrUndefined(broker(), arguments_list, effect)) {
// Massage the value inputs appropriately.
node->ReplaceInput(n.TargetIndex(), target);
node->ReplaceInput(n.ReceiverIndex(), this_argument);
node->ReplaceInput(n.ArgumentIndex(0), arguments_list);
while (arity-- > 1) node->RemoveInput(n.ArgumentIndex(1));
// Morph the {node} to a {JSCallWithArrayLike}.
NodeProperties::ChangeOp(
node, javascript()->CallWithArrayLike(p.frequency(), p.feedback(),
p.speculation_mode(),
new_feedback_relation));
return Changed(node).FollowedBy(ReduceJSCallWithArrayLike(node));
}
// 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(), p.feedback(),
p.speculation_mode(),
new_feedback_relation),
target, this_argument, arguments_list, n.feedback_vector(), 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(JSCallNode::ArityForArgc(0)), target, this_argument,
n.feedback_vector(), 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);
}
// ES section #sec-function.prototype.bind
Reduction JSCallReducer::ReduceFunctionPrototypeBind(Node* node) {
JSCallNode n(node);
CallParameters const& p = n.Parameters();
if (p.speculation_mode() != SpeculationMode::kAllowSpeculation) {
return NoChange();
}
// Value inputs to the {node} are as follows:
//
// - target, which is Function.prototype.bind JSFunction
// - receiver, which is the [[BoundTargetFunction]]
// - bound_this (optional), which is the [[BoundThis]]
// - and all the remaining value inputs are [[BoundArguments]]
Node* receiver = n.receiver();
Node* context = n.context();
Effect effect = n.effect();
Control control = n.control();
// Ensure that the {receiver} is known to be a JSBoundFunction or
// a JSFunction with the same [[Prototype]], and all maps we've
// seen for the {receiver} so far indicate that {receiver} is
// definitely a constructor or not a constructor.
MapInference inference(broker(), receiver, effect);
if (!inference.HaveMaps()) return NoChange();
ZoneRefSet<Map> const& receiver_maps = inference.GetMaps();
MapRef first_receiver_map = receiver_maps[0];
bool const is_constructor = first_receiver_map.is_constructor();
HeapObjectRef prototype = first_receiver_map.prototype(broker());
for (MapRef receiver_map : receiver_maps) {
HeapObjectRef map_prototype = receiver_map.prototype(broker());
// Check for consistency among the {receiver_maps}.
if (!map_prototype.equals(prototype) ||
receiver_map.is_constructor() != is_constructor ||
!InstanceTypeChecker::IsJSFunctionOrBoundFunctionOrWrappedFunction(
receiver_map.instance_type())) {
return inference.NoChange();
}
// Disallow binding of slow-mode functions. We need to figure out
// whether the length and name property are in the original state.
if (receiver_map.is_dictionary_map()) return inference.NoChange();
// Check whether the length and name properties are still present
// as AccessorInfo objects. In that case, their values can be
// recomputed even if the actual value of the object changes.
// This mirrors the checks done in builtins-function-gen.cc at
// runtime otherwise.
int minimum_nof_descriptors =
std::max(
{JSFunctionOrBoundFunctionOrWrappedFunction::kLengthDescriptorIndex,
JSFunctionOrBoundFunctionOrWrappedFunction::
kNameDescriptorIndex}) +
1;
if (receiver_map.NumberOfOwnDescriptors() < minimum_nof_descriptors) {
return inference.NoChange();
}
const InternalIndex kLengthIndex(
JSFunctionOrBoundFunctionOrWrappedFunction::kLengthDescriptorIndex);
const InternalIndex kNameIndex(
JSFunctionOrBoundFunctionOrWrappedFunction::kNameDescriptorIndex);
StringRef length_string = broker()->length_string();
StringRef name_string = broker()->name_string();
OptionalObjectRef length_value(
receiver_map.GetStrongValue(broker(), kLengthIndex));
OptionalObjectRef name_value(
receiver_map.GetStrongValue(broker(), kNameIndex));
if (!length_value || !name_value) {
TRACE_BROKER_MISSING(
broker(), "name or length descriptors on map " << receiver_map);
return inference.NoChange();
}
if (!receiver_map.GetPropertyKey(broker(), kLengthIndex)
.equals(length_string) ||
!length_value->IsAccessorInfo() ||
!receiver_map.GetPropertyKey(broker(), kNameIndex)
.equals(name_string) ||
!name_value->IsAccessorInfo()) {
return inference.NoChange();
}
}
// Choose the map for the resulting JSBoundFunction (but bail out in case of a
// custom prototype).
MapRef map =
is_constructor
? native_context().bound_function_with_constructor_map(broker())
: native_context().bound_function_without_constructor_map(broker());
if (!map.prototype(broker()).equals(prototype)) return inference.NoChange();
inference.RelyOnMapsPreferStability(dependencies(), jsgraph(), &effect,
control, p.feedback());
// Replace the {node} with a JSCreateBoundFunction.
static constexpr int kBoundThis = 1;
static constexpr int kReceiverContextEffectAndControl = 4;
int const arity = n.ArgumentCount();
if (arity > 0) {
MapRef fixed_array_map = broker()->fixed_array_map();
AllocationBuilder ab(jsgraph(), broker(), effect, control);
if (!ab.CanAllocateArray(arity, fixed_array_map)) {
return NoChange();
}
}
int const arity_with_bound_this = std::max(arity, kBoundThis);
int const input_count =
arity_with_bound_this + kReceiverContextEffectAndControl;
Node** inputs = graph()->zone()->AllocateArray<Node*>(input_count);
int cursor = 0;
inputs[cursor++] = receiver;
inputs[cursor++] = n.ArgumentOrUndefined(0, jsgraph()); // bound_this.
for (int i = 1; i < arity; ++i) {
inputs[cursor++] = n.Argument(i);
}
inputs[cursor++] = context;
inputs[cursor++] = effect;
inputs[cursor++] = control;
DCHECK_EQ(cursor, input_count);
Node* value = effect =
graph()->NewNode(javascript()->CreateBoundFunction(
arity_with_bound_this - kBoundThis, 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) {
JSCallNode n(node);
CallParameters const& p = n.Parameters();
Node* target = n.target();
Effect effect = n.effect();
Control control = n.control();
// 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.HasResolvedValue() && m.Ref(broker()).IsJSFunction()) {
JSFunctionRef function = m.Ref(broker()).AsJSFunction();
context = jsgraph()->ConstantNoHole(function.context(broker()), broker());
} 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.
int arity = p.arity_without_implicit_args();
ConvertReceiverMode convert_mode;
if (arity == 0) {
// The thisArg was not provided, use undefined as receiver.
convert_mode = ConvertReceiverMode::kNullOrUndefined;
node->ReplaceInput(n.TargetIndex(), n.receiver());
node->ReplaceInput(n.ReceiverIndex(), jsgraph()->UndefinedConstant());
} else {
// Just remove the target, which is the first value input.
convert_mode = ConvertReceiverMode::kAny;
node->RemoveInput(n.TargetIndex());
--arity;
}
NodeProperties::ChangeOp(
node, javascript()->Call(JSCallNode::ArityForArgc(arity), p.frequency(),
p.feedback(), convert_mode, p.speculation_mode(),
CallFeedbackRelation::kUnrelated));
// Try to further reduce the JSCall {node}.
return Changed(node).FollowedBy(ReduceJSCall(node));
}
// ES6 section 19.2.3.6 Function.prototype [ @@hasInstance ] (V)
Reduction JSCallReducer::ReduceFunctionPrototypeHasInstance(Node* node) {
JSCallNode n(node);
Node* receiver = n.receiver();
Node* object = n.ArgumentOrUndefined(0, jsgraph());
Node* context = n.context();
FrameState frame_state = n.frame_state();
Effect effect = n.effect();
Control control = n.control();
// 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) {
Effect effect{NodeProperties::GetEffectInput(node)};
// Try to determine the {object} map.
MapInference inference(broker(), object, effect);
if (!inference.HaveMaps()) return NoChange();
ZoneRefSet<Map> const& object_maps = inference.GetMaps();
MapRef candidate_map = object_maps[0];
HeapObjectRef candidate_prototype = candidate_map.prototype(broker());
// Check if we can constant-fold the {candidate_prototype}.
for (size_t i = 0; i < object_maps.size(); ++i) {
MapRef object_map = object_maps[i];
HeapObjectRef map_prototype = object_map.prototype(broker());
if (IsSpecialReceiverInstanceType(object_map.instance_type()) ||
!map_prototype.equals(candidate_prototype)) {
// We exclude special receivers, like JSProxy or API objects that
// might require access checks here; we also don't want to deal
// with hidden prototypes at this point.
return inference.NoChange();
}
// The above check also excludes maps for primitive values, which is
// important because we are not applying [[ToObject]] here as expected.
DCHECK(!object_map.IsPrimitiveMap() && object_map.IsJSReceiverMap());
}
if (!inference.RelyOnMapsViaStability(dependencies())) {
return inference.NoChange();
}
Node* value = jsgraph()->ConstantNoHole(candidate_prototype, broker());
ReplaceWithValue(node, value);
return Replace(value);
}
// ES6 section 19.1.2.11 Object.getPrototypeOf ( O )
Reduction JSCallReducer::ReduceObjectGetPrototypeOf(Node* node) {
JSCallNode n(node);
Node* object = n.ArgumentOrUndefined(0, jsgraph());
return ReduceObjectGetPrototype(node, object);
}
// ES section #sec-object.is
Reduction JSCallReducer::ReduceObjectIs(Node* node) {
JSCallNode n(node);
Node* lhs = n.ArgumentOrUndefined(0, jsgraph());
Node* rhs = n.ArgumentOrUndefined(1, jsgraph());
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) {
JSCallNode n(node);
return ReduceObjectGetPrototype(node, n.receiver());
}
// ES #sec-object.prototype.hasownproperty
Reduction JSCallReducer::ReduceObjectPrototypeHasOwnProperty(Node* node) {
JSCallNode call_node(node);
Node* receiver = call_node.receiver();
Node* name = call_node.ArgumentOrUndefined(0, jsgraph());
Effect effect = call_node.effect();
Control control = call_node.control();
// 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) {
JSForInNextNode n(name);
if (n.Parameters().mode() != ForInMode::kGeneric) {
Node* object = n.receiver();
Node* cache_type = n.cache_type();
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);
}
// We can also optimize for this case below:
// receiver(is a heap constant with fast map)
// ^
// | object(all keys are enumerable)
// | ^
// | |
// | JSForInNext
// | ^
// +----+ |
// | |
// JSCall[hasOwnProperty]
// We can replace the {JSCall} with several internalized string
// comparisons.
if (receiver->opcode() == IrOpcode::kHeapConstant) {
MapInference inference(broker(), receiver, effect);
if (!inference.HaveMaps()) {
return inference.NoChange();
}
const ZoneRefSet<Map>& receiver_maps = inference.GetMaps();
if (receiver_maps.size() == 1) {
const MapRef receiver_map = *receiver_maps.begin();
InstanceType instance_type = receiver_map.instance_type();
int const nof = receiver_map.NumberOfOwnDescriptors();
// We set a heuristic value to limit the compare instructions number.
if (nof > 4 || instance_type <= LAST_SPECIAL_RECEIVER_TYPE ||
receiver_map.is_dictionary_map()) {
return inference.NoChange();
}
// Replace builtin call with several internalized string comparisons.
CallParameters const& p = call_node.Parameters();
inference.RelyOnMapsPreferStability(dependencies(), jsgraph(),
&effect, control, p.feedback());
#define __ gasm.
JSGraphAssembler gasm(broker(), jsgraph(), jsgraph()->zone(),
BranchSemantics::kJS);
gasm.InitializeEffectControl(effect, control);
auto done = __ MakeLabel(MachineRepresentation::kTagged);
const DescriptorArrayRef descriptor_array =
receiver_map.instance_descriptors(broker());
for (InternalIndex key_index : InternalIndex::Range(nof)) {
NameRef receiver_key =
descriptor_array.GetPropertyKey(broker(), key_index);
Node* lhs = jsgraph()->HeapConstantNoHole(receiver_key.object());
__ GotoIf(__ ReferenceEqual(TNode<Object>::UncheckedCast(lhs),
TNode<Object>::UncheckedCast(name)),
&done, __ TrueConstant());
}
__ Goto(&done, __ FalseConstant());
__ Bind(&done);
Node* value = done.PhiAt(0);
ReplaceWithValue(node, value, gasm.effect(), gasm.control());
return Replace(value);
#undef __
}
return inference.NoChange();
}
}
}
return NoChange();
}
// ES #sec-object.prototype.isprototypeof
Reduction JSCallReducer::ReduceObjectPrototypeIsPrototypeOf(Node* node) {
JSCallNode n(node);
Node* receiver = n.receiver();
Node* value = n.ArgumentOrUndefined(0, jsgraph());
Effect effect = n.effect();
// Ensure that the {receiver} is known to be a JSReceiver (so that
// the ToObject step of Object.prototype.isPrototypeOf is a no-op).
MapInference inference(broker(), receiver, effect);
if (!inference.HaveMaps() || !inference.AllOfInstanceTypesAreJSReceiver()) {
return NoChange();
}
// We don't check whether {value} is a proper JSReceiver here explicitly,
// and don't explicitly rule out Primitive {value}s, since all of them
// have null as their prototype, so the prototype chain walk inside the
// JSHasInPrototypeChain operator immediately aborts and yields false.
NodeProperties::ReplaceValueInput(node, value, n.TargetIndex());
for (int i = node->op()->ValueInputCount(); i > 2; i--) {
node->RemoveInput(2);
}
NodeProperties::ChangeOp(node, javascript()->HasInPrototypeChain());
return Changed(node);
}
// ES6 section 26.1.1 Reflect.apply ( target, thisArgument, argumentsList )
Reduction JSCallReducer::ReduceReflectApply(Node* node) {
JSCallNode n(node);
CallParameters const& p = n.Parameters();
int arity = p.arity_without_implicit_args();
// Massage value inputs appropriately.
static_assert(n.ReceiverIndex() > n.TargetIndex());
node->RemoveInput(n.ReceiverIndex());
node->RemoveInput(n.TargetIndex());
while (arity < 3) {
node->InsertInput(graph()->zone(), arity++, jsgraph()->UndefinedConstant());
}
while (arity-- > 3) {
node->RemoveInput(arity);
}
NodeProperties::ChangeOp(
node, javascript()->CallWithArrayLike(p.frequency(), p.feedback(),
p.speculation_mode(),
CallFeedbackRelation::kUnrelated));
return Changed(node).FollowedBy(ReduceJSCallWithArrayLike(node));
}
// ES6 section 26.1.2 Reflect.construct ( target, argumentsList [, newTarget] )
Reduction JSCallReducer::ReduceReflectConstruct(Node* node) {
JSCallNode n(node);
CallParameters const& p = n.Parameters();
int arity = p.arity_without_implicit_args();
// Massage value inputs appropriately.
TNode<Object> arg_target = n.ArgumentOrUndefined(0, jsgraph());
TNode<Object> arg_argument_list = n.ArgumentOrUndefined(1, jsgraph());
TNode<Object> arg_new_target = n.ArgumentOr(2, arg_target);
static_assert(n.ReceiverIndex() > n.TargetIndex());
node->RemoveInput(n.ReceiverIndex());
node->RemoveInput(n.TargetIndex());
// TODO(jgruber): This pattern essentially ensures that we have the correct
// number of inputs for a given argument count. Wrap it in a helper function.
static_assert(JSConstructNode::FirstArgumentIndex() == 2);
while (arity < 3) {
node->InsertInput(graph()->zone(), arity++, jsgraph()->UndefinedConstant());
}
while (arity-- > 3) {
node->RemoveInput(arity);
}
static_assert(JSConstructNode::TargetIndex() == 0);
static_assert(JSConstructNode::NewTargetIndex() == 1);
static_assert(JSConstructNode::kFeedbackVectorIsLastInput);
node->ReplaceInput(JSConstructNode::TargetIndex(), arg_target);
node->ReplaceInput(JSConstructNode::NewTargetIndex(), arg_new_target);
node->ReplaceInput(JSConstructNode::ArgumentIndex(0), arg_argument_list);
NodeProperties::ChangeOp(
node, javascript()->ConstructWithArrayLike(p.frequency(), p.feedback()));
return Changed(node).FollowedBy(ReduceJSConstructWithArrayLike(node));
}
// ES6 section 26.1.7 Reflect.getPrototypeOf ( target )
Reduction JSCallReducer::ReduceReflectGetPrototypeOf(Node* node) {
JSCallNode n(node);
Node* target = n.ArgumentOrUndefined(0, jsgraph());
return ReduceObjectGetPrototype(node, target);
}
// ES6 section #sec-object.create Object.create(proto, properties)
Reduction JSCallReducer::ReduceObjectCreate(Node* node) {
JSCallNode n(node);
Node* properties = n.ArgumentOrUndefined(1, jsgraph());
if (properties != jsgraph()->UndefinedConstant()) return NoChange();
Node* context = n.context();
FrameState frame_state = n.frame_state();
Effect effect = n.effect();
Control control = n.control();
Node* prototype = n.ArgumentOrUndefined(0, jsgraph());
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) {
JSCallNode n(node);
CallParameters const& p = n.Parameters();
int arity = p.arity_without_implicit_args();
if (arity != 2) return NoChange();
Node* target = n.Argument(0);
Node* key = n.Argument(1);
Node* context = n.context();
FrameState frame_state = n.frame_state();
Effect effect = n.effect();
Control control = n.control();
// 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()->ConstantNoHole(
static_cast<int>(MessageTemplate::kCalledOnNonObject)),
jsgraph()->HeapConstantNoHole(factory()->ReflectGet_string()), context,
frame_state, efalse, if_false);
}
// Otherwise just use the existing GetPropertyStub.
Node* if_true = graph()->NewNode(common()->IfTrue(), branch);
Node* etrue = effect;
Node* vtrue;
{
Callable callable = Builtins::CallableFor(isolate(), Builtin::kGetProperty);
auto call_descriptor = Linkage::GetStubCallDescriptor(
graph()->zone(), callable.descriptor(),
callable.descriptor().GetStackParameterCount(),
CallDescriptor::kNeedsFrameState, Operator::kNoProperties);
Node* stub_code = jsgraph()->HeapConstantNoHole(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);
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) {
JSCallNode n(node);
Node* target = n.ArgumentOrUndefined(0, jsgraph());
Node* key = n.ArgumentOrUndefined(1, jsgraph());
Node* context = n.context();
Effect effect = n.effect();
Control control = n.control();
FrameState frame_state = n.frame_state();
// 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()->ConstantNoHole(
static_cast<int>(MessageTemplate::kCalledOnNonObject)),
jsgraph()->HeapConstantNoHole(factory()->ReflectHas_string()), context,
frame_state, efalse, if_false);
}
// Otherwise just use the existing {JSHasProperty} logic.
Node* if_true = graph()->NewNode(common()->IfTrue(), branch);
Node* etrue = effect;
Node* vtrue;
{
// TODO(magardn): collect feedback so this can be optimized
vtrue = etrue = if_true = graph()->NewNode(
javascript()->HasProperty(FeedbackSource()), target, key,
jsgraph()->UndefinedConstant(), 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);
MergeControlToEnd(graph(), common(), if_false);
// Continue on the regular path.
ReplaceWithValue(node, vtrue, etrue, if_true);
return Changed(vtrue);
}
namespace {
bool CanInlineArrayIteratingBuiltin(JSHeapBroker* broker,
ZoneRefSet<Map> const& receiver_maps,
ElementsKind* kind_return) {
DCHECK_NE(0, receiver_maps.size());
*kind_return = receiver_maps[0].elements_kind();
for (MapRef map : receiver_maps) {
if (!map.supports_fast_array_iteration(broker) ||
!UnionElementsKindUptoSize(kind_return, map.elements_kind())) {
return false;
}
}
return true;
}
bool CanInlineArrayResizingBuiltin(JSHeapBroker* broker,
ZoneRefSet<Map> const& receiver_maps,
std::vector<ElementsKind>* kinds,
bool builtin_is_push = false) {
DCHECK_NE(0, receiver_maps.size());
for (MapRef map : receiver_maps) {
if (!map.supports_fast_array_resize(broker)) return false;
// TODO(turbofan): We should also handle fast holey double elements once
// we got the hole NaN mess sorted out in TurboFan/V8.
if (map.elements_kind() == HOLEY_DOUBLE_ELEMENTS && !builtin_is_push) {
return false;
}
ElementsKind current_kind = map.elements_kind();
auto kind_ptr = kinds->data();
size_t i;
for (i = 0; i < kinds->size(); i++, kind_ptr++) {
if (UnionElementsKindUptoPackedness(kind_ptr, current_kind)) {
break;
}
}
if (i == kinds->size()) kinds->push_back(current_kind);
}
return true;
}
// Wraps common setup code for iterating array builtins.
class IteratingArrayBuiltinHelper {
public:
IteratingArrayBuiltinHelper(Node* node, JSHeapBroker* broker,
JSGraph* jsgraph,
CompilationDependencies* dependencies)
: receiver_(NodeProperties::GetValueInput(node, 1)),
effect_(NodeProperties::GetEffectInput(node)),
control_(NodeProperties::GetControlInput(node)),
inference_(broker, receiver_, effect_) {
if (!v8_flags.turbo_inline_array_builtins) return;
DCHECK_EQ(IrOpcode::kJSCall, node->opcode());
const CallParameters& p = CallParametersOf(node->op());
if (p.speculation_mode() != SpeculationMode::kAllowSpeculation) {
return;
}
// Try to determine the {receiver} map.
if (!inference_.HaveMaps()) return;
ZoneRefSet<Map> const& receiver_maps = inference_.GetMaps();
if (!CanInlineArrayIteratingBuiltin(broker, receiver_maps,
&elements_kind_)) {
return;
}
// TODO(jgruber): May only be needed for holey elements kinds.
if (!dependencies->DependOnNoElementsProtector()) return;
has_stability_dependency_ = inference_.RelyOnMapsPreferStability(
dependencies, jsgraph, &effect_, control_, p.feedback());
can_reduce_ = true;
}
bool can_reduce() const { return can_reduce_; }
bool has_stability_dependency() const { return has_stability_dependency_; }
Effect effect() const { return effect_; }
Control control() const { return control_; }
MapInference* inference() { return &inference_; }
ElementsKind elements_kind() const { return elements_kind_; }
private:
bool can_reduce_ = false;
bool has_stability_dependency_ = false;
Node* receiver_;
Effect effect_;
Control control_;
MapInference inference_;
ElementsKind elements_kind_;
};
} // namespace
Reduction JSCallReducer::ReduceArrayForEach(Node* node,
SharedFunctionInfoRef shared) {
IteratingArrayBuiltinHelper h(node, broker(), jsgraph(), dependencies());
if (!h.can_reduce()) return h.inference()->NoChange();
IteratingArrayBuiltinReducerAssembler a(this, node);
a.InitializeEffectControl(h.effect(), h.control());
TNode<Object> subgraph = a.ReduceArrayPrototypeForEach(
h.inference(), h.has_stability_dependency(), h.elements_kind(), shared);
return ReplaceWithSubgraph(&a, subgraph);
}
Reduction JSCallReducer::ReduceArrayReduce(Node* node,
SharedFunctionInfoRef shared) {
IteratingArrayBuiltinHelper h(node, broker(), jsgraph(), dependencies());
if (!h.can_reduce()) return h.inference()->NoChange();
IteratingArrayBuiltinReducerAssembler a(this, node);
a.InitializeEffectControl(h.effect(), h.control());
TNode<Object> subgraph = a.ReduceArrayPrototypeReduce(
h.inference(), h.has_stability_dependency(), h.elements_kind(),
ArrayReduceDirection::kLeft, shared);
return ReplaceWithSubgraph(&a, subgraph);
}
Reduction JSCallReducer::ReduceArrayReduceRight(Node* node,
SharedFunctionInfoRef shared) {
IteratingArrayBuiltinHelper h(node, broker(), jsgraph(), dependencies());
if (!h.can_reduce()) return h.inference()->NoChange();
IteratingArrayBuiltinReducerAssembler a(this, node);
a.InitializeEffectControl(h.effect(), h.control());
TNode<Object> subgraph = a.ReduceArrayPrototypeReduce(
h.inference(), h.has_stability_dependency(), h.elements_kind(),
ArrayReduceDirection::kRight, shared);
return ReplaceWithSubgraph(&a, subgraph);
}
Reduction JSCallReducer::ReduceArrayMap(Node* node,
SharedFunctionInfoRef shared) {
IteratingArrayBuiltinHelper h(node, broker(), jsgraph(), dependencies());
if (!h.can_reduce()) return h.inference()->NoChange();
// Calls CreateArray and thus requires this additional protector dependency.
if (!dependencies()->DependOnArraySpeciesProtector()) {
return h.inference()->NoChange();
}
IteratingArrayBuiltinReducerAssembler a(this, node);
a.InitializeEffectControl(h.effect(), h.control());
TNode<Object> subgraph =
a.ReduceArrayPrototypeMap(h.inference(), h.has_stability_dependency(),
h.elements_kind(), shared, native_context());
return ReplaceWithSubgraph(&a, subgraph);
}
Reduction JSCallReducer::ReduceArrayFilter(Node* node,
SharedFunctionInfoRef shared) {
IteratingArrayBuiltinHelper h(node, broker(), jsgraph(), dependencies());
if (!h.can_reduce()) return h.inference()->NoChange();
// Calls CreateArray and thus requires this additional protector dependency.
if (!dependencies()->DependOnArraySpeciesProtector()) {
return h.inference()->NoChange();
}
IteratingArrayBuiltinReducerAssembler a(this, node);
a.InitializeEffectControl(h.effect(), h.control());
TNode<Object> subgraph =
a.ReduceArrayPrototypeFilter(h.inference(), h.has_stability_dependency(),
h.elements_kind(), shared, native_context());
return ReplaceWithSubgraph(&a, subgraph);
}
Reduction JSCallReducer::ReduceArrayFind(Node* node,
SharedFunctionInfoRef shared) {
IteratingArrayBuiltinHelper h(node, broker(), jsgraph(), dependencies());
if (!h.can_reduce()) return h.inference()->NoChange();
IteratingArrayBuiltinReducerAssembler a(this, node);
a.InitializeEffectControl(h.effect(), h.control());
TNode<Object> subgraph = a.ReduceArrayPrototypeFind(
h.inference(), h.has_stability_dependency(), h.elements_kind(), shared,
native_context(), ArrayFindVariant::kFind);
return ReplaceWithSubgraph(&a, subgraph);
}
Reduction JSCallReducer::ReduceArrayFindIndex(Node* node,
SharedFunctionInfoRef shared) {
IteratingArrayBuiltinHelper h(node, broker(), jsgraph(), dependencies());
if (!h.can_reduce()) return h.inference()->NoChange();
IteratingArrayBuiltinReducerAssembler a(this, node);
a.InitializeEffectControl(h.effect(), h.control());
TNode<Object> subgraph = a.ReduceArrayPrototypeFind(
h.inference(), h.has_stability_dependency(), h.elements_kind(), shared,
native_context(), ArrayFindVariant::kFindIndex);
return ReplaceWithSubgraph(&a, subgraph);
}
Reduction JSCallReducer::ReduceArrayEvery(Node* node,
SharedFunctionInfoRef shared) {
IteratingArrayBuiltinHelper h(node, broker(), jsgraph(), dependencies());
if (!h.can_reduce()) return h.inference()->NoChange();
IteratingArrayBuiltinReducerAssembler a(this, node);
a.InitializeEffectControl(h.effect(), h.control());
TNode<Object> subgraph = a.ReduceArrayPrototypeEverySome(
h.inference(), h.has_stability_dependency(), h.elements_kind(), shared,
native_context(), ArrayEverySomeVariant::kEvery);
return ReplaceWithSubgraph(&a, subgraph);
}
// ES7 Array.prototype.inludes(searchElement[, fromIndex])
// #sec-array.prototype.includes
Reduction JSCallReducer::ReduceArrayIncludes(Node* node) {
IteratingArrayBuiltinHelper h(node, broker(), jsgraph(), dependencies());
if (!h.can_reduce()) return h.inference()->NoChange();
IteratingArrayBuiltinReducerAssembler a(this, node);
a.InitializeEffectControl(h.effect(), h.control());
TNode<Object> subgraph = a.ReduceArrayPrototypeIndexOfIncludes(
h.elements_kind(), ArrayIndexOfIncludesVariant::kIncludes);
return ReplaceWithSubgraph(&a, subgraph);
}
// ES6 Array.prototype.indexOf(searchElement[, fromIndex])
// #sec-array.prototype.indexof
Reduction JSCallReducer::ReduceArrayIndexOf(Node* node) {
IteratingArrayBuiltinHelper h(node, broker(), jsgraph(), dependencies());
if (!h.can_reduce()) return h.inference()->NoChange();
IteratingArrayBuiltinReducerAssembler a(this, node);
a.InitializeEffectControl(h.effect(), h.control());
TNode<Object> subgraph = a.ReduceArrayPrototypeIndexOfIncludes(
h.elements_kind(), ArrayIndexOfIncludesVariant::kIndexOf);
return ReplaceWithSubgraph(&a, subgraph);
}
Reduction JSCallReducer::ReduceArraySome(Node* node,
SharedFunctionInfoRef shared) {
IteratingArrayBuiltinHelper h(node, broker(), jsgraph(), dependencies());
if (!h.can_reduce()) return h.inference()->NoChange();
IteratingArrayBuiltinReducerAssembler a(this, node);
a.InitializeEffectControl(h.effect(), h.control());
TNode<Object> subgraph = a.ReduceArrayPrototypeEverySome(
h.inference(), h.has_stability_dependency(), h.elements_kind(), shared,
native_context(), ArrayEverySomeVariant::kSome);
return ReplaceWithSubgraph(&a, subgraph);
}
#if V8_ENABLE_WEBASSEMBLY
Reduction JSCallReducer::ReduceWasmMethodWrapper(Node* node,
JSFunctionRef function,
SharedFunctionInfoRef shared) {
if (!shared.HasBuiltinId() ||
shared.builtin_id() != Builtin::kWasmMethodWrapper) {
return NoChange();
}
JSCallNode n(node);
CallParameters const& p = n.Parameters();
if (p.feedback().IsValid() &&
p.speculation_mode() != SpeculationMode::kAllowSpeculation) {
return NoChange();
}
// Duplicate the receiver into the first argument slot...
node->InsertInput(graph()->zone(), n.FirstArgumentIndex(), n.receiver());
NodeProperties::ChangeOp(
node, javascript()->Call(
JSCallNode::ArityForArgc(n.ArgumentCount() + 1), p.frequency(),
p.feedback(), ConvertReceiverMode::kAny, p.speculation_mode()));
// ...and call the target function directly.
ContextRef context = function.context(broker());
OptionalObjectRef target =
context.get(broker(), wasm::kMethodWrapperContextSlot);
if (!target.has_value()) {
// This is unexpected; if you see this happening please tell
// jkummerow@chromium.org how to reproduce it.
TRACE_BROKER_MISSING(broker(), "target value for context " << context);
return NoChange();
}
node->ReplaceInput(n.TargetIndex(),
jsgraph()->ConstantNoHole(target.value(), broker()));
return Changed(node).FollowedBy(ReduceJSCall(node));
}
bool CanInlineJSToWasmCall(const wasm::CanonicalSig* wasm_signature) {
if (wasm_signature->return_count() > 1) {
return false;
}
for (auto type : wasm_signature->all()) {
#if defined(V8_TARGET_ARCH_32_BIT)
if (type == wasm::kWasmI64) return false;
#endif
if (type != wasm::kWasmI32 && type != wasm::kWasmI64 &&
type != wasm::kWasmF32 && type != wasm::kWasmF64 &&
type != wasm::kWasmExternRef) {
return false;
}
}
return true;
}
Reduction JSCallReducer::ReduceCallWasmFunction(Node* node,
SharedFunctionInfoRef shared) {
DCHECK(flags() & kInlineJSToWasmCalls);
JSCallNode n(node);
const CallParameters& p = n.Parameters();
// Avoid deoptimization loops if feedback says we should be conservative.
if (p.feedback().IsValid() &&
p.speculation_mode() != SpeculationMode::kAllowSpeculation) {
return NoChange();
}
// Read the trusted object only once to ensure a consistent view on it.
Tagged<Object> trusted_data = shared.object()->GetTrustedData(isolate());
Tagged<WasmExportedFunctionData> function_data;
if (!TryCast(trusted_data, &function_data)) return NoChange();
if (function_data->is_promising()) return NoChange();
Tagged<WasmTrustedInstanceData> instance_data =
function_data->instance_data();
const wasm::CanonicalSig* wasm_signature = function_data->internal()->sig();
if (!CanInlineJSToWasmCall(wasm_signature)) {
return NoChange();
}
wasm::NativeModule* native_module = instance_data->native_module();
int wasm_function_index = function_data->function_index();
bool receiver_is_first_param = function_data->receiver_is_first_param() != 0;
if (wasm_native_module_for_inlining_ == nullptr) {
wasm_native_module_for_inlining_ = native_module;
}
// TODO(mliedtke): We should be able to remove module, signature, native
// module and function index from the SharedFunctionInfoRef. However, for some
// reason I may dereference the SharedFunctionInfoRef here but not in
// JSInliningHeuristic later on.
const Operator* op = javascript()->CallWasm(
native_module, wasm_function_index, shared, p.feedback());
size_t actual_arity = n.ArgumentCount();
DCHECK(JSCallNode::kFeedbackVectorIsLastInput);
DCHECK_EQ(actual_arity + JSWasmCallNode::kExtraInputCount - 1,
n.FeedbackVectorIndex());
size_t expected_arity = wasm_signature->parameter_count();
// Duplicate the receiver into the first argument slot if requested.
if (receiver_is_first_param) {
node->InsertInput(graph()->zone(), n.FirstArgumentIndex(),
node->InputAt(n.ReceiverIndex()));
actual_arity++;
}
// Remove additional inputs.
while (actual_arity > expected_arity) {
int removal_index =
static_cast<int>(n.FirstArgumentIndex() + expected_arity);
DCHECK_LT(removal_index, static_cast<int>(node->InputCount()));
node->RemoveInput(removal_index);
actual_arity--;
}
// Add missing inputs.
while (actual_arity < expected_arity) {
int insertion_index = n.ArgumentIndex(n.ArgumentCount());
node->InsertInput(graph()->zone(), insertion_index,
jsgraph()->UndefinedConstant());
actual_arity++;
}
NodeProperties::ChangeOp(node, op);
return Changed(node);
}
#endif // V8_ENABLE_WEBASSEMBLY
Reduction JSCallReducer::ReduceCallApiFunction(Node* node,
SharedFunctionInfoRef shared) {
JSCallNode n(node);
CallParameters const& p = n.Parameters();
int const argc = p.arity_without_implicit_args();
Node* target = n.target();
Node* global_proxy = jsgraph()->ConstantNoHole(
native_context().global_proxy_object(broker()), broker());
Node* receiver = (p.convert_mode() == ConvertReceiverMode::kNullOrUndefined)
? global_proxy
: n.receiver();
Node* context = n.context();
Effect effect = n.effect();
Control control = n.control();
FrameState frame_state = n.frame_state();
if (!shared.function_template_info(broker()).has_value()) {
TRACE_BROKER_MISSING(
broker(), "FunctionTemplateInfo for function with SFI " << shared);
return NoChange();
}
// See if we can optimize this API call to {shared}.
FunctionTemplateInfoRef function_template_info(
shared.function_template_info(broker()).value());
if (function_template_info.accept_any_receiver() &&
function_template_info.is_signature_undefined(broker())) {
// We might be able to
// optimize the API call depending on the {function_template_info}.
// If the API function accepts any kind of {receiver}, we only need to
// ensure that the {receiver} is actually a JSReceiver at this point,
// and also pass that as the {holder}. There are two independent bits
// here:
//
// a. When the "accept any receiver" bit is set, it means we don't
// need to perform access checks, even if the {receiver}'s map
// has the "needs access check" bit set.
// b. When the {function_template_info} has no signature, we don't
// need to do the compatible receiver check, since all receivers
// are considered compatible at that point, and the {receiver}
// will be pass as the {holder}.
//
receiver = effect = graph()->NewNode(
simplified()->ConvertReceiver(p.convert_mode()), receiver,
jsgraph()->ConstantNoHole(native_context(), broker()), global_proxy,
effect, control);
} else {
// Try to infer the {receiver} maps from the graph.
MapInference inference(broker(), receiver, effect);
if (inference.HaveMaps()) {
ZoneRefSet<Map> const& receiver_maps = inference.GetMaps();
MapRef first_receiver_map = receiver_maps[0];
// See if we can constant-fold the compatible receiver checks.
HolderLookupResult api_holder =
function_template_info.LookupHolderOfExpectedType(broker(),
first_receiver_map);
if (api_holder.lookup == CallOptimization::kHolderNotFound) {
return inference.NoChange();
}
// Check that all {receiver_maps} are actually JSReceiver maps and
// that the {function_template_info} accepts them without access
// checks (even if "access check needed" is set for {receiver}).
//
// Note that we don't need to know the concrete {receiver} maps here,
// meaning it's fine if the {receiver_maps} are unreliable, and we also
// don't need to install any stability dependencies, since the only
// relevant information regarding the {receiver} is the Map::constructor
// field on the root map (which is different from the JavaScript exposed
// "constructor" property) and that field cannot change.
//
// So if we know that {receiver} had a certain constructor at some point
// in the past (i.e. it had a certain map), then this constructor is going
// to be the same later, since this information cannot change with map
// transitions.
//
// The same is true for the instance type, e.g. we still know that the
// instance type is JSObject even if that information is unreliable, and
// the "access check needed" bit, which also cannot change later.
CHECK(first_receiver_map.IsJSReceiverMap());
CHECK(!first_receiver_map.is_access_check_needed() ||
function_template_info.accept_any_receiver());
for (size_t i = 1; i < receiver_maps.size(); ++i) {
MapRef receiver_map = receiver_maps[i];
HolderLookupResult holder_i =
function_template_info.LookupHolderOfExpectedType(broker(),
receiver_map);
if (api_holder.lookup != holder_i.lookup) return inference.NoChange();
DCHECK(holder_i.lookup == CallOptimization::kHolderFound ||
holder_i.lookup == CallOptimization::kHolderIsReceiver);
if (holder_i.lookup == CallOptimization::kHolderFound) {
DCHECK(api_holder.holder.has_value() && holder_i.holder.has_value());
if (!api_holder.holder->equals(*holder_i.holder)) {
return inference.NoChange();
}
}
CHECK(receiver_map.IsJSReceiverMap());
CHECK(!receiver_map.is_access_check_needed() ||
function_template_info.accept_any_receiver());
}
if (p.speculation_mode() != SpeculationMode::kAllowSpeculation &&
!inference.RelyOnMapsViaStability(dependencies())) {
// We were not able to make the receiver maps reliable without map
// checks but doing map checks would lead to deopt loops, so give up.
return inference.NoChange();
}
// TODO(neis): The maps were used in a way that does not actually require
// map checks or stability dependencies.
inference.RelyOnMapsPreferStability(dependencies(), jsgraph(), &effect,
control, p.feedback());
} else {
// We don't have enough information to eliminate the access check
// and/or the compatible receiver check, so use the generic builtin
// that does those checks dynamically. This is still significantly
// faster than the generic call sequence.
Builtin builtin_name;
if (function_template_info.accept_any_receiver()) {
DCHECK(!function_template_info.is_signature_undefined(broker()));
builtin_name = Builtin::kCallFunctionTemplate_CheckCompatibleReceiver;
} else if (function_template_info.is_signature_undefined(broker())) {
builtin_name = Builtin::kCallFunctionTemplate_CheckAccess;
} else {
builtin_name =
Builtin::kCallFunctionTemplate_CheckAccessAndCompatibleReceiver;
}
// The CallFunctionTemplate builtin requires the {receiver} to be
// an actual JSReceiver, so make sure we do the proper conversion
// first if necessary.
receiver = effect = graph()->NewNode(
simplified()->ConvertReceiver(p.convert_mode()), receiver,
jsgraph()->ConstantNoHole(native_context(), broker()), global_proxy,
effect, control);
Callable callable = Builtins::CallableFor(isolate(), builtin_name);
auto call_descriptor = Linkage::GetStubCallDescriptor(
graph()->zone(), callable.descriptor(),
argc + 1 /* implicit receiver */, CallDescriptor::kNeedsFrameState);
node->RemoveInput(n.FeedbackVectorIndex());
node->InsertInput(graph()->zone(), 0,
jsgraph()->HeapConstantNoHole(callable.code()));
node->ReplaceInput(
1, jsgraph()->ConstantNoHole(function_template_info, broker()));
node->InsertInput(graph()->zone(), 2,
jsgraph()->Int32Constant(JSParameterCount(argc)));
node->ReplaceInput(3, receiver); // Update receiver input.
node->ReplaceInput(6 + argc, effect); // Update effect input.
NodeProperties::ChangeOp(node, common()->Call(call_descriptor));
return Changed(node);
}
}
// TODO(turbofan): Consider introducing a JSCallApiCallback operator for
// this and lower it during JSGenericLowering, and unify this with the
// JSNativeContextSpecialization::InlineApiCall method a bit.
compiler::OptionalObjectRef maybe_callback_data =
function_template_info.callback_data(broker());
// Check if the function has an associated C++ code to execute.
if (!maybe_callback_data.has_value()) {
// TODO(ishell): consider generating "return undefined" for empty function
// instead of failing.
TRACE_BROKER_MISSING(broker(), "call code for function template info "
<< function_template_info);
return NoChange();
}
// Handles overloaded functions.
FastApiCallFunction c_function = fast_api_call::GetFastApiCallTarget(
broker(), function_template_info, argc);
if (c_function.address) {
FastApiCallReducerAssembler a(this, node, function_template_info,
c_function, receiver, shared, target, argc,
effect);
Node* fast_call_subgraph = a.ReduceFastApiCall();
return Replace(fast_call_subgraph);
}
// Slow call
bool no_profiling = broker()->dependencies()->DependOnNoProfilingProtector();
Callable call_api_callback = Builtins::CallableFor(
isolate(), no_profiling ? Builtin::kCallApiCallbackOptimizedNoProfiling
: Builtin::kCallApiCallbackOptimized);
CallInterfaceDescriptor cid = call_api_callback.descriptor();
auto call_descriptor =
Linkage::GetStubCallDescriptor(graph()->zone(), cid, argc + 1 /*
implicit receiver */, CallDescriptor::kNeedsFrameState);
ApiFunction api_function(function_template_info.callback(broker()));
ExternalReference function_reference = ExternalReference::Create(
&api_function, ExternalReference::DIRECT_API_CALL);
Node* continuation_frame_state = CreateInlinedApiFunctionFrameState(
jsgraph(), shared, target, context, receiver, frame_state);
node->RemoveInput(n.FeedbackVectorIndex());
node->InsertInput(graph()->zone(), 0,
jsgraph()->HeapConstantNoHole(call_api_callback.code()));
node->ReplaceInput(1, jsgraph()->ExternalConstant(function_reference));
node->InsertInput(graph()->zone(), 2, jsgraph()->ConstantNoHole(argc));
node->InsertInput(
graph()->zone(), 3,
jsgraph()->HeapConstantNoHole(function_template_info.object()));
node->ReplaceInput(4, receiver); // Update receiver input.
// 6 + argc is context input.
node->ReplaceInput(5 + argc + 1, continuation_frame_state);
node->ReplaceInput(5 + argc + 2, effect);
NodeProperties::ChangeOp(node, common()->Call(call_descriptor));
return Changed(node);
}
namespace {
// Check whether elements aren't mutated; we play it extremely safe here by
// explicitly checking that {node} is only used by {LoadField} or
// {LoadElement}.
bool IsSafeArgumentsElements(Node* node) {
for (Edge const edge : node->use_edges()) {
if (!NodeProperties::IsValueEdge(edge)) continue;
if (edge.from()->opcode() != IrOpcode::kLoadField &&
edge.from()->opcode() != IrOpcode::kLoadElement) {
return false;
}
}
return true;
}
#ifdef DEBUG
bool IsCallOrConstructWithArrayLike(Node* node) {
return node->opcode() == IrOpcode::kJSCallWithArrayLike ||
node->opcode() == IrOpcode::kJSConstructWithArrayLike;
}
#endif
bool IsCallOrConstructWithSpread(Node* node) {
return node->opcode() == IrOpcode::kJSCallWithSpread ||
node->opcode() == IrOpcode::kJSConstructWithSpread;
}
bool IsCallWithArrayLikeOrSpread(Node* node) {
return node->opcode() == IrOpcode::kJSCallWithArrayLike ||
node->opcode() == IrOpcode::kJSCallWithSpread;
}
} // namespace
Node* JSCallReducer::ConvertHoleToUndefined(Node* value, ElementsKind kind) {
DCHECK(IsHoleyElementsKind(kind));
if (kind == HOLEY_DOUBLE_ELEMENTS) {
#ifdef V8_ENABLE_UNDEFINED_DOUBLE
return graph()->NewNode(
simplified()->ChangeFloat64OrUndefinedOrHoleToTagged(), value);
#else
return graph()->NewNode(simplified()->ChangeFloat64HoleToTagged(), value);
#endif // V8_ENABLE_UNDEFINED_DOUBLE
}
return graph()->NewNode(simplified()->ConvertTaggedHoleToUndefined(), value);
}
void JSCallReducer::CheckIfConstructor(Node* construct) {
JSConstructNode n(construct);
Node* new_target = n.new_target();
Control control = n.control();
Node* check =
graph()->NewNode(simplified()->ObjectIsConstructor(), new_target);
Node* check_branch =
graph()->NewNode(common()->Branch(BranchHint::kTrue), check, control);
Node* check_fail = graph()->NewNode(common()->IfFalse(), check_branch);
Node* check_throw = check_fail = graph()->NewNode(
javascript()->CallRuntime(Runtime::kThrowTypeError, 2),
jsgraph()->ConstantNoHole(
static_cast<int>(MessageTemplate::kNotConstructor)),
new_target, n.context(), n.frame_state(), n.effect(), check_fail);
control = graph()->NewNode(common()->IfTrue(), check_branch);
NodeProperties::ReplaceControlInput(construct, control);
// Rewire potential exception edges.
Node* on_exception = nullptr;
if (NodeProperties::IsExceptionalCall(construct, &on_exception)) {
// Create appropriate {IfException} and {IfSuccess} nodes.
Node* if_exception =
graph()->NewNode(common()->IfException(), check_throw, check_fail);
check_fail = graph()->NewNode(common()->IfSuccess(), check_fail);
// Join the exception edges.
Node* merge =
graph()->NewNode(common()->Merge(2), if_exception, on_exception);
Node* ephi = graph()->NewNode(common()->EffectPhi(2), if_exception,
on_exception, merge);
Node* phi =
graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2),
if_exception, on_exception, merge);
ReplaceWithValue(on_exception, phi, ephi, merge);
merge->ReplaceInput(1, on_exception);
ephi->ReplaceInput(1, on_exception);
phi->ReplaceInput(1, on_exception);
}
// The above %ThrowTypeError runtime call is an unconditional throw,
// making it impossible to return a successful completion in this case. We
// simply connect the successful completion to the graph end.
Node* throw_node =
graph()->NewNode(common()->Throw(), check_throw, check_fail);
MergeControlToEnd(graph(), common(), throw_node);
}
namespace {
bool ShouldUseCallICFeedback(Node* node) {
HeapObjectMatcher m(node);
if (m.HasResolvedValue() || m.IsCheckClosure() || m.IsJSCreateClosure()) {
// Don't use CallIC feedback when we know the function
// being called, i.e. either know the closure itself or
// at least the SharedFunctionInfo.
return false;
} else if (m.IsPhi()) {
// Protect against endless loops here.
Node* control = NodeProperties::GetControlInput(node);
if (control->opcode() == IrOpcode::kLoop ||
control->opcode() == IrOpcode::kDead) {
return true;
}
// Check if {node} is a Phi of nodes which shouldn't
// use CallIC feedback (not looking through loops).
int const value_input_count = m.node()->op()->ValueInputCount();
for (int n = 0; n < value_input_count; ++n) {
if (ShouldUseCallICFeedback(node->InputAt(n))) return true;
}
return false;
}
return true;
}
} // namespace
Node* JSCallReducer::CheckArrayLength(Node* array, ElementsKind elements_kind,
uint32_t array_length,
const FeedbackSource& feedback_source,
Effect effect, Control control) {
Node* length = effect = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForJSArrayLength(elements_kind)),
array, effect, control);
Node* check = graph()->NewNode(simplified()->NumberEqual(), length,
jsgraph()->ConstantNoHole(array_length));
return graph()->NewNode(
simplified()->CheckIf(DeoptimizeReason::kArrayLengthChanged,
feedback_source),
check, effect, control);
}
Reduction
JSCallReducer::ReduceCallOrConstructWithArrayLikeOrSpreadOfCreateArguments(
Node* node, Node* arguments_list, int arraylike_or_spread_index,
CallFrequency const& frequency, FeedbackSource const& feedback,
SpeculationMode speculation_mode, CallFeedbackRelation feedback_relation) {
DCHECK_EQ(arguments_list->opcode(), IrOpcode::kJSCreateArguments);
// Check if {node} is the only value user of {arguments_list} (except for
// value uses in frame states). If not, we give up for now.
for (Edge edge : arguments_list->use_edges()) {
if (!NodeProperties::IsValueEdge(edge)) continue;
Node* const user = edge.from();
switch (user->opcode()) {
case IrOpcode::kCheckMaps:
case IrOpcode::kFrameState:
case IrOpcode::kStateValues:
case IrOpcode::kReferenceEqual:
case IrOpcode::kReturn:
// Ignore safe uses that definitely don't mess with the arguments.
continue;
case IrOpcode::kLoadField: {
DCHECK_EQ(arguments_list, user->InputAt(0));
FieldAccess const& access = FieldAccessOf(user->op());
if (access.offset == JSArray::kLengthOffset) {
// Ignore uses for arguments#length.
static_assert(
static_cast<int>(JSArray::kLengthOffset) ==
static_cast<int>(JSStrictArgumentsObject::kLengthOffset));
static_assert(
static_cast<int>(JSArray::kLengthOffset) ==
static_cast<int>(JSSloppyArgumentsObject::kLengthOffset));
continue;
} else if (access.offset == JSObject::kElementsOffset) {
// Ignore safe uses for arguments#elements.
if (IsSafeArgumentsElements(user)) continue;
}
break;
}
case IrOpcode::kJSCallWithArrayLike: {
// Ignore uses as argumentsList input to calls with array like.
JSCallWithArrayLikeNode n(user);
if (edge.index() == n.ArgumentIndex(0)) continue;
break;
}
case IrOpcode::kJSConstructWithArrayLike: {
// Ignore uses as argumentsList input to calls with array like.
JSConstructWithArrayLikeNode n(user);
if (edge.index() == n.ArgumentIndex(0)) continue;
break;
}
case IrOpcode::kJSCallWithSpread: {
// Ignore uses as spread input to calls with spread.
JSCallWithSpreadNode n(user);
if (edge.index() == n.LastArgumentIndex()) continue;
break;
}
case IrOpcode::kJSConstructWithSpread: {
// Ignore uses as spread input to construct with spread.
JSConstructWithSpreadNode n(user);
if (edge.index() == n.LastArgumentIndex()) continue;
break;
}
default:
break;
}
// We cannot currently reduce the {node} to something better than what
// it already is, but we might be able to do something about the {node}
// later, so put it on the waitlist and try again during finalization.
waitlist_.insert(node);
return NoChange();
}
// Get to the actual frame state from which to extract the arguments;
// we can only optimize this in case the {node} was already inlined into
// some other function (and same for the {arguments_list}).
CreateArgumentsType const type = CreateArgumentsTypeOf(arguments_list->op());
FrameState frame_state =
FrameState{NodeProperties::GetFrameStateInput(arguments_list)};
int formal_parameter_count;
{
Handle<SharedFunctionInfo> shared;
if (!frame_state.frame_state_info().shared_info().ToHandle(&shared)) {
return NoChange();
}
formal_parameter_count =
MakeRef(broker(), shared)
.internal_formal_parameter_count_without_receiver_deprecated();
}
if (type == CreateArgumentsType::kMappedArguments) {
// Mapped arguments (sloppy mode) that are aliased can only be handled
// here if there's no side-effect between the {node} and the {arg_array}.
// TODO(turbofan): Further relax this constraint.
if (formal_parameter_count != 0) {
Node* effect = NodeProperties::GetEffectInput(node);
if (!NodeProperties::NoObservableSideEffectBetween(effect,
arguments_list)) {
return NoChange();
}
}
}
// For call/construct with spread, we need to also install a code
// dependency on the array iterator lookup protector cell to ensure
// that no one messed with the %ArrayIteratorPrototype%.next method.
if (IsCallOrConstructWithSpread(node)) {
if (!dependencies()->DependOnArrayIteratorProtector()) return NoChange();
}
// Remove the {arguments_list} input from the {node}.
node->RemoveInput(arraylike_or_spread_index);
// The index of the first relevant parameter. Only non-zero when looking at
// rest parameters, in which case it is set to the index of the first rest
// parameter.
const int start_index = (type == CreateArgumentsType::kRestParameter)
? formal_parameter_count
: 0;
// After removing the arraylike or spread object, the argument count is:
int argc =
arraylike_or_spread_index - JSCallOrConstructNode::FirstArgumentIndex();
// Check if are spreading to inlined arguments or to the arguments of
// the outermost function.
if (frame_state.outer_frame_state()->opcode() != IrOpcode::kFrameState) {
Operator const* op;
if (IsCallWithArrayLikeOrSpread(node)) {
static constexpr int kTargetAndReceiver = 2;
op = javascript()->CallForwardVarargs(argc + kTargetAndReceiver,
start_index);
} else {
static constexpr int kTargetAndNewTarget = 2;
op = javascript()->ConstructForwardVarargs(argc + kTargetAndNewTarget,
start_index);
}
node->RemoveInput(JSCallOrConstructNode::FeedbackVectorIndexForArgc(argc));
NodeProperties::ChangeOp(node, op);
return Changed(node);
}
// Get to the actual frame state from which to extract the arguments;
// we can only optimize this in case the {node} was already inlined into
// some other function (and same for the {arg_array}).
FrameState outer_state{frame_state.outer_frame_state()};
FrameStateInfo outer_info = outer_state.frame_state_info();
if (outer_info.type() == FrameStateType::kInlinedExtraArguments) {
// Need to take the parameters from the inlined extra arguments frame state.
frame_state = outer_state;
}
// Add the actual parameters to the {node}, skipping the receiver.
StateValuesAccess parameters_access(frame_state.parameters());
for (auto it = parameters_access.begin_without_receiver_and_skip(start_index);
!it.done(); ++it) {
DCHECK_NOT_NULL(it.node());
node->InsertInput(graph()->zone(),
JSCallOrConstructNode::ArgumentIndex(argc++), it.node());
}
if (IsCallWithArrayLikeOrSpread(node)) {
NodeProperties::ChangeOp(
node, javascript()->Call(JSCallNode::ArityForArgc(argc), frequency,
feedback, ConvertReceiverMode::kAny,
speculation_mode, feedback_relation));
return Changed(node).FollowedBy(ReduceJSCall(node));
} else {
NodeProperties::ChangeOp(
node, javascript()->Construct(JSConstructNode::ArityForArgc(argc),
frequency, feedback));
// Check whether the given new target value is a constructor function. The
// replacement {JSConstruct} operator only checks the passed target value
// but relies on the new target value to be implicitly valid.
CheckIfConstructor(node);
return Changed(node).FollowedBy(ReduceJSConstruct(node));
}
}
Reduction JSCallReducer::ReduceCallOrConstructWithArrayLikeOrSpread(
Node* node, int argument_count, int arraylike_or_spread_index,
CallFrequency const& frequency, FeedbackSource const& feedback_source,
SpeculationMode speculation_mode, CallFeedbackRelation feedback_relation,
Node* target, Effect effect, Control control) {
DCHECK(IsCallOrConstructWithArrayLike(node) ||
IsCallOrConstructWithSpread(node));
DCHECK_IMPLIES(speculation_mode == SpeculationMode::kAllowSpeculation,
feedback_source.IsValid());
// Let's not add instructions to the graph (like e.g., CheckIfConstructor
// does) if the resulting call might be dead.
if (feedback_source.IsValid()) {
ProcessedFeedback const& call_feedback =
broker()->GetFeedbackForCall(feedback_source);
if (call_feedback.IsInsufficient()) {
return ReduceForInsufficientFeedback(
node, DeoptimizeReason::kInsufficientTypeFeedbackForCall);
}
}
Node* arguments_list =
NodeProperties::GetValueInput(node, arraylike_or_spread_index);
if (arguments_list->opcode() == IrOpcode::kJSCreateArguments) {
return ReduceCallOrConstructWithArrayLikeOrSpreadOfCreateArguments(
node, arguments_list, arraylike_or_spread_index, frequency,
feedback_source, speculation_mode, feedback_relation);
}
if (!v8_flags.turbo_optimize_apply) return NoChange();
// Optimization of construct nodes not supported yet.
if (!IsCallWithArrayLikeOrSpread(node)) return NoChange();
// Avoid deoptimization loops.
if (speculation_mode != SpeculationMode::kAllowSpeculation) return NoChange();
// Only optimize with array literals.
if (arguments_list->opcode() != IrOpcode::kJSCreateLiteralArray &&
arguments_list->opcode() != IrOpcode::kJSCreateEmptyLiteralArray) {
return NoChange();
}
// For call/construct with spread, we need to also install a code
// dependency on the array iterator lookup protector cell to ensure
// that no one messed with the %ArrayIteratorPrototype%.next method.
if (IsCallOrConstructWithSpread(node)) {
if (!dependencies()->DependOnArrayIteratorProtector()) return NoChange();
}
if (arguments_list->opcode() == IrOpcode::kJSCreateEmptyLiteralArray) {
if (generated_calls_with_array_like_or_spread_.count(node)) {
return NoChange(); // Avoid infinite recursion.
}
JSCallReducerAssembler a(this, node);
Node* subgraph = a.ReduceJSCallWithArrayLikeOrSpreadOfEmpty(
&generated_calls_with_array_like_or_spread_);
return ReplaceWithSubgraph(&a, subgraph);
}
DCHECK_EQ(arguments_list->opcode(), IrOpcode::kJSCreateLiteralArray);
// Find array length and elements' kind from the feedback's allocation
// site's boilerplate JSArray.
JSCreateLiteralOpNode args_node(arguments_list);
CreateLiteralParameters const& args_params = args_node.Parameters();
const FeedbackSource& array_feedback = args_params.feedback();
const ProcessedFeedback& feedback =
broker()->GetFeedbackForArrayOrObjectLiteral(array_feedback);
if (feedback.IsInsufficient()) return NoChange();
AllocationSiteRef site = feedback.AsLiteral().value();
if (!site.boilerplate(broker()).has_value()) return NoChange();
JSArrayRef boilerplate_array = site.boilerplate(broker())->AsJSArray();
int const array_length =
boilerplate_array.GetBoilerplateLength(broker()).AsSmi();
SBXCHECK_GE(array_length, 0);
// We'll replace the arguments_list input with {array_length} element loads.
uint32_t new_argument_count =
static_cast<uint32_t>(array_length) + argument_count - 1;
// Do not optimize calls with a large number of arguments.
// Arbitrarily sets the limit to 32 arguments.
const uint32_t kMaxArityForOptimizedFunctionApply = 32;
if (new_argument_count > kMaxArityForOptimizedFunctionApply) {
return NoChange();
}
// Determine the array's map.
MapRef array_map = boilerplate_array.map(broker());
if (!array_map.supports_fast_array_iteration(broker())) {
return NoChange();
}
// Check and depend on NoElementsProtector.
if (!dependencies()->DependOnNoElementsProtector()) {
return NoChange();
}
// Remove the {arguments_list} node which will be replaced by a sequence of
// LoadElement nodes.
node->RemoveInput(arraylike_or_spread_index);
// Speculate on that array's map is still equal to the dynamic map of
// arguments_list; generate a map check.
effect = graph()->NewNode(
simplified()->CheckMaps(CheckMapsFlag::kNone, ZoneRefSet<Map>(array_map),
feedback_source),
arguments_list, effect, control);
// Speculate on that array's length being equal to the dynamic length of
// arguments_list; generate a deopt check.
ElementsKind elements_kind = array_map.elements_kind();
effect = CheckArrayLength(arguments_list, elements_kind, array_length,
feedback_source, effect, control);
// Generate N element loads to replace the {arguments_list} node with a set
// of arguments loaded from it.
Node* elements = effect = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForJSObjectElements()),
arguments_list, effect, control);
for (int i = 0; i < array_length; i++) {
// Load the i-th element from the array.
Node* index = jsgraph()->ConstantNoHole(i);
Node* load = effect = graph()->NewNode(
simplified()->LoadElement(
AccessBuilder::ForFixedArrayElement(elements_kind)),
elements, index, effect, control);
// In "holey" arrays some arguments might be missing and we pass
// 'undefined' instead.
if (IsHoleyElementsKind(elements_kind)) {
load = ConvertHoleToUndefined(load, elements_kind);
}
node->InsertInput(graph()->zone(), arraylike_or_spread_index + i, load);
}
NodeProperties::ChangeOp(
node, javascript()->Call(
JSCallNode::ArityForArgc(static_cast<int>(new_argument_count)),
frequency, feedback_source, ConvertReceiverMode::kAny,
speculation_mode, CallFeedbackRelation::kUnrelated));
NodeProperties::ReplaceEffectInput(node, effect);
return Changed(node).FollowedBy(ReduceJSCall(node));
}
bool JSCallReducer::IsBuiltinOrApiFunction(JSFunctionRef function) const {
// TODO(neis): Add a way to check if function template info isn't serialized
// and add a warning in such cases. Currently we can't tell if function
// template info doesn't exist or wasn't serialized.
return function.shared(broker()).HasBuiltinId() ||
function.shared(broker()).function_template_info(broker()).has_value();
}
Reduction JSCallReducer::ReduceJSCall(Node* node) {
if (broker()->StackHasOverflowed()) return NoChange();
JSCallNode n(node);
CallParameters const& p = n.Parameters();
Node* target = n.target();
Effect effect = n.effect();
Control control = n.control();
int arity = p.arity_without_implicit_args();
auto NoChangeOrSoftDeopt = [&]() {
if (p.feedback().IsValid()) {
ProcessedFeedback const& feedback =
broker()->GetFeedbackForCall(p.feedback());
if (feedback.IsInsufficient()) {
return ReduceForInsufficientFeedback(
node, DeoptimizeReason::kInsufficientTypeFeedbackForCall);
}
}
return NoChange();
};
// Try to specialize JSCall {node}s with constant {target}s.
HeapObjectMatcher m(target);
if (m.HasResolvedValue()) {
ObjectRef target_ref = m.Ref(broker());
if (target_ref.IsJSFunction()) {
JSFunctionRef function = target_ref.AsJSFunction();
// Don't inline cross native context.
if (!function.native_context(broker()).equals(native_context())) {
return NoChange();
}
SharedFunctionInfoRef shared = function.shared(broker());
Reduction res = ReduceJSCall(node, shared);
#if V8_ENABLE_WEBASSEMBLY
if (!res.Changed()) {
res = ReduceWasmMethodWrapper(node, function, shared);
}
#endif // V8_ENABLE_WEBASSEMBLY
if (!res.Changed()) return NoChangeOrSoftDeopt();
return res;
} else if (target_ref.IsJSBoundFunction()) {
JSBoundFunctionRef function = target_ref.AsJSBoundFunction();
ObjectRef bound_this = function.bound_this(broker());
ConvertReceiverMode const convert_mode =
bound_this.IsNullOrUndefined()
? ConvertReceiverMode::kNullOrUndefined
: ConvertReceiverMode::kNotNullOrUndefined;
// TODO(jgruber): Inline this block below once TryGet is guaranteed to
// succeed.
FixedArrayRef bound_arguments = function.bound_arguments(broker());
const uint32_t bound_arguments_length = bound_arguments.length();
if (arity + bound_arguments_length + /* receiver */ 1 >
Code::kMaxArguments) {
return NoChange();
}
static constexpr int kInlineSize = 16; // Arbitrary.
base::SmallVector<Node*, kInlineSize> args;
for (uint32_t i = 0; i < bound_arguments_length; ++i) {
OptionalObjectRef maybe_arg = bound_arguments.TryGet(broker(), i);
if (!maybe_arg.has_value()) {
TRACE_BROKER_MISSING(broker(), "bound argument");
return NoChangeOrSoftDeopt();
}
args.emplace_back(
jsgraph()->ConstantNoHole(maybe_arg.value(), broker()));
}
// Patch {node} to use [[BoundTargetFunction]] and [[BoundThis]].
NodeProperties::ReplaceValueInput(
node,
jsgraph()->ConstantNoHole(function.bound_target_function(broker()),
broker()),
JSCallNode::TargetIndex());
NodeProperties::ReplaceValueInput(
node, jsgraph()->ConstantNoHole(bound_this, broker()),
JSCallNode::ReceiverIndex());
// Insert the [[BoundArguments]] for {node}.
for (uint32_t i = 0; i < bound_arguments_length; ++i) {
node->InsertInput(graph()->zone(), i + 2, args[i]);
arity++;
}
NodeProperties::ChangeOp(
node,
javascript()->Call(JSCallNode::ArityForArgc(arity), p.frequency(),
p.feedback(), convert_mode, p.speculation_mode(),
CallFeedbackRelation::kUnrelated));
// Try to further reduce the JSCall {node}.
return Changed(node).FollowedBy(ReduceJSCall(node));
}
// Don't mess with other {node}s that have a constant {target}.
// TODO(bmeurer): Also support proxies here.
return NoChangeOrSoftDeopt();
}
// If {target} is the result of a JSCreateClosure operation, we can
// just immediately try to inline based on the SharedFunctionInfo,
// since TurboFan generally doesn't inline cross-context, and hence
// the {target} must have the same native context as the call site.
// Same if the {target} is the result of a CheckClosure operation.
if (target->opcode() == IrOpcode::kJSCreateClosure) {
CreateClosureParameters const& params =
JSCreateClosureNode{target}.Parameters();
Reduction res = ReduceJSCall(node, params.shared_info());
if (!res.Changed()) return NoChangeOrSoftDeopt();
return res;
} else if (target->opcode() == IrOpcode::kCheckClosure) {
FeedbackCellRef cell = MakeRef(broker(), FeedbackCellOf(target->op()));
OptionalSharedFunctionInfoRef shared = cell.shared_function_info(broker());
if (!shared.has_value()) {
TRACE_BROKER_MISSING(broker(), "Unable to reduce JSCall. FeedbackCell "
<< cell << " has no FeedbackVector");
return NoChangeOrSoftDeopt();
}
Reduction res = ReduceJSCall(node, *shared);
if (!res.Changed()) return NoChangeOrSoftDeopt();
return res;
}
// If {target} is the result of a JSCreateBoundFunction operation,
// we can just fold the construction and call the bound target
// function directly instead.
if (target->opcode() == IrOpcode::kJSCreateBoundFunction) {
Node* bound_target_function = NodeProperties::GetValueInput(target, 0);
Node* bound_this = NodeProperties::GetValueInput(target, 1);
uint32_t const bound_arguments_length =
static_cast<int>(CreateBoundFunctionParametersOf(target->op()).arity());
// Patch the {node} to use [[BoundTargetFunction]] and [[BoundThis]].
NodeProperties::ReplaceValueInput(node, bound_target_function,
n.TargetIndex());
NodeProperties::ReplaceValueInput(node, bound_this, n.ReceiverIndex());
// Insert the [[BoundArguments]] for {node}.
for (uint32_t i = 0; i < bound_arguments_length; ++i) {
Node* value = NodeProperties::GetValueInput(target, 2 + i);
node->InsertInput(graph()->zone(), n.ArgumentIndex(i), value);
arity++;
}
// Update the JSCall operator on {node}.
ConvertReceiverMode const convert_mode =
NodeProperties::CanBeNullOrUndefined(broker(), bound_this, effect)
? ConvertReceiverMode::kAny
: ConvertReceiverMode::kNotNullOrUndefined;
NodeProperties::ChangeOp(
node,
javascript()->Call(JSCallNode::ArityForArgc(arity), p.frequency(),
p.feedback(), convert_mode, p.speculation_mode(),
CallFeedbackRelation::kUnrelated));
// Try to further reduce the JSCall {node}.
return Changed(node).FollowedBy(ReduceJSCall(node));
}
if (!ShouldUseCallICFeedback(target) ||
p.feedback_relation() == CallFeedbackRelation::kUnrelated ||
!p.feedback().IsValid()) {
return NoChange();
}
ProcessedFeedback const& feedback =
broker()->GetFeedbackForCall(p.feedback());
if (feedback.IsInsufficient()) {
return ReduceForInsufficientFeedback(
node, DeoptimizeReason::kInsufficientTypeFeedbackForCall);
}
OptionalHeapObjectRef feedback_target;
if (p.feedback_relation() == CallFeedbackRelation::kTarget) {
feedback_target = feedback.AsCall().target();
} else {
DCHECK_EQ(p.feedback_relation(), CallFeedbackRelation::kReceiver);
feedback_target = native_context().function_prototype_apply(broker());
}
if (feedback_target.has_value() &&
feedback_target->map(broker()).is_callable()) {
Node* target_function =
jsgraph()->ConstantNoHole(*feedback_target, broker());
// Check that the {target} is still the {target_function}.
Node* check = graph()->NewNode(simplified()->ReferenceEqual(), target,
target_function);
effect = graph()->NewNode(
simplified()->CheckIf(DeoptimizeReason::kWrongCallTarget), check,
effect, control);
// Specialize the JSCall node to the {target_function}.
NodeProperties::ReplaceValueInput(node, target_function, n.TargetIndex());
NodeProperties::ReplaceEffectInput(node, effect);
// Try to further reduce the JSCall {node}.
return Changed(node).FollowedBy(ReduceJSCall(node));
} else if (feedback_target.has_value() && feedback_target->IsFeedbackCell()) {
FeedbackCellRef feedback_cell = feedback_target.value().AsFeedbackCell();
// TODO(neis): This check seems unnecessary.
if (feedback_cell.feedback_vector(broker()).has_value()) {
// Check that {target} is a closure with given {feedback_cell},
// which uniquely identifies a given function inside a native context.
Node* target_closure = effect =
graph()->NewNode(simplified()->CheckClosure(feedback_cell.object()),
target, effect, control);
// Specialize the JSCall node to the {target_closure}.
NodeProperties::ReplaceValueInput(node, target_closure, n.TargetIndex());
NodeProperties::ReplaceEffectInput(node, effect);
// Try to further reduce the JSCall {node}.
return Changed(node).FollowedBy(ReduceJSCall(node));
}
}
return NoChange();
}
Reduction JSCallReducer::ReduceJSCall(Node* node,
SharedFunctionInfoRef shared) {
JSCallNode n(node);
Node* target = n.target();
// Do not reduce calls to functions with break points.
// If this state changes during background compilation, the compilation
// job will be aborted from the main thread (see
// Debug::PrepareFunctionForDebugExecution()).
if (shared.HasBreakInfo(broker())) return NoChange();
// Class constructors are callable, but [[Call]] will raise an exception.
// See ES6 section 9.2.1 [[Call]] ( thisArgument, argumentsList ).
if (IsClassConstructor(shared.kind())) {
NodeProperties::ReplaceValueInputs(node, target);
NodeProperties::ChangeOp(
node, javascript()->CallRuntime(
Runtime::kThrowConstructorNonCallableError, 1));
return Changed(node);
}
// Check for known builtin functions.
Builtin builtin =
shared.HasBuiltinId() ? shared.builtin_id() : Builtin::kNoBuiltinId;
switch (builtin) {
case Builtin::kArrayConstructor:
return ReduceArrayConstructor(node);
case Builtin::kBooleanConstructor:
return ReduceBooleanConstructor(node);
case Builtin::kFunctionPrototypeApply:
return ReduceFunctionPrototypeApply(node);
case Builtin::kFastFunctionPrototypeBind:
return ReduceFunctionPrototypeBind(node);
case Builtin::kFunctionPrototypeCall:
return ReduceFunctionPrototypeCall(node);
case Builtin::kFunctionPrototypeHasInstance:
return ReduceFunctionPrototypeHasInstance(node);
case Builtin::kObjectConstructor:
return ReduceObjectConstructor(node);
case Builtin::kObjectCreate:
return ReduceObjectCreate(node);
case Builtin::kObjectGetPrototypeOf:
return ReduceObjectGetPrototypeOf(node);
case Builtin::kObjectIs:
return ReduceObjectIs(node);
case Builtin::kObjectPrototypeGetProto:
return ReduceObjectPrototypeGetProto(node);
case Builtin::kObjectPrototypeHasOwnProperty:
return ReduceObjectPrototypeHasOwnProperty(node);
case Builtin::kObjectPrototypeIsPrototypeOf:
return ReduceObjectPrototypeIsPrototypeOf(node);
case Builtin::kReflectApply:
return ReduceReflectApply(node);
case Builtin::kReflectConstruct:
return ReduceReflectConstruct(node);
case Builtin::kReflectGet:
return ReduceReflectGet(node);
case Builtin::kReflectGetPrototypeOf:
return ReduceReflectGetPrototypeOf(node);
case Builtin::kReflectHas:
return ReduceReflectHas(node);
case Builtin::kArrayForEach:
return ReduceArrayForEach(node, shared);
case Builtin::kArrayMap:
return ReduceArrayMap(node, shared);
case Builtin::kArrayFilter:
return ReduceArrayFilter(node, shared);
case Builtin::kArrayReduce:
return ReduceArrayReduce(node, shared);
case Builtin::kArrayReduceRight:
return ReduceArrayReduceRight(node, shared);
case Builtin::kArrayPrototypeFind:
return ReduceArrayFind(node, shared);
case Builtin::kArrayPrototypeFindIndex:
return ReduceArrayFindIndex(node, shared);
case Builtin::kArrayEvery:
return ReduceArrayEvery(node, shared);
case Builtin::kArrayIndexOf:
return ReduceArrayIndexOf(node);
case Builtin::kArrayIncludes:
return ReduceArrayIncludes(node);
case Builtin::kArraySome:
return ReduceArraySome(node, shared);
case Builtin::kArrayPrototypeAt:
return ReduceArrayPrototypeAt(node);
case Builtin::kArrayPrototypePush:
return ReduceArrayPrototypePush(node);
case Builtin::kArrayPrototypePop:
return ReduceArrayPrototypePop(node);
// TODO(v8:14409): The current implementation of the inlined
// ArrayPrototypeShift version doesn't seem to be beneficial and even
// counter-productive at least for Object ElementsKinds. Disable it until
// improvements/better heuristics have been implemented.
// case Builtin::kArrayPrototypeShift:
// return ReduceArrayPrototypeShift(node);
case Builtin::kArrayPrototypeSlice:
return ReduceArrayPrototypeSlice(node);
case Builtin::kArrayPrototypeEntries:
return ReduceArrayIterator(node, ArrayIteratorKind::kArrayLike,
IterationKind::kEntries);
case Builtin::kArrayPrototypeKeys:
return ReduceArrayIterator(node, ArrayIteratorKind::kArrayLike,
IterationKind::kKeys);
case Builtin::kArrayPrototypeValues:
return ReduceArrayIterator(node, ArrayIteratorKind::kArrayLike,
IterationKind::kValues);
case Builtin::kArrayIteratorPrototypeNext:
return ReduceArrayIteratorPrototypeNext(node);
case Builtin::kArrayIsArray:
return ReduceArrayIsArray(node);
case Builtin::kArrayBufferIsView:
return ReduceArrayBufferIsView(node);
case Builtin::kDataViewPrototypeGetByteLength:
// TODO(v8:11111): Optimize for JS_RAB_GSAB_DATA_VIEW_TYPE too.
return ReduceArrayBufferViewByteLengthAccessor(node, JS_DATA_VIEW_TYPE,
builtin);
case Builtin::kDataViewPrototypeGetByteOffset:
// TODO(v8:11111): Optimize for JS_RAB_GSAB_DATA_VIEW_TYPE too.
return ReduceArrayBufferViewByteOffsetAccessor(node, JS_DATA_VIEW_TYPE,
builtin);
case Builtin::kDataViewPrototypeGetUint8:
return ReduceDataViewAccess(node, DataViewAccess::kGet,
ExternalArrayType::kExternalUint8Array);
case Builtin::kDataViewPrototypeGetInt8:
return ReduceDataViewAccess(node, DataViewAccess::kGet,
ExternalArrayType::kExternalInt8Array);
case Builtin::kDataViewPrototypeGetUint16:
return ReduceDataViewAccess(node, DataViewAccess::kGet,
ExternalArrayType::kExternalUint16Array);
case Builtin::kDataViewPrototypeGetInt16:
return ReduceDataViewAccess(node, DataViewAccess::kGet,
ExternalArrayType::kExternalInt16Array);
case Builtin::kDataViewPrototypeGetUint32:
return ReduceDataViewAccess(node, DataViewAccess::kGet,
ExternalArrayType::kExternalUint32Array);
case Builtin::kDataViewPrototypeGetInt32:
return ReduceDataViewAccess(node, DataViewAccess::kGet,
ExternalArrayType::kExternalInt32Array);
case Builtin::kDataViewPrototypeGetFloat16:
return ReduceDataViewAccess(node, DataViewAccess::kGet,
ExternalArrayType::kExternalFloat16Array);
case Builtin::kDataViewPrototypeGetFloat32:
return ReduceDataViewAccess(node, DataViewAccess::kGet,
ExternalArrayType::kExternalFloat32Array);
case Builtin::kDataViewPrototypeGetFloat64:
return ReduceDataViewAccess(node, DataViewAccess::kGet,
ExternalArrayType::kExternalFloat64Array);
case Builtin::kDataViewPrototypeGetBigInt64:
return ReduceDataViewAccess(node, DataViewAccess::kGet,
ExternalArrayType::kExternalBigInt64Array);
case Builtin::kDataViewPrototypeGetBigUint64:
return ReduceDataViewAccess(node, DataViewAccess::kGet,
ExternalArrayType::kExternalBigUint64Array);
case Builtin::kDataViewPrototypeSetUint8:
return ReduceDataViewAccess(node, DataViewAccess::kSet,
ExternalArrayType::kExternalUint8Array);
case Builtin::kDataViewPrototypeSetInt8:
return ReduceDataViewAccess(node, DataViewAccess::kSet,
ExternalArrayType::kExternalInt8Array);
case Builtin::kDataViewPrototypeSetUint16:
return ReduceDataViewAccess(node, DataViewAccess::kSet,
ExternalArrayType::kExternalUint16Array);
case Builtin::kDataViewPrototypeSetInt16:
return ReduceDataViewAccess(node, DataViewAccess::kSet,
ExternalArrayType::kExternalInt16Array);
case Builtin::kDataViewPrototypeSetUint32:
return ReduceDataViewAccess(node, DataViewAccess::kSet,
ExternalArrayType::kExternalUint32Array);
case Builtin::kDataViewPrototypeSetInt32:
return ReduceDataViewAccess(node, DataViewAccess::kSet,
ExternalArrayType::kExternalInt32Array);
case Builtin::kDataViewPrototypeSetFloat16:
return ReduceDataViewAccess(node, DataViewAccess::kSet,
ExternalArrayType::kExternalFloat16Array);
case Builtin::kDataViewPrototypeSetFloat32:
return ReduceDataViewAccess(node, DataViewAccess::kSet,
ExternalArrayType::kExternalFloat32Array);
case Builtin::kDataViewPrototypeSetFloat64:
return ReduceDataViewAccess(node, DataViewAccess::kSet,
ExternalArrayType::kExternalFloat64Array);
case Builtin::kDataViewPrototypeSetBigInt64:
return ReduceDataViewAccess(node, DataViewAccess::kSet,
ExternalArrayType::kExternalBigInt64Array);
case Builtin::kDataViewPrototypeSetBigUint64:
return ReduceDataViewAccess(node, DataViewAccess::kSet,
ExternalArrayType::kExternalBigUint64Array);
case Builtin::kDatePrototypeGetFullYear:
return ReduceDatePrototypeGetField(node, JSDate::kYear);
case Builtin::kDatePrototypeGetMonth:
return ReduceDatePrototypeGetField(node, JSDate::kMonth);
case Builtin::kDatePrototypeGetDate:
return ReduceDatePrototypeGetField(node, JSDate::kDay);
case Builtin::kDatePrototypeGetDay:
return ReduceDatePrototypeGetField(node, JSDate::kWeekday);
case Builtin::kDatePrototypeGetHours:
return ReduceDatePrototypeGetField(node, JSDate::kHour);
case Builtin::kDatePrototypeGetMinutes:
return ReduceDatePrototypeGetField(node, JSDate::kMinute);
case Builtin::kDatePrototypeGetSeconds:
return ReduceDatePrototypeGetField(node, JSDate::kSecond);
case Builtin::kTypedArrayPrototypeByteLength:
return ReduceArrayBufferViewByteLengthAccessor(node, JS_TYPED_ARRAY_TYPE,
builtin);
case Builtin::kTypedArrayPrototypeByteOffset:
return ReduceArrayBufferViewByteOffsetAccessor(node, JS_TYPED_ARRAY_TYPE,
builtin);
case Builtin::kTypedArrayPrototypeLength:
return ReduceTypedArrayPrototypeLength(node);
case Builtin::kTypedArrayPrototypeToStringTag:
return ReduceTypedArrayPrototypeToStringTag(node);
case Builtin::kMathAbs:
return ReduceMathUnary(node, simplified()->NumberAbs());
case Builtin::kMathAcos:
return ReduceMathUnary(node, simplified()->NumberAcos());
case Builtin::kMathAcosh:
return ReduceMathUnary(node, simplified()->NumberAcosh());
case Builtin::kMathAsin:
return ReduceMathUnary(node, simplified()->NumberAsin());
case Builtin::kMathAsinh:
return ReduceMathUnary(node, simplified()->NumberAsinh());
case Builtin::kMathAtan:
return ReduceMathUnary(node, simplified()->NumberAtan());
case Builtin::kMathAtanh:
return ReduceMathUnary(node, simplified()->NumberAtanh());
case Builtin::kMathCbrt:
return ReduceMathUnary(node, simplified()->NumberCbrt());
case Builtin::kMathCeil:
return ReduceMathUnary(node, simplified()->NumberCeil());
case Builtin::kMathCos:
return ReduceMathUnary(node, simplified()->NumberCos());
case Builtin::kMathCosh:
return ReduceMathUnary(node, simplified()->NumberCosh());
case Builtin::kMathExp:
return ReduceMathUnary(node, simplified()->NumberExp());
case Builtin::kMathExpm1:
return ReduceMathUnary(node, simplified()->NumberExpm1());
case Builtin::kMathFloor:
return ReduceMathUnary(node, simplified()->NumberFloor());
case Builtin::kMathFround:
return ReduceMathUnary(node, simplified()->NumberFround());
case Builtin::kMathLog:
return ReduceMathUnary(node, simplified()->NumberLog());
case Builtin::kMathLog1p:
return ReduceMathUnary(node, simplified()->NumberLog1p());
case Builtin::kMathLog10:
return ReduceMathUnary(node, simplified()->NumberLog10());
case Builtin::kMathLog2:
return ReduceMathUnary(node, simplified()->NumberLog2());
case Builtin::kMathRound:
return ReduceMathUnary(node, simplified()->NumberRound());
case Builtin::kMathSign:
return ReduceMathUnary(node, simplified()->NumberSign());
case Builtin::kMathSin:
return ReduceMathUnary(node, simplified()->NumberSin());
case Builtin::kMathSinh:
return ReduceMathUnary(node, simplified()->NumberSinh());
case Builtin::kMathSqrt:
return ReduceMathUnary(node, simplified()->NumberSqrt());
case Builtin::kMathTan:
return ReduceMathUnary(node, simplified()->NumberTan());
case Builtin::kMathTanh:
return ReduceMathUnary(node, simplified()->NumberTanh());
case Builtin::kMathTrunc:
return ReduceMathUnary(node, simplified()->NumberTrunc());
case Builtin::kMathAtan2:
return ReduceMathBinary(node, simplified()->NumberAtan2());
case Builtin::kMathPow:
return ReduceMathBinary(node, simplified()->NumberPow());
case Builtin::kMathClz32:
return ReduceMathClz32(node);
case Builtin::kMathImul:
return ReduceMathImul(node);
case Builtin::kMathMax:
return ReduceMathMinMax(node, simplified()->NumberMax(),
jsgraph()->ConstantNoHole(-V8_INFINITY));
case Builtin::kMathMin:
return ReduceMathMinMax(node, simplified()->NumberMin(),
jsgraph()->ConstantNoHole(V8_INFINITY));
case Builtin::kNumberIsFinite:
return ReduceNumberIsFinite(node);
case Builtin::kNumberIsInteger:
return ReduceNumberIsInteger(node);
case Builtin::kNumberIsSafeInteger:
return ReduceNumberIsSafeInteger(node);
case Builtin::kNumberIsNaN:
return ReduceNumberIsNaN(node);
case Builtin::kNumberParseInt:
return ReduceNumberParseInt(node);
case Builtin::kGlobalIsFinite:
return ReduceGlobalIsFinite(node);
case Builtin::kGlobalIsNaN:
return ReduceGlobalIsNaN(node);
case Builtin::kMapPrototypeGet:
return ReduceMapPrototypeGet(node);
case Builtin::kMapPrototypeHas:
return ReduceMapPrototypeHas(node);
case Builtin::kSetPrototypeHas:
return ReduceSetPrototypeHas(node);
case Builtin::kRegExpPrototypeTest:
return ReduceRegExpPrototypeTest(node);
case Builtin::kReturnReceiver:
return ReduceReturnReceiver(node);
case Builtin::kStringPrototypeIndexOf:
return ReduceStringPrototypeIndexOfIncludes(
node, StringIndexOfIncludesVariant::kIndexOf);
case Builtin::kStringPrototypeIncludes:
return ReduceStringPrototypeIndexOfIncludes(
node, StringIndexOfIncludesVariant::kIncludes);
case Builtin::kStringPrototypeCharAt:
return ReduceStringPrototypeCharAt(node);
case Builtin::kStringPrototypeCharCodeAt:
return ReduceStringPrototypeStringCharCodeAt(node);
case Builtin::kStringPrototypeCodePointAt:
return ReduceStringPrototypeStringCodePointAt(node);
case Builtin::kStringPrototypeSubstring:
return ReduceStringPrototypeSubstring(node);
case Builtin::kStringPrototypeSlice:
return ReduceStringPrototypeSlice(node);
case Builtin::kStringPrototypeSubstr:
return ReduceStringPrototypeSubstr(node);
case Builtin::kStringPrototypeStartsWith:
return ReduceStringPrototypeStartsWith(node);
case Builtin::kStringPrototypeEndsWith:
return ReduceStringPrototypeEndsWith(node);
#ifdef V8_INTL_SUPPORT
case Builtin::kStringPrototypeLocaleCompareIntl:
return ReduceStringPrototypeLocaleCompareIntl(node);
case Builtin::kStringPrototypeToLowerCaseIntl:
return ReduceStringPrototypeToLowerCaseIntl(node);
case Builtin::kStringPrototypeToUpperCaseIntl:
return ReduceStringPrototypeToUpperCaseIntl(node);
#endif // V8_INTL_SUPPORT
case Builtin::kStringFromCharCode:
return ReduceStringFromCharCode(node);
case Builtin::kStringFromCodePoint:
return ReduceStringFromCodePoint(node);
case Builtin::kStringPrototypeIterator:
return ReduceStringPrototypeIterator(node);
case Builtin::kStringIteratorPrototypeNext:
return ReduceStringIteratorPrototypeNext(node);
case Builtin::kStringPrototypeConcat:
return ReduceStringPrototypeConcat(node);
case Builtin::kTypedArrayPrototypeEntries:
return ReduceArrayIterator(node, ArrayIteratorKind::kTypedArray,
IterationKind::kEntries);
case Builtin::kTypedArrayPrototypeKeys:
return ReduceArrayIterator(node, ArrayIteratorKind::kTypedArray,
IterationKind::kKeys);
case Builtin::kTypedArrayPrototypeValues:
return ReduceArrayIterator(node, ArrayIteratorKind::kTypedArray,
IterationKind::kValues);
case Builtin::kPromisePrototypeCatch:
return ReducePromisePrototypeCatch(node);
case Builtin::kPromisePrototypeFinally:
return ReducePromisePrototypeFinally(node);
case Builtin::kPromisePrototypeThen:
return ReducePromisePrototypeThen(node);
case Builtin::kPromiseResolveTrampoline:
return ReducePromiseResolveTrampoline(node);
case Builtin::kMapPrototypeEntries:
return ReduceCollectionIteration(node, CollectionKind::kMap,
IterationKind::kEntries);
case Builtin::kMapPrototypeKeys:
return ReduceCollectionIteration(node, CollectionKind::kMap,
IterationKind::kKeys);
case Builtin::kMapPrototypeGetSize:
return ReduceCollectionPrototypeSize(node, CollectionKind::kMap);
case Builtin::kMapPrototypeValues:
return ReduceCollectionIteration(node, CollectionKind::kMap,
IterationKind::kValues);
case Builtin::kMapIteratorPrototypeNext:
return ReduceCollectionIteratorPrototypeNext(
node, OrderedHashMap::kEntrySize, factory()->empty_ordered_hash_map(),
FIRST_JS_MAP_ITERATOR_TYPE, LAST_JS_MAP_ITERATOR_TYPE);
case Builtin::kSetPrototypeEntries:
return ReduceCollectionIteration(node, CollectionKind::kSet,
IterationKind::kEntries);
case Builtin::kSetPrototypeGetSize:
return ReduceCollectionPrototypeSize(node, CollectionKind::kSet);
case Builtin::kSetPrototypeValues:
return ReduceCollectionIteration(node, CollectionKind::kSet,
IterationKind::kValues);
case Builtin::kSetIteratorPrototypeNext:
return ReduceCollectionIteratorPrototypeNext(
node, OrderedHashSet::kEntrySize, factory()->empty_ordered_hash_set(),
FIRST_JS_SET_ITERATOR_TYPE, LAST_JS_SET_ITERATOR_TYPE);
case Builtin::kDatePrototypeGetTime:
return ReduceDatePrototypeGetTime(node);
case Builtin::kDateNow:
return ReduceDateNow(node);
case Builtin::kNumberConstructor:
return ReduceNumberConstructor(node);
case Builtin::kBigIntConstructor:
return ReduceBigIntConstructor(node);
case Builtin::kBigIntAsIntN:
case Builtin::kBigIntAsUintN:
return ReduceBigIntAsN(node, builtin);
#ifdef V8_ENABLE_CONTINUATION_PRESERVED_EMBEDDER_DATA
case Builtin::kGetContinuationPreservedEmbedderData:
return ReduceGetContinuationPreservedEmbedderData(node);
case Builtin::kSetContinuationPreservedEmbedderData:
return ReduceSetContinuationPreservedEmbedderData(node);
#endif // V8_ENABLE_CONTINUATION_PRESERVED_EMBEDDER_DATA
default:
break;
}
if (shared.function_template_info(broker()).has_value()) {
return ReduceCallApiFunction(node, shared);
}
#if V8_ENABLE_WEBASSEMBLY
if ((flags() & kInlineJSToWasmCalls) &&
// Peek at the trusted object; ReduceCallWasmFunction will do that again
// and crash if this is not a WasmExportedFunctionData any more then.
IsWasmExportedFunctionData(shared.object()->GetTrustedData(isolate()))) {
return ReduceCallWasmFunction(node, shared);
}
#endif // V8_ENABLE_WEBASSEMBLY
return NoChange();
}
TNode<Object> JSCallReducerAssembler::ReduceJSCallWithArrayLikeOrSpreadOfEmpty(
std::unordered_set<Node*>* generated_calls_with_array_like_or_spread) {
DCHECK_EQ(generated_calls_with_array_like_or_spread->count(node_ptr()), 0);
JSCallWithArrayLikeOrSpreadNode n(node_ptr());
CallParameters const& p = n.Parameters();
TNode<Object> arguments_list = n.LastArgument();
DCHECK_EQ(static_cast<Node*>(arguments_list)->opcode(),
IrOpcode::kJSCreateEmptyLiteralArray);
// Check that arguments_list's prototype is still an array prototype.
TNode<Map> map = LoadMap(TNode<HeapObject>::UncheckedCast(arguments_list));
TNode<HeapObject> proto = TNode<HeapObject>::UncheckedCast(
LoadField(AccessBuilder::ForMapPrototype(), map));
TNode<HeapObject> initial_array_prototype =
HeapConstant(broker()
->target_native_context()
.initial_array_prototype(broker())
.object());
TNode<Boolean> check = ReferenceEqual(proto, initial_array_prototype);
CheckIf(check, DeoptimizeReason::kWrongMap, p.feedback());
// Turn the JSCallWithArrayLike or JSCallWithSpread roughly into:
//
// "arguments_list array is still empty?"
// |
// |
// Branch
// / \
// / \
// IfTrue IfFalse
// | |
// | |
// JSCall JSCallWithArrayLike/JSCallWithSpread
// \ /
// \ /
// Merge
TNode<Number> length = TNode<Number>::UncheckedCast(
LoadField(AccessBuilder::ForJSArrayLength(NO_ELEMENTS), arguments_list));
return SelectIf<Object>(NumberEqual(length, ZeroConstant()))
.Then([&]() {
TNode<Object> call = CopyNode();
static_cast<Node*>(call)->RemoveInput(n.LastArgumentIndex());
NodeProperties::ChangeOp(
call, javascript()->Call(p.arity() - 1, p.frequency(), p.feedback(),
p.convert_mode(), p.speculation_mode(),
p.feedback_relation()));
return call;
})
.Else([&]() {
TNode<Object> call = CopyNode();
generated_calls_with_array_like_or_spread->insert(call);
return call;
})
.ExpectFalse()
.Value();
}
namespace {
// Check if the target is a class constructor.
// We need to check all cases where the target will be typed as Function
// to prevent later optimizations from using the CallFunction trampoline,
// skipping the instance type check.
bool TargetIsClassConstructor(Node* node, JSHeapBroker* broker) {
Node* target = NodeProperties::GetValueInput(node, 0);
OptionalSharedFunctionInfoRef shared;
HeapObjectMatcher m(target);
if (m.HasResolvedValue()) {
ObjectRef target_ref = m.Ref(broker);
if (target_ref.IsJSFunction()) {
JSFunctionRef function = target_ref.AsJSFunction();
shared = function.shared(broker);
}
} else if (target->opcode() == IrOpcode::kJSCreateClosure) {
CreateClosureParameters const& ccp =
JSCreateClosureNode{target}.Parameters();
shared = ccp.shared_info();
} else if (target->opcode() == IrOpcode::kCheckClosure) {
FeedbackCellRef cell = MakeRef(broker, FeedbackCellOf(target->op()));
shared = cell.shared_function_info(broker);
}
if (shared.has_value() && IsClassConstructor(shared->kind())) return true;
return false;
}
} // namespace
Reduction JSCallReducer::ReduceJSCallWithArrayLike(Node* node) {
JSCallWithArrayLikeNode n(node);
CallParameters const& p = n.Parameters();
DCHECK_EQ(p.arity_without_implicit_args(), 1); // The arraylike object.
// Class constructors are callable, but [[Call]] will raise an exception.
// See ES6 section 9.2.1 [[Call]] ( thisArgument, argumentsList ).
if (TargetIsClassConstructor(node, broker())) {
return NoChange();
}
std::optional<Reduction> maybe_result =
TryReduceJSCallMathMinMaxWithArrayLike(node);
if (maybe_result.has_value()) {
return maybe_result.value();
}
return ReduceCallOrConstructWithArrayLikeOrSpread(
node, n.ArgumentCount(), n.LastArgumentIndex(), p.frequency(),
p.feedback(), p.speculation_mode(), p.feedback_relation(), n.target(),
n.effect(), n.control());
}
Reduction JSCallReducer::ReduceJSCallWithSpread(Node* node) {
JSCallWithSpreadNode n(node);
CallParameters const& p = n.Parameters();
DCHECK_GE(p.arity_without_implicit_args(), 1); // At least the spread.
// Class constructors are callable, but [[Call]] will raise an exception.
// See ES6 section 9.2.1 [[Call]] ( thisArgument, argumentsList ).
if (TargetIsClassConstructor(node, broker())) {
return NoChange();
}
return ReduceCallOrConstructWithArrayLikeOrSpread(
node, n.ArgumentCount(), n.LastArgumentIndex(), p.frequency(),
p.feedback(), p.speculation_mode(), p.feedback_relation(), n.target(),
n.effect(), n.control());
}
Reduction JSCallReducer::ReduceJSConstruct(Node* node) {
if (broker()->StackHasOverflowed()) return NoChange();
JSConstructNode n(node);
ConstructParameters const& p = n.Parameters();
int arity = p.arity_without_implicit_args();
Node* target = n.target();
Node* new_target = n.new_target();
Effect effect = n.effect();
Control control = n.control();
if (p.feedback().IsValid()) {
ProcessedFeedback const& feedback =
broker()->GetFeedbackForCall(p.feedback());
if (feedback.IsInsufficient()) {
return ReduceForInsufficientFeedback(
node, DeoptimizeReason::kInsufficientTypeFeedbackForConstruct);
}
OptionalHeapObjectRef feedback_target = feedback.AsCall().target();
if (feedback_target.has_value() && feedback_target->IsAllocationSite()) {
// The feedback is an AllocationSite, which means we have called the
// Array function and collected transition (and pretenuring) feedback
// for the resulting arrays. This has to be kept in sync with the
// implementation in Ignition.
Node* array_function = jsgraph()->ConstantNoHole(
native_context().array_function(broker()), broker());
// Check that the {target} is still the {array_function}.
Node* check = graph()->NewNode(simplified()->ReferenceEqual(), target,
array_function);
effect = graph()->NewNode(
simplified()->CheckIf(DeoptimizeReason::kWrongCallTarget), check,
effect, control);
// Turn the {node} into a {JSCreateArray} call.
NodeProperties::ReplaceEffectInput(node, effect);
static_assert(JSConstructNode::NewTargetIndex() == 1);
node->ReplaceInput(n.NewTargetIndex(), array_function);
node->RemoveInput(n.FeedbackVectorIndex());
NodeProperties::ChangeOp(
node,
javascript()->CreateArray(arity, feedback_target->AsAllocationSite(),
FeedbackSource()));
return Changed(node);
} else if (feedback_target.has_value() &&
!HeapObjectMatcher(new_target).HasResolvedValue() &&
feedback_target->map(broker()).is_constructor()) {
Node* new_target_feedback =
jsgraph()->ConstantNoHole(*feedback_target, broker());
// Check that the {new_target} is still the {new_target_feedback}.
Node* check = graph()->NewNode(simplified()->ReferenceEqual(), new_target,
new_target_feedback);
effect = graph()->NewNode(
simplified()->CheckIf(DeoptimizeReason::kWrongCallTarget), check,
effect, control);
// Specialize the JSConstruct node to the {new_target_feedback}.
node->ReplaceInput(n.NewTargetIndex(), new_target_feedback);
NodeProperties::ReplaceEffectInput(node, effect);
if (target == new_target) {
node->ReplaceInput(n.TargetIndex(), new_target_feedback);
}
// Try to further reduce the JSConstruct {node}.
return Changed(node).FollowedBy(ReduceJSConstruct(node));
}
}
// Try to specialize JSConstruct {node}s with constant {target}s.
HeapObjectMatcher m(target);
if (m.HasResolvedValue()) {
HeapObjectRef target_ref = m.Ref(broker());
// Raise a TypeError if the {target} is not a constructor.
if (!target_ref.map(broker()).is_constructor()) {
NodeProperties::ReplaceValueInputs(node, target);
NodeProperties::ChangeOp(node,
javascript()->CallRuntime(
Runtime::kThrowConstructedNonConstructable));
return Changed(node);
}
if (target_ref.IsJSFunction()) {
JSFunctionRef function = target_ref.AsJSFunction();
// Do not reduce constructors with break points.
// If this state changes during background compilation, the compilation
// job will be aborted from the main thread (see
// Debug::PrepareFunctionForDebugExecution()).
SharedFunctionInfoRef sfi = function.shared(broker());
if (sfi.HasBreakInfo(broker())) return NoChange();
// Don't inline cross native context.
if (!function.native_context(broker()).equals(native_context())) {
return NoChange();
}
// Check for known builtin functions.
Builtin builtin =
sfi.HasBuiltinId() ? sfi.builtin_id() : Builtin::kNoBuiltinId;
switch (builtin) {
case Builtin::kArrayConstructor: {
// TODO(bmeurer): Deal with Array subclasses here.
// Turn the {node} into a {JSCreateArray} call.
static_assert(JSConstructNode::NewTargetIndex() == 1);
node->ReplaceInput(n.NewTargetIndex(), new_target);
node->RemoveInput(n.FeedbackVectorIndex());
NodeProperties::ChangeOp(
node,
javascript()->CreateArray(arity, std::nullopt, p.feedback()));
return Changed(node);
}
case Builtin::kObjectConstructor: {
// If no value is passed, we can immediately lower to a simple
// JSCreate and don't need to do any massaging of the {node}.
if (arity == 0) {
node->RemoveInput(n.FeedbackVectorIndex());
NodeProperties::ChangeOp(node, javascript()->Create());
return Changed(node);
}
// If {target} is not the same as {new_target} (i.e. the Object
// constructor), {value} will be ignored and therefore we can lower
// to {JSCreate}. See https://tc39.es/ecma262/#sec-object-value.
HeapObjectMatcher mnew_target(new_target);
if (mnew_target.HasResolvedValue() &&
!mnew_target.Ref(broker()).equals(function)) {
// Drop the value inputs.
node->RemoveInput(n.FeedbackVectorIndex());
for (int i = n.ArgumentCount() - 1; i >= 0; i--) {
node->RemoveInput(n.ArgumentIndex(i));
}
NodeProperties::ChangeOp(node, javascript()->Create());
return Changed(node);
}
break;
}
case Builtin::kPromiseConstructor:
return ReducePromiseConstructor(node);
case Builtin::kStringConstructor:
return ReduceStringConstructor(node, function);
case Builtin::kTypedArrayConstructor:
return ReduceTypedArrayConstructor(node, function.shared(broker()));
default:
break;
}
} else if (target_ref.IsJSBoundFunction()) {
JSBoundFunctionRef function = target_ref.AsJSBoundFunction();
JSReceiverRef bound_target_function =
function.bound_target_function(broker());
FixedArrayRef bound_arguments = function.bound_arguments(broker());
const uint32_t bound_arguments_length = bound_arguments.length();
// TODO(jgruber): Inline this block below once TryGet is guaranteed to
// succeed.
static constexpr int kInlineSize = 16; // Arbitrary.
base::SmallVector<Node*, kInlineSize> args;
for (uint32_t i = 0; i < bound_arguments_length; ++i) {
OptionalObjectRef maybe_arg = bound_arguments.TryGet(broker(), i);
if (!maybe_arg.has_value()) {
TRACE_BROKER_MISSING(broker(), "bound argument");
return NoChange();
}
args.emplace_back(
jsgraph()->ConstantNoHole(maybe_arg.value(), broker()));
}
// Patch {node} to use [[BoundTargetFunction]].
node->ReplaceInput(n.TargetIndex(), jsgraph()->ConstantNoHole(
bound_target_function, broker()));
// Patch {node} to use [[BoundTargetFunction]]
// as new.target if {new_target} equals {target}.
if (target == new_target) {
node->ReplaceInput(
n.NewTargetIndex(),
jsgraph()->ConstantNoHole(bound_target_function, broker()));
} else {
node->ReplaceInput(
n.NewTargetIndex(),
graph()->NewNode(
common()->Select(MachineRepresentation::kTagged),
graph()->NewNode(simplified()->ReferenceEqual(), target,
new_target),
jsgraph()->ConstantNoHole(bound_target_function, broker()),
new_target));
}
// Insert the [[BoundArguments]] for {node}.
for (uint32_t i = 0; i < bound_arguments_length; ++i) {
node->InsertInput(graph()->zone(), n.ArgumentIndex(i), args[i]);
arity++;
}
// Update the JSConstruct operator on {node}.
NodeProperties::ChangeOp(
node, javascript()->Construct(JSConstructNode::ArityForArgc(arity),
p.frequency(), FeedbackSource()));
// Try to further reduce the JSConstruct {node}.
return Changed(node).FollowedBy(ReduceJSConstruct(node));
}
// TODO(bmeurer): Also support optimizing proxies here.
}
// If {target} is the result of a JSCreateBoundFunction operation,
// we can just fold the construction and construct the bound target
// function directly instead.
if (target->opcode() == IrOpcode::kJSCreateBoundFunction) {
Node* bound_target_function = NodeProperties::GetValueInput(target, 0);
uint32_t const bound_arguments_length =
static_cast<int>(CreateBoundFunctionParametersOf(target->op()).arity());
// Patch the {node} to use [[BoundTargetFunction]].
node->ReplaceInput(n.TargetIndex(), bound_target_function);
// Patch {node} to use [[BoundTargetFunction]]
// as new.target if {new_target} equals {target}.
if (target == new_target) {
node->ReplaceInput(n.NewTargetIndex(), bound_target_function);
} else {
node->ReplaceInput(
n.NewTargetIndex(),
graph()->NewNode(common()->Select(MachineRepresentation::kTagged),
graph()->NewNode(simplified()->ReferenceEqual(),
target, new_target),
bound_target_function, new_target));
}
// Insert the [[BoundArguments]] for {node}.
for (uint32_t i = 0; i < bound_arguments_length; ++i) {
Node* value = NodeProperties::GetValueInput(target, 2 + i);
node->InsertInput(graph()->zone(), n.ArgumentIndex(i), value);
arity++;
}
// Update the JSConstruct operator on {node}.
NodeProperties::ChangeOp(
node, javascript()->Construct(JSConstructNode::ArityForArgc(arity),
p.frequency(), FeedbackSource()));
// Try to further reduce the JSConstruct {node}.
return Changed(node).FollowedBy(ReduceJSConstruct(node));
}
return NoChange();
}
// ES #sec-string.prototype.indexof
// ES #sec-string.prototype.includes
Reduction JSCallReducer::ReduceStringPrototypeIndexOfIncludes(
Node* node, StringIndexOfIncludesVariant variant) {
JSCallNode n(node);
CallParameters const& p = n.Parameters();
if (p.speculation_mode() != SpeculationMode::kAllowSpeculation) {
return NoChange();
}
Effect effect = n.effect();
Control control = n.control();
if (n.ArgumentCount() > 0) {
Node* receiver = n.receiver();
Node* new_receiver = effect = graph()->NewNode(
simplified()->CheckString(p.feedback()), receiver, effect, control);
Node* search_string = n.Argument(0);
Node* new_search_string = effect =
graph()->NewNode(simplified()->CheckString(p.feedback()), search_string,
effect, control);
Node* new_position = jsgraph()->ZeroConstant();
if (n.ArgumentCount() > 1) {
Node* position = n.Argument(1);
new_position = effect = graph()->NewNode(
simplified()->CheckSmi(p.feedback()), position, effect, control);
Node* receiver_length =
graph()->NewNode(simplified()->StringLength(), new_receiver);
new_position = graph()->NewNode(
simplified()->NumberMin(),
graph()->NewNode(simplified()->NumberMax(), new_position,
jsgraph()->ZeroConstant()),
receiver_length);
}
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());
if (variant == StringIndexOfIncludesVariant::kIndexOf) {
return Changed(node);
} else {
DCHECK(variant == StringIndexOfIncludesVariant::kIncludes);
Node* result =
graph()->NewNode(simplified()->BooleanNot(),
graph()->NewNode(simplified()->NumberEqual(), node,
jsgraph()->SmiConstant(-1)));
return Replace(result);
}
}
return NoChange();
}
// ES #sec-string.prototype.substring
Reduction JSCallReducer::ReduceStringPrototypeSubstring(Node* node) {
JSCallNode n(node);
CallParameters const& p = n.Parameters();
if (n.ArgumentCount() < 1) return NoChange();
if (p.speculation_mode() != SpeculationMode::kAllowSpeculation) {
return NoChange();
}
JSCallReducerAssembler a(this, node);
Node* subgraph = a.ReduceStringPrototypeSubstring();
return ReplaceWithSubgraph(&a, subgraph);
}
// ES #sec-string.prototype.slice
Reduction JSCallReducer::ReduceStringPrototypeSlice(Node* node) {
JSCallNode n(node);
CallParameters const& p = n.Parameters();
if (n.ArgumentCount() < 1) return NoChange();
if (p.speculation_mode() != SpeculationMode::kAllowSpeculation) {
return NoChange();
}
JSCallReducerAssembler a(this, node);
Node* subgraph = a.ReduceStringPrototypeSlice();
return ReplaceWithSubgraph(&a, subgraph);
}
// ES #sec-string.prototype.substr
Reduction JSCallReducer::ReduceStringPrototypeSubstr(Node* node) {
JSCallNode n(node);
CallParameters const& p = n.Parameters();
if (n.ArgumentCount() < 1) return NoChange();
if (p.speculation_mode() != SpeculationMode::kAllowSpeculation) {
return NoChange();
}
Effect effect = n.effect();
Control control = n.control();
Node* receiver = n.receiver();
Node* start = n.Argument(0);
Node* end = n.ArgumentOrUndefined(1, jsgraph());
receiver = effect = graph()->NewNode(simplified()->CheckString(p.feedback()),
receiver, effect, control);
start = effect = graph()->NewNode(simplified()->CheckSmi(p.feedback()), start,
effect, control);
Node* length = graph()->NewNode(simplified()->StringLength(), receiver);
// Replace {end} argument with {length} if it is undefined.
{
Node* check = graph()->NewNode(simplified()->ReferenceEqual(), end,
jsgraph()->UndefinedConstant());
Node* branch =
graph()->NewNode(common()->Branch(BranchHint::kFalse), check, control);
Node* if_true = graph()->NewNode(common()->IfTrue(), branch);
Node* etrue = effect;
Node* vtrue = length;
Node* if_false = graph()->NewNode(common()->IfFalse(), branch);
Node* efalse = effect;
Node* vfalse = efalse = graph()->NewNode(
simplified()->CheckSmi(p.feedback()), end, efalse, if_false);
control = graph()->NewNode(common()->Merge(2), if_true, if_false);
effect = graph()->NewNode(common()->EffectPhi(2), etrue, efalse, control);
end = graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2),
vtrue, vfalse, control);
}
Node* initStart = graph()->NewNode(
common()->Select(MachineRepresentation::kTagged, BranchHint::kFalse),
graph()->NewNode(simplified()->NumberLessThan(), start,
jsgraph()->ZeroConstant()),
graph()->NewNode(
simplified()->NumberMax(),
graph()->NewNode(simplified()->NumberAdd(), length, start),
jsgraph()->ZeroConstant()),
start);
// The select above guarantees that initStart is non-negative, but
// our typer can't figure that out yet.
initStart = effect = graph()->NewNode(
common()->TypeGuard(Type::UnsignedSmall()), initStart, effect, control);
Node* resultLength = graph()->NewNode(
simplified()->NumberMin(),
graph()->NewNode(simplified()->NumberMax(), end,
jsgraph()->ZeroConstant()),
graph()->NewNode(simplified()->NumberSubtract(), length, initStart));
// The the select below uses {resultLength} only if {resultLength > 0},
// but our typer can't figure that out yet.
Node* to = effect = graph()->NewNode(
common()->TypeGuard(Type::UnsignedSmall()),
graph()->NewNode(simplified()->NumberAdd(), initStart, resultLength),
effect, control);
Node* result_string = nullptr;
// Return empty string if {from} is smaller than {to}.
{
Node* check = graph()->NewNode(simplified()->NumberLessThan(),
jsgraph()->ZeroConstant(), resultLength);
Node* branch =
graph()->NewNode(common()->Branch(BranchHint::kTrue), check, control);
Node* if_true = graph()->NewNode(common()->IfTrue(), branch);
Node* etrue = effect;
Node* vtrue = etrue =
graph()->NewNode(simplified()->StringSubstring(), receiver, initStart,
to, etrue, if_true);
Node* if_false = graph()->NewNode(common()->IfFalse(), branch);
Node* efalse = effect;
Node* vfalse = jsgraph()->EmptyStringConstant();
control = graph()->NewNode(common()->Merge(2), if_true, if_false);
effect = graph()->NewNode(common()->EffectPhi(2), etrue, efalse, control);
result_string =
graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2),
vtrue, vfalse, control);
}
ReplaceWithValue(node, result_string, effect, control);
return Replace(result_string);
}
Reduction JSCallReducer::ReduceJSConstructWithArrayLike(Node* node) {
JSConstructWithArrayLikeNode n(node);
ConstructParameters const& p = n.Parameters();
const int arraylike_index = n.LastArgumentIndex();
DCHECK_EQ(n.ArgumentCount(), 1); // The arraylike object.
return ReduceCallOrConstructWithArrayLikeOrSpread(
node, n.ArgumentCount(), arraylike_index, p.frequency(), p.feedback(),
SpeculationMode::kDisallowSpeculation, CallFeedbackRelation::kTarget,
n.target(), n.effect(), n.control());
}
Reduction JSCallReducer::ReduceJSConstructWithSpread(Node* node) {
JSConstructWithSpreadNode n(node);
ConstructParameters const& p = n.Parameters();
const int spread_index = n.LastArgumentIndex();
DCHECK_GE(n.ArgumentCount(), 1); // At least the spread.
return ReduceCallOrConstructWithArrayLikeOrSpread(
node, n.ArgumentCount(), spread_index, p.frequency(), p.feedback(),
SpeculationMode::kDisallowSpeculation, CallFeedbackRelation::kTarget,
n.target(), n.effect(), n.control());
}
Reduction JSCallReducer::ReduceJSConstructForwardAllArgs(Node* node) {
JSConstructForwardAllArgsNode n(node);
DCHECK_EQ(n.ArgumentCount(), 0);
// If this frame is not being inlined, JSConstructForwardAllArgs will be
// lowered later in JSGenericLowering to a builtin call.
FrameState frame_state = n.frame_state();
if (frame_state.outer_frame_state()->opcode() != IrOpcode::kFrameState) {
return NoChange();
}
// Hook up the arguments directly when forwarding arguments of inlined frames.
FrameState outer_state{frame_state.outer_frame_state()};
FrameStateInfo outer_info = outer_state.frame_state_info();
if (outer_info.type() == FrameStateType::kInlinedExtraArguments) {
frame_state = outer_state;
}
int argc = 0;
StateValuesAccess parameters_access(frame_state.parameters());
for (auto it = parameters_access.begin_without_receiver(); !it.done(); ++it) {
DCHECK_NOT_NULL(it.node());
node->InsertInput(graph()->zone(),
JSCallOrConstructNode::ArgumentIndex(argc++), it.node());
}
ConstructParameters const& p = n.Parameters();
NodeProperties::ChangeOp(
node, javascript()->Construct(JSConstructNode::ArityForArgc(argc),
p.frequency(), p.feedback()));
CheckIfConstructor(node);
return Changed(node).FollowedBy(ReduceJSConstruct(node));
}
Reduction JSCallReducer::ReduceReturnReceiver(Node* node) {
JSCallNode n(node);
Node* receiver = n.receiver();
ReplaceWithValue(node, receiver);
return Replace(receiver);
}
Reduction JSCallReducer::ReduceForInsufficientFeedback(
Node* node, DeoptimizeReason reason) {
DCHECK(node->opcode() == IrOpcode::kJSCall ||
node->opcode() == IrOpcode::kJSCallWithSpread ||
node->opcode() == IrOpcode::kJSCallWithArrayLike ||
node->opcode() == IrOpcode::kJSConstruct ||
node->opcode() == IrOpcode::kJSConstructWithSpread ||
node->opcode() == IrOpcode::kJSConstructWithArrayLike);
if (!(flags() & kBailoutOnUninitialized)) return NoChange();
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node);
Node* frame_state =
NodeProperties::FindFrameStateBefore(node, jsgraph()->Dead());
Node* deoptimize =
graph()->NewNode(common()->Deoptimize(reason, FeedbackSource()),
frame_state, effect, control);
MergeControlToEnd(graph(), common(), deoptimize);
node->TrimInputCount(0);
NodeProperties::ChangeOp(node, common()->Dead());
return Changed(node);
}
Node* JSCallReducer::LoadReceiverElementsKind(Node* receiver, Effect* effect,
Control control) {
Node* effect_node = *effect;
Node* receiver_map = effect_node =
graph()->NewNode(simplified()->LoadField(AccessBuilder::ForMap()),
receiver, effect_node, control);
Node* receiver_bit_field2 = effect_node = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForMapBitField2()), receiver_map,
effect_node, control);
Node* receiver_elements_kind = graph()->NewNode(
simplified()->NumberShiftRightLogical(),
graph()->NewNode(
simplified()->NumberBitwiseAnd(), receiver_bit_field2,
jsgraph()->ConstantNoHole(Map::Bits2::ElementsKindBits::kMask)),
jsgraph()->ConstantNoHole(Map::Bits2::ElementsKindBits::kShift));
*effect = effect_node;
return receiver_elements_kind;
}
void JSCallReducer::CheckIfElementsKind(Node* receiver_elements_kind,
ElementsKind kind, Node* control,
Node** if_true, Node** if_false) {
Node* is_packed_kind =
graph()->NewNode(simplified()->NumberEqual(), receiver_elements_kind,
jsgraph()->ConstantNoHole(GetPackedElementsKind(kind)));
Node* packed_branch =
graph()->NewNode(common()->Branch(), is_packed_kind, control);
Node* if_packed = graph()->NewNode(common()->IfTrue(), packed_branch);
if (IsHoleyElementsKind(kind)) {
Node* if_not_packed = graph()->NewNode(common()->IfFalse(), packed_branch);
Node* is_holey_kind =
graph()->NewNode(simplified()->NumberEqual(), receiver_elements_kind,
jsgraph()->ConstantNoHole(GetHoleyElementsKind(kind)));
Node* holey_branch =
graph()->NewNode(common()->Branch(), is_holey_kind, if_not_packed);
Node* if_holey = graph()->NewNode(common()->IfTrue(), holey_branch);
Node* if_not_packed_not_holey =
graph()->NewNode(common()->IfFalse(), holey_branch);
*if_true = graph()->NewNode(common()->Merge(2), if_packed, if_holey);
*if_false = if_not_packed_not_holey;
} else {
*if_true = if_packed;
*if_false = graph()->NewNode(common()->IfFalse(), packed_branch);
}
}
// ES6 section 23.1.3.1 Array.prototype.at ( )
Reduction JSCallReducer::ReduceArrayPrototypeAt(Node* node) {
if (!v8_flags.turbo_inline_array_builtins) return NoChange();
JSCallNode n(node);
CallParameters const& p = n.Parameters();
if (p.speculation_mode() != SpeculationMode::kAllowSpeculation) {
return NoChange();
}
Node* receiver = n.receiver();
Effect effect = n.effect();
Control control = n.control();
MapInference inference(broker(), receiver, effect);
if (!inference.HaveMaps()) return NoChange();
// Collecting maps, and checking if a fallback builtin call will be required
// (it is required if at least one map doesn't support fast array iteration).
ZoneVector<MapRef> maps(broker()->zone());
bool needs_fallback_builtin_call = false;
for (MapRef map : inference.GetMaps()) {
if (map.supports_fast_array_iteration(broker())) {
maps.push_back(map);
} else {
needs_fallback_builtin_call = true;
}
}
inference.RelyOnMapsPreferStability(dependencies(), jsgraph(), &effect,
control, p.feedback());
if (maps.empty()) {
// No map in the feedback supports fast iteration. Keeping the builtin call.
return NoChange();
}
if (!dependencies()->DependOnNoElementsProtector()) {
return NoChange();
}
IteratingArrayBuiltinReducerAssembler a(this, node);
a.InitializeEffectControl(effect, control);
TNode<Object> subgraph =
a.ReduceArrayPrototypeAt(maps, needs_fallback_builtin_call);
return ReplaceWithSubgraph(&a, subgraph);
}
// ES6 section 22.1.3.18 Array.prototype.push ( )
Reduction JSCallReducer::ReduceArrayPrototypePush(Node* node) {
JSCallNode n(node);
CallParameters const& p = n.Parameters();
if (p.speculation_mode() != SpeculationMode::kAllowSpeculation) {
return NoChange();
}
Node* receiver = n.receiver();
Effect effect = n.effect();
Control control = n.control();
MapInference inference(broker(), receiver, effect);
if (!inference.HaveMaps()) return NoChange();
ZoneRefSet<Map> const& receiver_maps = inference.GetMaps();
std::vector<ElementsKind> kinds;
if (!CanInlineArrayResizingBuiltin(broker(), receiver_maps, &kinds, true)) {
return inference.NoChange();
}
if (!dependencies()->DependOnNoElementsProtector()) {
return inference.NoChange();
}
inference.RelyOnMapsPreferStability(dependencies(), jsgraph(), &effect,
control, p.feedback());
IteratingArrayBuiltinReducerAssembler a(this, node);
a.InitializeEffectControl(effect, control);
TNode<Object> subgraph = a.ReduceArrayPrototypePush(&inference);
return ReplaceWithSubgraph(&a, subgraph);
}
// ES6 section 22.1.3.17 Array.prototype.pop ( )
Reduction JSCallReducer::ReduceArrayPrototypePop(Node* node) {
JSCallNode n(node);
CallParameters const& p = n.Parameters();
if (p.speculation_mode() != SpeculationMode::kAllowSpeculation) {
return NoChange();
}
Effect effect = n.effect();
Control control = n.control();
Node* receiver = n.receiver();
MapInference inference(broker(), receiver, effect);
if (!inference.HaveMaps()) return NoChange();
ZoneRefSet<Map> const& receiver_maps = inference.GetMaps();
std::vector<ElementsKind> kinds;
if (!CanInlineArrayResizingBuiltin(broker(), receiver_maps, &kinds)) {
return inference.NoChange();
}
if (!dependencies()->DependOnNoElementsProtector()) {
return inference.NoChange();
}
inference.RelyOnMapsPreferStability(dependencies(), jsgraph(), &effect,
control, p.feedback());
std::vector<Node*> controls_to_merge;
std::vector<Node*> effects_to_merge;
std::vector<Node*> values_to_merge;
Node* value = jsgraph()->UndefinedConstant();
Node* receiver_elements_kind =
LoadReceiverElementsKind(receiver, &effect, control);
Node* next_control = control;
Node* next_effect = effect;
for (size_t i = 0; i < kinds.size(); i++) {
ElementsKind kind = kinds[i];
control = next_control;
effect = next_effect;
// We do not need branch for the last elements kind.
if (i != kinds.size() - 1) {
Node* control_node = control;
CheckIfElementsKind(receiver_elements_kind, kind, control_node,
&control_node, &next_control);
control = control_node;
}
// 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(turbofan): 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}.
Node* new_length = graph()->NewNode(simplified()->NumberSubtract(),
length, jsgraph()->OneConstant());
if (v8_flags.turbo_typer_hardening) {
new_length = efalse = graph()->NewNode(
simplified()->CheckBounds(p.feedback(),
CheckBoundsFlag::kAbortOnOutOfBounds),
new_length, length, efalse, if_false);
}
// Store the new {length} to the {receiver}.
efalse = graph()->NewNode(
simplified()->StoreField(AccessBuilder::ForJSArrayLength(kind)),
receiver, new_length, efalse, if_false);
// Load the last entry from the {elements}.
vfalse = efalse = graph()->NewNode(
simplified()->LoadElement(AccessBuilder::ForFixedArrayElement(kind)),
elements, new_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, new_length, jsgraph()->TheHoleConstant(), efalse, if_false);
}
control = graph()->NewNode(common()->Merge(2), if_true, if_false);
effect = graph()->NewNode(common()->EffectPhi(2), etrue, efalse, control);
value = graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2),
vtrue, vfalse, control);
// Convert the hole to undefined. Do this last, so that we can optimize
// conversion operator via some smart strength reduction in many cases.
if (IsHoleyElementsKind(kind)) {
value =
graph()->NewNode(simplified()->ConvertTaggedHoleToUndefined(), value);
}
controls_to_merge.push_back(control);
effects_to_merge.push_back(effect);
values_to_merge.push_back(value);
}
if (controls_to_merge.size() > 1) {
int const count = static_cast<int>(controls_to_merge.size());
control = graph()->NewNode(common()->Merge(count), count,
&controls_to_merge.front());
effects_to_merge.push_back(control);
effect = graph()->NewNode(common()->EffectPhi(count), count + 1,
&effects_to_merge.front());
values_to_merge.push_back(control);
value =
graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, count),
count + 1, &values_to_merge.front());
}
ReplaceWithValue(node, value, effect, control);
return Replace(value);
}
// ES6 section 22.1.3.22 Array.prototype.shift ( )
// Currently disabled. See https://crbug.com/v8/14409
Reduction JSCallReducer::ReduceArrayPrototypeShift(Node* node) {
JSCallNode n(node);
CallParameters const& p = n.Parameters();
if (p.speculation_mode() != SpeculationMode::kAllowSpeculation) {
return NoChange();
}
Node* target = n.target();
Node* receiver = n.receiver();
Node* context = n.context();
FrameState frame_state = n.frame_state();
Effect effect = n.effect();
Control control = n.control();
MapInference inference(broker(), receiver, effect);
if (!inference.HaveMaps()) return NoChange();
ZoneRefSet<Map> const& receiver_maps = inference.GetMaps();
std::vector<ElementsKind> kinds;
if (!CanInlineArrayResizingBuiltin(broker(), receiver_maps, &kinds)) {
return inference.NoChange();
}
if (!dependencies()->DependOnNoElementsProtector()) {
return inference.NoChange();
}
inference.RelyOnMapsPreferStability(dependencies(), jsgraph(), &effect,
control, p.feedback());
std::vector<Node*> controls_to_merge;
std::vector<Node*> effects_to_merge;
std::vector<Node*> values_to_merge;
Node* value = jsgraph()->UndefinedConstant();
Node* receiver_elements_kind =
LoadReceiverElementsKind(receiver, &effect, control);
Node* next_control = control;
Node* next_effect = effect;
for (size_t i = 0; i < kinds.size(); i++) {
ElementsKind kind = kinds[i];
control = next_control;
effect = next_effect;
// We do not need branch for the last elements kind.
if (i != kinds.size() - 1) {
Node* control_node = control;
CheckIfElementsKind(receiver_elements_kind, kind, control_node,
&control_node, &next_control);
control = control_node;
}
// 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()->ConstantNoHole(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);
MergeControlToEnd(graph(), common(), terminate);
Node* index = graph()->NewNode(
common()->Phi(MachineRepresentation::kTagged, 2),
jsgraph()->OneConstant(),
jsgraph()->ConstantNoHole(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* control2 = graph()->NewNode(common()->IfTrue(), branch2);
Node* effect2 = etrue1;
ElementAccess const access =
AccessBuilder::ForFixedArrayElement(kind);
// When disable v8_flags.turbo_loop_variable, typer cannot infer index
// is in [1, kMaxCopyElements-1], and will break in representing
// kRepFloat64 (Range(1, inf)) to kRepWord64 when converting
// input for kLoadElement. So we need to add type guard here.
// And we need to use index when using NumberLessThan to check
// terminate and updating index, otherwise which will break inducing
// variables in LoopVariableOptimizer.
static_assert(JSArray::kMaxCopyElements < kSmiMaxValue);
Node* index_retyped = effect2 =
graph()->NewNode(common()->TypeGuard(Type::UnsignedSmall()),
index, effect2, control2);
Node* value2 = effect2 =
graph()->NewNode(simplified()->LoadElement(access), elements,
index_retyped, effect2, control2);
effect2 = graph()->NewNode(
simplified()->StoreElement(access), elements,
graph()->NewNode(simplified()->NumberSubtract(), index_retyped,
jsgraph()->OneConstant()),
value2, effect2, control2);
loop->ReplaceInput(1, control2);
eloop->ReplaceInput(1, effect2);
index->ReplaceInput(1,
graph()->NewNode(simplified()->NumberAdd(), index,
jsgraph()->OneConstant()));
}
// Compute the new {length}.
Node* new_length = graph()->NewNode(simplified()->NumberSubtract(),
length, jsgraph()->OneConstant());
if (v8_flags.turbo_typer_hardening) {
new_length = etrue1 = graph()->NewNode(
simplified()->CheckBounds(p.feedback(),
CheckBoundsFlag::kAbortOnOutOfBounds),
new_length, length, etrue1, if_true1);
}
// Store the new {length} to the {receiver}.
etrue1 = graph()->NewNode(
simplified()->StoreField(AccessBuilder::ForJSArrayLength(kind)),
receiver, new_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, new_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 Builtin builtin = Builtin::kArrayShift;
auto call_descriptor = Linkage::GetCPPBuiltinCallDescriptor(
graph()->zone(), BuiltinArguments::kNumExtraArgsWithReceiver,
Builtins::name(builtin), node->op()->properties(),
CallDescriptor::kNeedsFrameState);
const bool has_builtin_exit_frame = true;
Node* stub_code = jsgraph()->CEntryStubConstant(1, ArgvMode::kStack,
has_builtin_exit_frame);
Address builtin_entry = Builtins::CppEntryOf(builtin);
Node* entry = jsgraph()->ExternalConstant(
ExternalReference::Create(builtin_entry));
Node* argc = jsgraph()->ConstantNoHole(
BuiltinArguments::kNumExtraArgsWithReceiver);
static_assert(BuiltinArguments::kNewTargetIndex == 0);
static_assert(BuiltinArguments::kTargetIndex == 1);
static_assert(BuiltinArguments::kArgcIndex == 2);
static_assert(BuiltinArguments::kPaddingIndex == 3);
if_false1 = efalse1 = vfalse1 =
graph()->NewNode(common()->Call(call_descriptor), stub_code,
receiver, jsgraph()->PaddingConstant(), argc,
target, jsgraph()->UndefinedConstant(), entry,
argc, context, frame_state, efalse1, if_false1);
}
if_false0 = graph()->NewNode(common()->Merge(2), if_true1, if_false1);
efalse0 =
graph()->NewNode(common()->EffectPhi(2), etrue1, efalse1, if_false0);
vfalse0 =
graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2),
vtrue1, vfalse1, if_false0);
}
control = graph()->NewNode(common()->Merge(2), if_true0, if_false0);
effect = graph()->NewNode(common()->EffectPhi(2), etrue0, efalse0, control);
value = graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2),
vtrue0, vfalse0, control);
// Convert the hole to undefined. Do this last, so that we can optimize
// conversion operator via some smart strength reduction in many cases.
if (IsHoleyElementsKind(kind)) {
value =
graph()->NewNode(simplified()->ConvertTaggedHoleToUndefined(), value);
}
controls_to_merge.push_back(control);
effects_to_merge.push_back(effect);
values_to_merge.push_back(value);
}
if (controls_to_merge.size() > 1) {
int const count = static_cast<int>(controls_to_merge.size());
control = graph()->NewNode(common()->Merge(count), count,
&controls_to_merge.front());
effects_to_merge.push_back(control);
effect = graph()->NewNode(common()->EffectPhi(count), count + 1,
&effects_to_merge.front());
values_to_merge.push_back(control);
value =
graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, count),
count + 1, &values_to_merge.front());
}
ReplaceWithValue(node, value, effect, control);
return Replace(value);
}
// ES6 section 22.1.3.23 Array.prototype.slice ( )
Reduction JSCallReducer::ReduceArrayPrototypeSlice(Node* node) {
if (!v8_flags.turbo_inline_array_builtins) return NoChange();
JSCallNode n(node);
CallParameters const& p = n.Parameters();
if (p.speculation_mode() != SpeculationMode::kAllowSpeculation) {
return NoChange();
}
Node* receiver = n.receiver();
Node* start = n.ArgumentOr(0, jsgraph()->ZeroConstant());
Node* end = n.ArgumentOrUndefined(1, jsgraph());
Node* context = n.context();
Effect effect = n.effect();
Control control = n.control();
// Optimize for the case where we simply clone the {receiver}, i.e. when the
// {start} is zero and the {end} is undefined (meaning it will be set to
// {receiver}s "length" property).
// This logic should be in sync with ArrayPrototypeSlice (to a reasonable
// degree). This is because CloneFastJSArray produces arrays which are
// potentially COW. If there's a discrepancy, TF generates code which produces
// a COW array and then expects it to be non-COW (or the other way around) ->
// immediate deopt.
// LINT.IfChange(ArrayPrototypeSlice)
if (!NumberMatcher(start).Is(0) ||
!HeapObjectMatcher(end).Is(factory()->undefined_value())) {
return NoChange();
}
// LINT.ThenChange(/src/builtins/array-slice.tq:ArrayPrototypeSlice)
MapInference inference(broker(), receiver, effect);
if (!inference.HaveMaps()) return NoChange();
ZoneRefSet<Map> const& receiver_maps = inference.GetMaps();
// Check that the maps are of JSArray (and more).
// TODO(turbofan): Consider adding special case for the common pattern
// `slice.call(arguments)`, for example jQuery makes heavy use of that.
bool can_be_holey = false;
for (MapRef receiver_map : receiver_maps) {
if (!receiver_map.supports_fast_array_iteration(broker())) {
return inference.NoChange();
}
if (IsHoleyElementsKind(receiver_map.elements_kind())) {
can_be_holey = true;
}
}
if (!dependencies()->DependOnArraySpeciesProtector()) {
return inference.NoChange();
}
if (can_be_holey && !dependencies()->DependOnNoElementsProtector()) {
return inference.NoChange();
}
inference.RelyOnMapsPreferStability(dependencies(), jsgraph(), &effect,
control, p.feedback());
// TODO(turbofan): We can do even better here, either adding a CloneArray
// simplified operator, whose output type indicates that it's an Array,
// saving subsequent checks, or yet better, by introducing new operators
// CopySmiOrObjectElements / CopyDoubleElements and inlining the JSArray
// allocation in here. That way we'd even get escape analysis and scalar
// replacement to help in some cases.
Callable callable =
Builtins::CallableFor(isolate(), Builtin::kCloneFastJSArray);
auto call_descriptor = Linkage::GetStubCallDescriptor(
graph()->zone(), callable.descriptor(),
callable.descriptor().GetStackParameterCount(), CallDescriptor::kNoFlags,
Operator::kNoThrow | Operator::kNoDeopt);
// Calls to Builtin::kCloneFastJSArray produce COW arrays
// if the original array is COW
Node* clone = effect =
graph()->NewNode(common()->Call(call_descriptor),
jsgraph()->HeapConstantNoHole(callable.code()), receiver,
context, effect, control);
ReplaceWithValue(node, clone, effect, control);
return Replace(clone);
}
// ES6 section 22.1.2.2 Array.isArray ( arg )
Reduction JSCallReducer::ReduceArrayIsArray(Node* node) {
// We certainly know that undefined is not an array.
JSCallNode n(node);
if (n.ArgumentCount() < 1) {
Node* value = jsgraph()->FalseConstant();
ReplaceWithValue(node, value);
return Replace(value);
}
Effect effect = n.effect();
Control control = n.control();
Node* context = n.context();
FrameState frame_state = n.frame_state();
Node* object = n.Argument(0);
node->ReplaceInput(0, object);
node->ReplaceInput(1, context);
node->ReplaceInput(2, frame_state);
node->ReplaceInput(3, effect);
node->ReplaceInput(4, control);
node->TrimInputCount(5);
NodeProperties::ChangeOp(node, javascript()->ObjectIsArray());
return Changed(node);
}
Reduction JSCallReducer::ReduceArrayIterator(Node* node,
ArrayIteratorKind array_kind,
IterationKind iteration_kind) {
JSCallNode n(node);
Node* receiver = n.receiver();
Node* context = n.context();
Effect effect = n.effect();
Control control = n.control();
// Check if we know that {receiver} is a valid JSReceiver.
MapInference inference(broker(), receiver, effect);
if (!inference.HaveMaps() || !inference.AllOfInstanceTypesAreJSReceiver()) {
return NoChange();
}
// TypedArray iteration is stricter: it throws if the receiver is not a typed
// array. So don't bother optimizing in that case.
if (array_kind == ArrayIteratorKind::kTypedArray &&
!inference.AllOfInstanceTypesAre(InstanceType::JS_TYPED_ARRAY_TYPE)) {
return NoChange();
}
if (array_kind == ArrayIteratorKind::kTypedArray) {
// Make sure we deopt when the JSArrayBuffer is detached.
if (!dependencies()->DependOnArrayBufferDetachingProtector()) {
CallParameters const& p = CallParametersOf(node->op());
if (p.speculation_mode() != SpeculationMode::kAllowSpeculation) {
return NoChange();
}
std::set<ElementsKind> elements_kinds;
for (MapRef map : inference.GetMaps()) {
elements_kinds.insert(map.elements_kind());
}
// The following detach check is built with the given {elements_kinds} in
// mind, so we have to install dependency on those.
inference.RelyOnMapsPreferStability(
dependencies(), jsgraph(), &effect, control,
CallParametersOf(node->op()).feedback());
JSCallReducerAssembler a(this, node);
a.CheckIfTypedArrayWasDetachedOrOutOfBounds(
TNode<JSTypedArray>::UncheckedCast(receiver),
std::move(elements_kinds), p.feedback());
std::tie(effect, control) = ReleaseEffectAndControlFromAssembler(&a);
}
}
// JSCreateArrayIterator doesn't have control output, so we bypass the old
// JSCall node on the control chain.
ReplaceWithValue(node, node, node, control);
// Morph the {node} into a JSCreateArrayIterator with the given {kind}.
node->ReplaceInput(0, receiver);
node->ReplaceInput(1, context);
node->ReplaceInput(2, effect);
node->ReplaceInput(3, control);
node->TrimInputCount(4);
NodeProperties::ChangeOp(node,
javascript()->CreateArrayIterator(iteration_kind));
return Changed(node);
}
// ES #sec-%arrayiteratorprototype%.next
Reduction JSCallReducer::ReduceArrayIteratorPrototypeNext(Node* node) {
JSCallNode n(node);
CallParameters const& p = n.Parameters();
Node* iterator = n.receiver();
Node* context = n.context();
Effect effect = n.effect();
Control control = n.control();
if (p.speculation_mode() != SpeculationMode::kAllowSpeculation) {
return NoChange();
}
if (iterator->opcode() != IrOpcode::kJSCreateArrayIterator) return NoChange();
IterationKind const iteration_kind =
CreateArrayIteratorParametersOf(iterator->op()).kind();
Node* iterated_object = NodeProperties::GetValueInput(iterator, 0);
Effect iterator_effect{NodeProperties::GetEffectInput(iterator)};
MapInference inference(broker(), iterated_object, iterator_effect);
if (!inference.HaveMaps()) return NoChange();
ZoneRefSet<Map> const& iterated_object_maps = inference.GetMaps();
// Check that various {iterated_object_maps} have compatible elements kinds.
ElementsKind elements_kind = iterated_object_maps[0].elements_kind();
if (IsTypedArrayElementsKind(elements_kind)) {
// TurboFan doesn't support loading from BigInt typed arrays yet.
if (elements_kind == BIGUINT64_ELEMENTS ||
elements_kind == BIGINT64_ELEMENTS) {
return inference.NoChange();
}
for (MapRef iterated_object_map : iterated_object_maps) {
if (iterated_object_map.elements_kind() != elements_kind) {
return inference.NoChange();
}
}
} else {
if (!CanInlineArrayIteratingBuiltin(broker(), iterated_object_maps,
&elements_kind)) {
return inference.NoChange();
}
}
if (IsHoleyElementsKind(elements_kind) &&
!dependencies()->DependOnNoElementsProtector()) {
return inference.NoChange();
}
// Since the map inference was done relative to {iterator_effect} rather than
// {effect}, we need to guard the use of the map(s) even when the inference
// was reliable.
inference.InsertMapChecks(jsgraph(), &effect, control, p.feedback());
if (IsTypedArrayElementsKind(elements_kind)) {
// See if we can skip the detaching check.
if (!dependencies()->DependOnArrayBufferDetachingProtector()) {
// Bail out if the {iterated_object}s JSArrayBuffer was detached.
Node* buffer = effect = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForJSArrayBufferViewBuffer()),
iterated_object, effect, control);
Node* buffer_bit_field = effect = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForJSArrayBufferBitField()),
buffer, effect, control);
Node* check = graph()->NewNode(
simplified()->NumberEqual(),
graph()->NewNode(
simplified()->NumberBitwiseAnd(), buffer_bit_field,
jsgraph()->ConstantNoHole(JSArrayBuffer::WasDetachedBit::kMask)),
jsgraph()->ZeroConstant());
effect = graph()->NewNode(
simplified()->CheckIf(DeoptimizeReason::kArrayBufferWasDetached,
p.feedback()),
check, effect, control);
}
}
// Load the [[NextIndex]] from the {iterator} and leverage the fact
// that we definitely know that it's in Unsigned32 range since the
// {iterated_object} is either a JSArray or a JSTypedArray. For the
// latter case we even know that it's a Smi in UnsignedSmall range.
FieldAccess index_access = AccessBuilder::ForJSArrayIteratorNextIndex();
if (!IsTypedArrayElementsKind(elements_kind)) {
index_access.type = TypeCache::Get()->kJSArrayLengthType;
}
Node* index = effect = graph()->NewNode(simplified()->LoadField(index_access),
iterator, effect, control);
// Load the elements of the {iterated_object}. While it feels
// counter-intuitive to place the elements pointer load before
// the condition below, as it might not be needed (if the {index}
// is out of bounds for the {iterated_object}), it's better this
// way as it allows the LoadElimination to eliminate redundant
// reloads of the elements pointer.
Node* elements = effect = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForJSObjectElements()),
iterated_object, effect, control);
// Load the length of the {iterated_object}. Due to the map checks we
// already know something about the length here, which we can leverage
// to generate Word32 operations below without additional checking.
Node* length;
if (IsTypedArrayElementsKind(elements_kind)) {
Node* byte_length = effect = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForJSTypedArrayByteLength()),
iterated_object, effect, control);
Node* byte_length_shifted = graph()->NewNode(
jsgraph()->machine()->WordShr(), byte_length,
jsgraph()->UintPtrConstant(ElementsKindToShiftSize(elements_kind)));
length = graph()->NewNode(
common()->ExitMachineGraph(MachineType::PointerRepresentation(),
TypeCache::Get()->kJSTypedArrayLengthType),
byte_length_shifted);
} else {
length = effect = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForJSArrayLength(elements_kind)),
iterated_object, effect, control);
}
// Check whether {index} is within the valid range for the {iterated_object}.
Node* check = graph()->NewNode(simplified()->NumberLessThan(), index, length);
Node* branch =
graph()->NewNode(common()->Branch(BranchHint::kNone), check, control);
Node* done_true;
Node* value_true;
Node* etrue = effect;
Node* if_true = graph()->NewNode(common()->IfTrue(), branch);
{
// This extra check exists to refine the type of {index} but also to break
// an exploitation technique that abuses typer mismatches.
if (v8_flags.turbo_typer_hardening) {
index = etrue = graph()->NewNode(
simplified()->CheckBounds(
p.feedback(), CheckBoundsFlag::kAbortOnOutOfBounds |
(IsTypedArrayElementsKind(elements_kind)
? CheckBoundsFlag::kAllow64BitBounds
: CheckBoundsFlag(0))),
index, length, etrue, if_true);
}
done_true = jsgraph()->FalseConstant();
if (iteration_kind == IterationKind::kKeys) {
// Just return the {index}.
value_true = index;
} else {
DCHECK(iteration_kind == IterationKind::kEntries ||
iteration_kind == IterationKind::kValues);
if (IsTypedArrayElementsKind(elements_kind)) {
Node* base_ptr = etrue =
graph()->NewNode(simplified()->LoadField(
AccessBuilder::ForJSTypedArrayBasePointer()),
iterated_object, etrue, if_true);
Node* external_ptr = etrue = graph()->NewNode(
simplified()->LoadField(
AccessBuilder::ForJSTypedArrayExternalPointer()),
iterated_object, etrue, if_true);
ExternalArrayType array_type = kExternalInt8Array;
switch (elements_kind) {
#define TYPED_ARRAY_CASE(Type, type, TYPE, ctype) \
case TYPE##_ELEMENTS: \
array_type = kExternal##Type##Array; \
break;
TYPED_ARRAYS(TYPED_ARRAY_CASE)
default:
UNREACHABLE();
#undef TYPED_ARRAY_CASE
}
Node* buffer = etrue =
graph()->NewNode(simplified()->LoadField(
AccessBuilder::ForJSArrayBufferViewBuffer()),
iterated_object, etrue, if_true);
value_true = etrue =
graph()->NewNode(simplified()->LoadTypedElement(array_type), buffer,
base_ptr, external_ptr, index, etrue, if_true);
} else {
value_true = etrue = graph()->NewNode(
simplified()->LoadElement(
AccessBuilder::ForFixedArrayElement(elements_kind)),
elements, index, etrue, if_true);
// Convert hole to undefined if needed.
if (IsHoleyElementsKind(elements_kind)) {
value_true = ConvertHoleToUndefined(value_true, elements_kind);
}
}
if (iteration_kind == IterationKind::kEntries) {
// Allocate elements for key/value pair
value_true = etrue =
graph()->NewNode(javascript()->CreateKeyValueArray(), index,
value_true, context, etrue);
} else {
DCHECK_EQ(IterationKind::kValues, iteration_kind);
}
}
// Increment the [[NextIndex]] field in the {iterator}. The TypeGuards
// above guarantee that the {next_index} is in the UnsignedSmall range.
Node* next_index = graph()->NewNode(simplified()->NumberAdd(), index,
jsgraph()->OneConstant());
etrue = graph()->NewNode(simplified()->StoreField(index_access), iterator,
next_index, etrue, if_true);
}
Node* done_false;
Node* value_false;
Node* efalse = effect;
Node* if_false = graph()->NewNode(common()->IfFalse(), branch);
{
// iterator.[[NextIndex]] >= array.length, stop iterating.
done_false = jsgraph()->TrueConstant();
value_false = jsgraph()->UndefinedConstant();
// Mark the {iterator} as exhausted by setting the [[NextIndex]] to a
// value that will never pass the length check again (aka the maximum
// value possible for the specific iterated object). Note that this is
// different from what the specification says, which is changing the
// [[IteratedObject]] field to undefined, but that makes it difficult
// to eliminate the map checks and "length" accesses in for..of loops.
Node* end_index = jsgraph()->ConstantNoHole(index_access.type.Max());
efalse = graph()->NewNode(simplified()->StoreField(index_access), iterator,
end_index, efalse, if_false);
}
control = graph()->NewNode(common()->Merge(2), if_true, if_false);
effect = graph()->NewNode(common()->EffectPhi(2), etrue, efalse, control);
Node* value =
graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2),
value_true, value_false, control);
Node* done =
graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2),
done_true, done_false, control);
// Create IteratorResult object.
value = effect = graph()->NewNode(javascript()->CreateIterResultObject(),
value, done, context, effect);
ReplaceWithValue(node, value, effect, control);
return Replace(value);
}
// ES6 section 21.1.3.2 String.prototype.charCodeAt ( pos )
Reduction JSCallReducer::ReduceStringPrototypeStringCharCodeAt(Node* node) {
JSCallNode n(node);
CallParameters const& p = n.Parameters();
if (p.speculation_mode() != SpeculationMode::kAllowSpeculation &&
p.speculation_mode() !=
SpeculationMode::kDisallowBoundsCheckSpeculation) {
return NoChange();
}
JSCallReducerAssembler a(this, node);
Node* subgraph = a.ReduceStringPrototypeCharCodeAt(p.speculation_mode());
return ReplaceWithSubgraph(&a, subgraph);
}
// ES6 section 21.1.3.3 String.prototype.codePointAt ( pos )
Reduction JSCallReducer::ReduceStringPrototypeStringCodePointAt(Node* node) {
JSCallNode n(node);
CallParameters const& p = n.Parameters();
if (p.speculation_mode() != SpeculationMode::kAllowSpeculation) {
return NoChange();
}
Node* receiver = n.receiver();
Node* index = n.ArgumentOr(0, jsgraph()->ZeroConstant());
Effect effect = n.effect();
Control control = n.control();
// Ensure that the {receiver} is actually a String.
receiver = effect = graph()->NewNode(simplified()->CheckString(p.feedback()),
receiver, effect, control);
// Determine the {receiver} length.
Node* receiver_length =
graph()->NewNode(simplified()->StringLength(), receiver);
// TODO(olivf): Support SpeculationMode::kOutOfBands.
// Check that the {index} is within range.
index = effect = graph()->NewNode(simplified()->CheckBounds(p.feedback()),
index, receiver_length, effect, control);
// Return the character from the {receiver} as single character string.
Node* value = effect = graph()->NewNode(simplified()->StringCodePointAt(),
receiver, index, effect, control);
ReplaceWithValue(node, value, effect, control);
return Replace(value);
}
// ES section 21.1.3.20
// String.prototype.startsWith ( searchString [ , position ] )
Reduction JSCallReducer::ReduceStringPrototypeStartsWith(Node* node) {
JSCallNode n(node);
CallParameters const& p = n.Parameters();
if (p.speculation_mode() != SpeculationMode::kAllowSpeculation) {
return NoChange();
}
TNode<Object> search_element = n.ArgumentOrUndefined(0, jsgraph());
// Here are three conditions:
// First, If search_element is definitely not a string, we make no change.
// Second, If search_element is definitely a string and its length is less
// or equal than max inline matching sequence threshold, we could inline
// the entire matching sequence.
// Third, we try to inline, and have a runtime deopt if search_element is
// not a string.
HeapObjectMatcher search_element_matcher(search_element);
if (search_element_matcher.HasResolvedValue()) {
ObjectRef target_ref = search_element_matcher.Ref(broker());
if (!target_ref.IsString()) return NoChange();
StringRef search_element_string = target_ref.AsString();
if (!search_element_string.IsContentAccessible()) return NoChange();
int length = search_element_string.length();
// If search_element's length is less or equal than
// kMaxInlineMatchSequence, we inline the entire
// matching sequence.
if (length <= kMaxInlineMatchSequence) {
JSCallReducerAssembler a(this, node);
Node* subgraph = a.ReduceStringPrototypeStartsWith(search_element_string);
return ReplaceWithSubgraph(&a, subgraph);
}
}
JSCallReducerAssembler a(this, node);
Node* subgraph = a.ReduceStringPrototypeStartsWith();
return ReplaceWithSubgraph(&a, subgraph);
}
// ES section 21.1.3.7
// String.prototype.endsWith(searchString [, endPosition])
Reduction JSCallReducer::ReduceStringPrototypeEndsWith(Node* node) {
JSCallNode n(node);
CallParameters const& p = n.Parameters();
if (p.speculation_mode() != SpeculationMode::kAllowSpeculation) {
return NoChange();
}
TNode<Object> search_element = n.ArgumentOrUndefined(0, jsgraph());
// Here are three conditions:
// First, If search_element is definitely not a string, we make no change.
// Second, If search_element is definitely a string and its length is less
// or equal than max inline matching sequence threshold, we could inline
// the entire matching sequence.
// Third, we try to inline, and have a runtime deopt if search_element is
// not a string.
HeapObjectMatcher search_element_matcher(search_element);
if (search_element_matcher.HasResolvedValue()) {
ObjectRef target_ref = search_element_matcher.Ref(broker());
if (!target_ref.IsString()) return NoChange();
StringRef search_element_string = target_ref.AsString();
if (!search_element_string.IsContentAccessible()) return NoChange();
int length = search_element_string.length();
// If search_element's length is less or equal than
// kMaxInlineMatchSequence, we inline the entire
// matching sequence.
if (length <= kMaxInlineMatchSequence) {
JSCallReducerAssembler a(this, node);
Node* subgraph = a.ReduceStringPrototypeEndsWith(search_element_string);
return ReplaceWithSubgraph(&a, subgraph);
}
}
JSCallReducerAssembler a(this, node);
Node* subgraph = a.ReduceStringPrototypeEndsWith();
return ReplaceWithSubgraph(&a, subgraph);
}
// ES section 21.1.3.1 String.prototype.charAt ( pos )
Reduction JSCallReducer::ReduceStringPrototypeCharAt(Node* node) {
JSCallNode n(node);
CallParameters const& p = n.Parameters();
if (p.speculation_mode() != SpeculationMode::kAllowSpeculation &&
p.speculation_mode() !=
SpeculationMode::kDisallowBoundsCheckSpeculation) {
return NoChange();
}
Node* receiver = n.receiver();
Node* index = n.ArgumentOr(0, jsgraph()->ZeroConstant());
// Attempt to constant fold the operation when we know
// that receiver is a string and index is a number.
HeapObjectMatcher receiver_matcher(receiver);
NumberMatcher index_matcher(index);
if (receiver_matcher.HasResolvedValue()) {
HeapObjectRef receiver_ref = receiver_matcher.Ref(broker());
if (!receiver_ref.IsString()) return NoChange();
StringRef receiver_string = receiver_ref.AsString();
bool is_content_accessible = receiver_string.IsContentAccessible();
bool is_integer_in_max_range =
index_matcher.IsInteger() &&
index_matcher.IsInRange(
0.0, static_cast<double>(JSObject::kMaxElementIndex));
if (is_content_accessible && is_integer_in_max_range) {
JSCallReducerAssembler a(this, node);
Node* subgraph = a.ReduceStringPrototypeCharAt(
receiver_string,
static_cast<uint32_t>(index_matcher.ResolvedValue()));
return ReplaceWithSubgraph(&a, subgraph);
}
}
JSCallReducerAssembler a(this, node);
Node* subgraph = a.ReduceStringPrototypeCharAt(p.speculation_mode());
return ReplaceWithSubgraph(&a, subgraph);
}
#ifdef V8_INTL_SUPPORT
Reduction JSCallReducer::ReduceStringPrototypeToLowerCaseIntl(Node* node) {
JSCallNode n(node);
CallParameters const& p = n.Parameters();
if (p.speculation_mode() != SpeculationMode::kAllowSpeculation) {
return NoChange();
}
Effect effect = n.effect();
Control control = n.control();
Node* receiver = effect = graph()->NewNode(
simplified()->CheckString(p.feedback()), n.receiver(), effect, control);
NodeProperties::ReplaceEffectInput(node, effect);
RelaxEffectsAndControls(node);
node->ReplaceInput(0, receiver);
node->TrimInputCount(1);
NodeProperties::ChangeOp(node, simplified()->StringToLowerCaseIntl());
NodeProperties::SetType(node, Type::String());
return Changed(node);
}
Reduction JSCallReducer::ReduceStringPrototypeToUpperCaseIntl(Node* node) {
JSCallNode n(node);
CallParameters const& p = n.Parameters();
if (p.speculation_mode() != SpeculationMode::kAllowSpeculation) {
return NoChange();
}
Effect effect = n.effect();
Control control = n.control();
Node* receiver = effect = graph()->NewNode(
simplified()->CheckString(p.feedback()), n.receiver(), effect, control);
NodeProperties::ReplaceEffectInput(node, effect);
RelaxEffectsAndControls(node);
node->ReplaceInput(0, receiver);
node->TrimInputCount(1);
NodeProperties::ChangeOp(node, simplified()->StringToUpperCaseIntl());
NodeProperties::SetType(node, Type::String());
return Changed(node);
}
#endif // V8_INTL_SUPPORT
// ES #sec-string.fromcharcode
Reduction JSCallReducer::ReduceStringFromCharCode(Node* node) {
JSCallNode n(node);
CallParameters const& p = n.Parameters();
if (p.speculation_mode() != SpeculationMode::kAllowSpeculation) {
return NoChange();
}
if (n.ArgumentCount() == 1) {
Effect effect = n.effect();
Control control = n.control();
Node* input = n.Argument(0);
input = effect = graph()->NewNode(
simplified()->SpeculativeToNumber(NumberOperationHint::kNumberOrOddball,
p.feedback()),
input, effect, control);
Node* value =
graph()->NewNode(simplified()->StringFromSingleCharCode(), input);
ReplaceWithValue(node, value, effect);
return Replace(value);
}
return NoChange();
}
// ES #sec-string.fromcodepoint
Reduction JSCallReducer::ReduceStringFromCodePoint(Node* node) {
JSCallNode n(node);
CallParameters const& p = n.Parameters();
if (p.speculation_mode() != SpeculationMode::kAllowSpeculation) {
return NoChange();
}
if (n.ArgumentCount() != 1) return NoChange();
Effect effect = n.effect();
Control control = n.control();
Node* input = n.Argument(0);
input = effect = graph()->NewNode(
simplified()->CheckBounds(p.feedback(),
CheckBoundsFlag::kConvertStringAndMinusZero),
input, jsgraph()->ConstantNoHole(0x10FFFF + 1), effect, control);
Node* value =
graph()->NewNode(simplified()->StringFromSingleCodePoint(), input);
ReplaceWithValue(node, value, effect);
return Replace(value);
}
Reduction JSCallReducer::ReduceStringPrototypeIterator(Node* node) {
JSCallNode n(node);
CallParameters const& p = n.Parameters();
if (p.speculation_mode() != SpeculationMode::kAllowSpeculation) {
return NoChange();
}
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node);
Node* receiver = effect = graph()->NewNode(
simplified()->CheckString(p.feedback()), n.receiver(), effect, control);
Node* iterator = effect =
graph()->NewNode(javascript()->CreateStringIterator(), receiver,
jsgraph()->NoContextConstant(), effect);
ReplaceWithValue(node, iterator, effect, control);
return Replace(iterator);
}
#ifdef V8_INTL_SUPPORT
Reduction JSCallReducer::ReduceStringPrototypeLocaleCompareIntl(Node* node) {
JSCallNode n(node);
// Signature: receiver.localeCompare(compareString, locales, options)
if (n.ArgumentCount() < 1 || n.ArgumentCount() > 3) {
return NoChange();
}
{
DirectHandle<Object> locales;
{
HeapObjectMatcher m(n.ArgumentOrUndefined(1, jsgraph()));
if (!m.HasResolvedValue()) return NoChange();
if (m.Is(factory()->undefined_value())) {
locales = factory()->undefined_value();
} else {
ObjectRef ref = m.Ref(broker());
if (!ref.IsString()) return NoChange();
StringRef sref = ref.AsString();
if (std::optional<Handle<String>> maybe_locales =
sref.ObjectIfContentAccessible(broker())) {
locales = *maybe_locales;
} else {
return NoChange();
}
}
}
TNode<Object> options = n.ArgumentOrUndefined(2, jsgraph());
{
HeapObjectMatcher m(options);
if (!m.Is(factory()->undefined_value())) {
return NoChange();
}
}
if (Intl::CompareStringsOptionsFor(broker()->local_isolate_or_isolate(),
locales, factory()->undefined_value()) !=
Intl::CompareStringsOptions::kTryFastPath) {
return NoChange();
}
}
Callable callable =
Builtins::CallableFor(isolate(), Builtin::kStringFastLocaleCompare);
auto call_descriptor = Linkage::GetStubCallDescriptor(
graph()->zone(), callable.descriptor(),
callable.descriptor().GetStackParameterCount(),
CallDescriptor::kNeedsFrameState);
node->RemoveInput(n.FeedbackVectorIndex());
if (n.ArgumentCount() == 3) {
node->RemoveInput(n.ArgumentIndex(2));
} else if (n.ArgumentCount() == 1) {
node->InsertInput(graph()->zone(), n.LastArgumentIndex() + 1,
jsgraph()->UndefinedConstant());
} else {
DCHECK_EQ(2, n.ArgumentCount());
}
node->InsertInput(graph()->zone(), 0,
jsgraph()->HeapConstantNoHole(callable.code()));
NodeProperties::ChangeOp(node, common()->Call(call_descriptor));
return Changed(node);
}
#endif // V8_INTL_SUPPORT
Reduction JSCallReducer::ReduceStringIteratorPrototypeNext(Node* node) {
JSCallNode n(node);
Node* receiver = n.receiver();
Effect effect = n.effect();
Control control = n.control();
Node* context = n.context();
MapInference inference(broker(), receiver, effect);
if (!inference.HaveMaps() ||
!inference.AllOfInstanceTypesAre(JS_STRING_ITERATOR_TYPE)) {
return NoChange();
}
Node* string = effect = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForJSStringIteratorString()),
receiver, effect, control);
Node* index = effect = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForJSStringIteratorIndex()),
receiver, effect, control);
Node* length = graph()->NewNode(simplified()->StringLength(), string);
// branch0: if (index < length)
Node* check0 =
graph()->NewNode(simplified()->NumberLessThan(), index, length);
Node* branch0 =
graph()->NewNode(common()->Branch(BranchHint::kNone), check0, control);
Node* etrue0 = effect;
Node* if_true0 = graph()->NewNode(common()->IfTrue(), branch0);
Node* done_true;
Node* vtrue0;
{
done_true = jsgraph()->FalseConstant();
vtrue0 = etrue0 = graph()->NewNode(simplified()->StringFromCodePointAt(),
string, index, etrue0, if_true0);
// Update iterator.[[NextIndex]]
Node* char_length = graph()->NewNode(simplified()->StringLength(), vtrue0);
index = graph()->NewNode(simplified()->NumberAdd(), index, char_length);
etrue0 = graph()->NewNode(
simplified()->StoreField(AccessBuilder::ForJSStringIteratorIndex()),
receiver, index, etrue0, if_true0);
}
Node* if_false0 = graph()->NewNode(common()->IfFalse(), branch0);
Node* done_false;
Node* vfalse0;
{
vfalse0 = jsgraph()->UndefinedConstant();
done_false = jsgraph()->TrueConstant();
}
control = graph()->NewNode(common()->Merge(2), if_true0, if_false0);
effect = graph()->NewNode(common()->EffectPhi(2), etrue0, effect, control);
Node* value =
graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2), vtrue0,
vfalse0, control);
Node* done =
graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2),
done_true, done_false, control);
value = effect = graph()->NewNode(javascript()->CreateIterResultObject(),
value, done, context, effect);
ReplaceWithValue(node, value, effect, control);
return Replace(value);
}
// ES #sec-string.prototype.concat
Reduction JSCallReducer::ReduceStringPrototypeConcat(Node* node) {
JSCallNode n(node);
CallParameters const& p = n.Parameters();
const int parameter_count = n.ArgumentCount();
if (parameter_count > 1) return NoChange();
if (p.speculation_mode() != SpeculationMode::kAllowSpeculation) {
return NoChange();
}
Effect effect = n.effect();
Control control = n.control();
Node* receiver = effect = graph()->NewNode(
simplified()->CheckString(p.feedback()), n.receiver(), effect, control);
if (parameter_count == 0) {
ReplaceWithValue(node, receiver, effect, control);
return Replace(receiver);
}
Node* argument = effect = graph()->NewNode(
simplified()->CheckString(p.feedback()), n.Argument(0), effect, control);
Node* receiver_length =
graph()->NewNode(simplified()->StringLength(), receiver);
Node* argument_length =
graph()->NewNode(simplified()->StringLength(), argument);
Node* length = graph()->NewNode(simplified()->NumberAdd(), receiver_length,
argument_length);
length = effect = graph()->NewNode(
simplified()->CheckBounds(p.feedback()), length,
jsgraph()->ConstantNoHole(String::kMaxLength + 1), effect, control);
Node* value = graph()->NewNode(simplified()->StringConcat(), length, receiver,
argument);
ReplaceWithValue(node, value, effect, control);
return Replace(value);
}
namespace {
FrameState CreateStringCreateLazyDeoptContinuationFrameState(
JSGraph* graph, SharedFunctionInfoRef shared, Node* target, Node* context,
Node* outer_frame_state) {
Node* const receiver = graph->TheHoleConstant();
Node* stack_parameters[]{receiver};
const int stack_parameter_count = arraysize(stack_parameters);
return CreateJavaScriptBuiltinContinuationFrameState(
graph, shared, Builtin::kStringCreateLazyDeoptContinuation, target,
context, stack_parameters, stack_parameter_count, outer_frame_state,
ContinuationFrameStateMode::LAZY);
}
} // namespace
Reduction JSCallReducer::ReduceStringConstructor(Node* node,
JSFunctionRef constructor) {
JSConstructNode n(node);
if (n.target() != n.new_target()) return NoChange();
DCHECK_EQ(constructor, native_context().string_function(broker_));
DCHECK(constructor.initial_map(broker_).IsJSPrimitiveWrapperMap());
Node* context = n.context();
FrameState frame_state = n.frame_state();
Effect effect = n.effect();
Control control = n.control();
Node* primitive_value;
if (n.ArgumentCount() == 0) {
primitive_value = jsgraph()->EmptyStringConstant();
} else {
// Insert a construct stub frame into the chain of frame states. This will
// reconstruct the proper frame when deoptimizing within the constructor.
frame_state = CreateConstructInvokeStubFrameState(
node, frame_state, constructor.shared(broker_), context, common(),
graph());
// This continuation takes the newly created primitive string, and wraps it
// in a string wrapper, matching CreateStringWrapper.
Node* continuation_frame_state =
CreateStringCreateLazyDeoptContinuationFrameState(
jsgraph(), constructor.shared(broker_), n.target(), context,
frame_state);
primitive_value = effect = control =
graph()->NewNode(javascript()->ToString(), n.Argument(0), context,
continuation_frame_state, effect, control);
// Rewire potential exception edges.
Node* on_exception = nullptr;
if (NodeProperties::IsExceptionalCall(node, &on_exception)) {
// Create appropriate {IfException} and {IfSuccess} nodes.
Node* if_exception =
graph()->NewNode(common()->IfException(), effect, control);
control = graph()->NewNode(common()->IfSuccess(), control);
// Join the exception edges.
Node* merge =
graph()->NewNode(common()->Merge(2), if_exception, on_exception);
Node* ephi = graph()->NewNode(common()->EffectPhi(2), if_exception,
on_exception, merge);
Node* phi =
graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2),
if_exception, on_exception, merge);
ReplaceWithValue(on_exception, phi, ephi, merge);
merge->ReplaceInput(1, on_exception);
ephi->ReplaceInput(1, on_exception);
phi->ReplaceInput(1, on_exception);
}
}
RelaxControls(node, control);
node->ReplaceInput(0, primitive_value);
node->ReplaceInput(1, context);
node->ReplaceInput(2, effect);
node->TrimInputCount(3);
NodeProperties::ChangeOp(node, javascript()->CreateStringWrapper());
return Changed(node);
}
Reduction JSCallReducer::ReducePromiseConstructor(Node* node) {
PromiseBuiltinReducerAssembler a(this, node);
// We only inline when we have the executor.
if (a.ConstructArity() < 1) return NoChange();
// Only handle builtins Promises, not subclasses.
if (a.TargetInput() != a.NewTargetInput()) return NoChange();
if (!dependencies()->DependOnPromiseHookProtector()) return NoChange();
TNode<Object> subgraph = a.ReducePromiseConstructor(native_context());
return ReplaceWithSubgraph(&a, subgraph);
}
bool JSCallReducer::DoPromiseChecks(MapInference* inference) {
if (!inference->HaveMaps()) return false;
ZoneRefSet<Map> const& receiver_maps = inference->GetMaps();
// Check whether all {receiver_maps} are JSPromise maps and
// have the initial Promise.prototype as their [[Prototype]].
for (MapRef receiver_map : receiver_maps) {
if (!receiver_map.IsJSPromiseMap()) return false;
HeapObjectRef prototype = receiver_map.prototype(broker());
if (!prototype.equals(native_context().promise_prototype(broker()))) {
return false;
}
}
return true;
}
// ES section #sec-promise.prototype.catch
Reduction JSCallReducer::ReducePromisePrototypeCatch(Node* node) {
JSCallNode n(node);
CallParameters const& p = n.Parameters();
if (p.speculation_mode() != SpeculationMode::kAllowSpeculation) {
return NoChange();
}
int arity = p.arity_without_implicit_args();
Node* receiver = n.receiver();
Effect effect = n.effect();
Control control = n.control();
MapInference inference(broker(), receiver, effect);
if (!DoPromiseChecks(&inference)) return inference.NoChange();
if (!dependencies()->DependOnPromiseThenProtector()) {
return inference.NoChange();
}
inference.RelyOnMapsPreferStability(dependencies(), jsgraph(), &effect,
control, p.feedback());
// Massage the {node} to call "then" instead by first removing all inputs
// following the onRejected parameter, and then filling up the parameters
// to two inputs from the left with undefined.
Node* target = jsgraph()->ConstantNoHole(
native_context().promise_then(broker()), broker());
NodeProperties::ReplaceValueInput(node, target, 0);
NodeProperties::ReplaceEffectInput(node, effect);
for (; arity > 1; --arity) node->RemoveInput(3);
for (; arity < 2; ++arity) {
node->InsertInput(graph()->zone(), 2, jsgraph()->UndefinedConstant());
}
NodeProperties::ChangeOp(
node, javascript()->Call(
JSCallNode::ArityForArgc(arity), p.frequency(), p.feedback(),
ConvertReceiverMode::kNotNullOrUndefined, p.speculation_mode(),
CallFeedbackRelation::kUnrelated));
return Changed(node).FollowedBy(ReducePromisePrototypeThen(node));
}
Node* JSCallReducer::CreateClosureFromBuiltinSharedFunctionInfo(
SharedFunctionInfoRef shared, Node* context, Node* effect, Node* control) {
DCHECK(shared.HasBuiltinId());
Handle<FeedbackCell> feedback_cell =
isolate()->factory()->many_closures_cell();
Callable const callable =
Builtins::CallableFor(isolate(), shared.builtin_id());
CodeRef code = MakeRef(broker(), *callable.code());
return graph()->NewNode(javascript()->CreateClosure(shared, code),
jsgraph()->HeapConstantNoHole(feedback_cell), context,
effect, control);
}
// ES section #sec-promise.prototype.finally
Reduction JSCallReducer::ReducePromisePrototypeFinally(Node* node) {
JSCallNode n(node);
CallParameters const& p = n.Parameters();
int arity = p.arity_without_implicit_args();
Node* receiver = n.receiver();
Node* on_finally = n.ArgumentOrUndefined(0, jsgraph());
Effect effect = n.effect();
Control control = n.control();
if (p.speculation_mode() != SpeculationMode::kAllowSpeculation) {
return NoChange();
}
MapInference inference(broker(), receiver, effect);
if (!DoPromiseChecks(&inference)) return inference.NoChange();
ZoneRefSet<Map> const& receiver_maps = inference.GetMaps();
if (!dependencies()->DependOnPromiseHookProtector()) {
return inference.NoChange();
}
if (!dependencies()->DependOnPromiseThenProtector()) {
return inference.NoChange();
}
if (!dependencies()->DependOnPromiseSpeciesProtector()) {
return inference.NoChange();
}
inference.RelyOnMapsPreferStability(dependencies(), jsgraph(), &effect,
control, p.feedback());
// Check if {on_finally} is callable, and if so wrap it into appropriate
// closures that perform the finalization.
Node* check = graph()->NewNode(simplified()->ObjectIsCallable(), on_finally);
Node* branch =
graph()->NewNode(common()->Branch(BranchHint::kTrue), check, control);
Node* if_true = graph()->NewNode(common()->IfTrue(), branch);
Node* etrue = effect;
Node* catch_true;
Node* then_true;
{
Node* context = jsgraph()->ConstantNoHole(native_context(), broker());
Node* constructor = jsgraph()->ConstantNoHole(
native_context().promise_function(broker()), broker());
// Allocate shared context for the closures below.
context = etrue = graph()->NewNode(
javascript()->CreateFunctionContext(
native_context().scope_info(broker()),
int{PromiseBuiltins::kPromiseFinallyContextLength} -
Context::MIN_CONTEXT_SLOTS,
FUNCTION_SCOPE),
context, etrue, if_true);
etrue = graph()->NewNode(
simplified()->StoreField(
AccessBuilder::ForContextSlot(PromiseBuiltins::kOnFinallySlot)),
context, on_finally, etrue, if_true);
etrue = graph()->NewNode(
simplified()->StoreField(
AccessBuilder::ForContextSlot(PromiseBuiltins::kConstructorSlot)),
context, constructor, etrue, if_true);
// Allocate the closure for the reject case.
SharedFunctionInfoRef promise_catch_finally =
MakeRef(broker(), factory()->promise_catch_finally_shared_fun());
catch_true = etrue = CreateClosureFromBuiltinSharedFunctionInfo(
promise_catch_finally, context, etrue, if_true);
// Allocate the closure for the fulfill case.
SharedFunctionInfoRef promise_then_finally =
MakeRef(broker(), factory()->promise_then_finally_shared_fun());
then_true = etrue = CreateClosureFromBuiltinSharedFunctionInfo(
promise_then_finally, context, etrue, if_true);
}
Node* if_false = graph()->NewNode(common()->IfFalse(), branch);
Node* efalse = effect;
Node* catch_false = on_finally;
Node* then_false = on_finally;
control = graph()->NewNode(common()->Merge(2), if_true, if_false);
effect = graph()->NewNode(common()->EffectPhi(2), etrue, efalse, control);
Node* catch_finally =
graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2),
catch_true, catch_false, control);
Node* then_finally =
graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2),
then_true, then_false, control);
// At this point we definitely know that {receiver} has one of the
// {receiver_maps}, so insert a MapGuard as a hint for the lowering
// of the call to "then" below.
{
effect = graph()->NewNode(simplified()->MapGuard(receiver_maps), receiver,
effect, control);
}
// Massage the {node} to call "then" instead by first removing all inputs
// following the onFinally parameter, and then replacing the only parameter
// input with the {on_finally} value.
Node* target = jsgraph()->ConstantNoHole(
native_context().promise_then(broker()), broker());
NodeProperties::ReplaceValueInput(node, target, n.TargetIndex());
NodeProperties::ReplaceEffectInput(node, effect);
NodeProperties::ReplaceControlInput(node, control);
for (; arity > 2; --arity) node->RemoveInput(2);
for (; arity < 2; ++arity) {
node->InsertInput(graph()->zone(), 2, then_finally);
}
node->ReplaceInput(2, then_finally);
node->ReplaceInput(3, catch_finally);
NodeProperties::ChangeOp(
node, javascript()->Call(
JSCallNode::ArityForArgc(arity), p.frequency(), p.feedback(),
ConvertReceiverMode::kNotNullOrUndefined, p.speculation_mode(),
CallFeedbackRelation::kUnrelated));
return Changed(node).FollowedBy(ReducePromisePrototypeThen(node));
}
Reduction JSCallReducer::ReducePromisePrototypeThen(Node* node) {
JSCallNode n(node);
CallParameters const& p = n.Parameters();
if (p.speculation_mode() != SpeculationMode::kAllowSpeculation) {
return NoChange();
}
Node* receiver = n.receiver();
Node* on_fulfilled = n.ArgumentOrUndefined(0, jsgraph());
Node* on_rejected = n.ArgumentOrUndefined(1, jsgraph());
Node* context = n.context();
Effect effect = n.effect();
Control control = n.control();
FrameState frame_state = n.frame_state();
MapInference inference(broker(), receiver, effect);
if (!DoPromiseChecks(&inference)) return inference.NoChange();
if (!dependencies()->DependOnPromiseHookProtector()) {
return inference.NoChange();
}
if (!dependencies()->DependOnPromiseSpeciesProtector()) {
return inference.NoChange();
}
inference.RelyOnMapsPreferStability(dependencies(), jsgraph(), &effect,
control, p.feedback());
// Check that {on_fulfilled} is callable.
on_fulfilled = graph()->NewNode(
common()->Select(MachineRepresentation::kTagged, BranchHint::kTrue),
graph()->NewNode(simplified()->ObjectIsCallable(), on_fulfilled),
on_fulfilled, jsgraph()->UndefinedConstant());
// Check that {on_rejected} is callable.
on_rejected = graph()->NewNode(
common()->Select(MachineRepresentation::kTagged, BranchHint::kTrue),
graph()->NewNode(simplified()->ObjectIsCallable(), on_rejected),
on_rejected, jsgraph()->UndefinedConstant());
// Create the resulting JSPromise.
Node* promise = effect =
graph()->NewNode(javascript()->CreatePromise(), context, effect);
// Chain {result} onto {receiver}.
promise = effect = graph()->NewNode(
javascript()->PerformPromiseThen(), receiver, on_fulfilled, on_rejected,
promise, context, frame_state, effect, control);
// At this point we know that {promise} is going to have the
// initial Promise map, since even if {PerformPromiseThen}
// above called into the host rejection tracker, the {promise}
// doesn't escape to user JavaScript. So bake this information
// into the graph such that subsequent passes can use the
// information for further optimizations.
MapRef promise_map =
native_context().promise_function(broker()).initial_map(broker());
effect =
graph()->NewNode(simplified()->MapGuard(ZoneRefSet<Map>(promise_map)),
promise, effect, control);
ReplaceWithValue(node, promise, effect, control);
return Replace(promise);
}
// ES section #sec-promise.resolve
Reduction JSCallReducer::ReducePromiseResolveTrampoline(Node* node) {
JSCallNode n(node);
Node* receiver = n.receiver();
Node* value = n.ArgumentOrUndefined(0, jsgraph());
Node* context = n.context();
Effect effect = n.effect();
Control control = n.control();
FrameState frame_state = n.frame_state();
// Only reduce when the receiver is guaranteed to be a JSReceiver.
MapInference inference(broker(), receiver, effect);
if (!inference.HaveMaps() || !inference.AllOfInstanceTypesAreJSReceiver()) {
return NoChange();
}
// Morph the {node} into a JSPromiseResolve operation.
node->ReplaceInput(0, receiver);
node->ReplaceInput(1, value);
node->ReplaceInput(2, context);
node->ReplaceInput(3, frame_state);
node->ReplaceInput(4, effect);
node->ReplaceInput(5, control);
node->TrimInputCount(6);
NodeProperties::ChangeOp(node, javascript()->PromiseResolve());
return Changed(node);
}
// ES #sec-typedarray-constructors
Reduction JSCallReducer::ReduceTypedArrayConstructor(
Node* node, SharedFunctionInfoRef shared) {
JSConstructNode n(node);
Node* target = n.target();
Node* arg0 = n.ArgumentOrUndefined(0, jsgraph());
Node* arg1 = n.ArgumentOrUndefined(1, jsgraph());
Node* arg2 = n.ArgumentOrUndefined(2, jsgraph());
Node* new_target = n.new_target();
Node* context = n.context();
FrameState frame_state = n.frame_state();
Effect effect = n.effect();
Control control = n.control();
// Insert a construct stub frame into the chain of frame states. This will
// reconstruct the proper frame when deoptimizing within the constructor.
frame_state = CreateConstructInvokeStubFrameState(node, frame_state, shared,
context, common(), graph());
// This continuation just returns the newly created JSTypedArray. We
// pass the_hole as the receiver, just like the builtin construct stub
// does in this case.
Node* const receiver = jsgraph()->TheHoleConstant();
Node* continuation_frame_state = CreateGenericLazyDeoptContinuationFrameState(
jsgraph(), shared, target, context, receiver, frame_state);
Node* result = graph()->NewNode(javascript()->CreateTypedArray(), target,
new_target, arg0, arg1, arg2, context,
continuation_frame_state, effect, control);
return Replace(result);
}
// ES #sec-get-%typedarray%.prototype-@@tostringtag
Reduction JSCallReducer::ReduceTypedArrayPrototypeToStringTag(Node* node) {
Node* receiver = NodeProperties::GetValueInput(node, 1);
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node);
NodeVector values(graph()->zone());
NodeVector effects(graph()->zone());
NodeVector controls(graph()->zone());
Node* smi_check = graph()->NewNode(simplified()->ObjectIsSmi(), receiver);
control = graph()->NewNode(common()->Branch(BranchHint::kFalse), smi_check,
control);
values.push_back(jsgraph()->UndefinedConstant());
effects.push_back(effect);
controls.push_back(graph()->NewNode(common()->IfTrue(), control));
control = graph()->NewNode(common()->IfFalse(), control);
Node* receiver_map = effect =
graph()->NewNode(simplified()->LoadField(AccessBuilder::ForMap()),
receiver, effect, control);
Node* receiver_bit_field2 = effect = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForMapBitField2()), receiver_map,
effect, control);
Node* receiver_elements_kind = graph()->NewNode(
simplified()->NumberShiftRightLogical(),
graph()->NewNode(
simplified()->NumberBitwiseAnd(), receiver_bit_field2,
jsgraph()->ConstantNoHole(Map::Bits2::ElementsKindBits::kMask)),
jsgraph()->ConstantNoHole(Map::Bits2::ElementsKindBits::kShift));
// Offset the elements kind by FIRST_FIXED_TYPED_ARRAY_ELEMENTS_KIND,
// so that the branch cascade below is turned into a simple table
// switch by the ControlFlowOptimizer later.
receiver_elements_kind = graph()->NewNode(
simplified()->NumberSubtract(), receiver_elements_kind,
jsgraph()->ConstantNoHole(FIRST_FIXED_TYPED_ARRAY_ELEMENTS_KIND));
// To be converted into a switch by the ControlFlowOptimizer, the below
// code requires that TYPED_ARRAYS and RAB_GSAB_TYPED_ARRAYS are consecutive.
static_assert(LAST_FIXED_TYPED_ARRAY_ELEMENTS_KIND + 1 ==
FIRST_RAB_GSAB_FIXED_TYPED_ARRAY_ELEMENTS_KIND);
#define TYPED_ARRAY_CASE(Type, type, TYPE, ctype) \
do { \
Node* check = graph()->NewNode( \
simplified()->NumberEqual(), receiver_elements_kind, \
jsgraph()->ConstantNoHole(TYPE##_ELEMENTS - \
FIRST_FIXED_TYPED_ARRAY_ELEMENTS_KIND)); \
control = graph()->NewNode(common()->Branch(), check, control); \
values.push_back(jsgraph()->ConstantNoHole( \
broker()->GetTypedArrayStringTag(TYPE##_ELEMENTS), broker())); \
effects.push_back(effect); \
controls.push_back(graph()->NewNode(common()->IfTrue(), control)); \
control = graph()->NewNode(common()->IfFalse(), control); \
} while (false);
TYPED_ARRAYS(TYPED_ARRAY_CASE)
RAB_GSAB_TYPED_ARRAYS(TYPED_ARRAY_CASE)
#undef TYPED_ARRAY_CASE
values.push_back(jsgraph()->UndefinedConstant());
effects.push_back(effect);
controls.push_back(control);
int const count = static_cast<int>(controls.size());
control = graph()->NewNode(common()->Merge(count), count, &controls.front());
effects.push_back(control);
effect =
graph()->NewNode(common()->EffectPhi(count), count + 1, &effects.front());
values.push_back(control);
Node* value =
graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, count),
count + 1, &values.front());
ReplaceWithValue(node, value, effect, control);
return Replace(value);
}
Reduction JSCallReducer::ReduceArrayBufferViewByteLengthAccessor(
Node* node, InstanceType instance_type, Builtin builtin) {
// TODO(v8:11111): Optimize for JS_RAB_GSAB_DATA_VIEW_TYPE too.
DCHECK(instance_type == JS_TYPED_ARRAY_TYPE ||
instance_type == JS_DATA_VIEW_TYPE);
Node* receiver = NodeProperties::GetValueInput(node, 1);
Effect effect{NodeProperties::GetEffectInput(node)};
Control control{NodeProperties::GetControlInput(node)};
MapInference inference(broker(), receiver, effect);
if (!inference.HaveMaps() ||
!inference.AllOfInstanceTypesAre(instance_type)) {
return inference.NoChange();
}
std::set<ElementsKind> elements_kinds;
bool maybe_rab_gsab = false;
if (instance_type == JS_TYPED_ARRAY_TYPE) {
for (MapRef map : inference.GetMaps()) {
ElementsKind kind = map.elements_kind();
elements_kinds.insert(kind);
if (IsRabGsabTypedArrayElementsKind(kind)) maybe_rab_gsab = true;
}
}
if (!maybe_rab_gsab) {
// We do not perform any change depending on this inference.
Reduction unused_reduction = inference.NoChange();
USE(unused_reduction);
// Call default implementation for non-rab/gsab TAs.
return ReduceArrayBufferViewAccessor(
node, instance_type, AccessBuilder::ForJSArrayBufferViewByteLength(),
builtin);
}
const CallParameters& p = CallParametersOf(node->op());
if (p.speculation_mode() != SpeculationMode::kAllowSpeculation) {
return inference.NoChange();
}
DCHECK(p.feedback().IsValid());
inference.RelyOnMapsPreferStability(dependencies(), jsgraph(), &effect,
control, p.feedback());
const bool depended_on_detaching_protector =
dependencies()->DependOnArrayBufferDetachingProtector();
if (!depended_on_detaching_protector && instance_type == JS_DATA_VIEW_TYPE) {
// DataView prototype accessors throw on detached ArrayBuffers instead of
// return 0, so skip the optimization.
//
// TODO(turbofan): Ideally we would bail out if the buffer is actually
// detached.
return inference.NoChange();
}
JSCallReducerAssembler a(this, node);
TNode<JSTypedArray> typed_array =
TNode<JSTypedArray>::UncheckedCast(receiver);
TNode<Number> length = a.ArrayBufferViewByteLength(
typed_array, instance_type, std::move(elements_kinds), a.ContextInput());
return ReplaceWithSubgraph(&a, length);
}
Reduction JSCallReducer::ReduceArrayBufferViewByteOffsetAccessor(
Node* node, InstanceType instance_type, Builtin builtin) {
// TODO(v8:11111): Optimize for JS_RAB_GSAB_DATA_VIEW_TYPE too.
DCHECK(instance_type == JS_TYPED_ARRAY_TYPE ||
instance_type == JS_DATA_VIEW_TYPE);
Node* receiver = NodeProperties::GetValueInput(node, 1);
Effect effect{NodeProperties::GetEffectInput(node)};
Control control{NodeProperties::GetControlInput(node)};
MapInference inference(broker(), receiver, effect);
if (!inference.HaveMaps() ||
!inference.AllOfInstanceTypesAre(instance_type)) {
return inference.NoChange();
}
std::set<ElementsKind> elements_kinds;
bool maybe_rab_gsab = false;
if (instance_type == JS_TYPED_ARRAY_TYPE) {
for (MapRef map : inference.GetMaps()) {
ElementsKind kind = map.elements_kind();
elements_kinds.insert(kind);
if (IsRabGsabTypedArrayElementsKind(kind)) maybe_rab_gsab = true;
}
}
if (!maybe_rab_gsab) {
// We do not perform any change depending on this inference.
Reduction unused_reduction = inference.NoChange();
USE(unused_reduction);
// Call default implementation for non-rab/gsab TAs.
return ReduceArrayBufferViewAccessor(
node, instance_type, AccessBuilder::ForJSArrayBufferViewByteOffset(),
builtin);
}
// TODO(v8:11111): Optimize for RAG/GSAB TypedArray/DataView.
return inference.NoChange();
}
Reduction JSCallReducer::ReduceTypedArrayPrototypeLength(Node* node) {
Node* receiver = NodeProperties::GetValueInput(node, 1);
Effect effect{NodeProperties::GetEffectInput(node)};
Control control{NodeProperties::GetControlInput(node)};
MapInference inference(broker(), receiver, effect);
if (!inference.HaveMaps() ||
!inference.AllOfInstanceTypesAre(JS_TYPED_ARRAY_TYPE)) {
return inference.NoChange();
}
std::set<ElementsKind> elements_kinds;
bool maybe_rab_gsab = false;
for (MapRef map : inference.GetMaps()) {
ElementsKind kind = map.elements_kind();
elements_kinds.insert(kind);
if (IsRabGsabTypedArrayElementsKind(kind)) maybe_rab_gsab = true;
}
if (!maybe_rab_gsab) {
// We do not perform any change depending on this inference.
Reduction unused_reduction = inference.NoChange();
USE(unused_reduction);
// Call default implementation for non-rab/gsab TAs.
return ReduceArrayBufferViewAccessor(
node, JS_TYPED_ARRAY_TYPE, AccessBuilder::ForJSTypedArrayByteLength(),
Builtin::kTypedArrayPrototypeLength);
}
if (!inference.RelyOnMapsViaStability(dependencies())) {
return inference.NoChange();
}
JSCallReducerAssembler a(this, node);
TNode<JSTypedArray> typed_array =
TNode<JSTypedArray>::UncheckedCast(receiver);
TNode<Number> length = a.TypedArrayLength(
typed_array, std::move(elements_kinds), a.ContextInput());
if (!dependencies()->DependOnArrayBufferDetachingProtector()) {
length =
a.MachineSelectIf<Number>(a.ArrayBufferViewDetachedBit(typed_array))
.Then([&]() { return a.NumberConstant(0); })
.Else([&]() { return length; })
.ExpectFalse()
.Value();
}
return ReplaceWithSubgraph(&a, length);
}
// ES #sec-number.isfinite
Reduction JSCallReducer::ReduceNumberIsFinite(Node* node) {
JSCallNode n(node);
if (n.ArgumentCount() < 1) {
Node* value = jsgraph()->FalseConstant();
ReplaceWithValue(node, value);
return Replace(value);
}
Node* input = n.Argument(0);
Node* value = graph()->NewNode(simplified()->ObjectIsFiniteNumber(), input);
ReplaceWithValue(node, value);
return Replace(value);
}
// ES #sec-number.isfinite
Reduction JSCallReducer::ReduceNumberIsInteger(Node* node) {
JSCallNode n(node);
if (n.ArgumentCount() < 1) {
Node* value = jsgraph()->FalseConstant();
ReplaceWithValue(node, value);
return Replace(value);
}
Node* input = n.Argument(0);
Node* value = graph()->NewNode(simplified()->ObjectIsInteger(), input);
ReplaceWithValue(node, value);
return Replace(value);
}
// ES #sec-number.issafeinteger
Reduction JSCallReducer::ReduceNumberIsSafeInteger(Node* node) {
JSCallNode n(node);
if (n.ArgumentCount() < 1) {
Node* value = jsgraph()->FalseConstant();
ReplaceWithValue(node, value);
return Replace(value);
}
Node* input = n.Argument(0);
Node* value = graph()->NewNode(simplified()->ObjectIsSafeInteger(), input);
ReplaceWithValue(node, value);
return Replace(value);
}
// ES #sec-number.isnan
Reduction JSCallReducer::ReduceNumberIsNaN(Node* node) {
JSCallNode n(node);
if (n.ArgumentCount() < 1) {
Node* value = jsgraph()->FalseConstant();
ReplaceWithValue(node, value);
return Replace(value);
}
Node* input = n.Argument(0);
Node* value = graph()->NewNode(simplified()->ObjectIsNaN(), input);
ReplaceWithValue(node, value);
return Replace(value);
}
Reduction JSCallReducer::ReduceMapPrototypeGet(Node* node) {
// We only optimize if we have target, receiver and key parameters.
JSCallNode n(node);
if (n.ArgumentCount() != 1) return NoChange();
Node* receiver = NodeProperties::GetValueInput(node, 1);
Effect effect{NodeProperties::GetEffectInput(node)};
Control control{NodeProperties::GetControlInput(node)};
Node* key = NodeProperties::GetValueInput(node, 2);
MapInference inference(broker(), receiver, effect);
if (!inference.HaveMaps() || !inference.AllOfInstanceTypesAre(JS_MAP_TYPE)) {
return NoChange();
}
Node* table = effect = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForJSCollectionTable()), receiver,
effect, control);
Node* entry = effect = graph()->NewNode(
simplified()->FindOrderedCollectionEntry(CollectionKind::kMap), table,
key, effect, control);
Node* check = graph()->NewNode(simplified()->NumberEqual(), entry,
jsgraph()->MinusOneConstant());
Node* branch = graph()->NewNode(common()->Branch(), check, control);
// Key not found.
Node* if_true = graph()->NewNode(common()->IfTrue(), branch);
Node* etrue = effect;
Node* vtrue = jsgraph()->UndefinedConstant();
// Key found.
Node* if_false = graph()->NewNode(common()->IfFalse(), branch);
Node* efalse = effect;
Node* vfalse = efalse = graph()->NewNode(
simplified()->LoadElement(AccessBuilder::ForOrderedHashMapEntryValue()),
table, entry, efalse, if_false);
control = graph()->NewNode(common()->Merge(2), if_true, if_false);
Node* value = graph()->NewNode(
common()->Phi(MachineRepresentation::kTagged, 2), vtrue, vfalse, control);
effect = graph()->NewNode(common()->EffectPhi(2), etrue, efalse, control);
ReplaceWithValue(node, value, effect, control);
return Replace(value);
}
namespace {
InstanceType InstanceTypeForCollectionKind(CollectionKind kind) {
switch (kind) {
case CollectionKind::kMap:
return JS_MAP_TYPE;
case CollectionKind::kSet:
return JS_SET_TYPE;
}
UNREACHABLE();
}
} // namespace
Reduction JSCallReducer::ReduceCollectionPrototypeHas(
Node* node, CollectionKind collection_kind) {
// We only optimize if we have target, receiver and key parameters.
JSCallNode n(node);
if (n.ArgumentCount() != 1) return NoChange();
Node* receiver = NodeProperties::GetValueInput(node, 1);
Effect effect{NodeProperties::GetEffectInput(node)};
Control control{NodeProperties::GetControlInput(node)};
Node* key = NodeProperties::GetValueInput(node, 2);
InstanceType instance_type = InstanceTypeForCollectionKind(collection_kind);
MapInference inference(broker(), receiver, effect);
if (!inference.HaveMaps() ||
!inference.AllOfInstanceTypesAre(instance_type)) {
return NoChange();
}
Node* table = effect = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForJSCollectionTable()), receiver,
effect, control);
Node* index = effect = graph()->NewNode(
simplified()->FindOrderedCollectionEntry(collection_kind), table, key,
effect, control);
Node* value = graph()->NewNode(simplified()->NumberEqual(), index,
jsgraph()->MinusOneConstant());
value = graph()->NewNode(simplified()->BooleanNot(), value);
ReplaceWithValue(node, value, effect, control);
return Replace(value);
}
Reduction JSCallReducer::ReduceMapPrototypeHas(Node* node) {
return ReduceCollectionPrototypeHas(node, CollectionKind::kMap);
}
Reduction JSCallReducer::ReduceSetPrototypeHas(Node* node) {
return ReduceCollectionPrototypeHas(node, CollectionKind::kSet);
}
Reduction JSCallReducer::ReduceCollectionIteration(
Node* node, CollectionKind collection_kind, IterationKind iteration_kind) {
DCHECK_EQ(IrOpcode::kJSCall, node->opcode());
Node* receiver = NodeProperties::GetValueInput(node, 1);
Node* context = NodeProperties::GetContextInput(node);
Effect effect{NodeProperties::GetEffectInput(node)};
Control control{NodeProperties::GetControlInput(node)};
InstanceType type = InstanceTypeForCollectionKind(collection_kind);
MapInference inference(broker(), receiver, effect);
if (!inference.HaveMaps() || !inference.AllOfInstanceTypesAre(type)) {
return NoChange();
}
Node* js_create_iterator = effect = graph()->NewNode(
javascript()->CreateCollectionIterator(collection_kind, iteration_kind),
receiver, context, effect, control);
ReplaceWithValue(node, js_create_iterator, effect);
return Replace(js_create_iterator);
}
Reduction JSCallReducer::ReduceCollectionPrototypeSize(
Node* node, CollectionKind collection_kind) {
DCHECK_EQ(IrOpcode::kJSCall, node->opcode());
Node* receiver = NodeProperties::GetValueInput(node, 1);
Effect effect{NodeProperties::GetEffectInput(node)};
Control control{NodeProperties::GetControlInput(node)};
InstanceType type = InstanceTypeForCollectionKind(collection_kind);
MapInference inference(broker(), receiver, effect);
if (!inference.HaveMaps() || !inference.AllOfInstanceTypesAre(type)) {
return NoChange();
}
Node* table = effect = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForJSCollectionTable()), receiver,
effect, control);
Node* value = effect = graph()->NewNode(
simplified()->LoadField(
AccessBuilder::ForOrderedHashMapOrSetNumberOfElements()),
table, effect, control);
ReplaceWithValue(node, value, effect, control);
return Replace(value);
}
Reduction JSCallReducer::ReduceCollectionIteratorPrototypeNext(
Node* node, int entry_size, Handle<HeapObject> empty_collection,
InstanceType collection_iterator_instance_type_first,
InstanceType collection_iterator_instance_type_last) {
JSCallNode n(node);
CallParameters const& p = n.Parameters();
if (p.speculation_mode() != SpeculationMode::kAllowSpeculation) {
return NoChange();
}
Node* receiver = n.receiver();
Node* context = n.context();
Effect effect = n.effect();
Control control = n.control();
// A word of warning to begin with: This whole method might look a bit
// strange at times, but that's mostly because it was carefully handcrafted
// to allow for full escape analysis and scalar replacement of both the
// collection iterator object and the iterator results, including the
// key-value arrays in case of Set/Map entry iteration.
//
// TODO(turbofan): Currently the escape analysis (and the store-load
// forwarding) is unable to eliminate the allocations for the key-value
// arrays in case of Set/Map entry iteration, and we should investigate
// how to update the escape analysis / arrange the graph in a way that
// this becomes possible.
InstanceType receiver_instance_type;
{
MapInference inference(broker(), receiver, effect);
if (!inference.HaveMaps()) return NoChange();
ZoneRefSet<Map> const& receiver_maps = inference.GetMaps();
receiver_instance_type = receiver_maps[0].instance_type();
for (size_t i = 1; i < receiver_maps.size(); ++i) {
if (receiver_maps[i].instance_type() != receiver_instance_type) {
return inference.NoChange();
}
}
if (receiver_instance_type < collection_iterator_instance_type_first ||
receiver_instance_type > collection_iterator_instance_type_last) {
return inference.NoChange();
}
inference.RelyOnMapsPreferStability(dependencies(), jsgraph(), &effect,
control, p.feedback());
}
// Transition the JSCollectionIterator {receiver} if necessary
// (i.e. there were certain mutations while we're iterating).
{
Node* done_loop;
Node* done_eloop;
Node* loop = control =
graph()->NewNode(common()->Loop(2), control, control);
Node* eloop = effect =
graph()->NewNode(common()->EffectPhi(2), effect, effect, loop);
Node* terminate = graph()->NewNode(common()->Terminate(), eloop, loop);
MergeControlToEnd(graph(), common(), terminate);
// Check if reached the final table of the {receiver}.
Node* table = effect = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForJSCollectionIteratorTable()),
receiver, effect, control);
Node* next_table = effect =
graph()->NewNode(simplified()->LoadField(
AccessBuilder::ForOrderedHashMapOrSetNextTable()),
table, effect, control);
Node* check = graph()->NewNode(simplified()->ObjectIsSmi(), next_table);
control =
graph()->NewNode(common()->Branch(BranchHint::kTrue), check, control);
// Abort the {loop} when we reach the final table.
done_loop = graph()->NewNode(common()->IfTrue(), control);
done_eloop = effect;
// Migrate to the {next_table} otherwise.
control = graph()->NewNode(common()->IfFalse(), control);
// Self-heal the {receiver}s index.
Node* index = effect = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForJSCollectionIteratorIndex()),
receiver, effect, control);
Callable const callable =
Builtins::CallableFor(isolate(), Builtin::kOrderedHashTableHealIndex);
auto call_descriptor = Linkage::GetStubCallDescriptor(
graph()->zone(), callable.descriptor(),
callable.descriptor().GetStackParameterCount(),
CallDescriptor::kNoFlags, Operator::kEliminatable);
index = effect =
graph()->NewNode(common()->Call(call_descriptor),
jsgraph()->HeapConstantNoHole(callable.code()), table,
index, jsgraph()->NoContextConstant(), effect);
index = effect = graph()->NewNode(
common()->TypeGuard(TypeCache::Get()->kFixedArrayLengthType), index,
effect, control);
// Update the {index} and {table} on the {receiver}.
effect = graph()->NewNode(
simplified()->StoreField(AccessBuilder::ForJSCollectionIteratorIndex()),
receiver, index, effect, control);
effect = graph()->NewNode(
simplified()->StoreField(AccessBuilder::ForJSCollectionIteratorTable()),
receiver, next_table, effect, control);
// Tie the knot.
loop->ReplaceInput(1, control);
eloop->ReplaceInput(1, effect);
control = done_loop;
effect = done_eloop;
}
// Get current index and table from the JSCollectionIterator {receiver}.
Node* index = effect = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForJSCollectionIteratorIndex()),
receiver, effect, control);
Node* table = effect = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForJSCollectionIteratorTable()),
receiver, effect, control);
// Create the {JSIteratorResult} first to ensure that we always have
// a dominating Allocate node for the allocation folding phase.
Node* iterator_result = effect = graph()->NewNode(
javascript()->CreateIterResultObject(), jsgraph()->UndefinedConstant(),
jsgraph()->TrueConstant(), context, effect);
// Look for the next non-holey key, starting from {index} in the {table}.
Node* controls[2];
Node* effects[3];
{
// Compute the currently used capacity.
Node* number_of_buckets = effect = graph()->NewNode(
simplified()->LoadField(
AccessBuilder::ForOrderedHashMapOrSetNumberOfBuckets()),
table, effect, control);
Node* number_of_elements = effect = graph()->NewNode(
simplified()->LoadField(
AccessBuilder::ForOrderedHashMapOrSetNumberOfElements()),
table, effect, control);
Node* number_of_deleted_elements = effect = graph()->NewNode(
simplified()->LoadField(
AccessBuilder::ForOrderedHashMapOrSetNumberOfDeletedElements()),
table, effect, control);
Node* used_capacity =
graph()->NewNode(simplified()->NumberAdd(), number_of_elements,
number_of_deleted_elements);
// Skip holes and update the {index}.
Node* loop = graph()->NewNode(common()->Loop(2), control, control);
Node* eloop =
graph()->NewNode(common()->EffectPhi(2), effect, effect, loop);
Node* terminate = graph()->NewNode(common()->Terminate(), eloop, loop);
MergeControlToEnd(graph(), common(), terminate);
Node* iloop = graph()->NewNode(
common()->Phi(MachineRepresentation::kTagged, 2), index, index, loop);
index = effect = graph()->NewNode(
common()->TypeGuard(TypeCache::Get()->kFixedArrayLengthType), iloop,
eloop, control);
{
Node* check0 = graph()->NewNode(simplified()->NumberLessThan(), index,
used_capacity);
Node* branch0 =
graph()->NewNode(common()->Branch(BranchHint::kTrue), check0, loop);
Node* if_false0 = graph()->NewNode(common()->IfFalse(), branch0);
Node* efalse0 = effect;
{
// Mark the {receiver} as exhausted.
efalse0 = graph()->NewNode(
simplified()->StoreField(
AccessBuilder::ForJSCollectionIteratorTable()),
receiver, jsgraph()->HeapConstantNoHole(empty_collection), efalse0,
if_false0);
controls[0] = if_false0;
effects[0] = efalse0;
}
Node* if_true0 = graph()->NewNode(common()->IfTrue(), branch0);
Node* etrue0 = effect;
{
// Load the key of the entry.
static_assert(OrderedHashMap::HashTableStartIndex() ==
OrderedHashSet::HashTableStartIndex());
Node* entry_start_position = graph()->NewNode(
simplified()->NumberAdd(),
graph()->NewNode(
simplified()->NumberAdd(),
graph()->NewNode(simplified()->NumberMultiply(), index,
jsgraph()->ConstantNoHole(entry_size)),
number_of_buckets),
jsgraph()->ConstantNoHole(OrderedHashMap::HashTableStartIndex()));
Node* entry_key = etrue0 = graph()->NewNode(
simplified()->LoadElement(AccessBuilder::ForFixedArrayElement()),
table, entry_start_position, etrue0, if_true0);
// Advance the index.
index = graph()->NewNode(simplified()->NumberAdd(), index,
jsgraph()->OneConstant());
Node* check1 =
graph()->NewNode(simplified()->ReferenceEqual(), entry_key,
jsgraph()->HashTableHoleConstant());
Node* branch1 = graph()->NewNode(common()->Branch(BranchHint::kFalse),
check1, if_true0);
{
// Abort loop with resulting value.
control = graph()->NewNode(common()->IfFalse(), branch1);
effect = etrue0;
Node* value = effect =
graph()->NewNode(common()->TypeGuard(Type::NonInternal()),
entry_key, effect, control);
Node* done = jsgraph()->FalseConstant();
// Advance the index on the {receiver}.
effect = graph()->NewNode(
simplified()->StoreField(
AccessBuilder::ForJSCollectionIteratorIndex()),
receiver, index, effect, control);
// The actual {value} depends on the {receiver} iteration type.
switch (receiver_instance_type) {
case JS_MAP_KEY_ITERATOR_TYPE:
case JS_SET_VALUE_ITERATOR_TYPE:
break;
case JS_SET_KEY_VALUE_ITERATOR_TYPE:
value = effect =
graph()->NewNode(javascript()->CreateKeyValueArray(), value,
value, context, effect);
break;
case JS_MAP_VALUE_ITERATOR_TYPE:
value = effect = graph()->NewNode(
simplified()->LoadElement(
AccessBuilder::ForFixedArrayElement()),
table,
graph()->NewNode(
simplified()->NumberAdd(), entry_start_position,
jsgraph()->ConstantNoHole(OrderedHashMap::kValueOffset)),
effect, control);
break;
case JS_MAP_KEY_VALUE_ITERATOR_TYPE:
value = effect = graph()->NewNode(
simplified()->LoadElement(
AccessBuilder::ForFixedArrayElement()),
table,
graph()->NewNode(
simplified()->NumberAdd(), entry_start_position,
jsgraph()->ConstantNoHole(OrderedHashMap::kValueOffset)),
effect, control);
value = effect =
graph()->NewNode(javascript()->CreateKeyValueArray(),
entry_key, value, context, effect);
break;
default:
UNREACHABLE();
}
// Store final {value} and {done} into the {iterator_result}.
effect =
graph()->NewNode(simplified()->StoreField(
AccessBuilder::ForJSIteratorResultValue()),
iterator_result, value, effect, control);
effect =
graph()->NewNode(simplified()->StoreField(
AccessBuilder::ForJSIteratorResultDone()),
iterator_result, done, effect, control);
controls[1] = control;
effects[1] = effect;
}
// Continue with next loop index.
loop->ReplaceInput(1, graph()->NewNode(common()->IfTrue(), branch1));
eloop->ReplaceInput(1, etrue0);
iloop->ReplaceInput(1, index);
}
}
control = effects[2] = graph()->NewNode(common()->Merge(2), 2, controls);
effect = graph()->NewNode(common()->EffectPhi(2), 3, effects);
}
// Yield the final {iterator_result}.
ReplaceWithValue(node, iterator_result, effect, control);
return Replace(iterator_result);
}
Reduction JSCallReducer::ReduceArrayBufferIsView(Node* node) {
JSCallNode n(node);
Node* value = n.ArgumentOrUndefined(0, jsgraph());
RelaxEffectsAndControls(node);
node->ReplaceInput(0, value);
node->TrimInputCount(1);
NodeProperties::ChangeOp(node, simplified()->ObjectIsArrayBufferView());
return Changed(node);
}
Reduction JSCallReducer::ReduceArrayBufferViewAccessor(
Node* node, InstanceType instance_type, FieldAccess const& access,
Builtin builtin) {
// TODO(v8:11111): Optimize for JS_RAB_GSAB_DATA_VIEW_TYPE too.
TNode<JSArrayBufferView> receiver = TNode<JSArrayBufferView>::UncheckedCast(
NodeProperties::GetValueInput(node, 1));
Effect effect{NodeProperties::GetEffectInput(node)};
MapInference inference(broker(), receiver, effect);
if (!inference.HaveMaps() ||
!inference.AllOfInstanceTypesAre(instance_type)) {
return inference.NoChange();
}
DCHECK_IMPLIES((builtin == Builtin::kTypedArrayPrototypeLength ||
builtin == Builtin::kTypedArrayPrototypeByteLength),
base::none_of(inference.GetMaps(), [](MapRef map) {
return IsRabGsabTypedArrayElementsKind(map.elements_kind());
}));
if (!inference.RelyOnMapsViaStability(dependencies())) {
return inference.NoChange();
}
const bool depended_on_detaching_protector =
dependencies()->DependOnArrayBufferDetachingProtector();
if (!depended_on_detaching_protector && instance_type == JS_DATA_VIEW_TYPE) {
// DataView prototype accessors throw on detached ArrayBuffers instead of
// return 0, so skip the optimization.
//
// TODO(turbofan): Ideally we would bail out if the buffer is actually
// detached.
return inference.NoChange();
}
JSCallReducerAssembler a(this, node, effect);
// Load the {receiver}s field.
DCHECK_EQ(access.machine_type.representation(),
MachineType::PointerRepresentation());
TNode<UintPtrT> value = a.EnterMachineGraph<UintPtrT>(
a.LoadField<UintPtrT>(access, receiver), UseInfo::Word());
// See if we can skip the detaching check.
if (!depended_on_detaching_protector) {
// Check whether {receiver}s JSArrayBuffer was detached.
//
// TODO(turbofan): Ideally we would bail out here if the {receiver}s
// JSArrayBuffer was detached, but there's no way to guard against
// deoptimization loops right now, since the JSCall {node} is usually
// created from a LOAD_IC inlining, and so there's no CALL_IC slot
// from which we could use the speculation bit.
value = a.MachineSelect<UintPtrT>(a.ArrayBufferViewDetachedBit(receiver),
a.UintPtrConstant(0), value,
BranchHint::kFalse);
}
if (builtin == Builtin::kTypedArrayPrototypeLength) {
TNode<UintPtrT> byte_length = value;
// Divide the byte length by element size.
TNode<Map> map = a.LoadMap(TNode<HeapObject>::UncheckedCast(receiver));
TNode<Uint32T> elements_kind = a.LoadElementsKind(map);
TNode<Uint32T> shift = a.LookupByteShiftForElementsKind(elements_kind);
value = TNode<UintPtrT>::UncheckedCast(
a.WordShr(byte_length, a.ChangeUint32ToUintPtr(shift)));
}
TNode<Number> result =
a.ExitMachineGraph<Number>(value, MachineType::PointerRepresentation(),
TypeCache::Get()->kJSTypedArrayLengthType);
return ReplaceWithSubgraph(&a, result);
}
Reduction JSCallReducer::ReduceDataViewAccess(Node* node, DataViewAccess access,
ExternalArrayType element_type) {
// TODO(v8:11111): Optimize for JS_RAB_GSAB_DATA_VIEW_TYPE too.
JSCallNode n(node);
CallParameters const& p = n.Parameters();
size_t const element_size = ExternalArrayElementSize(element_type);
Effect effect = n.effect();
Control control = n.control();
Node* receiver = n.receiver();
Node* offset = n.ArgumentOr(0, jsgraph()->ZeroConstant());
Node* value = nullptr;
if (!Is64() && (element_type == kExternalBigInt64Array ||
element_type == kExternalBigUint64Array)) {
return NoChange();
}
if (access == DataViewAccess::kSet) {
value = n.ArgumentOrUndefined(1, jsgraph());
}
const int endian_index = (access == DataViewAccess::kGet ? 1 : 2);
Node* is_little_endian =
n.ArgumentOr(endian_index, jsgraph()->FalseConstant());
if (p.speculation_mode() != SpeculationMode::kAllowSpeculation) {
return NoChange();
}
// Only do stuff if the {receiver} is really a DataView.
MapInference inference(broker(), receiver, effect);
if (!inference.HaveMaps() ||
!inference.AllOfInstanceTypesAre(JS_DATA_VIEW_TYPE)) {
return NoChange();
}
// Check that the {offset} is within range for the {receiver}.
HeapObjectMatcher m(receiver);
if (m.HasResolvedValue() && m.Ref(broker()).IsJSDataView()) {
// We only deal with DataViews here whose [[ByteLength]] is at least
// {element_size}, as for all other DataViews it'll be out-of-bounds.
JSDataViewRef dataview = m.Ref(broker()).AsJSDataView();
size_t length = dataview.byte_length();
if (length < element_size) return NoChange();
// Check that the {offset} is within range of the {length}.
Node* byte_length = jsgraph()->ConstantNoHole(length - (element_size - 1));
offset = effect =
graph()->NewNode(simplified()->CheckBounds(
p.feedback(), CheckBoundsFlag::kAllow64BitBounds),
offset, byte_length, effect, control);
} else {
// We only deal with DataViews here that have Smi [[ByteLength]]s.
Node* byte_length = effect =
graph()->NewNode(simplified()->LoadField(
AccessBuilder::ForJSArrayBufferViewByteLength()),
receiver, effect, control);
if (element_size > 1) {
// For non-byte accesses we also need to check that the {offset}
// plus the {element_size}-1 fits within the given {byte_length}.
// So to keep this as a single check on the {offset}, we subtract
// the {element_size}-1 from the {byte_length} here (clamped to
// positive safe integer range), and perform a check against that
// with the {offset} below.
byte_length = graph()->NewNode(
simplified()->NumberMax(), jsgraph()->ZeroConstant(),
graph()->NewNode(simplified()->NumberSubtract(), byte_length,
jsgraph()->ConstantNoHole(element_size - 1)));
}
// Check that the {offset} is within range of the {byte_length}.
offset = effect =
graph()->NewNode(simplified()->CheckBounds(
p.feedback(), CheckBoundsFlag::kAllow64BitBounds),
offset, byte_length, effect, control);
}
// Coerce {is_little_endian} to boolean.
is_little_endian =
graph()->NewNode(simplified()->ToBoolean(), is_little_endian);
// Coerce {value} to Number.
if (access == DataViewAccess::kSet) {
if (element_type == kExternalBigInt64Array ||
element_type == kExternalBigUint64Array) {
value = effect =
graph()->NewNode(simplified()->SpeculativeToBigInt(
BigIntOperationHint::kBigInt, p.feedback()),
value, effect, control);
} else {
value = effect = graph()->NewNode(
simplified()->SpeculativeToNumber(
NumberOperationHint::kNumberOrOddball, p.feedback()),
value, effect, control);
}
}
// We need to retain either the {receiver} itself or its backing
// JSArrayBuffer to make sure that the GC doesn't collect the raw
// memory. We default to {receiver} here, and only use the buffer
// if we anyways have to load it (to reduce register pressure).
Node* buffer_or_receiver = receiver;
if (!dependencies()->DependOnArrayBufferDetachingProtector()) {
// Get the underlying buffer and check that it has not been detached.
Node* buffer = effect = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForJSArrayBufferViewBuffer()),
receiver, effect, control);
// Bail out if the {buffer} was detached.
Node* buffer_bit_field = effect = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForJSArrayBufferBitField()),
buffer, effect, control);
Node* check = graph()->NewNode(
simplified()->NumberEqual(),
graph()->NewNode(
simplified()->NumberBitwiseAnd(), buffer_bit_field,
jsgraph()->ConstantNoHole(JSArrayBuffer::WasDetachedBit::kMask)),
jsgraph()->ZeroConstant());
effect = graph()->NewNode(
simplified()->CheckIf(DeoptimizeReason::kArrayBufferWasDetached,
p.feedback()),
check, effect, control);
// We can reduce register pressure by holding on to the {buffer}
// now to retain the backing store memory.
buffer_or_receiver = buffer;
}
// Load the {receiver}s data pointer.
Node* data_pointer = effect = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForJSDataViewDataPointer()),
receiver, effect, control);
switch (access) {
case DataViewAccess::kGet:
// Perform the load.
value = effect = graph()->NewNode(
simplified()->LoadDataViewElement(element_type), buffer_or_receiver,
data_pointer, offset, is_little_endian, effect, control);
break;
case DataViewAccess::kSet:
// Perform the store.
effect = graph()->NewNode(
simplified()->StoreDataViewElement(element_type), buffer_or_receiver,
data_pointer, offset, value, is_little_endian, effect, control);
value = jsgraph()->UndefinedConstant();
break;
}
ReplaceWithValue(node, value, effect, control);
return Changed(value);
}
// ES6 section 18.2.2 isFinite ( number )
Reduction JSCallReducer::ReduceGlobalIsFinite(Node* node) {
JSCallNode n(node);
CallParameters const& p = n.Parameters();
if (p.speculation_mode() != SpeculationMode::kAllowSpeculation) {
return NoChange();
}
if (n.ArgumentCount() < 1) {
Node* value = jsgraph()->FalseConstant();
ReplaceWithValue(node, value);
return Replace(value);
}
Effect effect = n.effect();
Control control = n.control();
Node* input = n.Argument(0);
input = effect =
graph()->NewNode(simplified()->SpeculativeToNumber(
NumberOperationHint::kNumberOrOddball, p.feedback()),
input, effect, control);
Node* value = graph()->NewNode(simplified()->NumberIsFinite(), input);
ReplaceWithValue(node, value, effect);
return Replace(value);
}
// ES6 section 18.2.3 isNaN ( number )
Reduction JSCallReducer::ReduceGlobalIsNaN(Node* node) {
JSCallNode n(node);
CallParameters const& p = n.Parameters();
if (p.speculation_mode() != SpeculationMode::kAllowSpeculation) {
return NoChange();
}
if (n.ArgumentCount() < 1) {
Node* value = jsgraph()->TrueConstant();
ReplaceWithValue(node, value);
return Replace(value);
}
Effect effect = n.effect();
Control control = n.control();
Node* input = n.Argument(0);
input = effect =
graph()->NewNode(simplified()->SpeculativeToNumber(
NumberOperationHint::kNumberOrOddball, p.feedback()),
input, effect, control);
Node* value = graph()->NewNode(simplified()->NumberIsNaN(), input);
ReplaceWithValue(node, value, effect);
return Replace(value);
}
// ES6 section 20.3.4.10 Date.prototype.getTime ( )
Reduction JSCallReducer::ReduceDatePrototypeGetTime(Node* node) {
Node* receiver = NodeProperties::GetValueInput(node, 1);
Effect effect{NodeProperties::GetEffectInput(node)};
Control control{NodeProperties::GetControlInput(node)};
MapInference inference(broker(), receiver, effect);
if (!inference.HaveMaps() || !inference.AllOfInstanceTypesAre(JS_DATE_TYPE)) {
return NoChange();
}
Node* value = effect =
graph()->NewNode(simplified()->LoadField(AccessBuilder::ForJSDateValue()),
receiver, effect, control);
ReplaceWithValue(node, value, effect, control);
return Replace(value);
}
// https://tc39.es/ecma262/#sec-date.prototype.getdate
// https://tc39.es/ecma262/#sec-date.prototype.getday
// https://tc39.es/ecma262/#sec-date.prototype.getfullyear
// https://tc39.es/ecma262/#sec-date.prototype.gethours
// https://tc39.es/ecma262/#sec-date.prototype.getminutes
// https://tc39.es/ecma262/#sec-date.prototype.getmonth
// https://tc39.es/ecma262/#sec-date.prototype.getseconds
Reduction JSCallReducer::ReduceDatePrototypeGetField(Node* node,
JSDate::FieldIndex field) {
DCHECK_LT(field, JSDate::kFirstUncachedField);
if (!v8_flags.turbofan_inline_date_accessors) return NoChange();
Node* receiver = NodeProperties::GetValueInput(node, 1);
Effect effect{NodeProperties::GetEffectInput(node)};
Control control{NodeProperties::GetControlInput(node)};
MapInference inference(broker(), receiver, effect);
if (!inference.HaveMaps() || !inference.AllOfInstanceTypesAre(JS_DATE_TYPE)) {
return NoChange();
}
if (!dependencies()->DependOnNoDateTimeConfigurationChangeProtector()) {
return NoChange();
}
#define __ gasm.
JSGraphAssembler gasm(broker(), jsgraph(), jsgraph()->zone(),
BranchSemantics::kJS);
gasm.InitializeEffectControl(effect, control);
Node* value =
__ LoadField<Object>(AccessBuilder::ForJSDateField(field),
TNode<HeapObject>::UncheckedCast(receiver));
ReplaceWithValue(node, value, gasm.effect(), gasm.control());
return Replace(value);
#undef __
}
// ES6 section 20.3.3.1 Date.now ( )
Reduction JSCallReducer::ReduceDateNow(Node* node) {
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node);
Node* value = effect =
graph()->NewNode(simplified()->DateNow(), effect, control);
ReplaceWithValue(node, value, effect, control);
return Replace(value);
}
// ES6 section 20.1.2.13 Number.parseInt ( string, radix )
Reduction JSCallReducer::ReduceNumberParseInt(Node* node) {
JSCallNode n(node);
if (n.ArgumentCount() < 1) {
Node* value = jsgraph()->NaNConstant();
ReplaceWithValue(node, value);
return Replace(value);
}
Effect effect = n.effect();
Control control = n.control();
Node* context = n.context();
FrameState frame_state = n.frame_state();
Node* object = n.Argument(0);
Node* radix = n.ArgumentOrUndefined(1, jsgraph());
// Try constant-folding when input is a string constant.
HeapObjectMatcher object_matcher(object);
HeapObjectMatcher radix_object_matcher(radix);
NumberMatcher radix_number_matcher(radix);
if (object_matcher.HasResolvedValue() &&
object_matcher.Ref(broker()).IsString() &&
(radix_object_matcher.Is(factory()->undefined_value()) ||
radix_number_matcher.HasResolvedValue())) {
StringRef input_value = object_matcher.Ref(broker()).AsString();
// {undefined} is treated same as 0.
int radix_value = radix_object_matcher.Is(factory()->undefined_value())
? 0
: DoubleToInt32(radix_number_matcher.ResolvedValue());
if (radix_value != 0 && (radix_value < 2 || radix_value > 36)) {
Node* value = jsgraph()->NaNConstant();
ReplaceWithValue(node, value);
return Replace(value);
}
std::optional<double> number = input_value.ToInt(broker(), radix_value);
if (number.has_value()) {
Node* result = graph()->NewNode(common()->NumberConstant(number.value()));
ReplaceWithValue(node, result);
return Replace(result);
}
}
node->ReplaceInput(0, object);
node->ReplaceInput(1, radix);
node->ReplaceInput(2, context);
node->ReplaceInput(3, frame_state);
node->ReplaceInput(4, effect);
node->ReplaceInput(5, control);
node->TrimInputCount(6);
NodeProperties::ChangeOp(node, javascript()->ParseInt());
return Changed(node);
}
Reduction JSCallReducer::ReduceRegExpPrototypeTest(Node* node) {
JSCallNode n(node);
CallParameters const& p = n.Parameters();
if (v8_flags.force_slow_path) return NoChange();
if (n.ArgumentCount() < 1) return NoChange();
if (p.speculation_mode() != SpeculationMode::kAllowSpeculation) {
return NoChange();
}
Effect effect = n.effect();
Control control = n.control();
Node* regexp = n.receiver();
// Only the initial JSRegExp map is valid here, since the following lastIndex
// check as well as the lowered builtin call rely on a known location of the
// lastIndex field.
MapRef regexp_initial_map =
native_context().regexp_function(broker()).initial_map(broker());
MapInference inference(broker(), regexp, effect);
if (!inference.Is(regexp_initial_map)) return inference.NoChange();
ZoneRefSet<Map> const& regexp_maps = inference.GetMaps();
ZoneVector<PropertyAccessInfo> access_infos(graph()->zone());
AccessInfoFactory access_info_factory(broker(), graph()->zone());
for (MapRef map : regexp_maps) {
access_infos.push_back(broker()->GetPropertyAccessInfo(
map, broker()->exec_string(), AccessMode::kLoad));
}
PropertyAccessInfo ai_exec =
access_info_factory.FinalizePropertyAccessInfosAsOne(access_infos,
AccessMode::kLoad);
if (ai_exec.IsInvalid()) return inference.NoChange();
if (!ai_exec.IsFastDataConstant()) return inference.NoChange();
// Do not reduce if the exec method is not on the prototype chain.
OptionalJSObjectRef holder = ai_exec.holder();
if (!holder.has_value()) return inference.NoChange();
// Bail out if the exec method is not the original one.
if (ai_exec.field_representation().IsDouble()) return inference.NoChange();
OptionalObjectRef constant = holder->GetOwnFastConstantDataProperty(
broker(), ai_exec.field_representation(), ai_exec.field_index(),
dependencies());
if (!constant.has_value() ||
!constant->equals(native_context().regexp_exec_function(broker()))) {
return inference.NoChange();
}
// Add proper dependencies on the {regexp}s [[Prototype]]s.
dependencies()->DependOnStablePrototypeChains(
ai_exec.lookup_start_object_maps(), kStartAtPrototype, holder.value());
inference.RelyOnMapsPreferStability(dependencies(), jsgraph(), &effect,
control, p.feedback());
Node* context = n.context();
FrameState frame_state = n.frame_state();
Node* search = n.Argument(0);
Node* search_string = effect = graph()->NewNode(
simplified()->CheckString(p.feedback()), search, effect, control);
Node* lastIndex = effect = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForJSRegExpLastIndex()), regexp,
effect, control);
Node* lastIndexSmi = effect = graph()->NewNode(
simplified()->CheckSmi(p.feedback()), lastIndex, effect, control);
Node* is_positive = graph()->NewNode(simplified()->NumberLessThanOrEqual(),
jsgraph()->ZeroConstant(), lastIndexSmi);
effect = graph()->NewNode(
simplified()->CheckIf(DeoptimizeReason::kNotASmi, p.feedback()),
is_positive, effect, control);
node->ReplaceInput(0, regexp);
node->ReplaceInput(1, search_string);
node->ReplaceInput(2, context);
node->ReplaceInput(3, frame_state);
node->ReplaceInput(4, effect);
node->ReplaceInput(5, control);
node->TrimInputCount(6);
NodeProperties::ChangeOp(node, javascript()->RegExpTest());
return Changed(node);
}
// ES section #sec-number-constructor
Reduction JSCallReducer::ReduceNumberConstructor(Node* node) {
JSCallNode n(node);
Node* target = n.target();
Node* receiver = n.receiver();
Node* value = n.ArgumentOr(0, jsgraph()->ZeroConstant());
Node* context = n.context();
FrameState frame_state = n.frame_state();
// Create the artificial frame state in the middle of the Number constructor.
SharedFunctionInfoRef shared_info =
native_context().number_function(broker()).shared(broker());
Node* continuation_frame_state = CreateGenericLazyDeoptContinuationFrameState(
jsgraph(), shared_info, target, context, receiver, frame_state);
// Convert the {value} to a Number.
NodeProperties::ReplaceValueInputs(node, value);
NodeProperties::ChangeOp(node, javascript()->ToNumberConvertBigInt());
NodeProperties::ReplaceFrameStateInput(node, continuation_frame_state);
return Changed(node);
}
// ES section #sec-bigint-constructor
Reduction JSCallReducer::ReduceBigIntConstructor(Node* node) {
if (!jsgraph()->machine()->Is64()) return NoChange();
JSCallNode n(node);
if (n.ArgumentCount() < 1) {
return NoChange();
}
Node* target = n.target();
Node* receiver = n.receiver();
Node* value = n.Argument(0);
Node* context = n.context();
FrameState frame_state = n.frame_state();
// Create the artificial frame state in the middle of the BigInt constructor.
SharedFunctionInfoRef shared_info =
native_context().bigint_function(broker()).shared(broker());
Node* continuation_frame_state = CreateGenericLazyDeoptContinuationFrameState(
jsgraph(), shared_info, target, context, receiver, frame_state);
// Convert the {value} to a BigInt.
NodeProperties::ReplaceValueInputs(node, value);
NodeProperties::ChangeOp(node, javascript()->ToBigIntConvertNumber());
NodeProperties::ReplaceFrameStateInput(node, continuation_frame_state);
return Changed(node);
}
Reduction JSCallReducer::ReduceBigIntAsN(Node* node, Builtin builtin) {
DCHECK(builtin == Builtin::kBigIntAsIntN ||
builtin == Builtin::kBigIntAsUintN);
if (!jsgraph()->machine()->Is64()) return NoChange();
JSCallNode n(node);
CallParameters const& p = n.Parameters();
if (p.speculation_mode() != SpeculationMode::kAllowSpeculation) {
return NoChange();
}
if (n.ArgumentCount() < 2) {
return NoChange();
}
Effect effect = n.effect();
Control control = n.control();
Node* bits = n.Argument(0);
Node* value = n.Argument(1);
NumberMatcher matcher(bits);
if (matcher.IsInteger() && matcher.IsInRange(0, 64)) {
const int bits_value = static_cast<int>(matcher.ResolvedValue());
value = effect = graph()->NewNode(
(builtin == Builtin::kBigIntAsIntN
? simplified()->SpeculativeBigIntAsIntN(bits_value, p.feedback())
: simplified()->SpeculativeBigIntAsUintN(bits_value,
p.feedback())),
value, effect, control);
ReplaceWithValue(node, value, effect);
return Replace(value);
}
return NoChange();
}
std::optional<Reduction> JSCallReducer::TryReduceJSCallMathMinMaxWithArrayLike(
Node* node) {
if (!v8_flags.turbo_optimize_math_minmax) return std::nullopt;
JSCallWithArrayLikeNode n(node);
CallParameters const& p = n.Parameters();
Node* target = n.target();
Effect effect = n.effect();
Control control = n.control();
if (p.speculation_mode() != SpeculationMode::kAllowSpeculation) {
return std::nullopt;
}
if (n.ArgumentCount() != 1) {
return std::nullopt;
}
if (!dependencies()->DependOnNoElementsProtector()) {
return std::nullopt;
}
// These ops are handled by ReduceCallOrConstructWithArrayLikeOrSpread.
// IrOpcode::kJSCreateEmptyLiteralArray is not included, since arguments_list
// for Math.min/min is not likely to keep empty.
Node* arguments_list = n.Argument(0);
if (arguments_list->opcode() == IrOpcode::kJSCreateLiteralArray ||
arguments_list->opcode() == IrOpcode::kJSCreateArguments) {
return std::nullopt;
}
HeapObjectMatcher m(target);
if (m.HasResolvedValue()) {
ObjectRef target_ref = m.Ref(broker());
if (target_ref.IsJSFunction()) {
JSFunctionRef function = target_ref.AsJSFunction();
// Don't inline cross native context.
if (!function.native_context(broker()).equals(native_context())) {
return std::nullopt;
}
SharedFunctionInfoRef shared = function.shared(broker());
Builtin builtin =
shared.HasBuiltinId() ? shared.builtin_id() : Builtin::kNoBuiltinId;
if (builtin == Builtin::kMathMax || builtin == Builtin::kMathMin) {
return ReduceJSCallMathMinMaxWithArrayLike(node, builtin);
} else {
return std::nullopt;
}
}
}
// Try specialize the JSCallWithArrayLike node with feedback target.
if (ShouldUseCallICFeedback(target) &&
p.feedback_relation() == CallFeedbackRelation::kTarget &&
p.feedback().IsValid()) {
ProcessedFeedback const& feedback =
broker()->GetFeedbackForCall(p.feedback());
if (feedback.IsInsufficient()) {
return std::nullopt;
}
OptionalHeapObjectRef feedback_target = feedback.AsCall().target();
if (feedback_target.has_value() &&
feedback_target->map(broker()).is_callable()) {
Node* target_function =
jsgraph()->ConstantNoHole(*feedback_target, broker());
ObjectRef target_ref = feedback_target.value();
if (!target_ref.IsJSFunction()) {
return std::nullopt;
}
JSFunctionRef function = target_ref.AsJSFunction();
SharedFunctionInfoRef shared = function.shared(broker());
Builtin builtin =
shared.HasBuiltinId() ? shared.builtin_id() : Builtin::kNoBuiltinId;
if (builtin == Builtin::kMathMax || builtin == Builtin::kMathMin) {
// Check that the {target} is still the {target_function}.
Node* check = graph()->NewNode(simplified()->ReferenceEqual(), target,
target_function);
effect = graph()->NewNode(
simplified()->CheckIf(DeoptimizeReason::kWrongCallTarget), check,
effect, control);
// Specialize the JSCallWithArrayLike node to the {target_function}.
NodeProperties::ReplaceValueInput(node, target_function,
n.TargetIndex());
NodeProperties::ReplaceEffectInput(node, effect);
// Try to further reduce the Call MathMin/Max with double array.
return Changed(node).FollowedBy(
ReduceJSCallMathMinMaxWithArrayLike(node, builtin));
}
}
}
return std::nullopt;
}
Reduction JSCallReducer::ReduceJSCallMathMinMaxWithArrayLike(Node* node,
Builtin builtin) {
JSCallWithArrayLikeNode n(node);
DCHECK_NE(n.Parameters().speculation_mode(),
SpeculationMode::kDisallowSpeculation);
DCHECK_EQ(n.ArgumentCount(), 1);
JSCallReducerAssembler a(this, node);
Node* subgraph = a.ReduceJSCallMathMinMaxWithArrayLike(builtin);
return ReplaceWithSubgraph(&a, subgraph);
}
#ifdef V8_ENABLE_CONTINUATION_PRESERVED_EMBEDDER_DATA
Reduction JSCallReducer::ReduceGetContinuationPreservedEmbedderData(
Node* node) {
JSCallNode n(node);
Effect effect = n.effect();
Control control = n.control();
Node* value = effect = graph()->NewNode(
simplified()->GetContinuationPreservedEmbedderData(), effect);
ReplaceWithValue(node, value, effect, control);
return Replace(node);
}
Reduction JSCallReducer::ReduceSetContinuationPreservedEmbedderData(
Node* node) {
JSCallNode n(node);
Effect effect = n.effect();
Control control = n.control();
if (n.ArgumentCount() == 0) return NoChange();
effect =
graph()->NewNode(simplified()->SetContinuationPreservedEmbedderData(),
n.Argument(0), effect);
Node* value = jsgraph()->UndefinedConstant();
ReplaceWithValue(node, value, effect, control);
return Replace(node);
}
#endif // V8_ENABLE_CONTINUATION_PRESERVED_EMBEDDER_DATA
CompilationDependencies* JSCallReducer::dependencies() const {
return broker()->dependencies();
}
TFGraph* JSCallReducer::graph() const { return jsgraph()->graph(); }
Isolate* JSCallReducer::isolate() const { return jsgraph()->isolate(); }
Factory* JSCallReducer::factory() const { return isolate()->factory(); }
NativeContextRef JSCallReducer::native_context() const {
return broker()->target_native_context();
}
CommonOperatorBuilder* JSCallReducer::common() const {
return jsgraph()->common();
}
JSOperatorBuilder* JSCallReducer::javascript() const {
return jsgraph()->javascript();
}
SimplifiedOperatorBuilder* JSCallReducer::simplified() const {
return jsgraph()->simplified();
}
} // namespace compiler
} // namespace internal
} // namespace v8