[iterate-4A] jit: per-block GPR register caching — bodies now beat interp
Guest GPRs were reloaded from PpcContext memory on every use and stored back after every write, forcing memory traffic that held the JIT bodies at mere interpreter parity. Now each GPR is loaded ONCE per block into a Cranelift SSA value (kept in a host register by the register allocator); writes update an in-EmitCtx cache (RegCache: [Option<Value>;32] + dirty bits) without storing; dirty GPRs are flushed to memory only at the block boundary (cache_flush at the end of emit_node_body). Sound because nothing a block calls back into touches guest GPRs: the memory trampolines take addr/val as args (I pass the cached SSA values), and the FP-punt runs interpreter FP ops that touch fpr/fpscr/cr only. So the cache never goes stale mid-block; flushing at the boundary makes gpr[] interpreter-identical for the runner and the next block. FPR/CR still go through memory (the FP-punt writes them, which would need flush-around-punt — deferred). emit_op / ea_d / ea_x / gpr32 / store_gpr32z rewired to cache_read_gpr / cache_write_gpr; cache_read_gpr copies the Option out before re-borrowing (a RefCell double-borrow would panic). Validated: 7/7 jit unit tests; diff MISMATCHES=0 over 47.9M blocks (foreground). Perf (foreground, RUST_LOG=warn, -n 3e9 to amortize compile cost — -n<=1.5e9 is compile-dominated and misleads): interp 95.8 MIPS -> jit-no-chain 100.2 MIPS (bodies now BEAT interp, were parity) -> chain 100.4 MIPS / 57.95s = -15.6% wall vs interp (was ~-6% at B2), dispatch 18.9%, 110M calls (1.95x merge). Same-batch -n 2e9: interp 44.8s -> chain 41.0s = -8.6%. Register caching is what makes the bodies fast enough that chaining's dispatch cut converts to real wall time (B2's bodies had slowed to ~82 MIPS and cancelled the win). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -197,10 +197,73 @@ struct Trampolines {
|
||||
/// Per-compile emit context: the two entry-block pointer params plus the
|
||||
/// trampoline `FuncRef`s. `ctxp` is `*mut PpcContext`, `memenv` is `*const
|
||||
/// MemEnv`, both as IR `Value`s.
|
||||
///
|
||||
/// `regs` is a per-block **GPR register cache**: instead of re-loading a guest
|
||||
/// GPR from `PpcContext` memory on every use and storing it back after every
|
||||
/// write, a GPR is loaded once (on first read within a block) into a Cranelift
|
||||
/// SSA value that the register allocator keeps in a host register, and dirty
|
||||
/// GPRs are written back to memory only at the block boundary ([`cache_flush`]).
|
||||
/// This removes the redundant per-instruction memory traffic that kept the JIT
|
||||
/// bodies merely at interpreter parity. It is sound because nothing a block
|
||||
/// calls back into (the memory trampolines take addr/val as args; the FP-punt
|
||||
/// runs interpreter FP ops that touch fpr/fpscr/cr only) reads or writes guest
|
||||
/// GPRs — so the cache never goes stale mid-block.
|
||||
struct EmitCtx {
|
||||
ctxp: Value,
|
||||
memenv: Value,
|
||||
tr: Trampolines,
|
||||
regs: std::cell::RefCell<RegCache>,
|
||||
}
|
||||
|
||||
/// Per-block GPR register cache (see [`EmitCtx::regs`]). Reset at each block
|
||||
/// boundary; `gpr[r]` is the current SSA value of guest r`r` (I64), `dirty[r]`
|
||||
/// marks it as written-but-not-yet-flushed to `PpcContext` memory.
|
||||
struct RegCache {
|
||||
gpr: [Option<Value>; 32],
|
||||
dirty: [bool; 32],
|
||||
}
|
||||
|
||||
impl RegCache {
|
||||
fn new() -> Self {
|
||||
Self { gpr: [None; 32], dirty: [false; 32] }
|
||||
}
|
||||
}
|
||||
|
||||
/// Read guest GPR `r` as an I64: the cached SSA value if present, else load it
|
||||
/// from `PpcContext` memory once and cache it.
|
||||
fn cache_read_gpr(b: &mut FunctionBuilder, ec: &EmitCtx, r: usize) -> Value {
|
||||
// Copy the cached Option out and drop the borrow before any re-borrow below
|
||||
// (RefCell would panic on an overlapping borrow_mut).
|
||||
let cached = ec.regs.borrow().gpr[r];
|
||||
if let Some(v) = cached {
|
||||
return v;
|
||||
}
|
||||
let v = load_gpr(b, ec.ctxp, r);
|
||||
ec.regs.borrow_mut().gpr[r] = Some(v);
|
||||
v
|
||||
}
|
||||
|
||||
/// Write guest GPR `r` (I64 SSA value) into the cache, marking it dirty. The
|
||||
/// store to `PpcContext` memory is deferred to [`cache_flush`] at the block
|
||||
/// boundary.
|
||||
fn cache_write_gpr(ec: &EmitCtx, r: usize, v: Value) {
|
||||
let mut c = ec.regs.borrow_mut();
|
||||
c.gpr[r] = Some(v);
|
||||
c.dirty[r] = true;
|
||||
}
|
||||
|
||||
/// Flush all dirty cached GPRs back to `PpcContext` memory and clear the cache,
|
||||
/// so the block boundary (and the next block, which re-loads) sees the same
|
||||
/// `gpr[]` state the interpreter would. Called at the end of every block body.
|
||||
fn cache_flush(b: &mut FunctionBuilder, ec: &EmitCtx) {
|
||||
let mut c = ec.regs.borrow_mut();
|
||||
for r in 0..32 {
|
||||
if c.dirty[r] {
|
||||
let v = c.gpr[r].expect("dirty gpr has a value");
|
||||
store_gpr(b, ec.ctxp, r, v);
|
||||
}
|
||||
}
|
||||
*c = RegCache::new();
|
||||
}
|
||||
|
||||
// ---- compiled-block cache -------------------------------------------------
|
||||
@@ -508,6 +571,7 @@ impl Jit {
|
||||
ctxp: b.block_params(entry)[0],
|
||||
memenv: b.block_params(entry)[1],
|
||||
tr,
|
||||
regs: std::cell::RefCell::new(RegCache::new()),
|
||||
};
|
||||
let _ = emit_node_body(&mut b, &ec, block);
|
||||
let ret = b.ins().iconst(types::I32, RET_CONTINUE as i64);
|
||||
@@ -580,7 +644,7 @@ impl Jit {
|
||||
let ctxp = b.block_params(ib)[0];
|
||||
let memenv = b.block_params(ib)[1];
|
||||
let remaining = b.block_params(ib)[2]; // I64 remaining budget
|
||||
let ec = EmitCtx { ctxp, memenv, tr };
|
||||
let ec = EmitCtx { ctxp, memenv, tr, regs: std::cell::RefCell::new(RegCache::new()) };
|
||||
|
||||
// Entry-sampled constants. The entry block dominates every node
|
||||
// (including across loop back-edges), so these are usable at all
|
||||
@@ -736,6 +800,9 @@ fn emit_node_body(b: &mut FunctionBuilder, ec: &EmitCtx, block: &DecodedBlock) -
|
||||
emit_op(b, ec, instr);
|
||||
}
|
||||
}
|
||||
// Write back any GPRs cached in host registers during the block, so the
|
||||
// boundary and the next block observe the interpreter-identical `gpr[]`.
|
||||
cache_flush(b, ec);
|
||||
// A branch terminator writes `pc` itself (to its computed target); otherwise
|
||||
// the block is straight-line and `pc` advances to `end_pc`. `writes_pc` is
|
||||
// only ever true for the last instruction, so testing it is sufficient.
|
||||
@@ -995,19 +1062,19 @@ fn store_lr(b: &mut FunctionBuilder, ctxp: Value, val_u64: u64) {
|
||||
b.ins().store(MemFlags::trusted(), v, ctxp, off(offset_of!(PpcContext, lr)));
|
||||
}
|
||||
|
||||
/// Low 32 bits of GPR `r` as an `I32`.
|
||||
/// Low 32 bits of GPR `r` as an `I32` (via the register cache).
|
||||
#[inline]
|
||||
fn gpr32(b: &mut FunctionBuilder, ctxp: Value, r: usize) -> Value {
|
||||
let v = load_gpr(b, ctxp, r);
|
||||
fn gpr32(b: &mut FunctionBuilder, ec: &EmitCtx, r: usize) -> Value {
|
||||
let v = cache_read_gpr(b, ec, r);
|
||||
b.ins().ireduce(types::I32, v)
|
||||
}
|
||||
|
||||
/// Store an `I32` result into GPR `ra`, zero-extended to 64 bits — the width
|
||||
/// contract of the `*cx`/`nor`/`nand`/`rlwinm` ops (which zero the upper half).
|
||||
#[inline]
|
||||
fn store_gpr32z(b: &mut FunctionBuilder, ctxp: Value, ra: usize, v32: Value) {
|
||||
fn store_gpr32z(b: &mut FunctionBuilder, ec: &EmitCtx, ra: usize, v32: Value) {
|
||||
let z = b.ins().uextend(types::I64, v32);
|
||||
store_gpr(b, ctxp, ra, z);
|
||||
cache_write_gpr(ec, ra, z);
|
||||
}
|
||||
|
||||
/// PPC `rlwnm`-family mask. Mirrors the interpreter's `rlw_mask`; `mb`/`me` are
|
||||
@@ -1144,7 +1211,7 @@ fn ea_d(b: &mut FunctionBuilder, ec: &EmitCtx, ra: usize, disp: i32) -> Value {
|
||||
let base = if ra == 0 {
|
||||
b.ins().iconst(types::I64, 0)
|
||||
} else {
|
||||
load_gpr(b, ec.ctxp, ra)
|
||||
cache_read_gpr(b, ec, ra)
|
||||
};
|
||||
let ea64 = b.ins().iadd_imm(base, disp as i64);
|
||||
b.ins().ireduce(types::I32, ea64)
|
||||
@@ -1156,9 +1223,9 @@ fn ea_x(b: &mut FunctionBuilder, ec: &EmitCtx, ra: usize, rb: usize) -> Value {
|
||||
let base = if ra == 0 {
|
||||
b.ins().iconst(types::I64, 0)
|
||||
} else {
|
||||
load_gpr(b, ec.ctxp, ra)
|
||||
cache_read_gpr(b, ec, ra)
|
||||
};
|
||||
let rbv = load_gpr(b, ec.ctxp, rb);
|
||||
let rbv = cache_read_gpr(b, ec, rb);
|
||||
let ea64 = b.ins().iadd(base, rbv);
|
||||
b.ins().ireduce(types::I32, ea64)
|
||||
}
|
||||
@@ -1305,10 +1372,10 @@ fn emit_op(b: &mut FunctionBuilder, ec: &EmitCtx, instr: &DecodedInstr) {
|
||||
let rav = if ra == 0 {
|
||||
b.ins().iconst(types::I64, 0)
|
||||
} else {
|
||||
load_gpr(b, ctxp, ra)
|
||||
cache_read_gpr(b, ec, ra)
|
||||
};
|
||||
let res = b.ins().iadd_imm(rav, instr.simm16() as i64);
|
||||
store_gpr(b, ctxp, instr.rd(), res);
|
||||
cache_write_gpr(ec, instr.rd(), res);
|
||||
}
|
||||
// rd = (ra==0 ? 0 : gpr[ra]) + (EXTS(simm) << 16) [64-bit]
|
||||
PpcOpcode::addis => {
|
||||
@@ -1316,18 +1383,18 @@ fn emit_op(b: &mut FunctionBuilder, ec: &EmitCtx, instr: &DecodedInstr) {
|
||||
let rav = if ra == 0 {
|
||||
b.ins().iconst(types::I64, 0)
|
||||
} else {
|
||||
load_gpr(b, ctxp, ra)
|
||||
cache_read_gpr(b, ec, ra)
|
||||
};
|
||||
let res = b.ins().iadd_imm(rav, (instr.simm16() as i64) << 16);
|
||||
store_gpr(b, ctxp, instr.rd(), res);
|
||||
cache_write_gpr(ec, instr.rd(), res);
|
||||
}
|
||||
|
||||
// ---- reg-reg add/sub (OE=0). Full 64-bit result; CR0 on low 32. ----
|
||||
PpcOpcode::addx => {
|
||||
let ra = load_gpr(b, ctxp, instr.ra());
|
||||
let rb = load_gpr(b, ctxp, instr.rb());
|
||||
let ra = cache_read_gpr(b, ec, instr.ra());
|
||||
let rb = cache_read_gpr(b, ec, instr.rb());
|
||||
let res = b.ins().iadd(ra, rb);
|
||||
store_gpr(b, ctxp, instr.rd(), res);
|
||||
cache_write_gpr(ec, instr.rd(), res);
|
||||
if instr.rc_bit() {
|
||||
let lo = b.ins().ireduce(types::I32, res);
|
||||
emit_cr0_signed32(b, ctxp, lo);
|
||||
@@ -1335,10 +1402,10 @@ fn emit_op(b: &mut FunctionBuilder, ec: &EmitCtx, instr: &DecodedInstr) {
|
||||
}
|
||||
PpcOpcode::subfx => {
|
||||
// rd = rb - ra
|
||||
let ra = load_gpr(b, ctxp, instr.ra());
|
||||
let rb = load_gpr(b, ctxp, instr.rb());
|
||||
let ra = cache_read_gpr(b, ec, instr.ra());
|
||||
let rb = cache_read_gpr(b, ec, instr.rb());
|
||||
let res = b.ins().isub(rb, ra);
|
||||
store_gpr(b, ctxp, instr.rd(), res);
|
||||
cache_write_gpr(ec, instr.rd(), res);
|
||||
if instr.rc_bit() {
|
||||
let lo = b.ins().ireduce(types::I32, res);
|
||||
emit_cr0_signed32(b, ctxp, lo);
|
||||
@@ -1347,30 +1414,30 @@ fn emit_op(b: &mut FunctionBuilder, ec: &EmitCtx, instr: &DecodedInstr) {
|
||||
|
||||
// ---- reg-reg logical preserving all 64 bits; CR0 on low 32 if Rc. ----
|
||||
PpcOpcode::orx => {
|
||||
let rs = load_gpr(b, ctxp, instr.rs());
|
||||
let rb = load_gpr(b, ctxp, instr.rb());
|
||||
let rs = cache_read_gpr(b, ec, instr.rs());
|
||||
let rb = cache_read_gpr(b, ec, instr.rb());
|
||||
let res = b.ins().bor(rs, rb);
|
||||
store_gpr(b, ctxp, instr.ra(), res);
|
||||
cache_write_gpr(ec, instr.ra(), res);
|
||||
if instr.rc_bit() {
|
||||
let lo = b.ins().ireduce(types::I32, res);
|
||||
emit_cr0_signed32(b, ctxp, lo);
|
||||
}
|
||||
}
|
||||
PpcOpcode::andx => {
|
||||
let rs = load_gpr(b, ctxp, instr.rs());
|
||||
let rb = load_gpr(b, ctxp, instr.rb());
|
||||
let rs = cache_read_gpr(b, ec, instr.rs());
|
||||
let rb = cache_read_gpr(b, ec, instr.rb());
|
||||
let res = b.ins().band(rs, rb);
|
||||
store_gpr(b, ctxp, instr.ra(), res);
|
||||
cache_write_gpr(ec, instr.ra(), res);
|
||||
if instr.rc_bit() {
|
||||
let lo = b.ins().ireduce(types::I32, res);
|
||||
emit_cr0_signed32(b, ctxp, lo);
|
||||
}
|
||||
}
|
||||
PpcOpcode::xorx => {
|
||||
let rs = load_gpr(b, ctxp, instr.rs());
|
||||
let rb = load_gpr(b, ctxp, instr.rb());
|
||||
let rs = cache_read_gpr(b, ec, instr.rs());
|
||||
let rb = cache_read_gpr(b, ec, instr.rb());
|
||||
let res = b.ins().bxor(rs, rb);
|
||||
store_gpr(b, ctxp, instr.ra(), res);
|
||||
cache_write_gpr(ec, instr.ra(), res);
|
||||
if instr.rc_bit() {
|
||||
let lo = b.ins().ireduce(types::I32, res);
|
||||
emit_cr0_signed32(b, ctxp, lo);
|
||||
@@ -1379,41 +1446,41 @@ fn emit_op(b: &mut FunctionBuilder, ec: &EmitCtx, instr: &DecodedInstr) {
|
||||
|
||||
// ---- reg-reg logical operating in u32 (zeroes upper 32); CR0 low 32. ----
|
||||
PpcOpcode::norx => {
|
||||
let rs = gpr32(b, ctxp, instr.rs());
|
||||
let rb = gpr32(b, ctxp, instr.rb());
|
||||
let rs = gpr32(b, ec, instr.rs());
|
||||
let rb = gpr32(b, ec, instr.rb());
|
||||
let t = b.ins().bor(rs, rb);
|
||||
let res = b.ins().bnot(t);
|
||||
store_gpr32z(b, ctxp, instr.ra(), res);
|
||||
store_gpr32z(b, ec, instr.ra(), res);
|
||||
if instr.rc_bit() {
|
||||
emit_cr0_signed32(b, ctxp, res);
|
||||
}
|
||||
}
|
||||
PpcOpcode::nandx => {
|
||||
let rs = gpr32(b, ctxp, instr.rs());
|
||||
let rb = gpr32(b, ctxp, instr.rb());
|
||||
let rs = gpr32(b, ec, instr.rs());
|
||||
let rb = gpr32(b, ec, instr.rb());
|
||||
let t = b.ins().band(rs, rb);
|
||||
let res = b.ins().bnot(t);
|
||||
store_gpr32z(b, ctxp, instr.ra(), res);
|
||||
store_gpr32z(b, ec, instr.ra(), res);
|
||||
if instr.rc_bit() {
|
||||
emit_cr0_signed32(b, ctxp, res);
|
||||
}
|
||||
}
|
||||
PpcOpcode::andcx => {
|
||||
let rs = gpr32(b, ctxp, instr.rs());
|
||||
let rb = gpr32(b, ctxp, instr.rb());
|
||||
let rs = gpr32(b, ec, instr.rs());
|
||||
let rb = gpr32(b, ec, instr.rb());
|
||||
let nrb = b.ins().bnot(rb);
|
||||
let res = b.ins().band(rs, nrb);
|
||||
store_gpr32z(b, ctxp, instr.ra(), res);
|
||||
store_gpr32z(b, ec, instr.ra(), res);
|
||||
if instr.rc_bit() {
|
||||
emit_cr0_signed32(b, ctxp, res);
|
||||
}
|
||||
}
|
||||
PpcOpcode::orcx => {
|
||||
let rs = gpr32(b, ctxp, instr.rs());
|
||||
let rb = gpr32(b, ctxp, instr.rb());
|
||||
let rs = gpr32(b, ec, instr.rs());
|
||||
let rb = gpr32(b, ec, instr.rb());
|
||||
let nrb = b.ins().bnot(rb);
|
||||
let res = b.ins().bor(rs, nrb);
|
||||
store_gpr32z(b, ctxp, instr.ra(), res);
|
||||
store_gpr32z(b, ec, instr.ra(), res);
|
||||
if instr.rc_bit() {
|
||||
emit_cr0_signed32(b, ctxp, res);
|
||||
}
|
||||
@@ -1421,49 +1488,49 @@ fn emit_op(b: &mut FunctionBuilder, ec: &EmitCtx, instr: &DecodedInstr) {
|
||||
|
||||
// ---- immediate logical. 64-bit result. ori/oris/xori/xoris: no CR. ----
|
||||
PpcOpcode::ori => {
|
||||
let rs = load_gpr(b, ctxp, instr.rs());
|
||||
let rs = cache_read_gpr(b, ec, instr.rs());
|
||||
let res = b.ins().bor_imm(rs, instr.uimm16() as i64);
|
||||
store_gpr(b, ctxp, instr.ra(), res);
|
||||
cache_write_gpr(ec, instr.ra(), res);
|
||||
}
|
||||
PpcOpcode::oris => {
|
||||
let rs = load_gpr(b, ctxp, instr.rs());
|
||||
let rs = cache_read_gpr(b, ec, instr.rs());
|
||||
let res = b.ins().bor_imm(rs, (instr.uimm16() as i64) << 16);
|
||||
store_gpr(b, ctxp, instr.ra(), res);
|
||||
cache_write_gpr(ec, instr.ra(), res);
|
||||
}
|
||||
PpcOpcode::xori => {
|
||||
let rs = load_gpr(b, ctxp, instr.rs());
|
||||
let rs = cache_read_gpr(b, ec, instr.rs());
|
||||
let res = b.ins().bxor_imm(rs, instr.uimm16() as i64);
|
||||
store_gpr(b, ctxp, instr.ra(), res);
|
||||
cache_write_gpr(ec, instr.ra(), res);
|
||||
}
|
||||
PpcOpcode::xoris => {
|
||||
let rs = load_gpr(b, ctxp, instr.rs());
|
||||
let rs = cache_read_gpr(b, ec, instr.rs());
|
||||
let res = b.ins().bxor_imm(rs, (instr.uimm16() as i64) << 16);
|
||||
store_gpr(b, ctxp, instr.ra(), res);
|
||||
cache_write_gpr(ec, instr.ra(), res);
|
||||
}
|
||||
// andi./andis. always update CR0 (the `.` forms).
|
||||
PpcOpcode::andix => {
|
||||
let rs = load_gpr(b, ctxp, instr.rs());
|
||||
let rs = cache_read_gpr(b, ec, instr.rs());
|
||||
let res = b.ins().band_imm(rs, instr.uimm16() as i64);
|
||||
store_gpr(b, ctxp, instr.ra(), res);
|
||||
cache_write_gpr(ec, instr.ra(), res);
|
||||
let lo = b.ins().ireduce(types::I32, res);
|
||||
emit_cr0_signed32(b, ctxp, lo);
|
||||
}
|
||||
PpcOpcode::andisx => {
|
||||
let rs = load_gpr(b, ctxp, instr.rs());
|
||||
let rs = cache_read_gpr(b, ec, instr.rs());
|
||||
let res = b.ins().band_imm(rs, (instr.uimm16() as i64) << 16);
|
||||
store_gpr(b, ctxp, instr.ra(), res);
|
||||
cache_write_gpr(ec, instr.ra(), res);
|
||||
let lo = b.ins().ireduce(types::I32, res);
|
||||
emit_cr0_signed32(b, ctxp, lo);
|
||||
}
|
||||
|
||||
// ---- rotate-left word immediate then AND mask; zeroes upper 32. ----
|
||||
PpcOpcode::rlwinmx => {
|
||||
let rs = gpr32(b, ctxp, instr.rs());
|
||||
let rs = gpr32(b, ec, instr.rs());
|
||||
let shv = b.ins().iconst(types::I32, instr.sh() as i64);
|
||||
let rot = b.ins().rotl(rs, shv);
|
||||
let mask = rlw_mask(instr.mb(), instr.me());
|
||||
let res = b.ins().band_imm(rot, mask as i64);
|
||||
store_gpr32z(b, ctxp, instr.ra(), res);
|
||||
store_gpr32z(b, ec, instr.ra(), res);
|
||||
if instr.rc_bit() {
|
||||
emit_cr0_signed32(b, ctxp, res);
|
||||
}
|
||||
@@ -1472,16 +1539,16 @@ fn emit_op(b: &mut FunctionBuilder, ec: &EmitCtx, instr: &DecodedInstr) {
|
||||
// ---- compares. Write CR field crfd() unconditionally. ----
|
||||
PpcOpcode::cmp => {
|
||||
let (lt, gt, eq) = if instr.l() {
|
||||
let ra = load_gpr(b, ctxp, instr.ra());
|
||||
let rb = load_gpr(b, ctxp, instr.rb());
|
||||
let ra = cache_read_gpr(b, ec, instr.ra());
|
||||
let rb = cache_read_gpr(b, ec, instr.rb());
|
||||
(
|
||||
b.ins().icmp(IntCC::SignedLessThan, ra, rb),
|
||||
b.ins().icmp(IntCC::SignedGreaterThan, ra, rb),
|
||||
b.ins().icmp(IntCC::Equal, ra, rb),
|
||||
)
|
||||
} else {
|
||||
let ra = gpr32(b, ctxp, instr.ra());
|
||||
let rb = gpr32(b, ctxp, instr.rb());
|
||||
let ra = gpr32(b, ec, instr.ra());
|
||||
let rb = gpr32(b, ec, instr.rb());
|
||||
(
|
||||
b.ins().icmp(IntCC::SignedLessThan, ra, rb),
|
||||
b.ins().icmp(IntCC::SignedGreaterThan, ra, rb),
|
||||
@@ -1492,16 +1559,16 @@ fn emit_op(b: &mut FunctionBuilder, ec: &EmitCtx, instr: &DecodedInstr) {
|
||||
}
|
||||
PpcOpcode::cmpl => {
|
||||
let (lt, gt, eq) = if instr.l() {
|
||||
let ra = load_gpr(b, ctxp, instr.ra());
|
||||
let rb = load_gpr(b, ctxp, instr.rb());
|
||||
let ra = cache_read_gpr(b, ec, instr.ra());
|
||||
let rb = cache_read_gpr(b, ec, instr.rb());
|
||||
(
|
||||
b.ins().icmp(IntCC::UnsignedLessThan, ra, rb),
|
||||
b.ins().icmp(IntCC::UnsignedGreaterThan, ra, rb),
|
||||
b.ins().icmp(IntCC::Equal, ra, rb),
|
||||
)
|
||||
} else {
|
||||
let ra = gpr32(b, ctxp, instr.ra());
|
||||
let rb = gpr32(b, ctxp, instr.rb());
|
||||
let ra = gpr32(b, ec, instr.ra());
|
||||
let rb = gpr32(b, ec, instr.rb());
|
||||
(
|
||||
b.ins().icmp(IntCC::UnsignedLessThan, ra, rb),
|
||||
b.ins().icmp(IntCC::UnsignedGreaterThan, ra, rb),
|
||||
@@ -1513,14 +1580,14 @@ fn emit_op(b: &mut FunctionBuilder, ec: &EmitCtx, instr: &DecodedInstr) {
|
||||
PpcOpcode::cmpi => {
|
||||
let imm = instr.simm16() as i64; // sign-extended
|
||||
let (lt, gt, eq) = if instr.l() {
|
||||
let ra = load_gpr(b, ctxp, instr.ra());
|
||||
let ra = cache_read_gpr(b, ec, instr.ra());
|
||||
(
|
||||
b.ins().icmp_imm(IntCC::SignedLessThan, ra, imm),
|
||||
b.ins().icmp_imm(IntCC::SignedGreaterThan, ra, imm),
|
||||
b.ins().icmp_imm(IntCC::Equal, ra, imm),
|
||||
)
|
||||
} else {
|
||||
let ra = gpr32(b, ctxp, instr.ra());
|
||||
let ra = gpr32(b, ec, instr.ra());
|
||||
// 32-bit signed compare against sign-extended SIMM.
|
||||
let imm32 = instr.simm16() as i32 as i64;
|
||||
(
|
||||
@@ -1534,14 +1601,14 @@ fn emit_op(b: &mut FunctionBuilder, ec: &EmitCtx, instr: &DecodedInstr) {
|
||||
PpcOpcode::cmpli => {
|
||||
let imm = instr.uimm16() as i64; // zero-extended
|
||||
let (lt, gt, eq) = if instr.l() {
|
||||
let ra = load_gpr(b, ctxp, instr.ra());
|
||||
let ra = cache_read_gpr(b, ec, instr.ra());
|
||||
(
|
||||
b.ins().icmp_imm(IntCC::UnsignedLessThan, ra, imm),
|
||||
b.ins().icmp_imm(IntCC::UnsignedGreaterThan, ra, imm),
|
||||
b.ins().icmp_imm(IntCC::Equal, ra, imm),
|
||||
)
|
||||
} else {
|
||||
let ra = gpr32(b, ctxp, instr.ra());
|
||||
let ra = gpr32(b, ec, instr.ra());
|
||||
(
|
||||
b.ins().icmp_imm(IntCC::UnsignedLessThan, ra, imm),
|
||||
b.ins().icmp_imm(IntCC::UnsignedGreaterThan, ra, imm),
|
||||
@@ -1555,83 +1622,83 @@ fn emit_op(b: &mut FunctionBuilder, ec: &EmitCtx, instr: &DecodedInstr) {
|
||||
PpcOpcode::lbz => {
|
||||
let ea = ea_d(b, ec, instr.ra(), instr.d());
|
||||
let v = emit_inline_load(b, ec, ea, 1, ec.tr.read8);
|
||||
store_gpr(b, ctxp, instr.rd(), v);
|
||||
cache_write_gpr(ec, instr.rd(), v);
|
||||
}
|
||||
PpcOpcode::lbzx => {
|
||||
let ea = ea_x(b, ec, instr.ra(), instr.rb());
|
||||
let v = emit_inline_load(b, ec, ea, 1, ec.tr.read8);
|
||||
store_gpr(b, ctxp, instr.rd(), v);
|
||||
cache_write_gpr(ec, instr.rd(), v);
|
||||
}
|
||||
PpcOpcode::lhz => {
|
||||
let ea = ea_d(b, ec, instr.ra(), instr.d());
|
||||
let v = emit_inline_load(b, ec, ea, 2, ec.tr.read16);
|
||||
store_gpr(b, ctxp, instr.rd(), v);
|
||||
cache_write_gpr(ec, instr.rd(), v);
|
||||
}
|
||||
PpcOpcode::lhzx => {
|
||||
let ea = ea_x(b, ec, instr.ra(), instr.rb());
|
||||
let v = emit_inline_load(b, ec, ea, 2, ec.tr.read16);
|
||||
store_gpr(b, ctxp, instr.rd(), v);
|
||||
cache_write_gpr(ec, instr.rd(), v);
|
||||
}
|
||||
PpcOpcode::lwz => {
|
||||
let ea = ea_d(b, ec, instr.ra(), instr.d());
|
||||
let v = emit_inline_load(b, ec, ea, 4, ec.tr.read32);
|
||||
store_gpr(b, ctxp, instr.rd(), v);
|
||||
cache_write_gpr(ec, instr.rd(), v);
|
||||
}
|
||||
PpcOpcode::lwzx => {
|
||||
let ea = ea_x(b, ec, instr.ra(), instr.rb());
|
||||
let v = emit_inline_load(b, ec, ea, 4, ec.tr.read32);
|
||||
store_gpr(b, ctxp, instr.rd(), v);
|
||||
cache_write_gpr(ec, instr.rd(), v);
|
||||
}
|
||||
PpcOpcode::ld => {
|
||||
let ea = ea_d(b, ec, instr.ra(), instr.ds());
|
||||
let v = emit_inline_load(b, ec, ea, 8, ec.tr.read64);
|
||||
store_gpr(b, ctxp, instr.rd(), v);
|
||||
cache_write_gpr(ec, instr.rd(), v);
|
||||
}
|
||||
PpcOpcode::ldx => {
|
||||
let ea = ea_x(b, ec, instr.ra(), instr.rb());
|
||||
let v = emit_inline_load(b, ec, ea, 8, ec.tr.read64);
|
||||
store_gpr(b, ctxp, instr.rd(), v);
|
||||
cache_write_gpr(ec, instr.rd(), v);
|
||||
}
|
||||
|
||||
// ---- integer stores. Value is the low bits of gpr[rs]. ----
|
||||
PpcOpcode::stb => {
|
||||
let ea = ea_d(b, ec, instr.ra(), instr.d());
|
||||
let v = gpr32(b, ctxp, instr.rs());
|
||||
let v = gpr32(b, ec, instr.rs());
|
||||
call_store(b, ec, ec.tr.write8, ea, v);
|
||||
}
|
||||
PpcOpcode::stbx => {
|
||||
let ea = ea_x(b, ec, instr.ra(), instr.rb());
|
||||
let v = gpr32(b, ctxp, instr.rs());
|
||||
let v = gpr32(b, ec, instr.rs());
|
||||
call_store(b, ec, ec.tr.write8, ea, v);
|
||||
}
|
||||
PpcOpcode::sth => {
|
||||
let ea = ea_d(b, ec, instr.ra(), instr.d());
|
||||
let v = gpr32(b, ctxp, instr.rs());
|
||||
let v = gpr32(b, ec, instr.rs());
|
||||
call_store(b, ec, ec.tr.write16, ea, v);
|
||||
}
|
||||
PpcOpcode::sthx => {
|
||||
let ea = ea_x(b, ec, instr.ra(), instr.rb());
|
||||
let v = gpr32(b, ctxp, instr.rs());
|
||||
let v = gpr32(b, ec, instr.rs());
|
||||
call_store(b, ec, ec.tr.write16, ea, v);
|
||||
}
|
||||
PpcOpcode::stw => {
|
||||
let ea = ea_d(b, ec, instr.ra(), instr.d());
|
||||
let v = gpr32(b, ctxp, instr.rs());
|
||||
let v = gpr32(b, ec, instr.rs());
|
||||
call_store(b, ec, ec.tr.write32, ea, v);
|
||||
}
|
||||
PpcOpcode::stwx => {
|
||||
let ea = ea_x(b, ec, instr.ra(), instr.rb());
|
||||
let v = gpr32(b, ctxp, instr.rs());
|
||||
let v = gpr32(b, ec, instr.rs());
|
||||
call_store(b, ec, ec.tr.write32, ea, v);
|
||||
}
|
||||
PpcOpcode::std => {
|
||||
let ea = ea_d(b, ec, instr.ra(), instr.ds());
|
||||
let v = load_gpr(b, ctxp, instr.rs());
|
||||
let v = cache_read_gpr(b, ec, instr.rs());
|
||||
call_store(b, ec, ec.tr.write64, ea, v);
|
||||
}
|
||||
PpcOpcode::stdx => {
|
||||
let ea = ea_x(b, ec, instr.ra(), instr.rb());
|
||||
let v = load_gpr(b, ctxp, instr.rs());
|
||||
let v = cache_read_gpr(b, ec, instr.rs());
|
||||
call_store(b, ec, ec.tr.write64, ea, v);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user