/*========================== begin_copyright_notice ============================

Copyright (C) 2020-2021 Intel Corporation

SPDX-License-Identifier: MIT

============================= end_copyright_notice ===========================*/

#include "OpSpec.hpp"
#include "../asserts.hpp"

using namespace iga;

bool OpSpec::hasImpicitEm() const {
  switch (op) {
  case Op::SYNC:
  case Op::NOP:
  case Op::ILLEGAL:
  case Op::JMPI:
  case Op::WAIT:
    return true;
  default:
    return false;
  }
}

bool OpSpec::hasDstSubregister(bool isMacro) const {
  if (isDpasFormat())
    return false;
  return !isMacro && !isAnySendFormat();
}

Region OpSpec::implicitDstRegion(bool isMacro) const {
  Region rgn = Region::INVALID;
  bool hasRgn = implicitDstRegion(rgn, isMacro);
  IGA_ASSERT(hasRgn, "dst does not have an implicit region");
  return rgn;
}

bool OpSpec::implicitDstRegion(Region &rgn, bool isMacro) const {
  if (isAnySendFormat()) {
    if (platform >= Platform::XE3) {
      rgn = Region::INVALID;
      return true;
    }
    rgn = Region::DST1;
    return true;
  } else if (isTypedBranch()) {
    // call and ret have an implicit <1> as well
    rgn = Region::DST1;
    return true;
  } else if (isMacro) {
    // e.g. madm and math.invm/sqrtm
    rgn = Region::DST1;
    return true;
  } else if (isDpasFormat()) {
    rgn = Region::DST1;
    return true;
  } else {
    rgn = Region::INVALID;
    return false;
  }
}

bool OpSpec::implicitDstTypeVal(Type &type) const {
  if (isAnySendFormat() && platform >= Platform::XE3) {
    type = Type::INVALID;
    return true;
  }
  if (isSendFormat() && platform >= Platform::GEN8) {
    type = Type::UD;
    if (platform >= Platform::XE) {
      type = Type::UB;
    }
    return true;
  } else if (isTypedBranch()) {
    type = Type::D;
    return true;
  }
  type = Type::INVALID;
  return false;
}

bool OpSpec::hasSrcSubregister(int srcOpIx, bool isMacro) const {
  // only src2 of dpas has a subregister
  if (isDpasFormat() && srcOpIx < 2)
    return false;
  // from the formatter used to use:
  //   emitSubReg = !os.isAnySendFormat() && (os.op != Op::BRC || srcIx != 1)
  // => why BRC?
  // send instructions and math macros (including madm) don't emit
  // subregisters
  return !isAnySendFormat() && !isMacro;
}

bool OpSpec::hasImplicitSrcRegion(int srcOpIx, ExecSize es,
                                  bool isMacro) const {
  if (isAnySendFormat() && platform >= Platform::XE3) {
    // Xe3 uses INVALID as a "default"
    // OpSpec::implicitSrcRegion uses Region::INVALID as no implicit
    // src region ==> make an exception for this case
    return true;
  }
  return implicitSrcRegion(srcOpIx, es, isMacro) != Region::INVALID;
}
Region OpSpec::implicitSrcRegion(int srcOpIx, ExecSize execSize,
                                 bool isMacro) const {
  // TODO: fold this into implicitSrcRegionPtr and elide the macro hacking
  if (isAnySendFormat() && platform >= Platform::XE3)
    return Region::INVALID;
  //
  // TODO: this needs to work off the table from BXML
  if (isSendFormat() && platform < Platform::XE) {
    return Region::SRC010;
  } else if (isMacro) {
    if (isTernary()) {
      // ternary macro: e.g. madm
      if (srcOpIx == 2) {
        return Region::SRCXX1;
      } else {
        if (execSize == ExecSize::SIMD1) {
          return Region::SRC0X0;
        } else if (platform >= Platform::XE) {
          return Region::SRC1X0;
        } else {
          return Region::SRC2X1;
        }
      }
    } else {
      // basic macro: e.g. math.invm ...
      // math doesn't have implicit region on XE+
      if (platform >= Platform::XE)
        return Region::INVALID;
      if (execSize == ExecSize::SIMD1)
        return Region::SRC010;
      else
        return Region::SRC221;
    }
  } else if (isSendFormat() || isSendsFormat()) {
    // no regions on send's
    return Region::SRC010;
  } else if (isDpasFormat()) {
    if (srcOpIx == 2) {
      // only an implicit region on src1
      return Region::SRCXX1;
    } else {
      return Region::SRC1X0;
    }
  } else {
    if (platform >= Platform::XE && isBranching())
      return Region::SRC110;

    if (srcOpIx == 0) {
      switch (op) {
      case Op::JMPI:
      case Op::CALL:
      case Op::CALLA:
      case Op::BRD:
        return Region::SRC010;
        // GED won't let us set 221
      case Op::BRC:
        return Region::SRC221;
      case Op::RET: {
        if (platform >= Platform::XE)
          return Region::SRC010;
        else
          return Region::SRC221;
      }
      case Op::SYNC:
        return Region::SRC010;
      default:; // fallthrough to return nullptr
      }
    } else if (srcOpIx == 1) {
      // Encoder encodes Src0 into Src1, we have to lie here;
      // <2;2,1> gets manually set in Src0 explicitly by
      // the encoder, this is just for Src1
      switch (op) {
      case Op::BRC:
      case Op::CALL:
      case Op::CALLA:
        return Region::SRC010;
      default:; // fallthrough to return nullptr
      }
    }
    return Region::INVALID;
  }
}

