diff --git a/src/alloy/compiler/passes.h b/src/alloy/compiler/passes.h index e61ca5f2c..bb35604ef 100644 --- a/src/alloy/compiler/passes.h +++ b/src/alloy/compiler/passes.h @@ -12,7 +12,7 @@ //#include //#include -//#include +#include //#include #include @@ -69,7 +69,7 @@ // 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). +// instead as it may be faster (at least on the block-level). // // - ConstantPropagation // One ContextPromotion has run there will likely be a whole slew of diff --git a/src/alloy/compiler/passes/dead_code_elimination_pass.cc b/src/alloy/compiler/passes/dead_code_elimination_pass.cc new file mode 100644 index 000000000..674234bce --- /dev/null +++ b/src/alloy/compiler/passes/dead_code_elimination_pass.cc @@ -0,0 +1,123 @@ +/** + ****************************************************************************** + * 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 + +using namespace alloy; +using namespace alloy::compiler; +using namespace alloy::compiler::passes; +using namespace alloy::hir; + + +DeadCodeEliminationPass::DeadCodeEliminationPass() : + Pass() { +} + +DeadCodeEliminationPass::~DeadCodeEliminationPass() { +} + +int DeadCodeEliminationPass::Run(FunctionBuilder* builder) { + // ContextPromotion/DSE will likely leave around a lot of dead statements. + // Code generated for comparison/testing produces many unused statements and + // with proper use analysis it should be possible to remove most of them: + // After context promotion/simplification: + // v33.i8 = compare_ult v31.i32, 0 + // v34.i8 = compare_ugt v31.i32, 0 + // v35.i8 = compare_eq v31.i32, 0 + // store_context +300, v33.i8 + // store_context +301, v34.i8 + // store_context +302, v35.i8 + // branch_true v35.i8, loc_8201A484 + // After DSE: + // v33.i8 = compare_ult v31.i32, 0 + // v34.i8 = compare_ugt v31.i32, 0 + // v35.i8 = compare_eq v31.i32, 0 + // branch_true v35.i8, loc_8201A484 + // After DCE: + // v35.i8 = compare_eq v31.i32, 0 + // branch_true v35.i8, loc_8201A484 + + // We process DCE by reverse iterating over instructions and looking at the + // use count of the dest value. If it's zero, we can safely remove the + // instruction. Once we do that, the use counts of any of the src ops may + // go to zero and we recursively kill up the graph. This is kind of + // nasty in that we walk randomly and scribble all over memory, but (I think) + // it's better than doing passes over all instructions until we quiesce. + // To prevent our iteration from getting all messed up we just replace + // all removed ops with NOP and then do a single pass that removes them + // all. + + const OpcodeInfo* nop = builder->GetNopOpcode(); + + bool any_removed = false; + Block* block = builder->first_block(); + while (block) { + Instr* i = block->instr_tail; + while (i) { + Instr* prev = i->prev; + + const OpcodeInfo* opcode = i->opcode; + uint32_t signature = opcode->signature; + if (!(opcode->flags & OPCODE_FLAG_VOLATILE) && + i->dest && !i->dest->use_head) { + // Has no uses and is not volatile. This instruction can die! + MakeNopRecursive(nop, i); + any_removed = true; + } + + i = prev; + } + + block = block->next; + } + + // Remove all nops. + if (any_removed) { + Block* block = builder->first_block(); + while (block) { + Instr* i = block->instr_head; + while (i) { + Instr* next = i->next; + if (i->opcode->num == OPCODE_NOP) { + // Nop - remove! + i->Remove(); + } + i = next; + } + block = block->next; + } + } + + return 0; +} + +void DeadCodeEliminationPass::MakeNopRecursive( + const OpcodeInfo* nop, Instr* i) { + i->opcode = nop; + i->dest->def = NULL; + i->dest = NULL; + +#define MAKE_NOP_SRC(n) \ + if (i->src##n##_use) { \ + Value::Use* use = i->src##n##_use; \ + Value* value = i->src##n##.value; \ + i->src##n##_use = NULL; \ + i->src##n##.value = NULL; \ + value->RemoveUse(use); \ + if (!value->use_head) { \ + /* Value is now unused, so recursively kill it. */ \ + if (value->def && value->def != i) { \ + MakeNopRecursive(nop, value->def); \ + } \ + } \ + } + MAKE_NOP_SRC(1); + MAKE_NOP_SRC(2); + MAKE_NOP_SRC(3); +} diff --git a/src/alloy/compiler/passes/dead_code_elimination_pass.h b/src/alloy/compiler/passes/dead_code_elimination_pass.h new file mode 100644 index 000000000..5ce5156bd --- /dev/null +++ b/src/alloy/compiler/passes/dead_code_elimination_pass.h @@ -0,0 +1,38 @@ +/** + ****************************************************************************** + * 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. * + ****************************************************************************** + */ + +#ifndef ALLOY_COMPILER_PASSES_DEAD_CODE_ELIMINATION_PASS_H_ +#define ALLOY_COMPILER_PASSES_DEAD_CODE_ELIMINATION_PASS_H_ + +#include + + +namespace alloy { +namespace compiler { +namespace passes { + + +class DeadCodeEliminationPass : public Pass { +public: + DeadCodeEliminationPass(); + virtual ~DeadCodeEliminationPass(); + + virtual int Run(hir::FunctionBuilder* builder); + +private: + void MakeNopRecursive(const hir::OpcodeInfo* nop, hir::Instr* i); +}; + + +} // namespace passes +} // namespace compiler +} // namespace alloy + + +#endif // ALLOY_COMPILER_PASSES_DEAD_CODE_ELIMINATION_PASS_H_ diff --git a/src/alloy/compiler/passes/simplification_pass.cc b/src/alloy/compiler/passes/simplification_pass.cc index 06323f06e..3b8fe2e7c 100644 --- a/src/alloy/compiler/passes/simplification_pass.cc +++ b/src/alloy/compiler/passes/simplification_pass.cc @@ -8,7 +8,6 @@ */ #include -#include using namespace alloy; using namespace alloy::compiler; diff --git a/src/alloy/compiler/passes/sources.gypi b/src/alloy/compiler/passes/sources.gypi index 275c1ddc5..3fa9e27a9 100644 --- a/src/alloy/compiler/passes/sources.gypi +++ b/src/alloy/compiler/passes/sources.gypi @@ -5,8 +5,8 @@ #'constant_propagation_pass.h', #'context_promotion_pass.cc', #'context_promotion_pass.h', - #'dead_code_elimination_pass.cc', - #'dead_code_elimination_pass.h', + 'dead_code_elimination_pass.cc', + 'dead_code_elimination_pass.h', #'dead_store_elimination_pass.cc', #'dead_store_elimination_pass.h', 'simplification_pass.cc', diff --git a/src/alloy/frontend/ppc/ppc_translator.cc b/src/alloy/frontend/ppc/ppc_translator.cc index e72980b23..94de30a69 100644 --- a/src/alloy/frontend/ppc/ppc_translator.cc +++ b/src/alloy/frontend/ppc/ppc_translator.cc @@ -38,7 +38,7 @@ PPCTranslator::PPCTranslator(PPCFrontend* frontend) : //compiler_->AddPass(new passes::ByteSwapEliminationPass()); compiler_->AddPass(new passes::SimplificationPass()); //compiler_->AddPass(new passes::DeadStoreEliminationPass()); - //compiler_->AddPass(new passes::DeadCodeEliminationPass()); + compiler_->AddPass(new passes::DeadCodeEliminationPass()); Backend* backend = frontend->runtime()->backend(); assembler_ = backend->CreateAssembler(); diff --git a/src/alloy/hir/block.h b/src/alloy/hir/block.h index 4756c4fd7..d32caa262 100644 --- a/src/alloy/hir/block.h +++ b/src/alloy/hir/block.h @@ -16,6 +16,7 @@ namespace alloy { namespace hir { +class FunctionBuilder; class Instr; class Label; diff --git a/src/alloy/hir/function_builder.cc b/src/alloy/hir/function_builder.cc index 9aa145773..98fd23ad5 100644 --- a/src/alloy/hir/function_builder.cc +++ b/src/alloy/hir/function_builder.cc @@ -374,14 +374,17 @@ void FunctionBuilder::Comment(const char* format, ...) { i->src2.value = i->src3.value = NULL; } -void FunctionBuilder::Nop() { +const OpcodeInfo* FunctionBuilder::GetNopOpcode() const { STATIC_OPCODE( OPCODE_NOP, "nop", OPCODE_SIG_X, OPCODE_FLAG_IGNORE); + return &opcode; +} - Instr* i = AppendInstr(opcode, 0); +void FunctionBuilder::Nop() { + Instr* i = AppendInstr(*GetNopOpcode(), 0); i->src1.value = i->src2.value = i->src3.value = NULL; } diff --git a/src/alloy/hir/function_builder.h b/src/alloy/hir/function_builder.h index 931c8c2c1..dca09d72a 100644 --- a/src/alloy/hir/function_builder.h +++ b/src/alloy/hir/function_builder.h @@ -55,6 +55,7 @@ public: void Comment(const char* format, ...); + const OpcodeInfo* GetNopOpcode() const; void Nop(); // trace info/etc diff --git a/src/alloy/hir/instr.cc b/src/alloy/hir/instr.cc index bfeed371e..3ff9610e9 100644 --- a/src/alloy/hir/instr.cc +++ b/src/alloy/hir/instr.cc @@ -47,3 +47,33 @@ void Instr::set_src3(Value* value) { src3.value = value; src3_use = value ? value->AddUse(block->arena, this) : NULL; } + +void Instr::Remove() { + if (dest) { + XEASSERT(!dest->use_head); + dest->def = NULL; + } + if (src1_use) { + src1.value->RemoveUse(src1_use); + src1_use = NULL; + } + if (src2_use) { + src2.value->RemoveUse(src2_use); + src2_use = NULL; + } + if (src3_use) { + src3.value->RemoveUse(src3_use); + src3_use = NULL; + } + + if (prev) { + prev->next = next; + } else { + block->instr_head = next; + } + if (next) { + next->prev = prev; + } else { + block->instr_tail = prev; + } +} diff --git a/src/alloy/hir/instr.h b/src/alloy/hir/instr.h index 7a3fc81b6..517232cc5 100644 --- a/src/alloy/hir/instr.h +++ b/src/alloy/hir/instr.h @@ -53,6 +53,8 @@ public: void set_src1(Value* value); void set_src2(Value* value); void set_src3(Value* value); + + void Remove(); };