blob: c265caf9f0812986d5ad9dc8ba3e80dbddaf5047 [file] [log] [blame]
// 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"
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_);
if (m.right().HasValue() && m.right().Value()->IsString()) {
Handle<String> right_string = Handle<String>::cast(m.right().Value());
if (right_string->length() >= ConsString::kMinLength) return true;
}
if (m.left().HasValue() && m.left().Value()->IsString()) {
Handle<String> left_string = Handle<String>::cast(m.left().Value());
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(); }
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, Zone* zone)
: AdvancedReducer(editor),
jsgraph_(jsgraph),
empty_string_type_(
Type::HeapConstant(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 (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()) {
Handle<String> left = Handle<String>::cast(m.left().Value());
Handle<String> right = Handle<String>::cast(m.right().Value());
if (left->length() + right->length() > String::kMaxLength) {
// No point in trying to optimize this, as it will just throw.
return NoChange();
}
Node* value = jsgraph()->HeapConstant(
factory()->NewConsString(left, right).ToHandleChecked());
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 (BinaryOperationHintOf(node->op()) == BinaryOperationHint::kString) {
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);
CallDescriptor const* const desc = Linkage::GetStubCallDescriptor(
isolate(), 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(desc));
return Changed(node);
}
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);
Node* length =
(m.HasValue() && m.Value()->IsString())
? jsgraph()->Constant(Handle<String>::cast(m.Value())->length())
: graph()->NewNode(simplified()->StringLength(), value);
return 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.Value()->IsString()) {
Handle<Object> input_value = m.Value();
return Replace(jsgraph()->Constant(
String::ToNumber(Handle<String>::cast(input_value))));
}
}
if (input_type->IsHeapConstant()) {
Handle<Object> input_value = input_type->AsHeapConstant()->Value();
if (input_value->IsOddball()) {
return Replace(jsgraph()->Constant(
Oddball::ToNumber(Handle<Oddball>::cast(input_value))));
}
}
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()) {
// 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);
CallDescriptor const* const desc = Linkage::GetStubCallDescriptor(
isolate(), graph()->zone(), callable.descriptor(), 0,
CallDescriptor::kNeedsFrameState, node->op()->properties());
rfalse = efalse = if_false = graph()->NewNode(
common()->Call(desc), 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);
Handle<Name> name = NamedAccessOf(node->op()).name();
// Optimize "length" property of strings.
if (name.is_identical_to(factory()->length_string()) &&
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);
Node* module = NodeProperties::GetValueInput(node, 0);
Type* module_type = NodeProperties::GetType(module);
if (module_type->IsHeapConstant()) {
Handle<Module> module_constant =
Handle<Module>::cast(module_type->AsHeapConstant()->Value());
Handle<Cell> cell_constant(module_constant->GetCell(cell_index), isolate());
return jsgraph()->HeapConstant(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)),
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(Isolate* isolate, JSGraph* jsgraph, Node* node,
int builtin_index, int arity, CallDescriptor::Flags flags) {
// Patch {node} to a direct CEntryStub call.
//
// ----------- A r g u m e n t s -----------
// -- 0: CEntryStub
// --- 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(entry, isolate));
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();
CallDescriptor* desc = Linkage::GetCEntryStubCallDescriptor(
zone, kReturnCount, argc, debug_name, properties, flags);
NodeProperties::ChangeOp(node, jsgraph->common()->Call(desc));
}
bool NeedsArgumentAdaptorFrame(Handle<SharedFunctionInfo> 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->Is(Type::Function())) {
// 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(
isolate(), 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);
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node);
// Check if {target} is a known JSFunction.
if (target_type->IsHeapConstant() &&
target_type->AsHeapConstant()->Value()->IsJSFunction()) {
Handle<JSFunction> function =
Handle<JSFunction>::cast(target_type->AsHeapConstant()->Value());
Handle<SharedFunctionInfo> shared(function->shared(), isolate());
const int builtin_index = shared->construct_stub()->builtin_index();
const bool is_builtin = (builtin_index != -1);
CallDescriptor::Flags flags = CallDescriptor::kNeedsFrameState;
if (is_builtin && Builtins::HasCppImplementation(builtin_index) &&
!NeedsArgumentAdaptorFrame(shared, arity)) {
// Patch {node} to a direct CEntryStub call.
// Load the context from the {target}.
Node* context = effect = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForJSFunctionContext()),
target, effect, control);
NodeProperties::ReplaceContextInput(node, context);
// Update the effect dependency for the {node}.
NodeProperties::ReplaceEffectInput(node, effect);
ReduceBuiltin(isolate(), jsgraph(), node, builtin_index, arity, flags);
} else {
// Patch {node} to an indirect call via the {function}s construct stub.
Callable callable(handle(shared->construct_stub(), isolate()),
ConstructStubDescriptor(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()->UndefinedConstant());
node->InsertInput(graph()->zone(), 5, jsgraph()->UndefinedConstant());
NodeProperties::ChangeOp(
node, common()->Call(Linkage::GetStubCallDescriptor(
isolate(), graph()->zone(), callable.descriptor(),
1 + arity, flags)));
}
return Changed(node);
}
// Check if {target} is a JSFunction.
if (target_type->Is(Type::Function())) {
// Patch {node} to an indirect call via the ConstructFunction builtin.
Callable callable = CodeFactory::ConstructFunction(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()->UndefinedConstant());
NodeProperties::ChangeOp(
node, common()->Call(Linkage::GetStubCallDescriptor(
isolate(), graph()->zone(), callable.descriptor(), 1 + arity,
CallDescriptor::kNeedsFrameState)));
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(
isolate(), 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()->Value()->IsJSFunction()) {
Handle<JSFunction> function =
Handle<JSFunction>::cast(target_type->AsHeapConstant()->Value());
Handle<SharedFunctionInfo> shared(function->shared(), isolate());
const int builtin_index = shared->code()->builtin_index();
const bool is_builtin = (builtin_index != -1);
// 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()->HeapConstant(handle(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(
isolate(), graph()->zone(), callable.descriptor(),
1 + arity, flags)));
} else if (is_builtin && Builtins::HasCppImplementation(builtin_index)) {
// Patch {node} to a direct CEntryStub call.
ReduceBuiltin(isolate(), jsgraph(), node, builtin_index, arity, flags);
} 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(
isolate(), 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));
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);
CallDescriptor const* const desc = Linkage::GetStubCallDescriptor(
isolate(), graph()->zone(), callable.descriptor(), 0,
CallDescriptor::kNeedsFrameState);
vfalse = efalse = if_false = graph()->NewNode(
common()->Call(desc), 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 register_count = OpParameter<int>(node);
FieldAccess array_field = AccessBuilder::ForJSGeneratorObjectRegisterFile();
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 < register_count; ++i) {
Node* value = NodeProperties::GetValueInput(node, 3 + i);
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::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 = OpParameter<int>(node);
FieldAccess array_field = AccessBuilder::ForJSGeneratorObjectRegisterFile();
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::Reduce(Node* node) {
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::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::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);
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