Added recognition of impossible comparisons via NZM and optimize them away Recognize (x + -y) and transform to (x - y) for constants Recognize (~x ) + 1 and transform to -x Check and transform comparisons if theyre semantically equal to others Detect comparisons of single-bit values with their only possible non-zero value and transform to true/false tests Transform ==0 to IS_FALSE, !=0 to IS_TRUE Truncate to int8 if operand for IS_TRUE/IS_FALSE has a nzm of 1 Reduced code generated for SubDidCarry slightly Add special case for InstrEmit_srawix if mask == 1 Cut down the code generated for trap instructions, instead of naive or'ing or compare results do a switch and select the best condition Rerun simplification pass until no changes, as some optimizations will enable others to be done Enable rel32 call optimization by default
160 lines
5.1 KiB
C++
160 lines
5.1 KiB
C++
/**
|
|
******************************************************************************
|
|
* Xenia : Xbox 360 Emulator Research Project *
|
|
******************************************************************************
|
|
* Copyright 2013 Ben Vanik. All rights reserved. *
|
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
|
******************************************************************************
|
|
*/
|
|
|
|
#include "xenia/cpu/compiler/passes/context_promotion_pass.h"
|
|
|
|
#include "xenia/apu/apu_flags.h"
|
|
#include "xenia/base/cvar.h"
|
|
#include "xenia/base/profiling.h"
|
|
#include "xenia/cpu/compiler/compiler.h"
|
|
#include "xenia/cpu/ppc/ppc_context.h"
|
|
#include "xenia/cpu/processor.h"
|
|
|
|
DECLARE_bool(debug);
|
|
|
|
DEFINE_bool(store_all_context_values, false,
|
|
"Don't strip dead context stores to aid in debugging.", "CPU");
|
|
|
|
DEFINE_bool(full_optimization_even_with_debug, false,
|
|
"For developer use to analyze the quality of the generated code, "
|
|
"not intended for actual debugging of the code",
|
|
"CPU");
|
|
|
|
namespace xe {
|
|
namespace cpu {
|
|
namespace compiler {
|
|
namespace passes {
|
|
|
|
// TODO(benvanik): remove when enums redefined.
|
|
using namespace xe::cpu::hir;
|
|
|
|
using xe::cpu::hir::Block;
|
|
using xe::cpu::hir::HIRBuilder;
|
|
using xe::cpu::hir::Instr;
|
|
using xe::cpu::hir::Value;
|
|
|
|
ContextPromotionPass::ContextPromotionPass() : CompilerPass() {}
|
|
|
|
ContextPromotionPass::~ContextPromotionPass() {}
|
|
|
|
bool ContextPromotionPass::Initialize(Compiler* compiler) {
|
|
if (!CompilerPass::Initialize(compiler)) {
|
|
return false;
|
|
}
|
|
|
|
// This is a terrible implementation.
|
|
context_values_.resize(sizeof(ppc::PPCContext));
|
|
context_validity_.resize(static_cast<uint32_t>(sizeof(ppc::PPCContext)));
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ContextPromotionPass::Run(HIRBuilder* builder) {
|
|
// Like mem2reg, but because context memory is unaliasable it's easier to
|
|
// check and convert LoadContext/StoreContext into value operations.
|
|
// Example of load->value promotion:
|
|
// v0 = load_context +100
|
|
// store_context +200, v0
|
|
// v1 = load_context +100 <-- replace with v1 = v0
|
|
// store_context +200, v1
|
|
//
|
|
// It'd be possible in this stage to also remove redundant context stores:
|
|
// Example of dead store elimination:
|
|
// store_context +100, v0 <-- removed due to following store
|
|
// store_context +100, v1
|
|
// This is more generally done by DSE, however if it could be done here
|
|
// instead as it may be faster (at least on the block-level).
|
|
|
|
// Promote loads to values.
|
|
// Process each block independently, for now.
|
|
auto block = builder->first_block();
|
|
while (block) {
|
|
PromoteBlock(block);
|
|
block = block->next;
|
|
}
|
|
|
|
// Remove all dead stores.
|
|
// This will break debugging as we can't recover this information when
|
|
// trying to extract stack traces/register values, so we don't do that.
|
|
if (cvars::full_optimization_even_with_debug ||
|
|
(!cvars::debug && !cvars::store_all_context_values)) {
|
|
block = builder->first_block();
|
|
while (block) {
|
|
RemoveDeadStoresBlock(block);
|
|
block = block->next;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void ContextPromotionPass::PromoteBlock(Block* block) {
|
|
auto& validity = context_validity_;
|
|
validity.reset();
|
|
|
|
Instr* i = block->instr_head;
|
|
while (i) {
|
|
auto next = i->next;
|
|
if (i->opcode->flags & OPCODE_FLAG_VOLATILE) {
|
|
// Volatile instruction - requires all context values be flushed.
|
|
validity.reset();
|
|
} else if (i->opcode == &OPCODE_LOAD_CONTEXT_info) {
|
|
size_t offset = i->src1.offset;
|
|
if (validity.test(static_cast<uint32_t>(offset))) {
|
|
// Legit previous value, reuse.
|
|
Value* previous_value = context_values_[offset];
|
|
i->opcode = &hir::OPCODE_ASSIGN_info;
|
|
i->set_src1(previous_value);
|
|
} else {
|
|
// Store the loaded value into the table.
|
|
context_values_[offset] = i->dest;
|
|
validity.set(static_cast<uint32_t>(offset));
|
|
}
|
|
} else if (i->opcode == &OPCODE_STORE_CONTEXT_info) {
|
|
size_t offset = i->src1.offset;
|
|
Value* value = i->src2.value;
|
|
// Store value into the table for later.
|
|
context_values_[offset] = value;
|
|
validity.set(static_cast<uint32_t>(offset));
|
|
}
|
|
i = next;
|
|
}
|
|
}
|
|
|
|
void ContextPromotionPass::RemoveDeadStoresBlock(Block* block) {
|
|
auto& validity = context_validity_;
|
|
validity.reset();
|
|
|
|
// Walk backwards and mark offsets that are written to.
|
|
// If the offset was written to earlier, ignore the store.
|
|
Instr* i = block->instr_tail;
|
|
while (i) {
|
|
Instr* prev = i->prev;
|
|
if (i->opcode->flags & (OPCODE_FLAG_VOLATILE | OPCODE_FLAG_BRANCH)) {
|
|
// Volatile instruction - requires all context values be flushed.
|
|
validity.reset();
|
|
} else if (i->opcode == &OPCODE_STORE_CONTEXT_info) {
|
|
size_t offset = i->src1.offset;
|
|
if (!validity.test(static_cast<uint32_t>(offset))) {
|
|
// Offset not yet written, mark and continue.
|
|
validity.set(static_cast<uint32_t>(offset));
|
|
} else {
|
|
// Already written to. Remove this store.
|
|
i->Remove();
|
|
}
|
|
}
|
|
i = prev;
|
|
}
|
|
}
|
|
|
|
} // namespace passes
|
|
} // namespace compiler
|
|
} // namespace cpu
|
|
} // namespace xe
|