// Copyright 2014 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-typed-lowering.h"
|
|
#include "src/ast/modules.h"
|
#include "src/builtins/builtins-utils.h"
|
#include "src/code-factory.h"
|
#include "src/compiler/access-builder.h"
|
#include "src/compiler/allocation-builder.h"
|
#include "src/compiler/js-graph.h"
|
#include "src/compiler/linkage.h"
|
#include "src/compiler/node-matchers.h"
|
#include "src/compiler/node-properties.h"
|
#include "src/compiler/operator-properties.h"
|
#include "src/compiler/type-cache.h"
|
#include "src/compiler/types.h"
|
#include "src/objects-inl.h"
|
#include "src/objects/js-generator.h"
|
#include "src/objects/module-inl.h"
|
|
namespace v8 {
|
namespace internal {
|
namespace compiler {
|
|
// A helper class to simplify the process of reducing a single binop node with a
|
// JSOperator. This class manages the rewriting of context, control, and effect
|
// dependencies during lowering of a binop and contains numerous helper
|
// functions for matching the types of inputs to an operation.
|
class JSBinopReduction final {
|
public:
|
JSBinopReduction(JSTypedLowering* lowering, Node* node)
|
: lowering_(lowering), node_(node) {}
|
|
bool GetCompareNumberOperationHint(NumberOperationHint* hint) {
|
DCHECK_EQ(1, node_->op()->EffectOutputCount());
|
switch (CompareOperationHintOf(node_->op())) {
|
case CompareOperationHint::kSignedSmall:
|
*hint = NumberOperationHint::kSignedSmall;
|
return true;
|
case CompareOperationHint::kNumber:
|
*hint = NumberOperationHint::kNumber;
|
return true;
|
case CompareOperationHint::kNumberOrOddball:
|
*hint = NumberOperationHint::kNumberOrOddball;
|
return true;
|
case CompareOperationHint::kAny:
|
case CompareOperationHint::kNone:
|
case CompareOperationHint::kString:
|
case CompareOperationHint::kSymbol:
|
case CompareOperationHint::kBigInt:
|
case CompareOperationHint::kReceiver:
|
case CompareOperationHint::kInternalizedString:
|
break;
|
}
|
return false;
|
}
|
|
bool IsInternalizedStringCompareOperation() {
|
DCHECK_EQ(1, node_->op()->EffectOutputCount());
|
return (CompareOperationHintOf(node_->op()) ==
|
CompareOperationHint::kInternalizedString) &&
|
BothInputsMaybe(Type::InternalizedString());
|
}
|
|
bool IsReceiverCompareOperation() {
|
DCHECK_EQ(1, node_->op()->EffectOutputCount());
|
return (CompareOperationHintOf(node_->op()) ==
|
CompareOperationHint::kReceiver) &&
|
BothInputsMaybe(Type::Receiver());
|
}
|
|
bool IsStringCompareOperation() {
|
DCHECK_EQ(1, node_->op()->EffectOutputCount());
|
return (CompareOperationHintOf(node_->op()) ==
|
CompareOperationHint::kString) &&
|
BothInputsMaybe(Type::String());
|
}
|
|
bool IsSymbolCompareOperation() {
|
DCHECK_EQ(1, node_->op()->EffectOutputCount());
|
return (CompareOperationHintOf(node_->op()) ==
|
CompareOperationHint::kSymbol) &&
|
BothInputsMaybe(Type::Symbol());
|
}
|
|
// Check if a string addition will definitely result in creating a ConsString,
|
// i.e. if the combined length of the resulting string exceeds the ConsString
|
// minimum length.
|
bool ShouldCreateConsString() {
|
DCHECK_EQ(IrOpcode::kJSAdd, node_->opcode());
|
DCHECK(OneInputIs(Type::String()));
|
if (BothInputsAre(Type::String()) ||
|
BinaryOperationHintOf(node_->op()) == BinaryOperationHint::kString) {
|
HeapObjectBinopMatcher m(node_);
|
JSHeapBroker* broker = lowering_->js_heap_broker();
|
if (m.right().HasValue() && m.right().Ref(broker).IsString()) {
|
StringRef right_string = m.right().Ref(broker).AsString();
|
if (right_string.length() >= ConsString::kMinLength) return true;
|
}
|
if (m.left().HasValue() && m.left().Ref(broker).IsString()) {
|
StringRef left_string = m.left().Ref(broker).AsString();
|
if (left_string.length() >= ConsString::kMinLength) {
|
// The invariant for ConsString requires the left hand side to be
|
// a sequential or external string if the right hand side is the
|
// empty string. Since we don't know anything about the right hand
|
// side here, we must ensure that the left hand side satisfy the
|
// constraints independent of the right hand side.
|
return left_string.IsSeqString() || left_string.IsExternalString();
|
}
|
}
|
}
|
return false;
|
}
|
|
// Inserts a CheckReceiver for the left input.
|
void CheckLeftInputToReceiver() {
|
Node* left_input = graph()->NewNode(simplified()->CheckReceiver(), left(),
|
effect(), control());
|
node_->ReplaceInput(0, left_input);
|
update_effect(left_input);
|
}
|
|
// Checks that both inputs are Receiver, and if we don't know
|
// statically that one side is already a Receiver, insert a
|
// CheckReceiver node.
|
void CheckInputsToReceiver() {
|
if (!left_type().Is(Type::Receiver())) {
|
CheckLeftInputToReceiver();
|
}
|
if (!right_type().Is(Type::Receiver())) {
|
Node* right_input = graph()->NewNode(simplified()->CheckReceiver(),
|
right(), effect(), control());
|
node_->ReplaceInput(1, right_input);
|
update_effect(right_input);
|
}
|
}
|
|
// Checks that both inputs are Symbol, and if we don't know
|
// statically that one side is already a Symbol, insert a
|
// CheckSymbol node.
|
void CheckInputsToSymbol() {
|
if (!left_type().Is(Type::Symbol())) {
|
Node* left_input = graph()->NewNode(simplified()->CheckSymbol(), left(),
|
effect(), control());
|
node_->ReplaceInput(0, left_input);
|
update_effect(left_input);
|
}
|
if (!right_type().Is(Type::Symbol())) {
|
Node* right_input = graph()->NewNode(simplified()->CheckSymbol(), right(),
|
effect(), control());
|
node_->ReplaceInput(1, right_input);
|
update_effect(right_input);
|
}
|
}
|
|
// Checks that both inputs are String, and if we don't know
|
// statically that one side is already a String, insert a
|
// CheckString node.
|
void CheckInputsToString() {
|
if (!left_type().Is(Type::String())) {
|
Node* left_input =
|
graph()->NewNode(simplified()->CheckString(VectorSlotPair()), left(),
|
effect(), control());
|
node_->ReplaceInput(0, left_input);
|
update_effect(left_input);
|
}
|
if (!right_type().Is(Type::String())) {
|
Node* right_input =
|
graph()->NewNode(simplified()->CheckString(VectorSlotPair()), right(),
|
effect(), control());
|
node_->ReplaceInput(1, right_input);
|
update_effect(right_input);
|
}
|
}
|
|
// Checks that both inputs are InternalizedString, and if we don't know
|
// statically that one side is already an InternalizedString, insert a
|
// CheckInternalizedString node.
|
void CheckInputsToInternalizedString() {
|
if (!left_type().Is(Type::UniqueName())) {
|
Node* left_input = graph()->NewNode(
|
simplified()->CheckInternalizedString(), left(), effect(), control());
|
node_->ReplaceInput(0, left_input);
|
update_effect(left_input);
|
}
|
if (!right_type().Is(Type::UniqueName())) {
|
Node* right_input =
|
graph()->NewNode(simplified()->CheckInternalizedString(), right(),
|
effect(), control());
|
node_->ReplaceInput(1, right_input);
|
update_effect(right_input);
|
}
|
}
|
|
void ConvertInputsToNumber() {
|
DCHECK(left_type().Is(Type::PlainPrimitive()));
|
DCHECK(right_type().Is(Type::PlainPrimitive()));
|
node_->ReplaceInput(0, ConvertPlainPrimitiveToNumber(left()));
|
node_->ReplaceInput(1, ConvertPlainPrimitiveToNumber(right()));
|
}
|
|
void ConvertInputsToUI32(Signedness left_signedness,
|
Signedness right_signedness) {
|
node_->ReplaceInput(0, ConvertToUI32(left(), left_signedness));
|
node_->ReplaceInput(1, ConvertToUI32(right(), right_signedness));
|
}
|
|
void SwapInputs() {
|
Node* l = left();
|
Node* r = right();
|
node_->ReplaceInput(0, r);
|
node_->ReplaceInput(1, l);
|
}
|
|
// Remove all effect and control inputs and outputs to this node and change
|
// to the pure operator {op}.
|
Reduction ChangeToPureOperator(const Operator* op, Type type = Type::Any()) {
|
DCHECK_EQ(0, op->EffectInputCount());
|
DCHECK_EQ(false, OperatorProperties::HasContextInput(op));
|
DCHECK_EQ(0, op->ControlInputCount());
|
DCHECK_EQ(2, op->ValueInputCount());
|
|
// Remove the effects from the node, and update its effect/control usages.
|
if (node_->op()->EffectInputCount() > 0) {
|
lowering_->RelaxEffectsAndControls(node_);
|
}
|
// Remove the inputs corresponding to context, effect, and control.
|
NodeProperties::RemoveNonValueInputs(node_);
|
// Finally, update the operator to the new one.
|
NodeProperties::ChangeOp(node_, op);
|
|
// TODO(jarin): Replace the explicit typing hack with a call to some method
|
// that encapsulates changing the operator and re-typing.
|
Type node_type = NodeProperties::GetType(node_);
|
NodeProperties::SetType(node_, Type::Intersect(node_type, type, zone()));
|
|
return lowering_->Changed(node_);
|
}
|
|
Reduction ChangeToSpeculativeOperator(const Operator* op, Type upper_bound) {
|
DCHECK_EQ(1, op->EffectInputCount());
|
DCHECK_EQ(1, op->EffectOutputCount());
|
DCHECK_EQ(false, OperatorProperties::HasContextInput(op));
|
DCHECK_EQ(1, op->ControlInputCount());
|
DCHECK_EQ(0, op->ControlOutputCount());
|
DCHECK_EQ(0, OperatorProperties::GetFrameStateInputCount(op));
|
DCHECK_EQ(2, op->ValueInputCount());
|
|
DCHECK_EQ(1, node_->op()->EffectInputCount());
|
DCHECK_EQ(1, node_->op()->EffectOutputCount());
|
DCHECK_EQ(1, node_->op()->ControlInputCount());
|
DCHECK_EQ(2, node_->op()->ValueInputCount());
|
|
// Reconnect the control output to bypass the IfSuccess node and
|
// possibly disconnect from the IfException node.
|
lowering_->RelaxControls(node_);
|
|
// Remove the frame state and the context.
|
if (OperatorProperties::HasFrameStateInput(node_->op())) {
|
node_->RemoveInput(NodeProperties::FirstFrameStateIndex(node_));
|
}
|
node_->RemoveInput(NodeProperties::FirstContextIndex(node_));
|
|
NodeProperties::ChangeOp(node_, op);
|
|
// Update the type to number.
|
Type node_type = NodeProperties::GetType(node_);
|
NodeProperties::SetType(node_,
|
Type::Intersect(node_type, upper_bound, zone()));
|
|
return lowering_->Changed(node_);
|
}
|
|
const Operator* NumberOp() {
|
switch (node_->opcode()) {
|
case IrOpcode::kJSAdd:
|
return simplified()->NumberAdd();
|
case IrOpcode::kJSSubtract:
|
return simplified()->NumberSubtract();
|
case IrOpcode::kJSMultiply:
|
return simplified()->NumberMultiply();
|
case IrOpcode::kJSDivide:
|
return simplified()->NumberDivide();
|
case IrOpcode::kJSModulus:
|
return simplified()->NumberModulus();
|
case IrOpcode::kJSExponentiate:
|
return simplified()->NumberPow();
|
case IrOpcode::kJSBitwiseAnd:
|
return simplified()->NumberBitwiseAnd();
|
case IrOpcode::kJSBitwiseOr:
|
return simplified()->NumberBitwiseOr();
|
case IrOpcode::kJSBitwiseXor:
|
return simplified()->NumberBitwiseXor();
|
case IrOpcode::kJSShiftLeft:
|
return simplified()->NumberShiftLeft();
|
case IrOpcode::kJSShiftRight:
|
return simplified()->NumberShiftRight();
|
case IrOpcode::kJSShiftRightLogical:
|
return simplified()->NumberShiftRightLogical();
|
default:
|
break;
|
}
|
UNREACHABLE();
|
}
|
|
const Operator* NumberOpFromSpeculativeNumberOp() {
|
switch (node_->opcode()) {
|
case IrOpcode::kSpeculativeNumberEqual:
|
return simplified()->NumberEqual();
|
case IrOpcode::kSpeculativeNumberLessThan:
|
return simplified()->NumberLessThan();
|
case IrOpcode::kSpeculativeNumberLessThanOrEqual:
|
return simplified()->NumberLessThanOrEqual();
|
case IrOpcode::kSpeculativeNumberAdd:
|
// Handled by ReduceSpeculativeNumberAdd.
|
UNREACHABLE();
|
case IrOpcode::kSpeculativeNumberSubtract:
|
return simplified()->NumberSubtract();
|
case IrOpcode::kSpeculativeNumberMultiply:
|
return simplified()->NumberMultiply();
|
case IrOpcode::kSpeculativeNumberDivide:
|
return simplified()->NumberDivide();
|
case IrOpcode::kSpeculativeNumberModulus:
|
return simplified()->NumberModulus();
|
default:
|
break;
|
}
|
UNREACHABLE();
|
}
|
|
bool LeftInputIs(Type t) { return left_type().Is(t); }
|
|
bool RightInputIs(Type t) { return right_type().Is(t); }
|
|
bool OneInputIs(Type t) { return LeftInputIs(t) || RightInputIs(t); }
|
|
bool BothInputsAre(Type t) { return LeftInputIs(t) && RightInputIs(t); }
|
|
bool BothInputsMaybe(Type t) {
|
return left_type().Maybe(t) && right_type().Maybe(t);
|
}
|
|
bool OneInputCannotBe(Type t) {
|
return !left_type().Maybe(t) || !right_type().Maybe(t);
|
}
|
|
bool NeitherInputCanBe(Type t) {
|
return !left_type().Maybe(t) && !right_type().Maybe(t);
|
}
|
|
Node* effect() { return NodeProperties::GetEffectInput(node_); }
|
Node* control() { return NodeProperties::GetControlInput(node_); }
|
Node* context() { return NodeProperties::GetContextInput(node_); }
|
Node* left() { return NodeProperties::GetValueInput(node_, 0); }
|
Node* right() { return NodeProperties::GetValueInput(node_, 1); }
|
Type left_type() { return NodeProperties::GetType(node_->InputAt(0)); }
|
Type right_type() { return NodeProperties::GetType(node_->InputAt(1)); }
|
Type type() { return NodeProperties::GetType(node_); }
|
|
SimplifiedOperatorBuilder* simplified() { return lowering_->simplified(); }
|
Graph* graph() const { return lowering_->graph(); }
|
JSGraph* jsgraph() { return lowering_->jsgraph(); }
|
Isolate* isolate() { return jsgraph()->isolate(); }
|
JSOperatorBuilder* javascript() { return lowering_->javascript(); }
|
CommonOperatorBuilder* common() { return jsgraph()->common(); }
|
Zone* zone() const { return graph()->zone(); }
|
|
private:
|
JSTypedLowering* lowering_; // The containing lowering instance.
|
Node* node_; // The original node.
|
|
Node* ConvertPlainPrimitiveToNumber(Node* node) {
|
DCHECK(NodeProperties::GetType(node).Is(Type::PlainPrimitive()));
|
// Avoid inserting too many eager ToNumber() operations.
|
Reduction const reduction = lowering_->ReduceJSToNumberOrNumericInput(node);
|
if (reduction.Changed()) return reduction.replacement();
|
if (NodeProperties::GetType(node).Is(Type::Number())) {
|
return node;
|
}
|
return graph()->NewNode(simplified()->PlainPrimitiveToNumber(), node);
|
}
|
|
Node* ConvertToUI32(Node* node, Signedness signedness) {
|
// Avoid introducing too many eager NumberToXXnt32() operations.
|
Type type = NodeProperties::GetType(node);
|
if (signedness == kSigned) {
|
if (!type.Is(Type::Signed32())) {
|
node = graph()->NewNode(simplified()->NumberToInt32(), node);
|
}
|
} else {
|
DCHECK_EQ(kUnsigned, signedness);
|
if (!type.Is(Type::Unsigned32())) {
|
node = graph()->NewNode(simplified()->NumberToUint32(), node);
|
}
|
}
|
return node;
|
}
|
|
void update_effect(Node* effect) {
|
NodeProperties::ReplaceEffectInput(node_, effect);
|
}
|
};
|
|
|
// TODO(turbofan): js-typed-lowering improvements possible
|
// - immediately put in type bounds for all new nodes
|
// - relax effects from generic but not-side-effecting operations
|
|
JSTypedLowering::JSTypedLowering(Editor* editor, JSGraph* jsgraph,
|
JSHeapBroker* js_heap_broker, Zone* zone)
|
: AdvancedReducer(editor),
|
jsgraph_(jsgraph),
|
js_heap_broker_(js_heap_broker),
|
empty_string_type_(Type::HeapConstant(
|
js_heap_broker, factory()->empty_string(), graph()->zone())),
|
pointer_comparable_type_(
|
Type::Union(Type::Oddball(),
|
Type::Union(Type::SymbolOrReceiver(), empty_string_type_,
|
graph()->zone()),
|
graph()->zone())),
|
type_cache_(TypeCache::Get()) {}
|
|
Reduction JSTypedLowering::ReduceSpeculativeNumberAdd(Node* node) {
|
JSBinopReduction r(this, node);
|
NumberOperationHint hint = NumberOperationHintOf(node->op());
|
if ((hint == NumberOperationHint::kNumber ||
|
hint == NumberOperationHint::kNumberOrOddball) &&
|
r.BothInputsAre(Type::PlainPrimitive()) &&
|
r.NeitherInputCanBe(Type::StringOrReceiver())) {
|
// SpeculativeNumberAdd(x:-string, y:-string) =>
|
// NumberAdd(ToNumber(x), ToNumber(y))
|
r.ConvertInputsToNumber();
|
return r.ChangeToPureOperator(simplified()->NumberAdd(), Type::Number());
|
}
|
return NoChange();
|
}
|
|
Reduction JSTypedLowering::ReduceJSBitwiseNot(Node* node) {
|
Node* input = NodeProperties::GetValueInput(node, 0);
|
Type input_type = NodeProperties::GetType(input);
|
if (input_type.Is(Type::PlainPrimitive())) {
|
// JSBitwiseNot(x) => NumberBitwiseXor(ToInt32(x), -1)
|
node->InsertInput(graph()->zone(), 1, jsgraph()->SmiConstant(-1));
|
NodeProperties::ChangeOp(node, javascript()->BitwiseXor());
|
JSBinopReduction r(this, node);
|
r.ConvertInputsToNumber();
|
r.ConvertInputsToUI32(kSigned, kSigned);
|
return r.ChangeToPureOperator(r.NumberOp(), Type::Signed32());
|
}
|
return NoChange();
|
}
|
|
Reduction JSTypedLowering::ReduceJSDecrement(Node* node) {
|
Node* input = NodeProperties::GetValueInput(node, 0);
|
Type input_type = NodeProperties::GetType(input);
|
if (input_type.Is(Type::PlainPrimitive())) {
|
// JSDecrement(x) => NumberSubtract(ToNumber(x), 1)
|
node->InsertInput(graph()->zone(), 1, jsgraph()->OneConstant());
|
NodeProperties::ChangeOp(node, javascript()->Subtract());
|
JSBinopReduction r(this, node);
|
r.ConvertInputsToNumber();
|
DCHECK_EQ(simplified()->NumberSubtract(), r.NumberOp());
|
return r.ChangeToPureOperator(r.NumberOp(), Type::Number());
|
}
|
return NoChange();
|
}
|
|
Reduction JSTypedLowering::ReduceJSIncrement(Node* node) {
|
Node* input = NodeProperties::GetValueInput(node, 0);
|
Type input_type = NodeProperties::GetType(input);
|
if (input_type.Is(Type::PlainPrimitive())) {
|
// JSIncrement(x) => NumberAdd(ToNumber(x), 1)
|
node->InsertInput(graph()->zone(), 1, jsgraph()->OneConstant());
|
BinaryOperationHint hint = BinaryOperationHint::kAny; // Dummy.
|
NodeProperties::ChangeOp(node, javascript()->Add(hint));
|
JSBinopReduction r(this, node);
|
r.ConvertInputsToNumber();
|
DCHECK_EQ(simplified()->NumberAdd(), r.NumberOp());
|
return r.ChangeToPureOperator(r.NumberOp(), Type::Number());
|
}
|
return NoChange();
|
}
|
|
Reduction JSTypedLowering::ReduceJSNegate(Node* node) {
|
Node* input = NodeProperties::GetValueInput(node, 0);
|
Type input_type = NodeProperties::GetType(input);
|
if (input_type.Is(Type::PlainPrimitive())) {
|
// JSNegate(x) => NumberMultiply(ToNumber(x), -1)
|
node->InsertInput(graph()->zone(), 1, jsgraph()->SmiConstant(-1));
|
NodeProperties::ChangeOp(node, javascript()->Multiply());
|
JSBinopReduction r(this, node);
|
r.ConvertInputsToNumber();
|
return r.ChangeToPureOperator(r.NumberOp(), Type::Number());
|
}
|
return NoChange();
|
}
|
|
Reduction JSTypedLowering::ReduceJSAdd(Node* node) {
|
JSBinopReduction r(this, node);
|
if (r.BothInputsAre(Type::Number())) {
|
// JSAdd(x:number, y:number) => NumberAdd(x, y)
|
return r.ChangeToPureOperator(simplified()->NumberAdd(), Type::Number());
|
}
|
if (r.BothInputsAre(Type::PlainPrimitive()) &&
|
r.NeitherInputCanBe(Type::StringOrReceiver())) {
|
// JSAdd(x:-string, y:-string) => NumberAdd(ToNumber(x), ToNumber(y))
|
r.ConvertInputsToNumber();
|
return r.ChangeToPureOperator(simplified()->NumberAdd(), Type::Number());
|
}
|
if (BinaryOperationHintOf(node->op()) == BinaryOperationHint::kString) {
|
// Always bake in String feedback into the graph.
|
// TODO(bmeurer): Consider adding a SpeculativeStringAdd operator,
|
// and use that in JSTypeHintLowering instead of looking at the
|
// binary operation feedback here.
|
r.CheckInputsToString();
|
}
|
if (r.OneInputIs(Type::String())) {
|
// We know that (at least) one input is already a String,
|
// so try to strength-reduce the non-String input.
|
if (r.LeftInputIs(Type::String())) {
|
Reduction const reduction = ReduceJSToStringInput(r.right());
|
if (reduction.Changed()) {
|
NodeProperties::ReplaceValueInput(node, reduction.replacement(), 1);
|
}
|
} else if (r.RightInputIs(Type::String())) {
|
Reduction const reduction = ReduceJSToStringInput(r.left());
|
if (reduction.Changed()) {
|
NodeProperties::ReplaceValueInput(node, reduction.replacement(), 0);
|
}
|
}
|
// We might be able to constant-fold the String concatenation now.
|
if (r.BothInputsAre(Type::String())) {
|
HeapObjectBinopMatcher m(node);
|
if (m.IsFoldable()) {
|
StringRef left = m.left().Ref(js_heap_broker()).AsString();
|
StringRef right = m.right().Ref(js_heap_broker()).AsString();
|
if (left.length() + right.length() > String::kMaxLength) {
|
// No point in trying to optimize this, as it will just throw.
|
return NoChange();
|
}
|
// TODO(mslekova): get rid of these allows by doing either one of:
|
// 1. remove the optimization and check if it ruins the performance
|
// 2. leave a placeholder and do the actual allocations once back on the
|
// MT
|
AllowHandleDereference allow_handle_dereference;
|
AllowHandleAllocation allow_handle_allocation;
|
AllowHeapAllocation allow_heap_allocation;
|
ObjectRef cons(
|
js_heap_broker(),
|
factory()
|
->NewConsString(left.object<String>(), right.object<String>())
|
.ToHandleChecked());
|
Node* value = jsgraph()->Constant(cons);
|
ReplaceWithValue(node, value);
|
return Replace(value);
|
}
|
}
|
// We might know for sure that we're creating a ConsString here.
|
if (r.ShouldCreateConsString()) {
|
return ReduceCreateConsString(node);
|
}
|
// Eliminate useless concatenation of empty string.
|
if (r.BothInputsAre(Type::String())) {
|
Node* effect = NodeProperties::GetEffectInput(node);
|
Node* control = NodeProperties::GetControlInput(node);
|
if (r.LeftInputIs(empty_string_type_)) {
|
Node* value = effect =
|
graph()->NewNode(simplified()->CheckString(VectorSlotPair()),
|
r.right(), effect, control);
|
ReplaceWithValue(node, value, effect, control);
|
return Replace(value);
|
} else if (r.RightInputIs(empty_string_type_)) {
|
Node* value = effect =
|
graph()->NewNode(simplified()->CheckString(VectorSlotPair()),
|
r.left(), effect, control);
|
ReplaceWithValue(node, value, effect, control);
|
return Replace(value);
|
}
|
}
|
StringAddFlags flags = STRING_ADD_CHECK_NONE;
|
if (!r.LeftInputIs(Type::String())) {
|
flags = STRING_ADD_CONVERT_LEFT;
|
} else if (!r.RightInputIs(Type::String())) {
|
flags = STRING_ADD_CONVERT_RIGHT;
|
}
|
Operator::Properties properties = node->op()->properties();
|
if (r.NeitherInputCanBe(Type::Receiver())) {
|
// Both sides are already strings, so we know that the
|
// string addition will not cause any observable side
|
// effects; it can still throw obviously.
|
properties = Operator::kNoWrite | Operator::kNoDeopt;
|
}
|
// JSAdd(x:string, y) => CallStub[StringAdd](x, y)
|
// JSAdd(x, y:string) => CallStub[StringAdd](x, y)
|
Callable const callable =
|
CodeFactory::StringAdd(isolate(), flags, NOT_TENURED);
|
auto call_descriptor = Linkage::GetStubCallDescriptor(
|
graph()->zone(), callable.descriptor(), 0,
|
CallDescriptor::kNeedsFrameState, properties);
|
DCHECK_EQ(1, OperatorProperties::GetFrameStateInputCount(node->op()));
|
node->InsertInput(graph()->zone(), 0,
|
jsgraph()->HeapConstant(callable.code()));
|
NodeProperties::ChangeOp(node, common()->Call(call_descriptor));
|
return Changed(node);
|
}
|
// We never get here when we had String feedback.
|
DCHECK_NE(BinaryOperationHint::kString, BinaryOperationHintOf(node->op()));
|
return NoChange();
|
}
|
|
Reduction JSTypedLowering::ReduceNumberBinop(Node* node) {
|
JSBinopReduction r(this, node);
|
if (r.BothInputsAre(Type::PlainPrimitive())) {
|
r.ConvertInputsToNumber();
|
return r.ChangeToPureOperator(r.NumberOp(), Type::Number());
|
}
|
return NoChange();
|
}
|
|
Reduction JSTypedLowering::ReduceSpeculativeNumberBinop(Node* node) {
|
JSBinopReduction r(this, node);
|
NumberOperationHint hint = NumberOperationHintOf(node->op());
|
if ((hint == NumberOperationHint::kNumber ||
|
hint == NumberOperationHint::kNumberOrOddball) &&
|
r.BothInputsAre(Type::NumberOrUndefinedOrNullOrBoolean())) {
|
// We intentionally do this only in the Number and NumberOrOddball hint case
|
// because simplified lowering of these speculative ops may do some clever
|
// reductions in the other cases.
|
r.ConvertInputsToNumber();
|
return r.ChangeToPureOperator(r.NumberOpFromSpeculativeNumberOp(),
|
Type::Number());
|
}
|
return NoChange();
|
}
|
|
Reduction JSTypedLowering::ReduceInt32Binop(Node* node) {
|
JSBinopReduction r(this, node);
|
if (r.BothInputsAre(Type::PlainPrimitive())) {
|
r.ConvertInputsToNumber();
|
r.ConvertInputsToUI32(kSigned, kSigned);
|
return r.ChangeToPureOperator(r.NumberOp(), Type::Signed32());
|
}
|
return NoChange();
|
}
|
|
Reduction JSTypedLowering::ReduceUI32Shift(Node* node, Signedness signedness) {
|
JSBinopReduction r(this, node);
|
if (r.BothInputsAre(Type::PlainPrimitive())) {
|
r.ConvertInputsToNumber();
|
r.ConvertInputsToUI32(signedness, kUnsigned);
|
return r.ChangeToPureOperator(r.NumberOp(), signedness == kUnsigned
|
? Type::Unsigned32()
|
: Type::Signed32());
|
}
|
return NoChange();
|
}
|
|
Reduction JSTypedLowering::ReduceCreateConsString(Node* node) {
|
Node* first = NodeProperties::GetValueInput(node, 0);
|
Node* second = NodeProperties::GetValueInput(node, 1);
|
Node* context = NodeProperties::GetContextInput(node);
|
Node* frame_state = NodeProperties::GetFrameStateInput(node);
|
Node* effect = NodeProperties::GetEffectInput(node);
|
Node* control = NodeProperties::GetControlInput(node);
|
|
// Make sure {first} is actually a String.
|
Type first_type = NodeProperties::GetType(first);
|
if (!first_type.Is(Type::String())) {
|
first = effect = graph()->NewNode(
|
simplified()->CheckString(VectorSlotPair()), first, effect, control);
|
first_type = NodeProperties::GetType(first);
|
}
|
|
// Make sure {second} is actually a String.
|
Type second_type = NodeProperties::GetType(second);
|
if (!second_type.Is(Type::String())) {
|
second = effect = graph()->NewNode(
|
simplified()->CheckString(VectorSlotPair()), second, effect, control);
|
second_type = NodeProperties::GetType(second);
|
}
|
|
// Determine the {first} length.
|
Node* first_length = BuildGetStringLength(first);
|
Node* second_length = BuildGetStringLength(second);
|
|
// Compute the resulting length.
|
Node* length =
|
graph()->NewNode(simplified()->NumberAdd(), first_length, second_length);
|
|
if (isolate()->IsStringLengthOverflowIntact()) {
|
// We can just deoptimize if the {length} is out-of-bounds. Besides
|
// generating a shorter code sequence than the version below, this
|
// has the additional benefit of not holding on to the lazy {frame_state}
|
// and thus potentially reduces the number of live ranges and allows for
|
// more truncations.
|
length = effect = graph()->NewNode(
|
simplified()->CheckBounds(VectorSlotPair()), length,
|
jsgraph()->Constant(String::kMaxLength), effect, control);
|
} else {
|
// Check if we would overflow the allowed maximum string length.
|
Node* check =
|
graph()->NewNode(simplified()->NumberLessThanOrEqual(), length,
|
jsgraph()->Constant(String::kMaxLength));
|
Node* branch =
|
graph()->NewNode(common()->Branch(BranchHint::kTrue), check, control);
|
Node* if_false = graph()->NewNode(common()->IfFalse(), branch);
|
Node* efalse = effect;
|
{
|
// Throw a RangeError in case of overflow.
|
Node* vfalse = efalse = if_false = graph()->NewNode(
|
javascript()->CallRuntime(Runtime::kThrowInvalidStringLength),
|
context, frame_state, efalse, if_false);
|
|
// Update potential {IfException} uses of {node} to point to the
|
// %ThrowInvalidStringLength runtime call node instead.
|
Node* on_exception = nullptr;
|
if (NodeProperties::IsExceptionalCall(node, &on_exception)) {
|
NodeProperties::ReplaceControlInput(on_exception, vfalse);
|
NodeProperties::ReplaceEffectInput(on_exception, efalse);
|
if_false = graph()->NewNode(common()->IfSuccess(), vfalse);
|
Revisit(on_exception);
|
}
|
|
// The above %ThrowInvalidStringLength 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.
|
if_false = graph()->NewNode(common()->Throw(), efalse, if_false);
|
// TODO(bmeurer): This should be on the AdvancedReducer somehow.
|
NodeProperties::MergeControlToEnd(graph(), common(), if_false);
|
Revisit(graph()->end());
|
}
|
control = graph()->NewNode(common()->IfTrue(), branch);
|
length = effect =
|
graph()->NewNode(common()->TypeGuard(type_cache_.kStringLengthType),
|
length, effect, control);
|
}
|
|
Node* value =
|
graph()->NewNode(simplified()->NewConsString(), length, first, second);
|
ReplaceWithValue(node, value, effect, control);
|
return Replace(value);
|
}
|
|
Node* JSTypedLowering::BuildGetStringLength(Node* value) {
|
// TODO(bmeurer): Get rid of this hack and instead have a way to
|
// express the string length in the types.
|
HeapObjectMatcher m(value);
|
if (!m.HasValue() || !m.Ref(js_heap_broker()).IsString()) {
|
return graph()->NewNode(simplified()->StringLength(), value);
|
}
|
|
return jsgraph()->Constant(m.Ref(js_heap_broker()).AsString().length());
|
}
|
|
Reduction JSTypedLowering::ReduceSpeculativeNumberComparison(Node* node) {
|
JSBinopReduction r(this, node);
|
if (r.BothInputsAre(Type::Signed32()) ||
|
r.BothInputsAre(Type::Unsigned32())) {
|
return r.ChangeToPureOperator(r.NumberOpFromSpeculativeNumberOp());
|
}
|
return NoChange();
|
}
|
|
Reduction JSTypedLowering::ReduceJSComparison(Node* node) {
|
JSBinopReduction r(this, node);
|
if (r.BothInputsAre(Type::String())) {
|
// If both inputs are definitely strings, perform a string comparison.
|
const Operator* stringOp;
|
switch (node->opcode()) {
|
case IrOpcode::kJSLessThan:
|
stringOp = simplified()->StringLessThan();
|
break;
|
case IrOpcode::kJSGreaterThan:
|
stringOp = simplified()->StringLessThan();
|
r.SwapInputs(); // a > b => b < a
|
break;
|
case IrOpcode::kJSLessThanOrEqual:
|
stringOp = simplified()->StringLessThanOrEqual();
|
break;
|
case IrOpcode::kJSGreaterThanOrEqual:
|
stringOp = simplified()->StringLessThanOrEqual();
|
r.SwapInputs(); // a >= b => b <= a
|
break;
|
default:
|
return NoChange();
|
}
|
r.ChangeToPureOperator(stringOp);
|
return Changed(node);
|
}
|
|
const Operator* less_than;
|
const Operator* less_than_or_equal;
|
if (r.BothInputsAre(Type::Signed32()) ||
|
r.BothInputsAre(Type::Unsigned32())) {
|
less_than = simplified()->NumberLessThan();
|
less_than_or_equal = simplified()->NumberLessThanOrEqual();
|
} else if (r.OneInputCannotBe(Type::StringOrReceiver()) &&
|
r.BothInputsAre(Type::PlainPrimitive())) {
|
r.ConvertInputsToNumber();
|
less_than = simplified()->NumberLessThan();
|
less_than_or_equal = simplified()->NumberLessThanOrEqual();
|
} else if (r.IsStringCompareOperation()) {
|
r.CheckInputsToString();
|
less_than = simplified()->StringLessThan();
|
less_than_or_equal = simplified()->StringLessThanOrEqual();
|
} else {
|
return NoChange();
|
}
|
const Operator* comparison;
|
switch (node->opcode()) {
|
case IrOpcode::kJSLessThan:
|
comparison = less_than;
|
break;
|
case IrOpcode::kJSGreaterThan:
|
comparison = less_than;
|
r.SwapInputs(); // a > b => b < a
|
break;
|
case IrOpcode::kJSLessThanOrEqual:
|
comparison = less_than_or_equal;
|
break;
|
case IrOpcode::kJSGreaterThanOrEqual:
|
comparison = less_than_or_equal;
|
r.SwapInputs(); // a >= b => b <= a
|
break;
|
default:
|
return NoChange();
|
}
|
return r.ChangeToPureOperator(comparison);
|
}
|
|
Reduction JSTypedLowering::ReduceJSEqual(Node* node) {
|
JSBinopReduction r(this, node);
|
|
if (r.BothInputsAre(Type::UniqueName())) {
|
return r.ChangeToPureOperator(simplified()->ReferenceEqual());
|
}
|
if (r.IsInternalizedStringCompareOperation()) {
|
r.CheckInputsToInternalizedString();
|
return r.ChangeToPureOperator(simplified()->ReferenceEqual());
|
}
|
if (r.BothInputsAre(Type::String())) {
|
return r.ChangeToPureOperator(simplified()->StringEqual());
|
}
|
if (r.BothInputsAre(Type::Boolean())) {
|
return r.ChangeToPureOperator(simplified()->ReferenceEqual());
|
}
|
if (r.BothInputsAre(Type::Receiver())) {
|
return r.ChangeToPureOperator(simplified()->ReferenceEqual());
|
}
|
if (r.OneInputIs(Type::Undetectable())) {
|
RelaxEffectsAndControls(node);
|
node->RemoveInput(r.LeftInputIs(Type::Undetectable()) ? 0 : 1);
|
node->TrimInputCount(1);
|
NodeProperties::ChangeOp(node, simplified()->ObjectIsUndetectable());
|
return Changed(node);
|
}
|
|
if (r.BothInputsAre(Type::Signed32()) ||
|
r.BothInputsAre(Type::Unsigned32())) {
|
return r.ChangeToPureOperator(simplified()->NumberEqual());
|
} else if (r.BothInputsAre(Type::Number())) {
|
return r.ChangeToPureOperator(simplified()->NumberEqual());
|
} else if (r.IsReceiverCompareOperation()) {
|
r.CheckInputsToReceiver();
|
return r.ChangeToPureOperator(simplified()->ReferenceEqual());
|
} else if (r.IsStringCompareOperation()) {
|
r.CheckInputsToString();
|
return r.ChangeToPureOperator(simplified()->StringEqual());
|
} else if (r.IsSymbolCompareOperation()) {
|
r.CheckInputsToSymbol();
|
return r.ChangeToPureOperator(simplified()->ReferenceEqual());
|
}
|
return NoChange();
|
}
|
|
Reduction JSTypedLowering::ReduceJSStrictEqual(Node* node) {
|
JSBinopReduction r(this, node);
|
if (r.left() == r.right()) {
|
// x === x is always true if x != NaN
|
Node* replacement = graph()->NewNode(
|
simplified()->BooleanNot(),
|
graph()->NewNode(simplified()->ObjectIsNaN(), r.left()));
|
ReplaceWithValue(node, replacement);
|
return Replace(replacement);
|
}
|
if (r.OneInputCannotBe(Type::NumericOrString())) {
|
// For values with canonical representation (i.e. neither String nor
|
// Numeric) an empty type intersection means the values cannot be strictly
|
// equal.
|
if (!r.left_type().Maybe(r.right_type())) {
|
Node* replacement = jsgraph()->FalseConstant();
|
ReplaceWithValue(node, replacement);
|
return Replace(replacement);
|
}
|
}
|
|
if (r.BothInputsAre(Type::Unique())) {
|
return r.ChangeToPureOperator(simplified()->ReferenceEqual());
|
}
|
if (r.OneInputIs(pointer_comparable_type_)) {
|
return r.ChangeToPureOperator(simplified()->ReferenceEqual());
|
}
|
if (r.IsInternalizedStringCompareOperation()) {
|
r.CheckInputsToInternalizedString();
|
return r.ChangeToPureOperator(simplified()->ReferenceEqual());
|
}
|
if (r.BothInputsAre(Type::String())) {
|
return r.ChangeToPureOperator(simplified()->StringEqual());
|
}
|
|
NumberOperationHint hint;
|
if (r.BothInputsAre(Type::Signed32()) ||
|
r.BothInputsAre(Type::Unsigned32())) {
|
return r.ChangeToPureOperator(simplified()->NumberEqual());
|
} else if (r.GetCompareNumberOperationHint(&hint)) {
|
return r.ChangeToSpeculativeOperator(
|
simplified()->SpeculativeNumberEqual(hint), Type::Boolean());
|
} else if (r.BothInputsAre(Type::Number())) {
|
return r.ChangeToPureOperator(simplified()->NumberEqual());
|
} else if (r.IsReceiverCompareOperation()) {
|
// For strict equality, it's enough to know that one input is a Receiver,
|
// as a strict equality comparison with a Receiver can only yield true if
|
// both sides refer to the same Receiver than.
|
r.CheckLeftInputToReceiver();
|
return r.ChangeToPureOperator(simplified()->ReferenceEqual());
|
} else if (r.IsStringCompareOperation()) {
|
r.CheckInputsToString();
|
return r.ChangeToPureOperator(simplified()->StringEqual());
|
} else if (r.IsSymbolCompareOperation()) {
|
r.CheckInputsToSymbol();
|
return r.ChangeToPureOperator(simplified()->ReferenceEqual());
|
}
|
return NoChange();
|
}
|
|
Reduction JSTypedLowering::ReduceJSToInteger(Node* node) {
|
Node* const input = NodeProperties::GetValueInput(node, 0);
|
Type const input_type = NodeProperties::GetType(input);
|
if (input_type.Is(type_cache_.kIntegerOrMinusZero)) {
|
// JSToInteger(x:integer) => x
|
ReplaceWithValue(node, input);
|
return Replace(input);
|
}
|
return NoChange();
|
}
|
|
Reduction JSTypedLowering::ReduceJSToName(Node* node) {
|
Node* const input = NodeProperties::GetValueInput(node, 0);
|
Type const input_type = NodeProperties::GetType(input);
|
if (input_type.Is(Type::Name())) {
|
// JSToName(x:name) => x
|
ReplaceWithValue(node, input);
|
return Replace(input);
|
}
|
return NoChange();
|
}
|
|
Reduction JSTypedLowering::ReduceJSToLength(Node* node) {
|
Node* input = NodeProperties::GetValueInput(node, 0);
|
Type input_type = NodeProperties::GetType(input);
|
if (input_type.Is(type_cache_.kIntegerOrMinusZero)) {
|
if (input_type.IsNone() || input_type.Max() <= 0.0) {
|
input = jsgraph()->ZeroConstant();
|
} else if (input_type.Min() >= kMaxSafeInteger) {
|
input = jsgraph()->Constant(kMaxSafeInteger);
|
} else {
|
if (input_type.Min() <= 0.0) {
|
input = graph()->NewNode(simplified()->NumberMax(),
|
jsgraph()->ZeroConstant(), input);
|
}
|
if (input_type.Max() > kMaxSafeInteger) {
|
input = graph()->NewNode(simplified()->NumberMin(),
|
jsgraph()->Constant(kMaxSafeInteger), input);
|
}
|
}
|
ReplaceWithValue(node, input);
|
return Replace(input);
|
}
|
return NoChange();
|
}
|
|
Reduction JSTypedLowering::ReduceJSToNumberOrNumericInput(Node* input) {
|
// Try constant-folding of JSToNumber/JSToNumeric with constant inputs. Here
|
// we only cover cases where ToNumber and ToNumeric coincide.
|
Type input_type = NodeProperties::GetType(input);
|
|
if (input_type.Is(Type::String())) {
|
HeapObjectMatcher m(input);
|
if (m.HasValue() && m.Ref(js_heap_broker()).IsString()) {
|
StringRef input_value = m.Ref(js_heap_broker()).AsString();
|
double number;
|
ASSIGN_RETURN_NO_CHANGE_IF_DATA_MISSING(number, input_value.ToNumber());
|
return Replace(jsgraph()->Constant(number));
|
}
|
}
|
if (input_type.IsHeapConstant()) {
|
ObjectRef input_value = input_type.AsHeapConstant()->Ref();
|
if (input_value.oddball_type() != OddballType::kNone) {
|
return Replace(jsgraph()->Constant(input_value.OddballToNumber()));
|
}
|
}
|
if (input_type.Is(Type::Number())) {
|
// JSToNumber(x:number) => x
|
return Changed(input);
|
}
|
if (input_type.Is(Type::Undefined())) {
|
// JSToNumber(undefined) => #NaN
|
return Replace(jsgraph()->NaNConstant());
|
}
|
if (input_type.Is(Type::Null())) {
|
// JSToNumber(null) => #0
|
return Replace(jsgraph()->ZeroConstant());
|
}
|
return NoChange();
|
}
|
|
Reduction JSTypedLowering::ReduceJSToNumberOrNumeric(Node* node) {
|
// Try to reduce the input first.
|
Node* const input = node->InputAt(0);
|
Reduction reduction = ReduceJSToNumberOrNumericInput(input);
|
if (reduction.Changed()) {
|
ReplaceWithValue(node, reduction.replacement());
|
return reduction;
|
}
|
Type const input_type = NodeProperties::GetType(input);
|
if (input_type.Is(Type::PlainPrimitive())) {
|
RelaxEffectsAndControls(node);
|
node->TrimInputCount(1);
|
// For a PlainPrimitive, ToNumeric is the same as ToNumber.
|
Type node_type = NodeProperties::GetType(node);
|
NodeProperties::SetType(
|
node, Type::Intersect(node_type, Type::Number(), graph()->zone()));
|
NodeProperties::ChangeOp(node, simplified()->PlainPrimitiveToNumber());
|
return Changed(node);
|
}
|
// TODO(neis): Reduce ToNumeric to ToNumber if input can't be BigInt?
|
return NoChange();
|
}
|
|
Reduction JSTypedLowering::ReduceJSToStringInput(Node* input) {
|
if (input->opcode() == IrOpcode::kJSToString) {
|
// Recursively try to reduce the input first.
|
Reduction result = ReduceJSToString(input);
|
if (result.Changed()) return result;
|
return Changed(input); // JSToString(JSToString(x)) => JSToString(x)
|
}
|
Type input_type = NodeProperties::GetType(input);
|
if (input_type.Is(Type::String())) {
|
return Changed(input); // JSToString(x:string) => x
|
}
|
if (input_type.Is(Type::Boolean())) {
|
return Replace(graph()->NewNode(
|
common()->Select(MachineRepresentation::kTagged), input,
|
jsgraph()->HeapConstant(factory()->true_string()),
|
jsgraph()->HeapConstant(factory()->false_string())));
|
}
|
if (input_type.Is(Type::Undefined())) {
|
return Replace(jsgraph()->HeapConstant(factory()->undefined_string()));
|
}
|
if (input_type.Is(Type::Null())) {
|
return Replace(jsgraph()->HeapConstant(factory()->null_string()));
|
}
|
if (input_type.Is(Type::NaN())) {
|
return Replace(jsgraph()->HeapConstant(factory()->NaN_string()));
|
}
|
if (input_type.Is(Type::OrderedNumber()) &&
|
input_type.Min() == input_type.Max()) {
|
// TODO(mslekova): get rid of these allows by doing either one of:
|
// 1. remove the optimization and check if it ruins the performance
|
// 2. allocate all the ToString's from numbers before the compilation
|
// 3. leave a placeholder and do the actual allocations once back on the MT
|
AllowHandleDereference allow_handle_dereference;
|
AllowHandleAllocation allow_handle_allocation;
|
AllowHeapAllocation allow_heap_allocation;
|
// Note that we can use Type::OrderedNumber(), since
|
// both 0 and -0 map to the String "0" in JavaScript.
|
return Replace(jsgraph()->HeapConstant(
|
factory()->NumberToString(factory()->NewNumber(input_type.Min()))));
|
}
|
if (input_type.Is(Type::Number())) {
|
return Replace(graph()->NewNode(simplified()->NumberToString(), input));
|
}
|
return NoChange();
|
}
|
|
Reduction JSTypedLowering::ReduceJSToString(Node* node) {
|
DCHECK_EQ(IrOpcode::kJSToString, node->opcode());
|
// Try to reduce the input first.
|
Node* const input = node->InputAt(0);
|
Reduction reduction = ReduceJSToStringInput(input);
|
if (reduction.Changed()) {
|
ReplaceWithValue(node, reduction.replacement());
|
return reduction;
|
}
|
return NoChange();
|
}
|
|
Reduction JSTypedLowering::ReduceJSToObject(Node* node) {
|
DCHECK_EQ(IrOpcode::kJSToObject, node->opcode());
|
Node* receiver = NodeProperties::GetValueInput(node, 0);
|
Type receiver_type = NodeProperties::GetType(receiver);
|
Node* context = NodeProperties::GetContextInput(node);
|
Node* frame_state = NodeProperties::GetFrameStateInput(node);
|
Node* effect = NodeProperties::GetEffectInput(node);
|
Node* control = NodeProperties::GetControlInput(node);
|
if (receiver_type.Is(Type::Receiver())) {
|
ReplaceWithValue(node, receiver, effect, control);
|
return Replace(receiver);
|
}
|
|
// Check whether {receiver} is a spec object.
|
Node* check = graph()->NewNode(simplified()->ObjectIsReceiver(), receiver);
|
Node* branch =
|
graph()->NewNode(common()->Branch(BranchHint::kTrue), check, control);
|
|
Node* if_true = graph()->NewNode(common()->IfTrue(), branch);
|
Node* etrue = effect;
|
Node* rtrue = receiver;
|
|
Node* if_false = graph()->NewNode(common()->IfFalse(), branch);
|
Node* efalse = effect;
|
Node* rfalse;
|
{
|
// Convert {receiver} using the ToObjectStub.
|
Callable callable = Builtins::CallableFor(isolate(), Builtins::kToObject);
|
auto call_descriptor = Linkage::GetStubCallDescriptor(
|
graph()->zone(), callable.descriptor(), 0,
|
CallDescriptor::kNeedsFrameState, node->op()->properties());
|
rfalse = efalse = if_false =
|
graph()->NewNode(common()->Call(call_descriptor),
|
jsgraph()->HeapConstant(callable.code()), receiver,
|
context, frame_state, efalse, if_false);
|
}
|
|
// Update potential {IfException} uses of {node} to point to the above
|
// ToObject stub call node instead. Note that the stub can only throw on
|
// receivers that can be null or undefined.
|
Node* on_exception = nullptr;
|
if (receiver_type.Maybe(Type::NullOrUndefined()) &&
|
NodeProperties::IsExceptionalCall(node, &on_exception)) {
|
NodeProperties::ReplaceControlInput(on_exception, if_false);
|
NodeProperties::ReplaceEffectInput(on_exception, efalse);
|
if_false = graph()->NewNode(common()->IfSuccess(), if_false);
|
Revisit(on_exception);
|
}
|
|
control = graph()->NewNode(common()->Merge(2), if_true, if_false);
|
effect = graph()->NewNode(common()->EffectPhi(2), etrue, efalse, control);
|
|
// Morph the {node} into an appropriate Phi.
|
ReplaceWithValue(node, node, effect, control);
|
node->ReplaceInput(0, rtrue);
|
node->ReplaceInput(1, rfalse);
|
node->ReplaceInput(2, control);
|
node->TrimInputCount(3);
|
NodeProperties::ChangeOp(node,
|
common()->Phi(MachineRepresentation::kTagged, 2));
|
return Changed(node);
|
}
|
|
Reduction JSTypedLowering::ReduceJSLoadNamed(Node* node) {
|
DCHECK_EQ(IrOpcode::kJSLoadNamed, node->opcode());
|
Node* receiver = NodeProperties::GetValueInput(node, 0);
|
Type receiver_type = NodeProperties::GetType(receiver);
|
NameRef name(js_heap_broker(), NamedAccessOf(node->op()).name());
|
NameRef length_str(js_heap_broker(), factory()->length_string());
|
// Optimize "length" property of strings.
|
if (name.equals(length_str) && receiver_type.Is(Type::String())) {
|
Node* value = graph()->NewNode(simplified()->StringLength(), receiver);
|
ReplaceWithValue(node, value);
|
return Replace(value);
|
}
|
return NoChange();
|
}
|
|
Reduction JSTypedLowering::ReduceJSHasInPrototypeChain(Node* node) {
|
DCHECK_EQ(IrOpcode::kJSHasInPrototypeChain, node->opcode());
|
Node* value = NodeProperties::GetValueInput(node, 0);
|
Type value_type = NodeProperties::GetType(value);
|
Node* prototype = NodeProperties::GetValueInput(node, 1);
|
Node* context = NodeProperties::GetContextInput(node);
|
Node* frame_state = NodeProperties::GetFrameStateInput(node);
|
Node* effect = NodeProperties::GetEffectInput(node);
|
Node* control = NodeProperties::GetControlInput(node);
|
|
// If {value} cannot be a receiver, then it cannot have {prototype} in
|
// it's prototype chain (all Primitive values have a null prototype).
|
if (value_type.Is(Type::Primitive())) {
|
Node* value = jsgraph()->FalseConstant();
|
ReplaceWithValue(node, value, effect, control);
|
return Replace(value);
|
}
|
|
Node* check0 = graph()->NewNode(simplified()->ObjectIsSmi(), value);
|
Node* branch0 =
|
graph()->NewNode(common()->Branch(BranchHint::kFalse), check0, control);
|
|
Node* if_true0 = graph()->NewNode(common()->IfTrue(), branch0);
|
Node* etrue0 = effect;
|
Node* vtrue0 = jsgraph()->FalseConstant();
|
|
control = graph()->NewNode(common()->IfFalse(), branch0);
|
|
// Loop through the {value}s prototype chain looking for the {prototype}.
|
Node* loop = control = graph()->NewNode(common()->Loop(2), control, control);
|
Node* eloop = effect =
|
graph()->NewNode(common()->EffectPhi(2), effect, effect, loop);
|
Node* terminate = graph()->NewNode(common()->Terminate(), eloop, loop);
|
NodeProperties::MergeControlToEnd(graph(), common(), terminate);
|
Node* vloop = value = graph()->NewNode(
|
common()->Phi(MachineRepresentation::kTagged, 2), value, value, loop);
|
NodeProperties::SetType(vloop, Type::NonInternal());
|
|
// Load the {value} map and instance type.
|
Node* value_map = effect = graph()->NewNode(
|
simplified()->LoadField(AccessBuilder::ForMap()), value, effect, control);
|
Node* value_instance_type = effect = graph()->NewNode(
|
simplified()->LoadField(AccessBuilder::ForMapInstanceType()), value_map,
|
effect, control);
|
|
// Check if the {value} is a special receiver, because for special
|
// receivers, i.e. proxies or API values that need access checks,
|
// we have to use the %HasInPrototypeChain runtime function instead.
|
Node* check1 = graph()->NewNode(
|
simplified()->NumberLessThanOrEqual(), value_instance_type,
|
jsgraph()->Constant(LAST_SPECIAL_RECEIVER_TYPE));
|
Node* branch1 =
|
graph()->NewNode(common()->Branch(BranchHint::kFalse), check1, control);
|
|
control = graph()->NewNode(common()->IfFalse(), branch1);
|
|
Node* if_true1 = graph()->NewNode(common()->IfTrue(), branch1);
|
Node* etrue1 = effect;
|
Node* vtrue1;
|
|
// Check if the {value} is not a receiver at all.
|
Node* check10 =
|
graph()->NewNode(simplified()->NumberLessThan(), value_instance_type,
|
jsgraph()->Constant(FIRST_JS_RECEIVER_TYPE));
|
Node* branch10 =
|
graph()->NewNode(common()->Branch(BranchHint::kTrue), check10, if_true1);
|
|
// A primitive value cannot match the {prototype} we're looking for.
|
if_true1 = graph()->NewNode(common()->IfTrue(), branch10);
|
vtrue1 = jsgraph()->FalseConstant();
|
|
Node* if_false1 = graph()->NewNode(common()->IfFalse(), branch10);
|
Node* efalse1 = etrue1;
|
Node* vfalse1;
|
{
|
// Slow path, need to call the %HasInPrototypeChain runtime function.
|
vfalse1 = efalse1 = if_false1 = graph()->NewNode(
|
javascript()->CallRuntime(Runtime::kHasInPrototypeChain), value,
|
prototype, context, frame_state, efalse1, if_false1);
|
|
// Replace any potential {IfException} uses of {node} to catch
|
// exceptions from this %HasInPrototypeChain runtime call instead.
|
Node* on_exception = nullptr;
|
if (NodeProperties::IsExceptionalCall(node, &on_exception)) {
|
NodeProperties::ReplaceControlInput(on_exception, vfalse1);
|
NodeProperties::ReplaceEffectInput(on_exception, efalse1);
|
if_false1 = graph()->NewNode(common()->IfSuccess(), vfalse1);
|
Revisit(on_exception);
|
}
|
}
|
|
// Load the {value} prototype.
|
Node* value_prototype = effect = graph()->NewNode(
|
simplified()->LoadField(AccessBuilder::ForMapPrototype()), value_map,
|
effect, control);
|
|
// Check if we reached the end of {value}s prototype chain.
|
Node* check2 = graph()->NewNode(simplified()->ReferenceEqual(),
|
value_prototype, jsgraph()->NullConstant());
|
Node* branch2 = graph()->NewNode(common()->Branch(), check2, control);
|
|
Node* if_true2 = graph()->NewNode(common()->IfTrue(), branch2);
|
Node* etrue2 = effect;
|
Node* vtrue2 = jsgraph()->FalseConstant();
|
|
control = graph()->NewNode(common()->IfFalse(), branch2);
|
|
// Check if we reached the {prototype}.
|
Node* check3 = graph()->NewNode(simplified()->ReferenceEqual(),
|
value_prototype, prototype);
|
Node* branch3 = graph()->NewNode(common()->Branch(), check3, control);
|
|
Node* if_true3 = graph()->NewNode(common()->IfTrue(), branch3);
|
Node* etrue3 = effect;
|
Node* vtrue3 = jsgraph()->TrueConstant();
|
|
control = graph()->NewNode(common()->IfFalse(), branch3);
|
|
// Close the loop.
|
vloop->ReplaceInput(1, value_prototype);
|
eloop->ReplaceInput(1, effect);
|
loop->ReplaceInput(1, control);
|
|
control = graph()->NewNode(common()->Merge(5), if_true0, if_true1, if_true2,
|
if_true3, if_false1);
|
effect = graph()->NewNode(common()->EffectPhi(5), etrue0, etrue1, etrue2,
|
etrue3, efalse1, control);
|
|
// Morph the {node} into an appropriate Phi.
|
ReplaceWithValue(node, node, effect, control);
|
node->ReplaceInput(0, vtrue0);
|
node->ReplaceInput(1, vtrue1);
|
node->ReplaceInput(2, vtrue2);
|
node->ReplaceInput(3, vtrue3);
|
node->ReplaceInput(4, vfalse1);
|
node->ReplaceInput(5, control);
|
node->TrimInputCount(6);
|
NodeProperties::ChangeOp(node,
|
common()->Phi(MachineRepresentation::kTagged, 5));
|
return Changed(node);
|
}
|
|
Reduction JSTypedLowering::ReduceJSOrdinaryHasInstance(Node* node) {
|
DCHECK_EQ(IrOpcode::kJSOrdinaryHasInstance, node->opcode());
|
Node* constructor = NodeProperties::GetValueInput(node, 0);
|
Type constructor_type = NodeProperties::GetType(constructor);
|
Node* object = NodeProperties::GetValueInput(node, 1);
|
Type object_type = NodeProperties::GetType(object);
|
|
// Check if the {constructor} cannot be callable.
|
// See ES6 section 7.3.19 OrdinaryHasInstance ( C, O ) step 1.
|
if (!constructor_type.Maybe(Type::Callable())) {
|
Node* value = jsgraph()->FalseConstant();
|
ReplaceWithValue(node, value);
|
return Replace(value);
|
}
|
|
// If the {constructor} cannot be a JSBoundFunction and then {object}
|
// cannot be a JSReceiver, then this can be constant-folded to false.
|
// See ES6 section 7.3.19 OrdinaryHasInstance ( C, O ) step 2 and 3.
|
if (!object_type.Maybe(Type::Receiver()) &&
|
!constructor_type.Maybe(Type::BoundFunction())) {
|
Node* value = jsgraph()->FalseConstant();
|
ReplaceWithValue(node, value);
|
return Replace(value);
|
}
|
|
return NoChange();
|
}
|
|
Reduction JSTypedLowering::ReduceJSLoadContext(Node* node) {
|
DCHECK_EQ(IrOpcode::kJSLoadContext, node->opcode());
|
ContextAccess const& access = ContextAccessOf(node->op());
|
Node* effect = NodeProperties::GetEffectInput(node);
|
Node* context = NodeProperties::GetContextInput(node);
|
Node* control = graph()->start();
|
for (size_t i = 0; i < access.depth(); ++i) {
|
context = effect = graph()->NewNode(
|
simplified()->LoadField(
|
AccessBuilder::ForContextSlot(Context::PREVIOUS_INDEX)),
|
context, effect, control);
|
}
|
node->ReplaceInput(0, context);
|
node->ReplaceInput(1, effect);
|
node->AppendInput(jsgraph()->zone(), control);
|
NodeProperties::ChangeOp(
|
node,
|
simplified()->LoadField(AccessBuilder::ForContextSlot(access.index())));
|
return Changed(node);
|
}
|
|
Reduction JSTypedLowering::ReduceJSStoreContext(Node* node) {
|
DCHECK_EQ(IrOpcode::kJSStoreContext, node->opcode());
|
ContextAccess const& access = ContextAccessOf(node->op());
|
Node* effect = NodeProperties::GetEffectInput(node);
|
Node* context = NodeProperties::GetContextInput(node);
|
Node* control = graph()->start();
|
Node* value = NodeProperties::GetValueInput(node, 0);
|
for (size_t i = 0; i < access.depth(); ++i) {
|
context = effect = graph()->NewNode(
|
simplified()->LoadField(
|
AccessBuilder::ForContextSlot(Context::PREVIOUS_INDEX)),
|
context, effect, control);
|
}
|
node->ReplaceInput(0, context);
|
node->ReplaceInput(1, value);
|
node->ReplaceInput(2, effect);
|
NodeProperties::ChangeOp(
|
node,
|
simplified()->StoreField(AccessBuilder::ForContextSlot(access.index())));
|
return Changed(node);
|
}
|
|
Node* JSTypedLowering::BuildGetModuleCell(Node* node) {
|
DCHECK(node->opcode() == IrOpcode::kJSLoadModule ||
|
node->opcode() == IrOpcode::kJSStoreModule);
|
Node* effect = NodeProperties::GetEffectInput(node);
|
Node* control = NodeProperties::GetControlInput(node);
|
|
int32_t cell_index = OpParameter<int32_t>(node->op());
|
Node* module = NodeProperties::GetValueInput(node, 0);
|
Type module_type = NodeProperties::GetType(module);
|
|
if (module_type.IsHeapConstant()) {
|
ModuleRef module_constant = module_type.AsHeapConstant()->Ref().AsModule();
|
CellRef cell_constant = module_constant.GetCell(cell_index);
|
return jsgraph()->Constant(cell_constant);
|
}
|
|
FieldAccess field_access;
|
int index;
|
if (ModuleDescriptor::GetCellIndexKind(cell_index) ==
|
ModuleDescriptor::kExport) {
|
field_access = AccessBuilder::ForModuleRegularExports();
|
index = cell_index - 1;
|
} else {
|
DCHECK_EQ(ModuleDescriptor::GetCellIndexKind(cell_index),
|
ModuleDescriptor::kImport);
|
field_access = AccessBuilder::ForModuleRegularImports();
|
index = -cell_index - 1;
|
}
|
Node* array = effect = graph()->NewNode(simplified()->LoadField(field_access),
|
module, effect, control);
|
return graph()->NewNode(
|
simplified()->LoadField(AccessBuilder::ForFixedArraySlot(index)), array,
|
effect, control);
|
}
|
|
Reduction JSTypedLowering::ReduceJSLoadModule(Node* node) {
|
DCHECK_EQ(IrOpcode::kJSLoadModule, node->opcode());
|
Node* effect = NodeProperties::GetEffectInput(node);
|
Node* control = NodeProperties::GetControlInput(node);
|
|
Node* cell = BuildGetModuleCell(node);
|
if (cell->op()->EffectOutputCount() > 0) effect = cell;
|
Node* value = effect =
|
graph()->NewNode(simplified()->LoadField(AccessBuilder::ForCellValue()),
|
cell, effect, control);
|
|
ReplaceWithValue(node, value, effect, control);
|
return Changed(value);
|
}
|
|
Reduction JSTypedLowering::ReduceJSStoreModule(Node* node) {
|
DCHECK_EQ(IrOpcode::kJSStoreModule, node->opcode());
|
Node* effect = NodeProperties::GetEffectInput(node);
|
Node* control = NodeProperties::GetControlInput(node);
|
Node* value = NodeProperties::GetValueInput(node, 1);
|
DCHECK_EQ(
|
ModuleDescriptor::GetCellIndexKind(OpParameter<int32_t>(node->op())),
|
ModuleDescriptor::kExport);
|
|
Node* cell = BuildGetModuleCell(node);
|
if (cell->op()->EffectOutputCount() > 0) effect = cell;
|
effect =
|
graph()->NewNode(simplified()->StoreField(AccessBuilder::ForCellValue()),
|
cell, value, effect, control);
|
|
ReplaceWithValue(node, effect, effect, control);
|
return Changed(value);
|
}
|
|
namespace {
|
|
void ReduceBuiltin(JSGraph* jsgraph, Node* node, int builtin_index, int arity,
|
CallDescriptor::Flags flags) {
|
// Patch {node} to a direct CEntry call.
|
//
|
// ----------- A r g u m e n t s -----------
|
// -- 0: CEntry
|
// --- Stack args ---
|
// -- 1: receiver
|
// -- [2, 2 + n[: the n actual arguments passed to the builtin
|
// -- 2 + n: argc, including the receiver and implicit args (Smi)
|
// -- 2 + n + 1: target
|
// -- 2 + n + 2: new_target
|
// --- Register args ---
|
// -- 2 + n + 3: the C entry point
|
// -- 2 + n + 4: argc (Int32)
|
// -----------------------------------
|
|
// The logic contained here is mirrored in Builtins::Generate_Adaptor.
|
// Keep these in sync.
|
|
const bool is_construct = (node->opcode() == IrOpcode::kJSConstruct);
|
|
DCHECK(Builtins::HasCppImplementation(builtin_index));
|
|
Node* target = NodeProperties::GetValueInput(node, 0);
|
Node* new_target = is_construct
|
? NodeProperties::GetValueInput(node, arity + 1)
|
: jsgraph->UndefinedConstant();
|
|
// API and CPP builtins are implemented in C++, and we can inline both.
|
// CPP builtins create a builtin exit frame, API builtins don't.
|
const bool has_builtin_exit_frame = Builtins::IsCpp(builtin_index);
|
|
Node* stub = jsgraph->CEntryStubConstant(1, kDontSaveFPRegs, kArgvOnStack,
|
has_builtin_exit_frame);
|
node->ReplaceInput(0, stub);
|
|
Zone* zone = jsgraph->zone();
|
if (is_construct) {
|
// Unify representations between construct and call nodes.
|
// Remove new target and add receiver as a stack parameter.
|
Node* receiver = jsgraph->UndefinedConstant();
|
node->RemoveInput(arity + 1);
|
node->InsertInput(zone, 1, receiver);
|
}
|
|
const int argc = arity + BuiltinArguments::kNumExtraArgsWithReceiver;
|
Node* argc_node = jsgraph->Constant(argc);
|
|
static const int kStubAndReceiver = 2;
|
int cursor = arity + kStubAndReceiver;
|
node->InsertInput(zone, cursor++, jsgraph->PaddingConstant());
|
node->InsertInput(zone, cursor++, argc_node);
|
node->InsertInput(zone, cursor++, target);
|
node->InsertInput(zone, cursor++, new_target);
|
|
Address entry = Builtins::CppEntryOf(builtin_index);
|
ExternalReference entry_ref = ExternalReference::Create(entry);
|
Node* entry_node = jsgraph->ExternalConstant(entry_ref);
|
|
node->InsertInput(zone, cursor++, entry_node);
|
node->InsertInput(zone, cursor++, argc_node);
|
|
static const int kReturnCount = 1;
|
const char* debug_name = Builtins::name(builtin_index);
|
Operator::Properties properties = node->op()->properties();
|
auto call_descriptor = Linkage::GetCEntryStubCallDescriptor(
|
zone, kReturnCount, argc, debug_name, properties, flags);
|
|
NodeProperties::ChangeOp(node, jsgraph->common()->Call(call_descriptor));
|
}
|
|
bool NeedsArgumentAdaptorFrame(SharedFunctionInfoRef shared, int arity) {
|
static const int sentinel = SharedFunctionInfo::kDontAdaptArgumentsSentinel;
|
const int num_decl_parms = shared.internal_formal_parameter_count();
|
return (num_decl_parms != arity && num_decl_parms != sentinel);
|
}
|
|
} // namespace
|
|
Reduction JSTypedLowering::ReduceJSConstructForwardVarargs(Node* node) {
|
DCHECK_EQ(IrOpcode::kJSConstructForwardVarargs, node->opcode());
|
ConstructForwardVarargsParameters p =
|
ConstructForwardVarargsParametersOf(node->op());
|
DCHECK_LE(2u, p.arity());
|
int const arity = static_cast<int>(p.arity() - 2);
|
int const start_index = static_cast<int>(p.start_index());
|
Node* target = NodeProperties::GetValueInput(node, 0);
|
Type target_type = NodeProperties::GetType(target);
|
Node* new_target = NodeProperties::GetValueInput(node, arity + 1);
|
|
// Check if {target} is a JSFunction.
|
if (target_type.IsHeapConstant() &&
|
target_type.AsHeapConstant()->Ref().IsJSFunction()) {
|
// Only optimize [[Construct]] here if {function} is a Constructor.
|
JSFunctionRef function = target_type.AsHeapConstant()->Ref().AsJSFunction();
|
if (!function.IsConstructor()) return NoChange();
|
// Patch {node} to an indirect call via ConstructFunctionForwardVarargs.
|
Callable callable = CodeFactory::ConstructFunctionForwardVarargs(isolate());
|
node->RemoveInput(arity + 1);
|
node->InsertInput(graph()->zone(), 0,
|
jsgraph()->HeapConstant(callable.code()));
|
node->InsertInput(graph()->zone(), 2, new_target);
|
node->InsertInput(graph()->zone(), 3, jsgraph()->Constant(arity));
|
node->InsertInput(graph()->zone(), 4, jsgraph()->Constant(start_index));
|
node->InsertInput(graph()->zone(), 5, jsgraph()->UndefinedConstant());
|
NodeProperties::ChangeOp(
|
node, common()->Call(Linkage::GetStubCallDescriptor(
|
graph()->zone(), callable.descriptor(), arity + 1,
|
CallDescriptor::kNeedsFrameState)));
|
return Changed(node);
|
}
|
|
return NoChange();
|
}
|
|
Reduction JSTypedLowering::ReduceJSConstruct(Node* node) {
|
DCHECK_EQ(IrOpcode::kJSConstruct, node->opcode());
|
ConstructParameters const& p = ConstructParametersOf(node->op());
|
DCHECK_LE(2u, p.arity());
|
int const arity = static_cast<int>(p.arity() - 2);
|
Node* target = NodeProperties::GetValueInput(node, 0);
|
Type target_type = NodeProperties::GetType(target);
|
Node* new_target = NodeProperties::GetValueInput(node, arity + 1);
|
|
// Check if {target} is a known JSFunction.
|
if (target_type.IsHeapConstant() &&
|
target_type.AsHeapConstant()->Ref().IsJSFunction()) {
|
JSFunctionRef function = target_type.AsHeapConstant()->Ref().AsJSFunction();
|
SharedFunctionInfoRef shared = function.shared();
|
|
// Only optimize [[Construct]] here if {function} is a Constructor.
|
if (!function.IsConstructor()) return NoChange();
|
|
CallDescriptor::Flags flags = CallDescriptor::kNeedsFrameState;
|
|
// Patch {node} to an indirect call via the {function}s construct stub.
|
bool use_builtin_construct_stub = shared.construct_as_builtin();
|
|
CodeRef code(js_heap_broker(),
|
use_builtin_construct_stub
|
? BUILTIN_CODE(isolate(), JSBuiltinsConstructStub)
|
: BUILTIN_CODE(isolate(), JSConstructStubGeneric));
|
|
node->RemoveInput(arity + 1);
|
node->InsertInput(graph()->zone(), 0, jsgraph()->Constant(code));
|
node->InsertInput(graph()->zone(), 2, new_target);
|
node->InsertInput(graph()->zone(), 3, jsgraph()->Constant(arity));
|
node->InsertInput(graph()->zone(), 4, jsgraph()->UndefinedConstant());
|
node->InsertInput(graph()->zone(), 5, jsgraph()->UndefinedConstant());
|
NodeProperties::ChangeOp(
|
node,
|
common()->Call(Linkage::GetStubCallDescriptor(
|
graph()->zone(), ConstructStubDescriptor{}, 1 + arity, flags)));
|
|
return Changed(node);
|
}
|
|
return NoChange();
|
}
|
|
Reduction JSTypedLowering::ReduceJSCallForwardVarargs(Node* node) {
|
DCHECK_EQ(IrOpcode::kJSCallForwardVarargs, node->opcode());
|
CallForwardVarargsParameters p = CallForwardVarargsParametersOf(node->op());
|
DCHECK_LE(2u, p.arity());
|
int const arity = static_cast<int>(p.arity() - 2);
|
int const start_index = static_cast<int>(p.start_index());
|
Node* target = NodeProperties::GetValueInput(node, 0);
|
Type target_type = NodeProperties::GetType(target);
|
|
// Check if {target} is a JSFunction.
|
if (target_type.Is(Type::Function())) {
|
// Compute flags for the call.
|
CallDescriptor::Flags flags = CallDescriptor::kNeedsFrameState;
|
// Patch {node} to an indirect call via CallFunctionForwardVarargs.
|
Callable callable = CodeFactory::CallFunctionForwardVarargs(isolate());
|
node->InsertInput(graph()->zone(), 0,
|
jsgraph()->HeapConstant(callable.code()));
|
node->InsertInput(graph()->zone(), 2, jsgraph()->Constant(arity));
|
node->InsertInput(graph()->zone(), 3, jsgraph()->Constant(start_index));
|
NodeProperties::ChangeOp(
|
node, common()->Call(Linkage::GetStubCallDescriptor(
|
graph()->zone(), callable.descriptor(), arity + 1, flags)));
|
return Changed(node);
|
}
|
|
return NoChange();
|
}
|
|
Reduction JSTypedLowering::ReduceJSCall(Node* node) {
|
DCHECK_EQ(IrOpcode::kJSCall, node->opcode());
|
CallParameters const& p = CallParametersOf(node->op());
|
int const arity = static_cast<int>(p.arity() - 2);
|
ConvertReceiverMode convert_mode = p.convert_mode();
|
Node* target = NodeProperties::GetValueInput(node, 0);
|
Type target_type = NodeProperties::GetType(target);
|
Node* receiver = NodeProperties::GetValueInput(node, 1);
|
Type receiver_type = NodeProperties::GetType(receiver);
|
Node* effect = NodeProperties::GetEffectInput(node);
|
Node* control = NodeProperties::GetControlInput(node);
|
|
// Try to infer receiver {convert_mode} from {receiver} type.
|
if (receiver_type.Is(Type::NullOrUndefined())) {
|
convert_mode = ConvertReceiverMode::kNullOrUndefined;
|
} else if (!receiver_type.Maybe(Type::NullOrUndefined())) {
|
convert_mode = ConvertReceiverMode::kNotNullOrUndefined;
|
}
|
|
// Check if {target} is a known JSFunction.
|
if (target_type.IsHeapConstant() &&
|
target_type.AsHeapConstant()->Ref().IsJSFunction()) {
|
JSFunctionRef function = target_type.AsHeapConstant()->Ref().AsJSFunction();
|
SharedFunctionInfoRef shared = function.shared();
|
|
if (shared.HasBreakInfo()) {
|
// Do not inline the call if we need to check whether to break at entry.
|
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())) return NoChange();
|
|
// Load the context from the {target}.
|
Node* context = effect = graph()->NewNode(
|
simplified()->LoadField(AccessBuilder::ForJSFunctionContext()), target,
|
effect, control);
|
NodeProperties::ReplaceContextInput(node, context);
|
|
// Check if we need to convert the {receiver}.
|
if (is_sloppy(shared.language_mode()) && !shared.native() &&
|
!receiver_type.Is(Type::Receiver())) {
|
Node* global_proxy = jsgraph()->Constant(function.global_proxy());
|
receiver = effect =
|
graph()->NewNode(simplified()->ConvertReceiver(convert_mode),
|
receiver, global_proxy, effect, control);
|
NodeProperties::ReplaceValueInput(node, receiver, 1);
|
}
|
|
// Update the effect dependency for the {node}.
|
NodeProperties::ReplaceEffectInput(node, effect);
|
|
// Compute flags for the call.
|
CallDescriptor::Flags flags = CallDescriptor::kNeedsFrameState;
|
Node* new_target = jsgraph()->UndefinedConstant();
|
Node* argument_count = jsgraph()->Constant(arity);
|
|
if (NeedsArgumentAdaptorFrame(shared, arity)) {
|
// Patch {node} to an indirect call via the ArgumentsAdaptorTrampoline.
|
Callable callable = CodeFactory::ArgumentAdaptor(isolate());
|
node->InsertInput(graph()->zone(), 0,
|
jsgraph()->HeapConstant(callable.code()));
|
node->InsertInput(graph()->zone(), 2, new_target);
|
node->InsertInput(graph()->zone(), 3, argument_count);
|
node->InsertInput(
|
graph()->zone(), 4,
|
jsgraph()->Constant(shared.internal_formal_parameter_count()));
|
NodeProperties::ChangeOp(
|
node, common()->Call(Linkage::GetStubCallDescriptor(
|
graph()->zone(), callable.descriptor(), 1 + arity, flags)));
|
} else if (shared.HasBuiltinId() &&
|
Builtins::HasCppImplementation(shared.builtin_id())) {
|
// Patch {node} to a direct CEntry call.
|
ReduceBuiltin(jsgraph(), node, shared.builtin_id(), arity, flags);
|
} else if (shared.HasBuiltinId() &&
|
Builtins::KindOf(shared.builtin_id()) == Builtins::TFJ) {
|
// Patch {node} to a direct code object call.
|
Callable callable = Builtins::CallableFor(
|
isolate(), static_cast<Builtins::Name>(shared.builtin_id()));
|
CallDescriptor::Flags flags = CallDescriptor::kNeedsFrameState;
|
|
const CallInterfaceDescriptor& descriptor = callable.descriptor();
|
auto call_descriptor = Linkage::GetStubCallDescriptor(
|
graph()->zone(), descriptor, 1 + arity, flags);
|
Node* stub_code = jsgraph()->HeapConstant(callable.code());
|
node->InsertInput(graph()->zone(), 0, stub_code); // Code object.
|
node->InsertInput(graph()->zone(), 2, new_target);
|
node->InsertInput(graph()->zone(), 3, argument_count);
|
NodeProperties::ChangeOp(node, common()->Call(call_descriptor));
|
} else {
|
// Patch {node} to a direct call.
|
node->InsertInput(graph()->zone(), arity + 2, new_target);
|
node->InsertInput(graph()->zone(), arity + 3, argument_count);
|
NodeProperties::ChangeOp(node,
|
common()->Call(Linkage::GetJSCallDescriptor(
|
graph()->zone(), false, 1 + arity, flags)));
|
}
|
return Changed(node);
|
}
|
|
// Check if {target} is a JSFunction.
|
if (target_type.Is(Type::Function())) {
|
// Compute flags for the call.
|
CallDescriptor::Flags flags = CallDescriptor::kNeedsFrameState;
|
// Patch {node} to an indirect call via the CallFunction builtin.
|
Callable callable = CodeFactory::CallFunction(isolate(), convert_mode);
|
node->InsertInput(graph()->zone(), 0,
|
jsgraph()->HeapConstant(callable.code()));
|
node->InsertInput(graph()->zone(), 2, jsgraph()->Constant(arity));
|
NodeProperties::ChangeOp(
|
node, common()->Call(Linkage::GetStubCallDescriptor(
|
graph()->zone(), callable.descriptor(), 1 + arity, flags)));
|
return Changed(node);
|
}
|
|
// Maybe we did at least learn something about the {receiver}.
|
if (p.convert_mode() != convert_mode) {
|
NodeProperties::ChangeOp(
|
node, javascript()->Call(p.arity(), p.frequency(), p.feedback(),
|
convert_mode, p.speculation_mode()));
|
return Changed(node);
|
}
|
|
return NoChange();
|
}
|
|
Reduction JSTypedLowering::ReduceJSForInNext(Node* node) {
|
DCHECK_EQ(IrOpcode::kJSForInNext, node->opcode());
|
ForInMode const mode = ForInModeOf(node->op());
|
Node* receiver = NodeProperties::GetValueInput(node, 0);
|
Node* cache_array = NodeProperties::GetValueInput(node, 1);
|
Node* cache_type = NodeProperties::GetValueInput(node, 2);
|
Node* index = NodeProperties::GetValueInput(node, 3);
|
Node* context = NodeProperties::GetContextInput(node);
|
Node* frame_state = NodeProperties::GetFrameStateInput(node);
|
Node* effect = NodeProperties::GetEffectInput(node);
|
Node* control = NodeProperties::GetControlInput(node);
|
|
// Load the map of the {receiver}.
|
Node* receiver_map = effect =
|
graph()->NewNode(simplified()->LoadField(AccessBuilder::ForMap()),
|
receiver, effect, control);
|
|
switch (mode) {
|
case ForInMode::kUseEnumCacheKeys:
|
case ForInMode::kUseEnumCacheKeysAndIndices: {
|
// Ensure that the expected map still matches that of the {receiver}.
|
Node* check = graph()->NewNode(simplified()->ReferenceEqual(),
|
receiver_map, cache_type);
|
effect =
|
graph()->NewNode(simplified()->CheckIf(DeoptimizeReason::kWrongMap),
|
check, effect, control);
|
|
// Since the change to LoadElement() below is effectful, we connect
|
// node to all effect uses.
|
ReplaceWithValue(node, node, node, control);
|
|
// Morph the {node} into a LoadElement.
|
node->ReplaceInput(0, cache_array);
|
node->ReplaceInput(1, index);
|
node->ReplaceInput(2, effect);
|
node->ReplaceInput(3, control);
|
node->TrimInputCount(4);
|
NodeProperties::ChangeOp(
|
node,
|
simplified()->LoadElement(AccessBuilder::ForFixedArrayElement()));
|
NodeProperties::SetType(node, Type::InternalizedString());
|
break;
|
}
|
case ForInMode::kGeneric: {
|
// Load the next {key} from the {cache_array}.
|
Node* key = effect = graph()->NewNode(
|
simplified()->LoadElement(AccessBuilder::ForFixedArrayElement()),
|
cache_array, index, effect, control);
|
|
// Check if the expected map still matches that of the {receiver}.
|
Node* check = graph()->NewNode(simplified()->ReferenceEqual(),
|
receiver_map, cache_type);
|
Node* branch =
|
graph()->NewNode(common()->Branch(BranchHint::kTrue), check, control);
|
|
Node* if_true = graph()->NewNode(common()->IfTrue(), branch);
|
Node* etrue;
|
Node* vtrue;
|
{
|
// Don't need filtering since expected map still matches that of the
|
// {receiver}.
|
etrue = effect;
|
vtrue = key;
|
}
|
|
Node* if_false = graph()->NewNode(common()->IfFalse(), branch);
|
Node* efalse;
|
Node* vfalse;
|
{
|
// Filter the {key} to check if it's still a valid property of the
|
// {receiver} (does the ToName conversion implicitly).
|
Callable const callable =
|
Builtins::CallableFor(isolate(), Builtins::kForInFilter);
|
auto call_descriptor = Linkage::GetStubCallDescriptor(
|
graph()->zone(), callable.descriptor(), 0,
|
CallDescriptor::kNeedsFrameState);
|
vfalse = efalse = if_false =
|
graph()->NewNode(common()->Call(call_descriptor),
|
jsgraph()->HeapConstant(callable.code()), key,
|
receiver, context, frame_state, effect, if_false);
|
|
// Update potential {IfException} uses of {node} to point to the above
|
// ForInFilter stub call node instead.
|
Node* if_exception = nullptr;
|
if (NodeProperties::IsExceptionalCall(node, &if_exception)) {
|
if_false = graph()->NewNode(common()->IfSuccess(), vfalse);
|
NodeProperties::ReplaceControlInput(if_exception, vfalse);
|
NodeProperties::ReplaceEffectInput(if_exception, efalse);
|
Revisit(if_exception);
|
}
|
}
|
|
control = graph()->NewNode(common()->Merge(2), if_true, if_false);
|
effect = graph()->NewNode(common()->EffectPhi(2), etrue, efalse, control);
|
ReplaceWithValue(node, node, effect, control);
|
|
// Morph the {node} into a Phi.
|
node->ReplaceInput(0, vtrue);
|
node->ReplaceInput(1, vfalse);
|
node->ReplaceInput(2, control);
|
node->TrimInputCount(3);
|
NodeProperties::ChangeOp(
|
node, common()->Phi(MachineRepresentation::kTagged, 2));
|
}
|
}
|
|
return Changed(node);
|
}
|
|
Reduction JSTypedLowering::ReduceJSForInPrepare(Node* node) {
|
DCHECK_EQ(IrOpcode::kJSForInPrepare, node->opcode());
|
ForInMode const mode = ForInModeOf(node->op());
|
Node* enumerator = NodeProperties::GetValueInput(node, 0);
|
Node* effect = NodeProperties::GetEffectInput(node);
|
Node* control = NodeProperties::GetControlInput(node);
|
Node* cache_type = enumerator;
|
Node* cache_array = nullptr;
|
Node* cache_length = nullptr;
|
|
switch (mode) {
|
case ForInMode::kUseEnumCacheKeys:
|
case ForInMode::kUseEnumCacheKeysAndIndices: {
|
// Check that the {enumerator} is a Map.
|
effect = graph()->NewNode(
|
simplified()->CheckMaps(CheckMapsFlag::kNone,
|
ZoneHandleSet<Map>(factory()->meta_map())),
|
enumerator, effect, control);
|
|
// Load the enum cache from the {enumerator} map.
|
Node* descriptor_array = effect = graph()->NewNode(
|
simplified()->LoadField(AccessBuilder::ForMapDescriptors()),
|
enumerator, effect, control);
|
Node* enum_cache = effect = graph()->NewNode(
|
simplified()->LoadField(AccessBuilder::ForDescriptorArrayEnumCache()),
|
descriptor_array, effect, control);
|
cache_array = effect = graph()->NewNode(
|
simplified()->LoadField(AccessBuilder::ForEnumCacheKeys()),
|
enum_cache, effect, control);
|
|
// Load the enum length of the {enumerator} map.
|
Node* bit_field3 = effect = graph()->NewNode(
|
simplified()->LoadField(AccessBuilder::ForMapBitField3()), enumerator,
|
effect, control);
|
STATIC_ASSERT(Map::EnumLengthBits::kShift == 0);
|
cache_length =
|
graph()->NewNode(simplified()->NumberBitwiseAnd(), bit_field3,
|
jsgraph()->Constant(Map::EnumLengthBits::kMask));
|
break;
|
}
|
case ForInMode::kGeneric: {
|
// Check if the {enumerator} is a Map or a FixedArray.
|
Node* check = effect = graph()->NewNode(
|
simplified()->CompareMaps(ZoneHandleSet<Map>(factory()->meta_map())),
|
enumerator, effect, control);
|
Node* branch =
|
graph()->NewNode(common()->Branch(BranchHint::kTrue), check, control);
|
|
Node* if_true = graph()->NewNode(common()->IfTrue(), branch);
|
Node* etrue = effect;
|
Node* cache_array_true;
|
Node* cache_length_true;
|
{
|
// Load the enum cache from the {enumerator} map.
|
Node* descriptor_array = etrue = graph()->NewNode(
|
simplified()->LoadField(AccessBuilder::ForMapDescriptors()),
|
enumerator, etrue, if_true);
|
Node* enum_cache = etrue =
|
graph()->NewNode(simplified()->LoadField(
|
AccessBuilder::ForDescriptorArrayEnumCache()),
|
descriptor_array, etrue, if_true);
|
cache_array_true = etrue = graph()->NewNode(
|
simplified()->LoadField(AccessBuilder::ForEnumCacheKeys()),
|
enum_cache, etrue, if_true);
|
|
// Load the enum length of the {enumerator} map.
|
Node* bit_field3 = etrue = graph()->NewNode(
|
simplified()->LoadField(AccessBuilder::ForMapBitField3()),
|
enumerator, etrue, if_true);
|
STATIC_ASSERT(Map::EnumLengthBits::kShift == 0);
|
cache_length_true =
|
graph()->NewNode(simplified()->NumberBitwiseAnd(), bit_field3,
|
jsgraph()->Constant(Map::EnumLengthBits::kMask));
|
}
|
|
Node* if_false = graph()->NewNode(common()->IfFalse(), branch);
|
Node* efalse = effect;
|
Node* cache_array_false;
|
Node* cache_length_false;
|
{
|
// The {enumerator} is the FixedArray with the keys to iterate.
|
cache_array_false = enumerator;
|
cache_length_false = efalse = graph()->NewNode(
|
simplified()->LoadField(AccessBuilder::ForFixedArrayLength()),
|
cache_array_false, efalse, if_false);
|
}
|
|
// Rewrite the uses of the {node}.
|
control = graph()->NewNode(common()->Merge(2), if_true, if_false);
|
effect = graph()->NewNode(common()->EffectPhi(2), etrue, efalse, control);
|
cache_array =
|
graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2),
|
cache_array_true, cache_array_false, control);
|
cache_length =
|
graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2),
|
cache_length_true, cache_length_false, control);
|
break;
|
}
|
}
|
|
// Update the uses of {node}.
|
for (Edge edge : node->use_edges()) {
|
Node* const user = edge.from();
|
if (NodeProperties::IsEffectEdge(edge)) {
|
edge.UpdateTo(effect);
|
Revisit(user);
|
} else if (NodeProperties::IsControlEdge(edge)) {
|
edge.UpdateTo(control);
|
Revisit(user);
|
} else {
|
DCHECK(NodeProperties::IsValueEdge(edge));
|
switch (ProjectionIndexOf(user->op())) {
|
case 0:
|
Replace(user, cache_type);
|
break;
|
case 1:
|
Replace(user, cache_array);
|
break;
|
case 2:
|
Replace(user, cache_length);
|
break;
|
default:
|
UNREACHABLE();
|
}
|
}
|
}
|
node->Kill();
|
return Replace(effect);
|
}
|
|
Reduction JSTypedLowering::ReduceJSLoadMessage(Node* node) {
|
DCHECK_EQ(IrOpcode::kJSLoadMessage, node->opcode());
|
ExternalReference const ref =
|
ExternalReference::address_of_pending_message_obj(isolate());
|
node->ReplaceInput(0, jsgraph()->ExternalConstant(ref));
|
NodeProperties::ChangeOp(
|
node, simplified()->LoadField(AccessBuilder::ForExternalTaggedValue()));
|
return Changed(node);
|
}
|
|
Reduction JSTypedLowering::ReduceJSStoreMessage(Node* node) {
|
DCHECK_EQ(IrOpcode::kJSStoreMessage, node->opcode());
|
ExternalReference const ref =
|
ExternalReference::address_of_pending_message_obj(isolate());
|
Node* value = NodeProperties::GetValueInput(node, 0);
|
node->ReplaceInput(0, jsgraph()->ExternalConstant(ref));
|
node->ReplaceInput(1, value);
|
NodeProperties::ChangeOp(
|
node, simplified()->StoreField(AccessBuilder::ForExternalTaggedValue()));
|
return Changed(node);
|
}
|
|
Reduction JSTypedLowering::ReduceJSGeneratorStore(Node* node) {
|
DCHECK_EQ(IrOpcode::kJSGeneratorStore, node->opcode());
|
Node* generator = NodeProperties::GetValueInput(node, 0);
|
Node* continuation = NodeProperties::GetValueInput(node, 1);
|
Node* offset = NodeProperties::GetValueInput(node, 2);
|
Node* context = NodeProperties::GetContextInput(node);
|
Node* effect = NodeProperties::GetEffectInput(node);
|
Node* control = NodeProperties::GetControlInput(node);
|
int value_count = GeneratorStoreValueCountOf(node->op());
|
|
FieldAccess array_field =
|
AccessBuilder::ForJSGeneratorObjectParametersAndRegisters();
|
FieldAccess context_field = AccessBuilder::ForJSGeneratorObjectContext();
|
FieldAccess continuation_field =
|
AccessBuilder::ForJSGeneratorObjectContinuation();
|
FieldAccess input_or_debug_pos_field =
|
AccessBuilder::ForJSGeneratorObjectInputOrDebugPos();
|
|
Node* array = effect = graph()->NewNode(simplified()->LoadField(array_field),
|
generator, effect, control);
|
|
for (int i = 0; i < value_count; ++i) {
|
Node* value = NodeProperties::GetValueInput(node, 3 + i);
|
if (value != jsgraph()->OptimizedOutConstant()) {
|
effect = graph()->NewNode(
|
simplified()->StoreField(AccessBuilder::ForFixedArraySlot(i)), array,
|
value, effect, control);
|
}
|
}
|
|
effect = graph()->NewNode(simplified()->StoreField(context_field), generator,
|
context, effect, control);
|
effect = graph()->NewNode(simplified()->StoreField(continuation_field),
|
generator, continuation, effect, control);
|
effect = graph()->NewNode(simplified()->StoreField(input_or_debug_pos_field),
|
generator, offset, effect, control);
|
|
ReplaceWithValue(node, effect, effect, control);
|
return Changed(effect);
|
}
|
|
Reduction JSTypedLowering::ReduceJSGeneratorRestoreContinuation(Node* node) {
|
DCHECK_EQ(IrOpcode::kJSGeneratorRestoreContinuation, node->opcode());
|
Node* generator = NodeProperties::GetValueInput(node, 0);
|
Node* effect = NodeProperties::GetEffectInput(node);
|
Node* control = NodeProperties::GetControlInput(node);
|
|
FieldAccess continuation_field =
|
AccessBuilder::ForJSGeneratorObjectContinuation();
|
|
Node* continuation = effect = graph()->NewNode(
|
simplified()->LoadField(continuation_field), generator, effect, control);
|
Node* executing = jsgraph()->Constant(JSGeneratorObject::kGeneratorExecuting);
|
effect = graph()->NewNode(simplified()->StoreField(continuation_field),
|
generator, executing, effect, control);
|
|
ReplaceWithValue(node, continuation, effect, control);
|
return Changed(continuation);
|
}
|
|
Reduction JSTypedLowering::ReduceJSGeneratorRestoreContext(Node* node) {
|
DCHECK_EQ(IrOpcode::kJSGeneratorRestoreContext, node->opcode());
|
|
const Operator* new_op =
|
simplified()->LoadField(AccessBuilder::ForJSGeneratorObjectContext());
|
|
// Mutate the node in-place.
|
DCHECK(OperatorProperties::HasContextInput(node->op()));
|
DCHECK(!OperatorProperties::HasContextInput(new_op));
|
node->RemoveInput(NodeProperties::FirstContextIndex(node));
|
|
NodeProperties::ChangeOp(node, new_op);
|
return Changed(node);
|
}
|
|
Reduction JSTypedLowering::ReduceJSGeneratorRestoreRegister(Node* node) {
|
DCHECK_EQ(IrOpcode::kJSGeneratorRestoreRegister, node->opcode());
|
Node* generator = NodeProperties::GetValueInput(node, 0);
|
Node* effect = NodeProperties::GetEffectInput(node);
|
Node* control = NodeProperties::GetControlInput(node);
|
int index = RestoreRegisterIndexOf(node->op());
|
|
FieldAccess array_field =
|
AccessBuilder::ForJSGeneratorObjectParametersAndRegisters();
|
FieldAccess element_field = AccessBuilder::ForFixedArraySlot(index);
|
|
Node* array = effect = graph()->NewNode(simplified()->LoadField(array_field),
|
generator, effect, control);
|
Node* element = effect = graph()->NewNode(
|
simplified()->LoadField(element_field), array, effect, control);
|
Node* stale = jsgraph()->StaleRegisterConstant();
|
effect = graph()->NewNode(simplified()->StoreField(element_field), array,
|
stale, effect, control);
|
|
ReplaceWithValue(node, element, effect, control);
|
return Changed(element);
|
}
|
|
Reduction JSTypedLowering::ReduceJSGeneratorRestoreInputOrDebugPos(Node* node) {
|
DCHECK_EQ(IrOpcode::kJSGeneratorRestoreInputOrDebugPos, node->opcode());
|
|
FieldAccess input_or_debug_pos_field =
|
AccessBuilder::ForJSGeneratorObjectInputOrDebugPos();
|
const Operator* new_op = simplified()->LoadField(input_or_debug_pos_field);
|
|
// Mutate the node in-place.
|
DCHECK(OperatorProperties::HasContextInput(node->op()));
|
DCHECK(!OperatorProperties::HasContextInput(new_op));
|
node->RemoveInput(NodeProperties::FirstContextIndex(node));
|
|
NodeProperties::ChangeOp(node, new_op);
|
return Changed(node);
|
}
|
|
Reduction JSTypedLowering::ReduceObjectIsArray(Node* node) {
|
Node* value = NodeProperties::GetValueInput(node, 0);
|
Type value_type = NodeProperties::GetType(value);
|
Node* context = NodeProperties::GetContextInput(node);
|
Node* frame_state = NodeProperties::GetFrameStateInput(node);
|
Node* effect = NodeProperties::GetEffectInput(node);
|
Node* control = NodeProperties::GetControlInput(node);
|
|
// Constant-fold based on {value} type.
|
if (value_type.Is(Type::Array())) {
|
Node* value = jsgraph()->TrueConstant();
|
ReplaceWithValue(node, value);
|
return Replace(value);
|
} else if (!value_type.Maybe(Type::ArrayOrProxy())) {
|
Node* value = jsgraph()->FalseConstant();
|
ReplaceWithValue(node, value);
|
return Replace(value);
|
}
|
|
int count = 0;
|
Node* values[5];
|
Node* effects[5];
|
Node* controls[4];
|
|
// Check if the {value} is a Smi.
|
Node* check = graph()->NewNode(simplified()->ObjectIsSmi(), value);
|
control =
|
graph()->NewNode(common()->Branch(BranchHint::kFalse), check, control);
|
|
// The {value} is a Smi.
|
controls[count] = graph()->NewNode(common()->IfTrue(), control);
|
effects[count] = effect;
|
values[count] = jsgraph()->FalseConstant();
|
count++;
|
|
control = graph()->NewNode(common()->IfFalse(), control);
|
|
// Load the {value}s instance type.
|
Node* value_map = effect = graph()->NewNode(
|
simplified()->LoadField(AccessBuilder::ForMap()), value, effect, control);
|
Node* value_instance_type = effect = graph()->NewNode(
|
simplified()->LoadField(AccessBuilder::ForMapInstanceType()), value_map,
|
effect, control);
|
|
// Check if the {value} is a JSArray.
|
check = graph()->NewNode(simplified()->NumberEqual(), value_instance_type,
|
jsgraph()->Constant(JS_ARRAY_TYPE));
|
control = graph()->NewNode(common()->Branch(), check, control);
|
|
// The {value} is a JSArray.
|
controls[count] = graph()->NewNode(common()->IfTrue(), control);
|
effects[count] = effect;
|
values[count] = jsgraph()->TrueConstant();
|
count++;
|
|
control = graph()->NewNode(common()->IfFalse(), control);
|
|
// Check if the {value} is a JSProxy.
|
check = graph()->NewNode(simplified()->NumberEqual(), value_instance_type,
|
jsgraph()->Constant(JS_PROXY_TYPE));
|
control =
|
graph()->NewNode(common()->Branch(BranchHint::kFalse), check, control);
|
|
// The {value} is neither a JSArray nor a JSProxy.
|
controls[count] = graph()->NewNode(common()->IfFalse(), control);
|
effects[count] = effect;
|
values[count] = jsgraph()->FalseConstant();
|
count++;
|
|
control = graph()->NewNode(common()->IfTrue(), control);
|
|
// Let the %ArrayIsArray runtime function deal with the JSProxy {value}.
|
value = effect = control =
|
graph()->NewNode(javascript()->CallRuntime(Runtime::kArrayIsArray), value,
|
context, frame_state, effect, control);
|
NodeProperties::SetType(value, Type::Boolean());
|
|
// Update potential {IfException} uses of {node} to point to the above
|
// %ArrayIsArray runtime call node instead.
|
Node* on_exception = nullptr;
|
if (NodeProperties::IsExceptionalCall(node, &on_exception)) {
|
NodeProperties::ReplaceControlInput(on_exception, control);
|
NodeProperties::ReplaceEffectInput(on_exception, effect);
|
control = graph()->NewNode(common()->IfSuccess(), control);
|
Revisit(on_exception);
|
}
|
|
// The {value} is neither a JSArray nor a JSProxy.
|
controls[count] = control;
|
effects[count] = effect;
|
values[count] = value;
|
count++;
|
|
control = graph()->NewNode(common()->Merge(count), count, controls);
|
effects[count] = control;
|
values[count] = control;
|
effect = graph()->NewNode(common()->EffectPhi(count), count + 1, effects);
|
value = graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, count),
|
count + 1, values);
|
ReplaceWithValue(node, value, effect, control);
|
return Replace(value);
|
}
|
|
Reduction JSTypedLowering::ReduceJSParseInt(Node* node) {
|
Node* value = NodeProperties::GetValueInput(node, 0);
|
Type value_type = NodeProperties::GetType(value);
|
Node* radix = NodeProperties::GetValueInput(node, 1);
|
Type radix_type = NodeProperties::GetType(radix);
|
// We need kTenOrUndefined and kZeroOrUndefined because
|
// the type representing {0,10} would become the range 1-10.
|
if (value_type.Is(type_cache_.kSafeInteger) &&
|
(radix_type.Is(type_cache_.kTenOrUndefined) ||
|
radix_type.Is(type_cache_.kZeroOrUndefined))) {
|
// Number.parseInt(a:safe-integer) -> a
|
// Number.parseInt(a:safe-integer,b:#0\/undefined) -> a
|
// Number.parseInt(a:safe-integer,b:#10\/undefined) -> a
|
ReplaceWithValue(node, value);
|
return Replace(value);
|
}
|
return NoChange();
|
}
|
|
Reduction JSTypedLowering::Reduce(Node* node) {
|
DisallowHeapAccess no_heap_access;
|
|
switch (node->opcode()) {
|
case IrOpcode::kJSEqual:
|
return ReduceJSEqual(node);
|
case IrOpcode::kJSStrictEqual:
|
return ReduceJSStrictEqual(node);
|
case IrOpcode::kJSLessThan: // fall through
|
case IrOpcode::kJSGreaterThan: // fall through
|
case IrOpcode::kJSLessThanOrEqual: // fall through
|
case IrOpcode::kJSGreaterThanOrEqual:
|
return ReduceJSComparison(node);
|
case IrOpcode::kJSBitwiseOr:
|
case IrOpcode::kJSBitwiseXor:
|
case IrOpcode::kJSBitwiseAnd:
|
return ReduceInt32Binop(node);
|
case IrOpcode::kJSShiftLeft:
|
case IrOpcode::kJSShiftRight:
|
return ReduceUI32Shift(node, kSigned);
|
case IrOpcode::kJSShiftRightLogical:
|
return ReduceUI32Shift(node, kUnsigned);
|
case IrOpcode::kJSAdd:
|
return ReduceJSAdd(node);
|
case IrOpcode::kJSSubtract:
|
case IrOpcode::kJSMultiply:
|
case IrOpcode::kJSDivide:
|
case IrOpcode::kJSModulus:
|
case IrOpcode::kJSExponentiate:
|
return ReduceNumberBinop(node);
|
case IrOpcode::kJSBitwiseNot:
|
return ReduceJSBitwiseNot(node);
|
case IrOpcode::kJSDecrement:
|
return ReduceJSDecrement(node);
|
case IrOpcode::kJSIncrement:
|
return ReduceJSIncrement(node);
|
case IrOpcode::kJSNegate:
|
return ReduceJSNegate(node);
|
case IrOpcode::kJSHasInPrototypeChain:
|
return ReduceJSHasInPrototypeChain(node);
|
case IrOpcode::kJSOrdinaryHasInstance:
|
return ReduceJSOrdinaryHasInstance(node);
|
case IrOpcode::kJSToInteger:
|
return ReduceJSToInteger(node);
|
case IrOpcode::kJSToLength:
|
return ReduceJSToLength(node);
|
case IrOpcode::kJSToName:
|
return ReduceJSToName(node);
|
case IrOpcode::kJSToNumber:
|
case IrOpcode::kJSToNumberConvertBigInt:
|
case IrOpcode::kJSToNumeric:
|
return ReduceJSToNumberOrNumeric(node);
|
case IrOpcode::kJSToString:
|
return ReduceJSToString(node);
|
case IrOpcode::kJSToObject:
|
return ReduceJSToObject(node);
|
case IrOpcode::kJSLoadNamed:
|
return ReduceJSLoadNamed(node);
|
case IrOpcode::kJSLoadContext:
|
return ReduceJSLoadContext(node);
|
case IrOpcode::kJSStoreContext:
|
return ReduceJSStoreContext(node);
|
case IrOpcode::kJSLoadModule:
|
return ReduceJSLoadModule(node);
|
case IrOpcode::kJSStoreModule:
|
return ReduceJSStoreModule(node);
|
case IrOpcode::kJSConstructForwardVarargs:
|
return ReduceJSConstructForwardVarargs(node);
|
case IrOpcode::kJSConstruct:
|
return ReduceJSConstruct(node);
|
case IrOpcode::kJSCallForwardVarargs:
|
return ReduceJSCallForwardVarargs(node);
|
case IrOpcode::kJSCall:
|
return ReduceJSCall(node);
|
case IrOpcode::kJSForInPrepare:
|
return ReduceJSForInPrepare(node);
|
case IrOpcode::kJSForInNext:
|
return ReduceJSForInNext(node);
|
case IrOpcode::kJSLoadMessage:
|
return ReduceJSLoadMessage(node);
|
case IrOpcode::kJSStoreMessage:
|
return ReduceJSStoreMessage(node);
|
case IrOpcode::kJSGeneratorStore:
|
return ReduceJSGeneratorStore(node);
|
case IrOpcode::kJSGeneratorRestoreContinuation:
|
return ReduceJSGeneratorRestoreContinuation(node);
|
case IrOpcode::kJSGeneratorRestoreContext:
|
return ReduceJSGeneratorRestoreContext(node);
|
case IrOpcode::kJSGeneratorRestoreRegister:
|
return ReduceJSGeneratorRestoreRegister(node);
|
case IrOpcode::kJSGeneratorRestoreInputOrDebugPos:
|
return ReduceJSGeneratorRestoreInputOrDebugPos(node);
|
// TODO(mstarzinger): Simplified operations hiding in JS-level reducer not
|
// fooling anyone. Consider moving this into a separate reducer.
|
case IrOpcode::kSpeculativeNumberAdd:
|
return ReduceSpeculativeNumberAdd(node);
|
case IrOpcode::kSpeculativeNumberSubtract:
|
case IrOpcode::kSpeculativeNumberMultiply:
|
case IrOpcode::kSpeculativeNumberDivide:
|
case IrOpcode::kSpeculativeNumberModulus:
|
return ReduceSpeculativeNumberBinop(node);
|
case IrOpcode::kSpeculativeNumberEqual:
|
case IrOpcode::kSpeculativeNumberLessThan:
|
case IrOpcode::kSpeculativeNumberLessThanOrEqual:
|
return ReduceSpeculativeNumberComparison(node);
|
case IrOpcode::kJSObjectIsArray:
|
return ReduceObjectIsArray(node);
|
case IrOpcode::kJSParseInt:
|
return ReduceJSParseInt(node);
|
default:
|
break;
|
}
|
return NoChange();
|
}
|
|
|
Factory* JSTypedLowering::factory() const { return jsgraph()->factory(); }
|
|
|
Graph* JSTypedLowering::graph() const { return jsgraph()->graph(); }
|
|
|
Isolate* JSTypedLowering::isolate() const { return jsgraph()->isolate(); }
|
|
|
JSOperatorBuilder* JSTypedLowering::javascript() const {
|
return jsgraph()->javascript();
|
}
|
|
|
CommonOperatorBuilder* JSTypedLowering::common() const {
|
return jsgraph()->common();
|
}
|
|
SimplifiedOperatorBuilder* JSTypedLowering::simplified() const {
|
return jsgraph()->simplified();
|
}
|
|
} // namespace compiler
|
} // namespace internal
|
} // namespace v8
|