/** ****************************************************************************** * Xenia : Xbox 360 Emulator Research Project * ****************************************************************************** * Copyright 2022 Xenia Developers. All rights reserved. * * Released under the BSD license - see LICENSE in the root for more details. * ****************************************************************************** */ #include "xenia/cpu/backend/x64/x64_sequences.h" #include #include #include "xenia/cpu/backend/x64/x64_op.h" // For OPCODE_PACK/OPCODE_UNPACK #include "third_party/half/include/half.hpp" #include "xenia/base/cvar.h" #include "xenia/cpu/backend/x64/x64_stack_layout.h" DEFINE_bool(xop_rotates, false, "rotate via xop", "x64"); DEFINE_bool(xop_left_shifts, false, "shl via xop", "x64"); DEFINE_bool(xop_right_shifts, false, "shr via xop", "x64"); DEFINE_bool(xop_arithmetic_right_shifts, false, "sar via xop", "x64"); DEFINE_bool(xop_compares, true, "compare via xop", "x64"); namespace xe { namespace cpu { namespace backend { namespace x64 { volatile int anchor_vector = 0; // ============================================================================ // OPCODE_VECTOR_CONVERT_I2F // ============================================================================ struct VECTOR_CONVERT_I2F : Sequence> { static void Emit(X64Emitter& e, const EmitArgType& i) { e.ChangeMxcsrMode(MXCSRMode::Vmx); Xmm src1 = GetInputRegOrConstant(e, i.src1, e.xmm3); // flags = ARITHMETIC_UNSIGNED if (i.instr->flags & ARITHMETIC_UNSIGNED) { // Round manually to (1.stored mantissa bits * 2^31) or to 2^32 to the // nearest even (the only rounding mode used on AltiVec) if the number is // 0x80000000 or greater, instead of converting src & 0x7FFFFFFF and then // adding 2147483648.0f, which results in double rounding that can give a // result larger than needed - see OPCODE_VECTOR_CONVERT_I2F notes. // [0x80000000, 0xFFFFFFFF] case: // Round to the nearest even, from (0x80000000 | 31 stored mantissa bits) // to ((-1 << 23) | 23 stored mantissa bits), or to 0 if the result should // be 4294967296.0f. // xmm0 = src + 0b01111111 + ((src >> 8) & 1) // (xmm1 also used to launch reg + mem early and to require it late) e.vpaddd(e.xmm1, src1, e.GetXmmConstPtr(XMMInt127)); e.vpslld(e.xmm0, src1, 31 - 8); e.vpsrld(e.xmm0, e.xmm0, 31); e.vpaddd(e.xmm0, e.xmm0, e.xmm1); // xmm0 = (0xFF800000 | 23 explicit mantissa bits), or 0 if overflowed e.vpsrad(e.xmm0, e.xmm0, 8); // Calculate the result for the [0x80000000, 0xFFFFFFFF] case - take the // rounded mantissa, and add -1 or 0 to the exponent of 32, depending on // whether the number should be (1.stored mantissa bits * 2^31) or 2^32. // xmm0 = [0x80000000, 0xFFFFFFFF] case result e.vpaddd(e.xmm0, e.xmm0, e.GetXmmConstPtr(XMM2To32)); // [0x00000000, 0x7FFFFFFF] case // (during vblendvps reg -> vpaddd reg -> vpaddd mem dependency): // Convert from signed integer to float. // xmm1 = [0x00000000, 0x7FFFFFFF] case result e.vcvtdq2ps(e.xmm1, src1); // Merge the two ways depending on whether the number is >= 0x80000000 // (has high bit set). e.vblendvps(i.dest, e.xmm1, e.xmm0, src1); } else { e.vcvtdq2ps(i.dest, src1); } } }; EMITTER_OPCODE_TABLE(OPCODE_VECTOR_CONVERT_I2F, VECTOR_CONVERT_I2F); // ============================================================================ // OPCODE_VECTOR_CONVERT_F2I // ============================================================================ struct VECTOR_CONVERT_F2I : Sequence> { static void Emit(X64Emitter& e, const EmitArgType& i) { e.ChangeMxcsrMode(MXCSRMode::Vmx); Xmm src1 = GetInputRegOrConstant(e, i.src1, e.xmm3); if (i.instr->flags & ARITHMETIC_UNSIGNED) { if (e.IsFeatureEnabled(kX64EmitAVX512Ortho)) { Opmask mask = e.k1; // Mask positive values and unordered values // _CMP_NLT_UQ e.vcmpps(mask, i.src1, e.GetXmmConstPtr(XMMZero), 0x15); // vcvttps2udq will saturate overflowing positive values and unordered // values to UINT_MAX. Mask registers will write zero everywhere // else (negative values) e.vcvttps2udq(i.dest.reg() | mask | e.T_z, i.src1); return; } // clamp to min 0 e.vmaxps(e.xmm0, src1, e.GetXmmConstPtr(XMMZero)); // xmm1 = mask of values >= (unsigned)INT_MIN e.vcmpgeps(e.xmm1, e.xmm0, e.GetXmmConstPtr(XMMPosIntMinPS)); // scale any values >= (unsigned)INT_MIN back to [0, ...] e.vsubps(e.xmm2, e.xmm0, e.GetXmmConstPtr(XMMPosIntMinPS)); e.vblendvps(e.xmm0, e.xmm0, e.xmm2, e.xmm1); // xmm0 = [0, INT_MAX] // this may still contain values > INT_MAX (if src has vals > UINT_MAX) e.vcvttps2dq(i.dest, e.xmm0); // xmm0 = mask of values that need saturation e.vpcmpeqd(e.xmm0, i.dest, e.GetXmmConstPtr(XMMIntMin)); // scale values back above [INT_MIN, UINT_MAX] e.vpand(e.xmm1, e.xmm1, e.GetXmmConstPtr(XMMIntMin)); e.vpaddd(i.dest, i.dest, e.xmm1); // saturate values > UINT_MAX e.vpor(i.dest, i.dest, e.xmm0); } else { // xmm2 = NaN mask e.vcmpunordps(e.xmm2, src1, src1); // convert packed floats to packed dwords e.vcvttps2dq(e.xmm0, src1); // (high bit) xmm1 = dest is indeterminate and i.src1 >= 0 e.vpcmpeqd(e.xmm1, e.xmm0, e.GetXmmConstPtr(XMMIntMin)); e.vpandn(e.xmm1, src1, e.xmm1); // saturate positive values e.vblendvps(i.dest, e.xmm0, e.GetXmmConstPtr(XMMIntMax), e.xmm1); // mask NaNs e.vpandn(i.dest, e.xmm2, i.dest); } } }; EMITTER_OPCODE_TABLE(OPCODE_VECTOR_CONVERT_F2I, VECTOR_CONVERT_F2I); struct VECTOR_DENORMFLUSH : Sequence> { static void Emit(X64Emitter& e, const EmitArgType& i) { e.ChangeMxcsrMode(MXCSRMode::Vmx); e.vxorps(e.xmm1, e.xmm1, e.xmm1); // 0.25 P0123 e.vandps(e.xmm0, i.src1, e.GetXmmConstPtr(XMMSingleDenormalMask)); // 0.25 P0123 e.vcmpneqps(e.xmm2, e.xmm0, e.xmm1); // 0.5 P01 // todo: xop vpcmov here e.vandps(e.xmm1, i.src1, e.GetXmmConstPtr(XMMSignMaskF32)); // 0.5 P0123 take signs, zeros // must keep their signs e.vandps(e.xmm0, i.src1, e.xmm2); // P0123 e.vorps(i.dest, e.xmm0, e.xmm1); // P0123 make sure zeros keep signs // if it does not equal zero, we stay } }; EMITTER_OPCODE_TABLE(OPCODE_VECTOR_DENORMFLUSH, VECTOR_DENORMFLUSH); // ============================================================================ // OPCODE_LOAD_VECTOR_SHL // ============================================================================ static const vec128_t lvsl_table[16] = { vec128b(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15), vec128b(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16), vec128b(2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17), vec128b(3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18), vec128b(4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19), vec128b(5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20), vec128b(6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21), vec128b(7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22), vec128b(8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23), vec128b(9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24), vec128b(10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25), vec128b(11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26), vec128b(12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27), vec128b(13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28), vec128b(14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29), vec128b(15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30), }; struct LOAD_VECTOR_SHL_I8 : Sequence> { static void Emit(X64Emitter& e, const EmitArgType& i) { if (i.src1.is_constant) { auto sh = i.src1.constant(); assert_true(sh < xe::countof(lvsl_table)); if (sh == 0) { e.vmovdqa(i.dest, e.GetXmmConstPtr(XMMLVSLTableBase)); } else { // this is probably extremely rare e.LoadConstantXmm(i.dest, lvsl_table[sh]); } } else { // TODO(benvanik): find a cheaper way of doing this. // chrispy: removed mask, ppc_emit_altivec already pre-ands it. e.vmovd(e.xmm0, i.src1.reg().cvt32()); // broadcast byte // dont use broadcastb with avx2, its slower than shuf e.vpshufb(e.xmm0, e.xmm0, e.GetXmmConstPtr(XMMZero)); e.vpaddb(i.dest, e.xmm0, e.GetXmmConstPtr(XMMLVSLTableBase)); } } }; EMITTER_OPCODE_TABLE(OPCODE_LOAD_VECTOR_SHL, LOAD_VECTOR_SHL_I8); // ============================================================================ // OPCODE_LOAD_VECTOR_SHR // ============================================================================ static const vec128_t lvsr_table[16] = { vec128b(16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31), vec128b(15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30), vec128b(14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29), vec128b(13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28), vec128b(12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27), vec128b(11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26), vec128b(10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25), vec128b(9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24), vec128b(8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23), vec128b(7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22), vec128b(6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21), vec128b(5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20), vec128b(4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19), vec128b(3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18), vec128b(2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17), vec128b(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16), }; struct LOAD_VECTOR_SHR_I8 : Sequence> { static void Emit(X64Emitter& e, const EmitArgType& i) { if (i.src1.is_constant) { auto sh = i.src1.constant(); assert_true(sh < xe::countof(lvsr_table)); if (sh == 0) { e.vmovdqa(i.dest, e.GetXmmConstPtr(XMMLVSRTableBase)); } else { e.LoadConstantXmm(i.dest, lvsr_table[sh]); } } else { // TODO(benvanik): find a cheaper way of doing this. // chrispy: removed mask, ppc_emit_altivec already pre-ands it. removed // lookup as well, compute from LVSR base instead e.vmovd(e.xmm0, i.src1.reg().cvt32()); e.vmovdqa(e.xmm1, e.GetXmmConstPtr(XMMLVSRTableBase)); // broadcast byte // dont use broadcastb with avx2, its slower than shuf e.vpshufb(e.xmm0, e.xmm0, e.GetXmmConstPtr(XMMZero)); e.vpsubb(i.dest, e.xmm1, e.xmm0); } } }; EMITTER_OPCODE_TABLE(OPCODE_LOAD_VECTOR_SHR, LOAD_VECTOR_SHR_I8); // ============================================================================ // OPCODE_VECTOR_MAX // ============================================================================ struct VECTOR_MAX : Sequence> { static void Emit(X64Emitter& e, const EmitArgType& i) { EmitCommutativeBinaryXmmOp( e, i, [&i](X64Emitter& e, Xmm dest, Xmm src1, Xmm src2) { uint32_t part_type = i.instr->flags >> 8; if (i.instr->flags & ARITHMETIC_UNSIGNED) { switch (part_type) { case INT8_TYPE: e.vpmaxub(dest, src1, src2); break; case INT16_TYPE: e.vpmaxuw(dest, src1, src2); break; case INT32_TYPE: e.vpmaxud(dest, src1, src2); break; default: assert_unhandled_case(part_type); break; } } else { switch (part_type) { case INT8_TYPE: e.vpmaxsb(dest, src1, src2); break; case INT16_TYPE: e.vpmaxsw(dest, src1, src2); break; case INT32_TYPE: e.vpmaxsd(dest, src1, src2); break; default: assert_unhandled_case(part_type); break; } } }); } }; EMITTER_OPCODE_TABLE(OPCODE_VECTOR_MAX, VECTOR_MAX); // ============================================================================ // OPCODE_VECTOR_MIN // ============================================================================ struct VECTOR_MIN : Sequence> { static void Emit(X64Emitter& e, const EmitArgType& i) { EmitCommutativeBinaryXmmOp( e, i, [&i](X64Emitter& e, Xmm dest, Xmm src1, Xmm src2) { uint32_t part_type = i.instr->flags >> 8; if (i.instr->flags & ARITHMETIC_UNSIGNED) { switch (part_type) { case INT8_TYPE: e.vpminub(dest, src1, src2); break; case INT16_TYPE: e.vpminuw(dest, src1, src2); break; case INT32_TYPE: e.vpminud(dest, src1, src2); break; default: assert_unhandled_case(part_type); break; } } else { switch (part_type) { case INT8_TYPE: e.vpminsb(dest, src1, src2); break; case INT16_TYPE: e.vpminsw(dest, src1, src2); break; case INT32_TYPE: e.vpminsd(dest, src1, src2); break; default: assert_unhandled_case(part_type); break; } } }); } }; EMITTER_OPCODE_TABLE(OPCODE_VECTOR_MIN, VECTOR_MIN); // ============================================================================ // OPCODE_VECTOR_COMPARE_EQ // ============================================================================ struct VECTOR_COMPARE_EQ_V128 : Sequence> { static void Emit(X64Emitter& e, const EmitArgType& i) { EmitCommutativeBinaryXmmOp( e, i, [&i](X64Emitter& e, Xmm dest, Xmm src1, Xmm src2) { switch (i.instr->flags) { case INT8_TYPE: e.vpcmpeqb(dest, src1, src2); break; case INT16_TYPE: e.vpcmpeqw(dest, src1, src2); break; case INT32_TYPE: e.vpcmpeqd(dest, src1, src2); break; case FLOAT32_TYPE: e.ChangeMxcsrMode(MXCSRMode::Vmx); e.vcmpeqps(dest, src1, src2); break; } }); } }; EMITTER_OPCODE_TABLE(OPCODE_VECTOR_COMPARE_EQ, VECTOR_COMPARE_EQ_V128); // ============================================================================ // OPCODE_VECTOR_COMPARE_SGT // ============================================================================ struct VECTOR_COMPARE_SGT_V128 : Sequence> { static void Emit(X64Emitter& e, const EmitArgType& i) { EmitAssociativeBinaryXmmOp( e, i, [&i](X64Emitter& e, Xmm dest, Xmm src1, Xmm src2) { switch (i.instr->flags) { case INT8_TYPE: e.vpcmpgtb(dest, src1, src2); break; case INT16_TYPE: e.vpcmpgtw(dest, src1, src2); break; case INT32_TYPE: e.vpcmpgtd(dest, src1, src2); break; case FLOAT32_TYPE: e.ChangeMxcsrMode(MXCSRMode::Vmx); e.vcmpgtps(dest, src1, src2); break; } }); } }; EMITTER_OPCODE_TABLE(OPCODE_VECTOR_COMPARE_SGT, VECTOR_COMPARE_SGT_V128); // ============================================================================ // OPCODE_VECTOR_COMPARE_SGE // ============================================================================ struct VECTOR_COMPARE_SGE_V128 : Sequence> { static void Emit(X64Emitter& e, const EmitArgType& i) { EmitAssociativeBinaryXmmOp( e, i, [&i](X64Emitter& e, Xmm dest, Xmm src1, Xmm src2) { if (cvars::xop_compares && e.IsFeatureEnabled(kX64EmitXOP)) { switch (i.instr->flags) { case INT8_TYPE: e.vpcomb(dest, src1, src2, xopcompare_e::GTE); break; case INT16_TYPE: e.vpcomw(dest, src1, src2, xopcompare_e::GTE); break; case INT32_TYPE: e.vpcomd(dest, src1, src2, xopcompare_e::GTE); break; case FLOAT32_TYPE: e.ChangeMxcsrMode(MXCSRMode::Vmx); e.vcmpgeps(dest, src1, src2); break; } } else { switch (i.instr->flags) { case INT8_TYPE: e.vpcmpeqb(e.xmm0, src1, src2); e.vpcmpgtb(dest, src1, src2); e.vpor(dest, e.xmm0); break; case INT16_TYPE: e.vpcmpeqw(e.xmm0, src1, src2); e.vpcmpgtw(dest, src1, src2); e.vpor(dest, e.xmm0); break; case INT32_TYPE: e.vpcmpeqd(e.xmm0, src1, src2); e.vpcmpgtd(dest, src1, src2); e.vpor(dest, e.xmm0); break; case FLOAT32_TYPE: e.ChangeMxcsrMode(MXCSRMode::Vmx); e.vcmpgeps(dest, src1, src2); break; } } }); } }; EMITTER_OPCODE_TABLE(OPCODE_VECTOR_COMPARE_SGE, VECTOR_COMPARE_SGE_V128); // ============================================================================ // OPCODE_VECTOR_COMPARE_UGT // ============================================================================ struct VECTOR_COMPARE_UGT_V128 : Sequence> { static void Emit(X64Emitter& e, const EmitArgType& i) { if (e.IsFeatureEnabled(kX64EmitAVX512Ortho | kX64EmitAVX512BW | kX64EmitAVX512DQ) && (i.instr->flags != FLOAT32_TYPE)) { Xmm src1 = e.xmm0; if (i.src1.is_constant) { e.LoadConstantXmm(src1, i.src1.constant()); } else { src1 = i.src1; } Xmm src2 = e.xmm1; if (i.src2.is_constant) { e.LoadConstantXmm(src2, i.src2.constant()); } else { src2 = i.src2; } switch (i.instr->flags) { case INT8_TYPE: e.vpcmpub(e.k1, src1, src2, 0x6); e.vpmovm2b(i.dest, e.k1); break; case INT16_TYPE: e.vpcmpuw(e.k1, src1, src2, 0x6); e.vpmovm2w(i.dest, e.k1); break; case INT32_TYPE: e.vpcmpud(e.k1, src1, src2, 0x6); e.vpmovm2d(i.dest, e.k1); break; default: assert_always(); break; } return; } Xbyak::Address sign_addr = e.ptr[e.rax]; // dummy switch (i.instr->flags) { case INT8_TYPE: sign_addr = e.GetXmmConstPtr(XMMSignMaskI8); break; case INT16_TYPE: sign_addr = e.GetXmmConstPtr(XMMSignMaskI16); break; case INT32_TYPE: sign_addr = e.GetXmmConstPtr(XMMSignMaskI32); break; case FLOAT32_TYPE: e.ChangeMxcsrMode(MXCSRMode::Vmx); sign_addr = e.GetXmmConstPtr(XMMSignMaskF32); break; default: assert_always(); break; } if (i.src1.is_constant) { // TODO(benvanik): make this constant. e.LoadConstantXmm(e.xmm0, i.src1.constant()); e.vpxor(e.xmm0, sign_addr); } else { e.vpxor(e.xmm0, i.src1, sign_addr); } if (i.src2.is_constant) { // TODO(benvanik): make this constant. e.LoadConstantXmm(e.xmm1, i.src2.constant()); e.vpxor(e.xmm1, sign_addr); } else { e.vpxor(e.xmm1, i.src2, sign_addr); } switch (i.instr->flags) { case INT8_TYPE: e.vpcmpgtb(i.dest, e.xmm0, e.xmm1); break; case INT16_TYPE: e.vpcmpgtw(i.dest, e.xmm0, e.xmm1); break; case INT32_TYPE: e.vpcmpgtd(i.dest, e.xmm0, e.xmm1); break; case FLOAT32_TYPE: e.vcmpgtps(i.dest, e.xmm0, e.xmm1); break; } } }; EMITTER_OPCODE_TABLE(OPCODE_VECTOR_COMPARE_UGT, VECTOR_COMPARE_UGT_V128); // ============================================================================ // OPCODE_VECTOR_COMPARE_UGE // ============================================================================ struct VECTOR_COMPARE_UGE_V128 : Sequence> { static void Emit(X64Emitter& e, const EmitArgType& i) { Xbyak::Address sign_addr = e.ptr[e.rax]; // dummy switch (i.instr->flags) { case INT8_TYPE: sign_addr = e.GetXmmConstPtr(XMMSignMaskI8); break; case INT16_TYPE: sign_addr = e.GetXmmConstPtr(XMMSignMaskI16); break; case INT32_TYPE: sign_addr = e.GetXmmConstPtr(XMMSignMaskI32); break; case FLOAT32_TYPE: e.ChangeMxcsrMode(MXCSRMode::Vmx); sign_addr = e.GetXmmConstPtr(XMMSignMaskF32); break; } if (i.src1.is_constant) { // TODO(benvanik): make this constant. e.LoadConstantXmm(e.xmm0, i.src1.constant()); e.vpxor(e.xmm0, sign_addr); } else { e.vpxor(e.xmm0, i.src1, sign_addr); } if (i.src2.is_constant) { // TODO(benvanik): make this constant. e.LoadConstantXmm(e.xmm1, i.src2.constant()); e.vpxor(e.xmm1, sign_addr); } else { e.vpxor(e.xmm1, i.src2, sign_addr); } switch (i.instr->flags) { case INT8_TYPE: e.vpcmpeqb(e.xmm2, e.xmm0, e.xmm1); e.vpcmpgtb(i.dest, e.xmm0, e.xmm1); e.vpor(i.dest, e.xmm2); break; case INT16_TYPE: e.vpcmpeqw(e.xmm2, e.xmm0, e.xmm1); e.vpcmpgtw(i.dest, e.xmm0, e.xmm1); e.vpor(i.dest, e.xmm2); break; case INT32_TYPE: e.vpcmpeqd(e.xmm2, e.xmm0, e.xmm1); e.vpcmpgtd(i.dest, e.xmm0, e.xmm1); e.vpor(i.dest, e.xmm2); break; case FLOAT32_TYPE: e.vcmpgeps(i.dest, e.xmm0, e.xmm1); break; } } }; EMITTER_OPCODE_TABLE(OPCODE_VECTOR_COMPARE_UGE, VECTOR_COMPARE_UGE_V128); // ============================================================================ // OPCODE_VECTOR_ADD // ============================================================================ struct VECTOR_ADD : Sequence> { static void Emit(X64Emitter& e, const EmitArgType& i) { EmitCommutativeBinaryXmmOp( e, i, [&i](X64Emitter& e, const Xmm& dest, Xmm src1, Xmm src2) { const TypeName part_type = static_cast(i.instr->flags & 0xFF); const uint32_t arithmetic_flags = i.instr->flags >> 8; bool is_unsigned = !!(arithmetic_flags & ARITHMETIC_UNSIGNED); bool saturate = !!(arithmetic_flags & ARITHMETIC_SATURATE); switch (part_type) { case INT8_TYPE: if (saturate) { // TODO(benvanik): trace DID_SATURATE if (is_unsigned) { e.vpaddusb(dest, src1, src2); } else { e.vpaddsb(dest, src1, src2); } } else { e.vpaddb(dest, src1, src2); } break; case INT16_TYPE: if (saturate) { // TODO(benvanik): trace DID_SATURATE if (is_unsigned) { e.vpaddusw(dest, src1, src2); } else { e.vpaddsw(dest, src1, src2); } } else { e.vpaddw(dest, src1, src2); } break; case INT32_TYPE: if (saturate) { if (is_unsigned) { if (e.IsFeatureEnabled(kX64EmitAVX512Ortho)) { e.vpaddd(dest, src1, src2); Opmask saturate = e.k1; // _mm_cmplt_epu32_mask e.vpcmpud(saturate, dest, src1, 0x1); e.vpternlogd(dest | saturate, dest, dest, 0xFF); return; } // xmm0 is the only temp register that can be used by // src1/src2. e.vpaddd(e.xmm1, src1, src2); // If result is smaller than either of the inputs, we've // overflowed (only need to check one input) // if (src1 > res) then overflowed // http://locklessinc.com/articles/sat_arithmetic/ // chrispy: todo - add xop stuff here e.vpxor(e.xmm2, src1, e.GetXmmConstPtr(XMMSignMaskI32)); e.vpxor(e.xmm0, e.xmm1, e.GetXmmConstPtr(XMMSignMaskI32)); e.vpcmpgtd(e.xmm0, e.xmm2, e.xmm0); e.vpor(dest, e.xmm1, e.xmm0); } else { e.vpaddd(e.xmm1, src1, src2); if (e.IsFeatureEnabled(kX64EmitAVX512Ortho | kX64EmitAVX512DQ)) { e.vmovdqa32(e.xmm3, src1); e.vpternlogd(e.xmm3, e.xmm1, src2, 0b00100100); const Opmask saturate = e.k1; e.vpmovd2m(saturate, e.xmm3); e.vpsrad(e.xmm2, e.xmm1, 31); e.vpxord(e.xmm2, e.xmm2, e.GetXmmConstPtr(XMMSignMaskI32)); e.vpblendmd(dest | saturate, e.xmm1, e.xmm2); return; } // Overflow results if two inputs are the same sign and the // result isn't the same sign. if ((s32b)(~(src1 ^ src2) & // (src1 ^ res)) < 0) then overflowed // http://locklessinc.com/articles/sat_arithmetic/ e.vpxor(e.xmm2, src1, src2); e.vpxor(e.xmm3, src1, e.xmm1); e.vpandn(e.xmm2, e.xmm2, e.xmm3); // Set any negative overflowed elements of src1 to INT_MIN e.vpand(e.xmm3, src1, e.xmm2); e.vblendvps(e.xmm1, e.xmm1, e.GetXmmConstPtr(XMMSignMaskI32), e.xmm3); // Set any positive overflowed elements of src1 to INT_MAX e.vpandn(e.xmm3, src1, e.xmm2); e.vblendvps(dest, e.xmm1, e.GetXmmConstPtr(XMMAbsMaskPS), e.xmm3); } } else { e.vpaddd(dest, src1, src2); } break; case FLOAT32_TYPE: assert_false(is_unsigned); assert_false(saturate); e.ChangeMxcsrMode(MXCSRMode::Vmx); e.vaddps(dest, src1, src2); break; default: assert_unhandled_case(part_type); break; } }); } }; EMITTER_OPCODE_TABLE(OPCODE_VECTOR_ADD, VECTOR_ADD); // ============================================================================ // OPCODE_VECTOR_SUB // ============================================================================ struct VECTOR_SUB : Sequence> { static void Emit(X64Emitter& e, const EmitArgType& i) { EmitCommutativeBinaryXmmOp( e, i, [&i](X64Emitter& e, const Xmm& dest, Xmm src1, Xmm src2) { const TypeName part_type = static_cast(i.instr->flags & 0xFF); const uint32_t arithmetic_flags = i.instr->flags >> 8; bool is_unsigned = !!(arithmetic_flags & ARITHMETIC_UNSIGNED); bool saturate = !!(arithmetic_flags & ARITHMETIC_SATURATE); switch (part_type) { case INT8_TYPE: if (saturate) { // TODO(benvanik): trace DID_SATURATE if (is_unsigned) { e.vpsubusb(dest, src1, src2); } else { e.vpsubsb(dest, src1, src2); } } else { e.vpsubb(dest, src1, src2); } break; case INT16_TYPE: if (saturate) { // TODO(benvanik): trace DID_SATURATE if (is_unsigned) { e.vpsubusw(dest, src1, src2); } else { e.vpsubsw(dest, src1, src2); } } else { e.vpsubw(dest, src1, src2); } break; case INT32_TYPE: if (saturate) { if (is_unsigned) { // xmm0 is the only temp register that can be used by // src1/src2. e.vpsubd(e.xmm1, src1, src2); if (e.IsFeatureEnabled(kX64EmitAVX512Ortho)) { // If the result is less or equal to the first operand then // we did not underflow Opmask not_underflow = e.k1; // _mm_cmple_epu32_mask e.vpcmpud(not_underflow, e.xmm1, src1, 0x2); // Copy over values that did not underflow, write zero // everywhere else e.vmovdqa32(dest | not_underflow | e.T_z, e.xmm1); return; } // If result is greater than either of the inputs, we've // underflowed (only need to check one input) // if (res > src1) then underflowed // http://locklessinc.com/articles/sat_arithmetic/ e.vpxor(e.xmm2, src1, e.GetXmmConstPtr(XMMSignMaskI32)); e.vpxor(e.xmm0, e.xmm1, e.GetXmmConstPtr(XMMSignMaskI32)); e.vpcmpgtd(e.xmm0, e.xmm0, e.xmm2); e.vpandn(dest, e.xmm0, e.xmm1); } else { e.vpsubd(e.xmm1, src1, src2); if (e.IsFeatureEnabled(kX64EmitAVX512Ortho | kX64EmitAVX512DQ)) { e.vmovdqa32(e.xmm3, src1); e.vpternlogd(e.xmm3, e.xmm1, src2, 0b00011000); const Opmask saturate = e.k1; e.vpmovd2m(saturate, e.xmm3); e.vpsrad(e.xmm2, e.xmm1, 31); e.vpxord(e.xmm2, e.xmm2, e.GetXmmConstPtr(XMMSignMaskI32)); e.vpblendmd(dest | saturate, e.xmm1, e.xmm2); return; } // We can only overflow if the signs of the operands are // opposite. If signs are opposite and result sign isn't the // same as src1's sign, we've overflowed. if ((s32b)((src1 ^ // src2) & (src1 ^ res)) < 0) then overflowed // http://locklessinc.com/articles/sat_arithmetic/ e.vpxor(e.xmm2, src1, src2); e.vpxor(e.xmm3, src1, e.xmm1); e.vpand(e.xmm2, e.xmm2, e.xmm3); // Set any negative overflowed elements of src1 to INT_MIN e.vpand(e.xmm3, src1, e.xmm2); e.vblendvps(e.xmm1, e.xmm1, e.GetXmmConstPtr(XMMSignMaskI32), e.xmm3); // Set any positive overflowed elements of src1 to INT_MAX e.vpandn(e.xmm3, src1, e.xmm2); e.vblendvps(dest, e.xmm1, e.GetXmmConstPtr(XMMAbsMaskPS), e.xmm3); } } else { e.vpsubd(dest, src1, src2); } break; case FLOAT32_TYPE: e.ChangeMxcsrMode(MXCSRMode::Vmx); e.vsubps(dest, src1, src2); break; default: assert_unhandled_case(part_type); break; } }); } }; EMITTER_OPCODE_TABLE(OPCODE_VECTOR_SUB, VECTOR_SUB); // ============================================================================ // OPCODE_VECTOR_SHL // ============================================================================ static XmmConst GetShiftmaskForType(unsigned typ) { if (typ == INT8_TYPE) { return XMMXOPByteShiftMask; } else if (typ == INT16_TYPE) { return XMMXOPWordShiftMask; } else { return XMMXOPDwordShiftMask; } } struct VECTOR_SHL_V128 : Sequence> { static void Emit(X64Emitter& e, const EmitArgType& i) { if (cvars::xop_left_shifts && e.IsFeatureEnabled(kX64EmitXOP)) { Xmm src1 = GetInputRegOrConstant(e, i.src1, e.xmm0); Xmm src2 = GetInputRegOrConstant(e, i.src2, e.xmm1); e.vpand(e.xmm2, src2, e.GetXmmConstPtr(GetShiftmaskForType(i.instr->flags))); switch (i.instr->flags) { case INT8_TYPE: e.vpshlb(i.dest, src1, e.xmm2); break; case INT16_TYPE: e.vpshlw(i.dest, src1, e.xmm2); break; case INT32_TYPE: e.vpshld(i.dest, src1, e.xmm2); break; } } else { switch (i.instr->flags) { case INT8_TYPE: EmitInt8(e, i); break; case INT16_TYPE: EmitInt16(e, i); break; case INT32_TYPE: EmitInt32(e, i); break; default: assert_always(); break; } } } static void EmitInt8(X64Emitter& e, const EmitArgType& i) { // TODO(benvanik): native version (with shift magic). if (e.IsFeatureEnabled(kX64EmitAVX2)) { if (!i.src2.is_constant) { // get high 8 bytes e.vpunpckhqdq(e.xmm1, i.src1, i.src1); e.vpunpckhqdq(e.xmm3, i.src2, i.src2); e.vpmovzxbd(e.ymm0, i.src1); e.vpmovzxbd(e.ymm1, e.xmm1); e.vpmovzxbd(e.ymm2, i.src2); e.vpmovzxbd(e.ymm3, e.xmm3); e.vpsllvd(e.ymm0, e.ymm0, e.ymm2); e.vpsllvd(e.ymm1, e.ymm1, e.ymm3); e.vextracti128(e.xmm2, e.ymm0, 1); e.vextracti128(e.xmm3, e.ymm1, 1); e.vpshufb(e.xmm0, e.xmm0, e.GetXmmConstPtr(XMMIntsToBytes)); e.vpshufb(e.xmm1, e.xmm1, e.GetXmmConstPtr(XMMIntsToBytes)); e.vpshufb(e.xmm2, e.xmm2, e.GetXmmConstPtr(XMMIntsToBytes)); e.vpshufb(e.xmm3, e.xmm3, e.GetXmmConstPtr(XMMIntsToBytes)); e.vpunpckldq(e.xmm0, e.xmm0, e.xmm1); e.vpunpckldq(e.xmm2, e.xmm2, e.xmm3); e.vpunpcklqdq(i.dest, e.xmm0, e.xmm2); return; } else { vec128_t constmask = i.src2.constant(); for (unsigned i = 0; i < 16; ++i) { constmask.u8[i] &= 7; } unsigned seenvalue = constmask.u8[0]; bool all_same = true; for (unsigned i = 1; i < 16; ++i) { if (constmask.u8[i] != seenvalue) { all_same = false; break; } } if (all_same) { e.vpmovzxbw(e.ymm0, i.src1); e.vpsllw(e.ymm0, e.ymm0, seenvalue); e.vextracti128(e.xmm1, e.ymm0, 1); e.vpshufb(e.xmm0, e.xmm0, e.GetXmmConstPtr(XMMShortsToBytes)); e.vpshufb(e.xmm1, e.xmm1, e.GetXmmConstPtr(XMMShortsToBytes)); e.vpunpcklqdq(i.dest, e.xmm0, e.xmm1); return; } else { e.LoadConstantXmm(e.xmm2, constmask); e.vpunpckhqdq(e.xmm1, i.src1, i.src1); e.vpunpckhqdq(e.xmm3, e.xmm2, e.xmm2); e.vpmovzxbd(e.ymm0, i.src1); e.vpmovzxbd(e.ymm1, e.xmm1); e.vpmovzxbd(e.ymm2, e.xmm2); e.vpmovzxbd(e.ymm3, e.xmm3); e.vpsllvd(e.ymm0, e.ymm0, e.ymm2); e.vpsllvd(e.ymm1, e.ymm1, e.ymm3); e.vextracti128(e.xmm2, e.ymm0, 1); e.vextracti128(e.xmm3, e.ymm1, 1); e.vpshufb(e.xmm0, e.xmm0, e.GetXmmConstPtr(XMMIntsToBytes)); e.vpshufb(e.xmm1, e.xmm1, e.GetXmmConstPtr(XMMIntsToBytes)); e.vpshufb(e.xmm2, e.xmm2, e.GetXmmConstPtr(XMMIntsToBytes)); e.vpshufb(e.xmm3, e.xmm3, e.GetXmmConstPtr(XMMIntsToBytes)); e.vpunpckldq(e.xmm0, e.xmm0, e.xmm1); e.vpunpckldq(e.xmm2, e.xmm2, e.xmm3); e.vpunpcklqdq(i.dest, e.xmm0, e.xmm2); return; } } } unsigned stack_offset_src1 = StackLayout::GUEST_SCRATCH; unsigned stack_offset_src2 = StackLayout::GUEST_SCRATCH + 16; if (i.src1.is_constant) { e.StashConstantXmm(0, i.src1.constant()); stack_offset_src1 = X64Emitter::kStashOffset; } else { e.vmovdqa(e.ptr[e.rsp + stack_offset_src1], i.src1); } if (i.src2.is_constant) { e.StashConstantXmm(1, i.src2.constant()); stack_offset_src2 = X64Emitter::kStashOffset + 16; } else { e.vmovdqa(e.ptr[e.rsp + stack_offset_src2], i.src2); } Xbyak::Label looper; e.xor_(e.edx, e.edx); e.L(looper); e.movzx(e.ecx, e.byte[e.rsp + stack_offset_src2 + e.rdx]); e.shl(e.byte[e.rsp + stack_offset_src1 + e.rdx], e.cl); if (e.IsFeatureEnabled(kX64FlagsIndependentVars)) { e.inc(e.edx); } else { e.add(e.edx, 1); } e.cmp(e.edx, 16); e.jnz(looper); e.vmovdqa(i.dest, e.byte[e.rsp + stack_offset_src1]); } static void EmitInt16(X64Emitter& e, const EmitArgType& i) { Xmm src1; if (i.src1.is_constant) { src1 = e.xmm2; e.LoadConstantXmm(src1, i.src1.constant()); } else { src1 = i.src1; } if (i.src2.is_constant) { const auto& shamt = i.src2.constant(); bool all_same = true; for (size_t n = 0; n < 8 - n; ++n) { if (shamt.u16[n] != shamt.u16[n + 1]) { all_same = false; break; } } if (all_same) { // Every count is the same, so we can use vpsllw. e.vpsllw(i.dest, src1, shamt.u16[0] & 0xF); return; } } // Shift 8 words in src1 by amount specified in src2. Xbyak::Label emu, end; // Only bother with this check if shift amt isn't constant. if (!i.src2.is_constant) { // See if the shift is equal first for a shortcut. e.vpshuflw(e.xmm0, i.src2, 0b00000000); e.vpshufd(e.xmm0, e.xmm0, 0b00000000); e.vpxor(e.xmm1, e.xmm0, i.src2); e.vptest(e.xmm1, e.xmm1); e.jnz(emu); // Equal. Shift using vpsllw. e.mov(e.rax, 0xF); e.vmovq(e.xmm1, e.rax); e.vpand(e.xmm0, e.xmm0, e.xmm1); e.vpsllw(i.dest, src1, e.xmm0); e.jmp(end); } // TODO(benvanik): native version (with shift magic). e.L(emu); unsigned stack_offset_src1 = StackLayout::GUEST_SCRATCH; unsigned stack_offset_src2 = StackLayout::GUEST_SCRATCH + 16; e.vmovdqa(e.ptr[e.rsp + stack_offset_src1], src1); if (i.src2.is_constant) { e.StashConstantXmm(1, i.src2.constant()); stack_offset_src2 = X64Emitter::kStashOffset + 16; } else { e.vmovdqa(e.ptr[e.rsp + stack_offset_src2], i.src2); } Xbyak::Label looper; e.xor_(e.edx, e.edx); e.L(looper); e.movzx(e.ecx, e.word[e.rsp + stack_offset_src2 + e.rdx]); e.shl(e.word[e.rsp + stack_offset_src1 + e.rdx], e.cl); e.add(e.edx, 2); e.cmp(e.edx, 16); e.jnz(looper); e.vmovdqa(i.dest, e.byte[e.rsp + stack_offset_src1]); e.L(end); } static void EmitInt32(X64Emitter& e, const EmitArgType& i) { Xmm src1; if (i.src1.is_constant) { src1 = e.xmm2; e.LoadConstantXmm(src1, i.src1.constant()); } else { src1 = i.src1; } if (i.src2.is_constant) { const auto& shamt = i.src2.constant(); bool all_same = true; for (size_t n = 0; n < 4 - n; ++n) { if (shamt.u32[n] != shamt.u32[n + 1]) { all_same = false; break; } } if (all_same) { // Every count is the same, so we can use vpslld. e.vpslld(i.dest, src1, shamt.u8[0] & 0x1F); return; } } if (e.IsFeatureEnabled(kX64EmitAVX2)) { if (i.src2.is_constant) { const auto& shamt = i.src2.constant(); // Counts differ, so pre-mask and load constant. vec128_t masked = i.src2.constant(); for (size_t n = 0; n < 4; ++n) { masked.u32[n] &= 0x1F; } e.LoadConstantXmm(e.xmm0, masked); e.vpsllvd(i.dest, src1, e.xmm0); } else { // Fully variable shift. // src shift mask may have values >31, and x86 sets to zero when // that happens so we mask. e.vandps(e.xmm0, i.src2, e.GetXmmConstPtr(XMMShiftMaskPS)); e.vpsllvd(i.dest, src1, e.xmm0); } } else { // Shift 4 words in src1 by amount specified in src2. Xbyak::Label emu, end; // See if the shift is equal first for a shortcut. // Only bother with this check if shift amt isn't constant. if (!i.src2.is_constant) { e.vpshufd(e.xmm0, i.src2, 0b00000000); e.vpxor(e.xmm1, e.xmm0, i.src2); e.vptest(e.xmm1, e.xmm1); e.jnz(emu); // Equal. Shift using vpsrad. e.mov(e.rax, 0x1F); e.vmovq(e.xmm1, e.rax); e.vpand(e.xmm0, e.xmm0, e.xmm1); e.vpslld(i.dest, src1, e.xmm0); e.jmp(end); } // TODO(benvanik): native version (with shift magic). e.L(emu); unsigned stack_offset_src1 = StackLayout::GUEST_SCRATCH; unsigned stack_offset_src2 = StackLayout::GUEST_SCRATCH + 16; e.vmovdqa(e.ptr[e.rsp + stack_offset_src1], src1); if (i.src2.is_constant) { e.StashConstantXmm(1, i.src2.constant()); stack_offset_src2 = X64Emitter::kStashOffset + 16; } else { e.vmovdqa(e.ptr[e.rsp + stack_offset_src2], i.src2); } Xbyak::Label looper; e.xor_(e.edx, e.edx); e.L(looper); e.mov(e.ecx, e.dword[e.rsp + stack_offset_src2 + e.rdx]); e.shl(e.dword[e.rsp + stack_offset_src1 + e.rdx], e.cl); e.add(e.edx, 4); e.cmp(e.edx, 16); e.jnz(looper); e.vmovdqa(i.dest, e.byte[e.rsp + stack_offset_src1]); e.L(end); } } }; EMITTER_OPCODE_TABLE(OPCODE_VECTOR_SHL, VECTOR_SHL_V128); // ============================================================================ // OPCODE_VECTOR_SHR // ============================================================================ struct VECTOR_SHR_V128 : Sequence> { static void Emit(X64Emitter& e, const EmitArgType& i) { if (cvars::xop_right_shifts && e.IsFeatureEnabled(kX64EmitXOP)) { Xmm src1 = GetInputRegOrConstant(e, i.src1, e.xmm0); Xmm src2 = GetInputRegOrConstant(e, i.src2, e.xmm1); e.vpand(e.xmm2, src2, e.GetXmmConstPtr(GetShiftmaskForType(i.instr->flags))); e.vpcmpeqb(e.xmm3, e.xmm3); switch (i.instr->flags) { case INT8_TYPE: e.vpsignb(e.xmm2, e.xmm3); e.vpshlb(i.dest, src1, e.xmm2); break; case INT16_TYPE: e.vpsignw(e.xmm2, e.xmm3); e.vpshlw(i.dest, src1, e.xmm2); break; case INT32_TYPE: e.vpsignd(e.xmm2, e.xmm3); e.vpshld(i.dest, src1, e.xmm2); break; } } else { switch (i.instr->flags) { case INT8_TYPE: EmitInt8(e, i); break; case INT16_TYPE: EmitInt16(e, i); break; case INT32_TYPE: EmitInt32(e, i); break; default: assert_always(); break; } } } static void EmitInt8(X64Emitter& e, const EmitArgType& i) { if (i.src2.is_constant && e.IsFeatureEnabled(kX64EmitGFNI)) { const auto& shamt = i.src2.constant(); bool all_same = true; for (size_t n = 0; n < 16 - n; ++n) { if (shamt.u8[n] != shamt.u8[n + 1]) { all_same = false; break; } } if (all_same) { // Every count is the same, so we can use gf2p8affineqb. const uint8_t shift_amount = shamt.u8[0] & 0b111; const uint64_t shift_matrix = UINT64_C(0x0102040810204080) << (shift_amount * 8); e.vgf2p8affineqb(i.dest, i.src1, e.StashConstantXmm(0, vec128q(shift_matrix)), 0); return; } } unsigned stack_offset_src1 = StackLayout::GUEST_SCRATCH; unsigned stack_offset_src2 = StackLayout::GUEST_SCRATCH + 16; if (i.src1.is_constant) { e.StashConstantXmm(0, i.src1.constant()); stack_offset_src1 = X64Emitter::kStashOffset; } else { e.vmovdqa(e.ptr[e.rsp + stack_offset_src1], i.src1); } if (i.src2.is_constant) { e.StashConstantXmm(1, i.src2.constant()); stack_offset_src2 = X64Emitter::kStashOffset + 16; } else { e.vmovdqa(e.ptr[e.rsp + stack_offset_src2], i.src2); } Xbyak::Label looper; e.xor_(e.edx, e.edx); e.L(looper); // movzx is to eliminate any possible dep on previous value of rcx at start // of loop e.movzx(e.ecx, e.byte[e.rsp + stack_offset_src2 + e.rdx]); // maybe using a memory operand as the left side isn't the best idea lol, // still better than callnativesafe though agners docs have no timing info // on shx [m], cl so shrug e.shr(e.byte[e.rsp + stack_offset_src1 + e.rdx], e.cl); if (e.IsFeatureEnabled(kX64FlagsIndependentVars)) { e.inc(e.edx); } else { e.add(e.edx, 1); } e.cmp(e.edx, 16); e.jnz(looper); e.vmovdqa(i.dest, e.byte[e.rsp + stack_offset_src1]); } static void EmitInt16(X64Emitter& e, const EmitArgType& i) { if (i.src2.is_constant) { const auto& shamt = i.src2.constant(); bool all_same = true; for (size_t n = 0; n < 8 - n; ++n) { if (shamt.u16[n] != shamt.u16[n + 1]) { all_same = false; break; } } if (all_same) { // Every count is the same, so we can use vpsllw. e.vpsrlw(i.dest, i.src1, shamt.u16[0] & 0xF); return; } } // Shift 8 words in src1 by amount specified in src2. Xbyak::Label emu, end; // See if the shift is equal first for a shortcut. // Only bother with this check if shift amt isn't constant. if (!i.src2.is_constant) { e.vpshuflw(e.xmm0, i.src2, 0b00000000); e.vpshufd(e.xmm0, e.xmm0, 0b00000000); e.vpxor(e.xmm1, e.xmm0, i.src2); e.vptest(e.xmm1, e.xmm1); e.jnz(emu); // Equal. Shift using vpsrlw. e.mov(e.rax, 0xF); e.vmovq(e.xmm1, e.rax); e.vpand(e.xmm0, e.xmm0, e.xmm1); e.vpsrlw(i.dest, i.src1, e.xmm0); e.jmp(end); } // TODO(benvanik): native version (with shift magic). e.L(emu); unsigned stack_offset_src1 = StackLayout::GUEST_SCRATCH; unsigned stack_offset_src2 = StackLayout::GUEST_SCRATCH + 16; if (i.src1.is_constant) { e.StashConstantXmm(0, i.src1.constant()); stack_offset_src1 = X64Emitter::kStashOffset; } else { e.vmovdqa(e.ptr[e.rsp + stack_offset_src1], i.src1); } if (i.src2.is_constant) { e.StashConstantXmm(1, i.src2.constant()); stack_offset_src2 = X64Emitter::kStashOffset + 16; } else { e.vmovdqa(e.ptr[e.rsp + stack_offset_src2], i.src2); } Xbyak::Label looper; e.xor_(e.edx, e.edx); e.L(looper); e.movzx(e.ecx, e.word[e.rsp + stack_offset_src2 + e.rdx]); e.shr(e.word[e.rsp + stack_offset_src1 + e.rdx], e.cl); e.add(e.edx, 2); e.cmp(e.edx, 16); e.jnz(looper); e.vmovdqa(i.dest, e.byte[e.rsp + stack_offset_src1]); e.L(end); } static void EmitInt32(X64Emitter& e, const EmitArgType& i) { Xmm src1; if (i.src1.is_constant) { src1 = e.xmm2; e.LoadConstantXmm(src1, i.src1.constant()); } else { src1 = i.src1; } if (i.src2.is_constant) { const auto& shamt = i.src2.constant(); bool all_same = true; for (size_t n = 0; n < 4 - n; ++n) { if (shamt.u32[n] != shamt.u32[n + 1]) { all_same = false; break; } } if (all_same) { // Every count is the same, so we can use vpsrld. e.vpsrld(i.dest, src1, shamt.u8[0] & 0x1F); return; } else { if (e.IsFeatureEnabled(kX64EmitAVX2)) { // Counts differ, so pre-mask and load constant. vec128_t masked = i.src2.constant(); for (size_t n = 0; n < 4; ++n) { masked.u32[n] &= 0x1F; } e.LoadConstantXmm(e.xmm0, masked); e.vpsrlvd(i.dest, src1, e.xmm0); return; } } } if (e.IsFeatureEnabled(kX64EmitAVX2)) { // Fully variable shift. // src shift mask may have values >31, and x86 sets to zero when // that happens so we mask. e.vandps(e.xmm0, i.src2, e.GetXmmConstPtr(XMMShiftMaskPS)); e.vpsrlvd(i.dest, src1, e.xmm0); } else { // Shift 4 words in src1 by amount specified in src2. Xbyak::Label emu, end; // See if the shift is equal first for a shortcut. // Only bother with this check if shift amt isn't constant. if (!i.src2.is_constant) { e.vpshufd(e.xmm0, i.src2, 0b00000000); e.vpxor(e.xmm1, e.xmm0, i.src2); e.vptest(e.xmm1, e.xmm1); e.jnz(emu); // Equal. Shift using vpsrld. e.mov(e.rax, 0x1F); e.vmovq(e.xmm1, e.rax); e.vpand(e.xmm0, e.xmm0, e.xmm1); e.vpsrld(i.dest, src1, e.xmm0); e.jmp(end); } // TODO(benvanik): native version. e.L(emu); unsigned stack_offset_src1 = StackLayout::GUEST_SCRATCH; unsigned stack_offset_src2 = StackLayout::GUEST_SCRATCH + 16; if (i.src1.is_constant) { e.StashConstantXmm(0, i.src1.constant()); stack_offset_src1 = X64Emitter::kStashOffset; } else { e.vmovdqa(e.ptr[e.rsp + stack_offset_src1], i.src1); } if (i.src2.is_constant) { e.StashConstantXmm(1, i.src2.constant()); stack_offset_src2 = X64Emitter::kStashOffset + 16; } else { e.vmovdqa(e.ptr[e.rsp + stack_offset_src2], i.src2); } Xbyak::Label looper; e.xor_(e.edx, e.edx); e.L(looper); e.mov(e.ecx, e.dword[e.rsp + stack_offset_src2 + e.rdx]); e.shr(e.dword[e.rsp + stack_offset_src1 + e.rdx], e.cl); e.add(e.edx, 4); e.cmp(e.edx, 16); e.jnz(looper); e.vmovdqa(i.dest, e.byte[e.rsp + stack_offset_src1]); e.L(end); } } }; EMITTER_OPCODE_TABLE(OPCODE_VECTOR_SHR, VECTOR_SHR_V128); // ============================================================================ // OPCODE_VECTOR_SHA // ============================================================================ struct VECTOR_SHA_V128 : Sequence> { static void Emit(X64Emitter& e, const EmitArgType& i) { if (cvars::xop_arithmetic_right_shifts && e.IsFeatureEnabled(kX64EmitXOP)) { Xmm src1 = GetInputRegOrConstant(e, i.src1, e.xmm0); Xmm src2 = GetInputRegOrConstant(e, i.src2, e.xmm1); e.vpand(e.xmm2, src2, e.GetXmmConstPtr(GetShiftmaskForType(i.instr->flags))); e.vpcmpeqb(e.xmm3, e.xmm3); switch (i.instr->flags) { case INT8_TYPE: e.vpsignb(e.xmm2, e.xmm3); e.vpshab(i.dest, src1, e.xmm2); break; case INT16_TYPE: e.vpsignw(e.xmm2, e.xmm3); e.vpshaw(i.dest, src1, e.xmm2); break; case INT32_TYPE: e.vpsignd(e.xmm2, e.xmm3); e.vpshad(i.dest, src1, e.xmm2); break; } } else { switch (i.instr->flags) { case INT8_TYPE: EmitInt8(e, i); break; case INT16_TYPE: EmitInt16(e, i); break; case INT32_TYPE: EmitInt32(e, i); break; default: assert_always(); break; } } } static void EmitInt8(X64Emitter& e, const EmitArgType& i) { unsigned stack_offset_src1 = StackLayout::GUEST_SCRATCH; unsigned stack_offset_src2 = StackLayout::GUEST_SCRATCH + 16; if (i.src2.is_constant) { const auto& shamt = i.src2.constant(); bool all_same = true; for (size_t n = 0; n < 16 - n; ++n) { if (shamt.u8[n] != shamt.u8[n + 1]) { all_same = false; break; } } if (e.IsFeatureEnabled(kX64EmitGFNI)) { if (all_same) { // Every count is the same, so we can use gf2p8affineqb. const uint8_t shift_amount = shamt.u8[0] & 0b111; const uint64_t shift_matrix = (UINT64_C(0x0102040810204080) << (shift_amount * 8)) | (UINT64_C(0x8080808080808080) >> (64 - shift_amount * 8)); ; e.vgf2p8affineqb(i.dest, i.src1, e.StashConstantXmm(0, vec128q(shift_matrix)), 0); return; } } else if (all_same) { Xmm to_be_shifted = GetInputRegOrConstant(e, i.src1, e.xmm1); e.vpmovsxbw(e.xmm0, to_be_shifted); //_mm_srai_epi16 / psraw e.vpunpckhqdq(e.xmm2, to_be_shifted, to_be_shifted); e.vpmovsxbw(e.xmm1, e.xmm2); e.vpsraw(e.xmm0, shamt.u8[0]); e.vpsraw(e.xmm1, shamt.u8[0]); e.vpacksswb(i.dest, e.xmm0, e.xmm1); return; } e.StashConstantXmm(1, i.src2.constant()); stack_offset_src2 = X64Emitter::kStashOffset + 16; } else { e.vmovdqa(e.ptr[e.rsp + stack_offset_src2], i.src2); } if (i.src1.is_constant) { e.StashConstantXmm(0, i.src1.constant()); stack_offset_src1 = X64Emitter::kStashOffset; } else { e.vmovdqa(e.ptr[e.rsp + stack_offset_src1], i.src1); } Xbyak::Label looper; e.xor_(e.edx, e.edx); e.L(looper); // movzx is to eliminate any possible dep on previous value of rcx at start // of loop e.movzx(e.ecx, e.byte[e.rsp + stack_offset_src2 + e.rdx]); // maybe using a memory operand as the left side isn't the best idea lol, // still better than callnativesafe though agners docs have no timing info // on shx [m], cl so shrug e.sar(e.byte[e.rsp + stack_offset_src1 + e.rdx], e.cl); if (e.IsFeatureEnabled(kX64FlagsIndependentVars)) { e.inc(e.edx); } else { e.add(e.edx, 1); } e.cmp(e.edx, 16); e.jnz(looper); e.vmovdqa(i.dest, e.byte[e.rsp + stack_offset_src1]); } static void EmitInt16(X64Emitter& e, const EmitArgType& i) { if (i.src2.is_constant) { const auto& shamt = i.src2.constant(); bool all_same = true; for (size_t n = 0; n < 8 - n; ++n) { if (shamt.u16[n] != shamt.u16[n + 1]) { all_same = false; break; } } if (all_same) { // Every count is the same, so we can use vpsraw. e.vpsraw(i.dest, i.src1, shamt.u16[0] & 0xF); return; } } // Shift 8 words in src1 by amount specified in src2. Xbyak::Label emu, end; // See if the shift is equal first for a shortcut. // Only bother with this check if shift amt isn't constant. if (!i.src2.is_constant) { e.vpshuflw(e.xmm0, i.src2, 0b00000000); e.vpshufd(e.xmm0, e.xmm0, 0b00000000); e.vpxor(e.xmm1, e.xmm0, i.src2); e.vptest(e.xmm1, e.xmm1); e.jnz(emu); // Equal. Shift using vpsraw. e.mov(e.rax, 0xF); e.vmovq(e.xmm1, e.rax); e.vpand(e.xmm0, e.xmm0, e.xmm1); e.vpsraw(i.dest, i.src1, e.xmm0); e.jmp(end); } // TODO(benvanik): native version (with shift magic). e.L(emu); unsigned stack_offset_src1 = StackLayout::GUEST_SCRATCH; unsigned stack_offset_src2 = StackLayout::GUEST_SCRATCH + 16; if (i.src1.is_constant) { e.StashConstantXmm(0, i.src1.constant()); stack_offset_src1 = X64Emitter::kStashOffset; } else { e.vmovdqa(e.ptr[e.rsp + stack_offset_src1], i.src1); } if (i.src2.is_constant) { e.StashConstantXmm(1, i.src2.constant()); stack_offset_src2 = X64Emitter::kStashOffset + 16; } else { e.vmovdqa(e.ptr[e.rsp + stack_offset_src2], i.src2); } Xbyak::Label looper; e.xor_(e.edx, e.edx); e.L(looper); e.movzx(e.ecx, e.word[e.rsp + stack_offset_src2 + e.rdx]); e.sar(e.word[e.rsp + stack_offset_src1 + e.rdx], e.cl); e.add(e.edx, 2); e.cmp(e.edx, 16); e.jnz(looper); e.vmovdqa(i.dest, e.byte[e.rsp + stack_offset_src1]); e.L(end); } static void EmitInt32(X64Emitter& e, const EmitArgType& i) { if (i.src2.is_constant) { const auto& shamt = i.src2.constant(); bool all_same = true; for (size_t n = 0; n < 4 - n; ++n) { if (shamt.u32[n] != shamt.u32[n + 1]) { all_same = false; break; } } if (all_same) { // Every count is the same, so we can use vpsrad. e.vpsrad(i.dest, i.src1, shamt.u32[0] & 0x1F); return; } } if (e.IsFeatureEnabled(kX64EmitAVX2)) { // src shift mask may have values >31, and x86 sets to zero when // that happens so we mask. if (i.src2.is_constant) { e.LoadConstantXmm(e.xmm0, i.src2.constant()); e.vpand(e.xmm0, e.GetXmmConstPtr(XMMShiftMaskPS)); } else { e.vpand(e.xmm0, i.src2, e.GetXmmConstPtr(XMMShiftMaskPS)); } e.vpsravd(i.dest, i.src1, e.xmm0); } else { // Shift 4 words in src1 by amount specified in src2. Xbyak::Label emu, end; // See if the shift is equal first for a shortcut. // Only bother with this check if shift amt isn't constant. if (!i.src2.is_constant) { e.vpshufd(e.xmm0, i.src2, 0b00000000); e.vpxor(e.xmm1, e.xmm0, i.src2); e.vptest(e.xmm1, e.xmm1); e.jnz(emu); // Equal. Shift using vpsrad. e.mov(e.rax, 0x1F); e.vmovq(e.xmm1, e.rax); e.vpand(e.xmm0, e.xmm0, e.xmm1); e.vpsrad(i.dest, i.src1, e.xmm0); e.jmp(end); } // TODO(benvanik): native version. e.L(emu); unsigned stack_offset_src1 = StackLayout::GUEST_SCRATCH; unsigned stack_offset_src2 = StackLayout::GUEST_SCRATCH + 16; if (i.src1.is_constant) { e.StashConstantXmm(0, i.src1.constant()); stack_offset_src1 = X64Emitter::kStashOffset; } else { e.vmovdqa(e.ptr[e.rsp + stack_offset_src1], i.src1); } if (i.src2.is_constant) { e.StashConstantXmm(1, i.src2.constant()); stack_offset_src2 = X64Emitter::kStashOffset + 16; } else { e.vmovdqa(e.ptr[e.rsp + stack_offset_src2], i.src2); } Xbyak::Label looper; e.xor_(e.edx, e.edx); e.L(looper); e.mov(e.ecx, e.dword[e.rsp + stack_offset_src2 + e.rdx]); e.sar(e.dword[e.rsp + stack_offset_src1 + e.rdx], e.cl); e.add(e.edx, 4); e.cmp(e.edx, 16); e.jnz(looper); e.vmovdqa(i.dest, e.byte[e.rsp + stack_offset_src1]); e.L(end); } } }; EMITTER_OPCODE_TABLE(OPCODE_VECTOR_SHA, VECTOR_SHA_V128); struct VECTOR_ROTATE_LEFT_V128 : Sequence> { static void Emit(X64Emitter& e, const EmitArgType& i) { if (cvars::xop_rotates && e.IsFeatureEnabled(kX64EmitXOP)) { Xmm src1 = GetInputRegOrConstant(e, i.src1, e.xmm0); Xmm src2 = GetInputRegOrConstant(e, i.src2, e.xmm1); e.vpand(e.xmm2, src2, e.GetXmmConstPtr(GetShiftmaskForType(i.instr->flags))); switch (i.instr->flags) { case INT8_TYPE: e.vprotb(i.dest, src1, e.xmm2); break; case INT16_TYPE: e.vprotw(i.dest, src1, e.xmm2); break; case INT32_TYPE: e.vprotd(i.dest, src1, e.xmm2); break; } } else { unsigned stack_offset_src1 = StackLayout::GUEST_SCRATCH; unsigned stack_offset_src2 = StackLayout::GUEST_SCRATCH + 16; switch (i.instr->flags) { case INT8_TYPE: { if (i.src1.is_constant) { e.StashConstantXmm(0, i.src1.constant()); stack_offset_src1 = X64Emitter::kStashOffset; } else { e.vmovdqa(e.ptr[e.rsp + stack_offset_src1], i.src1); } if (i.src2.is_constant) { e.StashConstantXmm(1, i.src2.constant()); stack_offset_src2 = X64Emitter::kStashOffset + 16; } else { e.vmovdqa(e.ptr[e.rsp + stack_offset_src2], i.src2); } Xbyak::Label rotate_iter; e.xor_(e.edx, e.edx); e.L(rotate_iter); e.movzx(e.ecx, e.byte[e.rsp + stack_offset_src2 + e.rdx]); e.rol(e.byte[e.rsp + stack_offset_src1 + e.rdx], e.cl); e.add(e.edx, 1); e.cmp(e.edx, 16); e.jnz(rotate_iter); e.vmovdqa(i.dest, e.byte[e.rsp + stack_offset_src1]); } break; case INT16_TYPE: { if (i.src1.is_constant) { e.StashConstantXmm(0, i.src1.constant()); stack_offset_src1 = X64Emitter::kStashOffset; } else { e.vmovdqa(e.ptr[e.rsp + stack_offset_src1], i.src1); } if (i.src2.is_constant) { e.StashConstantXmm(1, i.src2.constant()); stack_offset_src2 = X64Emitter::kStashOffset + 16; } else { e.vmovdqa(e.ptr[e.rsp + stack_offset_src2], i.src2); } Xbyak::Label rotate_iter; e.xor_(e.edx, e.edx); e.L(rotate_iter); e.movzx(e.ecx, e.word[e.rsp + stack_offset_src2 + e.rdx]); e.rol(e.word[e.rsp + stack_offset_src1 + e.rdx], e.cl); e.add(e.edx, 2); e.cmp(e.edx, 16); e.jnz(rotate_iter); e.vmovdqa(i.dest, e.byte[e.rsp + stack_offset_src1]); } break; case INT32_TYPE: { if (e.IsFeatureEnabled(kX64EmitAVX512Ortho)) { e.vprolvd(i.dest, i.src1, i.src2); } else if (e.IsFeatureEnabled(kX64EmitAVX2)) { Xmm temp = i.dest; if (i.dest == i.src1 || i.dest == i.src2) { temp = e.xmm2; } // Shift left (to get high bits): if (i.src2.is_constant) { e.LoadConstantXmm(temp, i.src2.constant()); e.vpand(e.xmm0, temp, e.GetXmmConstPtr(XMMShiftMaskPS)); } else { e.vpand(e.xmm0, i.src2, e.GetXmmConstPtr(XMMShiftMaskPS)); } e.vpsllvd(e.xmm1, i.src1, e.xmm0); // Shift right (to get low bits): e.vmovdqa(temp, e.GetXmmConstPtr(XMMPI32)); e.vpsubd(temp, e.xmm0); e.vpsrlvd(i.dest, i.src1, temp); // Merge: e.vpor(i.dest, e.xmm1); } else { if (i.src1.is_constant) { e.StashConstantXmm(0, i.src1.constant()); stack_offset_src1 = X64Emitter::kStashOffset; } else { e.vmovdqa(e.ptr[e.rsp + stack_offset_src1], i.src1); } if (i.src2.is_constant) { e.StashConstantXmm(1, i.src2.constant()); stack_offset_src2 = X64Emitter::kStashOffset + 16; } else { e.vmovdqa(e.ptr[e.rsp + stack_offset_src2], i.src2); } Xbyak::Label rotate_iter; e.xor_(e.edx, e.edx); e.L(rotate_iter); e.mov(e.ecx, e.dword[e.rsp + stack_offset_src2 + e.rdx]); e.rol(e.dword[e.rsp + stack_offset_src1 + e.rdx], e.cl); e.add(e.edx, 4); e.cmp(e.edx, 16); e.jnz(rotate_iter); e.vmovdqa(i.dest, e.byte[e.rsp + stack_offset_src1]); } break; } default: assert_always(); break; } } } }; EMITTER_OPCODE_TABLE(OPCODE_VECTOR_ROTATE_LEFT, VECTOR_ROTATE_LEFT_V128); struct VECTOR_AVERAGE : Sequence> { static void Emit(X64Emitter& e, const EmitArgType& i) { auto i_flags = i.instr->flags; EmitCommutativeBinaryXmmOp( e, i, [i_flags](X64Emitter& e, const Xmm& dest, const Xmm& src1, const Xmm& src2) { const TypeName part_type = static_cast(i_flags & 0xFF); const uint32_t arithmetic_flags = i_flags >> 8; bool is_unsigned = !!(arithmetic_flags & ARITHMETIC_UNSIGNED); unsigned stack_offset_src1 = StackLayout::GUEST_SCRATCH; unsigned stack_offset_src2 = StackLayout::GUEST_SCRATCH + 16; switch (part_type) { case INT8_TYPE: if (is_unsigned) { e.vpavgb(dest, src1, src2); } else { // todo: avx2 version or version that sign extends to two __m128 e.vmovdqa(e.ptr[e.rsp + stack_offset_src1], src1); e.vmovdqa(e.ptr[e.rsp + stack_offset_src2], src2); Xbyak::Label looper; e.xor_(e.edx, e.edx); e.L(looper); e.movsx(e.ecx, e.byte[e.rsp + stack_offset_src2 + e.rdx]); e.movsx(e.eax, e.byte[e.rsp + stack_offset_src1 + e.rdx]); e.lea(e.ecx, e.ptr[e.ecx + e.eax + 1]); e.sar(e.ecx, 1); e.mov(e.byte[e.rsp + stack_offset_src1 + e.rdx], e.cl); if (e.IsFeatureEnabled(kX64FlagsIndependentVars)) { e.inc(e.edx); } else { e.add(e.edx, 1); } e.cmp(e.edx, 16); e.jnz(looper); e.vmovdqa(dest, e.ptr[e.rsp + stack_offset_src1]); } break; case INT16_TYPE: if (is_unsigned) { e.vpavgw(dest, src1, src2); } else { e.vmovdqa(e.ptr[e.rsp + stack_offset_src1], src1); e.vmovdqa(e.ptr[e.rsp + stack_offset_src2], src2); Xbyak::Label looper; e.xor_(e.edx, e.edx); e.L(looper); e.movsx(e.ecx, e.word[e.rsp + stack_offset_src2 + e.rdx]); e.movsx(e.eax, e.word[e.rsp + stack_offset_src1 + e.rdx]); e.lea(e.ecx, e.ptr[e.ecx + e.eax + 1]); e.sar(e.ecx, 1); e.mov(e.word[e.rsp + stack_offset_src1 + e.rdx], e.cx); e.add(e.edx, 2); e.cmp(e.edx, 16); e.jnz(looper); e.vmovdqa(dest, e.ptr[e.rsp + stack_offset_src1]); } break; case INT32_TYPE: { // No 32bit averages in AVX. e.vmovdqa(e.ptr[e.rsp + stack_offset_src1], src1); e.vmovdqa(e.ptr[e.rsp + stack_offset_src2], src2); Xbyak::Label looper; e.xor_(e.edx, e.edx); e.L(looper); auto src2_current_ptr = e.dword[e.rsp + stack_offset_src2 + e.rdx]; auto src1_current_ptr = e.dword[e.rsp + stack_offset_src1 + e.rdx]; if (is_unsigned) { // implicit zero-ext e.mov(e.ecx, src2_current_ptr); e.mov(e.eax, src1_current_ptr); } else { e.movsxd(e.rcx, src2_current_ptr); e.movsxd(e.rax, src1_current_ptr); } e.lea(e.rcx, e.ptr[e.rcx + e.rax + 1]); if (is_unsigned) { e.shr(e.rcx, 1); } else { e.sar(e.rcx, 1); } e.mov(e.dword[e.rsp + stack_offset_src1 + e.rdx], e.ecx); e.add(e.edx, 4); e.cmp(e.edx, 16); e.jnz(looper); e.vmovdqa(dest, e.ptr[e.rsp + stack_offset_src1]); } break; default: assert_unhandled_case(part_type); break; } }); } }; EMITTER_OPCODE_TABLE(OPCODE_VECTOR_AVERAGE, VECTOR_AVERAGE); // ============================================================================ // OPCODE_INSERT // ============================================================================ struct INSERT_I8 : Sequence> { static void Emit(X64Emitter& e, const EmitArgType& i) { assert_true(i.src2.is_constant); e.vpinsrb(i.dest, i.src3.reg().cvt32(), i.src2.constant() ^ 0x3); } }; struct INSERT_I16 : Sequence> { static void Emit(X64Emitter& e, const EmitArgType& i) { assert_true(i.src2.is_constant); e.vpinsrw(i.dest, i.src3.reg().cvt32(), i.src2.constant() ^ 0x1); } }; struct INSERT_I32 : Sequence> { static void Emit(X64Emitter& e, const EmitArgType& i) { assert_true(i.src2.is_constant); e.vpinsrd(i.dest, i.src3, i.src2.constant()); } }; EMITTER_OPCODE_TABLE(OPCODE_INSERT, INSERT_I8, INSERT_I16, INSERT_I32); // ============================================================================ // OPCODE_EXTRACT // ============================================================================ // TODO(benvanik): sequence extract/splat: // v0.i32 = extract v0.v128, 0 // v0.v128 = splat v0.i32 // This can be a single broadcast. struct EXTRACT_I8 : Sequence> { static void Emit(X64Emitter& e, const EmitArgType& i) { if (i.src2.is_constant) { e.vpextrb(i.dest.reg().cvt32(), i.src1, VEC128_B(i.src2.constant())); } else { e.mov(e.eax, 0x00000003); e.xor_(e.al, i.src2); e.and_(e.al, 0x1F); e.vmovd(e.xmm0, e.eax); e.vpshufb(e.xmm0, i.src1, e.xmm0); e.vmovd(i.dest.reg().cvt32(), e.xmm0); e.and_(i.dest, uint8_t(0xFF)); } } }; struct EXTRACT_I16 : Sequence> { static void Emit(X64Emitter& e, const EmitArgType& i) { if (i.src2.is_constant) { e.vpextrw(i.dest.reg().cvt32(), i.src1, VEC128_W(i.src2.constant())); } else { e.mov(e.al, i.src2); e.xor_(e.al, 0x01); e.shl(e.al, 1); e.mov(e.ah, e.al); e.add(e.ah, 1); e.vmovd(e.xmm0, e.eax); e.vpshufb(e.xmm0, i.src1, e.xmm0); e.vmovd(i.dest.reg().cvt32(), e.xmm0); e.and_(i.dest.reg().cvt32(), 0xFFFFu); } } }; struct EXTRACT_I32 : Sequence> { static void Emit(X64Emitter& e, const EmitArgType& i) { static const vec128_t extract_table_32[4] = { vec128b(3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), vec128b(7, 6, 5, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), vec128b(11, 10, 9, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), vec128b(15, 14, 13, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), }; if (i.src2.is_constant) { // TODO(gibbed): add support to constant propagation pass for // OPCODE_EXTRACT. Xmm src1; if (i.src1.is_constant) { src1 = e.xmm0; e.LoadConstantXmm(src1, i.src1.constant()); } else { src1 = i.src1; } if (i.src2.constant() == 0) { e.vmovd(i.dest, src1); } else { e.vpextrd(i.dest, src1, VEC128_D(i.src2.constant())); } } else { // TODO(benvanik): try out hlide's version: // e.mov(e.eax, 3); // e.and_(e.al, i.src2); // eax = [(i&3), 0, 0, 0] // e.imul(e.eax, 0x04040404); // [(i&3)*4, (i&3)*4, (i&3)*4, (i&3)*4] // e.add(e.eax, 0x00010203); // [((i&3)*4)+3, ((i&3)*4)+2, ((i&3)*4)+1, // ((i&3)*4)+0] // e.vmovd(e.xmm0, e.eax); // e.vpshufb(e.xmm0, i.src1, e.xmm0); // e.vmovd(i.dest.reg().cvt32(), e.xmm0); // Get the desired word in xmm0, then extract that. Xmm src1; if (i.src1.is_constant) { src1 = e.xmm1; e.LoadConstantXmm(src1, i.src1.constant()); } else { src1 = i.src1.reg(); } e.xor_(e.rax, e.rax); e.mov(e.al, i.src2); e.and_(e.al, 0x03); e.shl(e.al, 4); e.mov(e.rdx, reinterpret_cast(extract_table_32)); e.vmovaps(e.xmm0, e.ptr[e.rdx + e.rax]); e.vpshufb(e.xmm0, src1, e.xmm0); e.vpextrd(i.dest, e.xmm0, 0); } } }; EMITTER_OPCODE_TABLE(OPCODE_EXTRACT, EXTRACT_I8, EXTRACT_I16, EXTRACT_I32); // ============================================================================ // OPCODE_SPLAT // ============================================================================ // Copy a value into all elements of a vector struct SPLAT_I8 : Sequence> { static void Emit(X64Emitter& e, const EmitArgType& i) { if (i.src1.is_constant) { if (e.IsFeatureEnabled(kX64EmitGFNI)) { e.pxor(e.xmm0, e.xmm0); e.gf2p8affineqb(i.dest, e.xmm0, i.src1.constant()); return; } e.mov(e.eax, i.src1.constant()); e.vmovd(e.xmm0, e.eax); } else { e.vmovd(e.xmm0, i.src1.reg().cvt32()); } if (e.IsFeatureEnabled(kX64EmitAVX2)) { e.vpbroadcastb(i.dest, e.xmm0); } else { e.vpunpcklbw(e.xmm0, e.xmm0); e.vpunpcklwd(e.xmm0, e.xmm0); e.vpshufd(i.dest, e.xmm0, 0); } } }; struct SPLAT_I16 : Sequence> { static void Emit(X64Emitter& e, const EmitArgType& i) { if (i.src1.is_constant) { // TODO(benvanik): faster constant splats. e.mov(e.eax, i.src1.constant()); e.vmovd(e.xmm0, e.eax); } else { e.vmovd(e.xmm0, i.src1.reg().cvt32()); } if (e.IsFeatureEnabled(kX64EmitAVX2)) { e.vpbroadcastw(i.dest, e.xmm0); } else { e.vpunpcklwd(e.xmm0, e.xmm0); // unpack low word data e.vpshufd(i.dest, e.xmm0, 0); } } }; struct SPLAT_I32 : Sequence> { static void Emit(X64Emitter& e, const EmitArgType& i) { if (i.src1.is_constant) { // TODO(benvanik): faster constant splats. e.mov(e.eax, i.src1.constant()); e.vmovd(e.xmm0, e.eax); } else { e.vmovd(e.xmm0, i.src1); } if (e.IsFeatureEnabled(kX64EmitAVX2)) { e.vpbroadcastd(i.dest, e.xmm0); } else { e.vpshufd(i.dest, e.xmm0, 0); } } }; struct SPLAT_F32 : Sequence> { static void Emit(X64Emitter& e, const EmitArgType& i) { if (e.IsFeatureEnabled(kX64EmitAVX2)) { if (i.src1.is_constant) { // TODO(benvanik): faster constant splats. e.mov(e.eax, i.src1.value->constant.i32); e.vmovd(e.xmm0, e.eax); e.vbroadcastss(i.dest, e.xmm0); } else { e.vbroadcastss(i.dest, i.src1); } } else { if (i.src1.is_constant) { e.mov(e.eax, i.src1.value->constant.i32); e.vmovd(i.dest, e.eax); e.vshufps(i.dest, i.dest, i.dest, 0); } else { e.vshufps(i.dest, i.src1, i.src1, 0); } } } }; EMITTER_OPCODE_TABLE(OPCODE_SPLAT, SPLAT_I8, SPLAT_I16, SPLAT_I32, SPLAT_F32); // ============================================================================ // OPCODE_PERMUTE // ============================================================================ struct PERMUTE_I32 : Sequence> { static void Emit(X64Emitter& e, const EmitArgType& i) { assert_true(i.instr->flags == INT32_TYPE); // Permute words between src2 and src3. // TODO(benvanik): check src3 for zero. if 0, we can use pshufb. if (i.src1.is_constant) { uint32_t control = i.src1.constant(); // Shuffle things into the right places in dest & xmm0, // then we blend them together. uint32_t src_control = (((control >> 24) & 0x3) << 6) | (((control >> 16) & 0x3) << 4) | (((control >> 8) & 0x3) << 2) | (((control >> 0) & 0x3) << 0); uint32_t blend_control = 0; if (e.IsFeatureEnabled(kX64EmitAVX2)) { // Blender for vpblendd blend_control = (((control >> 26) & 0x1) << 3) | (((control >> 18) & 0x1) << 2) | (((control >> 10) & 0x1) << 1) | (((control >> 2) & 0x1) << 0); } else { // Blender for vpblendw blend_control = (((control >> 26) & 0x1) << 6) | (((control >> 18) & 0x1) << 4) | (((control >> 10) & 0x1) << 2) | (((control >> 2) & 0x1) << 0); blend_control |= blend_control << 1; } // TODO(benvanik): if src2/src3 are constants, shuffle now! Xmm src2; if (i.src2.is_constant) { src2 = e.xmm1; e.LoadConstantXmm(src2, i.src2.constant()); } else { src2 = i.src2; } Xmm src3; if (i.src3.is_constant) { src3 = e.xmm2; e.LoadConstantXmm(src3, i.src3.constant()); } else { src3 = i.src3; } if (i.dest != src3) { e.vpshufd(i.dest, src2, src_control); e.vpshufd(e.xmm0, src3, src_control); } else { e.vmovaps(e.xmm0, src3); e.vpshufd(i.dest, src2, src_control); e.vpshufd(e.xmm0, e.xmm0, src_control); } if (e.IsFeatureEnabled(kX64EmitAVX2)) { e.vpblendd(i.dest, e.xmm0, blend_control); // $0 = $1 $2 } else { e.vpblendw(i.dest, e.xmm0, blend_control); // $0 = $1 $2 } } else { // Permute by non-constant. assert_always(); } } }; // todo: use this on const src1 static vec128_t FixupConstantShuf8(vec128_t input) { for (uint32_t i = 0; i < 16; ++i) { input.u8[i] ^= 0x03; input.u8[i] &= 0x1F; } return input; } struct PERMUTE_V128 : Sequence> { static void EmitByInt8(X64Emitter& e, const EmitArgType& i) { // TODO(benvanik): find out how to do this with only one temp register! // Permute bytes between src2 and src3. // src1 is an array of indices corresponding to positions within src2 and // src3. if (i.src3.value->IsConstantZero()) { // Permuting with src2/zero, so just shuffle/mask. if (i.src2.value->IsConstantZero()) { // src2 & src3 are zero, so result will always be zero. e.vpxor(i.dest, i.dest); } else { // Control mask needs to be shuffled. if (i.src1.is_constant) { e.LoadConstantXmm(e.xmm0, i.src1.constant()); e.vxorps(e.xmm0, e.xmm0, e.GetXmmConstPtr(XMMSwapWordMask)); } else { e.vxorps(e.xmm0, i.src1, e.GetXmmConstPtr(XMMSwapWordMask)); } if (e.IsFeatureEnabled(kX64EmitAVX512Ortho | kX64EmitAVX512VBMI)) { Xmm table_lo = e.xmm1; if (i.src2.is_constant) { e.LoadConstantXmm(table_lo, i.src2.constant()); } else { table_lo = i.src2; } Opmask zeroes = e.k1; // _mm_cmple_epu8_mask e.vpcmpub(zeroes, e.xmm0, e.GetXmmConstPtr(XMMPermuteControl15), 2); e.vpermb(i.dest.reg() | zeroes | e.T_z, e.xmm0, table_lo); return; } e.vpand(e.xmm0, e.GetXmmConstPtr(XMMPermuteByteMask)); if (i.src2.is_constant) { e.LoadConstantXmm(i.dest, i.src2.constant()); e.vpshufb(i.dest, i.dest, e.xmm0); } else { e.vpshufb(i.dest, i.src2, e.xmm0); } // Build a mask with values in src2 having 0 and values in src3 having // 1. e.vpcmpgtb(e.xmm0, e.xmm0, e.GetXmmConstPtr(XMMPermuteControl15)); e.vpandn(i.dest, e.xmm0, i.dest); } } else { // General permute. // Control mask needs to be shuffled. // TODO(benvanik): do constants here instead of in generated code. if (e.IsFeatureEnabled(kX64EmitAVX512Ortho | kX64EmitAVX512BW | kX64EmitAVX512VBMI)) { Xmm table_idx = e.xmm0; if (i.src1.is_constant) { e.LoadConstantXmm(table_idx, i.src1.constant()); e.vxorps(table_idx, table_idx, e.GetXmmConstPtr(XMMSwapWordMask)); } else { e.vxorps(table_idx, i.src1, e.GetXmmConstPtr(XMMSwapWordMask)); } Xmm table_lo = e.xmm1; if (i.src2.value->IsConstantZero()) { e.vpxor(table_lo, table_lo); } else if (i.src2.is_constant) { e.LoadConstantXmm(table_lo, i.src2.constant()); } else { table_lo = i.src2; } Xmm table_hi = e.xmm2; if (i.src3.value->IsConstantZero()) { e.vpxor(table_hi, table_hi); } else if (i.src3.is_constant) { e.LoadConstantXmm(table_hi, i.src3.constant()); } else { table_hi = i.src3; } e.vpermi2b(table_idx, table_lo, table_hi); e.vmovdqu8(i.dest, table_idx); return; } if (i.src1.is_constant) { e.LoadConstantXmm(e.xmm2, i.src1.constant()); e.vxorps(e.xmm2, e.xmm2, e.GetXmmConstPtr(XMMSwapWordMask)); } else { e.vxorps(e.xmm2, i.src1, e.GetXmmConstPtr(XMMSwapWordMask)); } e.vpand(e.xmm2, e.GetXmmConstPtr(XMMPermuteByteMask)); Xmm src2_shuf = e.xmm0; if (i.src2.value->IsConstantZero()) { e.vpxor(src2_shuf, src2_shuf); } else if (i.src2.is_constant) { e.LoadConstantXmm(src2_shuf, i.src2.constant()); e.vpshufb(src2_shuf, src2_shuf, e.xmm2); } else { e.vpshufb(src2_shuf, i.src2, e.xmm2); } Xmm src3_shuf = e.xmm1; if (i.src3.value->IsConstantZero()) { e.vpxor(src3_shuf, src3_shuf); } else if (i.src3.is_constant) { e.LoadConstantXmm(src3_shuf, i.src3.constant()); e.vpshufb(src3_shuf, src3_shuf, e.xmm2); } else { e.vpshufb(src3_shuf, i.src3, e.xmm2); } // Build a mask with values in src2 having 0 and values in src3 // having 1. e.vpcmpgtb(i.dest, e.xmm2, e.GetXmmConstPtr(XMMPermuteControl15)); e.vpblendvb(i.dest, src2_shuf, src3_shuf, i.dest); } } static void EmitByInt16(X64Emitter& e, const EmitArgType& i) { // src1 is an array of indices corresponding to positions within src2 and // src3 if (e.IsFeatureEnabled(kX64EmitAVX512Ortho | kX64EmitAVX512BW)) { e.LoadConstantXmm(e.xmm1, vec128s(0x1)); Xmm table_idx = e.xmm0; if (i.src1.is_constant) { e.LoadConstantXmm(table_idx, i.src1.constant()); e.vpxord(table_idx, table_idx, e.xmm1); } else { e.vpxord(table_idx, i.src1, e.xmm1); } Xmm table_lo = e.xmm1; if (i.src2.is_constant) { e.LoadConstantXmm(table_lo, i.src2.constant()); } else { table_lo = i.src2; } Xmm table_hi = e.xmm2; if (i.src3.is_constant) { e.LoadConstantXmm(table_hi, i.src3.constant()); } else { table_hi = i.src3; } e.vpermi2w(table_idx, table_lo, table_hi); e.vmovdqu8(i.dest, table_idx); return; } assert_true(i.src1.is_constant); vec128_t perm = (i.src1.constant() & vec128s(0xF)) ^ vec128s(0x1); vec128_t perm_ctrl = vec128b(0); for (int i = 0; i < 8; i++) { perm_ctrl.i16[i] = perm.i16[i] > 7 ? -1 : 0; auto v = uint8_t(perm.u16[i]); perm.u8[i * 2] = v * 2; perm.u8[i * 2 + 1] = v * 2 + 1; } e.LoadConstantXmm(e.xmm0, perm); if (i.src2.is_constant) { e.LoadConstantXmm(e.xmm1, i.src2.constant()); } else { e.vmovdqa(e.xmm1, i.src2); } if (i.src3.is_constant) { e.LoadConstantXmm(e.xmm2, i.src3.constant()); } else { e.vmovdqa(e.xmm2, i.src3); } e.vpshufb(e.xmm1, e.xmm1, e.xmm0); e.vpshufb(e.xmm2, e.xmm2, e.xmm0); uint8_t mask = 0; for (int i = 0; i < 8; i++) { if (perm_ctrl.i16[i] == 0) { mask |= 1 << (7 - i); } } e.vpblendw(i.dest, e.xmm1, e.xmm2, mask); } static void EmitByInt32(X64Emitter& e, const EmitArgType& i) { assert_always(); } static void Emit(X64Emitter& e, const EmitArgType& i) { switch (i.instr->flags) { case INT8_TYPE: EmitByInt8(e, i); break; case INT16_TYPE: EmitByInt16(e, i); break; case INT32_TYPE: EmitByInt32(e, i); break; default: assert_unhandled_case(i.instr->flags); return; } } }; EMITTER_OPCODE_TABLE(OPCODE_PERMUTE, PERMUTE_I32, PERMUTE_V128); template static void emit_fast_f16_unpack(X64Emitter& e, const Inst& i, XmmConst initial_shuffle) { auto src1 = i.src1; e.vpshufb(i.dest, src1, e.GetXmmConstPtr(initial_shuffle)); e.vpmovsxwd(e.xmm1, i.dest); e.vpsrld(e.xmm2, e.xmm1, 10); e.vpmovsxwd(e.xmm0, i.dest); e.vpand(e.xmm0, e.xmm0, e.GetXmmConstPtr(XMMSignMaskPS)); e.vpand(e.xmm2, e.xmm2, e.GetXmmConstPtr(XMMPermuteByteMask)); e.vpslld(e.xmm3, e.xmm2, 23); e.vpaddd(e.xmm3, e.xmm3, e.GetXmmConstPtr(XMMF16UnpackLCPI2)); e.vpcmpeqd(e.xmm2, e.xmm2, e.GetXmmConstPtr(XMMZero)); e.vpslld(e.xmm1, e.xmm1, 13); e.vpandn(e.xmm1, e.xmm2, e.xmm1); e.vpandn(e.xmm2, e.xmm2, e.xmm3); e.vpand(e.xmm1, e.xmm1, e.GetXmmConstPtr(XMMF16UnpackLCPI3)); e.vpor(e.xmm0, e.xmm1, e.xmm0); e.vpor(i.dest, e.xmm0, e.xmm2); } template static void emit_fast_f16_pack(X64Emitter& e, const Inst& i, XmmConst final_shuffle) { e.vpaddd(e.xmm1, i.src1, e.GetXmmConstPtr(XMMF16PackLCPI0)); e.vpand(e.xmm2, i.src1, e.GetXmmConstPtr(XMMAbsMaskPS)); e.vmovdqa(e.xmm3, e.GetXmmConstPtr(XMMF16PackLCPI2)); e.vpcmpgtd(e.xmm3, e.xmm3, e.xmm2); e.vpsrld(e.xmm1, e.xmm1, 13); e.vpaddd(e.xmm2, e.xmm2, e.GetXmmConstPtr(XMMF16PackLCPI3)); e.vpminud(e.xmm0, e.xmm2, e.GetXmmConstPtr(XMMF16PackLCPI4)); e.vpcmpeqd(e.xmm2, e.xmm2, e.xmm0); e.vmovdqa(e.xmm0, e.GetXmmConstPtr(XMMF16PackLCPI5)); e.vpand(e.xmm1, e.xmm1, e.xmm0); e.vpand(e.xmm1, e.xmm2, e.xmm1); e.vpxor(e.xmm2, e.xmm2, e.xmm2); e.vblendvps(e.xmm1, e.xmm0, e.xmm1, e.xmm3); e.vpsrld(e.xmm0, i.src1, 16); e.vpand(e.xmm0, e.xmm0, e.GetXmmConstPtr(XMMF16PackLCPI6)); e.vorps(e.xmm0, e.xmm1, e.xmm0); e.vpackusdw(i.dest, e.xmm0, e.xmm2); e.vpshufb(i.dest, i.dest, e.GetXmmConstPtr(final_shuffle)); } // ============================================================================ // OPCODE_SWIZZLE // ============================================================================ struct SWIZZLE : Sequence> { static void Emit(X64Emitter& e, const EmitArgType& i) { auto element_type = i.instr->flags; if (element_type == INT8_TYPE) { assert_always(); } else if (element_type == INT16_TYPE) { assert_always(); } else if (element_type == INT32_TYPE || element_type == FLOAT32_TYPE) { uint8_t swizzle_mask = static_cast(i.src2.value); Xmm src1; if (i.src1.is_constant) { src1 = e.xmm0; e.LoadConstantXmm(src1, i.src1.constant()); } else { src1 = i.src1; } if (element_type == INT32_TYPE) { e.vpshufd(i.dest, src1, swizzle_mask); } else if (element_type == FLOAT32_TYPE) { e.vshufps(i.dest, src1, src1, swizzle_mask); } } else if (element_type == INT64_TYPE || element_type == FLOAT64_TYPE) { assert_always(); } else { assert_always(); } } }; EMITTER_OPCODE_TABLE(OPCODE_SWIZZLE, SWIZZLE); // ============================================================================ // OPCODE_PACK // ============================================================================ struct PACK : Sequence> { static void Emit(X64Emitter& e, const EmitArgType& i) { e.ChangeMxcsrMode(MXCSRMode::Vmx); switch (i.instr->flags & PACK_TYPE_MODE) { case PACK_TYPE_D3DCOLOR: EmitD3DCOLOR(e, i); break; case PACK_TYPE_FLOAT16_2: EmitFLOAT16_2(e, i); break; case PACK_TYPE_FLOAT16_4: EmitFLOAT16_4(e, i); break; case PACK_TYPE_SHORT_2: EmitSHORT_2(e, i); break; case PACK_TYPE_SHORT_4: EmitSHORT_4(e, i); break; case PACK_TYPE_UINT_2101010: EmitUINT_2101010(e, i); break; case PACK_TYPE_ULONG_4202020: EmitULONG_4202020(e, i); break; case PACK_TYPE_8_IN_16: Emit8_IN_16(e, i, i.instr->flags); break; case PACK_TYPE_16_IN_32: Emit16_IN_32(e, i, i.instr->flags); break; default: assert_unhandled_case(i.instr->flags); break; } } static void EmitD3DCOLOR(X64Emitter& e, const EmitArgType& i) { assert_true(i.src2.value->IsConstantZero()); Xmm src; if (i.src1.is_constant) { src = i.dest; e.LoadConstantXmm(src, i.src1.constant()); } else { src = i.src1; } // Saturate to [3,3....] so that only values between 3...[00] and 3...[FF] // are valid - max before min to pack NaN as zero (5454082B is heavily // affected by the order - packs 0xFFFFFFFF in matrix code to get a 0 // constant). e.vmaxps(i.dest, src, e.GetXmmConstPtr(XMM3333)); e.vminps(i.dest, i.dest, e.GetXmmConstPtr(XMMPackD3DCOLORSat)); // Extract bytes. // RGBA (XYZW) -> ARGB (WXYZ) // w = ((src1.uw & 0xFF) << 24) | ((src1.ux & 0xFF) << 16) | // ((src1.uy & 0xFF) << 8) | (src1.uz & 0xFF) e.vpshufb(i.dest, i.dest, e.GetXmmConstPtr(XMMPackD3DCOLOR)); } static __m128i EmulateFLOAT16_2(void*, __m128 src1) { alignas(16) float a[4]; alignas(16) uint16_t b[8]; _mm_store_ps(a, src1); std::memset(b, 0, sizeof(b)); for (int i = 0; i < 2; i++) { b[7 - i] = float_to_xenos_half(a[i]); } return _mm_load_si128(reinterpret_cast<__m128i*>(b)); } static void EmitFLOAT16_2(X64Emitter& e, const EmitArgType& i) { assert_true(i.src2.value->IsConstantZero()); // http://blogs.msdn.com/b/chuckw/archive/2012/09/11/directxmath-f16c-and-fma.aspx // dest = [(src1.x | src1.y), 0, 0, 0] if (i.src1.is_constant) { e.lea(e.GetNativeParam(0), e.StashConstantXmm(0, i.src1.constant())); } else { e.lea(e.GetNativeParam(0), e.StashXmm(0, i.src1)); } e.CallNativeSafe(reinterpret_cast(EmulateFLOAT16_2)); e.vmovaps(i.dest, e.xmm0); } static void EmitFLOAT16_4(X64Emitter& e, const EmitArgType& i) { if (!i.src1.is_constant) { emit_fast_f16_pack(e, i, XMMPackFLOAT16_4); } else { vec128_t result = vec128b(0); for (unsigned idx = 0; idx < 4; ++idx) { result.u16[(7 - (idx ^ 2))] = float_to_xenos_half(i.src1.constant().f32[idx]); } e.LoadConstantXmm(i.dest, result); } } static void EmitSHORT_2(X64Emitter& e, const EmitArgType& i) { assert_true(i.src2.value->IsConstantZero()); Xmm src; if (i.src1.is_constant) { src = i.dest; e.LoadConstantXmm(src, i.src1.constant()); } else { src = i.src1; } // Saturate. e.vmaxps(i.dest, src, e.GetXmmConstPtr(XMMPackSHORT_Min)); e.vminps(i.dest, i.dest, e.GetXmmConstPtr(XMMPackSHORT_Max)); // Pack. e.vpshufb(i.dest, i.dest, e.GetXmmConstPtr(XMMPackSHORT_2)); } static void EmitSHORT_4(X64Emitter& e, const EmitArgType& i) { assert_true(i.src2.value->IsConstantZero()); Xmm src; if (i.src1.is_constant) { src = i.dest; e.LoadConstantXmm(src, i.src1.constant()); } else { src = i.src1; } // Saturate. e.vmaxps(i.dest, src, e.GetXmmConstPtr(XMMPackSHORT_Min)); e.vminps(i.dest, i.dest, e.GetXmmConstPtr(XMMPackSHORT_Max)); // Pack. e.vpshufb(i.dest, i.dest, e.GetXmmConstPtr(XMMPackSHORT_4)); } static void EmitUINT_2101010(X64Emitter& e, const EmitArgType& i) { // https://www.opengl.org/registry/specs/ARB/vertex_type_2_10_10_10_rev.txt // XYZ are 10 bits, signed and saturated. // W is 2 bits, unsigned and saturated. Xmm src; if (i.src1.is_constant) { src = i.dest; e.LoadConstantXmm(src, i.src1.constant()); } else { src = i.src1; } // Saturate. e.vmaxps(i.dest, src, e.GetXmmConstPtr(XMMPackUINT_2101010_MinUnpacked)); e.vminps(i.dest, i.dest, e.GetXmmConstPtr(XMMPackUINT_2101010_MaxUnpacked)); // Remove the unneeded bits of the floats. e.vpand(i.dest, e.GetXmmConstPtr(XMMPackUINT_2101010_MaskUnpacked)); if (e.IsFeatureEnabled(kX64EmitAVX2)) { // Shift the components up. e.vpsllvd(i.dest, i.dest, e.GetXmmConstPtr(XMMPackUINT_2101010_Shift)); } else { // Duplicate all the components into bits 10-19. e.vpslld(e.xmm0, i.dest, 10); e.vpor(i.dest, e.xmm0); // Duplicate all the components into bits 20-39 // (so alpha will be in 30-31). e.vpslld(e.xmm0, i.dest, 20); e.vpor(i.dest, e.xmm0); // Leave only the needed components. e.vpand(i.dest, e.GetXmmConstPtr(XMMPackUINT_2101010_MaskPacked)); } // Combine the components. e.vshufps(e.xmm0, i.dest, i.dest, _MM_SHUFFLE(2, 3, 0, 1)); e.vorps(i.dest, e.xmm0); e.vshufps(e.xmm0, i.dest, i.dest, _MM_SHUFFLE(1, 0, 3, 2)); e.vorps(i.dest, e.xmm0); } static void EmitULONG_4202020(X64Emitter& e, const EmitArgType& i) { // XYZ are 20 bits, signed and saturated. // W is 4 bits, unsigned and saturated. Xmm src; if (i.src1.is_constant) { src = i.dest; e.LoadConstantXmm(src, i.src1.constant()); } else { src = i.src1; } // Saturate. e.vmaxps(i.dest, src, e.GetXmmConstPtr(XMMPackULONG_4202020_MinUnpacked)); e.vminps(i.dest, i.dest, e.GetXmmConstPtr(XMMPackULONG_4202020_MaxUnpacked)); // Remove the unneeded bits of the floats (so excess nibbles will also be // cleared). e.vpand(i.dest, e.GetXmmConstPtr(XMMPackULONG_4202020_MaskUnpacked)); // Store Y and W shifted left by 4 so vpshufb can be used with them. e.vpslld(e.xmm0, i.dest, 4); // Place XZ where they're supposed to be. e.vpshufb(i.dest, i.dest, e.GetXmmConstPtr(XMMPackULONG_4202020_PermuteXZ)); // Place YW. e.vpshufb(e.xmm0, e.xmm0, e.GetXmmConstPtr(XMMPackULONG_4202020_PermuteYW)); // Merge XZ and YW. e.vorps(i.dest, e.xmm0); } static __m128i EmulatePack8_IN_16_UN_UN_SAT(void*, __m128i src1, __m128i src2) { alignas(16) uint16_t a[8]; alignas(16) uint16_t b[8]; alignas(16) uint8_t c[16]; _mm_store_si128(reinterpret_cast<__m128i*>(a), src1); _mm_store_si128(reinterpret_cast<__m128i*>(b), src2); for (int i = 0; i < 8; ++i) { c[i] = uint8_t(std::max(uint16_t(0), std::min(uint16_t(255), a[i]))); c[i + 8] = uint8_t(std::max(uint16_t(0), std::min(uint16_t(255), b[i]))); } return _mm_load_si128(reinterpret_cast<__m128i*>(c)); } static __m128i EmulatePack8_IN_16_UN_UN(void*, __m128i src1, __m128i src2) { alignas(16) uint8_t a[16]; alignas(16) uint8_t b[16]; alignas(16) uint8_t c[16]; _mm_store_si128(reinterpret_cast<__m128i*>(a), src1); _mm_store_si128(reinterpret_cast<__m128i*>(b), src2); for (int i = 0; i < 8; ++i) { c[i] = a[i * 2]; c[i + 8] = b[i * 2]; } return _mm_load_si128(reinterpret_cast<__m128i*>(c)); } static void Emit8_IN_16(X64Emitter& e, const EmitArgType& i, uint32_t flags) { // TODO(benvanik): handle src2 (or src1) being constant zero if (IsPackInUnsigned(flags)) { if (IsPackOutUnsigned(flags)) { if (IsPackOutSaturate(flags)) { // unsigned -> unsigned + saturate if (i.src2.is_constant) { e.lea(e.GetNativeParam(1), e.StashConstantXmm(1, i.src2.constant())); } else { e.lea(e.GetNativeParam(1), e.StashXmm(1, i.src2)); } e.lea(e.GetNativeParam(0), e.StashXmm(0, i.src1)); e.CallNativeSafe( reinterpret_cast(EmulatePack8_IN_16_UN_UN_SAT)); e.vmovaps(i.dest, e.xmm0); e.vpshufb(i.dest, i.dest, e.GetXmmConstPtr(XMMByteOrderMask)); } else { // unsigned -> unsigned e.lea(e.GetNativeParam(1), e.StashXmm(1, i.src2)); e.lea(e.GetNativeParam(0), e.StashXmm(0, i.src1)); e.CallNativeSafe(reinterpret_cast(EmulatePack8_IN_16_UN_UN)); e.vmovaps(i.dest, e.xmm0); e.vpshufb(i.dest, i.dest, e.GetXmmConstPtr(XMMByteOrderMask)); } } else { if (IsPackOutSaturate(flags)) { // unsigned -> signed + saturate assert_always(); } else { // unsigned -> signed assert_always(); } } } else { if (IsPackOutUnsigned(flags)) { if (IsPackOutSaturate(flags)) { // signed -> unsigned + saturate // PACKUSWB / SaturateSignedWordToUnsignedByte Xbyak::Xmm src2 = i.src2.is_constant ? e.xmm0 : i.src2; if (i.src2.is_constant) { e.LoadConstantXmm(src2, i.src2.constant()); } e.vpackuswb(i.dest, i.src1, src2); e.vpshufb(i.dest, i.dest, e.GetXmmConstPtr(XMMByteOrderMask)); } else { // signed -> unsigned assert_always(); } } else { if (IsPackOutSaturate(flags)) { // signed -> signed + saturate // PACKSSWB / SaturateSignedWordToSignedByte e.vpacksswb(i.dest, i.src1, i.src2); e.vpshufb(i.dest, i.dest, e.GetXmmConstPtr(XMMByteOrderMask)); } else { // signed -> signed assert_always(); } } } } // Pack 2 32-bit vectors into a 16-bit vector. static void Emit16_IN_32(X64Emitter& e, const EmitArgType& i, uint32_t flags) { // TODO(benvanik): handle src2 (or src1) being constant zero if (IsPackInUnsigned(flags)) { if (IsPackOutUnsigned(flags)) { if (IsPackOutSaturate(flags)) { // unsigned -> unsigned + saturate // Construct a saturation max value e.mov(e.eax, 0xFFFFu); e.vmovd(e.xmm0, e.eax); e.vpshufd(e.xmm0, e.xmm0, 0b00000000); if (!i.src1.is_constant) { e.vpminud(e.xmm1, i.src1, e.xmm0); // Saturate src1 e.vpshuflw(e.xmm1, e.xmm1, 0b00100010); e.vpshufhw(e.xmm1, e.xmm1, 0b00100010); e.vpshufd(e.xmm1, e.xmm1, 0b00001000); } else { // TODO(DrChat): Non-zero constants assert_true(i.src1.constant().u64[0] == 0 && i.src1.constant().u64[1] == 0); e.vpxor(e.xmm1, e.xmm1); } if (!i.src2.is_constant) { e.vpminud(i.dest, i.src2, e.xmm0); // Saturate src2 e.vpshuflw(i.dest, i.dest, 0b00100010); e.vpshufhw(i.dest, i.dest, 0b00100010); e.vpshufd(i.dest, i.dest, 0b10000000); } else { // TODO(DrChat): Non-zero constants assert_true(i.src2.constant().u64[0] == 0 && i.src2.constant().u64[1] == 0); e.vpxor(i.dest, i.dest); } e.vpblendw(i.dest, i.dest, e.xmm1, 0b00001111); } else { // unsigned -> unsigned e.vmovaps(e.xmm0, i.src1); e.vpshuflw(e.xmm0, e.xmm0, 0b00100010); e.vpshufhw(e.xmm0, e.xmm0, 0b00100010); e.vpshufd(e.xmm0, e.xmm0, 0b00001000); e.vmovaps(i.dest, i.src2); e.vpshuflw(i.dest, i.dest, 0b00100010); e.vpshufhw(i.dest, i.dest, 0b00100010); e.vpshufd(i.dest, i.dest, 0b10000000); e.vpblendw(i.dest, i.dest, e.xmm0, 0b00001111); } } else { if (IsPackOutSaturate(flags)) { // unsigned -> signed + saturate assert_always(); } else { // unsigned -> signed assert_always(); } } } else { if (IsPackOutUnsigned(flags)) { if (IsPackOutSaturate(flags)) { // signed -> unsigned + saturate // PACKUSDW // TMP[15:0] <- (DEST[31:0] < 0) ? 0 : DEST[15:0]; // DEST[15:0] <- (DEST[31:0] > FFFFH) ? FFFFH : TMP[15:0]; e.vpackusdw(i.dest, i.src1, i.src2); e.vpshuflw(i.dest, i.dest, 0b10110001); e.vpshufhw(i.dest, i.dest, 0b10110001); } else { // signed -> unsigned assert_always(); } } else { if (IsPackOutSaturate(flags)) { // signed -> signed + saturate // PACKSSDW / SaturateSignedDwordToSignedWord Xmm src2; if (!i.src2.is_constant) { src2 = i.src2; } else { assert_false(i.src1 == e.xmm0); src2 = e.xmm0; e.LoadConstantXmm(src2, i.src2.constant()); } e.vpackssdw(i.dest, i.src1, src2); e.vpshuflw(i.dest, i.dest, 0b10110001); e.vpshufhw(i.dest, i.dest, 0b10110001); } else { // signed -> signed assert_always(); } } } } }; EMITTER_OPCODE_TABLE(OPCODE_PACK, PACK); // ============================================================================ // OPCODE_UNPACK // ============================================================================ struct UNPACK : Sequence> { static void Emit(X64Emitter& e, const EmitArgType& i) { e.ChangeMxcsrMode(MXCSRMode::Vmx); switch (i.instr->flags & PACK_TYPE_MODE) { case PACK_TYPE_D3DCOLOR: EmitD3DCOLOR(e, i); break; case PACK_TYPE_FLOAT16_2: EmitFLOAT16_2(e, i); break; case PACK_TYPE_FLOAT16_4: EmitFLOAT16_4(e, i); break; case PACK_TYPE_SHORT_2: EmitSHORT_2(e, i); break; case PACK_TYPE_SHORT_4: EmitSHORT_4(e, i); break; case PACK_TYPE_UINT_2101010: EmitUINT_2101010(e, i); break; case PACK_TYPE_ULONG_4202020: EmitULONG_4202020(e, i); break; case PACK_TYPE_8_IN_16: Emit8_IN_16(e, i, i.instr->flags); break; case PACK_TYPE_16_IN_32: Emit16_IN_32(e, i, i.instr->flags); break; default: assert_unhandled_case(i.instr->flags); break; } } static void EmitD3DCOLOR(X64Emitter& e, const EmitArgType& i) { // ARGB (WXYZ) -> RGBA (XYZW) Xmm src; if (i.src1.is_constant) { if (i.src1.value->IsConstantZero()) { e.vmovaps(i.dest, e.GetXmmConstPtr(XMMOne)); return; } src = i.dest; e.LoadConstantXmm(src, i.src1.constant()); } else { src = i.src1; } // src = ZZYYXXWW // Unpack to 000000ZZ,000000YY,000000XX,000000WW e.vpshufb(i.dest, src, e.GetXmmConstPtr(XMMUnpackD3DCOLOR)); // Add 1.0f to each. e.vpor(i.dest, e.GetXmmConstPtr(XMMOne)); // To convert to 0 to 1, games multiply by 0x47008081 and add 0xC7008081. } static __m128 EmulateFLOAT16_2(void*, __m128i src1) { alignas(16) uint16_t a[8]; alignas(16) float b[4]; _mm_store_si128(reinterpret_cast<__m128i*>(a), src1); for (int i = 0; i < 2; i++) { b[i] = xenos_half_to_float(a[VEC128_W(6 + i)]); } // Constants, or something b[2] = 0.f; b[3] = 1.f; return _mm_load_ps(b); } static void EmitFLOAT16_2(X64Emitter& e, const EmitArgType& i) { // 1 bit sign, 5 bit exponent, 10 bit mantissa // D3D10 half float format // TODO(benvanik): // http://blogs.msdn.com/b/chuckw/archive/2012/09/11/directxmath-f16c-and-fma.aspx // Use _mm_cvtph_ps -- requires very modern processors (SSE5+) // Unpacking half floats: // http://fgiesen.wordpress.com/2012/03/28/half-to-float-done-quic/ // Packing half floats: https://gist.github.com/rygorous/2156668 // Load source, move from tight pack of X16Y16.... to X16...Y16... // Also zero out the high end. // TODO(benvanik): special case constant unpacks that just get 0/1/etc. if (i.src1.is_constant) { e.lea(e.GetNativeParam(0), e.StashConstantXmm(0, i.src1.constant())); } else { e.lea(e.GetNativeParam(0), e.StashXmm(0, i.src1)); } e.CallNativeSafe(reinterpret_cast(EmulateFLOAT16_2)); e.vmovaps(i.dest, e.xmm0); } static void EmitFLOAT16_4(X64Emitter& e, const EmitArgType& i) { if (i.src1.is_constant) { vec128_t result{}; for (int idx = 0; idx < 4; ++idx) { result.f32[idx] = xenos_half_to_float(i.src1.constant().u16[VEC128_W(4 + idx)]); } e.LoadConstantXmm(i.dest, result); } else { emit_fast_f16_unpack(e, i, XMMUnpackFLOAT16_4); } } static void EmitSHORT_2(X64Emitter& e, const EmitArgType& i) { // (VD.x) = 3.0 + (VB.x>>16)*2^-22 // (VD.y) = 3.0 + (VB.x)*2^-22 // (VD.z) = 0.0 // (VD.w) = 1.0 (games splat W after unpacking to get vectors of 1.0f) // src is (xx,xx,xx,VALUE) Xmm src; if (i.src1.is_constant) { if (i.src1.value->IsConstantZero()) { e.vmovdqa(i.dest, e.GetXmmConstPtr(XMM3301)); return; } // TODO(benvanik): check other common constants/perform shuffle/or here. src = i.dest; e.LoadConstantXmm(src, i.src1.constant()); } else { src = i.src1; } // Shuffle bytes. e.vpshufb(i.dest, src, e.GetXmmConstPtr(XMMUnpackSHORT_2)); // If negative, make smaller than 3 - sign extend before adding. e.vpslld(i.dest, 16); e.vpsrad(i.dest, 16); // Add 3,3,0,1. e.vpaddd(i.dest, e.GetXmmConstPtr(XMM3301)); // Return quiet NaNs in case of negative overflow. e.vcmpeqps(e.xmm0, i.dest, e.GetXmmConstPtr(XMMUnpackSHORT_Overflow)); e.vblendvps(i.dest, i.dest, e.GetXmmConstPtr(XMMQNaN), e.xmm0); } static void EmitSHORT_4(X64Emitter& e, const EmitArgType& i) { // (VD.x) = 3.0 + (VB.x>>16)*2^-22 // (VD.y) = 3.0 + (VB.x)*2^-22 // (VD.z) = 3.0 + (VB.y>>16)*2^-22 // (VD.w) = 3.0 + (VB.y)*2^-22 // src is (xx,xx,VALUE,VALUE) Xmm src; if (i.src1.is_constant) { if (i.src1.value->IsConstantZero()) { e.vmovdqa(i.dest, e.GetXmmConstPtr(XMM3333)); return; } // TODO(benvanik): check other common constants/perform shuffle/or here. src = i.dest; e.LoadConstantXmm(src, i.src1.constant()); } else { src = i.src1; } // Shuffle bytes. e.vpshufb(i.dest, src, e.GetXmmConstPtr(XMMUnpackSHORT_4)); // If negative, make smaller than 3 - sign extend before adding. e.vpslld(i.dest, 16); e.vpsrad(i.dest, 16); // Add 3,3,3,3. e.vpaddd(i.dest, e.GetXmmConstPtr(XMM3333)); // Return quiet NaNs in case of negative overflow. e.vcmpeqps(e.xmm0, i.dest, e.GetXmmConstPtr(XMMUnpackSHORT_Overflow)); e.vblendvps(i.dest, i.dest, e.GetXmmConstPtr(XMMQNaN), e.xmm0); } static void EmitUINT_2101010(X64Emitter& e, const EmitArgType& i) { Xmm src; if (i.src1.is_constant) { if (i.src1.value->IsConstantZero()) { e.vmovdqa(i.dest, e.GetXmmConstPtr(XMM3331)); return; } src = i.dest; e.LoadConstantXmm(src, i.src1.constant()); } else { src = i.src1; } // Splat W. e.vshufps(i.dest, src, src, _MM_SHUFFLE(3, 3, 3, 3)); // Keep only the needed components. // Red in 0-9 now, green in 10-19, blue in 20-29, alpha in 30-31. e.vpand(i.dest, e.GetXmmConstPtr(XMMPackUINT_2101010_MaskPacked)); if (e.IsFeatureEnabled(kX64EmitAVX2)) { // Shift the components down. e.vpsrlvd(i.dest, i.dest, e.GetXmmConstPtr(XMMPackUINT_2101010_Shift)); } else { // Duplicate green in 0-9 and alpha in 20-21. e.vpsrld(e.xmm0, i.dest, 10); e.vpor(i.dest, e.xmm0); // Duplicate blue in 0-9 and alpha in 0-1. e.vpsrld(e.xmm0, i.dest, 20); e.vpor(i.dest, e.xmm0); // Remove higher duplicate components. e.vpand(i.dest, e.GetXmmConstPtr(XMMPackUINT_2101010_MaskUnpacked)); } // If XYZ are negative, make smaller than 3 - sign extend XYZ before adding. // W is unsigned. e.vpslld(i.dest, 22); e.vpsrad(i.dest, 22); // Add 3,3,3,1. e.vpaddd(i.dest, e.GetXmmConstPtr(XMM3331)); // Return quiet NaNs in case of negative overflow. e.vcmpeqps(e.xmm0, i.dest, e.GetXmmConstPtr(XMMUnpackUINT_2101010_Overflow)); e.vblendvps(i.dest, i.dest, e.GetXmmConstPtr(XMMQNaN), e.xmm0); // To convert XYZ to -1 to 1, games multiply by 0x46004020 & sub 0x46C06030. // For W to 0 to 1, they multiply by and subtract 0x4A2AAAAB. } static void EmitULONG_4202020(X64Emitter& e, const EmitArgType& i) { Xmm src; if (i.src1.is_constant) { if (i.src1.value->IsConstantZero()) { e.vmovdqa(i.dest, e.GetXmmConstPtr(XMM3331)); return; } src = i.dest; e.LoadConstantXmm(src, i.src1.constant()); } else { src = i.src1; } // Extract pairs of nibbles to XZYW. XZ will have excess 4 upper bits, YW // will have excess 4 lower bits. e.vpshufb(i.dest, src, e.GetXmmConstPtr(XMMUnpackULONG_4202020_Permute)); // Drop the excess nibble of YW. e.vpsrld(e.xmm0, i.dest, 4); // Merge XZ and YW now both starting at offset 0. e.vshufps(i.dest, i.dest, e.xmm0, _MM_SHUFFLE(3, 2, 1, 0)); // Reorder as XYZW. e.vshufps(i.dest, i.dest, _MM_SHUFFLE(3, 1, 2, 0)); // Drop the excess upper nibble in XZ and sign-extend XYZ. e.vpslld(i.dest, 12); e.vpsrad(i.dest, 12); // Add 3,3,3,1. e.vpaddd(i.dest, e.GetXmmConstPtr(XMM3331)); // Return quiet NaNs in case of negative overflow. e.vcmpeqps(e.xmm0, i.dest, e.GetXmmConstPtr(XMMUnpackULONG_4202020_Overflow)); e.vblendvps(i.dest, i.dest, e.GetXmmConstPtr(XMMQNaN), e.xmm0); } static void Emit8_IN_16(X64Emitter& e, const EmitArgType& i, uint32_t flags) { assert_false(IsPackOutSaturate(flags)); Xmm src; if (i.src1.is_constant) { src = i.dest; e.LoadConstantXmm(src, i.src1.constant()); } else { src = i.src1; } if (IsPackToLo(flags)) { // Unpack to LO. if (IsPackInUnsigned(flags)) { if (IsPackOutUnsigned(flags)) { // unsigned -> unsigned assert_always(); } else { // unsigned -> signed assert_always(); } } else { if (IsPackOutUnsigned(flags)) { // signed -> unsigned assert_always(); } else { // signed -> signed e.vpshufb(i.dest, src, e.GetXmmConstPtr(XMMByteOrderMask)); e.vpunpckhbw(i.dest, i.dest, i.dest); e.vpsraw(i.dest, 8); } } } else { // Unpack to HI. if (IsPackInUnsigned(flags)) { if (IsPackOutUnsigned(flags)) { // unsigned -> unsigned assert_always(); } else { // unsigned -> signed assert_always(); } } else { if (IsPackOutUnsigned(flags)) { // signed -> unsigned assert_always(); } else { // signed -> signed e.vpshufb(i.dest, src, e.GetXmmConstPtr(XMMByteOrderMask)); e.vpunpcklbw(i.dest, i.dest, i.dest); e.vpsraw(i.dest, 8); } } } } static void Emit16_IN_32(X64Emitter& e, const EmitArgType& i, uint32_t flags) { assert_false(IsPackOutSaturate(flags)); Xmm src; if (i.src1.is_constant) { src = i.dest; e.LoadConstantXmm(src, i.src1.constant()); } else { src = i.src1; } if (IsPackToLo(flags)) { // Unpack to LO. if (IsPackInUnsigned(flags)) { if (IsPackOutUnsigned(flags)) { // unsigned -> unsigned assert_always(); } else { // unsigned -> signed assert_always(); } } else { if (IsPackOutUnsigned(flags)) { // signed -> unsigned assert_always(); } else { // signed -> signed e.vpunpckhwd(i.dest, src, src); e.vpsrad(i.dest, 16); } } } else { // Unpack to HI. if (IsPackInUnsigned(flags)) { if (IsPackOutUnsigned(flags)) { // unsigned -> unsigned assert_always(); } else { // unsigned -> signed assert_always(); } } else { if (IsPackOutUnsigned(flags)) { // signed -> unsigned assert_always(); } else { // signed -> signed e.vpunpcklwd(i.dest, src, src); e.vpsrad(i.dest, 16); } } } e.vpshufd(i.dest, i.dest, 0xB1); } }; EMITTER_OPCODE_TABLE(OPCODE_UNPACK, UNPACK); struct SET_NJM_I8 : Sequence> { static void Emit(X64Emitter& e, const EmitArgType& i) { auto addr_vmx = e.GetBackendCtxPtr(offsetof(X64BackendContext, mxcsr_vmx)); addr_vmx.setBit(32); if (i.src1.is_constant) { if (i.src1.constant() == 0) { // turn off daz/flush2z e.mov(addr_vmx, _MM_MASK_MASK); } else { e.mov(addr_vmx, DEFAULT_VMX_MXCSR); } } else { e.test(i.src1, i.src1); e.mov(e.edx, DEFAULT_VMX_MXCSR); e.mov(e.eax, _MM_MASK_MASK); e.cmove(e.edx, e.eax); e.mov(addr_vmx, e.edx); } e.ChangeMxcsrMode(MXCSRMode::Vmx); } }; EMITTER_OPCODE_TABLE(OPCODE_SET_NJM, SET_NJM_I8); } // namespace x64 } // namespace backend } // namespace cpu } // namespace xe