// Copyright 2019 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/decompression-elimination.h" #include "src/compiler/node-properties.h" #include "src/compiler/simplified-operator.h" #include "test/unittests/compiler/graph-reducer-unittest.h" #include "test/unittests/compiler/graph-unittest.h" #include "test/unittests/compiler/node-test-utils.h" #include "testing/gmock-support.h" using testing::_; using testing::StrictMock; namespace v8 { namespace internal { namespace compiler { class DecompressionEliminationTest : public GraphTest { public: DecompressionEliminationTest() : GraphTest(), machine_(zone(), MachineType::PointerRepresentation(), MachineOperatorBuilder::kNoFlags), simplified_(zone()) {} ~DecompressionEliminationTest() override = default; protected: Reduction Reduce(StrictMock* editor, Node* node) { DecompressionElimination decompression_elimination(editor, graph(), machine(), common()); return decompression_elimination.Reduce(node); } Reduction Reduce(Node* node) { StrictMock editor; return Reduce(&editor, node); } Node* GetUniqueValueUse(Node* node) { Node* value_use = nullptr; for (Edge edge : node->use_edges()) { if (NodeProperties::IsValueEdge(edge)) { if (value_use) { return nullptr; } else { value_use = edge.from(); } } } // Return the value use of node after the reduction, if there is exactly one return value_use; } const Operator* DecompressionOpFromAccess(const ElementAccess access) { switch (access.machine_type.representation()) { case MachineRepresentation::kCompressed: return machine()->ChangeCompressedToTagged(); case MachineRepresentation::kCompressedSigned: return machine()->ChangeCompressedSignedToTaggedSigned(); case MachineRepresentation::kCompressedPointer: return machine()->ChangeCompressedPointerToTaggedPointer(); default: UNREACHABLE(); } } const Operator* CompressionOpFromAccess(const ElementAccess access) { switch (access.machine_type.representation()) { case MachineRepresentation::kCompressed: return machine()->ChangeTaggedToCompressed(); case MachineRepresentation::kCompressedSigned: return machine()->ChangeTaggedSignedToCompressedSigned(); case MachineRepresentation::kCompressedPointer: return machine()->ChangeTaggedPointerToCompressedPointer(); default: UNREACHABLE(); } } // 'Global' accesses used to simplify the tests. ElementAccess const any_access = {kTaggedBase, kTaggedSize, Type::Any(), MachineType::AnyCompressed(), kNoWriteBarrier}; ElementAccess const signed_access = {kTaggedBase, kTaggedSize, Type::Any(), MachineType::CompressedSigned(), kNoWriteBarrier}; ElementAccess const pointer_access = {kTaggedBase, kTaggedSize, Type::Any(), MachineType::CompressedPointer(), kNoWriteBarrier}; const ElementAccess element_accesses[3] = {any_access, signed_access, pointer_access}; MachineOperatorBuilder* machine() { return &machine_; } SimplifiedOperatorBuilder* simplified() { return &simplified_; } private: MachineOperatorBuilder machine_; SimplifiedOperatorBuilder simplified_; }; // ----------------------------------------------------------------------------- // Direct Decompression & Compression. TEST_F(DecompressionEliminationTest, BasicDecompressionCompression) { // Skip test if pointer compression is not enabled. if (!COMPRESS_POINTERS_BOOL) { return; } // Define variables. Node* const control = graph()->start(); Node* object = Parameter(Type::Any(), 0); Node* effect = graph()->start(); Node* index = Parameter(Type::UnsignedSmall(), 1); // Pairs of accesses const std::pair accesses[] = { {any_access, any_access}, {signed_access, any_access}, {pointer_access, any_access}, {any_access, signed_access}, {signed_access, signed_access}, {any_access, pointer_access}, {pointer_access, pointer_access}}; for (size_t i = 0; i < arraysize(accesses); ++i) { // Create the graph. Node* load = graph()->NewNode(simplified()->LoadElement(accesses[i].first), object, index, effect, control); Node* change_to_tagged = graph()->NewNode(DecompressionOpFromAccess(accesses[i].first), load); Node* change_to_compressed = graph()->NewNode( CompressionOpFromAccess(accesses[i].second), change_to_tagged); effect = graph()->NewNode(simplified()->StoreElement(accesses[i].second), object, index, change_to_compressed, effect, control); // Reduce. Reduction r = Reduce(change_to_compressed); ASSERT_TRUE(r.Changed()); EXPECT_EQ(load, r.replacement()); } } // ----------------------------------------------------------------------------- // Direct Compression & Decompression TEST_F(DecompressionEliminationTest, BasicCompressionDecompression) { // Skip test if pointer compression is not enabled. if (!COMPRESS_POINTERS_BOOL) { return; } // Define variables. Node* const control = graph()->start(); Node* object = Parameter(Type::Any(), 0); Node* effect = graph()->start(); Node* index = Parameter(Type::UnsignedSmall(), 1); // Pairs of accesses const std::pair accesses[] = { {any_access, any_access}, {signed_access, any_access}, {pointer_access, any_access}, {any_access, signed_access}, {signed_access, signed_access}, {any_access, pointer_access}, {pointer_access, pointer_access}}; for (size_t i = 0; i < arraysize(accesses); ++i) { // Create the graph. Node* load = graph()->NewNode(simplified()->LoadElement(accesses[i].first), object, index, effect, control); Node* change_to_compressed = graph()->NewNode(CompressionOpFromAccess(accesses[i].first), load); Node* change_to_tagged = graph()->NewNode( DecompressionOpFromAccess(accesses[i].second), change_to_compressed); effect = graph()->NewNode(simplified()->StoreElement(accesses[i].second), object, index, change_to_tagged, effect, control); // Reduce. Reduction r = Reduce(change_to_tagged); ASSERT_TRUE(r.Changed()); EXPECT_EQ(load, r.replacement()); } } // ----------------------------------------------------------------------------- // Compress after constant. TEST_F(DecompressionEliminationTest, CompressionAfterInt64Constant) { // Skip test if pointer compression is not enabled. if (!COMPRESS_POINTERS_BOOL) { return; } // Define variables. Node* const control = graph()->start(); Node* object = Parameter(Type::Any(), 0); Node* effect = graph()->start(); Node* index = Parameter(Type::UnsignedSmall(), 1); const int64_t constants[] = {static_cast(0x0000000000000000), static_cast(0x0000000000000001), static_cast(0x0000FFFFFFFF0000), static_cast(0x7FFFFFFFFFFFFFFF), static_cast(0x8000000000000000), static_cast(0x8000000000000001), static_cast(0x8000FFFFFFFF0000), static_cast(0x8FFFFFFFFFFFFFFF), static_cast(0xFFFFFFFFFFFFFFFF)}; // For every access. for (size_t i = 0; i < arraysize(element_accesses); ++i) { // For every Int64Constant. for (size_t j = 0; j < arraysize(constants); ++j) { // Create the graph. Node* constant = graph()->NewNode(common()->Int64Constant(constants[j])); Node* change_to_compressed = graph()->NewNode( CompressionOpFromAccess(element_accesses[i]), constant); effect = graph()->NewNode(simplified()->StoreElement(element_accesses[i]), object, index, change_to_compressed, effect, control); // Reduce. Reduction r = Reduce(change_to_compressed); ASSERT_TRUE(r.Changed()); EXPECT_EQ(r.replacement()->opcode(), IrOpcode::kInt32Constant); } } } TEST_F(DecompressionEliminationTest, CompressionAfterHeapConstant) { // Skip test if pointer compression is not enabled. if (!COMPRESS_POINTERS_BOOL) { return; } // Define variables. Node* const control = graph()->start(); Node* object = Parameter(Type::Any(), 0); Node* effect = graph()->start(); Node* index = Parameter(Type::UnsignedSmall(), 1); const Handle heap_constants[] = { factory()->NewHeapNumber(0.0), factory()->NewHeapNumber(-0.0), factory()->NewHeapNumber(11.2), factory()->NewHeapNumber(-11.2), factory()->NewHeapNumber(3.1415 + 1.4142), factory()->NewHeapNumber(3.1415 - 1.4142), factory()->NewHeapNumber(0x0000000000000000), factory()->NewHeapNumber(0x0000000000000001), factory()->NewHeapNumber(0x0000FFFFFFFF0000), factory()->NewHeapNumber(0x7FFFFFFFFFFFFFFF), factory()->NewHeapNumber(0x8000000000000000), factory()->NewHeapNumber(0x8000000000000001), factory()->NewHeapNumber(0x8000FFFFFFFF0000), factory()->NewHeapNumber(0x8FFFFFFFFFFFFFFF), factory()->NewHeapNumber(0xFFFFFFFFFFFFFFFF)}; // For every access. for (size_t i = 0; i < arraysize(element_accesses); ++i) { // For every HeapNumber. for (size_t j = 0; j < arraysize(heap_constants); ++j) { // Create the graph. Node* constant = graph()->NewNode(common()->HeapConstant(heap_constants[j])); Node* change_to_compressed = graph()->NewNode( CompressionOpFromAccess(element_accesses[i]), constant); effect = graph()->NewNode(simplified()->StoreElement(element_accesses[i]), object, index, change_to_compressed, effect, control); // Reduce. Reduction r = Reduce(change_to_compressed); ASSERT_TRUE(r.Changed()); EXPECT_EQ(r.replacement()->opcode(), IrOpcode::kCompressedHeapConstant); } } } // ----------------------------------------------------------------------------- // Phi. TEST_F(DecompressionEliminationTest, PhiOneDecompress) { // Skip test if pointer compression is not enabled. if (!COMPRESS_POINTERS_BOOL) { return; } // Define variables. Node* const control = graph()->start(); Node* object = Parameter(Type::Any(), 0); Node* effect = graph()->start(); Node* index = Parameter(Type::UnsignedSmall(), 1); const int number_of_inputs = 1; // For every access. for (size_t i = 0; i < arraysize(element_accesses); ++i) { // Create the graph. Node* load = graph()->NewNode(simplified()->LoadElement(element_accesses[i]), object, index, effect, control); Node* change_to_tagged = graph()->NewNode(DecompressionOpFromAccess(element_accesses[i]), load); Node* phi = graph()->NewNode( common()->Phi(MachineRepresentation::kTagged, number_of_inputs), change_to_tagged, control); // Reduce. StrictMock editor; EXPECT_CALL(editor, ReplaceWithValue(phi, _, _, _)); Reduction r = Reduce(&editor, phi); ASSERT_TRUE(r.Changed()); // Get the actual decompress after the Phi, and check against the expected // one. Node* decompress = GetUniqueValueUse(phi); EXPECT_EQ(DecompressionOpFromAccess(element_accesses[i]), decompress->op()); } } TEST_F(DecompressionEliminationTest, PhiThreeDecompressSameRepresentation) { // Skip test if pointer compression is not enabled. if (!COMPRESS_POINTERS_BOOL) { return; } // Define variables. Node* const control = graph()->start(); Node* object = Parameter(Type::Any(), 0); Node* effect = graph()->start(); Node* index = Parameter(Type::UnsignedSmall(), 1); const int number_of_inputs = 3; // For every access. for (size_t i = 0; i < arraysize(element_accesses); ++i) { // Create the graph. Node* load1 = graph()->NewNode(simplified()->LoadElement(element_accesses[i]), object, index, effect, control); Node* load2 = graph()->NewNode(simplified()->LoadElement(element_accesses[i]), object, index, effect, control); Node* load3 = graph()->NewNode(simplified()->LoadElement(element_accesses[i]), object, index, effect, control); Node* change_to_tagged_1 = graph()->NewNode(DecompressionOpFromAccess(element_accesses[i]), load1); Node* change_to_tagged_2 = graph()->NewNode(DecompressionOpFromAccess(element_accesses[i]), load2); Node* change_to_tagged_3 = graph()->NewNode(DecompressionOpFromAccess(element_accesses[i]), load3); Node* phi = graph()->NewNode( common()->Phi(MachineRepresentation::kTagged, number_of_inputs), change_to_tagged_1, change_to_tagged_2, change_to_tagged_3, control); // Reduce. StrictMock editor; EXPECT_CALL(editor, ReplaceWithValue(phi, _, _, _)); Reduction r = Reduce(&editor, phi); ASSERT_TRUE(r.Changed()); // Get the actual decompress after the Phi, and check against the expected // one. Node* decompress = GetUniqueValueUse(phi); EXPECT_EQ(DecompressionOpFromAccess(element_accesses[i]), decompress->op()); } } TEST_F(DecompressionEliminationTest, PhiThreeDecompressOneAnyRepresentation) { // Skip test if pointer compression is not enabled. if (!COMPRESS_POINTERS_BOOL) { return; } // Define variables. Node* const control = graph()->start(); Node* object = Parameter(Type::Any(), 0); Node* effect = graph()->start(); Node* index = Parameter(Type::UnsignedSmall(), 1); const int number_of_inputs = 3; // Signed and Pointer (and not Any) accesses. const ElementAccess not_any_accesses[] = {signed_access, pointer_access}; // For every access. for (size_t i = 0; i < arraysize(not_any_accesses); ++i) { // Create the graph. Node* load1 = graph()->NewNode(simplified()->LoadElement(not_any_accesses[i]), object, index, effect, control); Node* load2 = graph()->NewNode(simplified()->LoadElement(not_any_accesses[i]), object, index, effect, control); // Note that load3 loads a CompressedAny instead of not_any_accesses[i] Node* load3 = graph()->NewNode(simplified()->LoadElement(any_access), object, index, effect, control); Node* change_to_tagged_1 = graph()->NewNode(DecompressionOpFromAccess(not_any_accesses[i]), load1); Node* change_to_tagged_2 = graph()->NewNode(DecompressionOpFromAccess(not_any_accesses[i]), load2); Node* change_to_tagged_3 = graph()->NewNode(machine()->ChangeCompressedToTagged(), load3); Node* phi = graph()->NewNode( common()->Phi(MachineRepresentation::kTagged, number_of_inputs), change_to_tagged_1, change_to_tagged_2, change_to_tagged_3, control); // Reduce. StrictMock editor; EXPECT_CALL(editor, ReplaceWithValue(phi, _, _, _)); Reduction r = Reduce(&editor, phi); ASSERT_TRUE(r.Changed()); // Get the actual decompress after the Phi, and check against the expected // one. Node* decompress = GetUniqueValueUse(phi); EXPECT_EQ(machine()->ChangeCompressedToTagged(), decompress->op()); } } TEST_F(DecompressionEliminationTest, PhiThreeInputsOneNotDecompressed) { // Skip test if pointer compression is not enabled. if (!COMPRESS_POINTERS_BOOL) { return; } // Define variables. Node* const control = graph()->start(); Node* object = Parameter(Type::Any(), 0); Node* effect = graph()->start(); Node* index = Parameter(Type::UnsignedSmall(), 1); const int number_of_inputs = 3; // For every access. for (size_t i = 0; i < arraysize(element_accesses); ++i) { // Create the graph. Node* load1 = graph()->NewNode(simplified()->LoadElement(element_accesses[i]), object, index, effect, control); Node* load2 = graph()->NewNode(simplified()->LoadElement(element_accesses[i]), object, index, effect, control); Node* load3 = graph()->NewNode(simplified()->LoadElement(element_accesses[i]), object, index, effect, control); Node* change_to_tagged_1 = graph()->NewNode(DecompressionOpFromAccess(element_accesses[i]), load1); Node* change_to_tagged_2 = graph()->NewNode(DecompressionOpFromAccess(element_accesses[i]), load2); Node* phi = graph()->NewNode( common()->Phi(MachineRepresentation::kTagged, number_of_inputs), change_to_tagged_1, change_to_tagged_2, load3, control); // Reduce. Reduction r = Reduce(phi); ASSERT_FALSE(r.Changed()); } } // In the case of having one decompress Signed and one Pointer, we have to // generate the conservative decompress any after the Phi. TEST_F(DecompressionEliminationTest, PhiTwoDecompressesOneSignedOnePointer) { // Skip test if pointer compression is not enabled. if (!COMPRESS_POINTERS_BOOL) { return; } // Define variables. Node* const control = graph()->start(); Node* object = Parameter(Type::Any(), 0); Node* effect = graph()->start(); Node* index = Parameter(Type::UnsignedSmall(), 1); const int number_of_inputs = 2; // Create the graph. Node* load1 = graph()->NewNode(simplified()->LoadElement(signed_access), object, index, effect, control); Node* load2 = graph()->NewNode(simplified()->LoadElement(pointer_access), object, index, effect, control); Node* change_to_tagged_1 = graph()->NewNode(DecompressionOpFromAccess(signed_access), load1); Node* change_to_tagged_2 = graph()->NewNode(DecompressionOpFromAccess(pointer_access), load2); Node* phi = graph()->NewNode( common()->Phi(MachineRepresentation::kTagged, number_of_inputs), change_to_tagged_1, change_to_tagged_2, control); // Reduce. StrictMock editor; EXPECT_CALL(editor, ReplaceWithValue(phi, _, _, _)); Reduction r = Reduce(&editor, phi); ASSERT_TRUE(r.Changed()); // Get the actual decompress after the Phi, and check against the expected // one. Node* decompress = GetUniqueValueUse(phi); EXPECT_EQ(machine()->ChangeCompressedToTagged(), decompress->op()); } // ----------------------------------------------------------------------------- // TypedStateValues. TEST_F(DecompressionEliminationTest, TypedStateValuesOneDecompress) { // Skip test if pointer compression is not enabled. if (!COMPRESS_POINTERS_BOOL) { return; } // Define variables. Node* const control = graph()->start(); Node* object = Parameter(Type::Any(), 0); Node* effect = graph()->start(); Node* index = Parameter(Type::UnsignedSmall(), 1); const int number_of_inputs = 1; const ZoneVector* types = new (graph()->zone()->New(sizeof(ZoneVector))) ZoneVector(number_of_inputs, graph()->zone()); SparseInputMask dense = SparseInputMask::Dense(); // For every access. for (size_t i = 0; i < arraysize(element_accesses); ++i) { // Create the graph. Node* load = graph()->NewNode(simplified()->LoadElement(element_accesses[i]), object, index, effect, control); Node* change_to_tagged = graph()->NewNode(DecompressionOpFromAccess(element_accesses[i]), load); Node* typedStateValues = graph()->NewNode( common()->TypedStateValues(types, dense), change_to_tagged); // Reduce. StrictMock editor; DecompressionElimination decompression_elimination(&editor, graph(), machine(), common()); Reduction r = decompression_elimination.Reduce(typedStateValues); ASSERT_TRUE(r.Changed()); EXPECT_EQ(r.replacement()->InputAt(0), load); } } TEST_F(DecompressionEliminationTest, TypedStateValuesTwoDecompresses) { // Skip test if pointer compression is not enabled. if (!COMPRESS_POINTERS_BOOL) { return; } // Define variables. Node* const control = graph()->start(); Node* object = Parameter(Type::Any(), 0); Node* effect = graph()->start(); Node* index = Parameter(Type::UnsignedSmall(), 1); const int number_of_inputs = 3; const ZoneVector* types = new (graph()->zone()->New(sizeof(ZoneVector))) ZoneVector(number_of_inputs, graph()->zone()); SparseInputMask dense = SparseInputMask::Dense(); // For every access. for (size_t i = 0; i < arraysize(element_accesses); ++i) { // Create the graph. Node* load1 = graph()->NewNode(simplified()->LoadElement(element_accesses[i]), object, index, effect, control); Node* change_to_tagged_1 = graph()->NewNode(DecompressionOpFromAccess(element_accesses[i]), load1); Node* load2 = graph()->NewNode(simplified()->LoadElement(element_accesses[i]), object, index, effect, control); Node* change_to_tagged_2 = graph()->NewNode(DecompressionOpFromAccess(element_accesses[i]), load2); Node* typedStateValues = graph()->NewNode(common()->TypedStateValues(types, dense), change_to_tagged_1, load1, change_to_tagged_2); // Reduce. StrictMock editor; DecompressionElimination decompression_elimination(&editor, graph(), machine(), common()); Reduction r = decompression_elimination.Reduce(typedStateValues); ASSERT_TRUE(r.Changed()); EXPECT_EQ(r.replacement()->InputAt(0), load1); // Note that the input at index 1 didn't change. EXPECT_EQ(r.replacement()->InputAt(1), load1); EXPECT_EQ(r.replacement()->InputAt(2), load2); } } TEST_F(DecompressionEliminationTest, TypedStateValuesAllDecompresses) { // Skip test if pointer compression is not enabled. if (!COMPRESS_POINTERS_BOOL) { return; } // Define variables. Node* const control = graph()->start(); Node* object = Parameter(Type::Any(), 0); Node* effect = graph()->start(); Node* index = Parameter(Type::UnsignedSmall(), 1); const int number_of_inputs = 3; const ZoneVector* types = new (graph()->zone()->New(sizeof(ZoneVector))) ZoneVector(number_of_inputs, graph()->zone()); SparseInputMask dense = SparseInputMask::Dense(); // For every access. for (size_t i = 0; i < arraysize(element_accesses); ++i) { // Create the graph. Node* load1 = graph()->NewNode(simplified()->LoadElement(element_accesses[i]), object, index, effect, control); Node* change_to_tagged_1 = graph()->NewNode(DecompressionOpFromAccess(element_accesses[i]), load1); Node* load2 = graph()->NewNode(simplified()->LoadElement(element_accesses[i]), object, index, effect, control); Node* change_to_tagged_2 = graph()->NewNode(DecompressionOpFromAccess(element_accesses[i]), load2); Node* load3 = graph()->NewNode(simplified()->LoadElement(element_accesses[i]), object, index, effect, control); Node* change_to_tagged_3 = graph()->NewNode(DecompressionOpFromAccess(element_accesses[i]), load3); Node* typedStateValues = graph()->NewNode( common()->TypedStateValues(types, dense), change_to_tagged_1, change_to_tagged_2, change_to_tagged_3); // Reduce. StrictMock editor; DecompressionElimination decompression_elimination(&editor, graph(), machine(), common()); Reduction r = decompression_elimination.Reduce(typedStateValues); ASSERT_TRUE(r.Changed()); EXPECT_EQ(r.replacement()->InputAt(0), load1); EXPECT_EQ(r.replacement()->InputAt(1), load2); EXPECT_EQ(r.replacement()->InputAt(2), load3); } } TEST_F(DecompressionEliminationTest, TypedStateValuesNoDecompresses) { // Skip test if pointer compression is not enabled. if (!COMPRESS_POINTERS_BOOL) { return; } // Define variables. Node* const control = graph()->start(); Node* object = Parameter(Type::Any(), 0); Node* effect = graph()->start(); Node* index = Parameter(Type::UnsignedSmall(), 1); const int number_of_inputs = 3; const ZoneVector* types = new (graph()->zone()->New(sizeof(ZoneVector))) ZoneVector(number_of_inputs, graph()->zone()); SparseInputMask dense = SparseInputMask::Dense(); // For every access. for (size_t i = 0; i < arraysize(element_accesses); ++i) { // Create the graph. Node* load = graph()->NewNode(simplified()->LoadElement(element_accesses[i]), object, index, effect, control); Node* typedStateValues = graph()->NewNode( common()->TypedStateValues(types, dense), load, load, load); // Reduce. StrictMock editor; DecompressionElimination decompression_elimination(&editor, graph(), machine(), common()); Reduction r = decompression_elimination.Reduce(typedStateValues); ASSERT_FALSE(r.Changed()); } } // ----------------------------------------------------------------------------- // Word64Equal comparison of two decompressions. TEST_F(DecompressionEliminationTest, TwoDecompressionWord64Equal) { // Skip test if pointer compression is not enabled. if (!COMPRESS_POINTERS_BOOL) { return; } // Define variables. Node* const control = graph()->start(); Node* object = Parameter(Type::Any(), 0); Node* effect = graph()->start(); Node* index = Parameter(Type::UnsignedSmall(), 1); // For every decompression (lhs). for (size_t i = 0; i < arraysize(element_accesses); ++i) { // For every decompression (rhs) for (size_t j = 0; j < arraysize(element_accesses); ++j) { // Create the graph. Node* load1 = graph()->NewNode(simplified()->LoadElement(element_accesses[i]), object, index, effect, control); Node* change_to_tagged_1 = graph()->NewNode( DecompressionOpFromAccess(element_accesses[i]), load1); Node* load2 = graph()->NewNode(simplified()->LoadElement(element_accesses[j]), object, index, effect, control); Node* change_to_tagged_2 = graph()->NewNode( DecompressionOpFromAccess(element_accesses[i]), load2); Node* comparison = graph()->NewNode( machine()->Word64Equal(), change_to_tagged_1, change_to_tagged_2); // Reduce. Reduction r = Reduce(comparison); ASSERT_TRUE(r.Changed()); EXPECT_EQ(r.replacement()->opcode(), IrOpcode::kWord32Equal); } } } // ----------------------------------------------------------------------------- // Word64Equal comparison of two decompressions, where lhs == rhs. TEST_F(DecompressionEliminationTest, TwoDecompressionWord64EqualSameInput) { // Skip test if pointer compression is not enabled. if (!COMPRESS_POINTERS_BOOL) { return; } // Define variables. Node* const control = graph()->start(); Node* object = Parameter(Type::Any(), 0); Node* effect = graph()->start(); Node* index = Parameter(Type::UnsignedSmall(), 1); // For every access. (same for lhs and rhs) for (size_t i = 0; i < arraysize(element_accesses); ++i) { // Create the graph. Node* load = graph()->NewNode(simplified()->LoadElement(element_accesses[i]), object, index, effect, control); Node* change_to_tagged = graph()->NewNode(DecompressionOpFromAccess(element_accesses[i]), load); Node* comparison = graph()->NewNode(machine()->Word64Equal(), change_to_tagged, change_to_tagged); // Reduce. Reduction r = Reduce(comparison); ASSERT_TRUE(r.Changed()); EXPECT_EQ(r.replacement()->opcode(), IrOpcode::kWord32Equal); } } // ----------------------------------------------------------------------------- // Word64Equal comparison of decompress and a constant. TEST_F(DecompressionEliminationTest, DecompressionConstantWord64Equal) { // Skip test if pointer compression is not enabled. if (!COMPRESS_POINTERS_BOOL) { return; } // Define variables. Node* const control = graph()->start(); Node* object = Parameter(Type::Any(), 0); Node* effect = graph()->start(); Node* index = Parameter(Type::UnsignedSmall(), 1); const int64_t constants[] = {static_cast(0x0000000000000000), static_cast(0x0000000000000001), static_cast(0x0000FFFFFFFF0000), static_cast(0x7FFFFFFFFFFFFFFF), static_cast(0x8000000000000000), static_cast(0x8000000000000001), static_cast(0x8000FFFFFFFF0000), static_cast(0x8FFFFFFFFFFFFFFF), static_cast(0xFFFFFFFFFFFFFFFF)}; // For every decompression (lhs). for (size_t i = 0; i < arraysize(element_accesses); ++i) { // For every constant (rhs). for (size_t j = 0; j < arraysize(constants); ++j) { // Test with both (lhs, rhs) combinations. for (bool lhs_is_decompression : {false, true}) { // Create the graph. Node* load = graph()->NewNode(simplified()->LoadElement(element_accesses[i]), object, index, effect, control); Node* change_to_tagged = graph()->NewNode( DecompressionOpFromAccess(element_accesses[i]), load); Node* constant = graph()->NewNode(common()->Int64Constant(constants[j])); Node* lhs = lhs_is_decompression ? change_to_tagged : constant; Node* rhs = lhs_is_decompression ? constant : change_to_tagged; Node* comparison = graph()->NewNode(machine()->Word64Equal(), lhs, rhs); // Reduce. Reduction r = Reduce(comparison); ASSERT_TRUE(r.Changed()); EXPECT_EQ(r.replacement()->opcode(), IrOpcode::kWord32Equal); } } } } TEST_F(DecompressionEliminationTest, DecompressionHeapConstantWord64Equal) { // Skip test if pointer compression is not enabled. if (!COMPRESS_POINTERS_BOOL) { return; } // Define variables. Node* const control = graph()->start(); Node* object = Parameter(Type::Any(), 0); Node* effect = graph()->start(); Node* index = Parameter(Type::UnsignedSmall(), 1); const Handle heap_constants[] = { factory()->NewHeapNumber(0.0), factory()->NewHeapNumber(-0.0), factory()->NewHeapNumber(11.2), factory()->NewHeapNumber(-11.2), factory()->NewHeapNumber(3.1415 + 1.4142), factory()->NewHeapNumber(3.1415 - 1.4142), factory()->NewHeapNumber(0x0000000000000000), factory()->NewHeapNumber(0x0000000000000001), factory()->NewHeapNumber(0x0000FFFFFFFF0000), factory()->NewHeapNumber(0x7FFFFFFFFFFFFFFF), factory()->NewHeapNumber(0x8000000000000000), factory()->NewHeapNumber(0x8000000000000001), factory()->NewHeapNumber(0x8000FFFFFFFF0000), factory()->NewHeapNumber(0x8FFFFFFFFFFFFFFF), factory()->NewHeapNumber(0xFFFFFFFFFFFFFFFF)}; // For every decompression (lhs). for (size_t i = 0; i < arraysize(element_accesses); ++i) { // For every constant (rhs). for (size_t j = 0; j < arraysize(heap_constants); ++j) { // Test with both (lhs, rhs) combinations. for (bool lhs_is_decompression : {false, true}) { // Create the graph. Node* load = graph()->NewNode(simplified()->LoadElement(element_accesses[i]), object, index, effect, control); Node* change_to_tagged = graph()->NewNode( DecompressionOpFromAccess(element_accesses[i]), load); Node* constant = graph()->NewNode(common()->HeapConstant(heap_constants[j])); Node* lhs = lhs_is_decompression ? change_to_tagged : constant; Node* rhs = lhs_is_decompression ? constant : change_to_tagged; Node* comparison = graph()->NewNode(machine()->Word64Equal(), lhs, rhs); // Reduce. Reduction r = Reduce(comparison); ASSERT_TRUE(r.Changed()); EXPECT_EQ(r.replacement()->opcode(), IrOpcode::kWord32Equal); } } } } } // namespace compiler } // namespace internal } // namespace v8