| // 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/codegen/tick-counter.h" |
| #include "src/compiler/js-graph.h" |
| #include "src/compiler/js-heap-broker.h" |
| #include "src/compiler/js-heap-copy-reducer.h" |
| #include "src/compiler/js-typed-lowering.h" |
| #include "src/compiler/machine-operator.h" |
| #include "src/compiler/node-properties.h" |
| #include "src/compiler/opcodes.h" |
| #include "src/compiler/operator-properties.h" |
| #include "src/compiler/simplified-operator.h" |
| #include "src/compiler/typer.h" |
| #include "src/execution/isolate.h" |
| #include "src/heap/factory-inl.h" |
| #include "src/objects/objects.h" |
| #include "test/cctest/cctest.h" |
| |
| namespace v8 { |
| namespace internal { |
| namespace compiler { |
| |
| class JSTypedLoweringTester : public HandleAndZoneScope { |
| public: |
| explicit JSTypedLoweringTester(int num_parameters = 0) |
| : HandleAndZoneScope(kCompressGraphZone), |
| isolate(main_isolate()), |
| canonical(isolate), |
| js_heap_broker(isolate, main_zone()), |
| binop(nullptr), |
| unop(nullptr), |
| javascript(main_zone()), |
| machine(main_zone()), |
| simplified(main_zone()), |
| common(main_zone()), |
| graph(main_zone()), |
| typer(&js_heap_broker, Typer::kNoFlags, &graph, &tick_counter), |
| context_node(nullptr) { |
| graph.SetStart(graph.NewNode(common.Start(num_parameters))); |
| graph.SetEnd(graph.NewNode(common.End(1), graph.start())); |
| typer.Run(); |
| } |
| |
| Isolate* isolate; |
| TickCounter tick_counter; |
| CanonicalHandleScope canonical; |
| JSHeapBroker js_heap_broker; |
| const Operator* binop; |
| const Operator* unop; |
| JSOperatorBuilder javascript; |
| MachineOperatorBuilder machine; |
| SimplifiedOperatorBuilder simplified; |
| CommonOperatorBuilder common; |
| Graph graph; |
| Typer typer; |
| Node* context_node; |
| |
| Node* Parameter(Type t, int32_t index = 0) { |
| Node* n = graph.NewNode(common.Parameter(index), graph.start()); |
| NodeProperties::SetType(n, t); |
| return n; |
| } |
| |
| Node* UndefinedConstant() { |
| Handle<HeapObject> value = isolate->factory()->undefined_value(); |
| return graph.NewNode(common.HeapConstant(value)); |
| } |
| |
| Node* HeapConstant(Handle<HeapObject> constant) { |
| return graph.NewNode(common.HeapConstant(constant)); |
| } |
| |
| Node* EmptyFrameState(Node* context) { |
| Node* parameters = |
| graph.NewNode(common.StateValues(0, SparseInputMask::Dense())); |
| Node* locals = |
| graph.NewNode(common.StateValues(0, SparseInputMask::Dense())); |
| Node* stack = |
| graph.NewNode(common.StateValues(0, SparseInputMask::Dense())); |
| |
| Node* state_node = graph.NewNode( |
| common.FrameState(BailoutId::None(), OutputFrameStateCombine::Ignore(), |
| nullptr), |
| parameters, locals, stack, context, UndefinedConstant(), graph.start()); |
| |
| return state_node; |
| } |
| |
| Node* reduce(Node* node) { |
| JSHeapCopyReducer heap_copy_reducer(&js_heap_broker); |
| CHECK(!heap_copy_reducer.Reduce(node).Changed()); |
| JSGraph jsgraph(main_isolate(), &graph, &common, &javascript, &simplified, |
| &machine); |
| GraphReducer graph_reducer(main_zone(), &graph, &tick_counter, |
| &js_heap_broker); |
| JSTypedLowering reducer(&graph_reducer, &jsgraph, &js_heap_broker, |
| main_zone()); |
| Reduction reduction = reducer.Reduce(node); |
| if (reduction.Changed()) return reduction.replacement(); |
| return node; |
| } |
| |
| Node* start() { return graph.start(); } |
| |
| Node* context() { |
| if (context_node == nullptr) { |
| context_node = graph.NewNode(common.Parameter(-1), graph.start()); |
| } |
| return context_node; |
| } |
| |
| Node* control() { return start(); } |
| |
| void CheckBinop(IrOpcode::Value expected, Node* node) { |
| CHECK_EQ(expected, node->opcode()); |
| } |
| |
| void CheckBinop(const Operator* expected, Node* node) { |
| CHECK_EQ(expected->opcode(), node->op()->opcode()); |
| } |
| |
| Node* ReduceUnop(const Operator* op, Type input_type) { |
| return reduce(Unop(op, Parameter(input_type))); |
| } |
| |
| Node* ReduceBinop(const Operator* op, Type left_type, Type right_type) { |
| return reduce(Binop(op, Parameter(left_type, 0), Parameter(right_type, 1))); |
| } |
| |
| Node* Binop(const Operator* op, Node* left, Node* right) { |
| // JS binops also require context, effect, and control |
| std::vector<Node*> inputs; |
| inputs.push_back(left); |
| inputs.push_back(right); |
| if (JSOperator::IsBinaryWithFeedback(op->opcode())) { |
| inputs.push_back(UndefinedConstant()); // Feedback vector. |
| } |
| if (OperatorProperties::HasContextInput(op)) { |
| inputs.push_back(context()); |
| } |
| for (int i = 0; i < OperatorProperties::GetFrameStateInputCount(op); i++) { |
| inputs.push_back(EmptyFrameState(context())); |
| } |
| if (op->EffectInputCount() > 0) { |
| inputs.push_back(start()); |
| } |
| if (op->ControlInputCount() > 0) { |
| inputs.push_back(control()); |
| } |
| return graph.NewNode(op, static_cast<int>(inputs.size()), |
| &(inputs.front())); |
| } |
| |
| Node* Unop(const Operator* op, Node* input) { |
| DCHECK(!JSOperator::IsUnaryWithFeedback(op->opcode())); |
| // JS unops also require context, effect, and control |
| if (OperatorProperties::GetFrameStateInputCount(op) > 0) { |
| CHECK_EQ(1, OperatorProperties::GetFrameStateInputCount(op)); |
| return graph.NewNode(op, input, context(), EmptyFrameState(context()), |
| start(), control()); |
| } else { |
| return graph.NewNode(op, input, context(), start(), control()); |
| } |
| } |
| |
| Node* UseForEffect(Node* node) { |
| Node* merge = graph.NewNode(common.Merge(1), start()); |
| return graph.NewNode(common.EffectPhi(1), node, merge); |
| } |
| |
| void CheckEffectInput(Node* effect, Node* use) { |
| CHECK_EQ(effect, NodeProperties::GetEffectInput(use)); |
| } |
| |
| void CheckNumberConstant(double expected, Node* result) { |
| CHECK_EQ(IrOpcode::kNumberConstant, result->opcode()); |
| CHECK_EQ(expected, OpParameter<double>(result->op())); |
| } |
| |
| void CheckNaN(Node* result) { |
| CHECK_EQ(IrOpcode::kNumberConstant, result->opcode()); |
| double value = OpParameter<double>(result->op()); |
| CHECK(std::isnan(value)); |
| } |
| |
| void CheckTrue(Node* result) { |
| CheckHandle(isolate->factory()->true_value(), result); |
| } |
| |
| void CheckFalse(Node* result) { |
| CheckHandle(isolate->factory()->false_value(), result); |
| } |
| |
| void CheckHandle(Handle<HeapObject> expected, Node* result) { |
| CHECK_EQ(IrOpcode::kHeapConstant, result->opcode()); |
| Handle<HeapObject> value = HeapConstantOf(result->op()); |
| CHECK_EQ(*expected, *value); |
| } |
| }; |
| |
| static Type kStringTypes[] = {Type::InternalizedString(), Type::String()}; |
| |
| static Type kInt32Types[] = {Type::UnsignedSmall(), Type::Negative32(), |
| Type::Unsigned31(), Type::SignedSmall(), |
| Type::Signed32(), Type::Unsigned32(), |
| Type::Integral32()}; |
| |
| static Type kNumberTypes[] = { |
| Type::UnsignedSmall(), Type::Negative32(), Type::Unsigned31(), |
| Type::SignedSmall(), Type::Signed32(), Type::Unsigned32(), |
| Type::Integral32(), Type::MinusZero(), Type::NaN(), |
| Type::OrderedNumber(), Type::PlainNumber(), Type::Number()}; |
| |
| static Type I32Type(bool is_signed) { |
| return is_signed ? Type::Signed32() : Type::Unsigned32(); |
| } |
| |
| |
| static IrOpcode::Value NumberToI32(bool is_signed) { |
| return is_signed ? IrOpcode::kNumberToInt32 : IrOpcode::kNumberToUint32; |
| } |
| |
| namespace { |
| |
| FeedbackSource FeedbackSourceWithOneBinarySlot(JSTypedLoweringTester* R) { |
| return FeedbackSource{FeedbackVector::NewWithOneBinarySlotForTesting( |
| R->main_zone(), R->main_isolate()), |
| FeedbackSlot{0}}; |
| } |
| |
| FeedbackSource FeedbackSourceWithOneCompareSlot(JSTypedLoweringTester* R) { |
| return FeedbackSource{FeedbackVector::NewWithOneCompareSlotForTesting( |
| R->main_zone(), R->main_isolate()), |
| FeedbackSlot{0}}; |
| } |
| |
| } // namespace |
| |
| TEST(StringBinops) { |
| JSTypedLoweringTester R; |
| |
| for (size_t i = 0; i < arraysize(kStringTypes); ++i) { |
| Node* p0 = R.Parameter(kStringTypes[i], 0); |
| |
| for (size_t j = 0; j < arraysize(kStringTypes); ++j) { |
| Node* p1 = R.Parameter(kStringTypes[j], 1); |
| |
| Node* add = R.Binop(R.javascript.Add(FeedbackSourceWithOneBinarySlot(&R)), |
| p0, p1); |
| Node* r = R.reduce(add); |
| |
| R.CheckBinop(IrOpcode::kStringConcat, r); |
| CHECK_EQ(p0, r->InputAt(1)); |
| CHECK_EQ(p1, r->InputAt(2)); |
| } |
| } |
| } |
| |
| TEST(AddNumber1) { |
| JSTypedLoweringTester R; |
| for (size_t i = 0; i < arraysize(kNumberTypes); ++i) { |
| Node* p0 = R.Parameter(kNumberTypes[i], 0); |
| Node* p1 = R.Parameter(kNumberTypes[i], 1); |
| Node* add = |
| R.Binop(R.javascript.Add(FeedbackSourceWithOneBinarySlot(&R)), p0, p1); |
| Node* r = R.reduce(add); |
| |
| R.CheckBinop(IrOpcode::kNumberAdd, r); |
| CHECK_EQ(p0, r->InputAt(0)); |
| CHECK_EQ(p1, r->InputAt(1)); |
| } |
| } |
| |
| TEST(NumberBinops) { |
| JSTypedLoweringTester R; |
| FeedbackSource feedback_source = FeedbackSourceWithOneBinarySlot(&R); |
| const Operator* ops[] = { |
| R.javascript.Add(feedback_source), R.simplified.NumberAdd(), |
| R.javascript.Subtract(feedback_source), R.simplified.NumberSubtract(), |
| R.javascript.Multiply(feedback_source), R.simplified.NumberMultiply(), |
| R.javascript.Divide(feedback_source), R.simplified.NumberDivide(), |
| R.javascript.Modulus(feedback_source), R.simplified.NumberModulus(), |
| }; |
| |
| for (size_t i = 0; i < arraysize(kNumberTypes); ++i) { |
| Node* p0 = R.Parameter(kNumberTypes[i], 0); |
| |
| for (size_t j = 0; j < arraysize(kNumberTypes); ++j) { |
| Node* p1 = R.Parameter(kNumberTypes[j], 1); |
| |
| for (size_t k = 0; k < arraysize(ops); k += 2) { |
| Node* add = R.Binop(ops[k], p0, p1); |
| Node* r = R.reduce(add); |
| |
| R.CheckBinop(ops[k + 1], r); |
| CHECK_EQ(p0, r->InputAt(0)); |
| CHECK_EQ(p1, r->InputAt(1)); |
| } |
| } |
| } |
| } |
| |
| |
| static void CheckToI32(Node* old_input, Node* new_input, bool is_signed) { |
| Type old_type = NodeProperties::GetType(old_input); |
| Type new_type = NodeProperties::GetType(new_input); |
| Type expected_type = I32Type(is_signed); |
| CHECK(new_type.Is(expected_type)); |
| if (old_type.Is(expected_type)) { |
| CHECK_EQ(old_input, new_input); |
| } else if (new_input->opcode() == IrOpcode::kNumberConstant) { |
| double v = OpParameter<double>(new_input->op()); |
| double e = static_cast<double>(is_signed ? FastD2I(v) : FastD2UI(v)); |
| CHECK_EQ(e, v); |
| } |
| } |
| |
| |
| // A helper class for testing lowering of bitwise shift operators. |
| class JSBitwiseShiftTypedLoweringTester : public JSTypedLoweringTester { |
| public: |
| JSBitwiseShiftTypedLoweringTester() : JSTypedLoweringTester() { |
| int i = 0; |
| FeedbackSource feedback_source = FeedbackSourceWithOneBinarySlot(this); |
| set(i++, javascript.ShiftLeft(feedback_source), true); |
| set(i++, simplified.NumberShiftLeft(), false); |
| set(i++, javascript.ShiftRight(feedback_source), true); |
| set(i++, simplified.NumberShiftRight(), false); |
| set(i++, javascript.ShiftRightLogical(feedback_source), false); |
| set(i++, simplified.NumberShiftRightLogical(), false); |
| } |
| static const int kNumberOps = 6; |
| const Operator* ops[kNumberOps]; |
| bool signedness[kNumberOps]; |
| |
| private: |
| void set(int idx, const Operator* op, bool s) { |
| ops[idx] = op; |
| signedness[idx] = s; |
| } |
| }; |
| |
| |
| TEST(Int32BitwiseShifts) { |
| JSBitwiseShiftTypedLoweringTester R; |
| |
| Type types[] = { |
| Type::SignedSmall(), Type::UnsignedSmall(), Type::Negative32(), |
| Type::Unsigned31(), Type::Unsigned32(), Type::Signed32(), |
| Type::MinusZero(), Type::NaN(), Type::Undefined(), |
| Type::Null(), Type::Boolean(), Type::Number(), |
| Type::PlainNumber(), Type::String()}; |
| |
| for (size_t i = 0; i < arraysize(types); ++i) { |
| Node* p0 = R.Parameter(types[i], 0); |
| |
| for (size_t j = 0; j < arraysize(types); ++j) { |
| Node* p1 = R.Parameter(types[j], 1); |
| |
| for (int k = 0; k < R.kNumberOps; k += 2) { |
| Node* add = R.Binop(R.ops[k], p0, p1); |
| Node* r = R.reduce(add); |
| |
| R.CheckBinop(R.ops[k + 1], r); |
| Node* r0 = r->InputAt(0); |
| Node* r1 = r->InputAt(1); |
| |
| CheckToI32(p0, r0, R.signedness[k]); |
| CheckToI32(p1, r1, false); |
| } |
| } |
| } |
| } |
| |
| |
| // A helper class for testing lowering of bitwise operators. |
| class JSBitwiseTypedLoweringTester : public JSTypedLoweringTester { |
| public: |
| JSBitwiseTypedLoweringTester() : JSTypedLoweringTester() { |
| int i = 0; |
| FeedbackSource feedback_source = FeedbackSourceWithOneBinarySlot(this); |
| set(i++, javascript.BitwiseOr(feedback_source), true); |
| set(i++, simplified.NumberBitwiseOr(), true); |
| set(i++, javascript.BitwiseXor(feedback_source), true); |
| set(i++, simplified.NumberBitwiseXor(), true); |
| set(i++, javascript.BitwiseAnd(feedback_source), true); |
| set(i++, simplified.NumberBitwiseAnd(), true); |
| } |
| static const int kNumberOps = 6; |
| const Operator* ops[kNumberOps]; |
| bool signedness[kNumberOps]; |
| |
| private: |
| void set(int idx, const Operator* op, bool s) { |
| ops[idx] = op; |
| signedness[idx] = s; |
| } |
| }; |
| |
| |
| TEST(Int32BitwiseBinops) { |
| JSBitwiseTypedLoweringTester R; |
| |
| Type types[] = { |
| Type::SignedSmall(), Type::UnsignedSmall(), Type::Unsigned32(), |
| Type::Signed32(), Type::MinusZero(), Type::NaN(), |
| Type::OrderedNumber(), Type::PlainNumber(), Type::Undefined(), |
| Type::Null(), Type::Boolean(), Type::Number(), |
| Type::String()}; |
| |
| for (size_t i = 0; i < arraysize(types); ++i) { |
| Node* p0 = R.Parameter(types[i], 0); |
| |
| for (size_t j = 0; j < arraysize(types); ++j) { |
| Node* p1 = R.Parameter(types[j], 1); |
| |
| for (int k = 0; k < R.kNumberOps; k += 2) { |
| Node* add = R.Binop(R.ops[k], p0, p1); |
| Node* r = R.reduce(add); |
| |
| R.CheckBinop(R.ops[k + 1], r); |
| |
| CheckToI32(p0, r->InputAt(0), R.signedness[k]); |
| CheckToI32(p1, r->InputAt(1), R.signedness[k + 1]); |
| } |
| } |
| } |
| } |
| |
| |
| TEST(JSToNumber1) { |
| JSTypedLoweringTester R; |
| const Operator* ton = R.javascript.ToNumber(); |
| |
| for (size_t i = 0; i < arraysize(kNumberTypes); i++) { // ToNumber(number) |
| Node* r = R.ReduceUnop(ton, kNumberTypes[i]); |
| CHECK_EQ(IrOpcode::kParameter, r->opcode()); |
| } |
| |
| { // ToNumber(undefined) |
| Node* r = R.ReduceUnop(ton, Type::Undefined()); |
| R.CheckNaN(r); |
| } |
| |
| { // ToNumber(null) |
| Node* r = R.ReduceUnop(ton, Type::Null()); |
| R.CheckNumberConstant(0.0, r); |
| } |
| } |
| |
| |
| TEST(JSToNumber_replacement) { |
| JSTypedLoweringTester R; |
| |
| Type types[] = {Type::Null(), Type::Undefined(), Type::Number()}; |
| |
| for (size_t i = 0; i < arraysize(types); i++) { |
| Node* n = R.Parameter(types[i]); |
| Node* c = |
| R.graph.NewNode(R.javascript.ToNumber(), n, R.context(), |
| R.EmptyFrameState(R.context()), R.start(), R.start()); |
| Node* effect_use = R.UseForEffect(c); |
| Node* add = R.graph.NewNode(R.simplified.ReferenceEqual(), n, c); |
| |
| R.CheckEffectInput(c, effect_use); |
| Node* r = R.reduce(c); |
| |
| if (types[i].Is(Type::Number())) { |
| CHECK_EQ(n, r); |
| } else { |
| CHECK_EQ(IrOpcode::kNumberConstant, r->opcode()); |
| } |
| |
| CHECK_EQ(n, add->InputAt(0)); |
| CHECK_EQ(r, add->InputAt(1)); |
| R.CheckEffectInput(R.start(), effect_use); |
| } |
| } |
| |
| |
| TEST(JSToNumberOfConstant) { |
| JSTypedLoweringTester R; |
| |
| const Operator* ops[] = {R.common.NumberConstant(0), |
| R.common.NumberConstant(-1), |
| R.common.NumberConstant(0.1)}; |
| |
| for (size_t i = 0; i < arraysize(ops); i++) { |
| Node* n = R.graph.NewNode(ops[i]); |
| Node* convert = R.Unop(R.javascript.ToNumber(), n); |
| Node* r = R.reduce(convert); |
| // Note that either outcome below is correct. It only depends on whether |
| // the types of constants are eagerly computed or only computed by the |
| // typing pass. |
| if (NodeProperties::GetType(n).Is(Type::Number())) { |
| // If number constants are eagerly typed, then reduction should |
| // remove the ToNumber. |
| CHECK_EQ(n, r); |
| } else { |
| // Otherwise, type-based lowering should only look at the type, and |
| // *not* try to constant fold. |
| CHECK_EQ(convert, r); |
| } |
| } |
| } |
| |
| |
| TEST(JSToNumberOfNumberOrOtherPrimitive) { |
| JSTypedLoweringTester R; |
| Type others[] = {Type::Undefined(), Type::Null(), Type::Boolean(), |
| Type::String()}; |
| |
| for (size_t i = 0; i < arraysize(others); i++) { |
| Type t = Type::Union(Type::Number(), others[i], R.main_zone()); |
| Node* r = R.ReduceUnop(R.javascript.ToNumber(), t); |
| CHECK_EQ(IrOpcode::kPlainPrimitiveToNumber, r->opcode()); |
| } |
| } |
| |
| |
| TEST(JSToString1) { |
| JSTypedLoweringTester R; |
| |
| for (size_t i = 0; i < arraysize(kStringTypes); i++) { |
| Node* r = R.ReduceUnop(R.javascript.ToString(), kStringTypes[i]); |
| CHECK_EQ(IrOpcode::kParameter, r->opcode()); |
| } |
| |
| const Operator* op = R.javascript.ToString(); |
| |
| { // ToString(undefined) => "undefined" |
| Node* r = R.ReduceUnop(op, Type::Undefined()); |
| R.CheckHandle(R.isolate->factory()->undefined_string(), r); |
| } |
| |
| { // ToString(null) => "null" |
| Node* r = R.ReduceUnop(op, Type::Null()); |
| R.CheckHandle(R.isolate->factory()->null_string(), r); |
| } |
| |
| { // ToString(boolean) |
| Node* r = R.ReduceUnop(op, Type::Boolean()); |
| CHECK_EQ(IrOpcode::kSelect, r->opcode()); |
| } |
| |
| { // ToString(number) |
| Node* r = R.ReduceUnop(op, Type::Number()); |
| CHECK_EQ(IrOpcode::kNumberToString, r->opcode()); |
| } |
| |
| { // ToString(string) |
| Node* r = R.ReduceUnop(op, Type::String()); |
| CHECK_EQ(IrOpcode::kParameter, r->opcode()); // No-op |
| } |
| |
| { // ToString(object) |
| Node* r = R.ReduceUnop(op, Type::Object()); |
| CHECK_EQ(IrOpcode::kJSToString, r->opcode()); // No reduction. |
| } |
| } |
| |
| |
| TEST(JSToString_replacement) { |
| JSTypedLoweringTester R; |
| |
| Type types[] = {Type::Null(), Type::Undefined(), Type::String()}; |
| |
| for (size_t i = 0; i < arraysize(types); i++) { |
| Node* n = R.Parameter(types[i]); |
| Node* c = |
| R.graph.NewNode(R.javascript.ToString(), n, R.context(), |
| R.EmptyFrameState(R.context()), R.start(), R.start()); |
| Node* effect_use = R.UseForEffect(c); |
| Node* add = R.graph.NewNode(R.simplified.ReferenceEqual(), n, c); |
| |
| R.CheckEffectInput(c, effect_use); |
| Node* r = R.reduce(c); |
| |
| if (types[i].Is(Type::String())) { |
| CHECK_EQ(n, r); |
| } else { |
| CHECK_EQ(IrOpcode::kHeapConstant, r->opcode()); |
| } |
| |
| CHECK_EQ(n, add->InputAt(0)); |
| CHECK_EQ(r, add->InputAt(1)); |
| R.CheckEffectInput(R.start(), effect_use); |
| } |
| } |
| |
| TEST(StringComparison) { |
| JSTypedLoweringTester R; |
| FeedbackSource feedback_source = FeedbackSourceWithOneCompareSlot(&R); |
| |
| const Operator* ops[] = {R.javascript.LessThan(feedback_source), |
| R.simplified.StringLessThan(), |
| R.javascript.LessThanOrEqual(feedback_source), |
| R.simplified.StringLessThanOrEqual(), |
| R.javascript.GreaterThan(feedback_source), |
| R.simplified.StringLessThan(), |
| R.javascript.GreaterThanOrEqual(feedback_source), |
| R.simplified.StringLessThanOrEqual()}; |
| |
| for (size_t i = 0; i < arraysize(kStringTypes); i++) { |
| Node* p0 = R.Parameter(kStringTypes[i], 0); |
| for (size_t j = 0; j < arraysize(kStringTypes); j++) { |
| Node* p1 = R.Parameter(kStringTypes[j], 1); |
| |
| for (size_t k = 0; k < arraysize(ops); k += 2) { |
| Node* cmp = R.Binop(ops[k], p0, p1); |
| Node* r = R.reduce(cmp); |
| |
| R.CheckBinop(ops[k + 1], r); |
| if (k >= 4) { |
| // GreaterThan and GreaterThanOrEqual commute the inputs |
| // and use the LessThan and LessThanOrEqual operators. |
| CHECK_EQ(p1, r->InputAt(0)); |
| CHECK_EQ(p0, r->InputAt(1)); |
| } else { |
| CHECK_EQ(p0, r->InputAt(0)); |
| CHECK_EQ(p1, r->InputAt(1)); |
| } |
| } |
| } |
| } |
| } |
| |
| |
| static void CheckIsConvertedToNumber(Node* val, Node* converted) { |
| if (NodeProperties::GetType(val).Is(Type::Number())) { |
| CHECK_EQ(val, converted); |
| } else { |
| if (converted->opcode() == IrOpcode::kNumberConstant) return; |
| CHECK(IrOpcode::kJSToNumber == converted->opcode() || |
| IrOpcode::kJSToNumberConvertBigInt == converted->opcode()); |
| CHECK_EQ(val, converted->InputAt(0)); |
| } |
| } |
| |
| TEST(NumberComparison) { |
| JSTypedLoweringTester R; |
| FeedbackSource feedback_source = FeedbackSourceWithOneCompareSlot(&R); |
| |
| const Operator* ops[] = {R.javascript.LessThan(feedback_source), |
| R.simplified.NumberLessThan(), |
| R.javascript.LessThanOrEqual(feedback_source), |
| R.simplified.NumberLessThanOrEqual(), |
| R.javascript.GreaterThan(feedback_source), |
| R.simplified.NumberLessThan(), |
| R.javascript.GreaterThanOrEqual(feedback_source), |
| R.simplified.NumberLessThanOrEqual()}; |
| |
| Node* const p0 = R.Parameter(Type::Number(), 0); |
| Node* const p1 = R.Parameter(Type::Number(), 1); |
| |
| for (size_t k = 0; k < arraysize(ops); k += 2) { |
| Node* cmp = R.Binop(ops[k], p0, p1); |
| Node* r = R.reduce(cmp); |
| |
| R.CheckBinop(ops[k + 1], r); |
| if (k >= 4) { |
| // GreaterThan and GreaterThanOrEqual commute the inputs |
| // and use the LessThan and LessThanOrEqual operators. |
| CheckIsConvertedToNumber(p1, r->InputAt(0)); |
| CheckIsConvertedToNumber(p0, r->InputAt(1)); |
| } else { |
| CheckIsConvertedToNumber(p0, r->InputAt(0)); |
| CheckIsConvertedToNumber(p1, r->InputAt(1)); |
| } |
| } |
| } |
| |
| TEST(MixedComparison1) { |
| JSTypedLoweringTester R; |
| FeedbackSource feedback_source = FeedbackSourceWithOneCompareSlot(&R); |
| |
| Type types[] = {Type::Number(), Type::String(), |
| Type::Union(Type::Number(), Type::String(), R.main_zone())}; |
| |
| for (size_t i = 0; i < arraysize(types); i++) { |
| Node* p0 = R.Parameter(types[i], 0); |
| |
| for (size_t j = 0; j < arraysize(types); j++) { |
| Node* p1 = R.Parameter(types[j], 1); |
| { |
| const Operator* less_than = R.javascript.LessThan(feedback_source); |
| Node* cmp = R.Binop(less_than, p0, p1); |
| Node* r = R.reduce(cmp); |
| if (types[i].Is(Type::String()) && types[j].Is(Type::String())) { |
| R.CheckBinop(R.simplified.StringLessThan(), r); |
| } else if ((types[i].Is(Type::Number()) && |
| types[j].Is(Type::Number())) || |
| (!types[i].Maybe(Type::String()) || |
| !types[j].Maybe(Type::String()))) { |
| R.CheckBinop(R.simplified.NumberLessThan(), r); |
| } else { |
| // No reduction of mixed types. |
| CHECK_EQ(r->op(), less_than); |
| } |
| } |
| } |
| } |
| } |
| |
| TEST(RemoveToNumberEffects) { |
| JSTypedLoweringTester R; |
| |
| FeedbackSource feedback_source = FeedbackSourceWithOneBinarySlot(&R); |
| Node* feedback = R.UndefinedConstant(); |
| Node* effect_use = nullptr; |
| Node* zero = R.graph.NewNode(R.common.NumberConstant(0)); |
| for (int i = 0; i < 10; i++) { |
| Node* p0 = R.Parameter(Type::Number()); |
| Node* ton = R.Unop(R.javascript.ToNumber(), p0); |
| Node* frame_state = R.EmptyFrameState(R.context()); |
| effect_use = nullptr; |
| |
| switch (i) { |
| case 0: |
| CHECK_EQ(1, OperatorProperties::GetFrameStateInputCount( |
| R.javascript.ToNumber())); |
| effect_use = R.graph.NewNode(R.javascript.ToNumber(), p0, R.context(), |
| frame_state, ton, R.start()); |
| break; |
| case 1: |
| CHECK_EQ(1, OperatorProperties::GetFrameStateInputCount( |
| R.javascript.ToNumber())); |
| effect_use = R.graph.NewNode(R.javascript.ToNumber(), ton, R.context(), |
| frame_state, ton, R.start()); |
| break; |
| case 2: |
| effect_use = R.graph.NewNode(R.common.EffectPhi(1), ton, R.start()); |
| break; |
| case 3: |
| effect_use = |
| R.graph.NewNode(R.javascript.Add(feedback_source), ton, ton, |
| feedback, R.context(), frame_state, ton, R.start()); |
| break; |
| case 4: |
| effect_use = |
| R.graph.NewNode(R.javascript.Add(feedback_source), p0, p0, feedback, |
| R.context(), frame_state, ton, R.start()); |
| break; |
| case 5: |
| effect_use = |
| R.graph.NewNode(R.common.Return(), zero, p0, ton, R.start()); |
| break; |
| case 6: |
| effect_use = |
| R.graph.NewNode(R.common.Return(), zero, ton, ton, R.start()); |
| } |
| |
| R.CheckEffectInput(R.start(), ton); |
| if (effect_use != nullptr) R.CheckEffectInput(ton, effect_use); |
| |
| Node* r = R.reduce(ton); |
| CHECK_EQ(p0, r); |
| CHECK_NE(R.start(), r); |
| |
| if (effect_use != nullptr) { |
| R.CheckEffectInput(R.start(), effect_use); |
| // Check that value uses of ToNumber() do not go to start(). |
| for (int i = 0; i < effect_use->op()->ValueInputCount(); i++) { |
| CHECK_NE(R.start(), effect_use->InputAt(i)); |
| } |
| } |
| } |
| |
| CHECK(!effect_use); // should have done all cases above. |
| } |
| |
| |
| // Helper class for testing the reduction of a single binop. |
| class BinopEffectsTester { |
| public: |
| BinopEffectsTester(const Operator* op, Type t0, Type t1) |
| : R(0), |
| p0(R.Parameter(t0, 0)), |
| p1(R.Parameter(t1, 1)), |
| binop(R.Binop(op, p0, p1)), |
| effect_use(R.graph.NewNode(R.common.EffectPhi(1), binop, R.start())) { |
| // Effects should be ordered start -> binop -> effect_use |
| R.CheckEffectInput(R.start(), binop); |
| R.CheckEffectInput(binop, effect_use); |
| result = R.reduce(binop); |
| } |
| |
| JSTypedLoweringTester R; |
| Node* p0; |
| Node* p1; |
| Node* binop; |
| Node* effect_use; |
| Node* result; |
| |
| void CheckEffectsRemoved() { R.CheckEffectInput(R.start(), effect_use); } |
| |
| void CheckEffectOrdering(Node* n0) { |
| R.CheckEffectInput(R.start(), n0); |
| R.CheckEffectInput(n0, effect_use); |
| } |
| |
| void CheckEffectOrdering(Node* n0, Node* n1) { |
| R.CheckEffectInput(R.start(), n0); |
| R.CheckEffectInput(n0, n1); |
| R.CheckEffectInput(n1, effect_use); |
| } |
| |
| Node* CheckConvertedInput(IrOpcode::Value opcode, int which, bool effects) { |
| return CheckConverted(opcode, result->InputAt(which), effects); |
| } |
| |
| Node* CheckConverted(IrOpcode::Value opcode, Node* node, bool effects) { |
| CHECK_EQ(opcode, node->opcode()); |
| if (effects) { |
| CHECK_LT(0, node->op()->EffectInputCount()); |
| } else { |
| CHECK_EQ(0, node->op()->EffectInputCount()); |
| } |
| return node; |
| } |
| |
| Node* CheckNoOp(int which) { |
| CHECK_EQ(which == 0 ? p0 : p1, result->InputAt(which)); |
| return result->InputAt(which); |
| } |
| }; |
| |
| |
| // Helper function for strict and non-strict equality reductions. |
| void CheckEqualityReduction(JSTypedLoweringTester* R, bool strict, Node* l, |
| Node* r, IrOpcode::Value expected) { |
| FeedbackSource feedback_source = FeedbackSourceWithOneCompareSlot(R); |
| for (int j = 0; j < 2; j++) { |
| Node* p0 = j == 0 ? l : r; |
| Node* p1 = j == 1 ? l : r; |
| |
| { |
| const Operator* op = strict ? R->javascript.StrictEqual(feedback_source) |
| : R->javascript.Equal(feedback_source); |
| Node* eq = R->Binop(op, p0, p1); |
| Node* r = R->reduce(eq); |
| R->CheckBinop(expected, r); |
| } |
| } |
| } |
| |
| |
| TEST(EqualityForNumbers) { |
| JSTypedLoweringTester R; |
| |
| Type simple_number_types[] = {Type::UnsignedSmall(), Type::SignedSmall(), |
| Type::Signed32(), Type::Unsigned32(), |
| Type::Number()}; |
| |
| for (size_t i = 0; i < arraysize(simple_number_types); ++i) { |
| Node* p0 = R.Parameter(simple_number_types[i], 0); |
| |
| for (size_t j = 0; j < arraysize(simple_number_types); ++j) { |
| Node* p1 = R.Parameter(simple_number_types[j], 1); |
| |
| CheckEqualityReduction(&R, true, p0, p1, IrOpcode::kNumberEqual); |
| CheckEqualityReduction(&R, false, p0, p1, IrOpcode::kNumberEqual); |
| } |
| } |
| } |
| |
| |
| TEST(StrictEqualityForRefEqualTypes) { |
| JSTypedLoweringTester R; |
| |
| Type types[] = {Type::Undefined(), Type::Null(), Type::Boolean(), |
| Type::Object(), Type::Receiver()}; |
| |
| Node* p0 = R.Parameter(Type::Any()); |
| for (size_t i = 0; i < arraysize(types); i++) { |
| Node* p1 = R.Parameter(types[i]); |
| CheckEqualityReduction(&R, true, p0, p1, IrOpcode::kReferenceEqual); |
| } |
| } |
| |
| TEST(StrictEqualityForUnique) { |
| JSTypedLoweringTester R; |
| |
| Node* p0 = R.Parameter(Type::Unique()); |
| Node* p1 = R.Parameter(Type::Unique()); |
| CheckEqualityReduction(&R, true, p0, p1, IrOpcode::kReferenceEqual); |
| CheckEqualityReduction(&R, true, p1, p0, IrOpcode::kReferenceEqual); |
| } |
| |
| TEST(StringEquality) { |
| JSTypedLoweringTester R; |
| Node* p0 = R.Parameter(Type::String()); |
| Node* p1 = R.Parameter(Type::String()); |
| |
| CheckEqualityReduction(&R, true, p0, p1, IrOpcode::kStringEqual); |
| CheckEqualityReduction(&R, false, p0, p1, IrOpcode::kStringEqual); |
| } |
| |
| TEST(RemovePureNumberBinopEffects) { |
| JSTypedLoweringTester R; |
| FeedbackSource binary_source = FeedbackSourceWithOneBinarySlot(&R); |
| FeedbackSource compare_source = FeedbackSourceWithOneCompareSlot(&R); |
| |
| const Operator* ops[] = { |
| R.javascript.Equal(compare_source), |
| R.simplified.NumberEqual(), |
| R.javascript.Add(binary_source), |
| R.simplified.NumberAdd(), |
| R.javascript.Subtract(binary_source), |
| R.simplified.NumberSubtract(), |
| R.javascript.Multiply(binary_source), |
| R.simplified.NumberMultiply(), |
| R.javascript.Divide(binary_source), |
| R.simplified.NumberDivide(), |
| R.javascript.Modulus(binary_source), |
| R.simplified.NumberModulus(), |
| R.javascript.LessThan(compare_source), |
| R.simplified.NumberLessThan(), |
| R.javascript.LessThanOrEqual(compare_source), |
| R.simplified.NumberLessThanOrEqual(), |
| }; |
| |
| for (size_t j = 0; j < arraysize(ops); j += 2) { |
| BinopEffectsTester B(ops[j], Type::Number(), Type::Number()); |
| CHECK_EQ(ops[j + 1]->opcode(), B.result->op()->opcode()); |
| |
| B.R.CheckBinop(B.result->opcode(), B.result); |
| |
| B.CheckNoOp(0); |
| B.CheckNoOp(1); |
| |
| B.CheckEffectsRemoved(); |
| } |
| } |
| |
| TEST(Int32BinopEffects) { |
| JSBitwiseTypedLoweringTester R; |
| for (int j = 0; j < R.kNumberOps; j += 2) { |
| bool signed_left = R.signedness[j], signed_right = R.signedness[j + 1]; |
| BinopEffectsTester B(R.ops[j], I32Type(signed_left), I32Type(signed_right)); |
| CHECK_EQ(R.ops[j + 1]->opcode(), B.result->op()->opcode()); |
| |
| B.R.CheckBinop(B.result->opcode(), B.result); |
| |
| B.CheckNoOp(0); |
| B.CheckNoOp(1); |
| |
| B.CheckEffectsRemoved(); |
| } |
| |
| for (int j = 0; j < R.kNumberOps; j += 2) { |
| bool signed_left = R.signedness[j], signed_right = R.signedness[j + 1]; |
| BinopEffectsTester B(R.ops[j], Type::Number(), Type::Number()); |
| CHECK_EQ(R.ops[j + 1]->opcode(), B.result->op()->opcode()); |
| |
| B.R.CheckBinop(B.result->opcode(), B.result); |
| |
| B.CheckConvertedInput(NumberToI32(signed_left), 0, false); |
| B.CheckConvertedInput(NumberToI32(signed_right), 1, false); |
| |
| B.CheckEffectsRemoved(); |
| } |
| |
| for (int j = 0; j < R.kNumberOps; j += 2) { |
| bool signed_left = R.signedness[j]; |
| BinopEffectsTester B(R.ops[j], Type::Number(), Type::Boolean()); |
| |
| B.R.CheckBinop(B.result->opcode(), B.result); |
| |
| B.CheckConvertedInput(NumberToI32(signed_left), 0, false); |
| B.CheckConvertedInput(IrOpcode::kPlainPrimitiveToNumber, 1, false); |
| |
| B.CheckEffectsRemoved(); |
| } |
| |
| for (int j = 0; j < R.kNumberOps; j += 2) { |
| bool signed_right = R.signedness[j + 1]; |
| BinopEffectsTester B(R.ops[j], Type::Boolean(), Type::Number()); |
| |
| B.R.CheckBinop(B.result->opcode(), B.result); |
| |
| B.CheckConvertedInput(IrOpcode::kPlainPrimitiveToNumber, 0, false); |
| B.CheckConvertedInput(NumberToI32(signed_right), 1, false); |
| |
| B.CheckEffectsRemoved(); |
| } |
| |
| for (int j = 0; j < R.kNumberOps; j += 2) { |
| BinopEffectsTester B(R.ops[j], Type::Boolean(), Type::Boolean()); |
| |
| B.R.CheckBinop(B.result->opcode(), B.result); |
| |
| B.CheckConvertedInput(IrOpcode::kPlainPrimitiveToNumber, 0, false); |
| B.CheckConvertedInput(IrOpcode::kPlainPrimitiveToNumber, 1, false); |
| |
| B.CheckEffectsRemoved(); |
| } |
| } |
| |
| TEST(Int32AddNarrowing) { |
| { |
| JSBitwiseTypedLoweringTester R; |
| |
| for (int o = 0; o < R.kNumberOps; o += 2) { |
| for (size_t i = 0; i < arraysize(kInt32Types); i++) { |
| Node* n0 = R.Parameter(kInt32Types[i]); |
| for (size_t j = 0; j < arraysize(kInt32Types); j++) { |
| Node* n1 = R.Parameter(kInt32Types[j]); |
| Node* one = R.graph.NewNode(R.common.NumberConstant(1)); |
| |
| for (int l = 0; l < 2; l++) { |
| Node* add_node = R.Binop(R.simplified.NumberAdd(), n0, n1); |
| Node* or_node = |
| R.Binop(R.ops[o], l ? add_node : one, l ? one : add_node); |
| Node* r = R.reduce(or_node); |
| |
| CHECK_EQ(R.ops[o + 1]->opcode(), r->op()->opcode()); |
| CHECK_EQ(IrOpcode::kNumberAdd, add_node->opcode()); |
| } |
| } |
| } |
| } |
| } |
| { |
| JSBitwiseShiftTypedLoweringTester R; |
| |
| for (int o = 0; o < R.kNumberOps; o += 2) { |
| for (size_t i = 0; i < arraysize(kInt32Types); i++) { |
| Node* n0 = R.Parameter(kInt32Types[i]); |
| for (size_t j = 0; j < arraysize(kInt32Types); j++) { |
| Node* n1 = R.Parameter(kInt32Types[j]); |
| Node* one = R.graph.NewNode(R.common.NumberConstant(1)); |
| |
| for (int l = 0; l < 2; l++) { |
| Node* add_node = R.Binop(R.simplified.NumberAdd(), n0, n1); |
| Node* or_node = |
| R.Binop(R.ops[o], l ? add_node : one, l ? one : add_node); |
| Node* r = R.reduce(or_node); |
| |
| CHECK_EQ(R.ops[o + 1]->opcode(), r->op()->opcode()); |
| CHECK_EQ(IrOpcode::kNumberAdd, add_node->opcode()); |
| } |
| } |
| } |
| } |
| } |
| { |
| JSBitwiseTypedLoweringTester R; |
| |
| for (int o = 0; o < R.kNumberOps; o += 2) { |
| Node* n0 = R.Parameter(I32Type(R.signedness[o])); |
| Node* n1 = R.Parameter(I32Type(R.signedness[o + 1])); |
| Node* one = R.graph.NewNode(R.common.NumberConstant(1)); |
| |
| Node* add_node = R.Binop(R.simplified.NumberAdd(), n0, n1); |
| Node* or_node = R.Binop(R.ops[o], add_node, one); |
| Node* other_use = R.Binop(R.simplified.NumberAdd(), add_node, one); |
| Node* r = R.reduce(or_node); |
| CHECK_EQ(R.ops[o + 1]->opcode(), r->op()->opcode()); |
| CHECK_EQ(IrOpcode::kNumberAdd, add_node->opcode()); |
| // Conversion to int32 should be done. |
| CheckToI32(add_node, r->InputAt(0), R.signedness[o]); |
| CheckToI32(one, r->InputAt(1), R.signedness[o + 1]); |
| // The other use should also not be touched. |
| CHECK_EQ(add_node, other_use->InputAt(0)); |
| CHECK_EQ(one, other_use->InputAt(1)); |
| } |
| } |
| } |
| |
| TEST(Int32Comparisons) { |
| JSTypedLoweringTester R; |
| FeedbackSource feedback_source = FeedbackSourceWithOneCompareSlot(&R); |
| |
| struct Entry { |
| const Operator* js_op; |
| const Operator* num_op; |
| bool commute; |
| }; |
| |
| Entry ops[] = {{R.javascript.LessThan(feedback_source), |
| R.simplified.NumberLessThan(), false}, |
| {R.javascript.LessThanOrEqual(feedback_source), |
| R.simplified.NumberLessThanOrEqual(), false}, |
| {R.javascript.GreaterThan(feedback_source), |
| R.simplified.NumberLessThan(), true}, |
| {R.javascript.GreaterThanOrEqual(feedback_source), |
| R.simplified.NumberLessThanOrEqual(), true}}; |
| |
| for (size_t o = 0; o < arraysize(ops); o++) { |
| for (size_t i = 0; i < arraysize(kNumberTypes); i++) { |
| Type t0 = kNumberTypes[i]; |
| Node* p0 = R.Parameter(t0, 0); |
| |
| for (size_t j = 0; j < arraysize(kNumberTypes); j++) { |
| Type t1 = kNumberTypes[j]; |
| Node* p1 = R.Parameter(t1, 1); |
| |
| Node* cmp = R.Binop(ops[o].js_op, p0, p1); |
| Node* r = R.reduce(cmp); |
| |
| R.CheckBinop(ops[o].num_op, r); |
| if (ops[o].commute) { |
| CHECK_EQ(p1, r->InputAt(0)); |
| CHECK_EQ(p0, r->InputAt(1)); |
| } else { |
| CHECK_EQ(p0, r->InputAt(0)); |
| CHECK_EQ(p1, r->InputAt(1)); |
| } |
| } |
| } |
| } |
| } |
| |
| } // namespace compiler |
| } // namespace internal |
| } // namespace v8 |