//===-- SPIRVInstPrinter.cpp - Output SPIR-V MCInsts as ASM -----*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This class prints a SPIR-V MCInst to a .s file.
//
//===----------------------------------------------------------------------===//

#include "SPIRVInstPrinter.h"
#include "SPIRV.h"
#include "SPIRVBaseInfo.h"
#include "llvm/CodeGen/Register.h"
#include "llvm/MC/MCAsmInfo.h"
#include "llvm/MC/MCExpr.h"
#include "llvm/MC/MCInst.h"
#include "llvm/MC/MCInstrInfo.h"
#include "llvm/MC/MCSymbol.h"
#include "llvm/Support/Casting.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/FormattedStream.h"

using namespace llvm;

#define DEBUG_TYPE "asm-printer"

// Include the auto-generated portion of the assembly writer.
#include "SPIRVGenAsmWriter.inc"

void SPIRVInstPrinter::printRemainingVariableOps(const MCInst *MI,
                                                 unsigned StartIndex,
                                                 raw_ostream &O,
                                                 bool SkipFirstSpace,
                                                 bool SkipImmediates) {
  const unsigned NumOps = MI->getNumOperands();
  for (unsigned i = StartIndex; i < NumOps; ++i) {
    if (!SkipImmediates || !MI->getOperand(i).isImm()) {
      if (!SkipFirstSpace || i != StartIndex)
        O << ' ';
      printOperand(MI, i, O);
    }
  }
}

void SPIRVInstPrinter::printOpConstantVarOps(const MCInst *MI,
                                             unsigned StartIndex,
                                             raw_ostream &O) {
  O << ' ';
  if (MI->getNumOperands() - StartIndex == 2) { // Handle 64 bit literals.
    uint64_t Imm = MI->getOperand(StartIndex).getImm();
    Imm |= (MI->getOperand(StartIndex + 1).getImm() << 32);
    O << Imm;
  } else {
    printRemainingVariableOps(MI, StartIndex, O, true, false);
  }
}

void SPIRVInstPrinter::recordOpExtInstImport(const MCInst *MI) {
  // TODO: insert {Reg, Set} into ExtInstSetIDs map.
}

