Files
kobalicek b56f4176cb Codebase update and improvements, instruction DB update
* Denested src folder to root, renamed testing to asmjit-testing

  * Refactored how headers are included into <asmjit/...> form. This
    is necessary as compilers would never simplify a path once a ..
    appears in include directory - then paths such as ../core/../core
    appeared in asserts, which was ugly

  * Moved support utilities into asmjit/support/... (still included
    by asmjit/core.h for convenience and compatibility)

  * Added CMakePresets.json for making it easy to develop AsmJit

  * Reworked CMakeLists to be shorter and use CMake option(),
    etc... This simplifies it and makes it using more standard
    features

  * ASMJIT_EMBED now creates asmjit_embed INTERFACE library,
    which is accessible via asmjit::asmjit target - this simplifies
    embedding and makes it the same as library targets from a CMake
    perspective

  * Removed ASMJIT_DEPS - this is now provided by cmake target
    aliases - 'asmjit::asmjit' so users should not need this variable

  * Changed meaning of ASMJIT_LIBS - this now contains only AsmJit
    dependencies without asmjit::asmjit target alias. Don't rely on
    ASMJIT_LIBS anymore as it's only used internally

  * Removed ASMJIT_NO_DEPRECATED option - AsmJit is not going
    to provide controllable deprecations in the future

  * Removed ASMJIT_NO_VALIDATION in favor of ASMJIT_NO_INTROSPECTION,
    which now controls query, features, and validation API presence

  * Removed ASMJIT_DIR option - it was never really needed

  * Removed AMX_TRANSPOSE feature from instruction database (X86).
    Intel has removed it as well, so it's a feature that won't
    be siliconized
2025-11-02 22:31:46 +01:00

7592 lines
274 KiB
C++

// This file is part of AsmJit project <https://asmjit.com>
//
// See <asmjit/core.h> or LICENSE.md for license and copyright information
// SPDX-License-Identifier: Zlib
#include <asmjit/core/api-build_p.h>
#include <asmjit/ujit/ujitbase.h>
#if defined(ASMJIT_UJIT_X86)
#include <asmjit/ujit/unicompiler.h>
#include <asmjit/ujit/unicompiler_utils_p.h>
#include <asmjit/ujit/unicondition.h>
ASMJIT_BEGIN_SUB_NAMESPACE(ujit)
using GPExt = UniCompiler::GPExt;
using SSEExt = UniCompiler::SSEExt;
using AVXExt = UniCompiler::AVXExt;
namespace Inst { using namespace x86::Inst; }
// ujit::UniCompiler - Constants
// =============================
static constexpr OperandSignature signature_of_xmm_ymm_zmm[] = {
OperandSignature{RegTraits<RegType::kVec128>::kSignature},
OperandSignature{RegTraits<RegType::kVec256>::kSignature},
OperandSignature{RegTraits<RegType::kVec512>::kSignature}
};
static ASMJIT_INLINE RegType vec_reg_type_from_width(VecWidth vw) noexcept {
return RegType(uint32_t(RegType::kVec128) + uint32_t(vw));
}
// ujit::UniCompiler - Construction & Destruction
// ==============================================
UniCompiler::UniCompiler(BackendCompiler* cc, const CpuFeatures& features, CpuHints cpu_hints, VecConstTableRef ct_ref) noexcept
: cc(cc),
_ct_ref(ct_ref),
_features(features),
_cpu_hints(cpu_hints),
_vec_reg_count(16),
_common_table_offset(128) {
_scalar_op_behavior = ScalarOpBehavior::kPreservingVec128;
_fmin_fmax_op_behavior = FMinFMaxOpBehavior::kTernaryLogic;
_fmadd_op_behavior = FMAddOpBehavior::kNoFMA; // Will be changed by _init_extensions() if supported.
_float_to_int_outside_range_behavior = FloatToIntOutsideRangeBehavior::kSmallestValue;
_init_extensions(features);
}
UniCompiler::~UniCompiler() noexcept {}
// ujit::UniCompiler - CPU Architecture, Features and Optimization Options
// =======================================================================
void UniCompiler::_init_extensions(const CpuFeatures& features) noexcept {
uint32_t gp_ext_mask = 0;
uint32_t sse_ext_mask = 0;
uint64_t avx_ext_mask = 0;
if (features.x86().has_adx()) gp_ext_mask |= 1u << uint32_t(GPExt::kADX);
if (features.x86().has_bmi()) gp_ext_mask |= 1u << uint32_t(GPExt::kBMI);
if (features.x86().has_bmi2()) gp_ext_mask |= 1u << uint32_t(GPExt::kBMI2);
if (features.x86().has_lzcnt()) gp_ext_mask |= 1u << uint32_t(GPExt::kLZCNT);
if (features.x86().has_movbe()) gp_ext_mask |= 1u << uint32_t(GPExt::kMOVBE);
if (features.x86().has_popcnt()) gp_ext_mask |= 1u << uint32_t(GPExt::kPOPCNT);
sse_ext_mask |= 1u << uint32_t(SSEExt::kSSE2);
if (features.x86().has_sse3()) sse_ext_mask |= 1u << uint32_t(SSEExt::kSSE3);
if (features.x86().has_ssse3()) sse_ext_mask |= 1u << uint32_t(SSEExt::kSSSE3);
if (features.x86().has_sse4_1()) sse_ext_mask |= 1u << uint32_t(SSEExt::kSSE4_1);
if (features.x86().has_sse4_2()) sse_ext_mask |= 1u << uint32_t(SSEExt::kSSE4_2);
if (features.x86().has_pclmulqdq()) sse_ext_mask |= 1u << uint32_t(SSEExt::kPCLMULQDQ);
if (features.x86().has_avx()) {
avx_ext_mask |= uint64_t(1) << uint32_t(AVXExt::kAVX);
if (features.x86().has_avx2() ) avx_ext_mask |= uint64_t(1) << uint32_t(AVXExt::kAVX2);
if (features.x86().has_f16c() ) avx_ext_mask |= uint64_t(1) << uint32_t(AVXExt::kF16C);
if (features.x86().has_fma() ) avx_ext_mask |= uint64_t(1) << uint32_t(AVXExt::kFMA);
if (features.x86().has_gfni() ) avx_ext_mask |= uint64_t(1) << uint32_t(AVXExt::kGFNI);
if (features.x86().has_vaes() ) avx_ext_mask |= uint64_t(1) << uint32_t(AVXExt::kVAES);
if (features.x86().has_vpclmulqdq() ) avx_ext_mask |= uint64_t(1) << uint32_t(AVXExt::kVPCLMULQDQ);
if (features.x86().has_avx_ifma() ) avx_ext_mask |= uint64_t(1) << uint32_t(AVXExt::kAVX_IFMA);
if (features.x86().has_avx_ne_convert() ) avx_ext_mask |= uint64_t(1) << uint32_t(AVXExt::kAVX_NE_CONVERT);
if (features.x86().has_avx_vnni() ) avx_ext_mask |= uint64_t(1) << uint32_t(AVXExt::kAVX_VNNI);
if (features.x86().has_avx_vnni_int8() ) avx_ext_mask |= uint64_t(1) << uint32_t(AVXExt::kAVX_VNNI_INT8);
if (features.x86().has_avx_vnni_int16() ) avx_ext_mask |= uint64_t(1) << uint32_t(AVXExt::kAVX_VNNI_INT16);
}
if (features.x86().has_avx2() && features.x86().has_avx512_f() &&
features.x86().has_avx512_cd() && features.x86().has_avx512_bw() &&
features.x86().has_avx512_dq() && features.x86().has_avx512_vl()) {
_vec_reg_count = 32;
avx_ext_mask |= uint64_t(1) << uint32_t(AVXExt::kAVX512);
if (features.x86().has_avx512_bf16() ) avx_ext_mask |= uint64_t(1) << uint32_t(AVXExt::kAVX512_BF16);
if (features.x86().has_avx512_bitalg() ) avx_ext_mask |= uint64_t(1) << uint32_t(AVXExt::kAVX512_BITALG);
if (features.x86().has_avx512_fp16() ) avx_ext_mask |= uint64_t(1) << uint32_t(AVXExt::kAVX512_FP16);
if (features.x86().has_avx512_ifma() ) avx_ext_mask |= uint64_t(1) << uint32_t(AVXExt::kAVX512_IFMA);
if (features.x86().has_avx512_vbmi() ) avx_ext_mask |= uint64_t(1) << uint32_t(AVXExt::kAVX512_VBMI);
if (features.x86().has_avx512_vbmi2() ) avx_ext_mask |= uint64_t(1) << uint32_t(AVXExt::kAVX512_VBMI2);
if (features.x86().has_avx512_vnni() ) avx_ext_mask |= uint64_t(1) << uint32_t(AVXExt::kAVX512_VNNI);
if (features.x86().has_avx512_vpopcntdq()) avx_ext_mask |= uint64_t(1) << uint32_t(AVXExt::kAVX512_VPOPCNTDQ);
}
_gp_ext_mask = gp_ext_mask;
_sse_ext_mask = sse_ext_mask;
_avx_ext_mask = avx_ext_mask;
if (has_fma()) {
_fmadd_op_behavior = FMAddOpBehavior::kFMAStoreToAny;
}
}
VecWidth UniCompiler::max_vec_width_from_cpu_features() noexcept {
// Use 512-bit SIMD width if AVX512 is available and the target is 64-bit. We never use 512-bit SIMD in 32-bit mode
// as it doesn't have enough registers to hold 512-bit constants and we don't store 512-bit constants in memory
// (they must be broadcasted to full width).
if (has_avx512() && is_64bit())
return VecWidth::k512;
// Use 256-bit SIMD width if AVX2 is available.
if (has_avx2())
return VecWidth::k256;
return VecWidth::k128;
}
void UniCompiler::init_vec_width(VecWidth vw) noexcept {
_vec_width = vw;
_vec_reg_type = vec_reg_type_from_width(vw);
_vec_type_id = RegUtils::type_id_of(_vec_reg_type);
_vec_multiplier = uint8_t(1u << (uint32_t(_vec_reg_type) - uint32_t(RegType::kVec128)));
}
bool UniCompiler::has_masked_access_of(uint32_t data_size) const noexcept {
switch (data_size) {
case 1: return has_cpu_hint(CpuHints::kVecMaskedOps8);
case 2: return has_cpu_hint(CpuHints::kVecMaskedOps16);
case 4: return has_cpu_hint(CpuHints::kVecMaskedOps32);
case 8: return has_cpu_hint(CpuHints::kVecMaskedOps64);
default:
return false;
}
}
// ujit::UniCompiler - Embed
// =========================
void UniCompiler::embed_jump_table(Span<const Label> jump_table, const Label& jump_table_base, uint32_t entry_size) {
static const uint8_t zeros[8] {};
for (const Label& label : jump_table) {
if (label.is_valid()) {
cc->embed_label_delta(label, jump_table_base, entry_size);
}
else {
cc->embed(zeros, entry_size);
}
}
}
// ujit::UniCompiler - Function
// ============================
void UniCompiler::hook_func() noexcept {
FuncNode* func = cc->func();
_func_init_hook = func;
if (func && has_avx()) {
func->frame().set_avx_enabled();
func->frame().set_avx_auto_cleanup();
if (has_avx512()) {
func->frame().set_avx512_enabled();
}
}
}
void UniCompiler::unhook_func() noexcept {
_func_init_hook = nullptr;
}
// ujit::UniCompiler - Constants
// =============================
void UniCompiler::_init_vec_const_table_ptr() {
const void* ct_addr = ct_ptr<void>();
if (!_common_table_ptr.is_valid()) {
ScopedInjector injector(cc, &_func_init_hook);
_common_table_ptr = new_gpz("common_table_ptr");
cc->mov(_common_table_ptr, (int64_t)ct_addr + _common_table_offset);
}
}
x86::KReg UniCompiler::k_const(uint64_t value) {
uint32_t slot;
for (slot = 0; slot < kMaxKRegConstCount; slot++)
if (_k_reg[slot].is_valid() && _k_imm[slot] == value)
return _k_reg[slot];
BaseNode* prev_node = nullptr;
Gp tmp;
x86::KReg kReg;
if (slot < kMaxKRegConstCount) {
prev_node = cc->set_cursor(_func_init_hook);
}
if (value & 0xFFFFFFFF00000000u) {
tmp = new_gp64("kTmp");
kReg = cc->new_kq("k0x%016llX", (unsigned long long)value);
cc->mov(tmp, value);
cc->kmovq(kReg, tmp);
}
else {
tmp = new_gp32("kTmp");
kReg = cc->new_kd("k0x%08llX", (unsigned long long)value);
cc->mov(tmp, value);
cc->kmovd(kReg, tmp);
}
if (slot < kMaxKRegConstCount) {
_k_reg[slot] = kReg;
_func_init_hook = cc->set_cursor(prev_node);
}
return kReg;
}
Operand UniCompiler::simd_const(const void* c, Bcst bcst_width, VecWidth const_width) {
size_t const_count = _vec_consts.size();
for (size_t i = 0; i < const_count; i++) {
if (_vec_consts[i].ptr == c) {
return Vec(signature_of_xmm_ymm_zmm[size_t(const_width)], _vec_consts[i].virt_reg_id);
}
}
// We don't use memory constants when compiling for AVX-512, because we don't store 64-byte constants and AVX-512
// has enough registers to hold all the constants that we need. However, in SSE/AVX2 case, we don't want so many
// constants in registers as that could limit registers that we need during fetching and composition.
if (!has_avx512()) {
bool use_vreg = (c == &ct().p_0000000000000000); // Required if the CPU doesn't have SSE4.1.
if (!use_vreg) {
return simd_mem_const(c, bcst_width, const_width);
}
}
return Vec(signature_of_xmm_ymm_zmm[size_t(const_width)], _new_vec_const(c, bcst_width == Bcst::kNA_Unique).id());
}
Operand UniCompiler::simd_const(const void* c, Bcst bcst_width, const Vec& similar_to) {
VecWidth const_width = VecWidth(uint32_t(similar_to.reg_type()) - uint32_t(RegType::kVec128));
return simd_const(c, bcst_width, const_width);
}
Operand UniCompiler::simd_const(const void* c, Bcst bcst_width, const VecArray& similar_to) {
ASMJIT_ASSERT(!similar_to.is_empty());
VecWidth const_width = VecWidth(uint32_t(similar_to[0].reg_type()) - uint32_t(RegType::kVec128));
return simd_const(c, bcst_width, const_width);
}
Vec UniCompiler::simd_vec_const(const void* c, Bcst bcst_width, VecWidth const_width) {
size_t const_count = _vec_consts.size();
for (size_t i = 0; i < const_count; i++)
if (_vec_consts[i].ptr == c)
return Vec(signature_of_xmm_ymm_zmm[size_t(const_width)], _vec_consts[i].virt_reg_id);
return Vec(signature_of_xmm_ymm_zmm[size_t(const_width)], _new_vec_const(c, bcst_width == Bcst::kNA_Unique).id());
}
Vec UniCompiler::simd_vec_const(const void* c, Bcst bcst_width, const Vec& similar_to) {
VecWidth const_width = VecWidth(uint32_t(similar_to.reg_type()) - uint32_t(RegType::kVec128));
return simd_vec_const(c, bcst_width, const_width);
}
Vec UniCompiler::simd_vec_const(const void* c, Bcst bcst_width, const VecArray& similar_to) {
ASMJIT_ASSERT(!similar_to.is_empty());
VecWidth const_width = VecWidth(uint32_t(similar_to[0].reg_type()) - uint32_t(RegType::kVec128));
return simd_vec_const(c, bcst_width, const_width);
}
x86::Mem UniCompiler::simd_mem_const(const void* c, Bcst bcst_width, VecWidth const_width) {
x86::Mem m = _get_mem_const(c);
if (const_width != VecWidth::k512)
return m;
x86::Mem::Broadcast bcst = x86::Mem::Broadcast::kNone;
switch (bcst_width) {
case Bcst::k8: bcst = x86::Mem::Broadcast::k1To64; break;
case Bcst::k16: bcst = x86::Mem::Broadcast::k1To32; break;
case Bcst::k32: bcst = x86::Mem::Broadcast::k1To16; break;
case Bcst::k64: bcst = x86::Mem::Broadcast::k1To8; break;
default: bcst = x86::Mem::Broadcast::kNone; break;
}
m.set_broadcast(bcst);
return m;
}
x86::Mem UniCompiler::simd_mem_const(const void* c, Bcst bcst_width, const Vec& similar_to) {
VecWidth const_width = VecWidth(uint32_t(similar_to.reg_type()) - uint32_t(RegType::kVec128));
return simd_mem_const(c, bcst_width, const_width);
}
x86::Mem UniCompiler::simd_mem_const(const void* c, Bcst bcst_width, const VecArray& similar_to) {
ASMJIT_ASSERT(!similar_to.is_empty());
VecWidth const_width = VecWidth(uint32_t(similar_to[0].reg_type()) - uint32_t(RegType::kVec128));
return simd_mem_const(c, bcst_width, const_width);
}
x86::Mem UniCompiler::_get_mem_const(const void* c) {
// Make sure we are addressing a constant from the `commonTable` constant pool.
const void* ct_addr = ct_ptr<void>();
ASMJIT_ASSERT((uintptr_t)c >= (uintptr_t)ct_addr &&
(uintptr_t)c < (uintptr_t)ct_addr + _ct_ref.size);
if (is_32bit()) {
// 32-bit mode - These constants will never move in memory so the absolute addressing is a win/win as we can save
// one GP register that can be used for something else.
return x86::ptr((uint64_t)c);
}
else {
// 64-bit mode - One GP register is sacrificed to hold the pointer to the `ct`. This is probably the safest
// approach as relying on absolute addressing or anything else could lead to problems or performance issues.
_init_vec_const_table_ptr();
int32_t disp = int32_t((intptr_t)c - (intptr_t)ct_addr);
return x86::ptr(_common_table_ptr, disp - _common_table_offset);
}
}
Vec UniCompiler::_new_vec_const(const void* c, bool is_unique_const) {
Vec vec;
const char* special_const_name = nullptr;
if (special_const_name) {
vec = new_vec_with_width(vec_width(), special_const_name);
}
else {
uint64_t u0 = static_cast<const uint64_t*>(c)[0];
uint64_t u1 = static_cast<const uint64_t*>(c)[1];
if (u0 != u1)
vec = new_vec_with_width(vec_width(), "c_0x%016llX%016llX", (unsigned long long)u1, (unsigned long long)u0);
else if ((u0 >> 32) != (u0 & 0xFFFFFFFFu))
vec = new_vec_with_width(vec_width(), "c_0x%016llX", (unsigned long long)u0);
else if (((u0 >> 16) & 0xFFFFu) != (u0 & 0xFFFFu))
vec = new_vec_with_width(vec_width(), "c_0x%08X", (unsigned)(u0 & 0xFFFFFFFFu));
else
vec = new_vec_with_width(vec_width(), "c_0x%04X", (unsigned)(u0 & 0xFFFFu));
}
VecConstData const_data;
const_data.ptr = c;
const_data.virt_reg_id = vec.id();
_vec_consts.append(arena(), const_data);
if (c == &ct().p_0000000000000000) {
ScopedInjector inject(cc, &_func_init_hook);
v_zero_i(vec.xmm());
}
else {
// NOTE: _get_mem_const() must be outside of injected code as it uses injection too.
Mem m = _get_mem_const(c);
ScopedInjector inject(cc, &_func_init_hook);
if (has_avx512() && !vec.is_vec128() && !is_unique_const)
cc->vbroadcasti32x4(vec, m);
else if (has_avx2() && vec.is_vec256() && !is_unique_const)
cc->vbroadcasti128(vec, m);
else if (has_avx512())
cc->vmovdqa32(vec, m); // EVEX prefix has a compressed displacement, which is smaller.
else
v_loadavec(vec, m);
}
return vec;
}
// ujit::UniCompiler - Stack
// =========================
x86::Mem UniCompiler::tmp_stack(StackId id, uint32_t size) {
ASMJIT_ASSERT(Support::is_power_of_2(size));
ASMJIT_ASSERT(size <= 64);
// Only used by asserts.
Support::maybe_unused(size);
Mem& stack = _tmp_stack[size_t(id)];
if (!stack.base_id())
stack = cc->new_stack(64, 16, "tmp_stack");
return stack;
}
// ujit::UniCompiler - General Purpose Instructions - Conditions
// =============================================================
static constexpr InstId condition_to_inst_id[size_t(UniOpCond::kMaxValue) + 1] = {
Inst::kIdAnd, // UniOpCond::kAssignAnd
Inst::kIdOr, // UniOpCond::kAssignOr
Inst::kIdXor, // UniOpCond::kAssignXor
Inst::kIdAdd, // UniOpCond::kAssignAdd
Inst::kIdSub, // UniOpCond::kAssignSub
Inst::kIdShr, // UniOpCond::kAssignShr
Inst::kIdTest, // UniOpCond::kTest
Inst::kIdBt, // UniOpCond::kBitTest
Inst::kIdCmp // UniOpCond::kCompare
};
class ConditionApplier : public UniCondition {
public:
ASMJIT_INLINE ConditionApplier(const UniCondition& condition) noexcept : UniCondition(condition) {
// The first operand must always be a register.
ASMJIT_ASSERT(a.is_gp());
}
ASMJIT_NOINLINE void optimize(UniCompiler& uc) noexcept {
switch (op) {
case UniOpCond::kAssignShr:
if (b.is_imm() && b.as<Imm>().value() == 0) {
if (a.is_gp32()) {
// Shifting by 0 would not set the flags...
op = UniOpCond::kAssignAnd;
b = a;
}
else {
op = UniOpCond::kTest;
b = a;
}
}
break;
case UniOpCond::kCompare:
if (b.is_imm() && b.as<Imm>().value() == 0 && (cond == CondCode::kEqual || cond == CondCode::kNotEqual)) {
op = UniOpCond::kTest;
b = a;
reverse();
}
break;
case UniOpCond::kBitTest: {
if (b.is_imm()) {
uint64_t bit_index = b.as<Imm>().value_as<uint64_t>();
// NOTE: AMD has no performance difference between 'test' and 'bt' instructions, however, Intel can execute less
// 'bt' instructions per cycle than 'test's, so we prefer 'test' if bit_index is low. Additionally, we only use
// test on 64-bit hardware as it's guaranteed that any register index is encodable. On 32-bit hardware only the
// first 4 registers can be used, which could mean that the register would have to be moved just to be tested,
// which is something we would like to avoid.
if (uc.is_64bit() && bit_index < 8) {
op = UniOpCond::kTest;
b = Imm(1u << bit_index);
cond = cond == CondCode::kC ? CondCode::kNZ : CondCode::kZ;
}
}
break;
}
default:
break;
}
}
ASMJIT_INLINE void reverse() noexcept {
cond = x86::reverse_cond(cond);
}
ASMJIT_NOINLINE void emit(UniCompiler& uc) {
BackendCompiler* cc = uc.cc;
InstId inst_id = condition_to_inst_id[size_t(op)];
if (inst_id == Inst::kIdTest && cc->is_64bit()) {
if (b.is_imm() && b.as<Imm>().value_as<uint64_t>() <= 255u) {
// Emit 8-bit operation if targeting 64-bit mode and the immediate fits 8 bits.
cc->test(a.as<Gp>().r8(), b.as<Imm>());
return;
}
else if (a.as<Gp>().size() > 4 && b.is_imm() && uint64_t(b.as<Imm>().value()) <= 0xFFFFFFFFu) {
// Emit 32-bit operation if targeting 64-bit mode and the immediate is lesser than UINT32_MAX.
// This possibly saves a REX prefix required to promote the instruction to a 64-bit operation.
cc->test(a.as<Gp>().r32(), b.as<Imm>());
return;
}
}
if (inst_id == Inst::kIdShr && b.is_reg()) {
cc->emit(inst_id, a, b.as<Gp>().r8());
return;
}
cc->emit(inst_id, a, b);
}
};
// ujit::UniCompiler - General Purpose Instructions - Emit
// =======================================================
void UniCompiler::emit_mov(const Gp& dst, const Operand_& src) {
if (src.is_imm() && src.as<Imm>().value() == 0) {
Gp r(dst);
if (r.is_gp64())
r = r.r32();
cc->xor_(r, r);
}
else {
cc->emit(Inst::kIdMov, dst, src);
}
}
void UniCompiler::emit_m(UniOpM op, const Mem& m_) {
static constexpr uint8_t size_table[] = {
1, // Prefetch
0, // kStoreZeroReg
1, // kStoreZeroU8
2, // kStoreZeroU16
4, // kStoreZeroU32
8 // kStoreZeroU64
};
if (op == UniOpM::kPrefetch) {
cc->prefetcht0(m_);
}
else {
Mem m(m_);
uint32_t size = size_table[size_t(op)];
if (size == 0)
size = cc->register_size();
m.set_size(size);
cc->mov(m, 0);
}
}
void UniCompiler::emit_rm(UniOpRM op, const Gp& dst, const Mem& src) {
static constexpr uint8_t size_table[] = {
0, // kLoadReg
1, // kLoadI8
1, // kLoadU8
2, // kLoadI16
2, // kLoadU16
4, // kLoadI32
4, // kLoadU32
8, // kLoadI64
8, // kLoadU64
1, // kLoadMergeU8
1, // kLoadShiftU8
2, // kLoadMergeU16
2 // kLoadShiftU16
};
Gp r(dst);
Mem m(src);
InstId inst_id = Inst::kIdMov;
uint32_t size = size_table[size_t(op)];
switch (op) {
case UniOpRM::kLoadReg:
size = dst.size();
break;
case UniOpRM::kLoadU8:
case UniOpRM::kLoadU16:
case UniOpRM::kLoadU32:
r.set_signature(RegTraits<RegType::kGp32>::kSignature);
if (size < 4)
inst_id = Inst::kIdMovzx;
break;
case UniOpRM::kLoadI8:
case UniOpRM::kLoadI16:
inst_id = Inst::kIdMovsx;
break;
case UniOpRM::kLoadI32:
inst_id = dst.is_gp64() ? Inst::kIdMovsxd : Inst::kIdMov;
break;
case UniOpRM::kLoadI64:
case UniOpRM::kLoadU64:
ASMJIT_ASSERT(dst.is_gp64());
m.set_size(8);
break;
case UniOpRM::kLoadShiftU8:
cc->shl(r, 8);
[[fallthrough]];
case UniOpRM::kLoadMergeU8:
r = r.r8();
break;
case UniOpRM::kLoadShiftU16:
cc->shl(r, 16);
[[fallthrough]];
case UniOpRM::kLoadMergeU16:
r = r.r16();
break;
default:
ASMJIT_NOT_REACHED();
}
m.set_size(size);
cc->emit(inst_id, r, m);
}
struct UniOpMRInfo {
uint16_t inst_id;
uint16_t size;
};
void UniCompiler::emit_mr(UniOpMR op, const Mem& dst, const Gp& src) {
static constexpr UniOpMRInfo op_info_table[] = {
{ Inst::kIdMov, 0 }, // kStoreReg
{ Inst::kIdMov, 1 }, // kStoreU8
{ Inst::kIdMov, 2 }, // kStoreU16
{ Inst::kIdMov, 4 }, // kStoreU32
{ Inst::kIdMov, 8 }, // kStoreU64
{ Inst::kIdAdd, 0 }, // kAddReg,
{ Inst::kIdAdd, 1 }, // kAddU8,
{ Inst::kIdAdd, 2 }, // kAddU16,
{ Inst::kIdAdd, 4 }, // kAddU32,
{ Inst::kIdAdd, 8 } // kAddU64
};
Mem m(dst);
Gp r = src;
const UniOpMRInfo& op_info = op_info_table[size_t(op)];
uint32_t size = op_info.size;
switch (size) {
case 0: size = src.size(); break;
case 1: r = src.r8(); break;
case 2: r = src.r16(); break;
case 4: r = src.r32(); break;
case 8: r = src.r64(); break;
default:
ASMJIT_NOT_REACHED();
}
m.set_size(size);
cc->emit(op_info.inst_id, m, r);
}
void UniCompiler::emit_cmov(const Gp& dst, const Operand_& sel, const UniCondition& condition) {
ConditionApplier ca(condition);
ca.optimize(*this);
ca.emit(*this);
cc->emit(Inst::cmovcc_from_cond(ca.cond), dst, sel);
}
void UniCompiler::emit_select(const Gp& dst, const Operand_& sel1_, const Operand_& sel2_, const UniCondition& condition) {
ConditionApplier ca(condition);
ca.optimize(*this);
bool dst_is_a = ca.a.is_reg() && dst.id() == ca.a.as<Reg>().id();
bool dst_is_b = ca.b.is_reg() && dst.id() == ca.b.as<Reg>().id();
Operand sel1(sel1_);
Operand sel2(sel2_);
// Reverse the condition if we can place the immediate value first or if `dst == sel2`.
if ((!sel1.is_imm() && sel2.is_imm()) || (sel2.is_reg() && dst.id() == sel2.id())) {
ca.reverse();
std::swap(sel1, sel2);
}
bool dst_is_sel = sel1.is_reg() && dst.id() == sel1.id();
if (sel1 == sel2) {
if (!dst_is_sel)
cc->emit(Inst::kIdMov, dst, sel1);
return;
}
if (sel1.is_imm() && sel1.as<Imm>().value() == 0 && !dst_is_a && !dst_is_b && !dst_is_sel) {
cc->xor_(dst, dst);
ca.emit(*this);
}
else {
ca.emit(*this);
if (!dst_is_sel)
cc->emit(Inst::kIdMov, dst, sel1);
}
if (sel2.is_imm()) {
int64_t value = sel2.as<Imm>().value();
Mem sel2_mem = cc->new_const(ConstPoolScope::kLocal, &value, dst.size());
sel2 = sel2_mem;
}
cc->emit(Inst::cmovcc_from_cond(x86::negate_cond(ca.cond)), dst, sel2);
}
void UniCompiler::emit_2i(UniOpRR op, const Gp& dst, const Operand_& src_) {
Operand src(src_);
// Notes
//
// - CTZ:
// - INTEL - No difference, `bsf` and `tzcnt` both have latency ~2.5 cycles.
// - AMD - Big difference, `tzcnt` has only ~1.5 cycle latency while `bsf` has ~2.5 cycles.
// ArithOp Reg, Any
// ----------------
if (src.is_reg_or_mem()) {
switch (op) {
case UniOpRR::kCLZ: {
if (has_lzcnt()) {
cc->emit(Inst::kIdLzcnt, dst, src);
}
else {
uint32_t msk = (dst.size() * 8u) - 1u;
cc->emit(Inst::kIdBsr, dst, src);
cc->xor_(dst, msk);
}
return;
}
case UniOpRR::kCTZ: {
cc->emit(has_bmi() ? Inst::kIdTzcnt : Inst::kIdBsf, dst, src);
return;
}
case UniOpRR::kReflect: {
int nBits = int(dst.size()) * 8 - 1;
if (src.is_reg() && dst.id() == src.as<Reg>().id()) {
ASMJIT_ASSERT(dst.size() == src.as<Reg>().size());
Gp copy = new_similar_reg(dst, "@copy");
cc->mov(copy, dst);
cc->sar(copy, nBits);
cc->xor_(dst, copy);
}
else {
cc->emit(Inst::kIdMov, dst, src);
cc->sar(dst, nBits);
cc->emit(Inst::kIdXor, dst, src);
}
return;
}
default:
break;
}
}
// ArithOp Reg, Mem
// ----------------
if (src.is_mem()) {
switch (op) {
case UniOpRR::kBSwap: {
if (has_movbe()) {
cc->movbe(dst, src.as<Mem>());
}
else {
cc->mov(dst, src.as<Mem>());
cc->bswap(dst);
}
return;
}
default:
break;
}
Gp src_gp = new_similar_reg(dst, "@src");
cc->mov(src_gp, src.as<Mem>());
src = src_gp;
}
// ArithOp Reg, Reg
// ----------------
if (src.is_reg()) {
const Gp& src_gp = src.as<Gp>();
bool dst_is_src = dst.id() == src_gp.id();
switch (op) {
case UniOpRR::kAbs: {
if (dst_is_src) {
Gp tmp = new_similar_reg(dst, "@tmp");
cc->mov(tmp, dst);
cc->neg(dst);
cc->cmovs(dst, tmp);
}
else {
cc->mov(dst, src_gp);
cc->neg(dst);
cc->cmovs(dst, src_gp);
}
return;
}
case UniOpRR::kBSwap: {
if (!dst_is_src)
cc->mov(dst, src_gp);
cc->bswap(dst);
return;
}
case UniOpRR::kNeg:
case UniOpRR::kNot: {
if (!dst_is_src)
cc->mov(dst, src_gp);
cc->emit(op == UniOpRR::kNeg ? Inst::kIdNeg : Inst::kIdNot, dst);
return;
}
default:
break;
}
}
// Everything should be handled, so this should never be reached!
ASMJIT_NOT_REACHED();
}
static constexpr uint64_t kOp3ICommutativeMask =
(uint64_t(1) << unsigned(UniOpRRR::kAnd )) |
(uint64_t(1) << unsigned(UniOpRRR::kOr )) |
(uint64_t(1) << unsigned(UniOpRRR::kXor )) |
(uint64_t(1) << unsigned(UniOpRRR::kAdd )) |
(uint64_t(1) << unsigned(UniOpRRR::kMul )) |
(uint64_t(1) << unsigned(UniOpRRR::kSMin)) |
(uint64_t(1) << unsigned(UniOpRRR::kSMax)) |
(uint64_t(1) << unsigned(UniOpRRR::kUMin)) |
(uint64_t(1) << unsigned(UniOpRRR::kUMax)) ;
static ASMJIT_INLINE_NODEBUG bool is_op3i_commutative(UniOpRRR op) {
return (kOp3ICommutativeMask & (uint64_t(1) << unsigned(op))) != 0;
}
struct UniOpRRRMinMaxCMovInst { InstId a, b; };
void UniCompiler::emit_3i(UniOpRRR op, const Gp& dst, const Operand_& src1_, const Operand_& src2_) {
Operand src1(src1_);
Operand src2(src2_);
static constexpr UniOpRRRMinMaxCMovInst arith_min_max_cmov_inst_table[4] = {
{ Inst::kIdCmovl, Inst::kIdCmovg }, // MinI
{ Inst::kIdCmovg, Inst::kIdCmovl }, // MaxI
{ Inst::kIdCmovb, Inst::kIdCmova }, // MinU
{ Inst::kIdCmova, Inst::kIdCmovb } // MaxU
};
static constexpr InstId legacy_shift_inst_table[5] = {
Inst::kIdShl, // SHL
Inst::kIdShr, // SHR
Inst::kIdSar, // SAR
Inst::kIdRol, // ROL
Inst::kIdRor // ROR
};
static constexpr InstId legacy_logical_inst_table[3] = {
Inst::kIdAnd, // AND
Inst::kIdOr, // OR
Inst::kIdXor // XOR
};
static constexpr InstId bmi2_shift_inst_table[5] = {
Inst::kIdShlx, // SHL
Inst::kIdShrx, // SHR
Inst::kIdSarx, // SAR
Inst::kIdNone, // ROL (doesn't exist).
Inst::kIdNone // ROR (can only be used with immediate, special handling).
};
// ArithOp Reg, Mem, Imm
// ---------------------
if (src1.is_mem() && src2.is_imm()) {
const Mem& a = src1.as<Mem>();
const Imm& b = src2.as<Imm>();
switch (op) {
case UniOpRRR::kMul:
cc->imul(dst, a, b);
return;
default:
break;
}
cc->mov(dst, a);
src1 = dst;
}
if (!src1.is_reg() && is_op3i_commutative(op)) {
std::swap(src1, src2);
}
// ArithOp Reg, Reg, Imm
// ---------------------
if (src1.is_reg() && src2.is_imm()) {
const Gp& a = src1.as<Gp>();
const Imm& b = src2.as<Imm>();
bool dst_is_a = dst.id() == a.id();
ASMJIT_ASSERT(dst.size() == a.size());
switch (op) {
case UniOpRRR::kAnd:
case UniOpRRR::kOr:
case UniOpRRR::kXor: {
InstId inst_id = legacy_logical_inst_table[size_t(op) - size_t(UniOpRRR::kAnd)];
if (!dst_is_a)
cc->mov(dst, a);
cc->emit(inst_id, dst, b);
return;
}
case UniOpRRR::kBic: {
if (!dst_is_a)
cc->mov(dst, a);
Imm nImm(~b.value());
if (dst.size() <= 4)
nImm.sign_extend_int32();
cc->and_(dst, nImm);
return;
}
case UniOpRRR::kAdd: {
if (!dst_is_a && b.is_int32()) {
lea(dst, x86::ptr(a, b.value_as<int32_t>()));
}
else {
if (!dst_is_a)
cc->mov(dst, a);
if (b.value() == 128) {
cc->sub(dst, -128);
}
else {
cc->add(dst, b);
}
}
return;
}
case UniOpRRR::kSub: {
if (!dst_is_a) {
lea(dst, x86::ptr(a, int32_t(0u - b.value_as<uint32_t>())));
}
else {
cc->sub(dst, b);
}
return;
}
case UniOpRRR::kMul: {
int64_t val = b.value();
if (dst_is_a && Support::is_power_of_2(uint64_t(val))) {
cc->shl(dst, Support::ctz(val));
return;
}
switch (b.value()) {
case 0:
cc->xor_(dst, dst);
return;
case 1:
if (!dst_is_a)
cc->mov(dst, a);
return;
case 2:
lea(dst, x86::ptr(a, a));
return;
case 3:
lea(dst, x86::ptr(a, a, 1));
return;
case 5:
lea(dst, x86::ptr(a, a, 2));
return;
case 9:
lea(dst, x86::ptr(a, a, 3));
return;
default:
break;
}
cc->imul(dst, a, b);
return;
}
case UniOpRRR::kSMin:
case UniOpRRR::kSMax:
case UniOpRRR::kUMin:
case UniOpRRR::kUMax: {
const UniOpRRRMinMaxCMovInst& cmov_inst = arith_min_max_cmov_inst_table[size_t(op) - size_t(UniOpRRR::kSMin)];
if (dst_is_a) {
Gp tmp = new_similar_reg(dst, "@tmp");
cc->mov(tmp, b);
cc->cmp(dst, tmp);
cc->emit(cmov_inst.b, dst, tmp);
}
else {
cc->mov(dst, b);
cc->cmp(dst, a);
cc->emit(cmov_inst.b, dst, a); // cmov_inst.b is correct, we have reversed the comparison in this case.
}
return;
}
case UniOpRRR::kSll:
// Optimize `dst = dst << 1`.
if (b.value() == 1) {
if (dst_is_a) {
// `dst = dst + dst`.
cc->add(dst, dst);
}
else if (is_64bit()) {
// `dst = a + a` (using a 64-bit address saves address-override prefix).
cc->lea(dst, x86::ptr(a.r64(), a.r64()));
}
else {
// `dst = a + a`.
cc->lea(dst, x86::ptr(a, a));
}
return;
}
[[fallthrough]];
case UniOpRRR::kSrl:
case UniOpRRR::kSra: {
InstId legacy_inst_id = legacy_shift_inst_table[size_t(op) - size_t(UniOpRRR::kSll)];
if (!dst_is_a)
cc->mov(dst, a);
cc->emit(legacy_inst_id, dst, b);
return;
}
case UniOpRRR::kRol: {
if (has_bmi2()) {
uint32_t reg_size = dst.size() * 8u;
uint32_t imm = (reg_size - b.value_as<uint32_t>()) & Support::lsb_mask<uint32_t>(reg_size);
cc->rorx(dst, a, imm);
}
else {
if (!dst_is_a)
cc->mov(dst, a);
cc->rol(dst, b);
}
return;
}
case UniOpRRR::kRor: {
if (has_bmi2()) {
cc->rorx(dst, a, b);
}
else {
if (!dst_is_a)
cc->mov(dst, a);
cc->ror(dst, b);
}
return;
}
default:
break;
}
Gp bTmp = new_similar_reg(dst, "@bImm");
cc->mov(bTmp, b);
src2 = bTmp;
}
// ArithOp Reg, Mem, Reg
// ---------------------
if (src1.is_mem() && src2.is_reg()) {
const Mem& a = src1.as<Mem>();
const Gp& b = src2.as<Gp>();
bool dst_is_b = dst.id() == b.id();
switch (op) {
case UniOpRRR::kAnd:
case UniOpRRR::kOr:
case UniOpRRR::kXor:
case UniOpRRR::kAdd:
case UniOpRRR::kMul:
case UniOpRRR::kSMin:
case UniOpRRR::kSMax:
case UniOpRRR::kUMin:
case UniOpRRR::kUMax:
// These are commutative, so this should never happen as these should have been corrected to `Reg, Reg, Mem`.
ASMJIT_NOT_REACHED();
case UniOpRRR::kSub: {
ASMJIT_ASSERT(dst.size() == b.size());
if (dst_is_b) {
cc->neg(dst);
cc->add(dst, a);
return;
}
// Bail to `Reg, Reg, Reg` form.
break;
}
case UniOpRRR::kSll:
case UniOpRRR::kSrl:
case UniOpRRR::kSra: {
// Prefer BMI2 variants: SHLX, SHRX, SARX, and RORX.
if (has_bmi2()) {
InstId bmi2_inst_id = bmi2_shift_inst_table[size_t(op) - size_t(UniOpRRR::kSll)];
cc->emit(bmi2_inst_id, dst, a, b.clone_as(dst));
return;
}
// Bail to `Reg, Reg, Reg` form if BMI2 is not available.
break;
}
default:
break;
}
if (!dst_is_b) {
cc->mov(dst, a);
src1 = dst;
}
else {
Gp aTmp = new_similar_reg(dst, "@aTmp");
cc->mov(aTmp, a);
src1 = aTmp;
}
}
// ArithOp Reg, Reg, Mem
// ---------------------
if (src1.is_reg() && src2.is_mem()) {
const Gp& a = src1.as<Gp>();
const Mem& b = src2.as<Mem>();
bool dst_is_a = dst.id() == a.id();
ASMJIT_ASSERT(dst.size() == a.size());
switch (op) {
case UniOpRRR::kAnd:
case UniOpRRR::kOr:
case UniOpRRR::kXor: {
InstId inst_id = legacy_logical_inst_table[size_t(op) - size_t(UniOpRRR::kAnd)];
if (!dst_is_a)
cc->mov(dst, a);
cc->emit(inst_id, dst, b);
return;
}
case UniOpRRR::kBic: {
Gp tmp = new_similar_reg(dst);
cc->mov(tmp, b);
cc->not_(tmp);
if (!dst_is_a)
cc->mov(dst, a);
cc->and_(dst, tmp);
return;
}
case UniOpRRR::kAdd: {
if (!dst_is_a)
cc->mov(dst, a);
cc->add(dst, b);
return;
}
case UniOpRRR::kSub: {
if (!dst_is_a)
cc->mov(dst, a);
cc->sub(dst, b);
return;
}
case UniOpRRR::kMul: {
if (!dst_is_a)
cc->mov(dst, a);
cc->imul(dst, b);
return;
}
case UniOpRRR::kUDiv: {
Gp tmp1 = new_similar_reg(dst, "@tmp1");
cc->xor_(tmp1, tmp1);
if (dst_is_a) {
cc->div(tmp1, dst, b);
}
else {
cc->mov(dst, a);
cc->div(tmp1, dst, b);
}
return;
}
case UniOpRRR::kUMod: {
Gp tmp1 = new_similar_reg(dst, "@tmp1");
cc->xor_(tmp1, tmp1);
if (dst_is_a) {
cc->div(tmp1, dst, b);
cc->mov(dst, tmp1);
}
else {
Gp tmp2 = new_similar_reg(dst, "@tmp2");
cc->mov(tmp2, a);
cc->div(tmp1, tmp2, b);
cc->mov(dst, tmp1);
}
return;
}
case UniOpRRR::kSMin:
case UniOpRRR::kSMax:
case UniOpRRR::kUMin:
case UniOpRRR::kUMax: {
const UniOpRRRMinMaxCMovInst& cmov_inst = arith_min_max_cmov_inst_table[size_t(op) - size_t(UniOpRRR::kSMin)];
if (dst_is_a) {
cc->cmp(dst, b);
cc->emit(cmov_inst.b, dst, b);
}
else {
cc->mov(dst, b);
cc->cmp(dst, a);
cc->emit(cmov_inst.b, dst, a); // cmov_inst.b is correct, we have reversed the comparison in this case.
}
return;
}
case UniOpRRR::kSBound: {
cc->xor_(dst, dst);
cc->cmp(a, b);
cc->cmovbe(dst, a);
cc->cmovg(dst, b);
return;
}
default:
break;
}
Gp bTmp = new_similar_reg(dst, "@bTmp");
cc->mov(bTmp, b);
src2 = bTmp;
}
// ArithOp Reg, Reg, Reg
// ---------------------
if (src1.is_reg() && src2.is_reg()) {
const Gp& a = src1.as<Gp>();
const Gp& b = src2.as<Gp>();
bool aIsB = a.id() == b.id();
bool dst_is_a = dst.id() == a.id();
bool dst_is_b = dst.id() == b.id();
ASMJIT_ASSERT(dst.size() == a.size());
switch (op) {
case UniOpRRR::kAnd:
case UniOpRRR::kOr:
case UniOpRRR::kXor: {
ASMJIT_ASSERT(dst.size() == b.size());
InstId inst_id = legacy_logical_inst_table[size_t(op) - size_t(UniOpRRR::kAnd)];
if (!dst_is_a)
cc->mov(dst, a);
cc->emit(inst_id, dst, b);
return;
}
case UniOpRRR::kBic: {
ASMJIT_ASSERT(dst.size() == b.size());
if (has_bmi()) {
cc->andn(dst, b, a);
}
else if (dst_is_b) {
if (dst_is_a) {
cc->mov(dst, 0);
return;
}
cc->not_(dst);
cc->and_(dst, a);
}
else {
Gp tmp = new_similar_reg(dst, "@tmp");
cc->mov(tmp, b);
cc->not_(tmp);
if (!dst_is_a)
cc->mov(dst, a);
cc->and_(dst, tmp);
}
return;
}
case UniOpRRR::kAdd: {
ASMJIT_ASSERT(dst.size() == b.size());
if (dst_is_a || dst_is_b) {
cc->add(dst, dst_is_b ? a : b);
}
else if (dst.size() >= 4) {
if (is_64bit())
lea(dst, x86::ptr(a.r64(), b.r64()));
else
lea(dst, x86::ptr(a, b));
}
else {
cc->mov(dst, a);
cc->add(dst, b);
}
return;
}
case UniOpRRR::kSub: {
ASMJIT_ASSERT(dst.size() == b.size());
if (aIsB) {
cc->xor_(dst, dst);
}
else if (dst_is_a) {
cc->sub(dst, b);
}
else if (dst_is_b) {
cc->neg(dst);
cc->add(dst, a);
}
else {
cc->mov(dst, a);
cc->sub(dst, b);
}
return;
}
case UniOpRRR::kMul: {
ASMJIT_ASSERT(dst.size() == b.size());
if (!dst_is_a && !dst_is_b)
cc->mov(dst, a);
cc->imul(dst, dst_is_b ? a : b);
return;
}
case UniOpRRR::kUDiv: {
ASMJIT_ASSERT(dst.size() == b.size());
Gp tmp1 = new_similar_reg(dst, "@tmp1");
cc->xor_(tmp1, tmp1);
if (dst_is_a) {
cc->div(tmp1, dst, b);
}
else if (dst_is_b) {
Gp tmp2 = new_similar_reg(dst, "@tmp2");
cc->mov(tmp2, a);
cc->div(tmp1, tmp2, b);
cc->mov(dst, tmp2);
}
else {
cc->mov(dst, a);
cc->div(tmp1, dst, b);
}
return;
}
case UniOpRRR::kUMod: {
ASMJIT_ASSERT(dst.size() == b.size());
Gp tmp1 = new_similar_reg(dst, "@tmp1");
cc->xor_(tmp1, tmp1);
if (dst_is_a) {
cc->div(tmp1, dst, b);
cc->mov(dst, tmp1);
}
else {
Gp tmp2 = new_similar_reg(dst, "@tmp2");
cc->mov(tmp2, a);
cc->div(tmp1, tmp2, b);
cc->mov(dst, tmp1);
}
return;
}
case UniOpRRR::kSMin:
case UniOpRRR::kSMax:
case UniOpRRR::kUMin:
case UniOpRRR::kUMax: {
ASMJIT_ASSERT(dst.size() == b.size());
const UniOpRRRMinMaxCMovInst& cmov_inst = arith_min_max_cmov_inst_table[size_t(op) - size_t(UniOpRRR::kSMin)];
cc->cmp(a, b);
if (dst_is_b) {
cc->emit(cmov_inst.a, dst, a);
}
else {
if (!dst_is_a)
cc->mov(dst, a);
cc->emit(cmov_inst.b, dst, b);
}
return;
}
case UniOpRRR::kSll:
case UniOpRRR::kSrl:
case UniOpRRR::kSra:
case UniOpRRR::kRol:
case UniOpRRR::kRor: {
// Prefer BMI2 variants: SHLX, SHRX, SARX, and RORX.
if (has_bmi2()) {
InstId bmi2_inst_id = bmi2_shift_inst_table[size_t(op) - size_t(UniOpRRR::kSll)];
if (bmi2_inst_id != Inst::kIdNone) {
cc->emit(bmi2_inst_id, dst, a, b.clone_as(dst));
return;
}
}
InstId legacy_inst_id = legacy_shift_inst_table[size_t(op) - size_t(UniOpRRR::kSll)];
if (dst_is_a) {
cc->emit(legacy_inst_id, dst, b.r8());
return;
}
else if (dst_is_b) {
Gp tmp = new_gp32("@tmp");
if (!dst_is_a)
cc->mov(dst, a);
cc->mov(tmp, b.r32());
cc->emit(legacy_inst_id, dst, tmp.r8());
}
else {
cc->mov(dst, a);
cc->emit(legacy_inst_id, dst, b.r8());
}
return;
}
case UniOpRRR::kSBound: {
if (dst.id() == a.id()) {
Gp zero = new_similar_reg(dst, "@zero");
cc->xor_(zero, zero);
cc->cmp(dst, b);
cc->cmova(dst, zero);
cc->cmovg(dst, b);
}
else {
cc->xor_(dst, dst);
cc->cmp(a, b);
cc->cmovbe(dst, a);
cc->cmovg(dst, b);
}
return;
}
}
}
// Everything should be handled, so this should never be reached!
ASMJIT_NOT_REACHED();
}
void UniCompiler::emit_j(const Operand_& target) {
cc->emit(Inst::kIdJmp, target);
}
void UniCompiler::emit_j_if(const Label& target, const UniCondition& condition) {
ConditionApplier ca(condition);
ca.optimize(*this);
ca.emit(*this);
cc->j(ca.cond, target);
}
void UniCompiler::adds_u8(const Gp& dst, const Gp& src1, const Gp& src2) {
ASMJIT_ASSERT(dst.size() == src1.size());
ASMJIT_ASSERT(dst.size() == src2.size());
if (dst.id() == src1.id()) {
cc->add(dst.r8(), src2.r8());
}
else if (dst.id() == src2.id()) {
cc->add(dst.r8(), src1.r8());
}
else {
cc->mov(dst, src1);
cc->add(dst, src2);
}
Gp u8_msk = new_gp32("@u8_msk");
cc->sbb(u8_msk, u8_msk);
cc->or_(dst.r8(), u8_msk.r8());
}
void UniCompiler::inv_u8(const Gp& dst, const Gp& src) {
if (dst.id() != src.id())
cc->mov(dst, src);
cc->xor_(dst.r8(), 0xFF);
}
void UniCompiler::div_255_u32(const Gp& dst, const Gp& src) {
ASMJIT_ASSERT(dst.size() == src.size());
if (dst.id() == src.id()) {
// tmp = src + 128;
// dst = (tmp + (tmp >> 8)) >> 8
Gp tmp = new_similar_reg(dst, "@tmp");
cc->sub(dst, -128);
cc->mov(tmp, dst);
cc->shr(tmp, 8);
cc->add(dst, tmp);
cc->shr(dst, 8);
}
else {
// dst = (src + 128 + ((src + 128) >> 8)) >> 8
lea(dst, x86::ptr(src, 128));
cc->shr(dst, 8);
lea(dst, x86::ptr(dst, src, 0, 128));
cc->shr(dst, 8);
}
}
void UniCompiler::mul_257_hu16(const Gp& dst, const Gp& src) {
ASMJIT_ASSERT(dst.size() == src.size());
cc->imul(dst, src, 257);
cc->shr(dst, 16);
}
void UniCompiler::add_scaled(const Gp& dst, const Gp& a, int b) {
switch (b) {
case 1:
cc->add(dst, a);
return;
case 2:
case 4:
case 8: {
uint32_t shift = b == 2 ? 1 :
b == 4 ? 2 : 3;
lea(dst, x86::ptr(dst, a, shift));
return;
}
default: {
Gp tmp = new_similar_reg(dst, "@tmp");
cc->imul(tmp, a, b);
cc->add(dst, tmp);
return;
}
}
}
void UniCompiler::add_ext(const Gp& dst, const Gp& src_, const Gp& idx_, uint32_t scale, int32_t disp) {
ASMJIT_ASSERT(scale != 0u);
Gp src = src_.clone_as(dst);
Gp idx = idx_.clone_as(dst);
switch (scale) {
case 1:
if (dst.id() == src.id() && disp == 0) {
cc->add(dst, idx);
return;
}
[[fallthrough]];
case 2:
case 4:
case 8:
lea(dst, x86::ptr(src, idx, Support::ctz(scale), disp));
return;
default:
break;
}
if (src.id() == idx.id()) {
cc->imul(dst, src, scale + 1);
return;
}
if (dst.id() != idx.id() && scale == 3) {
lea(dst, x86::ptr(src, idx, 1, disp));
cc->add(dst, idx);
return;
}
Gp tmp = new_similar_reg(dst);
cc->imul(tmp, idx, scale);
cc->lea(dst, x86::ptr(src, tmp));
}
void UniCompiler::lea(const Gp& dst, const Mem& src) {
Mem m(src);
if (is_64bit() && dst.size() == 4) {
if (m.base_type() == RegType::kGp32) {
m.set_base_type(RegType::kGp64);
}
if (m.index_type() == RegType::kGp32) {
m.set_index_type(RegType::kGp64);
}
}
cc->lea(dst, m);
}
// ujit::UniCompiler - Vector Instructions - Constants
// ===================================================
//! Floating point mode is used in places that are generic and implement various functionality that needs more
//! than a single instruction. Typically implementing either higher level concepts or missing functionality.
enum FloatMode : uint8_t {
//! Scalar 32-bit floating point operation.
kF32S = 0,
//! Scalar 64-bit floating point operation.
kF64S = 1,
//! Vector 32-bit floating point operation.
kF32V = 2,
//! Vector 64-bit floating point operation.
kF64V = 3,
//! Used by non-floating point instructions.
kNone = 4
};
enum class ElementSize : uint8_t {
k8,
k16,
k32,
k64
};
enum class SameVecOp : uint8_t {
kNone = 0,
kZero = 1,
kOnes = 2,
kSrc = 3
};
enum class VecPart : uint8_t {
kNA = 0,
kLo = 0,
kHi = 1
};
enum class WideningOp : uint32_t {
kNone,
kI8ToI16,
kU8ToU16,
kI8ToI32,
kU8ToU32,
kU8ToU64,
kI16ToI32,
kU16ToU32,
kI32ToI64,
kU32ToU64
};
enum class NarrowingOp : uint32_t {
kNone,
kI16ToI8,
kI16ToU8,
kU16ToU8,
kI32ToI16,
kI32ToU16,
kU32ToU16,
kI64ToI32,
kI64ToU32,
kU64ToU32
};
enum class NarrowingMode : uint32_t {
kTruncate,
kSaturateSToU,
kSaturateSToS,
kSaturateUToU
};
[[maybe_unused]]
static ASMJIT_INLINE bool is_scalar_fp_op(FloatMode fm) noexcept { return fm <= kF64S; }
[[maybe_unused]]
static ASMJIT_INLINE bool is_f32_op(FloatMode fm) noexcept { return fm == kF32S || fm == kF32V; }
[[maybe_unused]]
static ASMJIT_INLINE bool is_f64_op(FloatMode fm) noexcept { return fm == kF64S || fm == kF64V; }
// ujit::UniCompiler - Vector Instructions - Broadcast / Shuffle Data
// ==================================================================
static constexpr uint16_t avx512_vinsert_128[] = {
Inst::kIdVinserti32x4,
Inst::kIdVinserti64x2,
Inst::kIdVinsertf32x4,
Inst::kIdVinsertf64x2
};
static constexpr uint16_t avx512_vshuf_128[] = {
Inst::kIdVshufi32x4,
Inst::kIdVshufi64x2,
Inst::kIdVshuff32x4,
Inst::kIdVshuff64x2
};
// ujit::UniCompiler - Vector Instructions - Integer Cmp/Min/Max Data
// ==================================================================
struct CmpMinMaxInst {
uint16_t peq;
uint16_t pgt;
uint16_t pmin;
uint16_t pmax;
};
static constexpr CmpMinMaxInst sse_cmp_min_max[] = {
{ Inst::kIdPcmpeqb, Inst::kIdPcmpgtb, Inst::kIdPminsb, Inst::kIdPmaxsb },
{ Inst::kIdPcmpeqb, Inst::kIdPcmpgtb, Inst::kIdPminub, Inst::kIdPmaxub },
{ Inst::kIdPcmpeqw, Inst::kIdPcmpgtw, Inst::kIdPminsw, Inst::kIdPmaxsw },
{ Inst::kIdPcmpeqw, Inst::kIdPcmpgtw, Inst::kIdPminuw, Inst::kIdPmaxuw },
{ Inst::kIdPcmpeqd, Inst::kIdPcmpgtd, Inst::kIdPminsd, Inst::kIdPmaxsd },
{ Inst::kIdPcmpeqd, Inst::kIdPcmpgtd, Inst::kIdPminud, Inst::kIdPmaxud },
{ Inst::kIdPcmpeqq, Inst::kIdPcmpgtq, Inst::kIdNone , Inst::kIdNone },
{ Inst::kIdPcmpeqq, Inst::kIdPcmpgtq, Inst::kIdNone , Inst::kIdNone },
};
static constexpr CmpMinMaxInst avx_cmp_min_max[] = {
{ Inst::kIdVpcmpeqb, Inst::kIdVpcmpgtb, Inst::kIdVpminsb, Inst::kIdVpmaxsb },
{ Inst::kIdVpcmpeqb, Inst::kIdVpcmpgtb, Inst::kIdVpminub, Inst::kIdVpmaxub },
{ Inst::kIdVpcmpeqw, Inst::kIdVpcmpgtw, Inst::kIdVpminsw, Inst::kIdVpmaxsw },
{ Inst::kIdVpcmpeqw, Inst::kIdVpcmpgtw, Inst::kIdVpminuw, Inst::kIdVpmaxuw },
{ Inst::kIdVpcmpeqd, Inst::kIdVpcmpgtd, Inst::kIdVpminsd, Inst::kIdVpmaxsd },
{ Inst::kIdVpcmpeqd, Inst::kIdVpcmpgtd, Inst::kIdVpminud, Inst::kIdVpmaxud },
{ Inst::kIdVpcmpeqq, Inst::kIdVpcmpgtq, Inst::kIdVpminsq, Inst::kIdVpmaxsq },
{ Inst::kIdVpcmpeqq, Inst::kIdVpcmpgtq, Inst::kIdVpminuq, Inst::kIdVpmaxuq },
};
// ujit::UniCompiler - Vector Instructions - Integer Conversion Data
// =================================================================
struct WideningOpInfo {
uint32_t mov : 16;
uint32_t unpack_lo : 16;
uint32_t unpack_hi : 13;
uint32_t sign_extends : 1;
uint32_t reserved : 5;
};
struct NarrowingOpInfo {
uint32_t mov : 13;
uint32_t pack : 13;
uint32_t sign : 1;
uint32_t mode : 2;
uint32_t reserved : 19;
};
static constexpr WideningOpInfo sse_int_widening_op_info[] = {
{ Inst::kIdNone , Inst::kIdNone , Inst::kIdNone , 0, 0 }, // kNone.
{ Inst::kIdPmovsxbw , Inst::kIdPunpcklbw , Inst::kIdPunpckhbw , 1, 0 }, // kI8ToI16.
{ Inst::kIdPmovzxbw , Inst::kIdPunpcklbw , Inst::kIdPunpckhbw , 0, 0 }, // kU8ToU16.
{ Inst::kIdPmovsxbd , Inst::kIdNone , Inst::kIdNone , 1, 0 }, // kI8ToI32.
{ Inst::kIdPmovzxbd , Inst::kIdNone , Inst::kIdNone , 0, 0 }, // kU8ToU32.
{ Inst::kIdPmovzxbq , Inst::kIdNone , Inst::kIdNone , 0, 0 }, // kU8ToU64.
{ Inst::kIdPmovsxwd , Inst::kIdPunpcklwd , Inst::kIdPunpckhwd , 1, 0 }, // kI16ToI32.
{ Inst::kIdPmovzxwd , Inst::kIdPunpcklwd , Inst::kIdPunpckhwd , 0, 0 }, // kU16ToU32.
{ Inst::kIdPmovsxdq , Inst::kIdPunpckldq , Inst::kIdPunpckhdq , 1, 0 }, // kI32ToI64.
{ Inst::kIdPmovzxdq , Inst::kIdPunpckldq , Inst::kIdPunpckhdq , 0, 0 } // kU32ToU64.
};
// ujit::UniCompiler - Vector Instructions - Float Instruction Data
// ================================================================
struct FloatInst {
uint16_t fmovs;
uint16_t fmova;
uint16_t fmovu;
uint16_t fand;
uint16_t for_;
uint16_t fxor;
uint16_t fandn;
uint16_t fadd;
uint16_t fsub;
uint16_t fmul;
uint16_t fdiv;
uint16_t fmin;
uint16_t fmax;
uint16_t fcmp;
uint16_t fround;
uint16_t frndscale;
uint16_t psrl;
uint16_t psll;
};
static constexpr FloatInst sse_float_inst[4] = {
{
Inst::kIdMovss,
Inst::kIdMovaps,
Inst::kIdMovups,
Inst::kIdAndps,
Inst::kIdOrps,
Inst::kIdXorps,
Inst::kIdAndnps,
Inst::kIdAddss,
Inst::kIdSubss,
Inst::kIdMulss,
Inst::kIdDivss,
Inst::kIdMinss,
Inst::kIdMaxss,
Inst::kIdCmpss,
Inst::kIdRoundss,
Inst::kIdNone,
Inst::kIdPsrld,
Inst::kIdPslld
},
{
Inst::kIdMovsd,
Inst::kIdMovaps,
Inst::kIdMovups,
Inst::kIdAndpd,
Inst::kIdOrpd,
Inst::kIdXorpd,
Inst::kIdAndnpd,
Inst::kIdAddsd,
Inst::kIdSubsd,
Inst::kIdMulsd,
Inst::kIdDivsd,
Inst::kIdMinsd,
Inst::kIdMaxsd,
Inst::kIdCmpsd,
Inst::kIdRoundsd,
Inst::kIdNone,
Inst::kIdPsrlq,
Inst::kIdPsllq
},
{
Inst::kIdMovaps,
Inst::kIdMovaps,
Inst::kIdMovups,
Inst::kIdAndps,
Inst::kIdOrps,
Inst::kIdXorps,
Inst::kIdAndnps,
Inst::kIdAddps,
Inst::kIdSubps,
Inst::kIdMulps,
Inst::kIdDivps,
Inst::kIdMinps,
Inst::kIdMaxps,
Inst::kIdCmpps,
Inst::kIdRoundps,
Inst::kIdNone,
Inst::kIdPsrld,
Inst::kIdPslld
},
{
Inst::kIdMovaps,
Inst::kIdMovaps,
Inst::kIdMovups,
Inst::kIdAndpd,
Inst::kIdOrpd,
Inst::kIdXorpd,
Inst::kIdAndnpd,
Inst::kIdAddpd,
Inst::kIdSubpd,
Inst::kIdMulpd,
Inst::kIdDivpd,
Inst::kIdMinpd,
Inst::kIdMaxpd,
Inst::kIdCmppd,
Inst::kIdRoundpd,
Inst::kIdNone,
Inst::kIdPsrlq,
Inst::kIdPsllq
}
};
static constexpr FloatInst avx_float_inst[4] = {
{
Inst::kIdVmovss,
Inst::kIdVmovaps,
Inst::kIdVmovups,
Inst::kIdVandps,
Inst::kIdVorps,
Inst::kIdVxorps,
Inst::kIdVandnps,
Inst::kIdVaddss,
Inst::kIdVsubss,
Inst::kIdVmulss,
Inst::kIdVdivss,
Inst::kIdVminss,
Inst::kIdVmaxss,
Inst::kIdVcmpss,
Inst::kIdVroundss,
Inst::kIdVrndscaless,
Inst::kIdVpsrld,
Inst::kIdVpslld
},
{
Inst::kIdVmovsd,
Inst::kIdVmovaps,
Inst::kIdVmovups,
Inst::kIdVandpd,
Inst::kIdVorpd,
Inst::kIdVxorpd,
Inst::kIdVandnpd,
Inst::kIdVaddsd,
Inst::kIdVsubsd,
Inst::kIdVmulsd,
Inst::kIdVdivsd,
Inst::kIdVminsd,
Inst::kIdVmaxsd,
Inst::kIdVcmpsd,
Inst::kIdVroundsd,
Inst::kIdVrndscalesd,
Inst::kIdVpsrlq,
Inst::kIdVpsllq
},
{
Inst::kIdVmovaps,
Inst::kIdVmovaps,
Inst::kIdVmovups,
Inst::kIdVandps,
Inst::kIdVorps,
Inst::kIdVxorps,
Inst::kIdVandnps,
Inst::kIdVaddps,
Inst::kIdVsubps,
Inst::kIdVmulps,
Inst::kIdVdivps,
Inst::kIdVminps,
Inst::kIdVmaxps,
Inst::kIdVcmpps,
Inst::kIdVroundps,
Inst::kIdVrndscaleps,
Inst::kIdVpsrld,
Inst::kIdVpslld
},
{
Inst::kIdVmovaps,
Inst::kIdVmovaps,
Inst::kIdVmovups,
Inst::kIdVandpd,
Inst::kIdVorpd,
Inst::kIdVxorpd,
Inst::kIdVandnpd,
Inst::kIdVaddpd,
Inst::kIdVsubpd,
Inst::kIdVmulpd,
Inst::kIdVdivpd,
Inst::kIdVminpd,
Inst::kIdVmaxpd,
Inst::kIdVcmppd,
Inst::kIdVroundpd,
Inst::kIdVrndscalepd,
Inst::kIdVpsrlq,
Inst::kIdVpsllq
}
};
// ujit::UniCompiler - Vector Instructions - UniOp Information
// ===========================================================
struct UniOpVInfo {
//! \name Members
//! \{
uint32_t sse_inst_id : 13;
uint32_t sse_op_count : 3;
uint32_t sse_ext : 3;
uint32_t avx_inst_id : 13;
uint32_t avx_ext : 6;
uint32_t commutative : 1;
uint32_t comparison : 1;
uint32_t same_vec_op : 3;
uint32_t use_imm : 1;
uint32_t imm : 8;
uint32_t float_mode : 3;
uint32_t element_size : 2;
uint32_t broadcast_size : 4;
uint32_t hi : 1;
uint32_t reserved : 3;
//! \}
};
#define DEFINE_OP(sse_inst_id, sse_op_count, sse_ext, avx_inst_id, avx_ext, commutative, comparison, same_vec_op, use_imm, imm, float_mode, element_size, broadcast_size, vec_part) \
UniOpVInfo { \
Inst::sse_inst_id, \
sse_op_count, \
uint8_t(SSEExt::sse_ext), \
Inst::avx_inst_id, \
uint8_t(AVXExt::avx_ext), \
commutative, \
comparison, \
uint8_t(SameVecOp::same_vec_op), \
use_imm, \
imm, \
uint8_t(FloatMode::float_mode), \
uint8_t(ElementSize::element_size), \
broadcast_size, \
uint8_t(VecPart::vec_part), \
0 \
}
static constexpr UniOpVInfo opcode_info_2v[size_t(UniOpVV::kMaxValue) + 1] = {
DEFINE_OP(kIdMovaps , 0, kIntrin, kIdVmovaps , kIntrin , 0, 0, kNone, 0, 0x00u, kNone, k8 , 0, kNA), // kMov.
DEFINE_OP(kIdMovq , 0, kIntrin, kIdVmovq , kIntrin , 0, 0, kNone, 0, 0x00u, kNone, k64, 0, kNA), // kMovU64.
DEFINE_OP(kIdNone , 0, kIntrin, kIdVpbroadcastb , kIntrin , 0, 0, kNone, 0, 0x01u, kNone, k8 , 0, kNA), // kBroadcastU8Z.
DEFINE_OP(kIdNone , 0, kIntrin, kIdVpbroadcastw , kIntrin , 0, 0, kNone, 0, 0x01u, kNone, k16, 0, kNA), // kBroadcastU16Z.
DEFINE_OP(kIdNone , 0, kIntrin, kIdVpbroadcastb , kIntrin , 0, 0, kNone, 0, 0x00u, kNone, k8 , 0, kNA), // kBroadcastU8.
DEFINE_OP(kIdNone , 0, kIntrin, kIdVpbroadcastw , kIntrin , 0, 0, kNone, 0, 0x00u, kNone, k16, 0, kNA), // kBroadcastU16.
DEFINE_OP(kIdNone , 0, kIntrin, kIdVpbroadcastd , kIntrin , 0, 0, kNone, 0, 0x00u, kNone, k32, 0, kNA), // kBroadcastU32.
DEFINE_OP(kIdNone , 0, kIntrin, kIdVpbroadcastq , kIntrin , 0, 0, kNone, 0, 0x00u, kNone, k64, 0, kNA), // kBroadcastU64.
DEFINE_OP(kIdNone , 0, kIntrin, kIdVbroadcastss , kIntrin , 0, 0, kNone, 0, 0x00u, kNone, k32, 0, kNA), // kBroadcastF32.
DEFINE_OP(kIdNone , 0, kIntrin, kIdVbroadcastsd , kIntrin , 0, 0, kNone, 0, 0x00u, kNone, k64, 0, kNA), // kBroadcastF64.
DEFINE_OP(kIdNone , 0, kIntrin, kIdVbroadcasti32x4, kIntrin , 0, 0, kNone, 0, 0x00u, kNone, k32, 0, kNA), // kBroadcastV128_U32.
DEFINE_OP(kIdNone , 0, kIntrin, kIdVbroadcasti64x2, kIntrin , 0, 0, kNone, 0, 0x00u, kNone, k64, 0, kNA), // kBroadcastV128_U64.
DEFINE_OP(kIdNone , 0, kIntrin, kIdVbroadcastf32x4, kIntrin , 0, 0, kNone, 0, 0x00u, kNone, k32, 0, kNA), // kBroadcastV128_F32.
DEFINE_OP(kIdNone , 0, kIntrin, kIdVbroadcastf64x2, kIntrin , 0, 0, kNone, 0, 0x00u, kNone, k64, 0, kNA), // kBroadcastV128_F64.
DEFINE_OP(kIdNone , 0, kIntrin, kIdVbroadcasti32x8, kIntrin , 0, 0, kNone, 0, 0x00u, kNone, k32, 0, kNA), // kBroadcastV256_U32.
DEFINE_OP(kIdNone , 0, kIntrin, kIdVbroadcasti64x4, kIntrin , 0, 0, kNone, 0, 0x00u, kNone, k64, 0, kNA), // kBroadcastV256_U64.
DEFINE_OP(kIdNone , 0, kIntrin, kIdVbroadcasti32x8, kIntrin , 0, 0, kNone, 0, 0x00u, kNone, k32, 0, kNA), // kBroadcastV256_F32.
DEFINE_OP(kIdNone , 0, kIntrin, kIdVbroadcasti64x4, kIntrin , 0, 0, kNone, 0, 0x00u, kNone, k64, 0, kNA), // kBroadcastV256_F64.
DEFINE_OP(kIdPabsb , 2, kSSSE3 , kIdVpabsb , kAVX , 0, 0, kNone, 0, 0x00u, kNone, k8 , 0, kNA), // kAbsI8.
DEFINE_OP(kIdPabsw , 2, kSSSE3 , kIdVpabsw , kAVX , 0, 0, kNone, 0, 0x00u, kNone, k16, 0, kNA), // kAbsI16.
DEFINE_OP(kIdPabsd , 2, kSSSE3 , kIdVpabsd , kAVX , 0, 0, kNone, 0, 0x00u, kNone, k32, 4, kNA), // kAbsI32.
DEFINE_OP(kIdNone , 0, kIntrin, kIdVpabsq , kAVX512 , 0, 0, kNone, 0, 0x00u, kNone, k64, 8, kNA), // kAbsI64.
DEFINE_OP(kIdNone , 0, kIntrin, kIdNone , kIntrin , 0, 0, kNone, 0, 0x00u, kNone, k32, 4, kNA), // kNotU32.
DEFINE_OP(kIdNone , 0, kIntrin, kIdNone , kIntrin , 0, 0, kNone, 0, 0x00u, kNone, k64, 8, kNA), // kNotU64.
DEFINE_OP(kIdPmovsxbw , 0, kIntrin, kIdVpmovsxbw , kIntrin , 0, 0, kNone, 0, 0x00u, kNone, k16, 0, kNA), // kCvtI8LoToI16.
DEFINE_OP(kIdPmovsxbw , 0, kIntrin, kIdVpmovsxbw , kIntrin , 0, 0, kNone, 0, 0x00u, kNone, k16, 0, kNA), // kCvtI8HiToI16.
DEFINE_OP(kIdPmovzxbw , 0, kIntrin, kIdVpmovzxbw , kIntrin , 0, 0, kNone, 0, 0x00u, kNone, k16, 0, kNA), // kCvtU8LoToU16.
DEFINE_OP(kIdPmovzxbw , 0, kIntrin, kIdVpmovzxbw , kIntrin , 0, 0, kNone, 0, 0x00u, kNone, k16, 0, kNA), // kCvtU8HiToU16.
DEFINE_OP(kIdPmovsxbd , 0, kIntrin, kIdVpmovsxbd , kIntrin , 0, 0, kNone, 0, 0x00u, kNone, k32, 0, kNA), // kCvtI8ToI32.
DEFINE_OP(kIdPmovzxbd , 0, kIntrin, kIdVpmovzxbd , kIntrin , 0, 0, kNone, 0, 0x00u, kNone, k32, 0, kNA), // kCvtU8ToU32.
DEFINE_OP(kIdPmovsxwd , 0, kIntrin, kIdVpmovsxwd , kIntrin , 0, 0, kNone, 0, 0x00u, kNone, k32, 0, kNA), // kCvtI16LoToI32.
DEFINE_OP(kIdPmovsxwd , 0, kIntrin, kIdVpmovsxwd , kIntrin , 0, 0, kNone, 0, 0x00u, kNone, k32, 0, kNA), // kCvtI16HiToI32.
DEFINE_OP(kIdPmovzxwd , 0, kIntrin, kIdVpmovzxwd , kIntrin , 0, 0, kNone, 0, 0x00u, kNone, k32, 0, kNA), // kCvtU16LoToU32.
DEFINE_OP(kIdPmovzxwd , 0, kIntrin, kIdVpmovzxwd , kIntrin , 0, 0, kNone, 0, 0x00u, kNone, k32, 0, kNA), // kCvtU16HiToU32.
DEFINE_OP(kIdPmovsxdq , 0, kIntrin, kIdVpmovsxdq , kIntrin , 0, 0, kNone, 0, 0x00u, kNone, k64, 0, kNA), // kCvtI32LoToI64.
DEFINE_OP(kIdPmovsxdq , 0, kIntrin, kIdVpmovsxdq , kIntrin , 0, 0, kNone, 0, 0x00u, kNone, k64, 0, kNA), // kCvtI32HiToI64.
DEFINE_OP(kIdPmovzxdq , 0, kIntrin, kIdVpmovzxdq , kIntrin , 0, 0, kNone, 0, 0x00u, kNone, k64, 0, kNA), // kCvtU32LoToU64.
DEFINE_OP(kIdPmovzxdq , 0, kIntrin, kIdVpmovzxdq , kIntrin , 0, 0, kNone, 0, 0x00u, kNone, k64, 0, kNA), // kCvtU32HiToU64.
DEFINE_OP(kIdAndps , 0, kIntrin, kIdVandps , kIntrin , 0, 0, kNone, 0, 0x00u, kF32S, k32, 4, kNA), // kAbsF32S.
DEFINE_OP(kIdAndpd , 0, kIntrin, kIdVandpd , kIntrin , 0, 0, kNone, 0, 0x00u, kF64S, k64, 8, kNA), // kAbsF64S.
DEFINE_OP(kIdAndps , 0, kIntrin, kIdVandps , kIntrin , 0, 0, kNone, 0, 0x00u, kF32V, k32, 4, kNA), // kAbsF32.
DEFINE_OP(kIdAndpd , 0, kIntrin, kIdVandpd , kIntrin , 0, 0, kNone, 0, 0x00u, kF64V, k64, 8, kNA), // kAbsF64.
DEFINE_OP(kIdXorps , 0, kIntrin, kIdVxorps , kIntrin , 0, 0, kNone, 0, 0x00u, kF32S, k32, 4, kNA), // kNegF32S.
DEFINE_OP(kIdXorpd , 0, kIntrin, kIdVxorpd , kIntrin , 0, 0, kNone, 0, 0x00u, kF64S, k64, 8, kNA), // kNegF64S.
DEFINE_OP(kIdXorps , 0, kIntrin, kIdVxorps , kIntrin , 0, 0, kNone, 0, 0x00u, kF32V, k32, 4, kNA), // kNegF32.
DEFINE_OP(kIdXorpd , 0, kIntrin, kIdVxorpd , kIntrin , 0, 0, kNone, 0, 0x00u, kF64V, k64, 8, kNA), // kNegF64.
DEFINE_OP(kIdNone , 0, kIntrin, kIdNone , kIntrin , 0, 0, kNone, 0, 0x00u, kNone, k32, 4, kNA), // kAbsU32.
DEFINE_OP(kIdNone , 0, kIntrin, kIdNone , kIntrin , 0, 0, kNone, 0, 0x00u, kNone, k64, 8, kNA), // kAbsU64.
DEFINE_OP(kIdRoundss , 2, kIntrin, kIdVroundss , kIntrin , 0, 0, kNone, 1, 0x0Bu, kF32S, k32, 4, kNA), // kTruncF32S.
DEFINE_OP(kIdRoundsd , 2, kIntrin, kIdVroundsd , kIntrin , 0, 0, kNone, 1, 0x0Bu, kF64S, k64, 8, kNA), // kTruncF64S.
DEFINE_OP(kIdRoundps , 2, kIntrin, kIdVroundps , kIntrin , 0, 0, kNone, 1, 0x0Bu, kF32V, k32, 4, kNA), // kTruncF32.
DEFINE_OP(kIdRoundpd , 2, kIntrin, kIdVroundpd , kIntrin , 0, 0, kNone, 1, 0x0Bu, kF64V, k64, 8, kNA), // kTruncF64.
DEFINE_OP(kIdRoundss , 2, kIntrin, kIdVroundss , kIntrin , 0, 0, kNone, 1, 0x09u, kF32S, k32, 4, kNA), // kFloorF32S.
DEFINE_OP(kIdRoundsd , 2, kIntrin, kIdVroundsd , kIntrin , 0, 0, kNone, 1, 0x09u, kF64S, k64, 8, kNA), // kFloorF64S.
DEFINE_OP(kIdRoundps , 2, kIntrin, kIdVroundps , kIntrin , 0, 0, kNone, 1, 0x09u, kF32V, k32, 4, kNA), // kFloorF32.
DEFINE_OP(kIdRoundpd , 2, kIntrin, kIdVroundpd , kIntrin , 0, 0, kNone, 1, 0x09u, kF64V, k64, 8, kNA), // kFloorF64.
DEFINE_OP(kIdRoundss , 2, kIntrin, kIdVroundss , kIntrin , 0, 0, kNone, 1, 0x0Au, kF32S, k32, 4, kNA), // kCeilF32S.
DEFINE_OP(kIdRoundsd , 2, kIntrin, kIdVroundsd , kIntrin , 0, 0, kNone, 1, 0x0Au, kF64S, k64, 8, kNA), // kCeilF64S.
DEFINE_OP(kIdRoundps , 2, kIntrin, kIdVroundps , kIntrin , 0, 0, kNone, 1, 0x0Au, kF32V, k32, 4, kNA), // kCeilF32.
DEFINE_OP(kIdRoundpd , 2, kIntrin, kIdVroundpd , kIntrin , 0, 0, kNone, 1, 0x0Au, kF64V, k64, 8, kNA), // kCeilF64.
DEFINE_OP(kIdRoundss , 2, kIntrin, kIdVroundss , kIntrin , 0, 0, kNone, 1, 0x08u, kF32S, k32, 4, kNA), // kRoundEvenF32S.
DEFINE_OP(kIdRoundsd , 2, kIntrin, kIdVroundsd , kIntrin , 0, 0, kNone, 1, 0x08u, kF64S, k64, 8, kNA), // kRoundEvenF64S.
DEFINE_OP(kIdRoundps , 2, kIntrin, kIdVroundps , kIntrin , 0, 0, kNone, 1, 0x08u, kF32V, k32, 4, kNA), // kRoundEvenF32.
DEFINE_OP(kIdRoundpd , 2, kIntrin, kIdVroundpd , kIntrin , 0, 0, kNone, 1, 0x08u, kF64V, k64, 8, kNA), // kRoundEvenF64.
DEFINE_OP(kIdRoundss , 2, kIntrin, kIdVroundss , kIntrin , 0, 0, kNone, 1, 0x0Bu, kF32S, k32, 4, kNA), // kRoundHalfAwayF32S.
DEFINE_OP(kIdRoundsd , 2, kIntrin, kIdVroundsd , kIntrin , 0, 0, kNone, 1, 0x0Bu, kF64S, k64, 8, kNA), // kRoundHalfAwayF64S.
DEFINE_OP(kIdRoundps , 2, kIntrin, kIdVroundps , kIntrin , 0, 0, kNone, 1, 0x0Bu, kF32V, k32, 4, kNA), // kRoundHalfAwayF32.
DEFINE_OP(kIdRoundpd , 2, kIntrin, kIdVroundpd , kIntrin , 0, 0, kNone, 1, 0x0Bu, kF64V, k64, 8, kNA), // kRoundHalfAwayF64.
DEFINE_OP(kIdRoundss , 2, kIntrin, kIdVroundss , kIntrin , 0, 0, kNone, 1, 0x09u, kF32S, k32, 4, kNA), // kRoundHalfUpF32S.
DEFINE_OP(kIdRoundsd , 2, kIntrin, kIdVroundsd , kIntrin , 0, 0, kNone, 1, 0x09u, kF64S, k64, 8, kNA), // kRoundHalfUpF64S.
DEFINE_OP(kIdRoundps , 2, kIntrin, kIdVroundps , kIntrin , 0, 0, kNone, 1, 0x09u, kF32V, k32, 4, kNA), // kRoundHalfUpF32.
DEFINE_OP(kIdRoundpd , 2, kIntrin, kIdVroundpd , kIntrin , 0, 0, kNone, 1, 0x09u, kF64V, k64, 8, kNA), // kRoundHalfUpF64.
DEFINE_OP(kIdNone , 0, kIntrin, kIdNone , kIntrin , 0, 0, kNone, 0, 0x00u, kNone, k32, 4, kNA), // kRcpF32.
DEFINE_OP(kIdNone , 0, kIntrin, kIdNone , kIntrin , 0, 0, kNone, 0, 0x00u, kNone, k64, 8, kNA), // kRcpF64.
DEFINE_OP(kIdSqrtss , 2, kIntrin, kIdVsqrtss , kIntrin , 0, 0, kNone, 0, 0x00u, kF32S, k32, 4, kNA), // kSqrtF32S.
DEFINE_OP(kIdSqrtsd , 2, kIntrin, kIdVsqrtsd , kIntrin , 0, 0, kNone, 0, 0x00u, kF64S, k64, 8, kNA), // kSqrtF64S.
DEFINE_OP(kIdSqrtps , 2, kSSE2 , kIdVsqrtps , kAVX , 0, 0, kNone, 0, 0x00u, kF32V, k32, 4, kNA), // kSqrtF32.
DEFINE_OP(kIdSqrtpd , 2, kSSE2 , kIdVsqrtpd , kAVX , 0, 0, kNone, 0, 0x00u, kF64V, k64, 8, kNA), // kSqrtF64.
DEFINE_OP(kIdCvtss2sd , 2, kIntrin, kIdVcvtss2sd , kIntrin , 0, 0, kNone, 0, 0x00u, kF64S, k64, 0, kNA), // kCvtF32ToF64S.
DEFINE_OP(kIdCvtsd2ss , 2, kIntrin, kIdVcvtsd2ss , kIntrin , 0, 0, kNone, 0, 0x00u, kF64S, k32, 0, kNA), // kCvtF64ToF32S.
DEFINE_OP(kIdCvtdq2ps , 2, kSSE2 , kIdVcvtdq2ps , kAVX , 0, 0, kNone, 0, 0x00u, kF32V, k32, 4, kNA), // kCvtI32ToF32.
DEFINE_OP(kIdCvtps2pd , 2, kSSE2 , kIdVcvtps2pd , kIntrin , 0, 0, kNone, 0, 0x00u, kF32V, k64, 4, kLo), // kCvtF32LoToF64.
DEFINE_OP(kIdCvtps2pd , 2, kIntrin, kIdVcvtps2pd , kIntrin , 0, 0, kNone, 0, 0x00u, kF32V, k64, 4, kHi), // kCvtF32HiToF64.
DEFINE_OP(kIdCvtpd2ps , 2, kSSE2 , kIdVcvtpd2ps , kIntrin , 0, 0, kNone, 0, 0x00u, kF64V, k32, 4, kLo), // kCvtF64ToF32Lo.
DEFINE_OP(kIdCvtpd2ps , 2, kIntrin, kIdVcvtpd2ps , kIntrin , 0, 0, kNone, 0, 0x00u, kF64V, k32, 4, kHi), // kCvtF64ToF32Hi.
DEFINE_OP(kIdCvtdq2pd , 2, kSSE2 , kIdVcvtdq2pd , kIntrin , 0, 0, kNone, 0, 0x00u, kNone, k64, 4, kLo), // kCvtI32LoToF64.
DEFINE_OP(kIdCvtdq2pd , 2, kIntrin, kIdVcvtdq2pd , kIntrin , 0, 0, kNone, 0, 0x00u, kNone, k64, 4, kHi), // kCvtI32HiToF64.
DEFINE_OP(kIdCvttps2dq , 2, kSSE2 , kIdVcvttps2dq , kAVX , 0, 0, kNone, 0, 0x00u, kF32V, k32, 4, kNA), // kCvtTruncF32ToI32.
DEFINE_OP(kIdCvttpd2dq , 2, kSSE2 , kIdVcvttpd2dq , kIntrin , 0, 0, kNone, 0, 0x00u, kF64V, k32, 4, kLo), // kCvtTruncF64ToI32Lo.
DEFINE_OP(kIdCvttpd2dq , 2, kIntrin, kIdVcvttpd2dq , kIntrin , 0, 0, kNone, 0, 0x00u, kF64V, k32, 4, kHi), // kCvtTruncF64ToI32Hi.
DEFINE_OP(kIdCvtps2dq , 2, kSSE2 , kIdVcvtps2dq , kAVX , 0, 0, kNone, 0, 0x00u, kF32V, k32, 4, kNA), // kCvtRoundF32ToI32.
DEFINE_OP(kIdCvtpd2dq , 2, kSSE2 , kIdVcvtpd2dq , kIntrin , 0, 0, kNone, 0, 0x00u, kF64V, k32, 4, kLo), // kCvtRoundF64ToI32Lo.
DEFINE_OP(kIdCvtpd2dq , 2, kIntrin, kIdVcvtpd2dq , kIntrin , 0, 0, kNone, 0, 0x00u, kF64V, k32, 4, kHi) // kCvtRoundF64ToI32Hi.
};
static constexpr UniOpVInfo opcode_info_2vs[size_t(UniOpVR::kMaxValue) + 1] = {
DEFINE_OP(kIdNone , 0, kIntrin, kIdNone , kIntrin , 0, 0, kNone, 0, 0x00u, kNone, k8 , 0, kNA), // kMov.
DEFINE_OP(kIdMovd , 0, kSSE2 , kIdVmovd , kAVX , 0, 0, kNone, 0, 0x00u, kNone, k32, 0, kNA), // kMovU32.
DEFINE_OP(kIdMovq , 0, kSSE2 , kIdVmovq , kAVX , 0, 0, kNone, 0, 0x00u, kNone, k64, 0, kNA), // kMovU64.
DEFINE_OP(kIdPinsrb , 0, kSSE4_1, kIdVpinsrb , kAVX , 0, 0, kNone, 0, 0x00u, kNone, k8 , 0, kNA), // kInsertU8.
DEFINE_OP(kIdPinsrw , 0, kSSE2 , kIdVpinsrw , kAVX , 0, 0, kNone, 0, 0x00u, kNone, k16, 0, kNA), // kInsertU16.
DEFINE_OP(kIdPinsrd , 0, kSSE4_1, kIdVpinsrd , kAVX , 0, 0, kNone, 0, 0x00u, kNone, k32, 0, kNA), // kInsertU32.
DEFINE_OP(kIdPinsrq , 0, kSSE4_1, kIdVpinsrq , kAVX , 0, 0, kNone, 0, 0x00u, kNone, k64, 0, kNA), // kInsertU64.
DEFINE_OP(kIdPextrb , 0, kSSE4_1, kIdVpextrb , kAVX , 0, 0, kNone, 0, 0x00u, kNone, k8 , 0, kNA), // kExtractU8.
DEFINE_OP(kIdPextrw , 0, kSSE2 , kIdVpextrw , kAVX , 0, 0, kNone, 0, 0x00u, kNone, k16, 0, kNA), // kExtractU16.
DEFINE_OP(kIdPextrd , 0, kSSE4_1, kIdVpextrd , kAVX , 0, 0, kNone, 0, 0x00u, kNone, k32, 0, kNA), // kExtractU32.
DEFINE_OP(kIdPextrq , 0, kSSE4_1, kIdVpextrq , kAVX , 0, 0, kNone, 0, 0x00u, kNone, k64, 0, kNA), // kExtractU64.
DEFINE_OP(kIdCvtsi2ss , 0, kSSE2 , kIdVcvtsi2ss , kAVX , 0, 0, kNone, 0, 0x00u, kNone, k32, 0, kNA), // kCvtIntToF32.
DEFINE_OP(kIdCvtsi2sd , 0, kSSE2 , kIdVcvtsi2sd , kAVX , 0, 0, kNone, 0, 0x00u, kNone, k64, 0, kNA), // kCvtIntToF64.
DEFINE_OP(kIdCvttss2si , 0, kSSE2 , kIdVcvttss2si , kAVX , 0, 0, kNone, 0, 0x00u, kNone, k32, 0, kNA), // kCvtTruncF32ToInt.
DEFINE_OP(kIdCvtss2si , 0, kSSE2 , kIdVcvtss2si , kAVX , 0, 0, kNone, 0, 0x00u, kNone, k32, 0, kNA), // kCvtRoundF32ToInt.
DEFINE_OP(kIdCvttsd2si , 0, kSSE2 , kIdVcvttsd2si , kAVX , 0, 0, kNone, 0, 0x00u, kNone, k64, 0, kNA), // kCvtTruncF64ToInt.
DEFINE_OP(kIdCvtsd2si , 0, kSSE2 , kIdVcvtsd2si , kAVX , 0, 0, kNone, 0, 0x00u, kNone, k64, 0, kNA) // kCvtRoundF64ToInt.
};
static constexpr UniOpVInfo opcode_info_2vi[size_t(UniOpVVI::kMaxValue) + 1] = {
DEFINE_OP(kIdPsllw , 2, kSSE2 , kIdVpsllw , kAVX512 , 0, 0, kNone, 0, 0x00u, kNone, k16, 0, kNA), // kSllU16.
DEFINE_OP(kIdPslld , 2, kSSE2 , kIdVpslld , kAVX512 , 0, 0, kNone, 0, 0x00u, kNone, k32, 4, kNA), // kSllU32.
DEFINE_OP(kIdPsllq , 2, kSSE2 , kIdVpsllq , kAVX512 , 0, 0, kNone, 0, 0x00u, kNone, k64, 8, kNA), // kSllU64.
DEFINE_OP(kIdPsrlw , 2, kSSE2 , kIdVpsrlw , kAVX512 , 0, 0, kNone, 0, 0x00u, kNone, k16, 0, kNA), // kSrlU16.
DEFINE_OP(kIdPsrld , 2, kSSE2 , kIdVpsrld , kAVX512 , 0, 0, kNone, 0, 0x00u, kNone, k32, 4, kNA), // kSrlU32.
DEFINE_OP(kIdPsrlq , 2, kSSE2 , kIdVpsrlq , kAVX512 , 0, 0, kNone, 0, 0x00u, kNone, k64, 8, kNA), // kSrlU64.
DEFINE_OP(kIdPsraw , 2, kSSE2 , kIdVpsraw , kAVX512 , 0, 0, kNone, 0, 0x00u, kNone, k16, 0, kNA), // kSraI16.
DEFINE_OP(kIdPsrad , 2, kSSE2 , kIdVpsrad , kAVX512 , 0, 0, kNone, 0, 0x00u, kNone, k32, 4, kNA), // kSraI32.
DEFINE_OP(kIdNone , 0, kIntrin, kIdVpsraq , kAVX512 , 0, 0, kNone, 0, 0x00u, kNone, k64, 8, kNA), // kSraI64.
DEFINE_OP(kIdPslldq , 2, kSSE2 , kIdVpslldq , kAVX512 , 0, 0, kNone, 0, 0x00u, kNone, k8 , 0, kNA), // kSllbU128.
DEFINE_OP(kIdPsrldq , 2, kSSE2 , kIdVpsrldq , kAVX512 , 0, 0, kNone, 0, 0x00u, kNone, k8 , 0, kNA), // kSrlbU128.
DEFINE_OP(kIdNone , 0, kIntrin, kIdNone , kIntrin , 0, 0, kNone, 0, 0x00u, kNone, k16, 0, kNA), // kSwizzleU16x4 (intrin).
DEFINE_OP(kIdPshuflw , 3, kIntrin, kIdVpshuflw , kIntrin , 0, 0, kNone, 0, 0x00u, kNone, k16, 0, kNA), // kSwizzleLoU16x4.
DEFINE_OP(kIdPshufhw , 3, kIntrin, kIdVpshufhw , kIntrin , 0, 0, kNone, 0, 0x00u, kNone, k16, 0, kNA), // kSwizzleHiU16x4.
DEFINE_OP(kIdPshufd , 3, kIntrin, kIdVpshufd , kIntrin , 0, 0, kNone, 0, 0x00u, kNone, k32, 0, kNA), // kSwizzleU32x4.
DEFINE_OP(kIdNone , 0, kIntrin, kIdNone , kIntrin , 0, 0, kNone, 0, 0x00u, kNone, k64, 0, kNA), // kSwizzleU64x2 (intrin).
DEFINE_OP(kIdNone , 0, kIntrin, kIdNone , kIntrin , 0, 0, kNone, 0, 0x00u, kF32V, k32, 0, kNA), // kSwizzleF32x4 (intrin).
DEFINE_OP(kIdNone , 0, kIntrin, kIdNone , kIntrin , 0, 0, kNone, 0, 0x00u, kF64V, k64, 0, kNA), // kSwizzleF64x2 (intrin).
DEFINE_OP(kIdNone , 0, kIntrin, kIdVpermq , kIntrin , 0, 0, kNone, 0, 0x00u, kNone, k64, 0, kNA), // kSwizzleU64x4 (intrin).
DEFINE_OP(kIdNone , 0, kIntrin, kIdVpermq , kIntrin , 0, 0, kNone, 0, 0x00u, kF64V, k64, 0, kNA), // kSwizzleF64x4 (intrin).
DEFINE_OP(kIdNone , 0, kIntrin, kIdNone , kIntrin , 0, 0, kNone, 0, 0x00u, kNone, k64, 0, kNA), // kExtractV128_I32 (intrin).
DEFINE_OP(kIdNone , 0, kIntrin, kIdNone , kIntrin , 0, 0, kNone, 0, 0x00u, kNone, k64, 0, kNA), // kExtractV128_I64 (intrin).
DEFINE_OP(kIdNone , 0, kIntrin, kIdNone , kIntrin , 0, 0, kNone, 0, 0x00u, kF32V, k64, 0, kNA), // kExtractV128_F32 (intrin).
DEFINE_OP(kIdNone , 0, kIntrin, kIdNone , kIntrin , 0, 0, kNone, 0, 0x00u, kF64V, k64, 0, kNA), // kExtractV128_F64 (intrin).
DEFINE_OP(kIdNone , 0, kIntrin, kIdNone , kIntrin , 0, 0, kNone, 0, 0x00u, kNone, k64, 0, kNA), // kExtractV256_I32 (intrin).
DEFINE_OP(kIdNone , 0, kIntrin, kIdNone , kIntrin , 0, 0, kNone, 0, 0x00u, kNone, k64, 0, kNA), // kExtractV256_I64 (intrin).
DEFINE_OP(kIdNone , 0, kIntrin, kIdNone , kIntrin , 0, 0, kNone, 0, 0x00u, kF32V, k64, 0, kNA), // kExtractV256_F32 (intrin).
DEFINE_OP(kIdNone , 0, kIntrin, kIdNone , kIntrin , 0, 0, kNone, 0, 0x00u, kF64V, k64, 0, kNA) // kExtractV256_F64 (intrin).
};
static constexpr UniOpVInfo opcode_info_3v[size_t(UniOpVVV::kMaxValue) + 1] = {
DEFINE_OP(kIdPand , 2, kSSE2 , kIdVpandd , kAVX , 1, 0, kSrc , 0, 0x00u, kNone, k32, 4, kNA), // kAndU32.
DEFINE_OP(kIdPand , 2, kSSE2 , kIdVpandq , kAVX , 1, 0, kSrc , 0, 0x00u, kNone, k64, 8, kNA), // kAndU64.
DEFINE_OP(kIdPor , 2, kSSE2 , kIdVpord , kAVX , 1, 0, kSrc , 0, 0x00u, kNone, k32, 4, kNA), // kOrU32.
DEFINE_OP(kIdPor , 2, kSSE2 , kIdVporq , kAVX , 1, 0, kSrc , 0, 0x00u, kNone, k64, 8, kNA), // kOrU64.
DEFINE_OP(kIdPxor , 2, kSSE2 , kIdVpxord , kAVX , 1, 0, kZero, 0, 0x00u, kNone, k32, 4, kNA), // kXorU32.
DEFINE_OP(kIdPxor , 2, kSSE2 , kIdVpxorq , kAVX , 1, 0, kZero, 0, 0x00u, kNone, k64, 8, kNA), // kXorU64.
DEFINE_OP(kIdPandn , 2, kSSE2 , kIdVpandnd , kAVX , 0, 0, kZero, 0, 0x00u, kNone, k32, 4, kNA), // kAndnU32.
DEFINE_OP(kIdPandn , 2, kSSE2 , kIdVpandnq , kAVX , 0, 0, kZero, 0, 0x00u, kNone, k64, 8, kNA), // kAndnU64.
DEFINE_OP(kIdPandn , 0, kIntrin, kIdVpandnd , kIntrin , 0, 0, kZero, 0, 0x00u, kNone, k32, 4, kNA), // kBicU32.
DEFINE_OP(kIdPandn , 0, kIntrin, kIdVpandnq , kIntrin , 0, 0, kZero, 0, 0x00u, kNone, k64, 8, kNA), // kBicU64.
DEFINE_OP(kIdPavgb , 2, kSSE2 , kIdVpavgb , kAVX , 1, 0, kSrc , 0, 0x00u, kNone, k8 , 0, kNA), // kAvgrU8.
DEFINE_OP(kIdPavgw , 2, kSSE2 , kIdVpavgw , kAVX , 1, 0, kSrc , 0, 0x00u, kNone, k16, 0, kNA), // kAvgrU16.
DEFINE_OP(kIdPaddb , 2, kSSE2 , kIdVpaddb , kAVX , 1, 0, kNone, 0, 0x00u, kNone, k8 , 0, kNA), // kAddU8.
DEFINE_OP(kIdPaddw , 2, kSSE2 , kIdVpaddw , kAVX , 1, 0, kNone, 0, 0x00u, kNone, k16, 0, kNA), // kAddU16.
DEFINE_OP(kIdPaddd , 2, kSSE2 , kIdVpaddd , kAVX , 1, 0, kNone, 0, 0x00u, kNone, k32, 4, kNA), // kAddU32.
DEFINE_OP(kIdPaddq , 2, kSSE2 , kIdVpaddq , kAVX , 1, 0, kNone, 0, 0x00u, kNone, k64, 8, kNA), // kAddU64.
DEFINE_OP(kIdPsubb , 2, kSSE2 , kIdVpsubb , kAVX , 0, 0, kZero, 0, 0x00u, kNone, k8 , 0, kNA), // kSubU8.
DEFINE_OP(kIdPsubw , 2, kSSE2 , kIdVpsubw , kAVX , 0, 0, kZero, 0, 0x00u, kNone, k16, 0, kNA), // kSubU16.
DEFINE_OP(kIdPsubd , 2, kSSE2 , kIdVpsubd , kAVX , 0, 0, kZero, 0, 0x00u, kNone, k32, 4, kNA), // kSubU32.
DEFINE_OP(kIdPsubq , 2, kSSE2 , kIdVpsubq , kAVX , 0, 0, kZero, 0, 0x00u, kNone, k64, 8, kNA), // kSubU64.
DEFINE_OP(kIdPaddsb , 2, kSSE2 , kIdVpaddsb , kAVX , 1, 0, kNone, 0, 0x00u, kNone, k8 , 0, kNA), // kAddsI8.
DEFINE_OP(kIdPaddusb , 2, kSSE2 , kIdVpaddusb , kAVX , 1, 0, kNone, 0, 0x00u, kNone, k8 , 0, kNA), // kAddsU8.
DEFINE_OP(kIdPaddsw , 2, kSSE2 , kIdVpaddsw , kAVX , 1, 0, kNone, 0, 0x00u, kNone, k16, 0, kNA), // kAddsI16.
DEFINE_OP(kIdPaddusw , 2, kSSE2 , kIdVpaddusw , kAVX , 1, 0, kNone, 0, 0x00u, kNone, k16, 0, kNA), // kAddsU16.
DEFINE_OP(kIdPsubsb , 2, kSSE2 , kIdVpsubsb , kAVX , 0, 0, kNone, 0, 0x00u, kNone, k8 , 0, kNA), // kSubsI8.
DEFINE_OP(kIdPsubusb , 2, kSSE2 , kIdVpsubusb , kAVX , 0, 0, kZero, 0, 0x00u, kNone, k8 , 0, kNA), // kSubsU8.
DEFINE_OP(kIdPsubsw , 2, kSSE2 , kIdVpsubsw , kAVX , 0, 0, kNone, 0, 0x00u, kNone, k16, 0, kNA), // kSubsI16.
DEFINE_OP(kIdPsubusw , 2, kSSE2 , kIdVpsubusw , kAVX , 0, 0, kZero, 0, 0x00u, kNone, k16, 0, kNA), // kSubsU16.
DEFINE_OP(kIdPmullw , 2, kSSE2 , kIdVpmullw , kAVX , 1, 0, kNone, 0, 0x00u, kNone, k16, 0, kNA), // kMulU16.
DEFINE_OP(kIdPmulld , 2, kSSE4_1, kIdVpmulld , kAVX , 1, 0, kNone, 0, 0x00u, kNone, k32, 4, kNA), // kMulU32.
DEFINE_OP(kIdNone , 0, kIntrin, kIdVpmullq , kAVX512 , 1, 0, kNone, 0, 0x00u, kNone, k64, 8, kNA), // kMulU64.
DEFINE_OP(kIdPmulhw , 2, kSSE2 , kIdVpmulhw , kAVX , 1, 0, kNone, 0, 0x00u, kNone, k16, 0, kNA), // kMulhI16.
DEFINE_OP(kIdPmulhuw , 2, kSSE2 , kIdVpmulhuw , kAVX , 1, 0, kNone, 0, 0x00u, kNone, k16, 0, kNA), // kMulhU16.
DEFINE_OP(kIdNone , 0, kIntrin, kIdNone , kIntrin , 0, 0, kNone, 0, 0x00u, kNone, k16, 0, kNA), // kMulU64_LoU32.
DEFINE_OP(kIdPmaddwd , 2, kSSE2 , kIdVpmaddwd , kAVX , 1, 0, kNone, 0, 0x00u, kNone, k16, 0, kNA), // kMHAddI16_I32.
DEFINE_OP(kIdPminsb , 2, kSSE4_1, kIdVpminsb , kAVX , 1, 0, kSrc , 0, 0x00u, kNone, k8 , 0, kNA), // kMinI8.
DEFINE_OP(kIdPminub , 2, kSSE2 , kIdVpminub , kAVX , 1, 0, kSrc , 0, 0x00u, kNone, k8 , 0, kNA), // kMinU8.
DEFINE_OP(kIdPminsw , 2, kSSE2 , kIdVpminsw , kAVX , 1, 0, kSrc , 0, 0x00u, kNone, k16, 0, kNA), // kMinI16.
DEFINE_OP(kIdPminuw , 2, kSSE4_1, kIdVpminuw , kAVX , 1, 0, kSrc , 0, 0x00u, kNone, k16, 0, kNA), // kMinU16.
DEFINE_OP(kIdPminsd , 2, kSSE4_1, kIdVpminsd , kAVX , 1, 0, kSrc , 0, 0x00u, kNone, k32, 4, kNA), // kMinI32.
DEFINE_OP(kIdPminud , 2, kSSE4_1, kIdVpminud , kAVX , 1, 0, kSrc , 0, 0x00u, kNone, k32, 4, kNA), // kMinU32.
DEFINE_OP(kIdNone , 0, kIntrin, kIdVpminsq , kAVX512 , 1, 0, kSrc , 0, 0x00u, kNone, k64, 8, kNA), // kMinI64.
DEFINE_OP(kIdNone , 0, kIntrin, kIdVpminuq , kAVX512 , 1, 0, kSrc , 0, 0x00u, kNone, k64, 8, kNA), // kMinU64.
DEFINE_OP(kIdPmaxsb , 2, kSSE4_1, kIdVpmaxsb , kAVX , 1, 0, kSrc , 0, 0x00u, kNone, k8 , 0, kNA), // kMaxI8.
DEFINE_OP(kIdPmaxub , 2, kSSE2 , kIdVpmaxub , kAVX , 1, 0, kSrc , 0, 0x00u, kNone, k8 , 0, kNA), // kMaxU8.
DEFINE_OP(kIdPmaxsw , 2, kSSE2 , kIdVpmaxsw , kAVX , 1, 0, kSrc , 0, 0x00u, kNone, k16, 0, kNA), // kMaxI16.
DEFINE_OP(kIdPmaxuw , 2, kSSE4_1, kIdVpmaxuw , kAVX , 1, 0, kSrc , 0, 0x00u, kNone, k16, 0, kNA), // kMaxU16.
DEFINE_OP(kIdPmaxsd , 2, kSSE4_1, kIdVpmaxsd , kAVX , 1, 0, kSrc , 0, 0x00u, kNone, k32, 4, kNA), // kMaxI32.
DEFINE_OP(kIdPmaxud , 2, kSSE4_1, kIdVpmaxud , kAVX , 1, 0, kSrc , 0, 0x00u, kNone, k32, 4, kNA), // kMaxU32.
DEFINE_OP(kIdNone , 0, kIntrin, kIdVpmaxsq , kAVX512 , 1, 0, kSrc , 0, 0x00u, kNone, k64, 8, kNA), // kMaxI64.
DEFINE_OP(kIdNone , 0, kIntrin, kIdVpmaxuq , kAVX512 , 1, 0, kSrc , 0, 0x00u, kNone, k64, 8, kNA), // kMaxU64.
DEFINE_OP(kIdPcmpeqb , 2, kSSE2 , kIdVpcmpeqb , kAVX , 1, 1, kOnes, 0, 0x00u, kNone, k8 , 0, kNA), // kCmpEqU8.
DEFINE_OP(kIdPcmpeqw , 2, kSSE2 , kIdVpcmpeqw , kAVX , 1, 1, kOnes, 0, 0x00u, kNone, k16, 0, kNA), // kCmpEqU16.
DEFINE_OP(kIdPcmpeqd , 2, kSSE2 , kIdVpcmpeqd , kAVX , 1, 1, kOnes, 0, 0x00u, kNone, k32, 4, kNA), // kCmpEqU32.
DEFINE_OP(kIdPcmpeqq , 2, kSSE4_1, kIdVpcmpeqq , kAVX , 1, 1, kOnes, 0, 0x00u, kNone, k64, 8, kNA), // kCmpEqU64.
DEFINE_OP(kIdPcmpgtb , 2, kSSE2 , kIdVpcmpgtb , kAVX , 0, 1, kZero, 0, 0x00u, kNone, k8 , 0, kNA), // kCmpGtI8.
DEFINE_OP(kIdPcmpgtb , 0, kIntrin, kIdVpcmpub , kAVX512 , 0, 1, kZero, 1, 0x06u, kNone, k8 , 0, kNA), // kCmpGtU8.
DEFINE_OP(kIdPcmpgtw , 2, kSSE2 , kIdVpcmpgtw , kAVX , 0, 1, kZero, 0, 0x00u, kNone, k16, 0, kNA), // kCmpGtI16.
DEFINE_OP(kIdPcmpgtw , 0, kIntrin, kIdVpcmpuw , kAVX512 , 0, 1, kZero, 1, 0x06u, kNone, k16, 0, kNA), // kCmpGtU16.
DEFINE_OP(kIdPcmpgtd , 2, kSSE2 , kIdVpcmpgtd , kAVX , 0, 1, kZero, 0, 0x00u, kNone, k32, 4, kNA), // kCmpGtI32.
DEFINE_OP(kIdPcmpgtd , 0, kIntrin, kIdVpcmpud , kAVX512 , 0, 1, kZero, 1, 0x06u, kNone, k32, 4, kNA), // kCmpGtU32.
DEFINE_OP(kIdPcmpgtq , 2, kSSE4_2, kIdVpcmpgtq , kAVX , 0, 1, kZero, 0, 0x00u, kNone, k64, 8, kNA), // kCmpGtI64.
DEFINE_OP(kIdPcmpgtq , 0, kIntrin, kIdVpcmpuq , kAVX512 , 0, 1, kZero, 1, 0x06u, kNone, k64, 8, kNA), // kCmpGtU64.
DEFINE_OP(kIdPcmpgtb , 0, kIntrin, kIdVpcmpb , kAVX512 , 0, 1, kOnes, 1, 0x05u, kNone, k8 , 0, kNA), // kCmpGeI8.
DEFINE_OP(kIdPcmpgtb , 0, kIntrin, kIdVpcmpub , kAVX512 , 0, 1, kOnes, 1, 0x05u, kNone, k8 , 0, kNA), // kCmpGeU8.
DEFINE_OP(kIdPcmpgtw , 0, kIntrin, kIdVpcmpw , kAVX512 , 0, 1, kOnes, 1, 0x05u, kNone, k16, 0, kNA), // kCmpGeI16.
DEFINE_OP(kIdPcmpgtw , 0, kIntrin, kIdVpcmpuw , kAVX512 , 0, 1, kOnes, 1, 0x05u, kNone, k16, 0, kNA), // kCmpGeU16.
DEFINE_OP(kIdPcmpgtd , 0, kIntrin, kIdVpcmpd , kAVX512 , 0, 1, kOnes, 1, 0x05u, kNone, k32, 4, kNA), // kCmpGeI32.
DEFINE_OP(kIdPcmpgtd , 0, kIntrin, kIdVpcmpud , kAVX512 , 0, 1, kOnes, 1, 0x05u, kNone, k32, 4, kNA), // kCmpGeU32.
DEFINE_OP(kIdPcmpgtq , 0, kIntrin, kIdVpcmpq , kAVX512 , 0, 1, kOnes, 1, 0x05u, kNone, k64, 8, kNA), // kCmpGeI64.
DEFINE_OP(kIdPcmpgtq , 0, kIntrin, kIdVpcmpuq , kAVX512 , 0, 1, kOnes, 1, 0x05u, kNone, k64, 8, kNA), // kCmpGeU64.
DEFINE_OP(kIdPcmpgtb , 0, kIntrin, kIdVpcmpb , kAVX512 , 0, 1, kZero, 1, 0x01u, kNone, k8 , 0, kNA), // kCmpLtI8.
DEFINE_OP(kIdPcmpgtb , 0, kIntrin, kIdVpcmpub , kAVX512 , 0, 1, kZero, 1, 0x01u, kNone, k8 , 0, kNA), // kCmpLtU8.
DEFINE_OP(kIdPcmpgtw , 0, kIntrin, kIdVpcmpw , kAVX512 , 0, 1, kZero, 1, 0x01u, kNone, k16, 0, kNA), // kCmpLtI16.
DEFINE_OP(kIdPcmpgtw , 0, kIntrin, kIdVpcmpuw , kAVX512 , 0, 1, kZero, 1, 0x01u, kNone, k16, 0, kNA), // kCmpLtU16.
DEFINE_OP(kIdPcmpgtd , 0, kIntrin, kIdVpcmpd , kAVX512 , 0, 1, kZero, 1, 0x01u, kNone, k32, 4, kNA), // kCmpLtI32.
DEFINE_OP(kIdPcmpgtd , 0, kIntrin, kIdVpcmpud , kAVX512 , 0, 1, kZero, 1, 0x01u, kNone, k32, 4, kNA), // kCmpLtU32.
DEFINE_OP(kIdPcmpgtq , 0, kIntrin, kIdVpcmpq , kAVX512 , 0, 1, kZero, 1, 0x01u, kNone, k64, 8, kNA), // kCmpLtI64.
DEFINE_OP(kIdPcmpgtq , 0, kIntrin, kIdVpcmpuq , kAVX512 , 0, 1, kZero, 1, 0x01u, kNone, k64, 8, kNA), // kCmpLtU64.
DEFINE_OP(kIdPcmpgtb , 0, kIntrin, kIdVpcmpb , kAVX512 , 0, 1, kOnes, 1, 0x02u, kNone, k8 , 0, kNA), // kCmpLeI8.
DEFINE_OP(kIdPcmpgtb , 0, kIntrin, kIdVpcmpub , kAVX512 , 0, 1, kOnes, 1, 0x02u, kNone, k8 , 0, kNA), // kCmpLeU8.
DEFINE_OP(kIdPcmpgtw , 0, kIntrin, kIdVpcmpw , kAVX512 , 0, 1, kOnes, 1, 0x02u, kNone, k16, 0, kNA), // kCmpLeI16.
DEFINE_OP(kIdPcmpgtw , 0, kIntrin, kIdVpcmpuw , kAVX512 , 0, 1, kOnes, 1, 0x02u, kNone, k16, 0, kNA), // kCmpLeU16.
DEFINE_OP(kIdPcmpgtd , 0, kIntrin, kIdVpcmpd , kAVX512 , 0, 1, kOnes, 1, 0x02u, kNone, k32, 4, kNA), // kCmpLeI32.
DEFINE_OP(kIdPcmpgtd , 0, kIntrin, kIdVpcmpud , kAVX512 , 0, 1, kOnes, 1, 0x02u, kNone, k32, 4, kNA), // kCmpLeU32.
DEFINE_OP(kIdPcmpgtq , 0, kIntrin, kIdVpcmpq , kAVX512 , 0, 1, kOnes, 1, 0x02u, kNone, k64, 8, kNA), // kCmpLeI64.
DEFINE_OP(kIdPcmpgtq , 0, kIntrin, kIdVpcmpuq , kAVX512 , 0, 1, kOnes, 1, 0x02u, kNone, k64, 8, kNA), // kCmpLeU64.
DEFINE_OP(kIdAndps , 2, kSSE2 , kIdVandps , kAVX , 1, 0, kSrc , 0, 0x00u, kF32V, k32, 4, kNA), // kAndF32.
DEFINE_OP(kIdAndpd , 2, kSSE2 , kIdVandpd , kAVX , 1, 0, kSrc , 0, 0x00u, kF64V, k64, 8, kNA), // kAndF64.
DEFINE_OP(kIdOrps , 2, kSSE2 , kIdVorps , kAVX , 1, 0, kSrc , 0, 0x00u, kF32V, k32, 4, kNA), // kOrF32.
DEFINE_OP(kIdOrpd , 2, kSSE2 , kIdVorpd , kAVX , 1, 0, kSrc , 0, 0x00u, kF64V, k64, 8, kNA), // kOrF64.
DEFINE_OP(kIdXorps , 2, kSSE2 , kIdVxorps , kAVX , 1, 0, kZero, 0, 0x00u, kF32V, k32, 4, kNA), // kXorF32.
DEFINE_OP(kIdXorpd , 2, kSSE2 , kIdVxorpd , kAVX , 1, 0, kZero, 0, 0x00u, kF64V, k64, 8, kNA), // kXorF64.
DEFINE_OP(kIdAndnps , 2, kSSE2 , kIdVandnps , kAVX , 0, 0, kZero, 0, 0x00u, kF32V, k32, 4, kNA), // kAndnF32.
DEFINE_OP(kIdAndnpd , 2, kSSE2 , kIdVandnpd , kAVX , 0, 0, kZero, 0, 0x00u, kF64V, k64, 8, kNA), // kAndnF64.
DEFINE_OP(kIdAndnps , 0, kIntrin, kIdVandnps , kIntrin , 0, 0, kZero, 0, 0x00u, kF32V, k32, 4, kNA), // kBicF32.
DEFINE_OP(kIdAndnpd , 0, kIntrin, kIdVandnpd , kIntrin , 0, 0, kZero, 0, 0x00u, kF64V, k64, 8, kNA), // kBicF64.
DEFINE_OP(kIdAddss , 2, kSSE2 , kIdVaddss , kAVX , 0, 0, kNone, 0, 0x00u, kF32S, k32, 4, kNA), // kAddF32S.
DEFINE_OP(kIdAddsd , 2, kSSE2 , kIdVaddsd , kAVX , 0, 0, kNone, 0, 0x00u, kF64S, k64, 8, kNA), // kAddF64S.
DEFINE_OP(kIdAddps , 2, kSSE2 , kIdVaddps , kAVX , 1, 0, kNone, 0, 0x00u, kF32V, k32, 4, kNA), // kAddF32.
DEFINE_OP(kIdAddpd , 2, kSSE2 , kIdVaddpd , kAVX , 1, 0, kNone, 0, 0x00u, kF64V, k64, 8, kNA), // kAddF64.
DEFINE_OP(kIdSubss , 2, kSSE2 , kIdVsubss , kAVX , 0, 0, kNone, 0, 0x00u, kF32S, k32, 4, kNA), // kSubF32S.
DEFINE_OP(kIdSubsd , 2, kSSE2 , kIdVsubsd , kAVX , 0, 0, kNone, 0, 0x00u, kF64S, k64, 8, kNA), // kSubF64S.
DEFINE_OP(kIdSubps , 2, kSSE2 , kIdVsubps , kAVX , 0, 0, kNone, 0, 0x00u, kF32V, k32, 4, kNA), // kSubF32.
DEFINE_OP(kIdSubpd , 2, kSSE2 , kIdVsubpd , kAVX , 0, 0, kNone, 0, 0x00u, kF64V, k64, 8, kNA), // kSubF64.
DEFINE_OP(kIdMulss , 2, kSSE2 , kIdVmulss , kAVX , 0, 0, kNone, 0, 0x00u, kF32S, k32, 4, kNA), // kMulF32S.
DEFINE_OP(kIdMulsd , 2, kSSE2 , kIdVmulsd , kAVX , 0, 0, kNone, 0, 0x00u, kF64S, k64, 8, kNA), // kMulF64S.
DEFINE_OP(kIdMulps , 2, kSSE2 , kIdVmulps , kAVX , 1, 0, kNone, 0, 0x00u, kF32V, k32, 4, kNA), // kMulF32.
DEFINE_OP(kIdMulpd , 2, kSSE2 , kIdVmulpd , kAVX , 1, 0, kNone, 0, 0x00u, kF64V, k64, 8, kNA), // kMulF64.
DEFINE_OP(kIdDivss , 2, kSSE2 , kIdVdivss , kAVX , 0, 0, kNone, 0, 0x00u, kF32S, k32, 4, kNA), // kDivF32S.
DEFINE_OP(kIdDivsd , 2, kSSE2 , kIdVdivsd , kAVX , 0, 0, kNone, 0, 0x00u, kF64S, k64, 8, kNA), // kDivF64S.
DEFINE_OP(kIdDivps , 2, kSSE2 , kIdVdivps , kAVX , 0, 0, kNone, 0, 0x00u, kF32V, k32, 4, kNA), // kDivF32.
DEFINE_OP(kIdDivpd , 2, kSSE2 , kIdVdivpd , kAVX , 0, 0, kNone, 0, 0x00u, kF64V, k64, 8, kNA), // kDivF64.
DEFINE_OP(kIdNone , 0, kIntrin, kIdNone , kIntrin , 0, 0, kNone, 0, 0x00u, kF32S, k32, 4, kNA), // kModF32S.
DEFINE_OP(kIdNone , 0, kIntrin, kIdNone , kIntrin , 0, 0, kNone, 0, 0x00u, kF64S, k64, 8, kNA), // kModF64S.
DEFINE_OP(kIdNone , 0, kIntrin, kIdNone , kIntrin , 0, 0, kNone, 0, 0x00u, kF32V, k32, 4, kNA), // kModF32.
DEFINE_OP(kIdNone , 0, kIntrin, kIdNone , kIntrin , 0, 0, kNone, 0, 0x00u, kF64V, k64, 8, kNA), // kModF64.
DEFINE_OP(kIdMinss , 2, kSSE2 , kIdVminss , kAVX , 0, 0, kSrc , 0, 0x00u, kF32S, k32, 4, kNA), // kMinF32S.
DEFINE_OP(kIdMinsd , 2, kSSE2 , kIdVminsd , kAVX , 0, 0, kSrc , 0, 0x00u, kF64S, k64, 8, kNA), // kMinF64S.
DEFINE_OP(kIdMinps , 2, kSSE2 , kIdVminps , kAVX , 0, 0, kSrc , 0, 0x00u, kF32V, k32, 4, kNA), // kMinF32.
DEFINE_OP(kIdMinpd , 2, kSSE2 , kIdVminpd , kAVX , 0, 0, kSrc , 0, 0x00u, kF64V, k64, 8, kNA), // kMinF64.
DEFINE_OP(kIdMaxss , 2, kSSE2 , kIdVmaxss , kAVX , 0, 0, kSrc , 0, 0x00u, kF32S, k32, 4, kNA), // kMaxF32S.
DEFINE_OP(kIdMaxsd , 2, kSSE2 , kIdVmaxsd , kAVX , 0, 0, kSrc , 0, 0x00u, kF64S, k64, 8, kNA), // kMaxF64S.
DEFINE_OP(kIdMaxps , 2, kSSE2 , kIdVmaxps , kAVX , 0, 0, kSrc , 0, 0x00u, kF32V, k32, 4, kNA), // kMaxF32.
DEFINE_OP(kIdMaxpd , 2, kSSE2 , kIdVmaxpd , kAVX , 0, 0, kSrc , 0, 0x00u, kF64V, k64, 8, kNA), // kMaxF64.
DEFINE_OP(kIdCmpss , 2, kIntrin, kIdVcmpss , kAVX , 1, 1, kNone, 1, 0x00u, kF32S, k32, 4, kNA), // kCmpEqF32S (eq ordered quiet).
DEFINE_OP(kIdCmpsd , 2, kIntrin, kIdVcmpsd , kAVX , 1, 1, kNone, 1, 0x00u, kF64S, k64, 8, kNA), // kCmpEqF64S (eq ordered quiet).
DEFINE_OP(kIdCmpps , 2, kIntrin, kIdVcmpps , kAVX , 1, 1, kNone, 1, 0x00u, kF32V, k32, 4, kNA), // kCmpEqF32 (eq ordered quiet).
DEFINE_OP(kIdCmppd , 2, kIntrin, kIdVcmppd , kAVX , 1, 1, kNone, 1, 0x00u, kF64V, k64, 8, kNA), // kCmpEqF64 (eq ordered quiet).
DEFINE_OP(kIdCmpss , 2, kIntrin, kIdVcmpss , kAVX , 1, 1, kNone, 1, 0x04u, kF32S, k32, 4, kNA), // kCmpNeF32S (ne unordered quiet).
DEFINE_OP(kIdCmpsd , 2, kIntrin, kIdVcmpsd , kAVX , 1, 1, kNone, 1, 0x04u, kF64S, k64, 8, kNA), // kCmpNeF64S (ne unordered quiet).
DEFINE_OP(kIdCmpps , 2, kIntrin, kIdVcmpps , kAVX , 1, 1, kNone, 1, 0x04u, kF32V, k32, 4, kNA), // kCmpNeF32 (ne unordered quiet).
DEFINE_OP(kIdCmppd , 2, kIntrin, kIdVcmppd , kAVX , 1, 1, kNone, 1, 0x04u, kF64V, k64, 8, kNA), // kCmpNeF64 (ne unordered quiet).
DEFINE_OP(kIdCmpss , 2, kIntrin, kIdVcmpss , kAVX , 0, 1, kNone, 1, 0x1Eu, kF32S, k32, 4, kNA), // kCmpGtF32S (gt ordered quiet).
DEFINE_OP(kIdCmpsd , 2, kIntrin, kIdVcmpsd , kAVX , 0, 1, kNone, 1, 0x1Eu, kF64S, k64, 8, kNA), // kCmpGtF64S (gt ordered quiet).
DEFINE_OP(kIdCmpps , 2, kIntrin, kIdVcmpps , kAVX , 0, 1, kNone, 1, 0x1Eu, kF32V, k32, 4, kNA), // kCmpGtF32 (gt ordered quiet).
DEFINE_OP(kIdCmppd , 2, kIntrin, kIdVcmppd , kAVX , 0, 1, kNone, 1, 0x1Eu, kF64V, k64, 8, kNA), // kCmpGtF64 (gt ordered quiet).
DEFINE_OP(kIdCmpss , 2, kIntrin, kIdVcmpss , kAVX , 0, 1, kNone, 1, 0x1Du, kF32S, k32, 4, kNA), // kCmpGeF32S (ge ordered quiet).
DEFINE_OP(kIdCmpsd , 2, kIntrin, kIdVcmpsd , kAVX , 0, 1, kNone, 1, 0x1Du, kF64S, k64, 8, kNA), // kCmpGeF64S (ge ordered quiet).
DEFINE_OP(kIdCmpps , 2, kIntrin, kIdVcmpps , kAVX , 0, 1, kNone, 1, 0x1Du, kF32V, k32, 4, kNA), // kCmpGeF32 (ge ordered quiet).
DEFINE_OP(kIdCmppd , 2, kIntrin, kIdVcmppd , kAVX , 0, 1, kNone, 1, 0x1Du, kF64V, k64, 8, kNA), // kCmpGeF64 (ge ordered quiet).
DEFINE_OP(kIdCmpss , 2, kIntrin, kIdVcmpss , kAVX , 0, 1, kNone, 1, 0x11u, kF32S, k32, 4, kNA), // kCmpLtF32S (lt ordered quiet).
DEFINE_OP(kIdCmpsd , 2, kIntrin, kIdVcmpsd , kAVX , 0, 1, kNone, 1, 0x11u, kF64S, k64, 8, kNA), // kCmpLtF64S (lt ordered quiet).
DEFINE_OP(kIdCmpps , 2, kIntrin, kIdVcmpps , kAVX , 0, 1, kNone, 1, 0x11u, kF32V, k32, 4, kNA), // kCmpLtF32 (lt ordered quiet).
DEFINE_OP(kIdCmppd , 2, kIntrin, kIdVcmppd , kAVX , 0, 1, kNone, 1, 0x11u, kF64V, k64, 8, kNA), // kCmpLtF64 (lt ordered quiet).
DEFINE_OP(kIdCmpss , 2, kIntrin, kIdVcmpss , kAVX , 0, 1, kNone, 1, 0x12u, kF32S, k32, 4, kNA), // kCmpLeF32S (le ordered quiet).
DEFINE_OP(kIdCmpsd , 2, kIntrin, kIdVcmpsd , kAVX , 0, 1, kNone, 1, 0x12u, kF64S, k64, 8, kNA), // kCmpLeF64S (le ordered quiet).
DEFINE_OP(kIdCmpps , 2, kIntrin, kIdVcmpps , kAVX , 0, 1, kNone, 1, 0x12u, kF32V, k32, 4, kNA), // kCmpLeF32 (le ordered quiet).
DEFINE_OP(kIdCmppd , 2, kIntrin, kIdVcmppd , kAVX , 0, 1, kNone, 1, 0x12u, kF64V, k64, 8, kNA), // kCmpLeF64 (le ordered quiet).
DEFINE_OP(kIdCmpss , 2, kIntrin, kIdVcmpss , kAVX , 1, 1, kNone, 1, 0x07u, kF32S, k32, 4, kNA), // kCmpOrdF32S (ordered quiet).
DEFINE_OP(kIdCmpsd , 2, kIntrin, kIdVcmpsd , kAVX , 1, 1, kNone, 1, 0x07u, kF64S, k64, 8, kNA), // kCmpOrdF64S (ordered quiet).
DEFINE_OP(kIdCmpps , 2, kIntrin, kIdVcmpps , kAVX , 1, 1, kNone, 1, 0x07u, kF32V, k32, 4, kNA), // kCmpOrdF32 (ordered quiet).
DEFINE_OP(kIdCmppd , 2, kIntrin, kIdVcmppd , kAVX , 1, 1, kNone, 1, 0x07u, kF64V, k64, 8, kNA), // kCmpOrdF64 (ordered quiet).
DEFINE_OP(kIdCmpss , 2, kIntrin, kIdVcmpss , kAVX , 1, 1, kNone, 1, 0x03u, kF32S, k32, 4, kNA), // kCmpUnordF32S (unordered quiet).
DEFINE_OP(kIdCmpsd , 2, kIntrin, kIdVcmpsd , kAVX , 1, 1, kNone, 1, 0x03u, kF64S, k64, 8, kNA), // kCmpUnordF64S (unordered quiet).
DEFINE_OP(kIdCmpps , 2, kIntrin, kIdVcmpps , kAVX , 1, 1, kNone, 1, 0x03u, kF32V, k32, 4, kNA), // kCmpUnordF32 (unordered quiet).
DEFINE_OP(kIdCmppd , 2, kIntrin, kIdVcmppd , kAVX , 1, 1, kNone, 1, 0x03u, kF64V, k64, 8, kNA), // kCmpUnordF64 (unordered quiet).
DEFINE_OP(kIdHaddpd , 2, kSSE3 , kIdVhaddpd , kIntrin , 0, 0, kNone, 0, 0x00u, kF64V, k64, 0, kNA), // kHAddF64.
DEFINE_OP(kIdNone , 0, kIntrin, kIdNone , kIntrin , 0, 0, kNone, 0, 0x00u, kNone, k64, 0, kNA), // kCombineLoHiU64.
DEFINE_OP(kIdNone , 0, kIntrin, kIdNone , kIntrin , 0, 0, kNone, 0, 0x00u, kNone, k64, 0, kNA), // kCombineLoHiF64.
DEFINE_OP(kIdNone , 0, kIntrin, kIdNone , kIntrin , 0, 0, kSrc , 0, 0x00u, kNone, k64, 0, kNA), // kCombineHiLoU64.
DEFINE_OP(kIdNone , 0, kIntrin, kIdNone , kIntrin , 0, 0, kSrc , 0, 0x00u, kNone, k64, 0, kNA), // kCombineHiLoF64.
DEFINE_OP(kIdPunpcklbw , 2, kSSE2 , kIdVpunpcklbw , kAVX , 0, 0, kNone, 0, 0x00u, kNone, k8 , 0, kLo), // kInterleaveLoU8.
DEFINE_OP(kIdPunpckhbw , 2, kSSE2 , kIdVpunpckhbw , kAVX , 0, 0, kNone, 0, 0x00u, kNone, k8 , 0, kHi), // kInterleaveHiU8.
DEFINE_OP(kIdPunpcklwd , 2, kSSE2 , kIdVpunpcklwd , kAVX , 0, 0, kNone, 0, 0x00u, kNone, k16, 0, kLo), // kInterleaveLoU16.
DEFINE_OP(kIdPunpckhwd , 2, kSSE2 , kIdVpunpckhwd , kAVX , 0, 0, kNone, 0, 0x00u, kNone, k16, 0, kHi), // kInterleaveHiU16.
DEFINE_OP(kIdPunpckldq , 2, kSSE2 , kIdVpunpckldq , kAVX , 0, 0, kNone, 0, 0x00u, kNone, k32, 0, kLo), // kInterleaveLoU32.
DEFINE_OP(kIdPunpckhdq , 2, kSSE2 , kIdVpunpckhdq , kAVX , 0, 0, kNone, 0, 0x00u, kNone, k32, 0, kHi), // kInterleaveHiU32.
DEFINE_OP(kIdPunpcklqdq , 2, kSSE2 , kIdVpunpcklqdq , kAVX , 0, 0, kNone, 0, 0x00u, kNone, k64, 0, kLo), // kInterleaveLoU64.
DEFINE_OP(kIdPunpckhqdq , 2, kSSE2 , kIdVpunpckhqdq , kAVX , 0, 0, kNone, 0, 0x00u, kNone, k64, 0, kHi), // kInterleaveHiU64.
DEFINE_OP(kIdUnpcklps , 2, kSSE2 , kIdVunpcklps , kAVX , 0, 0, kNone, 0, 0x00u, kNone, k32, 0, kLo), // kInterleaveLoF32.
DEFINE_OP(kIdUnpckhps , 2, kSSE2 , kIdVunpckhps , kAVX , 0, 0, kNone, 0, 0x00u, kNone, k32, 0, kHi), // kInterleaveHiF32.
DEFINE_OP(kIdUnpcklpd , 2, kSSE2 , kIdVunpcklpd , kAVX , 0, 0, kNone, 0, 0x00u, kNone, k64, 0, kLo), // kInterleaveLoF64.
DEFINE_OP(kIdUnpckhpd , 2, kSSE2 , kIdVunpckhpd , kAVX , 0, 0, kNone, 0, 0x00u, kNone, k64, 0, kHi), // kInterleaveHiF64.
DEFINE_OP(kIdPacksswb , 2, kSSE2 , kIdVpacksswb , kAVX , 0, 0, kNone, 0, 0x00u, kNone, k16, 0, kNA), // kPacksI16_I8.
DEFINE_OP(kIdPackuswb , 2, kSSE2 , kIdVpackuswb , kAVX , 0, 0, kNone, 0, 0x00u, kNone, k16, 0, kNA), // kPacksI16_U8.
DEFINE_OP(kIdPackssdw , 2, kSSE2 , kIdVpackssdw , kAVX , 0, 0, kNone, 0, 0x00u, kNone, k32, 0, kNA), // kPacksI32_I16.
DEFINE_OP(kIdPackusdw , 2, kSSE4_1, kIdVpackusdw , kAVX , 0, 0, kNone, 0, 0x00u, kNone, k32, 0, kNA), // kPacksI32_U16.
DEFINE_OP(kIdPshufb , 2, kSSSE3 , kIdVpshufb , kAVX , 0, 0, kNone, 0, 0x00u, kNone, k8 , 0, kNA), // kSwizzlev_U8.
DEFINE_OP(kIdNone , 0, kIntrin, kIdVpermb , kAVX512_VBMI, 0, 0, kNone, 0, 0x00u, kNone, k8 , 0, kNA), // kPermuteU8.
DEFINE_OP(kIdNone , 0, kIntrin, kIdVpermw , kAVX512 , 0, 0, kNone, 0, 0x00u, kNone, k16, 0, kNA), // kPermuteU16.
DEFINE_OP(kIdNone , 0, kIntrin, kIdVpermd , kAVX512 , 0, 0, kNone, 0, 0x00u, kNone, k32, 0, kNA), // kPermuteU32.
DEFINE_OP(kIdNone , 0, kIntrin, kIdVpermq , kAVX512 , 0, 0, kNone, 0, 0x00u, kNone, k64, 0, kNA) // kPermuteU64.
};
static constexpr UniOpVInfo opcode_info_3vi[size_t(UniOpVVVI::kMaxValue) + 1] = {
DEFINE_OP(kIdPalignr , 2, kIntrin, kIdVpalignr , kIntrin , 0, 0, kNone, 0, 0x00u, kNone, k8 , 0, kNA), // kAlignr_U128.
DEFINE_OP(kIdShufps , 2, kIntrin, kIdVshufps , kIntrin , 0, 0, kNone, 0, 0x00u, kNone, k32, 4, kNA), // kInterleaveShuffleU32x4.
DEFINE_OP(kIdShufpd , 2, kIntrin, kIdVshufpd , kIntrin , 0, 0, kNone, 0, 0x00u, kNone, k64, 8, kNA), // kInterleaveShuffleU64x2.
DEFINE_OP(kIdShufps , 2, kIntrin, kIdVshufps , kIntrin , 0, 0, kNone, 0, 0x00u, kNone, k32, 4, kNA), // kInterleaveShuffleF32x4.
DEFINE_OP(kIdShufpd , 2, kIntrin, kIdVshufpd , kIntrin , 0, 0, kNone, 0, 0x00u, kNone, k64, 8, kNA), // kInterleaveShuffleF64x2.
DEFINE_OP(kIdNone , 0, kIntrin, kIdVinserti32x4 , kIntrin , 0, 0, kNone, 0, 0x00u, kNone, k8 , 0, kNA), // kInsertV128_U32.
DEFINE_OP(kIdNone , 0, kIntrin, kIdVinserti64x2 , kIntrin , 0, 0, kNone, 0, 0x00u, kNone, k8 , 0, kNA), // kInsertV128_F32.
DEFINE_OP(kIdNone , 0, kIntrin, kIdVinsertf32x4 , kIntrin , 0, 0, kNone, 0, 0x00u, kNone, k8 , 0, kNA), // kInsertV128_U64.
DEFINE_OP(kIdNone , 0, kIntrin, kIdVinsertf64x2 , kIntrin , 0, 0, kNone, 0, 0x00u, kNone, k8 , 0, kNA), // kInsertV128_F64.
DEFINE_OP(kIdNone , 0, kIntrin, kIdVinserti32x8 , kIntrin , 0, 0, kNone, 0, 0x00u, kNone, k8 , 0, kNA), // kInsertV256_U32.
DEFINE_OP(kIdNone , 0, kIntrin, kIdVinsertf32x8 , kIntrin , 0, 0, kNone, 0, 0x00u, kNone, k8 , 0, kNA), // kInsertV256_F32.
DEFINE_OP(kIdNone , 0, kIntrin, kIdVinserti64x4 , kIntrin , 0, 0, kNone, 0, 0x00u, kNone, k8 , 0, kNA), // kInsertV256_U64.
DEFINE_OP(kIdNone , 0, kIntrin, kIdVinsertf64x4 , kIntrin , 0, 0, kNone, 0, 0x00u, kNone, k8 , 0, kNA) // kInsertV256_F64.
};
static constexpr UniOpVInfo opcode_info_4v[size_t(UniOpVVV::kMaxValue) + 1] = {
DEFINE_OP(kIdPblendvb , 0, kIntrin, kIdVpblendvb , kIntrin , 0, 0, kNone, 0, 0x00u, kNone, k8 , 0, kNA), // kBlendV_U8.
DEFINE_OP(kIdPmullw , 0, kIntrin, kIdVpmullw , kIntrin , 1, 0, kNone, 0, 0x00u, kNone, k16, 0, kNA), // kMAddU16.
DEFINE_OP(kIdPmulld , 0, kIntrin, kIdVpmulld , kIntrin , 1, 0, kNone, 0, 0x00u, kNone, k32, 4, kNA), // kMAddU32.
DEFINE_OP(kIdNone , 0, kIntrin, kIdNone , kIntrin , 0, 0, kNone, 0, 0x00u, kF32S, k32, 4, kNA), // kMAddF32S.
DEFINE_OP(kIdNone , 0, kIntrin, kIdNone , kIntrin , 0, 0, kNone, 0, 0x00u, kF64S, k64, 8, kNA), // kMAddF64S.
DEFINE_OP(kIdNone , 0, kIntrin, kIdNone , kIntrin , 0, 0, kNone, 0, 0x00u, kF32V, k32, 4, kNA), // kMAddF32.
DEFINE_OP(kIdNone , 0, kIntrin, kIdNone , kIntrin , 0, 0, kNone, 0, 0x00u, kF64V, k64, 8, kNA), // kMAddF64.
DEFINE_OP(kIdNone , 0, kIntrin, kIdNone , kIntrin , 0, 0, kNone, 0, 0x01u, kF32S, k32, 4, kNA), // kMSubF32S.
DEFINE_OP(kIdNone , 0, kIntrin, kIdNone , kIntrin , 0, 0, kNone, 0, 0x01u, kF64S, k64, 8, kNA), // kMSubF64S.
DEFINE_OP(kIdNone , 0, kIntrin, kIdNone , kIntrin , 0, 0, kNone, 0, 0x01u, kF32V, k32, 4, kNA), // kMSubF32.
DEFINE_OP(kIdNone , 0, kIntrin, kIdNone , kIntrin , 0, 0, kNone, 0, 0x01u, kF64V, k64, 8, kNA), // kMSubF64.
DEFINE_OP(kIdNone , 0, kIntrin, kIdNone , kIntrin , 0, 0, kNone, 0, 0x02u, kF32S, k32, 4, kNA), // kNMAddF32S.
DEFINE_OP(kIdNone , 0, kIntrin, kIdNone , kIntrin , 0, 0, kNone, 0, 0x02u, kF64S, k64, 8, kNA), // kNMAddF64S.
DEFINE_OP(kIdNone , 0, kIntrin, kIdNone , kIntrin , 0, 0, kNone, 0, 0x02u, kF32V, k32, 4, kNA), // kNMAddF32.
DEFINE_OP(kIdNone , 0, kIntrin, kIdNone , kIntrin , 0, 0, kNone, 0, 0x02u, kF64V, k64, 8, kNA), // kNMAddF64.
DEFINE_OP(kIdNone , 0, kIntrin, kIdNone , kIntrin , 0, 0, kNone, 0, 0x03u, kF32S, k32, 4, kNA), // kNMSubF32S.
DEFINE_OP(kIdNone , 0, kIntrin, kIdNone , kIntrin , 0, 0, kNone, 0, 0x03u, kF64S, k64, 8, kNA), // kNMSubF64S.
DEFINE_OP(kIdNone , 0, kIntrin, kIdNone , kIntrin , 0, 0, kNone, 0, 0x03u, kF32V, k32, 4, kNA), // kNMSubF32.
DEFINE_OP(kIdNone , 0, kIntrin, kIdNone , kIntrin , 0, 0, kNone, 0, 0x03u, kF64V, k64, 8, kNA) // kNMSubF64.
};
#undef DEFINE_OP
struct UniOpVMInfo {
//! \name Members
//! \{
uint32_t sse_inst_id : 13;
uint32_t avx_inst_id : 13;
uint32_t reserved1 : 6;
uint32_t cvt : 5;
uint32_t mem_size : 8;
uint32_t mem_size_shift : 3;
uint32_t reserved2 : 3;
//! \}
};
#define DEFINE_OP(sse_inst_id, avx_inst_id, cvt, mem_size, mem_size_shift) \
UniOpVMInfo { \
Inst::sse_inst_id, \
Inst::avx_inst_id, \
0, \
uint8_t(WideningOp::cvt), \
mem_size, \
mem_size_shift, \
0 \
}
static constexpr UniOpVMInfo opcode_info_2vm[size_t(UniOpVM::kMaxValue) + 1] = {
DEFINE_OP(kIdNone , kIdNone , kNone , 1, 0), // kLoad8.
DEFINE_OP(kIdNone , kIdVmovsh , kNone , 2, 0), // kLoad16_U16.
DEFINE_OP(kIdMovd , kIdVmovd , kNone , 4, 0), // kLoad32_U32.
DEFINE_OP(kIdMovss , kIdVmovss , kNone , 4, 0), // kLoad32_F32.
DEFINE_OP(kIdMovq , kIdVmovq , kNone , 8, 0), // kLoad64_U32.
DEFINE_OP(kIdMovq , kIdVmovq , kNone , 8, 0), // kLoad64_U64.
DEFINE_OP(kIdMovq , kIdVmovq , kNone , 8, 0), // kLoad64_F32.
DEFINE_OP(kIdMovsd , kIdVmovsd , kNone , 8, 0), // kLoad64_F64.
DEFINE_OP(kIdNone , kIdNone , kNone , 16, 0), // kLoad128_U32.
DEFINE_OP(kIdNone , kIdNone , kNone , 16, 0), // kLoad128_U64.
DEFINE_OP(kIdNone , kIdNone , kNone , 16, 0), // kLoad128_F32.
DEFINE_OP(kIdNone , kIdNone , kNone , 16, 0), // kLoad128_F64.
DEFINE_OP(kIdNone , kIdNone , kNone , 32, 0), // kLoad256_U32.
DEFINE_OP(kIdNone , kIdNone , kNone , 32, 0), // kLoad256_U64.
DEFINE_OP(kIdNone , kIdNone , kNone , 32, 0), // kLoad256_F32.
DEFINE_OP(kIdNone , kIdNone , kNone , 32, 0), // kLoad256_F64.
DEFINE_OP(kIdNone , kIdNone , kNone , 64, 0), // kLoad512_U32.
DEFINE_OP(kIdNone , kIdNone , kNone , 64, 0), // kLoad512_U64.
DEFINE_OP(kIdNone , kIdNone , kNone , 64, 0), // kLoad512_F32.
DEFINE_OP(kIdNone , kIdNone , kNone , 64, 0), // kLoad512_F64.
DEFINE_OP(kIdNone , kIdNone , kNone , 0, 0), // kLoadN_U32.
DEFINE_OP(kIdNone , kIdNone , kNone , 0, 0), // kLoadN_U64.
DEFINE_OP(kIdNone , kIdNone , kNone , 0, 0), // kLoadN_F32.
DEFINE_OP(kIdNone , kIdNone , kNone , 0, 0), // kLoadN_F64.
DEFINE_OP(kIdPmovzxbq , kIdVpmovzxbq , kU8ToU64 , 2, 3), // kLoadCvt16_U8ToU64.
DEFINE_OP(kIdPmovzxbq , kIdVpmovzxbq , kU8ToU64 , 4, 3), // kLoadCvt32_U8ToU64.
DEFINE_OP(kIdPmovzxbq , kIdVpmovzxbq , kU8ToU64 , 8, 3), // kLoadCvt64_U8ToU64.
DEFINE_OP(kIdPmovsxbw , kIdVpmovsxbw , kI8ToI16 , 4, 1), // kLoadCvt32_I8ToI16.
DEFINE_OP(kIdPmovzxbw , kIdVpmovzxbw , kU8ToU16 , 4, 1), // kLoadCvt32_U8ToU16.
DEFINE_OP(kIdPmovsxbd , kIdVpmovsxbd , kI8ToI32 , 4, 2), // kLoadCvt32_I8ToI32.
DEFINE_OP(kIdPmovzxbd , kIdVpmovzxbd , kU8ToU32 , 4, 2), // kLoadCvt32_U8ToU32.
DEFINE_OP(kIdPmovsxwd , kIdVpmovsxwd , kI16ToI32, 4, 1), // kLoadCvt32_I16ToI32.
DEFINE_OP(kIdPmovzxwd , kIdVpmovzxwd , kU16ToU32, 4, 1), // kLoadCvt32_U16ToU32.
DEFINE_OP(kIdPmovsxdq , kIdVpmovsxdq , kI32ToI64, 4, 1), // kLoadCvt32_I32ToI64.
DEFINE_OP(kIdPmovzxdq , kIdVpmovzxdq , kU32ToU64, 4, 1), // kLoadCvt32_U32ToU64.
DEFINE_OP(kIdPmovsxbw , kIdVpmovsxbw , kI8ToI16 , 8, 1), // kLoadCvt64_I8ToI16.
DEFINE_OP(kIdPmovzxbw , kIdVpmovzxbw , kU8ToU16 , 8, 1), // kLoadCvt64_U8ToU16.
DEFINE_OP(kIdPmovsxbd , kIdVpmovsxbd , kI8ToI32 , 8, 2), // kLoadCvt64_I8ToI32.
DEFINE_OP(kIdPmovzxbd , kIdVpmovzxbd , kU8ToU32 , 8, 2), // kLoadCvt64_U8ToU32.
DEFINE_OP(kIdPmovsxwd , kIdVpmovsxwd , kI16ToI32, 8, 1), // kLoadCvt64_I16ToI32.
DEFINE_OP(kIdPmovzxwd , kIdVpmovzxwd , kU16ToU32, 8, 1), // kLoadCvt64_U16ToU32.
DEFINE_OP(kIdPmovsxdq , kIdVpmovsxdq , kI32ToI64, 8, 1), // kLoadCvt64_I32ToI64.
DEFINE_OP(kIdPmovzxdq , kIdVpmovzxdq , kU32ToU64, 8, 1), // kLoadCvt64_U32ToU64.
DEFINE_OP(kIdNone , kIdVpmovsxbw , kI8ToI16 , 16, 3), // kLoadCvt128_I8ToI16.
DEFINE_OP(kIdNone , kIdVpmovzxbw , kU8ToU16 , 16, 3), // kLoadCvt128_U8ToU16.
DEFINE_OP(kIdNone , kIdVpmovsxbd , kI8ToI32 , 16, 2), // kLoadCvt128_I8ToI32.
DEFINE_OP(kIdNone , kIdVpmovzxbd , kU8ToU32 , 16, 2), // kLoadCvt128_U8ToU32.
DEFINE_OP(kIdNone , kIdVpmovsxwd , kI16ToI32, 16, 1), // kLoadCvt128_I16ToI32.
DEFINE_OP(kIdNone , kIdVpmovzxwd , kU16ToU32, 16, 1), // kLoadCvt128_U16ToU32.
DEFINE_OP(kIdNone , kIdVpmovsxdq , kI32ToI64, 16, 1), // kLoadCvt128_I32ToI64.
DEFINE_OP(kIdNone , kIdVpmovzxdq , kU32ToU64, 16, 1), // kLoadCvt128_U32ToU64.
DEFINE_OP(kIdNone , kIdVpmovsxbw , kI8ToI16 , 32, 1), // kLoadCvt256_I8ToI16.
DEFINE_OP(kIdNone , kIdVpmovzxbw , kU8ToU16 , 32, 1), // kLoadCvt256_U8ToU16.
DEFINE_OP(kIdNone , kIdVpmovsxwd , kI16ToI32, 32, 1), // kLoadCvt256_I16ToI32.
DEFINE_OP(kIdNone , kIdVpmovzxwd , kU16ToU32, 32, 1), // kLoadCvt256_U16ToU32.
DEFINE_OP(kIdNone , kIdVpmovsxdq , kI32ToI64, 32, 1), // kLoadCvt256_I32ToI64.
DEFINE_OP(kIdNone , kIdVpmovzxdq , kU32ToU64, 32, 1), // kLoadCvt256_U32ToU64.
DEFINE_OP(kIdPmovzxbq , kIdVpmovzxbq , kU8ToU64 , 0, 3), // kLoadCvtN_U8ToU64.
DEFINE_OP(kIdPmovsxbw , kIdVpmovsxbw , kI8ToI16 , 0, 1), // kLoadCvtN_I8ToI16.
DEFINE_OP(kIdPmovzxbw , kIdVpmovzxbw , kU8ToU16 , 0, 1), // kLoadCvtN_U8ToU16.
DEFINE_OP(kIdPmovsxbd , kIdVpmovsxbd , kI8ToI32 , 0, 2), // kLoadCvtN_I8ToI32.
DEFINE_OP(kIdPmovzxbd , kIdVpmovzxbd , kU8ToU32 , 0, 2), // kLoadCvtN_U8ToU32.
DEFINE_OP(kIdPmovsxwd , kIdVpmovsxwd , kI16ToI32, 0, 1), // kLoadCvtN_I16ToI32.
DEFINE_OP(kIdPmovzxwd , kIdVpmovzxwd , kU16ToU32, 0, 1), // kLoadCvtN_U16ToU32.
DEFINE_OP(kIdPmovsxdq , kIdVpmovsxdq , kI32ToI64, 0, 1), // kLoadCvtN_I32ToI64.
DEFINE_OP(kIdPmovzxdq , kIdVpmovzxdq , kU32ToU64, 0, 1), // kLoadCvtN_U32ToU64.
DEFINE_OP(kIdPinsrb , kIdVpinsrb , kNone , 1, 0), // kLoadInsertU8.
DEFINE_OP(kIdPinsrw , kIdVpinsrw , kNone , 2, 0), // kLoadInsertU16.
DEFINE_OP(kIdPinsrd , kIdVpinsrd , kNone , 4, 0), // kLoadInsertU32.
DEFINE_OP(kIdPinsrq , kIdVpinsrq , kNone , 8, 0), // kLoadInsertU64.
DEFINE_OP(kIdInsertps , kIdVinsertps , kNone , 4, 0), // kLoadInsertF32.
DEFINE_OP(kIdNone , kIdNone , kNone , 8, 0), // kLoadInsertF32x2.
DEFINE_OP(kIdNone , kIdNone , kNone , 8, 0) // kLoadInsertF64.
};
#undef DEFINE_OP
#define DEFINE_OP(sse_inst_id, avx_inst_id, cvt, mem_size, mem_size_shift) \
UniOpVMInfo { \
Inst::sse_inst_id, \
Inst::avx_inst_id, \
0, \
uint8_t(NarrowingOp::cvt), \
mem_size, \
mem_size_shift, \
0 \
}
static constexpr UniOpVMInfo opcode_info_2mv[size_t(UniOpMV::kMaxValue) + 1] = {
DEFINE_OP(kIdNone , kIdNone , kNone , 1, 0), // kStore8.
DEFINE_OP(kIdNone , kIdNone , kNone , 2, 0), // kStore16_U16.
DEFINE_OP(kIdMovd , kIdVmovd , kNone , 4, 0), // kStore32_U32.
DEFINE_OP(kIdMovss , kIdVmovss , kNone , 4, 0), // kStore32_F32.
DEFINE_OP(kIdMovq , kIdVmovq , kNone , 8, 0), // kStore64_U32.
DEFINE_OP(kIdMovq , kIdVmovq , kNone , 8, 0), // kStore64_U64.
DEFINE_OP(kIdMovq , kIdVmovq , kNone , 8, 0), // kStore64_F32.
DEFINE_OP(kIdMovsd , kIdVmovsd , kNone , 8, 0), // kStore64_F64.
DEFINE_OP(kIdNone , kIdNone , kNone , 16, 0), // kStore128_U32.
DEFINE_OP(kIdNone , kIdNone , kNone , 16, 0), // kStore128_U64.
DEFINE_OP(kIdNone , kIdNone , kNone , 16, 0), // kStore128_F32.
DEFINE_OP(kIdNone , kIdNone , kNone , 16, 0), // kStore128_F64.
DEFINE_OP(kIdNone , kIdNone , kNone , 32, 0), // kStore256_U32.
DEFINE_OP(kIdNone , kIdNone , kNone , 32, 0), // kStore256_U64.
DEFINE_OP(kIdNone , kIdNone , kNone , 32, 0), // kStore256_F32.
DEFINE_OP(kIdNone , kIdNone , kNone , 32, 0), // kStore256_F64.
DEFINE_OP(kIdNone , kIdNone , kNone , 64, 0), // kStore512_U32.
DEFINE_OP(kIdNone , kIdNone , kNone , 64, 0), // kStore512_U64.
DEFINE_OP(kIdNone , kIdNone , kNone , 64, 0), // kStore512_F32.
DEFINE_OP(kIdNone , kIdNone , kNone , 64, 0), // kStore512_F64.
DEFINE_OP(kIdNone , kIdNone , kNone , 0, 0), // kStoreN_U32.
DEFINE_OP(kIdNone , kIdNone , kNone , 0, 0), // kStoreN_U64.
DEFINE_OP(kIdNone , kIdNone , kNone , 0, 0), // kStoreN_F32.
DEFINE_OP(kIdNone , kIdNone , kNone , 0, 0), // kStoreN_F64.
/*
DEFINE_OP(kIdNone , kIdNone , kU16ToU8 , 8, 1), // kStoreCvtz64_U16ToU8.
DEFINE_OP(kIdNone , kIdNone , kU32ToU16, 8, 1), // kStoreCvtz64_U32ToU16.
DEFINE_OP(kIdNone , kIdNone , kU64ToU32, 8, 1), // kStoreCvtz64_U64ToU32.
DEFINE_OP(kIdNone , kIdNone , kI16ToI8 , 8, 1), // kStoreCvts64_I16ToI8.
DEFINE_OP(kIdNone , kIdNone , kI16ToU8 , 8, 1), // kStoreCvts64_I16ToU8.
DEFINE_OP(kIdNone , kIdNone , kU16ToU8 , 8, 1), // kStoreCvts64_U16ToU8.
DEFINE_OP(kIdNone , kIdNone , kI32ToI16, 8, 1), // kStoreCvts64_I32ToI16.
DEFINE_OP(kIdNone , kIdNone , kU32ToU16, 8, 1), // kStoreCvts64_U32ToU16.
DEFINE_OP(kIdNone , kIdNone , kI64ToI32, 8, 1), // kStoreCvts64_I64ToI32.
DEFINE_OP(kIdNone , kIdNone , kU64ToU32, 8, 1), // kStoreCvts64_U64ToU32.
DEFINE_OP(kIdNone , kIdNone , kU16ToU8 , 16, 1), // kStoreCvtz128_U16ToU8.
DEFINE_OP(kIdNone , kIdNone , kU32ToU16, 16, 1), // kStoreCvtz128_U32ToU16.
DEFINE_OP(kIdNone , kIdNone , kU64ToU32, 16, 1), // kStoreCvtz128_U64ToU32.
DEFINE_OP(kIdNone , kIdNone , kI16ToI8 , 16, 1), // kStoreCvts128_I16ToI8.
DEFINE_OP(kIdNone , kIdNone , kI16ToU8 , 16, 1), // kStoreCvts128_I16ToU8.
DEFINE_OP(kIdNone , kIdNone , kU16ToU8 , 16, 1), // kStoreCvts128_U16ToU8.
DEFINE_OP(kIdNone , kIdNone , kI32ToI16, 16, 1), // kStoreCvts128_I32ToI16.
DEFINE_OP(kIdNone , kIdNone , kU32ToU16, 16, 1), // kStoreCvts128_U32ToU16.
DEFINE_OP(kIdNone , kIdNone , kI64ToI32, 16, 1), // kStoreCvts128_I64ToI32.
DEFINE_OP(kIdNone , kIdNone , kU64ToU32, 16, 1), // kStoreCvts128_U64ToU32.
DEFINE_OP(kIdNone , kIdNone , kU16ToU8 , 32, 1), // kStoreCvtz256_U16ToU8.
DEFINE_OP(kIdNone , kIdNone , kU32ToU16, 32, 1), // kStoreCvtz256_U32ToU16.
DEFINE_OP(kIdNone , kIdNone , kU64ToU32, 32, 1), // kStoreCvtz256_U64ToU32.
DEFINE_OP(kIdNone , kIdNone , kI16ToI8 , 32, 1), // kStoreCvts256_I16ToI8.
DEFINE_OP(kIdNone , kIdNone , kI16ToU8 , 32, 1), // kStoreCvts256_I16ToU8.
DEFINE_OP(kIdNone , kIdNone , kU16ToU8 , 32, 1), // kStoreCvts256_U16ToU8.
DEFINE_OP(kIdNone , kIdNone , kI32ToI16, 32, 1), // kStoreCvts256_I32ToI16.
DEFINE_OP(kIdNone , kIdNone , kU32ToU16, 32, 1), // kStoreCvts256_U32ToU16.
DEFINE_OP(kIdNone , kIdNone , kI64ToI32, 32, 1), // kStoreCvts256_I64ToI32.
DEFINE_OP(kIdNone , kIdNone , kU64ToU32, 32, 1), // kStoreCvts256_U64ToU32.
DEFINE_OP(kIdNone , kIdNone , kU16ToU8 , 0, 1), // kStoreCvtzN_U16ToU8.
DEFINE_OP(kIdNone , kIdNone , kU32ToU16, 0, 1), // kStoreCvtzN_U32ToU16.
DEFINE_OP(kIdNone , kIdNone , kU64ToU32, 0, 1), // kStoreCvtzN_U64ToU32.
DEFINE_OP(kIdNone , kIdNone , kI16ToI8 , 0, 1), // kStoreCvtsN_I16ToI8.
DEFINE_OP(kIdNone , kIdNone , kI16ToU8 , 0, 1), // kStoreCvtsN_I16ToU8.
DEFINE_OP(kIdNone , kIdNone , kU16ToU8 , 0, 1), // kStoreCvtsN_U16ToU8.
DEFINE_OP(kIdNone , kIdNone , kI32ToI16, 0, 1), // kStoreCvtsN_I32ToI16.
DEFINE_OP(kIdNone , kIdNone , kU32ToU16, 0, 1), // kStoreCvtsN_U32ToU16.
DEFINE_OP(kIdNone , kIdNone , kI64ToI32, 0, 1), // kStoreCvtsN_I64ToI32.
DEFINE_OP(kIdNone , kIdNone , kU64ToU32, 0, 1) // kStoreCvtsN_U64ToU32.
*/
DEFINE_OP(kIdPextrw , kIdVpextrw , kNone , 2, 0), // kStoreExtractU16.
DEFINE_OP(kIdPextrd , kIdVpextrd , kNone , 4, 0), // kStoreExtractU32.
DEFINE_OP(kIdPextrq , kIdVpextrq , kNone , 8, 0) // kStoreExtractU64.
};
#undef DEFINE_OP
// ujit::UniCompiler - Vector Instructions - Utility Functions
// ===========================================================
static ASMJIT_NOINLINE void UniCompiler_load_into(UniCompiler& uc, const Vec& vec, const Mem& mem, uint32_t broadcast_size = 0) {
BackendCompiler* cc = uc.cc;
Mem m(mem);
if (mem.has_broadcast() && broadcast_size) {
m.reset_broadcast();
switch (broadcast_size) {
case 1: cc->vpbroadcastb(vec, m); break;
case 2: cc->vpbroadcastw(vec, m); break;
case 4: cc->vpbroadcastd(vec, m); break;
case 8: cc->vpbroadcastq(vec, m); break;
default:
ASMJIT_NOT_REACHED();
}
}
else {
m.set_size(vec.size());
if (vec.is_vec512())
cc->vmovdqu32(vec, m);
else if (uc.has_avx())
cc->vmovdqu(vec, m);
else
cc->movdqu(vec, m);
}
}
// TODO: Unused for now...
[[maybe_unused]]
static ASMJIT_NOINLINE void UniCompiler_move_to_dst(UniCompiler& uc, const Vec& dst, const Operand_& src, uint32_t broadcast_size = 0) {
if (src.is_reg()) {
ASMJIT_ASSERT(src.is_vec());
if (dst.id() != src.as<Reg>().id()) {
uc.v_mov(dst, src);
}
}
else if (src.is_mem()) {
UniCompiler_load_into(uc, dst, src.as<Mem>(), broadcast_size);
}
else {
ASMJIT_NOT_REACHED();
}
}
static ASMJIT_NOINLINE Vec UniCompiler_load_new(UniCompiler& uc, const Vec& ref, const Mem& mem, uint32_t broadcast_size = 0) {
Vec vec = uc.new_similar_reg(ref, "@vec_m");
UniCompiler_load_into(uc, vec, mem, broadcast_size);
return vec;
}
static ASMJIT_INLINE bool is_same_vec(const Vec& a, const Operand_& b) noexcept {
return b.is_reg() && a.id() == b.as<Reg>().id();
}
static ASMJIT_INLINE Operand get_fop_one(UniCompiler& uc, const Vec& dst, FloatMode fm) {
Operand op;
if (is_f32_op(fm))
op = uc.simd_const(&uc.ct().f32_1, Bcst::k32, dst);
else
op = uc.simd_const(&uc.ct().f64_1, Bcst::k64, dst);
return op;
}
static ASMJIT_INLINE Operand get_fop_half_minus_1ulp(UniCompiler& uc, const Vec& dst, FloatMode fm) {
Operand op;
if (is_f32_op(fm))
op = uc.simd_const(&uc.ct().f32_0_5_minus_1ulp, Bcst::k32, dst);
else
op = uc.simd_const(&uc.ct().f64_0_5_minus_1ulp, Bcst::k64, dst);
return op;
}
static ASMJIT_INLINE Operand get_fop_round_magic(UniCompiler& uc, const Vec& dst, FloatMode fm) {
Operand op;
if (is_f32_op(fm))
op = uc.simd_const(&uc.ct().f32_round_magic, Bcst::k32, dst);
else
op = uc.simd_const(&uc.ct().f64_round_magic, Bcst::k64, dst);
return op;
}
static ASMJIT_INLINE Operand get_fop_msb_bit(UniCompiler& uc, const Vec& dst, FloatMode fm) {
Operand op;
if (is_f32_op(fm))
op = uc.simd_const(&uc.ct().p_8000000080000000, Bcst::k32, dst);
else
op = uc.simd_const(&uc.ct().p_8000000000000000, Bcst::k64, dst);
return op;
}
static ASMJIT_NOINLINE void sse_mov(UniCompiler& uc, const Vec& dst, const Operand_& src) {
BackendCompiler* cc = uc.cc;
if (src.is_mem())
cc->emit(Inst::kIdMovups, dst, src);
else if (dst.id() != src.id())
cc->emit(Inst::kIdMovaps, dst, src);
}
static ASMJIT_NOINLINE void sse_fmov(UniCompiler& uc, const Vec& dst, const Operand_& src, FloatMode fm) {
BackendCompiler* cc = uc.cc;
if (src.is_reg()) {
if (dst.id() != src.id()) {
cc->emit(Inst::kIdMovaps, dst, src);
}
}
else if (is_scalar_fp_op(fm)) {
cc->emit(sse_float_inst[size_t(fm)].fmovs, dst, src);
}
else {
cc->emit(sse_float_inst[size_t(fm)].fmovu, dst, src);
}
}
static ASMJIT_NOINLINE Vec sse_copy(UniCompiler& uc, const Vec& vec, const char* name) {
Vec copy = uc.new_similar_reg(vec, name);
uc.cc->emit(Inst::kIdMovaps, copy, vec);
return copy;
}
static ASMJIT_NOINLINE void sse_make_vec(UniCompiler& uc, Operand_& op, const char* name) {
if (op.is_mem()) {
Vec tmp = uc.new_vec128(name);
sse_mov(uc, tmp, op);
op = tmp;
}
}
static ASMJIT_INLINE uint32_t shuf_imm2_from_swizzle(Swizzle2 s) noexcept {
return x86::shuffle_imm((s.value >> 8) & 0x1, s.value & 0x1);
}
static ASMJIT_INLINE uint32_t shuf_imm2_from_swizzle_with_width(Swizzle2 s, VecWidth w) noexcept {
static constexpr uint32_t multipliers[] = { 0x1, 0x5, 0x55 };
return shuf_imm2_from_swizzle(s) * multipliers[size_t(w)];
}
static ASMJIT_INLINE uint32_t shuf_imm4_from_swizzle(Swizzle4 s) noexcept {
return x86::shuffle_imm((s.value >> 24 & 0x3), (s.value >> 16) & 0x3, (s.value >> 8) & 0x3, s.value & 0x3);
}
static ASMJIT_INLINE uint32_t shuf_imm4_from_swizzle(Swizzle2 s) noexcept {
uint32_t imm0 = uint32_t(s.value ) & 1u;
uint32_t imm1 = uint32_t(s.value >> 8) & 1u;
return x86::shuffle_imm(imm1 * 2u + 1u, imm1 * 2u, imm0 * 2u + 1u, imm0 * 2u);
}
static ASMJIT_NOINLINE void sse_bit_not(UniCompiler& uc, const Vec& dst, const Operand_& src) {
BackendCompiler* cc = uc.cc;
sse_mov(uc, dst, src);
Operand ones = uc.simd_const(&uc.ct().p_FFFFFFFFFFFFFFFF, Bcst::k32, dst);
cc->emit(Inst::kIdPxor, dst, ones);
}
static ASMJIT_NOINLINE void sse_msb_flip(UniCompiler& uc, const Vec& dst, const Operand_& src, ElementSize sz) {
BackendCompiler* cc = uc.cc;
const void* msk_data {};
switch (sz) {
case ElementSize::k8 : msk_data = &uc.ct().p_8080808080808080; break;
case ElementSize::k16: msk_data = &uc.ct().p_8000800080008000; break;
case ElementSize::k32: msk_data = &uc.ct().p_8000000080000000; break;
case ElementSize::k64: msk_data = &uc.ct().p_8000000000000000; break;
default:
ASMJIT_NOT_REACHED();
}
Operand msk = uc.simd_const(msk_data, Bcst::kNA, dst);
sse_mov(uc, dst, src);
cc->emit(Inst::kIdPxor, dst, msk);
}
static ASMJIT_NOINLINE void sse_fsign_flip(UniCompiler& uc, const Vec& dst, const Operand_& src, FloatMode fm) {
BackendCompiler* cc = uc.cc;
const FloatInst& fi = sse_float_inst[size_t(fm)];
Operand msk;
switch (fm) {
case FloatMode::kF32S: msk = uc.simd_const(&uc.ct().sign32_scalar, Bcst::k32, dst); break;
case FloatMode::kF64S: msk = uc.simd_const(&uc.ct().sign64_scalar, Bcst::k64, dst); break;
case FloatMode::kF32V: msk = uc.simd_const(&uc.ct().p_8000000080000000, Bcst::k32, dst); break;
case FloatMode::kF64V: msk = uc.simd_const(&uc.ct().p_8000000000000000, Bcst::k64, dst); break;
default:
ASMJIT_NOT_REACHED();
}
sse_fmov(uc, dst, src, fm);
cc->emit(fi.fxor, dst, msk);
}
// Possibly the best solution:
// https://stackoverflow.com/questions/65166174/how-to-simulate-pcmpgtq-on-sse2
static ASMJIT_NOINLINE void sse_cmp_gt_i64(UniCompiler& uc, const Vec& dst, const Operand_& a, const Operand_& b) {
BackendCompiler* cc = uc.cc;
if (uc.has_sse4_2()) {
if (is_same_vec(dst, a)) {
cc->emit(Inst::kIdPcmpgtq, dst, b);
}
else {
Operand_ second = b;
if (is_same_vec(dst, b)) {
second = cc->new_similar_reg(dst, "@tmp");
sse_mov(uc, second.as<Vec>(), b);
}
sse_mov(uc, dst, a);
cc->emit(Inst::kIdPcmpgtq, dst, second);
}
}
else {
Vec tmp1 = cc->new_similar_reg(dst, "@tmp1");
Vec tmp2 = cc->new_similar_reg(dst, "@tmp2");
cc->emit(Inst::kIdMovdqa, tmp1, a);
cc->emit(Inst::kIdMovdqa, tmp2, b);
cc->emit(Inst::kIdPcmpeqd, tmp1, tmp2);
cc->emit(Inst::kIdPsubq, tmp2, a);
cc->emit(Inst::kIdPand, tmp1, tmp2);
if (!is_same_vec(dst, b)) {
sse_mov(uc, dst, a);
cc->emit(Inst::kIdPcmpgtd, dst, b);
cc->emit(Inst::kIdPor, dst, tmp1);
cc->emit(Inst::kIdPshufd, dst, dst, x86::shuffle_imm(3, 3, 1, 1));
}
else {
sse_mov(uc, tmp2, a);
cc->emit(Inst::kIdPcmpgtd, tmp2, b);
cc->emit(Inst::kIdPor, tmp2, tmp1);
cc->emit(Inst::kIdPshufd, dst, tmp2, x86::shuffle_imm(3, 3, 1, 1));
}
}
}
// Possibly the best solution:
// https://stackoverflow.com/questions/65441496/what-is-the-most-efficient-way-to-do-unsigned-64-bit-comparison-on-sse2
static ASMJIT_NOINLINE void sse_cmp_gt_u64(UniCompiler& uc, const Vec& dst, const Operand_& a, const Operand_& b) {
BackendCompiler* cc = uc.cc;
if (uc.has_sse4_2()) {
Operand msk = uc.simd_const(&uc.ct().p_8000000000000000, Bcst::k64, dst);
Vec tmp = cc->new_similar_reg(dst, "@tmp");
if (is_same_vec(dst, a)) {
sse_mov(uc, tmp, msk);
cc->emit(Inst::kIdPxor, dst, tmp);
cc->emit(Inst::kIdPxor, tmp, b);
cc->emit(Inst::kIdPcmpgtq, dst, tmp);
}
else {
sse_mov(uc, tmp, b);
sse_mov(uc, dst, a);
cc->emit(Inst::kIdPxor, dst, msk);
cc->emit(Inst::kIdPxor, tmp, msk);
cc->emit(Inst::kIdPcmpgtq, dst, tmp);
}
}
else {
Vec tmp1 = cc->new_similar_reg(dst, "@tmp1");
Vec tmp2 = cc->new_similar_reg(dst, "@tmp2");
Vec tmp3 = cc->new_similar_reg(dst, "@tmp3");
sse_mov(uc, tmp1, b); // tmp1 = b;
sse_mov(uc, tmp2, a); // tmp2 = a;
cc->emit(Inst::kIdMovaps, tmp3, tmp1); // tmp3 = b;
cc->emit(Inst::kIdPsubq, tmp3, tmp2); // tmp3 = b - a
cc->emit(Inst::kIdPxor, tmp2, tmp1); // tmp2 = b ^ a
cc->emit(Inst::kIdPandn, tmp1, a); // tmp1 =~b & a
cc->emit(Inst::kIdPandn, tmp2, tmp3); // tmp2 =~(b ^ a) & (b - a)
cc->emit(Inst::kIdPor, tmp1, tmp2); // tmp2 =~(b ^ a) & (b - a) | (~b & a)
cc->emit(Inst::kIdPsrad, tmp1, 31); // tmp1 =~(b ^ a) & (b - a) | (~b & a) - repeated MSB bits in 32-bit lanes
cc->emit(Inst::kIdPshufd, dst, tmp1, x86::shuffle_imm(3, 3, 1, 1));
}
}
static ASMJIT_NOINLINE void sse_select(UniCompiler& uc, const Vec& dst, const Vec& a, const Operand_& b, const Vec& msk) {
BackendCompiler* cc = uc.cc;
sse_mov(uc, dst, a);
cc->emit(Inst::kIdPand, dst, msk);
cc->emit(Inst::kIdPandn, msk, b);
cc->emit(Inst::kIdPor, dst, msk);
}
static ASMJIT_NOINLINE void sse_int_widen(UniCompiler& uc, const Vec& dst, const Vec& src, WideningOp cvt) {
BackendCompiler* cc = uc.cc;
WideningOpInfo cvt_info = sse_int_widening_op_info[size_t(cvt)];
if (uc.has_sse4_1()) {
cc->emit(cvt_info.mov, dst, src);
return;
}
if (!cvt_info.sign_extends && cvt_info.unpack_lo != Inst::kIdNone) {
Operand zero = uc.simd_const(&uc.ct().p_0000000000000000, Bcst::kNA, dst);
sse_mov(uc, dst, src);
cc->emit(cvt_info.unpack_lo, dst, zero);
return;
}
switch (cvt) {
case WideningOp::kI8ToI16: {
cc->overwrite().emit(cvt_info.unpack_lo, dst, src);
cc->psraw(dst, 8);
return;
}
case WideningOp::kI8ToI32: {
cc->overwrite().emit(Inst::kIdPunpcklbw, dst, src);
cc->punpcklwd(dst, dst);
cc->psrad(dst, 24);
return;
}
case WideningOp::kU8ToU32: {
Operand zero = uc.simd_const(&uc.ct().p_0000000000000000, Bcst::kNA, dst);
sse_mov(uc, dst, src);
cc->emit(Inst::kIdPunpcklbw, dst, zero);
cc->emit(Inst::kIdPunpcklwd, dst, zero);
return;
}
case WideningOp::kU8ToU64: {
Operand zero = uc.simd_const(&uc.ct().p_0000000000000000, Bcst::kNA, dst);
sse_mov(uc, dst, src);
cc->emit(Inst::kIdPunpcklbw, dst, zero);
cc->emit(Inst::kIdPunpcklwd, dst, zero);
cc->emit(Inst::kIdPunpckldq, dst, zero);
return;
}
case WideningOp::kI16ToI32: {
cc->overwrite().emit(cvt_info.unpack_lo, dst, src);
cc->psrad(dst, 16);
return;
}
case WideningOp::kI32ToI64: {
Vec tmp = uc.new_similar_reg(dst, "@tmp");
sse_mov(uc, tmp, src);
sse_mov(uc, dst, src);
cc->psrad(tmp, 31);
cc->punpckldq(dst, tmp);
return;
}
default:
ASMJIT_NOT_REACHED();
}
}
static ASMJIT_NOINLINE void sse_round(UniCompiler& uc, const Vec& dst, const Operand& src, FloatMode fm, x86::RoundImm round_mode) {
BackendCompiler* cc = uc.cc;
uint32_t is_f32 = fm == FloatMode::kF32S || fm == FloatMode::kF32V;
const FloatInst& fi = sse_float_inst[size_t(fm)];
// NOTE: This may be dead code as the compiler handles this case well, however, if this function is
// called as a helper we don't want to emit a longer sequence if we can just use a single instruction.
if (uc.has_sse4_1()) {
cc->emit(fi.fround, dst, src, round_mode | x86::RoundImm::kSuppress);
return;
}
// round_max (f32) == 0x4B000000
// round_max (f64) == 0x4330000000000000
Operand maxn = get_fop_round_magic(uc, dst, fm);
Vec t1 = uc.new_similar_reg(dst, "@t1");
Vec t2 = uc.new_similar_reg(dst, "@t2");
Vec t3 = uc.new_similar_reg(dst, "@t3");
if (round_mode == x86::RoundImm::kTrunc) {
if (fm == FloatMode::kF32S || (fm == FloatMode::kF64S && cc->is_64bit())) {
Gp r;
Operand msb;
if (fm == FloatMode::kF32S) {
r = uc.new_gp32("@gp_tmp");
msb = uc.simd_const(&uc.ct().p_8000000080000000, Bcst::k32, dst);
}
else {
r = uc.new_gp64("@gp_tmp");
msb = uc.simd_const(&uc.ct().p_8000000000000000, Bcst::k64, dst);
}
sse_fmov(uc, dst, src, fm);
if (fm == FloatMode::kF32S)
cc->cvttss2si(r, dst);
else
cc->cvttsd2si(r, dst);
cc->emit(fi.fmova, t2, msb);
cc->emit(fi.fandn, t2, dst);
cc->emit(fi.fxor, t1, t1);
if (fm == FloatMode::kF32S)
cc->cvtsi2ss(t1, r);
else
cc->cvtsi2sd(t1, r);
cc->emit(fi.fcmp, t2, maxn, x86::CmpImm::kLT);
cc->emit(fi.fand, t1, t2);
cc->emit(fi.fandn, t2, dst);
cc->emit(fi.for_, t2, t1);
cc->emit(fi.fmovs, dst, t2);
return;
}
}
if (round_mode == x86::RoundImm::kNearest) {
// Pure SSE2 round-to-even implementation:
//
// float round_even(float x) {
// float magic = x >= 0 ? pow(2, 22) : pow(2, 22) + pow(2, 21);
// return x >= magic ? x : x + magic - magic;
// }
//
// double round_even(double x) {
// double magic = x >= 0 ? pow(2, 52) : pow(2, 52) + pow(2, 51);
// return x >= magic ? x : x + magic - magic;
// }
sse_fmov(uc, dst, src, fm);
cc->emit(fi.fmova, t3, dst);
// cc->emit(fi.psrl, t3, Imm(is_f32 ? 31 : 63));
// cc->emit(fi.psll, t3, Imm(is_f32 ? 23 : 51));
// cc->emit(fi.for_, t3, maxn);
cc->emit(fi.psrl, t3, Imm(is_f32 ? 31 : 63));
cc->emit(fi.psll, t3, Imm(is_f32 ? 23 : 52));
cc->emit(is_f32 ? Inst::kIdPaddd : Inst::kIdPaddq, t3, maxn);
cc->emit(fi.fmova, t1, dst);
cc->emit(fi.fcmp, t1, t3, x86::CmpImm::kLT);
cc->emit(fi.fand, t1, t3);
cc->emit(fi.fadd, dst, t1);
cc->emit(fi.fsub, dst, t1);
return;
}
Operand one = get_fop_one(uc, dst, fm);
if (round_mode == x86::RoundImm::kTrunc) {
// Should be handled earlier.
ASMJIT_ASSERT(fm != FloatMode::kF32S);
Operand msb;
if (fm == FloatMode::kF32V) {
msb = uc.simd_const(&uc.ct().p_8000000080000000, Bcst::k32, dst);
sse_fmov(uc, dst, src, fm);
cc->cvttps2dq(t1, dst);
cc->emit(fi.fmova, t2, msb);
cc->emit(fi.fandn, t2, dst);
cc->cvtdq2ps(t1, t1);
cc->emit(fi.fcmp, t2, maxn, x86::CmpImm::kLT);
cc->emit(fi.fand, t1, t2);
cc->emit(fi.fandn, t2, dst);
cc->emit(fi.for_, t2, t1);
cc->emit(fi.fmova, dst, t2);
}
else {
msb = uc.simd_const(&uc.ct().p_8000000000000000, Bcst::k64, dst);
sse_fmov(uc, dst, src, fm);
cc->emit(fi.fmova, t3, msb);
cc->emit(fi.fandn, t3, dst);
cc->emit(fi.fmova, t2, t3);
cc->emit(fi.fcmp, t2, maxn, x86::CmpImm::kLT);
cc->emit(fi.fand, t2, maxn);
cc->emit(fi.fmova, t1, t3);
cc->emit(fi.fadd, t1, t2);
cc->emit(fi.fsub, t1, t2);
cc->emit(fi.fcmp, t3, t1, x86::CmpImm::kLT);
cc->emit(fi.fand, t3, one);
cc->emit(fi.fsub, t1, t3);
cc->emit(fi.fand, dst, msb);
cc->emit(fi.for_, dst, t1);
return;
}
return;
}
// Round up & down needs a correction as adding and subtracting magic number rounds to nearest.
if (round_mode == x86::RoundImm::kDown || round_mode == x86::RoundImm::kUp) {
InstId correction_inst_id = round_mode == x86::RoundImm::kDown ? fi.fsub : fi.fadd;
x86::CmpImm correction_predicate = round_mode == x86::RoundImm::kDown ? x86::CmpImm::kLT : x86::CmpImm::kNLE;
sse_fmov(uc, dst, src, fm);
// maxn (f32) == 0x4B000000 (f64) == 0x4330000000000000
// t3 (f32) == 0x00800000 (f64) == 0x0008000000000000
cc->emit(fi.fmova, t3, dst);
cc->emit(fi.psrl, t3, Imm(is_f32 ? 31 : 63));
cc->emit(fi.psll, t3, Imm(is_f32 ? 23 : 52));
cc->emit(is_f32 ? Inst::kIdPaddd : Inst::kIdPaddq, t3, maxn);
cc->emit(fi.fmova, t1, dst);
cc->emit(fi.fmova, t2, dst);
cc->emit(fi.fadd, t2, t3);
cc->emit(fi.fsub, t2, t3);
cc->emit(fi.fcmp, t1, t3, x86::CmpImm::kNLT);
cc->emit(fi.fmova, t3, dst);
cc->emit(fi.fcmp, t3, t2, correction_predicate);
cc->emit(fi.fand, t3, one);
cc->emit(fi.fand, dst, t1);
cc->emit(correction_inst_id, t2, t3);
cc->emit(fi.fandn, t1, t2);
cc->emit(fi.for_, dst, t1);
return;
}
ASMJIT_NOT_REACHED();
}
static ASMJIT_NOINLINE void avx_mov(UniCompiler& uc, const Vec& dst, const Operand_& src) {
BackendCompiler* cc = uc.cc;
InstId inst_id = 0;
if (dst.is_vec512()) {
inst_id = src.is_mem() ? Inst::kIdVmovdqu32 : Inst::kIdVmovdqa32;
}
else {
inst_id = src.is_mem() ? Inst::kIdVmovdqu : Inst::kIdVmovdqa;
}
cc->emit(inst_id, dst, src);
}
static ASMJIT_NOINLINE void avx_fmov(UniCompiler& uc, const Vec& dst, const Operand_& src, FloatMode fm) {
BackendCompiler* cc = uc.cc;
if (src.is_reg()) {
if (dst.id() != src.id()) {
if (fm <= FloatMode::kF64S)
cc->emit(Inst::kIdVmovaps, dst.xmm(), src);
else
cc->emit(Inst::kIdVmovaps, dst, src);
}
}
else if (is_scalar_fp_op(fm)) {
cc->emit(avx_float_inst[size_t(fm)].fmovs, dst, src);
}
else {
cc->emit(avx_float_inst[size_t(fm)].fmovu, dst, src);
}
}
static ASMJIT_NOINLINE void avx_make_vec(UniCompiler& uc, Operand_& op, const Vec& ref, const char* name) {
if (op.is_mem()) {
Vec tmp = uc.new_similar_reg(ref, name);
avx_mov(uc, tmp, op);
op = tmp;
}
}
static ASMJIT_NOINLINE void avx_zero(UniCompiler& uc, const Vec& dst) {
BackendCompiler* cc = uc.cc;
Vec x = dst.xmm();
cc->vpxor(x, x, x);
return;
}
static ASMJIT_NOINLINE void avx_ones(UniCompiler& uc, const Vec& dst) {
BackendCompiler* cc = uc.cc;
if (uc.has_avx512())
cc->emit(Inst::kIdVpternlogd, dst, dst, dst, 0xFF);
else
cc->emit(Inst::kIdVpcmpeqb, dst, dst, dst);
}
static ASMJIT_NOINLINE void avx_bit_not(UniCompiler& uc, const Vec& dst, const Operand_& src) {
BackendCompiler* cc = uc.cc;
if (uc.has_avx512()) {
if (src.is_reg())
cc->overwrite().emit(Inst::kIdVpternlogd, dst, src, src, 0x55);
else
cc->overwrite().emit(Inst::kIdVpternlogd, dst, dst, src, 0x55);
return;
}
Operand ones = uc.simd_const(&uc.ct().p_FFFFFFFFFFFFFFFF, Bcst::k32, dst);
if (!src.is_reg()) {
if (ones.is_reg()) {
cc->emit(Inst::kIdVpxor, dst, ones, src);
}
else {
avx_mov(uc, dst, src);
cc->emit(Inst::kIdVpxor, dst, dst, ones);
}
}
else {
cc->emit(Inst::kIdVpxor, dst, src, ones);
}
}
static ASMJIT_NOINLINE void avx_isign_flip(UniCompiler& uc, const Vec& dst, const Operand_& src, ElementSize sz) {
BackendCompiler* cc = uc.cc;
Operand msk;
InstId xor_ = (uc.has_avx512() && dst.is_vec512()) ? Inst::kIdVpxord : Inst::kIdVpxor;
switch (sz) {
case ElementSize::k8: msk = uc.simd_const(&uc.ct().p_8080808080808080, Bcst::kNA, dst); break;
case ElementSize::k16: msk = uc.simd_const(&uc.ct().p_8000800080008000, Bcst::kNA, dst); break;
case ElementSize::k32: msk = uc.simd_const(&uc.ct().p_8000000080000000, Bcst::k32, dst); break;
case ElementSize::k64: msk = uc.simd_const(&uc.ct().p_8000000000000000, Bcst::k64, dst); break;
}
if (src.is_reg()) {
cc->emit(xor_, dst, src, msk);
}
else if (msk.is_reg()) {
cc->emit(xor_, dst, msk, src);
}
else {
avx_mov(uc, dst, src);
cc->emit(xor_, dst, dst, msk);
}
}
static ASMJIT_NOINLINE void avx_fsign_flip(UniCompiler& uc, const Vec& dst, const Operand_& src, FloatMode fm) {
BackendCompiler* cc = uc.cc;
const FloatInst& fi = avx_float_inst[size_t(fm)];
Operand msk;
switch (fm) {
case FloatMode::kF32S: msk = uc.simd_const(&uc.ct().sign32_scalar, Bcst::kNA, dst); break;
case FloatMode::kF64S: msk = uc.simd_const(&uc.ct().sign64_scalar, Bcst::kNA, dst); break;
case FloatMode::kF32V: msk = uc.simd_const(&uc.ct().p_8000000080000000, Bcst::k32, dst); break;
case FloatMode::kF64V: msk = uc.simd_const(&uc.ct().p_8000000000000000, Bcst::k64, dst); break;
default:
ASMJIT_NOT_REACHED();
}
if (src.is_reg()) {
cc->emit(fi.fxor, dst, src, msk);
}
else if (msk.is_reg() && fm >= FloatMode::kF32V) {
cc->emit(fi.fxor, dst, msk, src);
}
else {
avx_fmov(uc, dst, src, fm);
cc->emit(fi.fxor, dst, dst, msk);
}
}
// ujit::UniCompiler - Vector Instructions - OpArray Iterator
// ==========================================================
template<typename T>
class OpArrayIter {
public:
const T& _op;
ASMJIT_INLINE_NODEBUG OpArrayIter(const T& op) noexcept : _op(op) {}
ASMJIT_INLINE_NODEBUG const T& op() const noexcept { return _op; }
ASMJIT_INLINE_NODEBUG void next() noexcept {}
};
template<>
class OpArrayIter<OpArray> {
public:
const OpArray& _opArray;
size_t _i {};
size_t _n {};
ASMJIT_INLINE_NODEBUG OpArrayIter(const OpArray& op_array) noexcept : _opArray(op_array), _i(0), _n(op_array.size()) {}
ASMJIT_INLINE_NODEBUG const Operand_& op() const noexcept { return _opArray[_i]; }
ASMJIT_INLINE_NODEBUG void next() noexcept { if (++_i >= _n) _i = 0; }
};
template<typename Src>
static ASMJIT_INLINE void emit_2v_t(UniCompiler& uc, UniOpVV op, const OpArray& dst_, const Src& src_) {
size_t n = dst_.size();
OpArrayIter<Src> src(src_);
for (size_t i = 0; i < n; i++) {
uc.emit_2v(op, dst_[i], src.op());
src.next();
}
}
template<typename Src>
static ASMJIT_INLINE void emit_2vi_t(UniCompiler& uc, UniOpVVI op, const OpArray& dst_, const Src& src_, uint32_t imm) {
size_t n = dst_.size();
OpArrayIter<Src> src(src_);
for (size_t i = 0; i < n; i++) {
uc.emit_2vi(op, dst_[i], src.op(), imm);
src.next();
}
}
template<typename Src1, typename Src2>
static ASMJIT_INLINE void emit_3v_t(UniCompiler& uc, UniOpVVV op, const OpArray& dst_, const Src1& src1_, const Src2& src2_) {
size_t n = dst_.size();
OpArrayIter<Src1> src1(src1_);
OpArrayIter<Src2> src2(src2_);
for (size_t i = 0; i < n; i++) {
uc.emit_3v(op, dst_[i], src1.op(), src2.op());
src1.next();
src2.next();
}
}
template<typename Src1, typename Src2>
static ASMJIT_INLINE void emit_3vi_t(UniCompiler& uc, UniOpVVVI op, const OpArray& dst_, const Src1& src1_, const Src2& src2_, uint32_t imm) {
size_t n = dst_.size();
OpArrayIter<Src1> src1(src1_);
OpArrayIter<Src2> src2(src2_);
for (size_t i = 0; i < n; i++) {
uc.emit_3vi(op, dst_[i], src1.op(), src2.op(), imm);
src1.next();
src2.next();
}
}
template<typename Src1, typename Src2, typename Src3>
static ASMJIT_INLINE void emit_4v_t(UniCompiler& uc, UniOpVVVV op, const OpArray& dst_, const Src1& src1_, const Src2& src2_, const Src3& src3_) {
size_t n = dst_.size();
OpArrayIter<Src1> src1(src1_);
OpArrayIter<Src2> src2(src2_);
OpArrayIter<Src3> src3(src3_);
for (size_t i = 0; i < n; i++) {
uc.emit_4v(op, dst_[i], src1.op(), src2.op(), src3.op());
src1.next();
src2.next();
src3.next();
}
}
// ujit::UniCompiler - Vector Instructions - Emit 2V
// =================================================
void UniCompiler::emit_2v(UniOpVV op, const Operand_& dst_, const Operand_& src_) {
ASMJIT_ASSERT(dst_.is_vec());
Vec dst(dst_.as<Vec>());
Operand src(src_);
UniOpVInfo op_info = opcode_info_2v[size_t(op)];
if (has_avx()) {
// AVX Implementation
// ------------------
InstId inst_id = op_info.avx_inst_id;
if (has_avx_ext(AVXExt(op_info.avx_ext))) {
ASMJIT_ASSERT(inst_id != Inst::kIdNone);
if (op_info.use_imm)
cc->emit(inst_id, dst, src, Imm(op_info.imm));
else
cc->emit(inst_id, dst, src);
return;
}
switch (op) {
case UniOpVV::kMov: {
cc->emit(Inst::kIdVmovaps, dst, src);
return;
}
case UniOpVV::kMovU64: {
if (src.is_vec())
src = src.as<Vec>().xmm();
cc->emit(Inst::kIdVmovq, dst.xmm(), src);
return;
}
case UniOpVV::kBroadcastU8Z:
case UniOpVV::kBroadcastU16Z:
case UniOpVV::kBroadcastU8:
case UniOpVV::kBroadcastU16:
case UniOpVV::kBroadcastU32:
case UniOpVV::kBroadcastU64:
case UniOpVV::kBroadcastF32:
case UniOpVV::kBroadcastF64: {
// Intrinsic - 32/64-bit broadcasts require AVX, 8/16-bit broadcasts require AVX2/AVX512.
ASMJIT_ASSERT(src.is_reg() || src.is_mem());
ElementSize element_size = ElementSize(op_info.element_size);
if (src.is_gp()) {
Gp src_gp = src.as<Gp>();
if (element_size <= ElementSize::k32)
src_gp = src_gp.r32();
else
src_gp = src_gp.r64();
// AVX512 provides broadcast instructions for both GP, XMM, and memory sources, however, from GP register
// only VP instructions are available, so we have to convert VBROADCAST[SS|SD] to VPBROADCAST[D|Q].
if (has_avx512()) {
if (op == UniOpVV::kBroadcastF32) inst_id = Inst::kIdVpbroadcastd;
if (op == UniOpVV::kBroadcastF64) inst_id = Inst::kIdVpbroadcastq;
cc->emit(inst_id, dst, src_gp);
return;
}
// We can handle BroadcastU[8|16]Z differently when AVX2 is not present. Since the opcode has guaranteed
// source, which has zerod the rest of the register, we are going to multiply with a constant to extend
// the data into 32 bits, and then we can just use VBROADCASTSS, which would do the rest.
if (!has_avx2() && element_size <= ElementSize::k16 && op_info.imm == 0x01u) {
Gp expanded = new_gp32("@expanded");
cc->imul(expanded, src_gp, element_size == ElementSize::k8 ? 0x01010101u : 0x00010001u);
cc->vmovd(dst.xmm(), expanded);
cc->vpshufd(dst.xmm(), dst.xmm(), x86::shuffle_imm(0, 0, 0, 0));
if (!dst.is_vec128())
cc->emit(Inst::kIdVinsertf128, dst, dst, dst.xmm(), 0);
return;
}
// AVX/AVX2 doesn't provide broadcast from GP to XMM, we have to move to XMM first.
InstId mov = element_size <= ElementSize::k32 ? Inst::kIdVmovd : Inst::kIdVmovq;
cc->emit(mov, dst.xmm(), src_gp);
src = dst.xmm();
}
// We have ether a broadcast from memory or an XMM register - AVX2 requires special handling from here...
if (!has_avx2()) {
Vec dst_xmm = dst.xmm();
if (element_size <= ElementSize::k16) {
// AVX doesn't provide 8-bit and 16-bit broadcasts - the simplest way is to just use VPSHUFB to repeat the byte.
InstId insert_inst_id = element_size == ElementSize::k8 ? Inst::kIdVpinsrb : Inst::kIdVpinsrw;
const void* pred_data = element_size == ElementSize::k8 ? static_cast<const void*>(&ct().p_0000000000000000)
: static_cast<const void*>(&ct().p_0100010001000100);
Vec pred = simd_vec_const(pred_data, Bcst::k32, dst_xmm);
if (src.is_mem()) {
cc->emit(insert_inst_id, dst_xmm, pred, src, 0);
cc->vpshufb(dst_xmm, dst_xmm, pred);
}
else {
cc->vpshufb(dst_xmm, src.as<Vec>().xmm(), pred);
}
}
else {
// AVX doesn't have VPBROADCAST[D|Q], but it has VBROADCAST[SS|SD], which do the same. However,
// these cannot be used when the source is a register - initially these instructions only allowed
// broadcasting from memory, then with AVX2 a version that broadcasts from a register was added.
if (src.is_mem()) {
InstId bcst_inst_id = (element_size == ElementSize::k32) ? Inst::kIdVbroadcastss : Inst::kIdVbroadcastsd;
if (dst.is_vec128() && bcst_inst_id == Inst::kIdVbroadcastsd)
bcst_inst_id = Inst::kIdVmovddup;
cc->emit(bcst_inst_id, dst, src.as<Mem>());
return;
}
Vec src_xmm = src.as<Vec>().xmm();
if (element_size == ElementSize::k32)
cc->vpshufd(dst_xmm, src_xmm, x86::shuffle_imm(0, 0, 0, 0));
else
cc->vmovddup(dst_xmm, src_xmm);
}
if (!dst.is_vec128())
cc->emit(Inst::kIdVinsertf128, dst, dst, dst_xmm, 0);
return;
}
// VBROADCASTSD cannot be used when XMM is a destination, in that case we must use VMOVDDUP.
if (dst.is_vec128() && inst_id == Inst::kIdVbroadcastsd)
inst_id = Inst::kIdVmovddup;
if (src.is_mem()) {
Mem m = src.as<Mem>();
m.set_size(1u << op_info.element_size);
cc->emit(inst_id, dst, m);
}
else {
cc->emit(inst_id, dst, src.as<Vec>().xmm());
}
return;
}
case UniOpVV::kBroadcastV128_U32:
case UniOpVV::kBroadcastV128_U64:
case UniOpVV::kBroadcastV128_F32:
case UniOpVV::kBroadcastV128_F64: {
if (src.is_reg()) {
ASMJIT_ASSERT(src.is_vec());
src = src.as<Vec>().xmm();
}
// 128-bit broadcast is like 128-bit mov in this case as we don't have a wider destination.
if (dst.is_vec128()) {
avx_mov(*this, dst, src);
return;
}
// Broadcast instructions only work when the source is a memory operand.
if (src.is_mem()) {
if (!has_avx512()) {
ASMJIT_ASSERT(dst.is_vec256());
inst_id = (op >= UniOpVV::kBroadcastV128_F32 || !has_avx2()) ? Inst::kIdVbroadcastf128 : Inst::kIdVbroadcasti128;
}
cc->emit(inst_id, dst, src);
return;
}
// Broadcast with a register source operand is implemented via insert in AVX/AVX2 case.
if (dst.is_vec256()) {
if (!has_avx512())
inst_id = (op >= UniOpVV::kBroadcastV128_F32 || !has_avx2()) ? Inst::kIdVinsertf128 : Inst::kIdVinserti128;
else
inst_id = avx512_vinsert_128[size_t(op) - size_t(UniOpVV::kBroadcastV128_U32)];
cc->emit(inst_id, dst, src.as<Vec>().ymm(), src, 1);
return;
}
// Broadcast with a register to 512-bits is implemented via 128-bit shuffle.
ASMJIT_ASSERT(dst.is_vec512());
inst_id = avx512_vshuf_128[size_t(op) - size_t(UniOpVV::kBroadcastV128_U32)];
src = src.as<Vec>().zmm();
cc->emit(inst_id, dst, src, src, x86::shuffle_imm(0, 0, 0, 0));
return;
}
case UniOpVV::kBroadcastV256_U32:
case UniOpVV::kBroadcastV256_U64:
case UniOpVV::kBroadcastV256_F32:
case UniOpVV::kBroadcastV256_F64: {
if (src.is_reg()) {
ASMJIT_ASSERT(src.is_vec());
src = src.as<Vec>().ymm();
}
// Cannot broadcast 256-bit vector to a 128-bit or 256-bit vector...
if (!dst.is_vec512()) {
avx_mov(*this, dst.ymm(), src);
return;
}
if (src.is_mem()) {
cc->emit(inst_id, dst, src);
return;
}
inst_id = avx512_vshuf_128[size_t(op) - size_t(UniOpVV::kBroadcastV256_U32)];
src = src.as<Vec>().zmm();
cc->emit(inst_id, dst, src, src, x86::shuffle_imm(1, 0, 1, 0));
return;
}
case UniOpVV::kAbsI64: {
// Native operation requires AVX512, which is not supported by the target.
Vec tmp = new_similar_reg(dst, "@tmp");
cc->vpxor(tmp, tmp, tmp);
cc->emit(Inst::kIdVpsubq, tmp, tmp, src);
cc->emit(Inst::kIdVblendvpd, dst, tmp, src, tmp);
return;
}
case UniOpVV::kNotU32:
case UniOpVV::kNotU64:
case UniOpVV::kNotF32:
case UniOpVV::kNotF64: {
avx_bit_not(*this, dst, src);
return;
}
case UniOpVV::kCvtI8ToI32:
case UniOpVV::kCvtU8ToU32: {
if (src.is_reg())
src.as<Vec>().set_signature(signature_of_xmm_ymm_zmm[0]);
else
src.as<Mem>().set_size(dst.size() / 4u);
cc->emit(inst_id, dst, src);
return;
}
case UniOpVV::kCvtI8HiToI16:
case UniOpVV::kCvtU8HiToU16:
case UniOpVV::kCvtI16HiToI32:
case UniOpVV::kCvtU16HiToU32:
case UniOpVV::kCvtI32HiToI64:
case UniOpVV::kCvtU32HiToU64:
if (src.is_vec()) {
if (dst.is_vec128()) {
Vec tmp = new_vec128("@tmp");
cc->vpshufd(tmp, src.as<Vec>(), x86::shuffle_imm(3, 2, 3, 2));
src = tmp;
}
else if (dst.is_vec256()) {
Vec tmp = new_vec128("@tmp");
cc->vextractf128(tmp, src.as<Vec>().ymm(), 1u);
src = tmp;
}
else if (dst.is_vec512()) {
Vec tmp = new_vec256("@tmp");
cc->vextracti32x8(tmp, src.as<Vec>().zmm(), 1u);
src = tmp;
}
else {
ASMJIT_NOT_REACHED();
}
}
else if (src.is_mem()) {
src.as<Mem>().add_offset(dst.size() / 2u);
}
else {
ASMJIT_NOT_REACHED();
}
[[fallthrough]];
case UniOpVV::kCvtI8LoToI16:
case UniOpVV::kCvtU8LoToU16:
case UniOpVV::kCvtI16LoToI32:
case UniOpVV::kCvtU16LoToU32:
case UniOpVV::kCvtI32LoToI64:
case UniOpVV::kCvtU32LoToU64: {
if (src.is_reg())
src.as<Vec>().set_signature(signature_of_xmm_ymm_zmm[dst.size() >> 6]);
else
src.as<Mem>().set_size(dst.size() / 2u);
cc->emit(inst_id, dst, src);
return;
}
case UniOpVV::kAbsF32S:
case UniOpVV::kAbsF64S:
case UniOpVV::kAbsF32:
case UniOpVV::kAbsF64:
case UniOpVV::kNegF32S:
case UniOpVV::kNegF64S:
case UniOpVV::kNegF32:
case UniOpVV::kNegF64: {
// Intrinsic.
FloatMode fm = FloatMode(op_info.float_mode);
const void* msk_data =
op == UniOpVV::kAbsF32 || op == UniOpVV::kAbsF32S ? static_cast<const void*>(&ct().p_7FFFFFFF7FFFFFFF) :
op == UniOpVV::kAbsF64 || op == UniOpVV::kAbsF64S ? static_cast<const void*>(&ct().p_7FFFFFFFFFFFFFFF) :
op == UniOpVV::kNegF32 || op == UniOpVV::kNegF32S ? static_cast<const void*>(&ct().p_8000000080000000) :
static_cast<const void*>(&ct().p_8000000000000000);
Operand msk = simd_const(msk_data, Bcst(op_info.broadcast_size), dst);
if (src.is_mem() && is_scalar_fp_op(fm)) {
avx_fmov(*this, dst, src, fm);
cc->emit(inst_id, dst, dst, msk);
}
else if (src.is_mem() && msk.is_mem()) {
avx_fmov(*this, dst, msk, fm);
cc->emit(inst_id, dst, dst, src);
}
else if (src.is_mem()) {
cc->emit(inst_id, dst, msk, src);
}
else {
cc->emit(inst_id, dst, src, msk);
}
return;
}
case UniOpVV::kRcpF32: {
// Intrinsic.
Vec one = simd_vec_const(&ct().f32_1, Bcst::k32, dst);
cc->emit(Inst::kIdVdivps, dst, one, src);
return;
}
case UniOpVV::kRcpF64: {
// Intrinsic.
Vec one = simd_vec_const(&ct().f64_1, Bcst::k32, dst);
cc->emit(Inst::kIdVdivpd, dst, one, src);
return;
}
case UniOpVV::kTruncF32S:
case UniOpVV::kTruncF64S:
case UniOpVV::kTruncF32:
case UniOpVV::kTruncF64:
case UniOpVV::kFloorF32S:
case UniOpVV::kFloorF64S:
case UniOpVV::kFloorF32:
case UniOpVV::kFloorF64:
case UniOpVV::kCeilF32S:
case UniOpVV::kCeilF64S:
case UniOpVV::kCeilF32:
case UniOpVV::kCeilF64:
case UniOpVV::kRoundEvenF32S:
case UniOpVV::kRoundEvenF64S:
case UniOpVV::kRoundEvenF32:
case UniOpVV::kRoundEvenF64: {
FloatMode fm = FloatMode(op_info.float_mode);
if (is_scalar_fp_op(fm)) {
dst = dst.xmm();
}
if (has_avx512() && dst.is_vec512()) {
// AVX512 uses a different name.
constexpr uint16_t avx512_rndscale[4] = {
Inst::kIdVrndscaless,
Inst::kIdVrndscalesd,
Inst::kIdVrndscaleps,
Inst::kIdVrndscalepd
};
inst_id = avx512_rndscale[(size_t(op) - size_t(UniOpVV::kTruncF32S)) & 0x3];
}
if (is_scalar_fp_op(fm)) {
// These instructions use 3 operand form for historical reasons.
if (src.is_mem()) {
cc->emit(avx_float_inst[size_t(op_info.float_mode)].fmovs, dst, src);
cc->emit(inst_id, dst, dst, dst, uint32_t(op_info.imm));
}
else {
src = src.as<Vec>().xmm();
cc->emit(inst_id, dst, src, src, uint32_t(op_info.imm));
}
}
else {
cc->emit(inst_id, dst, src, uint32_t(op_info.imm));
}
return;
}
case UniOpVV::kRoundHalfAwayF32S:
case UniOpVV::kRoundHalfAwayF64S:
case UniOpVV::kRoundHalfAwayF32:
case UniOpVV::kRoundHalfAwayF64: {
// Intrinsic.
FloatMode fm = FloatMode(op_info.float_mode);
const FloatInst& fi = avx_float_inst[fm];
if (is_scalar_fp_op(fm)) {
dst = dst.xmm();
if (src.is_vec()) {
src = src.as<Vec>().clone_as(dst);
}
}
if (src.is_mem()) {
avx_fmov(*this, dst, src, fm);
src = dst;
}
Operand half = get_fop_half_minus_1ulp(*this, dst, fm);
Operand msb = get_fop_msb_bit(*this, dst, fm);
Vec tmp = new_similar_reg(dst, "@tmp");
if (has_avx512()) {
cc->emit(fi.fmova, tmp, msb);
cc->emit(Inst::kIdVpternlogd, tmp, src, half, 0xEAu); // tmp = (msb & src) | half
}
else {
cc->emit(fi.fand, tmp, src, msb);
cc->emit(fi.for_, tmp, tmp, half);
}
cc->emit(fi.fadd, dst, src, tmp);
if (is_scalar_fp_op(fm)) {
cc->emit(fi.fround, dst, dst, dst, x86::RoundImm::kTrunc | x86::RoundImm::kSuppress);
}
else {
InstId round_inst = dst.is_vec512() ? fi.frndscale : fi.fround;
cc->emit(round_inst, dst, dst, x86::RoundImm::kTrunc | x86::RoundImm::kSuppress);
}
return;
}
case UniOpVV::kRoundHalfUpF32S:
case UniOpVV::kRoundHalfUpF64S:
case UniOpVV::kRoundHalfUpF32:
case UniOpVV::kRoundHalfUpF64: {
// Intrinsic.
FloatMode fm = FloatMode(op_info.float_mode);
if (is_scalar_fp_op(fm)) {
dst = dst.xmm();
}
Operand half = get_fop_half_minus_1ulp(*this, dst, fm);
UniOpVVV add_op = translate_op(op, UniOpVV::kRoundHalfUpF32S, UniOpVVV::kAddF32S);
UniOpVV floor_op = translate_op(op, UniOpVV::kRoundHalfUpF32S, UniOpVV::kFloorF32S);
if (src.is_mem()) {
Vec tmp = new_similar_reg(dst, "@tmp");
avx_fmov(*this, tmp, src, fm);
emit_3v(add_op, tmp, tmp, half);
emit_2v(floor_op, dst, tmp);
}
else {
emit_3v(add_op, dst, src.as<Vec>().clone_as(dst), half);
emit_2v(floor_op, dst, dst);
}
return;
}
case UniOpVV::kSqrtF32S:
case UniOpVV::kSqrtF64S: {
dst = dst.xmm();
// Intrinsic - these instructions use 3 operand form for historical reasons.
if (src.is_mem()) {
avx_fmov(*this, dst, src, FloatMode(op_info.float_mode));
cc->emit(inst_id, dst, dst, dst);
}
else {
src = src.as<Vec>().xmm();
cc->emit(inst_id, dst, src, src);
}
return;
}
case UniOpVV::kCvtF32ToF64S:
case UniOpVV::kCvtF64ToF32S: {
dst = dst.xmm();
if (src.is_vec())
src = src.as<Vec>().xmm();
// Intrinsic - these instructions use 3 operand form for historical reasons.
Vec zeros = simd_vec_const(&ct().p_0000000000000000, Bcst::k32, dst);
cc->emit(inst_id, dst, zeros, src);
return;
}
case UniOpVV::kCvtF32LoToF64:
case UniOpVV::kCvtI32LoToF64: {
// Intrinsic - widening conversion - low part conversions are native, high part emulated.
if (src.is_reg()) {
uint32_t w = dst.size() >> 6;
src.set_signature(signature_of_xmm_ymm_zmm[w]);
}
else {
uint32_t w = dst.size() >> 4;
src.as<Mem>().set_size(w * 8u);
}
cc->emit(inst_id, dst, src);
return;
}
case UniOpVV::kCvtF32HiToF64:
case UniOpVV::kCvtI32HiToF64: {
if (src.is_reg()) {
uint32_t w = dst.size() >> 6;
Vec tmp = new_vec_with_width(VecWidth(w), "@tmp");
src.set_signature(signature_of_xmm_ymm_zmm[w]);
if (dst.is_vec512()) {
cc->vextracti32x8(tmp, src.as<Vec>().zmm(), 1u);
cc->emit(inst_id, dst, tmp);
}
else if (dst.is_vec256()) {
if (has_avx512())
cc->vextracti32x4(tmp, src.as<Vec>().ymm(), 1u);
else
cc->vextracti128(tmp, src.as<Vec>().ymm(), 1u);
cc->emit(inst_id, dst, tmp);
}
else {
cc->vpshufd(tmp, src.as<Vec>(), x86::shuffle_imm(3, 2, 3, 2));
cc->emit(inst_id, dst, tmp);
}
}
else {
uint32_t w = dst.size() >> 4;
src.as<Mem>().set_size(w * 8u);
src.as<Mem>().add_offset(w * 8u);
cc->emit(inst_id, dst, src);
}
return;
}
case UniOpVV::kCvtF64ToF32Lo:
case UniOpVV::kCvtTruncF64ToI32Lo:
case UniOpVV::kCvtRoundF64ToI32Lo: {
// Intrinsic - narrowing conversion - low part conversions are native, high part emulated.
uint32_t dst_size = Support::max(dst.size() / 2u, src.x86_rm_size());
uint32_t w = dst_size >> 5;
dst.set_signature(signature_of_xmm_ymm_zmm[w ? w - 1u : 0u]);
if (src.is_reg())
src.set_signature(signature_of_xmm_ymm_zmm[w]);
else if (src.x86_rm_size() == 0)
src.as<Mem>().set_size(w * 32u);
cc->emit(inst_id, dst, src);
return;
}
case UniOpVV::kCvtF64ToF32Hi:
case UniOpVV::kCvtTruncF64ToI32Hi:
case UniOpVV::kCvtRoundF64ToI32Hi: {
uint32_t w = dst.size() >> 6;
Vec tmp = new_vec_with_width(VecWidth(w), "@tmp");
if (src.is_mem())
src.as<Mem>().set_size(dst.size());
cc->emit(inst_id, tmp, src);
if (dst.is_vec512())
cc->vinserti32x8(dst, dst, tmp.ymm(), 1);
else if (dst.is_vec256())
cc->vinserti128(dst, dst, tmp.xmm(), 1);
else
cc->vunpcklpd(dst, dst, tmp);
return;
}
default:
ASMJIT_NOT_REACHED();
}
}
else {
// SSE Implementation
// ------------------
InstId inst_id = op_info.sse_inst_id;
if (has_sse_ext(SSEExt(op_info.sse_ext))) {
ASMJIT_ASSERT(inst_id != Inst::kIdNone);
if (op_info.use_imm)
cc->emit(inst_id, dst, src, Imm(op_info.imm));
else
cc->emit(inst_id, dst, src);
return;
}
switch (op) {
case UniOpVV::kMov: {
cc->emit(Inst::kIdMovaps, dst, src);
return;
}
case UniOpVV::kMovU64: {
cc->emit(Inst::kIdMovq, dst, src);
return;
}
case UniOpVV::kBroadcastU8Z:
case UniOpVV::kBroadcastU16Z:
case UniOpVV::kBroadcastU8:
case UniOpVV::kBroadcastU16: {
// Intrinsic - 8/16-bit broadcasts are generally not available in SSE mode - we have to emulate.
ASMJIT_ASSERT(src.is_reg() || src.is_mem());
ElementSize element_size = ElementSize(op_info.element_size);
if (src.is_mem() || src.is_gp()) {
Gp tmp = new_gp32("@tmp");
uint32_t mul_by = element_size == ElementSize::k8 ? 0x01010101u : 0x00010001u;
if (src.is_mem()) {
src.as<Mem>().set_size(element_size == ElementSize::k8 ? 1 : 2);
cc->movzx(tmp, src.as<Mem>());
cc->imul(tmp, tmp, mul_by);
}
else if (op_info.imm == 0x01) {
// OPTIMIZATION: If it's guaranteed that the unused part of the register is zero, we can imul without zero extending.
cc->imul(tmp, src.as<Gp>().r32(), mul_by);
}
else {
OperandSignature src_signature = OperandSignature{
element_size == ElementSize::k8 ? RegTraits<RegType::kGp8Lo>::kSignature : RegTraits<RegType::kGp16>::kSignature};
src.as<Gp>().set_signature(src_signature);
cc->movzx(tmp, src.as<Gp>());
cc->imul(tmp, tmp, mul_by);
}
cc->emit(Inst::kIdMovd, dst, tmp);
cc->emit(Inst::kIdPshufd, dst, dst, x86::shuffle_imm(0, 0, 0, 0));
return;
}
ASMJIT_ASSERT(src.is_vec());
if (has_ssse3()) {
if (element_size == ElementSize::k8 || (element_size == ElementSize::k16 && is_same_vec(dst, src))) {
Operand predicate = element_size == ElementSize::k8 ? simd_const(&ct().p_0000000000000000, Bcst::kNA, dst.as<Vec>())
: simd_const(&ct().p_0100010001000100, Bcst::kNA, dst.as<Vec>());
sse_mov(*this, dst, src);
cc->emit(Inst::kIdPshufb, dst, predicate);
return;
}
}
if (element_size == ElementSize::k8) {
sse_mov(*this, dst, src);
cc->emit(Inst::kIdPunpcklbw, dst, dst);
src = dst;
}
cc->emit(Inst::kIdPshuflw, dst, src, x86::shuffle_imm(0, 0, 0, 0));
cc->emit(Inst::kIdPshufd, dst, dst, x86::shuffle_imm(0, 0, 0, 0));
return;
}
case UniOpVV::kBroadcastU32:
case UniOpVV::kBroadcastF32: {
// Intrinsic - 32-bit broadcast is generally not available in SSE mode - we have to emulate.
ASMJIT_ASSERT(src.is_reg() || src.is_mem());
if (src.is_gp()) {
cc->emit(Inst::kIdMovd, dst, src.as<Gp>().r32());
src = dst;
}
if (src.is_reg()) {
cc->emit(Inst::kIdPshufd, dst, src, x86::shuffle_imm(0, 0, 0, 0));
}
else {
cc->emit(Inst::kIdMovd, dst, src);
cc->emit(Inst::kIdPshufd, dst, dst, x86::shuffle_imm(0, 0, 0, 0));
}
return;
}
case UniOpVV::kBroadcastU64:
case UniOpVV::kBroadcastF64: {
// Intrinsic - 64-bit broadcast is generally not available in SSE mode - we have to emulate.
ASMJIT_ASSERT(src.is_reg() || src.is_mem());
if (src.is_gp()) {
cc->emit(Inst::kIdMovq, dst, src.as<Gp>().r64());
src = dst;
}
if (has_sse3()) {
cc->emit(Inst::kIdMovddup, dst, src);
}
else if (src.is_reg()) {
cc->emit(Inst::kIdPshufd, dst, src, x86::shuffle_imm(1, 0, 1, 0));
}
else {
cc->emit(Inst::kIdMovq, dst, src);
cc->emit(Inst::kIdPshufd, dst, dst, x86::shuffle_imm(1, 0, 1, 0));
}
return;
}
case UniOpVV::kBroadcastV128_U32:
case UniOpVV::kBroadcastV128_U64:
case UniOpVV::kBroadcastV128_F32:
case UniOpVV::kBroadcastV128_F64: {
// 128-bit broadcast is like 128-bit mov in this case as we don't have wider vectors.
sse_mov(*this, dst, src);
return;
}
case UniOpVV::kAbsI8: {
// Native operation requires SSSE3, which is not supported by the target.
if (is_same_vec(dst, src)) {
Vec tmp = new_similar_reg(dst, "@tmp");
cc->emit(Inst::kIdPxor, tmp, tmp);
cc->emit(Inst::kIdPsubb, tmp, dst);
cc->emit(Inst::kIdPminub, dst, tmp);
}
else {
cc->emit(Inst::kIdPxor, dst, dst);
cc->emit(Inst::kIdPsubb, dst, src);
cc->emit(Inst::kIdPminub, dst, src);
}
return;
}
case UniOpVV::kAbsI16: {
// Native operation requires SSSE3, which is not supported by the target.
if (is_same_vec(dst, src)) {
Vec tmp = new_similar_reg(dst, "@tmp");
cc->emit(Inst::kIdPxor, tmp, tmp);
cc->emit(Inst::kIdPsubw, tmp, dst);
cc->emit(Inst::kIdPmaxsw, dst, tmp);
}
else {
cc->emit(Inst::kIdPxor, dst, dst);
cc->emit(Inst::kIdPsubw, dst, src);
cc->emit(Inst::kIdPmaxsw, dst, src);
}
return;
}
case UniOpVV::kAbsI32: {
// Native operation requires SSSE3, which is not supported by the target.
Vec tmp = new_similar_reg(dst, "@tmp");
cc->emit(Inst::kIdMovaps, tmp, src);
cc->emit(Inst::kIdPsrad, tmp, 31);
sse_mov(*this, dst, src);
cc->emit(Inst::kIdPxor, dst, tmp);
cc->emit(Inst::kIdPsubd, dst, tmp);
return;
}
case UniOpVV::kAbsI64: {
// Native operation requires AVX512, which is not supported by the target.
Vec tmp = new_similar_reg(dst, "@tmp");
cc->emit(Inst::kIdPshufd, tmp, src, x86::shuffle_imm(3, 3, 1, 1));
cc->emit(Inst::kIdPsrad, tmp, 31);
sse_mov(*this, dst, src);
cc->emit(Inst::kIdPxor, dst, tmp);
cc->emit(Inst::kIdPsubq, dst, tmp);
return;
}
case UniOpVV::kNotU32:
case UniOpVV::kNotU64:
case UniOpVV::kNotF32:
case UniOpVV::kNotF64: {
sse_bit_not(*this, dst, src);
return;
}
case UniOpVV::kCvtI8ToI32:
case UniOpVV::kCvtU8ToU32: {
if (src.is_mem())
src.as<Mem>().set_size(4u);
if (has_sse4_1()) {
cc->emit(inst_id, dst, src);
return;
}
if (src.is_mem()) {
cc->movd(dst, src.as<x86::Mem>());
src = dst;
}
WideningOp cvt = (op == UniOpVV::kCvtI8ToI32) ? WideningOp::kI8ToI32 : WideningOp::kU8ToU32;
sse_int_widen(*this, dst, src.as<Vec>(), cvt);
return;
}
case UniOpVV::kCvtU8HiToU16:
case UniOpVV::kCvtU16HiToU32:
case UniOpVV::kCvtU32HiToU64:
if (src.is_vec() && dst.id() != src.id() && has_sse4_1()) {
cc->pshufd(dst, src.as<Vec>(), x86::shuffle_imm(3, 2, 3, 2));
cc->emit(inst_id, dst, dst);
return;
}
[[fallthrough]];
case UniOpVV::kCvtI8HiToI16:
case UniOpVV::kCvtI16HiToI32:
case UniOpVV::kCvtI32HiToI64:
if (src.is_vec()) {
sse_mov(*this, dst, src);
switch (op) {
case UniOpVV::kCvtI8HiToI16: {
cc->punpckhbw(dst, dst);
cc->psraw(dst, 8);
break;
}
case UniOpVV::kCvtU8HiToU16: {
cc->emit(Inst::kIdPunpckhbw, dst, simd_const(&ct().p_0000000000000000, Bcst::kNA, dst));
break;
}
case UniOpVV::kCvtI16HiToI32: {
cc->punpckhwd(dst, dst);
cc->psrad(dst, 16);
break;
}
case UniOpVV::kCvtU16HiToU32: {
cc->emit(Inst::kIdPunpckhwd, dst, simd_const(&ct().p_0000000000000000, Bcst::kNA, dst));
break;
}
case UniOpVV::kCvtI32HiToI64: {
Vec tmp = new_vec128("@tmp");
sse_mov(*this, tmp, dst);
cc->psrad(tmp, 31);
cc->punpckhdq(dst, tmp);
break;
}
case UniOpVV::kCvtU32HiToU64: {
cc->emit(Inst::kIdPunpckhdq, dst, simd_const(&ct().p_0000000000000000, Bcst::kNA, dst));
break;
}
default:
ASMJIT_NOT_REACHED();
}
return;
}
else if (src.is_mem()) {
src.as<Mem>().add_offset(8u);
op = UniOpVV(uint32_t(op) - 1);
}
else {
ASMJIT_NOT_REACHED();
}
[[fallthrough]];
case UniOpVV::kCvtI8LoToI16:
case UniOpVV::kCvtU8LoToU16:
case UniOpVV::kCvtI16LoToI32:
case UniOpVV::kCvtU16LoToU32:
case UniOpVV::kCvtI32LoToI64:
case UniOpVV::kCvtU32LoToU64: {
if (src.is_mem())
src.as<Mem>().set_size(8u);
if (has_sse4_1()) {
cc->emit(inst_id, dst, src);
return;
}
if (src.is_mem()) {
cc->movq(dst, src.as<x86::Mem>());
src = dst;
}
WideningOp cvt {};
switch (op) {
case UniOpVV::kCvtI8LoToI16 : cvt = WideningOp::kI8ToI16; break;
case UniOpVV::kCvtU8LoToU16 : cvt = WideningOp::kU8ToU16; break;
case UniOpVV::kCvtI16LoToI32: cvt = WideningOp::kI16ToI32; break;
case UniOpVV::kCvtU16LoToU32: cvt = WideningOp::kU16ToU32; break;
case UniOpVV::kCvtI32LoToI64: cvt = WideningOp::kI32ToI64; break;
case UniOpVV::kCvtU32LoToU64: cvt = WideningOp::kU32ToU64; break;
default:
ASMJIT_NOT_REACHED();
}
sse_int_widen(*this, dst, src.as<Vec>(), cvt);
return;
}
case UniOpVV::kTruncF32:
case UniOpVV::kTruncF64:
case UniOpVV::kFloorF32:
case UniOpVV::kFloorF64:
case UniOpVV::kCeilF32:
case UniOpVV::kCeilF64:
case UniOpVV::kRoundEvenF32:
case UniOpVV::kRoundEvenF64:
// Native operation requires SSE4.1.
if (has_sse4_1()) {
cc->emit(inst_id, dst, src, Imm(op_info.imm));
return;
}
[[fallthrough]];
case UniOpVV::kTruncF32S:
case UniOpVV::kTruncF64S:
case UniOpVV::kFloorF32S:
case UniOpVV::kFloorF64S:
case UniOpVV::kCeilF32S:
case UniOpVV::kCeilF64S:
case UniOpVV::kRoundEvenF32S:
case UniOpVV::kRoundEvenF64S: {
// Native operation requires SSE4.1.
if (has_sse4_1()) {
sse_fmov(*this, dst, src, FloatMode(op_info.float_mode));
cc->emit(inst_id, dst, dst, Imm(op_info.imm));
return;
}
sse_round(*this, dst, src, FloatMode(op_info.float_mode), x86::RoundImm(op_info.imm & 0x7));
return;
}
case UniOpVV::kRoundHalfAwayF32S:
case UniOpVV::kRoundHalfAwayF64S:
case UniOpVV::kRoundHalfAwayF32:
case UniOpVV::kRoundHalfAwayF64: {
// Intrinsic.
FloatMode fm = FloatMode(op_info.float_mode);
const FloatInst& fi = sse_float_inst[fm];
Operand half = get_fop_half_minus_1ulp(*this, dst, fm);
Operand msb = get_fop_msb_bit(*this, dst, fm);
Vec tmp = new_similar_reg(dst, "@tmp");
sse_fmov(*this, dst, src, fm);
sse_mov(*this, tmp, msb);
cc->emit(fi.fand, tmp, dst);
cc->emit(fi.for_, tmp, half);
cc->emit(fi.fadd, dst, tmp);
sse_round(*this, dst, dst, fm, x86::RoundImm(op_info.imm & 0x7));
return;
}
case UniOpVV::kRoundHalfUpF32S:
case UniOpVV::kRoundHalfUpF64S:
case UniOpVV::kRoundHalfUpF32:
case UniOpVV::kRoundHalfUpF64: {
// Intrinsic.
FloatMode fm = FloatMode(op_info.float_mode);
const FloatInst& fi = sse_float_inst[fm];
Operand half = get_fop_half_minus_1ulp(*this, dst, fm);
sse_fmov(*this, dst, src, fm);
cc->emit(fi.fadd, dst, half);
sse_round(*this, dst, dst, fm, x86::RoundImm(op_info.imm & 0x7));
return;
}
case UniOpVV::kAbsF32S:
case UniOpVV::kAbsF64S:
case UniOpVV::kAbsF32:
case UniOpVV::kAbsF64:
case UniOpVV::kNegF32S:
case UniOpVV::kNegF64S:
case UniOpVV::kNegF32:
case UniOpVV::kNegF64: {
// Intrinsic.
FloatMode fm = FloatMode(op_info.float_mode);
const void* msk_data =
op == UniOpVV::kAbsF32 || op == UniOpVV::kAbsF32S ? static_cast<const void*>(&ct().p_7FFFFFFF7FFFFFFF) :
op == UniOpVV::kAbsF64 || op == UniOpVV::kAbsF64S ? static_cast<const void*>(&ct().p_7FFFFFFFFFFFFFFF) :
op == UniOpVV::kNegF32 || op == UniOpVV::kNegF32S ? static_cast<const void*>(&ct().p_8000000080000000) :
static_cast<const void*>(&ct().p_8000000000000000);
Operand msk = simd_const(msk_data, Bcst(op_info.broadcast_size), dst);
sse_fmov(*this, dst, src, fm);
cc->emit(inst_id, dst, msk);
return;
}
case UniOpVV::kRcpF32: {
Operand one = simd_const(&ct().f32_1, Bcst::k32, dst);
if (is_same_vec(dst, src)) {
Vec tmp = new_similar_reg(dst, "@tmp");
sse_mov(*this, tmp, one);
cc->emit(Inst::kIdDivps, tmp, src);
sse_mov(*this, dst, tmp);
}
else {
sse_mov(*this, dst, one);
cc->emit(Inst::kIdDivps, dst, src);
}
return;
}
case UniOpVV::kRcpF64: {
Operand one = simd_const(&ct().f64_1, Bcst::k64, dst);
if (is_same_vec(dst, src)) {
Vec tmp = new_similar_reg(dst, "@tmp");
sse_mov(*this, tmp, one);
cc->emit(Inst::kIdDivpd, tmp, src);
sse_mov(*this, dst, tmp);
}
else {
sse_mov(*this, dst, one);
cc->emit(Inst::kIdDivpd, dst, src);
}
return;
}
case UniOpVV::kSqrtF32S:
case UniOpVV::kSqrtF64S: {
sse_mov(*this, dst, src);
cc->emit(inst_id, dst, dst);
return;
}
case UniOpVV::kCvtF32ToF64S:
case UniOpVV::kCvtF64ToF32S: {
if (is_same_vec(dst, src)) {
cc->emit(inst_id, dst, src);
}
else {
cc->emit(Inst::kIdXorps, dst, dst);
cc->emit(inst_id, dst, src);
}
return;
}
case UniOpVV::kCvtF32HiToF64:
case UniOpVV::kCvtI32HiToF64: {
if (src.is_mem()) {
Mem mem(src.as<Mem>());
mem.add_offset(8);
cc->emit(inst_id, dst, mem);
}
else {
if (is_same_vec(dst, src))
cc->emit(Inst::kIdMovhlps, dst, src);
else
cc->emit(Inst::kIdPshufd, dst, src, x86::shuffle_imm(3, 2, 3, 2));
cc->emit(inst_id, dst, dst);
}
return;
}
case UniOpVV::kCvtF64ToF32Hi:
case UniOpVV::kCvtTruncF64ToI32Hi:
case UniOpVV::kCvtRoundF64ToI32Hi: {
Vec tmp = new_vec128("@tmp");
if (src.is_mem())
src.as<Mem>().set_size(dst.size());
cc->emit(inst_id, tmp, src);
cc->emit(Inst::kIdUnpcklpd, dst, tmp);
return;
}
default:
ASMJIT_NOT_REACHED();
}
}
}
void UniCompiler::emit_2v(UniOpVV op, const OpArray& dst_, const Operand_& src_) { emit_2v_t(*this, op, dst_, src_); }
void UniCompiler::emit_2v(UniOpVV op, const OpArray& dst_, const OpArray& src_) { emit_2v_t(*this, op, dst_, src_); }
// ujit::UniCompiler - Vector Instructions - Emit 2VI
// ==================================================
void UniCompiler::emit_2vi(UniOpVVI op, const Operand_& dst_, const Operand_& src_, uint32_t imm) {
ASMJIT_ASSERT(dst_.is_vec());
Vec dst(dst_.as<Vec>());
Operand src(src_);
UniOpVInfo op_info = opcode_info_2vi[size_t(op)];
if (has_avx()) {
// AVX Implementation
// ------------------
InstId inst_id = op_info.avx_inst_id;
if (has_avx_ext(AVXExt(op_info.avx_ext))) {
ASMJIT_ASSERT(inst_id != Inst::kIdNone);
cc->emit(inst_id, dst, src, Imm(imm));
return;
}
switch (op) {
case UniOpVVI::kSllU16:
case UniOpVVI::kSllU32:
case UniOpVVI::kSllU64:
case UniOpVVI::kSrlU16:
case UniOpVVI::kSrlU32:
case UniOpVVI::kSrlU64:
case UniOpVVI::kSraI16:
case UniOpVVI::kSraI32:
case UniOpVVI::kSllbU128:
case UniOpVVI::kSrlbU128: {
// This instruction requires AVX-512 if the source is a memory operand.
if (src.is_mem()) {
avx_mov(*this, dst, src);
cc->emit(inst_id, dst, dst, imm);
}
else {
cc->emit(inst_id, dst, src, imm);
}
return;
}
case UniOpVVI::kSraI64: {
// Native operation requires AVX-512, which is not supported by the target.
if (imm == 0) {
avx_mov(*this, dst, src);
return;
}
if (imm == 63) {
cc->emit(Inst::kIdVpshufd, dst, src, x86::shuffle_imm(3, 3, 1, 1));
cc->emit(Inst::kIdVpsrad, dst, dst, 31);
return;
}
Vec tmp = new_similar_reg(dst, "@tmp");
if (src.is_mem()) {
avx_mov(*this, dst, src);
src = dst;
}
if (imm <= 32) {
cc->emit(Inst::kIdVpsrad, tmp, src, Support::min<uint32_t>(imm, 31u));
cc->emit(Inst::kIdVpsrlq, dst, src, imm);
cc->emit(Inst::kIdVpblendw, dst, dst, tmp, 0xCC);
return;
}
cc->emit(Inst::kIdVpshufd, tmp, src, x86::shuffle_imm(3, 3, 1, 1));
cc->emit(Inst::kIdVpsrad, tmp, tmp, 31);
cc->emit(Inst::kIdVpsrlq, dst, src, imm);
cc->emit(Inst::kIdVpsllq, tmp, tmp, 64u - imm);
cc->emit(Inst::kIdVpor, dst, dst, tmp);
return;
}
case UniOpVVI::kSwizzleU16x4: {
// Intrinsic.
// TODO: [JIT] OPTIMIZATION: Use VPSHUFB instead where appropriate.
uint32_t shuf_imm = shuf_imm4_from_swizzle(Swizzle4{imm});
cc->emit(Inst::kIdVpshuflw, dst, src, shuf_imm);
cc->emit(Inst::kIdVpshufhw, dst, dst, shuf_imm);
return;
}
case UniOpVVI::kSwizzleLoU16x4:
case UniOpVVI::kSwizzleHiU16x4:
case UniOpVVI::kSwizzleU32x4: {
// Intrinsic (AVX | AVX512).
ASMJIT_ASSERT(inst_id != Inst::kIdNone);
uint32_t shuf_imm = shuf_imm4_from_swizzle(Swizzle4{imm});
cc->emit(inst_id, dst, src, shuf_imm);
return;
}
case UniOpVVI::kSwizzleU64x2: {
// Intrinsic (AVX | AVX512).
if (Swizzle2{imm} == swizzle(0, 0)) {
cc->emit(Inst::kIdVmovddup, dst, src);
}
else if (Swizzle2{imm} == swizzle(0, 0) && src.is_reg()) {
cc->emit(Inst::kIdVpunpcklqdq, dst, src, src);
}
else if (Swizzle2{imm} == swizzle(1, 1) && src.is_reg()) {
cc->emit(Inst::kIdVpunpckhqdq, dst, src, src);
}
else {
uint32_t shuf_imm = shuf_imm4_from_swizzle(Swizzle2{imm});
cc->emit(Inst::kIdVpshufd, dst, src, shuf_imm);
}
return;
}
case UniOpVVI::kSwizzleF32x4: {
// Intrinsic (AVX | AVX512).
uint32_t shuf_imm = shuf_imm4_from_swizzle(Swizzle4{imm});
if (src.is_reg())
cc->emit(Inst::kIdVshufps, dst, src, src, shuf_imm);
else
cc->emit(Inst::kIdVpshufd, dst, src, shuf_imm);
return;
}
case UniOpVVI::kSwizzleF64x2: {
// Intrinsic (AVX | AVX512).
if (Swizzle2{imm} == swizzle(0, 0) && !dst.is_vec512()) {
cc->emit(Inst::kIdVmovddup, dst, src);
}
else if (Swizzle2{imm} == swizzle(0, 0) && src.is_reg()) {
cc->emit(Inst::kIdVunpcklpd, dst, src, src);
}
else if (Swizzle2{imm} == swizzle(1, 1) && src.is_reg()) {
cc->emit(Inst::kIdVunpckhpd, dst, src, src);
}
else if (src.is_reg()) {
uint32_t shuf_imm = shuf_imm2_from_swizzle_with_width(Swizzle2{imm}, VecWidthUtils::vec_width_of(dst));
cc->emit(Inst::kIdVshufpd, dst, src, src, shuf_imm);
}
else {
uint32_t shuf_imm = shuf_imm4_from_swizzle(Swizzle2{imm});
cc->emit(Inst::kIdVpshufd, dst, src, shuf_imm);
}
return;
}
case UniOpVVI::kSwizzleF64x4:
case UniOpVVI::kSwizzleU64x4: {
uint32_t shuf_imm = shuf_imm4_from_swizzle(Swizzle4{imm});
cc->emit(op_info.avx_inst_id, dst, src, shuf_imm);
return;
}
case UniOpVVI::kExtractV128_I32:
case UniOpVVI::kExtractV128_I64:
case UniOpVVI::kExtractV128_F32:
case UniOpVVI::kExtractV128_F64: {
// Intrinsic (AVX | AVX512).
ASMJIT_ASSERT(imm < 4);
dst.set_signature(signature_of_xmm_ymm_zmm[0]);
if (src.is_mem()) {
src.as<Mem>().add_offset(imm * 16u);
v_loadu128(dst, src.as<x86::Mem>());
return;
}
if (src.as<Vec>().is_vec512()) {
ASMJIT_ASSERT(imm < 4);
cc->vextracti32x4(dst, src.as<Vec>(), imm);
}
else if (src.as<Vec>().is_vec256()) {
ASMJIT_ASSERT(imm < 2);
cc->vextractf128(dst, src.as<Vec>(), imm);
}
else {
ASMJIT_NOT_REACHED();
}
return;
}
case UniOpVVI::kExtractV256_I32:
case UniOpVVI::kExtractV256_I64:
case UniOpVVI::kExtractV256_F32:
case UniOpVVI::kExtractV256_F64: {
// Intrinsic (AVX | AVX512).
ASMJIT_ASSERT(imm < 2);
dst.set_signature(signature_of_xmm_ymm_zmm[1]);
if (src.is_mem()) {
src.as<Mem>().add_offset(imm * 32u);
v_loadu256(dst, src.as<Mem>());
return;
}
ASMJIT_ASSERT(src.as<Vec>().is_vec512());
cc->vextracti32x8(dst, src.as<Vec>(), imm);
return;
}
default:
ASMJIT_NOT_REACHED();
}
}
else {
// SSE Implementation
// ------------------
InstId inst_id = op_info.sse_inst_id;
if (has_sse_ext(SSEExt(op_info.sse_ext))) {
ASMJIT_ASSERT(inst_id != Inst::kIdNone);
if (op_info.sse_op_count == 2) {
sse_mov(*this, dst, src);
cc->emit(inst_id, dst, imm);
return;
}
else if (op_info.sse_op_count == 3) {
cc->emit(inst_id, dst, src, imm);
return;
}
ASMJIT_NOT_REACHED();
}
switch (op) {
case UniOpVVI::kSraI64: {
// Intrinsic (SSE2).
if (imm == 0) {
sse_mov(*this, dst, src);
return;
}
if (imm == 63) {
cc->emit(Inst::kIdPshufd, dst, src, x86::shuffle_imm(3, 3, 1, 1));
cc->emit(Inst::kIdPsrad, dst, 31);
return;
}
Vec tmp = new_similar_reg(dst, "@tmp");
if (imm <= 32 && has_sse4_1()) {
sse_mov(*this, dst, src);
sse_mov(*this, tmp, src.is_reg() ? src.as<Vec>() : dst);
cc->emit(Inst::kIdPsrad, tmp, Support::min<uint32_t>(imm, 31u));
cc->emit(Inst::kIdPsrlq, dst, imm);
cc->emit(Inst::kIdPblendw, dst, tmp, 0xCC);
return;
}
sse_mov(*this, dst, src);
cc->emit(Inst::kIdPshufd, tmp, src.is_reg() ? src.as<Vec>() : dst, x86::shuffle_imm(3, 3, 1, 1));
cc->emit(Inst::kIdPsrad, tmp, 31);
cc->emit(Inst::kIdPsrlq, dst, imm);
cc->emit(Inst::kIdPsllq, tmp, 64u - imm);
cc->emit(Inst::kIdPor, dst, tmp);
return;
}
case UniOpVVI::kSwizzleU16x4: {
// Intrinsic (SSE2).
// TODO: [JIT] OPTIMIZATION: Use VPSHUFB instead where appropriate.
uint32_t shuf_imm = shuf_imm4_from_swizzle(Swizzle4{imm});
cc->emit(Inst::kIdPshuflw, dst, src, shuf_imm);
cc->emit(Inst::kIdPshufhw, dst, dst, shuf_imm);
return;
}
case UniOpVVI::kSwizzleLoU16x4:
case UniOpVVI::kSwizzleHiU16x4:
case UniOpVVI::kSwizzleU32x4: {
// Intrinsic (SSE2).
ASMJIT_ASSERT(inst_id != Inst::kIdNone);
uint32_t shuf_imm = shuf_imm4_from_swizzle(Swizzle4{imm});
cc->emit(inst_id, dst, src, shuf_imm);
return;
}
case UniOpVVI::kSwizzleU64x2: {
// Intrinsic (SSE2 | SSE3).
if (Swizzle2{imm} == swizzle(1, 0)) {
sse_mov(*this, dst, src);
}
else if (Swizzle2{imm} == swizzle(0, 0) && has_sse3()) {
cc->emit(Inst::kIdMovddup, dst, src);
}
else if (Swizzle2{imm} == swizzle(0, 0) && is_same_vec(dst, src)) {
cc->emit(Inst::kIdPunpcklqdq, dst, src);
}
else if (Swizzle2{imm} == swizzle(1, 1) && is_same_vec(dst, src)) {
cc->emit(Inst::kIdPunpckhqdq, dst, src);
}
else {
uint32_t shuf_imm = shuf_imm4_from_swizzle(Swizzle2{imm});
cc->emit(Inst::kIdPshufd, dst, src, shuf_imm);
}
return;
}
case UniOpVVI::kSwizzleF32x4: {
// Intrinsic (SSE2).
uint32_t shuf_imm = shuf_imm4_from_swizzle(Swizzle4{imm});
if (is_same_vec(dst, src))
cc->emit(Inst::kIdShufps, dst, dst, shuf_imm);
else
cc->emit(Inst::kIdPshufd, dst, src, shuf_imm);
return;
}
case UniOpVVI::kSwizzleF64x2: {
// Intrinsic (SSE2 | SSE3).
if (Swizzle2{imm} == swizzle(1, 0)) {
sse_mov(*this, dst, src);
}
else if (Swizzle2{imm} == swizzle(0, 0) && has_sse3()) {
cc->emit(Inst::kIdMovddup, dst, src);
}
else if (Swizzle2{imm} == swizzle(0, 0) && is_same_vec(dst, src)) {
cc->emit(Inst::kIdUnpcklpd, dst, src);
}
else if (Swizzle2{imm} == swizzle(1, 1) && is_same_vec(dst, src)) {
cc->emit(Inst::kIdUnpckhpd, dst, src);
}
else if (is_same_vec(dst, src)) {
uint32_t shuf_imm = shuf_imm2_from_swizzle(Swizzle2{imm});
cc->emit(Inst::kIdShufpd, dst, dst, shuf_imm);
}
else {
uint32_t shuf_imm = shuf_imm4_from_swizzle(Swizzle2{imm});
cc->emit(Inst::kIdPshufd, dst, src, shuf_imm);
}
return;
}
case UniOpVVI::kSwizzleF64x4:
case UniOpVVI::kSwizzleU64x4:
case UniOpVVI::kExtractV128_I32:
case UniOpVVI::kExtractV128_I64:
case UniOpVVI::kExtractV128_F32:
case UniOpVVI::kExtractV128_F64:
case UniOpVVI::kExtractV256_I32:
case UniOpVVI::kExtractV256_I64:
case UniOpVVI::kExtractV256_F32:
case UniOpVVI::kExtractV256_F64:
// Not supported in SSE mode.
ASMJIT_NOT_REACHED();
default:
ASMJIT_NOT_REACHED();
}
}
}
void UniCompiler::emit_2vi(UniOpVVI op, const OpArray& dst_, const Operand_& src_, uint32_t imm) { emit_2vi_t(*this, op, dst_, src_, imm); }
void UniCompiler::emit_2vi(UniOpVVI op, const OpArray& dst_, const OpArray& src_, uint32_t imm) { emit_2vi_t(*this, op, dst_, src_, imm); }
// ujit::UniCompiler - Vector Instructions - Emit 2VS
// ==================================================
void UniCompiler::emit_2vs(UniOpVR op, const Operand_& dst_, const Operand_& src_, uint32_t idx) {
UniOpVInfo op_info = opcode_info_2vs[size_t(op)];
Operand src(src_);
Operand dst(dst_);
if (has_avx()) {
// AVX Implementation
// ------------------
switch (op) {
case UniOpVR::kMov: {
ASMJIT_ASSERT(dst.is_reg());
ASMJIT_ASSERT(src.is_reg());
if (dst.is_gp() && src.is_vec()) {
if (dst.as<Reg>().size() <= 4)
cc->emit(Inst::kIdVmovd, dst.as<Gp>().r32(), src.as<Vec>().xmm());
else
cc->emit(Inst::kIdVmovq, dst.as<Gp>().r64(), src.as<Vec>().xmm());
return;
}
if (dst.is_vec() && src.is_gp()) {
if (src.as<Reg>().size() <= 4)
cc->emit(Inst::kIdVmovd, dst.as<Vec>().xmm(), src.as<Gp>().r32());
else
cc->emit(Inst::kIdVmovq, dst.as<Vec>().xmm(), src.as<Gp>().r64());
return;
}
ASMJIT_NOT_REACHED();
}
case UniOpVR::kMovU32:
case UniOpVR::kMovU64: {
ASMJIT_ASSERT(dst.is_reg());
ASMJIT_ASSERT(src.is_reg());
if (dst.is_gp() && src.is_vec()) {
if (op == UniOpVR::kMovU32)
cc->emit(Inst::kIdVmovd, dst.as<Gp>().r32(), src.as<Vec>().xmm());
else
cc->emit(Inst::kIdVmovq, dst.as<Gp>().r64(), src.as<Vec>().xmm());
return;
}
if (dst.is_vec() && src.is_gp()) {
if (op == UniOpVR::kMovU32)
cc->emit(Inst::kIdVmovd, dst.as<Vec>().xmm(), src.as<Gp>().r32());
else
cc->emit(Inst::kIdVmovq, dst.as<Vec>().xmm(), src.as<Gp>().r64());
return;
}
ASMJIT_NOT_REACHED();
}
case UniOpVR::kInsertU8:
case UniOpVR::kInsertU16:
case UniOpVR::kInsertU32:
case UniOpVR::kInsertU64: {
ASMJIT_ASSERT(dst.is_vec());
ASMJIT_ASSERT(src.is_gp());
dst = dst.as<Vec>().xmm();
if (op != UniOpVR::kInsertU64)
src = src.as<Gp>().r32();
cc->emit(op_info.avx_inst_id, dst, dst, src, idx);
return;
}
case UniOpVR::kExtractU8:
case UniOpVR::kExtractU16:
case UniOpVR::kExtractU32:
case UniOpVR::kExtractU64: {
ASMJIT_ASSERT(dst.is_gp());
ASMJIT_ASSERT(src.is_vec());
src = src.as<Vec>().xmm();
if (op != UniOpVR::kExtractU64)
dst = dst.as<Gp>().r32();
if (op == UniOpVR::kExtractU32 && idx == 0) {
cc->vmovd(dst.as<Gp>(), src.as<Vec>());
return;
}
if (op == UniOpVR::kExtractU64) {
cc->vmovq(dst.as<Gp>(), src.as<Vec>());
return;
}
cc->emit(op_info.avx_inst_id, dst, src, idx);
return;
}
case UniOpVR::kCvtIntToF32:
case UniOpVR::kCvtIntToF64: {
dst = dst.as<Vec>().xmm();
cc->emit(Inst::kIdVpxor, dst, dst, dst);
cc->emit(op_info.avx_inst_id, dst, dst, src);
return;
}
case UniOpVR::kCvtTruncF32ToInt:
case UniOpVR::kCvtRoundF32ToInt:
case UniOpVR::kCvtTruncF64ToInt:
case UniOpVR::kCvtRoundF64ToInt: {
if (src.is_vec())
src = src.as<Vec>().xmm();
cc->emit(op_info.avx_inst_id, dst, src);
return;
}
default:
ASMJIT_NOT_REACHED();
}
}
else {
// SSE Implementation
// ------------------
switch (op) {
case UniOpVR::kMov: {
ASMJIT_ASSERT(dst.is_reg());
ASMJIT_ASSERT(src.is_reg());
if (dst.is_gp() && src.is_vec()) {
if (dst.as<Reg>().size() <= 4)
cc->emit(Inst::kIdMovd, dst.as<Gp>().r32(), src.as<Vec>().xmm());
else
cc->emit(Inst::kIdMovq, dst.as<Gp>().r64(), src.as<Vec>().xmm());
return;
}
if (dst.is_vec() && src.is_gp()) {
if (src.as<Reg>().size() <= 4)
cc->emit(Inst::kIdMovd, dst.as<Vec>().xmm(), src.as<Gp>().r32());
else
cc->emit(Inst::kIdMovq, dst.as<Vec>().xmm(), src.as<Gp>().r64());
return;
}
ASMJIT_NOT_REACHED();
}
case UniOpVR::kMovU32:
case UniOpVR::kMovU64: {
ASMJIT_ASSERT(dst.is_reg());
ASMJIT_ASSERT(src.is_reg());
if (dst.is_gp() && src.is_vec()) {
if (op == UniOpVR::kMovU32)
cc->emit(Inst::kIdMovd, dst.as<Gp>().r32(), src.as<Vec>().xmm());
else
cc->emit(Inst::kIdMovq, dst.as<Gp>().r64(), src.as<Vec>().xmm());
return;
}
if (dst.is_vec() && src.is_gp()) {
if (op == UniOpVR::kMovU32)
cc->emit(Inst::kIdMovd, dst.as<Vec>().xmm(), src.as<Gp>().r32());
else
cc->emit(Inst::kIdMovq, dst.as<Vec>().xmm(), src.as<Gp>().r64());
return;
}
ASMJIT_NOT_REACHED();
}
case UniOpVR::kInsertU8:
case UniOpVR::kInsertU16:
case UniOpVR::kInsertU32:
case UniOpVR::kInsertU64: {
ASMJIT_ASSERT(dst.is_vec());
ASMJIT_ASSERT(src.is_gp());
if (op != UniOpVR::kInsertU64)
src = src.as<Gp>().r32();
if (has_sse_ext(SSEExt(op_info.sse_ext))) {
cc->emit(op_info.sse_inst_id, dst, src, idx);
}
else if (op == UniOpVR::kInsertU8) {
Gp tmp = new_gp32("@tmp");
cc->pextrw(tmp, dst.as<Vec>(), idx / 2u);
if (idx & 1)
cc->mov(tmp.r8_hi(), src.as<Gp>().r8());
else
cc->mov(tmp.r8(), src.as<Gp>().r8());
cc->pinsrw(dst.as<Vec>(), tmp, idx / 2u);
}
else if (op == UniOpVR::kInsertU32) {
if (idx == 0) {
Vec tmp = new_vec128("@tmp");
cc->movd(tmp, src.as<Gp>());
cc->movss(dst.as<Vec>(), tmp);
}
else {
Gp tmp = new_gp32("@tmp");
cc->pinsrw(dst.as<Vec>(), src.as<Gp>(), idx * 2u);
cc->mov(tmp.as<Gp>(), src.as<Gp>());
cc->shr(tmp.as<Gp>(), 16);
cc->pinsrw(dst.as<Vec>(), tmp, idx * 2u + 1u);
}
}
else {
Vec tmp = new_vec128("@tmp");
cc->movq(tmp, src.as<Gp>());
if (idx == 0)
cc->movsd(dst.as<Vec>(), tmp);
else
cc->punpcklqdq(dst.as<Vec>(), tmp);
}
return;
}
case UniOpVR::kExtractU8:
case UniOpVR::kExtractU16:
case UniOpVR::kExtractU32:
case UniOpVR::kExtractU64: {
ASMJIT_ASSERT(dst.is_gp());
ASMJIT_ASSERT(src.is_vec());
if (op != UniOpVR::kExtractU64)
dst = dst.as<Gp>().r32();
if (op == UniOpVR::kExtractU32 && idx == 0) {
cc->movd(dst.as<Gp>(), src.as<Vec>());
}
else if (op == UniOpVR::kExtractU64 && idx == 0) {
cc->movq(dst.as<Gp>(), src.as<Vec>());
}
else if (has_sse_ext(SSEExt(op_info.sse_ext))) {
cc->emit(op_info.sse_inst_id, dst, src, idx);
}
else if (op == UniOpVR::kExtractU8) {
cc->pextrw(dst.as<Gp>(), src.as<Vec>(), idx / 2u);
if (idx & 1)
cc->shr(dst.as<Gp>(), 8);
else
cc->and_(dst.as<Gp>(), 0xFF);
}
else if (op == UniOpVR::kExtractU32) {
Vec tmp = new_similar_reg(dst.as<Vec>(), "@tmp");
cc->pshufd(tmp, src.as<Vec>(), x86::shuffle_imm(idx, idx, idx, idx));
cc->movd(dst.as<Gp>(), tmp);
}
else {
Vec tmp = new_similar_reg(dst.as<Vec>(), "@tmp");
cc->pshufd(tmp, src.as<Vec>(), x86::shuffle_imm(3, 2, 3, 2));
cc->movq(dst.as<Gp>(), tmp);
}
return;
}
case UniOpVR::kCvtIntToF32:
case UniOpVR::kCvtIntToF64: {
dst = dst.as<Vec>().xmm();
cc->pxor(dst.as<Vec>(), dst.as<Vec>());
cc->emit(op_info.sse_inst_id, dst, src);
return;
}
case UniOpVR::kCvtTruncF32ToInt:
case UniOpVR::kCvtRoundF32ToInt:
case UniOpVR::kCvtTruncF64ToInt:
case UniOpVR::kCvtRoundF64ToInt: {
cc->emit(op_info.sse_inst_id, dst, src);
return;
}
default:
ASMJIT_NOT_REACHED();
}
}
}
// ujit::UniCompiler - Vector Instructions - Emit 2VM
// ==================================================
void UniCompiler::emit_vm(UniOpVM op, const Vec& dst_, const Mem& src_, Alignment alignment, uint32_t idx) {
ASMJIT_ASSERT(dst_.is_vec());
ASMJIT_ASSERT(src_.is_mem());
Vec dst(dst_);
Mem src(src_);
UniOpVMInfo op_info = opcode_info_2vm[size_t(op)];
if (has_avx()) {
// AVX Implementation
// ------------------
switch (op) {
case UniOpVM::kLoad8: {
dst = dst.xmm();
src.set_size(1);
avx_zero(*this, dst);
cc->vpinsrb(dst, dst, src, 0);
return;
}
case UniOpVM::kLoad16_U16:
if (!has_avx512_fp16()) {
dst = dst.xmm();
src.set_size(1);
avx_zero(*this, dst);
cc->vpinsrw(dst, dst, src, 0);
}
[[fallthrough]];
case UniOpVM::kLoad32_U32:
case UniOpVM::kLoad32_F32:
case UniOpVM::kLoad64_U32:
case UniOpVM::kLoad64_U64:
case UniOpVM::kLoad64_F32:
case UniOpVM::kLoad64_F64: {
dst.set_signature(signature_of_xmm_ymm_zmm[0]);
src.set_size(op_info.mem_size);
cc->emit(op_info.avx_inst_id, dst, src);
return;
}
case UniOpVM::kLoad128_U32:
case UniOpVM::kLoad128_U64:
case UniOpVM::kLoad128_F32:
case UniOpVM::kLoad128_F64:
case UniOpVM::kLoad256_U32:
case UniOpVM::kLoad256_U64:
case UniOpVM::kLoad256_F32:
case UniOpVM::kLoad256_F64:
case UniOpVM::kLoad512_U32:
case UniOpVM::kLoad512_U64:
case UniOpVM::kLoad512_F32:
case UniOpVM::kLoad512_F64:
ASMJIT_ASSERT(dst.size() >= op_info.mem_size);
dst.set_signature(signature_of_xmm_ymm_zmm[op_info.mem_size >> 5]);
[[fallthrough]];
case UniOpVM::kLoadN_U32:
case UniOpVM::kLoadN_U64:
case UniOpVM::kLoadN_F32:
case UniOpVM::kLoadN_F64: {
src.set_size(dst.size());
cc->emit((uint32_t(alignment) == 0u || uint32_t(alignment) >= dst.size()) ? Inst::kIdVmovaps : Inst::kIdVmovups, dst, src);
return;
}
case UniOpVM::kLoadCvt16_U8ToU64:
case UniOpVM::kLoadCvt32_U8ToU64:
case UniOpVM::kLoadCvt64_U8ToU64:
dst.set_signature(signature_of_xmm_ymm_zmm[op_info.mem_size >> 2]);
[[fallthrough]];
case UniOpVM::kLoadCvtN_U8ToU64: {
ASMJIT_ASSERT(dst.size() >= op_info.mem_size * 8u);
src.set_size(dst.size() / 8u);
cc->emit(op_info.avx_inst_id, dst, src);
return;
}
case UniOpVM::kLoadCvt32_I8ToI32:
case UniOpVM::kLoadCvt32_U8ToU32:
case UniOpVM::kLoadCvt64_I8ToI32:
case UniOpVM::kLoadCvt64_U8ToU32:
case UniOpVM::kLoadCvt128_I8ToI32:
case UniOpVM::kLoadCvt128_U8ToU32:
dst.set_signature(signature_of_xmm_ymm_zmm[op_info.mem_size >> 3]);
[[fallthrough]];
case UniOpVM::kLoadCvtN_I8ToI32:
case UniOpVM::kLoadCvtN_U8ToU32: {
ASMJIT_ASSERT(dst.size() >= op_info.mem_size * 4u);
src.set_size(dst.size() / 4u);
cc->emit(op_info.avx_inst_id, dst, src);
return;
}
case UniOpVM::kLoadCvt32_I8ToI16:
case UniOpVM::kLoadCvt32_U8ToU16:
case UniOpVM::kLoadCvt32_I16ToI32:
case UniOpVM::kLoadCvt32_U16ToU32:
case UniOpVM::kLoadCvt32_I32ToI64:
case UniOpVM::kLoadCvt32_U32ToU64: {
dst.set_signature(signature_of_xmm_ymm_zmm[0]);
src.set_size(4);
cc->vmovd(dst, src);
cc->emit(op_info.avx_inst_id, dst, dst);
return;
}
case UniOpVM::kLoadCvt64_I8ToI16:
case UniOpVM::kLoadCvt64_U8ToU16:
case UniOpVM::kLoadCvt64_I16ToI32:
case UniOpVM::kLoadCvt64_U16ToU32:
case UniOpVM::kLoadCvt64_I32ToI64:
case UniOpVM::kLoadCvt64_U32ToU64:
case UniOpVM::kLoadCvt128_I8ToI16:
case UniOpVM::kLoadCvt128_U8ToU16:
case UniOpVM::kLoadCvt128_I16ToI32:
case UniOpVM::kLoadCvt128_U16ToU32:
case UniOpVM::kLoadCvt128_I32ToI64:
case UniOpVM::kLoadCvt128_U32ToU64:
case UniOpVM::kLoadCvt256_I8ToI16:
case UniOpVM::kLoadCvt256_U8ToU16:
case UniOpVM::kLoadCvt256_I16ToI32:
case UniOpVM::kLoadCvt256_U16ToU32:
case UniOpVM::kLoadCvt256_I32ToI64:
case UniOpVM::kLoadCvt256_U32ToU64:
ASMJIT_ASSERT(dst.size() >= op_info.mem_size * 2u);
dst.set_signature(signature_of_xmm_ymm_zmm[op_info.mem_size >> 4]);
[[fallthrough]];
case UniOpVM::kLoadCvtN_I8ToI16:
case UniOpVM::kLoadCvtN_U8ToU16:
case UniOpVM::kLoadCvtN_I16ToI32:
case UniOpVM::kLoadCvtN_U16ToU32:
case UniOpVM::kLoadCvtN_I32ToI64:
case UniOpVM::kLoadCvtN_U32ToU64: {
src.set_size(dst.size() / 2u);
cc->emit(op_info.avx_inst_id, dst, src);
return;
}
case UniOpVM::kLoadInsertU8:
case UniOpVM::kLoadInsertU16:
case UniOpVM::kLoadInsertU32:
case UniOpVM::kLoadInsertF32: {
dst = dst.as<Vec>().xmm();
cc->emit(op_info.avx_inst_id, dst, dst, src, idx);
return;
}
case UniOpVM::kLoadInsertU64: {
dst = dst.as<Vec>().xmm();
if (is_64bit()) {
cc->emit(op_info.avx_inst_id, dst, dst, src, idx);
}
else {
if (idx == 0)
cc->vmovlpd(dst, dst, src);
else
cc->vmovhpd(dst, dst, src);
}
return;
}
case UniOpVM::kLoadInsertF32x2: {
if (idx == 0)
cc->emit(Inst::kIdVmovlps, dst, dst, src);
else
cc->emit(Inst::kIdVmovhps, dst, dst, src);
return;
}
case UniOpVM::kLoadInsertF64: {
if (idx == 0)
cc->emit(Inst::kIdVmovlpd, dst, dst, src);
else
cc->emit(Inst::kIdVmovhpd, dst, dst, src);
return;
}
default:
ASMJIT_NOT_REACHED();
}
}
else {
// SSE Implementation
// ------------------
ASMJIT_ASSERT(dst.is_vec128());
switch (op) {
case UniOpVM::kLoad8: {
src.set_size(1);
if (has_sse4_1()) {
cc->xorps(dst, dst);
cc->pinsrb(dst, src, 0);
}
else {
Gp tmp = new_gp32("@tmp");
cc->movzx(tmp, src);
cc->movd(dst, tmp);
}
return;
}
case UniOpVM::kLoad16_U16: {
src.set_size(2);
cc->xorps(dst, dst);
cc->pinsrw(dst, src, 0);
return;
}
case UniOpVM::kLoad32_U32:
case UniOpVM::kLoad32_F32:
case UniOpVM::kLoad64_U32:
case UniOpVM::kLoad64_U64:
case UniOpVM::kLoad64_F32:
case UniOpVM::kLoad64_F64: {
src.set_size(op_info.mem_size);
cc->emit(op_info.sse_inst_id, dst, src);
return;
}
case UniOpVM::kLoad128_U32:
case UniOpVM::kLoad128_U64:
case UniOpVM::kLoad128_F32:
case UniOpVM::kLoad128_F64:
case UniOpVM::kLoadN_U32:
case UniOpVM::kLoadN_U64:
case UniOpVM::kLoadN_F32:
case UniOpVM::kLoadN_F64: {
src.set_size(16);
cc->emit((uint32_t(alignment) == 0u || uint32_t(alignment) >= 16u) ? Inst::kIdMovaps : Inst::kIdMovups, dst, src);
return;
}
case UniOpVM::kLoadCvt16_U8ToU64:
case UniOpVM::kLoadCvtN_U8ToU64: {
if (has_sse4_1()) {
src.set_size(2);
cc->emit(op_info.avx_inst_id, dst, src);
}
else {
src.set_size(1);
Gp tmp = new_gp32("@tmp");
cc->movzx(tmp, src);
cc->movd(dst, tmp);
src.add_offset(1);
cc->movzx(tmp, src);
cc->pinsrw(dst, src, 4);
}
return;
}
case UniOpVM::kLoadCvt32_I8ToI32:
case UniOpVM::kLoadCvt32_U8ToU32:
case UniOpVM::kLoadCvtN_I8ToI32:
case UniOpVM::kLoadCvtN_U8ToU32:
if (has_sse4_1()) {
src.set_size(4);
cc->emit(op_info.sse_inst_id, dst, src);
return;
}
[[fallthrough]];
case UniOpVM::kLoadCvt32_I8ToI16:
case UniOpVM::kLoadCvt32_U8ToU16:
case UniOpVM::kLoadCvt32_I16ToI32:
case UniOpVM::kLoadCvt32_U16ToU32:
case UniOpVM::kLoadCvt32_I32ToI64:
case UniOpVM::kLoadCvt32_U32ToU64: {
src.set_size(4);
cc->vmovd(dst, src);
sse_int_widen(*this, dst, dst, WideningOp(op_info.cvt));
return;
}
case UniOpVM::kLoadCvt64_I8ToI16:
case UniOpVM::kLoadCvt64_U8ToU16:
case UniOpVM::kLoadCvt64_I16ToI32:
case UniOpVM::kLoadCvt64_U16ToU32:
case UniOpVM::kLoadCvt64_I32ToI64:
case UniOpVM::kLoadCvt64_U32ToU64:
case UniOpVM::kLoadCvtN_I8ToI16:
case UniOpVM::kLoadCvtN_U8ToU16:
case UniOpVM::kLoadCvtN_I16ToI32:
case UniOpVM::kLoadCvtN_U16ToU32:
case UniOpVM::kLoadCvtN_I32ToI64:
case UniOpVM::kLoadCvtN_U32ToU64: {
src.set_size(8);
if (has_sse4_1()) {
InstId inst = op_info.sse_inst_id;
cc->emit(inst, dst, src);
}
else {
cc->movq(dst, src);
sse_int_widen(*this, dst, dst, WideningOp(op_info.cvt));
}
return;
}
case UniOpVM::kLoadInsertU16: {
cc->emit(op_info.sse_inst_id, dst, dst, idx);
return;
}
case UniOpVM::kLoadInsertF32:
op = UniOpVM::kLoadInsertU32;
[[fallthrough]];
case UniOpVM::kLoadInsertU8:
case UniOpVM::kLoadInsertU32:
case UniOpVM::kLoadInsertU64: {
if (has_sse4_1() && (op != UniOpVM::kLoadInsertU64 || is_64bit())) {
cc->emit(op_info.sse_inst_id, dst, src, idx);
return;
}
if (op == UniOpVM::kLoadInsertU8) {
Gp tmp = new_gp32("@tmp");
src.set_size(1);
cc->pextrw(tmp, dst, idx / 2u);
if (idx & 1)
cc->mov(tmp.r8_hi(), src);
else
cc->mov(tmp.r8(), src);
cc->pinsrw(dst, tmp, idx / 2u);
return;
}
if (op == UniOpVM::kLoadInsertU32) {
if (idx == 0) {
Vec tmp = new_vec128("@tmp");
cc->movd(tmp, src);
cc->movss(dst, tmp);
}
else {
cc->pinsrw(dst, src, idx * 2u);
src.add_offset(2);
cc->pinsrw(dst, src, idx * 2u + 1);
}
return;
}
ASMJIT_ASSERT(op == UniOpVM::kLoadInsertU64);
if (idx == 0)
cc->movlpd(dst, src);
else
cc->movhpd(dst, src);
return;
}
case UniOpVM::kLoadInsertF32x2: {
if (idx == 0)
cc->movlps(dst, src);
else
cc->movhps(dst, src);
return;
}
case UniOpVM::kLoadInsertF64: {
if (idx == 0)
cc->movlpd(dst, src);
else
cc->movhpd(dst, src);
return;
}
case UniOpVM::kLoad256_U32:
case UniOpVM::kLoad256_U64:
case UniOpVM::kLoad256_F32:
case UniOpVM::kLoad256_F64:
case UniOpVM::kLoad512_U32:
case UniOpVM::kLoad512_U64:
case UniOpVM::kLoad512_F32:
case UniOpVM::kLoad512_F64:
case UniOpVM::kLoadCvt32_U8ToU64:
case UniOpVM::kLoadCvt64_U8ToU64:
case UniOpVM::kLoadCvt64_I8ToI32:
case UniOpVM::kLoadCvt64_U8ToU32:
case UniOpVM::kLoadCvt128_I8ToI16:
case UniOpVM::kLoadCvt128_U8ToU16:
case UniOpVM::kLoadCvt128_I8ToI32:
case UniOpVM::kLoadCvt128_U8ToU32:
case UniOpVM::kLoadCvt128_I16ToI32:
case UniOpVM::kLoadCvt128_U16ToU32:
case UniOpVM::kLoadCvt128_I32ToI64:
case UniOpVM::kLoadCvt128_U32ToU64:
case UniOpVM::kLoadCvt256_I8ToI16:
case UniOpVM::kLoadCvt256_U8ToU16:
case UniOpVM::kLoadCvt256_I16ToI32:
case UniOpVM::kLoadCvt256_U16ToU32:
case UniOpVM::kLoadCvt256_I32ToI64:
case UniOpVM::kLoadCvt256_U32ToU64:
ASMJIT_NOT_REACHED();
default:
ASMJIT_NOT_REACHED();
}
}
}
void UniCompiler::emit_vm(UniOpVM op, const OpArray& dst_, const Mem& src_, Alignment alignment, uint32_t idx) {
Mem src(src_);
UniOpVMInfo op_info = opcode_info_2vm[size_t(op)];
uint32_t mem_size = op_info.mem_size;
if (mem_size == 0) {
uint32_t mem_size_shift = op_info.mem_size_shift;
for (size_t i = 0, n = dst_.size(); i < n; i++) {
ASMJIT_ASSERT(dst_[i].is_reg() && dst_[i].is_vec());
const Vec& dst = dst_[i].as<Vec>();
mem_size = dst.size() >> mem_size_shift;
emit_vm(op, dst, src, uint32_t(alignment) > 0u ? alignment : Alignment(mem_size), idx);
src.add_offset_lo32(int32_t(mem_size));
}
}
else {
if (uint32_t(alignment) == 0u) {
alignment = Alignment(mem_size);
}
for (size_t i = 0, n = dst_.size(); i < n; i++) {
ASMJIT_ASSERT(dst_[i].is_reg() && dst_[i].is_vec());
const Vec& dst = dst_[i].as<Vec>();
emit_vm(op, dst, src, alignment, idx);
src.add_offset_lo32(int32_t(mem_size));
}
}
}
void UniCompiler::emit_mv(UniOpMV op, const Mem& dst_, const Vec& src_, Alignment alignment, uint32_t idx) {
ASMJIT_ASSERT(dst_.is_mem());
ASMJIT_ASSERT(src_.is_reg() && src_.is_vec());
Mem dst(dst_);
Vec src(src_);
UniOpVMInfo op_info = opcode_info_2mv[size_t(op)];
if (has_avx()) {
// AVX Implementation
// ------------------
switch (op) {
case UniOpMV::kStore8: {
dst.set_size(1);
cc->vpextrb(dst, src.xmm(), 0);
return;
}
case UniOpMV::kStore16_U16: {
dst.set_size(2);
cc->vpextrw(dst, src.xmm(), 0);
return;
}
case UniOpMV::kStore32_U32:
case UniOpMV::kStore32_F32:
case UniOpMV::kStore64_U32:
case UniOpMV::kStore64_U64:
case UniOpMV::kStore64_F32:
case UniOpMV::kStore64_F64: {
dst.set_size(op_info.mem_size);
cc->emit(op_info.avx_inst_id, dst, src.xmm());
return;
}
case UniOpMV::kStore128_U32:
case UniOpMV::kStore128_U64:
case UniOpMV::kStore128_F32:
case UniOpMV::kStore128_F64:
case UniOpMV::kStore256_U32:
case UniOpMV::kStore256_U64:
case UniOpMV::kStore256_F32:
case UniOpMV::kStore256_F64:
case UniOpMV::kStore512_U32:
case UniOpMV::kStore512_U64:
case UniOpMV::kStore512_F32:
case UniOpMV::kStore512_F64:
ASMJIT_ASSERT(src.size() >= op_info.mem_size);
src.set_signature(signature_of_xmm_ymm_zmm[op_info.mem_size >> 5]);
[[fallthrough]];
case UniOpMV::kStoreN_U32:
case UniOpMV::kStoreN_U64:
case UniOpMV::kStoreN_F32:
case UniOpMV::kStoreN_F64: {
InstId inst = (uint32_t(alignment) == 0 || uint32_t(alignment) >= src.size()) ? Inst::kIdVmovaps : Inst::kIdVmovups;
dst.set_size(src.size());
cc->emit(inst, dst, src);
return;
}
/*
case UniOpMV::kStoreCvtz64_U16ToU8:
case UniOpMV::kStoreCvtz64_U32ToU16:
case UniOpMV::kStoreCvtz64_U64ToU32:
case UniOpMV::kStoreCvts64_I16ToI8:
case UniOpMV::kStoreCvts64_I16ToU8:
case UniOpMV::kStoreCvts64_U16ToU8:
case UniOpMV::kStoreCvts64_I32ToI16:
case UniOpMV::kStoreCvts64_U32ToU16:
case UniOpMV::kStoreCvts64_I64ToI32:
case UniOpMV::kStoreCvts64_U64ToU32:
case UniOpMV::kStoreCvtz128_U16ToU8:
case UniOpMV::kStoreCvtz128_U32ToU16:
case UniOpMV::kStoreCvtz128_U64ToU32:
case UniOpMV::kStoreCvts128_I16ToI8:
case UniOpMV::kStoreCvts128_I16ToU8:
case UniOpMV::kStoreCvts128_U16ToU8:
case UniOpMV::kStoreCvts128_I32ToI16:
case UniOpMV::kStoreCvts128_U32ToU16:
case UniOpMV::kStoreCvts128_I64ToI32:
case UniOpMV::kStoreCvts128_U64ToU32:
case UniOpMV::kStoreCvtz256_U16ToU8:
case UniOpMV::kStoreCvtz256_U32ToU16:
case UniOpMV::kStoreCvtz256_U64ToU32:
case UniOpMV::kStoreCvts256_I16ToI8:
case UniOpMV::kStoreCvts256_I16ToU8:
case UniOpMV::kStoreCvts256_U16ToU8:
case UniOpMV::kStoreCvts256_I32ToI16:
case UniOpMV::kStoreCvts256_U32ToU16:
case UniOpMV::kStoreCvts256_I64ToI32:
case UniOpMV::kStoreCvts256_U64ToU32:
case UniOpMV::kStoreCvtzN_U16ToU8:
case UniOpMV::kStoreCvtzN_U32ToU16:
case UniOpMV::kStoreCvtzN_U64ToU32:
case UniOpMV::kStoreCvtsN_I16ToI8:
case UniOpMV::kStoreCvtsN_I16ToU8:
case UniOpMV::kStoreCvtsN_U16ToU8:
case UniOpMV::kStoreCvtsN_I32ToI16:
case UniOpMV::kStoreCvtsN_U32ToU16:
case UniOpMV::kStoreCvtsN_I64ToI32:
case UniOpMV::kStoreCvtsN_U64ToU32:
*/
case UniOpMV::kStoreExtractU16:
case UniOpMV::kStoreExtractU32:
case UniOpMV::kStoreExtractU64: {
src = src.xmm();
if (op == UniOpMV::kStoreExtractU32) {
if (idx == 0) {
cc->vmovd(dst, src);
return;
}
}
if (op == UniOpMV::kStoreExtractU64) {
if (idx == 0) {
cc->vmovq(dst, src);
return;
}
else if (!is_64bit()) {
cc->vmovhpd(dst, src);
return;
}
}
cc->emit(op_info.avx_inst_id, dst, src, idx);
return;
}
default:
ASMJIT_NOT_REACHED();
}
}
else {
// SSE Implementation
// ------------------
ASMJIT_ASSERT(src.is_vec128());
switch (op) {
case UniOpMV::kStore8: {
dst.set_size(1);
if (has_sse4_1()) {
cc->pextrb(dst, src, 0);
}
else {
Gp tmp = new_gp32("@tmp");
cc->movd(tmp, src);
cc->mov(dst, tmp.r8());
}
return;
}
case UniOpMV::kStore16_U16: {
dst.set_size(2);
if (has_sse4_1()) {
cc->pextrw(dst, src, 0);
}
else {
Gp tmp = new_gp32("@tmp");
cc->movd(tmp, src);
cc->mov(dst, tmp.r16());
}
return;
}
case UniOpMV::kStore32_U32:
case UniOpMV::kStore32_F32:
case UniOpMV::kStore64_U32:
case UniOpMV::kStore64_U64:
case UniOpMV::kStore64_F32:
case UniOpMV::kStore64_F64: {
dst.set_size(op_info.mem_size);
cc->emit(op_info.sse_inst_id, dst, src);
return;
}
case UniOpMV::kStore128_U32:
case UniOpMV::kStore128_U64:
case UniOpMV::kStore128_F32:
case UniOpMV::kStore128_F64:
case UniOpMV::kStoreN_U32:
case UniOpMV::kStoreN_U64:
case UniOpMV::kStoreN_F32:
case UniOpMV::kStoreN_F64: {
InstId inst = (uint32_t(alignment) == 0u || uint32_t(alignment) >= 16u) ? Inst::kIdMovaps : Inst::kIdMovups;
dst.set_size(16);
cc->emit(inst, dst, src);
return;
}
/*
case UniOpMV::kStoreCvtz64_U16ToU8:
case UniOpMV::kStoreCvtz64_U32ToU16:
case UniOpMV::kStoreCvtz64_U64ToU32:
case UniOpMV::kStoreCvts64_I16ToI8:
case UniOpMV::kStoreCvts64_I16ToU8:
case UniOpMV::kStoreCvts64_U16ToU8:
case UniOpMV::kStoreCvts64_I32ToI16:
case UniOpMV::kStoreCvts64_U32ToU16:
case UniOpMV::kStoreCvts64_I64ToI32:
case UniOpMV::kStoreCvts64_U64ToU32:
case UniOpMV::kStoreCvtzN_U16ToU8:
case UniOpMV::kStoreCvtzN_U32ToU16:
case UniOpMV::kStoreCvtzN_U64ToU32:
case UniOpMV::kStoreCvtsN_I16ToI8:
case UniOpMV::kStoreCvtsN_I16ToU8:
case UniOpMV::kStoreCvtsN_U16ToU8:
case UniOpMV::kStoreCvtsN_I32ToI16:
case UniOpMV::kStoreCvtsN_U32ToU16:
case UniOpMV::kStoreCvtsN_I64ToI32:
case UniOpMV::kStoreCvtsN_U64ToU32: {
UNIMPLEMENTED();
return;
}
*/
case UniOpMV::kStore256_U32:
case UniOpMV::kStore256_U64:
case UniOpMV::kStore256_F32:
case UniOpMV::kStore256_F64:
case UniOpMV::kStore512_U32:
case UniOpMV::kStore512_U64:
case UniOpMV::kStore512_F32:
case UniOpMV::kStore512_F64:
/*
case UniOpMV::kStoreCvtz128_U16ToU8:
case UniOpMV::kStoreCvtz128_U32ToU16:
case UniOpMV::kStoreCvtz128_U64ToU32:
case UniOpMV::kStoreCvts128_I16ToI8:
case UniOpMV::kStoreCvts128_I16ToU8:
case UniOpMV::kStoreCvts128_U16ToU8:
case UniOpMV::kStoreCvts128_I32ToI16:
case UniOpMV::kStoreCvts128_U32ToU16:
case UniOpMV::kStoreCvts128_I64ToI32:
case UniOpMV::kStoreCvts128_U64ToU32:
case UniOpMV::kStoreCvtz256_U16ToU8:
case UniOpMV::kStoreCvtz256_U32ToU16:
case UniOpMV::kStoreCvtz256_U64ToU32:
case UniOpMV::kStoreCvts256_I16ToI8:
case UniOpMV::kStoreCvts256_I16ToU8:
case UniOpMV::kStoreCvts256_U16ToU8:
case UniOpMV::kStoreCvts256_I32ToI16:
case UniOpMV::kStoreCvts256_U32ToU16:
case UniOpMV::kStoreCvts256_I64ToI32:
case UniOpMV::kStoreCvts256_U64ToU32:
*/
ASMJIT_NOT_REACHED();
case UniOpMV::kStoreExtractU16:
case UniOpMV::kStoreExtractU32:
case UniOpMV::kStoreExtractU64: {
if (op == UniOpMV::kStoreExtractU32) {
if (idx == 0) {
cc->movd(dst, src);
return;
}
}
if (op == UniOpMV::kStoreExtractU64) {
if (idx == 0) {
cc->movq(dst, src);
return;
}
if (idx == 1) {
cc->movhps(dst, src);
return;
}
}
if (has_sse4_1()) {
cc->emit(op_info.sse_inst_id, dst, src, idx);
return;
}
// SSE4.1 not available - only required when extracting 16-bit and 32-bit quantities as 64-bit quantities
// were already handled. Additionally, there is no PEXTRW instruction in SSE2 that would extract to memory,
// this instruction was added by SSE4.1 as well (there are actually two forms of PEXTRW).
if (op == UniOpMV::kStoreExtractU16) {
Gp tmp = new_gp32("@pextrw_tmp");
cc->pextrw(tmp, src, idx);
cc->mov(dst, tmp);
return;
}
if (op == UniOpMV::kStoreExtractU32) {
Vec tmp = new_vec128("@pextrd_tmp");
cc->pshufd(tmp, src, x86::shuffle_imm(idx, idx, idx, idx));
cc->movd(dst, tmp);
return;
}
ASMJIT_NOT_REACHED();
}
default:
ASMJIT_NOT_REACHED();
}
}
}
void UniCompiler::emit_mv(UniOpMV op, const Mem& dst_, const OpArray& src_, Alignment alignment, uint32_t idx) {
Support::maybe_unused(idx);
Mem dst(dst_);
UniOpVMInfo op_info = opcode_info_2mv[size_t(op)];
uint32_t mem_size = op_info.mem_size;
if (mem_size == 0) {
for (size_t i = 0, n = src_.size(); i < n; i++) {
ASMJIT_ASSERT(src_[i].is_reg() && src_[i].is_vec());
const Vec& src = src_[i].as<Vec>();
mem_size = src.size();
emit_mv(op, dst, src, uint32_t(alignment) > 0u ? alignment : Alignment(mem_size));
dst.add_offset_lo32(int32_t(mem_size));
}
}
else {
if (uint32_t(alignment) == 0) {
alignment = Alignment(mem_size);
}
for (size_t i = 0, n = src_.size(); i < n; i++) {
ASMJIT_ASSERT(src_[i].is_reg() && src_[i].is_vec());
const Vec& src = src_[i].as<Vec>();
emit_mv(op, dst, src, alignment);
dst.add_offset_lo32(int32_t(mem_size));
}
}
}
// ujit::UniCompiler - Vector Instructions - Emit 3V
// =================================================
void UniCompiler::emit_3v(UniOpVVV op, const Operand_& dst_, const Operand_& src1_, const Operand_& src2_) {
ASMJIT_ASSERT(dst_.is_vec());
ASMJIT_ASSERT(src1_.is_vec());
Vec dst(dst_.as<Vec>());
Vec src1v(src1_.as<Vec>().clone_as(dst));
Operand src2(src2_);
UniOpVInfo op_info = opcode_info_3v[size_t(op)];
if (has_avx()) {
// AVX Implementation
// ------------------
InstId inst_id = op_info.avx_inst_id;
static constexpr InstId avx_vpmovm2v_table[] = {
Inst::kIdVpmovm2b,
Inst::kIdVpmovm2w,
Inst::kIdVpmovm2d,
Inst::kIdVpmovm2q
};
if (is_same_vec(src1v, src2)) {
switch (SameVecOp(op_info.same_vec_op)) {
case SameVecOp::kZero: avx_zero(*this, dst); return;
case SameVecOp::kOnes: avx_ones(*this, dst); return;
case SameVecOp::kSrc: avx_mov(*this, dst, src1v); return;
default:
break;
}
}
if (has_avx_ext(AVXExt(op_info.avx_ext))) {
ASMJIT_ASSERT(inst_id != Inst::kIdNone);
FloatMode fm = FloatMode(op_info.float_mode);
if (is_scalar_fp_op(fm)) {
dst.set_signature(signature_of_xmm_ymm_zmm[0]);
src1v.set_signature(signature_of_xmm_ymm_zmm[0]);
if (src2.is_vec())
src2.as<Vec>().set_signature(signature_of_xmm_ymm_zmm[0]);
}
if (op >= UniOpVVV::kAndU32 && op <= UniOpVVV::kAndnU64 && !has_avx512()) {
static constexpr uint16_t avx512_to_avx_bitwise_map[] = {
Inst::kIdVpand , Inst::kIdVpand ,
Inst::kIdVpor , Inst::kIdVpor ,
Inst::kIdVpxor , Inst::kIdVpxor ,
Inst::kIdVpandn, Inst::kIdVpandn
};
inst_id = avx512_to_avx_bitwise_map[size_t(op) - size_t(UniOpVVV::kAndU32)];
}
if (op_info.comparison && ((dst.is_vec512()) ||
(src2.is_mem() && src2.as<Mem>().has_broadcast()) ||
(AVXExt(op_info.avx_ext) == AVXExt::kAVX512))) {
// AVX-512 instructions change semantics when it comes to comparisons. Instead of having a VEC destination
// we need a K destination. To not change semantics to our users we just convert the predicate to a VEC mask.
x86::KReg kTmp = cc->new_kq("@kTmp");
InstId kMovM = avx_vpmovm2v_table[op_info.element_size];
if (op_info.use_imm)
cc->emit(inst_id, kTmp, src1v, src2, Imm(op_info.imm));
else
cc->emit(inst_id, kTmp, src1v, src2);
cc->emit(kMovM, dst, kTmp);
return;
}
if (op_info.use_imm)
cc->emit(inst_id, dst, src1v, src2, Imm(op_info.imm));
else
cc->emit(inst_id, dst, src1v, src2);
return;
}
switch (op) {
case UniOpVVV::kBicU32:
case UniOpVVV::kBicU64:
case UniOpVVV::kBicF32:
case UniOpVVV::kBicF64: {
if (has_avx512()) {
uint32_t ternlog_inst = ElementSize(op_info.element_size) == ElementSize::k32 ? Inst::kIdVpternlogd : Inst::kIdVpternlogq;
if (src2.is_mem())
cc->emit(ternlog_inst, dst, src1v, src2.as<Mem>(), 0x44);
else
cc->emit(inst_id, dst, src2, src1v);
return;
}
if (op <= UniOpVVV::kBicU64)
inst_id = Inst::kIdVpandn;
if (src2.is_mem()) {
src2 = UniCompiler_load_new(*this, dst, src2.as<Mem>(), op_info.broadcast_size);
}
cc->emit(inst_id, dst, src2, src1v);
return;
}
// dst = a - (floor(a / b) * b).
case UniOpVVV::kModF32S:
case UniOpVVV::kModF64S:
case UniOpVVV::kModF32:
case UniOpVVV::kModF64: {
FloatMode fm = FloatMode(op_info.float_mode);
UniOpVV trunc_op = translate_op(op, UniOpVVV::kModF32S, UniOpVV::kTruncF32);
const FloatInst& fi = avx_float_inst[fm];
x86::Vec tmp = new_similar_reg(dst, "@mod_tmp");
cc->emit(fi.fdiv, tmp, src1v, src2);
emit_2v(trunc_op, tmp, tmp);
cc->emit(fi.fmul, tmp, tmp, src2);
cc->emit(fi.fsub, dst, src1v, tmp);
return;
}
case UniOpVVV::kMulU64: {
// Native operation requires AVX512, which is not supported by the target.
if (src2.is_mem()) {
src2 = UniCompiler_load_new(*this, dst, src2.as<Mem>(), op_info.broadcast_size);
}
Vec src2v = src2.as<Vec>().clone_as(dst);
Vec al_bh = new_similar_reg(dst, "@al_bh");
Vec ah_bl = new_similar_reg(dst, "@ah_bl");
Vec hi_part = new_similar_reg(dst, "@hi_part");
cc->vpsrlq(al_bh, src2v, 32);
cc->vpsrlq(ah_bl, src1v, 32);
cc->vpmuludq(al_bh, al_bh, src1v);
cc->vpmuludq(ah_bl, ah_bl, src2v);
cc->vpmuludq(dst, src1v, src2v);
cc->vpaddq(hi_part, al_bh, ah_bl);
cc->vpsllq(hi_part, hi_part, 32);
cc->vpaddq(dst, dst, hi_part);
return;
}
case UniOpVVV::kMulU64_LoU32: {
// Intrinsic.
Vec tmp = new_similar_reg(dst.as<Vec>(), "@tmp");
if (has_avx512()) {
Vec msk = simd_vec_const(&ct().p_FFFFFFFF00000000, Bcst::k64, dst);
cc->emit(Inst::kIdVpandnq, tmp, msk, src2);
cc->emit(Inst::kIdVpmullq, dst, src1v, tmp);
}
else {
cc->emit(Inst::kIdVpshufd, tmp, src1v, x86::shuffle_imm(2, 3, 0, 1));
cc->emit(Inst::kIdVpmuludq, tmp, tmp, src2);
cc->emit(Inst::kIdVpmuludq, dst, src1v, src2);
cc->emit(Inst::kIdVpsllq, tmp, tmp, 32);
cc->emit(Inst::kIdVpaddq, dst, dst, tmp);
}
return;
}
case UniOpVVV::kMinI64:
case UniOpVVV::kMaxI64: {
// Native operation requires AVX512, which is not supported by the target.
if (src2.is_mem()) {
src2 = UniCompiler_load_new(*this, dst, src2.as<Mem>(), op_info.broadcast_size);
}
ASMJIT_ASSERT(src2.is_vec());
Vec src2v = src2.as<Vec>().clone_as(dst);
Vec msk = dst;
if (dst.id() == src1v.id() || dst.id() == src2v.id()) {
msk = new_similar_reg(dst, "@msk");
}
cc->vpcmpgtq(msk, src1v, src2v); // msk = src1 > src2
if (op == UniOpVVV::kMinI64)
cc->vblendvpd(dst, src1v, src2v, msk); // dst = msk == 0 ? src1 : src2;
else
cc->vblendvpd(dst, src2v, src1v, msk); // dst = msk == 0 ? src2 : src1;
return;
}
case UniOpVVV::kMinU64:
case UniOpVVV::kMaxU64: {
if (src2.is_mem()) {
src2 = UniCompiler_load_new(*this, dst, src2.as<Mem>(), op_info.broadcast_size);
}
ASMJIT_ASSERT(src2.is_vec());
Vec src2v = src2.as<Vec>().clone_as(dst);
Vec tmp1 = dst;
Vec tmp2 = new_similar_reg(dst, "@tmp2");
if (dst.id() == src1v.id() || dst.id() == src2v.id()) {
tmp1 = new_similar_reg(dst, "@tmp1");
}
avx_isign_flip(*this, tmp1, src1v, ElementSize::k64);
avx_isign_flip(*this, tmp2, src2v, ElementSize::k64);
cc->vpcmpgtq(tmp1, tmp1, tmp2); // tmp1 = src1 > src2
if (op == UniOpVVV::kMinU64)
cc->vblendvpd(dst, src1v, src2v, tmp1); // dst = tmp1 == 0 ? src1 : src2;
else
cc->vblendvpd(dst, src2v, src1v, tmp1); // dst = tmp1 == 0 ? src2 : src1;
return;
}
case UniOpVVV::kCmpGtU8:
case UniOpVVV::kCmpGtU16:
case UniOpVVV::kCmpGtU32: {
// Native operation requires AVX512, which is not supported by the target.
CmpMinMaxInst inst = avx_cmp_min_max[(size_t(op) - size_t(UniOpVVV::kCmpGtI8)) & 0x7u];
if (is_same_vec(dst, src1v)) {
Vec tmp = new_similar_reg(dst, "@tmp");
cc->emit(inst.pmin, tmp, src1v, src2);
cc->emit(inst.peq, dst, dst, tmp);
}
else {
cc->emit(inst.pmin, dst, src1v, src2);
cc->emit(inst.peq, dst, dst, src1v);
}
avx_bit_not(*this, dst, dst);
return;
}
case UniOpVVV::kCmpGtU64:
case UniOpVVV::kCmpLeU64: {
Vec tmp = new_similar_reg(dst, "@tmp");
avx_isign_flip(*this, tmp, src2, ElementSize::k64);
avx_isign_flip(*this, dst, src1v, ElementSize::k64);
cc->emit(Inst::kIdVpcmpgtq, dst, dst, tmp);
if (op == UniOpVVV::kCmpLeU64) {
avx_bit_not(*this, dst, dst);
}
return;
}
case UniOpVVV::kCmpGeI8:
case UniOpVVV::kCmpGeU8:
case UniOpVVV::kCmpGeI16:
case UniOpVVV::kCmpGeU16:
case UniOpVVV::kCmpGeI32:
case UniOpVVV::kCmpGeU32: {
CmpMinMaxInst inst = avx_cmp_min_max[(size_t(op) - size_t(UniOpVVV::kCmpGeI8)) & 0x7u];
if (dst.id() == src1v.id()) {
if (!src2.is_reg()) {
Vec tmp = new_similar_reg(dst, "@tmp");
cc->emit(inst.pmax, tmp, src1v, src2);
cc->emit(inst.peq, dst, tmp, src1v);
}
else {
cc->emit(inst.pmin, dst, src1v, src2);
cc->emit(inst.peq, dst, dst, src2);
}
}
else {
cc->emit(inst.pmax, dst, src1v, src2);
cc->emit(inst.peq, dst, dst, src1v);
}
return;
}
case UniOpVVV::kCmpLtI8:
case UniOpVVV::kCmpLtI16:
case UniOpVVV::kCmpLtI32:
case UniOpVVV::kCmpLtI64:
case UniOpVVV::kCmpGeI64: {
if (!src2.is_reg()) {
Vec tmp = new_similar_reg(dst, "@tmp");
avx_mov(*this, tmp, src2);
src2 = tmp;
}
CmpMinMaxInst inst = avx_cmp_min_max[(size_t(op) - size_t(UniOpVVV::kCmpLtI8)) & 0x7u];
cc->emit(inst.pgt, dst, src2, src1v);
if (op == UniOpVVV::kCmpGeI64) {
avx_bit_not(*this, dst, dst);
}
return;
}
case UniOpVVV::kCmpLtU8:
case UniOpVVV::kCmpLtU16:
case UniOpVVV::kCmpLtU32:
case UniOpVVV::kCmpLtU64:
case UniOpVVV::kCmpGeU64: {
Vec tmp = new_similar_reg(dst, "@tmp");
avx_isign_flip(*this, tmp, src2, ElementSize(op_info.element_size));
avx_isign_flip(*this, dst, src1v, ElementSize(op_info.element_size));
CmpMinMaxInst inst = avx_cmp_min_max[(size_t(op) - size_t(UniOpVVV::kCmpLtI8)) & 0x7u];
cc->emit(inst.pgt, dst, tmp, dst);
if (op == UniOpVVV::kCmpGeU64) {
avx_bit_not(*this, dst, dst);
}
return;
}
case UniOpVVV::kCmpLeI8:
case UniOpVVV::kCmpLeU8:
case UniOpVVV::kCmpLeI16:
case UniOpVVV::kCmpLeU16:
case UniOpVVV::kCmpLeI32:
case UniOpVVV::kCmpLeU32: {
CmpMinMaxInst inst = avx_cmp_min_max[(size_t(op) - size_t(UniOpVVV::kCmpLeI8)) & 0x7u];
if (dst.id() == src1v.id()) {
if (!src2.is_reg()) {
Vec tmp = new_similar_reg(dst, "@tmp");
cc->emit(inst.pmin, tmp, src1v, src2);
cc->emit(inst.peq, dst, tmp, src1v);
}
else {
cc->emit(inst.pmax, dst, src1v, src2);
cc->emit(inst.peq, dst, dst, src2);
}
}
else {
cc->emit(inst.pmin, dst, src1v, src2);
cc->emit(inst.peq, dst, dst, src1v);
}
return;
}
case UniOpVVV::kCmpLeI64: {
cc->emit(Inst::kIdVpcmpgtq, dst, src1v, src2);
avx_bit_not(*this, dst, dst);
return;
}
case UniOpVVV::kHAddF64: {
if (has_avx512() && dst.is_vec512()) {
// [B A] [C A]
// [D C] -> [D B]
Vec tmp = new_similar_reg(dst, "@tmp");
cc->emit(Inst::kIdVunpckhpd, tmp, src1v, src2);
cc->emit(Inst::kIdVunpcklpd, dst, src1v, src2);
cc->vaddpd(dst, dst, tmp);
}
else {
cc->emit(inst_id, dst, src1v, src2);
}
return;
}
case UniOpVVV::kCombineLoHiU64:
case UniOpVVV::kCombineLoHiF64: {
// Intrinsic - dst = {src1.u64[0], src2.64[1]} - combining low part of src1 and high part of src1.
if (!src2.is_reg()) {
Vec tmp = new_similar_reg(dst, "@tmp");
avx_mov(*this, tmp, src2);
src2 = tmp;
}
uint32_t shuf_imm = shuf_imm2_from_swizzle_with_width(swizzle(0, 1), VecWidthUtils::vec_width_of(dst));
cc->emit(Inst::kIdVshufpd, dst, src2, src1v, shuf_imm);
return;
}
case UniOpVVV::kCombineHiLoU64:
case UniOpVVV::kCombineHiLoF64: {
// Intrinsic - dst = {src1.u64[1], src2.u64[0]} - combining high part of src1 and low part of src2.
if (dst.is_vec128()) {
if (src2.is_vec())
cc->emit(Inst::kIdVmovsd, dst, src1v.xmm(), src2.as<Vec>().xmm());
else
cc->emit(Inst::kIdVmovlpd, dst, src1v.xmm(), src2);
return;
}
if (!src2.is_reg()) {
Vec tmp = new_similar_reg(dst, "@tmp");
avx_mov(*this, tmp, src2);
src2 = tmp;
}
uint32_t shuf_imm = shuf_imm2_from_swizzle_with_width(swizzle(1, 0), VecWidthUtils::vec_width_of(dst));
cc->emit(Inst::kIdVshufpd, dst, src2, src1v, shuf_imm);
return;
}
default:
ASMJIT_NOT_REACHED();
}
}
else {
// SSE Implementation
// ------------------
InstId inst_id = op_info.sse_inst_id;
// SSE floating point comparison cannot use the extended predicates as introduced by AVX.
static constexpr uint8_t sse_fcmp_imm_table[] = {
0x00u, // kCmpEq (eq ordered quiet).
0x04u, // kCmpNe (ne ordered quiet).
0x01u, // kCmpGt (lt ordered quiet <reversed>).
0x02u, // kCmpGe (le ordered quiet <reversed>).
0x01u, // kCmpLt (lt ordered quiet).
0x02u, // kCmpLe (le ordered quiet).
0x07u, // kCmpOrd (ordered quiet).
0x03u // kCmpUnord (unordered quiet).
};
if (is_same_vec(dst, src2) && op_info.commutative) {
std::swap(src1v, src2.as<Vec>());
}
if (is_same_vec(src1v, src2)) {
switch (SameVecOp(op_info.same_vec_op)) {
case SameVecOp::kZero:
cc->emit(Inst::kIdPxor, dst, dst);
return;
case SameVecOp::kOnes:
cc->emit(Inst::kIdPcmpeqb, dst, dst);
return;
case SameVecOp::kSrc:
sse_mov(*this, dst, src1v);
return;
default:
break;
}
}
if (has_sse_ext(SSEExt(op_info.sse_ext))) {
ASMJIT_ASSERT(inst_id != Inst::kIdNone);
if (!is_same_vec(dst, src1v)) {
if (is_same_vec(dst, src2)) {
Vec tmp = new_similar_reg(dst, "tmp");
sse_mov(*this, tmp, src2);
src2 = tmp;
}
sse_mov(*this, dst, src1v);
}
if (op_info.use_imm)
cc->emit(inst_id, dst, src2, Imm(op_info.imm));
else
cc->emit(inst_id, dst, src2);
return;
}
switch (op) {
case UniOpVVV::kBicU32:
case UniOpVVV::kBicU64:
case UniOpVVV::kBicF32:
case UniOpVVV::kBicF64: {
if (is_same_vec(dst, src2)) {
cc->emit(inst_id, dst, src1v);
return;
}
if (is_same_vec(dst, src1v)) {
Vec tmp = new_similar_reg(dst);
sse_mov(*this, tmp, src1v);
src1v = tmp;
}
sse_mov(*this, dst, src2);
cc->emit(inst_id, dst, src1v);
return;
}
// dst = a - (floor(a / b) * b).
case UniOpVVV::kModF32S:
case UniOpVVV::kModF64S:
case UniOpVVV::kModF32:
case UniOpVVV::kModF64: {
FloatMode fm = FloatMode(op_info.float_mode);
UniOpVV trunc_op = translate_op(op, UniOpVVV::kModF32S, UniOpVV::kTruncF32);
const FloatInst& fi = sse_float_inst[fm];
x86::Vec tmp = new_similar_reg(dst, "@mod_tmp");
cc->emit(fi.fmova, tmp, src1v);
cc->emit(fi.fdiv, tmp, src2);
emit_2v(trunc_op, tmp, tmp);
cc->emit(fi.fmul, tmp, src2);
sse_fmov(*this, dst, src1v, fm);
cc->emit(fi.fsub, dst, tmp);
return;
}
case UniOpVVV::kMulU32: {
// Native operation requires SSE4.1, which is not supported by the target.
Vec tmp1 = new_similar_reg(dst, "tmp1");
Vec tmp2 = new_similar_reg(dst, "tmp2");
cc->emit(Inst::kIdPshufd, tmp1, src1v, x86::shuffle_imm(3, 3, 1, 1));
cc->emit(Inst::kIdPshufd, tmp2, src2, x86::shuffle_imm(3, 3, 1, 1));
cc->emit(Inst::kIdPmuludq, tmp1, tmp2);
sse_mov(*this, dst, src1v);
cc->emit(Inst::kIdPmuludq, dst, src2);
cc->emit(Inst::kIdShufps, dst, tmp1, x86::shuffle_imm(2, 0, 2, 0));
cc->emit(Inst::kIdPshufd, dst, dst, x86::shuffle_imm(3, 1, 2, 0));
return;
}
case UniOpVVV::kMulU64: {
// Native operation requires AVX512, which is not supported by the target.
Vec al_bh = new_similar_reg(dst, "@al_bh");
Vec ah_bl = new_similar_reg(dst, "@ah_bl");
cc->emit(Inst::kIdPshufd, al_bh, src2, x86::shuffle_imm(3, 3, 1, 1));
cc->emit(Inst::kIdPshufd, ah_bl, src1v, x86::shuffle_imm(3, 3, 1, 1));
cc->emit(Inst::kIdPmuludq, al_bh, src1v);
cc->emit(Inst::kIdPmuludq, ah_bl, src2);
cc->emit(Inst::kIdPaddq, al_bh, ah_bl);
sse_mov(*this, dst, src1v);
cc->emit(Inst::kIdPmuludq, dst, src2);
cc->emit(Inst::kIdPsllq, al_bh, 32);
cc->emit(Inst::kIdPaddq, dst, al_bh);
return;
}
case UniOpVVV::kMulU64_LoU32: {
Vec tmp = new_similar_reg(dst.as<Vec>(), "@tmp");
cc->emit(Inst::kIdPshufd, tmp, src1v, x86::shuffle_imm(2, 3, 0, 1));
cc->emit(Inst::kIdPmuludq, tmp, src2);
if (dst.id() == src2.id()) {
cc->emit(Inst::kIdPmuludq, dst, src1v);
}
else {
sse_mov(*this, dst, src1v);
cc->emit(Inst::kIdPmuludq, dst, src2);
}
cc->emit(Inst::kIdPsllq, tmp, 32);
cc->emit(Inst::kIdPaddq, dst, tmp);
return;
}
// Native operation requires AVX512, which is not supported by the target.
case UniOpVVV::kMinI64:
if (!has_sse4_2()) {
Vec msk = new_vec128("@msk");
sse_cmp_gt_i64(*this, msk, src2, src1v);
sse_select(*this, dst, src1v, src2, msk);
return;
}
[[fallthrough]];
case UniOpVVV::kMinI8:
case UniOpVVV::kMinI32: {
// Native operation requires SSE4.1, which is not supported by the target.
InstId cmp_inst_id = op == UniOpVVV::kMinI8 ? Inst::kIdPcmpgtb :
op == UniOpVVV::kMinI32 ? Inst::kIdPcmpgtd : Inst::kIdPcmpgtq;
Vec msk = new_vec128("@msk");
cc->emit(Inst::kIdMovaps, msk, src2);
cc->emit(cmp_inst_id, msk, src1v);
sse_select(*this, dst, src1v, src2, msk);
return;
}
case UniOpVVV::kMaxI64:
// Native operation requires AVX512, which is not supported by the target.
if (!has_sse4_2()) {
Vec msk = new_vec128("@msk");
sse_cmp_gt_i64(*this, msk, src1v, src2);
sse_select(*this, dst, src1v, src2, msk);
return;
}
[[fallthrough]];
case UniOpVVV::kMaxI8:
case UniOpVVV::kMaxI32: {
// Native operation requires SSE4.1, which is not supported by the target.
InstId cmp_inst_id = op == UniOpVVV::kMaxI8 ? Inst::kIdPcmpgtb :
op == UniOpVVV::kMaxI32 ? Inst::kIdPcmpgtd : Inst::kIdPcmpgtq;
Vec msk = new_vec128("@msk");
cc->emit(Inst::kIdMovaps, msk, src1v);
cc->emit(cmp_inst_id, msk, src2);
sse_select(*this, dst, src1v, src2, msk);
return;
}
case UniOpVVV::kMinU16: {
// Native operation requires SSE4.1, which is not supported by the target.
Vec tmp = new_vec128("@tmp");
cc->emit(Inst::kIdMovaps, tmp, src1v);
cc->emit(Inst::kIdPsubusw, tmp, src2);
sse_mov(*this, dst, src1v);
cc->emit(Inst::kIdPsubw, dst, tmp);
return;
}
case UniOpVVV::kMaxU16: {
// Native operation requires SSE4.1, which is not supported by the target.
sse_mov(*this, dst, src1v);
cc->emit(Inst::kIdPsubusw, dst, src2);
cc->emit(Inst::kIdPaddw, dst, src2);
return;
}
case UniOpVVV::kMinU32:
case UniOpVVV::kMaxU32: {
// Native operation requires SSE4.1, which is not supported by the target.
Operand flip_mask = simd_const(&ct().p_8000000080000000, Bcst::kNA, dst);
Vec tmp1 = new_similar_reg(dst, "@tmp1");
Vec tmp2 = new_similar_reg(dst, "@tmp2");
if (op == UniOpVVV::kMinU32) {
sse_mov(*this, tmp1, src2);
sse_mov(*this, tmp2, src1v);
}
else {
sse_mov(*this, tmp1, src1v);
sse_mov(*this, tmp2, src2);
}
cc->emit(Inst::kIdPxor, tmp1, flip_mask);
cc->emit(Inst::kIdPxor, tmp2, flip_mask);
cc->emit(Inst::kIdPcmpgtd, tmp1, tmp2);
sse_select(*this, dst, src1v, src2, tmp1);
return;
}
case UniOpVVV::kMinU64: {
// Native operation requires AVX512, which is not supported by the target.
Vec msk = new_similar_reg(dst, "@tmp1");
sse_cmp_gt_u64(*this, msk, src2, src1v);
sse_select(*this, dst, src1v, src2, msk);
return;
}
case UniOpVVV::kMaxU64: {
// Native operation requires AVX512, which is not supported by the target.
Vec msk = new_similar_reg(dst, "@tmp1");
sse_cmp_gt_u64(*this, msk, src1v, src2);
sse_select(*this, dst, src1v, src2, msk);
return;
}
case UniOpVVV::kCmpEqU64: {
// Native operation requires SSE4.1, which is not supported by the target.
Vec tmp = new_similar_reg(dst, "@tmp");
sse_mov(*this, dst, src1v);
cc->emit(Inst::kIdPcmpeqd, dst, src2);
cc->emit(Inst::kIdPshufd, tmp, dst, x86::shuffle_imm(2, 3, 0, 1));
cc->emit(Inst::kIdPand, dst, tmp);
return;
}
case UniOpVVV::kCmpGtI64: {
// Native operation requires SSE4.2, which is not supported by the target.
sse_cmp_gt_i64(*this, dst, src1v, src2);
return;
}
case UniOpVVV::kCmpGtU8:
case UniOpVVV::kCmpGtU16:
case UniOpVVV::kCmpGtU32: {
CmpMinMaxInst inst = sse_cmp_min_max[size_t(op) - size_t(UniOpVVV::kCmpGtI8)];
if (has_sse4_1() || op == UniOpVVV::kCmpGtU8) {
if (dst.id() == src1v.id()) {
Vec tmp = new_similar_reg(dst, "@tmp");
cc->emit(Inst::kIdMovaps, tmp, src1v);
cc->emit(inst.pmin, tmp, src2);
cc->emit(inst.peq, dst, tmp);
}
else if (is_same_vec(dst, src2)) {
cc->emit(inst.pmin, dst, src1v);
cc->emit(inst.peq, dst, src1v);
}
else {
cc->emit(Inst::kIdMovaps, dst, src1v);
cc->emit(inst.pmin, dst, src2);
cc->emit(inst.peq, dst, src1v);
}
sse_bit_not(*this, dst, dst);
return;
}
Vec tmp = new_similar_reg(dst, "@tmp");
sse_msb_flip(*this, tmp, src2, ElementSize(op_info.element_size));
sse_msb_flip(*this, dst, src1v, ElementSize(op_info.element_size));
cc->emit(inst.pgt, dst, tmp);
return;
}
case UniOpVVV::kCmpGtU64: {
// Native operation requires AVX512, which is not supported by the target.
sse_cmp_gt_u64(*this, dst, src1v, src2);
return;
}
case UniOpVVV::kCmpGeI8:
case UniOpVVV::kCmpGeU8:
case UniOpVVV::kCmpGeI16:
case UniOpVVV::kCmpGeU16:
case UniOpVVV::kCmpGeI32:
case UniOpVVV::kCmpGeU32:
// Native operation requires AVX512, which is not supported by the target.
if (has_sse4_1() || op == UniOpVVV::kCmpGeU8 || op == UniOpVVV::kCmpGeI16) {
CmpMinMaxInst inst = sse_cmp_min_max[size_t(op) - size_t(UniOpVVV::kCmpGeI8)];
if (dst.id() == src1v.id()) {
Vec tmp = new_similar_reg(dst, "@tmp");
cc->emit(Inst::kIdMovaps, tmp, src1v);
cc->emit(inst.pmax, tmp, src2);
cc->emit(inst.peq, dst, tmp);
}
else if (is_same_vec(dst, src2)) {
cc->emit(inst.pmax, dst, src1v);
cc->emit(inst.peq, dst, src1v);
}
else {
cc->emit(Inst::kIdMovaps, dst, src1v);
cc->emit(inst.pmax, dst, src2);
cc->emit(inst.peq, dst, src1v);
}
return;
}
if (op == UniOpVVV::kCmpGeU16) {
Vec tmp = new_similar_reg(dst, "@tmp");
sse_mov(*this, tmp, src1v);
cc->emit(Inst::kIdPsubusw, tmp, src2);
cc->emit(Inst::kIdPaddw, tmp, src2);
sse_mov(*this, dst, src1v);
cc->emit(Inst::kIdPcmpeqw, dst, tmp);
return;
}
[[fallthrough]];
case UniOpVVV::kCmpGeI64:
case UniOpVVV::kCmpGeU64:
// Native operation requires AVX512, which is not supported by the target.
if (src2.is_mem()) {
Vec tmp = new_similar_reg(dst, "@tmp");
sse_mov(*this, tmp, src2);
src2 = tmp;
}
switch (op) {
case UniOpVVV::kCmpGeI8: v_cmp_gt_i8(dst, src2, src1v); break;
case UniOpVVV::kCmpGeI32: v_cmp_gt_i32(dst, src2, src1v); break;
case UniOpVVV::kCmpGeU32: v_cmp_gt_u32(dst, src2, src1v); break;
case UniOpVVV::kCmpGeI64: v_cmp_gt_i64(dst, src2, src1v); break;
case UniOpVVV::kCmpGeU64: v_cmp_gt_u64(dst, src2, src1v); break;
default:
ASMJIT_NOT_REACHED();
}
sse_bit_not(*this, dst, dst);
return;
case UniOpVVV::kCmpLtI8:
case UniOpVVV::kCmpLtI16:
case UniOpVVV::kCmpLtI32: {
if (is_same_vec(dst, src1v)) {
Vec tmp = new_similar_reg(dst, "@tmp");
sse_mov(*this, tmp, src1v);
src1v = tmp;
}
sse_mov(*this, dst, src2);
cc->emit(inst_id, dst, src1v);
return;
}
case UniOpVVV::kCmpLtU8:
case UniOpVVV::kCmpLtU16:
case UniOpVVV::kCmpLtU32: {
Vec tmp = new_similar_reg(dst, "@tmp");
sse_mov(*this, tmp, src1v);
sse_msb_flip(*this, tmp, src1v, ElementSize(op_info.element_size));
sse_msb_flip(*this, dst, src2, ElementSize(op_info.element_size));
cc->emit(inst_id, dst, tmp);
return;
}
case UniOpVVV::kCmpLtI64: {
// Native operation requires AVX512, which is not supported by the target.
sse_cmp_gt_i64(*this, dst, src2, src1v);
return;
}
case UniOpVVV::kCmpLtU64: {
// Native operation requires AVX512, which is not supported by the target.
sse_cmp_gt_u64(*this, dst, src2, src1v);
return;
}
case UniOpVVV::kCmpLeU8: {
if (is_same_vec(dst, src2)) {
Vec tmp = new_similar_reg(dst, "@tmp");
sse_mov(*this, tmp, src2);
src2 = tmp;
}
sse_mov(*this, dst, src1v);
cc->emit(Inst::kIdPsubusb, dst, src2);
Vec zeros = simd_vec_const(&ct().p_0000000000000000, Bcst::k32, dst);
cc->emit(Inst::kIdPcmpeqb, dst, zeros);
return;
}
case UniOpVVV::kCmpLeI8:
case UniOpVVV::kCmpLeI16:
case UniOpVVV::kCmpLeU16:
case UniOpVVV::kCmpLeI32:
case UniOpVVV::kCmpLeU32:
if (has_sse4_1() || op == UniOpVVV::kCmpLeU8 || op == UniOpVVV::kCmpLeI16) {
CmpMinMaxInst inst = sse_cmp_min_max[size_t(op) - size_t(UniOpVVV::kCmpLeI8)];
if (dst.id() == src1v.id()) {
Vec tmp = new_similar_reg(dst, "@tmp");
cc->emit(Inst::kIdMovaps, tmp, src1v);
cc->emit(inst.pmin, tmp, src2);
cc->emit(inst.peq, dst, tmp);
}
else if (is_same_vec(dst, src2)) {
cc->emit(inst.pmin, dst, src1v);
cc->emit(inst.peq, dst, src1v);
}
else {
cc->emit(Inst::kIdMovaps, dst, src1v);
cc->emit(inst.pmin, dst, src2);
cc->emit(inst.peq, dst, src1v);
}
return;
}
[[fallthrough]];
case UniOpVVV::kCmpLeI64:
case UniOpVVV::kCmpLeU64:
switch (op) {
case UniOpVVV::kCmpLeI8: v_cmp_gt_i8(dst, src1v, src2); break;
case UniOpVVV::kCmpLeU16: v_cmp_gt_u16(dst, src1v, src2); break;
case UniOpVVV::kCmpLeI32: v_cmp_gt_i32(dst, src1v, src2); break;
case UniOpVVV::kCmpLeU32: v_cmp_gt_u32(dst, src1v, src2); break;
case UniOpVVV::kCmpLeI64: v_cmp_gt_i64(dst, src1v, src2); break;
case UniOpVVV::kCmpLeU64: v_cmp_gt_u64(dst, src1v, src2); break;
default:
ASMJIT_NOT_REACHED();
}
sse_bit_not(*this, dst, dst);
return;
case UniOpVVV::kCmpLtF32S:
case UniOpVVV::kCmpLtF64S:
case UniOpVVV::kCmpLtF32:
case UniOpVVV::kCmpLtF64:
case UniOpVVV::kCmpLeF32S:
case UniOpVVV::kCmpLeF64S:
case UniOpVVV::kCmpLeF32:
case UniOpVVV::kCmpLeF64:
if (is_same_vec(dst, src2)) {
uint8_t pred = sse_fcmp_imm_table[(size_t(op) - size_t(UniOpVVV::kCmpEqF32S)) / 4u];
// Unfortunately we have to do two moves, because there are no predicates that
// we could use in case of reversed operands (AVX is much better in this regard).
Vec tmp = new_similar_reg(dst, "@tmp");
sse_mov(*this, tmp, src2);
sse_mov(*this, dst, src1v);
cc->emit(inst_id, dst, tmp, pred);
return;
}
[[fallthrough]];
case UniOpVVV::kCmpEqF32S:
case UniOpVVV::kCmpEqF64S:
case UniOpVVV::kCmpEqF32:
case UniOpVVV::kCmpEqF64:
case UniOpVVV::kCmpNeF32S:
case UniOpVVV::kCmpNeF64S:
case UniOpVVV::kCmpNeF32:
case UniOpVVV::kCmpNeF64:
case UniOpVVV::kCmpOrdF32S:
case UniOpVVV::kCmpOrdF64S:
case UniOpVVV::kCmpOrdF32:
case UniOpVVV::kCmpOrdF64:
case UniOpVVV::kCmpUnordF32S:
case UniOpVVV::kCmpUnordF64S:
case UniOpVVV::kCmpUnordF32:
case UniOpVVV::kCmpUnordF64: {
uint8_t pred = sse_fcmp_imm_table[(size_t(op) - size_t(UniOpVVV::kCmpEqF32S)) / 4u];
sse_mov(*this, dst, src1v);
cc->emit(inst_id, dst, src2, pred);
return;
}
case UniOpVVV::kCmpGtF32S:
case UniOpVVV::kCmpGtF64S:
case UniOpVVV::kCmpGtF32:
case UniOpVVV::kCmpGtF64:
case UniOpVVV::kCmpGeF32S:
case UniOpVVV::kCmpGeF64S:
case UniOpVVV::kCmpGeF32:
case UniOpVVV::kCmpGeF64: {
// Since SSE compare doesn't provide these modes natively, we have to reverse the operands.
uint8_t pred = sse_fcmp_imm_table[(size_t(op) - size_t(UniOpVVV::kCmpEqF32S)) / 4u];
if (dst.id() != src1v.id()) {
sse_mov(*this, dst, src2);
cc->emit(inst_id, dst, src1v, pred);
}
else {
Vec tmp = new_similar_reg(dst, "@tmp");
sse_mov(*this, tmp, src2);
cc->emit(inst_id, tmp, src1v, pred);
sse_mov(*this, dst, tmp);
}
return;
}
case UniOpVVV::kHAddF64: {
// Native operation requires SSE3, which is not supported by the target.
if (is_same_vec(src1v, src2)) {
if (is_same_vec(dst, src1v)) {
Vec tmp = cc->new_similar_reg(dst, "@tmp");
v_swap_f64(tmp, dst);
cc->addpd(dst, tmp);
}
else {
v_swap_f64(dst, src1v);
cc->addpd(dst, src1v);
}
}
else {
// [B A] [C A]
// [D C] -> [D B]
Vec tmp = new_similar_reg(dst, "@tmp");
if (src2.is_mem()) {
Mem m(src2.as<Mem>());
sse_mov(*this, dst, src1v);
v_swap_f64(tmp, dst);
cc->movhpd(dst, m);
m.add_offset(8);
cc->movhpd(tmp, m);
cc->addpd(dst, tmp);
}
else if (is_same_vec(dst, src2)) {
sse_mov(*this, tmp, src1v);
cc->unpcklpd(tmp, src2.as<Vec>());
cc->movhlps(dst, src1v);
cc->addpd(dst, tmp);
}
else {
sse_mov(*this, tmp, src1v);
cc->unpckhpd(tmp, src2.as<Vec>());
sse_mov(*this, dst, src1v);
cc->unpcklpd(dst, src2.as<Vec>());
cc->addpd(dst, tmp.as<Vec>());
}
}
return;
}
case UniOpVVV::kCombineLoHiU64:
case UniOpVVV::kCombineLoHiF64: {
// Intrinsic - dst = {src1.u64[0], src2.64[1]} - combining low part of src1 and high part of src1.
if (src2.is_mem()) {
Mem m = src2.as<Mem>().clone_adjusted(8);
cc->emit(Inst::kIdPshufd, dst, src1v, x86::shuffle_imm(1, 0, 1, 0));
cc->emit(Inst::kIdMovlpd, dst, m);
return;
}
if (is_same_vec(dst, src2)) {
// dst = {src1.u64[0], dst.u64[1]}
cc->emit(Inst::kIdShufpd, dst, src1v, x86::shuffle_imm(0, 1));
return;
}
else if (is_same_vec(dst, src1v)) {
// dst = {dst.u64[0], src2.u64[1]}
if (has_ssse3()) {
cc->emit(Inst::kIdPalignr, dst, src2, 8);
return;
}
}
if (has_sse3())
cc->emit(Inst::kIdMovddup, dst, src1v);
else
cc->emit(Inst::kIdPshufd, dst, src1v, x86::shuffle_imm(1, 0, 1, 0));
cc->emit(Inst::kIdMovhlps, dst, src2);
return;
}
case UniOpVVV::kCombineHiLoU64:
case UniOpVVV::kCombineHiLoF64: {
// Intrinsic - dst = {src1.u64[1], src2.64[0]} - combining high part of src1 and low part of src2.
if (src2.is_mem()) {
sse_mov(*this, dst, src1v);
cc->emit(Inst::kIdMovlpd, dst, src2);
}
else if (is_same_vec(dst, src2)) {
// dst = {src1.u64[1], dst.u64[0]}
cc->emit(Inst::kIdShufpd, dst, src1v, 0x2);
}
else {
// dst = {src1.u64[1], src2.u64[0]}
sse_mov(*this, dst, src1v);
cc->emit(Inst::kIdMovsd, dst, src2);
}
return;
}
case UniOpVVV::kPacksI32_U16: {
// Native operation requires SSE4.1, which is not supported by the target.
// NOTE: This one is generally tricky and involves a lot of operations. There are hacks available to shorten the
// sequence, but then it would not cover all the inputs, so this is essentially a code necessary to handle all of
// them. The trick here is to perform unsigned saturation first (that's why we fill one reg with MSB bits of the
// input and then use ANDN), and then to bias the input in a way to make the result use signed saturation. The
// last step is to convert the biased value back.
//
// In general, if you hit this code-path (not having SSE4.1 and still needing exactly this instruction) I would
// recommend using a different strategy in this case, completely avoiding this code path. Usually, inputs are not
// arbitrary and knowing the range could help a lot to reduce the approach to use a native 'packssdw' instruction.
Operand bias = simd_const(&ct().p_0000800000008000, Bcst::kNA, dst);
Operand unbias = simd_const(&ct().p_8000800080008000, Bcst::kNA, dst);
if (is_same_vec(src1v, src2)) {
Vec tmp = dst;
if (is_same_vec(dst, src1v))
tmp = new_similar_reg(dst, "@tmp1");
sse_mov(*this, tmp, src1v);
cc->emit(Inst::kIdPsrad, tmp, 31);
cc->emit(Inst::kIdPandn, tmp, src1v);
cc->emit(Inst::kIdPsubd, tmp, bias);
cc->emit(Inst::kIdPackssdw, tmp, tmp);
cc->emit(Inst::kIdPaddw, tmp, unbias);
sse_mov(*this, dst, tmp);
}
else {
Vec tmp1 = new_similar_reg(dst, "@tmp1");
Vec tmp2 = new_similar_reg(dst, "@tmp2");
sse_mov(*this, tmp1, src1v);
sse_mov(*this, tmp2, src2);
cc->emit(Inst::kIdPsrad, tmp1, 31);
cc->emit(Inst::kIdPsrad, tmp2, 31);
cc->emit(Inst::kIdPandn, tmp1, src1v);
cc->emit(Inst::kIdPandn, tmp2, src2);
cc->emit(Inst::kIdPsubd, tmp1, bias);
cc->emit(Inst::kIdPsubd, tmp2, bias);
cc->emit(Inst::kIdPackssdw, tmp1, tmp2);
cc->emit(Inst::kIdPaddw, tmp1, unbias);
sse_mov(*this, dst, tmp1);
}
return;
}
case UniOpVVV::kSwizzlev_U8: {
// Native operation requires SSSE3, which is not supported by the target.
//
// NOTE: This is basically a very slow emulation as there is no way how to implement this operation with SSE2 SIMD.
Mem m_data = tmp_stack(StackId::kCustom, 64);
Mem m_pred = m_data.clone_adjusted(32);
m_data.set_size(1);
m_pred.set_size(1);
cc->movaps(m_data, src1v);
// The trick is to AND all indexes by 0x0F and then to do unsigned minimum so all indexes are in [0, 17) range,
// where index 16 maps to zero.
Vec tmp = new_similar_reg(dst, "@tmp");
cc->vmovaps(tmp, simd_mem_const(&ct().p_0F0F0F0F0F0F0F0F, Bcst::kNA, tmp));
cc->pand(tmp, src2.as<Vec>());
cc->pminub(tmp, simd_mem_const(&ct().p_1010101010101010, Bcst::kNA, tmp));
cc->movaps(m_pred, tmp);
cc->mov(m_data.clone_adjusted(16), 0);
Gp acc = new_gpz("@acc");
Gp idx = new_gpz("@idx");
// Process 2 bytes at a time, then use PINSRW to merge them with the destination.
for (uint32_t i = 0; i < 8; i++) {
cc->movzx(acc.r32(), m_pred); m_pred.add_offset(1);
cc->movzx(idx.r32(), m_pred); m_pred.add_offset(1);
m_data.set_index(acc);
cc->movzx(acc, m_data);
m_data.set_index(idx);
cc->mov(acc.r8_hi(), m_data);
if (i == 0)
cc->movd(dst, acc.r32());
else
cc->pinsrw(dst, acc.r32(), i);
}
return;
}
default:
ASMJIT_NOT_REACHED();
}
}
}
void UniCompiler::emit_3v(UniOpVVV op, const OpArray& dst_, const Operand_& src1_, const OpArray& src2_) { emit_3v_t(*this, op, dst_, src1_, src2_); }
void UniCompiler::emit_3v(UniOpVVV op, const OpArray& dst_, const OpArray& src1_, const Operand_& src2_) { emit_3v_t(*this, op, dst_, src1_, src2_); }
void UniCompiler::emit_3v(UniOpVVV op, const OpArray& dst_, const OpArray& src1_, const OpArray& src2_) { emit_3v_t(*this, op, dst_, src1_, src2_); }
// ujit::UniCompiler - Vector Instructions - Emit 3VI
// ==================================================
void UniCompiler::emit_3vi(UniOpVVVI op, const Operand_& dst_, const Operand_& src1_, const Operand_& src2_, uint32_t imm) {
ASMJIT_ASSERT(dst_.is_vec());
ASMJIT_ASSERT(src1_.is_vec());
Vec dst(dst_.as<Vec>());
Vec src1v(src1_.as<Vec>().clone_as(dst));
Operand src2(src2_);
UniOpVInfo op_info = opcode_info_3vi[size_t(op)];
if (has_avx()) {
// AVX Implementation
// ------------------
InstId inst_id = op_info.avx_inst_id;
if (has_avx_ext(AVXExt(op_info.avx_ext))) {
ASMJIT_ASSERT(inst_id != Inst::kIdNone);
cc->emit(inst_id, dst, src1v, src2, imm);
return;
}
switch (op) {
// Intrin - short-circuit if possible based on the predicate.
case UniOpVVVI::kAlignr_U128: {
if (imm == 0) {
avx_mov(*this, dst, src2);
return;
}
if (is_same_vec(src1v, src2)) {
if (imm == 4 || imm == 8 || imm == 12) {
uint32_t pred = imm == 4 ? x86::shuffle_imm(0, 3, 2, 1) :
imm == 8 ? x86::shuffle_imm(1, 0, 3, 2) :
imm == 12 ? x86::shuffle_imm(2, 1, 0, 3) : 0;
cc->vpshufd(dst, src1v, pred);
return;
}
}
cc->emit(Inst::kIdVpalignr, dst, src1v, src2, imm);
return;
}
// Intrin - maps directly to the corresponding instruction, but imm must be converted.
case UniOpVVVI::kInterleaveShuffleU32x4:
case UniOpVVVI::kInterleaveShuffleF32x4: {
if (is_same_vec(src1v, src2)) {
UniOpVVI simplified_op = (op == UniOpVVVI::kInterleaveShuffleU32x4) ? UniOpVVI::kSwizzleU32x4 : UniOpVVI::kSwizzleF32x4;
emit_2vi(simplified_op, dst, src1v, imm);
}
else {
uint32_t shuf_imm = shuf_imm4_from_swizzle(Swizzle4{imm});
cc->emit(inst_id, dst, src1v, src2, shuf_imm);
}
return;
}
// Intrin - maps directly to the corresponding instruction, but imm must be converted.
case UniOpVVVI::kInterleaveShuffleU64x2:
case UniOpVVVI::kInterleaveShuffleF64x2: {
if (is_same_vec(src1v, src2)) {
UniOpVVI simplified_op = (op == UniOpVVVI::kInterleaveShuffleU64x2) ? UniOpVVI::kSwizzleU64x2 : UniOpVVI::kSwizzleF64x2;
emit_2vi(simplified_op, dst, src1v, imm);
}
else {
uint32_t shuf_imm = shuf_imm2_from_swizzle_with_width(Swizzle2{imm}, VecWidthUtils::vec_width_of(dst));
cc->emit(inst_id, dst, src1v, src2, shuf_imm);
}
return;
}
case UniOpVVVI::kInsertV128_U32:
case UniOpVVVI::kInsertV128_F32:
case UniOpVVVI::kInsertV128_U64:
case UniOpVVVI::kInsertV128_F64: {
src1v.set_signature(dst.signature());
if (src2.is_mem())
src2.as<Mem>().set_size(16);
else
src2.set_signature(signature_of_xmm_ymm_zmm[0]);
if (!has_avx512()) {
if (has_avx2() && (op == UniOpVVVI::kInsertV128_U32 || op == UniOpVVVI::kInsertV128_U64))
inst_id = Inst::kIdVinserti128;
else
inst_id = Inst::kIdVinsertf128;
}
cc->emit(inst_id, dst, src1v, src2, imm);
return;
}
case UniOpVVVI::kInsertV256_U32:
case UniOpVVVI::kInsertV256_F32:
case UniOpVVVI::kInsertV256_U64:
case UniOpVVVI::kInsertV256_F64: {
ASMJIT_ASSERT(has_avx512());
src1v.set_signature(dst.signature());
if (src2.is_mem())
src2.as<Mem>().set_size(32);
else
src2.set_signature(signature_of_xmm_ymm_zmm[1]);
cc->emit(inst_id, dst, src1v, src2, imm);
return;
}
default:
ASMJIT_NOT_REACHED();
}
}
else {
// SSE Implementation
// ------------------
InstId inst_id = op_info.sse_inst_id;
if (is_same_vec(dst, src2) && op_info.commutative) {
std::swap(src1v, src2.as<Vec>());
}
// All operations are intrinsics in this case - no direct mapping to instructions without an additional logic.
ASMJIT_ASSERT(!has_sse_ext(SSEExt(op_info.sse_ext)));
switch (op) {
// Intrin - short-circuit if possible based on the predicate.
case UniOpVVVI::kAlignr_U128: {
if (imm == 0) {
sse_mov(*this, dst, src2);
return;
}
if (is_same_vec(src1v, src2)) {
if (imm == 4 || imm == 8 || imm == 12) {
uint32_t pred = imm == 4 ? x86::shuffle_imm(0, 3, 2, 1) :
imm == 8 ? x86::shuffle_imm(1, 0, 3, 2) :
imm == 12 ? x86::shuffle_imm(2, 1, 0, 3) : 0;
cc->emit(Inst::kIdPshufd, dst, src1v, pred);
return;
}
}
if (has_ssse3()) {
if (is_same_vec(dst, src2) && !is_same_vec(dst, src1v)) {
Vec tmp = new_similar_reg(dst, "@tmp");
sse_mov(*this, tmp, src2);
src2 = tmp;
}
sse_mov(*this, dst, src1v);
cc->emit(Inst::kIdPalignr, dst, src2, imm);
return;
}
Vec tmp = new_similar_reg(dst, "@tmp");
uint32_t src1_shift = (16u - imm) & 15;
uint32_t src2_shift = imm;
if (is_same_vec(dst, src1v)) {
sse_mov(*this, tmp, src2);
cc->emit(Inst::kIdPsrldq, tmp, src2_shift);
cc->emit(Inst::kIdPslldq, dst, src1_shift);
}
else {
sse_mov(*this, tmp, src1v);
sse_mov(*this, dst, src2);
cc->emit(Inst::kIdPslldq, tmp, src1_shift);
cc->emit(Inst::kIdPsrldq, dst, src2_shift);
}
cc->emit(Inst::kIdPor, dst, tmp);
return;
}
// Intrin - maps directly to the corresponding instruction, but imm must be converted.
case UniOpVVVI::kInterleaveShuffleU32x4:
case UniOpVVVI::kInterleaveShuffleU64x2:
case UniOpVVVI::kInterleaveShuffleF32x4:
case UniOpVVVI::kInterleaveShuffleF64x2: {
uint32_t shuf_imm;
ElementSize element_size = ElementSize(op_info.element_size);
if (element_size == ElementSize::k32)
shuf_imm = shuf_imm4_from_swizzle(Swizzle4{imm});
else
shuf_imm = shuf_imm2_from_swizzle(Swizzle2{imm});
if (is_same_vec(src1v, src2)) {
UniOpVVI vvi_op = translate_op(op, UniOpVVVI::kInterleaveShuffleU32x4, UniOpVVI::kSwizzleU32x4);
emit_2vi(vvi_op, dst, src1v, imm);
return;
}
else if (is_same_vec(dst, src1v)) {
cc->emit(inst_id, dst, src2, shuf_imm);
}
else if (is_same_vec(dst, src2)) {
// The predicate has to be reversed as we want to swap low/high 64-bit lanes afterwards.
if (element_size == ElementSize::k32)
shuf_imm = (shuf_imm >> 4) | ((shuf_imm & 0xF) << 4);
else
shuf_imm = (shuf_imm >> 1) | ((shuf_imm & 0x1) << 1);
cc->emit(inst_id, dst, src1v, shuf_imm);
cc->emit(Inst::kIdPshufd, dst, dst, x86::shuffle_imm(1, 0, 3, 2));
}
else {
sse_mov(*this, dst, src1v);
cc->emit(inst_id, dst, src2, shuf_imm);
}
return;
}
case UniOpVVVI::kInsertV128_U32:
case UniOpVVVI::kInsertV128_F32:
case UniOpVVVI::kInsertV128_U64:
case UniOpVVVI::kInsertV128_F64:
case UniOpVVVI::kInsertV256_U32:
case UniOpVVVI::kInsertV256_F32:
case UniOpVVVI::kInsertV256_U64:
case UniOpVVVI::kInsertV256_F64:
// These are not available in SSE mode (256-bit vectors require AVX)
ASMJIT_NOT_REACHED();
default:
ASMJIT_NOT_REACHED();
}
}
}
void UniCompiler::emit_3vi(UniOpVVVI op, const OpArray& dst_, const Operand_& src1_, const OpArray& src2_, uint32_t imm) { emit_3vi_t(*this, op, dst_, src1_, src2_, imm); }
void UniCompiler::emit_3vi(UniOpVVVI op, const OpArray& dst_, const OpArray& src1_, const Operand_& src2_, uint32_t imm) { emit_3vi_t(*this, op, dst_, src1_, src2_, imm); }
void UniCompiler::emit_3vi(UniOpVVVI op, const OpArray& dst_, const OpArray& src1_, const OpArray& src2_, uint32_t imm) { emit_3vi_t(*this, op, dst_, src1_, src2_, imm); }
// ujit::UniCompiler - Vector Instructions - Emit 4V
// =================================================
void UniCompiler::emit_4v(UniOpVVVV op, const Operand_& dst_, const Operand_& src1_, const Operand_& src2_, const Operand_& src3_) {
ASMJIT_ASSERT(dst_.is_vec());
ASMJIT_ASSERT(src1_.is_vec());
Vec dst(dst_.as<Vec>());
Vec src1(src1_.as<Vec>().clone_as(dst));
Operand src2(src2_);
Operand src3(src3_);
UniOpVInfo op_info = opcode_info_4v[size_t(op)];
if (has_avx()) {
// AVX Implementation
// ------------------
InstId inst_id = op_info.avx_inst_id;
if (is_same_vec(dst, src2) && op_info.commutative) {
std::swap(src1, src2.as<Vec>());
}
if (has_avx_ext(AVXExt(op_info.avx_ext))) {
ASMJIT_ASSERT(inst_id != Inst::kIdNone);
cc->emit(inst_id, dst, src1, src2, src3);
return;
}
switch (op) {
case UniOpVVVV::kBlendV_U8: {
// Blend(a, b, cond) == (a & ~cond) | (b & cond)
avx_make_vec(*this, src3, dst, "msk");
cc->emit(op_info.avx_inst_id, dst, src1, src2, src3);
return;
}
case UniOpVVVV::kMAddU16:
case UniOpVVVV::kMAddU32: {
static constexpr uint16_t add_inst_table[2] = {
Inst::kIdVpaddw,
Inst::kIdVpaddd
};
Vec tmp = dst;
if (is_same_vec(dst, src3)) {
tmp = new_similar_reg(dst, "@tmp");
}
InstId add_inst_id = add_inst_table[size_t(op) - size_t(UniOpVVVV::kMAddU16)];
cc->emit(inst_id, tmp, src1, src2);
cc->emit(add_inst_id, dst, tmp, src3);
return;
}
case UniOpVVVV::kMAddF32S:
case UniOpVVVV::kMAddF64S:
case UniOpVVVV::kMAddF32:
case UniOpVVVV::kMAddF64:
case UniOpVVVV::kMSubF32S:
case UniOpVVVV::kMSubF64S:
case UniOpVVVV::kMSubF32:
case UniOpVVVV::kMSubF64:
case UniOpVVVV::kNMAddF32S:
case UniOpVVVV::kNMAddF64S:
case UniOpVVVV::kNMAddF32:
case UniOpVVVV::kNMAddF64:
case UniOpVVVV::kNMSubF32S:
case UniOpVVVV::kNMSubF64S:
case UniOpVVVV::kNMSubF32:
case UniOpVVVV::kNMSubF64: {
// 4 operand operation:
//
// madd(dst, a, b, c) -> dst = a * b + c
// msub(dst, a, b, c) -> dst = a * b - c
// nmadd(dst, a, b, c) -> dst = -a * b + c
// nmsub(dst, a, b, c) -> dst = -a * b - c
//
// 3 operand operation (FMA):
//
// vfmadd213 a, b, c -> a = a * b + c
// vfmadd132 a, b, c -> a = a * c + b
// vfmadd231 a, b, c -> a = b * c + a
// vfnmadd213 a, b, c -> a = -a * b + c
// vfnmadd132 a, b, c -> a = -a * c + b
// vfnmadd231 a, b, c -> a = -b * c + a
// vfsubd213 a, b, c -> a = a * b - c
// vfsubd132 a, b, c -> a = a * c - b
// vfsubd231 a, b, c -> a = b * c - a
// vfnsubd213 a, b, c -> a = -a * b - c
// vfnsubd132 a, b, c -> a = -a * c - b
// vfnsubd231 a, b, c -> a = -b * c - a
size_t fma_id = size_t(op) - size_t(UniOpVVVV::kMAddF32S);
FloatMode fm = FloatMode(op_info.float_mode);
if (is_scalar_fp_op(fm)) {
dst.set_signature(signature_of_xmm_ymm_zmm[0]);
src1.set_signature(signature_of_xmm_ymm_zmm[0]);
if (src2.is_vec())
src2.set_signature(signature_of_xmm_ymm_zmm[0]);
if (src3.is_vec())
src3.set_signature(signature_of_xmm_ymm_zmm[0]);
}
if (has_fma()) {
// There is a variation of instructions, which can be used, but each has only 3 operands. Since we
// allow 4 operands (having a separate desgination) we have to map our 4 operand representation to
// 3 operand representation as used by FMA.
static constexpr uint16_t fma_ab_add_c[16] = {
Inst::kIdVfmadd213ss , Inst::kIdVfmadd213sd , Inst::kIdVfmadd213ps , Inst::kIdVfmadd213pd ,
Inst::kIdVfmsub213ss , Inst::kIdVfmsub213sd , Inst::kIdVfmsub213ps , Inst::kIdVfmsub213pd ,
Inst::kIdVfnmadd213ss, Inst::kIdVfnmadd213sd, Inst::kIdVfnmadd213ps, Inst::kIdVfnmadd213pd,
Inst::kIdVfnmsub213ss, Inst::kIdVfnmsub213sd, Inst::kIdVfnmsub213ps, Inst::kIdVfnmsub213pd
};
static constexpr uint16_t fma_ac_add_b[16] = {
Inst::kIdVfmadd132ss , Inst::kIdVfmadd132sd , Inst::kIdVfmadd132ps , Inst::kIdVfmadd132pd ,
Inst::kIdVfmsub132ss , Inst::kIdVfmsub132sd , Inst::kIdVfmsub132ps , Inst::kIdVfmsub132pd ,
Inst::kIdVfnmadd132ss, Inst::kIdVfnmadd132sd, Inst::kIdVfnmadd132ps, Inst::kIdVfnmadd132pd,
Inst::kIdVfnmsub132ss, Inst::kIdVfnmsub132sd, Inst::kIdVfnmsub132ps, Inst::kIdVfnmsub132pd
};
static constexpr uint16_t fma_bc_add_a[16] = {
Inst::kIdVfmadd231ss , Inst::kIdVfmadd231sd , Inst::kIdVfmadd231ps , Inst::kIdVfmadd231pd ,
Inst::kIdVfmsub231ss , Inst::kIdVfmsub231sd , Inst::kIdVfmsub231ps , Inst::kIdVfmsub231pd ,
Inst::kIdVfnmadd231ss, Inst::kIdVfnmadd231sd, Inst::kIdVfnmadd231ps, Inst::kIdVfnmadd231pd,
Inst::kIdVfnmsub231ss, Inst::kIdVfnmsub231sd, Inst::kIdVfnmsub231ps, Inst::kIdVfnmsub231pd
};
if (is_same_vec(dst, src1)) {
if (src2.is_reg())
cc->emit(fma_ab_add_c[fma_id], dst, src2, src3);
else
cc->emit(fma_ac_add_b[fma_id], dst, src3, src2);
}
else if (is_same_vec(dst, src2)) {
cc->emit(fma_ab_add_c[fma_id], dst, src1, src3);
}
else if (is_same_vec(dst, src3)) {
cc->emit(fma_bc_add_a[fma_id], dst, src1, src2);
}
else {
avx_mov(*this, dst, src1);
if (!src2.is_reg())
cc->emit(fma_ac_add_b[fma_id], dst, src3, src2);
else if (!src3.is_reg())
cc->emit(fma_ab_add_c[fma_id], dst, src1, src3);
else
cc->emit(fma_ab_add_c[fma_id], dst, src2, src3);
}
return;
}
else {
// MAdd/MSub - native FMA not available so we have to do MUL followed by either ADD or SUB.
const FloatInst& fi = avx_float_inst[size_t(fm)];
bool mul_add = (op_info.imm & 0x01u) == 0u;
bool neg_mul = (op_info.imm & 0x02u) != 0u;
InstId fi_facc = mul_add ? fi.fadd : fi.fsub;
if (!neg_mul) {
// MAdd or MSub Operation.
if (is_same_vec(dst, src3)) {
Vec tmp = new_similar_reg(dst, "@tmp");
cc->emit(fi.fmul, tmp, src1, src2);
cc->emit(fi_facc, dst, tmp, src3);
}
else {
cc->emit(fi.fmul, dst, src1, src2);
cc->emit(fi_facc, dst, dst, src3);
}
}
else {
// NMAdd or NMSub Operation.
Vec tmp = new_similar_reg(dst, "@tmp");
avx_fsign_flip(*this, tmp, src1, fm);
cc->emit(fi.fmul, tmp, tmp, src2);
cc->emit(fi_facc, dst, tmp, src3);
}
return;
}
}
default:
ASMJIT_NOT_REACHED();
}
}
else {
// SSE Implementation
// ------------------
switch (op) {
case UniOpVVVV::kBlendV_U8: {
// Blend(a, b, cond) == (a & ~cond) | (b & cond)
if (has_sse4_1()) {
if (is_same_vec(dst, src1) || (!is_same_vec(dst, src2) && !is_same_vec(dst, src3))) {
sse_make_vec(*this, src3, "tmp");
sse_mov(*this, dst, src1);
cc->emit(op_info.sse_inst_id, dst, src2, src3);
return;
}
}
// Blend(a, b, cond) == a ^ ((a ^ b) & cond)
// == b ^ ((a ^ b) & ~cond)
if (is_same_vec(dst, src1)) {
Vec tmp = new_vec128("@tmp");
v_xor_i32(tmp, dst, src2);
v_and_i32(tmp, tmp, src3);
v_xor_i32(dst, dst, tmp);
}
else if (is_same_vec(dst, src3)) {
Vec tmp = new_vec128("@tmp");
v_xor_i32(tmp, src1, src2);
v_andn_i32(dst, dst, tmp);
v_xor_i32(dst, dst, src2);
}
else {
v_xor_i32(dst, src2, src1);
v_and_i32(dst, dst, src3);
v_xor_i32(dst, dst, src1);
}
return;
}
case UniOpVVVV::kMAddU16:
case UniOpVVVV::kMAddU32: {
Vec tmp = dst;
if (is_same_vec(dst, src3)) {
tmp = new_similar_reg(dst, "@tmp");
}
if (op == UniOpVVVV::kMAddU16) {
v_mul_u16(tmp, src1, src2);
v_add_u16(dst, tmp, src3);
}
else {
v_mul_u32(tmp, src1, src2);
v_add_u32(dst, tmp, src3);
}
return;
}
case UniOpVVVV::kMAddF32S:
case UniOpVVVV::kMAddF64S:
case UniOpVVVV::kMSubF32S:
case UniOpVVVV::kMSubF64S:
case UniOpVVVV::kMAddF32:
case UniOpVVVV::kMAddF64:
case UniOpVVVV::kMSubF32:
case UniOpVVVV::kMSubF64:
case UniOpVVVV::kNMAddF32S:
case UniOpVVVV::kNMAddF64S:
case UniOpVVVV::kNMSubF32S:
case UniOpVVVV::kNMSubF64S:
case UniOpVVVV::kNMAddF32:
case UniOpVVVV::kNMAddF64:
case UniOpVVVV::kNMSubF32:
case UniOpVVVV::kNMSubF64: {
FloatMode fm = FloatMode(op_info.float_mode);
bool mul_add = (op_info.imm & 0x01u) == 0u;
bool neg_mul = (op_info.imm & 0x02u) != 0u;
if (is_same_vec(dst, src2)) {
// Unfortunately, to follow the FMA behavior in scalar case, we have to copy.
if (fm <= FloatMode::kF64S)
src2 = sse_copy(*this, src2.as<Vec>(), "@copy_src2");
else
std::swap(src1, src2.as<Vec>());
}
const FloatInst& fi = sse_float_inst[size_t(fm)];
InstId fi_facc = mul_add ? fi.fadd : fi.fsub;
if (is_same_vec(dst, src3)) {
if (fm <= FloatMode::kF64S || !mul_add) {
// Copy if we couldn't avoid the extra move.
src3 = sse_copy(*this, src3.as<Vec>(), "@copy_src3");
}
else {
Vec tmp = cc->new_similar_reg(dst, "@tmp");
sse_mov(*this, tmp, src1);
cc->emit(fi.fmul, tmp, src2);
cc->emit(neg_mul ? fi.fsub : fi.fadd, dst, tmp);
return;
}
}
if (neg_mul)
sse_fsign_flip(*this, dst, src1, fm);
else
sse_mov(*this, dst, src1);
cc->emit(fi.fmul, dst, src2);
cc->emit(fi_facc, dst, src3);
return;
}
default:
ASMJIT_NOT_REACHED();
}
}
}
void UniCompiler::emit_4v(UniOpVVVV op, const OpArray& dst_, const Operand_& src1_, const Operand_& src2_, const OpArray& src3_) { emit_4v_t(*this, op, dst_, src1_, src2_, src3_); }
void UniCompiler::emit_4v(UniOpVVVV op, const OpArray& dst_, const Operand_& src1_, const OpArray& src2_, const Operand& src3_) { emit_4v_t(*this, op, dst_, src1_, src2_, src3_); }
void UniCompiler::emit_4v(UniOpVVVV op, const OpArray& dst_, const Operand_& src1_, const OpArray& src2_, const OpArray& src3_) { emit_4v_t(*this, op, dst_, src1_, src2_, src3_); }
void UniCompiler::emit_4v(UniOpVVVV op, const OpArray& dst_, const OpArray& src1_, const Operand_& src2_, const Operand& src3_) { emit_4v_t(*this, op, dst_, src1_, src2_, src3_); }
void UniCompiler::emit_4v(UniOpVVVV op, const OpArray& dst_, const OpArray& src1_, const Operand_& src2_, const OpArray& src3_) { emit_4v_t(*this, op, dst_, src1_, src2_, src3_); }
void UniCompiler::emit_4v(UniOpVVVV op, const OpArray& dst_, const OpArray& src1_, const OpArray& src2_, const Operand& src3_) { emit_4v_t(*this, op, dst_, src1_, src2_, src3_); }
void UniCompiler::emit_4v(UniOpVVVV op, const OpArray& dst_, const OpArray& src1_, const OpArray& src2_, const OpArray& src3_) { emit_4v_t(*this, op, dst_, src1_, src2_, src3_); }
ASMJIT_END_SUB_NAMESPACE
#endif