The hand-written parts of the manual still described how the retired xenia-rs interpreter behaved: its snapshots, Rust casts and helpers. Each of those 490 statements is now either restated as what Canary's emitters and x64 backend actually do (at the pinned canary_experimental commit), or dropped where it only made sense for xenia-rs. Checking them turned up claims that were wrong, not just outdated: - VSCR[SAT] is never modelled in Canary (DID_SATURATE is a stub and mfvscr cannot see it); the pages said saturating ops set it stickily. - Canary does not implement lswi/lswx/stswi/stswx, dcbi, mtfsb0/mtfsb1, vmsum*, vmhaddshs, vupkhpx/vupklpx, and most SPRs; pages described them as working. - Traps evaluate TO in Canary; stvebx/stvehx/stvewx store one element, not 16 bytes; mtmsrd writes only EE; fres/frsqrte/vrsqrtefp precision claims and the stfs "rounds under RN / sets FPSCR" claim contradicted the spec. - Reservations are a 64 KiB block bitmap plus a value compare, not per-address tracking. Claims that neither Canary's source nor a public spec settles are marked unverified (NI at boot, vmaddcfp128 operand order, estimate bit-exactness). Generated regions are untouched; re-running the generator changes nothing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
202 lines
8.4 KiB
Markdown
202 lines
8.4 KiB
Markdown
# `mfspr` — Move from Special-Purpose Register
|
||
|
||
> **Category:** [Control / CR / SPR](../categories/control.md) · **Form:** [XFX](../forms/XFX.md) · **Opcode:** `0x7c0002a6`
|
||
|
||
<!-- GENERATED: BEGIN -->
|
||
|
||
## 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)
|
||
<details><summary>Canary emitter (frozen snapshot @ <code>f21ebd49e9</code>)</summary>
|
||
|
||
```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;
|
||
}
|
||
```
|
||
</details>
|
||
|
||
<!-- GENERATED: END -->
|
||
|
||
## 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/)
|