Merge pull request 'docs(ppc-manual): check every xenia-rs claim against Canary's source' (#45) from docs/ppc-manual-canary-claims into main
Some checks failed
CI / Native — linux (push) Has been cancelled
CI / WASM — Web (push) Has been cancelled
CI / Formatting (push) Has been cancelled

Reviewed-on: #45
This commit was merged in pull request #45.
This commit is contained in:
2026-09-16 20:03:03 +00:00
254 changed files with 491 additions and 494 deletions

View File

@@ -121,9 +121,9 @@ int InstrEmit_addcx(PPCHIRBuilder& f, const InstrData& i) {
## Special Cases & Edge Conditions
- **Carry-out is mandatory.** `XER[CA]` is updated unconditionally — `addcx` exists *to* produce the carry. It seeds a multi-word add chain that continues with [`addex`](addex.md) for middle words and [`addzex`](addzex.md)/[`addmex`](addmex.md) for the final word.
- **Carry detection by overflow comparison.** Xenia computes `CA = (result < RA)` — the standard unsigned-add overflow test. Equivalent to `CA = (RA + RB) >> 64` mathematically. This is correct for the 64-bit operand width that the Xenon implements; the spec also allows a 32-bit width selected by the implementation but the 970/Xenon use 64-bit add throughout.
- **No trap on signed overflow.** `addco`/`addco.` only set `XER[OV]` and sticky `XER[SO]`; they do not raise an exception. Xenia-rs leaves the `OE` branch as a `// TODO` (see [`addx`](addx.md) for the same gap).
- **64-bit CR update on Xenon, 32-bit in xenia-rs.** The `Rc=1` CR0 compare reads `result as i32 as i64` in [`interpreter.rs:97`](https://git.mc02.dev/fabi/xenia-rs/src/commit/8401d4d5112849bf7b0f78f9d620f3620b1ce58d/crates/xenia-cpu/src/interpreter.rs); spec demands the full 64-bit signed compare. Flag this as a xenia-rs quirk if you need bit-exact behaviour.
- **Carry is computed on the low 32 bits in Canary.** `AddDidCarry` truncates both operands to `INT32` and tests `RB > ~RA`, so `CA` is the carry out of the low word rather than out of the full 64-bit add. A translator matching Canary derives `CA` from the low 32 bits; the 64-bit PowerPC definition takes the carry out of the most-significant bit.
- **No trap on signed overflow.** `addco`/`addco.` only set `XER[OV]` and sticky `XER[SO]`; they do not raise an exception. Canary does not implement the `OE` path at all — the branch is `XEINSTRNOTIMPLEMENTED()` — and on that path it does not store `CA` either (see [`addx`](addx.md)).
- **64-bit CR update on Xenon, 32-bit in Canary.** `f.UpdateCR(0, v)` truncates `v` to `INT32` before the signed compare with zero; the spec compares the full 64-bit result. Flag this if you need bit-exact 64-bit behaviour.
- **`XER[SO]` is sticky** — only `mcrxr` clears it. The `Rc=1` form folds it into `CR0[SO]`.
- **Operand aliasing is legal**, just like [`addx`](addx.md). `addc r3, r3, r3` simply doubles `r3` and records whether the result wrapped.

View File

@@ -122,9 +122,9 @@ int InstrEmit_addex(PPCHIRBuilder& f, const InstrData& i) {
## Special Cases & Edge Conditions
- **Carry-in is consumed and carry-out is produced.** `addex` is the middle link of a multi-word add chain seeded by [`addcx`](addcx.md): `RT ← RA + RB + XER[CA]`, then `XER[CA] ← carry_out`.
- **Carry-out detection handles both edges.** Xenia checks `result < ra OR (ca != 0 && result == ra)` — that second clause covers the case where adding the carry-in alone causes the result to *exactly equal* `RA` (i.e. `RB == ~0 && CA == 1`), which still constitutes overflow. The naive `result < ra` test misses it.
- **No trap on signed overflow.** `addeo`/`addeo.` only update `XER[OV]` and sticky `XER[SO]`; xenia-rs leaves the `OE` branch unimplemented.
- **64-bit CR update on Xenon, 32-bit in xenia-rs.** The `Rc=1` arm uses `result as i32 as i64`. For multi-word adds whose final word is the high 32 bits of a 64-bit value, this distinction matters; see [`addx`](addx.md).
- **Carry-out detection handles both edges.** Canary's `AddWithCarryDidCarry` (after PearPC) sets `CA` if `(a + b) <u a` *or* `(a + b + ca) <u ca` — the second clause covers the case where adding the carry-in alone wraps (`RB == ~0 && CA == 1`), which the naive `result < ra` test misses. ⚠️ It works on the low 32 bits of `RA`/`RB`, so `CA` is the 32-bit carry, not the 64-bit one.
- **No trap on signed overflow.** `addeo`/`addeo.` only update `XER[OV]` and sticky `XER[SO]`. Canary's `OE` branch is an empty stub ("TODO: Handle overflow flag"), so `OV` is never set — unlike `addme`/`addze`, `CA` is still stored.
- **64-bit CR update on Xenon, 32-bit in Canary.** `f.UpdateCR(0, v)` truncates `v` to `INT32` before the signed compare with zero. For multi-word adds whose final word is the high 32 bits of a 64-bit value, this distinction matters; see [`addx`](addx.md).
- **`XER[CA]` must be initialised** by an earlier [`addcx`](addcx.md), [`subfcx`](subfcx.md), or `mtspr` to XER. Reading stale `CA` from an unrelated instruction is the most common bug in hand-written multi-word arithmetic.
- **`XER[SO]` is sticky** until cleared by `mcrxr`; `Rc=1` copies it into `CR0[SO]`.

View File

@@ -113,7 +113,7 @@ int InstrEmit_addicx(PPCHIRBuilder& f, const InstrData& i) {
- **CR0 update is unconditional.** Unlike XO-form `Rc=1` instructions, `addic.` always updates `CR0` from the result; the `.` is part of the mnemonic itself.
- **Common idiom: `addic. rN, rN, -1`** — decrements `rN` and sets `CR0[EQ]` when it reaches zero, in a single instruction. Frequently used as a loop counter (often paired with `bne+ loop`).
- **`XER[CA]` written same as [`addic`](addic.md).** The carry-out from the unsigned 64-bit add is recorded; the `.` only adds the CR update on top.
- **64-bit CR update on Xenon, 32-bit in xenia-rs.** [`interpreter.rs:65`](https://git.mc02.dev/fabi/xenia-rs/src/commit/8401d4d5112849bf7b0f78f9d620f3620b1ce58d/crates/xenia-cpu/src/interpreter.rs) computes `result as i32 as i64`; spec demands a full 64-bit compare-to-zero. The truncation is a documented xenia-rs quirk shared with the rest of the carrying-add family.
- **64-bit CR update on Xenon, 32-bit in Canary.** `f.UpdateCR(0, v)` truncates `v` to `INT32` before the signed compare with zero; the spec demands a full 64-bit compare-to-zero. The same truncation applies across the carrying-add family.
- **`SIMM` is sign-extended** to 64 bits before the add — `addic. r3, r4, -1` adds `~0` and never sets `CR0[EQ]` unless `r4 == 1`.
## Related Instructions

View File

@@ -122,9 +122,9 @@ int InstrEmit_addmex(PPCHIRBuilder& f, const InstrData& i) {
- **No `RB` field used.** `addmex` is encoded in XO-form but ignores the `RB` slot — assemblers must still emit a value (typically zero). Disassemblers that parse a non-zero `RB` should not flag it as illegal; it is simply unused.
- **Operation is `RA + CA + (1)`**, i.e. `RA - 1 + CA`. Used to terminate a multi-word *subtract* chain when the high source word is implicitly all-ones (e.g. computing `-x` as `~x + 1` across 128 bits).
- **Carry-out predicate is `RA != 0 OR CA != 0`.** Equivalently, `CA' = NOT(RA == 0 AND CA == 0)`. Adding `1` to anything except a zero-with-no-carry produces a carry-out (no borrow needed). This terse form in xenia-rs is correct but easy to misread.
- **Overflow not implemented in xenia-rs.** The `OE=1` path is silently a no-op; spec says set `XER[OV]` if the signed result wraps.
- **64-bit CR update on Xenon, 32-bit in xenia-rs.** [`interpreter.rs:139`](https://git.mc02.dev/fabi/xenia-rs/src/commit/8401d4d5112849bf7b0f78f9d620f3620b1ce58d/crates/xenia-cpu/src/interpreter.rs) — same quirk as the rest of the add family.
- **Carry-out predicate is `RA != 0 OR CA != 0`.** Equivalently, `CA' = NOT(RA == 0 AND CA == 0)`: adding `1` to anything except a zero-with-no-carry produces a carry-out. Canary evaluates it on the low word (`AddWithCarryDidCarry` truncates to `INT32`), so there the predicate is `RA[32:63] != 0 OR CA != 0`.
- **Overflow not implemented in Canary.** The `OE=1` branch is `XEINSTRNOTIMPLEMENTED()` and does not store `CA` either; spec says set `XER[OV]` if the signed result wraps.
- **64-bit CR update on Xenon, 32-bit in Canary** — `f.UpdateCR(0, v)`, the same `INT32` truncation as the rest of the add family.
- **`XER[CA]` must be initialised** by an earlier carrying instruction. `addme` is a *terminator*, not a seed.
## Related Instructions

View File

@@ -124,8 +124,8 @@ if Rc then
- **No trap on overflow.** `addo` / `addo.` record overflow in `XER[OV]` and sticky-set `XER[SO]`. A trap can only be produced by a separate `td`/`tw` instruction examining the result.
- **Signed-overflow predicate.** Overflow occurs iff both addends share a sign bit and the result has the opposite sign bit: `OV = ((~(a ^ b)) & (a ^ rt)) >> 63`. Unsigned carry is *not* tracked — use [`addcx`](addcx.md) when you need `XER[CA]`.
- **`XER[SO]` is sticky.** Once set, it remains set until cleared by `mcrxr`. The `.` record forms copy it into `CR0[SO]`.
- **64-bit CR update on Xenon.** The Xbox 360 Xenon CPU is 64-bit, so `add.` compares the full 64-bit result against zero. **Xenia-rs presently truncates to 32 bits** before the CR update (`result as i32 as i64` in [`interpreter.rs:95`](https://git.mc02.dev/fabi/xenia-rs/src/commit/8401d4d5112849bf7b0f78f9d620f3620b1ce58d/crates/xenia-cpu/src/interpreter.rs)). If your translator must match xenia bit-for-bit, emit a 32-bit compare; if it must be spec-correct, emit a 64-bit compare. Most Xbox 360 object code works either way because results that overflow 32 bits are rare outside of explicit 64-bit math.
- **OE overflow detection not emulated in xenia-rs.** The `addo` / `addo.` branch in `interpreter.rs` is a `TODO` stub. A faithful translator should still emit the overflow check — titles rarely observe `XER[OV]`, but it's occasionally used by profiling / sanity-checking code paths.
- **64-bit CR update on Xenon.** The Xbox 360 Xenon CPU is 64-bit, so `add.` compares the full 64-bit result against zero. **Canary truncates to 32 bits** before the CR update (`f.UpdateCR(0, v)` compares `Truncate(v, INT32)` with zero). If your translator must match Canary bit-for-bit, emit a 32-bit compare; if it must be spec-correct, emit a 64-bit compare. Most Xbox 360 object code works either way because results that overflow 32 bits are rare outside of explicit 64-bit math.
- **OE overflow detection is not implemented in Canary.** The `addo` / `addo.` branch is `XEINSTRNOTIMPLEMENTED()`. A faithful translator should still emit the overflow check — titles rarely observe `XER[OV]`, but it's occasionally used by profiling / sanity-checking code paths.
- **Operand aliasing.** `add r3, r3, r3`, `add r3, r3, r4`, `add r3, r4, r3` are all legal. The addition reads both source operands before writing `RT`.
- **No immediate form.** For `RT = RA + imm` use [`addi`](addi.md) / [`addis`](addis.md). Those are distinct opcodes, not a flag on `add`.

View File

@@ -123,8 +123,8 @@ int InstrEmit_addzex(PPCHIRBuilder& f, const InstrData& i) {
- **No `RB` field used.** Like [`addmex`](addmex.md), this XO-form instruction ignores the `RB` slot. Assemblers emit zero there.
- **Operation is `RA + 0 + CA``RA + CA`.** Used to terminate the *high word* of a multi-word add chain seeded by [`addcx`](addcx.md). After the low-word `addc` produces the carry, all middle words use [`addex`](addex.md), and the final word that has no register operand uses `addze`.
- **Carry-out is the simple unsigned overflow test** `result < ra` — same predicate as [`addcx`](addcx.md). `CA' = 1` only if `RA == ~0 && CA == 1`.
- **`OE=1` not implemented in xenia-rs.** The interpreter has no overflow branch at all; spec asks for the standard signed-overflow detect.
- **64-bit CR update on Xenon, 32-bit in xenia-rs** (truncation in [`interpreter.rs:128`](https://git.mc02.dev/fabi/xenia-rs/src/commit/8401d4d5112849bf7b0f78f9d620f3620b1ce58d/crates/xenia-cpu/src/interpreter.rs) — see [`addx`](addx.md) for context).
- **`OE=1` not implemented in Canary.** The branch is `XEINSTRNOTIMPLEMENTED()` and does not store `CA`; spec asks for the standard signed-overflow detect.
- **64-bit CR update on Xenon, 32-bit in Canary** (`f.UpdateCR(0, v)` — see [`addx`](addx.md) for context).
- **Common idiom: extracting a carry as a 0/1.** `addze rT, 0` (or `addze rT, rN` where `rN == 0`) materialises `XER[CA]` into `rT` as a plain integer.
## Related Instructions

View File

@@ -109,8 +109,8 @@ int InstrEmit_andcx(PPCHIRBuilder& f, const InstrData& i) {
- **Common idiom: `andc r3, r3, r3`** zeroes `r3` (every bit ANDed with its own complement). Cheaper-looking than `xor r3, r3, r3` on some pipelines but functionally identical; the assembler often prefers the `xor` idiom.
- **Operand convention is the X-form one** (`RA` is the destination, `RS` and `RB` are sources). Same gotcha as [`andx`](andx.md).
- **No `OE`/`XER` side effects.** Only `CR0` is updated when `Rc=1`.
- **64-bit operation** on Xenon; the AND is computed across all 64 bits of `RS` and `~RB`. Xenia-rs uses Rust's bitwise `!` on `u64`, which is the correct full-width complement.
- **64-bit CR update on Xenon, 32-bit in xenia-rs.** [`interpreter.rs:352`](https://git.mc02.dev/fabi/xenia-rs/src/commit/8401d4d5112849bf7b0f78f9d620f3620b1ce58d/crates/xenia-cpu/src/interpreter.rs) — same truncation pattern.
- **64-bit operation** on Xenon; the AND is computed across all 64 bits of `RS` and `~RB`. Canary emits a single full-width `f.AndNot(RS, RB)`.
- **64-bit CR update on Xenon, 32-bit in Canary** — `f.UpdateCR(0, ra)`, the same `INT32` truncation.
## Related Instructions

View File

@@ -111,7 +111,7 @@ int InstrEmit_andisx(PPCHIRBuilder& f, const InstrData& i) {
- **Together with `andi.` covers the entire low 32 bits.** Any 32-bit mask can be applied with `andis. + andi.` (two instructions). Larger masks need `rlwinm` or a constructed register operand to [`andx`](andx.md).
- **High 32 bits of result are always zero.** Because the immediate is at bits 3247, no information from `RS[0:31]` survives. Useful as a quick "extract bits 3247, zero the rest" primitive.
- **CR0 update is unconditional** and uses the standard signed-compare-to-zero semantics with `XER[SO]` folded into `SO`.
- **64-bit CR update on Xenon, 32-bit in xenia-rs.** The `result as i32 as i64` truncation in [`interpreter.rs:326`](https://git.mc02.dev/fabi/xenia-rs/src/commit/8401d4d5112849bf7b0f78f9d620f3620b1ce58d/crates/xenia-cpu/src/interpreter.rs) is harmless: the result is bounded by `0x00000000_FFFF0000`, which fits the 32-bit window exactly.
- **64-bit CR update on Xenon, 32-bit in Canary — and here it matters.** The result can reach `0x00000000_FFFF0000`: positive as a 64-bit value, but negative once Canary's `f.UpdateCR(0, v)` truncates it to `INT32`. With bit 32 of the result set (e.g. `0x80000000`), the spec's `CR0` is `GT` while Canary's is `LT`.
## Related Instructions

View File

@@ -110,7 +110,7 @@ int InstrEmit_andix(PPCHIRBuilder& f, const InstrData& i) {
- **Cannot mask the high half of a register in one instruction.** The immediate covers bits 4863 only; for higher bits use [`andisx`](andisx.md) (covers bits 3247) or compose with `rlwinm`/`rldicl`.
- **CR0 update is unconditional.** This is part of the encoding, not a flag — the primary opcode (28) *is* `andi.`.
- **Common idiom: `andi. r0, rN, mask`** to test bits without disturbing the source — but note `r0` is overwritten and `CR0` is set. If you only need the CR result, prefer `extrwi`/`rlwinm.` for arbitrary masks.
- **64-bit CR update on Xenon, 32-bit in xenia-rs.** Since the AND result has zeros in bits 047, the low-32 truncation in [`interpreter.rs:321`](https://git.mc02.dev/fabi/xenia-rs/src/commit/8401d4d5112849bf7b0f78f9d620f3620b1ce58d/crates/xenia-cpu/src/interpreter.rs) is harmless here — the result fits in 16 bits, so spec and xenia agree.
- **64-bit CR update on Xenon, 32-bit in Canary.** Since the AND result has zeros in bits 047, Canary's `INT32` truncation is harmless here — the result fits in 16 bits, so spec and Canary agree.
## Related Instructions

View File

@@ -108,7 +108,7 @@ int InstrEmit_andx(PPCHIRBuilder& f, const InstrData& i) {
- **Operand convention is reversed.** Unlike the arithmetic XO-form (`add RT, RA, RB`), the logical X-form writes `RA` and reads `RS`/`RB`: `and RA, RS, RB`. The destination is the **second** operand encoded. This convention applies to the entire and/or/xor family; mixing them up is a frequent disassembly error.
- **No `OE`, no `XER[CA]`, no `XER[OV]`.** Logical operations never affect XER. Only `Rc=1` updates `CR0` (signed compare against zero, with `SO ← XER[SO]`).
- **64-bit AND on Xenon.** Both inputs are 64-bit GPRs; the result is the bitwise AND of all 64 bits.
- **64-bit CR update on Xenon, 32-bit in xenia-rs.** The interpreter's `Rc=1` path in [`interpreter.rs:347`](https://git.mc02.dev/fabi/xenia-rs/src/commit/8401d4d5112849bf7b0f78f9d620f3620b1ce58d/crates/xenia-cpu/src/interpreter.rs) compares `result as i32 as i64`. For an AND whose high 32 bits are non-zero but low 32 bits are zero (e.g. `r3 = 0x1_0000_0000`, `and. r4, r3, r3`), spec sets CR0 to GT but xenia would set EQ. Flag this if reproducing CR-sensitive behaviour.
- **64-bit CR update on Xenon, 32-bit in Canary.** `f.UpdateCR(0, v)` compares the low 32 bits. For an AND whose high 32 bits are non-zero but low 32 bits are zero (e.g. `r3 = 0x1_0000_0000`, `and. r4, r3, r3`), spec sets CR0 to `GT` but Canary sets `EQ`. Flag this if reproducing CR-sensitive behaviour.
- **Operand aliasing.** `and RA, RA, RA` is a no-op except for the optional CR0 update — this is the canonical "test register against zero" pattern when no `cmpwi` is desired (though `cmpwi` is more typical).
- **No simplified mnemonic for AND-immediate.** Use [`andix`](andix.md) (`andi.`) or [`andisx`](andisx.md) (`andis.`) for immediate operands; both are *always* record forms (no plain `andi`).

View File

@@ -143,7 +143,7 @@ CR[BF] <- { LT: a <s b, GT: a >s b, EQ: a = b, SO: XER[SO] } ; signed
- **SO is always copied from `XER[SO]`.** This makes overflow observable across arithmetic/compare sequences: an `addo.` followed by `beq` can branch on the record-form flag while `bso` can inspect the sticky overflow.
- **`cr0` is the default for record-form ALU**; by convention assemblers and generators reserve `cr0` for the chain of `Rc=1` instructions and use `cr1..cr7` (or `cmp` to an explicit field) for standalone compares. Don't assume `cmp` writes `cr0` unless the `BF` operand says so.
- **No register is written** beyond the 4-bit CR field. `cmp` has no `Rc` or `OE` bit.
- **Xenia-rs quirk.** The interpreter recomputes `EQ` after the signed compare to guard against a subtract-cancellation edge case; this is a defensive belt-and-braces against the 32-bit narrowing path. Functionally equivalent to the spec.
- **Canary is one direct compare.** `L` selects either the full 64-bit operands or both truncated to `INT32`, then `f.UpdateCR(BF, lhs, rhs)` sets `LT`/`GT`/`EQ` from a single signed comparison. ⚠️ It never writes the field's `SO` bit — spec copies `XER[SO]` into it — so Canary leaves `SO` holding whatever was there before.
## Related Instructions

View File

@@ -127,7 +127,7 @@ int InstrEmit_cmpi(PPCHIRBuilder& f, const InstrData& i) {
- **Simplified mnemonics dominate disassembly.** `cmpwi crN, RA, SIMM``cmpi crN, 0, RA, SIMM` and `cmpdi crN, RA, SIMM``cmpi crN, 1, RA, SIMM`. The default CR field is `cr0` if omitted.
- **`BF` is a CR field (07), not a bit.** Same convention as [`cmp`](cmp.md). Distinct standalone compares should target `cr1..cr7` to avoid clobbering the implicit `cr0` chain set up by `Rc=1` arithmetic.
- **SO is copied from `XER[SO]`.** This makes overflow observable downstream of an `addo.` / `mulo.` etc. via `bso`/`bns`.
- **Xenia-rs quirk.** The interpreter recomputes `EQ` after the signed subtract, defending against the same 32-bit narrowing edge case noted in [`cmp`](cmp.md). Functionally equivalent to spec.
- **Canary is one direct compare.** As in [`cmp`](cmp.md): the operand and `EXTS(SI)` are compared at 64 or 32 bits by `L`, via `f.UpdateCR(BF, lhs, rhs)`, which does not write the field's `SO` bit (spec copies `XER[SO]`).
- **No register written** other than the 4-bit CR field — there is no `Rc` or `OE` bit.
## Related Instructions

View File

@@ -126,7 +126,7 @@ int InstrEmit_cmpl(PPCHIRBuilder& f, const InstrData& i) {
## Special Cases & Edge Conditions
- **Unsigned compare.** Treats both operands as unsigned magnitudes. The simplified mnemonics are `cmplw` (`L=0`) and `cmpld` (`L=1`).
- **`L = 0`: 32-bit operands.** Xenia narrows both registers via `as u32 as u64` so the high 32 bits of `RA`/`RB` are ignored — this matches spec `(RA)[32:63]` semantics. Most Xbox 360 code uses this mode.
- **`L = 0`: 32-bit operands.** Canary truncates both registers to 32 bits before the unsigned compare, so the high 32 bits of `RA`/`RB` are ignored — this matches spec `(RA)[32:63]` semantics. Most Xbox 360 code uses this mode.
- **`L = 1`: full 64-bit unsigned compare.** Used in 64-bit pointer arithmetic; rare in game code but appears in kernel-side helpers.
- **SO is copied from `XER[SO]`.** `cmpl` does not clear or set sticky overflow; it just exposes the current `SO` in the destination CR field's `SO` slot.
- **`BF` is a CR field 07.** Same convention as [`cmp`](cmp.md). Two consecutive `cmpl` instructions with the same `BF` simply overwrite the previous result.

View File

@@ -114,7 +114,7 @@ int InstrEmit_cntlzdx(PPCHIRBuilder& f, const InstrData& i) {
- **Counts across the full 64 bits.** Use [`cntlzwx`](cntlzwx.md) when you only want to count the low 32 bits.
- **Useful as `floor(log2(x)) = 63 cntlzd(x)`** for nonzero `x`. Frequently used in fast normalization, priority encoders, and bit-vector operations.
- **`RB` field unused.** This is X-form but only `RS` is read; `RB` is a placeholder slot.
- **`Rc=1` quirk.** `update_cr_signed(0, RA as i64)` is correct in xenia-rs because the result fits in 7 bits and is non-negative. The CR0 result will always be `EQ` (when `RS != 0` and `RA != 0`? — actually `EQ` only when `RS[0] == 1`, i.e. `RA == 0`) or `GT` (when `RS != 0` so `RA > 0`); never `LT`. `EQ` corresponds to "high bit set in `RS`", a useful one-instruction sign test for negative-as-signed values.
- **`Rc=1` CR0 is never `LT`.** The count is 064, so Canary's `INT32`-truncating `f.UpdateCR(0, v)` loses nothing: `EQ` exactly when `RS[0] == 1` (count 0), otherwise `GT`. `EQ` therefore means "high bit set in `RS`" — a one-instruction sign test for negative-as-signed values.
- **No `XER` side effects.** Counts neither overflow nor carry.
## Related Instructions

View File

@@ -114,7 +114,7 @@ int InstrEmit_cntlzwx(PPCHIRBuilder& f, const InstrData& i) {
- **`RA = 32` when the low 32 bits are zero**, regardless of the high 32 bits. A common pitfall: `cntlzw` after computing a 64-bit value can give a counter-intuitive result when the leading 1-bit lives in the high half.
- **`RA = 0` when bit 32 (the sign bit of the low word) is set.** This makes `cntlzw RA, RS; cmpwi RA, 0` a one-instruction-pair "is the low half negative" test, though `srawi RA, RS, 31` is more idiomatic.
- **High 32 bits of the result are zero.** `RA[0:31] = 0`, `RA[32:63] = count`.
- **`Rc=1` CR0 update is small-positive-only.** Result fits in 6 bits; xenia's `as i32 as i64` truncation in [`interpreter.rs:404`](https://git.mc02.dev/fabi/xenia-rs/src/commit/8401d4d5112849bf7b0f78f9d620f3620b1ce58d/crates/xenia-cpu/src/interpreter.rs) is harmless. CR0 will be `EQ` only when `RS[32]` (sign bit of low word) is 1, otherwise `GT`.
- **`Rc=1` CR0 update is small-positive-only.** The result fits in 6 bits, so Canary's `INT32` truncation in `f.UpdateCR(0, v)` is harmless. CR0 is `EQ` only when `RS[32]` (sign bit of the low word) is 1, otherwise `GT`.
- **Useful for `floor(log2)` of a 32-bit value.** `31 - cntlzw(x)` for nonzero `x`.
## Related Instructions

View File

@@ -124,10 +124,10 @@ int InstrEmit_divdux(PPCHIRBuilder& f, const InstrData& i) {
## Special Cases & Edge Conditions
- **Single undefined case.** Division by zero (`RB == 0`). There is no `INT_MIN/1` overflow because both operands are unsigned. Xenia-rs returns 0 for the divide-by-zero case ([`interpreter.rs:306`](https://git.mc02.dev/fabi/xenia-rs/src/commit/8401d4d5112849bf7b0f78f9d620f3620b1ce58d/crates/xenia-cpu/src/interpreter.rs)); spec leaves `RT` boundedly undefined.
- **Single undefined case.** Division by zero (`RB == 0`). There is no `INT_MIN/1` overflow because both operands are unsigned. Canary's emitter does not check for zero (a `TODO`); on the x64 backend `DIV` pre-zeroes the result and skips the divide, so `RT = 0`. Spec leaves `RT` boundedly undefined.
- **No trap on Xenon.** As with [`divdx`](divdx.md), the processor does not raise an exception; consuming code must guard `RB` first (typically `cmpdi rb, 0; beq skip`).
- **`OE=1` should set `XER[OV]`** on `RB == 0`; xenia-rs ignores `OE` here.
- **`Rc=1` CR0 update is correctly 64-bit.** [`interpreter.rs:311`](https://git.mc02.dev/fabi/xenia-rs/src/commit/8401d4d5112849bf7b0f78f9d620f3620b1ce58d/crates/xenia-cpu/src/interpreter.rs) uses `as i64` directly, so the CR0 sign comparison reflects the full 64-bit unsigned quotient cast to signed. For very large unsigned quotients (`> INT64_MAX`) this CR0 will report `LT` even though the unsigned interpretation is positive — a rare but real source of CR-misuse bugs.
- **`OE=1` should set `XER[OV]`** on `RB == 0`; Canary's `OE` branch is `XEINSTRNOTIMPLEMENTED()`.
- **`Rc=1` CR0 update is 32-bit in Canary.** `f.UpdateCR(0, v)` compares the low 32 bits of the 64-bit unsigned quotient as a signed value: a quotient with bit 32 set reads `LT`, and one whose low word is zero reads `EQ` even if its high word is not. Spec compares the full 64-bit value.
- **Slow.** Same ~70-cycle non-pipelined cost as the signed variant; consider reciprocal multiply for hot loops.
- **Truncating quotient.** Same C-style toward-zero rounding (trivially equal to floor for unsigned).

View File

@@ -124,10 +124,10 @@ int InstrEmit_divdx(PPCHIRBuilder& f, const InstrData& i) {
## Special Cases & Edge Conditions
- **Two undefined-behaviour cases.** Division by zero (`RB == 0`) and signed-min divided by negative-one (`RA == INT64_MIN && RB == -1`, which would mathematically produce `2^63`, unrepresentable in `i64`). PowerISA leaves `RT` *boundedly undefined* in both cases; **xenia-rs returns 0** ([`interpreter.rs:293`](https://git.mc02.dev/fabi/xenia-rs/src/commit/8401d4d5112849bf7b0f78f9d620f3620b1ce58d/crates/xenia-cpu/src/interpreter.rs)). Matching this behaviour bit-for-bit is a defacto-spec on Xenon.
- **Two undefined-behaviour cases.** Division by zero (`RB == 0`) and signed-min divided by negative-one (`RA == INT64_MIN && RB == -1`, which would mathematically produce `2^63`, unrepresentable in `i64`). PowerISA leaves `RT` *boundedly undefined* in both cases. Canary's emitter checks neither; its x64 backend skips the divide in both and yields `RT = 0`, so a translator matching Canary returns 0.
- **No exception raised.** Xenon does not trap on either undefined case; the consuming code is expected to have validated `RB` first, e.g. with `cmpdi`/`bne`. If you need a trap, follow the divide with [`tw`](../control/tw.md)/`twi` (these live outside the ALU page set).
- **`OE=1` should set `XER[OV]`** for both undefined cases plus any operand triggering overflow; xenia-rs does not implement the `OE` branch.
- **`Rc=1` CR0 update is correctly 64-bit here.** Unlike most ALU pages, [`interpreter.rs:298`](https://git.mc02.dev/fabi/xenia-rs/src/commit/8401d4d5112849bf7b0f78f9d620f3620b1ce58d/crates/xenia-cpu/src/interpreter.rs) uses `as i64` (no `as i32` truncation) — divide is one of the few xenia-rs instructions that already matches Xenon spec for the CR0 width.
- **`OE=1` should set `XER[OV]`** for both undefined cases plus any operand triggering overflow; Canary's `OE` branch is `XEINSTRNOTIMPLEMENTED()`.
- **`Rc=1` CR0 update is 32-bit in Canary, as elsewhere.** `f.UpdateCR(0, v)` truncates the 64-bit quotient to `INT32` before the compare; spec compares all 64 bits.
- **Latency.** Integer divide is the slowest ALU instruction on Xenon — 70+ cycles, non-pipelined. Hot inner loops avoid it via reciprocal-multiply or shift; expect to see `mulhwu`-based reciprocals in optimised disassembly.
- **Rounds toward zero.** The signed quotient truncates toward zero, matching C99/C++11 `/` semantics. Use [`mulldx`](mulldx.md) and a subtract to recover the remainder; there is no `divmod` instruction.

View File

@@ -128,10 +128,10 @@ int InstrEmit_divwux(PPCHIRBuilder& f, const InstrData& i) {
## Special Cases & Edge Conditions
- **32-bit operands, zero-extended result.** Both `RA` and `RB` are read as their low 32 bits, unsigned (`as u32`); the quotient is computed as `u32`, then *zero-extended* to 64 bits. The high 32 bits of `RA`/`RB` are ignored on input and the high 32 bits of `RT` are zero on output.
- **Single undefined case.** Division by zero (`RB == 0`); xenia-rs returns 0 ([`interpreter.rs:251`](https://git.mc02.dev/fabi/xenia-rs/src/commit/8401d4d5112849bf7b0f78f9d620f3620b1ce58d/crates/xenia-cpu/src/interpreter.rs)). No `INT_MIN/-1` case because the operands are unsigned.
- **Single undefined case.** Division by zero (`RB == 0`); Canary's x64 backend skips the divide and yields `RT = 0` (the emitter itself does not check). No `INT_MIN/-1` case because the operands are unsigned.
- **No trap on Xenon.** Same as [`divdx`](divdx.md) — silent undefined result.
- **`OE=1` should set `XER[OV]` on `RB == 0`**; xenia-rs ignores this.
- **`Rc=1` CR0 update.** [`interpreter.rs:256`](https://git.mc02.dev/fabi/xenia-rs/src/commit/8401d4d5112849bf7b0f78f9d620f3620b1ce58d/crates/xenia-cpu/src/interpreter.rs) uses `as i32 as i64` — for an unsigned 32-bit quotient stored in the low 32 bits with high zeros, this matches spec exactly; the i32 view will be negative iff the unsigned quotient ≥ 2^31. Worth flagging when comparing CR0 against zero after a large `divwu`.
- **`OE=1` should set `XER[OV]` on `RB == 0`**; Canary's `OE` branch is `XEINSTRNOTIMPLEMENTED()`.
- **`Rc=1` CR0 update.** Canary zero-extends the 32-bit unsigned quotient into `RT` and `f.UpdateCR(0, v)` compares its low 32 bits as signed — so CR0 reads `LT` iff the unsigned quotient ≥ 2^31. Worth flagging when comparing CR0 against zero after a large `divwu`.
- **Truncating quotient.** Floor division for non-negative integers; matches C `unsigned` semantics.
- **Same slow non-pipelined latency** as `divw`.

View File

@@ -127,10 +127,10 @@ int InstrEmit_divwx(PPCHIRBuilder& f, const InstrData& i) {
## Special Cases & Edge Conditions
- **32-bit operands, sign-extended result.** Both `RA` and `RB` are read as their low 32 bits, signed; the quotient is computed as `i32`, then sign-extended to 64 bits before being stored in `RT`. The high 32 bits of `RA`/`RB` are *ignored*.
- **Two undefined cases:** `RB == 0` and `RA == INT32_MIN && RB == -1` (quotient `2^31` is unrepresentable). Xenia-rs returns 0 for both ([`interpreter.rs:238`](https://git.mc02.dev/fabi/xenia-rs/src/commit/8401d4d5112849bf7b0f78f9d620f3620b1ce58d/crates/xenia-cpu/src/interpreter.rs)); PowerISA leaves `RT` boundedly undefined.
- **Two undefined cases:** `RB == 0` and `RA == INT32_MIN && RB == -1` (quotient `2^31` is unrepresentable). Canary's x64 backend yields 0 for both — it skips the divide — while PowerISA leaves `RT` boundedly undefined.
- **No trap on Xenon.** Like [`divdx`](divdx.md), the processor silently produces an undefined value instead of raising an exception.
- **`OE=1` should set `XER[OV]`** in both undefined cases; xenia-rs does not implement this.
- **`Rc=1` CR0 update truncates to 32 bits in xenia-rs.** [`interpreter.rs:243`](https://git.mc02.dev/fabi/xenia-rs/src/commit/8401d4d5112849bf7b0f78f9d620f3620b1ce58d/crates/xenia-cpu/src/interpreter.rs) uses `as i32 as i64`. This is *correct* for `divw` because the result is already a sign-extended 32-bit value — high bits agree with the low-32 sign extension. So spec and xenia agree for this instruction.
- **`OE=1` should set `XER[OV]`** in both undefined cases; Canary's `OE` branch is `XEINSTRNOTIMPLEMENTED()`.
- **`Rc=1` CR0 matches spec here.** Canary stores the 32-bit quotient **zero**-extended (spec leaves `RT[0:31]` undefined), but `f.UpdateCR(0, v)` compares the low 32 bits as signed, which is exactly the sign of the 32-bit quotient.
- **Truncating quotient.** Rounds toward zero, matching C `/` for `int32_t`.
- **Slow.** Same ~30-cycle non-pipelined cost as 64-bit divide; faster than `divd` because the underlying datapath is narrower but still much slower than multiply-then-shift reciprocal sequences.

View File

@@ -98,9 +98,9 @@ int InstrEmit_eieio(PPCHIRBuilder& f, const InstrData& i) {
- **Memory-ordering barrier for caching-inhibited / guarded storage.** `eieio` ensures all preceding loads/stores to caching-inhibited or guarded memory complete before any subsequent such accesses begin. It is *weaker* than [`sync`](sync.md): it does not order cacheable storage and does not flush the store queue.
- **No register or CR effects.** Every operand field is unused; assemblers emit the canonical `0x7c0006ac` word.
- **Used at MMIO boundaries.** Driver code touching device registers (e.g. the GPU command processor on Xenon) typically pairs writes with `eieio` to enforce write ordering at the bus.
- **Xenia-rs is a no-op.** The interpreter trivially advances PC ([`interpreter.rs:1267`](https://git.mc02.dev/fabi/xenia-rs/src/commit/8401d4d5112849bf7b0f78f9d620f3620b1ce58d/crates/xenia-cpu/src/interpreter.rs)). Because xenia-rs is a single-threaded interpreter targeting userland Xbox 360 binaries — which never see real MMIO — this is correct: the host's natural program order suffices.
- **Canary emits a memory barrier, not a no-op.** `InstrEmit_eieio` is `f.MemoryBarrier()`. Userland Xbox 360 code never touches real MMIO, so for a sequential C translation the natural program order already suffices.
- **Categorised under ALU here**, but operationally it's a memory ordering primitive (xenia-canary places it in `ppc_emit_memory.cc`). Disassembly tools may bin it differently.
- **Distinct from `sync` and `isync`.** All three share xenia's no-op arm in [`interpreter.rs:1266`](https://git.mc02.dev/fabi/xenia-rs/src/commit/8401d4d5112849bf7b0f78f9d620f3620b1ce58d/crates/xenia-cpu/src/interpreter.rs); on real hardware they have very different semantics and latencies.
- **Distinct from `sync` and `isync`.** In Canary `eieio` and `sync` both emit `f.MemoryBarrier()`, while `isync` emits `f.Nop()`; on real hardware the three have very different semantics and latencies.
## Related Instructions

View File

@@ -110,7 +110,7 @@ int InstrEmit_eqvx(PPCHIRBuilder& f, const InstrData& i) {
- **Operand convention is X-form** (`RA` is destination; `RS`, `RB` are sources).
- **64-bit operation** on Xenon; `~` is full 64-bit on `u64`.
- **No `OE`, no `XER` side effects.** Only `Rc=1` updates `CR0`.
- **64-bit CR update on Xenon, 32-bit in xenia-rs.** [`interpreter.rs:382`](https://git.mc02.dev/fabi/xenia-rs/src/commit/8401d4d5112849bf7b0f78f9d620f3620b1ce58d/crates/xenia-cpu/src/interpreter.rs) truncates with `as i32 as i64`. Significant when the high 32 bits of the result differ from the low 32 — e.g. `eqv. RA, RS, RB` with `RS == 0x1_0000_0000`, `RB == 0`: spec sees `0xFFFFFFFE_FFFFFFFF` (`LT`), xenia sees `0xFFFFFFFFFFFFFFFF` (`LT`) — actually both negative here, but the *exact* CR contents differ for finer cases.
- **64-bit CR update on Xenon, 32-bit in Canary.** `f.UpdateCR(0, v)` compares only the low 32 bits, so spec and Canary disagree whenever the high word decides the sign, or the low word is zero while the high word is not.
## Related Instructions

View File

@@ -109,7 +109,7 @@ int InstrEmit_extsbx(PPCHIRBuilder& f, const InstrData& i) {
- **Sign-extends the low 8 bits of `RS` to 64 bits.** Bit 56 of `RS` (the sign bit of the byte) becomes bits 055 of `RA`; bits 5663 are copied verbatim.
- **Common after a byte load.** `lbz` zero-extends from memory; `extsb` converts the result to a signed-byte view. Many compilers emit this pair; the recent ISA `lba`/`lbau` family is *not* available on the Xenon, so this two-instruction sequence is the canonical pattern.
- **`Rc=1` updates CR0 from the full 64-bit signed value** — but xenia-rs truncates to 32 bits in [`interpreter.rs:384`](https://git.mc02.dev/fabi/xenia-rs/src/commit/8401d4d5112849bf7b0f78f9d620f3620b1ce58d/crates/xenia-cpu/src/interpreter.rs). For `extsb.` this is harmless because the result fits in 8 bits sign-extended; the low 32 bits already encode the sign correctly.
- **`Rc=1` updates CR0 from the full 64-bit signed value** — Canary truncates to 32 bits in `f.UpdateCR(0, v)`. For `extsb.` this is harmless: the result is sign-extended from 8 bits, so the low 32 bits already encode its sign and zero-ness.
- **Operand convention** is the X-form one (`RA` destination, `RS` source). Same as the rest of the logical family.
- **No `XER` side effects.**
- **`RB` field unused.** Set to 0 by assemblers; ignored on decode.

View File

@@ -109,7 +109,7 @@ int InstrEmit_extshx(PPCHIRBuilder& f, const InstrData& i) {
- **Sign-extends the low 16 bits of `RS` to 64 bits.** Bit 48 (the sign bit of the half-word) is replicated through bits 047 of `RA`.
- **Pairs with `lhz`** to convert an unsigned half-word load into a signed half-word value. Note that `lha` already does the sign extension on load — `extsh` is mostly emitted when the half-word is computed in a register first.
- **`Rc=1` CR0 update.** Xenia-rs uses `as i32 as i64` ([`interpreter.rs:389`](https://git.mc02.dev/fabi/xenia-rs/src/commit/8401d4d5112849bf7b0f78f9d620f3620b1ce58d/crates/xenia-cpu/src/interpreter.rs)) — harmless here because the sign-extended 16-bit value fits in 32 bits exactly.
- **`Rc=1` CR0 update.** Canary's `INT32` truncation in `f.UpdateCR(0, v)` is harmless here because the sign-extended 16-bit value fits in 32 bits exactly.
- **Operand convention** is the X-form one (`RA` destination, `RS` source).
- **No `XER` side effects.**
- **`RB` field unused.**

View File

@@ -109,7 +109,7 @@ int InstrEmit_extswx(PPCHIRBuilder& f, const InstrData& i) {
- **Sign-extends the low 32 bits of `RS` to 64 bits.** Bit 32 (sign bit of the word) is replicated through bits 031 of `RA`.
- **Used heavily in 32-to-64-bit promotion.** Most Xbox 360 ABI parameters are 32-bit; promoting a 32-bit `int` to a 64-bit GPR requires this instruction. Many functions emit it on entry to canonicalise their argument registers.
- **`Rc=1` CR0 update is correctly 64-bit.** [`interpreter.rs:399`](https://git.mc02.dev/fabi/xenia-rs/src/commit/8401d4d5112849bf7b0f78f9d620f3620b1ce58d/crates/xenia-cpu/src/interpreter.rs) uses `as i64` (no truncation) — one of the few xenia-rs sites where the spec width is honoured. The signed compare in CR0 reflects the full sign-extended value.
- **`Rc=1` CR0 update is harmless in Canary.** `f.UpdateCR(0, v)` truncates to `INT32`, but the result is the sign extension of its own low word, so sign and zero-ness agree with the full 64-bit compare.
- **Operand convention** is the X-form one (`RA` destination, `RS` source).
- **No `XER` side effects.**
- **`RB` field unused.**

View File

@@ -100,7 +100,7 @@ int InstrEmit_isync(PPCHIRBuilder& f, const InstrData& i) {
- **Stronger than [`sync`](sync.md) for instruction stream**, weaker for memory stream — `isync` does not order stores against later loads. It only forces a fetch refresh.
- **Common idiom: `dcbf` / `icbi` / `sync` / `isync`** — flush data cache, invalidate instruction cache, drain memory, refetch — used by JITs and self-modifying loaders.
- **No operands.** Encoded as a fixed-form `XL` instruction; assemblers always emit `0x4c00012c`.
- **Xenia-rs is a no-op.** [`interpreter.rs:1267`](https://git.mc02.dev/fabi/xenia-rs/src/commit/8401d4d5112849bf7b0f78f9d620f3620b1ce58d/crates/xenia-cpu/src/interpreter.rs) handles `sync`/`eieio`/`isync` together. Because xenia interprets in straight-line program order without any speculative instruction cache, no barrier behaviour is needed for correctness.
- **Canary emits `f.Nop()`** for `isync` (its own `XEINSTRNOTIMPLEMENTED()` is commented out), while `sync` and `eieio` emit `f.MemoryBarrier()`. A sequential C translation needs no barrier behaviour: straight-line program order already serialises.
- **Privilege level: user.** Unlike most cache management ops, `isync` is unprivileged and frequently appears in userland trampolines.
## Related Instructions

View File

@@ -113,8 +113,8 @@ int InstrEmit_mulhdux(PPCHIRBuilder& f, const InstrData& i) {
- **Returns the high 64 bits of an unsigned 64×64 product.** Operands are zero-extended (treated as unsigned) before multiplication. Pair with [`mulldx`](mulldx.md) for the low 64 bits to form a full 128-bit unsigned product.
- **No `OE` bit.** No overflow signal — the high half is a defined function of the inputs even when the product fills 128 bits.
- **Xenia uses native `u128`.** [`interpreter.rs:284`](https://git.mc02.dev/fabi/xenia-rs/src/commit/8401d4d5112849bf7b0f78f9d620f3620b1ce58d/crates/xenia-cpu/src/interpreter.rs) widens both operands then shifts. Note `as u128` *zero*-extends, in contrast to [`mulhdx`](mulhdx.md)'s `as i64 as i128` which sign-extends — this is the entire semantic difference.
- **`Rc=1` CR0 update is correctly 64-bit.** Uses `as i64` directly. Because the high half is unsigned, treating it as signed for CR0 means very large unsigned values appear `LT` — keep this in mind when interpreting the CR0 bits after `mulhdu.`.
- **Canary uses `f.MulHi(RA, RB, ARITHMETIC_UNSIGNED)`** — the high 64 bits of the 128-bit *unsigned* product. The signed sibling [`mulhdx`](mulhdx.md) is the same call without `ARITHMETIC_UNSIGNED`; that flag is the entire semantic difference.
- **`Rc=1` CR0 update is 32-bit in Canary.** `f.UpdateCR(0, v)` truncates the unsigned high half to `INT32` and compares it as signed, so a value with bit 32 set reads `LT` even though it is a large positive unsigned number. Spec compares all 64 bits (still as signed) — keep both in mind when reading CR0 after `mulhdu.`.
- **Used in reciprocal-multiply division strategies.** Compilers may strength-reduce divide-by-constant into `mulhdu` plus a shift; appears in optimised disassembly.
- **Slow.** Same multi-cycle cost as the signed variant.

View File

@@ -112,8 +112,8 @@ int InstrEmit_mulhdx(PPCHIRBuilder& f, const InstrData& i) {
- **Returns the high 64 bits of a signed 64×64 product.** Pair with [`mulldx`](mulldx.md) (which returns the low 64 bits) to obtain the full 128-bit product. Both must be issued separately; PowerPC has no fused multiply-double-wide instruction.
- **No `OE` bit.** This XO-form instruction has no overflow-enable variant — there is no "high half overflow" because the high half is always defined.
- **Xenia widens to `i128` natively.** [`interpreter.rs:275`](https://git.mc02.dev/fabi/xenia-rs/src/commit/8401d4d5112849bf7b0f78f9d620f3620b1ce58d/crates/xenia-cpu/src/interpreter.rs) does the multiply in 128 bits then extracts the high 64. The `i64 as i128` casts ensure signed extension on both sides.
- **`Rc=1` CR0 update is correctly 64-bit.** [`interpreter.rs:278`](https://git.mc02.dev/fabi/xenia-rs/src/commit/8401d4d5112849bf7b0f78f9d620f3620b1ce58d/crates/xenia-cpu/src/interpreter.rs) uses `as i64` directly. CR0 reflects the sign of the high half: `LT` if the product is negative, `GT` if positive and large enough to overflow into the high half, `EQ` if the product fits in 64 bits *signed* (so the high half is the sign-extension of the low half — but xenia's check uses raw signed-zero compare, which equates only when the high half is exactly zero, i.e. the product is in `[0, 2^63)`).
- **Canary uses `f.MulHi(RA, RB)`** (signed) — the high 64 bits of the 128-bit signed product of the two 64-bit operands.
- **`Rc=1` CR0 reflects the sign of the high half — at 32 bits in Canary.** Per spec: `LT` if the product is negative, `EQ` iff the high half is exactly zero (`0 ≤ product < 2^64`), otherwise `GT`. Canary's `f.UpdateCR(0, v)` compares only the high half's low 32 bits, so it can report `EQ` or the wrong sign when the high half's upper word is non-zero.
- **Use [`mulhdux`](mulhdux.md) for the unsigned high half.** The two instructions differ in whether the operands are sign- or zero-extended before the multiply.
- **Slow.** 64-bit multiply is multi-cycle on Xenon; combining `mulhd` with `mulld` for a full 128-bit product roughly doubles the cost.

View File

@@ -117,10 +117,10 @@ int InstrEmit_mulhwux(PPCHIRBuilder& f, const InstrData& i) {
## Special Cases & Edge Conditions
- **Inputs are the low 32 bits, zero-extended.** `RA[32:63]` and `RB[32:63]` are treated as unsigned, widened to 64-bit `u64`, multiplied; the high 32 bits of the 64-bit product land in `RT[32:63]`. Xenia masks the high 32 bits of `RT` to zero ([`interpreter.rs:231`](https://git.mc02.dev/fabi/xenia-rs/src/commit/8401d4d5112849bf7b0f78f9d620f3620b1ce58d/crates/xenia-cpu/src/interpreter.rs)).
- **Inputs are the low 32 bits, zero-extended.** `RA[32:63]` and `RB[32:63]` are treated as unsigned, widened to 64 bits and multiplied; the high 32 bits of the product land in `RT[32:63]`. Canary shifts the product *logically* (`f.Shr(…, 32)`), so `RT[0:31]` is zero.
- **Pair with [`mullwx`](mullwx.md) for the full 64-bit unsigned product.** `mullw` returns the low 32 sign-extended; for unsigned use, pair `mulhwu` with `rlwinm` to mask the low half. Xbox 360 compilers commonly emit this combination.
- **No `OE` bit.** Same family rule.
- **`Rc=1` CR0 update.** Uses `as i32 as i64` ([`interpreter.rs:234`](https://git.mc02.dev/fabi/xenia-rs/src/commit/8401d4d5112849bf7b0f78f9d620f3620b1ce58d/crates/xenia-cpu/src/interpreter.rs)). Because the result is bounded by `0xFFFFFFFF` and stored only in the low 32 bits, this CR0 will report `LT` for any unsigned high half ≥ `0x80000000` — a known signed/unsigned interpretation pitfall when `Rc=1` is used with `mulhwu`.
- **`Rc=1` CR0 update.** Canary's `f.UpdateCR(0, v)` compares the low 32 bits as signed. Since that word is the unsigned high half, CR0 reports `LT` for any high half ≥ `0x80000000` — a known signed/unsigned pitfall with `mulhwu.`.
- **Common idiom for multi-precision arithmetic.** `mulhwu` + `mullw` + `addc` chains build extended-precision multiplies entirely in 32-bit ops; useful for cryptographic code that targets the Xenon's 32-bit ABI.
- **Multi-cycle latency** like the rest of the multiply family.

View File

@@ -117,11 +117,11 @@ int InstrEmit_mulhwx(PPCHIRBuilder& f, const InstrData& i) {
## Special Cases & Edge Conditions
- **Inputs are the low 32 bits, signed-extended.** `RA[32:63]` and `RB[32:63]` are sign-extended to 64-bit signed values, multiplied, and the *high* 32 bits of the 64-bit product are returned in `RT[32:63]`. The high 32 bits of `RT` are *implementation-defined* per spec but xenia masks them to zero ([`interpreter.rs:222`](https://git.mc02.dev/fabi/xenia-rs/src/commit/8401d4d5112849bf7b0f78f9d620f3620b1ce58d/crates/xenia-cpu/src/interpreter.rs) `& 0xFFFF_FFFF`).
- **Inputs are the low 32 bits, sign-extended.** `RA[32:63]` and `RB[32:63]` are sign-extended to 64 bits and multiplied; the *high* 32 bits of the product are returned in `RT[32:63]`. Spec leaves `RT[0:31]` undefined; Canary shifts *arithmetically* (`f.Sha(…, 32)`), so `RT[0:31]` holds the sign extension of the high half.
- **Pair with [`mullwx`](mullwx.md) for the full 64-bit product.** Both can issue independently — no fused 32×32→64 instruction.
- **No `OE` bit.** Like all `mulh*` instructions, no overflow flag is produced; the high half is by definition defined.
- **Xenia-rs quirk: high 32 bits zeroed.** Because spec says they're "undefined", legitimately matching either zero, sign-extension, or garbage. Xenia chooses zero, which differs from the literal Xenon behaviour (which sign-extends in some microarchitecture cases). For game code that doesn't read those bits, the difference is invisible.
- **`Rc=1` CR0 update.** [`interpreter.rs:225`](https://git.mc02.dev/fabi/xenia-rs/src/commit/8401d4d5112849bf7b0f78f9d620f3620b1ce58d/crates/xenia-cpu/src/interpreter.rs) uses `as i32 as i64` — operates on the truncated low 32 bits, which is correct for the *defined* portion of the result.
- **`RT[0:31]` is implementation-specific.** Because spec leaves those bits undefined, zero, sign extension and garbage are all legal; Canary sign-extends (see above). Code that never reads the upper word cannot tell the difference.
- **`Rc=1` CR0 update.** Canary's `f.UpdateCR(0, v)` compares the low 32 bits — the *defined* portion of the result — so CR0 is correct whatever sits in `RT[0:31]`.
- **Multi-cycle latency.** Multiply is the slowest pipelined ALU op; `mulhw` shares the divider/multiplier unit.
## Related Instructions

View File

@@ -115,8 +115,8 @@ int InstrEmit_mulldx(PPCHIRBuilder& f, const InstrData& i) {
- **Returns the low 64 bits of a signed 64×64 product.** Equivalent to `(int64_t)(RA * RB)` modulo `2^64`. Both operands are full 64-bit signed; no truncation on input.
- **High bits silently lost.** The high 64 bits of the true product are discarded; pair with [`mulhdx`](mulhdx.md) (signed) or [`mulhdux`](mulhdux.md) (unsigned) to recover them.
- **`OE=1` should set `XER[OV]`** when the 128-bit signed product cannot be represented in 64 bits — i.e. when `mulhd RA, RB` is not the sign-extension of `mulld RA, RB`. **Xenia-rs does not implement** `OE` ([`interpreter.rs:264`](https://git.mc02.dev/fabi/xenia-rs/src/commit/8401d4d5112849bf7b0f78f9d620f3620b1ce58d/crates/xenia-cpu/src/interpreter.rs) has no `oe()` branch).
- **`Rc=1` CR0 update is correctly 64-bit.** [`interpreter.rs:269`](https://git.mc02.dev/fabi/xenia-rs/src/commit/8401d4d5112849bf7b0f78f9d620f3620b1ce58d/crates/xenia-cpu/src/interpreter.rs) uses `as i64` — full 64-bit signed compare. One of the few non-truncating CR0 sites in xenia-rs; means `mulld.` gives spec-correct CR0 even when the result has non-zero high 32 bits.
- **`OE=1` should set `XER[OV]`** when the 128-bit signed product cannot be represented in 64 bits — i.e. when `mulhd RA, RB` is not the sign-extension of `mulld RA, RB`. Canary's `OE` branch is `XEINSTRNOTIMPLEMENTED()`.
- **`Rc=1` CR0 update is 32-bit in Canary.** `f.UpdateCR(0, v)` truncates the 64-bit product to `INT32`, so `mulld.` gives a CR0 that differs from spec whenever the high 32 bits decide the sign, or the low word is zero while the high word is not.
- **Same instruction for signed and unsigned low halves.** Modular arithmetic is identical; only the high half (`mulhd` vs `mulhdu`) distinguishes the interpretations.
- **Multi-cycle latency** — slowest of the ALU pipelines after divide.

View File

@@ -99,7 +99,7 @@ int InstrEmit_mulli(PPCHIRBuilder& f, const InstrData& i) {
## Special Cases & Edge Conditions
- **64-bit operand, sign-extended 16-bit immediate.** Xenia reads the full 64-bit `RA` as `i64` and the immediate as a sign-extended `i64` ([`interpreter.rs:80-81`](https://git.mc02.dev/fabi/xenia-rs/src/commit/8401d4d5112849bf7b0f78f9d620f3620b1ce58d/crates/xenia-cpu/src/interpreter.rs)) — note this differs from the PPC pseudocode header which writes `(RA) * EXTS(SIMM)` as a 64-bit operation but other implementations sometimes treat it as 32×32. On the Xenon (and in xenia-rs), it is genuinely 64-bit.
- **64-bit operand, sign-extended 16-bit immediate.** Canary emits `f.Mul(RA, EXTS16(D))` on the full 64-bit `RA` genuinely 64-bit, not 32×32.
- **Returns the low 64 bits.** No high half is produced — equivalent to `(int64_t)RA * (int64_t)SIMM` modulo `2^64`. There is no `mulhi`-immediate instruction.
- **No `Rc`, no `OE`.** This D-form has no flag bits — strictly `RT ← RA * SIMM`. To check overflow, compare the result to `(int32_t)RA * SIMM` after the fact, or use [`mulldx`](mulldx.md) with `OE=1` after materialising the immediate.
- **Common compiler idiom.** `mulli` is heavily used for fixed-stride array indexing (`r3 *= sizeof_struct`) when the size is a small signed constant.

View File

@@ -129,8 +129,8 @@ if Rc then
- **Inputs are the low 32 bits.** `mullw` only looks at `RA[32:63]` and `RB[32:63]`; the high 32 bits of each source are ignored. This is a 32-bit × 32-bit → 64-bit signed multiply. For full 64-bit operands use [`mulldx`](mulldx.md).
- **Result is sign-extended to 64 bits.** The 64-bit product fits into a 64-bit GPR without loss. Subsequent 32-bit consumers see `RT[32:63]` (the low 32 bits of the product); use [`mulhwx`](mulhwx.md) for the signed high 32 bits or [`mulhwux`](mulhwux.md) for the unsigned high 32 bits, computed in parallel without this instruction.
- **`OE` overflow test is 32-bit.** `XER[OV]` is set iff the 64-bit signed product cannot be represented in 32 bits — equivalently, iff `RT[32] ≠ RT[33] = … = RT[63]` (sign bit disagrees with the next 32 bits). Xenia-rs does **not** implement this; `OE` on `mullwo` is a no-op in the interpreter.
- **Xenia-rs CR0 update bug footprint.** The interpreter computes CR0 from `result as i32 as i64` — the low 32 bits sign-extended. For a 32×32→64 multiply the high 32 bits may be non-zero even when the low 32 bits are zero, so xenia's CR0 can differ from the spec's (which compares the full 64-bit product to zero). In practice this matters only for code that relies on `mullw.` to detect overflow via CR0 — extremely rare.
- **`OE` overflow test is 32-bit.** `XER[OV]` is set iff the 64-bit signed product cannot be represented in 32 bits — iff `RT[0:32]` are not all equal (the product is not the sign extension of its low word). Canary's `OE` branch is `XEINSTRNOTIMPLEMENTED()`.
- **CR0 compares only the low 32 bits in Canary.** Canary stores the full 64-bit product of the sign-extended words, but `f.UpdateCR(0, v)` compares `Truncate(v, INT32)` with zero. The high 32 bits may be non-zero while the low 32 are zero, so Canary's CR0 can differ from spec's full 64-bit compare. It matters only for code that detects overflow through `mullw.`'s CR0 — rare.
- **Latency.** On the Xenon, `mullw` has higher latency than add/sub; many hot inner loops avoid it by strength-reduction or shift-add chains. This is irrelevant for correctness but sometimes explains surprising instruction sequences in disassembly.
## Related Instructions

View File

@@ -109,7 +109,7 @@ int InstrEmit_nandx(PPCHIRBuilder& f, const InstrData& i) {
- **Operand convention is X-form** (`RA` destination, `RS`/`RB` sources).
- **64-bit operation** on Xenon; `~` operates on the full `u64`.
- **No `OE` or `XER` side effects.** Only `Rc=1` updates `CR0` (signed compare to zero).
- **64-bit CR update on Xenon, 32-bit in xenia-rs.** [`interpreter.rs:377`](https://git.mc02.dev/fabi/xenia-rs/src/commit/8401d4d5112849bf7b0f78f9d620f3620b1ce58d/crates/xenia-cpu/src/interpreter.rs) truncates with `as i32 as i64`. NAND results frequently have all-ones high bits when the low half AND is non-saturating, so the truncation can change CR0 semantics in subtle ways — call out as a quirk if reproducing CR-sensitive behaviour.
- **64-bit CR update on Xenon, 32-bit in Canary.** `f.UpdateCR(0, v)` truncates to `INT32`. NAND results frequently have all-ones high bits, so the low word's sign can differ from the full word's — call it out if reproducing CR-sensitive behaviour.
- **Idiom: NAND of two equal values produces NOT.** `nand. RA, RS, RS``~RS` with CR0 update. Sometimes used by compilers when `not.` is unavailable in their tablegen.
## Related Instructions

View File

@@ -120,9 +120,9 @@ int InstrEmit_negx(PPCHIRBuilder& f, const InstrData& i) {
## Special Cases & Edge Conditions
- **Two's-complement negate.** `RT ← ~RA + 1`, equivalent to `0 RA`. A specialisation of [`subfx`](subfx.md) where `RB` is implicit zero.
- **`INT64_MIN` is its own negation.** `neg(0x8000000000000000) = 0x8000000000000000` — the only fixed point. `nego` should set `XER[OV]` in this case (it is the canonical signed-overflow trigger), but **xenia-rs does not implement `OE`** ([`interpreter.rs:201`](https://git.mc02.dev/fabi/xenia-rs/src/commit/8401d4d5112849bf7b0f78f9d620f3620b1ce58d/crates/xenia-cpu/src/interpreter.rs) has no `oe()` branch).
- **`INT64_MIN` is its own negation.** `neg(0x8000000000000000) = 0x8000000000000000` — the only fixed point, and the canonical signed-overflow trigger for which `nego` should set `XER[OV]`. Canary never sets `XER[OV]`: its `OE` path only special-cases an operand that is literally `0x8000000000000000` (`v->AsUint64()`), storing it unchanged and skipping the CR update.
- **`RB` field unused.** Set to 0 by assemblers; ignored.
- **`Rc=1` CR0 update truncates to 32 bits in xenia-rs.** [`interpreter.rs:204`](https://git.mc02.dev/fabi/xenia-rs/src/commit/8401d4d5112849bf7b0f78f9d620f3620b1ce58d/crates/xenia-cpu/src/interpreter.rs). Important: `neg.` of a 64-bit value with high bits set will give a CR0 that doesn't match spec (which compares the full 64-bit `~RA + 1` to zero).
- **`Rc=1` CR0 update truncates to 32 bits in Canary** (`f.UpdateCR(0, v)`). `neg.` of a 64-bit value whose high word decides the sign gives a CR0 that doesn't match spec, which compares the full 64-bit `~RA + 1` to zero.
- **No carry produced.** Use [`subfic`](subficx.md) `RT, RA, 0` (`RT ← 0 RA` with carry) when you need the borrow.
- **Latency: single cycle.** Negate is the cheapest XO-form ALU operation (cheaper than `subf` despite being a special case, because there's no `RB` operand fetch).

View File

@@ -109,7 +109,7 @@ int InstrEmit_norx(PPCHIRBuilder& f, const InstrData& i) {
- **Operand convention** is X-form (`RA` destination, `RS`/`RB` sources).
- **64-bit operation** on Xenon; full 64-bit complement via `!` on `u64`.
- **No `OE` or `XER` side effects.**
- **`Rc=1` CR0 update truncates to 32 bits in xenia-rs.** [`interpreter.rs:372`](https://git.mc02.dev/fabi/xenia-rs/src/commit/8401d4d5112849bf7b0f78f9d620f3620b1ce58d/crates/xenia-cpu/src/interpreter.rs) uses `as i32 as i64`. Note that NOR almost always produces results with high bits set (since the OR rarely covers all 64 bits), so the truncated CR0 is usually `LT` (negative low half) where spec might give a different signed compare for the full 64-bit value.
- **`Rc=1` CR0 update truncates to 32 bits in Canary** (`f.UpdateCR(0, v)`). NOR almost always sets high bits; the low word's sign decides Canary's CR0, the full word's sign decides spec's.
- **`nor.` after a clear-low operation is a common pattern** for testing whether some high-bit mask is empty.
## Related Instructions

View File

@@ -108,9 +108,9 @@ int InstrEmit_orcx(PPCHIRBuilder& f, const InstrData& i) {
- **`RA ← RS OR (NOT RB)`.** The complement is on `RB`. Useful for setting bits *outside* a mask — e.g. `orc r3, r3, r4` sets in `r3` every bit *not* set in `r4`.
- **Idiom: `orc RA, RS, RS`** = `RS | ~RS` = `-1` (all ones). Cheaper-looking than constructing `1` via `lis`+`ori`, but the assembler usually prefers `li RA, -1` or `eqv RA, RS, RS`.
- **Operand convention** is X-form (`RA` destination, `RS`/`RB` sources).
- **64-bit operation** on Xenon; xenia uses Rust's `!` on `u64` for full-width complement.
- **64-bit operation** on Xenon; Canary emits a full-width `f.Or(RS, f.Not(RB))`.
- **No `OE` or `XER` side effects.**
- **`Rc=1` CR0 update truncates to 32 bits in xenia-rs.** [`interpreter.rs:362`](https://git.mc02.dev/fabi/xenia-rs/src/commit/8401d4d5112849bf7b0f78f9d620f3620b1ce58d/crates/xenia-cpu/src/interpreter.rs). Because `~RB` typically has high bits set, `orc.` results often appear `LT` in the truncated CR0.
- **`Rc=1` CR0 update truncates to 32 bits in Canary** (`f.UpdateCR(0, v)`). Because `~RB` typically has high bits set, `orc.` results whose low word's sign differs from the full word's give different CR0 in spec and Canary.
## Related Instructions

View File

@@ -106,7 +106,7 @@ int InstrEmit_ori(PPCHIRBuilder& f, const InstrData& i) {
- **Immediate is zero-extended.** Only the low 16 bits of `RA` can be affected; the high 48 bits are passed through from `RS` unchanged.
- **`ori 0, 0, 0` is the canonical NOP.** All PowerPC NOPs assemble to this encoding (`0x60000000`). Disassemblers usually display this as `nop`.
- **Common idiom: build a 32-bit constant via `lis` + `ori`.** `lis r3, hi16; ori r3, r3, lo16` materialises any 32-bit immediate with no CR or XER disturbance.
- **64-bit operation in xenia-rs.** [`interpreter.rs:330`](https://git.mc02.dev/fabi/xenia-rs/src/commit/8401d4d5112849bf7b0f78f9d620f3620b1ce58d/crates/xenia-cpu/src/interpreter.rs) — full `u64` OR; high bits unchanged from `RS`.
- **64-bit operation in Canary.** `f.Or(RS, EXTZ16(UI))` on the full `u64`; the high bits come from `RS` unchanged. The canonical no-op `ori 0, 0, 0` is special-cased to `f.Nop()`.
- **`RA = 0` reads `r0`** (not the literal zero). Different from `addi`'s `RA0` semantics; `ori` uses the regular `RA` interpretation.
## Related Instructions

View File

@@ -102,7 +102,7 @@ int InstrEmit_oris(PPCHIRBuilder& f, const InstrData& i) {
- **No record form.** No `oris.` — same as [`ori`](ori.md). For CR0 updates use [`orx`](orx.md) with `Rc=1`.
- **Immediate is zero-extended *then* shifted left 16.** Only bits 3247 of `RA` (in PowerISA bit numbering) can be affected; the high 32 bits and low 16 bits of `RA` come from `RS` unchanged.
- **Common pair with `lis`** to load a 32-bit constant: `lis r3, hi16` (= `addis r3, 0, hi16`), then `ori r3, r3, lo16`. **For unsigned constants whose low half has the high bit set**, `lis` followed by `ori` works cleanly because `ori` is zero-extending; using `addi` instead would sign-extend `lo16` and corrupt the constant.
- **64-bit operation in xenia-rs.** [`interpreter.rs:334`](https://git.mc02.dev/fabi/xenia-rs/src/commit/8401d4d5112849bf7b0f78f9d620f3620b1ce58d/crates/xenia-cpu/src/interpreter.rs).
- **64-bit operation in Canary.** `f.Or(RS, EXTZ16(UI) << 16)` on the full `u64`.
- **No `XER`, no `CR` effect.** Pure register OR.
- **`RA = 0` reads `r0`** (not literal zero); see [`ori`](ori.md).

View File

@@ -126,7 +126,7 @@ int InstrEmit_orx(PPCHIRBuilder& f, const InstrData& i) {
- **Operand convention** is X-form (`RA` destination, `RS`/`RB` sources).
- **64-bit operation** on Xenon; full bitwise OR across 64 bits.
- **No `OE` or `XER` side effects.** Only `Rc=1` updates `CR0`.
- **64-bit CR update on Xenon, 32-bit in xenia-rs.** [`interpreter.rs:357`](https://git.mc02.dev/fabi/xenia-rs/src/commit/8401d4d5112849bf7b0f78f9d620f3620b1ce58d/crates/xenia-cpu/src/interpreter.rs) truncates with `as i32 as i64`. For `or. RA, RS, RS` (i.e. `mr.`), this means CR0 reflects the low 32 bits of `RS` only — distinguishable from spec only when the high 32 bits are non-zero with all-zero low 32.
- **64-bit CR update on Xenon, 32-bit in Canary.** `f.UpdateCR(0, v)` truncates to `INT32`. For `or. RA, RS, RS` (`mr.`), CR0 reflects the low 32 bits of `RS` only — distinguishable from spec when the high word decides the sign or is non-zero over an all-zero low word. Without `Rc`, Canary turns `or rX, rX, rX` into `f.Nop()` — except the exact word `0x7FFFFB78` (`or r31, r31, r31`), which becomes `f.DelayExecution()`: an idle/yield marker a translator should recognise.
- **`or 26, 26, 26` is the Xbox 360 NOP variant** historically used to mark cache lines or signal the dispatch unit (alongside `nop``ori 0,0,0`). Disassembly may show this — it has no architectural effect.
## Related Instructions

View File

@@ -129,9 +129,9 @@ int InstrEmit_rldclx(PPCHIRBuilder& f, const InstrData& i) {
- **`RA ← ROTL64(RS, RB[58:63]) & MASK(MB, 63)`.** Rotate `RS` left by `RB & 0x3F`, then *clear* bits to the left of `MB` — i.e. keep bits `MB..63`, force bits `0..MB-1` to zero.
- **Shift comes from a register.** Unlike [`rldiclx`](rldiclx.md), the rotate amount is dynamic. Only the low 6 bits of `RB` are used (`& 0x3F`); the upper 58 bits are silently ignored.
- **`MB` is a split 6-bit field.** Bit 5 of the encoded `mb/me` is *swapped* into bit position 5 (raw bit 30) — xenia decodes via `(instr.mb() << 1) | ((raw >> 1) & 1)` ([`interpreter.rs:587`](https://git.mc02.dev/fabi/xenia-rs/src/commit/8401d4d5112849bf7b0f78f9d620f3620b1ce58d/crates/xenia-cpu/src/interpreter.rs)). This MDS form is unusual; if you write a decoder, follow this exact bit assembly.
- **`MB` is a split 6-bit field.** Canary assembles it as `(MB5 << 5) | MB`: `MB` is instruction bits 2125 and `MB5` is bit 26, so the bit *after* the five-bit field is the most significant. Follow that assembly if you write a decoder.
- **Mask generation.** `rld_mask_left(MB)` is `(1 << (64 - MB)) - 1` — i.e. clear bits `0..MB-1`, keep bits `MB..63`. When `MB = 0` the mask is all ones; when `MB = 63` only bit 63 survives.
- **`Rc=1` CR0 is correctly 64-bit.** [`interpreter.rs:592`](https://git.mc02.dev/fabi/xenia-rs/src/commit/8401d4d5112849bf7b0f78f9d620f3620b1ce58d/crates/xenia-cpu/src/interpreter.rs) uses `as i64` directly — no truncation. The rotate-and-mask family is one of the few xenia-rs instruction groups that already does the spec-correct 64-bit CR0 compare.
- **`Rc=1` CR0 update is 32-bit in Canary.** `f.UpdateCR(0, v)` truncates to `INT32`; spec compares the full 64-bit rotated-and-masked value.
- **No `XER` effect.**
- **Use over [`rldiclx`](rldiclx.md)** when the shift amount is computed at runtime (e.g. via `cntlzd` for normalisation).

View File

@@ -129,9 +129,9 @@ int InstrEmit_rldcrx(PPCHIRBuilder& f, const InstrData& i) {
- **`RA ← ROTL64(RS, RB[58:63]) & MASK(0, ME)`.** Rotate `RS` left by `RB & 0x3F`, then *clear* bits to the right of `ME` — keep bits `0..ME`, force bits `ME+1..63` to zero.
- **Shift from register.** Same as [`rldclx`](rldclx.md): only the low 6 bits of `RB` count.
- **`ME` is a split 6-bit field.** Same swap-decoded layout as `MB` in `rldclx`: `(instr.mb() << 1) | ((raw >> 1) & 1)` ([`interpreter.rs:597`](https://git.mc02.dev/fabi/xenia-rs/src/commit/8401d4d5112849bf7b0f78f9d620f3620b1ce58d/crates/xenia-cpu/src/interpreter.rs)). Note that even though it represents `ME` here, xenia reads it from `instr.mb()` because the field shares the same encoding slot.
- **`ME` is a split 6-bit field.** Encoded exactly like `MB` in [`rldclx`](rldclx.md) — Canary even reads it from the same `InstrData.MDS` fields as `(MB5 << 5) | MB` — and applied as the mask end, `XEMASK(0, ME)`.
- **Mask generation.** `rld_mask_right(ME)` = `~((1 << (63 - ME)) - 1)` keeping bits `0..ME`. When `ME = 63` the mask is all ones; when `ME = 0` only bit 0 survives.
- **`Rc=1` CR0 is correctly 64-bit.** Uses `as i64` directly ([`interpreter.rs:602`](https://git.mc02.dev/fabi/xenia-rs/src/commit/8401d4d5112849bf7b0f78f9d620f3620b1ce58d/crates/xenia-cpu/src/interpreter.rs)).
- **`Rc=1` CR0 update is 32-bit in Canary** (`f.UpdateCR(0, v)`).
- **No `XER` effect.**
- **Useful for left-shift with arbitrary discard.** `rldcr RA, RS, RB, 63 - n` is functionally close to a left-shift-and-mask sequence, with the rotate variant additionally allowing wrap-around.

View File

@@ -136,9 +136,9 @@ int InstrEmit_rldiclx(PPCHIRBuilder& f, const InstrData& i) {
- `srdi RA, RS, n``rldicl RA, RS, 64-n, n` — logical right shift by `n`.
- `clrldi RA, RS, n``rldicl RA, RS, 0, n` — clear top `n` bits.
- `extrdi RA, RS, n, b``rldicl RA, RS, b+n, 64-n` — extract `n` bits starting at `b`.
- **`SH` is 6 bits, immediate** (bits 1620 + bit 30). Xenia uses `instr.sh64()` to assemble them.
- **`SH` is 6 bits, immediate** (bits 1620 + bit 30). Canary assembles it as `(SH5 << 5) | SH`. When `SH == 64 MB` it emits a plain logical shift right by `MB` — the `srdi` idiom — instead of rotate-and-mask.
- **`MB` is 6 bits, split-encoded** (`(instr.mb() << 1) | ((raw >> 1) & 1)`).
- **`Rc=1` CR0 is correctly 64-bit.** Uses `as i64` directly ([`interpreter.rs:551`](https://git.mc02.dev/fabi/xenia-rs/src/commit/8401d4d5112849bf7b0f78f9d620f3620b1ce58d/crates/xenia-cpu/src/interpreter.rs)).
- **`Rc=1` CR0 update is 32-bit in Canary** (`f.UpdateCR(0, v)`).
- **No `XER` effect.**
- **Often appears in compiled disassembly** as a generic 64-bit shift. Decoding back to the simplified mnemonic above makes the intent obvious.

View File

@@ -137,8 +137,8 @@ int InstrEmit_rldicrx(PPCHIRBuilder& f, const InstrData& i) {
- `clrrdi RA, RS, n``rldicr RA, RS, 0, 63-n` — clear low `n` bits.
- `extldi RA, RS, n, b``rldicr RA, RS, b, n-1` — extract `n` bits from position `b` left-aligned.
- **`SH` is 6 bits, immediate.** Same `instr.sh64()` decode as the rest of the family.
- **`ME` is 6 bits, split-encoded.** Xenia stores it via `instr.mb()` — the field shares the slot with `MB` from sister instructions; the operation just interprets it as the right edge.
- **`Rc=1` CR0 is correctly 64-bit.** Uses `as i64` directly ([`interpreter.rs:561`](https://git.mc02.dev/fabi/xenia-rs/src/commit/8401d4d5112849bf7b0f78f9d620f3620b1ce58d/crates/xenia-cpu/src/interpreter.rs)).
- **`ME` is 6 bits, split-encoded.** Canary reads it through the `MD` form's `MB`/`MB5` fields — the slot `MB` occupies in sister instructions; the operation just interprets it as the right edge.
- **`Rc=1` CR0 update is 32-bit in Canary** (`f.UpdateCR(0, v)`). When `ME == 63 SH`, Canary emits a plain `Shl` by `SH` — the `sldi` idiom — instead of rotate-and-mask.
- **No `XER` effect.**
- **Heavily emitted by 64-bit code generators** for left-shift-and-clear sequences. Recognising the simplified mnemonics aids disassembly.

View File

@@ -127,11 +127,11 @@ int InstrEmit_rldicx(PPCHIRBuilder& f, const InstrData& i) {
## Special Cases & Edge Conditions
- **`RA ← ROTL64(RS, SH) & MASK(MB, 63 - SH)`.** Rotate `RS` left by `SH` bits, then mask off both ends: clear bits `0..MB-1` *and* clear bits `64-SH..63`. This is the "clear at both edges" variant — useful for inserting a field into an otherwise-zero register.
- **`SH` is a 6-bit immediate** spanning bits 1620 plus bit 30 of the instruction word. Xenia uses the helper `instr.sh64()` ([`interpreter.rs:566`](https://git.mc02.dev/fabi/xenia-rs/src/commit/8401d4d5112849bf7b0f78f9d620f3620b1ce58d/crates/xenia-cpu/src/interpreter.rs)) to assemble the 6 bits.
- **`SH` is a 6-bit immediate** spanning bits 1620 plus bit 30 of the instruction word. Canary assembles it as `(SH5 << 5) | SH`.
- **`MB` is also 6-bit, split-encoded** like the rest of the `rld*` family: `(instr.mb() << 1) | ((raw >> 1) & 1)`.
- **Mask is computed as `MASK_LEFT(MB) AND MASK_RIGHT(63 - SH)`.** This produces the equivalent of "left-shift `RS` by `SH` then clear high bits above bit `MB`" — a common pattern when `MB ≤ 63 - SH`.
- **Equivalent to a logical shift when `MB = 0`.** `rldic RA, RS, SH, 0``sldi RA, RS, SH` (an alias the assembler may prefer).
- **`Rc=1` CR0 is correctly 64-bit.** [`interpreter.rs:571`](https://git.mc02.dev/fabi/xenia-rs/src/commit/8401d4d5112849bf7b0f78f9d620f3620b1ce58d/crates/xenia-cpu/src/interpreter.rs) uses `as i64` directly.
- **`Rc=1` CR0 update is 32-bit in Canary** (`f.UpdateCR(0, v)`).
- **No `XER` effect.**
## Related Instructions

View File

@@ -129,10 +129,10 @@ int InstrEmit_rldimix(PPCHIRBuilder& f, const InstrData& i) {
## Special Cases & Edge Conditions
- **`RA ← (ROTL64(RS, SH) & MASK) | (RA & ~MASK)`.** *Reads* the prior `RA` so it can preserve the bits outside the mask — this is the only `rld*` instruction with `RA` as both source and destination.
- **Mask is `MASK_LEFT(MB) AND MASK_RIGHT(63 - SH)`** ([`interpreter.rs:578`](https://git.mc02.dev/fabi/xenia-rs/src/commit/8401d4d5112849bf7b0f78f9d620f3620b1ce58d/crates/xenia-cpu/src/interpreter.rs)) — same span as [`rldicx`](rldicx.md), but the un-masked region is preserved in the destination instead of being zeroed.
- **Mask is `MASK(MB, 63 SH)`** — Canary's `XEMASK(mb, ~sh)` — the same span as [`rldicx`](rldicx.md), but the un-masked region is preserved from `RA` (`(v & m) | (RA & ~m)`) instead of being zeroed.
- **Use to insert a bit-field.** Common idiom: `rldimi RA, RS, b, mask_start` writes `RS`'s low (`64 - mask_start`) bits into `RA` starting at bit `b`.
- **`SH` and `MB` decoding** is identical to the rest of the family (6-bit `sh` via `instr.sh64()`, 6-bit `mb` via the swap layout).
- **`Rc=1` CR0 is correctly 64-bit.** Uses `as i64` directly.
- **`Rc=1` CR0 update is 32-bit in Canary** (`f.UpdateCR(0, v)`).
- **No `XER` effect.**
- **Compile-time pattern.** When you see `rldimi r3, r4, n, m`, the compiler is splicing a value into `r3`; recover the meaning by computing the mask `MASK(m, 63 - n)`.

View File

@@ -128,11 +128,11 @@ int InstrEmit_rlwimix(PPCHIRBuilder& f, const InstrData& i) {
## Special Cases & Edge Conditions
- **`RA ← (ROTL32(RS[32:63], SH) & MASK) | (RA[32:63] & ~MASK)`.** Reads the low 32 bits of `RS`, rotates them, then *inserts* under the mask back into the low 32 bits of `RA`. The high 32 bits of `RA` are *implementation-defined* per spec; **xenia-rs zeroes them** (the `as u32` cast at [`interpreter.rs:529`](https://git.mc02.dev/fabi/xenia-rs/src/commit/8401d4d5112849bf7b0f78f9d620f3620b1ce58d/crates/xenia-cpu/src/interpreter.rs) discards them on read, then `as u64` zero-extends on write).
- **Mask follows the standard `MB..ME` PPC convention.** Both `MB` and `ME` are 5-bit fields; the mask is contiguous when `MB <= ME`, and *wraps* around (a "donut" mask: bits `MB..31` and `0..ME`) when `MB > ME`. Xenia's `rlw_mask(mb, me)` helper handles both cases.
- **`RA ← (ROTL32(RS[32:63], SH) & MASK) | (RA & ~MASK)`.** Reads the low 32 bits of `RS`, rotates them, then *inserts* under the mask. The high 32 bits of `RA` are implementation-defined per spec. Canary rotates a doubled word (`(RS << 32) | RS[32:63]`), masks it with `XEMASK(MB + 32, ME + 32)` and ORs back `RA & ~mask` — so for a contiguous mask (`MB ≤ ME`) `RA`'s high 32 bits are **preserved**, not zeroed.
- **Mask follows the standard `MB..ME` PPC convention.** Both are 5-bit fields; the mask is contiguous when `MB <= ME` and *wraps* (a "donut" mask: bits `MB..31` and `0..ME`) when `MB > ME`. Canary computes it as `XEMASK(MB + 32, ME + 32)` on the 64-bit word, which gives the right low word in both cases.
- **`SH` is 5 bits.** Rotate amount is `SH mod 32`; values `≥ 32` are not encodable in this M-form.
- **Used for bit-field insertion** (`insrwi RA, RS, n, b``rlwimi RA, RS, 32-(b+n), b, b+n-1`). Compilers emit `rlwimi` extensively for struct-bitfield writes.
- **`Rc=1` CR0 update truncates to 32 bits in xenia-rs.** [`interpreter.rs:531`](https://git.mc02.dev/fabi/xenia-rs/src/commit/8401d4d5112849bf7b0f78f9d620f3620b1ce58d/crates/xenia-cpu/src/interpreter.rs). Since the high 32 bits of the result are zero, this matches spec's compare on the (defined) low half — but if a real Xenon left high bits non-zero, behaviour would diverge.
- **`Rc=1` CR0 update truncates to 32 bits in Canary** (`f.UpdateCR(0, v)`): it compares the low word only, while spec compares all 64 bits — including whatever `RA` kept in its high word.
- **No `XER` effect.**
## Related Instructions

View File

@@ -165,9 +165,9 @@ int InstrEmit_rlwinmx(PPCHIRBuilder& f, const InstrData& i) {
- `clrlwi RA, RS, n``rlwinm RA, RS, 0, n, 31` — clear high `n` bits.
- `clrrwi RA, RS, n``rlwinm RA, RS, 0, 0, 31-n` — clear low `n` bits.
- `extlwi`, `extrwi`, `clrlslwi` — full mnemonic family in PowerISA appendix.
- **Mask convention `MB..ME`** is contiguous when `MB ≤ ME`. When `MB > ME`, the mask is the *complement* of bits `ME+1..MB-1` — a donut/wrap mask. Xenia's `rlw_mask` handles both.
- **Mask convention `MB..ME`** is contiguous when `MB ≤ ME`. When `MB > ME`, the mask is the *complement* of bits `ME+1..MB-1` — a donut/wrap mask. Canary computes it as `XEMASK(MB + 32, ME + 32)` on the 64-bit word; about 90% of `rlwinm`s take its fast path, `(RS << SH) & 0xFFFFFFFF`, when the mask is exactly the bits the shift keeps.
- **`SH` is 5 bits**, rotate amount `0..31`.
- **`Rc=1` CR0 update truncates to 32 bits in xenia-rs.** [`interpreter.rs:518`](https://git.mc02.dev/fabi/xenia-rs/src/commit/8401d4d5112849bf7b0f78f9d620f3620b1ce58d/crates/xenia-cpu/src/interpreter.rs). Since the result fits in 32 bits, the truncation matches spec exactly.
- **`Rc=1` CR0 update truncates to 32 bits in Canary** (`f.UpdateCR(0, v)`), and that does **not** always match spec: whenever the result's bit 32 (the low word's most-significant bit) is set, spec sees a positive 64-bit value (`GT`) while Canary's `INT32` view is negative (`LT`). For a wrap mask (`MB > ME`) the result also has high-word bits set, as spec allows, so the two compares can disagree there too.
- **No `XER` effect.**
## Related Instructions

View File

@@ -122,12 +122,12 @@ int InstrEmit_rlwnmx(PPCHIRBuilder& f, const InstrData& i) {
## Special Cases & Edge Conditions
- **`RA ← ROTL32(RS[32:63], RB[59:63]) & MASK(MB, ME)`.** Identical to [`rlwinmx`](rlwinmx.md) except the rotate amount comes from the low 5 bits of `RB`. Xenia masks with `& 0x1F` ([`interpreter.rs:535`](https://git.mc02.dev/fabi/xenia-rs/src/commit/8401d4d5112849bf7b0f78f9d620f3620b1ce58d/crates/xenia-cpu/src/interpreter.rs)).
- **`RA ← ROTL32(RS[32:63], RB[59:63]) & MASK(MB, ME)`.** Identical to [`rlwinmx`](rlwinmx.md) except the rotate amount comes from the low 5 bits of `RB`; Canary takes `RB & 0x1F`.
- **Use over `rlwinm`** when the rotate amount is dynamic (e.g. computed from a `cntlzw` for normalisation, or read from a parameter).
- **Mask is still 5+5 bits immediate** — `MB` and `ME` are not register-sourced; only the shift is. This is the M-form's quirk: only one of (`SH`, `MB`, `ME`) is variable across the family.
- **Donut masks supported.** `MB > ME` produces a wraparound mask, same as `rlwinm`.
- **High 32 bits of `RA` are zero** (32-bit operation, then `as u64` zero-extends).
- **`Rc=1` CR0 update truncates to 32 bits in xenia-rs.** [`interpreter.rs:540`](https://git.mc02.dev/fabi/xenia-rs/src/commit/8401d4d5112849bf7b0f78f9d620f3620b1ce58d/crates/xenia-cpu/src/interpreter.rs) — harmless because the result already fits in 32 bits.
- **`Rc=1` CR0 update truncates to 32 bits in Canary** (`f.UpdateCR(0, v)`). Not harmless in general: whenever the result's bit 32 (the low word's most-significant bit) is set, spec sees a positive 64-bit value (`GT`) while Canary's `INT32` view is negative (`LT`), and a wrap mask leaves high-word bits set.
- **No `XER` effect.**
## Related Instructions

View File

@@ -116,9 +116,9 @@ int InstrEmit_sldx(PPCHIRBuilder& f, const InstrData& i) {
## Special Cases & Edge Conditions
- **64-bit logical left shift.** `RA ← RS << (RB & 0x7F)` if the shift count is `< 64`, otherwise `RA = 0`. Bits shifted past bit 0 are discarded.
- **Critical: shift count is *7 bits*, not 6.** PowerISA reads `RB[57:63]` (7 bits, `0..127`). Counts in `[64, 127]` produce zero, *not* `RS << (count mod 64)`. Xenia respects this with `& 0x7F` and an explicit `if sh < 64` check ([`interpreter.rs:464`](https://git.mc02.dev/fabi/xenia-rs/src/commit/8401d4d5112849bf7b0f78f9d620f3620b1ce58d/crates/xenia-cpu/src/interpreter.rs)). C semantics' undefined behaviour for `<<` with a count `>= width` is a spec-violation source if you naïvely translate.
- **Critical: shift count is *7 bits*, not 6.** PowerISA reads `RB[57:63]` (7 bits, `0..127`). Counts in `[64, 127]` produce zero, *not* `RS << (count mod 64)`. Canary respects this: `sh = RB & 0x7F`, then `Select(sh >> 6, 0, RS << sh)`. C's undefined behaviour for `<<` with a count `>= width` is a spec-violation source if you translate naïvely.
- **No `XER[CA]` produced** by left shifts. Logical right [`srdx`](srdx.md) and arithmetic right [`sradx`](sradx.md) differ here — arithmetic right *does* set `CA`.
- **`Rc=1` CR0 is correctly 64-bit.** [`interpreter.rs:467`](https://git.mc02.dev/fabi/xenia-rs/src/commit/8401d4d5112849bf7b0f78f9d620f3620b1ce58d/crates/xenia-cpu/src/interpreter.rs) uses `as i64` directly. CR0 reflects the sign of the full 64-bit shifted value (which is 0 for shifts ≥ 64, otherwise either `LT`/`GT`/`EQ`).
- **`Rc=1` CR0 update is 32-bit in Canary** (`f.UpdateCR(0, v)`); spec compares the full 64-bit shifted value (which is 0 for shifts ≥ 64).
- **Strength-reduced from `mulli` for power-of-two multipliers.**
- **No `OE` bit.**

View File

@@ -117,10 +117,10 @@ int InstrEmit_slwx(PPCHIRBuilder& f, const InstrData& i) {
## Special Cases & Edge Conditions
- **32-bit logical left shift, zero-extended to 64.** `RA ← (RS[32:63] << (RB & 0x3F))[32:63]` if `(RB & 0x3F) < 32`, else `RA = 0`. The high 32 bits of `RA` are always zero (zero-extension of the 32-bit result).
- **Shift count is 6 bits**, `RB[58:63]` — not 7 like [`sldx`](sldx.md). Counts in `[32, 63]` produce zero. Xenia reads the full register but the explicit `if sh < 32` guard in [`interpreter.rs:417`](https://git.mc02.dev/fabi/xenia-rs/src/commit/8401d4d5112849bf7b0f78f9d620f3620b1ce58d/crates/xenia-cpu/src/interpreter.rs) prevents Rust UB.
- **Spec quirk worth flagging:** xenia reads `ctx.gpr[instr.rb()] as u32`, which uses the low 32 bits of `RB`, not the spec's `RB & 0x3F`. For ordinary code these agree (counts ≤ 63), but a maliciously high `RB` could in principle differ. In practice this is a non-issue.
- **Shift count is 6 bits**, `RB[58:63]` — not 7 like [`sldx`](sldx.md). Counts in `[32, 63]` produce zero: Canary emits `Select(sh >> 5, 0, RS[32:63] << sh)` and zero-extends the 32-bit result.
- **Count masking matches spec in Canary.** Canary takes `RB & 0x3F` directly, so arbitrarily large `RB` values behave exactly as spec.
- **No `XER[CA]` for left shifts.**
- **`Rc=1` CR0 update truncates to 32 bits** ([`interpreter.rs:420`](https://git.mc02.dev/fabi/xenia-rs/src/commit/8401d4d5112849bf7b0f78f9d620f3620b1ce58d/crates/xenia-cpu/src/interpreter.rs)). Since the high 32 bits are zero, this matches spec exactly.
- **`Rc=1` CR0 update truncates to 32 bits in Canary** (`f.UpdateCR(0, v)`). The zero high word does not make that harmless: whenever the result's bit 32 (the low word's most-significant bit) is set, spec sees a positive 64-bit value (`GT`) while Canary's `INT32` view is negative (`LT`).
- **No `OE` bit.**
## Related Instructions

View File

@@ -131,10 +131,10 @@ int InstrEmit_sradix(PPCHIRBuilder& f, const InstrData& i) {
## Special Cases & Edge Conditions
- **`RA ← (i64)RS >> SH`**, with `XER[CA]` set when `RS` is negative AND any one-bit was shifted out.
- **`SH` is 6 bits.** Encoded in bits 1620 (`sh`) plus bit 30 (`sh5`); xenia uses `instr.sh64()` to assemble the 6 bits ([`interpreter.rs:496`](https://git.mc02.dev/fabi/xenia-rs/src/commit/8401d4d5112849bf7b0f78f9d620f3620b1ce58d/crates/xenia-cpu/src/interpreter.rs)). Range `0..63`.
- **`SH = 0`** is a no-op (sign-extends `RS` to itself), and explicitly clears `XER[CA]` ([`interpreter.rs:498`](https://git.mc02.dev/fabi/xenia-rs/src/commit/8401d4d5112849bf7b0f78f9d620f3620b1ce58d/crates/xenia-cpu/src/interpreter.rs)). This matches spec.
- **`SH` is 6 bits.** Encoded in bits 1620 (`sh`) plus bit 30 (`sh5`); Canary assembles `(SH5 << 5) | SH`. Range `0..63`.
- **`SH = 0`** is a no-op (sign-extends `RS` to itself) and explicitly clears `XER[CA]` — Canary stores `CA = 0` on that path. This matches spec.
- **Spec divergence: 6-bit immediate, no saturation arm.** Unlike [`sradx`](sradx.md) which has a 7-bit register count and saturates at `≥ 64`, `sradi` always uses a count `< 64` so no special saturation case is needed.
- **`Rc=1` CR0 is correctly 64-bit.** [`interpreter.rs:506`](https://git.mc02.dev/fabi/xenia-rs/src/commit/8401d4d5112849bf7b0f78f9d620f3620b1ce58d/crates/xenia-cpu/src/interpreter.rs).
- **`Rc=1` CR0 update is 32-bit in Canary** (`f.UpdateCR(0, v)`). The shift is arithmetic on all 64 bits, so the low word's sign can differ from the full value's — e.g. `0x00000000_C0000000` is positive (`GT`) but reads `LT` once truncated.
- **Idiom: `sradi rA, rS, n; addze rA, rA`** — signed integer divide by `2^n` rounded toward zero (the textbook PPC sequence).
- **No `OE` bit.**

View File

@@ -129,9 +129,9 @@ int InstrEmit_sradx(PPCHIRBuilder& f, const InstrData& i) {
- **64-bit arithmetic (sign-propagating) right shift.** `RA ← (i64)RS >> (RB & 0x7F)` with bits shifted in matching the sign bit of `RS`. Counts ≥ 64 saturate: `RA` becomes all-ones if `RS < 0`, else zero.
- **`XER[CA]` is the "lost-ones" indicator.** `CA = 1` iff `RS` is negative AND any of the bits shifted out were `1`. This makes `srad` / `sradi` the standard idiom for "divide negative integer by power of 2 with round-toward-zero" — followed by `addze` to compensate when `CA = 1`.
- **Three branches in xenia.** `sh == 0` (no shift, `CA=0`), `sh < 64` (normal shift, `CA` per spec), and `sh ≥ 64` (saturate to `0` or `1`, `CA` reflects sign). The `(rs as u64) << (64 - sh) != 0` check at [`interpreter.rs:486`](https://git.mc02.dev/fabi/xenia-rs/src/commit/8401d4d5112849bf7b0f78f9d620f3620b1ce58d/crates/xenia-cpu/src/interpreter.rs) extracts whether any non-zero bit was shifted out.
- **One expression in Canary, no branches.** Canary clamps the count to 63 (`sh = RB & 0x7F`, `Min(sh, 0x3F)`) and shifts arithmetically, which saturates counts ≥ 64 to `0` or `1`. `CA = RS < 0 AND (result << clamp) != RS`, i.e. set only for a negative source that shifted out a 1 bit; a count of 0 therefore gives `CA = 0`.
- **Shift count is 7 bits.** Same as [`sldx`](sldx.md): `RB[57:63]`.
- **`Rc=1` CR0 is correctly 64-bit.** [`interpreter.rs:489`](https://git.mc02.dev/fabi/xenia-rs/src/commit/8401d4d5112849bf7b0f78f9d620f3620b1ce58d/crates/xenia-cpu/src/interpreter.rs).
- **`Rc=1` CR0 update is 32-bit in Canary** (`f.UpdateCR(0, v)`); it can differ from spec when the high word decides the sign.
- **No `OE` bit.**
- **Used by signed-divide-by-power-of-2 idiom:** `srad rA, rS, n; addze rA, rA` produces `rS / 2^n` with truncation toward zero rather than toward `-∞`.

View File

@@ -137,11 +137,11 @@ int InstrEmit_srawix(PPCHIRBuilder& f, const InstrData& i) {
## Special Cases & Edge Conditions
- **`RA ← ((i32)RS >> SH) sign-extended`** with `XER[CA]` set when `RS` is negative AND any low bit was shifted out.
- **`SH` is 5 bits** (immediate, range `0..31`). Unlike [`srawx`](srawx.md), there is no saturation case because the count cannot exceed 31. Xenia reads it via `instr.sh()`.
- **`SH` is 5 bits** (immediate, range `0..31`). Unlike [`srawx`](srawx.md), there is no saturation case because the count cannot exceed 31. Canary reads it from the `RB` field slot (`i.X.RB`).
- **`SH = 0`** sign-extends `RS[32:63]` to 64 bits and clears `CA`. This is *not* a no-op when `RS`'s high 32 bits differ from the sign extension of bit 32.
- **Common idiom: `srawi rA, rS, 31`** materialises the 32-bit sign of `rS` as `0` or `1` — the canonical "sign mask" pattern. Often used for branchless `abs` or conditional negation.
- **Idiom: `srawi rA, rS, n; addze rA, rA`** — divide signed by `2^n` rounding toward zero.
- **`Rc=1` CR0 update truncates to 32 bits in xenia-rs.** [`interpreter.rs:457`](https://git.mc02.dev/fabi/xenia-rs/src/commit/8401d4d5112849bf7b0f78f9d620f3620b1ce58d/crates/xenia-cpu/src/interpreter.rs) — matches spec because the sign-extended result has consistent low/high 32-bit signs.
- **`Rc=1` CR0 update truncates to 32 bits in Canary** (`f.UpdateCR(0, v)`) — which matches spec here, because the result is sign-extended from its low word, so both halves agree on sign and zero-ness.
- **No `OE` bit.**
## Related Instructions

View File

@@ -128,10 +128,10 @@ int InstrEmit_srawx(PPCHIRBuilder& f, const InstrData& i) {
## Special Cases & Edge Conditions
- **32-bit arithmetic right shift, sign-extended to 64.** `RA ← ((i32)RS >> n) sign-extended`, with `XER[CA]` set when `RS[32] = 1` (negative) AND any low bit was shifted out.
- **Shift count is 6 bits**, `RB[58:63]`. Counts `≥ 32` saturate: `RA = -1` (all-ones, sign-extended) if `RS < 0`, else `0`. Xenia handles this in three branches ([`interpreter.rs:432-444`](https://git.mc02.dev/fabi/xenia-rs/src/commit/8401d4d5112849bf7b0f78f9d620f3620b1ce58d/crates/xenia-cpu/src/interpreter.rs)).
- **Shift count is 6 bits**, `RB[58:63]`. Counts `≥ 32` saturate: `RA = -1` (all-ones, sign-extended) if `RS < 0`, else `0`. Canary does it in one expression: `Sha(RS[32:63], Min(sh, 31))`, sign-extended to 64 bits.
- **`SH = 0`** sign-extends `RS` to 64 bits and clears `XER[CA]` — like `extsw`, but additionally writing CA.
- **Result is always sign-extended to 64 bits.** `RA[0:31]` matches the sign of `RA[32]`. This is the key difference from [`srwx`](srwx.md) (zero-extension).
- **`Rc=1` CR0 update truncates to 32 bits in xenia-rs.** [`interpreter.rs:443`](https://git.mc02.dev/fabi/xenia-rs/src/commit/8401d4d5112849bf7b0f78f9d620f3620b1ce58d/crates/xenia-cpu/src/interpreter.rs) — but since the result is sign-extended, the low 32 bits' sign matches the full 64-bit sign, so spec and xenia agree here.
- **`Rc=1` CR0 update truncates to 32 bits in Canary** (`f.UpdateCR(0, v)`) — but the result is sign-extended, so the low word's sign matches the full 64-bit sign, and spec and Canary agree here.
- **Used with [`addzex`](addzex.md)** for signed divide by `2^n` rounding toward zero.
- **No `OE` bit.**

View File

@@ -116,9 +116,9 @@ int InstrEmit_srdx(PPCHIRBuilder& f, const InstrData& i) {
## Special Cases & Edge Conditions
- **64-bit logical right shift.** `RA ← RS >> (RB & 0x7F)` if the count is `< 64`, else `RA = 0`. Bits shifted in from the high end are zero (no sign extension).
- **Shift count is 7 bits** (`RB[57:63]`). Counts `64..127` produce zero, not `RS >> (count mod 64)`. Xenia respects this with `& 0x7F` and an explicit `if sh < 64` check ([`interpreter.rs:472`](https://git.mc02.dev/fabi/xenia-rs/src/commit/8401d4d5112849bf7b0f78f9d620f3620b1ce58d/crates/xenia-cpu/src/interpreter.rs)).
- **Shift count is 7 bits** (`RB[57:63]`). Counts `64..127` produce zero, not `RS >> (count mod 64)`. Canary emits `Select(sh & 0x40, 0, RS >> sh)` with `sh = RB & 0x7F`.
- **No `XER[CA]` produced.** This is the logical right shift; for arithmetic shift with `XER[CA]` use [`sradx`](sradx.md).
- **`Rc=1` CR0 is correctly 64-bit.** [`interpreter.rs:475`](https://git.mc02.dev/fabi/xenia-rs/src/commit/8401d4d5112849bf7b0f78f9d620f3620b1ce58d/crates/xenia-cpu/src/interpreter.rs). Result is non-negative as a signed value (high bit is always cleared by the shift), so CR0 will only ever be `EQ` or `GT`.
- **`Rc=1` CR0 update is 32-bit in Canary** (`f.UpdateCR(0, v)`). Per spec, any non-zero count clears the top bit, so CR0 is `EQ` or `GT` (a count of 0 leaves `RS` itself, which may be negative). Canary compares only the low word, whose most-significant bit can still be set, so it can report `LT` where spec says `GT`.
- **No `OE` bit.**
- **The `srdi` simplified mnemonic** uses [`rldiclx`](rldiclx.md) instead — `rldicl rA, rS, 64-n, n` — because it can be combined with masking. `srd` is for runtime-variable counts.

View File

@@ -118,9 +118,9 @@ int InstrEmit_srwx(PPCHIRBuilder& f, const InstrData& i) {
## Special Cases & Edge Conditions
- **32-bit logical right shift, zero-extended to 64.** `RA ← (u32)RS >> (RB & 0x3F)` if count `< 32`, else `RA = 0`. The high 32 bits of `RA` are always zero.
- **Shift count is 6 bits**, `RB[58:63]`. Counts `[32, 63]` produce zero (not `RS >> (count mod 32)`); xenia's explicit `if sh < 32` guards against Rust UB ([`interpreter.rs:425`](https://git.mc02.dev/fabi/xenia-rs/src/commit/8401d4d5112849bf7b0f78f9d620f3620b1ce58d/crates/xenia-cpu/src/interpreter.rs)).
- **Shift count is 6 bits**, `RB[58:63]`. Counts `[32, 63]` produce zero (not `RS >> (count mod 32)`): Canary emits `Select(sh & 0x20, 0, RS[32:63] >> sh)` and zero-extends the result.
- **No `XER[CA]` produced.** For arithmetic shift with `XER[CA]` use [`srawx`](srawx.md).
- **`Rc=1` CR0 update truncates to 32 bits in xenia-rs.** [`interpreter.rs:428`](https://git.mc02.dev/fabi/xenia-rs/src/commit/8401d4d5112849bf7b0f78f9d620f3620b1ce58d/crates/xenia-cpu/src/interpreter.rs). Since the result has zeroed high 32 bits and zeroed sign bit (high bit of the 32-bit result is always 0 after a non-zero shift), CR0 will be `EQ` or `GT`.
- **`Rc=1` CR0 update truncates to 32 bits in Canary** (`f.UpdateCR(0, v)`). After a non-zero shift the 32-bit result's top bit is 0, so both give `EQ` or `GT`. With a count of 0 the result is `RS[32:63]` itself: if its top bit is set, spec (high word zero) says `GT` and Canary says `LT`.
- **No `OE` bit.**
- **`srwi` simplified mnemonic** uses [`rlwinmx`](rlwinmx.md), not this instruction. `srw` is for runtime-variable counts.

View File

@@ -119,9 +119,9 @@ int InstrEmit_subfcx(PPCHIRBuilder& f, const InstrData& i) {
## Special Cases & Edge Conditions
- **`RT ← RB RA` with `XER[CA]` set on no-borrow.** Same operand-order convention as [`subfx`](subfx.md): the *first* source is subtracted *from* the second.
- **`XER[CA] = 1` means *no borrow occurred*** — i.e. `RB >= RA` as unsigned. PowerISA encodes this as the carry-out of `~RA + RB + 1`, not as a borrow flag. Xenia's `if rb >= ra` test ([`interpreter.rs:157`](https://git.mc02.dev/fabi/xenia-rs/src/commit/8401d4d5112849bf7b0f78f9d620f3620b1ce58d/crates/xenia-cpu/src/interpreter.rs)) is the correct boolean encoding.
- **No trap on signed overflow.** `subfco` / `subfco.` set `XER[OV]` and sticky `XER[SO]`; xenia-rs leaves the `OE` arm unimplemented.
- **64-bit CR update on Xenon, 32-bit in xenia-rs.** [`interpreter.rs:160`](https://git.mc02.dev/fabi/xenia-rs/src/commit/8401d4d5112849bf7b0f78f9d620f3620b1ce58d/crates/xenia-cpu/src/interpreter.rs) truncates with `as i32 as i64`. Spec demands a full 64-bit signed compare for `subfc.`.
- **`XER[CA] = 1` means *no borrow occurred*** — i.e. `RB >= RA` as unsigned. PowerISA encodes this as the carry-out of `~RA + RB + 1`, not as a borrow flag. Canary's `SubDidCarry` computes it on the low 32 bits: `RB[32:63] >= RA[32:63]` unsigned.
- **No trap on signed overflow.** `subfco` / `subfco.` set `XER[OV]` and sticky `XER[SO]`. Canary's `OE` branch is `XEINSTRNOTIMPLEMENTED()` and does not store `CA` either.
- **64-bit CR update on Xenon, 32-bit in Canary** (`f.UpdateCR(0, v)`). Spec demands a full 64-bit signed compare for `subfc.`.
- **Seeds a multi-word subtract chain.** Use as the low-word op; continue with [`subfex`](subfex.md) for middle words and [`subfmex`](subfmex.md)/[`subfzex`](subfzex.md) for the high word.
- **Operand aliasing fine.** `subfc r3, r3, r3` always yields `0` with `CA = 1`.

View File

@@ -119,9 +119,9 @@ int InstrEmit_subfex(PPCHIRBuilder& f, const InstrData& i) {
## Special Cases & Edge Conditions
- **`RT ← ~RA + RB + XER[CA]`.** The middle link of a multi-word subtract chain seeded by [`subfcx`](subfcx.md). `XER[CA]` propagates the borrow from the previous word.
- **Carry-out predicate handles the boundary case.** Xenia computes `CA' = (rb > ra) || (rb == ra && CA != 0)` ([`interpreter.rs:170`](https://git.mc02.dev/fabi/xenia-rs/src/commit/8401d4d5112849bf7b0f78f9d620f3620b1ce58d/crates/xenia-cpu/src/interpreter.rs)). The second clause covers when `RB == RA` and the previous chain added a `+1` from the input carry — without it, the carry-out would be wrong.
- **`OE=1`** should set `XER[OV]` on signed overflow; xenia-rs ignores it.
- **64-bit CR update on Xenon, 32-bit in xenia-rs.** [`interpreter.rs:173`](https://git.mc02.dev/fabi/xenia-rs/src/commit/8401d4d5112849bf7b0f78f9d620f3620b1ce58d/crates/xenia-cpu/src/interpreter.rs).
- **Carry-out predicate handles the boundary case.** `CA' = (RB > RA) || (RB == RA && CA != 0)`. The second clause covers `RB == RA` with an incoming carry supplying the `+1` — without it, the carry-out would be wrong. Canary evaluates it on the low word via `AddWithCarryDidCarry(~RA, RB, CA)`.
- **`OE=1`** should set `XER[OV]` on signed overflow; Canary's `OE` branch is `XEINSTRNOTIMPLEMENTED()` and does not store `CA`.
- **64-bit CR update on Xenon, 32-bit in Canary** (`f.UpdateCR(0, v)`).
- **`XER[CA]` must be initialised** (typically by [`subfcx`](subfcx.md)). Reading stale `CA` is a frequent multi-word-subtract bug.
- **Symmetry with [`addex`](addex.md).** `subfe RT, RA, RB``adde RT, ~RA, RB` (with the implicit complement).

View File

@@ -108,7 +108,7 @@ int InstrEmit_subficx(PPCHIRBuilder& f, const InstrData& i) {
- **`RT ← SIMM RA` with `XER[CA]` always set.** Note the operand order: the *immediate* is the minuend, not the subtrahend. `subfic rD, rA, 1` computes `1 - rA`, useful for negation-plus-one or one's-complement-style operations.
- **Immediate is sign-extended** to 64 bits before the subtract. So `subfic rD, rA, -1` computes `-1 - rA`, equivalent to `~rA`.
- **`XER[CA] = 1` when `SIMM >= RA`** (no borrow). Computed in xenia as `if imm >= ra` ([`interpreter.rs:73`](https://git.mc02.dev/fabi/xenia-rs/src/commit/8401d4d5112849bf7b0f78f9d620f3620b1ce58d/crates/xenia-cpu/src/interpreter.rs)) — comparing the sign-extended unsigned representations.
- **`XER[CA] = 1` when `EXTS(SIMM) >= RA`** as unsigned (no borrow). Canary's `SubDidCarry` compares the low 32 bits: `EXTS(SIMM)[32:63] >= RA[32:63]`.
- **No `Rc`, no `OE`.** This D-form has no flag bits beyond the implicit `CA` write.
- **No record-form variant.** There is no `subfic.` in the ISA; if you need CR0 to also reflect the result, follow with a `cmpwi`.
- **Synthesised "subtract immediate"**. Assemblers sometimes accept `subi rD, rA, value` as a shorthand for `addi rD, rA, -value`, but for the carry-producing variant you must use `subfic` explicitly.

View File

@@ -124,9 +124,9 @@ int InstrEmit_subfmex(PPCHIRBuilder& f, const InstrData& i) {
- **`RT ← ~RA + (1) + XER[CA]``~RA 1 + CA`.** Terminator for a multi-word subtract chain when the high "minuend" word is implicitly all-ones (e.g. when computing `~x` style negation across many words).
- **`RB` field unused.** XO-form but only `RA` is read.
- **Carry-out predicate.** `CA' = (~RA != 0) || (CA != 0)` ([`interpreter.rs:191`](https://git.mc02.dev/fabi/xenia-rs/src/commit/8401d4d5112849bf7b0f78f9d620f3620b1ce58d/crates/xenia-cpu/src/interpreter.rs)). Only when `RA == ~0` (all ones) AND `CA == 0` does `CA'` become 0 — every other case produces no borrow on this final word.
- **`OE=1`** should set `XER[OV]` on signed overflow; xenia-rs ignores it.
- **64-bit CR update on Xenon, 32-bit in xenia-rs.** [`interpreter.rs:194`](https://git.mc02.dev/fabi/xenia-rs/src/commit/8401d4d5112849bf7b0f78f9d620f3620b1ce58d/crates/xenia-cpu/src/interpreter.rs).
- **Carry-out predicate.** `CA' = (~RA != 0) || (CA != 0)`. Only when `RA == ~0` (all ones) AND `CA == 0` does `CA'` become 0 — every other case produces no borrow on this final word. Canary evaluates it on the low word (`AddWithCarryDidCarry`).
- **`OE=1`** should set `XER[OV]` on signed overflow; Canary's `OE` branch is `XEINSTRNOTIMPLEMENTED()` and does not store `CA`.
- **64-bit CR update on Xenon, 32-bit in Canary** (`f.UpdateCR(0, v)`).
- **`XER[CA]` must be initialised** (typically by a [`subfcx`](subfcx.md) or [`subfex`](subfex.md) earlier in the chain).
- **Symmetric with [`addmex`](addmex.md)**, the add-side terminator.

View File

@@ -126,11 +126,11 @@ if Rc then
## Special Cases & Edge Conditions
- **Operand order gotcha.** `subf RT, RA, RB` computes `RT ← RB RA`, **not** `RA RB`. This reverses the intuitive ordering seen in x86/ARM. The assembler exposes a simplified mnemonic `sub RT, RX, RY``subf RT, RY, RX` that restores the natural order — watch for both forms in disassembly.
- **Implemented as add-with-complement.** Hardware (and xenia) compute `~RA + RB + 1`. All overflow/CR semantics are the same as [`addx`](addx.md) with one operand complemented.
- **Implemented as add-with-complement.** Hardware computes `~RA + RB + 1`; Canary emits the equivalent `RB RA`. All overflow/CR semantics are the same as [`addx`](addx.md) with one operand complemented.
- **No `XER[CA]` update** — use [`subfcx`](subfcx.md) if you need a borrow-out bit.
- **No trap on overflow.** `subfo` / `subfo.` only record the event in `XER[OV]` and sticky-set `XER[SO]`.
- **Signed-overflow predicate.** `OV = ((RA ^ RB) & (RB ^ RT)) >> 63` — set when operands have different signs and the result's sign differs from `RB`'s.
- **64-bit CR update on Xenon** (xenia-rs truncates to 32 bits; see [`addx`](addx.md) note).
- **64-bit CR update on Xenon** (Canary truncates to 32 bits via `f.UpdateCR(0, v)`; see the [`addx`](addx.md) note).
## Related Instructions

View File

@@ -123,9 +123,9 @@ int InstrEmit_subfzex(PPCHIRBuilder& f, const InstrData& i) {
- **`RT ← ~RA + 0 + XER[CA]``~RA + CA`.** The subtract-side high-word terminator for a multi-word subtract chain. Implements `0 - (...) - borrow` for the high word.
- **`RB` field unused.**
- **Carry-out predicate.** `CA' = (~RA != 0) || (CA != 0)` ([`interpreter.rs:180`](https://git.mc02.dev/fabi/xenia-rs/src/commit/8401d4d5112849bf7b0f78f9d620f3620b1ce58d/crates/xenia-cpu/src/interpreter.rs)). Only `RA == ~0 && CA == 0` produces `CA' = 0`; every other case gives no-borrow.
- **`OE=1`** should set `XER[OV]` on signed overflow; xenia-rs ignores.
- **64-bit CR update on Xenon, 32-bit in xenia-rs.** [`interpreter.rs:183`](https://git.mc02.dev/fabi/xenia-rs/src/commit/8401d4d5112849bf7b0f78f9d620f3620b1ce58d/crates/xenia-cpu/src/interpreter.rs).
- **Carry-out predicate.** `RT = ~RA + CA`, which carries out only when `~RA` is all ones and `CA = 1`: `CA' = (RA == 0) && (CA != 0)`. (This page previously gave `(~RA != 0) || CA`, which is [`subfmex`](subfmex.md)'s predicate.) Canary evaluates it on the low word via `AddWithCarryDidCarry(~RA, 0, CA)`.
- **`OE=1`** should set `XER[OV]` on signed overflow; Canary's `OE` branch is `XEINSTRNOTIMPLEMENTED()` and does not store `CA`.
- **64-bit CR update on Xenon, 32-bit in Canary** (`f.UpdateCR(0, v)`).
- **`XER[CA]` must be initialised** by an earlier carrying instruction.
- **Common idiom: extracting `XER[CA]` as 0/-1.** `subfze rT, rN` (where `rN == 0`) materialises `XER[CA]` to `0` or `-1` (`-1 = ~0 + CA = -1 + CA`); pair with [`addzex`](addzex.md) for `0/1` instead.

View File

@@ -99,7 +99,7 @@ int InstrEmit_sync(PPCHIRBuilder& f, const InstrData& i) {
- **`L` field selects sync class.** `L=0` is full *hwsync* (the default). `L=1` is `lwsync` — orders only loads-after-loads, loads-after-stores, and stores-after-stores (not stores-after-loads). The Xenon implements both via the same encoding with `L` (bit 9) selecting variant. Most disassembly shows the unsuffixed `sync` mnemonic, which assembles to `L=0`.
- **No register or CR effects.** Pure ordering primitive.
- **Used to implement release semantics.** A typical lock-release sequence is `sync; stw r0, lock`. Acquire side uses `lwsync` after the load.
- **Xenia-rs is a no-op.** [`interpreter.rs:1267`](https://git.mc02.dev/fabi/xenia-rs/src/commit/8401d4d5112849bf7b0f78f9d620f3620b1ce58d/crates/xenia-cpu/src/interpreter.rs) collapses `sync`, `eieio`, `isync` into PC-advance. Since xenia is single-threaded interpretation, host program order subsumes all PPC ordering.
- **Canary emits `f.MemoryBarrier()`**, as for `eieio`; `isync` is a plain `f.Nop()`. A sequential C translation needs neither: host program order subsumes the PPC ordering.
- **Distinct from [`isync`](isync.md)**, which orders the *instruction* stream — `sync` does not refetch instructions.
- **Slow on real hardware.** Hundreds of cycles when the store queue is full; hot paths avoid `sync` and use `lwsync` or no barrier when only single-thread ordering is needed.

View File

@@ -101,7 +101,7 @@ int InstrEmit_xori(PPCHIRBuilder& f, const InstrData& i) {
- **No record form.** Like [`ori`](ori.md), there is no `xori.`. To get a CR0 update follow with `cmpwi` or use [`xorx`](xorx.md) with `Rc=1`.
- **Immediate is zero-extended** to 64 bits. Only the low 16 bits of `RA` can be flipped; the high 48 bits are passed through from `RS` unchanged.
- **`xori 0, 0, 0` is a valid NOP encoding** but the canonical NOP is `ori 0, 0, 0`. Disassemblers should still display this as `xori r0, r0, 0` or recognise it as a no-op.
- **64-bit operation in xenia-rs.** [`interpreter.rs:338`](https://git.mc02.dev/fabi/xenia-rs/src/commit/8401d4d5112849bf7b0f78f9d620f3620b1ce58d/crates/xenia-cpu/src/interpreter.rs) — full `u64` XOR with the immediate.
- **64-bit operation in Canary.** `f.Xor(RS, EXTZ16(UI))` on the full `u64`.
- **No `XER`, no `CR`** side effects.
- **`RA = 0` reads `r0`** (not literal zero); see [`ori`](ori.md).
- **Useful for masked toggle.** `xori rA, rS, mask` flips the bits of `rS` indicated by `mask` (low 16 bits only).

View File

@@ -102,7 +102,7 @@ int InstrEmit_xoris(PPCHIRBuilder& f, const InstrData& i) {
- **No record form.** Like all immediate logicals other than `andi.`/`andis.`, `xoris` does not update CR0.
- **Immediate is zero-extended *then* shifted left 16.** Only bits 3247 of `RA` (PowerISA bit numbering) can be flipped; the high 32 bits and low 16 bits of `RA` come from `RS` unchanged.
- **Common pattern with [`xori`](xori.md)** to flip arbitrary 32-bit bitmasks: `xoris RA, RS, hi16; xori RA, RA, lo16`.
- **64-bit operation.** [`interpreter.rs:342`](https://git.mc02.dev/fabi/xenia-rs/src/commit/8401d4d5112849bf7b0f78f9d620f3620b1ce58d/crates/xenia-cpu/src/interpreter.rs).
- **64-bit operation.** Canary: `f.Xor(RS, EXTZ16(UI) << 16)` on the full `u64`.
- **No `XER`, no `CR`.**
- **`RA = 0` reads `r0`** (not literal zero).
- **Used to toggle the high half of a 32-bit word**, e.g. `xoris r3, r3, 0x8000` flips bit 32 (the sign bit of the low word) — a one-instruction sign-flip on a 32-bit value.

View File

@@ -110,7 +110,7 @@ int InstrEmit_xorx(PPCHIRBuilder& f, const InstrData& i) {
- **Operand convention** is X-form (`RA` destination, `RS`/`RB` sources).
- **64-bit operation** on Xenon.
- **No `OE` or `XER` side effects.** Only `Rc=1` updates `CR0`.
- **64-bit CR update on Xenon, 32-bit in xenia-rs.** [`interpreter.rs:367`](https://git.mc02.dev/fabi/xenia-rs/src/commit/8401d4d5112849bf7b0f78f9d620f3620b1ce58d/crates/xenia-cpu/src/interpreter.rs) truncates with `as i32 as i64`. For `xor.` whose result has differing high/low halves, spec and xenia diverge; `xor. RA, RS, RS` gives `EQ` either way.
- **64-bit CR update on Xenon, 32-bit in Canary** (`f.UpdateCR(0, v)`). For `xor.` whose result has differing high/low halves, spec and Canary diverge; `xor. RA, RS, RS` gives `EQ` either way.
- **Useful as bitmask toggle.** `xor r3, r3, r4` flips in `r3` every bit set in `r4`.
- **No `XER[CA]`.**

View File

@@ -131,7 +131,7 @@ int InstrEmit_bcctrx(PPCHIRBuilder& f, const InstrData& i) {
## Special Cases & Edge Conditions
- **No CTR decrement.** Unlike [`bcx`](bcx.md) and [`bclrx`](bclrx.md), `bcctr` cannot decrement CTR (the CTR is the *target*). The PowerISA reserves `BO[2] = 0` encodings — they are *invalid* on `bcctrx`. xenia silently ignores `BO[2]`/`BO[3]` and treats every `bcctr` as a pure CR-conditional branch, which matches both the canary emit and real Xenon hardware behaviour.
- **No CTR decrement.** Unlike [`bcx`](bcx.md) and [`bclrx`](bclrx.md), `bcctr` cannot decrement CTR (the CTR is the *target*). The PowerISA reserves `BO[2] = 0` encodings — they are *invalid* on `bcctrx`. Canary ignores `BO[2]`/`BO[3]` and treats every `bcctr` as a pure CR-conditional branch.
- **CTR alignment mask.** The target is `CTR & ~3`. Like `bclr`, the low two bits are stripped — a misaligned CTR is silently rounded down rather than trapping.
- **BO encoding (CR-only subset).** Because CTR-test bits are unused, only four `BO` patterns are meaningful:
@@ -142,9 +142,9 @@ int InstrEmit_bcctrx(PPCHIRBuilder& f, const InstrData& i) {
| `1z1zz` | branch always (`bctr`) |
| `0000z`/`001at`/etc. | reserved — implementation-defined |
- **Indirect call/dispatch idiom.** `mtctr rN; bctrl` is the canonical PPC indirect call: load function pointer into CTR, call. The xenia interpreter writes `LR ← CIA + 4` only when the branch is taken — this matches the PowerISA, but contrast with `bcx` where `LK` always writes LR (even if the branch is not taken). The C-translation reference handles this asymmetry explicitly.
- **`bctr` for switch tables.** Compilers emit `bctr` (not `bctrl`) for jump-table dispatch, with CTR loaded from a base + (index*4) lookup. Xenia honours this by simply jumping to `CTR & ~3`.
- **Synchronisation.** Marked `sync` in xenia's XML — context-synchronising. JIT backends must ensure prior side effects have committed before the indirect transfer.
- **Indirect call/dispatch idiom.** `mtctr rN; bctrl` is the canonical PPC indirect call: load function pointer into CTR, call. With `LK=1`, PowerISA writes `LR ← CIA + 4` whether or not the branch is taken, and Canary does the same — its `InstrEmit_branch` stores `LR` before the conditional transfer, exactly as for [`bcx`](bcx.md).
- **`bctr` for switch tables.** Compilers emit `bctr` (not `bctrl`) for jump-table dispatch, with CTR loaded from a base + (index*4) lookup. Canary emits an indirect call to the `CTR` value (`CallIndirect`).
- **Synchronisation.** Marked `sync` in Canary's `tools/ppc-instructions.xml` — context-synchronising. JIT backends must ensure prior side effects have committed before the indirect transfer.
- **No prediction hint sensitivity.** Xenon predicts indirect branches via a separate target cache; the `BO[4]` hint is mostly cosmetic for `bcctr`.
## Related Instructions

View File

@@ -187,10 +187,10 @@ The most common bclr instance in Xbox 360 disassembly is `BO = 0b10100` → `blr
- **LR alignment mask.** The target address is `LR & ~3` — the low 2 bits are cleared. This silently ignores a misaligned LR; incoming code should always produce 4-byte-aligned LR values.
- **Ordering of CTR decrement and branch.** The CTR is decremented **first**, then compared to zero **after** the decrement. So after `bdnz` at `CTR = 1`, the CTR becomes `0` and the branch is not taken.
- **Self-referential LR write.** `bclrl` writes `LR ← CIA + 4` **before** reading `LR` to set `NIA`. Per the PowerISA, `bclrl` reads the *old* `LR` for the branch target and writes the *new* `LR` with the return address, atomically from software's perspective. Xenia implements it this way (`next_pc` captured first, then `lr` written).
- **Self-referential LR write.** `bclrl` writes `LR ← CIA + 4` **before** reading `LR` to set `NIA`. Per the PowerISA, `bclrl` reads the *old* `LR` for the branch target and writes the *new* `LR` with the return address, atomically from software's perspective. Canary implements it this way: `InstrEmit_bclrx` loads `LR` as the target before `InstrEmit_branch` stores `CIA + 4`.
- **Branch prediction hints (`BO[4]`).** The Xenon does static prediction on the basis of these hints, but behaviour is architecturally unobservable. Translators may ignore them.
- **Synchronisation.** `bclr` is **context-synchronising** (hence the `sync` flag in xenia's XML). Translators must ensure side-effecting instructions preceding the branch have committed — trivial in a sequential C translation but relevant for JIT backends.
- **xenia's `LR_HALT_SENTINEL`.** Xenia sets `LR` to `0xBCBCBCBC` at thread start; when the top-level guest function returns via `blr`, the interpreter loop halts cleanly. Translators replicating guest behaviour don't need this — but if you generate a test harness, the sentinel is a convenient "function returned" signal.
- **Synchronisation.** `bclr` is **context-synchronising** (hence the `sync` flag in Canary's `tools/ppc-instructions.xml`). Translators must ensure side-effecting instructions preceding the branch have committed — trivial in a sequential C translation but relevant for JIT backends.
- **Canary's `0xBCBCBCBC` return sentinel.** When the host calls into a guest function, Canary's `Processor::Execute` sets `LR` to `0xBCBCBCBC`, so the top-level `blr` hands control back to the host. Translators replicating guest behaviour don't need this — but if you generate a test harness, the sentinel is a convenient "function returned" signal.
## Related Instructions

View File

@@ -183,7 +183,7 @@ int InstrEmit_bcx(PPCHIRBuilder& f, const InstrData& i) {
- **14-bit signed displacement.** `BD` is a 14-bit signed word-count, scaled by 4 — yielding a ±32 KiB byte range (`2^15 … +2^15 4`). For longer-range conditional control flow, compilers emit a short `bc` over an unconditional `b`.
- **CTR decrement happens before the test.** `BO[2]=0` decrements CTR *first*, then `ctr_ok` evaluates against the new value. The classic `bdnz loop` loops `N` times when CTR is initialised to `N`.
- **LR write is unconditional in xenia.** Xenia writes `LR ← CIA + 4` whenever `LK=1`, even on the not-taken path. This matches the PowerISA: `bcl` always sets `LR` regardless of branch outcome — exploited by `bcl 20, 31, $+4` as a self-PC capture (PIC trick).
- **LR write is unconditional in Canary.** Canary writes `LR ← CIA + 4` whenever `LK=1`, even on the not-taken path. This matches the PowerISA: `bcl` always sets `LR` regardless of branch outcome — exploited by `bcl 20, 31, $+4` as a self-PC capture (PIC trick).
- **`BO` encoding** — see `bclrx.md` for the full 5-bit table. `bcx` supports the full set, including CTR-only branches (`bdnz`, `bdz`).
- **Branch hint encoding.** PPC overloads `BO[4]` as a static prediction hint: 0 = "predict not taken", 1 = "predict taken". The Xenon honours it for forward branches; backwards conditional branches are predicted taken regardless. Translators may ignore the hint.
- **Synchronisation.** Marked `sync` — like all branches, `bcx` is context-synchronising. Trivial in interpretation; matters for JIT reorder windows.

View File

@@ -113,9 +113,9 @@ int InstrEmit_sc(PPCHIRBuilder& f, const InstrData& i) {
- `LEV = 2`**hypervisor syscall** (`HVcall`). On the Xenon, `sc 2` traps to the Xbox 360 hypervisor; this is how the kernel itself talks to the supervisor below it (e.g., for security operations, encrypted-memory accesses, page table updates).
- **`sc` as written by titles.** Almost all guest game code uses `LEV = 0` to call `XboxKrnl.exe`. Game disassembly will show large jump tables of small thunks each ending in `li r0, syscall_no; sc; blr`.
- **No condition or status side effects.** `sc` updates *no* general-purpose register on entry — neither LR nor CR. The kernel sees the GPR/FPR snapshot as-is and reads the syscall number out of `r0` (Xbox 360 ABI convention, not architectural).
- **Return path.** Hardware returns from `sc` via [`rfid`](../control/mtmsrd.md)-class instructions in the kernel handler; from the application's perspective execution resumes at `CIA + 4`. Xenia's interpreter realises this by simply pre-incrementing `pc` then returning `StepResult::SystemCall` — the host driver dispatches the syscall and re-enters the loop.
- **xenia divergence vs hardware.** xenia-rs *does not* model the `0xC00` exception vector or save SRR0/SRR1; the `LEV` operand is currently ignored. All `sc` instructions are treated identically and serviced by the host. This is sufficient because Xbox 360 titles don't observe SRR registers and the host kernel is implemented natively.
- **Synchronisation.** Marked `sync` in xenia's XML`sc` is context-synchronising (hardware completes all prior instructions before raising the exception). JITs must flush pending state before emitting the host call.
- **Return path.** Hardware returns from `sc` via [`rfid`](../control/mtmsrd.md)-class instructions in the kernel handler; from the application's perspective execution resumes at `CIA + 4`. Canary emits `sc` as a host call (`CallExtern` of its syscall handler), after which the translated code simply continues at `CIA + 4`.
- **Canary divergence vs hardware.** Canary does *not* model the `0xC00` exception vector or save SRR0/SRR1. `LEV=0` calls its syscall handler, `LEV=2` is Canary's own marker for an import call, and any other `LEV` is unimplemented. This is sufficient because Xbox 360 titles don't observe SRR registers and Canary implements the kernel natively.
- **Synchronisation.** Marked `sync` in Canary's `tools/ppc-instructions.xml``sc` is context-synchronising (hardware completes all prior instructions before raising the exception). JITs must flush pending state before emitting the host call.
- **Reserved bits.** Bit 30 is fixed `1`; bits 619 and 2729 are reserved (must be 0). The 1-bit field at position 30 distinguishes the `sc` encoding from `scv` (later PowerISA addition, not present on the Xenon).
## Related Instructions

View File

@@ -128,8 +128,8 @@ int InstrEmit_td(PPCHIRBuilder& f, const InstrData& i) {
- **64-bit comparison.** Unlike [`tw`](tw.md), `td` always compares the full 64-bit GPRs. On the Xenon (64-bit) this is meaningful; PPC32 implementations don't have `td`.
- **No register effects.** Only the side effect is the trap. No CR/LR/CTR/XER updates.
- **Hardware behaviour.** When the trap fires, hardware raises a Program interrupt with `SRR1[TRAP]` set and vectors to `0x700`. The Xbox 360 hypervisor / kernel handles it (assertion failure, debugger trap, etc.).
- **xenia simplification.** xenia-rs collapses all four trap variants (`td`, `tdi`, `tw`, `twi`) into one match arm that *unconditionally* logs and returns `StepResult::Trap` — it does **not** evaluate `TO` against the operands. This is a material divergence from the spec: in xenia every trap fires even if the condition is false. Real Xenon code rarely uses non-trivial `TO` masks (typical use is the unconditional `trap` for `__assert` / debugger break), so the divergence is normally invisible.
- **Distinguishing assert vs. break.** Compilers commonly emit `tdne r3, r3` (impossible) or `tdi 0, r0, 0` patterns that *cannot* trap as inert markers. Xenia's blanket trap would mis-fire on these — a small known bug; track it if you see spurious traps.
- **Canary evaluates the condition.** Canary evaluates `TO` properly: it compares the operands for each set bit and emits a conditional trap (`TrapTrue`), and `TO = 0` emits nothing. The `ignore_trap_instructions` cvar suppresses traps entirely.
- **Distinguishing assert vs. break.** Compilers commonly emit `tdne r3, r3` (impossible) or `tdi 0, r0, 0` patterns that *cannot* trap as inert markers. Canary evaluates `TO`, so these stay inert.
## Related Instructions

View File

@@ -116,7 +116,7 @@ int InstrEmit_tdi(PPCHIRBuilder& f, const InstrData& i) {
- **`TO = 31` is unconditional.** `tdi 31, 0, 0` is a debugger / assert trap. Compilers sometimes use it as a "should not reach" marker.
- **64-bit comparison only.** Unlike [`twi`](twi.md), `tdi` always compares the full 64-bit GPR — it has no PPC32 analogue. The Xenon's PPC64 mode makes this meaningful.
- **No register effects.** Pure side effect on success: Program interrupt → vector `0x700` with `SRR1[TRAP]=1`.
- **xenia simplification.** xenia-rs unconditionally treats `tdi` as a fired trap, regardless of `TO`/`RA`/`SIMM` values. This diverges from the spec — real hardware would silently fall through when no `TO` bit's condition holds. Most title code uses only the unconditional `trap` form, so the divergence is normally invisible; non-firing assertion patterns (e.g. `tdi 0, r0, 0`) will mis-fire under xenia.
- **Canary evaluates the condition.** Canary evaluates `TO` properly: it compares the operands for each set bit and emits a conditional trap (`TrapTrue`), and `TO = 0` emits nothing. So `tdi 0, r0, 0` falls through, as on hardware.
- **Reserved bits.** Bits 610 carry the `TO` field; there is no `Rc` / `OE` on D-form trap immediates.
## Related Instructions

View File

@@ -130,8 +130,8 @@ int InstrEmit_tw(PPCHIRBuilder& f, const InstrData& i) {
- **`tw 31, 0, 0` is `trap`.** The simplified mnemonic `trap` expands to `tw 31, r0, r0` — all five `TO` bits set ⇒ unconditional trap. Compilers and the kernel use this as the assertion / debugger break primitive; it appears as `0x7FE00008` in raw bytes.
- **Conditional asserts.** GCC's `__builtin_trap` and MSVC's `__assert` macros emit `tw` variants like `twge`/`twlt` to fault on bound-check failures.
- **No register effects.** Side effect only: Program interrupt (`0x700`) with `SRR1[TRAP]=1`.
- **xenia simplification.** xenia-rs collapses `td/tdi/tw/twi` into a single arm that *unconditionally* logs and returns `StepResult::Trap` — the `TO` operand is **not evaluated**. Real hardware would silently fall through when no `TO` bit's condition holds. In practice titles use mostly the unconditional `trap`, so the divergence rarely manifests, but inert-marker patterns like `tw 0, r0, r0` will fire under xenia.
- **Inert encoding.** `tw 0, r0, r0` (no `TO` bits set) can never trap on real hardware. It encodes as `0x7C000008` — sometimes used as a structured-NOP marker. Watch for it in xenia traces.
- **Canary evaluates the condition.** Canary evaluates `TO` properly: it compares the operands for each set bit and emits a conditional trap (`TrapTrue`), and `TO = 0` emits nothing. `tw` compares the sign-extended low 32 bits of both operands, and inert markers like `tw 0, r0, r0` fall through.
- **Inert encoding.** `tw 0, r0, r0` (no `TO` bits set) can never trap on real hardware, and Canary emits nothing for `TO = 0`. It encodes as `0x7C000008` — sometimes used as a structured-NOP marker.
## Related Instructions

View File

@@ -123,7 +123,7 @@ int InstrEmit_twi(PPCHIRBuilder& f, const InstrData& i) {
- **`twi 31, 0, 0` is unconditional trap.** All `TO` bits set ⇒ guaranteed trap. The simplified mnemonic family (`twnei`, `twgei`, …) is much more common in real code: bound checks, null checks, integer-divide-by-zero pre-checks.
- **Compiler usage.** Xbox 360 GCC emits `twnei rN, -1` and similar to validate handle-style return values; the kernel handler turns the trap into an exception delivered to the title.
- **No register effects.** Side effect: Program interrupt → vector `0x700` with `SRR1[TRAP]=1`.
- **xenia simplification.** Same as the other three trap forms — xenia-rs unconditionally returns `StepResult::Trap` whenever it decodes any of `tdi`/`twi`/`td`/`tw`, regardless of the `TO` mask or operands. This means `twi 0, r0, 0` (architecturally a guaranteed-no-trap encoding) will spuriously fire under xenia. Keep this in mind when triaging unexpected trap signals.
- **Canary evaluates the condition.** Canary evaluates `TO` properly: it compares the operands for each set bit and emits a conditional trap (`TrapTrue`), and `TO = 0` emits nothing. `twi 0, r0, 0` therefore falls through. One special case: `twi 31, r0, <imm>` (`TO = 31`, `RA = 0`) becomes an unconditional `Trap` carrying `<imm>` as its type.
- **No `Rc` / `OE`.** D-form trap immediates have neither.
## Related Instructions

View File

@@ -113,7 +113,7 @@ int InstrEmit_crand(PPCHIRBuilder& f, const InstrData& i) {
- **Combining branch conditions.** The classic use: synthesise complex branch conditions from multiple compare results. Example: `cmpw cr0, r3, r4; cmpw cr1, r5, r6; crand 4*cr0+2, 4*cr0+2, 4*cr1+2; beq cr0, label` branches if `r3==r4 AND r5==r6` using a single conditional branch.
- **No `Rc` / `OE`.** XL-form CR-logical ops never set CR0 or XER; they only update the named CR bit.
- **Not synchronising.** Pure data-flow on CR; freely reorderable.
- **xenia status.** xenia-rs decodes `crand` (decoder slot 540) but the interpreter snapshot is not embedded on this page — implementation lives in [`crates/xenia-cpu/src/interpreter.rs`](https://git.mc02.dev/fabi/xenia-rs/src/commit/8401d4d5112849bf7b0f78f9d620f3620b1ce58d/crates/xenia-cpu/src/interpreter.rs). xenia-canary's `InstrEmit_crand` emits the equivalent host AND of the two CR bits.
- **Canary status.** Canary's `InstrEmit_crand` emits a host AND of the two CR bits.
## Related Instructions

View File

@@ -113,7 +113,7 @@ int InstrEmit_crandc(PPCHIRBuilder& f, const InstrData& i) {
- **Use case.** Synthesises "branch if A *and not* B" predicates without a dedicated `cmp` of `B`. Example: branch only if `cr0.EQ` *and not* `cr1.SO``crandc 2, 2, 7` then `beq` on `cr0`.
- **No `Rc` / `OE`.** Pure CR-bit dataflow; doesn't update CR0 or XER.
- **Not synchronising.** Reorderable.
- **xenia status.** Interpreter handles via the generic CR-logical helper. xenia-canary's `InstrEmit_crandc` emits a host AND of `A` and bitwise-NOT of `B`.
- **Canary status.** Canary's `InstrEmit_crandc` emits a host AND of `A` and bitwise-NOT of `B`.
## Related Instructions

View File

@@ -113,7 +113,7 @@ int InstrEmit_creqv(PPCHIRBuilder& f, const InstrData& i) {
- **Use case.** Branch on "A == B" of two prior compare results. Example: `crxor` of CR0.SO and CR1.SO gives "differ"; `creqv` gives "agree".
- **No `Rc` / `OE`.** Doesn't touch CR0, XER, or any other state beyond the named bit.
- **Not synchronising.** Reorderable.
- **xenia status.** Interpreter dispatches through the generic CR-logical helper; canary emits the host XNOR equivalent. The `crset` simplified form is the most common occurrence in real Xbox 360 code.
- **Canary status.** Canary emits the host equality compare of the two bits (XNOR). The `crset` simplified form is the most common occurrence in real Xbox 360 code.
## Related Instructions

View File

@@ -113,7 +113,7 @@ int InstrEmit_crnand(PPCHIRBuilder& f, const InstrData& i) {
- **Use case.** Branch on "NOT (A AND B)". Less common than the De Morgan equivalent (`cror BT, ¬A, ¬B`), but saves an extra `crnot` step.
- **No `Rc` / `OE`.** No CR0 / XER side effects.
- **Not synchronising.** Reorderable.
- **xenia status.** Decoded by the generic XL-form CR-logical handler; the interpreter snapshot is shared with `crand`/`cror`/etc. xenia-canary's `InstrEmit_crnand` emits a host AND followed by NOT.
- **Canary status.** Canary's `InstrEmit_crnand` emits a host AND followed by NOT.
## Related Instructions

View File

@@ -113,7 +113,7 @@ int InstrEmit_crnor(PPCHIRBuilder& f, const InstrData& i) {
- **Use case.** Branch on "neither A nor B"; or, with `crnot`, simply complement a CR bit before consuming it in a `bcx`.
- **No `Rc` / `OE`.** Pure CR-bit dataflow; CR0/XER untouched.
- **Not synchronising.** Reorderable.
- **xenia status.** Decoded via the generic CR-logical handler. xenia-canary's `InstrEmit_crnor` emits a host OR followed by NOT.
- **Canary status.** Canary's `InstrEmit_crnor` emits a host OR followed by NOT.
## Related Instructions

View File

@@ -113,7 +113,7 @@ int InstrEmit_cror(PPCHIRBuilder& f, const InstrData& i) {
- **Use case.** Branch on "A OR B" of two prior compare results — saves an extra branch by collapsing two conditions.
- **No `Rc` / `OE`.** Pure CR-bit dataflow.
- **Not synchronising.** Reorderable.
- **xenia status.** Most-used CR-logical instruction in real code (almost always as `crmove`). Decoded by the generic XL-form CR-logical handler; canary emits a host OR.
- **Canary status.** Most-used CR-logical instruction in real code (almost always as `crmove`). Canary emits a host OR.
## Related Instructions

View File

@@ -114,7 +114,7 @@ int InstrEmit_crorc(PPCHIRBuilder& f, const InstrData& i) {
- **Use case.** Compose "if B then A" guards without a separate complement step.
- **No `Rc` / `OE`.** Doesn't update CR0 or XER.
- **Not synchronising.** Reorderable.
- **xenia status.** Decoded by the generic CR-logical handler; canary emits OR-with-NOT directly.
- **Canary status.** Canary emits OR-with-NOT directly.
## Related Instructions

View File

@@ -113,7 +113,7 @@ int InstrEmit_crxor(PPCHIRBuilder& f, const InstrData& i) {
- **Use case.** Branch on "A != B"; or, with the `crclr` idiom, zero a CR bit before fall-through CR computation.
- **No `Rc` / `OE`.** No CR0 / XER side effects.
- **Not synchronising.** Reorderable.
- **xenia status.** Common enough in real code (typically as `crclr 6` for the variadic-FP marker) that translators often special-case the `crclr` pattern. xenia-canary's `InstrEmit_crxor` emits a host XOR; xenia-rs decodes via the generic CR-logical handler.
- **Canary status.** Common enough in real code (typically as `crclr 6` for the variadic-FP marker) that translators often special-case the `crclr` pattern. Canary's `InstrEmit_crxor` emits a host XOR of the two bits.
## Related Instructions

View File

@@ -110,11 +110,11 @@ int InstrEmit_mcrf(PPCHIRBuilder& f, const InstrData& i) {
- **Field-level (4-bit) move.** Unlike the bit-level CR-logical family ([`crand`](crand.md), …, [`crxor`](crxor.md)), `mcrf` copies *all four* bits of a CR field (LT, GT, EQ, SO) in one instruction. `CRFD` and `CRFS` are 3-bit field indices (0..7), each naming a 4-bit slice of the 32-bit CR.
- **No source-field clobber.** The source field is read, not modified — `mcrf 0, 1` copies CR1 into CR0 leaving CR1 intact.
- **Same-field is a NOP.** `mcrf cr0, cr0` reads-then-writes the same field; xenia's interpreter still does the assignment but the architectural state is unchanged.
- **Same-field is a NOP.** `mcrf cr0, cr0` reads-then-writes the same field; Canary still emits the four bit copies but the architectural state is unchanged.
- **Use case.** Promote a non-default compare result into `cr0` so a default-`cr0` simplified branch (`beq label`) can consume it without spelling out `cr1`/`cr2`/etc. The alternative — `crmove` — would require four `cror` instructions to move all four bits.
- **No CR0/XER side effects.** Pure CR-field dataflow.
- **Not synchronising.** Reorderable.
- **xenia exact match.** xenia-rs models the CR as an array of eight 4-bit fields, so `mcrf` is a single struct copy (`ctx.cr[crfd] = ctx.cr[crfs]`). Matches PowerISA semantics exactly.
- **Canary exact match.** Canary copies the field bit by bit (`StoreCRField(crfd, bit, LoadCRField(crfs, bit))` for all four bits), matching PowerISA semantics exactly.
## Related Instructions

View File

@@ -129,7 +129,7 @@ int InstrEmit_mcrfs(PPCHIRBuilder& f, const InstrData& i) {
- **CR field destination.** `CRFD` is a 3-bit field index (0..7); the four bits land in their natural positions (LT, GT, EQ, SO) of the chosen CR field. After `mcrfs`, `crf` can be tested with the usual conditional branches.
- **Use case.** Inspect a particular FPSCR exception group, then act on it with a `bc` — e.g. test FPSCR[24..27] (the FI / FR / VXSNAN / VXISI cluster) and branch.
- **Privilege.** Non-privileged on the Xenon — application-visible.
- **xenia status.** Decoded (decoder slot 727), but the interpreter does **not** ship a body in the snapshot on this page — `mcrfs` is rare in title code. xenia's FPSCR model is incomplete (most exception bits are stubbed), so even when implemented, the cleared bits typically have no observable effect.
- **Canary status.** Implemented: Canary copies the FPSCR field into the CR field and then zeroes all four bits of that FPSCR field (PowerISA clears only the exception bits in it). Its FPSCR model is incomplete (`UpdateFPSCR` is a stub), so the cleared bits rarely have an observable effect.
- **No `Rc`.** X-form, but the `Rc` bit position is unused (reserved 0).
## Related Instructions

View File

@@ -130,7 +130,7 @@ int InstrEmit_mcrxr(PPCHIRBuilder& f, const InstrData& i) {
- **Use case.** Saturating-arithmetic loops sample XER[OV] periodically; `mcrxr cr0; bso cr0, overflow` is the canonical "did overflow happen since last check?" idiom.
- **CR field destination.** `CRFD` is a 3-bit index (0..7). All other CR fields are preserved.
- **No reads of GPRs.** `mcrxr` reads only XER, writes only the chosen CR field and XER.
- **xenia exact match.** xenia-rs implements the full sample-and-clear semantics: writes `lt = SO`, `gt = OV`, `eq = CA`, `so = false`, then zeroes `xer_so`, `xer_ov`, `xer_ca`. Matches PowerISA exactly.
- **Canary exact match.** Canary copies `XER[0:3]` (`SO`, `OV`, `CA` and the reserved bit) into the target CR field, then clears those four XER bits — the full sample-and-clear semantics of PowerISA.
- **Deprecated in newer PowerISA.** PowerISA v2.06+ marked `mcrxr` deprecated in favour of `mcrxrx` and explicit `mfxer`/`mtxer` patterns, but the Xenon predates that; titles still emit it freely.
## Related Instructions

View File

@@ -137,9 +137,9 @@ int InstrEmit_mfcr(PPCHIRBuilder& f, const InstrData& i) {
- **No CR side effect.** `mfcr` is a read; CR is unmodified. The XL-form's nominal `Rc` bit is unused on this opcode.
- **Saving CR across calls.** The Xbox 360 / SysV ABI requires non-volatile CR fields (CR2..CR4) to be preserved across calls. Standard prologue: `mfcr r12; stw r12, 8(r1)`. Epilogue restores via [`mtcrf`](mtcrf.md).
- **Bit ordering.** PowerPC numbers bits big-endian (bit 0 = MSB). The encoding into the GPR follows the same convention: CR0.LT lands in bit 32 of the doubleword (the MSB of the low word). C-side translations should mask with `0xFFFFFFFFu` before consuming.
- **`mfocrf` variant.** PowerISA defines `mfocrf` (one CR field), encoded as `mfcr` with the high bit of FXM set. xenia-rs decodes both as the same opcode and ignores the FXM hint, returning the entire CR. This is benign — the spec says implementations may treat `mfocrf` as `mfcr`.
- **`mfocrf` variant.** PowerISA defines `mfocrf` (one CR field), encoded as `mfcr` with the high bit of FXM set. Canary honours it: with exactly one FXM bit set it returns just that field, with any other count it returns 0. The spec leaves the other bits undefined, so this is compliant.
- **Not synchronising.** Reorderable.
- **xenia exact match.** xenia-rs packs its eight `CrField` structs into a `u64` via `ctx.cr()`, mirroring spec semantics.
- **Canary exact match.** Plain `mfcr` returns the whole CR (`LoadCR()`), mirroring spec semantics.
## Related Instructions
@@ -147,7 +147,7 @@ int InstrEmit_mfcr(PPCHIRBuilder& f, const InstrData& i) {
- [`mcrf`](mcrf.md), [`mcrxr`](mcrxr.md), [`mcrfs`](mcrfs.md) — narrower CR-field moves.
- [`mfspr`](mfspr.md), [`mtspr`](mtspr.md) — generic SPR moves; CR is *not* an SPR (it has its own opcode).
`mfcr` has no simplified mnemonics. `mfocrf RT, FXM` is a related encoding handled by the same xenia-rs slot.
`mfcr` has no simplified mnemonics. `mfocrf RT, FXM` is a related encoding handled by the same Canary emitter (`InstrEmit_mfcr`).
## IBM Reference

View File

@@ -108,11 +108,11 @@ int InstrEmit_mffsx(PPCHIRBuilder& f, const InstrData& i) {
## Special Cases & Edge Conditions
- **Operation.** Reads the 32-bit FPSCR and places it in the **low 32 bits** of `FRT`. The high 32 bits of the destination FPR are architecturally undefined; xenia leaves them as the bit-pattern of the FPSCR cast to `u64` (i.e. the high bits are zero, since FPSCR is 32-bit). PowerISA explicitly permits implementations to leave anything there.
- **Operation.** Reads the 32-bit FPSCR and places it in the **low 32 bits** of `FRT`. The high 32 bits of the destination FPR are architecturally undefined; Canary zero-extends the FPSCR to 64 bits and reinterprets that as the FPR's bit pattern, so the high bits are zero. PowerISA explicitly permits implementations to leave anything there.
- **Destination is an FPR, not a GPR.** Use [`stfd`](../memory/stfd.md) to spill the FPR to memory and reload via a GPR if the value is needed in the integer file.
- **`mffs.` (`Rc=1`) updates CR1.** The `Rc` bit copies the high four FPSCR bits (FX, FEX, VX, OX) into CR1's LT/GT/EQ/SO. xenia-rs implements this via `update_cr1_from_fpscr`.
- **`mffs.` (`Rc=1`) updates CR1.** The `Rc` bit copies the high four FPSCR bits (FX, FEX, VX, OX) into CR1's LT/GT/EQ/SO. Canary implements this via `CopyFPSCRToCR1`.
- **No FPSCR side effect.** Pure read; FPSCR is not modified (unlike [`mcrfs`](mcrfs.md), which clears sticky exception bits).
- **xenia simplification.** xenia-rs models FPSCR as a `u32` field but **does not actively maintain** most of the IEEE-754 sticky bits — the FPU paths typically leave FPSCR untouched. So `mffs` will return whatever was last explicitly set (often 0 / boot defaults). Real titles use it mostly to save/restore the rounding-mode field around library calls, which xenia happens to handle correctly.
- **Canary simplification.** Canary keeps FPSCR as a 32-bit field but **does not maintain** the IEEE-754 sticky bits — its `UpdateFPSCR` is a stub that only clears `FEX`/`VX`. So `mffs` returns what `mtfsf`/`mtfsfi`/`mcrfs` last wrote (initially 0), zero-extended into the FPR's bit pattern. Real titles use it mostly to save/restore the rounding-mode field around library calls, which works: restoring through `mtfsf` also reloads the host rounding mode.
- **Not synchronising.** Reorderable with non-FPU instructions.
## Related Instructions

View File

@@ -105,7 +105,7 @@ int InstrEmit_mfmsr(PPCHIRBuilder& f, const InstrData& i) {
## Special Cases & Edge Conditions
- **Privileged.** `mfmsr` is supervisor-only; executing it from problem state on real hardware raises a Privileged Instruction interrupt. Xbox 360 game code never executes it directly — it appears only in the kernel image (`xboxkrnl.exe`) and in xenia's HLE bridge.
- **Privileged.** `mfmsr` is supervisor-only; executing it from problem state on real hardware raises a Privileged Instruction interrupt. Title code does use it anyway — Project Sylpheed's disassembly in `sylpheed.db` has 612 `mfmsr` (and 1,239 `mtmsrd`) and Canary does not enforce privilege.
- **MSR layout (Xenon-relevant fields, big-endian bit numbering).**
| Bit | Name | Meaning |
@@ -120,9 +120,9 @@ int InstrEmit_mfmsr(PPCHIRBuilder& f, const InstrData& i) {
| 63 | RI | recoverable interrupt |
The Xenon also exposes `MSR[SF]` (bit 0) = 1 for 64-bit mode; `MSR[HV]` (bit 3) for hypervisor. See PowerISA Book III for the full table.
- **Synchronisation.** Marked `sync` in xenia's XML`mfmsr` is execution-synchronising on real hardware (drains the pipeline before sampling MSR).
- **xenia model.** xenia-rs stores MSR as a flat `u64` and returns it raw. No real bit semantics are modelled — the kernel HLE never observes individual MSR fields. The interpreter ignores privilege.
- **Read of an undocumented field returns 0.** Most of the MSR is zero in xenia because no path explicitly initialises it.
- **Synchronisation.** Marked `sync` in Canary's `tools/ppc-instructions.xml``mfmsr` is execution-synchronising on real hardware (drains the pipeline before sampling MSR).
- **Canary model.** Canary stores MSR as a flat 64-bit context field and `mfmsr` returns it raw. No real bit semantics are modelled, and privilege is ignored.
- **Initial value.** Canary starts every thread with `MSR = 0x9030` (its comment: "dumped from a real 360") and changes it only through `mtmsr`/`mtmsrd`.
## Related Instructions

View File

@@ -153,30 +153,30 @@ 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 modelled by xenia)
## SPR Map (Xenon subset, with Canary's behaviour)
| Decoded # | Name | Meaning | xenia-rs behaviour |
| Decoded # | Name | Meaning | Canary behaviour |
| --- | --- | --- | --- |
| 1 | `XER` | Fixed-point exception register (CA / OV / SO + length field) | packed with `ctx.xer()` |
| 8 | `LR` | Link register | `ctx.lr` |
| 9 | `CTR` | Count register | `ctx.ctr` |
| 18 | `DSISR` | Data-storage interrupt syndrome | returns 0 (stubbed) |
| 19 | `DAR` | Data-access register | returns 0 (stubbed) |
| 256 | `VRSAVE` | Vector-register save mask | `ctx.vrsave` |
| 268 | `TBL` | Time-base lower 32 bits | `ctx.timebase & 0xFFFFFFFF` |
| 269 | `TBU` | Time-base upper 32 bits | `ctx.timebase >> 32` |
| 272275 | `SPRG0..3` | Software scratch registers (kernel) | returns 0 (stubbed) |
| 287 | `PVR` | Processor-version register | `0x00710800` (Xenon signature) |
| 10081009 | `HID0/1` | Hardware implementation registers | returns 0 (stubbed) |
| 1023 | `PIR` | Processor-ID register | returns 0 (stubbed) |
| 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` |
| 272275 | `SPRG0..3` | Software scratch registers (kernel) | not implemented |
| 287 | `PVR` | Processor-version register | the `pvr` cvar (default `0x710700`) |
| 10081009 | `HID0/1` | Hardware implementation registers | not implemented |
| 1023 | `PIR` | Processor-ID register | not implemented |
Unrecognised SPRs return 0 and log a warning. Games rarely read unmodelled SPRs; when they do it's usually clock-skew or sanity checks.
"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; xenia exposes all SPRs without a privilege check because the captured title binaries never contain a real privileged read that should trap.
- **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 xenia accepts both. Prefer `mftb` in new translations.
- **`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).

View File

@@ -116,7 +116,7 @@ int InstrEmit_mftb(PPCHIRBuilder& f, const InstrData& i) {
| 268 | TBL | Time Base, lower 32 bits |
| 269 | TBU | Time Base, upper 32 bits |
Other selectors return 0 in xenia and are not used by titles.
Canary returns the full 64-bit guest clock for 268 and the clock's upper 32 bits for any other selector.
- **Atomic 64-bit read pattern.** Because `mftb` reads only 32 bits at a time, software performs the canonical retry loop to avoid TBL→TBU rollover skew:
```asm
retry:
@@ -127,9 +127,9 @@ int InstrEmit_mftb(PPCHIRBuilder& f, const InstrData& i) {
bne retry
```
- **Xenon clock rate.** Real hardware ticks the time base at ~3.2 GHz (one tick per CPU clock divided by the architectural ratio). The PVR signature the kernel exposes (`0x00710800`) and the kernel-reported tick rate jointly let titles convert TB ticks to seconds.
- **xenia behaviour.** xenia-rs stores `ctx.timebase` as a `u64` and **increments it once per interpreted instruction**, not per real-time wall clock. This guarantees deterministic replay (same trace ⇒ same TB readings) at the cost of decoupling guest time from host time. Games that rely on TB for real-time sync will run faster or slower depending on host throughput.
- **Canary behaviour.** Canary's `mftb` reads its guest clock (`LoadClock`), which follows host time — TB for SPR 268, otherwise just the upper 32 bits. Guest time therefore tracks real time, and TB readings are not reproducible from run to run.
- **`mftb RT` (no operand)** is the simplified mnemonic for `mftb RT, 268` — read the lower half. `mftbu RT` ≡ `mftb RT, 269`.
- **Deprecated alternative.** `mfspr RT, 268`/`269` works on the Xenon (xenia accepts both) but post-PowerISA v2.06 deprecated reading TB through `mfspr`. Prefer `mftb`.
- **Deprecated alternative.** `mfspr RT, 268`/`269` works on the Xenon (Canary accepts both) but post-PowerISA v2.06 deprecated reading TB through `mfspr`. Prefer `mftb`.
## Related Instructions

View File

@@ -115,7 +115,7 @@ int InstrEmit_mfvscr(PPCHIRBuilder& f, const InstrData& i) {
- **`SAT` is sticky.** Once a saturating vector instruction clamps a result, `VSCR[SAT]` becomes 1 and stays 1 until explicitly cleared via [`mtvscr`](mtvscr.md). Software polls it after a vector batch to detect overflow.
- **`NJ` controls denormals.** When `NJ=1` (the Xenon's default), AltiVec single-precision ops flush denormal inputs/outputs to zero (non-IEEE behaviour); `NJ=0` enforces full IEEE.
- **VRSAVE.** Writing the entire 128-bit `VD` consumes a vector register slot; software wishing to honour [`VRSAVE`](mtspr.md) bookkeeping should ensure the chosen `VD` is in the live mask.
- **xenia simplification.** xenia-rs stores VSCR as a single value of the same `vr` type (effectively a u128) and copies it directly into `ctx.vr[VD]`. Saturating ops in xenia-rs **do** maintain SAT correctly for the vector ops that are implemented; NJ is honoured for the denormal-flush paths but its effect is small in practice.
- **Canary simplification.** `mfvscr` copies Canary's `vscr_vec` into `VD`. Only `mtvscr` writes that value (it starts with `NJ` set), so `SAT` never appears: Canary does not record saturation at all.
- **Not synchronising.**
## Related Instructions

View File

@@ -134,10 +134,10 @@ int InstrEmit_mtcrf(PPCHIRBuilder& f, const InstrData& i) {
- **`CRM` is an 8-bit field-mask, MSB-first.** Each bit of `CRM` corresponds to one CR field: `CRM[0]` (mask bit `0x80`) selects CR0, `CRM[1]` (`0x40`) selects CR1, …, `CRM[7]` (`0x01`) selects CR7. Each *set* mask bit causes the corresponding 4-bit slice of `RS[32:63]` to overwrite that CR field; clear mask bits leave the field untouched.
- **Slice positions inside `RS`.** Big-endian: bits 32..35 of `RS` map to CR0, bits 36..39 to CR1, …, bits 60..63 to CR7. The high 32 bits of `RS` are ignored.
- **`mtcr RS` simplified mnemonic.** When `CRM = 0xFF`, all eight CR fields are written; assemblers fold this into `mtcr RS`. This is the dominant form (function epilogue restoring the saved CR).
- **`mtocrf` variant.** PowerISA defines `mtocrf` as the single-field variant — encoded with the high bit of FXM set and exactly one CRM bit set. xenia-rs treats both as the same opcode and processes whatever `CRM` mask is present, so `mtocrf` works correctly without special handling.
- **`mtocrf` variant.** PowerISA defines `mtocrf` as the single-field variant — encoded with the high bit of FXM set and exactly one CRM bit set. Canary distinguishes it: with exactly one bit set it writes that field, with any other count it zeroes the whole CR (the spec calls that case undefined).
- **Use case in ABI.** Save/restore non-volatile CR fields (CR2, CR3, CR4 on the Xbox 360 ABI). The standard restore is `lwz r12, 8(r1); mtcrf 0x38, r12``0x38` = bits for CR2|CR3|CR4 — preserving the volatile fields the callee may have already updated.
- **No CR0 / XER side effects.** `mtcrf` does not record into CR0; XER is untouched.
- **xenia exact match.** xenia-rs decomposes the CR into a `u32`, applies a per-field mask, and reassembles via `set_cr`. The 8-bit `CRM` walk matches the spec exactly.
- **Canary exact match.** For plain `mtcrf`, Canary walks the 8-bit `CRM` mask and writes each selected field from `RS` (`StoreCR(field, RS)`), matching the spec exactly.
- **Not synchronising.** Reorderable.
## Related Instructions
@@ -153,7 +153,7 @@ int InstrEmit_mtcrf(PPCHIRBuilder& f, const InstrData& i) {
| --- | --- | --- |
| `mtcr RS` | `mtcrf 0xFF, RS` | write all eight CR fields from low half of RS |
`mtocrf RS, FXM` is a related encoding handled by the same xenia-rs slot.
`mtocrf RS, FXM` is a related encoding handled by the same Canary emitter (`InstrEmit_mtcrf`).
## IBM Reference

View File

@@ -105,10 +105,10 @@ int InstrEmit_mtfsb0x(PPCHIRBuilder& f, const InstrData& i) {
- **Operation.** Clears (sets to 0) **a single named bit** of the 32-bit FPSCR. The bit is selected by `FPSCRD` (a 5-bit absolute index 0..31, big-endian: 0 = MSB = FX).
- **The mnemonic name "Bit 0" is misleading.** "0" refers to the *value being written*, not to bit position 0. Pair with [`mtfsb1x`](mtfsb1x.md) which writes a 1.
- **Restricted bits.** Per PowerISA, `mtfsb0` cannot clear bits 1 (FEX) or 2 (VX) — those are summary bits, derived from other FPSCR bits. xenia-rs does **not** enforce this restriction; it will happily flip any bit. In practice no Xbox 360 title relies on the restriction's enforcement.
- **Restricted bits.** Per PowerISA, `mtfsb0` cannot clear bits 1 (FEX) or 2 (VX) — those are summary bits, derived from other FPSCR bits. ⚠️ Canary does not implement `mtfsb0`: its emitter is `XEINSTRNOTIMPLEMENTED`, so translating one logs "Unimplemented instr" and, with the default `break_on_unimplemented_instructions`, breaks.
- **`Rc=1`.** `mtfsb0.` (`Rc=1`) updates **CR1** with the high four FPSCR bits (FX, FEX, VX, OX) after the clear. This is the FPU's record-form analogue.
- **Common use.** Reset a sticky exception bit ahead of a sequence of FP ops you want to monitor (e.g. `mtfsb0 5` to clear ZX before a divide series, then read it back).
- **xenia simplification.** xenia-rs maintains FPSCR as a `u32` and does the bit clear correctly, but most downstream FP instructions in xenia **do not update** FPSCR exception bits — so monitoring them after `mtfsb0` will see the bits stay at their seed value. Acceptable for titles that use FPSCR only to manage rounding / non-exception state.
- **Canary status.** With `mtfsb0` unimplemented, Canary never clears the bit (and breaks by default, see above). Most FP instructions in Canary do not update FPSCR exception bits either, so titles that manage rounding only through `mtfsf`/`mtfsfi` are unaffected.
- **Not synchronising.** Reorderable.
## Related Instructions

View File

@@ -105,10 +105,10 @@ int InstrEmit_mtfsb1x(PPCHIRBuilder& f, const InstrData& i) {
- **Operation.** Sets (writes 1 to) **a single named bit** of FPSCR. `FPSCRD` is a 5-bit absolute index (0..31), big-endian (0 = MSB = FX).
- **Mnemonic name.** "1" denotes the *value written*, not bit position. Pair with [`mtfsb0x`](mtfsb0x.md) for clears.
- **Restricted bits.** PowerISA forbids `mtfsb1` from setting FEX (bit 1) or VX (bit 2) directly — both are summary bits derived from other state. `mtfsb1` *can* set FX (bit 0), which is itself a sticky summary; this is occasionally used to force a `Program` interrupt for testing. xenia-rs does **not** enforce the restriction; setting summary bits will stick until cleared explicitly.
- **Restricted bits.** PowerISA forbids `mtfsb1` from setting FEX (bit 1) or VX (bit 2) directly — both are summary bits derived from other state. `mtfsb1` *can* set FX (bit 0), which is itself a sticky summary; this is occasionally used to force a `Program` interrupt for testing. ⚠️ Canary does not implement `mtfsb1`: its emitter is `XEINSTRNOTIMPLEMENTED`, so translating one logs "Unimplemented instr" and, with the default `break_on_unimplemented_instructions`, breaks.
- **`Rc=1`.** `mtfsb1.` (`Rc=1`) updates CR1 with the high four FPSCR bits (FX, FEX, VX, OX) after the set.
- **Common use.** Force-set a sticky exception bit to test exception-handling code paths. Also seen in floating-point library setup that wants a known FPSCR seed.
- **xenia simplification.** Same caveat as `mtfsb0`: xenia maintains FPSCR but most FP paths don't read it, so the set has limited downstream effect. The bit will read back correctly via [`mffsx`](mffsx.md).
- **Canary status.** Canary does not implement `mtfsb1` (`XEINSTRNOTIMPLEMENTED`), so the bit is never set and will not read back via [`mffsx`](mffsx.md).
- **Not synchronising.** Reorderable.
## Related Instructions

View File

@@ -127,8 +127,8 @@ int InstrEmit_mtfsfix(PPCHIRBuilder& f, const InstrData& i) {
- **Most common use: rounding-mode set.** `mtfsfi 7, 0` selects round-to-nearest, `mtfsfi 7, 1` round-toward-zero, `mtfsfi 7, 2` round-toward-+∞, `mtfsfi 7, 3` round-toward-−∞. The four immediate values map to RN per IEEE-754. Compilers emit this when transitioning into and out of strict-IEEE regions.
- **No FPR source.** Unlike [`mtfsfx`](mtfsfx.md), `mtfsfi` doesn't need an FPR — it carries its 4-bit value in the instruction word, making it cheaper for constant updates.
- **`Rc=1`.** `mtfsfi.` copies FPSCR's top 4 bits (FX, FEX, VX, OX) into CR1 after the write.
- **Restrictions in newer PowerISA.** v2.05+ disallows writing FEX/VX (summary bits) via `mtfsfi`. xenia-rs does **not** enforce this — the immediate goes straight into the chosen field.
- **xenia simplification.** xenia stores FPSCR as a `u32` and applies the field-shift correctly. Same caveat as `mtfsf`: most xenia FP paths don't honour FPSCR, so the rounding-mode change is architecturally visible (via [`mffsx`](mffsx.md)) but typically does not change subsequent FP results.
- **Restrictions in newer PowerISA.** v2.05+ disallows writing FEX/VX (summary bits) via `mtfsfi`. Canary does **not** enforce this — the immediate goes straight into the chosen field, and when that field holds `RN` Canary also reloads the host rounding mode.
- **Canary behaviour.** Canary applies the field shift correctly, and when the field holds `RN` it also reloads the host rounding mode — so a rounding-mode change made with `mtfsfi` does affect later FP results in Canary.
- **Not synchronising.** PowerISA recommends `isync` after rounding-mode changes if subsequent FP correctness depends on the new mode.
## Related Instructions

View File

@@ -141,8 +141,8 @@ int InstrEmit_mtfsfx(PPCHIRBuilder& f, const InstrData& i) {
- **Source is the LOW 32 bits of `FRB`.** The high 32 bits are ignored. Software that wants to write a 32-bit pattern typically constructs it in a GPR, stores to memory, and reloads as a double via [`lfd`](../memory/lfd.md).
- **Most common use: setting rounding mode.** Compilers wrap calls to `<fenv.h>`-style functions with `mtfsf 1, fX` to update only the rounding-mode field (RN, FPSCR field 7).
- **`Rc=1` updates CR1.** `mtfsf.` copies FPSCR's top 4 bits (FX, FEX, VX, OX) into CR1 after the write.
- **`L`/`W` bits.** PowerISA v2.05+ adds `L=1` to mean "write all FPSCR bits regardless of FM" and `W=1` to select the upper or lower 32 bits. xenia-rs **ignores** `L` and `W` (always treats `L=0, W=0`), which matches every real Xbox 360 use.
- **xenia simplification.** xenia maintains FPSCR as a `u32` and applies the field mask correctly. However, most FP instructions in xenia don't *read* FPSCR (e.g., divides ignore the rounding mode), so the architecturally-set rounding mode often has no actual effect on results. Acceptable for the title set xenia targets.
- **`L`/`W` bits.** PowerISA v2.05+ adds `L=1` to mean "write all FPSCR bits regardless of FM" and `W=1` to select the upper or lower 32 bits. Canary handles `L=0`; its `L=1` path writes FPSCR but still reports the instruction as unimplemented, and `W` is only asserted to be zero.
- **Canary behaviour.** Canary applies the field mask correctly and, when the mask covers `RN`, reloads the host rounding mode, so conversions and arithmetic that round follow the new mode. The exception bits are another matter: Canary's `UpdateFPSCR` is a stub, so they are never set by FP arithmetic.
- **Not synchronising.** Reorderable; PowerISA recommends an `isync` after FPSCR changes that affect subsequent FP behaviour.
## Related Instructions

View File

@@ -105,10 +105,10 @@ int InstrEmit_mtmsr(PPCHIRBuilder& f, const InstrData& i) {
- **Privileged.** `mtmsr` is supervisor-only on real hardware. Executing it from problem state raises a Privileged Instruction interrupt. Game code never emits it; only the kernel and exception-return paths use it.
- **32-bit form.** `mtmsr` writes the **low 32 bits** of MSR (legacy PPC32 form). On the Xenon (a PPC64 implementation), use [`mtmsrd`](mtmsrd.md) for the full 64-bit MSR. Some Xenon kernel sequences still use `mtmsr` to leave the high half untouched while flipping low-half flags like EE/PR.
- **Synchronisation.** Marked `sync``mtmsr` is **execution-synchronising**. The Xenon must drain all preceding instructions before the new MSR takes effect, and PowerISA recommends a following `isync` to guarantee subsequent instructions execute under the new MSR.
- **`L` operand.** Modern PowerISA defines an `L` bit selecting "EE/RI only" (`L=1`) versus "all" (`L=0`); xenia-rs ignores `L` and writes the entire MSR. Real Xbox 360 kernel code uses both `L=0` and `L=1`.
- **xenia model.** Treats MSR as a flat `u64` field. Both `mtmsr` and `mtmsrd` execute the same body — `ctx.msr = ctx.gpr[rs]`. No privilege or atomicity is enforced; no side effects on TLB / interrupt mask / endianness are simulated.
- **`L` operand.** Modern PowerISA defines an `L` bit selecting "EE/RI only" (`L=1`) versus "all" (`L=0`); Canary's `mtmsr` ignores `L` and writes the entire MSR. Real Xbox 360 kernel code uses both `L=0` and `L=1`.
- **Canary model.** Treats MSR as a flat 64-bit context field. `mtmsr` stores `RS` whole; `mtmsrd` differs (see its page). No privilege or atomicity is enforced; no side effects on TLB / interrupt mask / endianness are simulated.
- **No CR / XER side effects.**
- **Caveat for translators.** Because the host kernel runs natively in xenia, the guest MSR has no architectural meaning beyond storage. Code that reads it back via [`mfmsr`](mfmsr.md) will see exactly what was last written.
- **Caveat for translators.** Because Canary implements the kernel natively, the guest MSR has no architectural meaning beyond storage. Code that reads it back via [`mfmsr`](mfmsr.md) sees what `mtmsr` last wrote — `mtmsrd` changes only `EE`.
## Related Instructions

View File

@@ -112,9 +112,9 @@ int InstrEmit_mtmsrd(PPCHIRBuilder& f, const InstrData& i) {
- **Privileged.** Like [`mtmsr`](mtmsr.md), supervisor-only. Game code never emits it.
- **64-bit form.** Writes all 64 MSR bits — including `MSR[SF]` (bit 0) which selects 64-bit mode, `MSR[HV]` (bit 3, hypervisor), `MSR[EE]` (32, external interrupts), `MSR[PR]` (33, problem state), `MSR[FP]` (34), `MSR[ME]` (35, machine-check enable), `MSR[DR]`/`MSR[IR]` (data/instruction translation, 38/39), `MSR[RI]` (63, recoverable interrupt). On the Xenon kernel this is the canonical MSR-write instruction.
- **`L` operand.** Same `L`-bit selector as `mtmsr`: `L=1` updates only `MSR[EE]` and `MSR[RI]`; `L=0` updates the full register. xenia-rs ignores `L` and always writes the full doubleword (matching the typical kernel use).
- **`L` operand.** Same `L`-bit selector as `mtmsr`: `L=1` updates only `MSR[EE]` and `MSR[RI]`; `L=0` updates the full register. ⚠️ Canary ignores `L` and always updates **only `MSR[EE]`** (mask `0x8000`), leaving every other bit — including `RI` — unchanged.
- **Synchronisation.** Marked `sync` — execution-synchronising. PowerISA recommends `isync` afterwards if subsequent fetch / data semantics depend on the new MSR.
- **xenia model.** Shares one interpreter arm with `mtmsr`: `ctx.msr = ctx.gpr[rs]`. No architectural side effects beyond writing the storage; no privilege check.
- **Canary model.** Not shared with `mtmsr`: `mtmsrd` computes `(RS & 0x8000) | (MSR & ~0x8000)`. No architectural side effects beyond writing the storage; no privilege check.
- **No CR / XER updates.**
- **Used in interrupt return paths.** Kernel handlers commonly write SRR1 (saved MSR) into MSR via `mtmsrd` followed by `rfid` to atomically restore state and jump to SRR0.

View File

@@ -129,21 +129,21 @@ int InstrEmit_mtspr(PPCHIRBuilder& f, const InstrData& i) {
## Special Cases & Edge Conditions
- **SPR halves are swapped in the encoding.** As with [`mfspr`](mfspr.md), the 10-bit `spr` field stores the two 5-bit halves transposed. Software always names the *logical* SPR number; assemblers handle the swap. Decoded number `n = ((field & 0x1F) << 5) | ((field >> 5) & 0x1F)`.
- **SPRs writable from userspace (Xenon, modelled by xenia).**
- **SPRs writable from userspace (Xenon) — the only ones Canary implements.**
| Decoded # | Name | Effect |
| --- | --- | --- |
| 1 | XER | unpacked into `ctx.xer_so/xer_ov/xer_ca` and length field |
| 8 | LR | `ctx.lr ← RS` |
| 9 | CTR | `ctx.ctr ← RS` |
| 256 | VRSAVE | `ctx.vrsave ← RS & 0xFFFFFFFF` |
| 1 | XER | `StoreXER(RS)` |
| 8 | LR | `StoreLR(RS)` |
| 9 | CTR | `StoreCTR(RS)` |
| 256 | VRSAVE | low 32 bits of `RS` into `vrsave` |
- **SPRs xenia silently swallows (no observable effect).** SPRG0..3, HID0, HID1, DAR, DSISR these are kernel/diagnostic registers; xenia accepts the write to avoid spurious "unimplemented SPR" warnings, but the value is discarded.
- **Privileged SPRs.** On real hardware, writes to MSR-visible kernel SPRs (SPRG0..3, HID0/1, DSISR, DAR, PIR, etc.) require supervisor mode and trap from problem state. xenia does **not** enforce privilege.
- **Time-base writes are privileged.** `mtspr 268/269` (TBL/TBU) only works in supervisor mode on real hardware. xenia will warn `mtspr: unimplemented SPR` for these — do **not** assume the time base can be guest-written.
- **Every other SPR is unimplemented in Canary.** SPRG0..3, HID0, HID1, DAR, DSISR and the rest are not swallowed: translating the `mtspr` logs "Unimplemented instr" and, with the default `break_on_unimplemented_instructions`, breaks.
- **Privileged SPRs.** On real hardware, writes to MSR-visible kernel SPRs (SPRG0..3, HID0/1, DSISR, DAR, PIR, etc.) require supervisor mode and trap from problem state. Canary does **not** enforce privilege — it implements only XER, LR, CTR and VRSAVE and treats `mtspr` to any other SPR as an unimplemented instruction.
- **Time-base writes are privileged.** `mtspr 268/269` (TBL/TBU) only works in supervisor mode on real hardware, and Canary treats them as unimplemented — do **not** assume the time base can be guest-written.
- **Simplified mnemonics.** `mtxer RS``mtspr 1, RS`, `mtlr RS``mtspr 8, RS`, `mtctr RS``mtspr 9, RS`. These dominate Xbox 360 disassembly.
- **No CR / XER side effects.** `mtspr` itself doesn't record (the *target* SPR may itself be XER, in which case XER is being directly overwritten).
- **Not synchronising.** xenia's XML omits the `sync` flag; PowerISA does require some `mtspr` cases (e.g. SDR1, MMU regs) to be context-synchronising — none of them appear in title binaries.
- **Not synchronising.** Canary's `tools/ppc-instructions.xml` omits the `sync` flag; PowerISA does require some `mtspr` cases (e.g. SDR1, MMU regs) to be context-synchronising — none of them appear in title binaries.
## Related Instructions

View File

@@ -115,7 +115,7 @@ int InstrEmit_mtvscr(PPCHIRBuilder& f, const InstrData& i) {
- **Bits actually significant.** Of the 32 source bits, only **NJ (bit 16)** and **SAT (bit 31)** are architecturally meaningful on the Xenon. All other bits should be written as zero; behaviour for non-zero values is implementation-defined.
- **Clearing SAT.** The dominant use is `mtvscr vN` with `vN` zeroed via `vxor vN, vN, vN`, which writes VSCR=0 and thereby clears the sticky SAT bit before a fresh batch of saturating vector ops.
- **Setting NJ.** Switching to/from "Java mode" (`NJ=0`, full IEEE denormal handling) versus "Non-Java mode" (`NJ=1`, flush-to-zero) is the other meaningful use. Game audio / DSP code occasionally toggles this to match a precise IEEE expectation.
- **xenia simplification.** xenia-rs stores VSCR identically to a vector register and copies the source straight in: `ctx.vscr = ctx.vr[VB]`. Subsequent xenia AltiVec ops do consult `VSCR[SAT]` for sticky updates, so the architecturally-relevant behaviour is preserved. NJ's flush-to-zero semantics are honoured by xenia's vector denormal paths.
- **Canary simplification.** Canary copies the source straight into its `vscr_vec` and sets its NJ mode from bit 16 of the low word, which switches the host's VMX MXCSR between flush-to-zero + denormals-are-zero and IEEE. `SAT` is not modelled: no Canary AltiVec op records it.
- **Not synchronising.** PowerISA does not require `isync` after `mtvscr`, but library code occasionally pairs them as a defensive measure.
## Related Instructions

Some files were not shown because too many files have changed in this diff Show More