733 lines
28 KiB
C++
733 lines
28 KiB
C++
// [AsmJit]
|
|
// Complete x86/x64 JIT and Remote Assembler for C++.
|
|
//
|
|
// [License]
|
|
// Zlib - See LICENSE.md file in the package.
|
|
|
|
// [Export]
|
|
#define ASMJIT_EXPORTS
|
|
|
|
// [Dependencies]
|
|
#include "../base/misc_p.h"
|
|
#include "../base/utils.h"
|
|
#include "../x86/x86instimpl_p.h"
|
|
#include "../x86/x86operand.h"
|
|
|
|
// [Api-Begin]
|
|
#include "../asmjit_apibegin.h"
|
|
|
|
#pragma warning(disable: 4838) // warning C4838: conversion from '' to 'uint32_t' requires a narrowing conversion
|
|
|
|
namespace asmjit {
|
|
|
|
// ============================================================================
|
|
// [asmjit::X86InstImpl - Validate]
|
|
// ============================================================================
|
|
|
|
#if !defined(ASMJIT_DISABLE_VALIDATION)
|
|
template<uint32_t RegType>
|
|
struct X86OpTypeFromRegTypeT {
|
|
enum {
|
|
kValue = (RegType == X86Reg::kRegGpbLo) ? X86Inst::kOpGpbLo :
|
|
(RegType == X86Reg::kRegGpbHi) ? X86Inst::kOpGpbHi :
|
|
(RegType == X86Reg::kRegGpw ) ? X86Inst::kOpGpw :
|
|
(RegType == X86Reg::kRegGpd ) ? X86Inst::kOpGpd :
|
|
(RegType == X86Reg::kRegGpq ) ? X86Inst::kOpGpq :
|
|
(RegType == X86Reg::kRegXmm ) ? X86Inst::kOpXmm :
|
|
(RegType == X86Reg::kRegYmm ) ? X86Inst::kOpYmm :
|
|
(RegType == X86Reg::kRegZmm ) ? X86Inst::kOpZmm :
|
|
(RegType == X86Reg::kRegRip ) ? X86Inst::kOpNone :
|
|
(RegType == X86Reg::kRegSeg ) ? X86Inst::kOpSeg :
|
|
(RegType == X86Reg::kRegFp ) ? X86Inst::kOpFp :
|
|
(RegType == X86Reg::kRegMm ) ? X86Inst::kOpMm :
|
|
(RegType == X86Reg::kRegK ) ? X86Inst::kOpK :
|
|
(RegType == X86Reg::kRegBnd ) ? X86Inst::kOpBnd :
|
|
(RegType == X86Reg::kRegCr ) ? X86Inst::kOpCr :
|
|
(RegType == X86Reg::kRegDr ) ? X86Inst::kOpDr : X86Inst::kOpNone
|
|
};
|
|
};
|
|
|
|
template<uint32_t RegType>
|
|
struct X86RegMaskFromRegTypeT {
|
|
enum {
|
|
kMask = (RegType == X86Reg::kRegGpbLo) ? 0x0000000FU :
|
|
(RegType == X86Reg::kRegGpbHi) ? 0x0000000FU :
|
|
(RegType == X86Reg::kRegGpw ) ? 0x000000FFU :
|
|
(RegType == X86Reg::kRegGpd ) ? 0x000000FFU :
|
|
(RegType == X86Reg::kRegGpq ) ? 0x000000FFU :
|
|
(RegType == X86Reg::kRegXmm ) ? 0x000000FFU :
|
|
(RegType == X86Reg::kRegYmm ) ? 0x000000FFU :
|
|
(RegType == X86Reg::kRegZmm ) ? 0x000000FFU :
|
|
(RegType == X86Reg::kRegRip ) ? 0x00000001U :
|
|
(RegType == X86Reg::kRegSeg ) ? 0x0000007EU : // [ES|CS|SS|DS|FS|GS]
|
|
(RegType == X86Reg::kRegFp ) ? 0x000000FFU :
|
|
(RegType == X86Reg::kRegMm ) ? 0x000000FFU :
|
|
(RegType == X86Reg::kRegK ) ? 0x000000FFU :
|
|
(RegType == X86Reg::kRegBnd ) ? 0x0000000FU :
|
|
(RegType == X86Reg::kRegCr ) ? 0x0000FFFFU :
|
|
(RegType == X86Reg::kRegDr ) ? 0x000000FFU : X86Inst::kOpNone
|
|
};
|
|
};
|
|
|
|
template<uint32_t RegType>
|
|
struct X64RegMaskFromRegTypeT {
|
|
enum {
|
|
kMask = (RegType == X86Reg::kRegGpbLo) ? 0x0000FFFFU :
|
|
(RegType == X86Reg::kRegGpbHi) ? 0x0000000FU :
|
|
(RegType == X86Reg::kRegGpw ) ? 0x0000FFFFU :
|
|
(RegType == X86Reg::kRegGpd ) ? 0x0000FFFFU :
|
|
(RegType == X86Reg::kRegGpq ) ? 0x0000FFFFU :
|
|
(RegType == X86Reg::kRegXmm ) ? 0xFFFFFFFFU :
|
|
(RegType == X86Reg::kRegYmm ) ? 0xFFFFFFFFU :
|
|
(RegType == X86Reg::kRegZmm ) ? 0xFFFFFFFFU :
|
|
(RegType == X86Reg::kRegRip ) ? 0x00000001U :
|
|
(RegType == X86Reg::kRegSeg ) ? 0x0000007EU : // [ES|CS|SS|DS|FS|GS]
|
|
(RegType == X86Reg::kRegFp ) ? 0x000000FFU :
|
|
(RegType == X86Reg::kRegMm ) ? 0x000000FFU :
|
|
(RegType == X86Reg::kRegK ) ? 0x000000FFU :
|
|
(RegType == X86Reg::kRegBnd ) ? 0x0000000FU :
|
|
(RegType == X86Reg::kRegCr ) ? 0x0000FFFFU :
|
|
(RegType == X86Reg::kRegDr ) ? 0x0000FFFFU : X86Inst::kOpNone
|
|
};
|
|
};
|
|
|
|
struct X86ValidationData {
|
|
//! Allowed registers by reg-type (X86::kReg...).
|
|
uint32_t allowedRegMask[X86Reg::kRegMax + 1];
|
|
uint32_t allowedMemBaseRegs;
|
|
uint32_t allowedMemIndexRegs;
|
|
};
|
|
|
|
static const uint32_t _x86OpFlagFromRegType[X86Reg::kRegMax + 1] = {
|
|
ASMJIT_TABLE_T_32(X86OpTypeFromRegTypeT, kValue, 0)
|
|
};
|
|
|
|
static const X86ValidationData _x86ValidationData = {
|
|
{ ASMJIT_TABLE_T_32(X86RegMaskFromRegTypeT, kMask, 0) },
|
|
(1U << X86Reg::kRegGpw) | (1U << X86Reg::kRegGpd) | (1U << X86Reg::kRegRip) | (1U << Label::kLabelTag),
|
|
(1U << X86Reg::kRegGpw) | (1U << X86Reg::kRegGpd) | (1U << X86Reg::kRegXmm) | (1U << X86Reg::kRegYmm) | (1U << X86Reg::kRegZmm)
|
|
};
|
|
|
|
static const X86ValidationData _x64ValidationData = {
|
|
{ ASMJIT_TABLE_T_32(X64RegMaskFromRegTypeT, kMask, 0) },
|
|
(1U << X86Reg::kRegGpd) | (1U << X86Reg::kRegGpq) | (1U << X86Reg::kRegRip) | (1U << Label::kLabelTag),
|
|
(1U << X86Reg::kRegGpd) | (1U << X86Reg::kRegGpq) | (1U << X86Reg::kRegXmm) | (1U << X86Reg::kRegYmm) | (1U << X86Reg::kRegZmm)
|
|
};
|
|
|
|
static ASMJIT_INLINE bool x86CheckOSig(const X86Inst::OSignature& op, const X86Inst::OSignature& ref, bool& immOutOfRange) noexcept {
|
|
// Fail if operand types are incompatible.
|
|
uint32_t opFlags = op.flags;
|
|
if ((opFlags & ref.flags) == 0) {
|
|
// Mark temporarily `immOutOfRange` so we can return a more descriptive error.
|
|
if ((opFlags & X86Inst::kOpAllImm) && (ref.flags & X86Inst::kOpAllImm)) {
|
|
immOutOfRange = true;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// Fail if memory specific flags and sizes are incompatibles.
|
|
uint32_t opMemFlags = op.memFlags;
|
|
if (opMemFlags != 0) {
|
|
uint32_t refMemFlags = ref.memFlags;
|
|
if ((refMemFlags & opMemFlags) == 0)
|
|
return false;
|
|
|
|
if ((refMemFlags & X86Inst::kMemOpBaseOnly) && !(opMemFlags && X86Inst::kMemOpBaseOnly))
|
|
return false;
|
|
}
|
|
|
|
// Specific register index.
|
|
if (opFlags & X86Inst::kOpAllRegs) {
|
|
uint32_t refRegMask = ref.regMask;
|
|
if (refRegMask && !(op.regMask & refRegMask))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
ASMJIT_FAVOR_SIZE Error X86InstImpl::validate(uint32_t archType, const Inst::Detail& detail, const Operand_* operands, uint32_t count) noexcept {
|
|
uint32_t i;
|
|
uint32_t archMask;
|
|
const X86ValidationData* vd;
|
|
|
|
if (!ArchInfo::isX86Family(archType))
|
|
return DebugUtils::errored(kErrorInvalidArch);
|
|
|
|
if (archType == ArchInfo::kTypeX86) {
|
|
vd = &_x86ValidationData;
|
|
archMask = X86Inst::kArchMaskX86;
|
|
}
|
|
else {
|
|
vd = &_x64ValidationData;
|
|
archMask = X86Inst::kArchMaskX64;
|
|
}
|
|
|
|
// Get the instruction data.
|
|
uint32_t instId = detail.instId;
|
|
uint32_t options = detail.options;
|
|
|
|
if (ASMJIT_UNLIKELY(instId >= X86Inst::_kIdCount))
|
|
return DebugUtils::errored(kErrorInvalidArgument);
|
|
|
|
const X86Inst* iData = &X86InstDB::instData[instId];
|
|
uint32_t iFlags = iData->getFlags();
|
|
|
|
// Validate LOCK, XACQUIRE, and XRELEASE prefixes.
|
|
const uint32_t kLockXAcqRel = X86Inst::kOptionXAcquire | X86Inst::kOptionXRelease;
|
|
if (options & (X86Inst::kOptionLock | kLockXAcqRel)) {
|
|
if (options & X86Inst::kOptionLock) {
|
|
if (ASMJIT_UNLIKELY(!(iFlags & X86Inst::kFlagLock) && !(options & kLockXAcqRel)))
|
|
return DebugUtils::errored(kErrorInvalidLockPrefix);
|
|
|
|
if (ASMJIT_UNLIKELY(count < 1 || !operands[0].isMem()))
|
|
return DebugUtils::errored(kErrorInvalidLockPrefix);
|
|
}
|
|
|
|
if (options & kLockXAcqRel) {
|
|
if (ASMJIT_UNLIKELY(!(options & X86Inst::kOptionLock) || (options & kLockXAcqRel) == kLockXAcqRel))
|
|
return DebugUtils::errored(kErrorInvalidPrefixCombination);
|
|
|
|
if (ASMJIT_UNLIKELY((options & X86Inst::kOptionXAcquire) && !(iFlags & X86Inst::kFlagXAcquire)))
|
|
return DebugUtils::errored(kErrorInvalidXAcquirePrefix);
|
|
|
|
if (ASMJIT_UNLIKELY((options & X86Inst::kOptionXRelease) && !(iFlags & X86Inst::kFlagXRelease)))
|
|
return DebugUtils::errored(kErrorInvalidXReleasePrefix);
|
|
}
|
|
}
|
|
|
|
// Validate REP and REPNZ prefixes.
|
|
const uint32_t kRepRepRepnz = X86Inst::kOptionRep | X86Inst::kOptionRepnz;
|
|
if (options & kRepRepRepnz) {
|
|
if (ASMJIT_UNLIKELY((options & kRepRepRepnz) == kRepRepRepnz))
|
|
return DebugUtils::errored(kErrorInvalidPrefixCombination);
|
|
|
|
if (ASMJIT_UNLIKELY((options & X86Inst::kOptionRep) && !(iFlags & X86Inst::kFlagRep)))
|
|
return DebugUtils::errored(kErrorInvalidRepPrefix);
|
|
|
|
if (ASMJIT_UNLIKELY((options & X86Inst::kOptionRepnz) && !(iFlags & X86Inst::kFlagRepnz)))
|
|
return DebugUtils::errored(kErrorInvalidRepPrefix);
|
|
|
|
// TODO: Validate extraReg {cx|ecx|rcx}.
|
|
}
|
|
|
|
// Translate the given operands to `X86Inst::OSignature`.
|
|
X86Inst::OSignature oSigTranslated[6];
|
|
uint32_t combinedOpFlags = 0;
|
|
uint32_t combinedRegMask = 0;
|
|
|
|
const X86Mem* memOp = nullptr;
|
|
|
|
for (i = 0; i < count; i++) {
|
|
const Operand_& op = operands[i];
|
|
if (op.getOp() == Operand::kOpNone) break;
|
|
|
|
uint32_t opFlags = 0;
|
|
uint32_t memFlags = 0;
|
|
uint32_t regMask = 0;
|
|
|
|
switch (op.getOp()) {
|
|
case Operand::kOpReg: {
|
|
uint32_t regType = op.as<Reg>().getType();
|
|
if (ASMJIT_UNLIKELY(regType >= X86Reg::kRegCount))
|
|
return DebugUtils::errored(kErrorInvalidRegType);
|
|
|
|
opFlags = _x86OpFlagFromRegType[regType];
|
|
if (ASMJIT_UNLIKELY(opFlags == 0))
|
|
return DebugUtils::errored(kErrorInvalidRegType);
|
|
|
|
// If `regId` is equal or greater than Operand::kPackedIdMin 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.
|
|
// TODO: We need an option to refuse virtual regs here.
|
|
uint32_t regId = op.getId();
|
|
if (regId < Operand::kPackedIdMin) {
|
|
if (ASMJIT_UNLIKELY(regId >= 32))
|
|
return DebugUtils::errored(kErrorInvalidPhysId);
|
|
|
|
regMask = Utils::mask(regId);
|
|
if (ASMJIT_UNLIKELY((vd->allowedRegMask[regType] & regMask) == 0))
|
|
return DebugUtils::errored(kErrorInvalidPhysId);
|
|
|
|
combinedRegMask |= regMask;
|
|
}
|
|
else {
|
|
regMask = 0xFFFFFFFFU;
|
|
}
|
|
break;
|
|
}
|
|
|
|
// TODO: Validate base and index and combine with `combinedRegMask`.
|
|
case Operand::kOpMem: {
|
|
const X86Mem& m = op.as<X86Mem>();
|
|
|
|
uint32_t baseType = m.getBaseType();
|
|
uint32_t indexType = m.getIndexType();
|
|
|
|
memOp = &m;
|
|
|
|
if (m.getSegmentId() > 6)
|
|
return DebugUtils::errored(kErrorInvalidSegment);
|
|
|
|
if (baseType) {
|
|
uint32_t baseId = m.getBaseId();
|
|
|
|
if (m.isRegHome()) {
|
|
// Home address of 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::kPackedIdMin) {
|
|
// Physical base id.
|
|
regMask = Utils::mask(baseId);
|
|
combinedRegMask |= regMask;
|
|
}
|
|
else {
|
|
// Virtual base id - will the whole mask for implicit mem validation.
|
|
// The register is not assigned yet, so we cannot predict the phys id.
|
|
regMask = 0xFFFFFFFFU;
|
|
}
|
|
|
|
if (!indexType && !m.getOffsetLo32())
|
|
memFlags |= X86Inst::kMemOpBaseOnly;
|
|
}
|
|
else {
|
|
// Base is an address, make sure that the address doesn't overflow 32-bit
|
|
// integer (either int32_t or uint32_t) in 32-bit targets.
|
|
int64_t offset = m.getOffset();
|
|
if (archMask == X86Inst::kArchMaskX86 && !Utils::isInt32(offset) && !Utils::isUInt32(offset))
|
|
return DebugUtils::errored(kErrorInvalidAddress);
|
|
}
|
|
|
|
if (indexType) {
|
|
if (ASMJIT_UNLIKELY((vd->allowedMemIndexRegs & (1U << indexType)) == 0))
|
|
return DebugUtils::errored(kErrorInvalidAddress);
|
|
|
|
if (indexType == X86Reg::kRegXmm) {
|
|
opFlags |= X86Inst::kOpVm;
|
|
memFlags |= X86Inst::kMemOpVm32x | X86Inst::kMemOpVm64x;
|
|
}
|
|
else if (indexType == X86Reg::kRegYmm) {
|
|
opFlags |= X86Inst::kOpVm;
|
|
memFlags |= X86Inst::kMemOpVm32y | X86Inst::kMemOpVm64y;
|
|
}
|
|
else if (indexType == X86Reg::kRegZmm) {
|
|
opFlags |= X86Inst::kOpVm;
|
|
memFlags |= X86Inst::kMemOpVm32z | X86Inst::kMemOpVm64z;
|
|
}
|
|
else {
|
|
opFlags |= X86Inst::kOpMem;
|
|
if (baseType)
|
|
memFlags |= X86Inst::kMemOpMib;
|
|
}
|
|
|
|
// [RIP + {XMM|YMM|ZMM}] is not allowed.
|
|
if (baseType == X86Reg::kRegRip && (opFlags & X86Inst::kOpVm))
|
|
return DebugUtils::errored(kErrorInvalidAddress);
|
|
|
|
uint32_t indexId = m.getIndexId();
|
|
if (indexId < Operand::kPackedIdMin)
|
|
combinedRegMask |= Utils::mask(indexId);
|
|
|
|
// Only used for implicit memory operands having 'seg:[reg]' form, so clear it.
|
|
regMask = 0;
|
|
}
|
|
else {
|
|
opFlags |= X86Inst::kOpMem;
|
|
}
|
|
|
|
switch (m.getSize()) {
|
|
case 0: memFlags |= X86Inst::kMemOpAny ; break;
|
|
case 1: memFlags |= X86Inst::kMemOpM8 ; break;
|
|
case 2: memFlags |= X86Inst::kMemOpM16 ; break;
|
|
case 4: memFlags |= X86Inst::kMemOpM32 ; break;
|
|
case 6: memFlags |= X86Inst::kMemOpM48 ; break;
|
|
case 8: memFlags |= X86Inst::kMemOpM64 ; break;
|
|
case 10: memFlags |= X86Inst::kMemOpM80 ; break;
|
|
case 16: memFlags |= X86Inst::kMemOpM128; break;
|
|
case 32: memFlags |= X86Inst::kMemOpM256; break;
|
|
case 64: memFlags |= X86Inst::kMemOpM512; break;
|
|
default:
|
|
return DebugUtils::errored(kErrorInvalidOperandSize);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case Operand::kOpImm: {
|
|
uint64_t immValue = op.as<Imm>().getUInt64();
|
|
uint32_t immFlags = 0;
|
|
|
|
if (static_cast<int64_t>(immValue) >= 0) {
|
|
const uint32_t k32AndMore = X86Inst::kOpI32 | X86Inst::kOpU32 |
|
|
X86Inst::kOpI64 | X86Inst::kOpU64 ;
|
|
|
|
if (immValue <= 0xFU)
|
|
immFlags = X86Inst::kOpU4 | X86Inst::kOpI8 | X86Inst::kOpU8 | X86Inst::kOpI16 | X86Inst::kOpU16 | k32AndMore;
|
|
else if (immValue <= 0x7FU)
|
|
immFlags = X86Inst::kOpI8 | X86Inst::kOpU8 | X86Inst::kOpI16 | X86Inst::kOpU16 | k32AndMore;
|
|
else if (immValue <= 0xFFU)
|
|
immFlags = X86Inst::kOpU8 | X86Inst::kOpI16 | X86Inst::kOpU16 | k32AndMore;
|
|
else if (immValue <= 0x7FFFU)
|
|
immFlags = X86Inst::kOpI16 | X86Inst::kOpU16 | k32AndMore;
|
|
else if (immValue <= 0xFFFFU)
|
|
immFlags = X86Inst::kOpU16 | k32AndMore;
|
|
else if (immValue <= 0x7FFFFFFFU)
|
|
immFlags = k32AndMore;
|
|
else if (immValue <= 0xFFFFFFFFU)
|
|
immFlags = X86Inst::kOpU32 | X86Inst::kOpI64 | X86Inst::kOpU64;
|
|
else if (immValue <= ASMJIT_UINT64_C(0x7FFFFFFFFFFFFFFF))
|
|
immFlags = X86Inst::kOpI64 | X86Inst::kOpU64;
|
|
else
|
|
immFlags = X86Inst::kOpU64;
|
|
}
|
|
else {
|
|
// 2s complement negation, as our number is unsigned...
|
|
immValue = (~immValue + 1);
|
|
|
|
if (immValue <= 0x80U)
|
|
immFlags = X86Inst::kOpI8 | X86Inst::kOpI16 | X86Inst::kOpI32 | X86Inst::kOpI64;
|
|
else if (immValue <= 0x8000U)
|
|
immFlags = X86Inst::kOpI16 | X86Inst::kOpI32 | X86Inst::kOpI64;
|
|
else if (immValue <= 0x80000000U)
|
|
immFlags = X86Inst::kOpI32 | X86Inst::kOpI64;
|
|
else
|
|
immFlags = X86Inst::kOpI64;
|
|
}
|
|
opFlags |= immFlags;
|
|
break;
|
|
}
|
|
|
|
case Operand::kOpLabel: {
|
|
opFlags |= X86Inst::kOpRel8 | X86Inst::kOpRel32;
|
|
break;
|
|
}
|
|
|
|
default:
|
|
return DebugUtils::errored(kErrorInvalidState);
|
|
}
|
|
|
|
X86Inst::OSignature& tod = oSigTranslated[i];
|
|
tod.flags = opFlags;
|
|
tod.memFlags = static_cast<uint16_t>(memFlags);
|
|
tod.regMask = static_cast<uint8_t>(regMask & 0xFFU);
|
|
combinedOpFlags |= opFlags;
|
|
}
|
|
|
|
// Decrease the number of operands of those that are none. This is important
|
|
// as Assembler and CodeCompiler may just pass more operands where some of
|
|
// them are none (it 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 < count) {
|
|
while (--count > i)
|
|
if (ASMJIT_UNLIKELY(!operands[count].isNone()))
|
|
return DebugUtils::errored(kErrorInvalidState);
|
|
}
|
|
|
|
// Validate X86 and X64 specific cases.
|
|
if (archMask == X86Inst::kArchMaskX86) {
|
|
// Illegal use of 64-bit register in 32-bit mode.
|
|
if (ASMJIT_UNLIKELY((combinedOpFlags & X86Inst::kOpGpq) != 0))
|
|
return DebugUtils::errored(kErrorInvalidUseOfGpq);
|
|
}
|
|
else {
|
|
// Illegal use of a high 8-bit register with REX prefix.
|
|
if (ASMJIT_UNLIKELY((combinedOpFlags & X86Inst::kOpGpbHi) != 0 && (combinedRegMask & 0xFFFFFF00U) != 0))
|
|
return DebugUtils::errored(kErrorInvalidUseOfGpbHi);
|
|
}
|
|
|
|
// Validate instruction operands.
|
|
const X86Inst::CommonData* commonData = &iData->getCommonData();
|
|
const X86Inst::ISignature* iSig = X86InstDB::iSignatureData + commonData->_iSignatureIndex;
|
|
const X86Inst::ISignature* iEnd = iSig + commonData->_iSignatureCount;
|
|
|
|
if (iSig != iEnd) {
|
|
const X86Inst::OSignature* oSigData = X86InstDB::oSignatureData;
|
|
|
|
// 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->archMask & archMask) == 0) continue;
|
|
|
|
// Compare the operands table with reference operands.
|
|
uint32_t j = 0;
|
|
uint32_t iSigCount = iSig->opCount;
|
|
bool localImmOutOfRange = false;
|
|
|
|
if (iSigCount == count) {
|
|
for (j = 0; j < count; j++)
|
|
if (!x86CheckOSig(oSigTranslated[j], oSigData[iSig->operands[j]], localImmOutOfRange))
|
|
break;
|
|
}
|
|
else if (iSigCount - iSig->implicit == count) {
|
|
uint32_t r = 0;
|
|
for (j = 0; j < count && r < iSigCount; j++, r++) {
|
|
const X86Inst::OSignature* oChk = oSigTranslated + j;
|
|
const X86Inst::OSignature* oRef;
|
|
Next:
|
|
oRef = oSigData + iSig->operands[r];
|
|
// Skip implicit.
|
|
if ((oRef->flags & X86Inst::kOpImplicit) != 0) {
|
|
if (++r >= iSigCount)
|
|
break;
|
|
else
|
|
goto Next;
|
|
}
|
|
|
|
if (!x86CheckOSig(*oChk, *oRef, localImmOutOfRange))
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (j == count) {
|
|
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 AVX-512 options:
|
|
const RegOnly& extraReg = detail.extraReg;
|
|
const uint32_t kAvx512Options = X86Inst::kOptionZMask |
|
|
X86Inst::kOption1ToX |
|
|
X86Inst::kOptionER |
|
|
X86Inst::kOptionSAE ;
|
|
|
|
if (!extraReg.isNone() || (options & kAvx512Options)) {
|
|
if (commonData->hasFlag(X86Inst::kFlagEvex)) {
|
|
// Validate AVX-512 {k} and {k}{z}.
|
|
if (!extraReg.isNone()) {
|
|
// Mask can only be specified by a 'k' register.
|
|
if (ASMJIT_UNLIKELY(extraReg.getType() != X86Reg::kRegK))
|
|
return DebugUtils::errored(kErrorInvalidKMaskReg);
|
|
|
|
if (ASMJIT_UNLIKELY(!commonData->hasAvx512K()))
|
|
return DebugUtils::errored(kErrorInvalidKMaskUse);
|
|
}
|
|
|
|
if ((options & X86Inst::kOptionZMask)) {
|
|
if (ASMJIT_UNLIKELY((options & X86Inst::kOptionZMask) != 0 && !commonData->hasAvx512Z()))
|
|
return DebugUtils::errored(kErrorInvalidKZeroUse);
|
|
}
|
|
|
|
// Validate AVX-512 broadcast {1tox}.
|
|
if (options & X86Inst::kOption1ToX) {
|
|
if (ASMJIT_UNLIKELY(!memOp))
|
|
return DebugUtils::errored(kErrorInvalidBroadcast);
|
|
|
|
uint32_t size = memOp->getSize();
|
|
if (size != 0) {
|
|
// The the size is specified it has to match the broadcast size.
|
|
if (ASMJIT_UNLIKELY(commonData->hasAvx512B32() && size != 4))
|
|
return DebugUtils::errored(kErrorInvalidBroadcast);
|
|
|
|
if (ASMJIT_UNLIKELY(commonData->hasAvx512B64() && size != 8))
|
|
return DebugUtils::errored(kErrorInvalidBroadcast);
|
|
}
|
|
}
|
|
|
|
// Validate AVX-512 {sae} and {er}.
|
|
if (options & (X86Inst::kOptionSAE | X86Inst::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 & X86Inst::kOptionER) {
|
|
// NOTE: if both {sae} and {er} are set, we don't care, as {sae} is implied.
|
|
if (ASMJIT_UNLIKELY(!commonData->hasAvx512ER()))
|
|
return DebugUtils::errored(kErrorInvalidEROrSAE);
|
|
|
|
// {er} is defined for scalar ops or vector ops using zmm (LL = 10). 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 (commonData->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' enabled instruction with less than two operands.
|
|
ASMJIT_ASSERT(count >= 2);
|
|
if (ASMJIT_UNLIKELY(!X86Reg::isZmm(operands[0]) && !X86Reg::isZmm(operands[1])))
|
|
return DebugUtils::errored(kErrorInvalidEROrSAE);
|
|
}
|
|
}
|
|
else {
|
|
// {sae} doesn't have the same limitations as {er}, this is enough.
|
|
if (ASMJIT_UNLIKELY(!commonData->hasAvx512SAE()))
|
|
return DebugUtils::errored(kErrorInvalidEROrSAE);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
// Not AVX512 instruction - maybe OpExtra is xCX register used by REP/REPNZ prefix. Otherwise the instruction is invalid.
|
|
if ((options & kAvx512Options) || (options & (X86Inst::kOptionRep | X86Inst::kOptionRepnz)) == 0)
|
|
return DebugUtils::errored(kErrorInvalidInstruction);
|
|
}
|
|
}
|
|
|
|
return kErrorOk;
|
|
}
|
|
#endif
|
|
|
|
// ============================================================================
|
|
// [asmjit::X86InstImpl - CheckFeatures]
|
|
// ============================================================================
|
|
|
|
#if !defined(ASMJIT_DISABLE_EXTENSIONS)
|
|
ASMJIT_FAVOR_SIZE static uint32_t x86GetRegTypesMask(const Operand_* operands, uint32_t count) noexcept {
|
|
uint32_t mask = 0;
|
|
for (uint32_t i = 0; i < count; i++) {
|
|
const Operand_& op = operands[i];
|
|
if (op.isReg()) {
|
|
const Reg& reg = op.as<Reg>();
|
|
mask |= Utils::mask(reg.getType());
|
|
}
|
|
else if (op.isMem()) {
|
|
const Mem& mem = op.as<Mem>();
|
|
if (mem.hasBaseReg()) mask |= Utils::mask(mem.getBaseType());
|
|
if (mem.hasIndexReg()) mask |= Utils::mask(mem.getIndexType());
|
|
}
|
|
}
|
|
return mask;
|
|
}
|
|
|
|
ASMJIT_FAVOR_SIZE Error X86InstImpl::checkFeatures(uint32_t archType, const Inst::Detail& detail, const Operand_* operands, uint32_t count, CpuFeatures& out) noexcept {
|
|
if (!ArchInfo::isX86Family(archType))
|
|
return DebugUtils::errored(kErrorInvalidArch);
|
|
|
|
// Get the instruction data.
|
|
uint32_t instId = detail.instId;
|
|
if (ASMJIT_UNLIKELY(instId >= X86Inst::_kIdCount))
|
|
return DebugUtils::errored(kErrorInvalidArgument);
|
|
|
|
const X86Inst* iData = &X86InstDB::instData[instId];
|
|
const X86Inst::OperationData& od = iData->getOperationData();
|
|
|
|
const uint8_t* fData = od.getFeaturesData();
|
|
const uint8_t* fEnd = od.getFeaturesEnd();
|
|
|
|
// Copy all features to `out`.
|
|
out.reset();
|
|
do {
|
|
uint32_t feature = fData[0];
|
|
if (!feature)
|
|
break;
|
|
out.add(feature);
|
|
} while (++fData != fEnd);
|
|
|
|
// Since AsmJit merges all instructions that share the same name we have to
|
|
// deal with some special cases and also with MMX/SSE and AVX/AVX2 overlaps.
|
|
|
|
// Only proceed if there were some CPU flags set.
|
|
if (fData != od.getFeaturesData()) {
|
|
uint32_t mask = x86GetRegTypesMask(operands, count);
|
|
|
|
// Check for MMX vs SSE overlap.
|
|
if (out.has(CpuInfo::kX86FeatureMMX) || out.has(CpuInfo::kX86FeatureMMX2)) {
|
|
// Only instructions defined by SSE and SSE2 overlap. Instructions introduced
|
|
// by newer instruction sets like SSE3+ don't state MMX as they require SSE3+.
|
|
if (out.has(CpuInfo::kX86FeatureSSE) || out.has(CpuInfo::kX86FeatureSSE2)) {
|
|
if (!(mask & Utils::mask(X86Reg::kRegXmm))) {
|
|
// The instruction doesn't use XMM register(s), thus it's MMX/MMX2 only.
|
|
out.remove(CpuInfo::kX86FeatureSSE);
|
|
out.remove(CpuInfo::kX86FeatureSSE2);
|
|
}
|
|
else {
|
|
out.remove(CpuInfo::kX86FeatureMMX);
|
|
out.remove(CpuInfo::kX86FeatureMMX2);
|
|
}
|
|
|
|
// Special case: PEXTRW instruction is MMX/SSE2 instruction. However, this
|
|
// instruction couldn't access memory (only register to register extract) so
|
|
// when SSE4.1 introduced the whole family of PEXTR/PINSR instructions they
|
|
// also introduced PEXTRW with a new opcode 0x15 that can extract directly to
|
|
// memory. This instruction is, of course, not compatible with MMX/SSE2 one.
|
|
if (instId == X86Inst::kIdPextrw && count > 0 && !operands[0].isMem()) {
|
|
out.remove(CpuInfo::kX86FeatureSSE4_1);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check for AVX vs AVX2 overlap.
|
|
if (out.has(CpuInfo::kX86FeatureAVX) && out.has(CpuInfo::kX86FeatureAVX2)) {
|
|
bool isAVX2 = true;
|
|
// Special case: VBROADCASTSS and VBROADCASTSD were introduced in AVX, but
|
|
// only version that uses memory as a source operand. AVX2 then added support
|
|
// for register source operand.
|
|
if (instId == X86Inst::kIdVbroadcastss || instId == X86Inst::kIdVbroadcastsd) {
|
|
if (count > 1 && operands[0].isMem())
|
|
isAVX2 = false;
|
|
}
|
|
else {
|
|
// AVX instruction set doesn't support integer operations on YMM registers
|
|
// as these were later introcuced by AVX2. In our case we have to check if
|
|
// YMM register(s) are in use and if that is the case this is an AVX2 instruction.
|
|
if (!(mask & Utils::mask(X86Reg::kRegYmm, X86Reg::kRegZmm)))
|
|
isAVX2 = false;
|
|
}
|
|
|
|
if (isAVX2)
|
|
out.remove(CpuInfo::kX86FeatureAVX);
|
|
else
|
|
out.remove(CpuInfo::kX86FeatureAVX2);
|
|
}
|
|
|
|
// Check for AVX|AVX2|FMA|F16C vs AVX512 overlap.
|
|
if (out.has(CpuInfo::kX86FeatureAVX) || out.has(CpuInfo::kX86FeatureAVX2) || out.has(CpuInfo::kX86FeatureFMA) || out.has(CpuInfo::kX86FeatureF16C)) {
|
|
// Only AVX512-F|BW|DQ allow to encode AVX/AVX2 instructions
|
|
if (out.has(CpuInfo::kX86FeatureAVX512_F) || out.has(CpuInfo::kX86FeatureAVX512_BW) || out.has(CpuInfo::kX86FeatureAVX512_DQ)) {
|
|
uint32_t options = detail.options;
|
|
uint32_t kAvx512Options = X86Inst::kOptionEvex | X86Inst::_kOptionAvx512Mask;
|
|
|
|
if (!(mask & Utils::mask(X86Reg::kRegZmm, X86Reg::kRegK)) && !(options & (kAvx512Options)) && detail.extraReg.getType() != X86Reg::kRegK) {
|
|
out.remove(CpuInfo::kX86FeatureAVX512_F)
|
|
.remove(CpuInfo::kX86FeatureAVX512_BW)
|
|
.remove(CpuInfo::kX86FeatureAVX512_DQ)
|
|
.remove(CpuInfo::kX86FeatureAVX512_VL);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Remove or keep AVX512_VL feature.
|
|
if (out.has(CpuInfo::kX86FeatureAVX512_VL)) {
|
|
if (!(mask & Utils::mask(X86Reg::kRegZmm)))
|
|
out.remove(CpuInfo::kX86FeatureAVX512_VL);
|
|
}
|
|
}
|
|
|
|
return kErrorOk;
|
|
}
|
|
#endif
|
|
|
|
} // asmjit namespace
|
|
|
|
// [Api-End]
|
|
#include "../asmjit_apiend.h"
|