// Copyright 2017 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-type-hint-lowering.h"
|
|
#include "src/compiler/access-builder.h"
|
#include "src/compiler/js-graph.h"
|
#include "src/compiler/operator-properties.h"
|
#include "src/compiler/simplified-operator.h"
|
#include "src/feedback-vector.h"
|
#include "src/type-hints.h"
|
|
namespace v8 {
|
namespace internal {
|
namespace compiler {
|
|
namespace {
|
|
bool BinaryOperationHintToNumberOperationHint(
|
BinaryOperationHint binop_hint, NumberOperationHint* number_hint) {
|
switch (binop_hint) {
|
case BinaryOperationHint::kSignedSmall:
|
*number_hint = NumberOperationHint::kSignedSmall;
|
return true;
|
case BinaryOperationHint::kSignedSmallInputs:
|
*number_hint = NumberOperationHint::kSignedSmallInputs;
|
return true;
|
case BinaryOperationHint::kSigned32:
|
*number_hint = NumberOperationHint::kSigned32;
|
return true;
|
case BinaryOperationHint::kNumber:
|
*number_hint = NumberOperationHint::kNumber;
|
return true;
|
case BinaryOperationHint::kNumberOrOddball:
|
*number_hint = NumberOperationHint::kNumberOrOddball;
|
return true;
|
case BinaryOperationHint::kAny:
|
case BinaryOperationHint::kNone:
|
case BinaryOperationHint::kString:
|
case BinaryOperationHint::kBigInt:
|
break;
|
}
|
return false;
|
}
|
|
} // namespace
|
|
class JSSpeculativeBinopBuilder final {
|
public:
|
JSSpeculativeBinopBuilder(const JSTypeHintLowering* lowering,
|
const Operator* op, Node* left, Node* right,
|
Node* effect, Node* control, FeedbackSlot slot)
|
: lowering_(lowering),
|
op_(op),
|
left_(left),
|
right_(right),
|
effect_(effect),
|
control_(control),
|
slot_(slot) {}
|
|
BinaryOperationHint GetBinaryOperationHint() {
|
FeedbackNexus nexus(feedback_vector(), slot_);
|
return nexus.GetBinaryOperationFeedback();
|
}
|
|
CompareOperationHint GetCompareOperationHint() {
|
FeedbackNexus nexus(feedback_vector(), slot_);
|
return nexus.GetCompareOperationFeedback();
|
}
|
|
bool GetBinaryNumberOperationHint(NumberOperationHint* hint) {
|
return BinaryOperationHintToNumberOperationHint(GetBinaryOperationHint(),
|
hint);
|
}
|
|
bool GetCompareNumberOperationHint(NumberOperationHint* hint) {
|
switch (GetCompareOperationHint()) {
|
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;
|
}
|
|
const Operator* SpeculativeNumberOp(NumberOperationHint hint) {
|
switch (op_->opcode()) {
|
case IrOpcode::kJSAdd:
|
if (hint == NumberOperationHint::kSignedSmall ||
|
hint == NumberOperationHint::kSigned32) {
|
return simplified()->SpeculativeSafeIntegerAdd(hint);
|
} else {
|
return simplified()->SpeculativeNumberAdd(hint);
|
}
|
case IrOpcode::kJSSubtract:
|
if (hint == NumberOperationHint::kSignedSmall ||
|
hint == NumberOperationHint::kSigned32) {
|
return simplified()->SpeculativeSafeIntegerSubtract(hint);
|
} else {
|
return simplified()->SpeculativeNumberSubtract(hint);
|
}
|
case IrOpcode::kJSMultiply:
|
return simplified()->SpeculativeNumberMultiply(hint);
|
case IrOpcode::kJSDivide:
|
return simplified()->SpeculativeNumberDivide(hint);
|
case IrOpcode::kJSModulus:
|
return simplified()->SpeculativeNumberModulus(hint);
|
case IrOpcode::kJSBitwiseAnd:
|
return simplified()->SpeculativeNumberBitwiseAnd(hint);
|
case IrOpcode::kJSBitwiseOr:
|
return simplified()->SpeculativeNumberBitwiseOr(hint);
|
case IrOpcode::kJSBitwiseXor:
|
return simplified()->SpeculativeNumberBitwiseXor(hint);
|
case IrOpcode::kJSShiftLeft:
|
return simplified()->SpeculativeNumberShiftLeft(hint);
|
case IrOpcode::kJSShiftRight:
|
return simplified()->SpeculativeNumberShiftRight(hint);
|
case IrOpcode::kJSShiftRightLogical:
|
return simplified()->SpeculativeNumberShiftRightLogical(hint);
|
default:
|
break;
|
}
|
UNREACHABLE();
|
}
|
|
const Operator* SpeculativeCompareOp(NumberOperationHint hint) {
|
switch (op_->opcode()) {
|
case IrOpcode::kJSEqual:
|
return simplified()->SpeculativeNumberEqual(hint);
|
case IrOpcode::kJSLessThan:
|
return simplified()->SpeculativeNumberLessThan(hint);
|
case IrOpcode::kJSGreaterThan:
|
std::swap(left_, right_); // a > b => b < a
|
return simplified()->SpeculativeNumberLessThan(hint);
|
case IrOpcode::kJSLessThanOrEqual:
|
return simplified()->SpeculativeNumberLessThanOrEqual(hint);
|
case IrOpcode::kJSGreaterThanOrEqual:
|
std::swap(left_, right_); // a >= b => b <= a
|
return simplified()->SpeculativeNumberLessThanOrEqual(hint);
|
default:
|
break;
|
}
|
UNREACHABLE();
|
}
|
|
Node* BuildSpeculativeOperation(const Operator* op) {
|
DCHECK_EQ(2, op->ValueInputCount());
|
DCHECK_EQ(1, op->EffectInputCount());
|
DCHECK_EQ(1, op->ControlInputCount());
|
DCHECK_EQ(false, OperatorProperties::HasFrameStateInput(op));
|
DCHECK_EQ(false, OperatorProperties::HasContextInput(op));
|
DCHECK_EQ(1, op->EffectOutputCount());
|
DCHECK_EQ(0, op->ControlOutputCount());
|
return graph()->NewNode(op, left_, right_, effect_, control_);
|
}
|
|
Node* TryBuildNumberBinop() {
|
NumberOperationHint hint;
|
if (GetBinaryNumberOperationHint(&hint)) {
|
const Operator* op = SpeculativeNumberOp(hint);
|
Node* node = BuildSpeculativeOperation(op);
|
return node;
|
}
|
return nullptr;
|
}
|
|
Node* TryBuildNumberCompare() {
|
NumberOperationHint hint;
|
if (GetCompareNumberOperationHint(&hint)) {
|
const Operator* op = SpeculativeCompareOp(hint);
|
Node* node = BuildSpeculativeOperation(op);
|
return node;
|
}
|
return nullptr;
|
}
|
|
JSGraph* jsgraph() const { return lowering_->jsgraph(); }
|
Isolate* isolate() const { return jsgraph()->isolate(); }
|
Graph* graph() const { return jsgraph()->graph(); }
|
JSOperatorBuilder* javascript() { return jsgraph()->javascript(); }
|
SimplifiedOperatorBuilder* simplified() { return jsgraph()->simplified(); }
|
CommonOperatorBuilder* common() { return jsgraph()->common(); }
|
const Handle<FeedbackVector>& feedback_vector() const {
|
return lowering_->feedback_vector();
|
}
|
|
private:
|
const JSTypeHintLowering* lowering_;
|
const Operator* op_;
|
Node* left_;
|
Node* right_;
|
Node* effect_;
|
Node* control_;
|
FeedbackSlot slot_;
|
};
|
|
JSTypeHintLowering::JSTypeHintLowering(JSGraph* jsgraph,
|
Handle<FeedbackVector> feedback_vector,
|
Flags flags)
|
: jsgraph_(jsgraph), flags_(flags), feedback_vector_(feedback_vector) {}
|
|
Isolate* JSTypeHintLowering::isolate() const { return jsgraph()->isolate(); }
|
|
JSTypeHintLowering::LoweringResult JSTypeHintLowering::ReduceUnaryOperation(
|
const Operator* op, Node* operand, Node* effect, Node* control,
|
FeedbackSlot slot) const {
|
DCHECK(!slot.IsInvalid());
|
FeedbackNexus nexus(feedback_vector(), slot);
|
if (Node* node = TryBuildSoftDeopt(
|
nexus, effect, control,
|
DeoptimizeReason::kInsufficientTypeFeedbackForUnaryOperation)) {
|
return LoweringResult::Exit(node);
|
}
|
|
Node* node;
|
switch (op->opcode()) {
|
case IrOpcode::kJSBitwiseNot: {
|
// Lower to a speculative xor with -1 if we have some kind of Number
|
// feedback.
|
JSSpeculativeBinopBuilder b(this, jsgraph()->javascript()->BitwiseXor(),
|
operand, jsgraph()->SmiConstant(-1), effect,
|
control, slot);
|
node = b.TryBuildNumberBinop();
|
break;
|
}
|
case IrOpcode::kJSDecrement: {
|
// Lower to a speculative subtraction of 1 if we have some kind of Number
|
// feedback.
|
JSSpeculativeBinopBuilder b(this, jsgraph()->javascript()->Subtract(),
|
operand, jsgraph()->SmiConstant(1), effect,
|
control, slot);
|
node = b.TryBuildNumberBinop();
|
break;
|
}
|
case IrOpcode::kJSIncrement: {
|
// Lower to a speculative addition of 1 if we have some kind of Number
|
// feedback.
|
BinaryOperationHint hint = BinaryOperationHint::kAny; // Dummy.
|
JSSpeculativeBinopBuilder b(this, jsgraph()->javascript()->Add(hint),
|
operand, jsgraph()->SmiConstant(1), effect,
|
control, slot);
|
node = b.TryBuildNumberBinop();
|
break;
|
}
|
case IrOpcode::kJSNegate: {
|
// Lower to a speculative multiplication with -1 if we have some kind of
|
// Number feedback.
|
JSSpeculativeBinopBuilder b(this, jsgraph()->javascript()->Multiply(),
|
operand, jsgraph()->SmiConstant(-1), effect,
|
control, slot);
|
node = b.TryBuildNumberBinop();
|
break;
|
}
|
default:
|
UNREACHABLE();
|
break;
|
}
|
|
if (node != nullptr) {
|
return LoweringResult::SideEffectFree(node, node, control);
|
} else {
|
return LoweringResult::NoChange();
|
}
|
}
|
|
JSTypeHintLowering::LoweringResult JSTypeHintLowering::ReduceBinaryOperation(
|
const Operator* op, Node* left, Node* right, Node* effect, Node* control,
|
FeedbackSlot slot) const {
|
switch (op->opcode()) {
|
case IrOpcode::kJSStrictEqual: {
|
DCHECK(!slot.IsInvalid());
|
FeedbackNexus nexus(feedback_vector(), slot);
|
if (Node* node = TryBuildSoftDeopt(
|
nexus, effect, control,
|
DeoptimizeReason::kInsufficientTypeFeedbackForCompareOperation)) {
|
return LoweringResult::Exit(node);
|
}
|
// TODO(turbofan): Should we generally support early lowering of
|
// JSStrictEqual operators here?
|
break;
|
}
|
case IrOpcode::kJSEqual:
|
case IrOpcode::kJSLessThan:
|
case IrOpcode::kJSGreaterThan:
|
case IrOpcode::kJSLessThanOrEqual:
|
case IrOpcode::kJSGreaterThanOrEqual: {
|
DCHECK(!slot.IsInvalid());
|
FeedbackNexus nexus(feedback_vector(), slot);
|
if (Node* node = TryBuildSoftDeopt(
|
nexus, effect, control,
|
DeoptimizeReason::kInsufficientTypeFeedbackForCompareOperation)) {
|
return LoweringResult::Exit(node);
|
}
|
JSSpeculativeBinopBuilder b(this, op, left, right, effect, control, slot);
|
if (Node* node = b.TryBuildNumberCompare()) {
|
return LoweringResult::SideEffectFree(node, node, control);
|
}
|
break;
|
}
|
case IrOpcode::kJSInstanceOf: {
|
DCHECK(!slot.IsInvalid());
|
FeedbackNexus nexus(feedback_vector(), slot);
|
if (Node* node = TryBuildSoftDeopt(
|
nexus, effect, control,
|
DeoptimizeReason::kInsufficientTypeFeedbackForCompareOperation)) {
|
return LoweringResult::Exit(node);
|
}
|
// TODO(turbofan): Should we generally support early lowering of
|
// JSInstanceOf operators here?
|
break;
|
}
|
case IrOpcode::kJSBitwiseOr:
|
case IrOpcode::kJSBitwiseXor:
|
case IrOpcode::kJSBitwiseAnd:
|
case IrOpcode::kJSShiftLeft:
|
case IrOpcode::kJSShiftRight:
|
case IrOpcode::kJSShiftRightLogical:
|
case IrOpcode::kJSAdd:
|
case IrOpcode::kJSSubtract:
|
case IrOpcode::kJSMultiply:
|
case IrOpcode::kJSDivide:
|
case IrOpcode::kJSModulus: {
|
DCHECK(!slot.IsInvalid());
|
FeedbackNexus nexus(feedback_vector(), slot);
|
if (Node* node = TryBuildSoftDeopt(
|
nexus, effect, control,
|
DeoptimizeReason::kInsufficientTypeFeedbackForBinaryOperation)) {
|
return LoweringResult::Exit(node);
|
}
|
JSSpeculativeBinopBuilder b(this, op, left, right, effect, control, slot);
|
if (Node* node = b.TryBuildNumberBinop()) {
|
return LoweringResult::SideEffectFree(node, node, control);
|
}
|
break;
|
}
|
case IrOpcode::kJSExponentiate: {
|
// TODO(neis): Introduce a SpeculativeNumberPow operator?
|
break;
|
}
|
default:
|
UNREACHABLE();
|
break;
|
}
|
return LoweringResult::NoChange();
|
}
|
|
JSTypeHintLowering::LoweringResult JSTypeHintLowering::ReduceForInNextOperation(
|
Node* receiver, Node* cache_array, Node* cache_type, Node* index,
|
Node* effect, Node* control, FeedbackSlot slot) const {
|
DCHECK(!slot.IsInvalid());
|
FeedbackNexus nexus(feedback_vector(), slot);
|
if (Node* node = TryBuildSoftDeopt(
|
nexus, effect, control,
|
DeoptimizeReason::kInsufficientTypeFeedbackForForIn)) {
|
return LoweringResult::Exit(node);
|
}
|
return LoweringResult::NoChange();
|
}
|
|
JSTypeHintLowering::LoweringResult
|
JSTypeHintLowering::ReduceForInPrepareOperation(Node* enumerator, Node* effect,
|
Node* control,
|
FeedbackSlot slot) const {
|
DCHECK(!slot.IsInvalid());
|
FeedbackNexus nexus(feedback_vector(), slot);
|
if (Node* node = TryBuildSoftDeopt(
|
nexus, effect, control,
|
DeoptimizeReason::kInsufficientTypeFeedbackForForIn)) {
|
return LoweringResult::Exit(node);
|
}
|
return LoweringResult::NoChange();
|
}
|
|
JSTypeHintLowering::LoweringResult JSTypeHintLowering::ReduceToNumberOperation(
|
Node* input, Node* effect, Node* control, FeedbackSlot slot) const {
|
DCHECK(!slot.IsInvalid());
|
FeedbackNexus nexus(feedback_vector(), slot);
|
NumberOperationHint hint;
|
if (BinaryOperationHintToNumberOperationHint(
|
nexus.GetBinaryOperationFeedback(), &hint)) {
|
Node* node = jsgraph()->graph()->NewNode(
|
jsgraph()->simplified()->SpeculativeToNumber(hint, VectorSlotPair()),
|
input, effect, control);
|
return LoweringResult::SideEffectFree(node, node, control);
|
}
|
return LoweringResult::NoChange();
|
}
|
|
JSTypeHintLowering::LoweringResult JSTypeHintLowering::ReduceCallOperation(
|
const Operator* op, Node* const* args, int arg_count, Node* effect,
|
Node* control, FeedbackSlot slot) const {
|
DCHECK(op->opcode() == IrOpcode::kJSCall ||
|
op->opcode() == IrOpcode::kJSCallWithSpread);
|
DCHECK(!slot.IsInvalid());
|
FeedbackNexus nexus(feedback_vector(), slot);
|
if (Node* node = TryBuildSoftDeopt(
|
nexus, effect, control,
|
DeoptimizeReason::kInsufficientTypeFeedbackForCall)) {
|
return LoweringResult::Exit(node);
|
}
|
return LoweringResult::NoChange();
|
}
|
|
JSTypeHintLowering::LoweringResult JSTypeHintLowering::ReduceConstructOperation(
|
const Operator* op, Node* const* args, int arg_count, Node* effect,
|
Node* control, FeedbackSlot slot) const {
|
DCHECK(op->opcode() == IrOpcode::kJSConstruct ||
|
op->opcode() == IrOpcode::kJSConstructWithSpread);
|
DCHECK(!slot.IsInvalid());
|
FeedbackNexus nexus(feedback_vector(), slot);
|
if (Node* node = TryBuildSoftDeopt(
|
nexus, effect, control,
|
DeoptimizeReason::kInsufficientTypeFeedbackForConstruct)) {
|
return LoweringResult::Exit(node);
|
}
|
return LoweringResult::NoChange();
|
}
|
|
JSTypeHintLowering::LoweringResult JSTypeHintLowering::ReduceLoadNamedOperation(
|
const Operator* op, Node* receiver, Node* effect, Node* control,
|
FeedbackSlot slot) const {
|
DCHECK_EQ(IrOpcode::kJSLoadNamed, op->opcode());
|
DCHECK(!slot.IsInvalid());
|
FeedbackNexus nexus(feedback_vector(), slot);
|
if (Node* node = TryBuildSoftDeopt(
|
nexus, effect, control,
|
DeoptimizeReason::kInsufficientTypeFeedbackForGenericNamedAccess)) {
|
return LoweringResult::Exit(node);
|
}
|
return LoweringResult::NoChange();
|
}
|
|
JSTypeHintLowering::LoweringResult JSTypeHintLowering::ReduceLoadKeyedOperation(
|
const Operator* op, Node* obj, Node* key, Node* effect, Node* control,
|
FeedbackSlot slot) const {
|
DCHECK_EQ(IrOpcode::kJSLoadProperty, op->opcode());
|
DCHECK(!slot.IsInvalid());
|
FeedbackNexus nexus(feedback_vector(), slot);
|
if (Node* node = TryBuildSoftDeopt(
|
nexus, effect, control,
|
DeoptimizeReason::kInsufficientTypeFeedbackForGenericKeyedAccess)) {
|
return LoweringResult::Exit(node);
|
}
|
return LoweringResult::NoChange();
|
}
|
|
JSTypeHintLowering::LoweringResult
|
JSTypeHintLowering::ReduceStoreNamedOperation(const Operator* op, Node* obj,
|
Node* val, Node* effect,
|
Node* control,
|
FeedbackSlot slot) const {
|
DCHECK(op->opcode() == IrOpcode::kJSStoreNamed ||
|
op->opcode() == IrOpcode::kJSStoreNamedOwn);
|
DCHECK(!slot.IsInvalid());
|
FeedbackNexus nexus(feedback_vector(), slot);
|
if (Node* node = TryBuildSoftDeopt(
|
nexus, effect, control,
|
DeoptimizeReason::kInsufficientTypeFeedbackForGenericNamedAccess)) {
|
return LoweringResult::Exit(node);
|
}
|
return LoweringResult::NoChange();
|
}
|
|
JSTypeHintLowering::LoweringResult
|
JSTypeHintLowering::ReduceStoreKeyedOperation(const Operator* op, Node* obj,
|
Node* key, Node* val,
|
Node* effect, Node* control,
|
FeedbackSlot slot) const {
|
DCHECK(op->opcode() == IrOpcode::kJSStoreProperty ||
|
op->opcode() == IrOpcode::kJSStoreInArrayLiteral);
|
DCHECK(!slot.IsInvalid());
|
FeedbackNexus nexus(feedback_vector(), slot);
|
if (Node* node = TryBuildSoftDeopt(
|
nexus, effect, control,
|
DeoptimizeReason::kInsufficientTypeFeedbackForGenericKeyedAccess)) {
|
return LoweringResult::Exit(node);
|
}
|
return LoweringResult::NoChange();
|
}
|
|
Node* JSTypeHintLowering::TryBuildSoftDeopt(FeedbackNexus& nexus, Node* effect,
|
Node* control,
|
DeoptimizeReason reason) const {
|
if ((flags() & kBailoutOnUninitialized) && nexus.IsUninitialized()) {
|
Node* deoptimize = jsgraph()->graph()->NewNode(
|
jsgraph()->common()->Deoptimize(DeoptimizeKind::kSoft, reason,
|
VectorSlotPair()),
|
jsgraph()->Dead(), effect, control);
|
Node* frame_state = NodeProperties::FindFrameStateBefore(deoptimize);
|
deoptimize->ReplaceInput(0, frame_state);
|
return deoptimize;
|
}
|
return nullptr;
|
}
|
|
} // namespace compiler
|
} // namespace internal
|
} // namespace v8
|