// 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/objects/feedback-vector.h" #include "src/objects/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; } bool BinaryOperationHintToBigIntOperationHint( BinaryOperationHint binop_hint, BigIntOperationHint* bigint_hint) { switch (binop_hint) { case BinaryOperationHint::kSignedSmall: case BinaryOperationHint::kSignedSmallInputs: case BinaryOperationHint::kSigned32: case BinaryOperationHint::kNumber: case BinaryOperationHint::kNumberOrOddball: case BinaryOperationHint::kAny: case BinaryOperationHint::kNone: case BinaryOperationHint::kString: return false; case BinaryOperationHint::kBigInt: *bigint_hint = BigIntOperationHint::kBigInt; return true; } UNREACHABLE(); } } // 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 GetBinaryBigIntOperationHint(BigIntOperationHint* hint) { return BinaryOperationHintToBigIntOperationHint(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::kReceiverOrNullOrUndefined: 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* SpeculativeBigIntOp(BigIntOperationHint hint) { switch (op_->opcode()) { case IrOpcode::kJSAdd: return simplified()->SpeculativeBigIntAdd(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* TryBuildBigIntBinop() { BigIntOperationHint hint; if (GetBinaryBigIntOperationHint(&hint)) { const Operator* op = SpeculativeBigIntOp(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& 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 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(); if (!node) { FeedbackNexus nexus(feedback_vector(), slot); if (nexus.GetBinaryOperationFeedback() == BinaryOperationHint::kBigInt) { const Operator* op = jsgraph()->simplified()->SpeculativeBigIntNegate( BigIntOperationHint::kBigInt); node = jsgraph()->graph()->NewNode(op, operand, effect, control); } } break; } default: UNREACHABLE(); } 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); } if (op->opcode() == IrOpcode::kJSAdd) { if (Node* node = b.TryBuildBigIntBinop()) { return LoweringResult::SideEffectFree(node, node, control); } } break; } case IrOpcode::kJSExponentiate: { // TODO(neis): Introduce a SpeculativeNumberPow operator? break; } default: UNREACHABLE(); } 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, jsgraph()->Dead()); deoptimize->ReplaceInput(0, frame_state); return deoptimize; } return nullptr; } } // namespace compiler } // namespace internal } // namespace v8