// 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 "test/cctest/cctest.h" #include "src/base/utils/random-number-generator.h" #include "src/compiler/graph-inl.h" #include "src/compiler/js-graph.h" #include "src/compiler/machine-operator-reducer.h" #include "src/compiler/typer.h" #include "test/cctest/compiler/value-helper.h" using namespace v8::internal; using namespace v8::internal::compiler; template const Operator* NewConstantOperator(CommonOperatorBuilder* common, volatile T value); template <> const Operator* NewConstantOperator(CommonOperatorBuilder* common, volatile int32_t value) { return common->Int32Constant(value); } template <> const Operator* NewConstantOperator(CommonOperatorBuilder* common, volatile double value) { return common->Float64Constant(value); } template T ValueOfOperator(const Operator* op); template <> int32_t ValueOfOperator(const Operator* op) { CHECK_EQ(IrOpcode::kInt32Constant, op->opcode()); return OpParameter(op); } template <> double ValueOfOperator(const Operator* op) { CHECK_EQ(IrOpcode::kFloat64Constant, op->opcode()); return OpParameter(op); } class ReducerTester : public HandleAndZoneScope { public: explicit ReducerTester(int num_parameters = 0) : isolate(main_isolate()), binop(NULL), unop(NULL), common(main_zone()), graph(main_zone()), javascript(main_zone()), typer(main_zone()), jsgraph(&graph, &common, &javascript, &typer, &machine), maxuint32(Constant(kMaxUInt32)) { Node* s = graph.NewNode(common.Start(num_parameters)); graph.SetStart(s); } Isolate* isolate; const Operator* binop; const Operator* unop; MachineOperatorBuilder machine; CommonOperatorBuilder common; Graph graph; JSOperatorBuilder javascript; Typer typer; JSGraph jsgraph; Node* maxuint32; template Node* Constant(volatile T value) { return graph.NewNode(NewConstantOperator(&common, value)); } template const T ValueOf(const Operator* op) { return ValueOfOperator(op); } // Check that the reduction of this binop applied to constants {a} and {b} // yields the {expect} value. template void CheckFoldBinop(volatile T expect, volatile T a, volatile T b) { CheckFoldBinop(expect, Constant(a), Constant(b)); } // Check that the reduction of this binop applied to {a} and {b} yields // the {expect} value. template void CheckFoldBinop(volatile T expect, Node* a, Node* b) { CHECK_NE(NULL, binop); Node* n = graph.NewNode(binop, a, b); MachineOperatorReducer reducer(&jsgraph); Reduction reduction = reducer.Reduce(n); CHECK(reduction.Changed()); CHECK_NE(n, reduction.replacement()); CHECK_EQ(expect, ValueOf(reduction.replacement()->op())); } // Check that the reduction of this binop applied to {a} and {b} yields // the {expect} node. void CheckBinop(Node* expect, Node* a, Node* b) { CHECK_NE(NULL, binop); Node* n = graph.NewNode(binop, a, b); MachineOperatorReducer reducer(&jsgraph); Reduction reduction = reducer.Reduce(n); CHECK(reduction.Changed()); CHECK_EQ(expect, reduction.replacement()); } // Check that the reduction of this binop applied to {left} and {right} yields // this binop applied to {left_expect} and {right_expect}. void CheckFoldBinop(Node* left_expect, Node* right_expect, Node* left, Node* right) { CHECK_NE(NULL, binop); Node* n = graph.NewNode(binop, left, right); MachineOperatorReducer reducer(&jsgraph); Reduction reduction = reducer.Reduce(n); CHECK(reduction.Changed()); CHECK_EQ(binop, reduction.replacement()->op()); CHECK_EQ(left_expect, reduction.replacement()->InputAt(0)); CHECK_EQ(right_expect, reduction.replacement()->InputAt(1)); } // Check that the reduction of this binop applied to {left} and {right} yields // the {op_expect} applied to {left_expect} and {right_expect}. template void CheckFoldBinop(volatile T left_expect, const Operator* op_expect, Node* right_expect, Node* left, Node* right) { CHECK_NE(NULL, binop); Node* n = graph.NewNode(binop, left, right); MachineOperatorReducer reducer(&jsgraph); Reduction r = reducer.Reduce(n); CHECK(r.Changed()); CHECK_EQ(op_expect->opcode(), r.replacement()->op()->opcode()); CHECK_EQ(left_expect, ValueOf(r.replacement()->InputAt(0)->op())); CHECK_EQ(right_expect, r.replacement()->InputAt(1)); } // Check that the reduction of this binop applied to {left} and {right} yields // the {op_expect} applied to {left_expect} and {right_expect}. template void CheckFoldBinop(Node* left_expect, const Operator* op_expect, volatile T right_expect, Node* left, Node* right) { CHECK_NE(NULL, binop); Node* n = graph.NewNode(binop, left, right); MachineOperatorReducer reducer(&jsgraph); Reduction r = reducer.Reduce(n); CHECK(r.Changed()); CHECK_EQ(op_expect->opcode(), r.replacement()->op()->opcode()); CHECK_EQ(left_expect, r.replacement()->InputAt(0)); CHECK_EQ(right_expect, ValueOf(r.replacement()->InputAt(1)->op())); } // Check that if the given constant appears on the left, the reducer will // swap it to be on the right. template void CheckPutConstantOnRight(volatile T constant) { // TODO(titzer): CHECK(binop->HasProperty(Operator::kCommutative)); Node* p = Parameter(); Node* k = Constant(constant); { Node* n = graph.NewNode(binop, k, p); MachineOperatorReducer reducer(&jsgraph); Reduction reduction = reducer.Reduce(n); CHECK(!reduction.Changed() || reduction.replacement() == n); CHECK_EQ(p, n->InputAt(0)); CHECK_EQ(k, n->InputAt(1)); } { Node* n = graph.NewNode(binop, p, k); MachineOperatorReducer reducer(&jsgraph); Reduction reduction = reducer.Reduce(n); CHECK(!reduction.Changed()); CHECK_EQ(p, n->InputAt(0)); CHECK_EQ(k, n->InputAt(1)); } } // Check that if the given constant appears on the left, the reducer will // *NOT* swap it to be on the right. template void CheckDontPutConstantOnRight(volatile T constant) { CHECK(!binop->HasProperty(Operator::kCommutative)); Node* p = Parameter(); Node* k = Constant(constant); Node* n = graph.NewNode(binop, k, p); MachineOperatorReducer reducer(&jsgraph); Reduction reduction = reducer.Reduce(n); CHECK(!reduction.Changed()); CHECK_EQ(k, n->InputAt(0)); CHECK_EQ(p, n->InputAt(1)); } Node* Parameter(int32_t index = 0) { return graph.NewNode(common.Parameter(index), graph.start()); } }; TEST(ReduceWord32And) { ReducerTester R; R.binop = R.machine.Word32And(); FOR_INT32_INPUTS(pl) { FOR_INT32_INPUTS(pr) { int32_t x = *pl, y = *pr; R.CheckFoldBinop(x & y, x, y); } } R.CheckPutConstantOnRight(33); R.CheckPutConstantOnRight(44000); Node* x = R.Parameter(); Node* zero = R.Constant(0); Node* minus_1 = R.Constant(-1); R.CheckBinop(zero, x, zero); // x & 0 => 0 R.CheckBinop(zero, zero, x); // 0 & x => 0 R.CheckBinop(x, x, minus_1); // x & -1 => 0 R.CheckBinop(x, minus_1, x); // -1 & x => 0 R.CheckBinop(x, x, x); // x & x => x } TEST(ReduceWord32Or) { ReducerTester R; R.binop = R.machine.Word32Or(); FOR_INT32_INPUTS(pl) { FOR_INT32_INPUTS(pr) { int32_t x = *pl, y = *pr; R.CheckFoldBinop(x | y, x, y); } } R.CheckPutConstantOnRight(36); R.CheckPutConstantOnRight(44001); Node* x = R.Parameter(); Node* zero = R.Constant(0); Node* minus_1 = R.Constant(-1); R.CheckBinop(x, x, zero); // x & 0 => x R.CheckBinop(x, zero, x); // 0 & x => x R.CheckBinop(minus_1, x, minus_1); // x & -1 => -1 R.CheckBinop(minus_1, minus_1, x); // -1 & x => -1 R.CheckBinop(x, x, x); // x & x => x } TEST(ReduceWord32Xor) { ReducerTester R; R.binop = R.machine.Word32Xor(); FOR_INT32_INPUTS(pl) { FOR_INT32_INPUTS(pr) { int32_t x = *pl, y = *pr; R.CheckFoldBinop(x ^ y, x, y); } } R.CheckPutConstantOnRight(39); R.CheckPutConstantOnRight(4403); Node* x = R.Parameter(); Node* zero = R.Constant(0); R.CheckBinop(x, x, zero); // x ^ 0 => x R.CheckBinop(x, zero, x); // 0 ^ x => x R.CheckFoldBinop(0, x, x); // x ^ x => 0 } TEST(ReduceWord32Shl) { ReducerTester R; R.binop = R.machine.Word32Shl(); // TODO(titzer): out of range shifts FOR_INT32_INPUTS(i) { for (int y = 0; y < 32; y++) { int32_t x = *i; R.CheckFoldBinop(x << y, x, y); } } R.CheckDontPutConstantOnRight(44); Node* x = R.Parameter(); Node* zero = R.Constant(0); R.CheckBinop(x, x, zero); // x << 0 => x } TEST(ReduceWord32Shr) { ReducerTester R; R.binop = R.machine.Word32Shr(); // TODO(titzer): test out of range shifts FOR_UINT32_INPUTS(i) { for (uint32_t y = 0; y < 32; y++) { uint32_t x = *i; R.CheckFoldBinop(x >> y, x, y); } } R.CheckDontPutConstantOnRight(44); Node* x = R.Parameter(); Node* zero = R.Constant(0); R.CheckBinop(x, x, zero); // x >>> 0 => x } TEST(ReduceWord32Sar) { ReducerTester R; R.binop = R.machine.Word32Sar(); // TODO(titzer): test out of range shifts FOR_INT32_INPUTS(i) { for (int32_t y = 0; y < 32; y++) { int32_t x = *i; R.CheckFoldBinop(x >> y, x, y); } } R.CheckDontPutConstantOnRight(44); Node* x = R.Parameter(); Node* zero = R.Constant(0); R.CheckBinop(x, x, zero); // x >> 0 => x } TEST(ReduceWord32Equal) { ReducerTester R; R.binop = R.machine.Word32Equal(); FOR_INT32_INPUTS(pl) { FOR_INT32_INPUTS(pr) { int32_t x = *pl, y = *pr; R.CheckFoldBinop(x == y ? 1 : 0, x, y); } } R.CheckPutConstantOnRight(48); R.CheckPutConstantOnRight(-48); Node* x = R.Parameter(0); Node* y = R.Parameter(1); Node* zero = R.Constant(0); Node* sub = R.graph.NewNode(R.machine.Int32Sub(), x, y); R.CheckFoldBinop(1, x, x); // x == x => 1 R.CheckFoldBinop(x, y, sub, zero); // x - y == 0 => x == y R.CheckFoldBinop(x, y, zero, sub); // 0 == x - y => x == y } TEST(ReduceInt32Add) { ReducerTester R; R.binop = R.machine.Int32Add(); FOR_INT32_INPUTS(pl) { FOR_INT32_INPUTS(pr) { int32_t x = *pl, y = *pr; R.CheckFoldBinop(x + y, x, y); // TODO(titzer): signed overflow } } R.CheckPutConstantOnRight(41); R.CheckPutConstantOnRight(4407); Node* x = R.Parameter(); Node* zero = R.Constant(0); R.CheckBinop(x, x, zero); // x + 0 => x R.CheckBinop(x, zero, x); // 0 + x => x } TEST(ReduceInt32Sub) { ReducerTester R; R.binop = R.machine.Int32Sub(); FOR_INT32_INPUTS(pl) { FOR_INT32_INPUTS(pr) { int32_t x = *pl, y = *pr; R.CheckFoldBinop(x - y, x, y); } } R.CheckDontPutConstantOnRight(412); Node* x = R.Parameter(); Node* zero = R.Constant(0); R.CheckBinop(x, x, zero); // x - 0 => x } TEST(ReduceInt32Mul) { ReducerTester R; R.binop = R.machine.Int32Mul(); FOR_INT32_INPUTS(pl) { FOR_INT32_INPUTS(pr) { int32_t x = *pl, y = *pr; R.CheckFoldBinop(x * y, x, y); // TODO(titzer): signed overflow } } R.CheckPutConstantOnRight(4111); R.CheckPutConstantOnRight(-4407); Node* x = R.Parameter(); Node* zero = R.Constant(0); Node* one = R.Constant(1); Node* minus_one = R.Constant(-1); R.CheckBinop(zero, x, zero); // x * 0 => 0 R.CheckBinop(zero, zero, x); // 0 * x => 0 R.CheckBinop(x, x, one); // x * 1 => x R.CheckBinop(x, one, x); // 1 * x => x R.CheckFoldBinop(0, R.machine.Int32Sub(), x, minus_one, x); // -1 * x => 0 - x R.CheckFoldBinop(0, R.machine.Int32Sub(), x, x, minus_one); // x * -1 => 0 - x for (int32_t n = 1; n < 31; ++n) { Node* multiplier = R.Constant(1 << n); R.CheckFoldBinop(x, R.machine.Word32Shl(), n, x, multiplier); // x * 2^n => x << n R.CheckFoldBinop(x, R.machine.Word32Shl(), n, multiplier, x); // 2^n * x => x << n } } TEST(ReduceInt32Div) { ReducerTester R; R.binop = R.machine.Int32Div(); FOR_INT32_INPUTS(pl) { FOR_INT32_INPUTS(pr) { int32_t x = *pl, y = *pr; if (y == 0) continue; // TODO(titzer): test / 0 int32_t r = y == -1 ? -x : x / y; // INT_MIN / -1 may explode in C R.CheckFoldBinop(r, x, y); } } R.CheckDontPutConstantOnRight(41111); R.CheckDontPutConstantOnRight(-44071); Node* x = R.Parameter(); Node* one = R.Constant(1); Node* minus_one = R.Constant(-1); R.CheckBinop(x, x, one); // x / 1 => x // TODO(titzer): // 0 / x => 0 if x != 0 // TODO(titzer): // x / 2^n => x >> n and round R.CheckFoldBinop(0, R.machine.Int32Sub(), x, x, minus_one); // x / -1 => 0 - x } TEST(ReduceInt32UDiv) { ReducerTester R; R.binop = R.machine.Int32UDiv(); FOR_UINT32_INPUTS(pl) { FOR_UINT32_INPUTS(pr) { uint32_t x = *pl, y = *pr; if (y == 0) continue; // TODO(titzer): test / 0 R.CheckFoldBinop(x / y, x, y); } } R.CheckDontPutConstantOnRight(41311); R.CheckDontPutConstantOnRight(-44371); Node* x = R.Parameter(); Node* one = R.Constant(1); R.CheckBinop(x, x, one); // x / 1 => x // TODO(titzer): // 0 / x => 0 if x != 0 for (uint32_t n = 1; n < 32; ++n) { Node* divisor = R.Constant(1u << n); R.CheckFoldBinop(x, R.machine.Word32Shr(), n, x, divisor); // x / 2^n => x >> n } } TEST(ReduceInt32Mod) { ReducerTester R; R.binop = R.machine.Int32Mod(); FOR_INT32_INPUTS(pl) { FOR_INT32_INPUTS(pr) { int32_t x = *pl, y = *pr; if (y == 0) continue; // TODO(titzer): test % 0 int32_t r = y == -1 ? 0 : x % y; // INT_MIN % -1 may explode in C R.CheckFoldBinop(r, x, y); } } R.CheckDontPutConstantOnRight(413); R.CheckDontPutConstantOnRight(-4401); Node* x = R.Parameter(); Node* one = R.Constant(1); R.CheckFoldBinop(0, x, one); // x % 1 => 0 // TODO(titzer): // x % 2^n => x & 2^n-1 and round } TEST(ReduceInt32UMod) { ReducerTester R; R.binop = R.machine.Int32UMod(); FOR_INT32_INPUTS(pl) { FOR_INT32_INPUTS(pr) { uint32_t x = *pl, y = *pr; if (y == 0) continue; // TODO(titzer): test x % 0 R.CheckFoldBinop(x % y, x, y); } } R.CheckDontPutConstantOnRight(417); R.CheckDontPutConstantOnRight(-4371); Node* x = R.Parameter(); Node* one = R.Constant(1); R.CheckFoldBinop(0, x, one); // x % 1 => 0 for (uint32_t n = 1; n < 32; ++n) { Node* divisor = R.Constant(1u << n); R.CheckFoldBinop(x, R.machine.Word32And(), (1u << n) - 1, x, divisor); // x % 2^n => x & 2^n-1 } } TEST(ReduceInt32LessThan) { ReducerTester R; R.binop = R.machine.Int32LessThan(); FOR_INT32_INPUTS(pl) { FOR_INT32_INPUTS(pr) { int32_t x = *pl, y = *pr; R.CheckFoldBinop(x < y ? 1 : 0, x, y); } } R.CheckDontPutConstantOnRight(41399); R.CheckDontPutConstantOnRight(-440197); Node* x = R.Parameter(0); Node* y = R.Parameter(1); Node* zero = R.Constant(0); Node* sub = R.graph.NewNode(R.machine.Int32Sub(), x, y); R.CheckFoldBinop(0, x, x); // x < x => 0 R.CheckFoldBinop(x, y, sub, zero); // x - y < 0 => x < y R.CheckFoldBinop(y, x, zero, sub); // 0 < x - y => y < x } TEST(ReduceInt32LessThanOrEqual) { ReducerTester R; R.binop = R.machine.Int32LessThanOrEqual(); FOR_INT32_INPUTS(pl) { FOR_INT32_INPUTS(pr) { int32_t x = *pl, y = *pr; R.CheckFoldBinop(x <= y ? 1 : 0, x, y); } } FOR_INT32_INPUTS(i) { R.CheckDontPutConstantOnRight(*i); } Node* x = R.Parameter(0); Node* y = R.Parameter(1); Node* zero = R.Constant(0); Node* sub = R.graph.NewNode(R.machine.Int32Sub(), x, y); R.CheckFoldBinop(1, x, x); // x <= x => 1 R.CheckFoldBinop(x, y, sub, zero); // x - y <= 0 => x <= y R.CheckFoldBinop(y, x, zero, sub); // 0 <= x - y => y <= x } TEST(ReduceUint32LessThan) { ReducerTester R; R.binop = R.machine.Uint32LessThan(); FOR_UINT32_INPUTS(pl) { FOR_UINT32_INPUTS(pr) { uint32_t x = *pl, y = *pr; R.CheckFoldBinop(x < y ? 1 : 0, x, y); } } R.CheckDontPutConstantOnRight(41399); R.CheckDontPutConstantOnRight(-440197); Node* x = R.Parameter(); Node* max = R.maxuint32; Node* zero = R.Constant(0); R.CheckFoldBinop(0, max, x); // M < x => 0 R.CheckFoldBinop(0, x, zero); // x < 0 => 0 R.CheckFoldBinop(0, x, x); // x < x => 0 } TEST(ReduceUint32LessThanOrEqual) { ReducerTester R; R.binop = R.machine.Uint32LessThanOrEqual(); FOR_UINT32_INPUTS(pl) { FOR_UINT32_INPUTS(pr) { uint32_t x = *pl, y = *pr; R.CheckFoldBinop(x <= y ? 1 : 0, x, y); } } R.CheckDontPutConstantOnRight(41399); R.CheckDontPutConstantOnRight(-440197); Node* x = R.Parameter(); Node* max = R.maxuint32; Node* zero = R.Constant(0); R.CheckFoldBinop(1, x, max); // x <= M => 1 R.CheckFoldBinop(1, zero, x); // 0 <= x => 1 R.CheckFoldBinop(1, x, x); // x <= x => 1 } TEST(ReduceLoadStore) { ReducerTester R; Node* base = R.Constant(11); Node* index = R.Constant(4); Node* load = R.graph.NewNode(R.machine.Load(kMachInt32), base, index); { MachineOperatorReducer reducer(&R.jsgraph); Reduction reduction = reducer.Reduce(load); CHECK(!reduction.Changed()); // loads should not be reduced. } { Node* store = R.graph.NewNode( R.machine.Store(StoreRepresentation(kMachInt32, kNoWriteBarrier)), base, index, load); MachineOperatorReducer reducer(&R.jsgraph); Reduction reduction = reducer.Reduce(store); CHECK(!reduction.Changed()); // stores should not be reduced. } } static void CheckNans(ReducerTester* R) { Node* x = R->Parameter(); std::vector nans = ValueHelper::nan_vector(); for (std::vector::const_iterator pl = nans.begin(); pl != nans.end(); ++pl) { for (std::vector::const_iterator pr = nans.begin(); pr != nans.end(); ++pr) { Node* nan1 = R->Constant(*pl); Node* nan2 = R->Constant(*pr); R->CheckBinop(nan1, x, nan1); // x % NaN => NaN R->CheckBinop(nan1, nan1, x); // NaN % x => NaN R->CheckBinop(nan1, nan2, nan1); // NaN % NaN => NaN } } } TEST(ReduceFloat64Add) { ReducerTester R; R.binop = R.machine.Float64Add(); FOR_FLOAT64_INPUTS(pl) { FOR_FLOAT64_INPUTS(pr) { double x = *pl, y = *pr; R.CheckFoldBinop(x + y, x, y); } } FOR_FLOAT64_INPUTS(i) { R.CheckPutConstantOnRight(*i); } // TODO(titzer): CheckNans(&R); } TEST(ReduceFloat64Sub) { ReducerTester R; R.binop = R.machine.Float64Sub(); FOR_FLOAT64_INPUTS(pl) { FOR_FLOAT64_INPUTS(pr) { double x = *pl, y = *pr; R.CheckFoldBinop(x - y, x, y); } } // TODO(titzer): CheckNans(&R); } TEST(ReduceFloat64Mul) { ReducerTester R; R.binop = R.machine.Float64Mul(); FOR_FLOAT64_INPUTS(pl) { FOR_FLOAT64_INPUTS(pr) { double x = *pl, y = *pr; R.CheckFoldBinop(x * y, x, y); } } double inf = V8_INFINITY; R.CheckPutConstantOnRight(-inf); R.CheckPutConstantOnRight(-0.1); R.CheckPutConstantOnRight(0.1); R.CheckPutConstantOnRight(inf); Node* x = R.Parameter(); Node* one = R.Constant(1.0); R.CheckBinop(x, x, one); // x * 1.0 => x R.CheckBinop(x, one, x); // 1.0 * x => x CheckNans(&R); } TEST(ReduceFloat64Div) { ReducerTester R; R.binop = R.machine.Float64Div(); FOR_FLOAT64_INPUTS(pl) { FOR_FLOAT64_INPUTS(pr) { double x = *pl, y = *pr; R.CheckFoldBinop(x / y, x, y); } } Node* x = R.Parameter(); Node* one = R.Constant(1.0); R.CheckBinop(x, x, one); // x / 1.0 => x CheckNans(&R); } TEST(ReduceFloat64Mod) { ReducerTester R; R.binop = R.machine.Float64Mod(); FOR_FLOAT64_INPUTS(pl) { FOR_FLOAT64_INPUTS(pr) { double x = *pl, y = *pr; R.CheckFoldBinop(modulo(x, y), x, y); } } CheckNans(&R); } // TODO(titzer): test MachineOperatorReducer for Word64And // TODO(titzer): test MachineOperatorReducer for Word64Or // TODO(titzer): test MachineOperatorReducer for Word64Xor // TODO(titzer): test MachineOperatorReducer for Word64Shl // TODO(titzer): test MachineOperatorReducer for Word64Shr // TODO(titzer): test MachineOperatorReducer for Word64Sar // TODO(titzer): test MachineOperatorReducer for Word64Equal // TODO(titzer): test MachineOperatorReducer for Word64Not // TODO(titzer): test MachineOperatorReducer for Int64Add // TODO(titzer): test MachineOperatorReducer for Int64Sub // TODO(titzer): test MachineOperatorReducer for Int64Mul // TODO(titzer): test MachineOperatorReducer for Int64UMul // TODO(titzer): test MachineOperatorReducer for Int64Div // TODO(titzer): test MachineOperatorReducer for Int64UDiv // TODO(titzer): test MachineOperatorReducer for Int64Mod // TODO(titzer): test MachineOperatorReducer for Int64UMod // TODO(titzer): test MachineOperatorReducer for Int64Neg // TODO(titzer): test MachineOperatorReducer for ChangeInt32ToFloat64 // TODO(titzer): test MachineOperatorReducer for ChangeFloat64ToInt32 // TODO(titzer): test MachineOperatorReducer for Float64Compare