Merge pull request #52 from chrisps/canary_experimental

Fix previous batch of CPU changes
This commit is contained in:
Radosław Gliński
2022-07-18 09:20:35 +02:00
committed by GitHub
15 changed files with 856 additions and 170 deletions

View File

@@ -63,6 +63,10 @@ class Backend {
virtual void InstallBreakpoint(Breakpoint* breakpoint) {}
virtual void InstallBreakpoint(Breakpoint* breakpoint, Function* fn) {}
virtual void UninstallBreakpoint(Breakpoint* breakpoint) {}
// ctx points to the start of a ppccontext, ctx - page_allocation_granularity
// up until the start of ctx may be used by the backend to store whatever data
// they want
virtual void InitializeBackendContext(void* ctx) {}
protected:
Processor* processor_ = nullptr;

View File

@@ -32,6 +32,9 @@
#include "xenia/cpu/cpu_flags.h"
#include "xenia/cpu/function.h"
#include "xenia/cpu/function_debug_info.h"
#include "xenia/cpu/hir/instr.h"
#include "xenia/cpu/hir/opcodes.h"
#include "xenia/cpu/hir/value.h"
#include "xenia/cpu/processor.h"
#include "xenia/cpu/symbol.h"
#include "xenia/cpu/thread_state.h"
@@ -393,7 +396,8 @@ void X64Emitter::DebugBreak() {
}
uint64_t TrapDebugPrint(void* raw_context, uint64_t address) {
auto thread_state = *reinterpret_cast<ThreadState**>(raw_context);
auto thread_state =
reinterpret_cast<ppc::PPCContext_s*>(raw_context)->thread_state;
uint32_t str_ptr = uint32_t(thread_state->context()->r[3]);
// uint16_t str_len = uint16_t(thread_state->context()->r[4]);
auto str = thread_state->memory()->TranslateVirtual<const char*>(str_ptr);
@@ -408,7 +412,8 @@ uint64_t TrapDebugPrint(void* raw_context, uint64_t address) {
}
uint64_t TrapDebugBreak(void* raw_context, uint64_t address) {
auto thread_state = *reinterpret_cast<ThreadState**>(raw_context);
auto thread_state =
reinterpret_cast<ppc::PPCContext_s*>(raw_context)->thread_state;
XELOGE("tw/td forced trap hit! This should be a crash!");
if (cvars::break_on_debugbreak) {
xe::debugging::Break();
@@ -447,7 +452,8 @@ void X64Emitter::UnimplementedInstr(const hir::Instr* i) {
// This is used by the X64ThunkEmitter's ResolveFunctionThunk.
uint64_t ResolveFunction(void* raw_context, uint64_t target_address) {
auto thread_state = *reinterpret_cast<ThreadState**>(raw_context);
auto thread_state =
reinterpret_cast<ppc::PPCContext_s*>(raw_context)->thread_state;
// TODO(benvanik): required?
assert_not_zero(target_address);
@@ -1191,7 +1197,109 @@ Xbyak::Address X64Emitter::StashConstantXmm(int index, const vec128_t& v) {
MovMem64(addr + 8, v.high);
return ptr[addr];
}
static bool IsVectorCompare(const Instr* i) {
hir::Opcode op = i->opcode->num;
return op >= hir::OPCODE_VECTOR_COMPARE_EQ &&
op <= hir::OPCODE_VECTOR_COMPARE_UGE;
}
static bool IsFlaggedVectorOp(const Instr* i) {
if (IsVectorCompare(i)) {
return true;
}
hir::Opcode op = i->opcode->num;
using namespace hir;
switch (op) {
case OPCODE_VECTOR_SUB:
case OPCODE_VECTOR_ADD:
case OPCODE_SWIZZLE:
return true;
}
return false;
}
static SimdDomain GetDomainForFlaggedVectorOp(const hir::Instr* df) {
switch (df->flags) { // check what datatype we compared as
case hir::INT16_TYPE:
case hir::INT32_TYPE:
case hir::INT8_TYPE:
case hir::INT64_TYPE:
return SimdDomain::INTEGER;
case hir::FLOAT32_TYPE:
case hir::FLOAT64_TYPE: // pretty sure float64 doesnt occur with vectors.
// here for completeness
return SimdDomain::FLOATING;
default:
return SimdDomain::DONTCARE;
}
return SimdDomain::DONTCARE;
}
// this list is incomplete
static bool IsDefiniteIntegerDomainOpcode(hir::Opcode opc) {
using namespace hir;
switch (opc) {
case OPCODE_LOAD_VECTOR_SHL:
case OPCODE_LOAD_VECTOR_SHR:
case OPCODE_VECTOR_CONVERT_F2I:
case OPCODE_VECTOR_MIN: // there apparently is no FLOAT32_TYPE for min/maxs
// flags
case OPCODE_VECTOR_MAX:
case OPCODE_VECTOR_SHL:
case OPCODE_VECTOR_SHR:
case OPCODE_VECTOR_SHA:
case OPCODE_VECTOR_ROTATE_LEFT:
case OPCODE_VECTOR_AVERAGE: // apparently no float32 type for this
case OPCODE_EXTRACT:
case OPCODE_INSERT: // apparently no f32 type for these two
return true;
}
return false;
}
static bool IsDefiniteFloatingDomainOpcode(hir::Opcode opc) {
using namespace hir;
switch (opc) {
case OPCODE_VECTOR_CONVERT_I2F:
case OPCODE_VECTOR_DENORMFLUSH:
case OPCODE_DOT_PRODUCT_3:
case OPCODE_DOT_PRODUCT_4:
case OPCODE_LOG2:
case OPCODE_POW2:
case OPCODE_RECIP:
case OPCODE_ROUND:
case OPCODE_SQRT:
case OPCODE_MUL:
case OPCODE_MUL_SUB:
case OPCODE_MUL_ADD:
case OPCODE_ABS:
return true;
}
return false;
}
SimdDomain X64Emitter::DeduceSimdDomain(const hir::Value* for_value) {
hir::Instr* df = for_value->def;
if (!df) {
// todo: visit uses to figure out domain
return SimdDomain::DONTCARE;
} else {
SimdDomain result = SimdDomain::DONTCARE;
if (IsFlaggedVectorOp(df)) {
result = GetDomainForFlaggedVectorOp(df);
} else if (IsDefiniteIntegerDomainOpcode(df->opcode->num)) {
result = SimdDomain::INTEGER;
} else if (IsDefiniteFloatingDomainOpcode(df->opcode->num)) {
result = SimdDomain::FLOATING;
}
// todo: check if still dontcare, if so, visit uses of the value to figure
// it out
return result;
}
return SimdDomain::DONTCARE;
}
} // namespace x64
} // namespace backend
} // namespace cpu

View File

@@ -44,7 +44,39 @@ enum RegisterFlags {
REG_DEST = (1 << 0),
REG_ABCD = (1 << 1),
};
/*
SSE/AVX/AVX512 has seperate move instructions/shuffle instructions for float
data and int data for a reason most processors implement two distinct
pipelines, one for the integer domain and one for the floating point domain
currently, xenia makes no distinction between the two. Crossing domains is
expensive. On Zen processors the penalty is one cycle each time you cross,
plus the two pipelines need to synchronize Often xenia will emit an integer
instruction, then a floating instruction, then integer again. this
effectively adds at least two cycles to the time taken These values will in
the future be used as tags to operations that tell them which domain to
operate in, if its at all possible to avoid crossing
*/
enum class SimdDomain : uint32_t {
FLOATING,
INTEGER,
DONTCARE,
CONFLICTING // just used as a special result for PickDomain, different from
// dontcare (dontcare means we just dont know the domain,
// CONFLICTING means its used in multiple domains)
};
static SimdDomain PickDomain2(SimdDomain dom1, SimdDomain dom2) {
if (dom1 == dom2) {
return dom1;
}
if (dom1 == SimdDomain::DONTCARE) {
return dom2;
}
if (dom2 == SimdDomain::DONTCARE) {
return dom1;
}
return SimdDomain::CONFLICTING;
}
enum XmmConst {
XMMZero = 0,
XMMOne,
@@ -122,7 +154,7 @@ enum XmmConst {
XMMLVSLTableBase,
XMMLVSRTableBase,
XMMSingleDenormalMask,
XMMThreeFloatMask, //for clearing the fourth float prior to DOT_PRODUCT_3
XMMThreeFloatMask, // for clearing the fourth float prior to DOT_PRODUCT_3
XMMXenosF16ExtRangeStart
};
@@ -150,8 +182,9 @@ enum X64EmitterFeatureFlags {
kX64EmitAVX512Ortho = kX64EmitAVX512F | kX64EmitAVX512VL,
kX64EmitAVX512Ortho64 = kX64EmitAVX512Ortho | kX64EmitAVX512DQ,
kX64FastJrcx = 1 << 12, //jrcxz is as fast as any other jump ( >= Zen1)
kX64FastLoop = 1 << 13, //loop/loope/loopne is as fast as any other jump ( >= Zen2)
kX64FastJrcx = 1 << 12, // jrcxz is as fast as any other jump ( >= Zen1)
kX64FastLoop =
1 << 13, // loop/loope/loopne is as fast as any other jump ( >= Zen2)
kX64EmitAVX512VBMI = 1 << 14
};
class ResolvableGuestCall {
@@ -259,6 +292,7 @@ class X64Emitter : public Xbyak::CodeGenerator {
FunctionDebugInfo* debug_info() const { return debug_info_; }
size_t stack_size() const { return stack_size_; }
SimdDomain DeduceSimdDomain(const hir::Value* for_value);
protected:
void* Emplace(const EmitFunctionInfo& func_info,

View File

@@ -12,11 +12,11 @@
#include <algorithm>
#include <cstring>
#include "xenia/base/cvar.h"
#include "xenia/base/memory.h"
#include "xenia/cpu/backend/x64/x64_op.h"
#include "xenia/cpu/backend/x64/x64_tracers.h"
#include "xenia/cpu/ppc/ppc_context.h"
#include "xenia/base/cvar.h"
DEFINE_bool(
elide_e0_check, false,
@@ -83,11 +83,17 @@ RegExp ComputeMemoryAddressOffset(X64Emitter& e, const T& guest,
!is_definitely_not_eo(guest)) {
// Emulate the 4 KB physical address offset in 0xE0000000+ when can't do
// it via memory mapping.
// todo: do branching or use an alt membase and cmov
e.xor_(e.eax, e.eax);
e.cmp(guest.reg().cvt32(), 0xE0000000 - offset_const);
e.lea(e.edx, e.ptr[guest.reg().cvt32() + offset_const]);
e.cmp(e.edx, e.GetContextReg().cvt32());
e.setae(e.al);
e.shl(e.eax, 12);
e.add(e.eax, guest.reg().cvt32());
e.add(e.eax, e.edx);
return e.GetMembaseReg() + e.rax;
} else {
// Clear the top 32 bits, as they are likely garbage.
// TODO(benvanik): find a way to avoid doing this.
@@ -122,7 +128,7 @@ RegExp ComputeMemoryAddress(X64Emitter& e, const T& guest) {
// Emulate the 4 KB physical address offset in 0xE0000000+ when can't do
// it via memory mapping.
e.xor_(e.eax, e.eax);
e.cmp(guest.reg().cvt32(), 0xE0000000);
e.cmp(guest.reg().cvt32(), e.GetContextReg().cvt32());
e.setae(e.al);
e.shl(e.eax, 12);
e.add(e.eax, guest.reg().cvt32());
@@ -208,7 +214,7 @@ struct ATOMIC_COMPARE_EXCHANGE_I32
if (xe::memory::allocation_granularity() > 0x1000) {
// Emulate the 4 KB physical address offset in 0xE0000000+ when can't do
// it via memory mapping.
e.cmp(i.src1.reg().cvt32(), 0xE0000000);
e.cmp(i.src1.reg().cvt32(), e.GetContextReg().cvt32());
e.setae(e.cl);
e.movzx(e.ecx, e.cl);
e.shl(e.ecx, 12);
@@ -229,7 +235,7 @@ struct ATOMIC_COMPARE_EXCHANGE_I64
if (xe::memory::allocation_granularity() > 0x1000) {
// Emulate the 4 KB physical address offset in 0xE0000000+ when can't do
// it via memory mapping.
e.cmp(i.src1.reg().cvt32(), 0xE0000000);
e.cmp(i.src1.reg().cvt32(), e.GetContextReg().cvt32());
e.setae(e.cl);
e.movzx(e.ecx, e.cl);
e.shl(e.ecx, 12);
@@ -1113,7 +1119,7 @@ struct CACHE_CONTROL
if (xe::memory::allocation_granularity() > 0x1000) {
// Emulate the 4 KB physical address offset in 0xE0000000+ when can't do
// it via memory mapping.
e.cmp(i.src1.reg().cvt32(), 0xE0000000);
e.cmp(i.src1.reg().cvt32(), e.GetContextReg().cvt32());
e.setae(e.al);
e.movzx(e.eax, e.al);
e.shl(e.eax, 12);

View File

@@ -1826,7 +1826,7 @@ struct PERMUTE_I32
}
}
};
//todo: use this on const src1
// 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;
@@ -1984,7 +1984,11 @@ struct SWIZZLE
} else {
src1 = i.src1;
}
e.vpshufd(i.dest, src1, swizzle_mask);
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 {

View File

@@ -717,6 +717,9 @@ struct SELECT_V128_I8
static void Emit(X64Emitter& e, const EmitArgType& i) {
// TODO(benvanik): find a shorter sequence.
// dest = src1 != 0 ? src2 : src3
/*
chrispy: this is dead code, this sequence is never emitted
*/
e.movzx(e.eax, i.src1);
e.vmovd(e.xmm1, e.eax);
e.vpbroadcastd(e.xmm1, e.xmm1);
@@ -737,11 +740,46 @@ struct SELECT_V128_I8
e.vpor(i.dest, e.xmm1);
}
};
enum class PermittedBlend : uint32_t { NotPermitted, Int8, Ps };
static bool IsVectorCompare(const Instr* i) {
Opcode op = i->opcode->num;
return op >= OPCODE_VECTOR_COMPARE_EQ && op <= OPCODE_VECTOR_COMPARE_UGE;
}
/*
OPCODE_SELECT does a bit by bit selection, however, if the selector is the
result of a comparison or if each element may only be 0xff or 0 we may use a
blend instruction instead
*/
static PermittedBlend GetPermittedBlendForSelectV128(const Value* src1v) {
const Instr* df = src1v->def;
if (!df) {
return PermittedBlend::NotPermitted;
} else {
if (!IsVectorCompare(df)) {
return PermittedBlend::NotPermitted; // todo: check ors, ands of
// condition
} else {
switch (df->flags) { // check what datatype we compared as
case INT16_TYPE:
case INT32_TYPE:
case INT8_TYPE:
return PermittedBlend::Int8; // use vpblendvb
case FLOAT32_TYPE:
return PermittedBlend::Ps; // use vblendvps
default: // unknown type! just ignore
return PermittedBlend::NotPermitted;
}
}
}
}
struct SELECT_V128_V128
: Sequence<SELECT_V128_V128,
I<OPCODE_SELECT, V128Op, V128Op, V128Op, V128Op>> {
static void Emit(X64Emitter& e, const EmitArgType& i) {
Xmm src1 = i.src1.is_constant ? e.xmm0 : i.src1;
PermittedBlend mayblend = GetPermittedBlendForSelectV128(i.src1.value);
//todo: detect whether src1 is only 0 or FFFF and use blends if so. currently we only detect cmps
if (i.src1.is_constant) {
e.LoadConstantXmm(src1, i.src1.constant());
}
@@ -756,10 +794,16 @@ struct SELECT_V128_V128
e.LoadConstantXmm(src3, i.src3.constant());
}
// src1 ? src2 : src3;
e.vpandn(e.xmm3, src1, src2);
e.vpand(i.dest, src1, src3);
e.vpor(i.dest, i.dest, e.xmm3);
if (mayblend == PermittedBlend::Int8) {
e.vpblendvb(i.dest, src2, src3, src1);
} else if (mayblend == PermittedBlend::Ps) {
e.vblendvps(i.dest, src2, src3, src1);
} else {
// src1 ? src2 : src3;
e.vpandn(e.xmm3, src1, src2);
e.vpand(i.dest, src1, src3);
e.vpor(i.dest, i.dest, e.xmm3);
}
}
};
EMITTER_OPCODE_TABLE(OPCODE_SELECT, SELECT_I8, SELECT_I16, SELECT_I32,
@@ -2122,7 +2166,8 @@ struct MUL_ADD_V128
// TODO(benvanik): the vfmadd sequence produces slightly different results
// than vmul+vadd and it'd be nice to know why. Until we know, it's
// disabled so tests pass.
if (false && e.IsFeatureEnabled(kX64EmitFMA)) {
// chrispy: reenabled, i have added the DAZ behavior that was missing
if (true && e.IsFeatureEnabled(kX64EmitFMA)) {
EmitCommutativeBinaryXmmOp(e, i,
[&i](X64Emitter& e, const Xmm& dest,
const Xmm& src1, const Xmm& src2) {
@@ -2139,7 +2184,11 @@ struct MUL_ADD_V128
e.vfmadd231ps(i.dest, src1, src2);
} else {
// Dest not equal to anything
e.vmovdqa(i.dest, src1);
// e.vmovdqa(i.dest,
// src1);
// chrispy: vmovdqa was a domain pipeline
// hazard
e.vmovaps(i.dest, src1);
e.vfmadd213ps(i.dest, src2, src3);
}
});
@@ -2152,7 +2201,8 @@ struct MUL_ADD_V128
// If i.dest == i.src3, back up i.src3 so we don't overwrite it.
src3 = i.src3;
if (i.dest == i.src3) {
e.vmovdqa(e.xmm1, i.src3);
// e.vmovdqa(e.xmm1, i.src3);
e.vmovaps(e.xmm1, i.src3);
src3 = e.xmm1;
}
}
@@ -2384,17 +2434,17 @@ EMITTER_OPCODE_TABLE(OPCODE_NEG, NEG_I8, NEG_I16, NEG_I32, NEG_I64, NEG_F32,
// ============================================================================
struct ABS_F32 : Sequence<ABS_F32, I<OPCODE_ABS, F32Op, F32Op>> {
static void Emit(X64Emitter& e, const EmitArgType& i) {
e.vpand(i.dest, i.src1, e.GetXmmConstPtr(XMMAbsMaskPS));
e.vandps(i.dest, i.src1, e.GetXmmConstPtr(XMMAbsMaskPS));
}
};
struct ABS_F64 : Sequence<ABS_F64, I<OPCODE_ABS, F64Op, F64Op>> {
static void Emit(X64Emitter& e, const EmitArgType& i) {
e.vpand(i.dest, i.src1, e.GetXmmConstPtr(XMMAbsMaskPD));
e.vandpd(i.dest, i.src1, e.GetXmmConstPtr(XMMAbsMaskPD));
}
};
struct ABS_V128 : Sequence<ABS_V128, I<OPCODE_ABS, V128Op, V128Op>> {
static void Emit(X64Emitter& e, const EmitArgType& i) {
e.vpand(i.dest, i.src1, e.GetXmmConstPtr(XMMAbsMaskPS));
e.vandps(i.dest, i.src1, e.GetXmmConstPtr(XMMAbsMaskPS));
}
};
EMITTER_OPCODE_TABLE(OPCODE_ABS, ABS_F32, ABS_F64, ABS_V128);
@@ -2634,6 +2684,8 @@ struct DOT_PRODUCT_3_V128
*/
e.vstmxcsr(mxcsr_storage);
e.vmovaps(e.xmm2, e.GetXmmConstPtr(XMMThreeFloatMask));
e.mov(e.eax, 8);
auto src1v = e.xmm0;
@@ -2655,8 +2707,8 @@ struct DOT_PRODUCT_3_V128
// so that in the future this could be optimized away if the top is known to
// be zero. Right now im not sure that happens often though and its
// currently not worth it also, maybe pre-and if constant
e.vandps(e.xmm3, src1v, e.GetXmmConstPtr(XMMThreeFloatMask));
e.vandps(e.xmm2, src2v, e.GetXmmConstPtr(XMMThreeFloatMask));
e.vandps(e.xmm3, src1v, e.xmm2);
e.vandps(e.xmm2, src2v, e.xmm2);
e.and_(mxcsr_storage, e.eax);
e.vldmxcsr(mxcsr_storage); // overflow flag is cleared, now we're good to
@@ -2682,8 +2734,7 @@ struct DOT_PRODUCT_3_V128
Xbyak::Label ret_qnan;
Xbyak::Label done;
e.jnz(ret_qnan);
// e.vshufps(i.dest, e.xmm1,e.xmm1, 0); // broadcast
e.vbroadcastss(i.dest, e.xmm1);
e.vshufps(i.dest, e.xmm1, e.xmm1, 0); // broadcast
e.jmp(done);
e.L(ret_qnan);
e.vmovaps(i.dest, e.GetXmmConstPtr(XMMQNaN));
@@ -2728,27 +2779,7 @@ struct DOT_PRODUCT_4_V128
e.vcvtps2pd(e.ymm0, src1v);
e.vcvtps2pd(e.ymm1, src2v);
/*
e.vandps(e.xmm3, src1v, e.GetXmmConstPtr(XMMThreeFloatMask));
e.vandps(e.xmm2, src2v, e.GetXmmConstPtr(XMMThreeFloatMask));
e.and_(mxcsr_storage, e.eax);
e.vldmxcsr(mxcsr_storage); // overflow flag is cleared, now we're good to
// go
e.vcvtps2pd(e.ymm0, e.xmm3);
e.vcvtps2pd(e.ymm1, e.xmm2);
e.vmulpd(e.ymm5, e.ymm0, e.ymm1);
e.vextractf128(e.xmm4, e.ymm5, 1);
e.vunpckhpd(e.xmm3, e.xmm5, e.xmm5); // get element [1] in xmm3
e.vaddsd(e.xmm5, e.xmm5, e.xmm4);
e.not_(e.eax);
e.vaddsd(e.xmm2, e.xmm5, e.xmm3);
e.vcvtsd2ss(e.xmm1, e.xmm2);
*/
e.vmulpd(e.ymm3, e.ymm0, e.ymm1);
e.vextractf128(e.xmm2, e.ymm3, 1);
e.vaddpd(e.xmm3, e.xmm3, e.xmm2);
@@ -2765,8 +2796,7 @@ struct DOT_PRODUCT_4_V128
Xbyak::Label ret_qnan;
Xbyak::Label done;
e.jnz(ret_qnan); // reorder these jmps later, just want to get this fix in
// e.vshufps(i.dest, e.xmm1, e.xmm1, 0);
e.vbroadcastss(i.dest, e.xmm1);
e.vshufps(i.dest, e.xmm1, e.xmm1, 0);
e.jmp(done);
e.L(ret_qnan);
e.vmovaps(i.dest, e.GetXmmConstPtr(XMMQNaN));
@@ -2846,10 +2876,17 @@ struct AND_I64 : Sequence<AND_I64, I<OPCODE_AND, I64Op, I64Op, I64Op>> {
};
struct AND_V128 : Sequence<AND_V128, I<OPCODE_AND, V128Op, V128Op, V128Op>> {
static void Emit(X64Emitter& e, const EmitArgType& i) {
EmitCommutativeBinaryXmmOp(e, i,
[](X64Emitter& e, Xmm dest, Xmm src1, Xmm src2) {
e.vpand(dest, src1, src2);
});
SimdDomain dom = PickDomain2(e.DeduceSimdDomain(i.src1.value),
e.DeduceSimdDomain(i.src2.value));
EmitCommutativeBinaryXmmOp(
e, i, [dom](X64Emitter& e, Xmm dest, Xmm src1, Xmm src2) {
if (dom == SimdDomain::FLOATING) {
e.vandps(dest, src2, src1);
} else {
e.vpand(dest, src2, src1);
}
});
}
};
EMITTER_OPCODE_TABLE(OPCODE_AND, AND_I8, AND_I16, AND_I32, AND_I64, AND_V128);
@@ -2948,10 +2985,17 @@ struct AND_NOT_I64
struct AND_NOT_V128
: Sequence<AND_NOT_V128, I<OPCODE_AND_NOT, V128Op, V128Op, V128Op>> {
static void Emit(X64Emitter& e, const EmitArgType& i) {
EmitCommutativeBinaryXmmOp(e, i,
[](X64Emitter& e, Xmm dest, Xmm src1, Xmm src2) {
e.vpandn(dest, src2, src1);
});
SimdDomain dom = PickDomain2(e.DeduceSimdDomain(i.src1.value),
e.DeduceSimdDomain(i.src2.value));
EmitCommutativeBinaryXmmOp(
e, i, [dom](X64Emitter& e, Xmm dest, Xmm src1, Xmm src2) {
if (dom == SimdDomain::FLOATING) {
e.vandnps(dest, src2, src1);
} else {
e.vpandn(dest, src2, src1);
}
});
}
};
EMITTER_OPCODE_TABLE(OPCODE_AND_NOT, AND_NOT_I8, AND_NOT_I16, AND_NOT_I32,
@@ -2994,10 +3038,17 @@ struct OR_I64 : Sequence<OR_I64, I<OPCODE_OR, I64Op, I64Op, I64Op>> {
};
struct OR_V128 : Sequence<OR_V128, I<OPCODE_OR, V128Op, V128Op, V128Op>> {
static void Emit(X64Emitter& e, const EmitArgType& i) {
EmitCommutativeBinaryXmmOp(e, i,
[](X64Emitter& e, Xmm dest, Xmm src1, Xmm src2) {
e.vpor(dest, src1, src2);
});
SimdDomain dom = PickDomain2(e.DeduceSimdDomain(i.src1.value),
e.DeduceSimdDomain(i.src2.value));
EmitCommutativeBinaryXmmOp(
e, i, [dom](X64Emitter& e, Xmm dest, Xmm src1, Xmm src2) {
if (dom == SimdDomain::FLOATING) {
e.vorps(dest, src1, src2);
} else {
e.vpor(dest, src1, src2);
}
});
}
};
EMITTER_OPCODE_TABLE(OPCODE_OR, OR_I8, OR_I16, OR_I32, OR_I64, OR_V128);
@@ -3039,10 +3090,17 @@ struct XOR_I64 : Sequence<XOR_I64, I<OPCODE_XOR, I64Op, I64Op, I64Op>> {
};
struct XOR_V128 : Sequence<XOR_V128, I<OPCODE_XOR, V128Op, V128Op, V128Op>> {
static void Emit(X64Emitter& e, const EmitArgType& i) {
EmitCommutativeBinaryXmmOp(e, i,
[](X64Emitter& e, Xmm dest, Xmm src1, Xmm src2) {
e.vpxor(dest, src1, src2);
});
SimdDomain dom = PickDomain2(e.DeduceSimdDomain(i.src1.value),
e.DeduceSimdDomain(i.src2.value));
EmitCommutativeBinaryXmmOp(
e, i, [dom](X64Emitter& e, Xmm dest, Xmm src1, Xmm src2) {
if (dom == SimdDomain::FLOATING) {
e.vxorps(dest, src1, src2);
} else {
e.vpxor(dest, src1, src2);
}
});
}
};
EMITTER_OPCODE_TABLE(OPCODE_XOR, XOR_I8, XOR_I16, XOR_I32, XOR_I64, XOR_V128);
@@ -3078,8 +3136,15 @@ struct NOT_I64 : Sequence<NOT_I64, I<OPCODE_NOT, I64Op, I64Op>> {
};
struct NOT_V128 : Sequence<NOT_V128, I<OPCODE_NOT, V128Op, V128Op>> {
static void Emit(X64Emitter& e, const EmitArgType& i) {
// dest = src ^ 0xFFFF...
e.vpxor(i.dest, i.src1, e.GetXmmConstPtr(XMMFFFF /* FF... */));
SimdDomain domain =
e.DeduceSimdDomain(i.src1.value);
if (domain == SimdDomain::FLOATING) {
e.vxorps(i.dest, i.src1, e.GetXmmConstPtr(XMMFFFF /* FF... */));
} else {
// dest = src ^ 0xFFFF...
e.vpxor(i.dest, i.src1, e.GetXmmConstPtr(XMMFFFF /* FF... */));
}
}
};
EMITTER_OPCODE_TABLE(OPCODE_NOT, NOT_I8, NOT_I16, NOT_I32, NOT_I64, NOT_V128);
@@ -3217,7 +3282,7 @@ struct SHR_V128 : Sequence<SHR_V128, I<OPCODE_SHR, V128Op, V128Op, I8Op>> {
}
e.lea(e.GetNativeParam(0), e.StashXmm(0, i.src1));
e.CallNativeSafe(reinterpret_cast<void*>(EmulateShrV128));
e.vmovaps(i.dest, e.xmm0);
e.vmovdqa(i.dest, e.xmm0);
}
static __m128i EmulateShrV128(void*, __m128i src1, uint8_t src2) {
// Almost all instances are shamt = 1, but non-constant.