void SPIRVInstPrinter::printInst(const MCInst *MI, uint64_t Address,
                                 StringRef Annot, const MCSubtargetInfo &STI,
                                 raw_ostream &OS) {
  const unsigned OpCode = MI->getOpcode();
  printInstruction(MI, Address, OS);

  if (OpCode == SPIRV::OpDecorate) {
    printOpDecorate(MI, OS);
  } else if (OpCode == SPIRV::OpExtInstImport) {
    recordOpExtInstImport(MI);
  } else if (OpCode == SPIRV::OpExtInst) {
    printOpExtInst(MI, OS);
  } else {
    // Print any extra operands for variadic instructions.
    MCInstrDesc MCDesc = MII.get(OpCode);
    if (MCDesc.isVariadic()) {
      const unsigned NumFixedOps = MCDesc.getNumOperands();
      const unsigned LastFixedIndex = NumFixedOps - 1;
      const int FirstVariableIndex = NumFixedOps;
      if (NumFixedOps > 0 &&
          MCDesc.OpInfo[LastFixedIndex].OperandType == MCOI::OPERAND_UNKNOWN) {
        // For instructions where a custom type (not reg or immediate) comes as
        // the last operand before the variable_ops. This is usually a StringImm
        // operand, but there are a few other cases.
        switch (OpCode) {
        case SPIRV::OpTypeImage:
          OS << ' ';
          printAccessQualifier(MI, FirstVariableIndex, OS);
          break;
        case SPIRV::OpVariable:
          OS << ' ';
          printOperand(MI, FirstVariableIndex, OS);
          break;
        case SPIRV::OpEntryPoint: {
          // Print the interface ID operands, skipping the name's string
          // literal.
          printRemainingVariableOps(MI, NumFixedOps, OS, false, true);
          break;
        }
        case SPIRV::OpExecutionMode:
        case SPIRV::OpExecutionModeId:
        case SPIRV::OpLoopMerge: {
          // Print any literals after the OPERAND_UNKNOWN argument normally.
          printRemainingVariableOps(MI, NumFixedOps, OS);
          break;
        }
        default:
          break; // printStringImm has already been handled
        }
      } else {
        // For instructions with no fixed ops or a reg/immediate as the final
        // fixed operand, we can usually print the rest with "printOperand", but
        // check for a few cases with custom types first.
        switch (OpCode) {
        case SPIRV::OpLoad:
        case SPIRV::OpStore:
          OS << ' ';
          printMemoryOperand(MI, FirstVariableIndex, OS);
          printRemainingVariableOps(MI, FirstVariableIndex + 1, OS);
          break;
        case SPIRV::OpImageSampleImplicitLod:
        case SPIRV::OpImageSampleDrefImplicitLod:
        case SPIRV::OpImageSampleProjImplicitLod:
        case SPIRV::OpImageSampleProjDrefImplicitLod:
        case SPIRV::OpImageFetch:
        case SPIRV::OpImageGather:
        case SPIRV::OpImageDrefGather:
        case SPIRV::OpImageRead:
        case SPIRV::OpImageWrite:
        case SPIRV::OpImageSparseSampleImplicitLod:
        case SPIRV::OpImageSparseSampleDrefImplicitLod:
        case SPIRV::OpImageSparseSampleProjImplicitLod:
        case SPIRV::OpImageSparseSampleProjDrefImplicitLod:
        case SPIRV::OpImageSparseFetch:
        case SPIRV::OpImageSparseGather:
        case SPIRV::OpImageSparseDrefGather:
        case SPIRV::OpImageSparseRead:
        case SPIRV::OpImageSampleFootprintNV:
          OS << ' ';
          printImageOperand(MI, FirstVariableIndex, OS);
          printRemainingVariableOps(MI, NumFixedOps + 1, OS);
          break;
        case SPIRV::OpCopyMemory:
        case SPIRV::OpCopyMemorySized: {
          const unsigned NumOps = MI->getNumOperands();
          for (unsigned i = NumFixedOps; i < NumOps; ++i) {
            OS << ' ';
            printMemoryOperand(MI, i, OS);
            if (MI->getOperand(i).getImm() &
                static_cast<unsigned>(SPIRV::MemoryOperand::Aligned)) {
              assert(i + 1 < NumOps && "Missing alignment operand");
              OS << ' ';
              printOperand(MI, i + 1, OS);
              i += 1;
            }
          }
          break;
        }
        case SPIRV::OpConstantI:
        case SPIRV::OpConstantF:
          printOpConstantVarOps(MI, NumFixedOps, OS);
          break;
        default:
          printRemainingVariableOps(MI, NumFixedOps, OS);
          break;
        }
      }
    }
  }

  printAnnotation(OS, Annot);
}

void SPIRVInstPrinter::printOpExtInst(const MCInst *MI, raw_ostream &O) {
  // The fixed operands have already been printed, so just need to decide what
  // type of ExtInst operands to print based on the instruction set and number.
  MCInstrDesc MCDesc = MII.get(MI->getOpcode());
  unsigned NumFixedOps = MCDesc.getNumOperands();
  const auto NumOps = MI->getNumOperands();
  if (NumOps == NumFixedOps)
    return;

  O << ' ';

  // TODO: implement special printing for OpenCLExtInst::vstor*.
  printRemainingVariableOps(MI, NumFixedOps, O, true);
}

