in erts/emulator/asmjit/x86/x86instapi.cpp [241:765]
ASMJIT_FAVOR_SIZE Error InstInternal::validate(uint32_t arch, const BaseInst& inst, const Operand_* operands, size_t opCount, uint32_t validationFlags) noexcept {
// Only called when `arch` matches X86 family.
ASMJIT_ASSERT(Environment::isFamilyX86(arch));
const X86ValidationData* vd;
if (arch == Environment::kArchX86)
vd = &_x86ValidationData;
else
vd = &_x64ValidationData;
uint32_t i;
uint32_t mode = InstDB::modeFromArch(arch);
// Get the instruction data.
uint32_t instId = inst.id();
uint32_t options = inst.options();
if (ASMJIT_UNLIKELY(!Inst::isDefinedId(instId)))
return DebugUtils::errored(kErrorInvalidInstruction);
const InstDB::InstInfo& instInfo = InstDB::infoById(instId);
const InstDB::CommonInfo& commonInfo = instInfo.commonInfo();
uint32_t iFlags = instInfo.flags();
// --------------------------------------------------------------------------
// [Validate LOCK|XACQUIRE|XRELEASE]
// --------------------------------------------------------------------------
const uint32_t kLockXAcqRel = Inst::kOptionXAcquire | Inst::kOptionXRelease;
if (options & (Inst::kOptionLock | kLockXAcqRel)) {
if (options & Inst::kOptionLock) {
if (ASMJIT_UNLIKELY(!(iFlags & InstDB::kFlagLock) && !(options & kLockXAcqRel)))
return DebugUtils::errored(kErrorInvalidLockPrefix);
if (ASMJIT_UNLIKELY(opCount < 1 || !operands[0].isMem()))
return DebugUtils::errored(kErrorInvalidLockPrefix);
}
if (options & kLockXAcqRel) {
if (ASMJIT_UNLIKELY(!(options & Inst::kOptionLock) || (options & kLockXAcqRel) == kLockXAcqRel))
return DebugUtils::errored(kErrorInvalidPrefixCombination);
if (ASMJIT_UNLIKELY((options & Inst::kOptionXAcquire) && !(iFlags & InstDB::kFlagXAcquire)))
return DebugUtils::errored(kErrorInvalidXAcquirePrefix);
if (ASMJIT_UNLIKELY((options & Inst::kOptionXRelease) && !(iFlags & InstDB::kFlagXRelease)))
return DebugUtils::errored(kErrorInvalidXReleasePrefix);
}
}
// Validate REP and REPNE prefixes.
const uint32_t kRepAny = Inst::kOptionRep | Inst::kOptionRepne;
if (options & kRepAny) {
if (ASMJIT_UNLIKELY((options & kRepAny) == kRepAny))
return DebugUtils::errored(kErrorInvalidPrefixCombination);
if (ASMJIT_UNLIKELY(!(iFlags & InstDB::kFlagRep)))
return DebugUtils::errored(kErrorInvalidRepPrefix);
}
// --------------------------------------------------------------------------
// [Translate Each Operand to the Corresponding OpSignature]
// --------------------------------------------------------------------------
InstDB::OpSignature oSigTranslated[Globals::kMaxOpCount];
uint32_t combinedOpFlags = 0;
uint32_t combinedRegMask = 0;
const Mem* memOp = nullptr;
for (i = 0; i < opCount; i++) {
const Operand_& op = operands[i];
if (op.opType() == Operand::kOpNone)
break;
uint32_t opFlags = 0;
uint32_t memFlags = 0;
uint32_t regMask = 0;
switch (op.opType()) {
case Operand::kOpReg: {
uint32_t regType = op.as<BaseReg>().type();
if (ASMJIT_UNLIKELY(regType >= Reg::kTypeCount))
return DebugUtils::errored(kErrorInvalidRegType);
opFlags = _x86OpFlagFromRegType[regType];
if (ASMJIT_UNLIKELY(opFlags == 0))
return DebugUtils::errored(kErrorInvalidRegType);
// If `regId` is equal or greater than Operand::kVirtIdMin it means
// that the register is virtual and its index will be assigned later
// by the register allocator. We must pass unless asked to disallow
// virtual registers.
uint32_t regId = op.id();
if (regId < Operand::kVirtIdMin) {
if (ASMJIT_UNLIKELY(regId >= 32))
return DebugUtils::errored(kErrorInvalidPhysId);
if (ASMJIT_UNLIKELY(Support::bitTest(vd->allowedRegMask[regType], regId) == 0))
return DebugUtils::errored(kErrorInvalidPhysId);
regMask = Support::bitMask(regId);
combinedRegMask |= regMask;
}
else {
if (!(validationFlags & InstAPI::kValidationFlagVirtRegs))
return DebugUtils::errored(kErrorIllegalVirtReg);
regMask = 0xFFFFFFFFu;
}
break;
}
// TODO: Validate base and index and combine these with `combinedRegMask`.
case Operand::kOpMem: {
const Mem& m = op.as<Mem>();
memOp = &m;
uint32_t memSize = m.size();
uint32_t baseType = m.baseType();
uint32_t indexType = m.indexType();
if (m.segmentId() > 6)
return DebugUtils::errored(kErrorInvalidSegment);
// Validate AVX-512 broadcast {1tox}.
if (m.hasBroadcast()) {
if (memSize != 0) {
// If the size is specified it has to match the broadcast size.
if (ASMJIT_UNLIKELY(commonInfo.hasAvx512B32() && memSize != 4))
return DebugUtils::errored(kErrorInvalidBroadcast);
if (ASMJIT_UNLIKELY(commonInfo.hasAvx512B64() && memSize != 8))
return DebugUtils::errored(kErrorInvalidBroadcast);
}
else {
// If there is no size we implicitly calculate it so we can validate N in {1toN} properly.
memSize = commonInfo.hasAvx512B32() ? 4 : 8;
}
memSize <<= m.getBroadcast();
}
if (baseType != 0 && baseType > Label::kLabelTag) {
uint32_t baseId = m.baseId();
if (m.isRegHome()) {
// Home address of a virtual register. In such case we don't want to
// validate the type of the base register as it will always be patched
// to ESP|RSP.
}
else {
if (ASMJIT_UNLIKELY((vd->allowedMemBaseRegs & (1u << baseType)) == 0))
return DebugUtils::errored(kErrorInvalidAddress);
}
// Create information that will be validated only if this is an implicit
// memory operand. Basically only usable for string instructions and other
// instructions where memory operand is implicit and has 'seg:[reg]' form.
if (baseId < Operand::kVirtIdMin) {
if (ASMJIT_UNLIKELY(baseId >= 32))
return DebugUtils::errored(kErrorInvalidPhysId);
// Physical base id.
regMask = Support::bitMask(baseId);
combinedRegMask |= regMask;
}
else {
// Virtual base id - fill the whole mask for implicit mem validation.
// The register is not assigned yet, so we cannot predict the phys id.
if (!(validationFlags & InstAPI::kValidationFlagVirtRegs))
return DebugUtils::errored(kErrorIllegalVirtReg);
regMask = 0xFFFFFFFFu;
}
if (!indexType && !m.offsetLo32())
memFlags |= InstDB::kMemOpBaseOnly;
}
else if (baseType == Label::kLabelTag) {
// [Label] - there is no need to validate the base as it's label.
}
else {
// Base is a 64-bit address.
int64_t offset = m.offset();
if (!Support::isInt32(offset)) {
if (mode == InstDB::kModeX86) {
// 32-bit mode: Make sure that the address is either `int32_t` or `uint32_t`.
if (!Support::isUInt32(offset))
return DebugUtils::errored(kErrorInvalidAddress64Bit);
}
else {
// 64-bit mode: Zero extension is allowed if the address has 32-bit index
// register or the address has no index register (it's still encodable).
if (indexType) {
if (!Support::isUInt32(offset))
return DebugUtils::errored(kErrorInvalidAddress64Bit);
if (indexType != Reg::kTypeGpd)
return DebugUtils::errored(kErrorInvalidAddress64BitZeroExtension);
}
else {
// We don't validate absolute 64-bit addresses without an index register
// as this also depends on the target's base address. We don't have the
// information to do it at this moment.
}
}
}
}
if (indexType) {
if (ASMJIT_UNLIKELY((vd->allowedMemIndexRegs & (1u << indexType)) == 0))
return DebugUtils::errored(kErrorInvalidAddress);
if (indexType == Reg::kTypeXmm) {
opFlags |= InstDB::kOpVm;
memFlags |= InstDB::kMemOpVm32x | InstDB::kMemOpVm64x;
}
else if (indexType == Reg::kTypeYmm) {
opFlags |= InstDB::kOpVm;
memFlags |= InstDB::kMemOpVm32y | InstDB::kMemOpVm64y;
}
else if (indexType == Reg::kTypeZmm) {
opFlags |= InstDB::kOpVm;
memFlags |= InstDB::kMemOpVm32z | InstDB::kMemOpVm64z;
}
else {
opFlags |= InstDB::kOpMem;
if (baseType)
memFlags |= InstDB::kMemOpMib;
}
// [RIP + {XMM|YMM|ZMM}] is not allowed.
if (baseType == Reg::kTypeRip && (opFlags & InstDB::kOpVm))
return DebugUtils::errored(kErrorInvalidAddress);
uint32_t indexId = m.indexId();
if (indexId < Operand::kVirtIdMin) {
if (ASMJIT_UNLIKELY(indexId >= 32))
return DebugUtils::errored(kErrorInvalidPhysId);
combinedRegMask |= Support::bitMask(indexId);
}
else {
if (!(validationFlags & InstAPI::kValidationFlagVirtRegs))
return DebugUtils::errored(kErrorIllegalVirtReg);
}
// Only used for implicit memory operands having 'seg:[reg]' form, so clear it.
regMask = 0;
}
else {
opFlags |= InstDB::kOpMem;
}
switch (memSize) {
case 0: memFlags |= InstDB::kMemOpAny ; break;
case 1: memFlags |= InstDB::kMemOpM8 ; break;
case 2: memFlags |= InstDB::kMemOpM16 ; break;
case 4: memFlags |= InstDB::kMemOpM32 ; break;
case 6: memFlags |= InstDB::kMemOpM48 ; break;
case 8: memFlags |= InstDB::kMemOpM64 ; break;
case 10: memFlags |= InstDB::kMemOpM80 ; break;
case 16: memFlags |= InstDB::kMemOpM128; break;
case 32: memFlags |= InstDB::kMemOpM256; break;
case 64: memFlags |= InstDB::kMemOpM512; break;
default:
return DebugUtils::errored(kErrorInvalidOperandSize);
}
break;
}
case Operand::kOpImm: {
uint64_t immValue = op.as<Imm>().valueAs<uint64_t>();
uint32_t immFlags = 0;
if (int64_t(immValue) >= 0) {
if (immValue <= 0x7u)
immFlags = InstDB::kOpI64 | InstDB::kOpU64 | InstDB::kOpI32 | InstDB::kOpU32 |
InstDB::kOpI16 | InstDB::kOpU16 | InstDB::kOpI8 | InstDB::kOpU8 |
InstDB::kOpI4 | InstDB::kOpU4 ;
else if (immValue <= 0xFu)
immFlags = InstDB::kOpI64 | InstDB::kOpU64 | InstDB::kOpI32 | InstDB::kOpU32 |
InstDB::kOpI16 | InstDB::kOpU16 | InstDB::kOpI8 | InstDB::kOpU8 |
InstDB::kOpU4 ;
else if (immValue <= 0x7Fu)
immFlags = InstDB::kOpI64 | InstDB::kOpU64 | InstDB::kOpI32 | InstDB::kOpU32 |
InstDB::kOpI16 | InstDB::kOpU16 | InstDB::kOpI8 | InstDB::kOpU8 ;
else if (immValue <= 0xFFu)
immFlags = InstDB::kOpI64 | InstDB::kOpU64 | InstDB::kOpI32 | InstDB::kOpU32 |
InstDB::kOpI16 | InstDB::kOpU16 | InstDB::kOpU8 ;
else if (immValue <= 0x7FFFu)
immFlags = InstDB::kOpI64 | InstDB::kOpU64 | InstDB::kOpI32 | InstDB::kOpU32 |
InstDB::kOpI16 | InstDB::kOpU16 ;
else if (immValue <= 0xFFFFu)
immFlags = InstDB::kOpI64 | InstDB::kOpU64 | InstDB::kOpI32 | InstDB::kOpU32 |
InstDB::kOpU16 ;
else if (immValue <= 0x7FFFFFFFu)
immFlags = InstDB::kOpI64 | InstDB::kOpU64 | InstDB::kOpI32 | InstDB::kOpU32;
else if (immValue <= 0xFFFFFFFFu)
immFlags = InstDB::kOpI64 | InstDB::kOpU64 | InstDB::kOpU32;
else if (immValue <= 0x7FFFFFFFFFFFFFFFu)
immFlags = InstDB::kOpI64 | InstDB::kOpU64;
else
immFlags = InstDB::kOpU64;
}
else {
immValue = Support::neg(immValue);
if (immValue <= 0x8u)
immFlags = InstDB::kOpI64 | InstDB::kOpI32 | InstDB::kOpI16 | InstDB::kOpI8 | InstDB::kOpI4;
else if (immValue <= 0x80u)
immFlags = InstDB::kOpI64 | InstDB::kOpI32 | InstDB::kOpI16 | InstDB::kOpI8;
else if (immValue <= 0x8000u)
immFlags = InstDB::kOpI64 | InstDB::kOpI32 | InstDB::kOpI16;
else if (immValue <= 0x80000000u)
immFlags = InstDB::kOpI64 | InstDB::kOpI32;
else
immFlags = InstDB::kOpI64;
}
opFlags |= immFlags;
break;
}
case Operand::kOpLabel: {
opFlags |= InstDB::kOpRel8 | InstDB::kOpRel32;
break;
}
default:
return DebugUtils::errored(kErrorInvalidState);
}
InstDB::OpSignature& oSigDst = oSigTranslated[i];
oSigDst.opFlags = opFlags;
oSigDst.memFlags = uint16_t(memFlags);
oSigDst.regMask = uint8_t(regMask & 0xFFu);
combinedOpFlags |= opFlags;
}
// Decrease the number of operands of those that are none. This is important
// as Assembler and Compiler may just pass more operands padded with none
// (which means that no operand is given at that index). However, validate
// that there are no gaps (like [reg, none, reg] or [none, reg]).
if (i < opCount) {
while (--opCount > i)
if (ASMJIT_UNLIKELY(!operands[opCount].isNone()))
return DebugUtils::errored(kErrorInvalidInstruction);
}
// Validate X86 and X64 specific cases.
if (mode == InstDB::kModeX86) {
// Illegal use of 64-bit register in 32-bit mode.
if (ASMJIT_UNLIKELY((combinedOpFlags & InstDB::kOpGpq) != 0))
return DebugUtils::errored(kErrorInvalidUseOfGpq);
}
else {
// Illegal use of a high 8-bit register with REX prefix.
bool hasREX = inst.hasOption(Inst::kOptionRex) ||
((combinedRegMask & 0xFFFFFF00u) != 0);
if (ASMJIT_UNLIKELY(hasREX && (combinedOpFlags & InstDB::kOpGpbHi) != 0))
return DebugUtils::errored(kErrorInvalidUseOfGpbHi);
}
// --------------------------------------------------------------------------
// [Validate Instruction Signature by Comparing Against All `iSig` Rows]
// --------------------------------------------------------------------------
const InstDB::InstSignature* iSig = InstDB::_instSignatureTable + commonInfo._iSignatureIndex;
const InstDB::InstSignature* iEnd = iSig + commonInfo._iSignatureCount;
if (iSig != iEnd) {
const InstDB::OpSignature* opSignatureTable = InstDB::_opSignatureTable;
// If set it means that we matched a signature where only immediate value
// was out of bounds. We can return a more descriptive error if we know this.
bool globalImmOutOfRange = false;
do {
// Check if the architecture is compatible.
if ((iSig->modes & mode) == 0)
continue;
// Compare the operands table with reference operands.
uint32_t j = 0;
uint32_t iSigCount = iSig->opCount;
bool localImmOutOfRange = false;
if (iSigCount == opCount) {
for (j = 0; j < opCount; j++)
if (!x86CheckOSig(oSigTranslated[j], opSignatureTable[iSig->operands[j]], localImmOutOfRange))
break;
}
else if (iSigCount - iSig->implicit == opCount) {
uint32_t r = 0;
for (j = 0; j < opCount && r < iSigCount; j++, r++) {
const InstDB::OpSignature* oChk = oSigTranslated + j;
const InstDB::OpSignature* oRef;
Next:
oRef = opSignatureTable + iSig->operands[r];
// Skip implicit.
if ((oRef->opFlags & InstDB::kOpImplicit) != 0) {
if (++r >= iSigCount)
break;
else
goto Next;
}
if (!x86CheckOSig(*oChk, *oRef, localImmOutOfRange))
break;
}
}
if (j == opCount) {
if (!localImmOutOfRange) {
// Match, must clear possible `globalImmOutOfRange`.
globalImmOutOfRange = false;
break;
}
globalImmOutOfRange = localImmOutOfRange;
}
} while (++iSig != iEnd);
if (iSig == iEnd) {
if (globalImmOutOfRange)
return DebugUtils::errored(kErrorInvalidImmediate);
else
return DebugUtils::errored(kErrorInvalidInstruction);
}
}
// --------------------------------------------------------------------------
// [Validate AVX512 Options]
// --------------------------------------------------------------------------
const RegOnly& extraReg = inst.extraReg();
const uint32_t kAvx512Options = Inst::kOptionZMask |
Inst::kOptionER |
Inst::kOptionSAE ;
if (options & kAvx512Options) {
if (commonInfo.hasFlag(InstDB::kFlagEvex)) {
// Validate AVX-512 {z}.
if ((options & Inst::kOptionZMask)) {
if (ASMJIT_UNLIKELY((options & Inst::kOptionZMask) != 0 && !commonInfo.hasAvx512Z()))
return DebugUtils::errored(kErrorInvalidKZeroUse);
}
// Validate AVX-512 {sae} and {er}.
if (options & (Inst::kOptionSAE | Inst::kOptionER)) {
// Rounding control is impossible if the instruction is not reg-to-reg.
if (ASMJIT_UNLIKELY(memOp))
return DebugUtils::errored(kErrorInvalidEROrSAE);
// Check if {sae} or {er} is supported by the instruction.
if (options & Inst::kOptionER) {
// NOTE: if both {sae} and {er} are set, we don't care, as {sae} is implied.
if (ASMJIT_UNLIKELY(!commonInfo.hasAvx512ER()))
return DebugUtils::errored(kErrorInvalidEROrSAE);
}
else {
if (ASMJIT_UNLIKELY(!commonInfo.hasAvx512SAE()))
return DebugUtils::errored(kErrorInvalidEROrSAE);
}
// {sae} and {er} are defined for either scalar ops or vector ops that
// require LL to be 10 (512-bit vector operations). We don't need any
// more bits in the instruction database to be able to validate this, as
// each AVX512 instruction that has broadcast is vector instruction (in
// this case we require zmm registers), otherwise it's a scalar instruction,
// which is valid.
if (commonInfo.hasAvx512B()) {
// Supports broadcast, thus we require LL to be '10', which means there
// have to be ZMM registers used. We don't calculate LL here, but we know
// that it would be '10' if there is at least one ZMM register used.
// There is no {er}/{sae}-enabled instruction with less than two operands.
ASMJIT_ASSERT(opCount >= 2);
if (ASMJIT_UNLIKELY(!x86IsZmmOrM512(operands[0]) && !x86IsZmmOrM512(operands[1])))
return DebugUtils::errored(kErrorInvalidEROrSAE);
}
}
}
else {
// Not AVX512 instruction - maybe OpExtra is xCX register used by REP/REPNE
// prefix. Otherwise the instruction is invalid.
if ((options & kAvx512Options) || (options & kRepAny) == 0)
return DebugUtils::errored(kErrorInvalidInstruction);
}
}
// --------------------------------------------------------------------------
// [Validate {Extra} Register]
// --------------------------------------------------------------------------
if (extraReg.isReg()) {
if (options & kRepAny) {
// Validate REP|REPNE {cx|ecx|rcx}.
if (ASMJIT_UNLIKELY(iFlags & InstDB::kFlagRepIgnored))
return DebugUtils::errored(kErrorInvalidExtraReg);
if (extraReg.isPhysReg()) {
if (ASMJIT_UNLIKELY(extraReg.id() != Gp::kIdCx))
return DebugUtils::errored(kErrorInvalidExtraReg);
}
// The type of the {...} register must match the type of the base register
// of memory operand. So if the memory operand uses 32-bit register the
// count register must also be 32-bit, etc...
if (ASMJIT_UNLIKELY(!memOp || extraReg.type() != memOp->baseType()))
return DebugUtils::errored(kErrorInvalidExtraReg);
}
else if (commonInfo.hasFlag(InstDB::kFlagEvex)) {
// Validate AVX-512 {k}.
if (ASMJIT_UNLIKELY(extraReg.type() != Reg::kTypeKReg))
return DebugUtils::errored(kErrorInvalidExtraReg);
if (ASMJIT_UNLIKELY(extraReg.id() == 0 || !commonInfo.hasAvx512K()))
return DebugUtils::errored(kErrorInvalidKMaskUse);
}
else {
return DebugUtils::errored(kErrorInvalidExtraReg);
}
}
return kErrorOk;
}