The hand-written parts of the manual still described how the retired xenia-rs interpreter behaved: its snapshots, Rust casts and helpers. Each of those 490 statements is now either restated as what Canary's emitters and x64 backend actually do (at the pinned canary_experimental commit), or dropped where it only made sense for xenia-rs. Checking them turned up claims that were wrong, not just outdated: - VSCR[SAT] is never modelled in Canary (DID_SATURATE is a stub and mfvscr cannot see it); the pages said saturating ops set it stickily. - Canary does not implement lswi/lswx/stswi/stswx, dcbi, mtfsb0/mtfsb1, vmsum*, vmhaddshs, vupkhpx/vupklpx, and most SPRs; pages described them as working. - Traps evaluate TO in Canary; stvebx/stvehx/stvewx store one element, not 16 bytes; mtmsrd writes only EE; fres/frsqrte/vrsqrtefp precision claims and the stfs "rounds under RN / sets FPSCR" claim contradicted the spec. - Reservations are a 64 KiB block bitmap plus a value compare, not per-address tracking. Claims that neither Canary's source nor a public spec settles are marked unverified (NI at boot, vmaddcfp128 operand order, estimate bit-exactness). Generated regions are untouched; re-running the generator changes nothing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
9.4 KiB
bclrx — Branch Conditional to Link Register
Category: Branch & System · Form: XL · Opcode:
0x4c000020· sync
Assembler Mnemonics
| Mnemonic | XML entry | Flags | Description |
|---|---|---|---|
bclr |
bclrx |
— | Branch Conditional to Link Register |
bclrl |
bclrx |
LK=1 | Branch Conditional to Link Register |
Syntax
bclr[LK] [BO], [BI]
Encoding
bclrx — form XL
- Opcode word:
0x4c000020 - Primary opcode (bits 0–5):
19 - Extended opcode:
16 - Synchronising: yes
| Bits | Field | Meaning |
|---|---|---|
| 0–5 | OPCD |
primary opcode (19) |
| 6–10 | BT/BO |
target / branch options |
| 11–15 | BA/BI |
source A / CR bit to test |
| 16–20 | BB |
source B |
| 21–30 | XO |
extended opcode (10 bits) |
| 31 | LK |
link flag |
Operands
| Field | Role | Description |
|---|---|---|
LK |
bclrx: read | Link bit. When 1, LR ← address-of-next-instruction before the branch is taken. |
BO |
bclrx: read | 5-bit branch options — selects CTR decrement, CTR test polarity, and CR bit test polarity. See forms/XL.md. |
BI |
bclrx: read | CR bit index (0–31) selected by BO's condition test. |
CR |
bclrx: read (conditional) | Condition-register update. When Rc=1, CR field 0 (or CR6 for vector compares, CR1 for FPU) is updated from the result. |
CTR |
bclrx: read (conditional); bclrx: write (conditional) | Count register. Decremented and optionally tested by conditional branches when BO[2]=0. |
LR |
bclrx: write (conditional) | Link register. Written by bl/bla/bcl/bclrl/bcctrl; read by bclr/bclrl. |
Register Effects
bclrx
- Reads (always):
LK,BO,BI - Reads (conditional):
CR,CTR - Writes (always): none
- Writes (conditional):
CTR,LR
Status-Register Effects
No condition-register or status-register effects.
Operation (pseudocode)
if ¬BO[2] then CTR <- CTR − 1
ctr_ok <- BO[2] | ((CTR ≠ 0) XOR BO[3])
cond_ok <- BO[0] | (CR[BI] ≡ BO[1])
if ctr_ok & cond_ok then NIA <- LR[0:61] || 0b00
if LK then LR <- CIA + 4
C Translation Example
/* bclr/bclrl — branch conditional to LR */
if (!(insn.BO & 4)) ctr -= 1;
bool ctr_ok = (insn.BO & 4) || ((ctr != 0) ^ !!(insn.BO & 2));
bool cond_ok = (insn.BO & 16) || (cr_bit(insn.BI) == !!(insn.BO & 8));
uint32_t next = pc + 4;
if (ctr_ok && cond_ok) pc = lr & ~3u; else pc = next;
if (insn.LK) lr = next;
Implementation References
bclrx
- Canary XML:
tools/ppc-instructions.xml— search formnem="bclrx" - Canary emitter:
src/xenia/cpu/ppc/ppc_emit_control.cc:282 - Sylpheed opcode:
crates/sylpheed-ppc/src/opcode.rs:23 - Sylpheed decoder:
crates/sylpheed-ppc/src/decoder.rs:826
Canary emitter (frozen snapshot @ f21ebd49e9)
int InstrEmit_bclrx(PPCHIRBuilder& f, const InstrData& i) {
// if ¬BO[2] then
// CTR <- CTR - 1
// ctr_ok <- BO[2] | ((CTR[0:63] != 0) XOR BO[3]
// cond_ok <- BO[0] | (CR[BI+32] ≡ BO[1])
// if ctr_ok & cond_ok then
// NIA <- LR[0:61] || 0b00
// if LK then
// LR <- CIA + 4
// NOTE: the condition bits are reversed!
// 01234 (docs)
// 43210 (real)
Value* ctr_ok = NULL;
if (select_bits(i.XL.BO, 2, 2)) {
// Ignore ctr.
} else {
// Decrement counter.
Value* ctr = f.LoadCTR();
ctr = f.Sub(ctr, f.LoadConstantUint64(1));
f.StoreCTR(ctr);
// Ctr check.
ctr = f.Truncate(ctr, INT32_TYPE);
// TODO(benvanik): could do something similar to cond and avoid the
// is_true/branch_true pairing.
if (select_bits(i.XL.BO, 1, 1)) {
ctr_ok = f.IsFalse(ctr);
} else {
ctr_ok = f.IsTrue(ctr);
}
}
Value* cond_ok = NULL;
bool not_cond_ok = false;
if (select_bits(i.XL.BO, 4, 4)) {
// Ignore cond.
} else {
Value* cr = f.LoadCRField(i.XL.BI >> 2, i.XL.BI & 3);
cond_ok = cr;
if (select_bits(i.XL.BO, 3, 3)) {
// Expect true.
not_cond_ok = false;
} else {
// Expect false.
not_cond_ok = true;
}
}
// We do a bit of optimization here to make the llvm assembly easier to read.
Value* ok = NULL;
bool expect_true = true;
if (ctr_ok && cond_ok) {
if (not_cond_ok) {
cond_ok = f.IsFalse(cond_ok);
}
ok = f.And(ctr_ok, cond_ok);
} else if (ctr_ok) {
ok = ctr_ok;
} else if (cond_ok) {
ok = cond_ok;
expect_true = !not_cond_ok;
}
return InstrEmit_branch(f, "bclrx", i.address, f.LoadLR(), i.XL.LK, ok,
expect_true, true);
}
BO Encoding (5 bits)
BO controls two independent tests and two "hints". Bit 0 is the MSB.
| BO (binary) | CTR decrement? | CTR test | CR test | Meaning |
|---|---|---|---|---|
0000z |
yes | CTR ≠ 0 |
¬CR[BI] |
decrement, branch if CTR ≠ 0 and CR[BI] false |
0001z |
yes | CTR = 0 |
¬CR[BI] |
decrement, branch if CTR = 0 and CR[BI] false |
001at |
yes | CTR ≠ 0 / CTR = 0 |
— | decrement, branch on CTR only |
0100z |
no | — | ¬CR[BI] |
branch if CR[BI] false |
0101z |
no | — | CR[BI] |
branch if CR[BI] true |
011at |
no | — | — | branch always (z and t are prediction hints) |
1z00z |
yes | CTR ≠ 0 |
— | decrement, branch if CTR ≠ 0 |
1z01z |
yes | CTR = 0 |
— | decrement, branch if CTR = 0 |
1z1zz |
no | — | — | branch always |
Bit BO[0] = 1 disables the CR test; BO[2] = 1 disables the CTR decrement/test. BO[1] and BO[3] select the polarity of each test. BO[4] is a branch-prediction hint (0 = not taken, 1 = taken; ignored on the Xenon in most cases).
The most common bclr instance in Xbox 360 disassembly is BO = 0b10100 → blr (branch always to LR), the function epilogue. BO = 0b01100, BI = 2 → beqlr (return if cr0.EQ), also common.
Special Cases & Edge Conditions
- 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
bdnzatCTR = 1, the CTR becomes0and the branch is not taken. - Self-referential LR write.
bclrlwritesLR ← CIA + 4before readingLRto setNIA. Per the PowerISA,bclrlreads the oldLRfor the branch target and writes the newLRwith the return address, atomically from software's perspective. Canary implements it this way:InstrEmit_bclrxloadsLRas the target beforeInstrEmit_branchstoresCIA + 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.
bclris context-synchronising (hence thesyncflag in Canary'stools/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
0xBCBCBCBCreturn sentinel. When the host calls into a guest function, Canary'sProcessor::ExecutesetsLRto0xBCBCBCBC, so the top-levelblrhands 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
bcctrx— branch conditional to CTR (used by indirect calls / vtables).bcx— branch conditional to an immediate displacement (D-form).bx— unconditional branch (I-form).mtlr,mflr— set/get LR viamtspr 8, …/mfspr …, 8.sc— system call (alternative control-flow exit).
Simplified Mnemonics
Assemblers fold common BO/BI patterns to single mnemonics:
| Simplified | Expansion |
|---|---|
blr |
bclr BO=0b10100, BI=0 — branch always to LR |
blrl |
bclrl BO=0b10100, BI=0 — branch always to LR with link (tail-call trampoline) |
beqlr crN |
bclr BO=0b01100, BI=4·N+2 — return if crN.EQ |
bnelr crN |
bclr BO=0b00100, BI=4·N+2 — return if crN.NE |
bltlr crN |
bclr BO=0b01100, BI=4·N+0 — return if crN.LT |
bgelr crN |
bclr BO=0b00100, BI=4·N+0 — return if crN.GE |
bgtlr crN |
bclr BO=0b01100, BI=4·N+1 — return if crN.GT |
blelr crN |
bclr BO=0b00100, BI=4·N+1 — return if crN.LE |
Xbox 360 disassemblers almost always emit the simplified form; the translation agent should learn to recognise them.