//! Cranelift-based JIT: bytecode-to-native code generation.
//!
//! Translates JVM bytecode (bipush, iload_0..3, iadd, ireturn) to native code
//! using cranelift-jit. The compiled function uses helpers to read/write the
//! stack frame, preserving the existing ABI (memory*, frame*) -> i32.

use crate::class_file::{ClassFile, MethodInfo};
use crate::jit::JitError;
use crate::memory::{StackFrame, Value as JvmValue};
use cranelift::codegen::ir::{AbiParam, Signature};
use cranelift::codegen::isa::CallConv;
use cranelift::prelude::*;
use cranelift_frontend::{FunctionBuilder, FunctionBuilderContext};
use cranelift_jit::{JITBuilder, JITModule};
use cranelift_module::{Linkage, Module};

/// FFI helpers - called from JIT-compiled code to access the stack frame.
#[unsafe(no_mangle)]
pub extern "C" fn jvmrs_frame_get_local_int(frame_ptr: *mut std::ffi::c_void, index: u32) -> i32 {
    if frame_ptr.is_null() {
        return 0;
    }
    let frame = unsafe { &*(frame_ptr as *const StackFrame) };
    frame
        .locals
        .get(index as usize)
        .map(JvmValue::as_int)
        .unwrap_or(0)
}

#[unsafe(no_mangle)]
pub extern "C" fn jvmrs_frame_push_int(frame_ptr: *mut std::ffi::c_void, value: i32) {
    if frame_ptr.is_null() {
        return;
    }
    let frame = unsafe { &mut *(frame_ptr as *mut StackFrame) };
    let _ = frame.push(JvmValue::Int(value));
}

#[unsafe(no_mangle)]
pub extern "C" fn jvmrs_frame_pop_int(frame_ptr: *mut std::ffi::c_void) -> i32 {
    if frame_ptr.is_null() {
        return 0;
    }
    let frame = unsafe { &mut *(frame_ptr as *mut StackFrame) };
    frame.pop().map(|v| v.as_int()).unwrap_or(0)
}

#[unsafe(no_mangle)]
pub extern "C" fn jvmrs_frame_get_local_long(frame_ptr: *mut std::ffi::c_void, index: u32) -> i64 {
    if frame_ptr.is_null() {
        return 0;
    }
    let frame = unsafe { &*(frame_ptr as *const StackFrame) };
    frame.locals.get(index as usize).and_then(|v| match v {
        JvmValue::Long(l) => Some(*l),
        _ => None,
    }).unwrap_or(0)
}

#[unsafe(no_mangle)]
pub extern "C" fn jvmrs_frame_push_long(frame_ptr: *mut std::ffi::c_void, value: i64) {
    if frame_ptr.is_null() {
        return;
    }
    let frame = unsafe { &mut *(frame_ptr as *mut StackFrame) };
    let _ = frame.push(JvmValue::Long(value));
}

#[unsafe(no_mangle)]
pub extern "C" fn jvmrs_frame_get_local_float(frame_ptr: *mut std::ffi::c_void, index: u32) -> f32 {
    if frame_ptr.is_null() {
        return 0.0;
    }
    let frame = unsafe { &*(frame_ptr as *const StackFrame) };
    frame.locals.get(index as usize).and_then(|v| match v {
        JvmValue::Float(f) => Some(*f),
        _ => None,
    }).unwrap_or(0.0)
}

#[unsafe(no_mangle)]
pub extern "C" fn jvmrs_frame_push_float(frame_ptr: *mut std::ffi::c_void, value: f32) {
    if frame_ptr.is_null() {
        return;
    }
    let frame = unsafe { &mut *(frame_ptr as *mut StackFrame) };
    let _ = frame.push(JvmValue::Float(value));
}

#[unsafe(no_mangle)]
pub extern "C" fn jvmrs_frame_get_local_double(frame_ptr: *mut std::ffi::c_void, index: u32) -> f64 {
    if frame_ptr.is_null() {
        return 0.0;
    }
    let frame = unsafe { &*(frame_ptr as *const StackFrame) };
    frame.locals.get(index as usize).and_then(|v| match v {
        JvmValue::Double(d) => Some(*d),
        _ => None,
    }).unwrap_or(0.0)
}

#[unsafe(no_mangle)]
pub extern "C" fn jvmrs_frame_push_double(frame_ptr: *mut std::ffi::c_void, value: f64) {
    if frame_ptr.is_null() {
        return;
    }
    let frame = unsafe { &mut *(frame_ptr as *mut StackFrame) };
    let _ = frame.push(JvmValue::Double(value));
}

#[unsafe(no_mangle)]
pub extern "C" fn jvmrs_frame_get_local_ref(frame_ptr: *mut std::ffi::c_void, index: u32) -> u32 {
    if frame_ptr.is_null() {
        return 0;
    }
    let frame = unsafe { &*(frame_ptr as *const StackFrame) };
    frame.locals.get(index as usize).and_then(JvmValue::as_reference).unwrap_or(0)
}

#[unsafe(no_mangle)]
pub extern "C" fn jvmrs_frame_push_ref(frame_ptr: *mut std::ffi::c_void, value: u32) {
    if frame_ptr.is_null() {
        return;
    }
    let frame = unsafe { &mut *(frame_ptr as *mut StackFrame) };
    let _ = frame.push(JvmValue::Reference(value));
}

#[unsafe(no_mangle)]
pub extern "C" fn jvmrs_frame_inc_local_int(frame_ptr: *mut std::ffi::c_void, index: u32, delta: i32) {
    if frame_ptr.is_null() {
        return;
    }
    let frame = unsafe { &mut *(frame_ptr as *mut StackFrame) };
    if let Some(v) = frame.locals.get_mut(index as usize) {
        if let JvmValue::Int(i) = v {
            *i = i.wrapping_add(delta);
        }
    }
}

#[unsafe(no_mangle)]
pub extern "C" fn jvmrs_frame_set_local_int(frame_ptr: *mut std::ffi::c_void, index: u32, value: i32) {
    if frame_ptr.is_null() {
        return;
    }
    let frame = unsafe { &mut *(frame_ptr as *mut StackFrame) };
    if let Some(v) = frame.locals.get_mut(index as usize) {
        *v = JvmValue::Int(value);
    } else if index as usize == frame.locals.len() {
        let _ = frame.locals.push(JvmValue::Int(value));
    }
}

#[unsafe(no_mangle)]
pub extern "C" fn jvmrs_frame_set_local_long(frame_ptr: *mut std::ffi::c_void, index: u32, value: i64) {
    if frame_ptr.is_null() {
        return;
    }
    let frame = unsafe { &mut *(frame_ptr as *mut StackFrame) };
    if let Some(v) = frame.locals.get_mut(index as usize) {
        *v = JvmValue::Long(value);
    } else if index as usize == frame.locals.len() {
        let _ = frame.locals.push(JvmValue::Long(value));
    }
}

#[unsafe(no_mangle)]
pub extern "C" fn jvmrs_frame_set_local_float(frame_ptr: *mut std::ffi::c_void, index: u32, value: f32) {
    if frame_ptr.is_null() {
        return;
    }
    let frame = unsafe { &mut *(frame_ptr as *mut StackFrame) };
    if let Some(v) = frame.locals.get_mut(index as usize) {
        *v = JvmValue::Float(value);
    } else if index as usize == frame.locals.len() {
        let _ = frame.locals.push(JvmValue::Float(value));
    }
}

#[unsafe(no_mangle)]
pub extern "C" fn jvmrs_frame_set_local_double(frame_ptr: *mut std::ffi::c_void, index: u32, value: f64) {
    if frame_ptr.is_null() {
        return;
    }
    let frame = unsafe { &mut *(frame_ptr as *mut StackFrame) };
    if let Some(v) = frame.locals.get_mut(index as usize) {
        *v = JvmValue::Double(value);
    } else if index as usize == frame.locals.len() {
        let _ = frame.locals.push(JvmValue::Double(value));
    }
}

#[unsafe(no_mangle)]
pub extern "C" fn jvmrs_frame_set_local_ref(frame_ptr: *mut std::ffi::c_void, index: u32, value: u32) {
    if frame_ptr.is_null() {
        return;
    }
    let frame = unsafe { &mut *(frame_ptr as *mut StackFrame) };
    if let Some(v) = frame.locals.get_mut(index as usize) {
        *v = JvmValue::Reference(value);
    } else if index as usize == frame.locals.len() {
        let _ = frame.locals.push(JvmValue::Reference(value));
    }
}

