[iterate-4C] JIT Phase 2b-ii: native branches (bx/bcx/bclrx)

try_emit_native now returns a 3-state Emit enum (Fallback/Native/Branch);
compile_block appends the pc==expected_next discontinuity check after a
native branch, mirroring step_block. bx (aa/rel, lk), bcx (CTR
decrement + CTR/CR condition, cmov-selected target), bclrx (return via
lr & !3) emitted natively; bcctrx stays on fallback (dispatch_rec side
effect). count_only postlude (counters, no pc+=4) for branches.
Differential tests: unconditional/conditional/return branches over
exhaustive BO bits, CTR==1->0 boundary, lr alignment mask, both lk
states, asserting pc/lr/ctr/cr/counters. Golden n200m BYTE-IDENTICAL
with and without XENIA_JIT (11 tests green). Throughput 4.85->4.7s
(interp ~3.9s); crossover still pending rotate/shift (Phase 3).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-07-04 19:48:47 +02:00
parent 5e521f7f53
commit 701e4c399a
3 changed files with 332 additions and 44 deletions

View File

@@ -23,6 +23,20 @@ use xenia_cpu::decoder::DecodedInstr;
use crate::JitEnv;
/// Outcome of trying to emit one instruction natively.
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum Emit {
/// Not handled — the caller must emit the interpreter fallback + exit checks.
Fallback,
/// Fully handled; `pc += 4` and control always continues to the next
/// instruction (no exit check needed).
Native,
/// Handled, but the instruction may change `pc` discontinuously (a branch).
/// The caller must append the `pc == expected_next` check so a taken branch
/// ends the block and a fall-through continues — exactly like `step_block`.
Branch,
}
/// Which byte within a `CrField` — used to address the individual condition
/// flags the emitters write with `setcc`.
#[derive(Clone, Copy)]
@@ -40,6 +54,10 @@ pub struct Offsets {
pub pc: i32,
pub cycle: i32,
pub timebase: i32,
/// Link register (`lr: u64`).
lr: i32,
/// Count register (`ctr: u64`).
ctr: i32,
/// Base of the `cr: [CrField; 8]` array.
cr: i32,
/// Stride between adjacent `CrField`s (`size_of::<CrField>()`).
@@ -62,6 +80,8 @@ impl Offsets {
pc: core::mem::offset_of!(PpcContext, pc) as i32,
cycle: core::mem::offset_of!(PpcContext, cycle_count) as i32,
timebase: core::mem::offset_of!(PpcContext, timebase) as i32,
lr: core::mem::offset_of!(PpcContext, lr) as i32,
ctr: core::mem::offset_of!(PpcContext, ctr) as i32,
cr: core::mem::offset_of!(PpcContext, cr) as i32,
cr_stride: core::mem::size_of::<CrField>() as i32,
cr_lt: core::mem::offset_of!(CrField, lt) as i32,
@@ -87,6 +107,20 @@ impl Offsets {
};
self.cr + (field as i32) * self.cr_stride + within
}
/// Byte offset of absolute CR bit `bit` (0-31), matching
/// `PpcContext::get_cr_bit`: `field = bit/4`, `sub = bit%4` →
/// `lt/gt/eq/so`. The byte holds the flag as 0/1.
#[inline]
fn cr_bit_off(&self, bit: u32) -> i32 {
let field = (bit / 4) as usize;
let which = match bit % 4 {
0 => CrByte::Lt,
1 => CrByte::Gt,
2 => CrByte::Eq,
_ => CrByte::So,
};
self.cr_byte(field, which)
}
}
type Asm = dynasmrt::x64::Assembler;
@@ -104,15 +138,27 @@ fn advance_and_count(ops: &mut Asm, off: &Offsets) {
);
}
/// Try to emit native x64 for `instr`. Returns `true` if it fully handled the
/// instruction (computation + `advance_and_count`); `false` if the caller must
/// fall back to the interpreter for it.
/// Emit `cycle_count += 1; timebase += 1` WITHOUT touching `pc` — the postlude
/// for native branches, which set `pc` themselves.
#[inline]
fn count_only(ops: &mut Asm, off: &Offsets) {
dynasm!(ops
; .arch x64
; inc QWORD [r15 + off.cycle]
; inc QWORD [r15 + off.timebase]
);
}
/// Try to emit native x64 for `instr`. Returns [`Emit::Native`] if it fully
/// handled the instruction (computation + `advance_and_count`), [`Emit::Branch`]
/// if it handled a branch (caller must add the pc-discontinuity exit check), or
/// [`Emit::Fallback`] if the caller must fall back to the interpreter.
pub fn try_emit_native(
ops: &mut Asm,
off: &Offsets,
helpers: &crate::MemHelpers,
instr: &DecodedInstr,
) -> bool {
) -> Emit {
let ra = instr.ra();
let rb = instr.rb();
let rd = instr.rd(); // == rs()
@@ -126,7 +172,7 @@ pub fn try_emit_native(
}
dynasm!(ops ; .arch x64 ; mov [r15 + off.gpr(rd)], rax);
advance_and_count(ops, off);
true
Emit::Native
}
// rD = (rA==0 ? 0 : gpr[rA]) + (EXTS(SIMM) << 16) [64-bit]
PpcOpcode::addis => {
@@ -139,7 +185,7 @@ pub fn try_emit_native(
}
dynasm!(ops ; .arch x64 ; mov [r15 + off.gpr(rd)], rax);
advance_and_count(ops, off);
true
Emit::Native
}
// gpr[rA] = gpr[rS] | ZEXT(UIMM) [never records]
PpcOpcode::ori => {
@@ -150,7 +196,7 @@ pub fn try_emit_native(
}
dynasm!(ops ; .arch x64 ; mov [r15 + off.gpr(ra)], rax);
advance_and_count(ops, off);
true
Emit::Native
}
// gpr[rA] = gpr[rS] | (ZEXT(UIMM) << 16)
PpcOpcode::oris => {
@@ -162,7 +208,7 @@ pub fn try_emit_native(
; mov [r15 + off.gpr(ra)], rax
);
advance_and_count(ops, off);
true
Emit::Native
}
// gpr[rA] = gpr[rS] ^ ZEXT(UIMM)
PpcOpcode::xori => {
@@ -173,7 +219,7 @@ pub fn try_emit_native(
}
dynasm!(ops ; .arch x64 ; mov [r15 + off.gpr(ra)], rax);
advance_and_count(ops, off);
true
Emit::Native
}
// gpr[rA] = gpr[rS] ^ (ZEXT(UIMM) << 16)
PpcOpcode::xoris => {
@@ -185,13 +231,13 @@ pub fn try_emit_native(
; mov [r15 + off.gpr(ra)], rax
);
advance_and_count(ops, off);
true
Emit::Native
}
// gpr[rA] = gpr[rS] | gpr[rB] [64-bit]. Skip the db16cyc spin hint
// (returns Yield) and the recording form.
PpcOpcode::orx => {
if instr.rc_bit() || instr.raw == 0x7FFF_FB78 {
return false;
return Emit::Fallback;
}
dynasm!(ops ; .arch x64
; mov rax, [r15 + off.gpr(rd)]
@@ -199,12 +245,12 @@ pub fn try_emit_native(
; mov [r15 + off.gpr(ra)], rax
);
advance_and_count(ops, off);
true
Emit::Native
}
// gpr[rA] = gpr[rS] & gpr[rB] [64-bit]
PpcOpcode::andx => {
if instr.rc_bit() {
return false;
return Emit::Fallback;
}
dynasm!(ops ; .arch x64
; mov rax, [r15 + off.gpr(rd)]
@@ -212,12 +258,12 @@ pub fn try_emit_native(
; mov [r15 + off.gpr(ra)], rax
);
advance_and_count(ops, off);
true
Emit::Native
}
// gpr[rA] = gpr[rS] ^ gpr[rB] [64-bit]
PpcOpcode::xorx => {
if instr.rc_bit() {
return false;
return Emit::Fallback;
}
dynasm!(ops ; .arch x64
; mov rax, [r15 + off.gpr(rd)]
@@ -225,12 +271,12 @@ pub fn try_emit_native(
; mov [r15 + off.gpr(ra)], rax
);
advance_and_count(ops, off);
true
Emit::Native
}
// rD = gpr[rA] + gpr[rB] [64-bit]; skip OE/recording forms.
PpcOpcode::addx => {
if instr.oe() || instr.rc_bit() {
return false;
return Emit::Fallback;
}
dynasm!(ops ; .arch x64
; mov rax, [r15 + off.gpr(ra)]
@@ -238,12 +284,12 @@ pub fn try_emit_native(
; mov [r15 + off.gpr(rd)], rax
);
advance_and_count(ops, off);
true
Emit::Native
}
// rD = gpr[rB] - gpr[rA] [64-bit]; skip OE/recording forms.
PpcOpcode::subfx => {
if instr.oe() || instr.rc_bit() {
return false;
return Emit::Fallback;
}
dynasm!(ops ; .arch x64
; mov rax, [r15 + off.gpr(rb)]
@@ -251,12 +297,12 @@ pub fn try_emit_native(
; mov [r15 + off.gpr(rd)], rax
);
advance_and_count(ops, off);
true
Emit::Native
}
// rD = 0 - gpr[rA] [64-bit]; skip OE/recording forms.
PpcOpcode::negx => {
if instr.oe() || instr.rc_bit() {
return false;
return Emit::Fallback;
}
dynasm!(ops ; .arch x64
; mov rax, [r15 + off.gpr(ra)]
@@ -264,7 +310,7 @@ pub fn try_emit_native(
; mov [r15 + off.gpr(rd)], rax
);
advance_and_count(ops, off);
true
Emit::Native
}
// ===== Compares: cr[bf] = { lt, gt, eq, so=xer_so!=0 } =====
// cr[bf] = signed(ra ? imm) [L: 64-bit, else 32-bit]
@@ -278,7 +324,7 @@ pub fn try_emit_native(
}
emit_cr_from_flags(ops, off, bf, /*signed=*/ true);
advance_and_count(ops, off);
true
Emit::Native
}
// cr[bf] = unsigned(ra ? imm)
PpcOpcode::cmpli => {
@@ -291,7 +337,7 @@ pub fn try_emit_native(
}
emit_cr_from_flags(ops, off, bf, /*signed=*/ false);
advance_and_count(ops, off);
true
Emit::Native
}
// cr[bf] = signed(ra ? rb)
PpcOpcode::cmp => {
@@ -311,7 +357,7 @@ pub fn try_emit_native(
}
emit_cr_from_flags(ops, off, bf, /*signed=*/ true);
advance_and_count(ops, off);
true
Emit::Native
}
// cr[bf] = unsigned(ra ? rb)
PpcOpcode::cmpl => {
@@ -331,50 +377,162 @@ pub fn try_emit_native(
}
emit_cr_from_flags(ops, off, bf, /*signed=*/ false);
advance_and_count(ops, off);
true
Emit::Native
}
// ===== Loads (D-form): rD = EXT(mem[(rA==0?0:gpr[rA]) + EXTS(D)]) =====
PpcOpcode::lbz => {
emit_load(ops, off, helpers.read_u8, ra, rd, instr.d(), Ext::Zx8);
true
Emit::Native
}
PpcOpcode::lhz => {
emit_load(ops, off, helpers.read_u16, ra, rd, instr.d(), Ext::Zx16);
true
Emit::Native
}
PpcOpcode::lha => {
emit_load(ops, off, helpers.read_u16, ra, rd, instr.d(), Ext::Sx16);
true
Emit::Native
}
PpcOpcode::lwz => {
emit_load(ops, off, helpers.read_u32, ra, rd, instr.d(), Ext::Zx32);
true
Emit::Native
}
// ===== Stores: mem[(rA==0?0:gpr[rA]) + EXTS(D/DS)] = gpr[rS] =====
PpcOpcode::stb => {
emit_store(ops, off, helpers.store_u8, ra, rd, instr.d());
true
Emit::Native
}
PpcOpcode::sth => {
emit_store(ops, off, helpers.store_u16, ra, rd, instr.d());
true
Emit::Native
}
PpcOpcode::stw => {
emit_store(ops, off, helpers.store_u32, ra, rd, instr.d());
true
Emit::Native
}
PpcOpcode::std => {
// DS-form displacement (14-bit signed << 2).
emit_store(ops, off, helpers.store_u64, ra, rd, instr.ds());
true
Emit::Native
}
_ => false,
// ===== Branches (block terminators) =====
// Unconditional: target = aa ? LI : pc+LI; lk -> lr = pc+4; pc = target.
PpcOpcode::bx => {
if instr.lk() {
// lr = (pc+4) as u64, from the ORIGINAL pc (before it changes).
dynasm!(ops ; .arch x64
; mov edx, [r15 + off.pc]
; lea ecx, [rdx + 4] // 64-bit base wraps like (pc+4) as u32
; mov [r15 + off.lr], rcx
);
}
if instr.aa() {
let tgt = instr.li() as u32 as i32;
dynasm!(ops ; .arch x64 ; mov DWORD [r15 + off.pc], tgt);
} else {
let li = instr.li(); // signed offset (already <<2)
dynasm!(ops ; .arch x64 ; add DWORD [r15 + off.pc], li);
}
count_only(ops, off);
Emit::Branch
}
// Conditional: optional CTR decrement + CTR/CR test; taken -> pc=target,
// else pc+=4; lk -> lr = pc+4 in both cases (from the original pc).
PpcOpcode::bcx => {
let bo = instr.bo();
let bi = instr.bi();
emit_branch_cond(ops, off, bo, bi); // taken (0/1) -> al
dynasm!(ops ; .arch x64 ; mov edx, [r15 + off.pc]); // edx = original pc
if instr.lk() {
dynasm!(ops ; .arch x64
; lea ecx, [rdx + 4]
; mov [r15 + off.lr], rcx
);
}
// not-taken candidate (pc+4) in ecx; taken target in edx.
dynasm!(ops ; .arch x64 ; lea ecx, [rdx + 4]);
if instr.aa() {
let tgt = instr.bd() as u32 as i32;
dynasm!(ops ; .arch x64 ; mov edx, tgt);
} else {
let bd = instr.bd(); // signed offset (already <<2)
dynasm!(ops ; .arch x64 ; lea edx, [rdx + bd]);
}
dynasm!(ops ; .arch x64
; test al, al
; cmovne ecx, edx // taken -> target, else stays pc+4
; mov [r15 + off.pc], ecx
);
count_only(ops, off);
Emit::Branch
}
// Return via LR: taken -> pc = (lr as u32) & !3, else pc+=4;
// lk -> lr = pc+4 (set AFTER reading lr for the target).
PpcOpcode::bclrx => {
let bo = instr.bo();
let bi = instr.bi();
emit_branch_cond(ops, off, bo, bi); // taken (0/1) -> al
dynasm!(ops ; .arch x64
; mov edx, [r15 + off.pc]
; lea r8d, [rdx + 4] // next_pc = pc+4, preserved in r8
; mov edx, [r15 + off.lr] // edx = lr low 32
; and edx, -4 // & !3
; mov ecx, r8d // not-taken default = next_pc
; test al, al
; cmovne ecx, edx // taken -> lr & !3
; mov [r15 + off.pc], ecx
);
if instr.lk() {
dynasm!(ops ; .arch x64 ; mov [r15 + off.lr], r8);
}
count_only(ops, off);
Emit::Branch
}
_ => Emit::Fallback,
}
}
/// Emit the `bcx`/`bclrx` condition evaluation, leaving `taken` (0/1) in `al`.
/// Mirrors the interpreter: optionally decrement CTR (when `BO2` is clear), then
/// `ctr_ok = BO2 || (((ctr as u32)!=0) ^ BO3)` and
/// `cond_ok = BO0 || (get_cr_bit(bi) == BO1)`; `taken = ctr_ok && cond_ok`.
/// (`BO` bits are numbered from the MSB: BO0=0x10, BO1=0x08, BO2=0x04,
/// BO3=0x02.) Everything folds to constants at compile time except the runtime
/// CTR/CR reads. Clobbers rax, rcx.
fn emit_branch_cond(ops: &mut Asm, off: &Offsets, bo: u32, bi: u32) {
// ctr_ok -> al
if bo & 0b00100 != 0 {
dynasm!(ops ; .arch x64 ; mov al, 1); // CTR ignored: always ctr_ok
} else {
// Decrement CTR (u64), then test its low 32 bits against 0.
dynasm!(ops ; .arch x64
; dec QWORD [r15 + off.ctr]
; mov ecx, [r15 + off.ctr]
; test ecx, ecx
);
if bo & 0b00010 != 0 {
dynasm!(ops ; .arch x64 ; sete al); // branch if CTR == 0
} else {
dynasm!(ops ; .arch x64 ; setne al); // branch if CTR != 0
}
}
// cond_ok -> cl
if bo & 0b10000 != 0 {
dynasm!(ops ; .arch x64 ; mov cl, 1); // condition ignored: always cond_ok
} else {
let crbit = off.cr_bit_off(bi);
dynasm!(ops ; .arch x64 ; mov cl, [r15 + crbit]); // crbit is 0/1
if bo & 0b01000 == 0 {
// expected bit == 0: cond_ok = !crbit
dynasm!(ops ; .arch x64 ; xor cl, 1);
}
}
dynasm!(ops ; .arch x64 ; and al, cl); // taken = ctr_ok & cond_ok
}
/// Result-extension mode for a native load.
#[derive(Clone, Copy)]
enum Ext {

View File

@@ -242,11 +242,24 @@ fn compile_block(block: &DecodedBlock) -> CompiledBlock {
);
for instr in instrs.iter() {
// Native fast path: emitters fully handle the instruction (compute +
// pc+=4 + counter bumps) and never diverge control flow, so no exit
// checks are needed after them.
if emit::try_emit_native(&mut ops, &off, &mem_helpers, instr) {
continue;
match emit::try_emit_native(&mut ops, &off, &mem_helpers, instr) {
// Native non-branch: fully handled (compute + pc+=4 + counter
// bumps), control always continues — no exit check needed.
emit::Emit::Native => continue,
// Native branch: it set pc/lr/ctr + bumped counters. Append the same
// pc-discontinuity check the fallback path uses so a taken branch
// ends the block and a fall-through continues (matches step_block).
emit::Emit::Branch => {
let expected_next = instr.addr.wrapping_add(4) as i32;
dynasm!(ops
; .arch x64
; cmp DWORD [r15 + off.pc], expected_next
; jne =>l_cont
);
continue;
}
// Un-ported opcode: emit the interpreter fallback below.
emit::Emit::Fallback => {}
}
// Interpreter fallback for un-ported opcodes.
let instr_ptr = instr as *const DecodedInstr as usize as i64;

View File

@@ -148,6 +148,20 @@ fn enc_x(op: u32, b6_10: u32, b11_15: u32, b16_20: u32, xo: u32, rc: u32) -> u32
fn enc_xo(op: u32, b6_10: u32, b11_15: u32, b16_20: u32, oe: u32, xo: u32, rc: u32) -> u32 {
(op << 26) | (b6_10 << 21) | (b11_15 << 16) | (b16_20 << 11) | (oe << 10) | (xo << 1) | rc
}
/// I-form `bx` (op 18): 24-bit LI (bits 6-29, target offset >> 2), AA, LK.
fn enc_bx(off_bytes: i32, aa: u32, lk: u32) -> u32 {
let li24 = ((off_bytes >> 2) as u32) & 0x00FF_FFFF;
(18 << 26) | (li24 << 2) | (aa << 1) | lk
}
/// B-form `bcx` (op 16): BO, BI, 14-bit BD (bits 16-29, offset >> 2), AA, LK.
fn enc_bcx(bo: u32, bi: u32, off_bytes: i32, aa: u32, lk: u32) -> u32 {
let bd14 = ((off_bytes >> 2) as u32) & 0x0000_3FFF;
(16 << 26) | (bo << 21) | (bi << 16) | (bd14 << 2) | (aa << 1) | lk
}
/// XL-form `bclrx` (op 19, XO 16): BO, BI, LK.
fn enc_bclrx(bo: u32, bi: u32, lk: u32) -> u32 {
(19 << 26) | (bo << 21) | (bi << 16) | (16 << 1) | lk
}
/// Deterministic PRNG (splitmix64-ish) so failures are reproducible.
fn rng(state: &mut u64) -> u64 {
@@ -177,7 +191,7 @@ fn check(raw: u32, gpr: [u64; 32]) {
let helpers = crate::MemHelpers::resolve();
let mut probe = dynasmrt::x64::Assembler::new().unwrap();
assert!(
emit::try_emit_native(&mut probe, &off, &helpers, &instr),
emit::try_emit_native(&mut probe, &off, &helpers, &instr) != emit::Emit::Fallback,
"opcode not natively emitted for raw={raw:#010x} ({:?})",
instr.opcode
);
@@ -224,7 +238,7 @@ fn check_cmp(raw: u32, gpr: [u64; 32], xer_so: u8) {
let helpers = crate::MemHelpers::resolve();
let mut probe = dynasmrt::x64::Assembler::new().unwrap();
assert!(
emit::try_emit_native(&mut probe, &off, &helpers, &instr),
emit::try_emit_native(&mut probe, &off, &helpers, &instr) != emit::Emit::Fallback,
"compare not natively emitted raw={raw:#010x} ({:?})",
instr.opcode
);
@@ -358,6 +372,109 @@ fn compares_match() {
}
}
/// Branch differential check: seed pc/lr/ctr/cr identically, run the branch
/// through the interpreter and the JIT (a 1-instruction block), and assert
/// pc/lr/ctr/cr/counters/StepResult all match. Branches must be emitted as
/// [`emit::Emit::Branch`] specifically.
fn check_branch(raw: u32, lr: u64, ctr: u64, cr_seed: u8) {
let pc = 0x8200_1000u32;
let instr: DecodedInstr = decode(raw, pc);
let off = emit::Offsets::resolve();
let helpers = crate::MemHelpers::resolve();
let mut probe = dynasmrt::x64::Assembler::new().unwrap();
assert_eq!(
emit::try_emit_native(&mut probe, &off, &helpers, &instr),
emit::Emit::Branch,
"branch not emitted as Emit::Branch raw={raw:#010x} ({:?})",
instr.opcode
);
let seed = |c: &mut PpcContext| {
c.lr = lr;
c.ctr = ctr;
for (i, f) in c.cr.iter_mut().enumerate() {
*f = xenia_cpu::context::CrField::from_u8(cr_seed.wrapping_add(i as u8) & 0xF);
}
};
let mem = NoMem;
let mut a = ctx_from_gpr([0u64; 32], pc);
seed(&mut a);
let ra = interpret_one(&mut a, &mem, &instr);
a.cycle_count += 1;
a.timebase += 1;
let mut b = ctx_from_gpr([0u64; 32], pc);
seed(&mut b);
let block = DecodedBlock {
start_pc: pc,
end_pc: pc.wrapping_add(4),
page_version: 0,
instrs: vec![instr],
sync_sensitive: false,
};
let cb: CompiledBlock = compile_block(&block);
let rb = run_jit_block(&cb, &mut b, &mem);
assert_eq!(a.pc, b.pc, "pc mismatch raw={raw:#010x} ({:?})", instr.opcode);
assert_eq!(a.lr, b.lr, "lr mismatch raw={raw:#010x} ({:?})", instr.opcode);
assert_eq!(a.ctr, b.ctr, "ctr mismatch raw={raw:#010x} ({:?})", instr.opcode);
assert_eq!(a.cycle_count, b.cycle_count, "cycle mismatch raw={raw:#010x}");
assert_eq!(a.timebase, b.timebase, "timebase mismatch raw={raw:#010x}");
let cra: [u8; 8] = std::array::from_fn(|i| a.cr[i].as_u8());
let crb: [u8; 8] = std::array::from_fn(|i| b.cr[i].as_u8());
assert_eq!(cra, crb, "cr mismatch raw={raw:#010x}");
assert_eq!(ra, rb, "StepResult mismatch raw={raw:#010x}");
}
#[test]
fn unconditional_branch_matches() {
let mut s = 0xb00u64;
for _ in 0..ITERS {
// Non-negative and negative offsets (multiples of 4), both lk states.
let off = ((rng(&mut s) & 0x3FFF) as i32) * 4 - 0x8000;
for lk in [0u32, 1u32] {
check_branch(enc_bx(off, 0, lk), 0xdead_beef_1234_0000, 0x55, 0);
}
// Absolute form: small positive absolute target.
let abs = ((rng(&mut s) & 0x3FFF) as i32) * 4;
check_branch(enc_bx(abs, 1, 0), 0, 0, 0);
}
}
#[test]
fn conditional_branch_matches() {
let mut s = 0xbc0u64;
for _ in 0..ITERS {
let bo = (rng(&mut s) % 32) as u32; // exhaustive over the 5 BO bits
let bi = (rng(&mut s) % 32) as u32; // any CR bit
let off = ((rng(&mut s) & 0x1FFF) as i32) * 4 - 0x4000;
let ctr = rng(&mut s); // hits ctr==1 boundary (dec ->0) sometimes
let cr_seed = rng(&mut s) as u8;
for lk in [0u32, 1u32] {
check_branch(enc_bcx(bo, bi, off, 0, lk), 0, ctr, cr_seed);
}
// Force the CTR==1 -> 0 boundary explicitly.
check_branch(enc_bcx(bo, bi, off, 0, 0), 0, 1, cr_seed);
}
}
#[test]
fn return_branch_matches() {
let mut s = 0xb1cu64;
for _ in 0..ITERS {
let bo = (rng(&mut s) % 32) as u32;
let bi = (rng(&mut s) % 32) as u32;
let lr = rng(&mut s); // exercises the & !3 alignment mask
let ctr = rng(&mut s);
let cr_seed = rng(&mut s) as u8;
for lk in [0u32, 1u32] {
check_branch(enc_bclrx(bo, bi, lk), lr, ctr, cr_seed);
}
check_branch(enc_bclrx(bo, bi, 0), lr, 1, cr_seed);
}
}
/// The recording/OE forms and the db16cyc hint must NOT be natively emitted
/// (they fall back to the interpreter). Guards against a future emitter
/// accidentally handling a form it can't reproduce.
@@ -375,7 +492,7 @@ fn recording_and_hint_forms_fall_back() {
for raw in cases {
let instr = decode(raw, 0x8200_1000);
assert!(
!emit::try_emit_native(&mut probe, &off, &helpers, &instr),
emit::try_emit_native(&mut probe, &off, &helpers, &instr) == emit::Emit::Fallback,
"raw={raw:#010x} ({:?}) should fall back, not native",
instr.opcode
);
@@ -392,7 +509,7 @@ fn check_mem(raw: u32, gpr: [u64; 32]) {
let helpers = crate::MemHelpers::resolve();
let mut probe = dynasmrt::x64::Assembler::new().unwrap();
assert!(
emit::try_emit_native(&mut probe, &off, &helpers, &instr),
emit::try_emit_native(&mut probe, &off, &helpers, &instr) != emit::Emit::Fallback,
"mem opcode not natively emitted raw={raw:#010x} ({:?})",
instr.opcode
);