/// \file InstructionTranslator.cpp
/// This file implements the logic to translate a PTC instruction in to LLVM IR.

//
// This file is distributed under the MIT License. See LICENSE.md for details.
//

#include <cstdint>
#include <fstream>
#include <queue>
#include <set>
#include <sstream>

#include "llvm/ADT/STLExtras.h"
#include "llvm/IR/Attributes.h"
#include "llvm/IR/BasicBlock.h"
#include "llvm/IR/CFG.h"
#include "llvm/IR/Intrinsics.h"
#include "llvm/IR/Module.h"
#include "llvm/Support/Casting.h"

#include "revng/Lift/Lift.h"
#include "revng/Support/Assert.h"
#include "revng/Support/FunctionTags.h"
#include "revng/Support/IRHelpers.h"
#include "revng/Support/RandomAccessIterator.h"
#include "revng/Support/Range.h"

#include "InstructionTranslator.h"
#include "PTCInterface.h"
#include "VariableManager.h"

using namespace llvm;

static cl::opt<bool> RecordASM("record-asm",
                               cl::desc("create metadata for assembly"),
                               cl::cat(MainCategory));

using IT = InstructionTranslator;

namespace PTC {

template<bool C>
class InstructionImpl;

enum ArgumentType {
  In,
  Out,
  Const
};

template<typename T, typename Q, bool B>
using RAI = RandomAccessIterator<T, Q, B>;

template<ArgumentType Type, bool IsCall>
class InstructionArgumentsIterator
  : public RAI<uint64_t, InstructionArgumentsIterator<Type, IsCall>, false> {

public:
  using base = RandomAccessIterator<uint64_t,
                                    InstructionArgumentsIterator,
                                    false>;

  InstructionArgumentsIterator &
  operator=(const InstructionArgumentsIterator &R) {
    base::operator=(R);
    TheInstruction = R.TheInstruction;
    return *this;
  }

  InstructionArgumentsIterator(const InstructionArgumentsIterator &R) :
    base(R), TheInstruction(R.TheInstruction) {}

  InstructionArgumentsIterator(const InstructionArgumentsIterator &R,
                               unsigned Index) :
    base(Index), TheInstruction(R.TheInstruction) {}

  InstructionArgumentsIterator(PTCInstruction *TheInstruction, unsigned Index) :
    base(Index), TheInstruction(TheInstruction) {}

  bool isCompatible(const InstructionArgumentsIterator &R) const {
    return TheInstruction == R.TheInstruction;
  }

public:
  uint64_t get(unsigned Index) const;

private:
  PTCInstruction *TheInstruction = nullptr;
};

template<>
inline uint64_t
InstructionArgumentsIterator<In, true>::get(unsigned Index) const {
  return ptc_call_instruction_in_arg(&ptc, TheInstruction, Index);
}

template<>
inline uint64_t
InstructionArgumentsIterator<Const, true>::get(unsigned Index) const {
  return ptc_call_instruction_const_arg(&ptc, TheInstruction, Index);
}

template<>
inline uint64_t
InstructionArgumentsIterator<Out, true>::get(unsigned Index) const {
  return ptc_call_instruction_out_arg(&ptc, TheInstruction, Index);
}

template<>
inline uint64_t
InstructionArgumentsIterator<In, false>::get(unsigned Index) const {
  return ptc_instruction_in_arg(&ptc, TheInstruction, Index);
}

template<>
inline uint64_t
InstructionArgumentsIterator<Const, false>::get(unsigned Index) const {
  return ptc_instruction_const_arg(&ptc, TheInstruction, Index);
}

template<>
inline uint64_t
InstructionArgumentsIterator<Out, false>::get(unsigned Index) const {
  return ptc_instruction_out_arg(&ptc, TheInstruction, Index);
}

template<bool IsCall>
class InstructionImpl {
private:
  template<ArgumentType Type>
  using arguments = InstructionArgumentsIterator<Type, IsCall>;

public:
  InstructionImpl(PTCInstruction *TheInstruction) :
    TheInstruction(TheInstruction),
    InArguments(arguments<In>(TheInstruction, 0),
                arguments<In>(TheInstruction, inArgCount())),
    ConstArguments(arguments<Const>(TheInstruction, 0),
                   arguments<Const>(TheInstruction, constArgCount())),
    OutArguments(arguments<Out>(TheInstruction, 0),
                 arguments<Out>(TheInstruction, outArgCount())) {}

  PTCOpcode opcode() const { return TheInstruction->opc; }

  std::string helperName() const {
    revng_assert(IsCall);
    PTCHelperDef *Helper = ptc_find_helper(&ptc, ConstArguments[0]);
    revng_assert(Helper != nullptr && Helper->name != nullptr);
    return std::string(Helper->name);
  }

  uint64_t pc() const {
    revng_assert(opcode() == PTC_INSTRUCTION_op_debug_insn_start);
    uint64_t PC = ConstArguments[0];
    if (ConstArguments.size() > 1)
      PC |= ConstArguments[1] << 32;
    return PC;
  }

private:
  PTCInstruction *TheInstruction = nullptr;

public:
  const Range<InstructionArgumentsIterator<In, IsCall>> InArguments;
  const Range<InstructionArgumentsIterator<Const, IsCall>> ConstArguments;
  const Range<InstructionArgumentsIterator<Out, IsCall>> OutArguments;

private:
  unsigned inArgCount() const;
  unsigned constArgCount() const;
  unsigned outArgCount() const;
};

using Instruction = InstructionImpl<false>;
using CallInstruction = InstructionImpl<true>;

template<>
inline unsigned CallInstruction::inArgCount() const {
  return ptc_call_instruction_in_arg_count(&ptc, TheInstruction);
}

template<>
inline unsigned Instruction::inArgCount() const {
  return ptc_instruction_in_arg_count(&ptc, TheInstruction);
}

template<>
inline unsigned CallInstruction::constArgCount() const {
  return ptc_call_instruction_const_arg_count(&ptc, TheInstruction);
}

template<>
inline unsigned Instruction::constArgCount() const {
  return ptc_instruction_const_arg_count(&ptc, TheInstruction);
}

template<>
inline unsigned CallInstruction::outArgCount() const {
  return ptc_call_instruction_out_arg_count(&ptc, TheInstruction);
}

template<>
inline unsigned Instruction::outArgCount() const {
  return ptc_instruction_out_arg_count(&ptc, TheInstruction);
}

} // namespace PTC

/// Converts a PTC condition into an LLVM predicate
///
/// \param Condition the input PTC condition.
///
/// \return the corresponding LLVM predicate.
static CmpInst::Predicate conditionToPredicate(PTCCondition Condition) {
  switch (Condition) {
  case PTC_COND_NEVER:
    // TODO: this is probably wrong
    return CmpInst::FCMP_FALSE;
  case PTC_COND_ALWAYS:
    // TODO: this is probably wrong
    return CmpInst::FCMP_TRUE;
  case PTC_COND_EQ:
    return CmpInst::ICMP_EQ;
  case PTC_COND_NE:
    return CmpInst::ICMP_NE;
  case PTC_COND_LT:
    return CmpInst::ICMP_SLT;
  case PTC_COND_GE:
    return CmpInst::ICMP_SGE;
  case PTC_COND_LE:
    return CmpInst::ICMP_SLE;
  case PTC_COND_GT:
    return CmpInst::ICMP_SGT;
  case PTC_COND_LTU:
    return CmpInst::ICMP_ULT;
  case PTC_COND_GEU:
    return CmpInst::ICMP_UGE;
  case PTC_COND_LEU:
    return CmpInst::ICMP_ULE;
  case PTC_COND_GTU:
    return CmpInst::ICMP_UGT;
  default:
    revng_unreachable("Unknown comparison operator");
  }
}

