// Copyright (c) 2020 Vasyl Teliman
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "source/fuzz/transformation_propagate_instruction_down.h"

#include "source/fuzz/fuzzer_util.h"
#include "source/fuzz/instruction_descriptor.h"

namespace spvtools {
namespace fuzz {

TransformationPropagateInstructionDown::TransformationPropagateInstructionDown(
    const protobufs::TransformationPropagateInstructionDown& message)
    : message_(message) {}

TransformationPropagateInstructionDown::TransformationPropagateInstructionDown(
    uint32_t block_id, uint32_t phi_fresh_id,
    const std::map<uint32_t, uint32_t>& successor_id_to_fresh_id) {
  message_.set_block_id(block_id);
  message_.set_phi_fresh_id(phi_fresh_id);
  *message_.mutable_successor_id_to_fresh_id() =
      fuzzerutil::MapToRepeatedUInt32Pair(successor_id_to_fresh_id);
}

bool TransformationPropagateInstructionDown::IsApplicable(
    opt::IRContext* ir_context,
    const TransformationContext& transformation_context) const {
  // Check that we can apply this transformation to the |block_id|.
  if (!IsApplicableToBlock(ir_context, message_.block_id())) {
    return false;
  }

  const auto successor_id_to_fresh_id =
      fuzzerutil::RepeatedUInt32PairToMap(message_.successor_id_to_fresh_id());

  for (auto id : GetAcceptableSuccessors(ir_context, message_.block_id())) {
    // Each successor must have a fresh id in the |successor_id_to_fresh_id|
    // map, unless overflow ids are available.
    if (!successor_id_to_fresh_id.count(id) &&
        !transformation_context.GetOverflowIdSource()->HasOverflowIds()) {
      return false;
    }
  }

  std::vector<uint32_t> maybe_fresh_ids = {message_.phi_fresh_id()};
  maybe_fresh_ids.reserve(successor_id_to_fresh_id.size());
  for (const auto& entry : successor_id_to_fresh_id) {
    maybe_fresh_ids.push_back(entry.second);
  }

  // All ids must be unique and fresh.
  return !fuzzerutil::HasDuplicates(maybe_fresh_ids) &&
         std::all_of(maybe_fresh_ids.begin(), maybe_fresh_ids.end(),
                     [ir_context](uint32_t id) {
                       return fuzzerutil::IsFreshId(ir_context, id);
                     });
}

void TransformationPropagateInstructionDown::Apply(
    opt::IRContext* ir_context,
    TransformationContext* transformation_context) const {
  // Get instruction to propagate down. There must be one.
  auto* inst_to_propagate =
      GetInstructionToPropagate(ir_context, message_.block_id());
  assert(inst_to_propagate && "There must be an instruction to propagate");

  auto successor_id_to_fresh_id =
      fuzzerutil::RepeatedUInt32PairToMap(message_.successor_id_to_fresh_id());
  std::vector<uint32_t> created_inst_ids;
  auto successor_ids = GetAcceptableSuccessors(ir_context, message_.block_id());

  // Clone |inst_to_propagate| into every successor.
  for (auto successor_id : successor_ids) {
    std::unique_ptr<opt::Instruction> clone(
        inst_to_propagate->Clone(ir_context));

    uint32_t new_result_id;
    if (successor_id_to_fresh_id.count(successor_id)) {
      new_result_id = successor_id_to_fresh_id.at(successor_id);
    } else {
      assert(transformation_context->GetOverflowIdSource()->HasOverflowIds() &&
             "Overflow ids must be available");
      new_result_id =
          transformation_context->GetOverflowIdSource()->GetNextOverflowId();
      successor_id_to_fresh_id[successor_id] = new_result_id;
    }

    clone->SetResultId(new_result_id);
    fuzzerutil::UpdateModuleIdBound(ir_context, new_result_id);

    auto* insert_before_inst = GetFirstInsertBeforeInstruction(
        ir_context, successor_id, clone->opcode());
    assert(insert_before_inst && "Can't insert into one of the successors");

    insert_before_inst->InsertBefore(std::move(clone));
    created_inst_ids.push_back(new_result_id);
  }

  // Add an OpPhi instruction into the module if possible.
  if (auto merge_block_id = GetOpPhiBlockId(
          ir_context, message_.block_id(), *inst_to_propagate, successor_ids)) {
    opt::Instruction::OperandList in_operands;
    std::unordered_set<uint32_t> visited_predecessors;
    for (auto predecessor_id : ir_context->cfg()->preds(merge_block_id)) {
      if (visited_predecessors.count(predecessor_id)) {
        // Merge block might have multiple identical predecessors.
        continue;
      }

      visited_predecessors.insert(predecessor_id);

      const auto* dominator_analysis = ir_context->GetDominatorAnalysis(
          ir_context->cfg()->block(message_.block_id())->GetParent());

      // Find the successor of |source_block| that dominates the predecessor of
      // the merge block |predecessor_id|.
      auto it = std::find_if(
          successor_ids.begin(), successor_ids.end(),
          [predecessor_id, dominator_analysis](uint32_t successor_id) {
            return dominator_analysis->Dominates(successor_id, predecessor_id);
          });

      // OpPhi requires a single operand pair for every predecessor of the
      // OpPhi's block.
      assert(it != successor_ids.end() && "Unable to insert OpPhi");

      in_operands.push_back(
          {SPV_OPERAND_TYPE_ID, {successor_id_to_fresh_id.at(*it)}});
      in_operands.push_back({SPV_OPERAND_TYPE_ID, {predecessor_id}});
    }

    ir_context->cfg()
        ->block(merge_block_id)
        ->begin()
        ->InsertBefore(MakeUnique<opt::Instruction>(
            ir_context, SpvOpPhi, inst_to_propagate->type_id(),
            message_.phi_fresh_id(), std::move(in_operands)));

    fuzzerutil::UpdateModuleIdBound(ir_context, message_.phi_fresh_id());
    created_inst_ids.push_back(message_.phi_fresh_id());
  }

  // Make sure analyses are updated when we adjust users of |inst_to_propagate|.
  ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);

