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);
}