Error InstInternal::queryRWInfo()

in erts/emulator/asmjit/x86/x86instapi.cpp [812:1344]


Error InstInternal::queryRWInfo(uint32_t arch, const BaseInst& inst, const Operand_* operands, size_t opCount, InstRWInfo* out) noexcept {
  using namespace Status;

  // Only called when `arch` matches X86 family.
  ASMJIT_ASSERT(Environment::isFamilyX86(arch));

  // Get the instruction data.
  uint32_t instId = inst.id();
  if (ASMJIT_UNLIKELY(!Inst::isDefinedId(instId)))
    return DebugUtils::errored(kErrorInvalidInstruction);

  // Read/Write flags.
  const InstDB::CommonInfoTableB& tabB = InstDB::_commonInfoTableB[InstDB::_instInfoTable[instId]._commonInfoIndexB];
  const InstDB::RWFlagsInfoTable& rwFlags = InstDB::_rwFlagsInfoTable[tabB._rwFlagsIndex];


  // There are two data tables, one for `opCount == 2` and the second for
  // `opCount != 2`. There are two reasons for that:
  //   - There are instructions that share the same name that have both 2
  //     or 3 operands, which have different RW information / semantics.
  //   - There must be 2 tables otherwise the lookup index won't fit into
  //     8 bits (there is more than 256 records of combined rwInfo A and B).
  const InstDB::RWInfo& instRwInfo = opCount == 2 ? InstDB::rwInfoA[InstDB::rwInfoIndexA[instId]]
                                                  : InstDB::rwInfoB[InstDB::rwInfoIndexB[instId]];
  const InstDB::RWInfoRm& instRmInfo = InstDB::rwInfoRm[instRwInfo.rmInfo];

  out->_instFlags = 0;
  out->_opCount = uint8_t(opCount);
  out->_rmFeature = instRmInfo.rmFeature;
  out->_extraReg.reset();
  out->_readFlags = rwFlags.readFlags;
  out->_writeFlags = rwFlags.writeFlags;

  uint32_t nativeGpSize = Environment::registerSizeFromArch(arch);

  constexpr uint32_t R = OpRWInfo::kRead;
  constexpr uint32_t W = OpRWInfo::kWrite;
  constexpr uint32_t X = OpRWInfo::kRW;
  constexpr uint32_t RegM = OpRWInfo::kRegMem;
  constexpr uint32_t RegPhys = OpRWInfo::kRegPhysId;
  constexpr uint32_t MibRead = OpRWInfo::kMemBaseRead | OpRWInfo::kMemIndexRead;

  if (instRwInfo.category == InstDB::RWInfo::kCategoryGeneric) {
    uint32_t i;
    uint32_t rmOpsMask = 0;
    uint32_t rmMaxSize = 0;

    for (i = 0; i < opCount; i++) {
      OpRWInfo& op = out->_operands[i];
      const Operand_& srcOp = operands[i];
      const InstDB::RWInfoOp& rwOpData = InstDB::rwInfoOp[instRwInfo.opInfoIndex[i]];

      if (!srcOp.isRegOrMem()) {
        op.reset();
        continue;
      }

      op._opFlags = rwOpData.flags & ~(OpRWInfo::kZExt);
      op._physId = rwOpData.physId;
      op._rmSize = 0;
      op._resetReserved();

      uint64_t rByteMask = rwOpData.rByteMask;
      uint64_t wByteMask = rwOpData.wByteMask;

      if (op.isRead()  && !rByteMask) rByteMask = Support::lsbMask<uint64_t>(srcOp.size());
      if (op.isWrite() && !wByteMask) wByteMask = Support::lsbMask<uint64_t>(srcOp.size());

      op._readByteMask = rByteMask;
      op._writeByteMask = wByteMask;
      op._extendByteMask = 0;

      if (srcOp.isReg()) {
        // Zero extension.
        if (op.isWrite()) {
          if (srcOp.as<Reg>().isGp()) {
            // GP registers on X64 are special:
            //   - 8-bit and 16-bit writes aren't zero extended.
            //   - 32-bit writes ARE zero extended.
            rwZeroExtendGp(op, srcOp.as<Gp>(), nativeGpSize);
          }
          else if (rwOpData.flags & OpRWInfo::kZExt) {
            // Otherwise follow ZExt.
            rwZeroExtendNonVec(op, srcOp.as<Gp>());
          }
        }

        // Aggregate values required to calculate valid Reg/M info.
        rmMaxSize  = Support::max(rmMaxSize, srcOp.size());
        rmOpsMask |= Support::bitMask<uint32_t>(i);
      }
      else {
        const x86::Mem& memOp = srcOp.as<x86::Mem>();
        // The RW flags of BASE+INDEX are either provided by the data, which means
        // that the instruction is border-case, or they are deduced from the operand.
        if (memOp.hasBaseReg() && !(op.opFlags() & OpRWInfo::kMemBaseRW))
          op.addOpFlags(OpRWInfo::kMemBaseRead);
        if (memOp.hasIndexReg() && !(op.opFlags() & OpRWInfo::kMemIndexRW))
          op.addOpFlags(OpRWInfo::kMemIndexRead);
      }
    }

    rmOpsMask &= instRmInfo.rmOpsMask;
    if (rmOpsMask) {
      Support::BitWordIterator<uint32_t> it(rmOpsMask);
      do {
        i = it.next();

        OpRWInfo& op = out->_operands[i];
        op.addOpFlags(RegM);

        switch (instRmInfo.category) {
          case InstDB::RWInfoRm::kCategoryFixed:
            op.setRmSize(instRmInfo.fixedSize);
            break;
          case InstDB::RWInfoRm::kCategoryConsistent:
            op.setRmSize(operands[i].size());
            break;
          case InstDB::RWInfoRm::kCategoryHalf:
            op.setRmSize(rmMaxSize / 2u);
            break;
          case InstDB::RWInfoRm::kCategoryQuarter:
            op.setRmSize(rmMaxSize / 4u);
            break;
          case InstDB::RWInfoRm::kCategoryEighth:
            op.setRmSize(rmMaxSize / 8u);
            break;
        }
      } while (it.hasNext());
    }

    return kErrorOk;
  }

  switch (instRwInfo.category) {
    case InstDB::RWInfo::kCategoryMov: {
      // Special case for 'movhpd' instruction. Here there are some variants that
      // we have to handle as mov can be used to move between GP, segment, control
      // and debug registers. Moving between GP registers also allow to use memory
      // operand.

      if (opCount == 2) {
        if (operands[0].isReg() && operands[1].isReg()) {
          const Reg& o0 = operands[0].as<Reg>();
          const Reg& o1 = operands[1].as<Reg>();

          if (o0.isGp() && o1.isGp()) {
            out->_operands[0].reset(W | RegM, operands[0].size());
            out->_operands[1].reset(R | RegM, operands[1].size());

            rwZeroExtendGp(out->_operands[0], operands[0].as<Gp>(), nativeGpSize);
            return kErrorOk;
          }

          if (o0.isGp() && o1.isSReg()) {
            out->_operands[0].reset(W | RegM, nativeGpSize);
            out->_operands[0].setRmSize(2);
            out->_operands[1].reset(R, 2);
            return kErrorOk;
          }

          if (o0.isSReg() && o1.isGp()) {
            out->_operands[0].reset(W, 2);
            out->_operands[1].reset(R | RegM, 2);
            out->_operands[1].setRmSize(2);
            return kErrorOk;
          }

          if (o0.isGp() && (o1.isCReg() || o1.isDReg())) {
            out->_operands[0].reset(W, nativeGpSize);
            out->_operands[1].reset(R, nativeGpSize);
            out->_writeFlags = kOF | kSF | kZF | kAF | kPF | kCF;
            return kErrorOk;
          }

          if ((o0.isCReg() || o0.isDReg()) && o1.isGp()) {
            out->_operands[0].reset(W, nativeGpSize);
            out->_operands[1].reset(R, nativeGpSize);
            out->_writeFlags = kOF | kSF | kZF | kAF | kPF | kCF;
            return kErrorOk;
          }
        }

        if (operands[0].isReg() && operands[1].isMem()) {
          const Reg& o0 = operands[0].as<Reg>();
          const Mem& o1 = operands[1].as<Mem>();

          if (o0.isGp()) {
            if (!o1.isOffset64Bit())
              out->_operands[0].reset(W, o0.size());
            else
              out->_operands[0].reset(W | RegPhys, o0.size(), Gp::kIdAx);

            out->_operands[1].reset(R | MibRead, o0.size());
            rwZeroExtendGp(out->_operands[0], operands[0].as<Gp>(), nativeGpSize);
            return kErrorOk;
          }

          if (o0.isSReg()) {
            out->_operands[0].reset(W, 2);
            out->_operands[1].reset(R, 2);
            return kErrorOk;
          }
        }

        if (operands[0].isMem() && operands[1].isReg()) {
          const Mem& o0 = operands[0].as<Mem>();
          const Reg& o1 = operands[1].as<Reg>();

          if (o1.isGp()) {
            out->_operands[0].reset(W | MibRead, o1.size());
            if (!o0.isOffset64Bit())
              out->_operands[1].reset(R, o1.size());
            else
              out->_operands[1].reset(R | RegPhys, o1.size(), Gp::kIdAx);
            return kErrorOk;
          }

          if (o1.isSReg()) {
            out->_operands[0].reset(W | MibRead, 2);
            out->_operands[1].reset(R, 2);
            return kErrorOk;
          }
        }

        if (Reg::isGp(operands[0]) && operands[1].isImm()) {
          const Reg& o0 = operands[0].as<Reg>();
          out->_operands[0].reset(W | RegM, o0.size());
          out->_operands[1].reset();

          rwZeroExtendGp(out->_operands[0], operands[0].as<Gp>(), nativeGpSize);
          return kErrorOk;
        }

        if (operands[0].isMem() && operands[1].isImm()) {
          const Reg& o0 = operands[0].as<Reg>();
          out->_operands[0].reset(W | MibRead, o0.size());
          out->_operands[1].reset();
          return kErrorOk;
        }
      }
      break;
    }

    case InstDB::RWInfo::kCategoryImul: {
      // Special case for 'imul' instruction.
      //
      // There are 3 variants in general:
      //
      //   1. Standard multiplication: 'A = A * B'.
      //   2. Multiplication with imm: 'A = B * C'.
      //   3. Extended multiplication: 'A:B = B * C'.

      if (opCount == 2) {
        if (operands[0].isReg() && operands[1].isImm()) {
          out->_operands[0].reset(X, operands[0].size());
          out->_operands[1].reset();

          rwZeroExtendGp(out->_operands[0], operands[0].as<Gp>(), nativeGpSize);
          return kErrorOk;
        }

        if (Reg::isGpw(operands[0]) && operands[1].size() == 1) {
          // imul ax, r8/m8 <- AX = AL * r8/m8
          out->_operands[0].reset(X | RegPhys, 2, Gp::kIdAx);
          out->_operands[0].setReadByteMask(Support::lsbMask<uint64_t>(1));
          out->_operands[1].reset(R | RegM, 1);
        }
        else {
          // imul r?, r?/m?
          out->_operands[0].reset(X, operands[0].size());
          out->_operands[1].reset(R | RegM, operands[0].size());
          rwZeroExtendGp(out->_operands[0], operands[0].as<Gp>(), nativeGpSize);
        }

        if (operands[1].isMem())
          out->_operands[1].addOpFlags(MibRead);
        return kErrorOk;
      }

      if (opCount == 3) {
        if (operands[2].isImm()) {
          out->_operands[0].reset(W, operands[0].size());
          out->_operands[1].reset(R | RegM, operands[1].size());
          out->_operands[2].reset();

          rwZeroExtendGp(out->_operands[0], operands[0].as<Gp>(), nativeGpSize);
          if (operands[1].isMem())
            out->_operands[1].addOpFlags(MibRead);
          return kErrorOk;
        }
        else {
          out->_operands[0].reset(W | RegPhys, operands[0].size(), Gp::kIdDx);
          out->_operands[1].reset(X | RegPhys, operands[1].size(), Gp::kIdAx);
          out->_operands[2].reset(R | RegM, operands[2].size());

          rwZeroExtendGp(out->_operands[0], operands[0].as<Gp>(), nativeGpSize);
          rwZeroExtendGp(out->_operands[1], operands[1].as<Gp>(), nativeGpSize);
          if (operands[2].isMem())
            out->_operands[2].addOpFlags(MibRead);
          return kErrorOk;
        }
      }
      break;
    }

    case InstDB::RWInfo::kCategoryMovh64: {
      // Special case for 'movhpd|movhps' instructions. Note that this is only
      // required for legacy (non-AVX) variants as AVX instructions use either
      // 2 or 3 operands that are use `kCategoryGeneric`.
      if (opCount == 2) {
        if (BaseReg::isVec(operands[0]) && operands[1].isMem()) {
          out->_operands[0].reset(W, 8);
          out->_operands[0].setWriteByteMask(Support::lsbMask<uint64_t>(8) << 8);
          out->_operands[1].reset(R | MibRead, 8);
          return kErrorOk;
        }

        if (operands[0].isMem() && BaseReg::isVec(operands[1])) {
          out->_operands[0].reset(W | MibRead, 8);
          out->_operands[1].reset(R, 8);
          out->_operands[1].setReadByteMask(Support::lsbMask<uint64_t>(8) << 8);
          return kErrorOk;
        }
      }
      break;
    }

    case InstDB::RWInfo::kCategoryVmaskmov: {
      // Special case for 'vmaskmovpd|vmaskmovps|vpmaskmovd|vpmaskmovq' instructions.
      if (opCount == 3) {
        if (BaseReg::isVec(operands[0]) && BaseReg::isVec(operands[1]) && operands[2].isMem()) {
          out->_operands[0].reset(W, operands[0].size());
          out->_operands[1].reset(R, operands[1].size());
          out->_operands[2].reset(R | MibRead, operands[1].size());

          rwZeroExtendAvxVec(out->_operands[0], operands[0].as<Vec>());
          return kErrorOk;
        }

        if (operands[0].isMem() && BaseReg::isVec(operands[1]) && BaseReg::isVec(operands[2])) {
          out->_operands[0].reset(X | MibRead, operands[1].size());
          out->_operands[1].reset(R, operands[1].size());
          out->_operands[2].reset(R, operands[2].size());
          return kErrorOk;
        }
      }
      break;
    }

    case InstDB::RWInfo::kCategoryVmovddup: {
      // Special case for 'vmovddup' instruction. This instruction has an
      // interesting semantic as 128-bit XMM version only uses 64-bit memory
      // operand (m64), however, 256/512-bit versions use 256/512-bit memory
      // operand, respectively.
      if (opCount == 2) {
        if (BaseReg::isVec(operands[0]) && BaseReg::isVec(operands[1])) {
          uint32_t o0Size = operands[0].size();
          uint32_t o1Size = o0Size == 16 ? 8 : o0Size;

          out->_operands[0].reset(W, o0Size);
          out->_operands[1].reset(R | RegM, o1Size);
          out->_operands[1]._readByteMask &= 0x00FF00FF00FF00FFu;

          rwZeroExtendAvxVec(out->_operands[0], operands[0].as<Vec>());
          return kErrorOk;
        }

        if (BaseReg::isVec(operands[0]) && operands[1].isMem()) {
          uint32_t o0Size = operands[0].size();
          uint32_t o1Size = o0Size == 16 ? 8 : o0Size;

          out->_operands[0].reset(W, o0Size);
          out->_operands[1].reset(R | MibRead, o1Size);

          rwZeroExtendAvxVec(out->_operands[0], operands[0].as<Vec>());
          return kErrorOk;
        }
      }
      break;
    }

    case InstDB::RWInfo::kCategoryVmovmskpd:
    case InstDB::RWInfo::kCategoryVmovmskps: {
      // Special case for 'vmovmskpd|vmovmskps' instructions.
      if (opCount == 2) {
        if (BaseReg::isGp(operands[0]) && BaseReg::isVec(operands[1])) {
          out->_operands[0].reset(W, 1);
          out->_operands[0].setExtendByteMask(Support::lsbMask<uint32_t>(nativeGpSize - 1) << 1);
          out->_operands[1].reset(R, operands[1].size());
          return kErrorOk;
        }
      }
      break;
    }

    case InstDB::RWInfo::kCategoryVmov1_2:
    case InstDB::RWInfo::kCategoryVmov1_4:
    case InstDB::RWInfo::kCategoryVmov1_8: {
      // Special case for instructions where the destination is 1:N (narrowing).
      //
      // Vmov1_2:
      //   vcvtpd2dq|vcvttpd2dq
      //   vcvtpd2udq|vcvttpd2udq
      //   vcvtpd2ps|vcvtps2ph
      //   vcvtqq2ps|vcvtuqq2ps
      //   vpmovwb|vpmovswb|vpmovuswb
      //   vpmovdw|vpmovsdw|vpmovusdw
      //   vpmovqd|vpmovsqd|vpmovusqd
      //
      // Vmov1_4:
      //   vpmovdb|vpmovsdb|vpmovusdb
      //   vpmovqw|vpmovsqw|vpmovusqw
      //
      // Vmov1_8:
      //   pmovmskb|vpmovmskb
      //   vpmovqb|vpmovsqb|vpmovusqb
      uint32_t shift = instRwInfo.category - InstDB::RWInfo::kCategoryVmov1_2 + 1;

      if (opCount >= 2) {
        if (opCount >= 3) {
          if (opCount > 3)
            return DebugUtils::errored(kErrorInvalidInstruction);
          out->_operands[2].reset();
        }

        if (operands[0].isReg() && operands[1].isReg()) {
          uint32_t size1 = operands[1].size();
          uint32_t size0 = size1 >> shift;

          out->_operands[0].reset(W, size0);
          out->_operands[1].reset(R, size1);

          if (instRmInfo.rmOpsMask & 0x1) {
            out->_operands[0].addOpFlags(RegM);
            out->_operands[0].setRmSize(size0);
          }

          if (instRmInfo.rmOpsMask & 0x2) {
            out->_operands[1].addOpFlags(RegM);
            out->_operands[1].setRmSize(size1);
          }

          // Handle 'pmovmskb|vpmovmskb'.
          if (BaseReg::isGp(operands[0]))
            rwZeroExtendGp(out->_operands[0], operands[0].as<Gp>(), nativeGpSize);

          if (BaseReg::isVec(operands[0]))
            rwZeroExtendAvxVec(out->_operands[0], operands[0].as<Vec>());

          return kErrorOk;
        }

        if (operands[0].isReg() && operands[1].isMem()) {
          uint32_t size1 = operands[1].size() ? operands[1].size() : uint32_t(16);
          uint32_t size0 = size1 >> shift;

          out->_operands[0].reset(W, size0);
          out->_operands[1].reset(R | MibRead, size1);
          return kErrorOk;
        }

        if (operands[0].isMem() && operands[1].isReg()) {
          uint32_t size1 = operands[1].size();
          uint32_t size0 = size1 >> shift;

          out->_operands[0].reset(W | MibRead, size0);
          out->_operands[1].reset(R, size1);
          return kErrorOk;
        }
      }
      break;
    }

    case InstDB::RWInfo::kCategoryVmov2_1:
    case InstDB::RWInfo::kCategoryVmov4_1:
    case InstDB::RWInfo::kCategoryVmov8_1: {
      // Special case for instructions where the destination is N:1 (widening).
      //
      // Vmov2_1:
      //   vcvtdq2pd|vcvtudq2pd
      //   vcvtps2pd|vcvtph2ps
      //   vcvtps2qq|vcvtps2uqq
      //   vcvttps2qq|vcvttps2uqq
      //   vpmovsxbw|vpmovzxbw
      //   vpmovsxwd|vpmovzxwd
      //   vpmovsxdq|vpmovzxdq
      //
      // Vmov4_1:
      //   vpmovsxbd|vpmovzxbd
      //   vpmovsxwq|vpmovzxwq
      //
      // Vmov8_1:
      //   vpmovsxbq|vpmovzxbq
      uint32_t shift = instRwInfo.category - InstDB::RWInfo::kCategoryVmov2_1 + 1;

      if (opCount >= 2) {
        if (opCount >= 3) {
          if (opCount > 3)
            return DebugUtils::errored(kErrorInvalidInstruction);
          out->_operands[2].reset();
        }

        uint32_t size0 = operands[0].size();
        uint32_t size1 = size0 >> shift;

        out->_operands[0].reset(W, size0);
        out->_operands[1].reset(R, size1);

        if (operands[0].isReg() && operands[1].isReg()) {
          if (instRmInfo.rmOpsMask & 0x1) {
            out->_operands[0].addOpFlags(RegM);
            out->_operands[0].setRmSize(size0);
          }

          if (instRmInfo.rmOpsMask & 0x2) {
            out->_operands[1].addOpFlags(RegM);
            out->_operands[1].setRmSize(size1);
          }
          return kErrorOk;
        }

        if (operands[0].isReg() && operands[1].isMem()) {
          out->_operands[1].addOpFlags(MibRead);
          return kErrorOk;
        }
      }
      break;
    }
  }

  return DebugUtils::errored(kErrorInvalidInstruction);
}