void SPIRVInstPrinter::printOpDecorate(const MCInst *MI, raw_ostream &O) {
  // The fixed operands have already been printed, so just need to decide what
  // type of decoration operands to print based on the Decoration type.
  MCInstrDesc MCDesc = MII.get(MI->getOpcode());
  unsigned NumFixedOps = MCDesc.getNumOperands();

  if (NumFixedOps != MI->getNumOperands()) {
    auto DecOp = MI->getOperand(NumFixedOps - 1);
    auto Dec = static_cast<SPIRV::Decoration>(DecOp.getImm());

    O << ' ';

    switch (Dec) {
    case SPIRV::Decoration::BuiltIn:
      printBuiltIn(MI, NumFixedOps, O);
      break;
    case SPIRV::Decoration::UniformId:
      printScope(MI, NumFixedOps, O);
      break;
    case SPIRV::Decoration::FuncParamAttr:
      printFunctionParameterAttribute(MI, NumFixedOps, O);
      break;
    case SPIRV::Decoration::FPRoundingMode:
      printFPRoundingMode(MI, NumFixedOps, O);
      break;
    case SPIRV::Decoration::FPFastMathMode:
      printFPFastMathMode(MI, NumFixedOps, O);
      break;
    case SPIRV::Decoration::LinkageAttributes:
    case SPIRV::Decoration::UserSemantic:
      printStringImm(MI, NumFixedOps, O);
      break;
    default:
      printRemainingVariableOps(MI, NumFixedOps, O, true);
      break;
    }
  }
}

static void printExpr(const MCExpr *Expr, raw_ostream &O) {
#ifndef NDEBUG
  const MCSymbolRefExpr *SRE;

  if (const MCBinaryExpr *BE = dyn_cast<MCBinaryExpr>(Expr))
    SRE = cast<MCSymbolRefExpr>(BE->getLHS());
  else
    SRE = cast<MCSymbolRefExpr>(Expr);

  MCSymbolRefExpr::VariantKind Kind = SRE->getKind();

  assert(Kind == MCSymbolRefExpr::VK_None);
#endif
  O << *Expr;
}

void SPIRVInstPrinter::printOperand(const MCInst *MI, unsigned OpNo,
                                    raw_ostream &O, const char *Modifier) {
  assert((Modifier == 0 || Modifier[0] == 0) && "No modifiers supported");
  if (OpNo < MI->getNumOperands()) {
    const MCOperand &Op = MI->getOperand(OpNo);
    if (Op.isReg())
      O << '%' << (Register::virtReg2Index(Op.getReg()) + 1);
    else if (Op.isImm())
      O << formatImm((int64_t)Op.getImm());
    else if (Op.isDFPImm())
      O << formatImm((double)Op.getDFPImm());
    else if (Op.isExpr())
      printExpr(Op.getExpr(), O);
    else
      llvm_unreachable("Unexpected operand type");
  }
}

void SPIRVInstPrinter::printStringImm(const MCInst *MI, unsigned OpNo,
                                      raw_ostream &O) {
  const unsigned NumOps = MI->getNumOperands();
  unsigned StrStartIndex = OpNo;
  while (StrStartIndex < NumOps) {
    if (MI->getOperand(StrStartIndex).isReg())
      break;

    std::string Str = getSPIRVStringOperand(*MI, OpNo);
    if (StrStartIndex != OpNo)
      O << ' '; // Add a space if we're starting a new string/argument.
    O << '"';
    for (char c : Str) {
      if (c == '"')
        O.write('\\'); // Escape " characters (might break for complex UTF-8).
      O.write(c);
    }
    O << '"';

    unsigned numOpsInString = (Str.size() / 4) + 1;
    StrStartIndex += numOpsInString;

    // Check for final Op of "OpDecorate %x %stringImm %linkageAttribute".
    if (MI->getOpcode() == SPIRV::OpDecorate &&
        MI->getOperand(1).getImm() ==
            static_cast<unsigned>(SPIRV::Decoration::LinkageAttributes)) {
      O << ' ';
      printLinkageType(MI, StrStartIndex, O);
      break;
    }
  }
}

