blob: 7088fb0d43f2a94049b43639077ce4f6b9ffe1d4 [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 "include/v8-fast-api-calls.h"
#include "src/api/api-inl.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.h"
#include "src/compiler/compilation-dependencies.h"
#include "src/compiler/feedback-source.h"
#include "src/compiler/graph-assembler.h"
#include "src/compiler/js-graph.h"
#include "src/compiler/linkage.h"
#include "src/compiler/map-inference.h"
#include "src/compiler/node-matchers.h"
#include "src/compiler/property-access-builder.h"
#include "src/compiler/simplified-operator.h"
#include "src/compiler/type-cache.h"
#include "src/ic/call-optimization.h"
#include "src/logging/counters.h"
#include "src/objects/arguments-inl.h"
#include "src/objects/feedback-vector-inl.h"
#include "src/objects/js-array-buffer-inl.h"
#include "src/objects/js-array-inl.h"
#include "src/objects/js-objects.h"
#include "src/objects/objects-inl.h"
#include "src/objects/ordered-hash-table.h"
namespace v8 {
namespace internal {
namespace compiler {
// Shorter lambda declarations with less visual clutter.
#define _ [&]() // NOLINT(whitespace/braces)
class JSCallReducerAssembler : public JSGraphAssembler {
protected:
class CatchScope;
private:
static constexpr bool kMarkLoopExits = true;
public:
JSCallReducerAssembler(JSCallReducer* reducer, Node* node)
: JSGraphAssembler(
reducer->JSGraphForGraphAssembler(),
reducer->ZoneForGraphAssembler(),
[reducer](Node* n) { reducer->RevisitForGraphAssembler(n); },
nullptr, kMarkLoopExits),
node_(node),
outermost_catch_scope_(
CatchScope::Outermost(reducer->ZoneForGraphAssembler())),
catch_scope_(&outermost_catch_scope_) {
InitializeEffectControl(NodeProperties::GetEffectInput(node),
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);
outermost_catch_scope_.set_gasm(this);
}
TNode<Object> ReduceMathUnary(const Operator* op);
TNode<Object> ReduceMathBinary(const Operator* op);
TNode<String> ReduceStringPrototypeSubstring();
TNode<String> ReduceStringPrototypeSlice();
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>(); }
CatchScope* catch_scope() const { return catch_scope_; }
Node* outermost_handler() const { return outermost_handler_; }
Node* node_ptr() const { return node_; }
protected:
using NodeGenerator0 = std::function<TNode<Object>()>;
using VoidGenerator0 = std::function<void()>;
// TODO(jgruber): Currently IfBuilder0 and IfBuilder1 are implemented as
// separate classes. If, in the future, we encounter additional use cases that
// return more than 1 value, we should merge these back into a single variadic
// implementation.
class IfBuilder0 final {
public:
IfBuilder0(JSGraphAssembler* gasm, TNode<Boolean> cond, bool negate_cond)
: gasm_(gasm),
cond_(cond),
negate_cond_(negate_cond),
initial_effect_(gasm->effect()),
initial_control_(gasm->control()) {}
IfBuilder0& ExpectTrue() {
DCHECK_EQ(hint_, BranchHint::kNone);
hint_ = BranchHint::kTrue;
return *this;
}
IfBuilder0& ExpectFalse() {
DCHECK_EQ(hint_, BranchHint::kNone);
hint_ = BranchHint::kFalse;
return *this;
}
IfBuilder0& Then(const VoidGenerator0& body) {
then_body_ = body;
return *this;
}
IfBuilder0& Else(const VoidGenerator0& body) {
else_body_ = body;
return *this;
}
~IfBuilder0() {
// Ensure correct usage: effect/control must not have been modified while
// the IfBuilder0 instance is alive.
DCHECK_EQ(gasm_->effect(), initial_effect_);
DCHECK_EQ(gasm_->control(), initial_control_);
// Unlike IfBuilder1, this supports an empty then or else body. This is
// possible since the merge does not take any value inputs.
DCHECK(then_body_ || else_body_);
if (negate_cond_) std::swap(then_body_, else_body_);
auto if_true = (hint_ == BranchHint::kFalse) ? gasm_->MakeDeferredLabel()
: gasm_->MakeLabel();
auto if_false = (hint_ == BranchHint::kTrue) ? gasm_->MakeDeferredLabel()
: gasm_->MakeLabel();
auto merge = gasm_->MakeLabel();
gasm_->Branch(cond_, &if_true, &if_false);
gasm_->Bind(&if_true);
if (then_body_) then_body_();
if (gasm_->HasActiveBlock()) gasm_->Goto(&merge);
gasm_->Bind(&if_false);
if (else_body_) else_body_();
if (gasm_->HasActiveBlock()) gasm_->Goto(&merge);
gasm_->Bind(&merge);
}
IfBuilder0(const IfBuilder0&) = delete;
IfBuilder0& operator=(const IfBuilder0&) = delete;
private:
JSGraphAssembler* const gasm_;
const TNode<Boolean> cond_;
const bool negate_cond_;
const Effect initial_effect_;
const Control initial_control_;
BranchHint hint_ = BranchHint::kNone;
VoidGenerator0 then_body_;
VoidGenerator0 else_body_;
};
IfBuilder0 If(TNode<Boolean> cond) { return {this, cond, false}; }
IfBuilder0 IfNot(TNode<Boolean> cond) { return {this, cond, true}; }
template <typename T>
class IfBuilder1 {
using If1BodyFunction = std::function<TNode<T>()>;
public:
IfBuilder1(JSGraphAssembler* gasm, TNode<Boolean> cond)
: gasm_(gasm), cond_(cond) {}
V8_WARN_UNUSED_RESULT IfBuilder1& ExpectTrue() {
DCHECK_EQ(hint_, BranchHint::kNone);
hint_ = BranchHint::kTrue;
return *this;
}
V8_WARN_UNUSED_RESULT IfBuilder1& ExpectFalse() {
DCHECK_EQ(hint_, BranchHint::kNone);
hint_ = BranchHint::kFalse;
return *this;
}
V8_WARN_UNUSED_RESULT IfBuilder1& Then(const If1BodyFunction& body) {
then_body_ = body;
return *this;
}
V8_WARN_UNUSED_RESULT IfBuilder1& Else(const If1BodyFunction& body) {
else_body_ = body;
return *this;
}
V8_WARN_UNUSED_RESULT TNode<T> Value() {
DCHECK(then_body_);
DCHECK(else_body_);
auto if_true = (hint_ == BranchHint::kFalse) ? gasm_->MakeDeferredLabel()
: gasm_->MakeLabel();
auto if_false = (hint_ == BranchHint::kTrue) ? gasm_->MakeDeferredLabel()
: gasm_->MakeLabel();
auto merge = gasm_->MakeLabel(kPhiRepresentation);
gasm_->Branch(cond_, &if_true, &if_false);
gasm_->Bind(&if_true);
TNode<T> then_result = then_body_();
if (gasm_->HasActiveBlock()) gasm_->Goto(&merge, then_result);
gasm_->Bind(&if_false);
TNode<T> else_result = else_body_();
if (gasm_->HasActiveBlock()) {
gasm_->Goto(&merge, else_result);
}
gasm_->Bind(&merge);
return merge.PhiAt<T>(0);
}
private:
static constexpr MachineRepresentation kPhiRepresentation =
MachineRepresentation::kTagged;
JSGraphAssembler* const gasm_;
const TNode<Boolean> cond_;
BranchHint hint_ = BranchHint::kNone;
If1BodyFunction then_body_;
If1BodyFunction else_body_;
};
template <typename T>
IfBuilder1<T> SelectIf(TNode<Boolean> cond) {
return {this, cond};
}
// Simplified operators.
TNode<Number> SpeculativeToNumber(
TNode<Object> value,
NumberOperationHint hint = NumberOperationHint::kNumberOrOddball);
TNode<Smi> CheckSmi(TNode<Object> value);
TNode<String> CheckString(TNode<Object> value);
TNode<Number> CheckBounds(TNode<Number> value, TNode<Number> limit);
// 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);
TNode<Object> JSCallRuntime2(Runtime::FunctionId function_id,
TNode<Object> arg0, TNode<Object> arg1,
FrameState frame_state);
// 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,
const NativeContextRef& native_context);
TNode<Number> NumberInc(TNode<Number> value) {
return NumberAdd(value, OneConstant());
}
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());
}
}
// TODO(jgruber): Currently, it's the responsibility of the developer to note
// which operations may throw and appropriately wrap these in a call to
// MayThrow (see e.g. JSCall3 and CallRuntime2). A more methodical approach
// would be good.
TNode<Object> MayThrow(const NodeGenerator0& body) {
TNode<Object> result = body();
if (catch_scope()->has_handler()) {
// The IfException node is later merged into the outer graph.
// Note: AddNode is intentionally not called since effect and control
// should not be updated.
Node* if_exception =
graph()->NewNode(common()->IfException(), effect(), control());
catch_scope()->RegisterIfExceptionNode(if_exception);
// Control resumes here.
AddNode(graph()->NewNode(common()->IfSuccess(), control()));
}
return result;
}
// A catch scope represents a single catch handler. The handler can be
// custom catch logic within the reduction itself; or a catch handler in the
// outside graph into which the reduction will be integrated (in this case
// the scope is called 'outermost').
class CatchScope {
private:
// Only used to partially construct the outermost scope.
explicit CatchScope(Zone* zone) : if_exception_nodes_(zone) {}
// For all inner scopes.
CatchScope(Zone* zone, JSCallReducerAssembler* gasm)
: gasm_(gasm),
parent_(gasm->catch_scope_),
has_handler_(true),
if_exception_nodes_(zone) {
gasm_->catch_scope_ = this;
}
public:
~CatchScope() { gasm_->catch_scope_ = parent_; }
static CatchScope Outermost(Zone* zone) { return CatchScope{zone}; }
static CatchScope Inner(Zone* zone, JSCallReducerAssembler* gasm) {
return {zone, gasm};
}
bool has_handler() const { return has_handler_; }
bool is_outermost() const { return parent_ == nullptr; }
CatchScope* parent() const { return parent_; }
// Should only be used to initialize the outermost scope (inner scopes
// always have a handler and are passed the gasm pointer at construction).
void set_has_handler(bool v) {
DCHECK(is_outermost());
has_handler_ = v;
}
void set_gasm(JSCallReducerAssembler* v) {
DCHECK(is_outermost());
gasm_ = v;
}
bool has_exceptional_control_flow() const {
return !if_exception_nodes_.empty();
}
void RegisterIfExceptionNode(Node* if_exception) {
DCHECK(has_handler());
if_exception_nodes_.push_back(if_exception);
}
void MergeExceptionalPaths(TNode<Object>* exception_out, Effect* effect_out,
Control* control_out) {
DCHECK(has_handler());
DCHECK(has_exceptional_control_flow());
const int size = static_cast<int>(if_exception_nodes_.size());
if (size == 1) {
// No merge needed.
Node* e = if_exception_nodes_.at(0);
*exception_out = TNode<Object>::UncheckedCast(e);
*effect_out = Effect(e);
*control_out = Control(e);
} else {
DCHECK_GT(size, 1);
Node* merge = gasm_->graph()->NewNode(gasm_->common()->Merge(size),
size, if_exception_nodes_.data());
// These phis additionally take {merge} as an input. Temporarily add
// it to the list.
if_exception_nodes_.push_back(merge);
const int size_with_merge =
static_cast<int>(if_exception_nodes_.size());
Node* ephi = gasm_->graph()->NewNode(gasm_->common()->EffectPhi(size),
size_with_merge,
if_exception_nodes_.data());
Node* phi = gasm_->graph()->NewNode(
gasm_->common()->Phi(MachineRepresentation::kTagged, size),
size_with_merge, if_exception_nodes_.data());
if_exception_nodes_.pop_back();
*exception_out = TNode<Object>::UncheckedCast(phi);
*effect_out = Effect(ephi);
*control_out = Control(merge);
}
}
private:
JSCallReducerAssembler* gasm_ = nullptr;
CatchScope* const parent_ = nullptr;
bool has_handler_ = false;
NodeVector if_exception_nodes_;
};
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 = [=](TNode<Number> i) {
return NumberLessThan(i, excluded_limit);
};
auto step = [=](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, [=](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 = [=](TNode<Number> i) {
return NumberLessThan(i, excluded_limit);
};
auto step = [=](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(_ {
JSCallRuntime2(Runtime::kThrowTypeError,
NumberConstant(static_cast<double>(
MessageTemplate::kCalledNonCallable)),
maybe_callable, 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_));
}
JSOperatorBuilder* javascript() const { return jsgraph()->javascript(); }
private:
Node* const node_;
CatchScope outermost_catch_scope_;
Node* outermost_handler_;
CatchScope* catch_scope_;
friend class CatchScope;
};
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(FLAG_turbo_inline_array_builtins);
}
TNode<Object> ReduceArrayPrototypeForEach(
MapInference* inference, const bool has_stability_dependency,
ElementsKind kind, const SharedFunctionInfoRef& shared);
TNode<Object> ReduceArrayPrototypeReduce(MapInference* inference,
const bool has_stability_dependency,
ElementsKind kind,
ArrayReduceDirection direction,
const SharedFunctionInfoRef& shared);
TNode<JSArray> ReduceArrayPrototypeMap(
MapInference* inference, const bool has_stability_dependency,
ElementsKind kind, const SharedFunctionInfoRef& shared,
const NativeContextRef& native_context);
TNode<JSArray> ReduceArrayPrototypeFilter(
MapInference* inference, const bool has_stability_dependency,
ElementsKind kind, const SharedFunctionInfoRef& shared,
const NativeContextRef& native_context);
TNode<Object> ReduceArrayPrototypeFind(MapInference* inference,
const bool has_stability_dependency,
ElementsKind kind,
const SharedFunctionInfoRef& shared,
const NativeContextRef& native_context,
ArrayFindVariant variant);
TNode<Boolean> ReduceArrayPrototypeEverySome(
MapInference* inference, const bool has_stability_dependency,
ElementsKind kind, const SharedFunctionInfoRef& shared,
const NativeContextRef& native_context, ArrayEverySomeVariant variant);
TNode<Object> ReduceArrayPrototypeIndexOfIncludes(
ElementsKind kind, ArrayIndexOfIncludesVariant variant);
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, LoadSensitivity::kCritical),
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;
std::array<MachineRepresentation, sizeof...(Vars)> reps = {
MachineRepresentationOf<Vars>::value...};
auto if_not_hole =
MakeLabel<sizeof...(Vars)>(reps, GraphAssemblerLabelType::kNonDeferred);
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);
}
TNode<Number> CheckFloat64Hole(TNode<Number> value,
CheckFloat64HoleMode mode) {
return AddNode<Number>(
graph()->NewNode(simplified()->CheckFloat64Hole(mode, feedback()),
value, effect(), control()));
}
// May deopt for holey double elements.
TNode<Object> TryConvertHoleToUndefined(TNode<Object> value,
ElementsKind kind) {
DCHECK(IsHoleyElementsKind(kind));
if (kind == HOLEY_DOUBLE_ELEMENTS) {
// TODO(7409): avoid deopt if not all uses of value are truncated.
TNode<Number> number = TNode<Number>::UncheckedCast(value);
return CheckFloat64Hole(number, CheckFloat64HoleMode::kAllowReturnHole);
}
return ConvertTaggedHoleToUndefined(value);
}
};
class PromiseBuiltinReducerAssembler : public JSCallReducerAssembler {
public:
PromiseBuiltinReducerAssembler(JSCallReducer* reducer, Node* node,
JSHeapBroker* broker)
: JSCallReducerAssembler(reducer, node), broker_(broker) {
DCHECK_EQ(IrOpcode::kJSConstruct, node->opcode());
}
TNode<Object> ReducePromiseConstructor(
const 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(const NativeContextRef& native_context,
TNode<Context> outer_context,
int slot_count) {
return AddNode<Context>(graph()->NewNode(
javascript()->CreateFunctionContext(
native_context.scope_info().object(),
slot_count - Context::MIN_CONTEXT_SLOTS, FUNCTION_SCOPE),
outer_context, effect(), control()));
}
void StoreContextSlot(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(), static_cast<Builtins::Name>(shared.builtin_id()));
return AddNode<JSFunction>(graph()->NewNode(
javascript()->CreateClosure(shared.object(), callable.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()));
});
}
JSHeapBroker* const broker_;
};
class FastApiCallReducerAssembler : public JSCallReducerAssembler {
public:
FastApiCallReducerAssembler(
JSCallReducer* reducer, Node* node,
const FunctionTemplateInfoRef function_template_info, Node* receiver,
Node* holder, const SharedFunctionInfoRef shared, Node* target,
const int arity, Node* effect)
: JSCallReducerAssembler(reducer, node),
c_function_(function_template_info.c_function()),
c_signature_(function_template_info.c_signature()),
function_template_info_(function_template_info),
receiver_(receiver),
holder_(holder),
shared_(shared),
target_(target),
arity_(arity) {
DCHECK_EQ(IrOpcode::kJSCall, node->opcode());
DCHECK_NE(c_function_, kNullAddress);
CHECK_NOT_NULL(c_signature_);
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.
const int c_argument_count =
static_cast<int>(c_signature_->ArgumentCount());
CHECK_GE(c_argument_count, kReceiver);
int cursor = 0;
base::SmallVector<Node*, kInlineSize> inputs(c_argument_count + arity_ +
kExtraInputsCount);
inputs[cursor++] = ExternalConstant(ExternalReference::Create(c_function_));
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:
// [fast callee, receiver, ... C arguments,
// call code, external constant for function, argc, call handler info data,
// holder, receiver, ... JS arguments, context, new frame state]
CallHandlerInfoRef call_handler_info = *function_template_info_.call_code();
Callable call_api_callback = CodeFactory::CallApiCallback(isolate());
CallInterfaceDescriptor cid = call_api_callback.descriptor();
CallDescriptor* call_descriptor =
Linkage::GetStubCallDescriptor(graph()->zone(), cid, arity_ + kReceiver,
CallDescriptor::kNeedsFrameState);
ApiFunction api_function(call_handler_info.callback());
ExternalReference function_reference = ExternalReference::Create(
&api_function, ExternalReference::DIRECT_API_CALL);
Node* continuation_frame_state =
CreateGenericLazyDeoptContinuationFrameState(
jsgraph(), shared_, target_, ContextInput(), receiver_,
FrameStateInput());
inputs[cursor++] = HeapConstant(call_api_callback.code());
inputs[cursor++] = ExternalConstant(function_reference);
inputs[cursor++] = NumberConstant(arity_);
inputs[cursor++] = Constant(call_handler_info.data());
inputs[cursor++] = holder_;
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, c_argument_count + arity_ + kExtraInputsCount);
return FastApiCall(call_descriptor, inputs.begin(), inputs.size());
}
private:
static constexpr int kTarget = 1;
static constexpr int kEffectAndControl = 2;
static constexpr int kContextAndFrameState = 2;
static constexpr int kCallCodeDataAndArgc = 3;
static constexpr int kHolder = 1, kReceiver = 1;
static constexpr int kExtraInputsCount =
kTarget * 2 + kEffectAndControl + kContextAndFrameState +
kCallCodeDataAndArgc + kHolder + kReceiver;
static constexpr int kInlineSize = 12;
TNode<Object> FastApiCall(CallDescriptor* descriptor, Node** inputs,
size_t inputs_size) {
return AddNode<Object>(graph()->NewNode(
simplified()->FastApiCall(c_signature_, feedback(), descriptor),
static_cast<int>(inputs_size), inputs));
}
const Address c_function_;
const CFunctionInfo* const c_signature_;
const FunctionTemplateInfoRef function_template_info_;
Node* const receiver_;
Node* const holder_;
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<String> JSCallReducerAssembler::CheckString(TNode<Object> value) {
return AddNode<String>(graph()->NewNode(simplified()->CheckString(feedback()),
value, effect(), control()));
}
TNode<Number> JSCallReducerAssembler::CheckBounds(TNode<Number> value,
TNode<Number> limit) {
return AddNode<Number>(graph()->NewNode(simplified()->CheckBounds(feedback()),
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::JSCallRuntime2(
Runtime::FunctionId function_id, TNode<Object> arg0, TNode<Object> arg1,
FrameState frame_state) {
return MayThrow(_ {
return AddNode<Object>(
graph()->NewNode(javascript()->CallRuntime(function_id, 2), arg0, arg1,
ContextInput(), frame_state, effect(), control()));
});
}
TNode<JSArray> JSCallReducerAssembler::CreateArrayNoThrow(
TNode<Object> ctor, TNode<Number> size, FrameState frame_state) {
return AddNode<JSArray>(graph()->NewNode(
javascript()->CreateArray(1, MaybeHandle<AllocationSite>()), ctor, ctor,
size, ContextInput(), frame_state, effect(), control()));
}
TNode<JSArray> JSCallReducerAssembler::AllocateEmptyJSArray(
ElementsKind kind, const NativeContextRef& native_context) {
// TODO(jgruber): Port AllocationBuilder to JSGraphAssembler.
MapRef map = native_context.GetInitialJSArrayMap(kind);
AllocationBuilder ab(jsgraph(), 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<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<String> JSCallReducerAssembler::ReduceStringPrototypeSlice() {
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> 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();
}
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) {
Builtins::Name builtin = Builtins::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) {
Builtins::Name builtin = Builtins::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, const 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) {
Builtins::Name builtin =
(params.direction == ArrayReduceDirection::kLeft)
? Builtins::kArrayReduceLoopLazyDeoptContinuation
: Builtins::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) {
Builtins::Name builtin =
(params.direction == ArrayReduceDirection::kLeft)
? Builtins::kArrayReducePreLoopEagerDeoptContinuation
: Builtins::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) {
Builtins::Name builtin =
(params.direction == ArrayReduceDirection::kLeft)
? Builtins::kArrayReduceLoopLazyDeoptContinuation
: Builtins::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) {
Builtins::Name builtin =
(params.direction == ArrayReduceDirection::kLeft)
? Builtins::kArrayReduceLoopEagerDeoptContinuation
: Builtins::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,
const 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;
TNode<JSArray> a;
TNode<Object> original_length;
};
FrameState MapPreLoopLazyFrameState(const MapFrameStateParams& params) {
DCHECK(params.a.is_null());
Node* checkpoint_params[] = {params.receiver, params.callback,
params.this_arg, params.original_length};
return CreateJavaScriptBuiltinContinuationFrameState(
params.jsgraph, params.shared,
Builtins::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,
Builtins::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,
Builtins::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, const SharedFunctionInfoRef& shared,
const 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(kind).GetConstructor());
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(HOLEY_DOUBLE_ELEMENTS);
MapRef holey_map = native_context.GetInitialJSArrayMap(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,
Builtins::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
// Builtins::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,
Builtins::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,
Builtins::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, const SharedFunctionInfoRef& shared,
const 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).
const ElementsKind packed_kind = GetPackedElementsKind(kind);
TNode<JSArray> a = AllocateEmptyJSArray(packed_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) {
Builtins::Name builtin =
(variant == ArrayFindVariant::kFind)
? Builtins::kArrayFindLoopLazyDeoptContinuation
: Builtins::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) {
Builtins::Name builtin =
(variant == ArrayFindVariant::kFind)
? Builtins::kArrayFindLoopEagerDeoptContinuation
: Builtins::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) {
Builtins::Name builtin =
(variant == ArrayFindVariant::kFind)
? Builtins::kArrayFindLoopAfterCallbackLazyDeoptContinuation
: Builtins::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, const SharedFunctionInfoRef& shared,
const 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 = TryConvertHoleToUndefined(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) {
Builtins::Name builtin = (variant == ArrayEverySomeVariant::kEvery)
? Builtins::kArrayEveryLoopLazyDeoptContinuation
: Builtins::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) {
Builtins::Name builtin = (variant == ArrayEverySomeVariant::kEvery)
? Builtins::kArrayEveryLoopEagerDeoptContinuation
: Builtins::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, const SharedFunctionInfoRef& shared,
const 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:
case PACKED_ELEMENTS:
case HOLEY_ELEMENTS:
return Builtins::CallableFor(isolate,
Builtins::kArrayIndexOfSmiOrObject);
case PACKED_DOUBLE_ELEMENTS:
return Builtins::CallableFor(isolate,
Builtins::kArrayIndexOfPackedDoubles);
default:
DCHECK_EQ(HOLEY_DOUBLE_ELEMENTS, elements_kind);
return Builtins::CallableFor(isolate,
Builtins::kArrayIndexOfHoleyDoubles);
}
} else {
DCHECK_EQ(variant, ArrayIndexOfIncludesVariant::kIncludes);
switch (elements_kind) {
case PACKED_SMI_ELEMENTS:
case HOLEY_SMI_ELEMENTS:
case PACKED_ELEMENTS:
case HOLEY_ELEMENTS:
return Builtins::CallableFor(isolate,
Builtins::kArrayIncludesSmiOrObject);
case PACKED_DOUBLE_ELEMENTS:
return Builtins::CallableFor(isolate,
Builtins::kArrayIncludesPackedDoubles);
default:
DCHECK_EQ(HOLEY_DOUBLE_ELEMENTS, elements_kind);
return Builtins::CallableFor(isolate,
Builtins::kArrayIncludesHoleyDoubles);
}
}
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 CreateArtificialFrameState(Node* node, Node* outer_frame_state,
int parameter_count, BailoutId bailout_id,
FrameStateType frame_state_type,
const SharedFunctionInfoRef& shared,
Node* context,
CommonOperatorBuilder* common,
Graph* graph) {
const FrameStateFunctionInfo* state_info =
common->CreateFrameStateFunctionInfo(
frame_state_type, parameter_count + 1, 0, shared.object());
const Operator* op = common->FrameState(
bailout_id, OutputFrameStateCombine::Ignore(), state_info);
const Operator* op0 = common->StateValues(0, SparseInputMask::Dense());
Node* node0 = graph->NewNode(op0);
static constexpr int kTargetInputIndex = 0;
static constexpr int kReceiverInputIndex = 1;
const int parameter_count_with_receiver = parameter_count + 1;
std::vector<Node*> params;
params.reserve(parameter_count_with_receiver);
for (int i = 0; i < parameter_count_with_receiver; i++) {
params.push_back(node->InputAt(kReceiverInputIndex + i));
}
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,
Graph* graph) {
DCHECK_EQ(1, params.shared.internal_formal_parameter_count());
return CreateArtificialFrameState(
params.node_ptr, params.outer_frame_state, 1,
BailoutId::ConstructStubInvoke(), FrameStateType::kConstructStub,
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,
Builtins::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,
Builtins::kPromiseConstructorLazyDeoptContinuation, params.target,
params.context, checkpoint_params, arraysize(checkpoint_params),
constructor_frame_state, ContinuationFrameStateMode::LAZY_WITH_CATCH);
}
} // namespace
TNode<Object> PromiseBuiltinReducerAssembler::ReducePromiseConstructor(
const 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().shared();
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());
ThrowIfNotCallable(executor,
PromiseConstructorLazyFrameState(frame_state_params,
constructor_frame_state));
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);
StoreContextSlot(promise_context, PromiseBuiltins::kPromiseSlot, promise);
StoreContextSlot(promise_context, PromiseBuiltins::kAlreadyResolvedSlot,
FalseConstant());
StoreContextSlot(promise_context, PromiseBuiltins::kDebugEventSlot,
TrueConstant());
// Allocate closures for the resolve and reject cases.
SharedFunctionInfoRef resolve_sfi(
broker_, broker_->isolate()
->factory()
->promise_capability_default_resolve_shared_fun());
TNode<JSFunction> resolve =
CreateClosureFromBuiltinSharedFunctionInfo(resolve_sfi, promise_context);
SharedFunctionInfoRef reject_sfi(
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) {
CallPromiseReject(reject, exception, lazy_with_catch_frame_state);
});
return promise;
}
#undef _
bool JSCallReducer::should_disallow_heap_access() const {
return broker_->is_concurrent_inlining();
}
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::kDisallowSpeculation) {
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::kDisallowSpeculation) {
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::kDisallowSpeculation) {
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::kDisallowSpeculation) {
return NoChange();
}
if (n.ArgumentCount() < 1) {
Node* value = jsgraph()->Constant(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::kDisallowSpeculation) {
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) {
DisallowHeapAccessIf disallow_heap_access(should_disallow_heap_access());
switch (node->opcode()) {
case IrOpcode::kJSConstruct:
return ReduceJSConstruct(node);
case IrOpcode::kJSConstructWithArrayLike:
return ReduceJSConstructWithArrayLike(node);
case IrOpcode::kJSConstructWithSpread:
return ReduceJSConstructWithSpread(node);
case IrOpcode::kJSCall:
return ReduceJSCall(node);
case IrOpcode::kJSCallWithArrayLike:
return ReduceJSCallWithArrayLike(node);
case IrOpcode::kJSCallWithSpread:
return ReduceJSCallWithSpread(node);
default:
break;
}
return NoChange();
}
void JSCallReducer::Finalize() {
// TODO(turbofan): This is not the best solution; ideally we would be able
// to teach the GraphReducer about arbitrary dependencies between different
// nodes, even if they don't show up in the use list of the other node.
std::set<Node*> const waitlist = std::move(waitlist_);
for (Node* node : waitlist) {
if (!node->IsDead()) {
Reduction const reduction = Reduce(node);
if (reduction.Changed()) {
Node* replacement = reduction.replacement();
if (replacement != node) {
Replace(node, replacement);
}
}
}
}
}
// ES6 section 22.1.1 The Array Constructor
Reduction JSCallReducer::ReduceArrayConstructor(Node* node) {
DisallowHeapAccessIf disallow_heap_access(should_disallow_heap_access());
JSCallNode n(node);
Node* target = n.target();
CallParameters const& p = n.Parameters();
// Turn the {node} into a {JSCreateArray} call.
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, MaybeHandle<AllocationSite>()));
return Changed(node);