/** ****************************************************************************** * 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 using namespace xe::cpu::ppc; using namespace xe::cpu::sdb; using namespace xe::cpu::x64; using namespace AsmJit; DEFINE_bool(memory_address_verification, false, "Whether to add additional checks to generated memory load/stores."); DEFINE_bool(cache_registers, false, "Cache PPC registers inside of functions."); DEFINE_bool(log_codegen, false, "Log codegen to stdout."); DEFINE_bool(annotate_disassembly, true, "Annotate disassembled x64 code with comments."); /** * This generates function code. * One context is created and shared for each function to generate. * Each basic block in the function is created and stashed in one pass, then * filled in the next. * * This context object is a stateful representation of the current machine state * and all accessors to registers should occur through it. By doing so it's * possible to exploit the SSA nature of LLVM to reuse register values within * a function without needing to flush to memory. * * Function calls (any branch outside of the function) will result in an * expensive flush of registers. * * TODO(benvanik): track arguments by looking for register reads without writes * TODO(benvanik): avoid flushing registers for leaf nodes * TODO(benvnaik): pass return value in LLVM return, not by memory */ X64Emitter::X64Emitter(xe_memory_ref memory) : memory_(memory), logger_(NULL) { // I don't like doing this, but there's no public access to these members. assembler_._properties = compiler_._properties; // Grab global exports. cpu::GetGlobalExports(&global_exports_); // Lock used for all codegen. // It'd be nice to have multiple emitters/etc that could be used to prevent // contention of the JIT. lock_ = xe_mutex_alloc(10000); XEASSERTNOTNULL(lock_); // Setup logging. if (FLAGS_log_codegen) { logger_ = new FileLogger(stdout); logger_->setEnabled(true); assembler_.setLogger(logger_); compiler_.setLogger(logger_); } } X64Emitter::~X64Emitter() { Lock(); assembler_.clear(); compiler_.clear(); delete logger_; Unlock(); xe_mutex_free(lock_); lock_ = NULL; } void X64Emitter::Lock() { xe_mutex_lock(lock_); } void X64Emitter::Unlock() { xe_mutex_unlock(lock_); } int X64Emitter::PrepareFunction(FunctionSymbol* symbol) { int result_code = 1; Lock(); if (symbol->impl_value) { result_code = 0; XESUCCEED(); } // Create the custom redirector function. // This function will jump to the on-demand compilation routine to // generate the real function as required. Afterwards, it will be // overwritten with a jump to the new function. if (logger_) { logger_->setEnabled(false); } // PrepareFunction: // ; mov rcx, ppc_state -- comes in as arg // ; mov rdx, lr -- comes in as arg // mov r8, [emitter] // mov r9, [symbol] // call [OnDemandCompileTrampoline] // jmp [rax] #if defined(ASMJIT_WINDOWS) // Calling convetion: kX86FuncConvX64W // Arguments passed as RCX, RDX, R8, R9 assembler_.push(rcx); // ppc_state assembler_.push(rdx); // lr assembler_.sub(rsp, imm(0x20)); assembler_.mov(rcx, imm((uint64_t)this)); assembler_.mov(rdx, imm((uint64_t)symbol)); assembler_.call(X64Emitter::OnDemandCompileTrampoline); assembler_.add(rsp, imm(0x20)); assembler_.pop(rdx); // lr assembler_.pop(rcx); // ppc_state assembler_.jmp(rax); #else // Calling convetion: kX86FuncConvX64U // Arguments passed as RDI, RSI, RDX, RCX, R8, R9 assembler_.push(rdi); // ppc_state assembler_.push(rsi); // lr assembler_.sub(rsp, imm(0x20)); assembler_.mov(rdi, imm((uint64_t)this)); assembler_.mov(rsi, imm((uint64_t)symbol)); assembler_.call(X64Emitter::OnDemandCompileTrampoline); assembler_.add(rsp, imm(0x20)); assembler_.pop(rsi); // lr assembler_.pop(rdi); // ppc_state assembler_.jmp(rax); #endif // ASM_JIT_WINDOWS // Assemble and stash. void* fn_ptr = assembler_.make(); symbol->impl_value = fn_ptr; symbol->impl_size = assembler_.getCodeSize(); result_code = 0; XECLEANUP: assembler_.clear(); if (logger_) { logger_->setEnabled(true); } Unlock(); return result_code; } void* X64Emitter::OnDemandCompileTrampoline( X64Emitter* emitter, FunctionSymbol* symbol) { // This function is called by the redirector code from PrepareFunction. // We jump into the member OnDemandCompile and pass back the // result. return emitter->OnDemandCompile(symbol); } void* X64Emitter::OnDemandCompile(FunctionSymbol* symbol) { void* redirector_ptr = symbol->impl_value; size_t redirector_size = symbol->impl_size; // Generate the real function. int result_code = MakeFunction(symbol); if (result_code) { // Failed to make the function! We're hosed! // We'll likely crash now. XELOGCPU("Compile(%s): failed to make function", symbol->name()); XEASSERTALWAYS(); return 0; } // TODO(benvanik): find a way to patch in that is thread safe? // Overwrite the redirector function to jump to the new one. // This preserves the arguments passed to the redirector. uint8_t* bp = (uint8_t*)redirector_ptr; size_t o = 0; uint64_t target_ptr = (uint64_t)symbol->impl_value; if (target_ptr & ~0xFFFFFFFFLL) { // mov rax, imm64 bp[o++] = 0x48; bp[o++] = 0xB8; for (int n = 0; n < 8; n++) { bp[o++] = (target_ptr >> (n * 8)) & 0xFF; } } else { // mov rax, imm32 bp[o++] = 0x48; bp[o++] = 0xC7; bp[o++] = 0xC0; for (int n = 0; n < 4; n++) { bp[o++] = (target_ptr >> (n * 8)) & 0xFF; } } // jmp rax bp[o++] = 0xFF; bp[o++] = 0xE0; // Write some no-ops to cover up the rest of the redirection. // NOTE: not currently doing this as we overwrite the code that // got us here and endup just running nops. // while (o < redirector_size) { // bp[o++] = 0x90; // } return symbol->impl_value; } int X64Emitter::MakeFunction(FunctionSymbol* symbol) { X86Compiler& c = compiler_; int result_code = 1; Lock(); XELOGCPU("Compile(%s): beginning compilation...", symbol->name()); symbol_ = symbol; fn_block_ = NULL; return_block_ = Label(); internal_indirection_block_ = Label(); external_indirection_block_ = Label(); bbs_.clear(); access_bits_.Clear(); locals_.indirection_target = GpVar(); locals_.indirection_cia = GpVar(); locals_.xer = GpVar(); locals_.lr = GpVar(); locals_.ctr = GpVar(); for (size_t n = 0; n < XECOUNT(locals_.cr); n++) { locals_.cr[n] = GpVar(); } for (size_t n = 0; n < XECOUNT(locals_.gpr); n++) { locals_.gpr[n] = GpVar(); } for (size_t n = 0; n < XECOUNT(locals_.fpr); n++) { locals_.fpr[n] = GpVar(); } // Setup function. All share the same signature. compiler_.newFunc(kX86FuncConvDefault, FuncBuilder2()); // Elevate the priority of ppc_state, as it's often used. // TODO(benvanik): evaluate if this is a good idea. compiler_.setPriority(compiler_.getGpArg(0), 100); switch (symbol->type) { case FunctionSymbol::Kernel: if (symbol->kernel_export && symbol->kernel_export->is_implemented) { result_code = MakePresentImportFunction(); } else { result_code = MakeMissingImportFunction(); } break; case FunctionSymbol::User: result_code = MakeUserFunction(); break; default: XEASSERTALWAYS(); result_code = 1; break; } XEEXPECTZERO(result_code); compiler_.endFunc(); // Serialize to the assembler. compiler_.serialize(assembler_); if (compiler_.getError()) { result_code = 2; } else if (assembler_.getError()) { result_code = 3; } XEEXPECTZERO(result_code); // Perform final assembly/relocation. symbol->impl_value = assembler_.make(); if (FLAGS_log_codegen) { XELOGCPU("Compile(%s): compiled to 0x%p (%db)", symbol->name(), assembler_.getCode(), assembler_.getCodeSize()); // Dump x64 assembly. // This is not currently used as we are dumping from asmjit. // This format is more concise and prettier (though it lacks comments). #if 0 DISASM MyDisasm; memset(&MyDisasm, 0, sizeof(MyDisasm)); MyDisasm.Archi = 64; MyDisasm.Options = Tabulation + MasmSyntax + PrefixedNumeral; MyDisasm.EIP = (UIntPtr)assembler_.getCode(); void* eip_end = assembler_.getCode() + assembler_.getCodeSize(); while (MyDisasm.EIP < (UIntPtr)eip_end) { size_t len = Disasm(&MyDisasm); if (len == UNKNOWN_OPCODE) { break; } XELOGCPU("%p %s", MyDisasm.EIP, MyDisasm.CompleteInstr); MyDisasm.EIP += len; } #endif } result_code = 0; XECLEANUP: // Cleanup assembler/compiler. We keep them around to reuse their buffers. assembler_.clear(); compiler_.clear(); Unlock(); return result_code; } int X64Emitter::MakePresentImportFunction() { X86Compiler& c = compiler_; TraceKernelCall(); void* shim = symbol_->kernel_export->function_data.shim; void* shim_data = symbol_->kernel_export->function_data.shim_data; // void shim(ppc_state*, shim_data*) // TODO(benvanik): remove once fixed: https://code.google.com/p/asmjit/issues/detail?id=86 GpVar arg1 = c.newGpVar(kX86VarTypeGpq); c.mov(arg1, imm((uint64_t)shim_data)); X86CompilerFuncCall* call = c.call(shim); call->setComment(symbol_->kernel_export->name); call->setPrototype(kX86FuncConvDefault, FuncBuilder2()); call->setArgument(0, c.getGpArg(0)); call->setArgument(1, arg1); c.ret(); return 0; } int X64Emitter::MakeMissingImportFunction() { X86Compiler& c = compiler_; TraceKernelCall(); // TODO(benvanik): log better? c.ret(); return 0; } int X64Emitter::MakeUserFunction() { X86Compiler& c = compiler_; TraceUserCall(); // If this function is empty, abort! if (!symbol_->blocks.size()) { c.ret(); return 0; } // Pass 1 creates all of the labels - this way we can branch to them. // We also track registers used so that when know which ones to fill/spill. // No actual blocks or instructions are created here. // TODO(benvanik): move this to SDB? would remove an entire pass over the // code. for (std::map::iterator it = symbol_->blocks.begin(); it != symbol_->blocks.end(); ++it) { FunctionBlock* block = it->second; XEIGNORE(PrepareBasicBlock(block)); } // Setup all local variables now that we know what we need. // This happens in the entry block. SetupLocals(); // Setup initial register fill in the entry block. // We can only do this once all the locals have been created. FillRegisters(); // Pass 2 fills in instructions. for (std::map::iterator it = symbol_->blocks.begin(); it != symbol_->blocks.end(); ++it) { FunctionBlock* block = it->second; GenerateBasicBlock(block); } // Setup the shared return/indirection/etc blocks now that we know all the // blocks we need and all the registers used. GenerateSharedBlocks(); return 0; } X86Compiler& X64Emitter::compiler() { return compiler_; } FunctionSymbol* X64Emitter::symbol() { return symbol_; } FunctionBlock* X64Emitter::fn_block() { return fn_block_; } void X64Emitter::GenerateSharedBlocks() { X86Compiler& c = compiler_; // Create a return block, if it was used. if (return_block_.getId() != kInvalidValue) { // This spills registers and returns. All non-tail returns should branch // here to do the return and ensure registers are spilled. // This will be moved to the end after all the other blocks are created. if (FLAGS_annotate_disassembly) { c.comment("Shared return block"); } c.bind(return_block_); SpillRegisters(); c.ret(); } // Build indirection block on demand. // We have already prepped all basic blocks, so we can build these tables now. if (external_indirection_block_.getId() != kInvalidValue) { // This will spill registers and call the external function. // It is only meant for LK=0. c.bind(external_indirection_block_); if (FLAGS_annotate_disassembly) { c.comment("Shared external indirection block"); } SpillRegisters(); X86CompilerFuncCall* call = c.call(global_exports_.XeIndirectBranch); call->setPrototype(kX86FuncConvDefault, FuncBuilder3()); call->setArgument(0, c.getGpArg(0)); call->setArgument(1, locals_.indirection_target); call->setArgument(2, locals_.indirection_cia); GpVar target_ptr(c.newGpVar()); call->setReturn(target_ptr); // Call target. // void fn(ppc_state*, uint64_t) call = c.call(target_ptr); call->setComment("Indirection branch"); call->setPrototype(kX86FuncConvDefault, FuncBuilder2()); call->setArgument(0, c.getGpArg(0)); call->setArgument(1, locals_.indirection_cia); c.ret(); } if (internal_indirection_block_.getId() != kInvalidValue) { // This will not spill registers and instead try to switch on local blocks. // If it fails then the external indirection path is taken. // NOTE: we only generate this if a likely local branch is taken. c.bind(internal_indirection_block_); if (FLAGS_annotate_disassembly) { c.comment("Shared internal indirection block"); } c.int3(); // SwitchInst* switch_i = b.CreateSwitch( // b.CreateLoad(locals_.indirection_target), // external_indirection_block_, // static_cast(bbs_.size())); // for (std::map::iterator it = bbs_.begin(); // it != bbs_.end(); ++it) { // switch_i->addCase(b.getInt64(it->first), it->second); // } } } int X64Emitter::PrepareBasicBlock(FunctionBlock* block) { X86Compiler& c = compiler_; // Add an undefined entry in the table. // The label will be created on-demand. bbs_.insert(std::pair(block->start_address, c.newLabel())); // Scan and disassemble each instruction in the block to get accurate // register access bits. In the future we could do other optimization checks // in this pass. // TODO(benvanik): perhaps we want to stash this for each basic block? // We could use this for faster checking of cr/ca checks/etc. InstrAccessBits access_bits; uint8_t* p = xe_memory_addr(memory_, 0); for (uint32_t ia = block->start_address; ia <= block->end_address; ia += 4) { InstrData i; i.address = ia; i.code = XEGETUINT32BE(p + ia); i.type = ppc::GetInstrType(i.code); // Ignore unknown or ones with no disassembler fn. if (!i.type || !i.type->disassemble) { continue; } // We really need to know the registers modified, so die if we've been lazy // and haven't implemented the disassemble method yet. ppc::InstrDisasm d; XEASSERTNOTNULL(i.type->disassemble); int result_code = i.type->disassemble(i, d); XEASSERTZERO(result_code); if (result_code) { return result_code; } // Accumulate access bits. access_bits.Extend(d.access_bits); } // Add in access bits to function access bits. access_bits_.Extend(access_bits); return 0; } void X64Emitter::GenerateBasicBlock(FunctionBlock* block) { X86Compiler& c = compiler_; // Create new block. fn_block_ = block; if (FLAGS_log_codegen) { printf(" bb %.8X-%.8X:\n", block->start_address, block->end_address); } if (FLAGS_annotate_disassembly) { c.comment("bb %.8X - %.8X", block->start_address, block->end_address); } // This will create a label if it hasn't already been done. std::map::iterator label_it = bbs_.find(block->start_address); XEASSERT(label_it != bbs_.end()); c.bind(label_it->second); // Walk instructions in block. uint8_t* p = xe_memory_addr(memory_, 0); for (uint32_t ia = block->start_address; ia <= block->end_address; ia += 4) { InstrData i; i.address = ia; i.code = XEGETUINT32BE(p + ia); i.type = ppc::GetInstrType(i.code); // Add debugging tag. // TODO(benvanik): add debugging info? if (FLAGS_log_codegen || FLAGS_annotate_disassembly) { if (!i.type) { if (FLAGS_log_codegen) { printf("%.8X: %.8X ???", ia, i.code); } if (FLAGS_annotate_disassembly) { c.comment("%.8X: %.8X ???", ia, i.code); } } else if (i.type->disassemble) { ppc::InstrDisasm d; i.type->disassemble(i, d); std::string disasm; d.Dump(disasm); if (FLAGS_log_codegen) { printf(" %.8X: %.8X %s\n", ia, i.code, disasm.c_str()); } if (FLAGS_annotate_disassembly) { c.comment("%.8X: %.8X %s", ia, i.code, disasm.c_str()); } } else { if (FLAGS_log_codegen) { printf(" %.8X: %.8X %s ???\n", ia, i.code, i.type->name); } if (FLAGS_annotate_disassembly) { c.comment("%.8X: %.8X %s ???", ia, i.code, i.type->name); } } } TraceInstruction(i); if (!i.type) { XELOGCPU("Invalid instruction %.8X %.8X", ia, i.code); TraceInvalidInstruction(i); continue; } typedef int (*InstrEmitter)(X64Emitter& g, X86Compiler& c, InstrData& i); InstrEmitter emit = (InstrEmitter)i.type->emit; if (!i.type->emit || emit(*this, compiler_, i)) { // This printf is handy for sort/uniquify to find instructions. printf("unimplinstr %s\n", i.type->name); XELOGCPU("Unimplemented instr %.8X %.8X %s", ia, i.code, i.type->name); TraceInvalidInstruction(i); } } // If we fall through, create the branch. if (block->outgoing_type == FunctionBlock::kTargetNone) { // BasicBlock* next_bb = GetNextBasicBlock(); // XEASSERTNOTNULL(next_bb); // b.CreateBr(next_bb); } else if (block->outgoing_type == FunctionBlock::kTargetUnknown) { // Hrm. // TODO(benvanik): assert this doesn't occur - means a bad sdb run! XELOGCPU("SDB function scan error in %.8X: bb %.8X has unknown exit", symbol_->start_address, block->start_address); c.ret(); } // TODO(benvanik): finish up BB } Label& X64Emitter::GetReturnLabel() { X86Compiler& c = compiler_; // Implicit creation on first use. if (return_block_.getId() == kInvalidValue) { return_block_ = c.newLabel(); } return return_block_; } Label& X64Emitter::GetBlockLabel(uint32_t address) { std::map::iterator it = bbs_.find(address); return it->second; } int X64Emitter::CallFunction(FunctionSymbol* target_symbol, GpVar& lr, bool tail) { X86Compiler& c = compiler_; // Prep the target function. // If the target function was small we could try to make the whole thing now. PrepareFunction(target_symbol); SpillRegisters(); uint64_t target_ptr = (uint64_t)target_symbol->impl_value; XEASSERTNOTNULL(target_ptr); #if 0 if (tail) { // Tail calls are just jumps. #if defined(ASMJIT_WINDOWS) // Calling convetion: kX86FuncConvX64W // Arguments passed as RCX, RDX, R8, R9 c.alloc(c.getGpArg(0), rcx); c.alloc(lr, rdx); // TODO(benvanik): just use jmp when fixed: https://code.google.com/p/asmjit/issues/detail?id=67 // c.jmp(target_ptr); #else // Calling convetion: kX86FuncConvX64U // Arguments passed as RDI, RSI, RDX, RCX, R8, R9 c.alloc(c.getGpArg(0), rdi); c.alloc(lr, rsi); // TODO(benvanik): just use jmp when fixed: https://code.google.com/p/asmjit/issues/detail?id=67 // c.jmp(target_ptr); #endif // ASMJIT_WINDOWS } else { #else { #endif // tail call disable // void fn(ppc_state*, uint64_t) X86CompilerFuncCall* call = c.call(target_ptr); call->setComment(target_symbol->name()); call->setPrototype(kX86FuncConvDefault, FuncBuilder2()); call->setArgument(0, c.getGpArg(0)); call->setArgument(1, lr); if (tail) { c.ret(); } } return 0; } void X64Emitter::TraceKernelCall() { X86Compiler& c = compiler_; if (!FLAGS_trace_kernel_calls) { return; } for (int n = 0; n < 5; n++) { c.nop(); } if (FLAGS_annotate_disassembly) { c.comment("XeTraceKernelCall (+spill)"); } SpillRegisters(); // TODO(benvanik): remove once fixed: https://code.google.com/p/asmjit/issues/detail?id=86 GpVar arg1 = c.newGpVar(kX86VarTypeGpq); c.mov(arg1, imm((uint64_t)symbol_->start_address)); GpVar arg3 = c.newGpVar(kX86VarTypeGpq); c.mov(arg3, imm((uint64_t)symbol_->kernel_export)); X86CompilerFuncCall* call = c.call(global_exports_.XeTraceKernelCall); call->setPrototype(kX86FuncConvDefault, FuncBuilder4()); call->setArgument(0, c.getGpArg(0)); call->setArgument(1, arg1); call->setArgument(2, c.getGpArg(1)); call->setArgument(3, arg3); for (int n = 0; n < 2; n++) { c.nop(); } } void X64Emitter::TraceUserCall() { X86Compiler& c = compiler_; if (!FLAGS_trace_user_calls) { return; } for (int n = 0; n < 5; n++) { c.nop(); } if (FLAGS_annotate_disassembly) { c.comment("XeTraceUserCall (+spill)"); } SpillRegisters(); // TODO(benvanik): remove once fixed: https://code.google.com/p/asmjit/issues/detail?id=86 GpVar arg1 = c.newGpVar(kX86VarTypeGpq); c.mov(arg1, imm((uint64_t)symbol_->start_address)); GpVar arg3 = c.newGpVar(kX86VarTypeGpq); c.mov(arg3, imm((uint64_t)symbol_)); X86CompilerFuncCall* call = c.call(global_exports_.XeTraceUserCall); call->setPrototype(kX86FuncConvDefault, FuncBuilder4()); call->setArgument(0, c.getGpArg(0)); call->setArgument(1, arg1); call->setArgument(2, c.getGpArg(1)); call->setArgument(3, arg3); for (int n = 0; n < 2; n++) { c.nop(); } } void X64Emitter::TraceInstruction(InstrData& i) { X86Compiler& c = compiler_; if (!FLAGS_trace_instructions) { return; } for (int n = 0; n < 5; n++) { c.nop(); } if (FLAGS_annotate_disassembly) { c.comment("XeTraceInstruction (+spill)"); } SpillRegisters(); // TODO(benvanik): remove once fixed: https://code.google.com/p/asmjit/issues/detail?id=86 GpVar arg1 = c.newGpVar(kX86VarTypeGpq); c.mov(arg1, imm((uint64_t)i.address)); GpVar arg2 = c.newGpVar(kX86VarTypeGpq); c.mov(arg2, imm((uint64_t)i.code)); X86CompilerFuncCall* call = c.call(global_exports_.XeTraceInstruction); call->setPrototype(kX86FuncConvDefault, FuncBuilder3()); call->setArgument(0, c.getGpArg(0)); call->setArgument(1, arg1); call->setArgument(2, arg2); for (int n = 0; n < 2; n++) { c.nop(); } } void X64Emitter::TraceInvalidInstruction(InstrData& i) { X86Compiler& c = compiler_; #if 0 if (FLAGS_annotate_disassembly) { c.comment("XeInvalidInstruction (+spill)"); } for (int n = 0; n < 5; n++) { c.nop(); } SpillRegisters(); // TODO(benvanik): remove once fixed: https://code.google.com/p/asmjit/issues/detail?id=86 GpVar arg1 = c.newGpVar(kX86VarTypeGpq); c.mov(arg1, imm((uint64_t)i.address)); GpVar arg2 = c.newGpVar(kX86VarTypeGpq); c.mov(arg2, imm((uint64_t)i.code)); X86CompilerFuncCall* call = c.call(global_exports_.XeInvalidInstruction); call->setPrototype(kX86FuncConvDefault, FuncBuilder3()); call->setArgument(0, c.getGpArg(0)); call->setArgument(1, arg1); call->setArgument(2, arg2); for (int n = 0; n < 5; n++) { c.nop(); } #endif } void X64Emitter::TraceBranch(uint32_t cia) { X86Compiler& c = compiler_; if (!FLAGS_trace_branches) { return; } for (int n = 0; n < 5; n++) { c.nop(); } if (FLAGS_annotate_disassembly) { c.comment("XeTraceBranch (+spill)"); } SpillRegisters(); // Pick target. If it's an indirection the tracing function will handle it. uint64_t target = 0; switch (fn_block_->outgoing_type) { case FunctionBlock::kTargetBlock: target = fn_block_->outgoing_address; break; case FunctionBlock::kTargetFunction: target = fn_block_->outgoing_function->start_address; break; case FunctionBlock::kTargetLR: target = kXEPPCRegLR; break; case FunctionBlock::kTargetCTR: target = kXEPPCRegCTR; break; default: case FunctionBlock::kTargetNone: XEASSERTALWAYS(); break; } // TODO(benvanik): remove once fixed: https://code.google.com/p/asmjit/issues/detail?id=86 GpVar arg1 = c.newGpVar(kX86VarTypeGpq); c.mov(arg1, imm((uint64_t)cia)); GpVar arg2 = c.newGpVar(kX86VarTypeGpq); c.mov(arg2, imm((uint64_t)target)); X86CompilerFuncCall* call = c.call(global_exports_.XeTraceBranch); call->setComment("XeTraceBranch"); call->setPrototype(kX86FuncConvDefault, FuncBuilder3()); call->setArgument(0, c.getGpArg(0)); call->setArgument(1, arg1); call->setArgument(2, arg2); for (int n = 0; n < 2; n++) { c.nop(); } } int X64Emitter::GenerateIndirectionBranch(uint32_t cia, GpVar& target, bool lk, bool likely_local) { X86Compiler& c = compiler_; // This function is called by the control emitters when they know that an // indirect branch is required. // It first tries to see if the branch is to an address within the function // and, if so, uses a local switch table. If that fails because we don't know // the block the function is regenerated (ACK!). If the target is external // then an external call occurs. // Request builds of the indirection blocks on demand. // We can't build here because we don't know what registers will be needed // yet, so we just create the blocks and let GenerateSharedBlocks handle it // after we are done with all user instructions. if ((likely_local || !lk) && external_indirection_block_.getId() == kInvalidValue) { external_indirection_block_ = c.newLabel(); } if (likely_local && internal_indirection_block_.getId() == kInvalidValue) { internal_indirection_block_ = c.newLabel(); } if ((internal_indirection_block_.getId() != kInvalidValue || external_indirection_block_.getId() != kInvalidValue) && (locals_.indirection_cia.getId() == kInvalidValue)) { locals_.indirection_target = c.newGpVar(); locals_.indirection_cia = c.newGpVar(); } // Check to see if the target address is within the function. // If it is jump to that basic block. If the basic block is not found it means // we have a jump inside the function that wasn't identified via static // analysis. These are bad as they require function regeneration. if (likely_local) { // Note that we only support LK=0, as we are using shared tables. XEASSERT(!lk); c.mov(locals_.indirection_target, target); c.mov(locals_.indirection_cia, imm(cia)); // if (target >= start && target < end) jmp internal_indirection_block; // else jmp external_indirection_block; GpVar in_range(c.newGpVar()); c.cmp(target, imm(symbol_->start_address)); c.setge(in_range.r8Lo()); c.jl(external_indirection_block_, kCondHintLikely); c.cmp(target, imm(symbol_->end_address)); c.jge(external_indirection_block_, kCondHintLikely); c.jmp(internal_indirection_block_); return 0; } // If we are LK=0 jump to the shared indirection block. This prevents us // from needing to fill the registers again after the call and shares more // code. if (!lk) { c.mov(locals_.indirection_target, target); c.mov(locals_.indirection_cia, imm(cia)); c.jmp(external_indirection_block_); } else { // Slowest path - spill, call the external function, and fill. // We should avoid this at all costs. // Spill registers. We could probably share this. SpillRegisters(); // Grab the target of the indirection. // TODO(benvanik): remove once fixed: https://code.google.com/p/asmjit/issues/detail?id=86 GpVar arg2 = c.newGpVar(kX86VarTypeGpq); c.mov(arg2, imm(cia)); X86CompilerFuncCall* call = c.call(global_exports_.XeIndirectBranch); call->setPrototype(kX86FuncConvDefault, FuncBuilder3()); call->setArgument(0, c.getGpArg(0)); call->setArgument(1, target); call->setArgument(2, arg2); GpVar target_ptr(c.newGpVar()); call->setReturn(target_ptr); // Call target. // void fn(ppc_state*, uint64_t) call = c.call(target_ptr); call->setComment("Indirection branch"); call->setPrototype(kX86FuncConvDefault, FuncBuilder2()); call->setArgument(0, c.getGpArg(0)); call->setArgument(1, arg2); // TODO(benvanik): next_block/is_last_block/etc //if (next_block) { // Only refill if not a tail call. FillRegisters(); //} } return 0; } void X64Emitter::SetupLocals() { X86Compiler& c = compiler_; if (!FLAGS_cache_registers) { return; } uint64_t spr_t = access_bits_.spr; if (spr_t & 0x3) { locals_.xer = c.newGpVar(kX86VarTypeGpq, "xer"); } spr_t >>= 2; if (spr_t & 0x3) { locals_.lr = c.newGpVar(kX86VarTypeGpq, "lr"); } spr_t >>= 2; if (spr_t & 0x3) { locals_.ctr = c.newGpVar(kX86VarTypeGpq, "ctr"); } spr_t >>= 2; // TODO: FPCSR char name[8]; uint64_t cr_t = access_bits_.cr; for (int n = 0; n < 8; n++) { if (cr_t & 3) { xesnprintfa(name, XECOUNT(name), "cr%d", n); locals_.cr[n] = c.newGpVar(kX86VarTypeGpd, name); } cr_t >>= 2; } uint64_t gpr_t = access_bits_.gpr; for (int n = 0; n < 32; n++) { if (gpr_t & 3) { xesnprintfa(name, XECOUNT(name), "r%d", n); locals_.gpr[n] = c.newGpVar(kX86VarTypeGpq, name); } gpr_t >>= 2; } uint64_t fpr_t = access_bits_.fpr; for (int n = 0; n < 32; n++) { if (fpr_t & 3) { xesnprintfa(name, XECOUNT(name), "f%d", n); locals_.fpr[n] = c.newGpVar(kX86VarTypeXmmSD, name); } fpr_t >>= 2; } } void X64Emitter::FillRegisters() { X86Compiler& c = compiler_; if (!FLAGS_cache_registers) { return; } // This updates all of the local register values from the state memory. // It should be called on function entry for initial setup and after any // calls that may modify the registers. // TODO(benvanik): use access flags to see if we need to do reads/writes. if (locals_.xer.getId() != kInvalidValue) { if (FLAGS_annotate_disassembly) { c.comment("Filling XER"); } c.mov(locals_.xer, qword_ptr(c.getGpArg(0), offsetof(xe_ppc_state_t, xer))); } if (locals_.lr.getId() != kInvalidValue) { if (FLAGS_annotate_disassembly) { c.comment("Filling LR"); } c.mov(locals_.lr, qword_ptr(c.getGpArg(0), offsetof(xe_ppc_state_t, lr))); } if (locals_.ctr.getId() != kInvalidValue) { if (FLAGS_annotate_disassembly) { c.comment("Filling CTR"); } c.mov(locals_.ctr, qword_ptr(c.getGpArg(0), offsetof(xe_ppc_state_t, ctr))); } // Fill the split CR values by extracting each one from the CR. // This could probably be done faster via an extractvalues or something. // Perhaps we could also change it to be a vector<8*i8>. GpVar cr; GpVar cr_tmp; for (size_t n = 0; n < XECOUNT(locals_.cr); n++) { GpVar& cr_n = locals_.cr[n]; if (cr_n.getId() == kInvalidValue) { continue; } if (cr.getId() == kInvalidValue) { // Only fetch once. Doing this in here prevents us from having to // always fetch even if unused. if (FLAGS_annotate_disassembly) { c.comment("Filling CR"); } cr = c.newGpVar(); c.mov(cr, qword_ptr(c.getGpArg(0), offsetof(xe_ppc_state_t, cr))); cr_tmp = c.newGpVar(); } // (cr >> 28 - n * 4) & 0xF c.mov(cr_tmp, cr); if (n < 7) { c.shr(cr_tmp, imm(28 - n * 4)); } c.and_(cr_tmp, imm(0xF)); c.mov(cr_n, cr_tmp); } for (size_t n = 0; n < XECOUNT(locals_.gpr); n++) { if (locals_.gpr[n].getId() != kInvalidValue) { if (FLAGS_annotate_disassembly) { c.comment("Filling r%d", n); } c.mov(locals_.gpr[n], qword_ptr(c.getGpArg(0), offsetof(xe_ppc_state_t, r) + 8 * n)); } } for (size_t n = 0; n < XECOUNT(locals_.fpr); n++) { if (locals_.fpr[n].getId() != kInvalidValue) { if (FLAGS_annotate_disassembly) { c.comment("Filling f%d", n); } c.mov(locals_.fpr[n], qword_ptr(c.getGpArg(0), offsetof(xe_ppc_state_t, f) + 8 * n)); } } } void X64Emitter::SpillRegisters() { X86Compiler& c = compiler_; if (!FLAGS_cache_registers) { return; } // This flushes all local registers (if written) to the register bank and // resets their values. // TODO(benvanik): only flush if actually required, or selective flushes. if (locals_.xer.getId() != kInvalidValue) { if (FLAGS_annotate_disassembly) { c.comment("Spilling XER"); } c.mov(qword_ptr(c.getGpArg(0), offsetof(xe_ppc_state_t, xer)), locals_.xer); } if (locals_.lr.getId() != kInvalidValue) { if (FLAGS_annotate_disassembly) { c.comment("Spilling LR"); } c.mov(qword_ptr(c.getGpArg(0), offsetof(xe_ppc_state_t, lr)), locals_.lr); } if (locals_.ctr.getId() != kInvalidValue) { if (FLAGS_annotate_disassembly) { c.comment("Spilling CTR"); } c.mov(qword_ptr(c.getGpArg(0), offsetof(xe_ppc_state_t, ctr)), locals_.ctr); } // Stitch together all split CR values. // TODO(benvanik): don't flush across calls? GpVar cr; GpVar cr_tmp; for (size_t n = 0; n < XECOUNT(locals_.cr); n++) { GpVar& cr_n = locals_.cr[n]; if (cr_n.getId() == kInvalidValue) { continue; } if (cr_tmp.getId() == kInvalidValue) { cr_tmp = c.newGpVar(); if (FLAGS_annotate_disassembly) { c.comment("Spilling CR"); } } // cr |= (cr_n << n * 4) c.mov(cr_tmp, cr_n); if (n) { c.shl(cr_tmp, imm(n * 4)); } if (cr.getId() == kInvalidValue) { cr = c.newGpVar(); c.mov(cr, cr_tmp); } else { c.or_(cr, cr_tmp); } } if (cr.getId() != kInvalidValue) { c.mov(qword_ptr(c.getGpArg(0), offsetof(xe_ppc_state_t, cr)), cr); } for (uint32_t n = 0; n < XECOUNT(locals_.gpr); n++) { GpVar& v = locals_.gpr[n]; if (v.getId() != kInvalidValue) { if (FLAGS_annotate_disassembly) { c.comment("Spilling r%d", n); } c.mov(qword_ptr(c.getGpArg(0), offsetof(xe_ppc_state_t, r) + 8 * n), v); } } for (uint32_t n = 0; n < XECOUNT(locals_.fpr); n++) { GpVar& v = locals_.fpr[n]; if (v.getId() != kInvalidValue) { if (FLAGS_annotate_disassembly) { c.comment("Spilling f%d", n); } c.mov(qword_ptr(c.getGpArg(0), offsetof(xe_ppc_state_t, f) + 8 * n), v); } } } GpVar X64Emitter::xer_value() { X86Compiler& c = compiler_; if (FLAGS_cache_registers) { XEASSERT(locals_.xer.getId() != kInvalidValue); return locals_.xer; } else { GpVar value(c.newGpVar()); c.mov(value, qword_ptr(c.getGpArg(0), offsetof(xe_ppc_state_t, xer))); return value; } } void X64Emitter::update_xer_value(GpVar& value) { X86Compiler& c = compiler_; if (FLAGS_cache_registers) { XEASSERT(locals_.xer.getId() != kInvalidValue); c.mov(locals_.xer, zero_extend(value, 0, 8)); } else { c.mov(qword_ptr(c.getGpArg(0), offsetof(xe_ppc_state_t, xer)), zero_extend(value, 0, 8)); } } void X64Emitter::update_xer_with_overflow(GpVar& value) { X86Compiler& c = compiler_; GpVar xer(c.newGpVar()); c.mov(xer, xer_value()); c.and_(xer, imm(0xBFFFFFFF)); // clear bit 30 c.and_(value, imm(1)); c.shl(value, imm(30)); c.or_(xer, value); c.shl(value, imm(31)); // also stick value c.or_(xer, value); update_xer_value(xer); } // Set with a byte value indicating carry. void X64Emitter::update_xer_with_carry(GpVar& value) { X86Compiler& c = compiler_; GpVar xer(c.newGpVar()); c.mov(xer, xer_value()); c.and_(xer, imm(0xDFFFFFFF)); // clear bit 29 c.and_(value, imm(1)); c.shl(value, imm(29)); c.or_(xer, value); update_xer_value(xer); } #if 0 void X64Emitter::update_xer_with_overflow_and_carry(GpVar& value) { X86Compiler& c = compiler_; XEASSERT(locals_.xer.getId() != kInvalidValue); // Expects a i1 indicating overflow. // Trust the caller that if it's larger than that it's already truncated. value = zero_extend(value, 1, 8); // This is effectively an update_xer_with_overflow followed by an // update_xer_with_carry, but since the logic is largely the same share it. GpVar& xer = xer_value(); // clear bit 30 & 29 xer = jit_insn_and(fn_, xer, get_uint64(0xFFFFFFFF9FFFFFFF)); xer = jit_insn_or(fn_, xer, jit_insn_shl(fn_, value, get_uint32(31))); xer = jit_insn_or(fn_, xer, jit_insn_shl(fn_, value, get_uint32(30))); xer = jit_insn_or(fn_, xer, jit_insn_shl(fn_, value, get_uint32(29))); jit_insn_store(fn_, locals_.xer, value); } #endif GpVar X64Emitter::lr_value() { X86Compiler& c = compiler_; if (FLAGS_cache_registers) { XEASSERT(locals_.lr.getId() != kInvalidValue); return locals_.lr; } else { GpVar value(c.newGpVar()); c.mov(value, qword_ptr(c.getGpArg(0), offsetof(xe_ppc_state_t, lr))); return value; } } void X64Emitter::update_lr_value(GpVar& value) { X86Compiler& c = compiler_; if (FLAGS_cache_registers) { XEASSERT(locals_.lr.getId() != kInvalidValue); c.mov(locals_.lr, zero_extend(value, 0, 8)); } else { c.mov(qword_ptr(c.getGpArg(0), offsetof(xe_ppc_state_t, lr)), zero_extend(value, 0, 8)); } } void X64Emitter::update_lr_value(AsmJit::Imm& imm) { X86Compiler& c = compiler_; if (FLAGS_cache_registers) { XEASSERT(locals_.lr.getId() != kInvalidValue); c.mov(locals_.lr, imm); } else { c.mov(qword_ptr(c.getGpArg(0), offsetof(xe_ppc_state_t, lr)), imm); } } GpVar X64Emitter::ctr_value() { X86Compiler& c = compiler_; if (FLAGS_cache_registers) { XEASSERT(locals_.ctr.getId() != kInvalidValue); return locals_.ctr; } else { GpVar value(c.newGpVar()); c.mov(value, qword_ptr(c.getGpArg(0), offsetof(xe_ppc_state_t, ctr))); return value; } } void X64Emitter::update_ctr_value(GpVar& value) { X86Compiler& c = compiler_; if (FLAGS_cache_registers) { XEASSERT(locals_.ctr.getId() != kInvalidValue); c.mov(locals_.ctr, zero_extend(value, 0, 8)); } else { c.mov(qword_ptr(c.getGpArg(0), offsetof(xe_ppc_state_t, ctr)), zero_extend(value, 0, 8)); } } GpVar X64Emitter::cr_value(uint32_t n) { X86Compiler& c = compiler_; XEASSERT(n >= 0 && n < 8); if (FLAGS_cache_registers) { XEASSERT(locals_.cr[n].getId() != kInvalidValue); return locals_.cr[n]; } else { // TODO(benvanik): this can most definitely be made more efficient. GpVar value(c.newGpVar()); c.mov(value, qword_ptr(c.getGpArg(0), offsetof(xe_ppc_state_t, cr))); if (n < 7) { c.shr(value, imm(28 - n * 4)); } c.and_(value, imm(0xF)); return value; } } void X64Emitter::update_cr_value(uint32_t n, GpVar& value) { X86Compiler& c = compiler_; XEASSERT(n >= 0 && n < 8); if (FLAGS_cache_registers) { XEASSERT(locals_.cr[n].getId() != kInvalidValue); c.mov(locals_.cr[n], trunc(value, 1)); } else { // TODO(benvanik): this can most definitely be made more efficient. GpVar cr_tmp(c.newGpVar()); c.mov(cr_tmp, qword_ptr(c.getGpArg(0), offsetof(xe_ppc_state_t, cr))); GpVar cr_n(c.newGpVar()); c.mov(cr_n, value); c.and_(cr_n, imm(0xF)); if (n < 7) { c.shl(cr_n, imm(28 - n * 4)); } c.and_(cr_tmp, imm(~(0xF << (28 - n * 4)))); c.or_(cr_tmp, cr_n); c.mov(qword_ptr(c.getGpArg(0), offsetof(xe_ppc_state_t, cr)), cr_tmp); } } void X64Emitter::update_cr_with_cond(uint32_t n, GpVar& lhs) { X86Compiler& c = compiler_; // bit0 = RA < 0 // bit1 = RA > 0 // bit2 = RA = 0 // bit3 = XER[SO] // Compare and set bits. GpVar v_l(c.newGpVar()); GpVar v_g(c.newGpVar()); GpVar v_e(c.newGpVar()); c.cmp(lhs, imm(0)); c.setl(v_l.r8()); c.setg(v_g.r8()); c.sete(v_e.r8()); GpVar v(c.newGpVar()); c.shl(v_g, imm(1)); c.shl(v_e, imm(2)); c.mov(v, v_l.r8()); c.or_(v, v_g.r8()); c.or_(v, v_e.r8()); // TODO(benvanik): set bit 4 to XER[SO] // c.seto? // Insert the 4 bits into their location in the CR. update_cr_value(n, v); } void X64Emitter::update_cr_with_cond(uint32_t n, GpVar& lhs, GpVar& rhs) { X86Compiler& c = compiler_; // bit0 = RA < RB // bit1 = RA > RB // bit2 = RA = RB // bit3 = XER[SO] // Compare and set bits. GpVar v_l(c.newGpVar()); GpVar v_g(c.newGpVar()); GpVar v_e(c.newGpVar()); c.cmp(lhs, rhs); c.setl(v_l.r8()); c.setg(v_g.r8()); c.sete(v_e.r8()); GpVar v(c.newGpVar()); c.shl(v_g, imm(1)); c.shl(v_e, imm(2)); c.mov(v, v_l.r8()); c.or_(v, v_g.r8()); c.or_(v, v_e.r8()); // TODO(benvanik): set bit 4 to XER[SO] // c.seto? // Insert the 4 bits into their location in the CR. update_cr_value(n, v); } GpVar X64Emitter::gpr_value(uint32_t n) { X86Compiler& c = compiler_; XEASSERT(n >= 0 && n < 32); // Actually r0 is writable, even though nobody should ever do that. // Perhaps we can check usage and enable this if safe? // if (n == 0) { // return get_uint64(0); // } if (FLAGS_cache_registers) { XEASSERT(locals_.gpr[n].getId() != kInvalidValue); return locals_.gpr[n]; } else { GpVar value(c.newGpVar()); c.mov(value, qword_ptr(c.getGpArg(0), offsetof(xe_ppc_state_t, r) + 8 * n)); return value; } } void X64Emitter::update_gpr_value(uint32_t n, GpVar& value) { X86Compiler& c = compiler_; XEASSERT(n >= 0 && n < 32); // See above - r0 can be written. // if (n == 0) { // // Ignore writes to zero. // return; // } if (FLAGS_cache_registers) { XEASSERT(locals_.gpr[n].getId() != kInvalidValue); c.mov(locals_.gpr[n], zero_extend(value, 0, 8)); } else { c.mov(qword_ptr(c.getGpArg(0), offsetof(xe_ppc_state_t, r) + 8 * n), zero_extend(value, 0, 8)); } } GpVar X64Emitter::fpr_value(uint32_t n) { X86Compiler& c = compiler_; XEASSERT(n >= 0 && n < 32); if (FLAGS_cache_registers) { XEASSERT(locals_.fpr[n].getId() != kInvalidValue); return locals_.fpr[n]; } else { GpVar value(c.newGpVar()); c.mov(value, qword_ptr(c.getGpArg(0), offsetof(xe_ppc_state_t, f) + 8 * n)); return value; } } void X64Emitter::update_fpr_value(uint32_t n, GpVar& value) { X86Compiler& c = compiler_; XEASSERT(n >= 0 && n < 32); if (FLAGS_cache_registers) { XEASSERT(locals_.fpr[n].getId() != kInvalidValue); c.mov(locals_.fpr[n], value); } else { c.mov(qword_ptr(c.getGpArg(0), offsetof(xe_ppc_state_t, f) + 8 * n), value); } } GpVar X64Emitter::TouchMemoryAddress(uint32_t cia, GpVar& addr) { X86Compiler& c = compiler_; // Input address is always in 32-bit space. GpVar real_address(c.newGpVar()); c.mov(real_address.r32(), addr.r32()); // Add runtime memory address checks, if needed. if (FLAGS_memory_address_verification) { // BasicBlock* invalid_bb = BasicBlock::Create(*context_, "", fn_); // BasicBlock* valid_bb = BasicBlock::Create(*context_, "", fn_); // // The heap starts at 0x1000 - if we write below that we're boned. // jit_value_t gt = b.CreateICmpUGE(addr, b.getInt64(0x00001000)); // b.CreateCondBr(gt, valid_bb, invalid_bb); // b.SetInsertPoint(invalid_bb); // jit_value_t access_violation = gen_module_->getFunction("XeAccessViolation"); // SpillRegisters(); // b.CreateCall3(access_violation, // fn_->arg_begin(), // b.getInt32(cia), // addr); // b.CreateBr(valid_bb); // b.SetInsertPoint(valid_bb); } // Rebase off of memory pointer. uint64_t membase = (uint64_t)xe_memory_addr(memory_, 0); c.add(real_address, get_uint64(membase)); return real_address; } GpVar X64Emitter::ReadMemory( uint32_t cia, GpVar& addr, uint32_t size, bool acquire) { X86Compiler& c = compiler_; // Rebase off of memory base pointer. GpVar real_address = TouchMemoryAddress(cia, addr); if (acquire) { // TODO(benvanik): acquire semantics. // load_value->setAlignment(size); // load_value->setVolatile(true); // load_value->setAtomic(Acquire); XELOGE("Ignoring acquire semantics on read -- TODO"); } GpVar value(c.newGpVar()); bool needs_swap = false; switch (size) { case 1: c.mov(value.r8(), byte_ptr(real_address)); c.and_(value, imm(0xFF)); break; case 2: c.mov(value.r16(), word_ptr(real_address)); c.and_(value, imm(0xFFFF)); c.xchg(value.r8Lo(), value.r8Hi()); break; case 4: c.mov(value.r32(), dword_ptr(real_address)); // No need to and -- the mov to e*x will extend for us. c.bswap(value.r32()); break; case 8: c.mov(value, qword_ptr(real_address)); c.bswap(value.r64()); break; default: XEASSERTALWAYS(); c.mov(value, imm(0xDEADBEEF)); break; } return value; } void X64Emitter::WriteMemory( uint32_t cia, GpVar& addr, uint32_t size, GpVar& value, bool release) { X86Compiler& c = compiler_; // Rebase off of memory base pointer. GpVar real_address = TouchMemoryAddress(cia, addr); GpVar tmp; switch (size) { case 1: c.mov(byte_ptr(real_address), value.r8()); break; case 2: tmp = c.newGpVar(); c.mov(tmp, value); c.xchg(tmp.r8Lo(), tmp.r8Hi()); c.mov(word_ptr(real_address), tmp.r16()); break; case 4: tmp = c.newGpVar(); c.mov(tmp, value); c.bswap(tmp.r32()); c.mov(dword_ptr(real_address), tmp.r32()); break; case 8: tmp = c.newGpVar(); c.mov(tmp, value); c.bswap(tmp.r64()); c.mov(qword_ptr(real_address), tmp.r64()); break; default: XEASSERTALWAYS(); return; } // TODO(benvanik): release semantics if (release) { // store_value->setAlignment(size); // store_value->setVolatile(true); // store_value->setAtomic(Release); XELOGE("Ignoring release semantics on write -- TODO"); } } GpVar X64Emitter::get_uint64(uint64_t value) { X86Compiler& c = compiler_; GpVar v(c.newGpVar()); c.mov(v, imm(value)); return v; } GpVar X64Emitter::sign_extend(GpVar& value, int from_size, int to_size) { X86Compiler& c = compiler_; if (!from_size) { from_size = value.getSize(); } // No-op if the same size. if (from_size == to_size) { return value; } // TODO(benvanik): use movsx if value is in memory. // errrr.... could pin values to rax for cbw/cwde/cdqe // or, could use shift trick (may be slower): // shlq $(target_len-src_len), reg // sarq $(target_len-src_len), reg GpVar tmp(c.newGpVar()); switch (from_size) { case 1: switch (to_size) { case 1: XEASSERTALWAYS(); return value; case 2: c.mov(tmp, value); c.cbw(tmp); // b->w return tmp; case 4: c.mov(tmp, value); c.cbw(tmp); // b->w c.cwde(tmp); // w->d return tmp; case 8: c.mov(tmp, value); c.cbw(tmp); // b->w c.cwde(tmp); // w->d c.cdqe(tmp); // d->q return tmp; } break; case 2: switch (to_size) { case 1: XEASSERTALWAYS(); return value; case 2: XEASSERTALWAYS(); return value; case 4: c.mov(tmp, value); c.cwde(tmp); // w->d return tmp; case 8: c.mov(tmp, value); c.cwde(tmp); // w->d c.cdqe(tmp); // d->q return tmp; } break; case 4: switch (to_size) { case 1: XEASSERTALWAYS(); return value; case 2: XEASSERTALWAYS(); return value; case 4: XEASSERTALWAYS(); return value; case 8: c.mov(tmp, value); c.cdqe(tmp); // d->q return tmp; } break; case 8: break; } XEASSERTALWAYS(); return value; } GpVar X64Emitter::zero_extend(GpVar& value, int from_size, int to_size) { X86Compiler& c = compiler_; if (!from_size) { from_size = value.getSize(); } // No-op if the same size. if (from_size == to_size) { return value; } // TODO(benvanik): use movzx if value is in memory. GpVar tmp(c.newGpVar()); switch (from_size) { case 1: switch (to_size) { case 1: XEASSERTALWAYS(); return value; case 2: c.mov(tmp, value.r8()); return tmp.r16(); case 4: c.mov(tmp, value.r8()); return tmp.r32(); case 8: c.mov(tmp, value.r8()); return tmp.r64(); } break; case 2: switch (to_size) { case 1: XEASSERTALWAYS(); return value; case 2: XEASSERTALWAYS(); return value; case 4: c.mov(tmp, value.r16()); return tmp.r32(); case 8: c.mov(tmp, value.r16()); return tmp.r64(); } break; case 4: switch (to_size) { case 1: XEASSERTALWAYS(); return value; case 2: XEASSERTALWAYS(); return value; case 4: XEASSERTALWAYS(); return value; case 8: c.mov(tmp, value.r32()); return tmp.r64(); } break; case 8: break; } XEASSERTALWAYS(); return value; } GpVar X64Emitter::trunc(GpVar& value, int size) { X86Compiler& c = compiler_; XEASSERTALWAYS(); return value; #if 0 // No-op if the same size. if (value.getSize() == size) { return value; } switch (size) { case 1: return value.r8(); case 2: return value.r16(); case 4: return value.r32(); default: case 8: return value.r64(); } #endif }