  // Copy decorations from the original instructions to its propagated copies.
  for (auto id : created_inst_ids) {
    ir_context->get_decoration_mgr()->CloneDecorations(
        inst_to_propagate->result_id(), id);
  }

  // Remove all decorations from the original instruction.
  ir_context->get_decoration_mgr()->RemoveDecorationsFrom(
      inst_to_propagate->result_id());

  // Update every use of the |inst_to_propagate| with a result id of some of the
  // newly created instructions.
  ir_context->get_def_use_mgr()->ForEachUse(
      inst_to_propagate, [ir_context, &created_inst_ids](
                             opt::Instruction* user, uint32_t operand_index) {
        assert(ir_context->get_instr_block(user) &&
               "All decorations should have already been adjusted");

        auto in_operand_index =
            fuzzerutil::InOperandIndexFromOperandIndex(*user, operand_index);
        for (auto id : created_inst_ids) {
          if (fuzzerutil::IdIsAvailableAtUse(ir_context, user, in_operand_index,
                                             id)) {
            user->SetInOperand(in_operand_index, {id});
            return;
          }
        }

        // Every user of |inst_to_propagate| must be updated since we will
        // remove that instruction from the module.
        assert(false && "Every user of |inst_to_propagate| must be updated");
      });

  // Add synonyms about newly created instructions.
  assert(inst_to_propagate->HasResultId() &&
         "Result id is required to add facts");
  if (transformation_context->GetFactManager()->IdIsIrrelevant(
          inst_to_propagate->result_id())) {
    for (auto id : created_inst_ids) {
      transformation_context->GetFactManager()->AddFactIdIsIrrelevant(id);
    }
  } else {
    std::vector<uint32_t> non_irrelevant_ids;
    for (auto id : created_inst_ids) {
      // |id| can be irrelevant implicitly (e.g. if we propagate it into a dead
      // block).
      if (!transformation_context->GetFactManager()->IdIsIrrelevant(id)) {
        non_irrelevant_ids.push_back(id);
      }
    }

    if (transformation_context->GetFactManager()->PointeeValueIsIrrelevant(
            inst_to_propagate->result_id())) {
      for (auto id : non_irrelevant_ids) {
        transformation_context->GetFactManager()
            ->AddFactValueOfPointeeIsIrrelevant(id);
      }
    }

    for (auto id : non_irrelevant_ids) {
      transformation_context->GetFactManager()->AddFactDataSynonym(
          MakeDataDescriptor(id, {}),
          MakeDataDescriptor(non_irrelevant_ids[0], {}));
    }
  }