/// Obtains the LLVM binary operation corresponding to the specified PTC opcode.
///
/// \param Opcode the PTC opcode.
///
/// \return the LLVM binary operation matching opcode.
static Instruction::BinaryOps opcodeToBinaryOp(PTCOpcode Opcode) {
  switch (Opcode) {
  case PTC_INSTRUCTION_op_add_i32:
  case PTC_INSTRUCTION_op_add_i64:
  case PTC_INSTRUCTION_op_add2_i32:
  case PTC_INSTRUCTION_op_add2_i64:
    return Instruction::Add;
  case PTC_INSTRUCTION_op_sub_i32:
  case PTC_INSTRUCTION_op_sub_i64:
  case PTC_INSTRUCTION_op_sub2_i32:
  case PTC_INSTRUCTION_op_sub2_i64:
    return Instruction::Sub;
  case PTC_INSTRUCTION_op_mul_i32:
  case PTC_INSTRUCTION_op_mul_i64:
    return Instruction::Mul;
  case PTC_INSTRUCTION_op_div_i32:
  case PTC_INSTRUCTION_op_div_i64:
    return Instruction::SDiv;
  case PTC_INSTRUCTION_op_divu_i32:
  case PTC_INSTRUCTION_op_divu_i64:
    return Instruction::UDiv;
  case PTC_INSTRUCTION_op_rem_i32:
  case PTC_INSTRUCTION_op_rem_i64:
    return Instruction::SRem;
  case PTC_INSTRUCTION_op_remu_i32:
  case PTC_INSTRUCTION_op_remu_i64:
    return Instruction::URem;
  case PTC_INSTRUCTION_op_and_i32:
  case PTC_INSTRUCTION_op_and_i64:
    return Instruction::And;
  case PTC_INSTRUCTION_op_or_i32:
  case PTC_INSTRUCTION_op_or_i64:
    return Instruction::Or;
  case PTC_INSTRUCTION_op_xor_i32:
  case PTC_INSTRUCTION_op_xor_i64:
    return Instruction::Xor;
  case PTC_INSTRUCTION_op_shl_i32:
  case PTC_INSTRUCTION_op_shl_i64:
    return Instruction::Shl;
  case PTC_INSTRUCTION_op_shr_i32:
  case PTC_INSTRUCTION_op_shr_i64:
    return Instruction::LShr;
  case PTC_INSTRUCTION_op_sar_i32:
  case PTC_INSTRUCTION_op_sar_i64:
    return Instruction::AShr;
  default:
    revng_unreachable("PTC opcode is not a binary operator");
  }
}

/// Returns the maximum value which can be represented with the specified number
/// of bits.
static uint64_t getMaxValue(unsigned Bits) {
  if (Bits == 32)
    return 0xffffffff;
  else if (Bits == 64)
    return 0xffffffffffffffff;
  else
    revng_unreachable("Not the number of bits in a integer type");
}

/// Maps an opcode the corresponding input and output register size.
///
/// \return the size, in bits, of the registers used by the opcode.
static unsigned getRegisterSize(unsigned Opcode) {
  switch (Opcode) {
  case PTC_INSTRUCTION_op_add2_i32:
  case PTC_INSTRUCTION_op_add_i32:
  case PTC_INSTRUCTION_op_andc_i32:
  case PTC_INSTRUCTION_op_and_i32:
  case PTC_INSTRUCTION_op_brcond2_i32:
  case PTC_INSTRUCTION_op_brcond_i32:
  case PTC_INSTRUCTION_op_bswap16_i32:
  case PTC_INSTRUCTION_op_bswap32_i32:
  case PTC_INSTRUCTION_op_deposit_i32:
  case PTC_INSTRUCTION_op_div2_i32:
  case PTC_INSTRUCTION_op_div_i32:
  case PTC_INSTRUCTION_op_divu2_i32:
  case PTC_INSTRUCTION_op_divu_i32:
  case PTC_INSTRUCTION_op_eqv_i32:
  case PTC_INSTRUCTION_op_ext16s_i32:
  case PTC_INSTRUCTION_op_ext16u_i32:
  case PTC_INSTRUCTION_op_ext8s_i32:
  case PTC_INSTRUCTION_op_ext8u_i32:
  case PTC_INSTRUCTION_op_ld16s_i32:
  case PTC_INSTRUCTION_op_ld16u_i32:
  case PTC_INSTRUCTION_op_ld8s_i32:
  case PTC_INSTRUCTION_op_ld8u_i32:
  case PTC_INSTRUCTION_op_ld_i32:
  case PTC_INSTRUCTION_op_movcond_i32:
  case PTC_INSTRUCTION_op_mov_i32:
  case PTC_INSTRUCTION_op_movi_i32:
  case PTC_INSTRUCTION_op_mul_i32:
  case PTC_INSTRUCTION_op_muls2_i32:
  case PTC_INSTRUCTION_op_mulsh_i32:
  case PTC_INSTRUCTION_op_mulu2_i32:
  case PTC_INSTRUCTION_op_muluh_i32:
  case PTC_INSTRUCTION_op_nand_i32:
  case PTC_INSTRUCTION_op_neg_i32:
  case PTC_INSTRUCTION_op_nor_i32:
  case PTC_INSTRUCTION_op_not_i32:
  case PTC_INSTRUCTION_op_orc_i32:
  case PTC_INSTRUCTION_op_or_i32:
  case PTC_INSTRUCTION_op_qemu_ld_i32:
  case PTC_INSTRUCTION_op_qemu_st_i32:
  case PTC_INSTRUCTION_op_rem_i32:
  case PTC_INSTRUCTION_op_remu_i32:
  case PTC_INSTRUCTION_op_rotl_i32:
  case PTC_INSTRUCTION_op_rotr_i32:
  case PTC_INSTRUCTION_op_sar_i32:
  case PTC_INSTRUCTION_op_setcond2_i32:
  case PTC_INSTRUCTION_op_setcond_i32:
  case PTC_INSTRUCTION_op_shl_i32:
  case PTC_INSTRUCTION_op_shr_i32:
  case PTC_INSTRUCTION_op_st16_i32:
  case PTC_INSTRUCTION_op_st8_i32:
  case PTC_INSTRUCTION_op_st_i32:
  case PTC_INSTRUCTION_op_sub2_i32:
  case PTC_INSTRUCTION_op_sub_i32:
  case PTC_INSTRUCTION_op_trunc_shr_i32:
  case PTC_INSTRUCTION_op_xor_i32:
    return 32;
  case PTC_INSTRUCTION_op_add2_i64:
  case PTC_INSTRUCTION_op_add_i64:
  case PTC_INSTRUCTION_op_andc_i64:
  case PTC_INSTRUCTION_op_and_i64:
  case PTC_INSTRUCTION_op_brcond_i64:
  case PTC_INSTRUCTION_op_bswap16_i64:
  case PTC_INSTRUCTION_op_bswap32_i64:
  case PTC_INSTRUCTION_op_bswap64_i64:
  case PTC_INSTRUCTION_op_deposit_i64:
  case PTC_INSTRUCTION_op_div2_i64:
  case PTC_INSTRUCTION_op_div_i64:
  case PTC_INSTRUCTION_op_divu2_i64:
  case PTC_INSTRUCTION_op_divu_i64:
  case PTC_INSTRUCTION_op_eqv_i64:
  case PTC_INSTRUCTION_op_ext16s_i64:
  case PTC_INSTRUCTION_op_ext16u_i64:
  case PTC_INSTRUCTION_op_ext32s_i64:
  case PTC_INSTRUCTION_op_ext32u_i64:
  case PTC_INSTRUCTION_op_ext8s_i64:
  case PTC_INSTRUCTION_op_ext8u_i64:
  case PTC_INSTRUCTION_op_ld16s_i64:
  case PTC_INSTRUCTION_op_ld16u_i64:
  case PTC_INSTRUCTION_op_ld32s_i64:
  case PTC_INSTRUCTION_op_ld32u_i64:
  case PTC_INSTRUCTION_op_ld8s_i64:
  case PTC_INSTRUCTION_op_ld8u_i64:
  case PTC_INSTRUCTION_op_ld_i64:
  case PTC_INSTRUCTION_op_movcond_i64:
  case PTC_INSTRUCTION_op_mov_i64:
  case PTC_INSTRUCTION_op_movi_i64:
  case PTC_INSTRUCTION_op_mul_i64:
  case PTC_INSTRUCTION_op_muls2_i64:
  case PTC_INSTRUCTION_op_mulsh_i64:
  case PTC_INSTRUCTION_op_mulu2_i64:
  case PTC_INSTRUCTION_op_muluh_i64:
  case PTC_INSTRUCTION_op_nand_i64:
  case PTC_INSTRUCTION_op_neg_i64:
  case PTC_INSTRUCTION_op_nor_i64:
  case PTC_INSTRUCTION_op_not_i64:
  case PTC_INSTRUCTION_op_orc_i64:
  case PTC_INSTRUCTION_op_or_i64:
  case PTC_INSTRUCTION_op_qemu_ld_i64:
  case PTC_INSTRUCTION_op_qemu_st_i64:
  case PTC_INSTRUCTION_op_rem_i64:
  case PTC_INSTRUCTION_op_remu_i64:
  case PTC_INSTRUCTION_op_rotl_i64:
  case PTC_INSTRUCTION_op_rotr_i64:
  case PTC_INSTRUCTION_op_sar_i64:
  case PTC_INSTRUCTION_op_setcond_i64:
  case PTC_INSTRUCTION_op_shl_i64:
  case PTC_INSTRUCTION_op_shr_i64:
  case PTC_INSTRUCTION_op_st16_i64:
  case PTC_INSTRUCTION_op_st32_i64:
  case PTC_INSTRUCTION_op_st8_i64:
  case PTC_INSTRUCTION_op_st_i64:
  case PTC_INSTRUCTION_op_sub2_i64:
  case PTC_INSTRUCTION_op_sub_i64:
  case PTC_INSTRUCTION_op_xor_i64:
    return 64;
  case PTC_INSTRUCTION_op_br:
  case PTC_INSTRUCTION_op_call:
  case PTC_INSTRUCTION_op_debug_insn_start:
  case PTC_INSTRUCTION_op_discard:
  case PTC_INSTRUCTION_op_exit_tb:
  case PTC_INSTRUCTION_op_goto_tb:
  case PTC_INSTRUCTION_op_set_label:
    return 0;
  default:
    revng_unreachable("Unexpected opcode");
  }
}

