use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct ControlFlowInfo {
pub block_ends: HashMap<usize, usize>,
pub loop_starts: HashMap<usize, usize>,
}
pub fn analyze_control_flow(bytecode: &[u8]) -> Result<ControlFlowInfo, String> {
let mut block_ends = HashMap::new();
let mut loop_starts = HashMap::new();
let mut block_stack = Vec::new();
let mut pos = 0;
while pos < bytecode.len() {
let byte = bytecode[pos];
match byte {
0x02 => {
block_stack.push((pos, None)); pos += 1;
skip_block_type(bytecode, &mut pos);
}
0x03 => {
block_stack.push((pos, Some(pos))); pos += 1;
skip_block_type(bytecode, &mut pos);
}
0x04 => {
block_stack.push((pos, None));
pos += 1;
skip_block_type(bytecode, &mut pos);
}
0x0B => {
if let Some((start_pos, loop_flag)) = block_stack.pop() {
if let Some(_loop_start) = loop_flag {
loop_starts.insert(start_pos, start_pos);
} else {
block_ends.insert(start_pos, pos + 1);
}
}
pos += 1;
}
0x05 => {
pos += 1;
}
_ => {
pos += skip_instruction_bytes(byte);
}
}
}
Ok(ControlFlowInfo {
block_ends,
loop_starts,
})
}
fn skip_block_type(bytecode: &[u8], pos: &mut usize) {
if *pos < bytecode.len() {
*pos += 1; }
}
fn skip_instruction_bytes(byte: u8) -> usize {
match byte {
0x41..=0x42 => {
1
}
0x43..=0x44 => {
1
}
0x20..=0x22 => {
1
}
0x23..=0x24 => {
1
}
0x28..=0x3c => {
1
}
0x0c..=0x0d => {
1
}
0x0e => {
1
}
0x10..=0x11 => {
1
}
_ => {
1
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_analyze_empty_block() {
let bytecode = vec![0x02, 0x40, 0x01, 0x0b];
let info = analyze_control_flow(&bytecode).unwrap();
assert!(info.block_ends.contains_key(&0));
}
#[test]
fn test_analyze_loop() {
let bytecode = vec![0x03, 0x40, 0x41, 0x00, 0x0b];
let info = analyze_control_flow(&bytecode).unwrap();
assert!(info.loop_starts.contains_key(&0));
}
#[test]
fn test_analyze_nested_blocks() {
let bytecode = vec![0x02, 0x40, 0x02, 0x40, 0x0b, 0x0b];
let info = analyze_control_flow(&bytecode).unwrap();
assert_eq!(info.block_ends.len(), 2);
}
}