void SPIRVInstPrinter::printExtInst(const MCInst *MI, unsigned OpNo,
                                    raw_ostream &O) {
  llvm_unreachable("Unimplemented printExtInst");
}

void SPIRVInstPrinter::printCapability(const MCInst *MI, unsigned OpNo,
                                       raw_ostream &O) {
  if (OpNo < MI->getNumOperands()) {
    SPIRV::Capability e =
        static_cast<SPIRV::Capability>(MI->getOperand(OpNo).getImm());
    O << SPIRV::getCapabilityName(e);
  }
}

void SPIRVInstPrinter::printSourceLanguage(const MCInst *MI, unsigned OpNo,
                                           raw_ostream &O) {
  if (OpNo < MI->getNumOperands()) {
    SPIRV::SourceLanguage e =
        static_cast<SPIRV::SourceLanguage>(MI->getOperand(OpNo).getImm());
    O << SPIRV::getSourceLanguageName(e);
  }
}

void SPIRVInstPrinter::printExecutionModel(const MCInst *MI, unsigned OpNo,
                                           raw_ostream &O) {
  if (OpNo < MI->getNumOperands()) {
    SPIRV::ExecutionModel e =
        static_cast<SPIRV::ExecutionModel>(MI->getOperand(OpNo).getImm());
    O << SPIRV::getExecutionModelName(e);
  }
}

void SPIRVInstPrinter::printAddressingModel(const MCInst *MI, unsigned OpNo,
                                            raw_ostream &O) {
  if (OpNo < MI->getNumOperands()) {
    SPIRV::AddressingModel e =
        static_cast<SPIRV::AddressingModel>(MI->getOperand(OpNo).getImm());
    O << SPIRV::getAddressingModelName(e);
  }
}

void SPIRVInstPrinter::printMemoryModel(const MCInst *MI, unsigned OpNo,
                                        raw_ostream &O) {
  if (OpNo < MI->getNumOperands()) {
    SPIRV::MemoryModel e =
        static_cast<SPIRV::MemoryModel>(MI->getOperand(OpNo).getImm());
    O << SPIRV::getMemoryModelName(e);
  }
}

void SPIRVInstPrinter::printExecutionMode(const MCInst *MI, unsigned OpNo,
                                          raw_ostream &O) {
  if (OpNo < MI->getNumOperands()) {
    SPIRV::ExecutionMode e =
        static_cast<SPIRV::ExecutionMode>(MI->getOperand(OpNo).getImm());
    O << SPIRV::getExecutionModeName(e);
  }
}

void SPIRVInstPrinter::printStorageClass(const MCInst *MI, unsigned OpNo,
                                         raw_ostream &O) {
  if (OpNo < MI->getNumOperands()) {
    SPIRV::StorageClass e =
        static_cast<SPIRV::StorageClass>(MI->getOperand(OpNo).getImm());
    O << SPIRV::getStorageClassName(e);
  }
}

void SPIRVInstPrinter::printDim(const MCInst *MI, unsigned OpNo,
                                raw_ostream &O) {
  if (OpNo < MI->getNumOperands()) {
    SPIRV::Dim e = static_cast<SPIRV::Dim>(MI->getOperand(OpNo).getImm());
    O << SPIRV::getDimName(e);
  }
}

void SPIRVInstPrinter::printSamplerAddressingMode(const MCInst *MI,
                                                  unsigned OpNo,
                                                  raw_ostream &O) {
  if (OpNo < MI->getNumOperands()) {
    SPIRV::SamplerAddressingMode e = static_cast<SPIRV::SamplerAddressingMode>(
        MI->getOperand(OpNo).getImm());
    O << SPIRV::getSamplerAddressingModeName(e);
  }
}

void SPIRVInstPrinter::printSamplerFilterMode(const MCInst *MI, unsigned OpNo,
                                              raw_ostream &O) {
  if (OpNo < MI->getNumOperands()) {
    SPIRV::SamplerFilterMode e =
        static_cast<SPIRV::SamplerFilterMode>(MI->getOperand(OpNo).getImm());
    O << SPIRV::getSamplerFilterModeName(e);
  }
}

