use std::collections::HashMap;
use shape_value::MethodId;
use shape_vm::bytecode::{BytecodeProgram, OpCode, Operand};
#[derive(Debug, Clone)]
pub struct HofInlineSite {
pub callback_fn_id: Option<u16>,
}
#[derive(Debug, Clone, Default)]
pub struct HofInlinePlan {
pub sites: HashMap<usize, HofInlineSite>,
}
const HOF_METHODS: &[(u16, &str, usize)] = &[
(MethodId::MAP.0, "map", 1),
(MethodId::FILTER.0, "filter", 1),
(MethodId::REDUCE.0, "reduce", 2),
(MethodId::FIND.0, "find", 1),
(MethodId::FIND_INDEX.0, "findIndex", 1),
(MethodId::SOME.0, "some", 1),
(MethodId::EVERY.0, "every", 1),
(MethodId::FOR_EACH.0, "forEach", 1),
];
pub fn analyze_hof_inline(program: &BytecodeProgram) -> HofInlinePlan {
let mut plan = HofInlinePlan::default();
for (idx, instr) in program.instructions.iter().enumerate() {
if instr.opcode != OpCode::CallMethod {
continue;
}
let Some(Operand::TypedMethodCall {
method_id,
arg_count,
..
}) = instr.operand.as_ref()
else {
continue;
};
let Some(&(_, _name, _expected_args)) =
HOF_METHODS.iter().find(|(id, _, _)| *id == *method_id)
else {
continue;
};
let arg_count = *arg_count as usize;
if arg_count < 1 {
continue;
}
let callback_fn_id = resolve_callback_fn_id(program, idx, arg_count);
plan.sites.insert(
idx,
HofInlineSite {
callback_fn_id,
},
);
}
plan
}
fn resolve_callback_fn_id(
program: &BytecodeProgram,
call_idx: usize,
arg_count: usize,
) -> Option<u16> {
let search_start = call_idx.saturating_sub(1);
let search_end = call_idx.saturating_sub(10 + arg_count * 3);
let mut pushes_to_skip = 2 + (arg_count - 1);
for i in (search_end..=search_start).rev() {
let instr = &program.instructions[i];
match instr.opcode {
OpCode::PushConst => {
if pushes_to_skip > 0 {
pushes_to_skip -= 1;
continue;
}
if let Some(Operand::Function(fn_id)) = &instr.operand {
return Some(fn_id.0);
}
return None;
}
OpCode::MakeClosure => {
if pushes_to_skip > 0 {
pushes_to_skip -= 1;
continue;
}
return None;
}
_ => {}
}
}
None
}