/// Create a compare instruction given a comparison operator and the operands
///
/// \param Builder the builder to use to create the instruction.
/// \param RawCondition the PTC condition.
/// \param FirstOperand the first operand of the comparison.
/// \param SecondOperand the second operand of the comparison.
///
/// \return a compare instruction.
template<typename T>
static Value *createICmp(T &Builder,
                         uint64_t RawCondition,
                         Value *FirstOperand,
                         Value *SecondOperand) {
  PTCCondition Condition = static_cast<PTCCondition>(RawCondition);
  return Builder.CreateICmp(conditionToPredicate(Condition),
                            FirstOperand,
                            SecondOperand);
}

using LBM = IT::LabeledBlocksMap;
IT::InstructionTranslator(IRBuilder<> &Builder,
                          VariableManager &Variables,
                          JumpTargetManager &JumpTargets,
                          std::vector<BasicBlock *> Blocks,
                          bool EndianessMismatch,
                          ProgramCounterHandler *PCH) :
  Builder(Builder),
  Variables(Variables),
  JumpTargets(JumpTargets),
  Blocks(Blocks),
  TheModule(*Builder.GetInsertBlock()->getParent()->getParent()),
  TheFunction(Builder.GetInsertBlock()->getParent()),
  EndianessMismatch(EndianessMismatch),
  NewPCMarker(nullptr),
  LastPC(MetaAddress::invalid()),
  PCH(PCH) {

  auto &Context = TheModule.getContext();
  using FT = FunctionType;
  // The newpc function call takes the following parameters:
  //
  // * BasicBlockID of the instruction in string form
  // * instruction size
  // * isJT (-1: unknown, 0: no, 1: yes)
  // * inlining index
  // * pointer to the disassembled instruction
  // * all the local variables used by this instruction
  auto *NewPCMarkerTy = FT::get(Type::getVoidTy(Context),
                                { Type::getInt8PtrTy(Context),
                                  Type::getInt64Ty(Context),
                                  Type::getInt32Ty(Context),
                                  Type::getInt32Ty(Context),
                                  Type::getInt8PtrTy(Context),
                                  Type::getInt8PtrTy(Context) },
                                true);
  NewPCMarker = Function::Create(NewPCMarkerTy,
                                 GlobalValue::ExternalLinkage,
                                 "newpc",
                                 &TheModule);
  FunctionTags::Marker.addTo(NewPCMarker);
  NewPCMarker->addFnAttr(Attribute::WillReturn);
  NewPCMarker->addFnAttr(Attribute::NoUnwind);
  NewPCMarker->addFnAttr(Attribute::NoMerge);
}

void IT::finalizeNewPCMarkers() {
  size_t FixedArgCount = NewPCMarker->arg_size();

  llvm::SmallVector<CallInst *, 4> CallsToRemove;

  for (User *U : NewPCMarker->users()) {
    auto *Call = cast<CallInst>(U);

    // Report the instruction on the coverage CSV
    using namespace NewPCArguments;
    MetaAddress PC = addressFromNewPC(Call);
    uint64_t Size = getLimitedValue(Call->getArgOperand(InstructionSize));
    bool IsJT = JumpTargets.isJumpTarget(PC);

    // We already finished discovering new code to translate, so we can remove
    // the references to local variables as argument of the calls to newpc and
    // create room for more optimizations.
    if (Call->arg_size() != FixedArgCount) {
      SmallVector<Value *, 8> Args;
      auto *AI = Call->arg_begin();
      for (size_t Idx = 0; Idx < FixedArgCount; ++Idx, ++AI)
        Args.emplace_back(*AI);

      auto *NewCall = CallInst::Create(NewPCMarker, Args, "", Call);
      NewCall->setCallingConv(Call->getCallingConv());
      NewCall->setDebugLoc(Call->getDebugLoc());
      NewCall->copyMetadata(*Call);
      // Note: we intentionally do not copy attributes. We do not expect to have
      //       any and removing those on extra arguments leads to a mysterious
      //       failure in verify "Attribute after last parameter".

      revng_assert(Call->use_empty());
      CallsToRemove.push_back(Call);
    }
  }

  for (auto *Call : CallsToRemove)
    eraseFromParent(Call);
}

SmallSet<unsigned, 1> IT::preprocess(PTCInstructionList *InstructionList) {
  SmallSet<unsigned, 1> Result;

  for (unsigned I = 0; I < InstructionList->instruction_count; I++) {
    PTCInstruction &Instruction = InstructionList->instructions[I];
    switch (Instruction.opc) {
    case PTC_INSTRUCTION_op_movi_i32:
    case PTC_INSTRUCTION_op_movi_i64:
    case PTC_INSTRUCTION_op_mov_i32:
    case PTC_INSTRUCTION_op_mov_i64:
      break;
    default:
      continue;
    }

    const PTC::Instruction TheInstruction(&Instruction);
    unsigned OutArg = TheInstruction.OutArguments[0];
    PTCTemp *Temporary = ptc_temp_get(InstructionList, OutArg);

    if (!ptc_temp_is_global(InstructionList, OutArg))
      continue;

    if (0 != strcmp("btarget", Temporary->name))
      continue;

    for (unsigned J = I + 1; J < InstructionList->instruction_count; J++) {
      unsigned Opcode = InstructionList->instructions[J].opc;
      if (Opcode == PTC_INSTRUCTION_op_debug_insn_start)
        Result.insert(J);
    }

    break;
  }

  return Result;
}

