Adding some docs on CPU optimizations/potential work.
This commit is contained in:
@@ -53,11 +53,6 @@ static const size_t MAX_CODE_SIZE = 1 * 1024 * 1024;
|
||||
static const size_t STASH_OFFSET = 32;
|
||||
static const size_t STASH_OFFSET_HIGH = 32 + 32;
|
||||
|
||||
// If we are running with tracing on we have to store the EFLAGS in the stack,
|
||||
// otherwise our calls out to C to print will clear it before DID_CARRY/etc
|
||||
// can get the value.
|
||||
#define STORE_EFLAGS 1
|
||||
|
||||
const uint32_t X64Emitter::gpr_reg_map_[X64Emitter::GPR_COUNT] = {
|
||||
Operand::RBX, Operand::R12, Operand::R13, Operand::R14, Operand::R15,
|
||||
};
|
||||
@@ -539,25 +534,6 @@ void X64Emitter::nop(size_t length) {
|
||||
}
|
||||
}
|
||||
|
||||
void X64Emitter::LoadEflags() {
|
||||
#if STORE_EFLAGS
|
||||
mov(eax, dword[rsp + STASH_OFFSET]);
|
||||
btr(eax, 0);
|
||||
#else
|
||||
// EFLAGS already present.
|
||||
#endif // STORE_EFLAGS
|
||||
}
|
||||
|
||||
void X64Emitter::StoreEflags() {
|
||||
#if STORE_EFLAGS
|
||||
pushf();
|
||||
pop(dword[rsp + STASH_OFFSET]);
|
||||
#else
|
||||
// EFLAGS should have CA set?
|
||||
// (so long as we don't fuck with it)
|
||||
#endif // STORE_EFLAGS
|
||||
}
|
||||
|
||||
bool X64Emitter::ConstantFitsIn32Reg(uint64_t v) {
|
||||
if ((v & ~0x7FFFFFFF) == 0) {
|
||||
// Fits under 31 bits, so just load using normal mov.
|
||||
|
||||
@@ -173,9 +173,6 @@ class X64Emitter : public Xbyak::CodeGenerator {
|
||||
|
||||
// TODO(benvanik): Label for epilog (don't use strings).
|
||||
|
||||
void LoadEflags();
|
||||
void StoreEflags();
|
||||
|
||||
// Moves a 64bit immediate into memory.
|
||||
bool ConstantFitsIn32Reg(uint64_t v);
|
||||
void MovMem64(const Xbyak::RegExp& addr, uint64_t v);
|
||||
|
||||
@@ -24,157 +24,4 @@
|
||||
#include "xenia/cpu/compiler/passes/validation_pass.h"
|
||||
#include "xenia/cpu/compiler/passes/value_reduction_pass.h"
|
||||
|
||||
// TODO:
|
||||
// - mark_use/mark_set
|
||||
// For now: mark_all_changed on all calls
|
||||
// For external functions:
|
||||
// - load_context/mark_use on all arguments
|
||||
// - mark_set on return argument?
|
||||
// For internal functions:
|
||||
// - if liveness analysis already done, use that
|
||||
// - otherwise, assume everything dirty (ACK!)
|
||||
// - could use scanner to insert mark_use
|
||||
//
|
||||
// Maybe:
|
||||
// - v0.xx = load_constant <c>
|
||||
// - v0.xx = load_zero
|
||||
// Would prevent NULL defs on values, and make constant de-duping possible.
|
||||
// Not sure if it's worth it, though, as the extra register allocation
|
||||
// pressure due to de-duped constants seems like it would slow things down
|
||||
// a lot.
|
||||
//
|
||||
// - CFG:
|
||||
// Blocks need predecessors()/successor()
|
||||
// phi Instr reference
|
||||
//
|
||||
// - block liveness tracking (in/out)
|
||||
// Block gets:
|
||||
// AddIncomingValue(Value* value, Block* src_block) ??
|
||||
|
||||
// Potentially interesting passes:
|
||||
//
|
||||
// Run order:
|
||||
// ContextPromotion
|
||||
// Simplification
|
||||
// ConstantPropagation
|
||||
// TypePropagation
|
||||
// ByteSwapElimination
|
||||
// Simplification
|
||||
// DeadStoreElimination
|
||||
// DeadCodeElimination
|
||||
//
|
||||
// - TypePropagation
|
||||
// There are many extensions/truncations in generated code right now due to
|
||||
// various load/stores of varying widths. Being able to find and short-
|
||||
// circuit the conversions early on would make following passes cleaner
|
||||
// and faster as they'd have to trace through fewer value definitions.
|
||||
// Example (after ContextPromotion):
|
||||
// v81.i64 = load_context +88
|
||||
// v82.i32 = truncate v81.i64
|
||||
// v84.i32 = and v82.i32, 3F
|
||||
// v85.i64 = zero_extend v84.i32
|
||||
// v87.i64 = load_context +248
|
||||
// v88.i64 = v85.i64
|
||||
// v89.i32 = truncate v88.i64 <-- zero_extend/truncate => v84.i32
|
||||
// v90.i32 = byte_swap v89.i32
|
||||
// store v87.i64, v90.i32
|
||||
// after type propagation / simplification / DCE:
|
||||
// v81.i64 = load_context +88
|
||||
// v82.i32 = truncate v81.i64
|
||||
// v84.i32 = and v82.i32, 3F
|
||||
// v87.i64 = load_context +248
|
||||
// v90.i32 = byte_swap v84.i32
|
||||
// store v87.i64, v90.i32
|
||||
//
|
||||
// - ByteSwapElimination
|
||||
// Find chained byte swaps and replace with assignments. This is often found
|
||||
// in memcpy paths.
|
||||
// Example:
|
||||
// v0 = load ...
|
||||
// v1 = byte_swap v0
|
||||
// v2 = byte_swap v1
|
||||
// store ..., v2 <-- this could be v0
|
||||
//
|
||||
// It may be tricky to detect, though, as often times there are intervening
|
||||
// instructions:
|
||||
// v21.i32 = load v20.i64
|
||||
// v22.i32 = byte_swap v21.i32
|
||||
// v23.i64 = zero_extend v22.i32
|
||||
// v88.i64 = v23.i64 (from ContextPromotion)
|
||||
// v89.i32 = truncate v88.i64
|
||||
// v90.i32 = byte_swap v89.i32
|
||||
// store v87.i64, v90.i32
|
||||
// After type propagation:
|
||||
// v21.i32 = load v20.i64
|
||||
// v22.i32 = byte_swap v21.i32
|
||||
// v89.i32 = v22.i32
|
||||
// v90.i32 = byte_swap v89.i32
|
||||
// store v87.i64, v90.i32
|
||||
// This could ideally become:
|
||||
// v21.i32 = load v20.i64
|
||||
// ... (DCE takes care of this) ...
|
||||
// store v87.i64, v21.i32
|
||||
//
|
||||
// - DeadStoreElimination
|
||||
// Generic DSE pass, removing all redundant stores. ContextPromotion may be
|
||||
// able to take care of most of these, as the input assembly is generally
|
||||
// pretty optimized already. This pass would mainly be looking for introduced
|
||||
// stores, such as those from comparisons.
|
||||
//
|
||||
// Example:
|
||||
// <block0>:
|
||||
// v0 = compare_ult ... (later removed by DCE)
|
||||
// v1 = compare_ugt ... (later removed by DCE)
|
||||
// v2 = compare_eq ...
|
||||
// store_context +300, v0 <-- removed
|
||||
// store_context +301, v1 <-- removed
|
||||
// store_context +302, v2 <-- removed
|
||||
// branch_true v1, ...
|
||||
// <block1>:
|
||||
// v3 = compare_ult ...
|
||||
// v4 = compare_ugt ...
|
||||
// v5 = compare_eq ...
|
||||
// store_context +300, v3 <-- these may be required if at end of function
|
||||
// store_context +301, v4 or before a call
|
||||
// store_context +302, v5
|
||||
// branch_true v5, ...
|
||||
//
|
||||
// - X86Canonicalization
|
||||
// For various opcodes add copies/commute the arguments to match x86
|
||||
// operand semantics. This makes code generation easier and if done
|
||||
// before register allocation can prevent a lot of extra shuffling in
|
||||
// the emitted code.
|
||||
//
|
||||
// Example:
|
||||
// <block0>:
|
||||
// v0 = ...
|
||||
// v1 = ...
|
||||
// v2 = add v0, v1 <-- v1 now unused
|
||||
// Becomes:
|
||||
// v0 = ...
|
||||
// v1 = ...
|
||||
// v1 = add v1, v0 <-- src1 = dest/src, so reuse for both
|
||||
// by commuting and setting dest = src1
|
||||
//
|
||||
// - RegisterAllocation
|
||||
// Given a machine description (register classes, counts) run over values
|
||||
// and assign them to registers, adding spills as needed. It should be
|
||||
// possible to directly emit code from this form.
|
||||
//
|
||||
// Example:
|
||||
// <block0>:
|
||||
// v0 = load_context +0
|
||||
// v1 = load_context +1
|
||||
// v0 = add v0, v1
|
||||
// ...
|
||||
// v2 = mul v0, v1
|
||||
// Becomes:
|
||||
// reg0 = load_context +0
|
||||
// reg1 = load_context +1
|
||||
// reg2 = add reg0, reg1
|
||||
// store_local +123, reg2 <-- spill inserted
|
||||
// ...
|
||||
// reg0 = load_local +123 <-- load inserted
|
||||
// reg0 = mul reg0, reg1
|
||||
|
||||
#endif // XENIA_COMPILER_COMPILER_PASSES_H_
|
||||
|
||||
Reference in New Issue
Block a user