/* Copyright 2020 flyinghead This file is part of flycast. flycast 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. flycast 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 flycast. If not, see . */ #pragma once #include #include "types.h" #include "arm7.h" namespace aicaarm { struct ArmOp { enum OpType { AND, EOR, SUB, RSB, ADD, ADC, SBC, RSC, TST, TEQ, CMP, CMN, ORR, MOV, BIC, MVN, LDR, STR, B, BL, MSR, MRS, FALLBACK }; enum Condition { EQ, NE, CS, CC, MI, PL, VS, VC, HI, LS, GE, LT, GT, LE, AL, UC }; enum ShiftOp { LSL, LSR, ASR, ROR, RRX = ROR, }; static_assert(UC == 15, "UC == 15"); static const u32 OP_READS_FLAGS = 1; static const u32 OP_SETS_FLAGS = 4; static const u32 OP_SETS_PC = 8; ArmOp() : ArmOp(FALLBACK, AL) {} ArmOp(OpType type, Condition condition) : op_type(type), condition(condition) { if (condition != AL) flags |= OP_READS_FLAGS; } struct Register { Register() : armreg((Arm7Reg)0) {} Register(Arm7Reg armreg) : armreg(armreg) {} Register(Arm7Reg armreg, int version) : armreg(armreg), version(version) {} Arm7Reg armreg; int version = 0; }; struct Operand { Operand() : type(none) {} Operand(u32 v) : type(imm), imm_value(v) {} Operand(Arm7Reg r) : type(reg_), reg_value(r, 0) {} bool isImmediate() const { return type == imm; } bool isReg() const { return type == reg_; } bool isNone() const { return type == none; } u32 getImmediate() const { verify(isImmediate()); return imm_value; } void setImmediate(u32 v) { type = imm; imm_value = v; } Register& getReg() { verify(isReg()); return reg_value; } const Register& getReg() const { verify(isReg()); return reg_value; } void setReg(Arm7Reg armreg, int version = 0) { type = reg_; reg_value = Register(armreg, version); } bool isShifted() const { return !shift_imm || shift_value != 0 || shift_type != ArmOp::LSL; } enum { none, reg_, imm } type; union { u32 imm_value; Register reg_value; }; // For flexible operand ShiftOp shift_type = LSL; bool shift_imm = true; union { u32 shift_value = 0; Register shift_reg; }; }; OpType op_type; Operand rd; std::array arg; // For LDR/STR bool pre_index = false; bool add_offset = false; bool byte_xfer = false; bool write_back = false; Condition condition; u8 flags = 0; u8 cycles = 6; bool spsr = false; bool isLogicalOp() const { return op_type == AND || op_type == EOR || op_type == TST || op_type == TEQ || (op_type >= ORR && op_type <= MVN); } bool isCompOp() const { return op_type >= TST && op_type <= CMN; } const std::string& conditionToString() const { static const std::string labels[] = { "eq", "ne", "cs", "cc", "mi", "pl", "vs", "vc", "hi", "ls", "ge", "lt", "gt", "le", "", "uc" }; return labels[(int)condition]; } std::string regToString(const Register &r) const { return "r" + std::to_string((int)r.armreg) + ":" + std::to_string(r.version); } std::string shiftToString(ShiftOp type, u32 value) const { switch (type) { case LSL: return "LSL"; case LSR: return "LSR"; case ASR: return "ASR"; case ROR: return value == 0 ? "RRX" : "ROR"; default: return ""; } } std::string operandToString(const Operand& op) const { std::string s; if (op.isImmediate()) s = "#" + std::to_string(op.getImmediate()); else if (op.isReg()) s = regToString(op.getReg()); if (op.shift_type != LSL || op.shift_value != 0) { s += ", " + shiftToString(op.shift_type, op.shift_value); if (op.shift_imm) s += " #" + std::to_string(op.shift_value); else s += " " + regToString(op.shift_reg); } return s; } std::string toString() const { static const std::string labels[] = { "and", "eor", "sub", "rsb", "add", "adc", "sbc", "rsc", "tst", "teq", "cmp", "cmn", "orr", "mov", "bic", "mvn", "ldr", "str", "b", "bl", "msr", "mrs", "(fallback)", }; std::string s = labels[(int)op_type]; if (op_type <= MVN) { if (!isCompOp() && (flags & OP_SETS_FLAGS)) s += "s"; s += conditionToString(); if (rd.isReg()) s += " " + regToString(rd.getReg()) + ", "; else s += " "; if (!arg[0].isNone()) { s += operandToString(arg[0]); if (!arg[1].isNone()) { s += ", " + operandToString(arg[1]); if (!arg[2].isNone()) s += ", " + operandToString(arg[2]); } } } else if (op_type <= STR) { if (byte_xfer) s += "b"; s += conditionToString() + " "; if (rd.isReg()) s += regToString(rd.getReg()) + ", "; if (arg[2].isReg()) s += regToString(arg[2].getReg()) + ", "; s += "[" + operandToString(arg[0]); if (!pre_index) s += "]"; if (!arg[1].isNone()) s += ", " + operandToString(arg[1]); if (pre_index) s += "]"; if (write_back) s += "!"; } else if (op_type <= BL) { s += conditionToString() + " " + operandToString(arg[0]); } else if (op_type == MSR) { s += conditionToString() + " CPSR, " + operandToString(arg[0]); } else if (op_type == MRS) s += conditionToString() + " " + operandToString(rd) + ", CPSR"; return s; } }; template class ArmRegAlloc { struct RegAlloc { int host_reg = -1; u16 version = 0; bool dirty = false; bool temporary = false; }; std::array allocs; std::vector host_regs; const std::vector& block_ops; void allocReg(const ArmOp::Register& reg, bool write, bool temporary, u32 opidx) { RegAlloc& alloc = allocs[(int)reg.armreg]; if (alloc.host_reg == -1 || (alloc.version != reg.version && !write)) { if (host_regs.empty()) { // Need to flush a reg Arm7Reg bestReg = (Arm7Reg)-1; int bestRegUse = -1; for (u32 i = 0; i < allocs.size(); i++) { auto& alloc = allocs[i]; if (alloc.host_reg == -1) continue; if (!alloc.dirty) { int nextUse_ = nextUse((Arm7Reg)i, alloc.version, opidx); if (nextUse_ == -1) { host_regs.push_back(alloc.host_reg); alloc.host_reg = -1; break; } if (nextUse_ != (int)opidx && nextUse_ > bestRegUse) { bestRegUse = nextUse_; bestReg = (Arm7Reg)i; } } } if (host_regs.empty()) { if (bestReg == (Arm7Reg)-1) { for (u32 i = 0; i < allocs.size(); i++) { auto& alloc = allocs[i]; if (alloc.host_reg == -1) continue; int nextUse_ = nextUse((Arm7Reg)i, alloc.version, opidx); if (nextUse_ == -1) { bestReg = (Arm7Reg)i; break; } if (nextUse_ != (int)opidx && nextUse_ > bestRegUse) { bestRegUse = nextUse_; bestReg = (Arm7Reg)i; } } verify(bestReg != (Arm7Reg)-1); DEBUG_LOG(AICA_ARM, "Flushing dirty register r%d", bestReg); } flushReg(allocs[bestReg]); } verify(!host_regs.empty()); } alloc.host_reg = host_regs.back(); host_regs.pop_back(); alloc.version = reg.version; alloc.dirty = write; alloc.temporary = temporary; if (!write) static_cast(this)->LoadReg(alloc.host_reg, reg.armreg); } if (write) { alloc.dirty = true; alloc.version = reg.version; alloc.temporary = temporary; } } bool needsWriteback(const ArmOp::Register& reg, u32 opidx) { for (auto it = block_ops.begin() + opidx; it != block_ops.end(); it++) { if (it->op_type == ArmOp::FALLBACK) // assume the value is being used return true; if (it->rd.isReg() && it->rd.getReg().armreg == reg.armreg) { if (it->condition == ArmOp::AL) // register is overwritten so it's not used return false; else // might be overwritten but we don't know so write it back return true; } } // assume the value will be used return true; } // Returns the index of the next op that uses the given reg version. // returns -1 if not used in the remainder of the block or if a fallback is found int nextUse(Arm7Reg reg, int version, u32 opidx) { auto get_index_or_max = [version](const ArmOp::Register& opreg, int idx) { if (opreg.version == version) return idx; else return -1; }; for (auto it = block_ops.begin() + opidx; it != block_ops.end(); it++, opidx++) { if (it->op_type == ArmOp::FALLBACK) return -1; for (const auto& arg : it->arg) { if (arg.isReg() && arg.getReg().armreg == reg) return get_index_or_max(arg.getReg(), opidx); if (!arg.shift_imm && arg.shift_reg.armreg == reg) return get_index_or_max(arg.shift_reg, opidx); } if (it->rd.isReg() && it->rd.getReg().armreg == reg) return -1; } return -1; } void flushReg(RegAlloc& alloc) { if (alloc.dirty) { static_cast(this)->StoreReg(alloc.host_reg, (Arm7Reg)(&alloc - &allocs.front())); alloc.dirty = false; } host_regs.push_back(alloc.host_reg); alloc.host_reg = -1; } void flushAllRegs() { for (auto& alloc : allocs) if (alloc.host_reg != -1) flushReg(alloc); } public: ArmRegAlloc(const std::vector& block_ops) : block_ops(block_ops) { host_regs.clear(); for (int i = 0; i < max_regs; i++) host_regs.push_back(i); } void load(u32 opidx) { const ArmOp& op = block_ops[opidx]; if (op.op_type == ArmOp::FALLBACK) flushAllRegs(); else { bool conditional = op.condition != ArmOp::AL; for (const auto& arg : op.arg) { if (arg.isReg()) allocReg(arg.getReg(), false, conditional, opidx); if (!arg.shift_imm) allocReg(arg.shift_reg, false, conditional, opidx); } if (op.rd.isReg()) allocReg(op.rd.getReg(), true, conditional, opidx); } } void store(u32 opidx) { const ArmOp& op = block_ops[opidx]; if (op.op_type == ArmOp::FALLBACK) return; if (op.condition != ArmOp::AL) { for (auto& alloc : allocs) if (alloc.host_reg != -1 && alloc.temporary) flushReg(alloc); } else if (op.rd.isReg() && needsWriteback(op.rd.getReg(), opidx + 1)) { RegAlloc& alloc = allocs[(int)op.rd.getReg().armreg]; static_cast(this)->StoreReg(alloc.host_reg, op.rd.getReg().armreg); alloc.dirty = false; } } protected: int map(Arm7Reg r) { return allocs[(int)r].host_reg; } }; namespace recompiler { void init(); void flush(); void compile(); void *getMemOp(bool load, bool byte); template void DYNACALL MSR_do(u32 v); void DYNACALL interpret(u32 opcode); extern u8* icPtr; extern u8* ICache; const u32 ICacheSize = 1024 * 1024 * 4; static inline void *currentCode() { return icPtr; } static inline u32 spaceLeft() { return ICacheSize - (icPtr - ICache); } static inline bool empty() { return icPtr == ICache; } static inline void advance(u32 size) { icPtr += size; } extern ptrdiff_t rx_offset; static inline void *writeToExec(void * addr) { return (char *)addr + rx_offset; } static inline void *execToWrite(void * addr) { return (char *)addr - rx_offset; } } void arm7backend_compile(const std::vector& block_ops, u32 cycles); void arm7backend_flush(); extern void (*arm_compilecode)(); using arm_mainloop_t = void (*)(reg_pair *arm_regs, void (*entrypoints[])()); extern arm_mainloop_t arm_mainloop; }