CallInst *IT::emitNewPCCall(IRBuilder<> &Builder,
                            MetaAddress PC,
                            uint64_t Size,
                            Value *String) const {
  PointerType *Int8PtrTy = getStringPtrType(TheModule.getContext());
  auto *Int8NullPtr = ConstantPointerNull::get(Int8PtrTy);
  std::vector<Value *> Args = { BasicBlockID(PC).toValue(&TheModule),
                                Builder.getInt64(Size),
                                Builder.getInt32(-1),
                                Builder.getInt32(0),
                                String != nullptr ? String : Int8NullPtr,
                                Int8NullPtr };

  // Insert a call to NewPCMarker capturing all the local temporaries
  // This prevents SROA from transforming them in SSA values, which is bad
  // in case we have to split a basic block
  for (AllocaInst *Local : Variables.locals())
    Args.push_back(Local);

  return Builder.CreateCall(NewPCMarker, Args);
}

std::tuple<IT::TranslationResult, MDNode *, MetaAddress, MetaAddress>
IT::newInstruction(PTCInstruction *Instr,
                   PTCInstruction *Next,
                   MetaAddress StartPC,
                   MetaAddress EndPC,
                   bool IsFirst,
                   MetaAddress AbortAt) {
  using R = std::tuple<TranslationResult, MDNode *, MetaAddress, MetaAddress>;
  revng_assert(Instr != nullptr);

  LLVMContext &Context = TheModule.getContext();

  const PTC::Instruction TheInstruction(Instr);
  // A new original instruction, let's create a new metadata node
  // referencing it for all the next instructions to come
  MetaAddress PC = StartPC.replaceAddress(TheInstruction.pc());

  // Prevent translation of non-executable code
  if (not JumpTargets.isExecutableAddress(PC))
    return R{ Abort, nullptr, MetaAddress::invalid(), MetaAddress::invalid() };

  // Compute NextPC
  MetaAddress NextPC = MetaAddress::invalid();
  if (Next != nullptr)
    NextPC = StartPC.replaceAddress(PTC::Instruction(Next).pc());
  else
    NextPC = EndPC;

  PointerType *Int8PtrTy = getStringPtrType(Context);

  if (AbortAt.isValid() and NextPC.addressGreaterThan(AbortAt)) {
    emitNewPCCall(Builder, PC, 1, ConstantPointerNull::get(Int8PtrTy));
    return R{ Abort, nullptr, MetaAddress::invalid(), MetaAddress::invalid() };
  }

  MDNode *MDOriginalInstr = nullptr;
  Constant *String = nullptr;
  if (RecordASM) {
    std::stringstream OriginalStringStream;
    revng_assert(NextPC - PC);
    disassemble(OriginalStringStream, PC, *(NextPC - PC));
    std::string OriginalString = OriginalStringStream.str();

    // We don't deduplicate this string since performing a lookup each time is
    // increasingly expensive and we should have relatively few collisions
    std::string AddressName = JumpTargets.nameForAddress(PC);
    String = getUniqueString(&TheModule, OriginalString);

    auto *MDOriginalString = ConstantAsMetadata::get(String);
    auto *MDPC = ConstantAsMetadata::get(PC.toValue(&TheModule));
    MDOriginalInstr = MDNode::get(Context, { MDOriginalString, MDPC });
  } else {
    String = ConstantPointerNull::get(Int8PtrTy);
  }

  if (!IsFirst) {
    // Check if this PC already has a block and use it
    bool ShouldContinue;
    BasicBlock *DivergeTo = JumpTargets.newPC(PC, ShouldContinue);
    if (DivergeTo != nullptr) {
      Builder.CreateBr(DivergeTo);

      if (ShouldContinue) {
        // The block is empty, let's fill it
        Blocks.push_back(DivergeTo);
        Builder.SetInsertPoint(DivergeTo);
      } else {
        // The block contains already translated code, early exit
        return R{ Stop, MDOriginalInstr, PC, NextPC };
      }
    }
  }

  Variables.newBasicBlock();

  revng_assert(NextPC - PC);
  auto *Call = emitNewPCCall(Builder, PC, *(NextPC - PC), String);

  if (!IsFirst) {
    // Inform the JumpTargetManager about the new PC we met
    BasicBlock::iterator CurrentIt = Builder.GetInsertPoint();
    if (CurrentIt == Builder.GetInsertBlock()->begin())
      revng_assert(JumpTargets.getBlockAt(PC) == Builder.GetInsertBlock());
    else
      JumpTargets.registerInstruction(PC, Call);
  }

  return R{ Success, MDOriginalInstr, PC, NextPC };
}

IT::TranslationResult IT::translateCall(PTCInstruction *Instr) {
  const PTC::CallInstruction TheCall(Instr);

  std::vector<Value *> InArgs;

  for (uint64_t TemporaryId : TheCall.InArguments) {
    auto *Load = Variables.load(Builder, TemporaryId);
    if (Load == nullptr)
      return Abort;
    InArgs.push_back(Load);
  }

  const auto GetValueType = [](Value *Argument) { return Argument->getType(); };
  auto ValueTypes = llvm::map_range(InArgs, GetValueType);
  std::vector<Type *> InArgsType(ValueTypes.begin(), ValueTypes.end());

  // TODO: handle multiple return arguments
  revng_assert(TheCall.OutArguments.size() <= 1);

  Value *ResultDestination = nullptr;
  Type *ResultType = nullptr;

  if (TheCall.OutArguments.size() != 0) {
    ResultDestination = Variables.getOrCreate(TheCall.OutArguments[0]);
    if (ResultDestination == nullptr)
      return Abort;
    ResultType = getVariableType(ResultDestination);
  } else {
    ResultType = Builder.getVoidTy();
  }

  auto *CalleeType = FunctionType::get(ResultType,
                                       ArrayRef<Type *>(InArgsType),
                                       false);

  std::string HelperName = "helper_" + TheCall.helperName();
  FunctionCallee FDecl = TheModule.getOrInsertFunction(HelperName, CalleeType);

  FunctionTags::Helper.addTo(cast<Function>(skipCasts(FDecl.getCallee())));

  CallInst *Result = Builder.CreateCall(FDecl, InArgs);

  if (TheCall.OutArguments.size() != 0)
    Builder.CreateStore(Result, ResultDestination);

  return Success;
}

