diff --git a/src/base/flags.h b/src/base/flags.h index 467ecf67c9..4d9e14e5f9 100644 --- a/src/base/flags.h +++ b/src/base/flags.h @@ -5,6 +5,8 @@ #ifndef V8_BASE_FLAGS_H_ #define V8_BASE_FLAGS_H_ +#include + #include "src/base/compiler-specific.h" namespace v8 { @@ -30,6 +32,13 @@ class Flags final { : mask_(static_cast(flag)) {} explicit Flags(mask_type mask) : mask_(static_cast(mask)) {} + bool operator==(flag_type flag) const { + return mask_ == static_cast(flag); + } + bool operator!=(flag_type flag) const { + return mask_ != static_cast(flag); + } + Flags& operator&=(const Flags& flags) { mask_ &= flags.mask_; return *this; @@ -60,6 +69,8 @@ class Flags final { operator mask_type() const { return mask_; } bool operator!() const { return !mask_; } + friend size_t hash_value(const Flags& flags) { return flags.mask_; } + private: mask_type mask_; }; diff --git a/src/compiler/ast-graph-builder.cc b/src/compiler/ast-graph-builder.cc index 4dc3acdfa1..3e3e760504 100644 --- a/src/compiler/ast-graph-builder.cc +++ b/src/compiler/ast-graph-builder.cc @@ -94,11 +94,14 @@ class AstGraphBuilder::AstValueContext final : public AstContext { // Context to evaluate expression for a condition value (and side effects). class AstGraphBuilder::AstTestContext final : public AstContext { public: - explicit AstTestContext(AstGraphBuilder* owner) - : AstContext(owner, Expression::kTest) {} + AstTestContext(AstGraphBuilder* owner, TypeFeedbackId feedback_id) + : AstContext(owner, Expression::kTest), feedback_id_(feedback_id) {} ~AstTestContext() final; void ProduceValue(Node* value) final; Node* ConsumeValue() final; + + private: + TypeFeedbackId const feedback_id_; }; @@ -927,7 +930,7 @@ void AstGraphBuilder::AstValueContext::ProduceValue(Node* value) { void AstGraphBuilder::AstTestContext::ProduceValue(Node* value) { - environment()->Push(owner()->BuildToBoolean(value)); + environment()->Push(owner()->BuildToBoolean(value, feedback_id_)); } @@ -1034,7 +1037,7 @@ void AstGraphBuilder::VisitForEffect(Expression* expr) { void AstGraphBuilder::VisitForTest(Expression* expr) { - AstTestContext for_condition(this); + AstTestContext for_condition(this, expr->test_id()); if (!CheckStackOverflow()) { expr->Accept(this); } else { @@ -3011,7 +3014,7 @@ void AstGraphBuilder::VisitTypeof(UnaryOperation* expr) { void AstGraphBuilder::VisitNot(UnaryOperation* expr) { VisitForValue(expr->expression()); Node* operand = environment()->Pop(); - Node* input = BuildToBoolean(operand); + Node* input = BuildToBoolean(operand, expr->expression()->test_id()); Node* value = NewNode(common()->Select(kMachAnyTagged), input, jsgraph()->FalseConstant(), jsgraph()->TrueConstant()); ast_context()->ProduceValue(value); @@ -3030,7 +3033,7 @@ void AstGraphBuilder::VisitLogicalExpression(BinaryOperation* expr) { IfBuilder compare_if(this); VisitForValue(expr->left()); Node* condition = environment()->Top(); - compare_if.If(BuildToBoolean(condition)); + compare_if.If(BuildToBoolean(condition, expr->left()->test_id())); compare_if.Then(); if (is_logical_and) { environment()->Pop(); @@ -3682,9 +3685,14 @@ Node* AstGraphBuilder::BuildLoadFeedbackVector() { } -Node* AstGraphBuilder::BuildToBoolean(Node* input) { +Node* AstGraphBuilder::BuildToBoolean(Node* input, TypeFeedbackId feedback_id) { if (Node* node = TryFastToBoolean(input)) return node; - return NewNode(javascript()->ToBoolean(), input); + ToBooleanHints hints; + if (!type_hint_analysis_ || + !type_hint_analysis_->GetToBooleanHints(feedback_id, &hints)) { + hints = ToBooleanHint::kAny; + } + return NewNode(javascript()->ToBoolean(hints), input); } diff --git a/src/compiler/ast-graph-builder.h b/src/compiler/ast-graph-builder.h index e9730d1c1e..b89d6af164 100644 --- a/src/compiler/ast-graph-builder.h +++ b/src/compiler/ast-graph-builder.h @@ -321,7 +321,7 @@ class AstGraphBuilder : public AstVisitor { Node* BuildLoadImmutableObjectField(Node* object, int offset); // Builders for automatic type conversion. - Node* BuildToBoolean(Node* input); + Node* BuildToBoolean(Node* input, TypeFeedbackId feedback_id); Node* BuildToName(Node* input, BailoutId bailout_id); Node* BuildToObject(Node* input, BailoutId bailout_id); diff --git a/src/compiler/bytecode-graph-builder.cc b/src/compiler/bytecode-graph-builder.cc index 17f918e84b..5f96377007 100644 --- a/src/compiler/bytecode-graph-builder.cc +++ b/src/compiler/bytecode-graph-builder.cc @@ -971,8 +971,8 @@ void BytecodeGraphBuilder::VisitDec( void BytecodeGraphBuilder::VisitLogicalNot( const interpreter::BytecodeArrayIterator& iterator) { - Node* value = - NewNode(javascript()->ToBoolean(), environment()->LookupAccumulator()); + Node* value = NewNode(javascript()->ToBoolean(ToBooleanHint::kAny), + environment()->LookupAccumulator()); Node* node = NewNode(common()->Select(kMachAnyTagged), value, jsgraph()->FalseConstant(), jsgraph()->TrueConstant()); environment()->BindAccumulator(node); @@ -1094,7 +1094,7 @@ void BytecodeGraphBuilder::BuildCastOperator( void BytecodeGraphBuilder::VisitToBoolean( const interpreter::BytecodeArrayIterator& iterator) { - BuildCastOperator(javascript()->ToBoolean(), iterator); + BuildCastOperator(javascript()->ToBoolean(ToBooleanHint::kAny), iterator); } diff --git a/src/compiler/js-operator.cc b/src/compiler/js-operator.cc index fa27ac219f..5f9abceb40 100644 --- a/src/compiler/js-operator.cc +++ b/src/compiler/js-operator.cc @@ -46,6 +46,12 @@ ConvertReceiverMode ConvertReceiverModeOf(Operator const* op) { } +ToBooleanHints ToBooleanHintsOf(Operator const* op) { + DCHECK_EQ(IrOpcode::kJSToBoolean, op->opcode()); + return OpParameter(op); +} + + size_t hash_value(TailCallMode mode) { return base::hash_value(static_cast(mode)); } @@ -486,7 +492,6 @@ const CreateLiteralParameters& CreateLiteralParametersOf(const Operator* op) { V(NotEqual, Operator::kNoProperties, 2, 1) \ V(StrictEqual, Operator::kNoThrow, 2, 1) \ V(StrictNotEqual, Operator::kNoThrow, 2, 1) \ - V(ToBoolean, Operator::kEliminatable, 1, 1) \ V(ToNumber, Operator::kNoProperties, 1, 1) \ V(ToString, Operator::kNoProperties, 1, 1) \ V(ToName, Operator::kNoProperties, 1, 1) \ @@ -717,6 +722,16 @@ const Operator* JSOperatorBuilder::Modulus(LanguageMode language_mode, } +const Operator* JSOperatorBuilder::ToBoolean(ToBooleanHints hints) { + // TODO(turbofan): Cache most important versions of this operator. + return new (zone()) Operator1( //-- + IrOpcode::kJSToBoolean, Operator::kEliminatable, // opcode + "JSToBoolean", // name + 1, 1, 0, 1, 1, 0, // inputs/outputs + hints); // parameter +} + + const Operator* JSOperatorBuilder::CallFunction( size_t arity, LanguageMode language_mode, VectorSlotPair const& feedback, ConvertReceiverMode convert_mode, TailCallMode tail_call_mode) { diff --git a/src/compiler/js-operator.h b/src/compiler/js-operator.h index 8597f45881..b0520bc8ee 100644 --- a/src/compiler/js-operator.h +++ b/src/compiler/js-operator.h @@ -44,7 +44,11 @@ size_t hash_value(VectorSlotPair const&); // The ConvertReceiverMode is used as parameter by JSConvertReceiver operators. -ConvertReceiverMode ConvertReceiverModeOf(const Operator* op); +ConvertReceiverMode ConvertReceiverModeOf(Operator const* op); + + +// The ToBooleanHints are used as parameter by JSToBoolean operators. +ToBooleanHints ToBooleanHintsOf(Operator const* op); // Defines whether tail call optimization is allowed. @@ -497,7 +501,7 @@ class JSOperatorBuilder final : public ZoneObject { const Operator* Modulus(LanguageMode language_mode, BinaryOperationHints hints); - const Operator* ToBoolean(); + const Operator* ToBoolean(ToBooleanHints hints); const Operator* ToNumber(); const Operator* ToString(); const Operator* ToName(); diff --git a/src/compiler/type-hint-analyzer.cc b/src/compiler/type-hint-analyzer.cc index e86f94ab9f..42c4627b67 100644 --- a/src/compiler/type-hint-analyzer.cc +++ b/src/compiler/type-hint-analyzer.cc @@ -5,6 +5,7 @@ #include "src/compiler/type-hint-analyzer.h" #include "src/assembler.h" +#include "src/code-stubs.h" #include "src/compiler/type-hints.h" #include "src/ic/ic-state.h" @@ -41,6 +42,32 @@ bool TypeHintAnalysis::GetBinaryOperationHints( } +bool TypeHintAnalysis::GetToBooleanHints(TypeFeedbackId id, + ToBooleanHints* hints) const { + auto i = infos_.find(id); + if (i == infos_.end()) return false; + Handle code = i->second; + DCHECK_EQ(Code::TO_BOOLEAN_IC, code->kind()); + ToBooleanStub stub(code->GetIsolate(), code->extra_ic_state()); +// TODO(bmeurer): Replace ToBooleanStub::Types with ToBooleanHints. +#define ASSERT_COMPATIBLE(NAME, Name) \ + STATIC_ASSERT(1 << ToBooleanStub::NAME == \ + static_cast(ToBooleanHint::k##Name)) + ASSERT_COMPATIBLE(UNDEFINED, Undefined); + ASSERT_COMPATIBLE(BOOLEAN, Boolean); + ASSERT_COMPATIBLE(NULL_TYPE, Null); + ASSERT_COMPATIBLE(SMI, SmallInteger); + ASSERT_COMPATIBLE(SPEC_OBJECT, Receiver); + ASSERT_COMPATIBLE(STRING, String); + ASSERT_COMPATIBLE(SYMBOL, Symbol); + ASSERT_COMPATIBLE(HEAP_NUMBER, HeapNumber); + ASSERT_COMPATIBLE(SIMD_VALUE, SimdValue); +#undef ASSERT_COMPATIBLE + *hints = ToBooleanHints(stub.types().ToIntegral()); + return true; +} + + TypeHintAnalysis* TypeHintAnalyzer::Analyze(Handle code) { DisallowHeapAllocation no_gc; TypeHintAnalysis::Infos infos(zone()); @@ -51,13 +78,13 @@ TypeHintAnalysis* TypeHintAnalyzer::Analyze(Handle code) { Address target_address = rinfo->target_address(); Code* target = Code::GetCodeFromTargetAddress(target_address); switch (target->kind()) { - case Code::BINARY_OP_IC: { + case Code::BINARY_OP_IC: + case Code::TO_BOOLEAN_IC: { // Add this feedback to the {infos}. TypeFeedbackId id(static_cast(rinfo->data())); infos.insert(std::make_pair(id, handle(target, isolate))); break; } - default: // Ignore the remaining code objects. break; diff --git a/src/compiler/type-hint-analyzer.h b/src/compiler/type-hint-analyzer.h index ee3bebfd7c..1a79905633 100644 --- a/src/compiler/type-hint-analyzer.h +++ b/src/compiler/type-hint-analyzer.h @@ -5,18 +5,14 @@ #ifndef V8_COMPILER_TYPE_HINT_ANALYZER_H_ #define V8_COMPILER_TYPE_HINT_ANALYZER_H_ +#include "src/compiler/type-hints.h" #include "src/handles.h" -#include "src/utils.h" #include "src/zone-containers.h" namespace v8 { namespace internal { namespace compiler { -// Forward declarations. -class BinaryOperationHints; - - // The result of analyzing type hints. class TypeHintAnalysis final : public ZoneObject { public: @@ -26,6 +22,7 @@ class TypeHintAnalysis final : public ZoneObject { bool GetBinaryOperationHints(TypeFeedbackId id, BinaryOperationHints* hints) const; + bool GetToBooleanHints(TypeFeedbackId id, ToBooleanHints* hints) const; private: Infos const infos_; diff --git a/src/compiler/type-hints.cc b/src/compiler/type-hints.cc index 2cf8f4d6c0..06abad6380 100644 --- a/src/compiler/type-hints.cc +++ b/src/compiler/type-hints.cc @@ -32,6 +32,52 @@ std::ostream& operator<<(std::ostream& os, BinaryOperationHints hints) { return os << hints.left() << "*" << hints.right() << "->" << hints.result(); } + +std::ostream& operator<<(std::ostream& os, ToBooleanHint hint) { + switch (hint) { + case ToBooleanHint::kNone: + return os << "None"; + case ToBooleanHint::kUndefined: + return os << "Undefined"; + case ToBooleanHint::kBoolean: + return os << "Boolean"; + case ToBooleanHint::kNull: + return os << "Null"; + case ToBooleanHint::kSmallInteger: + return os << "SmallInteger"; + case ToBooleanHint::kReceiver: + return os << "Receiver"; + case ToBooleanHint::kString: + return os << "String"; + case ToBooleanHint::kSymbol: + return os << "Symbol"; + case ToBooleanHint::kHeapNumber: + return os << "HeapNumber"; + case ToBooleanHint::kSimdValue: + return os << "SimdValue"; + case ToBooleanHint::kAny: + return os << "Any"; + } + UNREACHABLE(); + return os; +} + + +std::ostream& operator<<(std::ostream& os, ToBooleanHints hints) { + if (hints == ToBooleanHint::kAny) return os << "Any"; + if (hints == ToBooleanHint::kNone) return os << "None"; + bool first = true; + for (ToBooleanHints::mask_type i = 0; i < sizeof(i) * CHAR_BIT; ++i) { + ToBooleanHint const hint = static_cast(1u << i); + if (hints & hint) { + if (!first) os << "|"; + first = false; + os << hint; + } + } + return os; +} + } // namespace compiler } // namespace internal } // namespace v8 diff --git a/src/compiler/type-hints.h b/src/compiler/type-hints.h index be8f6f7582..f1cc64036c 100644 --- a/src/compiler/type-hints.h +++ b/src/compiler/type-hints.h @@ -5,6 +5,7 @@ #ifndef V8_COMPILER_TYPE_HINTS_H_ #define V8_COMPILER_TYPE_HINTS_H_ +#include "src/base/flags.h" #include "src/utils.h" namespace v8 { @@ -51,6 +52,31 @@ class BinaryOperationHints final { std::ostream& operator<<(std::ostream&, BinaryOperationHints::Hint); std::ostream& operator<<(std::ostream&, BinaryOperationHints); + +// Type hints for the ToBoolean type conversion. +enum class ToBooleanHint : uint16_t { + kNone = 0u, + kUndefined = 1u << 0, + kBoolean = 1u << 1, + kNull = 1u << 2, + kSmallInteger = 1u << 3, + kReceiver = 1u << 4, + kString = 1u << 5, + kSymbol = 1u << 6, + kHeapNumber = 1u << 7, + kSimdValue = 1u << 8, + kAny = kUndefined | kBoolean | kNull | kSmallInteger | kReceiver | kString | + kSymbol | kHeapNumber | kSimdValue +}; + +std::ostream& operator<<(std::ostream&, ToBooleanHint); + +typedef base::Flags ToBooleanHints; + +std::ostream& operator<<(std::ostream&, ToBooleanHints); + +DEFINE_OPERATORS_FOR_FLAGS(ToBooleanHints) + } // namespace compiler } // namespace internal } // namespace v8 diff --git a/test/unittests/compiler/js-operator-unittest.cc b/test/unittests/compiler/js-operator-unittest.cc index f43c4096c2..59edfc2154 100644 --- a/test/unittests/compiler/js-operator-unittest.cc +++ b/test/unittests/compiler/js-operator-unittest.cc @@ -71,7 +71,6 @@ const SharedOperator kSharedOperators[] = { SHARED(NotEqual, Operator::kNoProperties, 2, 1, 1, 1, 1, 1, 2), SHARED(StrictEqual, Operator::kNoThrow, 2, 0, 1, 1, 1, 1, 0), SHARED(StrictNotEqual, Operator::kNoThrow, 2, 0, 1, 1, 1, 1, 0), - SHARED(ToBoolean, Operator::kEliminatable, 1, 0, 1, 0, 1, 1, 0), SHARED(ToNumber, Operator::kNoProperties, 1, 1, 1, 1, 1, 1, 2), SHARED(ToString, Operator::kNoProperties, 1, 1, 1, 1, 1, 1, 2), SHARED(ToName, Operator::kNoProperties, 1, 1, 1, 1, 1, 1, 2), diff --git a/test/unittests/compiler/js-typed-lowering-unittest.cc b/test/unittests/compiler/js-typed-lowering-unittest.cc index a92a68c582..dc5825326d 100644 --- a/test/unittests/compiler/js-typed-lowering-unittest.cc +++ b/test/unittests/compiler/js-typed-lowering-unittest.cc @@ -224,8 +224,9 @@ TEST_F(JSTypedLoweringTest, ParameterWithUndefined) { TEST_F(JSTypedLoweringTest, JSToBooleanWithBoolean) { Node* input = Parameter(Type::Boolean(), 0); Node* context = Parameter(Type::Any(), 1); - Reduction r = Reduce(graph()->NewNode(javascript()->ToBoolean(), input, - context, graph()->start())); + Reduction r = + Reduce(graph()->NewNode(javascript()->ToBoolean(ToBooleanHint::kAny), + input, context, graph()->start())); ASSERT_TRUE(r.Changed()); EXPECT_EQ(input, r.replacement()); } @@ -253,8 +254,9 @@ TEST_F(JSTypedLoweringTest, JSToBooleanWithFalsish) { zone()), 0); Node* context = Parameter(Type::Any(), 1); - Reduction r = Reduce(graph()->NewNode(javascript()->ToBoolean(), input, - context, graph()->start())); + Reduction r = + Reduce(graph()->NewNode(javascript()->ToBoolean(ToBooleanHint::kAny), + input, context, graph()->start())); ASSERT_TRUE(r.Changed()); EXPECT_THAT(r.replacement(), IsFalseConstant()); } @@ -268,8 +270,9 @@ TEST_F(JSTypedLoweringTest, JSToBooleanWithTruish) { zone()), 0); Node* context = Parameter(Type::Any(), 1); - Reduction r = Reduce(graph()->NewNode(javascript()->ToBoolean(), input, - context, graph()->start())); + Reduction r = + Reduce(graph()->NewNode(javascript()->ToBoolean(ToBooleanHint::kAny), + input, context, graph()->start())); ASSERT_TRUE(r.Changed()); EXPECT_THAT(r.replacement(), IsTrueConstant()); } @@ -278,8 +281,9 @@ TEST_F(JSTypedLoweringTest, JSToBooleanWithTruish) { TEST_F(JSTypedLoweringTest, JSToBooleanWithNonZeroPlainNumber) { Node* input = Parameter(Type::Range(1, V8_INFINITY, zone()), 0); Node* context = Parameter(Type::Any(), 1); - Reduction r = Reduce(graph()->NewNode(javascript()->ToBoolean(), input, - context, graph()->start())); + Reduction r = + Reduce(graph()->NewNode(javascript()->ToBoolean(ToBooleanHint::kAny), + input, context, graph()->start())); ASSERT_TRUE(r.Changed()); EXPECT_THAT(r.replacement(), IsTrueConstant()); } @@ -288,8 +292,9 @@ TEST_F(JSTypedLoweringTest, JSToBooleanWithNonZeroPlainNumber) { TEST_F(JSTypedLoweringTest, JSToBooleanWithOrderedNumber) { Node* input = Parameter(Type::OrderedNumber(), 0); Node* context = Parameter(Type::Any(), 1); - Reduction r = Reduce(graph()->NewNode(javascript()->ToBoolean(), input, - context, graph()->start())); + Reduction r = + Reduce(graph()->NewNode(javascript()->ToBoolean(ToBooleanHint::kAny), + input, context, graph()->start())); ASSERT_TRUE(r.Changed()); EXPECT_THAT(r.replacement(), IsBooleanNot(IsNumberEqual(input, IsNumberConstant(0.0)))); @@ -299,8 +304,9 @@ TEST_F(JSTypedLoweringTest, JSToBooleanWithOrderedNumber) { TEST_F(JSTypedLoweringTest, JSToBooleanWithString) { Node* input = Parameter(Type::String(), 0); Node* context = Parameter(Type::Any(), 1); - Reduction r = Reduce(graph()->NewNode(javascript()->ToBoolean(), input, - context, graph()->start())); + Reduction r = + Reduce(graph()->NewNode(javascript()->ToBoolean(ToBooleanHint::kAny), + input, context, graph()->start())); ASSERT_TRUE(r.Changed()); EXPECT_THAT( r.replacement(), @@ -313,8 +319,9 @@ TEST_F(JSTypedLoweringTest, JSToBooleanWithString) { TEST_F(JSTypedLoweringTest, JSToBooleanWithAny) { Node* input = Parameter(Type::Any(), 0); Node* context = Parameter(Type::Any(), 1); - Reduction r = Reduce(graph()->NewNode(javascript()->ToBoolean(), input, - context, graph()->start())); + Reduction r = + Reduce(graph()->NewNode(javascript()->ToBoolean(ToBooleanHint::kAny), + input, context, graph()->start())); ASSERT_FALSE(r.Changed()); }