[ptr-compr][turbofan] Adding Phi reductions to DecompressionElimination

This reduction replaces the Phi's input decompressions with their parent
node, if and only if all of the Phi's inputs are Decompress nodes.

Also, if we have different Decompress nodes as inputs, we need to use
a conservative decompression after the Phi.

Cq-Include-Trybots: luci.v8.try:v8_linux64_pointer_compression_rel_ng
Cq-Include-Trybots: luci.v8.try:v8_linux64_arm64_pointer_compression_rel_ng
Bug: v8:8977, v8:7703
Change-Id: I8cc0264f9d08fe5ad25364f18c9f305afc54529c
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1624785
Reviewed-by: Jaroslav Sevcik <jarin@chromium.org>
Commit-Queue: Santiago Aboy Solanes <solanes@chromium.org>
Cr-Commit-Position: refs/heads/master@{#61820}
diff --git a/src/compiler/decompression-elimination.cc b/src/compiler/decompression-elimination.cc
index e9aeea5..f7d616a 100644
--- a/src/compiler/decompression-elimination.cc
+++ b/src/compiler/decompression-elimination.cc
@@ -79,6 +79,63 @@
   }
 }
 
+Reduction DecompressionElimination::ReducePhi(Node* node) {
+  DCHECK_EQ(node->opcode(), IrOpcode::kPhi);
+
+  const int value_input_count = node->op()->ValueInputCount();
+
+  // Check if all input are decompress nodes, and if all are the same.
+  bool same_decompresses = true;
+  IrOpcode::Value first_opcode = node->InputAt(0)->opcode();
+  for (int i = 0; i < value_input_count; ++i) {
+    Node* input = node->InputAt(i);
+    if (IrOpcode::IsDecompressOpcode(input->opcode())) {
+      same_decompresses &= first_opcode == input->opcode();
+    } else {
+      return NoChange();
+    }
+  }
+
+  // By now, we know that all inputs are decompress nodes. If all are the same,
+  // we can grab the first one to be used after the Phi. If we have different
+  // Decompress nodes as inputs, we need to use a conservative decompression
+  // after the Phi.
+  const Operator* op;
+  if (same_decompresses) {
+    op = node->InputAt(0)->op();
+  } else {
+    op = machine()->ChangeCompressedToTagged();
+  }
+
+  // Rewire phi's inputs to be the compressed inputs.
+  for (int i = 0; i < value_input_count; ++i) {
+    Node* input = node->InputAt(i);
+    DCHECK_EQ(input->InputCount(), 1);
+    node->ReplaceInput(i, input->InputAt(0));
+  }
+
+  // Update the MachineRepresentation on the Phi.
+  MachineRepresentation rep;
+  switch (op->opcode()) {
+    case IrOpcode::kChangeCompressedToTagged:
+      rep = MachineRepresentation::kCompressed;
+      break;
+    case IrOpcode::kChangeCompressedSignedToTaggedSigned:
+      rep = MachineRepresentation::kCompressedSigned;
+      break;
+    case IrOpcode::kChangeCompressedPointerToTaggedPointer:
+      rep = MachineRepresentation::kCompressedPointer;
+      break;
+    default:
+      UNREACHABLE();
+  }
+  NodeProperties::ChangeOp(node, common()->Phi(rep, value_input_count));
+
+  // Add a decompress after the Phi. To do this, we need to replace the Phi with
+  // "Phi <- Decompress".
+  return Replace(graph()->NewNode(op, node));
+}
+
 Reduction DecompressionElimination::ReduceTypedStateValues(Node* node) {
   DCHECK_EQ(node->opcode(), IrOpcode::kTypedStateValues);
 
@@ -139,6 +196,8 @@
     case IrOpcode::kChangeTaggedSignedToCompressedSigned:
     case IrOpcode::kChangeTaggedPointerToCompressedPointer:
       return ReduceCompress(node);
+    case IrOpcode::kPhi:
+      return ReducePhi(node);
     case IrOpcode::kTypedStateValues:
       return ReduceTypedStateValues(node);
     case IrOpcode::kWord64Equal:
diff --git a/src/compiler/decompression-elimination.h b/src/compiler/decompression-elimination.h
index e223815..23773b8 100644
--- a/src/compiler/decompression-elimination.h
+++ b/src/compiler/decompression-elimination.h
@@ -48,7 +48,11 @@
   // Can be used for Any, Signed, and Pointer compressions.
   Reduction ReduceCompress(Node* node);
 
-  // Replaces TypedStateValues's input decompressions with their parent node.
+  // Replaces Phi's input decompressions with their input node, if and only if
+  // all of the Phi's inputs are Decompress nodes.
+  Reduction ReducePhi(Node* node);
+
+  // Replaces TypedStateValues's input decompressions with their input node.
   Reduction ReduceTypedStateValues(Node* node);
 
   // Replaces a Word64Equal with a Word32Equal if both of its inputs are
diff --git a/test/unittests/compiler/decompression-elimination-unittest.cc b/test/unittests/compiler/decompression-elimination-unittest.cc
index bd2029e..0ccf9af 100644
--- a/test/unittests/compiler/decompression-elimination-unittest.cc
+++ b/test/unittests/compiler/decompression-elimination-unittest.cc
@@ -251,6 +251,279 @@
 }
 
 // -----------------------------------------------------------------------------