/// Simple bytecode-level loop unrolling. Detects small, branch-free loops
/// with backward goto and duplicates the body (factor-2 unroll).
///
/// A loop is considered "simple" if:
/// - It terminates with a backward `goto` (0xa7)
/// - The body contains no branches (no if*, no goto inside body)
/// - The body is ≤ 8 instructions
/// - The last non-goto instruction is `iinc` (so we can double its delta)
///
/// Returns a new bytecode Vec if unrolling was applied.
/// Scan a bytecode snippet for branch opcodes, skipping multi-byte operands.
fn body_has_branch(body: &[u8]) -> bool {
    let mut pc = 0;
    while pc < body.len() {
        let op = body[pc];
        if matches!(op, 0x99..=0xa7) {
            return true;
        }
        pc += 1;
        // Skip operands for known multi-byte instructions
        match op {
            0x10 | 0x15 | 0x16 | 0x17 | 0x18 | 0x19
            | 0x36 | 0x37 | 0x38 | 0x39 | 0x3a | 0x84 => pc += 1,
            0x11 | 0x99..=0xa7 => pc += 2,
            _ => {}
        }
    }
    false
}

/// Simple bytecode-level loop unrolling. Detects small, branch-free loops
/// with backward goto and duplicates the body (factor-2 unroll).
///
/// A loop is considered "simple" if:
/// - It terminates with a backward `goto` (0xa7)
/// - The body contains no branches
/// - The body is ≤ 8 instructions
/// - The last instruction before goto is `iinc` (0x84)
///
/// Returns a new bytecode Vec if unrolling was applied.
fn unroll_loops(bytecode: &[u8]) -> Vec<u8> {
    const MAX_BODY_SIZE: usize = 8;
    let mut result = bytecode.to_vec();
    let mut changed = true;
    let mut iterations = 0;

    while changed && iterations < 1 {
        changed = false;
        iterations += 1;
        let mut i = 0;
        while i + 2 < result.len() {
            // Look for goto (0xa7) with a 2-byte offset
            if result[i] == 0xa7 {
                let offset = ((result[i + 1] as i16) << 8) | (result[i + 2] as i16);
                let target = (i as isize).wrapping_add(offset as isize);
                if target >= 0 && (target as usize) < i {
                    let target = target as usize;
                    let body_len = i - target;
                    if body_len > 0 && body_len <= MAX_BODY_SIZE {
                        // Extract body (from target to just before goto)
                        let body = &result[target..i];
                        // Check body is branch-free
                        if !body_has_branch(body) {
                            // Check last instruction before goto is iinc (0x84)
                            // iinc is 3 bytes: opcode + index + delta
                            if i >= target + 3 && result[i - 3] == 0x84 {
                                let idx = result[i - 2];
                                let delta = result[i - 1] as i8;
                                // Build unrolled body: body + body with doubled iinc delta
                                let mut new_body = body.to_vec();
                                new_body.extend_from_slice(body);
                                // Update the second iinc's delta
                                let delta_pos = new_body.len() - 1;
                                let doubled = delta.wrapping_mul(2);
                                new_body[delta_pos] = doubled as u8;
                                // Replace [target..i+3] with new_body + goto
                                let mut replacement = new_body;
                                // Recompute goto offset for new position
                                let goto_start = target + replacement.len();
                                let new_offset = (target as isize - goto_start as isize) as i16;
                                replacement.push(0xa7);
                                replacement.push((new_offset >> 8) as u8);
                                replacement.push((new_offset & 0xff) as u8);
                                result.splice(target..i + 3, replacement.iter().copied());
                                changed = true;
                                break; // restart scan after mutation
                            }
                        }
                    }
                }
            }
            i += 1;
        }
    }

    result
}

/// Cranelift JIT backend - compiles bytecode to native code.
pub struct CraneliftJitBackend {
    module: JITModule,
    builder_ctx: FunctionBuilderContext,
}

impl CraneliftJitBackend {
    /// Create a new JIT backend with FFI helpers registered.
    pub fn new() -> Result<Self, JitError> {
        let mut flag_builder = cranelift::codegen::settings::builder();
        flag_builder
            .set("use_colocated_libcalls", "false")
            .map_err(|e| JitError::CompilationFailed(e.to_string()))?;
        flag_builder
            .set("is_pic", "false")
            .map_err(|e| JitError::CompilationFailed(e.to_string()))?;

        let isa = cranelift_native::builder()
            .map_err(|e| JitError::CompilationFailed(format!("ISA: {}", e)))?
            .finish(cranelift::codegen::settings::Flags::new(flag_builder))
            .map_err(|e| JitError::CompilationFailed(e.to_string()))?;

        let mut builder = JITBuilder::with_isa(isa, cranelift_module::default_libcall_names());

        builder.symbol("jvmrs_frame_get_local_int", jvmrs_frame_get_local_int as *const u8);
        builder.symbol("jvmrs_frame_push_int", jvmrs_frame_push_int as *const u8);
        builder.symbol("jvmrs_frame_pop_int", jvmrs_frame_pop_int as *const u8);
        builder.symbol("jvmrs_frame_set_local_int", jvmrs_frame_set_local_int as *const u8);
        builder.symbol("jvmrs_frame_get_local_long", jvmrs_frame_get_local_long as *const u8);
        builder.symbol("jvmrs_frame_push_long", jvmrs_frame_push_long as *const u8);
        builder.symbol("jvmrs_frame_set_local_long", jvmrs_frame_set_local_long as *const u8);
        builder.symbol("jvmrs_frame_get_local_float", jvmrs_frame_get_local_float as *const u8);
        builder.symbol("jvmrs_frame_push_float", jvmrs_frame_push_float as *const u8);
        builder.symbol("jvmrs_frame_set_local_float", jvmrs_frame_set_local_float as *const u8);
        builder.symbol("jvmrs_frame_get_local_double", jvmrs_frame_get_local_double as *const u8);
        builder.symbol("jvmrs_frame_push_double", jvmrs_frame_push_double as *const u8);
        builder.symbol("jvmrs_frame_set_local_double", jvmrs_frame_set_local_double as *const u8);
        builder.symbol("jvmrs_frame_get_local_ref", jvmrs_frame_get_local_ref as *const u8);
        builder.symbol("jvmrs_frame_push_ref", jvmrs_frame_push_ref as *const u8);
        builder.symbol("jvmrs_frame_set_local_ref", jvmrs_frame_set_local_ref as *const u8);
        builder.symbol("jvmrs_frame_inc_local_int", jvmrs_frame_inc_local_int as *const u8);

        let module = JITModule::new(builder);

        Ok(Self {
            module,
            builder_ctx: FunctionBuilderContext::new(),
        })
    }

