[iterate-4A] jit: Cranelift block-JIT scaffolding + addi/addis PoC (pipeline proven)
Stage-2 foundation. Adds cranelift-jit/frontend/module/codegen 0.128.4 (the 1.90-compatible line; 0.133 needs Rust 1.94) and a new crates/xenia-cpu/src/jit.rs. Jit owns a JITModule (and thus all compiled code memory). compile(&DecodedBlock) lowers a block to native code ONLY if every opcode is covered() — otherwise returns None and the caller interprets the whole block (coverage grows opcode-by-opcode). ABI: extern "C" fn(*mut PpcContext, *const MemEnv) -> u32 (StepResult discriminant); guest registers are loaded/stored directly from the #[repr(C)] PpcContext at offset_of! offsets. A covered block is straight-line and always runs to completion, so pc advances to end_pc and cycle_count/timebase bump by the instruction count once — matching the interpreter's per-instruction bump. Covered set so far: addi, addis. Unit test jit_matches_interpreter_addi_block compiles a 32x addi block and asserts the JIT's r3/pc/cycle_count/timebase match the interpreter exactly — proves module setup, offset_of register access, IR emission, the extern "C" calling convention, and cycle/pc accounting end-to-end. Not yet wired into run_superblock (needs a compiled-block cache + routing); every future opcode will be validated against the interpreter via the M0 XENIA_JIT_DIFF harness before it counts as covered. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
241
Cargo.lock
generated
241
Cargo.lock
generated
@@ -87,6 +87,12 @@ dependencies = [
|
||||
"equator",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "allocator-api2"
|
||||
version = "0.2.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
|
||||
|
||||
[[package]]
|
||||
name = "android-activity"
|
||||
version = "0.6.1"
|
||||
@@ -521,6 +527,9 @@ name = "bumpalo"
|
||||
version = "3.20.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
|
||||
dependencies = [
|
||||
"allocator-api2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bytecheck"
|
||||
@@ -874,6 +883,167 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cranelift-assembler-x64"
|
||||
version = "0.128.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "50a04121a197fde2fe896f8e7cac9812fc41ed6ee9c63e1906090f9f497845f6"
|
||||
dependencies = [
|
||||
"cranelift-assembler-x64-meta",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cranelift-assembler-x64-meta"
|
||||
version = "0.128.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a09e699a94f477303820fb2167024f091543d6240783a2d3b01a3f21c42bc744"
|
||||
dependencies = [
|
||||
"cranelift-srcgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cranelift-bforest"
|
||||
version = "0.128.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f07732c662a9755529e332d86f8c5842171f6e98ba4d5976a178043dad838654"
|
||||
dependencies = [
|
||||
"cranelift-entity",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cranelift-bitset"
|
||||
version = "0.128.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "18391da761cf362a06def7a7cf11474d79e55801dd34c2e9ba105b33dc0aef88"
|
||||
|
||||
[[package]]
|
||||
name = "cranelift-codegen"
|
||||
version = "0.128.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b3a09b3042c69810d255aef59ddc3b3e4c0644d1d90ecfd6e3837798cc88a3c"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"cranelift-assembler-x64",
|
||||
"cranelift-bforest",
|
||||
"cranelift-bitset",
|
||||
"cranelift-codegen-meta",
|
||||
"cranelift-codegen-shared",
|
||||
"cranelift-control",
|
||||
"cranelift-entity",
|
||||
"cranelift-isle",
|
||||
"gimli",
|
||||
"hashbrown 0.15.5",
|
||||
"log",
|
||||
"regalloc2",
|
||||
"rustc-hash 2.1.2",
|
||||
"serde",
|
||||
"smallvec",
|
||||
"target-lexicon",
|
||||
"wasmtime-internal-math",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cranelift-codegen-meta"
|
||||
version = "0.128.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75817926ec812241889208d1b190cadb7fedded4592a4bb01b8524babb9e4849"
|
||||
dependencies = [
|
||||
"cranelift-assembler-x64-meta",
|
||||
"cranelift-codegen-shared",
|
||||
"cranelift-srcgen",
|
||||
"heck",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cranelift-codegen-shared"
|
||||
version = "0.128.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "859158f87a59476476eda3884d883c32e08a143cf3d315095533b362a3250a63"
|
||||
|
||||
[[package]]
|
||||
name = "cranelift-control"
|
||||
version = "0.128.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "03b65a9aec442d715cbf54d14548b8f395476c09cef7abe03e104a378291ab88"
|
||||
dependencies = [
|
||||
"arbitrary",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cranelift-entity"
|
||||
version = "0.128.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8334c99a7e86060c24028732efd23bac84585770dcb752329c69f135d64f2fc1"
|
||||
dependencies = [
|
||||
"cranelift-bitset",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cranelift-frontend"
|
||||
version = "0.128.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43ac6c095aa5b3e845d7ca3461e67e2b65249eb5401477a5ff9100369b745111"
|
||||
dependencies = [
|
||||
"cranelift-codegen",
|
||||
"log",
|
||||
"smallvec",
|
||||
"target-lexicon",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cranelift-isle"
|
||||
version = "0.128.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69d3d992870ed4f0f2e82e2175275cb3a123a46e9660c6558c46417b822c91fa"
|
||||
|
||||
[[package]]
|
||||
name = "cranelift-jit"
|
||||
version = "0.128.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "807781e9097feba24c31fc8d85a217c2fbfc6c8b72a87da8717c056dc8b24a87"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"cranelift-codegen",
|
||||
"cranelift-control",
|
||||
"cranelift-entity",
|
||||
"cranelift-module",
|
||||
"cranelift-native",
|
||||
"libc",
|
||||
"log",
|
||||
"region",
|
||||
"target-lexicon",
|
||||
"wasmtime-internal-jit-icache-coherence",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cranelift-module"
|
||||
version = "0.128.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6a588624367124596cb07c324fa865a4894200aede7fe933e817816188ace52"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"cranelift-codegen",
|
||||
"cranelift-control",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cranelift-native"
|
||||
version = "0.128.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee32e36beaf80f309edb535274cfe0349e1c5cf5799ba2d9f42e828285c6b52e"
|
||||
dependencies = [
|
||||
"cranelift-codegen",
|
||||
"libc",
|
||||
"target-lexicon",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cranelift-srcgen"
|
||||
version = "0.128.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "903adeaf4938e60209a97b53a2e4326cd2d356aab9764a1934630204bae381c9"
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.5.0"
|
||||
@@ -1359,6 +1529,11 @@ name = "gimli"
|
||||
version = "0.32.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7"
|
||||
dependencies = [
|
||||
"fallible-iterator",
|
||||
"indexmap",
|
||||
"stable_deref_trait",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gl_generator"
|
||||
@@ -2127,6 +2302,15 @@ version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
|
||||
|
||||
[[package]]
|
||||
name = "mach2"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "malloc_buf"
|
||||
version = "0.0.6"
|
||||
@@ -3183,6 +3367,20 @@ dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regalloc2"
|
||||
version = "0.13.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08effbc1fa53aaebff69521a5c05640523fab037b34a4a2c109506bc938246fa"
|
||||
dependencies = [
|
||||
"allocator-api2",
|
||||
"bumpalo",
|
||||
"hashbrown 0.15.5",
|
||||
"log",
|
||||
"rustc-hash 2.1.2",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.12.3"
|
||||
@@ -3212,6 +3410,18 @@ version = "0.8.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
|
||||
|
||||
[[package]]
|
||||
name = "region"
|
||||
version = "3.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6b6ebd13bc009aef9cd476c1310d49ac354d36e240cf1bd753290f3dc7199a7"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"libc",
|
||||
"mach2",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rend"
|
||||
version = "0.4.2"
|
||||
@@ -3808,6 +4018,12 @@ dependencies = [
|
||||
"xattr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "target-lexicon"
|
||||
version = "0.13.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca"
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.27.0"
|
||||
@@ -4378,6 +4594,27 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasmtime-internal-jit-icache-coherence"
|
||||
version = "41.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4b96df23179ae16d54fb3a420f84ffe4383ec9dd06fad3e5bc782f85f66e8e08"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasmtime-internal-math"
|
||||
version = "41.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "86d1380926682b44c383e9a67f47e7a95e60c6d3fa8c072294dab2c7de6168a0"
|
||||
dependencies = [
|
||||
"libm",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-backend"
|
||||
version = "0.3.15"
|
||||
@@ -5071,6 +5308,10 @@ name = "xenia-cpu"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"cranelift-codegen",
|
||||
"cranelift-frontend",
|
||||
"cranelift-jit",
|
||||
"cranelift-module",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror 2.0.18",
|
||||
|
||||
@@ -10,6 +10,10 @@ xenia-memory = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
bitflags = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
cranelift-jit = "0.128.4"
|
||||
cranelift-frontend = "0.128.4"
|
||||
cranelift-module = "0.128.4"
|
||||
cranelift-codegen = "0.128.4"
|
||||
|
||||
[dev-dependencies]
|
||||
serde = { workspace = true }
|
||||
|
||||
255
crates/xenia-cpu/src/jit.rs
Normal file
255
crates/xenia-cpu/src/jit.rs
Normal file
@@ -0,0 +1,255 @@
|
||||
//! Cranelift machine-code block JIT (Stage-2).
|
||||
//!
|
||||
//! Compiles a fully-covered [`DecodedBlock`] to a native function and runs it in
|
||||
//! place of the interpreter. A block is compiled **only if every opcode in it is
|
||||
//! covered** (see [`covered`]); otherwise the caller interprets the whole block.
|
||||
//! Coverage grows opcode-by-opcode, each one validated against the interpreter by
|
||||
//! the recompiler's differential harness (`XENIA_JIT_DIFF`).
|
||||
//!
|
||||
//! ## ABI
|
||||
//! Compiled block: `extern "C" fn(ctx: *mut PpcContext, mem: *const MemEnv) -> u32`.
|
||||
//! The return is a [`StepResult`] discriminant ([`RET_CONTINUE`] etc.). Guest
|
||||
//! registers are loaded/stored directly from the `#[repr(C)]` `PpcContext` at
|
||||
//! `offset_of!` offsets; memory goes through trampolines that call the live
|
||||
//! `MemoryAccess` (preserving MMIO / mem-watch / page-version) — added as
|
||||
//! load/store opcodes are covered.
|
||||
//!
|
||||
//! ## Determinism
|
||||
//! A covered block is straight-line and always runs to completion (covered ops
|
||||
//! never fault or yield), so `cycle_count`/`timebase` advance by exactly the
|
||||
//! block's instruction count — matching the interpreter's per-instruction bump.
|
||||
|
||||
use core::mem::offset_of;
|
||||
|
||||
use cranelift_codegen::ir::{types, AbiParam, InstBuilder, MemFlags, Value};
|
||||
use cranelift_frontend::{FunctionBuilder, FunctionBuilderContext};
|
||||
use cranelift_jit::{JITBuilder, JITModule};
|
||||
use cranelift_module::{default_libcall_names, Module};
|
||||
|
||||
use crate::block_cache::DecodedBlock;
|
||||
use crate::context::PpcContext;
|
||||
use crate::decoder::DecodedInstr;
|
||||
use crate::opcode::PpcOpcode;
|
||||
use xenia_memory::MemoryAccess;
|
||||
|
||||
/// `StepResult` discriminants returned by compiled blocks. Kept in sync with
|
||||
/// [`crate::interpreter::StepResult`] (only `Continue` is produced by covered
|
||||
/// blocks today; the rest exist for when branch/sc/trap ops are covered).
|
||||
pub const RET_CONTINUE: u32 = 0;
|
||||
|
||||
/// Thin environment handed to memory trampolines: wraps the live `MemoryAccess`.
|
||||
#[repr(C)]
|
||||
pub struct MemEnv<'a> {
|
||||
pub mem: &'a dyn MemoryAccess,
|
||||
}
|
||||
|
||||
/// A compiled block: a native entry point valid for as long as the owning
|
||||
/// [`Jit`]'s module lives (the module owns the executable memory).
|
||||
pub type CompiledFn = extern "C" fn(*mut PpcContext, *const MemEnv) -> u32;
|
||||
|
||||
/// Is this opcode currently lowerable to native code? Must mirror [`emit_op`].
|
||||
/// A block is JIT-compiled only if `covered` is true for all its instructions.
|
||||
pub fn covered(instr: &DecodedInstr) -> bool {
|
||||
matches!(instr.opcode, PpcOpcode::addi | PpcOpcode::addis)
|
||||
}
|
||||
|
||||
/// True if the whole block can be compiled (all opcodes covered).
|
||||
pub fn block_covered(block: &DecodedBlock) -> bool {
|
||||
block.instrs.iter().all(covered)
|
||||
}
|
||||
|
||||
/// The Cranelift JIT. Owns the module (and thus all compiled code memory), so it
|
||||
/// must outlive every [`CompiledFn`] it produces.
|
||||
pub struct Jit {
|
||||
module: JITModule,
|
||||
ctx: cranelift_codegen::Context,
|
||||
fbctx: FunctionBuilderContext,
|
||||
}
|
||||
|
||||
impl Jit {
|
||||
pub fn new() -> Result<Self, String> {
|
||||
// Native host ISA + default flags. `JITBuilder::new` resolves the host
|
||||
// target via cranelift-native (a transitive dep of cranelift-jit).
|
||||
let builder = JITBuilder::new(default_libcall_names()).map_err(|e| e.to_string())?;
|
||||
let module = JITModule::new(builder);
|
||||
let ctx = module.make_context();
|
||||
Ok(Self { module, ctx, fbctx: FunctionBuilderContext::new() })
|
||||
}
|
||||
|
||||
/// Compile `block` to native code, or return `None` if any opcode is
|
||||
/// uncovered (caller interprets the block).
|
||||
pub fn compile(&mut self, block: &DecodedBlock) -> Option<CompiledFn> {
|
||||
if !block_covered(block) {
|
||||
return None;
|
||||
}
|
||||
let ptr = self.module.target_config().pointer_type();
|
||||
self.module.clear_context(&mut self.ctx);
|
||||
self.ctx.func.signature.params.push(AbiParam::new(ptr)); // ctx
|
||||
self.ctx.func.signature.params.push(AbiParam::new(ptr)); // mem env
|
||||
self.ctx.func.signature.returns.push(AbiParam::new(types::I32));
|
||||
|
||||
{
|
||||
let mut b = FunctionBuilder::new(&mut self.ctx.func, &mut self.fbctx);
|
||||
let entry = b.create_block();
|
||||
b.append_block_params_for_function_params(entry);
|
||||
b.switch_to_block(entry);
|
||||
b.seal_block(entry);
|
||||
let ctxp = b.block_params(entry)[0];
|
||||
let _memenv = b.block_params(entry)[1];
|
||||
|
||||
for instr in &block.instrs {
|
||||
emit_op(&mut b, ctxp, instr);
|
||||
}
|
||||
|
||||
// Straight-line covered block: PC advances linearly to end_pc.
|
||||
let end_pc = b.ins().iconst(types::I32, block.end_pc as i64);
|
||||
b.ins().store(MemFlags::trusted(), end_pc, ctxp, off(offset_of!(PpcContext, pc)));
|
||||
|
||||
// cycle_count += N ; timebase += N (one per executed instruction).
|
||||
let n = block.instrs.len() as i64;
|
||||
bump_u64(&mut b, ctxp, offset_of!(PpcContext, cycle_count), n);
|
||||
bump_u64(&mut b, ctxp, offset_of!(PpcContext, timebase), n);
|
||||
|
||||
let ret = b.ins().iconst(types::I32, RET_CONTINUE as i64);
|
||||
b.ins().return_(&[ret]);
|
||||
b.finalize();
|
||||
}
|
||||
|
||||
let id = self
|
||||
.module
|
||||
.declare_anonymous_function(&self.ctx.func.signature)
|
||||
.ok()?;
|
||||
self.module.define_function(id, &mut self.ctx).ok()?;
|
||||
self.module.clear_context(&mut self.ctx);
|
||||
self.module.finalize_definitions().ok()?;
|
||||
let code = self.module.get_finalized_function(id);
|
||||
// Safety: the module owns this code for its lifetime; the signature
|
||||
// matches CompiledFn (SystemV/extern "C" on the host).
|
||||
Some(unsafe { std::mem::transmute::<*const u8, CompiledFn>(code) })
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn off(o: usize) -> i32 {
|
||||
o as i32
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn gpr_off(r: usize) -> i32 {
|
||||
(offset_of!(PpcContext, gpr) + r * 8) as i32
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn load_gpr(b: &mut FunctionBuilder, ctxp: Value, r: usize) -> Value {
|
||||
b.ins().load(types::I64, MemFlags::trusted(), ctxp, gpr_off(r))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn store_gpr(b: &mut FunctionBuilder, ctxp: Value, r: usize, v: Value) {
|
||||
b.ins().store(MemFlags::trusted(), v, ctxp, gpr_off(r));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn bump_u64(b: &mut FunctionBuilder, ctxp: Value, offset: usize, by: i64) {
|
||||
let cur = b.ins().load(types::I64, MemFlags::trusted(), ctxp, off(offset));
|
||||
let next = b.ins().iadd_imm(cur, by);
|
||||
b.ins().store(MemFlags::trusted(), next, ctxp, off(offset));
|
||||
}
|
||||
|
||||
/// Emit IR for one covered instruction. Must mirror [`covered`] and the exact
|
||||
/// semantics of the interpreter's `execute()` (validated by the diff harness).
|
||||
fn emit_op(b: &mut FunctionBuilder, ctxp: Value, instr: &DecodedInstr) {
|
||||
match instr.opcode {
|
||||
// rd = (ra==0 ? 0 : gpr[ra]) + EXTS(simm) [64-bit]
|
||||
PpcOpcode::addi => {
|
||||
let ra = instr.ra();
|
||||
let rav = if ra == 0 {
|
||||
b.ins().iconst(types::I64, 0)
|
||||
} else {
|
||||
load_gpr(b, ctxp, ra)
|
||||
};
|
||||
let res = b.ins().iadd_imm(rav, instr.simm16() as i64);
|
||||
store_gpr(b, ctxp, instr.rd(), res);
|
||||
}
|
||||
// rd = (ra==0 ? 0 : gpr[ra]) + (EXTS(simm) << 16) [64-bit]
|
||||
PpcOpcode::addis => {
|
||||
let ra = instr.ra();
|
||||
let rav = if ra == 0 {
|
||||
b.ins().iconst(types::I64, 0)
|
||||
} else {
|
||||
load_gpr(b, ctxp, ra)
|
||||
};
|
||||
let res = b.ins().iadd_imm(rav, (instr.simm16() as i64) << 16);
|
||||
store_gpr(b, ctxp, instr.rd(), res);
|
||||
}
|
||||
// covered() gates this — unreachable for uncovered opcodes.
|
||||
_ => unreachable!("emit_op called on uncovered opcode {:?}", instr.opcode),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::block_cache::BlockCache;
|
||||
|
||||
/// Mock memory that returns the same instruction word at every address.
|
||||
struct WordMem(u32);
|
||||
impl MemoryAccess for WordMem {
|
||||
fn read_u8(&self, _a: u32) -> u8 {
|
||||
0
|
||||
}
|
||||
fn read_u16(&self, _a: u32) -> u16 {
|
||||
0
|
||||
}
|
||||
fn read_u32(&self, _a: u32) -> u32 {
|
||||
self.0
|
||||
}
|
||||
fn read_u64(&self, _a: u32) -> u64 {
|
||||
((self.0 as u64) << 32) | self.0 as u64
|
||||
}
|
||||
fn write_u8(&self, _a: u32, _v: u8) {}
|
||||
fn write_u16(&self, _a: u32, _v: u16) {}
|
||||
fn write_u32(&self, _a: u32, _v: u32) {}
|
||||
fn write_u64(&self, _a: u32, _v: u64) {}
|
||||
fn translate(&self, _a: u32) -> Option<*const u8> {
|
||||
None
|
||||
}
|
||||
fn translate_mut(&self, _a: u32) -> Option<*mut u8> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
// addi rD, rA, SIMM = (14<<26)|(rD<<21)|(rA<<16)|(SIMM & 0xFFFF)
|
||||
fn addi(rd: u32, ra: u32, simm: u16) -> u32 {
|
||||
(14 << 26) | (rd << 21) | (ra << 16) | simm as u32
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn jit_matches_interpreter_addi_block() {
|
||||
// A block of `addi r3, r3, 1` — 32 instrs (hits MAX_BLOCK_INSTRS, no
|
||||
// terminator), fully covered.
|
||||
let mem = WordMem(addi(3, 3, 1));
|
||||
let mut bc = BlockCache::new();
|
||||
let block = bc.lookup_or_build(0x1000, &mem).clone();
|
||||
assert!(block_covered(&block), "addi block should be covered");
|
||||
|
||||
// Interpreter reference.
|
||||
let mut ref_ctx = PpcContext::new();
|
||||
ref_ctx.pc = 0x1000;
|
||||
let _ = crate::interpreter::step_block(&mut ref_ctx, &mem, &block);
|
||||
|
||||
// JIT candidate.
|
||||
let mut jit = Jit::new().expect("jit init");
|
||||
let f = jit.compile(&block).expect("compile");
|
||||
let mut jit_ctx = PpcContext::new();
|
||||
jit_ctx.pc = 0x1000;
|
||||
let env = MemEnv { mem: &mem };
|
||||
let r = f(&mut jit_ctx as *mut _, &env as *const _);
|
||||
|
||||
assert_eq!(r, RET_CONTINUE);
|
||||
assert_eq!(jit_ctx.gpr[3], ref_ctx.gpr[3], "r3 mismatch");
|
||||
assert_eq!(jit_ctx.pc, ref_ctx.pc, "pc mismatch");
|
||||
assert_eq!(jit_ctx.cycle_count, ref_ctx.cycle_count, "cycle mismatch");
|
||||
assert_eq!(jit_ctx.timebase, ref_ctx.timebase, "timebase mismatch");
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ pub mod dispatch_rec;
|
||||
pub mod disasm;
|
||||
pub mod fpscr;
|
||||
pub mod interpreter;
|
||||
pub mod jit;
|
||||
pub mod opcode;
|
||||
pub mod overflow;
|
||||
pub mod phaser;
|
||||
|
||||
Reference in New Issue
Block a user