void SPIRVInstPrinter::printImageFormat(const MCInst *MI, unsigned OpNo,
                                        raw_ostream &O) {
  if (OpNo < MI->getNumOperands()) {
    SPIRV::ImageFormat e =
        static_cast<SPIRV::ImageFormat>(MI->getOperand(OpNo).getImm());
    O << SPIRV::getImageFormatName(e);
  }
}

void SPIRVInstPrinter::printImageChannelOrder(const MCInst *MI, unsigned OpNo,
                                              raw_ostream &O) {
  if (OpNo < MI->getNumOperands()) {
    SPIRV::ImageChannelOrder e =
        static_cast<SPIRV::ImageChannelOrder>(MI->getOperand(OpNo).getImm());
    O << SPIRV::getImageChannelOrderName(e);
  }
}

void SPIRVInstPrinter::printImageChannelDataType(const MCInst *MI,
                                                 unsigned OpNo,
                                                 raw_ostream &O) {
  if (OpNo < MI->getNumOperands()) {
    SPIRV::ImageChannelDataType e =
        static_cast<SPIRV::ImageChannelDataType>(MI->getOperand(OpNo).getImm());
    O << SPIRV::getImageChannelDataTypeName(e);
  }
}

void SPIRVInstPrinter::printImageOperand(const MCInst *MI, unsigned OpNo,
                                         raw_ostream &O) {
  if (OpNo < MI->getNumOperands()) {
    unsigned e = static_cast<unsigned>(MI->getOperand(OpNo).getImm());
    O << SPIRV::getImageOperandName(e);
  }
}

void SPIRVInstPrinter::printFPFastMathMode(const MCInst *MI, unsigned OpNo,
                                           raw_ostream &O) {
  if (OpNo < MI->getNumOperands()) {
    unsigned e = static_cast<unsigned>(MI->getOperand(OpNo).getImm());
    O << SPIRV::getFPFastMathModeName(e);
  }
}

void SPIRVInstPrinter::printFPRoundingMode(const MCInst *MI, unsigned OpNo,
                                           raw_ostream &O) {
  if (OpNo < MI->getNumOperands()) {
    SPIRV::FPRoundingMode e =
        static_cast<SPIRV::FPRoundingMode>(MI->getOperand(OpNo).getImm());
    O << SPIRV::getFPRoundingModeName(e);
  }
}

void SPIRVInstPrinter::printLinkageType(const MCInst *MI, unsigned OpNo,
                                        raw_ostream &O) {
  if (OpNo < MI->getNumOperands()) {
    SPIRV::LinkageType e =
        static_cast<SPIRV::LinkageType>(MI->getOperand(OpNo).getImm());
    O << SPIRV::getLinkageTypeName(e);
  }
}

void SPIRVInstPrinter::printAccessQualifier(const MCInst *MI, unsigned OpNo,
                                            raw_ostream &O) {
  if (OpNo < MI->getNumOperands()) {
    SPIRV::AccessQualifier e =
        static_cast<SPIRV::AccessQualifier>(MI->getOperand(OpNo).getImm());
    O << SPIRV::getAccessQualifierName(e);
  }
}

void SPIRVInstPrinter::printFunctionParameterAttribute(const MCInst *MI,
                                                       unsigned OpNo,
                                                       raw_ostream &O) {
  if (OpNo < MI->getNumOperands()) {
    SPIRV::FunctionParameterAttribute e =
        static_cast<SPIRV::FunctionParameterAttribute>(
            MI->getOperand(OpNo).getImm());
    O << SPIRV::getFunctionParameterAttributeName(e);
  }
}

void SPIRVInstPrinter::printDecoration(const MCInst *MI, unsigned OpNo,
                                       raw_ostream &O) {
  if (OpNo < MI->getNumOperands()) {
    SPIRV::Decoration e =
        static_cast<SPIRV::Decoration>(MI->getOperand(OpNo).getImm());
    O << SPIRV::getDecorationName(e);
  }
}

