//---------------------------------------------------------------------------// // 1100101 |_ mov #0, r4 __ // // 11 |_ <0xb380 %5c4> / _|_ _____ ___ // // 0110 |_ 3.50 -> 3.60 | _\ \ / _ (_-< // // |_ base# + offset |_| /_\_\___/__/ // //---------------------------------------------------------------------------// // fxos/lang: Assembler language syntax // // This file defines the syntactic tools needed to read and manipulate // assembler instructions. // // The CpuRegister class is a glorified type-safe enumeration. Registers can be // named, fi. CpuRegister::R0; they can be constructed from their lowercase // name as a string, fi. CpuRegister("r0"); and they can be printed with the // .str() method. // // The Argument struct represents an argument to an instruction. This is // syntactic only; for instance Deref (@rn) does not mean that memory is // accessed, since [jmp @rn] or [ocbwb @rn] do not actually access @rn. // Constructor functions such as Argument_Deref() are provided. // // Finally, the Instruction struct represents an abstract instruction out of // context. Each Instruction object only models one particular instance of one // particular instruction, for instance [mov #14, r2] and not [mov #imm, rn]. // The rationale for this is disassembly speed and a number of simplifications // for passes; and there are less than 65'000 non-DSP instructions anyway. //--- #ifndef FXOS_LANG_H #define FXOS_LANG_H #include #include namespace FxOS { /* CPU register names, with a little meat for conversion to and from string */ class CpuRegister { public: // clang-format off enum CpuRegisterName: i8 { /* Value 0 is reserved for special purposes such as "no register" */ UNDEFINED = 0, /* Caller-saved general-purpose registers */ R0, R1, R2, R3, R4, R5, R6, R7, /* Banked general-purpose registers. fxos does not account for banking identities, these are just for naming and output. */ R0B, R1B, R2B, R3B, R4B, R5B, R6B, R7B, /* Callee-saved general-purpose registers */ R8, R9, R10, R11, R12, R13, R14, R15, /* System registers */ MACH, MACL, PR, PC, /* Control registers */ SR, SSR, SPC, GBR, VBR, DBR, SGR, }; // clang-format on CpuRegister() = default; /* Construction from CpuRegisterName */ constexpr CpuRegister(CpuRegisterName name): m_name(name) { } /* Construction from string */ CpuRegister(std::string register_name); /* Conversion to string */ std::string str() const noexcept; /* Conversion to CpuRegisterName for switch statements */ constexpr operator CpuRegisterName() noexcept { return m_name; } /* Comparison operators */ constexpr bool operator==(CpuRegister r) const { return m_name == r.m_name; } constexpr bool operator!=(CpuRegister r) const { return m_name != r.m_name; } private: CpuRegisterName m_name; }; /* Addressing modes for arguments */ struct AsmArgument { /* Various addressing modes in the language */ enum Kind : i8 { Reg, /* rn */ Deref, /* @rn */ PostInc, /* @rn+ */ PreDec, /* @-rn */ StructDeref, /* @(disp,rn) or @(disp,gbr) */ ArrayDeref, /* @(r0,rn) or @(r0,gbr) */ PcRel, /* @(disp,pc) with 4-alignment correction */ PcJump, /* pc+disp */ PcAddr, /* pc+disp (the address itself, for mova) */ Imm, /* #imm */ }; AsmArgument() = default; /* String representation */ std::string str() const; /* Addressing mode */ Kind kind; /* Base register. Valid for all modes except Imm */ CpuRegister base; /* Index register. Valid for ArrayDeref */ CpuRegister index; /* Operation size (0, 1, 2 or 4). Generally a multiplier for disp */ i8 opsize; union { /* Displacement in bytes. For StructDeref, PcRel, PcJump, and PcAddr */ int disp; /* Immediate value. Valid for Imm */ int imm; }; /* Get the PC-relative target, assuming the instruction is at the provided address, for arguments with PC-relative offsets. */ u32 getPCRelativeTarget(u32 pc, int size) const; }; /* AsmArgument constructors */ AsmArgument AsmArgument_Reg(CpuRegister base); AsmArgument AsmArgument_Deref(CpuRegister base); AsmArgument AsmArgument_PostInc(CpuRegister base); AsmArgument AsmArgument_PreDec(CpuRegister base); AsmArgument AsmArgument_StructDeref(int disp, int opsize, CpuRegister base); AsmArgument AsmArgument_ArrayDeref(CpuRegister index, CpuRegister base); AsmArgument AsmArgument_PcRel(int disp, int opsize); AsmArgument AsmArgument_PcJump(int disp); AsmArgument AsmArgument_PcAddr(int disp); AsmArgument AsmArgument_Imm(int imm); /* Assembler instruction */ struct AsmInstruction { enum Tag { IsReturn = 0x01, IsUnconditionalJump = 0x02, IsConditionalJump = 0x04, IsCall = 0x08, HasDelaySlot = 0x10, IsInvalidDelaySlot = 0x20, IsDynamicJump = 0x40, }; AsmInstruction() = default; /* Construct with one or several arguments */ AsmInstruction(char const *mnemonic); AsmInstruction(char const *mnemonic, AsmArgument arg); AsmInstruction(char const *mnemonic, AsmArgument arg1, AsmArgument arg2); /* Original opcode. Initialized to 0 when unset, which is an invalid instruction by design. */ u32 opcode; /* Operation size (0, 1, 2 or 4) */ i8 opsize; /* Number of arguments */ u8 arg_count; /* Instruction tags */ u16 tags; /* Mnemonic **without the size indicator** */ char mnemonic[12]; /* Arguments (up to 2) */ AsmArgument args[2]; //=== Instruction classes ===// /* Whether the instruction terminates the function it's in. */ bool isReturn() const { return (this->tags & Tag::IsReturn) != 0; } /* Whether the instruction is a conditional/unconditional static jump. */ bool isConditionalJump() const { return (this->tags & Tag::IsConditionalJump) != 0; } bool isUnconditionalJump() const { return (this->tags & Tag::IsUnconditionalJump) != 0; } bool isAnyStaticJump() const { int IsJump = Tag::IsConditionalJump | Tag::IsUnconditionalJump; return (this->tags & IsJump) != 0; } /* Whether the instruction jumps to a dynamic target. This does not include *calls* to dynamic targets. These jumps are always unconditional. */ bool isDynamicJump() const { return (this->tags & Tag::IsDynamicJump) != 0; } /* Whether the instruction is a function call. */ bool isCall() const { return (this->tags & Tag::IsCall) != 0; } /* Whether the instruction has a delay slot */ bool hasDelaySlot() const { return (this->tags & Tag::HasDelaySlot) != 0; } /* Wheher the instruction terminates its basic block. */ bool isBlockTerminator() const { return isAnyStaticJump() || isDynamicJump() || isReturn(); } /* Whether the instruction can be used in a delay slot. */ bool isValidDelaySlot() const { return !isBlockTerminator() && !hasDelaySlot() && (this->tags & Tag::IsInvalidDelaySlot) == 0; } //=== Instruction info ===// /* Get the PC-relative target, assuming the instruction is at the provided address, for instructions with PC-relative offsets. */ u32 getPCRelativeTarget(u32 pc) const; }; } /* namespace FxOS */ #endif /* FXOS_LANG_H */