Fixed a bug with readback_resolve and readback_memexport that was responsible for a large portion of their overhead. readback_memexport and resolve are now usable for games, depending on your hardware. in my case games that were slideshows now run at like 20-30 fps, and my hardware isnt the best for xenia.
add split_map class for mapping keys to values in a way that optimizes for frequent searches and infrequent insertions/removals remove jump table implementation of GetColorRenderTargetFormatComponentCount, it was appearing relatively high in profiles. instead pack the component counts into a single 32 bit word, which is indexed by shifting Add cvar to align all basic blocks to a boundary Add mmio aware load paths liberally apply XE_RESTRICT in ringbuffer related code Removed the IS_TRUE and IS_FALSE opcodes, they were pointless duplicates of COMPARE_EQ/COMPARE_NE and i want to simplify our set of opcodes for future backends More work on LVSR/LVSL/STVR/STVL opcodes Optimized X64 translated code emission, now only compute instrkey once Add code for pre-computing integer division magic numbers Optimized GetHostViewportInfo a little Move args for GetHostViewportInfo into a class, cache the result and compare for future queries. moved GetHostViewportInfo far lower on the profile Add (currently not functional, and very racy) asynchronous memcpy code. will improve it and actually use it in future commits. Add non-temporal memcpy function for huge page-aligned allocations. Used for copying to shared memory/readback hoist are_accumulated_render_targets_valid_ check out of loop in render_target_cache already bound check. Add stosb/movsb code for small constant memcpys/memsets that arent worth the overhead of memcpy/memset
This commit is contained in:
@@ -57,7 +57,11 @@ DEFINE_bool(enable_incorrect_roundingmode_behavior, false,
|
||||
"code. The workaround may cause reduced CPU performance but is a "
|
||||
"more accurate emulation",
|
||||
"x64");
|
||||
|
||||
DEFINE_uint32(align_all_basic_blocks, 0,
|
||||
"Aligns the start of all basic blocks to N bytes. Only specify a "
|
||||
"power of 2, 16 is the recommended value. Results in larger "
|
||||
"icache usage, but potentially faster loops",
|
||||
"x64");
|
||||
#if XE_X64_PROFILER_AVAILABLE == 1
|
||||
DEFINE_bool(instrument_call_times, false,
|
||||
"Compute time taken for functions, for profiling guest code",
|
||||
@@ -110,7 +114,6 @@ X64Emitter::X64Emitter(X64Backend* backend, XbyakAllocator* allocator)
|
||||
TEST_EMIT_FEATURE(kX64EmitLZCNT, Xbyak::util::Cpu::tLZCNT);
|
||||
TEST_EMIT_FEATURE(kX64EmitBMI1, Xbyak::util::Cpu::tBMI1);
|
||||
TEST_EMIT_FEATURE(kX64EmitBMI2, Xbyak::util::Cpu::tBMI2);
|
||||
TEST_EMIT_FEATURE(kX64EmitF16C, Xbyak::util::Cpu::tF16C);
|
||||
TEST_EMIT_FEATURE(kX64EmitMovbe, Xbyak::util::Cpu::tMOVBE);
|
||||
TEST_EMIT_FEATURE(kX64EmitGFNI, Xbyak::util::Cpu::tGFNI);
|
||||
TEST_EMIT_FEATURE(kX64EmitAVX512F, Xbyak::util::Cpu::tAVX512F);
|
||||
@@ -200,7 +203,55 @@ bool X64Emitter::Emit(GuestFunction* function, HIRBuilder* builder,
|
||||
|
||||
return true;
|
||||
}
|
||||
#pragma pack(push, 1)
|
||||
struct RGCEmitted {
|
||||
uint8_t ff_;
|
||||
uint32_t rgcid_;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
#if 0
|
||||
void X64Emitter::InjectCallAddresses(void* new_execute_address) {
|
||||
for (auto&& callsite : call_sites_) {
|
||||
RGCEmitted* hunter = (RGCEmitted*)new_execute_address;
|
||||
while (hunter->ff_ != 0xFF || hunter->rgcid_ != callsite.offset_) {
|
||||
hunter =
|
||||
reinterpret_cast<RGCEmitted*>(reinterpret_cast<char*>(hunter) + 1);
|
||||
}
|
||||
|
||||
hunter->ff_ = callsite.is_jump_ ? 0xE9 : 0xE8;
|
||||
hunter->rgcid_ =
|
||||
static_cast<uint32_t>(static_cast<intptr_t>(callsite.destination_) -
|
||||
reinterpret_cast<intptr_t>(hunter + 1));
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
void X64Emitter::InjectCallAddresses(void* new_execute_address) {
|
||||
#if 0
|
||||
RGCEmitted* hunter = (RGCEmitted*)new_execute_address;
|
||||
|
||||
std::map<uint32_t, ResolvableGuestCall*> id_to_rgc{};
|
||||
|
||||
for (auto&& callsite : call_sites_) {
|
||||
id_to_rgc[callsite.offset_] = &callsite;
|
||||
}
|
||||
#else
|
||||
RGCEmitted* hunter = (RGCEmitted*)new_execute_address;
|
||||
for (auto&& callsite : call_sites_) {
|
||||
while (hunter->ff_ != 0xFF || hunter->rgcid_ != callsite.offset_) {
|
||||
hunter =
|
||||
reinterpret_cast<RGCEmitted*>(reinterpret_cast<char*>(hunter) + 1);
|
||||
}
|
||||
|
||||
hunter->ff_ = callsite.is_jump_ ? 0xE9 : 0xE8;
|
||||
hunter->rgcid_ =
|
||||
static_cast<uint32_t>(static_cast<intptr_t>(callsite.destination_) -
|
||||
reinterpret_cast<intptr_t>(hunter + 1));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
void* X64Emitter::Emplace(const EmitFunctionInfo& func_info,
|
||||
GuestFunction* function) {
|
||||
// To avoid changing xbyak, we do a switcharoo here.
|
||||
@@ -218,25 +269,9 @@ void* X64Emitter::Emplace(const EmitFunctionInfo& func_info,
|
||||
if (function) {
|
||||
code_cache_->PlaceGuestCode(function->address(), top_, func_info, function,
|
||||
new_execute_address, new_write_address);
|
||||
if (cvars::resolve_rel32_guest_calls) {
|
||||
for (auto&& callsite : call_sites_) {
|
||||
#pragma pack(push, 1)
|
||||
struct RGCEmitted {
|
||||
uint8_t ff_;
|
||||
uint32_t rgcid_;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
RGCEmitted* hunter = (RGCEmitted*)new_execute_address;
|
||||
while (hunter->ff_ != 0xFF || hunter->rgcid_ != callsite.offset_) {
|
||||
hunter = reinterpret_cast<RGCEmitted*>(
|
||||
reinterpret_cast<char*>(hunter) + 1);
|
||||
}
|
||||
|
||||
hunter->ff_ = callsite.is_jump_ ? 0xE9 : 0xE8;
|
||||
hunter->rgcid_ =
|
||||
static_cast<uint32_t>(static_cast<intptr_t>(callsite.destination_) -
|
||||
reinterpret_cast<intptr_t>(hunter + 1));
|
||||
}
|
||||
if (cvars::resolve_rel32_guest_calls) {
|
||||
InjectCallAddresses(new_execute_address);
|
||||
}
|
||||
} else {
|
||||
code_cache_->PlaceHostCode(0, top_, func_info, new_execute_address,
|
||||
@@ -367,6 +402,9 @@ bool X64Emitter::Emit(HIRBuilder* builder, EmitFunctionInfo& func_info) {
|
||||
label = label->next;
|
||||
}
|
||||
|
||||
if (cvars::align_all_basic_blocks) {
|
||||
align(cvars::align_all_basic_blocks, true);
|
||||
}
|
||||
// Process instructions.
|
||||
const Instr* instr = block->instr_head;
|
||||
while (instr) {
|
||||
@@ -1000,12 +1038,6 @@ static const vec128_t xmm_consts[] = {
|
||||
vec128i(0x7f800000),
|
||||
/* XMMThreeFloatMask */
|
||||
vec128i(~0U, ~0U, ~0U, 0U),
|
||||
/*XMMXenosF16ExtRangeStart*/
|
||||
vec128f(65504),
|
||||
/*XMMVSRShlByteshuf*/
|
||||
v128_setr_bytes(13, 14, 15, 8, 9, 10, 11, 4, 5, 6, 7, 0, 1, 2, 3, 0x80),
|
||||
// XMMVSRMask
|
||||
vec128b(1),
|
||||
/*
|
||||
XMMF16UnpackLCPI2
|
||||
*/
|
||||
@@ -1036,8 +1068,7 @@ static const vec128_t xmm_consts[] = {
|
||||
/*XMMXOPWordShiftMask*/
|
||||
vec128s(15),
|
||||
/*XMMXOPDwordShiftMask*/
|
||||
vec128i(31)
|
||||
};
|
||||
vec128i(31)};
|
||||
|
||||
void* X64Emitter::FindByteConstantOffset(unsigned bytevalue) {
|
||||
for (auto& vec : xmm_consts) {
|
||||
|
||||
@@ -157,9 +157,6 @@ enum XmmConst {
|
||||
XMMLVSRTableBase,
|
||||
XMMSingleDenormalMask,
|
||||
XMMThreeFloatMask, // for clearing the fourth float prior to DOT_PRODUCT_3
|
||||
XMMXenosF16ExtRangeStart,
|
||||
XMMVSRShlByteshuf,
|
||||
XMMVSRMask,
|
||||
XMMF16UnpackLCPI2, // 0x38000000, 1/ 32768
|
||||
XMMF16UnpackLCPI3, // 0x0x7fe000007fe000
|
||||
XMMF16PackLCPI0,
|
||||
@@ -194,7 +191,7 @@ enum X64EmitterFeatureFlags {
|
||||
kX64EmitLZCNT = 1 << 2, // this is actually ABM and includes popcount
|
||||
kX64EmitBMI1 = 1 << 3,
|
||||
kX64EmitBMI2 = 1 << 4,
|
||||
kX64EmitF16C = 1 << 5,
|
||||
kX64EmitPrefetchW = 1 << 5,
|
||||
kX64EmitMovbe = 1 << 6,
|
||||
kX64EmitGFNI = 1 << 7,
|
||||
|
||||
@@ -215,11 +212,14 @@ enum X64EmitterFeatureFlags {
|
||||
// inc/dec) do not introduce false dependencies on EFLAGS
|
||||
// because the individual flags are treated as different vars by
|
||||
// the processor. (this applies to zen)
|
||||
kX64EmitPrefetchW = 1 << 16,
|
||||
kX64EmitXOP = 1 << 17, // chrispy: xop maps really well to many vmx
|
||||
kX64EmitXOP = 1 << 16, // chrispy: xop maps really well to many vmx
|
||||
// instructions, and FX users need the boost
|
||||
kX64EmitFMA4 = 1 << 18, // todo: also use on zen1?
|
||||
kX64EmitTBM = 1 << 19
|
||||
kX64EmitFMA4 = 1 << 17, // todo: also use on zen1?
|
||||
kX64EmitTBM = 1 << 18,
|
||||
// kX64XMMRegisterMergeOptimization = 1 << 19, //section 2.11.5, amd family
|
||||
// 17h/19h optimization manuals. allows us to save 1 byte on certain xmm
|
||||
// instructions by using the legacy sse version if we recently cleared the
|
||||
// high 128 bits of the
|
||||
};
|
||||
class ResolvableGuestCall {
|
||||
public:
|
||||
@@ -251,6 +251,7 @@ class X64Emitter : public Xbyak::CodeGenerator {
|
||||
uint32_t debug_info_flags, FunctionDebugInfo* debug_info,
|
||||
void** out_code_address, size_t* out_code_size,
|
||||
std::vector<SourceMapEntry>* out_source_map);
|
||||
void InjectCallAddresses(void* new_execute_addr);
|
||||
|
||||
public:
|
||||
// Reserved: rsp, rsi, rdi
|
||||
|
||||
@@ -43,23 +43,23 @@ enum KeyType {
|
||||
KEY_TYPE_V_F64 = OPCODE_SIG_TYPE_V + FLOAT64_TYPE,
|
||||
KEY_TYPE_V_V128 = OPCODE_SIG_TYPE_V + VEC128_TYPE,
|
||||
};
|
||||
|
||||
using InstrKeyValue = uint32_t;
|
||||
#pragma pack(push, 1)
|
||||
union InstrKey {
|
||||
uint32_t value;
|
||||
InstrKeyValue value;
|
||||
struct {
|
||||
uint32_t opcode : 8;
|
||||
uint32_t dest : 5;
|
||||
uint32_t src1 : 5;
|
||||
uint32_t src2 : 5;
|
||||
uint32_t src3 : 5;
|
||||
uint32_t reserved : 4;
|
||||
InstrKeyValue opcode : 8;
|
||||
InstrKeyValue dest : 5;
|
||||
InstrKeyValue src1 : 5;
|
||||
InstrKeyValue src2 : 5;
|
||||
InstrKeyValue src3 : 5;
|
||||
InstrKeyValue reserved : 4;
|
||||
};
|
||||
|
||||
operator uint32_t() const { return value; }
|
||||
operator InstrKeyValue() const { return value; }
|
||||
|
||||
InstrKey() : value(0) { static_assert_size(*this, sizeof(value)); }
|
||||
InstrKey(uint32_t v) : value(v) {}
|
||||
InstrKey(InstrKeyValue v) : value(v) {}
|
||||
|
||||
// this used to take about 1% cpu while precompiling
|
||||
// it kept reloading opcode, and also constantly repacking and unpacking the
|
||||
@@ -67,16 +67,16 @@ union InstrKey {
|
||||
InstrKey(const Instr* i) : value(0) {
|
||||
const OpcodeInfo* info = i->GetOpcodeInfo();
|
||||
|
||||
uint32_t sig = info->signature;
|
||||
InstrKeyValue sig = info->signature;
|
||||
|
||||
OpcodeSignatureType dest_type, src1_type, src2_type, src3_type;
|
||||
|
||||
UnpackOpcodeSig(sig, dest_type, src1_type, src2_type, src3_type);
|
||||
|
||||
uint32_t out_desttype = (uint32_t)dest_type;
|
||||
uint32_t out_src1type = (uint32_t)src1_type;
|
||||
uint32_t out_src2type = (uint32_t)src2_type;
|
||||
uint32_t out_src3type = (uint32_t)src3_type;
|
||||
InstrKeyValue out_desttype = (InstrKeyValue)dest_type;
|
||||
InstrKeyValue out_src1type = (InstrKeyValue)src1_type;
|
||||
InstrKeyValue out_src2type = (InstrKeyValue)src2_type;
|
||||
InstrKeyValue out_src3type = (InstrKeyValue)src3_type;
|
||||
|
||||
Value* destv = i->dest;
|
||||
// pre-deref, even if not value
|
||||
@@ -105,7 +105,7 @@ union InstrKey {
|
||||
template <Opcode OPCODE, KeyType DEST = KEY_TYPE_X, KeyType SRC1 = KEY_TYPE_X,
|
||||
KeyType SRC2 = KEY_TYPE_X, KeyType SRC3 = KEY_TYPE_X>
|
||||
struct Construct {
|
||||
static const uint32_t value =
|
||||
static const InstrKeyValue value =
|
||||
(OPCODE) | (DEST << 8) | (SRC1 << 13) | (SRC2 << 18) | (SRC3 << 23);
|
||||
};
|
||||
};
|
||||
@@ -307,8 +307,8 @@ struct I<OPCODE, DEST> : DestField<DEST> {
|
||||
protected:
|
||||
template <typename SEQ, typename T>
|
||||
friend struct Sequence;
|
||||
bool Load(const Instr* i) {
|
||||
if (InstrKey(i).value == key && BASE::LoadDest(i)) {
|
||||
bool Load(const Instr* i, InstrKeyValue kv) {
|
||||
if (kv == key && BASE::LoadDest(i)) {
|
||||
instr = i;
|
||||
return true;
|
||||
}
|
||||
@@ -329,8 +329,8 @@ struct I<OPCODE, DEST, SRC1> : DestField<DEST> {
|
||||
protected:
|
||||
template <typename SEQ, typename T>
|
||||
friend struct Sequence;
|
||||
bool Load(const Instr* i) {
|
||||
if (InstrKey(i).value == key && BASE::LoadDest(i)) {
|
||||
bool Load(const Instr* i, InstrKeyValue kv) {
|
||||
if (kv == key && BASE::LoadDest(i)) {
|
||||
instr = i;
|
||||
src1.Load(i->src1);
|
||||
return true;
|
||||
@@ -355,8 +355,8 @@ struct I<OPCODE, DEST, SRC1, SRC2> : DestField<DEST> {
|
||||
protected:
|
||||
template <typename SEQ, typename T>
|
||||
friend struct Sequence;
|
||||
bool Load(const Instr* i) {
|
||||
if (InstrKey(i).value == key && BASE::LoadDest(i)) {
|
||||
bool Load(const Instr* i, InstrKeyValue kv) {
|
||||
if (kv == key && BASE::LoadDest(i)) {
|
||||
instr = i;
|
||||
src1.Load(i->src1);
|
||||
src2.Load(i->src2);
|
||||
@@ -385,8 +385,8 @@ struct I<OPCODE, DEST, SRC1, SRC2, SRC3> : DestField<DEST> {
|
||||
protected:
|
||||
template <typename SEQ, typename T>
|
||||
friend struct Sequence;
|
||||
bool Load(const Instr* i) {
|
||||
if (InstrKey(i).value == key && BASE::LoadDest(i)) {
|
||||
bool Load(const Instr* i, InstrKeyValue ikey) {
|
||||
if (ikey == key && BASE::LoadDest(i)) {
|
||||
instr = i;
|
||||
src1.Load(i->src1);
|
||||
src2.Load(i->src2);
|
||||
@@ -422,9 +422,9 @@ struct Sequence {
|
||||
|
||||
static constexpr uint32_t head_key() { return T::key; }
|
||||
|
||||
static bool Select(X64Emitter& e, const Instr* i) {
|
||||
static bool Select(X64Emitter& e, const Instr* i, InstrKeyValue ikey) {
|
||||
T args;
|
||||
if (!args.Load(i)) {
|
||||
if (!args.Load(i, ikey)) {
|
||||
return false;
|
||||
}
|
||||
SEQ::Emit(e, args);
|
||||
|
||||
@@ -27,12 +27,6 @@ static void EmitFusedBranch(X64Emitter& e, const T& i) {
|
||||
if (valid) {
|
||||
auto name = i.src2.value->name;
|
||||
switch (opcode) {
|
||||
case OPCODE_IS_TRUE:
|
||||
e.jnz(name, e.T_NEAR);
|
||||
break;
|
||||
case OPCODE_IS_FALSE:
|
||||
e.jz(name, e.T_NEAR);
|
||||
break;
|
||||
case OPCODE_COMPARE_EQ:
|
||||
e.je(name, e.T_NEAR);
|
||||
break;
|
||||
@@ -299,26 +293,14 @@ struct CALL_TRUE_I64
|
||||
struct CALL_TRUE_F32
|
||||
: Sequence<CALL_TRUE_F32, I<OPCODE_CALL_TRUE, VoidOp, F32Op, SymbolOp>> {
|
||||
static void Emit(X64Emitter& e, const EmitArgType& i) {
|
||||
assert_true(i.src2.value->is_guest());
|
||||
e.vptest(i.src1, i.src1);
|
||||
Xbyak::Label skip;
|
||||
e.jz(skip);
|
||||
e.Call(i.instr, static_cast<GuestFunction*>(i.src2.value));
|
||||
e.L(skip);
|
||||
e.ForgetMxcsrMode();
|
||||
assert_impossible_sequence(CALL_TRUE_F32);
|
||||
}
|
||||
};
|
||||
|
||||
struct CALL_TRUE_F64
|
||||
: Sequence<CALL_TRUE_F64, I<OPCODE_CALL_TRUE, VoidOp, F64Op, SymbolOp>> {
|
||||
static void Emit(X64Emitter& e, const EmitArgType& i) {
|
||||
assert_true(i.src2.value->is_guest());
|
||||
e.vptest(i.src1, i.src1);
|
||||
Xbyak::Label skip;
|
||||
e.jz(skip);
|
||||
e.Call(i.instr, static_cast<GuestFunction*>(i.src2.value));
|
||||
e.L(skip);
|
||||
e.ForgetMxcsrMode();
|
||||
assert_impossible_sequence(CALL_TRUE_F64);
|
||||
}
|
||||
};
|
||||
EMITTER_OPCODE_TABLE(OPCODE_CALL_TRUE, CALL_TRUE_I8, CALL_TRUE_I16,
|
||||
@@ -404,22 +386,14 @@ struct CALL_INDIRECT_TRUE_F32
|
||||
: Sequence<CALL_INDIRECT_TRUE_F32,
|
||||
I<OPCODE_CALL_INDIRECT_TRUE, VoidOp, F32Op, I64Op>> {
|
||||
static void Emit(X64Emitter& e, const EmitArgType& i) {
|
||||
e.vptest(i.src1, i.src1);
|
||||
Xbyak::Label skip;
|
||||
e.jz(skip, CodeGenerator::T_NEAR);
|
||||
e.CallIndirect(i.instr, i.src2);
|
||||
e.L(skip);
|
||||
assert_impossible_sequence(CALL_INDIRECT_TRUE_F32);
|
||||
}
|
||||
};
|
||||
struct CALL_INDIRECT_TRUE_F64
|
||||
: Sequence<CALL_INDIRECT_TRUE_F64,
|
||||
I<OPCODE_CALL_INDIRECT_TRUE, VoidOp, F64Op, I64Op>> {
|
||||
static void Emit(X64Emitter& e, const EmitArgType& i) {
|
||||
e.vptest(i.src1, i.src1);
|
||||
Xbyak::Label skip;
|
||||
e.jz(skip, CodeGenerator::T_NEAR);
|
||||
e.CallIndirect(i.instr, i.src2);
|
||||
e.L(skip);
|
||||
assert_impossible_sequence(CALL_INDIRECT_TRUE_F64);
|
||||
}
|
||||
};
|
||||
EMITTER_OPCODE_TABLE(OPCODE_CALL_INDIRECT_TRUE, CALL_INDIRECT_TRUE_I8,
|
||||
@@ -486,15 +460,13 @@ struct RETURN_TRUE_I64
|
||||
struct RETURN_TRUE_F32
|
||||
: Sequence<RETURN_TRUE_F32, I<OPCODE_RETURN_TRUE, VoidOp, F32Op>> {
|
||||
static void Emit(X64Emitter& e, const EmitArgType& i) {
|
||||
e.vptest(i.src1, i.src1);
|
||||
e.jnz(e.epilog_label(), CodeGenerator::T_NEAR);
|
||||
assert_impossible_sequence(RETURN_TRUE_F32);
|
||||
}
|
||||
};
|
||||
struct RETURN_TRUE_F64
|
||||
: Sequence<RETURN_TRUE_F64, I<OPCODE_RETURN_TRUE, VoidOp, F64Op>> {
|
||||
static void Emit(X64Emitter& e, const EmitArgType& i) {
|
||||
e.vptest(i.src1, i.src1);
|
||||
e.jnz(e.epilog_label(), CodeGenerator::T_NEAR);
|
||||
assert_impossible_sequence(RETURN_TRUE_F64);
|
||||
}
|
||||
};
|
||||
EMITTER_OPCODE_TABLE(OPCODE_RETURN_TRUE, RETURN_TRUE_I8, RETURN_TRUE_I16,
|
||||
@@ -553,33 +525,25 @@ struct BRANCH_TRUE_I64
|
||||
struct BRANCH_TRUE_F32
|
||||
: Sequence<BRANCH_TRUE_F32, I<OPCODE_BRANCH_TRUE, VoidOp, F32Op, LabelOp>> {
|
||||
static void Emit(X64Emitter& e, const EmitArgType& i) {
|
||||
if (i.instr->prev && i.instr->prev->opcode == &OPCODE_IS_TRUE_info &&
|
||||
i.instr->prev->dest == i.src1.value) {
|
||||
e.jnz(i.src2.value->name, e.T_NEAR);
|
||||
} else if (i.instr->prev &&
|
||||
i.instr->prev->opcode == &OPCODE_IS_FALSE_info &&
|
||||
i.instr->prev->dest == i.src1.value) {
|
||||
e.jz(i.src2.value->name, e.T_NEAR);
|
||||
} else {
|
||||
e.vptest(i.src1, i.src1);
|
||||
e.jnz(i.src2.value->name, e.T_NEAR);
|
||||
}
|
||||
/*
|
||||
chrispy: right now, im not confident that we are always clearing
|
||||
the upper 96 bits of registers, making vptest extremely unsafe. many
|
||||
ss/sd operations copy over the upper 96 from the source, and for abs we
|
||||
negate ALL elements, making the top 64 bits contain 0x80000000 etc
|
||||
*/
|
||||
Xmm input = GetInputRegOrConstant(e, i.src1, e.xmm0);
|
||||
e.vmovd(e.eax, input);
|
||||
e.test(e.eax, e.eax);
|
||||
e.jnz(i.src2.value->name, e.T_NEAR);
|
||||
}
|
||||
};
|
||||
struct BRANCH_TRUE_F64
|
||||
: Sequence<BRANCH_TRUE_F64, I<OPCODE_BRANCH_TRUE, VoidOp, F64Op, LabelOp>> {
|
||||
static void Emit(X64Emitter& e, const EmitArgType& i) {
|
||||
if (i.instr->prev && i.instr->prev->opcode == &OPCODE_IS_TRUE_info &&
|
||||
i.instr->prev->dest == i.src1.value) {
|
||||
e.jnz(i.src2.value->name, e.T_NEAR);
|
||||
} else if (i.instr->prev &&
|
||||
i.instr->prev->opcode == &OPCODE_IS_FALSE_info &&
|
||||
i.instr->prev->dest == i.src1.value) {
|
||||
e.jz(i.src2.value->name, e.T_NEAR);
|
||||
} else {
|
||||
e.vptest(i.src1, i.src1);
|
||||
e.jnz(i.src2.value->name, e.T_NEAR);
|
||||
}
|
||||
Xmm input = GetInputRegOrConstant(e, i.src1, e.xmm0);
|
||||
e.vmovq(e.rax, input);
|
||||
e.test(e.rax, e.rax);
|
||||
e.jnz(i.src2.value->name, e.T_NEAR);
|
||||
}
|
||||
};
|
||||
EMITTER_OPCODE_TABLE(OPCODE_BRANCH_TRUE, BRANCH_TRUE_I8, BRANCH_TRUE_I16,
|
||||
@@ -624,7 +588,9 @@ struct BRANCH_FALSE_F32
|
||||
: Sequence<BRANCH_FALSE_F32,
|
||||
I<OPCODE_BRANCH_FALSE, VoidOp, F32Op, LabelOp>> {
|
||||
static void Emit(X64Emitter& e, const EmitArgType& i) {
|
||||
e.vptest(i.src1, i.src1);
|
||||
Xmm input = GetInputRegOrConstant(e, i.src1, e.xmm0);
|
||||
e.vmovd(e.eax, input);
|
||||
e.test(e.eax, e.eax);
|
||||
e.jz(i.src2.value->name, e.T_NEAR);
|
||||
}
|
||||
};
|
||||
@@ -632,7 +598,9 @@ struct BRANCH_FALSE_F64
|
||||
: Sequence<BRANCH_FALSE_F64,
|
||||
I<OPCODE_BRANCH_FALSE, VoidOp, F64Op, LabelOp>> {
|
||||
static void Emit(X64Emitter& e, const EmitArgType& i) {
|
||||
e.vptest(i.src1, i.src1);
|
||||
Xmm input = GetInputRegOrConstant(e, i.src1, e.xmm0);
|
||||
e.vmovq(e.rax, input);
|
||||
e.test(e.rax, e.rax);
|
||||
e.jz(i.src2.value->name, e.T_NEAR);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -975,6 +975,9 @@ static bool IsPossibleMMIOInstruction(X64Emitter& e, const hir::Instr* i) {
|
||||
if (!cvars::emit_mmio_aware_stores_for_recorded_exception_addresses) {
|
||||
return false;
|
||||
}
|
||||
if (IsTracingData()) { // incompatible with tracing
|
||||
return false;
|
||||
}
|
||||
uint32_t guestaddr = i->GuestAddressFor();
|
||||
if (!guestaddr) {
|
||||
return false;
|
||||
@@ -984,7 +987,54 @@ static bool IsPossibleMMIOInstruction(X64Emitter& e, const hir::Instr* i) {
|
||||
|
||||
return flags && flags->accessed_mmio;
|
||||
}
|
||||
template <typename T, bool swap>
|
||||
static void MMIOAwareStore(void* _ctx, unsigned int guestaddr, T value) {
|
||||
if (swap) {
|
||||
value = xe::byte_swap(value);
|
||||
}
|
||||
if (guestaddr >= 0xE0000000) {
|
||||
guestaddr += 0x1000;
|
||||
}
|
||||
|
||||
auto ctx = reinterpret_cast<ppc::PPCContext*>(_ctx);
|
||||
|
||||
auto gaddr = ctx->processor->memory()->LookupVirtualMappedRange(guestaddr);
|
||||
if (!gaddr) {
|
||||
*reinterpret_cast<T*>(ctx->virtual_membase + guestaddr) = value;
|
||||
} else {
|
||||
value = xe::byte_swap(value); /*
|
||||
was having issues, found by comparing the values used with exceptions
|
||||
to these that we were reversed...
|
||||
*/
|
||||
gaddr->write(nullptr, gaddr->callback_context, guestaddr, value);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T, bool swap>
|
||||
static T MMIOAwareLoad(void* _ctx, unsigned int guestaddr) {
|
||||
T value;
|
||||
|
||||
if (guestaddr >= 0xE0000000) {
|
||||
guestaddr += 0x1000;
|
||||
}
|
||||
|
||||
auto ctx = reinterpret_cast<ppc::PPCContext*>(_ctx);
|
||||
|
||||
auto gaddr = ctx->processor->memory()->LookupVirtualMappedRange(guestaddr);
|
||||
if (!gaddr) {
|
||||
value = *reinterpret_cast<T*>(ctx->virtual_membase + guestaddr);
|
||||
if (swap) {
|
||||
value = xe::byte_swap(value);
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
was having issues, found by comparing the values used with exceptions
|
||||
to these that we were reversed...
|
||||
*/
|
||||
value = gaddr->read(nullptr, gaddr->callback_context, guestaddr);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
// ============================================================================
|
||||
// OPCODE_LOAD_OFFSET
|
||||
// ============================================================================
|
||||
@@ -1016,16 +1066,38 @@ struct LOAD_OFFSET_I16
|
||||
struct LOAD_OFFSET_I32
|
||||
: Sequence<LOAD_OFFSET_I32, I<OPCODE_LOAD_OFFSET, I32Op, I64Op, I64Op>> {
|
||||
static void Emit(X64Emitter& e, const EmitArgType& i) {
|
||||
auto addr = ComputeMemoryAddressOffset(e, i.src1, i.src2);
|
||||
if (i.instr->flags & LoadStoreFlags::LOAD_STORE_BYTE_SWAP) {
|
||||
if (e.IsFeatureEnabled(kX64EmitMovbe)) {
|
||||
e.movbe(i.dest, e.dword[addr]);
|
||||
if (IsPossibleMMIOInstruction(e, i.instr)) {
|
||||
void* addrptr = (void*)&MMIOAwareLoad<uint32_t, false>;
|
||||
|
||||
if (i.instr->flags & LoadStoreFlags::LOAD_STORE_BYTE_SWAP) {
|
||||
addrptr = (void*)&MMIOAwareLoad<uint32_t, true>;
|
||||
}
|
||||
if (i.src1.is_constant) {
|
||||
e.mov(e.GetNativeParam(0).cvt32(), (uint32_t)i.src1.constant());
|
||||
} else {
|
||||
e.mov(e.GetNativeParam(0).cvt32(), i.src1.reg().cvt32());
|
||||
}
|
||||
|
||||
if (i.src2.is_constant) {
|
||||
e.add(e.GetNativeParam(0).cvt32(), (uint32_t)i.src2.constant());
|
||||
} else {
|
||||
e.add(e.GetNativeParam(0).cvt32(), i.src2.reg().cvt32());
|
||||
}
|
||||
|
||||
e.CallNativeSafe(addrptr);
|
||||
e.mov(i.dest, e.eax);
|
||||
} else {
|
||||
auto addr = ComputeMemoryAddressOffset(e, i.src1, i.src2);
|
||||
if (i.instr->flags & LoadStoreFlags::LOAD_STORE_BYTE_SWAP) {
|
||||
if (e.IsFeatureEnabled(kX64EmitMovbe)) {
|
||||
e.movbe(i.dest, e.dword[addr]);
|
||||
} else {
|
||||
e.mov(i.dest, e.dword[addr]);
|
||||
e.bswap(i.dest);
|
||||
}
|
||||
} else {
|
||||
e.mov(i.dest, e.dword[addr]);
|
||||
e.bswap(i.dest);
|
||||
}
|
||||
} else {
|
||||
e.mov(i.dest, e.dword[addr]);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1049,28 +1121,6 @@ struct LOAD_OFFSET_I64
|
||||
EMITTER_OPCODE_TABLE(OPCODE_LOAD_OFFSET, LOAD_OFFSET_I8, LOAD_OFFSET_I16,
|
||||
LOAD_OFFSET_I32, LOAD_OFFSET_I64);
|
||||
|
||||
template <typename T, bool swap>
|
||||
static void MMIOAwareStore(void* _ctx, unsigned int guestaddr, T value) {
|
||||
if (swap) {
|
||||
value = xe::byte_swap(value);
|
||||
}
|
||||
if (guestaddr >= 0xE0000000) {
|
||||
guestaddr += 0x1000;
|
||||
}
|
||||
|
||||
auto ctx = reinterpret_cast<ppc::PPCContext*>(_ctx);
|
||||
|
||||
auto gaddr = ctx->processor->memory()->LookupVirtualMappedRange(guestaddr);
|
||||
if (!gaddr) {
|
||||
*reinterpret_cast<T*>(ctx->virtual_membase + guestaddr) = value;
|
||||
} else {
|
||||
value = xe::byte_swap(value); /*
|
||||
was having issues, found by comparing the values used with exceptions
|
||||
to these that we were reversed...
|
||||
*/
|
||||
gaddr->write(nullptr, gaddr->callback_context, guestaddr, value);
|
||||
}
|
||||
}
|
||||
// ============================================================================
|
||||
// OPCODE_STORE_OFFSET
|
||||
// ============================================================================
|
||||
@@ -1225,21 +1275,37 @@ struct LOAD_I16 : Sequence<LOAD_I16, I<OPCODE_LOAD, I16Op, I64Op>> {
|
||||
};
|
||||
struct LOAD_I32 : Sequence<LOAD_I32, I<OPCODE_LOAD, I32Op, I64Op>> {
|
||||
static void Emit(X64Emitter& e, const EmitArgType& i) {
|
||||
auto addr = ComputeMemoryAddress(e, i.src1);
|
||||
if (i.instr->flags & LoadStoreFlags::LOAD_STORE_BYTE_SWAP) {
|
||||
if (e.IsFeatureEnabled(kX64EmitMovbe)) {
|
||||
e.movbe(i.dest, e.dword[addr]);
|
||||
if (IsPossibleMMIOInstruction(e, i.instr)) {
|
||||
void* addrptr = (void*)&MMIOAwareLoad<uint32_t, false>;
|
||||
|
||||
if (i.instr->flags & LoadStoreFlags::LOAD_STORE_BYTE_SWAP) {
|
||||
addrptr = (void*)&MMIOAwareLoad<uint32_t, true>;
|
||||
}
|
||||
if (i.src1.is_constant) {
|
||||
e.mov(e.GetNativeParam(0).cvt32(), (uint32_t)i.src1.constant());
|
||||
} else {
|
||||
e.mov(e.GetNativeParam(0).cvt32(), i.src1.reg().cvt32());
|
||||
}
|
||||
|
||||
e.CallNativeSafe(addrptr);
|
||||
e.mov(i.dest, e.eax);
|
||||
} else {
|
||||
auto addr = ComputeMemoryAddress(e, i.src1);
|
||||
if (i.instr->flags & LoadStoreFlags::LOAD_STORE_BYTE_SWAP) {
|
||||
if (e.IsFeatureEnabled(kX64EmitMovbe)) {
|
||||
e.movbe(i.dest, e.dword[addr]);
|
||||
} else {
|
||||
e.mov(i.dest, e.dword[addr]);
|
||||
e.bswap(i.dest);
|
||||
}
|
||||
} else {
|
||||
e.mov(i.dest, e.dword[addr]);
|
||||
e.bswap(i.dest);
|
||||
}
|
||||
} else {
|
||||
e.mov(i.dest, e.dword[addr]);
|
||||
}
|
||||
if (IsTracingData()) {
|
||||
e.mov(e.GetNativeParam(1).cvt32(), i.dest);
|
||||
e.lea(e.GetNativeParam(0), e.ptr[addr]);
|
||||
e.CallNative(reinterpret_cast<void*>(TraceMemoryLoadI32));
|
||||
if (IsTracingData()) {
|
||||
e.mov(e.GetNativeParam(1).cvt32(), i.dest);
|
||||
e.lea(e.GetNativeParam(0), e.ptr[addr]);
|
||||
e.CallNative(reinterpret_cast<void*>(TraceMemoryLoadI32));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1390,14 +1456,13 @@ struct STORE_I32 : Sequence<STORE_I32, I<OPCODE_STORE, VoidOp, I64Op, I32Op>> {
|
||||
} else {
|
||||
e.mov(e.dword[addr], i.src2);
|
||||
}
|
||||
if (IsTracingData()) {
|
||||
e.mov(e.GetNativeParam(1).cvt32(), e.dword[addr]);
|
||||
e.lea(e.GetNativeParam(0), e.ptr[addr]);
|
||||
e.CallNative(reinterpret_cast<void*>(TraceMemoryStoreI32));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (IsTracingData()) {
|
||||
auto addr = ComputeMemoryAddress(e, i.src1);
|
||||
e.mov(e.GetNativeParam(1).cvt32(), e.dword[addr]);
|
||||
e.lea(e.GetNativeParam(0), e.ptr[addr]);
|
||||
e.CallNative(reinterpret_cast<void*>(TraceMemoryStoreI32));
|
||||
}
|
||||
}
|
||||
};
|
||||
struct STORE_I64 : Sequence<STORE_I64, I<OPCODE_STORE, VoidOp, I64Op, I64Op>> {
|
||||
|
||||
@@ -19,15 +19,15 @@
|
||||
#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_rotates, false, "rotate via xop", "x64");
|
||||
|
||||
DEFINE_bool(xop_left_shifts, false, "shl 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_right_shifts, false, "shr via xop", "x64");
|
||||
|
||||
DEFINE_bool(xop_arithmetic_right_shifts, false, "sar via xop", "X64");
|
||||
DEFINE_bool(xop_arithmetic_right_shifts, false, "sar via xop", "x64");
|
||||
|
||||
DEFINE_bool(xop_compares, true, "compare via xop", "X64");
|
||||
DEFINE_bool(xop_compares, true, "compare via xop", "x64");
|
||||
|
||||
namespace xe {
|
||||
namespace cpu {
|
||||
|
||||
@@ -67,7 +67,7 @@ using namespace xe::cpu::hir;
|
||||
|
||||
using xe::cpu::hir::Instr;
|
||||
|
||||
typedef bool (*SequenceSelectFn)(X64Emitter&, const Instr*);
|
||||
typedef bool (*SequenceSelectFn)(X64Emitter&, const Instr*, InstrKeyValue ikey);
|
||||
std::unordered_map<uint32_t, SequenceSelectFn> sequence_table;
|
||||
|
||||
// ============================================================================
|
||||
@@ -868,59 +868,6 @@ static bool MayCombineSetxWithFollowingCtxStore(const hir::Instr* setx_insn,
|
||||
}
|
||||
return false;
|
||||
}
|
||||
#define EMITTER_IS_TRUE(typ, tester) \
|
||||
struct IS_TRUE_##typ \
|
||||
: Sequence<IS_TRUE_##typ, I<OPCODE_IS_TRUE, I8Op, typ##Op>> { \
|
||||
static void Emit(X64Emitter& e, const EmitArgType& i) { \
|
||||
e.tester(i.src1, i.src1); \
|
||||
unsigned ctxoffset = 0; \
|
||||
if (MayCombineSetxWithFollowingCtxStore(i.instr, ctxoffset)) { \
|
||||
e.setnz(e.byte[e.GetContextReg() + ctxoffset]); \
|
||||
} else { \
|
||||
e.setnz(i.dest); \
|
||||
} \
|
||||
} \
|
||||
}
|
||||
|
||||
#define EMITTER_IS_TRUE_INT(typ) EMITTER_IS_TRUE(typ, test)
|
||||
|
||||
EMITTER_IS_TRUE_INT(I8);
|
||||
EMITTER_IS_TRUE_INT(I16);
|
||||
EMITTER_IS_TRUE_INT(I32);
|
||||
EMITTER_IS_TRUE_INT(I64);
|
||||
EMITTER_IS_TRUE(F32, vtestps);
|
||||
EMITTER_IS_TRUE(F64, vtestpd);
|
||||
|
||||
EMITTER_IS_TRUE(V128, vptest);
|
||||
|
||||
EMITTER_OPCODE_TABLE(OPCODE_IS_TRUE, IS_TRUE_I8, IS_TRUE_I16, IS_TRUE_I32,
|
||||
IS_TRUE_I64, IS_TRUE_F32, IS_TRUE_F64, IS_TRUE_V128);
|
||||
|
||||
#define EMITTER_IS_FALSE(typ, tester) \
|
||||
struct IS_FALSE_##typ \
|
||||
: Sequence<IS_FALSE_##typ, I<OPCODE_IS_FALSE, I8Op, typ##Op>> { \
|
||||
static void Emit(X64Emitter& e, const EmitArgType& i) { \
|
||||
e.tester(i.src1, i.src1); \
|
||||
unsigned ctxoffset = 0; \
|
||||
if (MayCombineSetxWithFollowingCtxStore(i.instr, ctxoffset)) { \
|
||||
e.setz(e.byte[e.GetContextReg() + ctxoffset]); \
|
||||
} else { \
|
||||
e.setz(i.dest); \
|
||||
} \
|
||||
} \
|
||||
}
|
||||
#define EMITTER_IS_FALSE_INT(typ) EMITTER_IS_FALSE(typ, test)
|
||||
EMITTER_IS_FALSE_INT(I8);
|
||||
EMITTER_IS_FALSE_INT(I16);
|
||||
EMITTER_IS_FALSE_INT(I32);
|
||||
EMITTER_IS_FALSE_INT(I64);
|
||||
EMITTER_IS_FALSE(F32, vtestps);
|
||||
EMITTER_IS_FALSE(F64, vtestpd);
|
||||
|
||||
EMITTER_IS_FALSE(V128, vptest);
|
||||
|
||||
EMITTER_OPCODE_TABLE(OPCODE_IS_FALSE, IS_FALSE_I8, IS_FALSE_I16, IS_FALSE_I32,
|
||||
IS_FALSE_I64, IS_FALSE_F32, IS_FALSE_F64, IS_FALSE_V128);
|
||||
|
||||
// ============================================================================
|
||||
// OPCODE_IS_NAN
|
||||
@@ -3308,7 +3255,7 @@ bool SelectSequence(X64Emitter* e, const Instr* i, const Instr** new_tail) {
|
||||
|
||||
auto it = sequence_table.find(key);
|
||||
if (it != sequence_table.end()) {
|
||||
if (it->second(*e, i)) {
|
||||
if (it->second(*e, i, InstrKey(i))) {
|
||||
*new_tail = i->next;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ namespace x64 {
|
||||
|
||||
class X64Emitter;
|
||||
|
||||
typedef bool (*SequenceSelectFn)(X64Emitter&, const hir::Instr*);
|
||||
typedef bool (*SequenceSelectFn)(X64Emitter&, const hir::Instr*, uint32_t ikey);
|
||||
extern std::unordered_map<uint32_t, SequenceSelectFn> sequence_table;
|
||||
|
||||
template <typename T>
|
||||
|
||||
@@ -361,28 +361,6 @@ bool ConstantPropagationPass::Run(HIRBuilder* builder, bool& result) {
|
||||
}
|
||||
}
|
||||
break;
|
||||
case OPCODE_IS_TRUE:
|
||||
if (i->src1.value->IsConstant()) {
|
||||
if (i->src1.value->IsConstantTrue()) {
|
||||
v->set_constant(uint8_t(1));
|
||||
} else {
|
||||
v->set_constant(uint8_t(0));
|
||||
}
|
||||
i->Remove();
|
||||
result = true;
|
||||
}
|
||||
break;
|
||||
case OPCODE_IS_FALSE:
|
||||
if (i->src1.value->IsConstant()) {
|
||||
if (i->src1.value->IsConstantFalse()) {
|
||||
v->set_constant(uint8_t(1));
|
||||
} else {
|
||||
v->set_constant(uint8_t(0));
|
||||
}
|
||||
i->Remove();
|
||||
result = true;
|
||||
}
|
||||
break;
|
||||
case OPCODE_IS_NAN:
|
||||
if (i->src1.value->IsConstant()) {
|
||||
if (i->src1.value->type == FLOAT32_TYPE &&
|
||||
@@ -602,7 +580,7 @@ bool ConstantPropagationPass::Run(HIRBuilder* builder, bool& result) {
|
||||
if (i->src1.value->IsConstant() && i->src2.value->IsConstant()) {
|
||||
if (should_skip_because_of_float) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
v->set_from(i->src1.value);
|
||||
v->Max(i->src2.value);
|
||||
i->Remove();
|
||||
|
||||
@@ -214,12 +214,7 @@ bool SimplificationPass::CheckBooleanXor1(hir::Instr* i,
|
||||
bool need_zx = (tunflags & MOVTUNNEL_MOVZX) != 0;
|
||||
|
||||
Value* new_value = nullptr;
|
||||
if (xorop == OPCODE_IS_FALSE) {
|
||||
new_value = builder->IsTrue(xordef->src1.value);
|
||||
|
||||
} else if (xorop == OPCODE_IS_TRUE) {
|
||||
new_value = builder->IsFalse(xordef->src1.value);
|
||||
} else if (xorop == OPCODE_COMPARE_EQ) {
|
||||
if (xorop == OPCODE_COMPARE_EQ) {
|
||||
new_value = builder->CompareNE(xordef->src1.value, xordef->src2.value);
|
||||
|
||||
} else if (xorop == OPCODE_COMPARE_NE) {
|
||||
@@ -294,7 +289,7 @@ bool SimplificationPass::CheckXor(hir::Instr* i, hir::HIRBuilder* builder) {
|
||||
return false;
|
||||
}
|
||||
bool SimplificationPass::Is1BitOpcode(hir::Opcode def_opcode) {
|
||||
return def_opcode >= OPCODE_IS_TRUE && def_opcode <= OPCODE_DID_SATURATE;
|
||||
return def_opcode >= OPCODE_COMPARE_EQ && def_opcode <= OPCODE_DID_SATURATE;
|
||||
}
|
||||
|
||||
inline static uint64_t RotateOfSize(ScalarNZM nzm, unsigned rotation,
|
||||
@@ -804,24 +799,12 @@ bool SimplificationPass::CheckScalarConstCmp(hir::Instr* i,
|
||||
if (!var_definition) {
|
||||
return false;
|
||||
}
|
||||
// x == 0 -> !x
|
||||
if (cmpop == OPCODE_COMPARE_EQ && constant_unpacked == 0) {
|
||||
i->Replace(&OPCODE_IS_FALSE_info, 0);
|
||||
i->set_src1(variable);
|
||||
return true;
|
||||
}
|
||||
// x != 0 -> !!x
|
||||
if (cmpop == OPCODE_COMPARE_NE && constant_unpacked == 0) {
|
||||
i->Replace(&OPCODE_IS_TRUE_info, 0);
|
||||
i->set_src1(variable);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (cmpop == OPCODE_COMPARE_ULE &&
|
||||
constant_unpacked ==
|
||||
0) { // less than or equal to zero = (== 0) = IS_FALSE
|
||||
i->Replace(&OPCODE_IS_FALSE_info, 0);
|
||||
i->set_src1(variable);
|
||||
i->opcode = &OPCODE_COMPARE_EQ_info;
|
||||
|
||||
return true;
|
||||
}
|
||||
// todo: OPCODE_COMPARE_NE too?
|
||||
@@ -840,15 +823,20 @@ bool SimplificationPass::CheckScalarConstCmp(hir::Instr* i,
|
||||
}
|
||||
if (cmpop == OPCODE_COMPARE_ULT &&
|
||||
constant_unpacked == 1) { // unsigned lt 1 means == 0
|
||||
i->Replace(&OPCODE_IS_FALSE_info, 0);
|
||||
i->set_src1(variable);
|
||||
// i->Replace(&OPCODE_IS_FALSE_info, 0);
|
||||
|
||||
i->opcode = &OPCODE_COMPARE_EQ_info;
|
||||
|
||||
// i->set_src1(variable);
|
||||
i->set_src2(builder->LoadZero(variable->type));
|
||||
return true;
|
||||
}
|
||||
if (cmpop == OPCODE_COMPARE_UGT &&
|
||||
constant_unpacked == 0) { // unsigned gt 1 means != 0
|
||||
|
||||
i->Replace(&OPCODE_IS_TRUE_info, 0);
|
||||
i->set_src1(variable);
|
||||
// i->Replace(&OPCODE_IS_TRUE_info, 0);
|
||||
// i->set_src1(variable);
|
||||
i->opcode = &OPCODE_COMPARE_NE_info;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -870,8 +858,11 @@ bool SimplificationPass::CheckScalarConstCmp(hir::Instr* i,
|
||||
} else if (cmpop == OPCODE_COMPARE_SGT && signbit_definitely_0 &&
|
||||
constant_unpacked == 0) {
|
||||
// signbit cant be set, and checking if gt 0, so actually checking != 0
|
||||
i->Replace(&OPCODE_IS_TRUE_info, 0);
|
||||
i->set_src1(variable);
|
||||
// i->Replace(&OPCODE_IS_TRUE_info, 0);
|
||||
|
||||
// i->set_src1(variable);
|
||||
i->opcode = &OPCODE_COMPARE_NE_info;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -885,9 +876,9 @@ bool SimplificationPass::CheckScalarConstCmp(hir::Instr* i,
|
||||
Value* constant_replacement = nullptr;
|
||||
|
||||
if (cmpop == OPCODE_COMPARE_EQ || cmpop == OPCODE_COMPARE_UGE) {
|
||||
repl = &OPCODE_IS_TRUE_info;
|
||||
repl = &OPCODE_COMPARE_NE_info;
|
||||
} else if (cmpop == OPCODE_COMPARE_NE || cmpop == OPCODE_COMPARE_ULT) {
|
||||
repl = &OPCODE_IS_FALSE_info;
|
||||
repl = &OPCODE_COMPARE_EQ_info;
|
||||
|
||||
} else if (cmpop == OPCODE_COMPARE_UGT) {
|
||||
// impossible, cannot be greater than mask
|
||||
@@ -906,6 +897,7 @@ bool SimplificationPass::CheckScalarConstCmp(hir::Instr* i,
|
||||
if (repl) {
|
||||
i->Replace(repl, 0);
|
||||
i->set_src1(variable);
|
||||
i->set_src2(builder->LoadZero(variable->type));
|
||||
return true;
|
||||
}
|
||||
if (constant_replacement) {
|
||||
@@ -919,10 +911,16 @@ bool SimplificationPass::CheckScalarConstCmp(hir::Instr* i,
|
||||
}
|
||||
bool SimplificationPass::CheckIsTrueIsFalse(hir::Instr* i,
|
||||
hir::HIRBuilder* builder) {
|
||||
bool istrue = i->opcode == &OPCODE_IS_TRUE_info;
|
||||
bool isfalse = i->opcode == &OPCODE_IS_FALSE_info;
|
||||
bool istrue = i->opcode == &OPCODE_COMPARE_NE_info;
|
||||
bool isfalse = i->opcode == &OPCODE_COMPARE_EQ_info;
|
||||
|
||||
Value* input = i->src1.value;
|
||||
auto [input_cosntant, input] = i->BinaryValueArrangeAsConstAndVar();
|
||||
|
||||
if (!input_cosntant || input_cosntant->AsUint64() != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Value* input = i->src1.value;
|
||||
TypeName input_type = input->type;
|
||||
if (!IsScalarIntegralType(input_type)) {
|
||||
return false;
|
||||
@@ -1012,8 +1010,10 @@ bool SimplificationPass::CheckSHRByConst(hir::Instr* i,
|
||||
i->set_src1(isfalsetest);
|
||||
|
||||
} else {
|
||||
i->Replace(&OPCODE_IS_FALSE_info, 0);
|
||||
// i->Replace(&OPCODE_IS_FALSE_info, 0);
|
||||
i->Replace(&OPCODE_COMPARE_EQ_info, 0);
|
||||
i->set_src1(lz_input);
|
||||
i->set_src2(builder->LoadZero(lz_input->type));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -1067,7 +1067,7 @@ bool SimplificationPass::SimplifyBitArith(hir::HIRBuilder* builder) {
|
||||
while (i) {
|
||||
// vector types use the same opcodes as scalar ones for AND/OR/XOR! we
|
||||
// don't handle these in our simplifications, so skip
|
||||
if (i->dest && IsScalarIntegralType(i->dest->type)) {
|
||||
if (i->AllScalarIntegral()) {
|
||||
Opcode iop = i->opcode->num;
|
||||
|
||||
if (iop == OPCODE_OR) {
|
||||
@@ -1080,7 +1080,6 @@ bool SimplificationPass::SimplifyBitArith(hir::HIRBuilder* builder) {
|
||||
result |= CheckAdd(i, builder);
|
||||
} else if (IsScalarBasicCmp(iop)) {
|
||||
result |= CheckScalarConstCmp(i, builder);
|
||||
} else if (iop == OPCODE_IS_FALSE || iop == OPCODE_IS_TRUE) {
|
||||
result |= CheckIsTrueIsFalse(i, builder);
|
||||
} else if (iop == OPCODE_SHR) {
|
||||
result |= CheckSHR(i, builder);
|
||||
|
||||
@@ -1023,7 +1023,6 @@ Value* HIRBuilder::Truncate(Value* value, TypeName target_type) {
|
||||
|
||||
Value* HIRBuilder::Convert(Value* value, TypeName target_type,
|
||||
RoundMode round_mode) {
|
||||
|
||||
Instr* i =
|
||||
AppendInstr(OPCODE_CONVERT_info, round_mode, AllocValue(target_type));
|
||||
i->set_src1(value);
|
||||
@@ -1034,7 +1033,6 @@ Value* HIRBuilder::Convert(Value* value, TypeName target_type,
|
||||
Value* HIRBuilder::Round(Value* value, RoundMode round_mode) {
|
||||
ASSERT_FLOAT_OR_VECTOR_TYPE(value);
|
||||
|
||||
|
||||
Instr* i =
|
||||
AppendInstr(OPCODE_ROUND_info, round_mode, AllocValue(value->type));
|
||||
i->set_src1(value);
|
||||
@@ -1248,7 +1246,34 @@ void HIRBuilder::Store(Value* address, Value* value, uint32_t store_flags) {
|
||||
i->set_src2(value);
|
||||
i->src3.value = NULL;
|
||||
}
|
||||
|
||||
Value* HIRBuilder::LoadVectorLeft(Value* address) {
|
||||
ASSERT_ADDRESS_TYPE(address);
|
||||
Instr* i = AppendInstr(OPCODE_LVL_info, 0, AllocValue(VEC128_TYPE));
|
||||
i->set_src1(address);
|
||||
i->src2.value = i->src3.value = NULL;
|
||||
return i->dest;
|
||||
}
|
||||
Value* HIRBuilder::LoadVectorRight(Value* address) {
|
||||
ASSERT_ADDRESS_TYPE(address);
|
||||
Instr* i = AppendInstr(OPCODE_LVR_info, 0, AllocValue(VEC128_TYPE));
|
||||
i->set_src1(address);
|
||||
i->src2.value = i->src3.value = NULL;
|
||||
return i->dest;
|
||||
}
|
||||
void HIRBuilder::StoreVectorLeft(Value* address, Value* value) {
|
||||
ASSERT_ADDRESS_TYPE(address);
|
||||
Instr* i = AppendInstr(OPCODE_STVL_info, 0);
|
||||
i->set_src1(address);
|
||||
i->set_src2(value);
|
||||
i->src3.value = NULL;
|
||||
}
|
||||
void HIRBuilder::StoreVectorRight(Value* address, Value* value) {
|
||||
ASSERT_ADDRESS_TYPE(address);
|
||||
Instr* i = AppendInstr(OPCODE_STVR_info, 0);
|
||||
i->set_src1(address);
|
||||
i->set_src2(value);
|
||||
i->src3.value = NULL;
|
||||
}
|
||||
void HIRBuilder::Memset(Value* address, Value* value, Value* length) {
|
||||
ASSERT_ADDRESS_TYPE(address);
|
||||
ASSERT_TYPES_EQUAL(address, length);
|
||||
@@ -1283,7 +1308,7 @@ void HIRBuilder::SetNJM(Value* value) {
|
||||
Value* HIRBuilder::Max(Value* value1, Value* value2) {
|
||||
ASSERT_TYPES_EQUAL(value1, value2);
|
||||
|
||||
if (IsScalarIntegralType( value1->type) && value1->IsConstant() &&
|
||||
if (IsScalarIntegralType(value1->type) && value1->IsConstant() &&
|
||||
value2->IsConstant()) {
|
||||
return value1->Compare(OPCODE_COMPARE_SLT, value2) ? value2 : value1;
|
||||
}
|
||||
@@ -1351,27 +1376,51 @@ Value* HIRBuilder::Select(Value* cond, Value* value1, Value* value2) {
|
||||
i->set_src3(value2);
|
||||
return i->dest;
|
||||
}
|
||||
static Value* OrLanes32(HIRBuilder& f, Value* value) {
|
||||
hir::Value* v1 = f.Extract(value, (uint8_t)0, INT32_TYPE);
|
||||
hir::Value* v2 = f.Extract(value, (uint8_t)1, INT32_TYPE);
|
||||
hir::Value* v3 = f.Extract(value, (uint8_t)2, INT32_TYPE);
|
||||
hir::Value* ored = f.Or(v1, v2);
|
||||
|
||||
hir::Value* v4 = f.Extract(value, (uint8_t)3, INT32_TYPE);
|
||||
ored = f.Or(ored, v3);
|
||||
|
||||
ored = f.Or(ored, v4);
|
||||
return ored;
|
||||
}
|
||||
Value* HIRBuilder::IsTrue(Value* value) {
|
||||
assert_true(value);
|
||||
if (value->type == VEC128_TYPE) {
|
||||
// chrispy: this probably doesnt happen often enough to be worth its own
|
||||
// opcode or special code path but this could be optimized to not require as
|
||||
// many extracts, we can shuffle and or v128 and then extract the low
|
||||
|
||||
return CompareEQ(OrLanes32(*this, value), LoadZeroInt32());
|
||||
}
|
||||
|
||||
if (value->IsConstant()) {
|
||||
return LoadConstantInt8(value->IsConstantTrue() ? 1 : 0);
|
||||
}
|
||||
|
||||
Instr* i = AppendInstr(OPCODE_IS_TRUE_info, 0, AllocValue(INT8_TYPE));
|
||||
i->set_src1(value);
|
||||
i->src2.value = i->src3.value = NULL;
|
||||
return i->dest;
|
||||
return CompareNE(value, LoadZero(value->type));
|
||||
}
|
||||
|
||||
Value* HIRBuilder::IsFalse(Value* value) {
|
||||
assert_true(value);
|
||||
|
||||
if (value->type == VEC128_TYPE) {
|
||||
// chrispy: this probably doesnt happen often enough to be worth its own
|
||||
// opcode or special code path but this could be optimized to not require as
|
||||
// many extracts, we can shuffle and or v128 and then extract the low
|
||||
|
||||
return CompareEQ(OrLanes32(*this, value), LoadZeroInt32());
|
||||
}
|
||||
|
||||
if (value->IsConstant()) {
|
||||
return LoadConstantInt8(value->IsConstantFalse() ? 1 : 0);
|
||||
}
|
||||
|
||||
Instr* i = AppendInstr(OPCODE_IS_FALSE_info, 0, AllocValue(INT8_TYPE));
|
||||
i->set_src1(value);
|
||||
i->src2.value = i->src3.value = NULL;
|
||||
return i->dest;
|
||||
return CompareEQ(value, LoadZero(value->type));
|
||||
}
|
||||
|
||||
Value* HIRBuilder::IsNan(Value* value) {
|
||||
|
||||
@@ -166,6 +166,11 @@ class HIRBuilder {
|
||||
uint32_t store_flags = 0);
|
||||
|
||||
Value* Load(Value* address, TypeName type, uint32_t load_flags = 0);
|
||||
|
||||
Value* LoadVectorLeft(Value* address);
|
||||
Value* LoadVectorRight(Value* address);
|
||||
void StoreVectorLeft(Value* address, Value* value);
|
||||
void StoreVectorRight(Value* address, Value* value);
|
||||
void Store(Value* address, Value* value, uint32_t store_flags = 0);
|
||||
void Memset(Value* address, Value* value, Value* length);
|
||||
void CacheControl(Value* address, size_t cache_line_size,
|
||||
@@ -268,6 +273,7 @@ class HIRBuilder {
|
||||
Value* new_value);
|
||||
Value* AtomicAdd(Value* address, Value* value);
|
||||
Value* AtomicSub(Value* address, Value* value);
|
||||
|
||||
void SetNJM(Value* value);
|
||||
|
||||
protected:
|
||||
|
||||
@@ -213,7 +213,19 @@ uint32_t Instr::GuestAddressFor() const {
|
||||
|
||||
return 0; // eek.
|
||||
}
|
||||
bool Instr::AllScalarIntegral() {
|
||||
bool result = true;
|
||||
if (dest) {
|
||||
if (!IsScalarIntegralType(dest->type)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
VisitValueOperands([&result](Value* v, uint32_t idx) {
|
||||
result = result && IsScalarIntegralType(v->type);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
} // namespace hir
|
||||
} // namespace cpu
|
||||
} // namespace xe
|
||||
|
||||
@@ -171,6 +171,8 @@ if both are constant, return nullptr, nullptr
|
||||
const hir::Instr* GetNonFakePrev() const;
|
||||
|
||||
uint32_t GuestAddressFor() const;
|
||||
|
||||
bool AllScalarIntegral(); // dest and all srcs are scalar integral
|
||||
};
|
||||
|
||||
} // namespace hir
|
||||
|
||||
@@ -210,10 +210,10 @@ enum Opcode {
|
||||
OPCODE_STORE,
|
||||
// chrispy: todo: implement, our current codegen for the unaligned loads is
|
||||
// very bad
|
||||
OPCODE_LVLX,
|
||||
OPCODE_LVRX,
|
||||
OPCODE_STVLX,
|
||||
OPCODE_STVRX,
|
||||
OPCODE_LVL,
|
||||
OPCODE_LVR,
|
||||
OPCODE_STVL,
|
||||
OPCODE_STVR,
|
||||
OPCODE_MEMSET,
|
||||
OPCODE_CACHE_CONTROL,
|
||||
OPCODE_MEMORY_BARRIER,
|
||||
@@ -222,8 +222,6 @@ enum Opcode {
|
||||
OPCODE_MIN,
|
||||
OPCODE_VECTOR_MIN,
|
||||
OPCODE_SELECT,
|
||||
OPCODE_IS_TRUE,
|
||||
OPCODE_IS_FALSE,
|
||||
OPCODE_IS_NAN,
|
||||
OPCODE_COMPARE_EQ,
|
||||
OPCODE_COMPARE_NE,
|
||||
|
||||
@@ -303,17 +303,6 @@ DEFINE_OPCODE(
|
||||
OPCODE_SIG_V_V_V_V,
|
||||
0)
|
||||
|
||||
DEFINE_OPCODE(
|
||||
OPCODE_IS_TRUE,
|
||||
"is_true",
|
||||
OPCODE_SIG_V_V,
|
||||
0)
|
||||
|
||||
DEFINE_OPCODE(
|
||||
OPCODE_IS_FALSE,
|
||||
"is_false",
|
||||
OPCODE_SIG_V_V,
|
||||
0)
|
||||
|
||||
DEFINE_OPCODE(
|
||||
OPCODE_IS_NAN,
|
||||
@@ -706,4 +695,27 @@ DEFINE_OPCODE(
|
||||
OPCODE_SIG_X_V,
|
||||
0
|
||||
)
|
||||
DEFINE_OPCODE(
|
||||
OPCODE_LVL,
|
||||
"loadv_left",
|
||||
OPCODE_SIG_V_V,
|
||||
OPCODE_FLAG_MEMORY
|
||||
)
|
||||
|
||||
DEFINE_OPCODE(
|
||||
OPCODE_LVR,
|
||||
"loadv_right",
|
||||
OPCODE_SIG_V_V,
|
||||
OPCODE_FLAG_MEMORY
|
||||
)
|
||||
DEFINE_OPCODE(
|
||||
OPCODE_STVL,
|
||||
"storev_left",
|
||||
OPCODE_SIG_X_V_V,
|
||||
OPCODE_FLAG_MEMORY)
|
||||
|
||||
DEFINE_OPCODE(
|
||||
OPCODE_STVR,
|
||||
"storev_right",
|
||||
OPCODE_SIG_X_V_V,
|
||||
OPCODE_FLAG_MEMORY)
|
||||
|
||||
@@ -418,6 +418,10 @@ void PPCHIRBuilder::UpdateCR6(Value* src_value) {
|
||||
// Testing for all 1's and all 0's.
|
||||
// if (Rc) CR6 = all_equal | 0 | none_equal | 0
|
||||
// TODO(benvanik): efficient instruction?
|
||||
|
||||
// chrispy: nothing seems to write cr6_1, figure out if no documented
|
||||
// instructions write anything other than 0 to it and remove these stores if
|
||||
// so
|
||||
StoreContext(offsetof(PPCContext, cr6.cr6_1), LoadZeroInt8());
|
||||
StoreContext(offsetof(PPCContext, cr6.cr6_3), LoadZeroInt8());
|
||||
StoreContext(offsetof(PPCContext, cr6.cr6_all_equal),
|
||||
|
||||
Reference in New Issue
Block a user