/** ****************************************************************************** * 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/frontend/ppc_hir_builder.h" #include "xenia/base/byte_order.h" #include "xenia/base/logging.h" #include "xenia/base/memory.h" #include "xenia/cpu/cpu-private.h" #include "xenia/cpu/frontend/ppc_context.h" #include "xenia/cpu/frontend/ppc_disasm.h" #include "xenia/cpu/frontend/ppc_frontend.h" #include "xenia/cpu/frontend/ppc_instr.h" #include "xenia/cpu/hir/label.h" #include "xenia/cpu/processor.h" #include "xenia/profiling.h" namespace xe { namespace cpu { namespace frontend { // 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; PPCHIRBuilder::PPCHIRBuilder(PPCFrontend* frontend) : HIRBuilder(), frontend_(frontend), comment_buffer_(4096) {} PPCHIRBuilder::~PPCHIRBuilder() = default; void PPCHIRBuilder::Reset() { start_address_ = 0; instr_offset_list_ = NULL; label_list_ = NULL; with_debug_info_ = false; HIRBuilder::Reset(); } bool PPCHIRBuilder::Emit(FunctionInfo* symbol_info, uint32_t flags) { SCOPE_profile_cpu_f("cpu"); Memory* memory = frontend_->memory(); symbol_info_ = symbol_info; start_address_ = symbol_info->address(); instr_count_ = (symbol_info->end_address() - symbol_info->address()) / 4 + 1; with_debug_info_ = (flags & EMIT_DEBUG_COMMENTS) == EMIT_DEBUG_COMMENTS; if (with_debug_info_) { Comment("%s fn %.8X-%.8X %s", symbol_info->module()->name().c_str(), symbol_info->address(), symbol_info->end_address(), symbol_info->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 = symbol_info->address(); uint32_t end_address = symbol_info->end_address(); InstrData i; for (uint32_t address = start_address, offset = 0; address <= end_address; address += 4, offset++) { i.address = address; i.code = xe::load_and_swap(memory->TranslateVirtual(address)); // TODO(benvanik): find a way to avoid using the opcode tables. i.type = GetInstrType(i.code); trace_info_.dest_count = 0; // 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(); DisasmPPC(i, &comment_buffer_); Comment("%.8X %.8X %s", address, i.code, comment_buffer_.GetString()); first_instr = last_instr(); } // Mark source offset for debugging. // We could omit this if we never wanted to debug. SourceOffset(i.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 (!i.type) { XELOGE("Invalid instruction %.8llX %.8X", i.address, i.code); Comment("INVALID!"); // TraceInvalidInstruction(i); continue; } ++i.type->translation_count; typedef int (*InstrEmitter)(PPCHIRBuilder& f, InstrData& i); InstrEmitter emit = (InstrEmitter)i.type->emit; if (i.address == FLAGS_break_on_instruction) { Comment("--break-on-instruction target"); DebugBreak(); } if (!i.type->emit || emit(*this, i)) { XELOGE("Unimplemented instr %.8llX %.8X %s", i.address, i.code, i.type->name); Comment("UNIMPLEMENTED!"); // DebugBreak(); // TraceInvalidInstruction(i); } } 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)); } FunctionInfo* PPCHIRBuilder::LookupFunction(uint32_t address) { Processor* processor = frontend_->processor(); FunctionInfo* symbol_info; if (!processor->LookupFunctionInfo(address, &symbol_info)) { return nullptr; } return symbol_info; } 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), LoadConstant(uint8_t(1)))); StoreContext(offsetof(PPCContext, cr0) + (4 * n) + 1, And(Truncate(Shr(value, 4 * (7 - n) + 2), INT8_TYPE), LoadConstant(uint8_t(1)))); StoreContext(offsetof(PPCContext, cr0) + (4 * n) + 2, And(Truncate(Shr(value, 4 * (7 - n) + 1), INT8_TYPE), LoadConstant(uint8_t(1)))); StoreContext(offsetof(PPCContext, cr0) + (4 * n) + 3, And(Truncate(Shr(value, 4 * (7 - n) + 0), INT8_TYPE), LoadConstant(uint8_t(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), LoadZero(INT32_TYPE), 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), LoadZero(INT8_TYPE)); StoreContext(offsetof(PPCContext, cr6.cr6_3), LoadZero(INT8_TYPE)); 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::LoadMSR() { // bit 48 = EE; interrupt enabled // bit 62 = RI; recoverable interrupt // return 8000h if unlocked, else 0 CallExtern(frontend_->builtins()->check_global_lock); return LoadContext(offsetof(PPCContext, scratch), INT64_TYPE); } void PPCHIRBuilder::StoreMSR(Value* value) { // if & 0x8000 == 0, lock, else unlock StoreContext(offsetof(PPCContext, scratch), ZeroExtend(value, INT64_TYPE)); CallExtern(frontend_->builtins()->handle_global_lock); } Value* PPCHIRBuilder::LoadFPSCR() { return LoadContext(offsetof(PPCContext, fpscr), INT64_TYPE); } void PPCHIRBuilder::StoreFPSCR(Value* value) { assert_true(value->type == INT64_TYPE); StoreContext(offsetof(PPCContext, fpscr), value); auto& trace_reg = trace_info_.dests[trace_info_.dest_count++]; trace_reg.reg = 67; trace_reg.value = value; } Value* PPCHIRBuilder::LoadXER() { assert_always(); return NULL; } void PPCHIRBuilder::StoreXER(Value* value) { assert_always(); } 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; } Value* PPCHIRBuilder::LoadAcquire(Value* address, TypeName type, uint32_t load_flags) { AtomicExchange(LoadContext(offsetof(PPCContext, reserve_address), INT64_TYPE), Truncate(address, INT32_TYPE)); Value* value = Load(address, type, load_flags); // Save the value so that we can compare it later in StoreRelease. AtomicExchange(LoadContext(offsetof(PPCContext, reserve_value), INT64_TYPE), value); return value; } Value* PPCHIRBuilder::StoreRelease(Value* address, Value* value, uint32_t store_flags) { Value* old_address = AtomicExchange( LoadContext(offsetof(PPCContext, reserve_address), INT64_TYPE), LoadZero(INT32_TYPE)); // HACK: ensure the reservation addresses match AND the value hasn't changed. Value* old_value = AtomicExchange( LoadContext(offsetof(PPCContext, reserve_value), INT64_TYPE), LoadZero(value->type)); Value* current_value = Load(address, value->type); Value* eq = And(CompareEQ(Truncate(address, INT32_TYPE), old_address), CompareEQ(current_value, old_value)); StoreContext(offsetof(PPCContext, cr0.cr0_eq), eq); StoreContext(offsetof(PPCContext, cr0.cr0_lt), LoadZero(INT8_TYPE)); StoreContext(offsetof(PPCContext, cr0.cr0_gt), LoadZero(INT8_TYPE)); auto skip_label = NewLabel(); BranchFalse(eq, skip_label, BRANCH_UNLIKELY); Store(address, value, store_flags); MarkLabel(skip_label); return eq; } } // namespace frontend } // namespace cpu } // namespace xe