// 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"

namespace v8 {
namespace internal {
namespace compiler {

DecompressionElimination::DecompressionElimination(
    Editor* editor, Graph* graph, MachineOperatorBuilder* machine,
    CommonOperatorBuilder* common)
    : AdvancedReducer(editor),
      graph_(graph),
      machine_(machine),
      common_(common) {}

bool DecompressionElimination::IsReducibleConstantOpcode(
    IrOpcode::Value opcode) {
  switch (opcode) {
    case IrOpcode::kInt64Constant:
    case IrOpcode::kHeapConstant:
      return true;
    default:
      return false;
  }
}

bool DecompressionElimination::IsValidDecompress(
    IrOpcode::Value compressOpcode, IrOpcode::Value decompressOpcode) {
  switch (compressOpcode) {
    case IrOpcode::kChangeTaggedToCompressed:
      return IrOpcode::IsDecompressOpcode(decompressOpcode);
    case IrOpcode::kChangeTaggedSignedToCompressedSigned:
      return decompressOpcode ==
                 IrOpcode::kChangeCompressedSignedToTaggedSigned ||
             decompressOpcode == IrOpcode::kChangeCompressedToTagged;
    case IrOpcode::kChangeTaggedPointerToCompressedPointer:
      return decompressOpcode ==
                 IrOpcode::kChangeCompressedPointerToTaggedPointer ||
             decompressOpcode == IrOpcode::kChangeCompressedToTagged;
    default:
      UNREACHABLE();
  }
}

Node* DecompressionElimination::GetCompressedConstant(Node* constant) {
  switch (constant->opcode()) {
    case IrOpcode::kInt64Constant:
      return graph()->NewNode(common()->Int32Constant(
          static_cast<int32_t>(OpParameter<int64_t>(constant->op()))));
      break;
    case IrOpcode::kHeapConstant:
      return graph()->NewNode(
          common()->CompressedHeapConstant(HeapConstantOf(constant->op())));
    default:
      UNREACHABLE();
  }
}

Reduction DecompressionElimination::ReduceCompress(Node* node) {
  DCHECK(IrOpcode::IsCompressOpcode(node->opcode()));

  DCHECK_EQ(node->InputCount(), 1);
  Node* input_node = node->InputAt(0);
  IrOpcode::Value input_opcode = input_node->opcode();
  if (IrOpcode::IsDecompressOpcode(input_opcode)) {
    DCHECK(IsValidDecompress(node->opcode(), input_opcode));
    DCHECK_EQ(input_node->InputCount(), 1);
    return Replace(input_node->InputAt(0));
  } else if (IsReducibleConstantOpcode(input_opcode)) {
    return Replace(GetCompressedConstant(input_node));
  } else {
    return NoChange();
  }
}

Reduction DecompressionElimination::ReduceDecompress(Node* node) {
  DCHECK(IrOpcode::IsDecompressOpcode(node->opcode()));

  DCHECK_EQ(node->InputCount(), 1);
  Node* input_node = node->InputAt(0);
  IrOpcode::Value input_opcode = input_node->opcode();
  if (IrOpcode::IsCompressOpcode(input_opcode)) {
    DCHECK(IsValidDecompress(input_opcode, node->opcode()));
    DCHECK_EQ(input_node->InputCount(), 1);
    return Replace(input_node->InputAt(0));
  } else {
    return NoChange();
  }
}

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".
  Node* decompress = graph()->NewNode(op, node);
  ReplaceWithValue(node, decompress);
  decompress->ReplaceInput(0, node);
  return Changed(node);
}

Reduction DecompressionElimination::ReduceTypedStateValues(Node* node) {
  DCHECK_EQ(node->opcode(), IrOpcode::kTypedStateValues);

  bool any_change = false;
  for (int i = 0; i < node->InputCount(); ++i) {
    Node* input = node->InputAt(i);
    if (IrOpcode::IsDecompressOpcode(input->opcode())) {
      DCHECK_EQ(input->InputCount(), 1);
      node->ReplaceInput(i, input->InputAt(0));
      any_change = true;
    }
  }
  return any_change ? Changed(node) : NoChange();
}

Reduction DecompressionElimination::ReduceWord64Equal(Node* node) {
  DCHECK_EQ(node->opcode(), IrOpcode::kWord64Equal);

  DCHECK_EQ(node->InputCount(), 2);
  Node* lhs = node->InputAt(0);
  Node* rhs = node->InputAt(1);

  bool lhs_is_decompress = IrOpcode::IsDecompressOpcode(lhs->opcode());
  bool rhs_is_decompress = IrOpcode::IsDecompressOpcode(rhs->opcode());

  // Case where both of its inputs are Decompress nodes.
  if (lhs_is_decompress && rhs_is_decompress) {
    DCHECK_EQ(lhs->InputCount(), 1);
    node->ReplaceInput(0, lhs->InputAt(0));
    DCHECK_EQ(rhs->InputCount(), 1);
    node->ReplaceInput(1, rhs->InputAt(0));
    NodeProperties::ChangeOp(node, machine()->Word32Equal());
    return Changed(node);
  }

  bool lhs_is_constant = IsReducibleConstantOpcode(lhs->opcode());
  bool rhs_is_constant = IsReducibleConstantOpcode(rhs->opcode());

  // Case where one input is a Decompress node and the other a constant.
  if ((lhs_is_decompress && rhs_is_constant) ||
      (lhs_is_constant && rhs_is_decompress)) {
    node->ReplaceInput(
        0, lhs_is_decompress ? lhs->InputAt(0) : GetCompressedConstant(lhs));
    node->ReplaceInput(
        1, lhs_is_decompress ? GetCompressedConstant(rhs) : rhs->InputAt(0));
    NodeProperties::ChangeOp(node, machine()->Word32Equal());
    return Changed(node);
  }

  return NoChange();
}

Reduction DecompressionElimination::Reduce(Node* node) {
  DisallowHeapAccess no_heap_access;

  switch (node->opcode()) {
    case IrOpcode::kChangeTaggedToCompressed:
    case IrOpcode::kChangeTaggedSignedToCompressedSigned:
    case IrOpcode::kChangeTaggedPointerToCompressedPointer:
      return ReduceCompress(node);
    case IrOpcode::kChangeCompressedToTagged:
    case IrOpcode::kChangeCompressedSignedToTaggedSigned:
    case IrOpcode::kChangeCompressedPointerToTaggedPointer:
      return ReduceDecompress(node);
    case IrOpcode::kPhi:
      return ReducePhi(node);
    case IrOpcode::kTypedStateValues:
      return ReduceTypedStateValues(node);
    case IrOpcode::kWord64Equal:
      return ReduceWord64Equal(node);
    default:
      break;
  }

  return NoChange();
}

}  // namespace compiler
}  // namespace internal
}  // namespace v8