    /// Compile a method to native code. Returns the function pointer and code size.
    /// Supports ~60 opcodes: constants, loads, stores, stack ops, integer/float/double
    /// arithmetic, conversions, branches (ifeq/ifne/iflt/ifge/ifgt/ifle/if_icmp*), goto,
    /// returns, and iinc.
    pub fn compile(
        &mut self,
        _class: &ClassFile,
        method: &MethodInfo,
        func_name: &str,
    ) -> Result<(*const u8, usize), JitError> {
        let code_attr = method
            .attributes
            .iter()
            .find(|a| a.info.len() >= 8)
            .ok_or_else(|| JitError::InvalidMethod("No Code attribute".to_string()))?;

        let code_len = ((code_attr.info[4] as usize) << 24)
            | ((code_attr.info[5] as usize) << 16)
            | ((code_attr.info[6] as usize) << 8)
            | (code_attr.info[7] as usize);
        let raw_bytecode = code_attr.info.get(8..8 + code_len).unwrap_or(&[]);
        let bytecode = unroll_loops(raw_bytecode);

        // Signature: (memory: *mut Memory, frame: *mut StackFrame) -> i32
        let ptr_type = self.module.target_config().pointer_type();
        let mut sig = Signature::new(CallConv::SystemV);
        sig.params.push(AbiParam::new(ptr_type));
        sig.params.push(AbiParam::new(ptr_type));
        sig.returns.push(AbiParam::new(types::I32));

        // Helper registration macro to avoid repetition
        macro_rules! decl_helper {
            ($name:expr, ret $ret:expr, params [$($param:expr),*]) => {{
                let mut s = Signature::new(CallConv::SystemV);
                $( s.params.push(AbiParam::new($param)); )*
                s.returns.push(AbiParam::new($ret));
                self.module.declare_function($name, Linkage::Import, &s)
                    .map_err(|e| JitError::CompilationFailed(e.to_string()))?
            }};
            ($name:expr, void, params [$($param:expr),*]) => {{
                let mut s = Signature::new(CallConv::SystemV);
                $( s.params.push(AbiParam::new($param)); )*
                self.module.declare_function($name, Linkage::Import, &s)
                    .map_err(|e| JitError::CompilationFailed(e.to_string()))?
            }};
        }

        let get_int_fn    = decl_helper!("jvmrs_frame_get_local_int",    ret types::I32,  params [ptr_type, types::I32]);
        let push_int_fn   = decl_helper!("jvmrs_frame_push_int",       void,            params [ptr_type, types::I32]);
        let pop_int_fn    = decl_helper!("jvmrs_frame_pop_int",        ret types::I32,  params [ptr_type]);
        let set_int_fn    = decl_helper!("jvmrs_frame_set_local_int",  void,            params [ptr_type, types::I32, types::I32]);
        let get_long_fn   = decl_helper!("jvmrs_frame_get_local_long", ret types::I64,  params [ptr_type, types::I32]);
        let push_long_fn  = decl_helper!("jvmrs_frame_push_long",      void,            params [ptr_type, types::I64]);
        let set_long_fn   = decl_helper!("jvmrs_frame_set_local_long", void,            params [ptr_type, types::I32, types::I64]);
        let get_float_fn  = decl_helper!("jvmrs_frame_get_local_float", ret types::F32,  params [ptr_type, types::I32]);
        let push_float_fn = decl_helper!("jvmrs_frame_push_float",     void,            params [ptr_type, types::F32]);
        let set_float_fn  = decl_helper!("jvmrs_frame_set_local_float", void,            params [ptr_type, types::I32, types::F32]);
        let get_double_fn = decl_helper!("jvmrs_frame_get_local_double", ret types::F64, params [ptr_type, types::I32]);
        let push_double_fn= decl_helper!("jvmrs_frame_push_double",    void,            params [ptr_type, types::F64]);
        let set_double_fn = decl_helper!("jvmrs_frame_set_local_double", void,           params [ptr_type, types::I32, types::F64]);
        let get_ref_fn    = decl_helper!("jvmrs_frame_get_local_ref",  ret types::I32,  params [ptr_type, types::I32]);
        let push_ref_fn   = decl_helper!("jvmrs_frame_push_ref",       void,            params [ptr_type, types::I32]);
        let set_ref_fn    = decl_helper!("jvmrs_frame_set_local_ref",  void,            params [ptr_type, types::I32, types::I32]);
        let inc_int_fn    = decl_helper!("jvmrs_frame_inc_local_int",  void,            params [ptr_type, types::I32, types::I32]);

        let func_id = self
            .module
            .declare_function(func_name, Linkage::Local, &sig)
            .map_err(|e| JitError::CompilationFailed(e.to_string()))?;

        let mut ctx = self.module.make_context();
        ctx.func.signature = sig.clone();

        let get_int_ref    = self.module.declare_func_in_func(get_int_fn,    &mut ctx.func);
        let push_int_ref   = self.module.declare_func_in_func(push_int_fn,   &mut ctx.func);
        let pop_int_ref    = self.module.declare_func_in_func(pop_int_fn,    &mut ctx.func);
        let set_int_ref    = self.module.declare_func_in_func(set_int_fn,    &mut ctx.func);
        let get_long_ref   = self.module.declare_func_in_func(get_long_fn,   &mut ctx.func);
        let push_long_ref  = self.module.declare_func_in_func(push_long_fn,  &mut ctx.func);
        let set_long_ref   = self.module.declare_func_in_func(set_long_fn,   &mut ctx.func);
        let get_float_ref  = self.module.declare_func_in_func(get_float_fn,  &mut ctx.func);
        let push_float_ref = self.module.declare_func_in_func(push_float_fn, &mut ctx.func);
        let set_float_ref  = self.module.declare_func_in_func(set_float_fn,  &mut ctx.func);
        let get_double_ref = self.module.declare_func_in_func(get_double_fn, &mut ctx.func);
        let push_double_ref= self.module.declare_func_in_func(push_double_fn,&mut ctx.func);
        let set_double_ref = self.module.declare_func_in_func(set_double_fn, &mut ctx.func);
        let get_ref_ref    = self.module.declare_func_in_func(get_ref_fn,    &mut ctx.func);
        let push_ref_ref   = self.module.declare_func_in_func(push_ref_fn,   &mut ctx.func);
        let set_ref_ref    = self.module.declare_func_in_func(set_ref_fn,    &mut ctx.func);
        let inc_int_ref    = self.module.declare_func_in_func(inc_int_fn,    &mut ctx.func);

        let mut builder = FunctionBuilder::new(&mut ctx.func, &mut self.builder_ctx);

        let entry = builder.create_block();
        builder.append_block_params_for_function_params(entry);
        builder.switch_to_block(entry);
        builder.seal_block(entry);

        let params = builder.block_params(entry);
        let _memory_param = params[0];
        let frame_param = params[1];

        let i32_type = types::I32;
        let mut stack: Vec<cranelift::prelude::Value> = Vec::new();

        // Local variable register cache: when Some(v), the local is tracked as a
        // Cranelift virtual register and does not need frame access.
        let max_locals = ((code_attr.info[2] as usize) << 8) | (code_attr.info[3] as usize);
        let mut locals: Vec<Option<cranelift::prelude::Value>> = vec![None; max_locals.max(16)];

        let mut pc = 0usize;
        let mut compiled = false;

        // First pass: collect branch targets for block creation
        let mut branch_targets: std::collections::HashSet<usize> = std::collections::HashSet::new();
        let mut scan_pc = 0usize;
        while scan_pc < bytecode.len() {
            let op = bytecode[scan_pc];
            scan_pc += 1;
            match op {
                0x99..=0x9e => { // ifeq/ifne/iflt/ifge/ifgt/ifle
                    if scan_pc + 1 < bytecode.len() {
                        let offset = ((bytecode[scan_pc] as i16) << 8) | (bytecode[scan_pc + 1] as i16);
                        branch_targets.insert((scan_pc - 1).wrapping_add(offset as usize));
                        scan_pc += 2;
                    }
                }
                0x9f..=0xa4 => { // if_icmp*
                    if scan_pc + 1 < bytecode.len() {
                        let offset = ((bytecode[scan_pc] as i16) << 8) | (bytecode[scan_pc + 1] as i16);
                        branch_targets.insert((scan_pc - 1).wrapping_add(offset as usize));
                        scan_pc += 2;
                    }
                }
                0xa7 => { // goto
                    if scan_pc + 1 < bytecode.len() {
                        let offset = ((bytecode[scan_pc] as i16) << 8) | (bytecode[scan_pc + 1] as i16);
                        branch_targets.insert((scan_pc - 1).wrapping_add(offset as usize));
                        scan_pc += 2;
                    }
                }
                0x10 | 0x15 | 0x16 | 0x17 | 0x18 | 0x19 | 0x36 | 0x37 | 0x38 | 0x39 | 0x3a => scan_pc += 1,
                0x11 | 0x84 | 0x99..=0xa7 => scan_pc += 2,
                _ => {}
            }
        }
        branch_targets.insert(0);
        branch_targets.insert(bytecode.len());

        // Create Cranelift blocks for each branch target
        let mut blocks: std::collections::HashMap<usize, Block> = std::collections::HashMap::new();
        for &target in &branch_targets {
            let block = builder.create_block();
            blocks.insert(target, block);
        }

        // Start emission at the entry block (pc 0)
        let entry_block = *blocks.get(&0).unwrap_or(&entry);
        builder.switch_to_block(entry_block);

        // Type shorthands
        let i64_type = types::I64;
        let f32_type = types::F32;
        let f64_type = types::F64;

        while pc < bytecode.len() {
            // Switch to block for current PC if one exists
            if let Some(&block) = blocks.get(&pc) {
                if builder.current_block().unwrap() != block {
                    builder.ins().jump(block, &[]);
                    builder.switch_to_block(block);
                }
            }

            let opcode = bytecode[pc];
            pc += 1;

            match opcode {
                // ----- Constants -----
                0x00 => {} // nop
                0x01 => stack.push(builder.ins().iconst(i32_type, 0)), // aconst_null
                0x02 => stack.push(builder.ins().iconst(i32_type, -1)), // iconst_m1
                0x03 => stack.push(builder.ins().iconst(i32_type, 0)),
                0x04 => stack.push(builder.ins().iconst(i32_type, 1)),
                0x05 => stack.push(builder.ins().iconst(i32_type, 2)),
                0x06 => stack.push(builder.ins().iconst(i32_type, 3)),
                0x07 => stack.push(builder.ins().iconst(i32_type, 4)),
                0x08 => stack.push(builder.ins().iconst(i32_type, 5)),
                0x09 => stack.push(builder.ins().iconst(i64_type, 0)), // lconst_0
                0x0a => stack.push(builder.ins().iconst(i64_type, 1)), // lconst_1
                0x0b => stack.push(builder.ins().f32const(0.0)),       // fconst_0
                0x0c => stack.push(builder.ins().f32const(1.0)),       // fconst_1
                0x0d => stack.push(builder.ins().f32const(2.0)),       // fconst_2
                0x0e => stack.push(builder.ins().f64const(0.0)),       // dconst_0
                0x0f => stack.push(builder.ins().f64const(1.0)),       // dconst_1
                0x10 => { // bipush
                    if pc < bytecode.len() {
                        let v = bytecode[pc] as i8 as i32;
                        pc += 1;
                        stack.push(builder.ins().iconst(i32_type, v as i64));
                    }
                }
                0x11 => { // sipush
                    if pc + 1 < bytecode.len() {
                        let v = ((bytecode[pc] as i16) << 8) | (bytecode[pc + 1] as i16);
                        pc += 2;
                        stack.push(builder.ins().iconst(i32_type, v as i64));
                    }
                }
                0x12 | 0x13 => { // ldc / ldc_w (stub: push 0)
                    if opcode == 0x13 { pc += 1; } else { pc += 1; }
                    stack.push(builder.ins().iconst(i32_type, 0));
                }

                // ----- Loads -----
                0x1a..=0x1d => { // iload_0..3
                    let idx = (opcode - 0x1a) as usize;
                    if let Some(v) = locals.get(idx).copied().flatten() {
                        stack.push(v);
                    } else {
                        let idx_val = builder.ins().iconst(i32_type, idx as i64);
                        let call = builder.ins().call(get_int_ref, &[frame_param, idx_val]);
                        let v = builder.inst_results(call)[0];
                        locals[idx] = Some(v);
                        stack.push(v);
                    }
                }
                0x2a..=0x2d => { // aload_0..3
                    let idx = (opcode - 0x2a) as usize;
                    if let Some(v) = locals.get(idx).copied().flatten() {
                        stack.push(v);
                    } else {
                        let idx_val = builder.ins().iconst(i32_type, idx as i64);
                        let call = builder.ins().call(get_ref_ref, &[frame_param, idx_val]);
                        let v = builder.inst_results(call)[0];
                        locals[idx] = Some(v);
                        stack.push(v);
                    }
                }
                0x1e..=0x21 => { // lload_0..3
                    let idx = (opcode - 0x1e) as usize;
                    if let Some(v) = locals.get(idx).copied().flatten() {
                        stack.push(v);
                    } else {
                        let idx_val = builder.ins().iconst(i32_type, idx as i64);
                        let call = builder.ins().call(get_long_ref, &[frame_param, idx_val]);
                        let v = builder.inst_results(call)[0];
                        locals[idx] = Some(v);
                        stack.push(v);
                    }
                }
                0x22..=0x25 => { // fload_0..3
                    let idx = (opcode - 0x22) as usize;
                    if let Some(v) = locals.get(idx).copied().flatten() {
                        stack.push(v);
                    } else {
                        let idx_val = builder.ins().iconst(i32_type, idx as i64);
                        let call = builder.ins().call(get_float_ref, &[frame_param, idx_val]);
                        let v = builder.inst_results(call)[0];
                        locals[idx] = Some(v);
                        stack.push(v);
                    }
                }
                0x26..=0x29 => { // dload_0..3
                    let idx = (opcode - 0x26) as usize;
                    if let Some(v) = locals.get(idx).copied().flatten() {
                        stack.push(v);
                    } else {
                        let idx_val = builder.ins().iconst(i32_type, idx as i64);
                        let call = builder.ins().call(get_double_ref, &[frame_param, idx_val]);
                        let v = builder.inst_results(call)[0];
                        locals[idx] = Some(v);
                        stack.push(v);
                    }
                }
                0x15 => { // iload
                    if pc < bytecode.len() {
                        let idx = bytecode[pc] as usize;
                        pc += 1;
                        if let Some(v) = locals.get(idx).copied().flatten() {
                            stack.push(v);
                        } else {
                            let idx_val = builder.ins().iconst(i32_type, idx as i64);
                            let call = builder.ins().call(get_int_ref, &[frame_param, idx_val]);
                            let v = builder.inst_results(call)[0];
                            if idx < locals.len() { locals[idx] = Some(v); }
                            stack.push(v);
                        }
                    }
                }
                0x16 => { // lload
                    if pc < bytecode.len() {
                        let idx = bytecode[pc] as usize;
                        pc += 1;
                        if let Some(v) = locals.get(idx).copied().flatten() {
                            stack.push(v);
                        } else {
                            let idx_val = builder.ins().iconst(i32_type, idx as i64);
                            let call = builder.ins().call(get_long_ref, &[frame_param, idx_val]);
                            let v = builder.inst_results(call)[0];
                            if idx < locals.len() { locals[idx] = Some(v); }
                            stack.push(v);
                        }
                    }
                }
                0x17 => { // fload
                    if pc < bytecode.len() {
                        let idx = bytecode[pc] as usize;
                        pc += 1;
                        if let Some(v) = locals.get(idx).copied().flatten() {
                            stack.push(v);
                        } else {
                            let idx_val = builder.ins().iconst(i32_type, idx as i64);
                            let call = builder.ins().call(get_float_ref, &[frame_param, idx_val]);
                            let v = builder.inst_results(call)[0];
                            if idx < locals.len() { locals[idx] = Some(v); }
                            stack.push(v);
                        }
                    }
                }
                0x18 => { // dload
                    if pc < bytecode.len() {
                        let idx = bytecode[pc] as usize;
                        pc += 1;
                        if let Some(v) = locals.get(idx).copied().flatten() {
                            stack.push(v);
                        } else {
                            let idx_val = builder.ins().iconst(i32_type, idx as i64);
                            let call = builder.ins().call(get_double_ref, &[frame_param, idx_val]);
                            let v = builder.inst_results(call)[0];
                            if idx < locals.len() { locals[idx] = Some(v); }
                            stack.push(v);
                        }
                    }
                }
                0x19 => { // aload
                    if pc < bytecode.len() {
                        let idx = bytecode[pc] as usize;
                        pc += 1;
                        if let Some(v) = locals.get(idx).copied().flatten() {
                            stack.push(v);
                        } else {
                            let idx_val = builder.ins().iconst(i32_type, idx as i64);
                            let call = builder.ins().call(get_ref_ref, &[frame_param, idx_val]);
                            let v = builder.inst_results(call)[0];
                            if idx < locals.len() { locals[idx] = Some(v); }
                            stack.push(v);
                        }
                    }
                }

                // ----- Stores -----
                0x3b..=0x3e => { // istore_0..3
                    let idx = (opcode - 0x3b) as usize;
                    if let Some(v) = stack.pop() {
                        locals[idx] = Some(v);
                        let idx_val = builder.ins().iconst(i32_type, idx as i64);
                        builder.ins().call(set_int_ref, &[frame_param, idx_val, v]);
                    }
                }
                0x4b..=0x4e => { // astore_0..3
                    let idx = (opcode - 0x4b) as usize;
                    if let Some(v) = stack.pop() {
                        locals[idx] = Some(v);
                        let idx_val = builder.ins().iconst(i32_type, idx as i64);
                        builder.ins().call(set_ref_ref, &[frame_param, idx_val, v]);
                    }
                }
                0x3f..=0x42 => { // lstore_0..3
                    let idx = (opcode - 0x3f) as usize;
                    if let Some(v) = stack.pop() {
                        locals[idx] = Some(v);
                        let idx_val = builder.ins().iconst(i32_type, idx as i64);
                        builder.ins().call(set_long_ref, &[frame_param, idx_val, v]);
                    }
                }
                0x43..=0x46 => { // fstore_0..3
                    let idx = (opcode - 0x43) as usize;
                    if let Some(v) = stack.pop() {
                        locals[idx] = Some(v);
                        let idx_val = builder.ins().iconst(i32_type, idx as i64);
                        builder.ins().call(set_float_ref, &[frame_param, idx_val, v]);
                    }
                }
                0x47..=0x4a => { // dstore_0..3
                    let idx = (opcode - 0x47) as usize;
                    if let Some(v) = stack.pop() {
                        locals[idx] = Some(v);
                        let idx_val = builder.ins().iconst(i32_type, idx as i64);
                        builder.ins().call(set_double_ref, &[frame_param, idx_val, v]);
                    }
                }
                0x36 => { // istore
                    if pc < bytecode.len() {
                        let idx = bytecode[pc] as usize;
                        pc += 1;
                        if let Some(v) = stack.pop() {
                            if idx < locals.len() { locals[idx] = Some(v); }
                            let idx_val = builder.ins().iconst(i32_type, idx as i64);
                            builder.ins().call(set_int_ref, &[frame_param, idx_val, v]);
                        }
                    }
                }
                0x37 => { // lstore
                    if pc < bytecode.len() {
                        let idx = bytecode[pc] as usize;
                        pc += 1;
                        if let Some(v) = stack.pop() {
                            if idx < locals.len() { locals[idx] = Some(v); }
                            let idx_val = builder.ins().iconst(i32_type, idx as i64);
                            builder.ins().call(set_long_ref, &[frame_param, idx_val, v]);
                        }
                    }
                }
                0x38 => { // fstore
                    if pc < bytecode.len() {
                        let idx = bytecode[pc] as usize;
                        pc += 1;
                        if let Some(v) = stack.pop() {
                            if idx < locals.len() { locals[idx] = Some(v); }
                            let idx_val = builder.ins().iconst(i32_type, idx as i64);
                            builder.ins().call(set_float_ref, &[frame_param, idx_val, v]);
                        }
                    }
                }
                0x39 => { // dstore
                    if pc < bytecode.len() {
                        let idx = bytecode[pc] as usize;
                        pc += 1;
                        if let Some(v) = stack.pop() {
                            if idx < locals.len() { locals[idx] = Some(v); }
                            let idx_val = builder.ins().iconst(i32_type, idx as i64);
                            builder.ins().call(set_double_ref, &[frame_param, idx_val, v]);
                        }
                    }
                }
                0x3a => { // astore
                    if pc < bytecode.len() {
                        let idx = bytecode[pc] as usize;
                        pc += 1;
                        if let Some(v) = stack.pop() {
                            if idx < locals.len() { locals[idx] = Some(v); }
                            let idx_val = builder.ins().iconst(i32_type, idx as i64);
                            builder.ins().call(set_ref_ref, &[frame_param, idx_val, v]);
                        }
                    }
                }

                // ----- Stack -----
                0x57 => { stack.pop(); } // pop
                0x58 => { stack.pop(); stack.pop(); } // pop2
                0x59 => { // dup
                    let v = stack.last().copied().unwrap_or_else(|| builder.ins().iconst(i32_type, 0));
                    stack.push(v);
                }
                0x5a => { // dup_x1
                    let v1 = stack.pop().unwrap_or_else(|| builder.ins().iconst(i32_type, 0));
                    let v2 = stack.pop().unwrap_or_else(|| builder.ins().iconst(i32_type, 0));
                    stack.push(v1);
                    stack.push(v2);
                    stack.push(v1);
                }
                0x5b => { // dup_x2
                    let v1 = stack.pop().unwrap_or_else(|| builder.ins().iconst(i32_type, 0));
                    let v2 = stack.pop().unwrap_or_else(|| builder.ins().iconst(i32_type, 0));
                    let v3 = stack.pop().unwrap_or_else(|| builder.ins().iconst(i32_type, 0));
                    stack.push(v1);
                    stack.push(v3);
                    stack.push(v2);
                    stack.push(v1);
                }
                0x5c => { // dup2
                    let v1 = stack.pop().unwrap_or_else(|| builder.ins().iconst(i32_type, 0));
                    let v2 = stack.pop().unwrap_or_else(|| builder.ins().iconst(i32_type, 0));
                    stack.push(v2);
                    stack.push(v1);
                    stack.push(v2);
                    stack.push(v1);
                }
                0x5d => { // dup2_x1
                    let v1 = stack.pop().unwrap_or_else(|| builder.ins().iconst(i32_type, 0));
                    let v2 = stack.pop().unwrap_or_else(|| builder.ins().iconst(i32_type, 0));
                    let v3 = stack.pop().unwrap_or_else(|| builder.ins().iconst(i32_type, 0));
                    stack.push(v2);
                    stack.push(v1);
                    stack.push(v3);
                    stack.push(v2);
                    stack.push(v1);
                }
                0x5e => { // dup2_x2
                    let v1 = stack.pop().unwrap_or_else(|| builder.ins().iconst(i32_type, 0));
                    let v2 = stack.pop().unwrap_or_else(|| builder.ins().iconst(i32_type, 0));
                    let v3 = stack.pop().unwrap_or_else(|| builder.ins().iconst(i32_type, 0));
                    let v4 = stack.pop().unwrap_or_else(|| builder.ins().iconst(i32_type, 0));
                    stack.push(v2);
                    stack.push(v1);
                    stack.push(v4);
                    stack.push(v3);
                    stack.push(v2);
                    stack.push(v1);
                }
                0x5f => { // swap
                    let v1 = stack.pop().unwrap_or_else(|| builder.ins().iconst(i32_type, 0));
                    let v2 = stack.pop().unwrap_or_else(|| builder.ins().iconst(i32_type, 0));
                    stack.push(v1);
                    stack.push(v2);
                }

                // ----- Integer arithmetic -----
                0x60 => { // iadd
                    let b = stack.pop().unwrap_or_else(|| builder.ins().iconst(i32_type, 0));
                    let a = stack.pop().unwrap_or_else(|| builder.ins().iconst(i32_type, 0));
                    stack.push(builder.ins().iadd(a, b));
                }
                0x64 => { // isub
                    let b = stack.pop().unwrap_or_else(|| builder.ins().iconst(i32_type, 0));
                    let a = stack.pop().unwrap_or_else(|| builder.ins().iconst(i32_type, 0));
                    stack.push(builder.ins().isub(a, b));
                }
                0x68 => { // imul
                    let b = stack.pop().unwrap_or_else(|| builder.ins().iconst(i32_type, 0));
                    let a = stack.pop().unwrap_or_else(|| builder.ins().iconst(i32_type, 0));
                    stack.push(builder.ins().imul(a, b));
                }
                0x6c => { // idiv
                    let b = stack.pop().unwrap_or_else(|| builder.ins().iconst(i32_type, 0));
                    let a = stack.pop().unwrap_or_else(|| builder.ins().iconst(i32_type, 0));
                    stack.push(builder.ins().sdiv(a, b));
                }
                0x70 => { // irem
                    let b = stack.pop().unwrap_or_else(|| builder.ins().iconst(i32_type, 0));
                    let a = stack.pop().unwrap_or_else(|| builder.ins().iconst(i32_type, 0));
                    stack.push(builder.ins().srem(a, b));
                }
                0x74 => { // ineg
                    let a = stack.pop().unwrap_or_else(|| builder.ins().iconst(i32_type, 0));
                    stack.push(builder.ins().ineg(a));
                }
                0x61 => { // ladd
                    let b = stack.pop().unwrap_or_else(|| builder.ins().iconst(i64_type, 0));
                    let a = stack.pop().unwrap_or_else(|| builder.ins().iconst(i64_type, 0));
                    stack.push(builder.ins().iadd(a, b));
                }
                0x65 => { // lsub
                    let b = stack.pop().unwrap_or_else(|| builder.ins().iconst(i64_type, 0));
                    let a = stack.pop().unwrap_or_else(|| builder.ins().iconst(i64_type, 0));
                    stack.push(builder.ins().isub(a, b));
                }
                0x69 => { // lmul
                    let b = stack.pop().unwrap_or_else(|| builder.ins().iconst(i64_type, 0));
                    let a = stack.pop().unwrap_or_else(|| builder.ins().iconst(i64_type, 0));
                    stack.push(builder.ins().imul(a, b));
                }
                0x6d => { // ldiv
                    let b = stack.pop().unwrap_or_else(|| builder.ins().iconst(i64_type, 0));
                    let a = stack.pop().unwrap_or_else(|| builder.ins().iconst(i64_type, 0));
                    stack.push(builder.ins().sdiv(a, b));
                }
                0x71 => { // lrem
                    let b = stack.pop().unwrap_or_else(|| builder.ins().iconst(i64_type, 0));
                    let a = stack.pop().unwrap_or_else(|| builder.ins().iconst(i64_type, 0));
                    stack.push(builder.ins().srem(a, b));
                }
                0x75 => { // lneg
                    let a = stack.pop().unwrap_or_else(|| builder.ins().iconst(i64_type, 0));
                    stack.push(builder.ins().ineg(a));
                }
                0x78 => { // ishl
                    let shift = stack.pop().unwrap_or_else(|| builder.ins().iconst(i32_type, 0));
                    let a = stack.pop().unwrap_or_else(|| builder.ins().iconst(i32_type, 0));
                    let mask = builder.ins().iconst(i32_type, 0x1f);
                    let amt = builder.ins().band(shift, mask);
                    stack.push(builder.ins().ishl(a, amt));
                }
                0x79 => { // lshl
                    let shift = stack.pop().unwrap_or_else(|| builder.ins().iconst(i32_type, 0));
                    let a = stack.pop().unwrap_or_else(|| builder.ins().iconst(i64_type, 0));
                    let mask = builder.ins().iconst(i32_type, 0x3f);
                    let amt = builder.ins().band(shift, mask);
                    stack.push(builder.ins().ishl(a, amt));
                }
                0x7a => { // ishr
                    let shift = stack.pop().unwrap_or_else(|| builder.ins().iconst(i32_type, 0));
                    let a = stack.pop().unwrap_or_else(|| builder.ins().iconst(i32_type, 0));
                    let mask = builder.ins().iconst(i32_type, 0x1f);
                    let amt = builder.ins().band(shift, mask);
                    stack.push(builder.ins().sshr(a, amt));
                }
                0x7b => { // lshr
                    let shift = stack.pop().unwrap_or_else(|| builder.ins().iconst(i32_type, 0));
                    let a = stack.pop().unwrap_or_else(|| builder.ins().iconst(i64_type, 0));
                    let mask = builder.ins().iconst(i32_type, 0x3f);
                    let amt = builder.ins().band(shift, mask);
                    stack.push(builder.ins().sshr(a, amt));
                }
                0x7c => { // iushr
                    let shift = stack.pop().unwrap_or_else(|| builder.ins().iconst(i32_type, 0));
                    let a = stack.pop().unwrap_or_else(|| builder.ins().iconst(i32_type, 0));
                    let mask = builder.ins().iconst(i32_type, 0x1f);
                    let amt = builder.ins().band(shift, mask);
                    stack.push(builder.ins().ushr(a, amt));
                }
                0x7d => { // lushr
                    let shift = stack.pop().unwrap_or_else(|| builder.ins().iconst(i32_type, 0));
                    let a = stack.pop().unwrap_or_else(|| builder.ins().iconst(i64_type, 0));
                    let mask = builder.ins().iconst(i32_type, 0x3f);
                    let amt = builder.ins().band(shift, mask);
                    stack.push(builder.ins().ushr(a, amt));
                }
                0x7e => { // iand
                    let b = stack.pop().unwrap_or_else(|| builder.ins().iconst(i32_type, 0));
                    let a = stack.pop().unwrap_or_else(|| builder.ins().iconst(i32_type, 0));
                    stack.push(builder.ins().band(a, b));
                }
                0x7f => { // land
                    let b = stack.pop().unwrap_or_else(|| builder.ins().iconst(i64_type, 0));
                    let a = stack.pop().unwrap_or_else(|| builder.ins().iconst(i64_type, 0));
                    stack.push(builder.ins().band(a, b));
                }
                0x80 => { // ior
                    let b = stack.pop().unwrap_or_else(|| builder.ins().iconst(i32_type, 0));
                    let a = stack.pop().unwrap_or_else(|| builder.ins().iconst(i32_type, 0));
                    stack.push(builder.ins().bor(a, b));
                }
                0x81 => { // lor
                    let b = stack.pop().unwrap_or_else(|| builder.ins().iconst(i64_type, 0));
                    let a = stack.pop().unwrap_or_else(|| builder.ins().iconst(i64_type, 0));
                    stack.push(builder.ins().bor(a, b));
                }
                0x82 => { // ixor
                    let b = stack.pop().unwrap_or_else(|| builder.ins().iconst(i32_type, 0));
                    let a = stack.pop().unwrap_or_else(|| builder.ins().iconst(i32_type, 0));
                    stack.push(builder.ins().bxor(a, b));
                }
                0x83 => { // lxor
                    let b = stack.pop().unwrap_or_else(|| builder.ins().iconst(i64_type, 0));
                    let a = stack.pop().unwrap_or_else(|| builder.ins().iconst(i64_type, 0));
                    stack.push(builder.ins().bxor(a, b));
                }

                // ----- Float arithmetic -----
                0x62 => { // fadd
                    let b = stack.pop().unwrap_or_else(|| builder.ins().f32const(0.0));
                    let a = stack.pop().unwrap_or_else(|| builder.ins().f32const(0.0));
                    stack.push(builder.ins().fadd(a, b));
                }
                0x66 => { // fsub
                    let b = stack.pop().unwrap_or_else(|| builder.ins().f32const(0.0));
                    let a = stack.pop().unwrap_or_else(|| builder.ins().f32const(0.0));
                    stack.push(builder.ins().fsub(a, b));
                }
                0x6a => { // fmul
                    let b = stack.pop().unwrap_or_else(|| builder.ins().f32const(0.0));
                    let a = stack.pop().unwrap_or_else(|| builder.ins().f32const(0.0));
                    stack.push(builder.ins().fmul(a, b));
                }
                0x6e => { // fdiv
                    let b = stack.pop().unwrap_or_else(|| builder.ins().f32const(0.0));
                    let a = stack.pop().unwrap_or_else(|| builder.ins().f32const(0.0));
                    stack.push(builder.ins().fdiv(a, b));
                }

                // ----- Double arithmetic -----
                0x63 => { // dadd
                    let b = stack.pop().unwrap_or_else(|| builder.ins().f64const(0.0));
                    let a = stack.pop().unwrap_or_else(|| builder.ins().f64const(0.0));
                    stack.push(builder.ins().fadd(a, b));
                }
                0x67 => { // dsub
                    let b = stack.pop().unwrap_or_else(|| builder.ins().f64const(0.0));
                    let a = stack.pop().unwrap_or_else(|| builder.ins().f64const(0.0));
                    stack.push(builder.ins().fsub(a, b));
                }
                0x6b => { // dmul
                    let b = stack.pop().unwrap_or_else(|| builder.ins().f64const(0.0));
                    let a = stack.pop().unwrap_or_else(|| builder.ins().f64const(0.0));
                    stack.push(builder.ins().fmul(a, b));
                }
                0x6f => { // ddiv
                    let b = stack.pop().unwrap_or_else(|| builder.ins().f64const(0.0));
                    let a = stack.pop().unwrap_or_else(|| builder.ins().f64const(0.0));
                    stack.push(builder.ins().fdiv(a, b));
                }

                // ----- Conversions -----
                0x85 => { // i2l
                    let a = stack.pop().unwrap_or_else(|| builder.ins().iconst(i32_type, 0));
                    stack.push(builder.ins().sextend(i64_type, a));
                }
                0x86 => { // i2f
                    let a = stack.pop().unwrap_or_else(|| builder.ins().iconst(i32_type, 0));
                    stack.push(builder.ins().fcvt_from_sint(f32_type, a));
                }
                0x87 => { // i2d
                    let a = stack.pop().unwrap_or_else(|| builder.ins().iconst(i32_type, 0));
                    stack.push(builder.ins().fcvt_from_sint(f64_type, a));
                }
                0x88 => { // l2i
                    let a = stack.pop().unwrap_or_else(|| builder.ins().iconst(i64_type, 0));
                    stack.push(builder.ins().ireduce(i32_type, a));
                }
                0x89 => { // l2f
                    let a = stack.pop().unwrap_or_else(|| builder.ins().iconst(i64_type, 0));
                    stack.push(builder.ins().fcvt_from_sint(f32_type, a));
                }
                0x8a => { // l2d
                    let a = stack.pop().unwrap_or_else(|| builder.ins().iconst(i64_type, 0));
                    stack.push(builder.ins().fcvt_from_sint(f64_type, a));
                }
                0x8b => { // f2i
                    let a = stack.pop().unwrap_or_else(|| builder.ins().f32const(0.0));
                    stack.push(builder.ins().fcvt_to_sint_sat(i32_type, a));
                }
                0x8c => { // f2l
                    let a = stack.pop().unwrap_or_else(|| builder.ins().f32const(0.0));
                    stack.push(builder.ins().fcvt_to_sint_sat(i64_type, a));
                }
                0x8d => { // f2d
                    let a = stack.pop().unwrap_or_else(|| builder.ins().f32const(0.0));
                    stack.push(builder.ins().fpromote(f64_type, a));
                }
                0x8e => { // d2i
                    let a = stack.pop().unwrap_or_else(|| builder.ins().f64const(0.0));
                    stack.push(builder.ins().fcvt_to_sint_sat(i32_type, a));
                }
                0x8f => { // d2l
                    let a = stack.pop().unwrap_or_else(|| builder.ins().f64const(0.0));
                    stack.push(builder.ins().fcvt_to_sint_sat(i64_type, a));
                }
                0x90 => { // d2f
                    let a = stack.pop().unwrap_or_else(|| builder.ins().f64const(0.0));
                    stack.push(builder.ins().fdemote(f32_type, a));
                }
                0x91 => { // i2b
                    let a = stack.pop().unwrap_or_else(|| builder.ins().iconst(i32_type, 0));
                    let mask = builder.ins().iconst(i32_type, 0xff);
                    let t = builder.ins().band(a, mask);
                    let sign = builder.ins().iconst(i32_type, 0x80);
                    let has_sign = builder.ins().icmp(IntCC::SignedGreaterThanOrEqual, t, sign);
                    let ext = builder.ins().iconst(i32_type, 0xffffff00u32 as i64);
                    let extended = builder.ins().bor(t, ext);
                    stack.push(builder.ins().select(has_sign, extended, t));
                }
                0x92 => { // i2c
                    let a = stack.pop().unwrap_or_else(|| builder.ins().iconst(i32_type, 0));
                    let mask = builder.ins().iconst(i32_type, 0xffff);
                    stack.push(builder.ins().band(a, mask));
                }
                0x93 => { // i2s
                    let a = stack.pop().unwrap_or_else(|| builder.ins().iconst(i32_type, 0));
                    let mask = builder.ins().iconst(i32_type, 0xffff);
                    let t = builder.ins().band(a, mask);
                    let sign = builder.ins().iconst(i32_type, 0x8000);
                    let has_sign = builder.ins().icmp(IntCC::SignedGreaterThanOrEqual, t, sign);
                    let ext = builder.ins().iconst(i32_type, 0xffff0000u32 as i64);
                    let extended = builder.ins().bor(t, ext);
                    stack.push(builder.ins().select(has_sign, extended, t));
                }
                0x94 => { // lcmp
                    let b = stack.pop().unwrap_or_else(|| builder.ins().iconst(i64_type, 0));
                    let a = stack.pop().unwrap_or_else(|| builder.ins().iconst(i64_type, 0));
                    let eq = builder.ins().icmp(IntCC::Equal, a, b);
                    let lt = builder.ins().icmp(IntCC::SignedLessThan, a, b);
                    let one = builder.ins().iconst(i32_type, 1);
                    let neg_one = builder.ins().iconst(i32_type, -1);
                    let zero = builder.ins().iconst(i32_type, 0);
                    let t = builder.ins().select(lt, neg_one, one);
                    stack.push(builder.ins().select(eq, zero, t));
                }
                0x95 => { // fcmpg
                    let b = stack.pop().unwrap_or_else(|| builder.ins().f32const(0.0));
                    let a = stack.pop().unwrap_or_else(|| builder.ins().f32const(0.0));
                    let eq = builder.ins().fcmp(FloatCC::Equal, a, b);
                    let lt = builder.ins().fcmp(FloatCC::LessThan, a, b);
                    let one = builder.ins().iconst(i32_type, 1);
                    let neg_one = builder.ins().iconst(i32_type, -1);
                    let zero = builder.ins().iconst(i32_type, 0);
                    let t = builder.ins().select(lt, neg_one, one);
                    stack.push(builder.ins().select(eq, zero, t));
                }
                0x96 => { // fcmpl
                    let b = stack.pop().unwrap_or_else(|| builder.ins().f32const(0.0));
                    let a = stack.pop().unwrap_or_else(|| builder.ins().f32const(0.0));
                    let eq = builder.ins().fcmp(FloatCC::Equal, a, b);
                    let lt = builder.ins().fcmp(FloatCC::LessThan, a, b);
                    let one = builder.ins().iconst(i32_type, 1);
                    let neg_one = builder.ins().iconst(i32_type, -1);
                    let zero = builder.ins().iconst(i32_type, 0);
                    let t = builder.ins().select(lt, neg_one, one);
                    stack.push(builder.ins().select(eq, zero, t));
                }
                0x97 => { // dcmpg
                    let b = stack.pop().unwrap_or_else(|| builder.ins().f64const(0.0));
                    let a = stack.pop().unwrap_or_else(|| builder.ins().f64const(0.0));
                    let eq = builder.ins().fcmp(FloatCC::Equal, a, b);
                    let lt = builder.ins().fcmp(FloatCC::LessThan, a, b);
                    let one = builder.ins().iconst(i32_type, 1);
                    let neg_one = builder.ins().iconst(i32_type, -1);
                    let zero = builder.ins().iconst(i32_type, 0);
                    let t = builder.ins().select(lt, neg_one, one);
                    stack.push(builder.ins().select(eq, zero, t));
                }
                0x98 => { // dcmpl
                    let b = stack.pop().unwrap_or_else(|| builder.ins().f64const(0.0));
                    let a = stack.pop().unwrap_or_else(|| builder.ins().f64const(0.0));
                    let eq = builder.ins().fcmp(FloatCC::Equal, a, b);
                    let lt = builder.ins().fcmp(FloatCC::LessThan, a, b);
                    let one = builder.ins().iconst(i32_type, 1);
                    let neg_one = builder.ins().iconst(i32_type, -1);
                    let zero = builder.ins().iconst(i32_type, 0);
                    let t = builder.ins().select(lt, neg_one, one);
                    stack.push(builder.ins().select(eq, zero, t));
                }

                // ----- Increment -----
                0x84 => { // iinc
                    if pc + 1 < bytecode.len() {
                        let idx = bytecode[pc] as usize;
                        let delta_i = bytecode[pc + 1] as i8 as i64;
                        pc += 2;
                        if let Some(v) = locals.get(idx).copied().flatten() {
                            let delta_val = builder.ins().iconst(i32_type, delta_i);
                            let new_val = builder.ins().iadd(v, delta_val);
                            if idx < locals.len() { locals[idx] = Some(new_val); }
                            let idx_val = builder.ins().iconst(i32_type, idx as i64);
                            builder.ins().call(set_int_ref, &[frame_param, idx_val, new_val]);
                        } else {
                            let idx_val = builder.ins().iconst(i32_type, idx as i64);
                            let delta_val = builder.ins().iconst(i32_type, delta_i);
                            builder.ins().call(inc_int_ref, &[frame_param, idx_val, delta_val]);
                        }
                    }
                }

                // ----- Branch / control flow -----
                0x99 => { // ifeq
                    if pc + 1 < bytecode.len() {
                        let offset = ((bytecode[pc] as i16) << 8) | (bytecode[pc + 1] as i16);
                        pc += 2;
                        let target = (pc - 3).wrapping_add(offset as usize);
                        let val = stack.pop().unwrap_or_else(|| builder.ins().iconst(i32_type, 0));
                        let zero = builder.ins().iconst(i32_type, 0);
                        let fallthrough = *blocks.get(&pc).unwrap_or(&entry);
                        let branch_blk = *blocks.get(&target).unwrap_or(&entry);
                        let cond = builder.ins().icmp(IntCC::Equal, val, zero);
                        builder.ins().brif(cond, branch_blk, &[], fallthrough, &[]);
                    }
                }
                0x9a => { // ifne
                    if pc + 1 < bytecode.len() {
                        let offset = ((bytecode[pc] as i16) << 8) | (bytecode[pc + 1] as i16);
                        pc += 2;
                        let target = (pc - 3).wrapping_add(offset as usize);
                        let val = stack.pop().unwrap_or_else(|| builder.ins().iconst(i32_type, 0));
                        let zero = builder.ins().iconst(i32_type, 0);
                        let fallthrough = *blocks.get(&pc).unwrap_or(&entry);
                        let branch_blk = *blocks.get(&target).unwrap_or(&entry);
                        let cond = builder.ins().icmp(IntCC::NotEqual, val, zero);
                        builder.ins().brif(cond, branch_blk, &[], fallthrough, &[]);
                    }
                }
                0x9b => { // iflt
                    if pc + 1 < bytecode.len() {
                        let offset = ((bytecode[pc] as i16) << 8) | (bytecode[pc + 1] as i16);
                        pc += 2;
                        let target = (pc - 3).wrapping_add(offset as usize);
                        let val = stack.pop().unwrap_or_else(|| builder.ins().iconst(i32_type, 0));
                        let zero = builder.ins().iconst(i32_type, 0);
                        let fallthrough = *blocks.get(&pc).unwrap_or(&entry);
                        let branch_blk = *blocks.get(&target).unwrap_or(&entry);
                        let cond = builder.ins().icmp(IntCC::SignedLessThan, val, zero);
                        builder.ins().brif(cond, branch_blk, &[], fallthrough, &[]);
                    }
                }
                0x9c => { // ifge
                    if pc + 1 < bytecode.len() {
                        let offset = ((bytecode[pc] as i16) << 8) | (bytecode[pc + 1] as i16);
                        pc += 2;
                        let target = (pc - 3).wrapping_add(offset as usize);
                        let val = stack.pop().unwrap_or_else(|| builder.ins().iconst(i32_type, 0));
                        let zero = builder.ins().iconst(i32_type, 0);
                        let fallthrough = *blocks.get(&pc).unwrap_or(&entry);
                        let branch_blk = *blocks.get(&target).unwrap_or(&entry);
                        let cond = builder.ins().icmp(IntCC::SignedGreaterThanOrEqual, val, zero);
                        builder.ins().brif(cond, branch_blk, &[], fallthrough, &[]);
                    }
                }
                0x9d => { // ifgt
                    if pc + 1 < bytecode.len() {
                        let offset = ((bytecode[pc] as i16) << 8) | (bytecode[pc + 1] as i16);
                        pc += 2;
                        let target = (pc - 3).wrapping_add(offset as usize);
                        let val = stack.pop().unwrap_or_else(|| builder.ins().iconst(i32_type, 0));
                        let zero = builder.ins().iconst(i32_type, 0);
                        let fallthrough = *blocks.get(&pc).unwrap_or(&entry);
                        let branch_blk = *blocks.get(&target).unwrap_or(&entry);
                        let cond = builder.ins().icmp(IntCC::SignedGreaterThan, val, zero);
                        builder.ins().brif(cond, branch_blk, &[], fallthrough, &[]);
                    }
                }
                0x9e => { // ifle
                    if pc + 1 < bytecode.len() {
                        let offset = ((bytecode[pc] as i16) << 8) | (bytecode[pc + 1] as i16);
                        pc += 2;
                        let target = (pc - 3).wrapping_add(offset as usize);
                        let val = stack.pop().unwrap_or_else(|| builder.ins().iconst(i32_type, 0));
                        let zero = builder.ins().iconst(i32_type, 0);
                        let fallthrough = *blocks.get(&pc).unwrap_or(&entry);
                        let branch_blk = *blocks.get(&target).unwrap_or(&entry);
                        let cond = builder.ins().icmp(IntCC::SignedLessThanOrEqual, val, zero);
                        builder.ins().brif(cond, branch_blk, &[], fallthrough, &[]);
                    }
                }
                0x9f => { // if_icmpeq
                    if pc + 1 < bytecode.len() {
                        let offset = ((bytecode[pc] as i16) << 8) | (bytecode[pc + 1] as i16);
                        pc += 2;
                        let target = (pc - 3).wrapping_add(offset as usize);
                        let b = stack.pop().unwrap_or_else(|| builder.ins().iconst(i32_type, 0));
                        let a = stack.pop().unwrap_or_else(|| builder.ins().iconst(i32_type, 0));
                        let fallthrough = *blocks.get(&pc).unwrap_or(&entry);
                        let branch_blk = *blocks.get(&target).unwrap_or(&entry);
                        let cond = builder.ins().icmp(IntCC::Equal, a, b);
                        builder.ins().brif(cond, branch_blk, &[], fallthrough, &[]);
                    }
                }
                0xa0 => { // if_icmpne
                    if pc + 1 < bytecode.len() {
                        let offset = ((bytecode[pc] as i16) << 8) | (bytecode[pc + 1] as i16);
                        pc += 2;
                        let target = (pc - 3).wrapping_add(offset as usize);
                        let b = stack.pop().unwrap_or_else(|| builder.ins().iconst(i32_type, 0));
                        let a = stack.pop().unwrap_or_else(|| builder.ins().iconst(i32_type, 0));
                        let fallthrough = *blocks.get(&pc).unwrap_or(&entry);
                        let branch_blk = *blocks.get(&target).unwrap_or(&entry);
                        let cond = builder.ins().icmp(IntCC::NotEqual, a, b);
                        builder.ins().brif(cond, branch_blk, &[], fallthrough, &[]);
                    }
                }
                0xa7 => { // goto
                    if pc + 1 < bytecode.len() {
                        let offset = ((bytecode[pc] as i16) << 8) | (bytecode[pc + 1] as i16);
                        pc += 2;
                        let target = (pc - 3).wrapping_add(offset as usize);
                        let branch_blk = *blocks.get(&target).unwrap_or(&entry);
                        builder.ins().jump(branch_blk, &[]);
                    }
                }

                // ----- Returns -----
                0xb1 => { // return
                    let ret_val = builder.ins().iconst(i32_type, 0);
                    builder.ins().return_(&[ret_val]);
                    compiled = true;
                    break;
                }
                0xac => { // ireturn
                    let result = stack.pop().unwrap_or_else(|| builder.ins().iconst(i32_type, 0));
                    builder.ins().call(push_int_ref, &[frame_param, result]);
                    let ret_val = builder.ins().iconst(i32_type, 0);
                    builder.ins().return_(&[ret_val]);
                    compiled = true;
                    break;
                }
                0xad => { // lreturn
                    let result = stack.pop().unwrap_or_else(|| builder.ins().iconst(i64_type, 0));
                    builder.ins().call(push_long_ref, &[frame_param, result]);
                    let ret_val = builder.ins().iconst(i32_type, 0);
                    builder.ins().return_(&[ret_val]);
                    compiled = true;
                    break;
                }
                0xae => { // freturn
                    let result = stack.pop().unwrap_or_else(|| builder.ins().f32const(0.0));
                    builder.ins().call(push_float_ref, &[frame_param, result]);
                    let ret_val = builder.ins().iconst(i32_type, 0);
                    builder.ins().return_(&[ret_val]);
                    compiled = true;
                    break;
                }
                0xaf => { // dreturn
                    let result = stack.pop().unwrap_or_else(|| builder.ins().f64const(0.0));
                    builder.ins().call(push_double_ref, &[frame_param, result]);
                    let ret_val = builder.ins().iconst(i32_type, 0);
                    builder.ins().return_(&[ret_val]);
                    compiled = true;
                    break;
                }
                0xb0 => { // areturn
                    let result = stack.pop().unwrap_or_else(|| builder.ins().iconst(i32_type, 0));
                    builder.ins().call(push_ref_ref, &[frame_param, result]);
                    let ret_val = builder.ins().iconst(i32_type, 0);
                    builder.ins().return_(&[ret_val]);
                    compiled = true;
                    break;
                }

                // ----- Fallback -----
                _ => {
                    return Err(JitError::UnsupportedInstruction(format!(
                        "opcode 0x{:02x} at pc {}",
                        opcode, pc
                    )));
                }
            }
        }

        if !compiled {
            let ret_val = builder.ins().iconst(i32_type, 0);
            builder.ins().return_(&[ret_val]);
        }

        builder.finalize();

        self.module
            .define_function(func_id, &mut ctx)
            .map_err(|e| JitError::CompilationFailed(e.to_string()))?;

        self.module.clear_context(&mut ctx);

        self.module
            .finalize_definitions()
            .map_err(|e| JitError::LinkingError(e.to_string()))?;

        let code_ptr = self.module.get_finalized_function(func_id);
        let code_size = 0; // Not easily available from JITModule

        Ok((code_ptr, code_size))
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_unroll_simple_loop() {
        // Simple loop: iinc 0, 1; goto -3 (back to iinc)
        // Body is just iinc (3 bytes), goto (3 bytes)
        let bytecode = [0x84, 0x00, 0x01, 0xa7, 0xff, 0xfd];
        let unrolled = unroll_loops(&bytecode);
        // After unrolling: iinc 0, 1; iinc 0, 2; goto -6
        assert_eq!(unrolled.len(), 9);
        assert_eq!(unrolled[0], 0x84); // iinc
        assert_eq!(unrolled[1], 0x00); // index 0
        assert_eq!(unrolled[2], 0x01); // delta 1
        assert_eq!(unrolled[3], 0x84); // iinc
        assert_eq!(unrolled[4], 0x00); // index 0
        assert_eq!(unrolled[5], 0x02); // delta 2 (doubled)
        assert_eq!(unrolled[6], 0xa7); // goto
    }

    #[test]
    fn test_unroll_no_loop() {
        // No backward goto
        let bytecode = [0x04, 0x05, 0x60, 0xac]; // iconst_1, iconst_2, iadd, ireturn
        let unrolled = unroll_loops(&bytecode);
        assert_eq!(unrolled, bytecode);
    }

    #[test]
    fn test_unroll_forward_goto_not_unrolled() {
        // Forward goto should not be unrolled
        let bytecode = [0x04, 0xa7, 0x00, 0x02, 0x05, 0xac]; // iconst_1, goto +2, iconst_2, ireturn
        let unrolled = unroll_loops(&bytecode);
        assert_eq!(unrolled, bytecode);
    }

    #[test]
    fn test_unroll_loop_with_branch_not_unrolled() {
        // Loop with ifeq inside body should not be unrolled
        let bytecode = [
            0x04,       // iconst_1
            0x99, 0x00, 0x03, // ifeq +3
            0x84, 0x00, 0x01, // iinc 0, 1
            0xa7, 0xff, 0xf7, // goto -9
        ];
        let unrolled = unroll_loops(&bytecode);
        assert_eq!(unrolled, bytecode);
    }

    #[test]
    fn test_body_has_branch() {
        assert!(body_has_branch(&[0x99, 0x00, 0x01])); // ifeq
        assert!(!body_has_branch(&[0x04, 0x05, 0x60])); // iconst_1, iconst_2, iadd
        assert!(!body_has_branch(&[0x84, 0x00, 0x01])); // iinc (multi-byte, skip operands)
    }
}