bool OpSpec::isTypedBranch() const {
  // TODO: reduce this set as we are able to normalize bits
  return op == Op::BRD || op == Op::BRC || op == Op::RET || op == Op::JMPI ||
         op == Op::CALL || op == Op::CALLA;
}

bool OpSpec::supportsAccWrEn() const {
  return
      // AccWrCtrl is removed from XeHPC
      (platform <= Platform::XE_HPG) && !supportsBranchCtrl() &&
      !isBranching() && !isAnySendFormat() && !is(Op::NOP) && !is(Op::ILLEGAL);
}

bool OpSpec::supportsSubfunction() const {
  return supportsBranchCtrl() ||
         isAnySendFormat()    ||
         op == Op::MATH       ||
         op == Op::SYNC       ||
         op == Op::BFN        ||
         isDpasFormat();
}


bool OpSpec::isBinaryWithExecDataType() const {
  return false;
}

bool OpSpec::hasDstHorzStride() const {
  return !isDpasFormat();
}

unsigned OpSpec::getSourceCount(Subfunction sf) const {
  if (is(Op::MATH)) {
    IGA_ASSERT(sf.isValid(), "invalid math function");
    return GetSourceCount(sf.math);
  } else if (format & UNARY)
    return 1;
  else if (format & BINARY)
    return 2;
  else if (format & TERNARY)
    return 3;
  else
    return 0;
}

bool OpSpec::isVariableLatency() const {
  return isAnySendFormat() ||
         (is(Op::MATH) && platform < Platform::XE_HPC) // XeHPC+ math is fixed
         || isDpasFormat();
}

bool OpSpec::isFixedLatency() const {
  switch (op) {
  case Op::SYNC:
  case Op::ILLEGAL:
  case Op::NOP:
    // these get shot down after DepChk
    return false;
  default:
    return !isVariableLatency();
  }
}

bool OpSpec::implicitSrcTypeVal(int srcOpIx, bool isImmOrLbl,
                                Type &type) const {
  if (isAnySendFormat() && platform >= Platform::XE3) {
    return false;
  }
       // TODO: pull from BXML data (ideally somehow in the syntax)
  if (isTypedBranch()) {
    // branches no longer take types in XE
    if (platform >= Platform::XE) {
      type = Type::INVALID;
      return true;
    }
    // e.g. jmpi, call, or brc
    //   jmpi  r12.3:d
    //   brd   r12.3:d
    // we make the
    if (op == Op::BRC) {
      //   brc   r12.3[:d]   null[:ud]
      //   brc   LABEL[:d]   LABEL[:d]
      type = srcOpIx == 0 || isImmOrLbl ? Type::D : Type::UD;
    } else {
      type = Type::D;
    }
    return true;
  } else if (isBranching()) {
    // if, else, endif, while, break, cont, goto, join, ...
    // let GED pick the defaults
    type = Type::INVALID;
    return true;
  } else if (isSendFormat() && platform < Platform::XE) {
    // TRB: we don't print the type on send instructions unless it's
    // not :ud, this allows us to phase out types on send operands
    // while meaningless, apparently there is a requirement on SKL
    // requiring the sampler to read the type from the operand.
    //
    // Types on sends are totally gone in XE.
    type = Type::UD;
    return true;
  } else if (isAnySendFormat()) {
    // for sends src0 is :ud, src1 has no type bits
    if (platform < Platform::XE) {
      type = srcOpIx == 0 ? Type::UD : Type::INVALID;
    } else {
      type = Type::UB;
    }
    return true;
  } else if (is(Op::SYNC)) {
    // sync imm32          has ud type
    // sync reg32          type is required
    // sync null           type is ommitted
    if (isImmOrLbl) {
      type = Type::UD;
      return true;
    }
    type = Type::INVALID;
    return false;
  } else {
    type = Type::INVALID;
    return false;
  }
}
