use super::cfg::{assemble_dispatch_loop, Blocks};
use super::encode::*;
use crate::vm::instruction::{CompiledProgram, Constant, Op};
fn is_supported(op: &Op) -> bool {
matches!(
op,
Op::LoadConst { .. }
| Op::Move { .. }
| Op::Add { .. }
| Op::Sub { .. }
| Op::Mul { .. }
| Op::Div { .. }
| Op::Mod { .. }
| Op::BitXor { .. }
| Op::BitAnd { .. }
| Op::BitOr { .. }
| Op::Shl { .. }
| Op::Shr { .. }
| Op::Lt { .. }
| Op::Gt { .. }
| Op::LtEq { .. }
| Op::GtEq { .. }
| Op::Eq { .. }
| Op::NotEq { .. }
| Op::Jump { .. }
| Op::JumpIfFalse { .. }
| Op::JumpIfTrue { .. }
| Op::Return { .. }
| Op::ReturnNothing
)
}
pub fn compile_region_to_wasm(
ops: &[Op],
constants: &[Constant],
num_params: u32,
num_regs: u32,
) -> Option<Vec<u8>> {
let n = ops.len();
if n == 0 {
return None;
}
let mut has_return = false;
for op in ops {
if !is_supported(op) {
return None;
}
if matches!(op, Op::Return { .. }) {
has_return = true;
}
}
if !has_return {
return None;
}
let blocks = Blocks::new(ops)?;
let num_blocks = blocks.num_blocks();
let pc_local = num_regs;
let mut blocks_code: Vec<Vec<u8>> = Vec::with_capacity(num_blocks);
for k in 0..num_blocks {
let start = blocks.start(k);
let end = blocks.end(k);
let mut code = Vec::new();
let mut terminated = false;
for pc in start..end {
match ops[pc] {
Op::LoadConst { dst, idx } => {
let v = match constants.get(idx as usize)? {
Constant::Int(x) => *x,
_ => return None,
};
code.push(0x42); leb_i64(&mut code, v);
local_set(&mut code, dst as u32);
}
Op::Move { dst, src } => {
local_get(&mut code, src as u32);
local_set(&mut code, dst as u32);
}
Op::Add { dst, lhs, rhs } => emit_checked_addsub(&mut code, false, dst, lhs, rhs),
Op::Sub { dst, lhs, rhs } => emit_checked_addsub(&mut code, true, dst, lhs, rhs),
Op::Mul { dst, lhs, rhs } => emit_checked_mul(&mut code, dst, lhs, rhs),
Op::Div { dst, lhs, rhs } => arith(&mut code, 0x7F, dst, lhs, rhs), Op::Mod { dst, lhs, rhs } => arith(&mut code, 0x81, dst, lhs, rhs), Op::BitXor { dst, lhs, rhs } => arith(&mut code, 0x85, dst, lhs, rhs), Op::BitAnd { dst, lhs, rhs } => arith(&mut code, 0x83, dst, lhs, rhs), Op::BitOr { dst, lhs, rhs } => arith(&mut code, 0x84, dst, lhs, rhs), Op::Shl { dst, lhs, rhs } => arith(&mut code, 0x86, dst, lhs, rhs), Op::Shr { dst, lhs, rhs } => arith(&mut code, 0x87, dst, lhs, rhs), Op::Lt { dst, lhs, rhs } => compare(&mut code, 0x53, dst, lhs, rhs), Op::Gt { dst, lhs, rhs } => compare(&mut code, 0x55, dst, lhs, rhs), Op::LtEq { dst, lhs, rhs } => compare(&mut code, 0x57, dst, lhs, rhs), Op::GtEq { dst, lhs, rhs } => compare(&mut code, 0x59, dst, lhs, rhs), Op::Eq { dst, lhs, rhs } => compare(&mut code, 0x51, dst, lhs, rhs), Op::NotEq { dst, lhs, rhs } => compare(&mut code, 0x52, dst, lhs, rhs), Op::Jump { target } => {
code.push(0x41); leb_u32(&mut code, blocks.block_of(target) as u32);
local_set(&mut code, pc_local);
code.push(0x0C); leb_u32(&mut code, blocks.br_loop(k));
terminated = true;
break;
}
Op::JumpIfFalse { cond, target } => {
emit_cond_jump(&mut code, cond, true, blocks.block_of(target), blocks.block_of(pc + 1), pc_local, blocks.br_loop(k));
terminated = true;
break;
}
Op::JumpIfTrue { cond, target } => {
emit_cond_jump(&mut code, cond, false, blocks.block_of(target), blocks.block_of(pc + 1), pc_local, blocks.br_loop(k));
terminated = true;
break;
}
Op::Return { src } => {
local_get(&mut code, src as u32);
code.push(0x0F); terminated = true;
break;
}
Op::ReturnNothing => {
code.push(0x00); terminated = true;
break;
}
_ => return None,
}
}
if !terminated {
let next = blocks.block_of(end);
code.push(0x41);
leb_u32(&mut code, next as u32);
local_set(&mut code, pc_local);
code.push(0x0C);
leb_u32(&mut code, blocks.br_loop(k));
}
blocks_code.push(code);
}
let mut body = assemble_dispatch_loop(pc_local, &blocks_code);
body.push(0x00); body.push(0x0B);
let mut module = vec![0x00, 0x61, 0x73, 0x6D, 0x01, 0x00, 0x00, 0x00];
let mut ty = Vec::new();
leb_u32(&mut ty, 1);
ty.push(0x60);
leb_u32(&mut ty, num_params);
for _ in 0..num_params {
ty.push(I64);
}
leb_u32(&mut ty, 1);
ty.push(I64);
section(&mut module, 1, &ty);
let mut func = Vec::new();
leb_u32(&mut func, 1);
leb_u32(&mut func, 0);
section(&mut module, 3, &func);
let mut export = Vec::new();
leb_u32(&mut export, 1);
leb_u32(&mut export, 1);
export.push(b'f');
export.push(0x00);
leb_u32(&mut export, 0);
section(&mut module, 7, &export);
let num_i64_locals = num_regs.saturating_sub(num_params);
let mut entry = Vec::new();
leb_u32(&mut entry, if num_i64_locals > 0 { 2 } else { 1 }); if num_i64_locals > 0 {
leb_u32(&mut entry, num_i64_locals);
entry.push(I64);
}
leb_u32(&mut entry, 1);
entry.push(I32);
entry.extend_from_slice(&body);
let mut code_sec = Vec::new();
leb_u32(&mut code_sec, 1);
leb_u32(&mut code_sec, entry.len() as u32);
code_sec.extend_from_slice(&entry);
section(&mut module, 10, &code_sec);
Some(module)
}
pub fn compile_function_to_wasm(program: &CompiledProgram, fi: usize) -> Option<Vec<u8>> {
let f = program.functions.get(fi)?;
if f.ret_kind != Some(crate::vm::native_tier::SlotKind::Int) {
return None;
}
let entry = f.entry_pc;
let end = program
.functions
.iter()
.map(|g| g.entry_pc)
.filter(|&e| e > entry)
.min()
.unwrap_or(program.code.len());
if entry >= end || end > program.code.len() {
return None;
}
let rebase = |t: usize| -> Option<usize> {
if t >= entry && t < end {
Some(t - entry)
} else {
None }
};
let mut region: Vec<Op> = Vec::with_capacity(end - entry);
for &op in &program.code[entry..end] {
region.push(match op {
Op::Jump { target } => Op::Jump { target: rebase(target)? },
Op::JumpIfFalse { cond, target } => Op::JumpIfFalse { cond, target: rebase(target)? },
Op::JumpIfTrue { cond, target } => Op::JumpIfTrue { cond, target: rebase(target)? },
other => other,
});
}
compile_region_to_wasm(
®ion,
&program.constants,
f.param_count as u32,
f.register_count as u32,
)
}
#[cfg(all(test, feature = "wasm-jit", not(target_arch = "wasm32")))]
mod tests {
use super::*;
fn run(module: &[u8], arg: i64) -> i64 {
let engine = wasmi::Engine::default();
let m = wasmi::Module::new(&engine, module).expect("emitted bytes are valid wasm");
let mut store = wasmi::Store::new(&engine, ());
let instance = wasmi::Linker::<()>::new(&engine)
.instantiate(&mut store, &m)
.unwrap()
.start(&mut store)
.unwrap();
let f = instance.get_typed_func::<i64, i64>(&store, "f").unwrap();
f.call(&mut store, arg).unwrap()
}
#[test]
fn wasm_jit_emits_and_runs_straight_line() {
let ops = vec![
Op::Mul { dst: 1, lhs: 0, rhs: 0 },
Op::Add { dst: 2, lhs: 1, rhs: 0 },
Op::Return { src: 2 },
];
let module = compile_region_to_wasm(&ops, &[], 1, 3).expect("emits");
assert_eq!(run(&module, 5), 30);
assert_eq!(run(&module, 7), 56);
}
#[test]
fn wasm_jit_emits_and_runs_loop() {
let ops = vec![
Op::LoadConst { dst: 1, idx: 0 }, Op::LoadConst { dst: 2, idx: 1 }, Op::LoadConst { dst: 3, idx: 0 }, Op::Gt { dst: 4, lhs: 0, rhs: 3 }, Op::JumpIfFalse { cond: 4, target: 8 }, Op::Add { dst: 1, lhs: 1, rhs: 0 }, Op::Sub { dst: 0, lhs: 0, rhs: 2 }, Op::Jump { target: 3 }, Op::Return { src: 1 }, ];
let consts = vec![Constant::Int(0), Constant::Int(1)];
let module = compile_region_to_wasm(&ops, &consts, 1, 5).expect("loop region emits");
assert_eq!(run(&module, 5), 15); assert_eq!(run(&module, 10), 55);
assert_eq!(run(&module, 1), 1);
assert_eq!(run(&module, 0), 0);
assert_eq!(run(&module, 100), 5050);
}
#[test]
fn wasm_jit_emits_and_runs_branch() {
let ops = vec![
Op::LoadConst { dst: 1, idx: 0 }, Op::Lt { dst: 2, lhs: 0, rhs: 1 }, Op::JumpIfFalse { cond: 2, target: 6 }, Op::LoadConst { dst: 3, idx: 1 }, Op::Mul { dst: 4, lhs: 0, rhs: 3 }, Op::Return { src: 4 },
Op::LoadConst { dst: 5, idx: 2 }, Op::Add { dst: 4, lhs: 0, rhs: 5 }, Op::Return { src: 4 },
];
let consts = vec![Constant::Int(10), Constant::Int(2), Constant::Int(100)];
let module = compile_region_to_wasm(&ops, &consts, 1, 6).expect("branch region emits");
assert_eq!(run(&module, 3), 6);
assert_eq!(run(&module, 9), 18);
assert_eq!(run(&module, 10), 110);
assert_eq!(run(&module, 50), 150);
}
#[test]
fn wasm_jit_rejects_concurrency_op() {
let ops = vec![Op::ChanRecv { dst: 1, chan: 0 }, Op::Return { src: 1 }];
assert!(
compile_region_to_wasm(&ops, &[], 1, 2).is_none(),
"concurrency ops are WASM-JIT-ineligible"
);
}
#[test]
fn wasm_jit_requires_a_return() {
let ops = vec![Op::Add { dst: 1, lhs: 0, rhs: 0 }];
assert!(compile_region_to_wasm(&ops, &[], 1, 2).is_none());
}
#[test]
fn wasm_jit_rejects_type_ambiguous_ops() {
for op in [
Op::Not { dst: 1, src: 0 },
] {
let ops = vec![op, Op::Return { src: 1 }];
assert!(
compile_region_to_wasm(&ops, &[], 1, 2).is_none(),
"type-overloaded op must be WASM-JIT-ineligible (deopt, not miscompile): {:?}",
ops[0]
);
}
}
}