IT::TranslationResult
IT::translate(PTCInstruction *Instr, MetaAddress PC, MetaAddress NextPC) {
  const PTC::Instruction TheInstruction(Instr);

  std::vector<Value *> InArgs;
  for (uint64_t TemporaryId : TheInstruction.InArguments) {
    auto *Load = Variables.load(Builder, TemporaryId);
    if (Load == nullptr)
      return Abort;
    InArgs.push_back(Load);
  }

  auto ConstArgs = TheInstruction.ConstArguments;
  LastPC = PC;
  auto Result = translateOpcode(TheInstruction.opcode(),
                                ConstArgs.toVector(),
                                InArgs);

  // Check if there was an error while translating the instruction
  if (!Result)
    return Abort;

  size_t OutSize = TheInstruction.OutArguments.size();
  revng_assert(Result->size() == OutSize);

  // TODO: use ZipIterator here
  for (unsigned I = 0; I < Result->size(); I++) {
    auto *Destination = Variables.getOrCreate(TheInstruction.OutArguments[I]);
    if (Destination == nullptr)
      return Abort;

    auto *Store = Builder.CreateStore(Result.get()[I], Destination);

    if (PCH->affectsPC(Store)) {
      // This is a PC-related store
      PCH->handleStore(Builder, Store);
    } else {
      // If we're writing somewhere an immediate, register it for exploration
      if (auto *Constant = dyn_cast<ConstantInt>(Store->getValueOperand())) {
        MetaAddress Address = JumpTargets.fromPC(Constant->getLimitedValue());
        if (Address.isValid() and PC != Address and JumpTargets.isPC(Address)
            and not JumpTargets.hasJT(Address)) {
          JumpTargets.registerSimpleLiteral(Address);
        }
      }
    }
  }

  return Success;
}

void IT::registerDirectJumps() {

  for (BasicBlock *ExitBB : ExitBlocks) {
    auto [Result, NextPC] = PCH->getUniqueJumpTarget(ExitBB);
    if (Result == NextJumpTarget::Unique and JumpTargets.isPC(NextPC)
        and not JumpTargets.hasJT(NextPC)) {
      JumpTargets.registerJT(NextPC, JTReason::DirectJump);
    }
  }

  ExitBlocks.clear();
}