void SPIRVInstPrinter::printBuiltIn(const MCInst *MI, unsigned OpNo,
                                    raw_ostream &O) {
  if (OpNo < MI->getNumOperands()) {
    SPIRV::BuiltIn e =
        static_cast<SPIRV::BuiltIn>(MI->getOperand(OpNo).getImm());
    O << SPIRV::getBuiltInName(e);
  }
}

void SPIRVInstPrinter::printSelectionControl(const MCInst *MI, unsigned OpNo,
                                             raw_ostream &O) {
  if (OpNo < MI->getNumOperands()) {
    unsigned e = static_cast<unsigned>(MI->getOperand(OpNo).getImm());
    O << SPIRV::getSelectionControlName(e);
  }
}

void SPIRVInstPrinter::printLoopControl(const MCInst *MI, unsigned OpNo,
                                        raw_ostream &O) {
  if (OpNo < MI->getNumOperands()) {
    unsigned e = static_cast<unsigned>(MI->getOperand(OpNo).getImm());
    O << SPIRV::getLoopControlName(e);
  }
}

void SPIRVInstPrinter::printFunctionControl(const MCInst *MI, unsigned OpNo,
                                            raw_ostream &O) {
  if (OpNo < MI->getNumOperands()) {
    unsigned e = static_cast<unsigned>(MI->getOperand(OpNo).getImm());
    O << SPIRV::getFunctionControlName(e);
  }
}

void SPIRVInstPrinter::printMemorySemantics(const MCInst *MI, unsigned OpNo,
                                            raw_ostream &O) {
  if (OpNo < MI->getNumOperands()) {
    unsigned e = static_cast<unsigned>(MI->getOperand(OpNo).getImm());
    O << SPIRV::getMemorySemanticsName(e);
  }
}

void SPIRVInstPrinter::printMemoryOperand(const MCInst *MI, unsigned OpNo,
                                          raw_ostream &O) {
  if (OpNo < MI->getNumOperands()) {
    unsigned e = static_cast<unsigned>(MI->getOperand(OpNo).getImm());
    O << SPIRV::getMemoryOperandName(e);
  }
}

void SPIRVInstPrinter::printScope(const MCInst *MI, unsigned OpNo,
                                  raw_ostream &O) {
  if (OpNo < MI->getNumOperands()) {
    SPIRV::Scope e = static_cast<SPIRV::Scope>(MI->getOperand(OpNo).getImm());
    O << SPIRV::getScopeName(e);
  }
}

void SPIRVInstPrinter::printGroupOperation(const MCInst *MI, unsigned OpNo,
                                           raw_ostream &O) {
  if (OpNo < MI->getNumOperands()) {
    SPIRV::GroupOperation e =
        static_cast<SPIRV::GroupOperation>(MI->getOperand(OpNo).getImm());
    O << SPIRV::getGroupOperationName(e);
  }
}

void SPIRVInstPrinter::printKernelEnqueueFlags(const MCInst *MI, unsigned OpNo,
                                               raw_ostream &O) {
  if (OpNo < MI->getNumOperands()) {
    SPIRV::KernelEnqueueFlags e =
        static_cast<SPIRV::KernelEnqueueFlags>(MI->getOperand(OpNo).getImm());
    O << SPIRV::getKernelEnqueueFlagsName(e);
  }
}

void SPIRVInstPrinter::printKernelProfilingInfo(const MCInst *MI, unsigned OpNo,
                                                raw_ostream &O) {
  if (OpNo < MI->getNumOperands()) {
    SPIRV::KernelProfilingInfo e =
        static_cast<SPIRV::KernelProfilingInfo>(MI->getOperand(OpNo).getImm());
    O << SPIRV::getKernelProfilingInfoName(e);
  }
}