  // Remove the propagated instruction from the module.
  ir_context->KillInst(inst_to_propagate);

  // We've adjusted all users - make sure these changes are analyzed.
  ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
}

protobufs::Transformation TransformationPropagateInstructionDown::ToMessage()
    const {
  protobufs::Transformation result;
  *result.mutable_propagate_instruction_down() = message_;
  return result;
}

bool TransformationPropagateInstructionDown::IsOpcodeSupported(SpvOp opcode) {
  // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3605):
  //  We only support "simple" instructions that don't work with memory.
  //  We should extend this so that we support the ones that modify the memory
  //  too.
  switch (opcode) {
    case SpvOpUndef:
    case SpvOpAccessChain:
    case SpvOpInBoundsAccessChain:
    case SpvOpArrayLength:
    case SpvOpVectorExtractDynamic:
    case SpvOpVectorInsertDynamic:
    case SpvOpVectorShuffle:
    case SpvOpCompositeConstruct:
    case SpvOpCompositeExtract:
    case SpvOpCompositeInsert:
    case SpvOpCopyObject:
    case SpvOpTranspose:
    case SpvOpConvertFToU:
    case SpvOpConvertFToS:
    case SpvOpConvertSToF:
    case SpvOpConvertUToF:
    case SpvOpUConvert:
    case SpvOpSConvert:
    case SpvOpFConvert:
    case SpvOpQuantizeToF16:
    case SpvOpSatConvertSToU:
    case SpvOpSatConvertUToS:
    case SpvOpBitcast:
    case SpvOpSNegate:
    case SpvOpFNegate:
    case SpvOpIAdd:
    case SpvOpFAdd:
    case SpvOpISub:
    case SpvOpFSub:
    case SpvOpIMul:
    case SpvOpFMul:
    case SpvOpUDiv:
    case SpvOpSDiv:
    case SpvOpFDiv:
    case SpvOpUMod:
    case SpvOpSRem:
    case SpvOpSMod:
    case SpvOpFRem:
    case SpvOpFMod:
    case SpvOpVectorTimesScalar:
    case SpvOpMatrixTimesScalar:
    case SpvOpVectorTimesMatrix:
    case SpvOpMatrixTimesVector:
    case SpvOpMatrixTimesMatrix:
    case SpvOpOuterProduct:
    case SpvOpDot:
    case SpvOpIAddCarry:
    case SpvOpISubBorrow:
    case SpvOpUMulExtended:
    case SpvOpSMulExtended:
    case SpvOpAny:
    case SpvOpAll:
    case SpvOpIsNan:
    case SpvOpIsInf:
    case SpvOpIsFinite:
    case SpvOpIsNormal:
    case SpvOpSignBitSet:
    case SpvOpLessOrGreater:
    case SpvOpOrdered:
    case SpvOpUnordered:
    case SpvOpLogicalEqual:
    case SpvOpLogicalNotEqual:
    case SpvOpLogicalOr:
    case SpvOpLogicalAnd:
    case SpvOpLogicalNot:
    case SpvOpSelect:
    case SpvOpIEqual:
    case SpvOpINotEqual:
    case SpvOpUGreaterThan:
    case SpvOpSGreaterThan:
    case SpvOpUGreaterThanEqual:
    case SpvOpSGreaterThanEqual:
    case SpvOpULessThan:
    case SpvOpSLessThan:
    case SpvOpULessThanEqual:
    case SpvOpSLessThanEqual:
    case SpvOpFOrdEqual:
    case SpvOpFUnordEqual:
    case SpvOpFOrdNotEqual:
    case SpvOpFUnordNotEqual:
    case SpvOpFOrdLessThan:
    case SpvOpFUnordLessThan:
    case SpvOpFOrdGreaterThan:
    case SpvOpFUnordGreaterThan:
    case SpvOpFOrdLessThanEqual:
    case SpvOpFUnordLessThanEqual:
    case SpvOpFOrdGreaterThanEqual:
    case SpvOpFUnordGreaterThanEqual:
    case SpvOpShiftRightLogical:
    case SpvOpShiftRightArithmetic:
    case SpvOpShiftLeftLogical:
    case SpvOpBitwiseOr:
    case SpvOpBitwiseXor:
    case SpvOpBitwiseAnd:
    case SpvOpNot:
    case SpvOpBitFieldInsert:
    case SpvOpBitFieldSExtract:
    case SpvOpBitFieldUExtract:
    case SpvOpBitReverse:
    case SpvOpBitCount:
    case SpvOpCopyLogical:
    case SpvOpPtrEqual:
    case SpvOpPtrNotEqual:
      return true;
    default:
      return false;
  }
}

opt::Instruction*
TransformationPropagateInstructionDown::GetInstructionToPropagate(
    opt::IRContext* ir_context, uint32_t block_id) {
  auto* block = ir_context->cfg()->block(block_id);
  assert(block && "|block_id| is invalid");

  for (auto it = block->rbegin(); it != block->rend(); ++it) {
    if (!it->result_id() || !it->type_id() ||
        !IsOpcodeSupported(it->opcode())) {
      continue;
    }

    auto all_users_from_different_blocks =
        ir_context->get_def_use_mgr()->WhileEachUser(
            &*it, [ir_context, block](opt::Instruction* user) {
              return ir_context->get_instr_block(user) != block;
            });

    if (!all_users_from_different_blocks) {
      // We can't propagate an instruction if it's used in the same block.
      continue;
    }

    return &*it;
  }

  return nullptr;
}

bool TransformationPropagateInstructionDown::IsApplicableToBlock(
    opt::IRContext* ir_context, uint32_t block_id) {
  // Check that |block_id| is valid.
  const auto* block = fuzzerutil::MaybeFindBlock(ir_context, block_id);
  if (!block) {
    return false;
  }

  const auto* dominator_analysis =
      ir_context->GetDominatorAnalysis(block->GetParent());

  // |block| must be reachable.
  if (!dominator_analysis->IsReachable(block)) {
    return false;
  }

  // The block must have an instruction to propagate.
  const auto* inst_to_propagate =
      GetInstructionToPropagate(ir_context, block_id);
  if (!inst_to_propagate) {
    return false;
  }

  // Check that |block| has successors.
  auto successor_ids = GetAcceptableSuccessors(ir_context, block_id);
  if (successor_ids.empty()) {
    return false;
  }

  // Check that |successor_block| doesn't have any OpPhi instructions that
  // use |inst|.
  for (auto successor_id : successor_ids) {
    for (const auto& maybe_phi_inst : *ir_context->cfg()->block(successor_id)) {
      if (maybe_phi_inst.opcode() != SpvOpPhi) {
        // OpPhis can be intermixed with OpLine and OpNoLine.
        continue;
      }

      for (uint32_t i = 0; i < maybe_phi_inst.NumInOperands(); i += 2) {
        if (maybe_phi_inst.GetSingleWordInOperand(i) ==
            inst_to_propagate->result_id()) {
          return false;
        }
      }
    }
  }

  // Get the result id of the block we will insert OpPhi instruction into.
  // This is either 0 or a result id of some merge block in the function.
  auto phi_block_id =
      GetOpPhiBlockId(ir_context, block_id, *inst_to_propagate, successor_ids);

  // Make sure we can adjust all users of the propagated instruction.
  return ir_context->get_def_use_mgr()->WhileEachUse(
      inst_to_propagate,
      [ir_context, &successor_ids, dominator_analysis, phi_block_id](
          opt::Instruction* user, uint32_t index) {
        const auto* user_block = ir_context->get_instr_block(user);

        if (!user_block) {
          // |user| might be a global instruction (e.g. OpDecorate).
          return true;
        }

        // Check that at least one of the ids in |successor_ids| or a
        // |phi_block_id| dominates |user|'s block (or its predecessor if the
        // user is an OpPhi). We can't use fuzzerutil::IdIsAvailableAtUse since
        // the id in question hasn't yet been created in the module.
        auto block_id_to_dominate = user->opcode() == SpvOpPhi
                                        ? user->GetSingleWordOperand(index + 1)
                                        : user_block->id();

        if (phi_block_id != 0 &&
            dominator_analysis->Dominates(phi_block_id, block_id_to_dominate)) {
          return true;
        }

        return std::any_of(
            successor_ids.begin(), successor_ids.end(),
            [dominator_analysis, block_id_to_dominate](uint32_t id) {
              return dominator_analysis->Dominates(id, block_id_to_dominate);
            });
      });
}

opt::Instruction*
TransformationPropagateInstructionDown::GetFirstInsertBeforeInstruction(
    opt::IRContext* ir_context, uint32_t block_id, SpvOp opcode) {
  auto* block = ir_context->cfg()->block(block_id);

  auto it = block->begin();

  while (it != block->end() &&
         !fuzzerutil::CanInsertOpcodeBeforeInstruction(opcode, it)) {
    ++it;
  }

  return it == block->end() ? nullptr : &*it;
}

std::unordered_set<uint32_t>
TransformationPropagateInstructionDown::GetAcceptableSuccessors(
    opt::IRContext* ir_context, uint32_t block_id) {
  const auto* block = ir_context->cfg()->block(block_id);
  assert(block && "|block_id| is invalid");

  const auto* inst = GetInstructionToPropagate(ir_context, block_id);
  assert(inst && "The block must have an instruction to propagate");

  std::unordered_set<uint32_t> result;
  block->ForEachSuccessorLabel([ir_context, &result,
                                inst](uint32_t successor_id) {
    if (result.count(successor_id)) {
      return;
    }

    auto* successor_block = ir_context->cfg()->block(successor_id);

    // We can't propagate |inst| into |successor_block| if the latter is not
    // dominated by the |inst|'s dependencies.
    if (!inst->WhileEachInId([ir_context, successor_block](const uint32_t* id) {
          return fuzzerutil::IdIsAvailableBeforeInstruction(
              ir_context, &*successor_block->begin(), *id);
        })) {
      return;
    }

    // We don't propagate any "special" instructions (e.g. OpSelectionMerge
    // etc), thus, insertion point must always exist if the module is valid.
    assert(GetFirstInsertBeforeInstruction(ir_context, successor_id,
                                           inst->opcode()) &&
           "There must exist an insertion point.");

    result.insert(successor_id);
  });

  return result;
}

uint32_t TransformationPropagateInstructionDown::GetOpPhiBlockId(
    opt::IRContext* ir_context, uint32_t block_id,
    const opt::Instruction& inst_to_propagate,
    const std::unordered_set<uint32_t>& successor_ids) {
  const auto* block = ir_context->cfg()->block(block_id);

  // |block_id| must belong to some construct.
  auto merge_block_id =
      block->GetMergeInst()
          ? block->GetMergeInst()->GetSingleWordInOperand(0)
          : ir_context->GetStructuredCFGAnalysis()->MergeBlock(block_id);
  if (!merge_block_id) {
    return 0;
  }

  const auto* dominator_analysis =
      ir_context->GetDominatorAnalysis(block->GetParent());

  // Check that |merge_block_id| is reachable in the CFG and |block_id|
  // dominates |merge_block_id|.
  if (!dominator_analysis->IsReachable(merge_block_id) ||
      !dominator_analysis->Dominates(block_id, merge_block_id)) {
    return 0;
  }

  // We can't insert an OpPhi into |merge_block_id| if it's an acceptable
  // successor of |block_id|.
  if (successor_ids.count(merge_block_id)) {
    return 0;
  }

  // All predecessors of the merge block must be dominated by at least one
  // successor of the |block_id|.
  assert(!ir_context->cfg()->preds(merge_block_id).empty() &&
         "Merge block must be reachable");
  for (auto predecessor_id : ir_context->cfg()->preds(merge_block_id)) {
    if (std::none_of(
            successor_ids.begin(), successor_ids.end(),
            [dominator_analysis, predecessor_id](uint32_t successor_id) {
              return dominator_analysis->Dominates(successor_id,
                                                   predecessor_id);
            })) {
      return 0;
    }
  }

  const auto* propagate_type =
      ir_context->get_type_mgr()->GetType(inst_to_propagate.type_id());
  assert(propagate_type && "|inst_to_propagate| must have a valid type");

  // VariablePointers capability implicitly declares
  // VariablePointersStorageBuffer. We need those capabilities since otherwise
  // OpPhi instructions cannot have operands of pointer types.
  if (propagate_type->AsPointer() &&
      !ir_context->get_feature_mgr()->HasCapability(
          SpvCapabilityVariablePointersStorageBuffer)) {
    return 0;
  }

  return merge_block_id;
}

std::unordered_set<uint32_t>
TransformationPropagateInstructionDown::GetFreshIds() const {
  std::unordered_set<uint32_t> result = {message_.phi_fresh_id()};
  for (const auto& pair : message_.successor_id_to_fresh_id()) {
    result.insert(pair.second());
  }
  return result;
}

}  // namespace fuzz
}  // namespace spvtools