ErrorOr<std::vector<Value *>>
IT::translateOpcode(PTCOpcode Opcode,
                    std::vector<uint64_t> ConstArguments,
                    std::vector<Value *> InArguments) {
  LLVMContext &Context = TheModule.getContext();
  unsigned RegisterSize = getRegisterSize(Opcode);
  Type *RegisterType = nullptr;
  if (RegisterSize == 32)
    RegisterType = Builder.getInt32Ty();
  else if (RegisterSize == 64)
    RegisterType = Builder.getInt64Ty();
  else if (RegisterSize != 0)
    revng_unreachable("Unexpected register size");

  using v = std::vector<Value *>;
  switch (Opcode) {
  case PTC_INSTRUCTION_op_movi_i32:
  case PTC_INSTRUCTION_op_movi_i64:
    return v{ ConstantInt::get(RegisterType, ConstArguments[0]) };
  case PTC_INSTRUCTION_op_discard:
    // Let's overwrite the discarded temporary with a 0
    return v{ ConstantInt::get(RegisterType, 0) };
  case PTC_INSTRUCTION_op_mov_i32:
  case PTC_INSTRUCTION_op_mov_i64:
    return v{ Builder.CreateTrunc(InArguments[0], RegisterType) };
  case PTC_INSTRUCTION_op_setcond_i32:
  case PTC_INSTRUCTION_op_setcond_i64: {
    Value *Compare = createICmp(Builder,
                                ConstArguments[0],
                                InArguments[0],
                                InArguments[1]);
    // TODO: convert single-bit registers to i1
    return v{ Builder.CreateZExt(Compare, RegisterType) };
  }
  case PTC_INSTRUCTION_op_movcond_i32: // Resist the fallthrough temptation
  case PTC_INSTRUCTION_op_movcond_i64: {
    Value *Compare = createICmp(Builder,
                                ConstArguments[0],
                                InArguments[0],
                                InArguments[1]);
    Value *Select = Builder.CreateSelect(Compare,
                                         InArguments[2],
                                         InArguments[3]);
    return v{ Select };
  }
  case PTC_INSTRUCTION_op_qemu_ld_i32:
  case PTC_INSTRUCTION_op_qemu_ld_i64:
  case PTC_INSTRUCTION_op_qemu_st_i32:
  case PTC_INSTRUCTION_op_qemu_st_i64: {
    PTCLoadStoreArg MemoryAccess;
    MemoryAccess = ptc.parse_load_store_arg(ConstArguments[0]);

    // What are we supposed to do in this case?
    revng_assert(MemoryAccess.access_type != PTC_MEMORY_ACCESS_UNKNOWN);

    unsigned Alignment = 1;

    // Load size
    IntegerType *MemoryType = nullptr;
    switch (ptc_get_memory_access_size(MemoryAccess.type)) {
    case PTC_MO_8:
      MemoryType = Builder.getInt8Ty();
      break;
    case PTC_MO_16:
      MemoryType = Builder.getInt16Ty();
      break;
    case PTC_MO_32:
      MemoryType = Builder.getInt32Ty();
      break;
    case PTC_MO_64:
      MemoryType = Builder.getInt64Ty();
      break;
    default:
      revng_unreachable("Unexpected load size");
    }

    // If necessary, handle endianness mismatch
    // TODO: it might be a bit overkill, but it be nice to make this function
    //       template-parametric w.r.t. endianness mismatch
    Function *BSwapFunction = nullptr;
    if (MemoryType != Builder.getInt8Ty() and EndianessMismatch)
      BSwapFunction = Intrinsic::getDeclaration(&TheModule,
                                                Intrinsic::bswap,
                                                { MemoryType });

    bool SignExtend = ptc_is_sign_extended_load(MemoryAccess.type);

    Value *Pointer = nullptr;
    if (Opcode == PTC_INSTRUCTION_op_qemu_ld_i32
        || Opcode == PTC_INSTRUCTION_op_qemu_ld_i64) {

      Pointer = Builder.CreateIntToPtr(InArguments[0],
                                       MemoryType->getPointerTo());
      auto *Load = Builder.CreateAlignedLoad(MemoryType,
                                             Pointer,
                                             MaybeAlign(Alignment));
      Value *Loaded = Load;

      if (BSwapFunction != nullptr)
        Loaded = Builder.CreateCall(BSwapFunction, Load);

      if (SignExtend)
        return v{ Builder.CreateSExt(Loaded, RegisterType) };
      else
        return v{ Builder.CreateZExt(Loaded, RegisterType) };

    } else if (Opcode == PTC_INSTRUCTION_op_qemu_st_i32
               || Opcode == PTC_INSTRUCTION_op_qemu_st_i64) {

      Pointer = Builder.CreateIntToPtr(InArguments[1],
                                       MemoryType->getPointerTo());
      Value *Value = Builder.CreateTrunc(InArguments[0], MemoryType);

      if (BSwapFunction != nullptr)
        Value = Builder.CreateCall(BSwapFunction, Value);

      Builder.CreateAlignedStore(Value, Pointer, MaybeAlign(Alignment));

      return v{};
    } else {
      revng_unreachable("Unknown load type");
    }
  }
  case PTC_INSTRUCTION_op_ld8u_i32:
  case PTC_INSTRUCTION_op_ld8s_i32:
  case PTC_INSTRUCTION_op_ld16u_i32:
  case PTC_INSTRUCTION_op_ld16s_i32:
  case PTC_INSTRUCTION_op_ld_i32:
  case PTC_INSTRUCTION_op_ld8u_i64:
  case PTC_INSTRUCTION_op_ld8s_i64:
  case PTC_INSTRUCTION_op_ld16u_i64:
  case PTC_INSTRUCTION_op_ld16s_i64:
  case PTC_INSTRUCTION_op_ld32u_i64:
  case PTC_INSTRUCTION_op_ld32s_i64:
  case PTC_INSTRUCTION_op_ld_i64: {
    Value *Base = dyn_cast<LoadInst>(InArguments[0])->getPointerOperand();
    if (Base == nullptr || !Variables.isEnv(Base)) {
      // TODO: emit warning
      return std::errc::invalid_argument;
    }

    bool Signed;
    switch (Opcode) {
    case PTC_INSTRUCTION_op_ld_i32:
    case PTC_INSTRUCTION_op_ld_i64:

    case PTC_INSTRUCTION_op_ld8u_i32:
    case PTC_INSTRUCTION_op_ld16u_i32:
    case PTC_INSTRUCTION_op_ld8u_i64:
    case PTC_INSTRUCTION_op_ld16u_i64:
    case PTC_INSTRUCTION_op_ld32u_i64:
      Signed = false;
      break;
    case PTC_INSTRUCTION_op_ld8s_i32:
    case PTC_INSTRUCTION_op_ld16s_i32:
    case PTC_INSTRUCTION_op_ld8s_i64:
    case PTC_INSTRUCTION_op_ld16s_i64:
    case PTC_INSTRUCTION_op_ld32s_i64:
      Signed = true;
      break;
    default:
      revng_unreachable("Unexpected opcode");
    }

    unsigned LoadSize;
    switch (Opcode) {
    case PTC_INSTRUCTION_op_ld8u_i32:
    case PTC_INSTRUCTION_op_ld8s_i32:
    case PTC_INSTRUCTION_op_ld8u_i64:
    case PTC_INSTRUCTION_op_ld8s_i64:
      LoadSize = 1;
      break;
    case PTC_INSTRUCTION_op_ld16u_i32:
    case PTC_INSTRUCTION_op_ld16s_i32:
    case PTC_INSTRUCTION_op_ld16u_i64:
    case PTC_INSTRUCTION_op_ld16s_i64:
      LoadSize = 2;
      break;
    case PTC_INSTRUCTION_op_ld_i32:
    case PTC_INSTRUCTION_op_ld32u_i64:
    case PTC_INSTRUCTION_op_ld32s_i64:
      LoadSize = 4;
      break;
    case PTC_INSTRUCTION_op_ld_i64:
      LoadSize = 8;
      break;
    default:
      revng_unreachable("Unexpected opcode");
    }

    Value *Result = Variables.loadFromEnvOffset(Builder,
                                                LoadSize,
                                                ConstArguments[0]);
    revng_assert(Result != nullptr);

    // Zero/sign extend in the target dimension
    if (Signed)
      return v{ Builder.CreateSExt(Result, RegisterType) };
    else
      return v{ Builder.CreateZExt(Result, RegisterType) };
  }
  case PTC_INSTRUCTION_op_st8_i32:
  case PTC_INSTRUCTION_op_st16_i32:
  case PTC_INSTRUCTION_op_st_i32:
  case PTC_INSTRUCTION_op_st8_i64:
  case PTC_INSTRUCTION_op_st16_i64:
  case PTC_INSTRUCTION_op_st32_i64:
  case PTC_INSTRUCTION_op_st_i64: {
    unsigned StoreSize;
    switch (Opcode) {
    case PTC_INSTRUCTION_op_st8_i32:
    case PTC_INSTRUCTION_op_st8_i64:
      StoreSize = 1;
      break;
    case PTC_INSTRUCTION_op_st16_i32:
    case PTC_INSTRUCTION_op_st16_i64:
      StoreSize = 2;
      break;
    case PTC_INSTRUCTION_op_st_i32:
    case PTC_INSTRUCTION_op_st32_i64:
      StoreSize = 4;
      break;
    case PTC_INSTRUCTION_op_st_i64:
      StoreSize = 8;
      break;
    default:
      revng_unreachable("Unexpected opcode");
    }

    Value *Base = dyn_cast<LoadInst>(InArguments[1])->getPointerOperand();
    if (Base == nullptr || !Variables.isEnv(Base)) {
      // TODO: emit warning
      return std::errc::invalid_argument;
    }

    auto Result = Variables.storeToEnvOffset(Builder,
                                             StoreSize,
                                             ConstArguments[0],
                                             InArguments[0]);
    PCH->handleStore(Builder, *Result);

    return v{};
  }
  case PTC_INSTRUCTION_op_add_i32:
  case PTC_INSTRUCTION_op_sub_i32:
  case PTC_INSTRUCTION_op_mul_i32:
  case PTC_INSTRUCTION_op_div_i32:
  case PTC_INSTRUCTION_op_divu_i32:
  case PTC_INSTRUCTION_op_rem_i32:
  case PTC_INSTRUCTION_op_remu_i32:
  case PTC_INSTRUCTION_op_and_i32:
  case PTC_INSTRUCTION_op_or_i32:
  case PTC_INSTRUCTION_op_xor_i32:
  case PTC_INSTRUCTION_op_shl_i32:
  case PTC_INSTRUCTION_op_shr_i32:
  case PTC_INSTRUCTION_op_sar_i32:
  case PTC_INSTRUCTION_op_add_i64:
  case PTC_INSTRUCTION_op_sub_i64:
  case PTC_INSTRUCTION_op_mul_i64:
  case PTC_INSTRUCTION_op_div_i64:
  case PTC_INSTRUCTION_op_divu_i64:
  case PTC_INSTRUCTION_op_rem_i64:
  case PTC_INSTRUCTION_op_remu_i64:
  case PTC_INSTRUCTION_op_and_i64:
  case PTC_INSTRUCTION_op_or_i64:
  case PTC_INSTRUCTION_op_xor_i64:
  case PTC_INSTRUCTION_op_shl_i64:
  case PTC_INSTRUCTION_op_shr_i64:
  case PTC_INSTRUCTION_op_sar_i64: {
    // TODO: assert on sizes?
    Instruction::BinaryOps BinaryOp = opcodeToBinaryOp(Opcode);
    Value *Operation = Builder.CreateBinOp(BinaryOp,
                                           InArguments[0],
                                           InArguments[1]);
    return v{ Operation };
  }
  case PTC_INSTRUCTION_op_div2_i32:
  case PTC_INSTRUCTION_op_divu2_i32:
  case PTC_INSTRUCTION_op_div2_i64:
  case PTC_INSTRUCTION_op_divu2_i64: {
    Instruction::BinaryOps DivisionOp, RemainderOp;

    if (Opcode == PTC_INSTRUCTION_op_div2_i32
        || Opcode == PTC_INSTRUCTION_op_div2_i64) {
      DivisionOp = Instruction::SDiv;
      RemainderOp = Instruction::SRem;
    } else if (Opcode == PTC_INSTRUCTION_op_divu2_i32
               || Opcode == PTC_INSTRUCTION_op_divu2_i64) {
      DivisionOp = Instruction::UDiv;
      RemainderOp = Instruction::URem;
    } else {
      revng_unreachable("Unknown operation type");
    }

    // TODO: we're ignoring InArguments[1], which is the MSB
    // TODO: assert on sizes?
    Value *Division = Builder.CreateBinOp(DivisionOp,
                                          InArguments[0],
                                          InArguments[2]);
    Value *Remainder = Builder.CreateBinOp(RemainderOp,
                                           InArguments[0],
                                           InArguments[2]);
    return v{ Division, Remainder };
  }
  case PTC_INSTRUCTION_op_rotr_i32:
  case PTC_INSTRUCTION_op_rotr_i64:
  case PTC_INSTRUCTION_op_rotl_i32:
  case PTC_INSTRUCTION_op_rotl_i64: {
    Value *Bits = ConstantInt::get(RegisterType, RegisterSize);

    Instruction::BinaryOps FirstShiftOp, SecondShiftOp;
    if (Opcode == PTC_INSTRUCTION_op_rotl_i32
        || Opcode == PTC_INSTRUCTION_op_rotl_i64) {
      FirstShiftOp = Instruction::Shl;
      SecondShiftOp = Instruction::LShr;
    } else if (Opcode == PTC_INSTRUCTION_op_rotr_i32
               || Opcode == PTC_INSTRUCTION_op_rotr_i64) {
      FirstShiftOp = Instruction::LShr;
      SecondShiftOp = Instruction::Shl;
    } else {
      revng_unreachable("Unexpected opcode");
    }

    Value *FirstShift = Builder.CreateBinOp(FirstShiftOp,
                                            InArguments[0],
                                            InArguments[1]);
    Value *SecondShiftAmount = Builder.CreateSub(Bits, InArguments[1]);
    Value *SecondShift = Builder.CreateBinOp(SecondShiftOp,
                                             InArguments[0],
                                             SecondShiftAmount);

    return v{ Builder.CreateOr(FirstShift, SecondShift) };
  }
  case PTC_INSTRUCTION_op_deposit_i32:
  case PTC_INSTRUCTION_op_deposit_i64: {
    unsigned Position = ConstArguments[0];
    if (Position == RegisterSize)
      return v{ InArguments[0] };

    unsigned Length = ConstArguments[1];
    uint64_t Bits = 0;

    // Thou shall not << 32
    if (Length == RegisterSize)
      Bits = getMaxValue(RegisterSize);
    else
      Bits = (1 << Length) - 1;

    // result = (t1 & ~(bits << position)) | ((t2 & bits) << position)
    uint64_t BaseMask = ~(Bits << Position);
    Value *MaskedBase = Builder.CreateAnd(InArguments[0], BaseMask);
    Value *Deposit = Builder.CreateAnd(InArguments[1], Bits);
    Value *ShiftedDeposit = Builder.CreateShl(Deposit, Position);
    Value *Result = Builder.CreateOr(MaskedBase, ShiftedDeposit);

    return v{ Result };
  }
  case PTC_INSTRUCTION_op_ext8s_i32:
  case PTC_INSTRUCTION_op_ext16s_i32:
  case PTC_INSTRUCTION_op_ext8u_i32:
  case PTC_INSTRUCTION_op_ext16u_i32:
  case PTC_INSTRUCTION_op_ext8s_i64:
  case PTC_INSTRUCTION_op_ext16s_i64:
  case PTC_INSTRUCTION_op_ext32s_i64:
  case PTC_INSTRUCTION_op_ext8u_i64:
  case PTC_INSTRUCTION_op_ext16u_i64:
  case PTC_INSTRUCTION_op_ext32u_i64: {
    Type *SourceType = nullptr;
    switch (Opcode) {
    case PTC_INSTRUCTION_op_ext8s_i32:
    case PTC_INSTRUCTION_op_ext8u_i32:
    case PTC_INSTRUCTION_op_ext8s_i64:
    case PTC_INSTRUCTION_op_ext8u_i64:
      SourceType = Builder.getInt8Ty();
      break;
    case PTC_INSTRUCTION_op_ext16s_i32:
    case PTC_INSTRUCTION_op_ext16u_i32:
    case PTC_INSTRUCTION_op_ext16s_i64:
    case PTC_INSTRUCTION_op_ext16u_i64:
      SourceType = Builder.getInt16Ty();
      break;
    case PTC_INSTRUCTION_op_ext32s_i64:
    case PTC_INSTRUCTION_op_ext32u_i64:
      SourceType = Builder.getInt32Ty();
      break;
    default:
      revng_unreachable("Unexpected opcode");
    }

    Value *Truncated = Builder.CreateTrunc(InArguments[0], SourceType);

    switch (Opcode) {
    case PTC_INSTRUCTION_op_ext8s_i32:
    case PTC_INSTRUCTION_op_ext8s_i64:
    case PTC_INSTRUCTION_op_ext16s_i32:
    case PTC_INSTRUCTION_op_ext16s_i64:
    case PTC_INSTRUCTION_op_ext32s_i64:
      return v{ Builder.CreateSExt(Truncated, RegisterType) };
    case PTC_INSTRUCTION_op_ext8u_i32:
    case PTC_INSTRUCTION_op_ext8u_i64:
    case PTC_INSTRUCTION_op_ext16u_i32:
    case PTC_INSTRUCTION_op_ext16u_i64:
    case PTC_INSTRUCTION_op_ext32u_i64:
      return v{ Builder.CreateZExt(Truncated, RegisterType) };
    default:
      revng_unreachable("Unexpected opcode");
    }
  }
  case PTC_INSTRUCTION_op_not_i32:
  case PTC_INSTRUCTION_op_not_i64:
    return v{ Builder.CreateXor(InArguments[0], getMaxValue(RegisterSize)) };
  case PTC_INSTRUCTION_op_neg_i32:
  case PTC_INSTRUCTION_op_neg_i64: {
    auto *InitialValue = ConstantInt::get(RegisterType, 0);
    return v{ Builder.CreateSub(InitialValue, InArguments[0]) };
  }
  case PTC_INSTRUCTION_op_andc_i32:
  case PTC_INSTRUCTION_op_andc_i64:
  case PTC_INSTRUCTION_op_orc_i32:
  case PTC_INSTRUCTION_op_orc_i64:
  case PTC_INSTRUCTION_op_eqv_i32:
  case PTC_INSTRUCTION_op_eqv_i64: {
    Instruction::BinaryOps ExternalOp;
    switch (Opcode) {
    case PTC_INSTRUCTION_op_andc_i32:
    case PTC_INSTRUCTION_op_andc_i64:
      ExternalOp = Instruction::And;
      break;
    case PTC_INSTRUCTION_op_orc_i32:
    case PTC_INSTRUCTION_op_orc_i64:
      ExternalOp = Instruction::Or;
      break;
    case PTC_INSTRUCTION_op_eqv_i32:
    case PTC_INSTRUCTION_op_eqv_i64:
      ExternalOp = Instruction::Xor;
      break;
    default:
      revng_unreachable("Unexpected opcode");
    }

    Value *Negate = Builder.CreateXor(InArguments[1],
                                      getMaxValue(RegisterSize));
    Value *Result = Builder.CreateBinOp(ExternalOp, InArguments[0], Negate);
    return v{ Result };
  }
  case PTC_INSTRUCTION_op_nand_i32:
  case PTC_INSTRUCTION_op_nand_i64: {
    Value *AndValue = Builder.CreateAnd(InArguments[0], InArguments[1]);
    Value *Result = Builder.CreateXor(AndValue, getMaxValue(RegisterSize));
    return v{ Result };
  }
  case PTC_INSTRUCTION_op_nor_i32:
  case PTC_INSTRUCTION_op_nor_i64: {
    Value *OrValue = Builder.CreateOr(InArguments[0], InArguments[1]);
    Value *Result = Builder.CreateXor(OrValue, getMaxValue(RegisterSize));
    return v{ Result };
  }
  case PTC_INSTRUCTION_op_bswap16_i32:
  case PTC_INSTRUCTION_op_bswap32_i32:
  case PTC_INSTRUCTION_op_bswap16_i64:
  case PTC_INSTRUCTION_op_bswap32_i64:
  case PTC_INSTRUCTION_op_bswap64_i64: {
    Type *SwapType = nullptr;
    switch (Opcode) {
    case PTC_INSTRUCTION_op_bswap16_i32:
    case PTC_INSTRUCTION_op_bswap16_i64:
      SwapType = Builder.getInt16Ty();
      break;
    case PTC_INSTRUCTION_op_bswap32_i32:
    case PTC_INSTRUCTION_op_bswap32_i64:
      SwapType = Builder.getInt32Ty();
      break;
    case PTC_INSTRUCTION_op_bswap64_i64:
      SwapType = Builder.getInt64Ty();
      break;
    default:
      revng_unreachable("Unexpected opcode");
    }

    Value *Truncated = Builder.CreateTrunc(InArguments[0], SwapType);

    Function *BSwapFunction = Intrinsic::getDeclaration(&TheModule,
                                                        Intrinsic::bswap,
                                                        { SwapType });
    Value *Swapped = Builder.CreateCall(BSwapFunction, Truncated);

    return v{ Builder.CreateZExt(Swapped, RegisterType) };
  }
  case PTC_INSTRUCTION_op_set_label: {
    unsigned LabelId = ptc.get_arg_label_id(ConstArguments[0]);

    std::stringstream LabelSS;
    LabelSS << "bb." << JumpTargets.nameForAddress(LastPC);
    LabelSS << "_L" << std::dec << LabelId;
    std::string Label = LabelSS.str();

    BasicBlock *Fallthrough = nullptr;
    if (!LabeledBasicBlocks.contains(Label)) {
      Fallthrough = BasicBlock::Create(Context, Label, TheFunction);
      Fallthrough->moveAfter(Builder.GetInsertBlock());
      LabeledBasicBlocks[Label] = Fallthrough;
    } else {
      // A basic block with that label already exist
      Fallthrough = LabeledBasicBlocks[Label];

      // Ensure it's empty
      revng_assert(Fallthrough->begin() == Fallthrough->end());

      // Move it to the bottom
      Fallthrough->removeFromParent();
      TheFunction->insert(TheFunction->end(), Fallthrough);
    }

    Builder.CreateBr(Fallthrough);

    Blocks.push_back(Fallthrough);
    Builder.SetInsertPoint(Fallthrough);
    Variables.newBasicBlock();

    return v{};
  }
  case PTC_INSTRUCTION_op_br:
  case PTC_INSTRUCTION_op_brcond_i32:
  case PTC_INSTRUCTION_op_brcond2_i32:
  case PTC_INSTRUCTION_op_brcond_i64: {
    // We take the last constant arguments, which is the LabelId both in
    // conditional and unconditional jumps
    unsigned LabelId = ptc.get_arg_label_id(ConstArguments.back());

    std::stringstream LabelSS;
    LabelSS << "bb." << JumpTargets.nameForAddress(LastPC);
    LabelSS << "_L" << std::dec << LabelId;
    std::string Label = LabelSS.str();

    BasicBlock *Fallthrough = BasicBlock::Create(Context,
                                                 Label + "_ft",
                                                 TheFunction);

    // Look for a matching label
    BasicBlock *Target = nullptr;
    if (!LabeledBasicBlocks.contains(Label)) {
      // No matching label, create a temporary block
      Target = BasicBlock::Create(Context, Label, TheFunction);
      LabeledBasicBlocks[Label] = Target;
    } else {
      Target = LabeledBasicBlocks[Label];
    }

    if (Opcode == PTC_INSTRUCTION_op_br) {
      // Unconditional jump
      Builder.CreateBr(Target);
    } else if (Opcode == PTC_INSTRUCTION_op_brcond_i32
               || Opcode == PTC_INSTRUCTION_op_brcond_i64) {
      // Conditional jump
      Value *Compare = createICmp(Builder,
                                  ConstArguments[0],
                                  InArguments[0],
                                  InArguments[1]);
      Builder.CreateCondBr(Compare, Target, Fallthrough);
    } else {
      revng_unreachable("Unhandled opcode");
    }

    Blocks.push_back(Fallthrough);
    Builder.SetInsertPoint(Fallthrough);
    Variables.newBasicBlock();

    return v{};
  }
  case PTC_INSTRUCTION_op_exit_tb: {
    auto *Zero = ConstantInt::get(Type::getInt32Ty(Context), 0);
    Builder.CreateCall(JumpTargets.exitTB(), { Zero });
    Builder.CreateUnreachable();

    ExitBlocks.push_back(Builder.GetInsertBlock());

    auto *NextBB = BasicBlock::Create(Context, "", TheFunction);
    Blocks.push_back(NextBB);
    Builder.SetInsertPoint(NextBB);
    Variables.newBasicBlock();

    return v{};
  }
  case PTC_INSTRUCTION_op_goto_tb:
    // Nothing to do here
    return v{};
  case PTC_INSTRUCTION_op_add2_i32:
  case PTC_INSTRUCTION_op_sub2_i32:
  case PTC_INSTRUCTION_op_add2_i64:
  case PTC_INSTRUCTION_op_sub2_i64: {
    Value *FirstOpLow = nullptr;
    Value *FirstOpHigh = nullptr;
    Value *SecondOpLow = nullptr;
    Value *SecondOpHigh = nullptr;

    IntegerType *DestinationType = Builder.getIntNTy(RegisterSize * 2);

    FirstOpLow = Builder.CreateZExt(InArguments[0], DestinationType);
    FirstOpHigh = Builder.CreateZExt(InArguments[1], DestinationType);
    SecondOpLow = Builder.CreateZExt(InArguments[2], DestinationType);
    SecondOpHigh = Builder.CreateZExt(InArguments[3], DestinationType);

    FirstOpHigh = Builder.CreateShl(FirstOpHigh, RegisterSize);
    SecondOpHigh = Builder.CreateShl(SecondOpHigh, RegisterSize);

    Value *FirstOp = Builder.CreateOr(FirstOpHigh, FirstOpLow);
    Value *SecondOp = Builder.CreateOr(SecondOpHigh, SecondOpLow);

    Instruction::BinaryOps BinaryOp = opcodeToBinaryOp(Opcode);

    Value *Result = Builder.CreateBinOp(BinaryOp, FirstOp, SecondOp);

    Value *ResultLow = Builder.CreateTrunc(Result, RegisterType);
    Value *ShiftedResult = Builder.CreateLShr(Result, RegisterSize);
    Value *ResultHigh = Builder.CreateTrunc(ShiftedResult, RegisterType);

    return v{ ResultLow, ResultHigh };
  }
  case PTC_INSTRUCTION_op_mulu2_i32:
  case PTC_INSTRUCTION_op_mulu2_i64:
  case PTC_INSTRUCTION_op_muls2_i32:
  case PTC_INSTRUCTION_op_muls2_i64: {
    IntegerType *DestinationType = Builder.getIntNTy(RegisterSize * 2);

    Value *FirstOp = nullptr;
    Value *SecondOp = nullptr;

    if (Opcode == PTC_INSTRUCTION_op_mulu2_i32
        || Opcode == PTC_INSTRUCTION_op_mulu2_i64) {
      FirstOp = Builder.CreateZExt(InArguments[0], DestinationType);
      SecondOp = Builder.CreateZExt(InArguments[1], DestinationType);
    } else if (Opcode == PTC_INSTRUCTION_op_muls2_i32
               || Opcode == PTC_INSTRUCTION_op_muls2_i64) {
      FirstOp = Builder.CreateSExt(InArguments[0], DestinationType);
      SecondOp = Builder.CreateSExt(InArguments[1], DestinationType);
    } else {
      revng_unreachable("Unexpected opcode");
    }

    Value *Result = Builder.CreateMul(FirstOp, SecondOp);

    Value *ResultLow = Builder.CreateTrunc(Result, RegisterType);
    Value *ShiftedResult = Builder.CreateLShr(Result, RegisterSize);
    Value *ResultHigh = Builder.CreateTrunc(ShiftedResult, RegisterType);

    return v{ ResultLow, ResultHigh };
  }
  case PTC_INSTRUCTION_op_muluh_i32:
  case PTC_INSTRUCTION_op_mulsh_i32:
  case PTC_INSTRUCTION_op_muluh_i64:
  case PTC_INSTRUCTION_op_mulsh_i64:

  case PTC_INSTRUCTION_op_setcond2_i32:

  case PTC_INSTRUCTION_op_trunc_shr_i32:
    revng_unreachable("Instruction not implemented");
  default:
    revng_unreachable("Unknown opcode");
  }
}
