Files
Xenia-Canary/src/xenia/cpu/compiler/passes/simplification_pass.cc

192 lines
6.0 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/simplification_pass.h"
#include "xenia/base/profiling.h"
namespace xe {
namespace cpu {
namespace compiler {
namespace passes {
// TODO(benvanik): remove when enums redefined.
using namespace xe::cpu::hir;
using xe::cpu::hir::HIRBuilder;
using xe::cpu::hir::Instr;
using xe::cpu::hir::Value;
SimplificationPass::SimplificationPass() : ConditionalGroupSubpass() {}
SimplificationPass::~SimplificationPass() {}
bool SimplificationPass::Run(HIRBuilder* builder, bool& result) {
result = false;
result |= EliminateConversions(builder);
result |= SimplifyAssignments(builder);
return true;
}
bool SimplificationPass::EliminateConversions(HIRBuilder* builder) {
// First, we check for truncates/extensions that can be skipped.
// This generates some assignments which then the second step will clean up.
// Both zero/sign extends can be skipped:
// v1.i64 = zero/sign_extend v0.i32
// v2.i32 = truncate v1.i64
// becomes:
// v1.i64 = zero/sign_extend v0.i32 (may be dead code removed later)
// v2.i32 = v0.i32
bool result = false;
auto block = builder->first_block();
while (block) {
auto i = block->instr_head;
while (i) {
// To make things easier we check in reverse (source of truncate/extend
// back to definition).
if (i->opcode == &OPCODE_TRUNCATE_info) {
// Matches zero/sign_extend + truncate.
result |= CheckTruncate(i);
} else if (i->opcode == &OPCODE_BYTE_SWAP_info) {
// Matches byte swap + byte swap.
// This is pretty rare within the same basic block, but is in the
// memcpy hot path and (probably) worth it. Maybe.
result |= CheckByteSwap(i);
}
i = i->next;
}
block = block->next;
}
return result;
}
bool SimplificationPass::CheckTruncate(Instr* i) {
// Walk backward up src's chain looking for an extend. We may have
// assigns, so skip those.
auto src = i->src1.value;
auto def = src->def;
while (def && def->opcode == &OPCODE_ASSIGN_info) {
// Skip asignments.
def = def->src1.value->def;
}
if (def) {
if (def->opcode == &OPCODE_SIGN_EXTEND_info) {
// Value comes from a sign extend.
if (def->src1.value->type == i->dest->type) {
// Types match, use original by turning this into an assign.
i->Replace(&OPCODE_ASSIGN_info, 0);
i->set_src1(def->src1.value);
return true;
}
} else if (def->opcode == &OPCODE_ZERO_EXTEND_info) {
// Value comes from a zero extend.
if (def->src1.value->type == i->dest->type) {
// Types match, use original by turning this into an assign.
i->Replace(&OPCODE_ASSIGN_info, 0);
i->set_src1(def->src1.value);
return true;
}
}
}
return false;
}
bool SimplificationPass::CheckByteSwap(Instr* i) {
// Walk backward up src's chain looking for a byte swap. We may have
// assigns, so skip those.
auto src = i->src1.value;
auto def = src->def;
while (def && def->opcode == &OPCODE_ASSIGN_info) {
// Skip asignments.
def = def->src1.value->def;
}
if (def && def->opcode == &OPCODE_BYTE_SWAP_info) {
// Value comes from a byte swap.
if (def->src1.value->type == i->dest->type) {
// Types match, use original by turning this into an assign.
i->Replace(&OPCODE_ASSIGN_info, 0);
i->set_src1(def->src1.value);
return true;
}
}
return false;
}
bool SimplificationPass::SimplifyAssignments(HIRBuilder* builder) {
// Run over the instructions and rename assigned variables:
// v1 = v0
// v2 = v1
// v3 = add v0, v2
// becomes:
// v1 = v0
// v2 = v0
// v3 = add v0, v0
// This could be run several times, as it could make other passes faster
// to compute (for example, ConstantPropagation). DCE will take care of
// the useless assigns.
//
// We do this by walking each instruction. For each value op we
// look at its def instr to see if it's an assign - if so, we use the src
// of that instr. Because we may have chains, we do this recursively until
// we find a non-assign def.
bool result = false;
auto block = builder->first_block();
while (block) {
auto i = block->instr_head;
while (i) {
uint32_t signature = i->opcode->signature;
if (GET_OPCODE_SIG_TYPE_SRC1(signature) == OPCODE_SIG_TYPE_V) {
bool modified = false;
i->set_src1(CheckValue(i->src1.value, modified));
result |= modified;
}
if (GET_OPCODE_SIG_TYPE_SRC2(signature) == OPCODE_SIG_TYPE_V) {
bool modified = false;
i->set_src2(CheckValue(i->src2.value, modified));
result |= modified;
}
if (GET_OPCODE_SIG_TYPE_SRC3(signature) == OPCODE_SIG_TYPE_V) {
bool modified = false;
i->set_src3(CheckValue(i->src3.value, modified));
result |= modified;
}
i = i->next;
}
block = block->next;
}
return result;
}
Value* SimplificationPass::CheckValue(Value* value, bool& result) {
auto def = value->def;
if (def && def->opcode == &OPCODE_ASSIGN_info) {
// Value comes from an assignment - recursively find if it comes from
// another assignment. It probably doesn't, if we already replaced it.
auto replacement = def->src1.value;
while (true) {
def = replacement->def;
if (!def || def->opcode != &OPCODE_ASSIGN_info) {
break;
}
replacement = def->src1.value;
}
result = true;
return replacement;
}
result = false;
return value;
}
} // namespace passes
} // namespace compiler
} // namespace cpu
} // namespace xe