/** ****************************************************************************** * 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 #include #include #include #include #include using namespace alloy; using namespace alloy::backend; using namespace alloy::backend::x64; using namespace alloy::hir; using namespace alloy::runtime; using namespace Xbyak; namespace alloy { namespace backend { namespace x64 { static const size_t MAX_CODE_SIZE = 1 * 1024 * 1024; } // namespace x64 } // namespace backend } // namespace alloy X64Emitter::X64Emitter(X64Backend* backend, XbyakAllocator* allocator) : backend_(backend), code_cache_(backend->code_cache()), allocator_(allocator), current_instr_(0), CodeGenerator(MAX_CODE_SIZE, AutoGrow, allocator) { xe_zero_struct(®_state_, sizeof(reg_state_)); } X64Emitter::~X64Emitter() { delete allocator_; } int X64Emitter::Initialize() { return 0; } int X64Emitter::Emit( HIRBuilder* builder, uint32_t debug_info_flags, runtime::DebugInfo* debug_info, void*& out_code_address, size_t& out_code_size) { // Reset. if (debug_info_flags & DEBUG_INFO_SOURCE_MAP) { source_map_count_ = 0; source_map_arena_.Reset(); } // Fill the generator with code. int result = Emit(builder); if (result) { return result; } // Copy the final code to the cache and relocate it. out_code_size = getSize(); out_code_address = Emplace(code_cache_); // Stash source map. if (debug_info_flags & DEBUG_INFO_SOURCE_MAP) { debug_info->InitializeSourceMap( source_map_count_, (SourceMapEntry*)source_map_arena_.CloneContents()); } return 0; } void* X64Emitter::Emplace(X64CodeCache* code_cache) { // To avoid changing xbyak, we do a switcharoo here. // top_ points to the Xbyak buffer, and since we are in AutoGrow mode // it has pending relocations. We copy the top_ to our buffer, swap the // pointer, relocate, then return the original scratch pointer for use. uint8_t* old_address = top_; void* new_address = code_cache->PlaceCode(top_, size_); top_ = (uint8_t*)new_address; ready(); top_ = old_address; reset(); return new_address; } int X64Emitter::Emit(HIRBuilder* builder) { // These are the registers we will not be using. All others are fare game. const uint32_t reserved_regs = GetRegBit(rax) | GetRegBit(rcx) | GetRegBit(rdx) | GetRegBit(rsp) | GetRegBit(rbp) | GetRegBit(rsi) | GetRegBit(rdi) | GetRegBit(xmm0) | // TODO(benvanik): save so that we can use these. GetRegBit(r8) | GetRegBit(r9) | GetRegBit(r10) | GetRegBit(r11); // Function prolog. // Must be 16b aligned. // Windows is very strict about the form of this and the epilog: // http://msdn.microsoft.com/en-us/library/tawsa7cb.aspx // TODO(benvanik): save off non-volatile registers so we can use them: // RBX, RBP, RDI, RSI, RSP, R12, R13, R14, R15 // Only want to do this if we actually use them, though, otherwise // it just adds overhead. // IMPORTANT: any changes to the prolog must be kept in sync with // X64CodeCache, which dynamically generates exception information. // Adding or changing anything here must be matched! const bool emit_prolog = true; const size_t stack_size = 64; if (emit_prolog) { mov(qword[rsp + 8], rcx); sub(rsp, stack_size); mov(qword[rsp + 8 * 0], rbx); mov(qword[rsp + 8 * 1], r12); mov(qword[rsp + 8 * 2], r13); mov(qword[rsp + 8 * 3], r14); mov(qword[rsp + 8 * 4], r15); } // membase stays in rdx. If we evict it (like on function calls) we // must put it back. mov(rdx, qword[rcx + 8]); auto lowering_table = backend_->lowering_table(); // Body. auto block = builder->first_block(); while (block) { // Mark block labels. auto label = block->label_head; while (label) { L(label->name); label = label->next; } // Reset reg allocation state. // If we start keeping regs across blocks this needs to change. // We mark a few active so that the allocator doesn't use them. reg_state_.active_regs = reg_state_.live_regs = reserved_regs; // Add instructions. // The table will process sequences of instructions to (try to) // generate optimal code. current_instr_ = block->instr_head; if (lowering_table->ProcessBlock(*this, block)) { return 1; } block = block->next; } // Function epilog. L("epilog"); if (emit_prolog) { mov(rbx, qword[rsp + 8 * 0]); mov(r12, qword[rsp + 8 * 1]); mov(r13, qword[rsp + 8 * 2]); mov(r14, qword[rsp + 8 * 3]); mov(r15, qword[rsp + 8 * 4]); add(rsp, stack_size); } ret(); #if XE_DEBUG nop(); nop(); nop(); nop(); nop(); #endif // XE_DEBUG return 0; } void X64Emitter::EvictStaleRegs() { // NOTE: if we are getting called it's because we *need* a register. // We must get rid of something. uint32_t current_ordinal = current_instr_->ordinal; // Remove any register with no more uses. uint32_t new_live_regs = 0; for (size_t n = 0; n < 32; n++) { uint32_t bit = 1 << n; if (bit & reg_state_.active_regs) { // Register is active and cannot be freed. new_live_regs |= bit; continue; } if (!(bit & reg_state_.live_regs)) { // Register is not alive - nothing to do. continue; } // Register is live, not active. Check and see if we get rid of it. auto v = reg_state_.reg_values[n]; if (v->last_use->ordinal < current_ordinal) { reg_state_.reg_values[n] = NULL; } } // Hrm. We have spilled. if (reg_state_.live_regs == new_live_regs) { XEASSERTALWAYS(); } reg_state_.live_regs = new_live_regs; } void X64Emitter::FindFreeRegs( Value* v0, uint32_t& v0_idx, uint32_t v0_flags) { // If the value is already in a register, use it. if (v0->reg != -1) { // Already in a register. Mark active and return. v0_idx = v0->reg; reg_state_.active_regs |= 1 << v0_idx; return; } uint32_t avail_regs = 0; if (IsIntType(v0->type)) { if (v0_flags & REG_ABCD) { avail_regs = B00001111; } else { avail_regs = 0xFFFF; } } else { avail_regs = 0xFFFF0000; } uint32_t free_regs = avail_regs & ~reg_state_.live_regs; if (!free_regs) { // Need to evict something. EvictStaleRegs(); free_regs = avail_regs & ~reg_state_.live_regs; XEASSERT(free_regs); } // Find the first available. // We start from the MSB so that we get the non-rNx regs that are often // in short supply. _BitScanReverse((DWORD*)&v0_idx, free_regs); reg_state_.active_regs |= 1 << v0_idx; reg_state_.live_regs |= 1 << v0_idx; v0->reg = v0_idx; reg_state_.reg_values[v0_idx] = v0; } void X64Emitter::FindFreeRegs( Value* v0, uint32_t& v0_idx, uint32_t v0_flags, Value* v1, uint32_t& v1_idx, uint32_t v1_flags) { // TODO(benvanik): support REG_DEST reuse/etc. // Grab all already-present registers first. // This way we won't spill them trying to get new registers. bool need_v0 = v0->reg == -1; bool need_v1 = v1->reg == -1; if (!need_v0) { FindFreeRegs(v0, v0_idx, v0_flags); } if (!need_v1) { FindFreeRegs(v1, v1_idx, v1_flags); } // Grab any registers we still need. These calls may evict. if (need_v0) { FindFreeRegs(v0, v0_idx, v0_flags); } if (need_v1) { FindFreeRegs(v1, v1_idx, v1_flags); } } void X64Emitter::FindFreeRegs( Value* v0, uint32_t& v0_idx, uint32_t v0_flags, Value* v1, uint32_t& v1_idx, uint32_t v1_flags, Value* v2, uint32_t& v2_idx, uint32_t v2_flags) { // TODO(benvanik): support REG_DEST reuse/etc. // Grab all already-present registers first. // This way we won't spill them trying to get new registers. bool need_v0 = v0->reg == -1; bool need_v1 = v1->reg == -1; bool need_v2 = v2->reg == -1; if (!need_v0) { FindFreeRegs(v0, v0_idx, v0_flags); } if (!need_v1) { FindFreeRegs(v1, v1_idx, v1_flags); } if (!need_v2) { FindFreeRegs(v2, v2_idx, v2_flags); } // Grab any registers we still need. These calls may evict. if (need_v0) { FindFreeRegs(v0, v0_idx, v0_flags); } if (need_v1) { FindFreeRegs(v1, v1_idx, v1_flags); } if (need_v2) { FindFreeRegs(v2, v2_idx, v2_flags); } } void X64Emitter::FindFreeRegs( Value* v0, uint32_t& v0_idx, uint32_t v0_flags, Value* v1, uint32_t& v1_idx, uint32_t v1_flags, Value* v2, uint32_t& v2_idx, uint32_t v2_flags, Value* v3, uint32_t& v3_idx, uint32_t v3_flags) { // TODO(benvanik): support REG_DEST reuse/etc. // Grab all already-present registers first. // This way we won't spill them trying to get new registers. bool need_v0 = v0->reg == -1; bool need_v1 = v1->reg == -1; bool need_v2 = v2->reg == -1; bool need_v3 = v3->reg == -1; if (!need_v0) { FindFreeRegs(v0, v0_idx, v0_flags); } if (!need_v1) { FindFreeRegs(v1, v1_idx, v1_flags); } if (!need_v2) { FindFreeRegs(v2, v2_idx, v2_flags); } if (!need_v3) { FindFreeRegs(v3, v3_idx, v3_flags); } // Grab any registers we still need. These calls may evict. if (need_v0) { FindFreeRegs(v0, v0_idx, v0_flags); } if (need_v1) { FindFreeRegs(v1, v1_idx, v1_flags); } if (need_v2) { FindFreeRegs(v2, v2_idx, v2_flags); } if (need_v3) { FindFreeRegs(v3, v3_idx, v3_flags); } } Instr* X64Emitter::Advance(Instr* i) { auto next = i->next; current_instr_ = next; return next; } void X64Emitter::MarkSourceOffset(Instr* i) { auto entry = source_map_arena_.Alloc(); entry->source_offset = i->src1.offset; entry->hir_offset = uint32_t(i->block->ordinal << 16) | i->ordinal; entry->code_offset = getSize(); source_map_count_++; }