526 lines
17 KiB
C++
526 lines
17 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/ppc/ppc_hir_builder.h"
|
|
|
|
#include <cstring>
|
|
|
|
#include "xenia/base/byte_order.h"
|
|
#include "xenia/base/logging.h"
|
|
#include "xenia/base/memory.h"
|
|
#include "xenia/base/profiling.h"
|
|
#include "xenia/cpu/cpu_flags.h"
|
|
#include "xenia/cpu/hir/label.h"
|
|
#include "xenia/cpu/ppc/ppc_context.h"
|
|
#include "xenia/cpu/ppc/ppc_decode_data.h"
|
|
#include "xenia/cpu/ppc/ppc_frontend.h"
|
|
#include "xenia/cpu/ppc/ppc_opcode_info.h"
|
|
#include "xenia/cpu/processor.h"
|
|
|
|
namespace xe {
|
|
namespace cpu {
|
|
namespace ppc {
|
|
|
|
// TODO(benvanik): remove when enums redefined.
|
|
using namespace xe::cpu::hir;
|
|
|
|
using xe::cpu::hir::Label;
|
|
using xe::cpu::hir::TypeName;
|
|
using xe::cpu::hir::Value;
|
|
|
|
// The number of times each opcode has been translated.
|
|
// Accumulated across the entire run.
|
|
uint32_t opcode_translation_counts[static_cast<int>(PPCOpcode::kInvalid)] = {0};
|
|
|
|
void DumpAllOpcodeCounts() {
|
|
StringBuffer sb;
|
|
sb.Append("Instruction translation counts:\n");
|
|
for (size_t i = 0; i < xe::countof(opcode_translation_counts); ++i) {
|
|
auto opcode = static_cast<PPCOpcode>(i);
|
|
auto& opcode_info = GetOpcodeInfo(opcode);
|
|
auto& disasm_info = GetOpcodeDisasmInfo(opcode);
|
|
auto translation_count = opcode_translation_counts[i];
|
|
if (translation_count) {
|
|
sb.AppendFormat("%8d : %s\n", translation_count, disasm_info.name);
|
|
}
|
|
}
|
|
fprintf(stdout, "%s", sb.GetString());
|
|
fflush(stdout);
|
|
}
|
|
|
|
PPCHIRBuilder::PPCHIRBuilder(PPCFrontend* frontend)
|
|
: HIRBuilder(), frontend_(frontend), comment_buffer_(4096) {}
|
|
|
|
PPCHIRBuilder::~PPCHIRBuilder() = default;
|
|
|
|
PPCBuiltins* PPCHIRBuilder::builtins() const { return frontend_->builtins(); }
|
|
|
|
void PPCHIRBuilder::Reset() {
|
|
function_ = nullptr;
|
|
start_address_ = 0;
|
|
instr_count_ = 0;
|
|
instr_offset_list_ = NULL;
|
|
label_list_ = NULL;
|
|
with_debug_info_ = false;
|
|
HIRBuilder::Reset();
|
|
}
|
|
|
|
bool PPCHIRBuilder::Emit(GuestFunction* function, uint32_t flags) {
|
|
SCOPE_profile_cpu_f("cpu");
|
|
|
|
Memory* memory = frontend_->memory();
|
|
|
|
function_ = function;
|
|
start_address_ = function_->address();
|
|
instr_count_ = (function_->end_address() - function_->address()) / 4 + 1;
|
|
|
|
with_debug_info_ = (flags & EMIT_DEBUG_COMMENTS) == EMIT_DEBUG_COMMENTS;
|
|
if (with_debug_info_) {
|
|
CommentFormat("%s fn %.8X-%.8X %s", function_->module()->name().c_str(),
|
|
function_->address(), function_->end_address(),
|
|
function_->name().c_str());
|
|
}
|
|
|
|
// Allocate offset list.
|
|
// This is used to quickly map labels to instructions.
|
|
// The list is built as the instructions are traversed, with the values
|
|
// being the previous HIR Instr before the given instruction. An
|
|
// instruction may have a label assigned to it if it hasn't been hit
|
|
// yet.
|
|
size_t list_size = instr_count_ * sizeof(void*);
|
|
instr_offset_list_ = (Instr**)arena_->Alloc(list_size);
|
|
label_list_ = (Label**)arena_->Alloc(list_size);
|
|
std::memset(instr_offset_list_, 0, list_size);
|
|
std::memset(label_list_, 0, list_size);
|
|
|
|
// Always mark entry with label.
|
|
label_list_[0] = NewLabel();
|
|
|
|
uint32_t start_address = function_->address();
|
|
uint32_t end_address = function_->end_address();
|
|
for (uint32_t address = start_address, offset = 0; address <= end_address;
|
|
address += 4, offset++) {
|
|
trace_info_.dest_count = 0;
|
|
uint32_t code =
|
|
xe::load_and_swap<uint32_t>(memory->TranslateVirtual(address));
|
|
auto opcode = LookupOpcode(code);
|
|
auto& opcode_info = GetOpcodeInfo(opcode);
|
|
|
|
// Mark label, if we were assigned one earlier on in the walk.
|
|
// We may still get a label, but it'll be inserted by LookupLabel
|
|
// as needed.
|
|
Label* label = label_list_[offset];
|
|
if (label) {
|
|
MarkLabel(label);
|
|
}
|
|
|
|
Instr* first_instr = 0;
|
|
if (with_debug_info_) {
|
|
if (label) {
|
|
AnnotateLabel(address, label);
|
|
}
|
|
comment_buffer_.Reset();
|
|
comment_buffer_.AppendFormat("%.8X %.8X ", address, code);
|
|
DisasmPPC(address, code, &comment_buffer_);
|
|
Comment(comment_buffer_);
|
|
first_instr = last_instr();
|
|
}
|
|
|
|
// Mark source offset for debugging.
|
|
// We could omit this if we never wanted to debug.
|
|
SourceOffset(address);
|
|
if (!first_instr) {
|
|
first_instr = last_instr();
|
|
}
|
|
|
|
// Stash instruction offset. It's either the SOURCE_OFFSET or the COMMENT.
|
|
instr_offset_list_[offset] = first_instr;
|
|
|
|
if (opcode == PPCOpcode::kInvalid) {
|
|
XELOGE("Invalid instruction %.8llX %.8X", address, code);
|
|
Comment("INVALID!");
|
|
// TraceInvalidInstruction(i);
|
|
continue;
|
|
}
|
|
++opcode_translation_counts[static_cast<int>(opcode)];
|
|
|
|
// Synchronize the PPC context as required.
|
|
// This will ensure all registers are saved to the PPC context before this
|
|
// instruction executes.
|
|
if (opcode_info.type == PPCOpcodeType::kSync) {
|
|
ContextBarrier();
|
|
}
|
|
|
|
if (address == FLAGS_break_on_instruction) {
|
|
Comment("--break-on-instruction target");
|
|
|
|
if (FLAGS_break_condition_gpr < 0) {
|
|
DebugBreak();
|
|
} else {
|
|
auto left = LoadGPR(FLAGS_break_condition_gpr);
|
|
auto right = LoadConstantUint64(FLAGS_break_condition_value);
|
|
if (FLAGS_break_condition_truncate) {
|
|
left = Truncate(left, INT32_TYPE);
|
|
right = Truncate(right, INT32_TYPE);
|
|
}
|
|
TrapTrue(CompareEQ(left, right));
|
|
}
|
|
}
|
|
|
|
InstrData i;
|
|
i.address = address;
|
|
i.code = code;
|
|
i.opcode = opcode;
|
|
i.opcode_info = &opcode_info;
|
|
if (!opcode_info.emit || opcode_info.emit(*this, i)) {
|
|
auto& disasm_info = GetOpcodeDisasmInfo(opcode);
|
|
XELOGE("Unimplemented instr %.8llX %.8X %s", address, code,
|
|
disasm_info.name);
|
|
Comment("UNIMPLEMENTED!");
|
|
DebugBreak();
|
|
}
|
|
}
|
|
|
|
if (false) {
|
|
DumpAllOpcodeCounts();
|
|
}
|
|
|
|
return Finalize();
|
|
}
|
|
|
|
void PPCHIRBuilder::AnnotateLabel(uint32_t address, Label* label) {
|
|
char name_buffer[13];
|
|
snprintf(name_buffer, xe::countof(name_buffer), "loc_%.8X", address);
|
|
label->name = (char*)arena_->Alloc(sizeof(name_buffer));
|
|
memcpy(label->name, name_buffer, sizeof(name_buffer));
|
|
}
|
|
|
|
Function* PPCHIRBuilder::LookupFunction(uint32_t address) {
|
|
return frontend_->processor()->LookupFunction(address);
|
|
}
|
|
|
|
Label* PPCHIRBuilder::LookupLabel(uint32_t address) {
|
|
if (address < start_address_) {
|
|
return nullptr;
|
|
}
|
|
size_t offset = (address - start_address_) / 4;
|
|
if (offset >= instr_count_) {
|
|
return nullptr;
|
|
}
|
|
Label* label = label_list_[offset];
|
|
if (label) {
|
|
return label;
|
|
}
|
|
// No label. If we haven't yet hit the instruction in the walk
|
|
// then create a label. Otherwise, we must go back and insert
|
|
// the label.
|
|
label = NewLabel();
|
|
label_list_[offset] = label;
|
|
Instr* instr = instr_offset_list_[offset];
|
|
if (instr) {
|
|
if (instr->prev) {
|
|
// Insert label, breaking up existing instructions.
|
|
InsertLabel(label, instr->prev);
|
|
} else {
|
|
// Instruction is at the head of a block, so just add the label.
|
|
MarkLabel(label, instr->block);
|
|
}
|
|
|
|
// Annotate the label, as we won't do it later.
|
|
if (with_debug_info_) {
|
|
AnnotateLabel(address, label);
|
|
}
|
|
}
|
|
return label;
|
|
}
|
|
|
|
// Value* PPCHIRBuilder::LoadXER() {
|
|
//}
|
|
//
|
|
// void PPCHIRBuilder::StoreXER(Value* value) {
|
|
//}
|
|
|
|
Value* PPCHIRBuilder::LoadLR() {
|
|
return LoadContext(offsetof(PPCContext, lr), INT64_TYPE);
|
|
}
|
|
|
|
void PPCHIRBuilder::StoreLR(Value* value) {
|
|
assert_true(value->type == INT64_TYPE);
|
|
StoreContext(offsetof(PPCContext, lr), value);
|
|
|
|
auto& trace_reg = trace_info_.dests[trace_info_.dest_count++];
|
|
trace_reg.reg = 64;
|
|
trace_reg.value = value;
|
|
}
|
|
|
|
Value* PPCHIRBuilder::LoadCTR() {
|
|
return LoadContext(offsetof(PPCContext, ctr), INT64_TYPE);
|
|
}
|
|
|
|
void PPCHIRBuilder::StoreCTR(Value* value) {
|
|
assert_true(value->type == INT64_TYPE);
|
|
StoreContext(offsetof(PPCContext, ctr), value);
|
|
|
|
auto& trace_reg = trace_info_.dests[trace_info_.dest_count++];
|
|
trace_reg.reg = 65;
|
|
trace_reg.value = value;
|
|
}
|
|
|
|
Value* PPCHIRBuilder::LoadCR() {
|
|
// All bits. This is expensive, but seems to be less used than the
|
|
// field-specific LoadCR.
|
|
Value* v = LoadCR(0);
|
|
for (int i = 1; i <= 7; ++i) {
|
|
v = Or(v, LoadCR(i));
|
|
}
|
|
return v;
|
|
}
|
|
|
|
Value* PPCHIRBuilder::LoadCR(uint32_t n) {
|
|
// Construct the entire word of just the bits we care about.
|
|
// This makes it easier for the optimizer to exclude things, though
|
|
// we could be even more clever and watch sequences.
|
|
Value* v = Shl(ZeroExtend(LoadContext(offsetof(PPCContext, cr0) + (4 * n) + 0,
|
|
INT8_TYPE),
|
|
INT64_TYPE),
|
|
4 * (7 - n) + 3);
|
|
v = Or(v, Shl(ZeroExtend(LoadContext(offsetof(PPCContext, cr0) + (4 * n) + 1,
|
|
INT8_TYPE),
|
|
INT64_TYPE),
|
|
4 * (7 - n) + 2));
|
|
v = Or(v, Shl(ZeroExtend(LoadContext(offsetof(PPCContext, cr0) + (4 * n) + 2,
|
|
INT8_TYPE),
|
|
INT64_TYPE),
|
|
4 * (7 - n) + 1));
|
|
v = Or(v, Shl(ZeroExtend(LoadContext(offsetof(PPCContext, cr0) + (4 * n) + 3,
|
|
INT8_TYPE),
|
|
INT64_TYPE),
|
|
4 * (7 - n) + 0));
|
|
return v;
|
|
}
|
|
|
|
Value* PPCHIRBuilder::LoadCRField(uint32_t n, uint32_t bit) {
|
|
return LoadContext(offsetof(PPCContext, cr0) + (4 * n) + bit, INT8_TYPE);
|
|
}
|
|
|
|
void PPCHIRBuilder::StoreCR(Value* value) {
|
|
// All bits. This is expensive, but seems to be less used than the
|
|
// field-specific StoreCR.
|
|
for (int i = 0; i <= 7; ++i) {
|
|
StoreCR(i, value);
|
|
}
|
|
}
|
|
|
|
void PPCHIRBuilder::StoreCR(uint32_t n, Value* value) {
|
|
// Pull out the bits we are interested in.
|
|
// Optimization passes will kill any unneeded stores (mostly).
|
|
StoreContext(offsetof(PPCContext, cr0) + (4 * n) + 0,
|
|
And(Truncate(Shr(value, 4 * (7 - n) + 3), INT8_TYPE),
|
|
LoadConstantUint8(1)));
|
|
StoreContext(offsetof(PPCContext, cr0) + (4 * n) + 1,
|
|
And(Truncate(Shr(value, 4 * (7 - n) + 2), INT8_TYPE),
|
|
LoadConstantUint8(1)));
|
|
StoreContext(offsetof(PPCContext, cr0) + (4 * n) + 2,
|
|
And(Truncate(Shr(value, 4 * (7 - n) + 1), INT8_TYPE),
|
|
LoadConstantUint8(1)));
|
|
StoreContext(offsetof(PPCContext, cr0) + (4 * n) + 3,
|
|
And(Truncate(Shr(value, 4 * (7 - n) + 0), INT8_TYPE),
|
|
LoadConstantUint8(1)));
|
|
}
|
|
|
|
void PPCHIRBuilder::StoreCRField(uint32_t n, uint32_t bit, Value* value) {
|
|
StoreContext(offsetof(PPCContext, cr0) + (4 * n) + bit, value);
|
|
|
|
// TODO(benvanik): trace CR.
|
|
}
|
|
|
|
void PPCHIRBuilder::UpdateCR(uint32_t n, Value* lhs, bool is_signed) {
|
|
UpdateCR(n, Truncate(lhs, INT32_TYPE), LoadZeroInt32(), is_signed);
|
|
}
|
|
|
|
void PPCHIRBuilder::UpdateCR(uint32_t n, Value* lhs, Value* rhs,
|
|
bool is_signed) {
|
|
if (is_signed) {
|
|
Value* lt = CompareSLT(lhs, rhs);
|
|
StoreContext(offsetof(PPCContext, cr0) + (4 * n) + 0, lt);
|
|
Value* gt = CompareSGT(lhs, rhs);
|
|
StoreContext(offsetof(PPCContext, cr0) + (4 * n) + 1, gt);
|
|
} else {
|
|
Value* lt = CompareULT(lhs, rhs);
|
|
StoreContext(offsetof(PPCContext, cr0) + (4 * n) + 0, lt);
|
|
Value* gt = CompareUGT(lhs, rhs);
|
|
StoreContext(offsetof(PPCContext, cr0) + (4 * n) + 1, gt);
|
|
}
|
|
Value* eq = CompareEQ(lhs, rhs);
|
|
StoreContext(offsetof(PPCContext, cr0) + (4 * n) + 2, eq);
|
|
|
|
// Value* so = AllocValue(UINT8_TYPE);
|
|
// StoreContext(offsetof(PPCContext, cr) + (4 * n) + 3, so);
|
|
|
|
// TOOD(benvanik): trace CR.
|
|
}
|
|
|
|
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?
|
|
StoreContext(offsetof(PPCContext, cr6.cr6_1), LoadZeroInt8());
|
|
StoreContext(offsetof(PPCContext, cr6.cr6_3), LoadZeroInt8());
|
|
StoreContext(offsetof(PPCContext, cr6.cr6_all_equal),
|
|
IsFalse(Not(src_value)));
|
|
StoreContext(offsetof(PPCContext, cr6.cr6_none_equal), IsFalse(src_value));
|
|
|
|
// TOOD(benvanik): trace CR.
|
|
}
|
|
|
|
Value* PPCHIRBuilder::LoadFPSCR() {
|
|
return LoadContext(offsetof(PPCContext, fpscr), INT32_TYPE);
|
|
}
|
|
|
|
void PPCHIRBuilder::StoreFPSCR(Value* value) {
|
|
assert_true(value->type == INT32_TYPE);
|
|
StoreContext(offsetof(PPCContext, fpscr), value);
|
|
|
|
auto& trace_reg = trace_info_.dests[trace_info_.dest_count++];
|
|
trace_reg.reg = 67;
|
|
trace_reg.value = value;
|
|
}
|
|
|
|
void PPCHIRBuilder::UpdateFPSCR(Value* result, bool update_cr1) {
|
|
// TODO(benvanik): detect overflow and nan cases.
|
|
// fx and vx are the most important.
|
|
Value* fx = LoadConstantInt8(0);
|
|
Value* fex = LoadConstantInt8(0);
|
|
Value* vx = LoadConstantInt8(0);
|
|
Value* ox = LoadConstantInt8(0);
|
|
|
|
if (update_cr1) {
|
|
// Store into the CR1 field.
|
|
// We do this instead of just calling CopyFPSCRToCR1 so that we don't
|
|
// have to read back the bits and do shifting work.
|
|
StoreContext(offsetof(PPCContext, cr1.cr1_fx), fx);
|
|
StoreContext(offsetof(PPCContext, cr1.cr1_fex), fex);
|
|
StoreContext(offsetof(PPCContext, cr1.cr1_vx), vx);
|
|
StoreContext(offsetof(PPCContext, cr1.cr1_ox), ox);
|
|
}
|
|
|
|
// Generate our new bits.
|
|
Value* new_bits = Shl(ZeroExtend(fx, INT32_TYPE), 31);
|
|
new_bits = Or(new_bits, Shl(ZeroExtend(fex, INT32_TYPE), 30));
|
|
new_bits = Or(new_bits, Shl(ZeroExtend(vx, INT32_TYPE), 29));
|
|
new_bits = Or(new_bits, Shl(ZeroExtend(ox, INT32_TYPE), 28));
|
|
|
|
// Mix into fpscr while preserving sticky bits (FX and OX).
|
|
Value* bits = LoadFPSCR();
|
|
bits = Or(And(bits, LoadConstantUint32(0x9FFFFFFF)), new_bits);
|
|
StoreFPSCR(bits);
|
|
}
|
|
|
|
void PPCHIRBuilder::CopyFPSCRToCR1() {
|
|
// Pull out of FPSCR.
|
|
Value* fpscr = LoadFPSCR();
|
|
StoreContext(offsetof(PPCContext, cr1.cr1_fx),
|
|
And(Truncate(Shr(fpscr, 31), INT8_TYPE), LoadConstantInt8(1)));
|
|
StoreContext(offsetof(PPCContext, cr1.cr1_fex),
|
|
And(Truncate(Shr(fpscr, 30), INT8_TYPE), LoadConstantInt8(1)));
|
|
StoreContext(offsetof(PPCContext, cr1.cr1_vx),
|
|
And(Truncate(Shr(fpscr, 29), INT8_TYPE), LoadConstantInt8(1)));
|
|
StoreContext(offsetof(PPCContext, cr1.cr1_ox),
|
|
And(Truncate(Shr(fpscr, 28), INT8_TYPE), LoadConstantInt8(1)));
|
|
}
|
|
|
|
Value* PPCHIRBuilder::LoadXER() {
|
|
Value* v = Shl(ZeroExtend(LoadCA(), INT64_TYPE), 29);
|
|
// TODO(benvanik): construct with other flags; overflow, etc?
|
|
return v;
|
|
}
|
|
|
|
void PPCHIRBuilder::StoreXER(Value* value) {
|
|
// TODO(benvanik): use other fields? For now, just pull out CA.
|
|
StoreCA(Truncate(And(Shr(value, 29), LoadConstantInt64(1)), INT8_TYPE));
|
|
}
|
|
|
|
Value* PPCHIRBuilder::LoadCA() {
|
|
return LoadContext(offsetof(PPCContext, xer_ca), INT8_TYPE);
|
|
}
|
|
|
|
void PPCHIRBuilder::StoreCA(Value* value) {
|
|
assert_true(value->type == INT8_TYPE);
|
|
StoreContext(offsetof(PPCContext, xer_ca), value);
|
|
|
|
auto& trace_reg = trace_info_.dests[trace_info_.dest_count++];
|
|
trace_reg.reg = 66;
|
|
trace_reg.value = value;
|
|
}
|
|
|
|
Value* PPCHIRBuilder::LoadSAT() {
|
|
return LoadContext(offsetof(PPCContext, vscr_sat), INT8_TYPE);
|
|
}
|
|
|
|
void PPCHIRBuilder::StoreSAT(Value* value) {
|
|
value = Truncate(value, INT8_TYPE);
|
|
StoreContext(offsetof(PPCContext, vscr_sat), value);
|
|
|
|
auto& trace_reg = trace_info_.dests[trace_info_.dest_count++];
|
|
trace_reg.reg = 44;
|
|
trace_reg.value = value;
|
|
}
|
|
|
|
Value* PPCHIRBuilder::LoadGPR(uint32_t reg) {
|
|
return LoadContext(offsetof(PPCContext, r) + reg * 8, INT64_TYPE);
|
|
}
|
|
|
|
void PPCHIRBuilder::StoreGPR(uint32_t reg, Value* value) {
|
|
assert_true(value->type == INT64_TYPE);
|
|
StoreContext(offsetof(PPCContext, r) + reg * 8, value);
|
|
|
|
auto& trace_reg = trace_info_.dests[trace_info_.dest_count++];
|
|
trace_reg.reg = reg;
|
|
trace_reg.value = value;
|
|
}
|
|
|
|
Value* PPCHIRBuilder::LoadFPR(uint32_t reg) {
|
|
return LoadContext(offsetof(PPCContext, f) + reg * 8, FLOAT64_TYPE);
|
|
}
|
|
|
|
void PPCHIRBuilder::StoreFPR(uint32_t reg, Value* value) {
|
|
assert_true(value->type == FLOAT64_TYPE);
|
|
StoreContext(offsetof(PPCContext, f) + reg * 8, value);
|
|
|
|
auto& trace_reg = trace_info_.dests[trace_info_.dest_count++];
|
|
trace_reg.reg = reg + 32;
|
|
trace_reg.value = value;
|
|
}
|
|
|
|
Value* PPCHIRBuilder::LoadVR(uint32_t reg) {
|
|
return LoadContext(offsetof(PPCContext, v) + reg * 16, VEC128_TYPE);
|
|
}
|
|
|
|
void PPCHIRBuilder::StoreVR(uint32_t reg, Value* value) {
|
|
assert_true(value->type == VEC128_TYPE);
|
|
StoreContext(offsetof(PPCContext, v) + reg * 16, value);
|
|
|
|
auto& trace_reg = trace_info_.dests[trace_info_.dest_count++];
|
|
trace_reg.reg = 128 + reg;
|
|
trace_reg.value = value;
|
|
}
|
|
|
|
void PPCHIRBuilder::StoreReserved(Value* val) {
|
|
assert_true(val->type == INT64_TYPE);
|
|
StoreContext(offsetof(PPCContext, reserved_val), val);
|
|
}
|
|
|
|
Value* PPCHIRBuilder::LoadReserved() {
|
|
return LoadContext(offsetof(PPCContext, reserved_val), INT64_TYPE);
|
|
}
|
|
|
|
} // namespace ppc
|
|
} // namespace cpu
|
|
} // namespace xe
|