/* Copyright 2019 flyinghead This file is part of reicast. reicast is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. reicast is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with reicast. If not, see . */ #include "build.h" #if HOST_CPU == CPU_ARM64 && FEAT_AREC != DYNAREC_NONE #include #include "arm7.h" #include "arm_emitter/arm_coding.h" #include "deps/vixl/aarch64/macro-assembler-aarch64.h" using namespace vixl::aarch64; //#include "deps/vixl/aarch32/disasm-aarch32.h" extern void vmem_platform_flush_cache(void *icache_start, void *icache_end, void *dcache_start, void *dcache_end); extern u32 arm_single_op(u32 opcode); extern "C" void arm_dispatch(); extern "C" void arm_exit(); extern u8* icPtr; extern u8* ICache; extern const u32 ICacheSize; extern reg_pair arm_Reg[RN_ARM_REG_COUNT]; MacroAssembler *assembler; extern "C" void armFlushICache(void *bgn, void *end) { vmem_platform_flush_cache(bgn, end, bgn, end); } static MemOperand arm_reg_operand(u32 regn) { return MemOperand(x28, (u8*)&arm_Reg[regn].I - (u8*)&arm_Reg[0].I); } //helpers ... void LoadReg(ARM::eReg rd, u32 regn, ARM::ConditionCode cc = ARM::CC_AL) { assembler->Ldr(Register::GetWRegFromCode(rd), arm_reg_operand(regn)); } void StoreReg(ARM::eReg rd, u32 regn, ARM::ConditionCode cc = ARM::CC_AL) { assembler->Str(Register::GetWRegFromCode(rd), arm_reg_operand(regn)); } void *armv_start_conditional(ARM::ConditionCode cc) { if (cc == ARM::CC_AL) return NULL; Label *label = new Label(); verify(cc <= ARM::CC_LE); Condition condition = (Condition)((u32)cc ^ 1); assembler->B(label, condition); return label; } void armv_end_conditional(void *ref) { if (ref != NULL) { Label *label = (Label *)ref; assembler->Bind(label); delete label; } } //For COND void LoadFlags() { //Load flags LoadReg(ARM::r0, RN_PSR_FLAGS); //move them to flags register assembler->Msr(NZCV, x0); } void StoreFlags() { //get results from flags register assembler->Mrs(x1, NZCV); //Store flags StoreReg(ARM::r1, RN_PSR_FLAGS); } void armv_imm_to_reg(u32 regn, u32 imm) { assembler->Mov(w0, imm); assembler->Str(w0, arm_reg_operand(regn)); } void armv_call(void* loc) { ptrdiff_t offset = reinterpret_cast(loc) - assembler->GetBuffer()->GetStartAddress(); Label function_label; assembler->BindToOffset(&function_label, offset); assembler->Bl(&function_label); } void armv_setup() { assembler = new MacroAssembler(icPtr, ICache + ICacheSize - icPtr); } void armv_intpr(u32 opcd) { //Call interpreter assembler->Mov(w0, opcd); armv_call((void*)&arm_single_op); assembler->Sub(w27, w27, w0); } void armv_end(void* codestart, u32 cycl) { //Normal block end //cycle counter rv //pop registers & return assembler->Subs(w27, w27, cycl); ptrdiff_t offset = reinterpret_cast(arm_exit) - assembler->GetBuffer()->GetStartAddress(); Label arm_exit_label; assembler->BindToOffset(&arm_exit_label, offset); assembler->B(&arm_exit_label, mi); //statically predicted as not taken offset = reinterpret_cast(arm_dispatch) - assembler->GetBuffer()->GetStartAddress(); Label arm_dispatch_label; assembler->BindToOffset(&arm_dispatch_label, offset); assembler->B(&arm_dispatch_label); assembler->FinalizeCode(); verify(assembler->GetBuffer()->GetCursorOffset() <= assembler->GetBuffer()->GetCapacity()); vmem_platform_flush_cache( codestart, assembler->GetBuffer()->GetEndAddress(), codestart, assembler->GetBuffer()->GetEndAddress()); icPtr += assembler->GetBuffer()->GetSizeInBytes(); #if 0 Instruction* instr_start = (Instruction *)codestart; Instruction* instr_end = assembler->GetBuffer()->GetEndAddress(); Decoder decoder; Disassembler disasm; decoder.AppendVisitor(&disasm); Instruction* instr; for (instr = instr_start; instr < instr_end; instr += kInstructionSize) { decoder.Decode(instr); DEBUG_LOG(AICA_ARM, "arm64 arec\t %p:\t%s", reinterpret_cast(instr), disasm.GetOutput()); } #endif delete assembler; assembler = NULL; } //Hook cus varm misses this, so x86 needs special code void armv_MOV32(ARM::eReg regn, u32 imm) { assembler->Mov(Register::GetWRegFromCode(regn), imm); } void armv_mov(ARM::eReg regd, ARM::eReg regn) { assembler->Mov(Register::GetWRegFromCode(regd), Register::GetWRegFromCode(regn)); } void armv_add(ARM::eReg regd, ARM::eReg regn, ARM::eReg regm) { assembler->Add(Register::GetWRegFromCode(regd), Register::GetWRegFromCode(regn), Register::GetWRegFromCode(regm)); } void armv_sub(ARM::eReg regd, ARM::eReg regn, ARM::eReg regm) { assembler->Sub(Register::GetWRegFromCode(regd), Register::GetWRegFromCode(regn), Register::GetWRegFromCode(regm)); } void armv_add(ARM::eReg regd, ARM::eReg regn, s32 imm) { assembler->Add(Register::GetWRegFromCode(regd), Register::GetWRegFromCode(regn), imm); } void armv_lsl(ARM::eReg regd, ARM::eReg regn, u32 imm) { assembler->Lsl(Register::GetWRegFromCode(regd), Register::GetWRegFromCode(regn), imm); } void armv_bic(ARM::eReg regd, ARM::eReg regn, u32 imm) { assembler->Bic(Register::GetWRegFromCode(regd), Register::GetWRegFromCode(regn), imm); } class android_buf : public std::stringbuf { public: virtual int sync() override { DEBUG_LOG(AICA_ARM, "ARM7: %s", this->str().c_str()); str(""); return 0; } }; void armEmit32(u32 opcode) { #if 0 if (opcode != 0x00011001) { android_buf buffer; std::ostream cout(&buffer); vixl::aarch32::PrintDisassembler disasm(cout, 0); disasm.DecodeA32(opcode); cout.flush(); } #endif const Register& rd = Register::GetWRegFromCode((opcode >> 12) & 15); const Register& rn = Register::GetWRegFromCode((opcode >> 16) & 15); bool set_flags = opcode & (1 << 20); Operand op2; int op_type = (opcode >> 21) & 15; bool logical_op = op_type == 0 || op_type == 1 || op_type == 8 || op_type == 9 // AND, EOR, TST, TEQ || op_type == 12 || op_type == 13 || op_type == 15 || op_type == 14; // ORR, MOV, MVN, BIC bool set_carry_bit = false; ARM::ConditionCode condition = (ARM::ConditionCode)(opcode >> 28); void *cond_op_label = armv_start_conditional(condition); if (opcode & (1 << 25)) { // op2 is imm8r4 u32 rotate = ((opcode >> 8) & 15) << 1; u32 imm8 = opcode & 0xff; op2 = Operand((imm8 >> rotate) | (imm8 << (32 - rotate))); } else { // op2 is register const Register& rm = Register::GetWRegFromCode(opcode & 15); Shift shift = (Shift)((opcode >> 5) & 3); if (opcode & (1 << 4)) { // shift by register // FIXME Carry must be set based on shift/rotate //if (set_flags && logical_op) // die("shift by register with set flags C - not implemented"); const Register& shift_reg = Register::GetWRegFromCode((opcode >> 8) & 15); Label shift_by_32_label; switch (shift) { case LSL: case LSR: assembler->Mrs(x0, NZCV); assembler->Cmp(shift_reg, 32); if (shift == LSL) assembler->Lsl(w15, rm, shift_reg); else assembler->Lsr(w15, rm, shift_reg); assembler->Csel(w15, 0, w15, ge); // LSL and LSR by 32 or more gives 0 assembler->Msr(NZCV, x0); break; case ASR: assembler->Mrs(x0, NZCV); assembler->Cmp(shift_reg, 32); assembler->Asr(w15, rm, shift_reg); assembler->Sbfx(w13, rm, 31, 1); assembler->Csel(w15, w13, w15, ge); // ASR by 32 or more gives 0 or -1 depending on operand sign assembler->Msr(NZCV, x0); break; case ROR: assembler->Ror(w15, rm, shift_reg); break; default: die("Invalid shift"); break; } op2 = Operand(w15); } else { // shift by immediate u32 shift_imm = (opcode >> 7) & 0x1f; if (shift != ROR && shift_imm != 0 && !(set_flags && logical_op)) { op2 = Operand(rm, shift, shift_imm); } else if (shift_imm == 0) { if (shift == LSL) { op2 = Operand(rm); // LSL 0 is a no-op } else { // Shift by 32 if (set_flags && logical_op) set_carry_bit = true; if (shift == LSR) { if (set_flags && logical_op) assembler->Ubfx(w14, rm, 31, 1); // w14 = rm[31] assembler->Mov(w15, 0); // w15 = 0 } else if (shift == ASR) { if (set_flags && logical_op) assembler->Ubfx(w14, rm, 31, 1); // w14 = rm[31] assembler->Sbfx(w15, rm, 31, 1); // w15 = rm < 0 ? -1 : 0 } else if (shift == ROR) { // RRX assembler->Cset(w14, cs); // w14 = C assembler->Mov(w15, Operand(rm, LSR, 1)); // w15 = rm >> 1 assembler->Bfi(w15, w14, 31, 1); // w15[31] = C if (set_flags && logical_op) assembler->Ubfx(w14, rm, 0, 1); // w14 = rm[0] (new C) } else die("Invalid shift"); op2 = Operand(w15); } } else { // Carry must be preserved or Ror shift if (set_flags && logical_op) set_carry_bit = true; if (shift == LSL) { assembler->Ubfx(w14, rm, 32 - shift_imm, 1); // w14 = rm[lsb] assembler->Lsl(w15, rm, shift_imm); // w15 <<= shift } else { if (set_flags && logical_op) assembler->Ubfx(w14, rm, shift_imm - 1, 1); // w14 = rm[msb] if (shift == LSR) assembler->Lsr(w15, rm, shift_imm); // w15 >>= shift else if (shift == ASR) assembler->Asr(w15, rm, shift_imm); else if (shift == ROR) assembler->Ror(w15, rm, shift_imm); else die("Invalid shift"); } op2 = Operand(w15); } } } if (!set_carry_bit && (op_type == 8 || op_type == 9 // TST and TEQ always set flags || (logical_op && set_flags))) { // Logical ops should only affect the carry bit based on the op2 shift // Here we're not shifting so the carry bit should be preserved set_carry_bit = true; assembler->Cset(w14, cs); } switch (op_type) { case 0: // AND if (set_flags) assembler->Ands(rd, rn, op2); else assembler->And(rd, rn, op2); break; case 1: // EOR assembler->Eor(rd, rn, op2); if (set_flags) assembler->Tst(rd, rd); break; case 2: // SUB if (set_flags) assembler->Subs(rd, rn, op2); else assembler->Sub(rd, rn, op2); break; case 3: // RSB assembler->Neg(w0, rn); if (set_flags) assembler->Adds(rd, w0, op2); else assembler->Add(rd, w0, op2); break; case 4: // ADD if (set_flags) assembler->Adds(rd, rn, op2); else assembler->Add(rd, rn, op2); break; case 12: // ORR assembler->Orr(rd, rn, op2); if (set_flags) assembler->Tst(rd, rd); break; case 14: // BIC if (set_flags) assembler->Bics(rd, rn, op2); else assembler->Bic(rd, rn, op2); break; case 5: // ADC if (set_flags) assembler->Adcs(rd, rn, op2); else assembler->Adc(rd, rn, op2); break; case 6: // SBC if (set_flags) assembler->Sbcs(rd, rn, op2); else assembler->Sbc(rd, rn, op2); break; case 7: // RSC assembler->Ngc(w0, rn); if (set_flags) assembler->Adds(rd, w0, op2); else assembler->Add(rd, w0, op2); break; case 8: // TST assembler->Tst(rn, op2); break; case 9: // TEQ assembler->Eor(w0, rn, op2); assembler->Tst(w0, w0); break; case 10: // CMP assembler->Cmp(rn, op2); break; case 11: // CMN assembler->Cmn(rn, op2); break; case 13: // MOV assembler->Mov(rd, op2); if (set_flags) assembler->Tst(rd, rd); break; case 15: // MVN assembler->Mvn(rd, op2); if (set_flags) assembler->Tst(rd, rd); break; } if (set_carry_bit) { assembler->Mrs(x0, NZCV); assembler->Bfi(x0, x14, 29, 1); // C is bit 29 in NZCV assembler->Msr(NZCV, x0); } armv_end_conditional(cond_op_label); } // // Dynarec main loop // // w25 is used for temp mem save (post increment op2) // x26 is the entry points table // w27 is the cycle counter // x28 points to the arm7 registers base __asm__ ( ".globl arm_compilecode \n\t" ".hidden arm_compilecode \n" "arm_compilecode: \n\t" "bl CompileCode \n\t" "b arm_dispatch \n\t" ".globl arm_mainloop \n\t" ".hidden arm_mainloop \n" "arm_mainloop: \n\t" // arm_mainloop(cycles, regs, entry points) "stp x25, x26, [sp, #-48]! \n\t" "stp x27, x28, [sp, #16] \n\t" "stp x29, x30, [sp, #32] \n\t" "mov x28, x1 \n\t" // arm7 registers "mov x26, x2 \n\t" // lookup base "ldr w27, [x28, #192] \n\t" // cycle count "add w27, w27, w0 \n\t" // add cycles for this timeslice ".globl arm_dispatch \n\t" ".hidden arm_dispatch \n" "arm_dispatch: \n\t" "ldp w0, w1, [x28, #184] \n\t" // load Next PC, interrupt "ubfx w2, w0, #2, #21 \n\t" // w2 = pc >> 2. Note: assuming address space == 8 MB (23 bits) "cbnz w1, arm_dofiq \n\t" // if interrupt pending, handle it "add x2, x26, x2, lsl #3 \n\t" // x2 = EntryPoints + pc << 1 "ldr x3, [x2] \n\t" "br x3 \n" "arm_dofiq: \n\t" "bl CPUFiq \n\t" "b arm_dispatch \n\t" ".globl arm_exit \n\t" ".hidden arm_exit \n" "arm_exit: \n\t" "str w27, [x28, #192] \n\t" // if timeslice is over, save remaining cycles "ldp x29, x30, [sp, #32] \n\t" "ldp x27, x28, [sp, #16] \n\t" "ldp x25, x26, [sp], #48 \n\t" "ret \n" ); #endif // ARM64