# `mfspr` — Move from Special-Purpose Register > **Category:** [Control / CR / SPR](../categories/control.md) · **Form:** [XFX](../forms/XFX.md) · **Opcode:** `0x7c0002a6` ## Assembler Mnemonics | Mnemonic | XML entry | Flags | Description | | --- | --- | --- | --- | | `mfspr` | `mfspr` | — | Move from Special-Purpose Register | ## Syntax ```asm mfspr [RD], [SPR] ``` ## Encoding ### `mfspr` — form `XFX` - **Opcode word:** `0x7c0002a6` - **Primary opcode (bits 0–5):** `31` - **Extended opcode:** `339` - **Synchronising:** no | Bits | Field | Meaning | | --- | --- | --- | | 0–5 | `OPCD` | primary opcode (31) | | 6–10 | `RT` | destination / source GPR | | 11–20 | `spr/tbr/FXM` | SPR/TBR number (byte-swapped halves) or CR field mask | | 21–30 | `XO` | extended opcode | | 31 | `—` | reserved | ## Operands | Field | Role | Description | | --- | --- | --- | | `SPR` | mfspr: read | Special-Purpose-Register number. Encoded with the two 5-bit halves swapped (bits 11-15 become the high half, bits 16-20 the low half). | | `RD` | mfspr: write | Destination GPR. | ## Register Effects ### `mfspr` - **Reads (always):** `SPR` - **Reads (conditional):** _none_ - **Writes (always):** `RD` - **Writes (conditional):** _none_ ## Status-Register Effects _No condition-register or status-register effects._ ## Operation (pseudocode) ``` n <- spr_number(SPR) ; SPR field has its two 5-bit halves swapped RT <- SPR(n) ``` ## C Translation Example ```c /* mfspr RT, SPR — SPR field has swapped halves */ uint32_t n = ((insn.SPR & 0x1F) << 5) | ((insn.SPR >> 5) & 0x1F); switch (n) { case 1: r[insn.RT] = xer_pack(); break; /* XER */ case 8: r[insn.RT] = lr; break; /* LR */ case 9: r[insn.RT] = ctr; break; /* CTR */ case 256: r[insn.RT] = vrsave; break; /* VRSAVE*/ case 268: r[insn.RT] = tb & 0xFFFFFFFFu; break; /* TBL */ case 269: r[insn.RT] = tb >> 32; break; /* TBU */ default: r[insn.RT] = 0; break; } ``` ## Implementation References **`mfspr`** - Canary XML: [`tools/ppc-instructions.xml` — search for `mnem="mfspr"`](https://github.com/xenia-canary/xenia-canary/blob/f21ebd49e979e44f081f474df78c3fbfee9cb3f2/tools/ppc-instructions.xml) - Canary emitter: [`src/xenia/cpu/ppc/ppc_emit_control.cc:668`](https://github.com/xenia-canary/xenia-canary/blob/f21ebd49e979e44f081f474df78c3fbfee9cb3f2/src/xenia/cpu/ppc/ppc_emit_control.cc#L668) - Sylpheed opcode: [`crates/sylpheed-ppc/src/opcode.rs:174`](../../../crates/sylpheed-ppc/src/opcode.rs#L174) - Sylpheed decoder: [`crates/sylpheed-ppc/src/decoder.rs:914`](../../../crates/sylpheed-ppc/src/decoder.rs#L914)
Canary emitter (frozen snapshot @ f21ebd49e9) ```cpp int InstrEmit_mfspr(PPCHIRBuilder& f, const InstrData& i) { // n <- spr[5:9] || spr[0:4] // if length(SPR(n)) = 64 then // RT <- SPR(n) // else // RT <- i32.0 || SPR(n) Value* v; const uint32_t n = ((i.XFX.spr & 0x1F) << 5) | ((i.XFX.spr >> 5) & 0x1F); switch (n) { case 1: // XER v = f.LoadXER(); break; case 8: // LR v = f.LoadLR(); break; case 9: // CTR v = f.LoadCTR(); break; case 256: // VRSAVE v = f.ZeroExtend(f.LoadContext(offsetof(PPCContext, vrsave), INT32_TYPE), INT64_TYPE); break; case 268: // TB v = f.LoadClock(); break; case 269: // TBU v = f.Shr(f.LoadClock(), 32); break; case 287: // [ Processor Version Register (PVR) ] // PVR is a 32 bit, read-only register within the supervisor level. // Bits 0 to 15 are the version number. // Bits 16 to 31 are the revision number. // Known Values: 0x710600?, 0x710700, 0x710800 (Corona?); // Note: Some XEXs (such as mfgbootlauncher.xex) may check for a value // that's less than 0x710700. v = f.LoadConstantUint64(cvars::pvr); break; default: XEINSTRNOTIMPLEMENTED(); return 1; } f.StoreGPR(i.XFX.RT, v); return 0; } ```
## SPR Number Encoding — the "halves swap" The 10-bit `spr` field in the XFX form is **stored in a transposed order**: the bits that software names the *high* half (bits 5..9 of the SPR number) occupy instruction bits **16..20**, and the *low* half (bits 0..4) occupies instruction bits **11..15**. Software (and this manual) always refers to the logical, unswapped SPR number. ``` decoded_spr = ((field & 0x1F) << 5) | ((field >> 5) & 0x1F) ``` So a programmer writing `mfspr RT, 8` (read LR) encodes `spr-field = 0x100` — *not* `8`. Assemblers handle this transparently; disassemblers reverse it. When writing a translator that parses raw instruction words, swap the halves explicitly. ## SPR Map (Xenon subset, with Canary's behaviour) | Decoded # | Name | Meaning | Canary behaviour | | --- | --- | --- | --- | | 1 | `XER` | Fixed-point exception register (CA / OV / SO + length field) | `LoadXER()` | | 8 | `LR` | Link register | `LoadLR()` | | 9 | `CTR` | Count register | `LoadCTR()` | | 18 | `DSISR` | Data-storage interrupt syndrome | not implemented | | 19 | `DAR` | Data-access register | not implemented | | 256 | `VRSAVE` | Vector-register save mask | zero-extended `vrsave` | | 268 | `TBL` | Time-base lower 32 bits | the full 64-bit guest clock (`LoadClock`) | | 269 | `TBU` | Time-base upper 32 bits | guest clock `>> 32` | | 272–275 | `SPRG0..3` | Software scratch registers (kernel) | not implemented | | 287 | `PVR` | Processor-version register | the `pvr` cvar (default `0x710700`) | | 1008–1009 | `HID0/1` | Hardware implementation registers | not implemented | | 1023 | `PIR` | Processor-ID register | not implemented | "Not implemented" means Canary treats the `mfspr` as an unimplemented instruction: translating it logs "Unimplemented instr" and, with the default `break_on_unimplemented_instructions`, breaks. Games rarely read unmodelled SPRs; when they do it's usually clock-skew or sanity checks. ## Special Cases & Edge Conditions - **Privilege.** Some SPRs are privileged on real hardware (MSR, HID0/1, SPRG0..3, DSISR, DAR, PIR). Xbox 360 titles run in a mixed privilege model under the hypervisor; Canary does no privilege check, and of the privileged ones it implements none. - **`LR` and `CTR` have dedicated simplified mnemonics.** Assemblers recognise `mflr RT` ≡ `mfspr RT, 8` and `mfctr RT` ≡ `mfspr RT, 9`. Similarly `mfxer RT` ≡ `mfspr RT, 1`. Disassemblers emit the simplified forms; the translation agent should map both forms to the same abstract operation. - **`mftb` vs. `mfspr TBL/TBU`.** Reading the time-base has a dedicated X-form variant [`mftb`](mftb.md) that uses a separate opcode. Post-Xbox-360 PowerISA deprecated `mfspr TBL/TBU`, but Canary accepts both. Prefer `mftb` in new translations. - **Side-effect-free.** `mfspr` has no effect on any register beyond `RT`. It can be freely reordered with non-SPR-touching instructions. - **No `Rc` / `OE`.** This is an XFX-form instruction; bit 31 is reserved (0). ## Related Instructions - [`mtspr`](mtspr.md) — the inverse; write a GPR to an SPR. - [`mftb`](mftb.md) — read time-base (preferred over `mfspr TBL/TBU`). - [`mflr`](mfspr.md), [`mfctr`](mfspr.md), [`mfxer`](mfspr.md) — simplified mnemonics of this instruction. - [`mcrxr`](mcrxr.md) — move `XER[SO..CA]` to a CR field and clear them. ## Simplified Mnemonics | Simplified | Expansion | | --- | --- | | `mfxer RT` | `mfspr RT, 1` | | `mflr RT` | `mfspr RT, 8` | | `mfctr RT` | `mfspr RT, 9` | ## IBM Reference - [AIX 7.3 — `mfspr` (Move from Special Purpose Register)](https://www.ibm.com/docs/en/aix/7.3.0?topic=set-mfspr-move-from-special-purpose-register-instruction) - [PowerISA v2.07B — SPR number table and privilege rules](https://openpowerfoundation.org/specifications/isa/)