+// 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;
+
+  const Operator* decompression_ops[] = {
+      machine()->ChangeCompressedToTagged(),
+      machine()->ChangeCompressedSignedToTaggedSigned(),
+      machine()->ChangeCompressedPointerToTaggedPointer()};
+
+  const ElementAccess element_accesses[] = {
+      {kTaggedBase, kTaggedSize, Type::Any(), MachineType::AnyCompressed(),
+       kNoWriteBarrier},
+      {kTaggedBase, kTaggedSize, Type::Any(), MachineType::TaggedSigned(),
+       kNoWriteBarrier},
+      {kTaggedBase, kTaggedSize, Type::Any(), MachineType::TaggedPointer(),
+       kNoWriteBarrier}};
+
+  const IrOpcode::Value opcodes[] = {
+      IrOpcode::kChangeCompressedToTagged,
+      IrOpcode::kChangeCompressedSignedToTaggedSigned,
+      IrOpcode::kChangeCompressedPointerToTaggedPointer};
+
+  ASSERT_EQ(arraysize(decompression_ops), arraysize(element_accesses));
+  ASSERT_EQ(arraysize(opcodes), arraysize(element_accesses));
+
+  // 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(decompression_ops[i], load);
+    Node* phi = graph()->NewNode(
+        common()->Phi(MachineRepresentation::kTagged, number_of_inputs),
+        change_to_tagged, control);
+
+    // Reduce
+    Reduction r = Reduce(phi);
+    ASSERT_TRUE(r.Changed());
+    EXPECT_EQ(opcodes[i], r.replacement()->opcode());
+  }
+}
+
+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;
+
+  const Operator* decompression_ops[] = {
+      machine()->ChangeCompressedToTagged(),
+      machine()->ChangeCompressedSignedToTaggedSigned(),
+      machine()->ChangeCompressedPointerToTaggedPointer()};
+
+  const ElementAccess element_accesses[] = {
+      {kTaggedBase, kTaggedSize, Type::Any(), MachineType::AnyCompressed(),
+       kNoWriteBarrier},
+      {kTaggedBase, kTaggedSize, Type::Any(), MachineType::CompressedSigned(),
+       kNoWriteBarrier},
+      {kTaggedBase, kTaggedSize, Type::Any(), MachineType::CompressedPointer(),
+       kNoWriteBarrier}};
+
+  const IrOpcode::Value opcodes[] = {
+      IrOpcode::kChangeCompressedToTagged,
+      IrOpcode::kChangeCompressedSignedToTaggedSigned,
+      IrOpcode::kChangeCompressedPointerToTaggedPointer};
+
+  ASSERT_EQ(arraysize(decompression_ops), arraysize(element_accesses));
+  ASSERT_EQ(arraysize(opcodes), arraysize(element_accesses));
+
+  // 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_tagged1 = graph()->NewNode(decompression_ops[i], load1);
+    Node* change_to_tagged2 = graph()->NewNode(decompression_ops[i], load2);
+    Node* change_to_tagged3 = graph()->NewNode(decompression_ops[i], load3);
+
+    Node* phi = graph()->NewNode(
+        common()->Phi(MachineRepresentation::kTagged, number_of_inputs),
+        change_to_tagged1, change_to_tagged2, change_to_tagged3, control);
+
+    // Reduce
+    Reduction r = Reduce(phi);
+    ASSERT_TRUE(r.Changed());
+    EXPECT_EQ(opcodes[i], r.replacement()->opcode());
+  }
+}
+
+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;
+
+  const Operator* decompression_ops[] = {
+      machine()->ChangeCompressedSignedToTaggedSigned(),
+      machine()->ChangeCompressedPointerToTaggedPointer()};
+
+  const ElementAccess element_accesses[] = {
+      {kTaggedBase, kTaggedSize, Type::Any(), MachineType::CompressedSigned(),
+       kNoWriteBarrier},
+      {kTaggedBase, kTaggedSize, Type::Any(), MachineType::CompressedPointer(),
+       kNoWriteBarrier}};
+
+  const ElementAccess any_access = {kTaggedBase, kTaggedSize, Type::Any(),
+                                    MachineType::AnyCompressed(),
+                                    kNoWriteBarrier};
+
+  ASSERT_EQ(arraysize(decompression_ops), arraysize(element_accesses));
+
+  // 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);
+    // Note that load3 loads a CompressedAny instead of element_accesses[i]
+    Node* load3 = graph()->NewNode(simplified()->LoadElement(any_access),
+                                   object, index, effect, control);
+    Node* change_to_tagged1 = graph()->NewNode(decompression_ops[i], load1);
+    Node* change_to_tagged2 = graph()->NewNode(decompression_ops[i], load2);
+    Node* change_to_tagged3 =
+        graph()->NewNode(machine()->ChangeCompressedToTagged(), load3);
+
+    Node* phi = graph()->NewNode(
+        common()->Phi(MachineRepresentation::kTagged, number_of_inputs),
+        change_to_tagged1, change_to_tagged2, change_to_tagged3, control);
+
+    // Reduce
+    Reduction r = Reduce(phi);
+    ASSERT_TRUE(r.Changed());
+    EXPECT_EQ(IrOpcode::kChangeCompressedToTagged, r.replacement()->opcode());
+  }
+}
+
+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;
+
+  const Operator* decompression_ops[] = {
+      machine()->ChangeCompressedToTagged(),
+      machine()->ChangeCompressedSignedToTaggedSigned(),
+      machine()->ChangeCompressedPointerToTaggedPointer()};
+
+  const ElementAccess element_accesses[] = {
+      {kTaggedBase, kTaggedSize, Type::Any(), MachineType::AnyCompressed(),
+       kNoWriteBarrier},
+      {kTaggedBase, kTaggedSize, Type::Any(), MachineType::CompressedSigned(),
+       kNoWriteBarrier},
+      {kTaggedBase, kTaggedSize, Type::Any(), MachineType::CompressedPointer(),
+       kNoWriteBarrier}};
+
+  const IrOpcode::Value opcodes[] = {
+      IrOpcode::kChangeCompressedToTagged,
+      IrOpcode::kChangeCompressedSignedToTaggedSigned,
+      IrOpcode::kChangeCompressedPointerToTaggedPointer};
+
+  ASSERT_EQ(arraysize(decompression_ops), arraysize(element_accesses));
+  ASSERT_EQ(arraysize(opcodes), arraysize(element_accesses));
+
+  // 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_tagged1 = graph()->NewNode(decompression_ops[i], load1);
+    Node* change_to_tagged2 = graph()->NewNode(decompression_ops[i], load2);
+
+    Node* phi = graph()->NewNode(
+        common()->Phi(MachineRepresentation::kTagged, number_of_inputs),
+        change_to_tagged1, change_to_tagged2, 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;
+  const ElementAccess signed_access = {kTaggedBase, kTaggedSize, Type::Any(),
+                                       MachineType::CompressedSigned(),
+                                       kNoWriteBarrier};
+  const ElementAccess pointer_access = {kTaggedBase, kTaggedSize, Type::Any(),
+                                        MachineType::CompressedPointer(),
+                                        kNoWriteBarrier};
+
+  // 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_tagged1 = graph()->NewNode(
+      machine()->ChangeCompressedSignedToTaggedSigned(), load1);
+  Node* change_to_tagged2 = graph()->NewNode(
+      machine()->ChangeCompressedPointerToTaggedPointer(), load2);
+
+  Node* phi = graph()->NewNode(
+      common()->Phi(MachineRepresentation::kTagged, number_of_inputs),
+      change_to_tagged1, change_to_tagged2, control);
+
+  // Reduce
+  Reduction r = Reduce(phi);
+  ASSERT_TRUE(r.Changed());
+  EXPECT_EQ(IrOpcode::kChangeCompressedToTagged, r.replacement()->opcode());
+}
+
+// -----------------------------------------------------------------------------
 // TypedStateValues
 
 TEST_F(DecompressionEliminationTest, TypedStateValuesOneDecompress) {