From 916ebb401f69bc63217a735ca1dfeb512ce3151a Mon Sep 17 00:00:00 2001 From: dzu Date: Tue, 3 Feb 2026 14:39:54 +0000 Subject: [PATCH 1/4] feature-locked cancellation --- bcode-flow/Cargo.toml | 3 +++ bcode-flow/src/lib.rs | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/bcode-flow/Cargo.toml b/bcode-flow/Cargo.toml index 69d86ff..4e916fa 100644 --- a/bcode-flow/Cargo.toml +++ b/bcode-flow/Cargo.toml @@ -9,5 +9,8 @@ ferrobean-parser-raw.workspace = true ferrobean-common.workspace = true tracing.workspace = true +[features] +cancel = [] + [dev-dependencies] test-utils = {workspace = true, features = ["resolve"]} diff --git a/bcode-flow/src/lib.rs b/bcode-flow/src/lib.rs index 4942d45..9dc6140 100644 --- a/bcode-flow/src/lib.rs +++ b/bcode-flow/src/lib.rs @@ -109,7 +109,7 @@ pub enum FlowError { } // TODO: remove; silly me did not explicitly join a killed thread, and got main thread panic as a result >_< -#[cfg(test)] +#[cfg(any(test, feature = "cancel"))] mod cancel { use std::sync::atomic::{AtomicBool, Ordering}; extern crate std; @@ -130,7 +130,7 @@ pub use cancel::{reset as cancel_reset, trap as cancel_trap}; macro_rules! cancel { () => { - #[cfg(test)] + #[cfg(any(test, feature = "cancel"))] if $crate::cancel::CANCEL.load(std::sync::atomic::Ordering::Relaxed) { return Err($crate::FlowError::Cancelled); } From 1e7c7963a5b32b57c8b7d59630644284979b2797 Mon Sep 17 00:00:00 2001 From: dzu Date: Tue, 3 Feb 2026 14:41:23 +0000 Subject: [PATCH 2/4] remove `if` and `match` passes as current instruction dialect has no such concepts --- bcode-flow/src/lib.rs | 13 - bcode-flow/src/parse_if.rs | 421 --------------------------------- bcode-flow/src/parse_switch.rs | 364 ---------------------------- 3 files changed, 798 deletions(-) delete mode 100644 bcode-flow/src/parse_if.rs delete mode 100644 bcode-flow/src/parse_switch.rs diff --git a/bcode-flow/src/lib.rs b/bcode-flow/src/lib.rs index 9dc6140..11431dd 100644 --- a/bcode-flow/src/lib.rs +++ b/bcode-flow/src/lib.rs @@ -35,8 +35,6 @@ use crate::parse_try::{HandlerRange, NegativeHandlerRange}; const TARGET: &str = "ferrobean-top-loop"; const TARGET_LOOP: &str = "ferrobean-loops"; -const TARGET_MATCH: &str = "ferrobean-match"; -const TARGET_IF: &str = "ferrobean-ifs"; const TARGET_FWD: &str = "ferrobean-forward"; const TARGET_TRY: &str = "ferrobean-try"; const TARGET_PRUNE: &str = "ferrobean-prune"; @@ -1631,21 +1629,10 @@ pub fn parse_flow(code: &Code) -> Result, Flo // trace!(target: TARGET, inner_instrutions = format_args!("{inner_instrutions:#?}"), "after-prune"); // cancel!(); - // parse_switch::parse_switch(&mut inner_instrutions)?; - // trace!(target: TARGET, inner_instrutions = format_args!("{inner_instrutions:#?}"), "after-parse-match"); - // - // util::unreachable_prune(&mut inner_instrutions)?; - // trace!(target: TARGET, inner_instrutions = format_args!("{inner_instrutions:#?}"), "after-prune2"); - parse_loops::parse_loops(&mut inner_instrutions)?; trace!(target: TARGET, inner_instrutions = format_args!("{inner_instrutions:#?}"), "after-parse-loops"); cancel!(); - // panic!(); - - // parse_if::parse_if(&mut inner_instrutions)?; - // trace!(target: TARGET, inner_instrutions = format_args!("{inner_instrutions:#?}"), "after-parse-if"); - // no inner prune should be done up until this point parse_forward::parse_forward(&mut inner_instrutions)?; trace!(target: TARGET, inner_instrutions = format_args!("{inner_instrutions:#?}"), "after-parse-forward"); diff --git a/bcode-flow/src/parse_if.rs b/bcode-flow/src/parse_if.rs deleted file mode 100644 index 4366cae..0000000 --- a/bcode-flow/src/parse_if.rs +++ /dev/null @@ -1,421 +0,0 @@ -use alloc::{ - boxed::Box, - collections::{BTreeMap, BTreeSet}, - vec, - vec::Vec, -}; - -use ferrobean_common::TextStorage; - -use crate::{Condition, FlowError, InnerInstruction, InnerInstructionSpec, instruction_kind::Flow}; -use crate::{InnerInstructionVisitor, InnerInstructionVisitorMut}; - -use super::TARGET_IF as TARGET; - -struct Find { - // if jump candidates - jumps: Vec<(i32, i32)>, -} - -impl InnerInstructionVisitor for Find { - fn node_enter(&mut self, node: &InnerInstruction) -> Result<(), FlowError> { - let InnerInstructionSpec::Flow(flow) = &node.spec else { - return Ok(()); - }; - let pos = node.pos; - let mut record_offset = |offset: i32| { - if offset <= 0 { - return; - } - let target = pos + offset; - trace!(from = pos, to = target, "found-jump"); - self.jumps.push((target, pos)); - }; - match flow { - Flow::Throw - | Flow::Return(_) - | Flow::LookupSwitch { .. } - | Flow::TableSwitch { .. } => {} - Flow::Goto { offsets, _parsed } => { - for offset in offsets { - record_offset(*offset); - } - } - Flow::IfACmp(_, offset) - | Flow::IfICmp(_, offset) - | Flow::If(_, offset) - | Flow::IfNull(_, offset) => { - record_offset(i32::from(*offset)); - } - } - Ok(()) - } -} - -#[derive(Debug, PartialEq, PartialOrd, Eq, Ord)] -struct SuperIf { - start: i32, - body_start: i32, - destinations: Box<[i32]>, -} - -struct Parse { - all_jumps: Vec<(i32, i32)>, - superifs: Vec, -} - -impl InnerInstructionVisitor for Parse { - fn nodes_enter(&mut self, nodes: &[InnerInstruction]) -> Result<(), FlowError> { - #[cfg(test)] - if !nodes.is_sorted_by_key(|node| node.pos) { - internal_error!("nodes-unsorted", nodes); - } - let _span = trace_span!("parse-superif"); - if nodes.is_empty() { - trace!("ignore-nodes-empty"); - return Ok(()); - } - _span.record("start-pos", nodes.first().unwrap().pos); - _span.record("end-pos", nodes.last().unwrap().pos); - let _span = _span.enter(); - let start = nodes.first().unwrap().pos; - let end = nodes.last().unwrap().pos; - trace!(target: TARGET, start, end, "nodes-pos-range"); - let this_jumps_start = self.all_jumps.partition_point(|(to, _)| to < &start); - let this_jumps_len = - self.all_jumps[this_jumps_start..].partition_point(|(to, _)| to <= &end); - let mut this_jumps = self.all_jumps[this_jumps_start..][..this_jumps_len] - .iter() - .filter(|&(to, from)| { - let _span = trace_span!("check-jump", from, to); - let _span = _span.enter(); - if !(start..=end).contains(from) { - trace!(target: TARGET, "ignore-outer-from"); - return false; - } - let Ok(to_ind) = nodes.binary_search_by_key(to, |node| node.pos) else { - trace!(target: TARGET, "ignore-invalid-to"); - return false; - }; - if matches!(nodes[to_ind].spec, InnerInstructionSpec::Inner) { - trace!(target: TARGET, "ignore-to-inner"); - return false; - } - true - }) - .map(|(to, from)| (*from, *to)) - .collect::>(); - // now `this_jumps` contains pairs (from, to) - this_jumps.sort_unstable_by(|a, b| a.cmp(b).reverse()); - // now sorted in reverse order, so that he first jump coming is at the end - trace!(target: TARGET, ?this_jumps); - while let Some((start_pos, mut body_start_pos)) = this_jumps.pop() { - let _span = trace_span!("searching-loop", start_pos, body_start_pos); - let _span = _span.enter(); - // from represents body start. body actually starts at the first non-inner element of this scope - let Some(mut start_ind) = nodes - .partition_point(|node| node.pos <= start_pos) - .checked_sub(1) - else { - internal_error!("start-pos-out-of-scope", nodes, start_pos); - }; - if nodes - .binary_search_by_key(&body_start_pos, |node| node.pos) - .ok() - .is_none_or(|node_i| nodes[node_i].spec == InnerInstructionSpec::Inner) - { - trace!(target: TARGET, body_start_pos, "ignore-inner-body"); - continue; - } - trace!(target: TARGET, start_ind, start_pos = nodes[start_ind].pos, "initial-start-ind"); - // i points to the first node having position of at least `from` - // - // now, we iterate back, until we hit the non-inner node - while matches!(nodes[start_ind].spec, InnerInstructionSpec::Inner) { - let Some(start_ind_) = start_ind.checked_sub(1) else { - // somehow, the first node of the scope is inner - internal_error!("first-node-inner"); - }; - start_ind = start_ind_; - } - let start = nodes[start_ind].pos; - trace!(target: TARGET, start_ind, start_pos = nodes[start_ind].pos, "final-start-ind"); - let mut destinations = vec![body_start_pos]; - // now, we iterate over the rest of the jumps, until we reach the meet - while let Some((from, to)) = this_jumps.pop_if(|(from, _)| *from < body_start_pos) { - use core::cmp::min; - body_start_pos = min(body_start_pos, to); - destinations.push(to); - trace!(target: TARGET, from, to, body_start_pos, "add-destination"); - } - destinations.sort_unstable(); - destinations.dedup(); - let superif = SuperIf { - start, - body_start: body_start_pos, - destinations: destinations.into_boxed_slice(), - }; - trace!(?superif, "found-superif"); - self.superifs.push(superif); - } - - Ok(()) - } -} - -struct TransformSuperIf { - superifs: Vec, - // TODO: maybe use `Rc` instead of `Box` - superif_jump_map: BTreeMap, -} - -impl InnerInstructionVisitorMut for TransformSuperIf { - fn nodes_enter(&mut self, nodes: &mut [InnerInstruction]) -> Result<(), FlowError> { - let _span = trace_span!("transform-nodes"); - if nodes.is_empty() { - trace!("ignore-nodes-empty"); - return Ok(()); - } - _span.record("start-pos", nodes.first().unwrap().pos); - _span.record("end-pos", nodes.last().unwrap().pos); - let _span = _span.enter(); - for superif in &self.superifs { - let _span = trace_span!("try-superif", ?superif); - let _span = _span.enter(); - let Ok(start_ind) = nodes.binary_search_by_key(&superif.start, |node| node.pos) else { - trace!("start-out-of-scope"); - continue; - }; - if nodes[start_ind].spec == InnerInstructionSpec::Inner { - trace!("start-inner"); - continue; - } - let Ok(body_start_ind) = - nodes.binary_search_by_key(&superif.body_start, |node| node.pos) - else { - // warn!("inconsistent-condition-end"); - // loop avoidance, still nothing to warn about - continue; - }; - if nodes[body_start_ind].spec == InnerInstructionSpec::Inner { - trace!("body-inner"); - continue; - } - let Ok(past_end_ind) = - nodes.binary_search_by_key(superif.destinations.last().unwrap(), |node| node.pos) - else { - warn!("inconsistent-end"); - continue; - }; - trace!( - start_ind, - start_pos = nodes[start_ind].pos, - body_start_ind, - body_start_pos = nodes[body_start_ind].pos, - past_end_ind, - past_end_pos = nodes.get(past_end_ind).map(|node| node.pos), - "form-if", - ); - // first, extract the condition into a separate block - let condition = nodes[start_ind..body_start_ind] - .iter_mut() - .map(InnerInstruction::take) - .collect::>() - .into_boxed_slice(); - if condition.is_empty() { - internal_error!("empty-condition"); - } - // populate the branches - let mut branches: Box<[Vec>]> = - vec![vec![]; superif.destinations.len()].into_boxed_slice(); - let mut active_brances = BTreeSet::::new(); - for node in &mut nodes[body_start_ind..past_end_ind] - .iter_mut() - .map(InnerInstruction::take) - { - if let Ok(branch_no) = superif.destinations.binary_search(&node.pos) { - active_brances.insert(branch_no); - } - for &active_branch in &active_brances { - // TODO: maybe cheaper clone? somehow? - branches[active_branch].push(node.clone()); - } - // if matches!( - // node.spec, - // InnerInstructionSpec::Throw - // | InnerInstructionSpec::Return(_) - // | InnerInstructionSpec::LoopContinue { .. } - // | InnerInstructionSpec::IfResolve { .. } - // | InnerInstructionSpec::ForwardBreak { .. } - // | InnerInstructionSpec::Flow( - // Flow::Throw | Flow::Return(_) | Flow::Goto { .. } - // ) - // ) { - // // branch break - // active_brances.clear(); - // } - } - // add a dummy jump instruction to every branch, in case there is a jump to the end - if let Some(end_node) = nodes.get(past_end_ind) { - let end_nop = InnerInstruction { - pos: end_node.pos, - spec: InnerInstructionSpec::Nop, - artificial: true, - }; - for branch in &mut branches { - branch.push(end_nop.clone()); - } - } - // assign the superif instruction to the first instruction of the scope - nodes[start_ind].spec = InnerInstructionSpec::If { - condition, - branches: branches - .into_iter() - .map(Vec::into_boxed_slice) - .collect::>() - .into_boxed_slice(), - }; - nodes[start_ind].artificial = true; - trace!(target: TARGET, newnode=?nodes[start_ind], "create-if"); - // populate jump mappings - for (branch_no, &to) in superif.destinations.iter().enumerate() { - self.superif_jump_map.insert(to, (superif.start, branch_no)); - } - } - Ok(()) - } - - fn node_enter(&mut self, node: &mut InnerInstruction) -> Result<(), FlowError> { - let _span = trace_span!("transform-node", ?node); - let _span = _span.enter(); - let InnerInstructionSpec::Flow(flow) = &node.spec else { - trace!(target: TARGET, "ignore-nonflow"); - return Ok(()); - }; - let (if_pos, branch, condition) = 'resolve: { - macro_rules! check_offset { - ($offset:expr, $condition:expr) => { - if let Some(&(pos, branch)) = self.superif_jump_map.get(&(node.pos + ($offset))) - { - break 'resolve (pos, branch, $condition); - } - }; - } - match flow { - Flow::LookupSwitch { .. } - | Flow::TableSwitch { .. } - | Flow::Return(_) - | Flow::Throw => {} - Flow::Goto { offsets, _parsed } => { - for offset in offsets { - check_offset!(offset, Option::::None); - } - } - &Flow::IfACmp(bin_cmp, offset) => { - check_offset!(i32::from(offset), Some(Condition::IfACmp(bin_cmp))); - } - &Flow::IfICmp(cmp, offset) => { - check_offset!(i32::from(offset), Some(Condition::IfICmp(cmp))); - } - &Flow::If(cmp, offset) => { - check_offset!(i32::from(offset), Some(Condition::If(cmp))); - } - &Flow::IfNull(bin_cmp, offset) => { - check_offset!(i32::from(offset), Some(Condition::IfNull(bin_cmp))); - } - } - trace!(target: TARGET, resolves = ?self.superif_jump_map, "ignore-nonresolve"); - return Ok(()); - }; - node.spec = InnerInstructionSpec::IfResolve { - condition, - result: branch, - if_pos, - }; - node.artificial = true; - trace!(target: TARGET, newnode = ?node); - Ok(()) - } -} - -struct ConditionRevolve0; - -impl InnerInstructionVisitorMut for ConditionRevolve0 { - fn node_enter(&mut self, node: &mut InnerInstruction) -> Result<(), FlowError> { - let InnerInstructionSpec::If { - condition, - branches: _, - } = &mut node.spec - else { - return Ok(()); - }; - let mut condition1 = core::mem::take(condition).into_vec(); - // add the implicit resolve(0) instruction - condition1.push(InnerInstruction { - pos: condition1.last().unwrap().pos + 1, - spec: InnerInstructionSpec::IfResolve { - condition: None, - result: 0, - if_pos: node.pos, - }, - artificial: true, - }); - *condition = condition1.into_boxed_slice(); - Ok(()) - } -} - -pub fn parse_if(instructions: &mut [InnerInstruction]) -> Result<(), FlowError> { - // First, find all the jumps forward. These are potential superif resolution instructions - // - // Then, for each scope, filter out jumps into the current scope, and consider only jumps in this scope or in the subscope of this one - // - // For each jump-from location, starting at the beginning, find the location it jumps to. this target location is past the end of the condition block - // - // Filter out jump-from locations between the from- and to- of the selected jump. - // - // Pick the smallest target location. It marks the end of the condition block - // - // Pick the greatest target location. It marks the end of the superif statement. - // - // Parse the rest of the jumps contained in the superif statement. - // - // Iterate over instructions, keeping track of active branches, and populate them with the content (kinda like the switch statement) - // - // Provided the jumps vector is sorted by target position, this approach takes kinda-linear-but-not-really time, which is the point - let _span = trace_span!(target: TARGET, "parse-if"); - let _span = _span.enter(); - let mut find = Find { jumps: Vec::new() }; - trace_span!(target: TARGET, "find") - .in_scope(|| InnerInstruction::visit_slice(instructions, &mut find))?; - let mut jumps = find.jumps; - jumps.sort_unstable(); - jumps.dedup(); - trace!(target: TARGET, ?jumps); - - let mut parse = Parse { - all_jumps: jumps, - superifs: Vec::new(), - }; - trace_span!(target: TARGET, "parse") - .in_scope(|| InnerInstruction::visit_slice(instructions, &mut parse))?; - let mut superifs = parse.superifs; - superifs.sort_unstable(); - superifs.dedup(); - trace!(target: TARGET, ?superifs); - - trace_span!(target: TARGET, "transform").in_scope(|| { - InnerInstruction::visit_mut_slice( - instructions, - &mut TransformSuperIf { - superifs, - superif_jump_map: BTreeMap::new(), - }, - ) - })?; - - trace_span!(target: TARGET, "add-resolve-0") - .in_scope(|| InnerInstruction::visit_mut_slice(instructions, &mut ConditionRevolve0))?; - - Ok(()) -} diff --git a/bcode-flow/src/parse_switch.rs b/bcode-flow/src/parse_switch.rs deleted file mode 100644 index 10cac77..0000000 --- a/bcode-flow/src/parse_switch.rs +++ /dev/null @@ -1,364 +0,0 @@ -use alloc::collections::BTreeMap; - -use alloc::{boxed::Box, vec::Vec}; - -use ferrobean_common::TextStorage; -use ferrobean_parser_resolve::bcode::tag::LookupPair; - -use crate::util::ForwardJumps; -use crate::{ - FlowError, InnerInstruction, InnerInstructionSpec, InnerInstructionVisitorMut, - instruction_kind::Flow, -}; - -use super::TARGET_MATCH as TARGET; - -// struct SwitchData { -// start_ind: usize, -// #[allow(clippy::type_complexity)] -// branches: Box<[(Option, Vec>)]>, -// inactive_branches: BinaryHeap<( -// /* start index */ Reverse, -// /* branch index */ usize, -// )>, -// active_branches: Vec, -// breaks: BinaryHeap>, -// past_end_ind: usize, -// } -// impl SwitchData { -// fn push_instruction(&mut self, instruction: InnerInstruction) { -// for &ai in &self.active_branches { -// self.branches[ai].1.push(instruction.clone()); -// } -// } -// } -// -// /// Parses all the match statements and returns loop-less and match-less pieces of code with parsed match controls -// #[allow(clippy::too_many_lines)] -// pub(crate) fn parse_switch2( -// instructions: &mut [InnerInstruction], -// ) -> Result<(), FlowError> { -// // - do switches as they come -// // - once encountered a switch statement, determine its end (target of break jumps) -// // - immediately parse all the breaks related to the current switch -// // - record switch end, and put into a queue of active switch ends -// // - use end queue size as a hint for switch statement layers -// -// let len = instructions.len(); -// let mut switches_stack = Vec::>::new(); -// for ind in 0..len { -// // if there is an active switch -// { -// if let Some(switch) = switches_stack.last_mut() { -// // check if any new branch jumps here -// while switch -// .inactive_branches -// .peek() -// .is_some_and(|(Reverse(si), _)| *si == ind) -// { -// let (_, branch_index) = switch.inactive_branches.pop().unwrap(); -// switch.active_branches.push(branch_index); -// } -// } -// -// if switches_stack.is_empty() { -// // check if any of the active switches have a break at this position -// for layer in 0..switches_stack.len() { -// // check if break exists at this position -// while switches_stack[layer] -// .breaks -// .peek() -// .is_some_and(|Reverse(bi)| *bi == ind) -// { -// let _ = switches_stack[layer].breaks.pop().unwrap(); -// let pos = instructions[ind].pos; -// // push break instruction -// switches_stack[layer].push_instruction(InnerInstruction { -// pos, -// spec: InnerInstructionSpec::SwitchBreak { layer }, -// }); -// // reset active branches (switch was broken out of) -// switches_stack.last_mut().unwrap().active_branches.clear(); -// } -// } -// } -// } -// // try closing switch statements -// while let Some(mut switch) = switches_stack.pop_if(|s| s.past_end_ind == ind) { -// // instruction we are now at is past the end of the inner-most switch: -// // - pop the switch (done above) -// // - add a switch break to all active branches -// // - push it as a single instruction to the parent loop -// let layer = switches_stack.len(); -// switch.push_instruction(InnerInstruction { -// pos: instructions[switch.past_end_ind - 1].pos, -// spec: InnerInstructionSpec::SwitchBreak { layer }, -// }); -// let branches = switch -// .branches -// .into_iter() -// .map(|(m, ins)| (m, ins.into_boxed_slice())) -// .collect::>() -// .into_boxed_slice(); -// let pos = instructions[switch.start_ind].pos; -// let instruction = InnerInstruction { -// pos, -// spec: InnerInstructionSpec::Switch { layer, branches }, -// }; -// if let Some(top_switch) = switches_stack.last_mut() { -// for &ai in &top_switch.active_branches { -// top_switch.branches[ai].1.push(instruction.clone()); -// } -// } else { -// instructions[switch.start_ind] = instruction; -// } -// } -// // at this point, switch logic is handled -// // just kidding, here comes the worst part: actual switch parsing -// let instruction = instructions[ind].take(); -// let raw_branches = match instruction.spec { -// InnerInstructionSpec::Flow(Flow::TableSwitch { -// default, -// low, -// high: _, -// offsets, -// }) => { -// let mut branches = Vec::<(i32, Option)>::with_capacity(offsets.len() + 1); -// branches.push((default, None)); -// for (off, m) in offsets.iter().copied().zip(low..) { -// branches.push((off, Some(m))); -// } -// branches -// } -// InnerInstructionSpec::Flow(Flow::LookupSwitch { default, pairs }) => { -// let mut branches = Vec::<(i32, Option)>::with_capacity(pairs.len() + 1); -// branches.push((default, None)); -// for LookupPair { r#match, offset } in pairs.iter().copied() { -// branches.push((offset, Some(r#match))); -// } -// branches -// } -// InnerInstructionSpec::Loop { mut body } => { -// // parse switches inside loop bodies, and reassign -// parse_switch(&mut body)?; -// let instruction = InnerInstruction { -// pos: instruction.pos, -// spec: InnerInstructionSpec::Loop { body }, -// }; -// if let Some(switch) = switches_stack.last_mut() { -// switch.push_instruction(instruction); -// } else { -// instructions[ind] = instruction; -// } -// continue; -// } -// _ => { -// // just a normal instruction -- put it where it belongs, and that's about it -// if let Some(switch) = switches_stack.last_mut() { -// switch.push_instruction(instruction); -// } else { -// instructions[ind] = instruction; -// } -// continue; -// } -// }; -// let start_ind: usize = ind; -// let mut branches: Vec<(Option, Vec>)> = -// Vec::with_capacity(raw_branches.len()); -// let mut inactive_branches: BinaryHeap<( -// /* start index */ Reverse, -// /* branch index */ usize, -// )> = BinaryHeap::with_capacity(raw_branches.len()); -// let active_branches: Vec = Vec::with_capacity(raw_branches.len()); -// let mut breaks: BinaryHeap> = BinaryHeap::with_capacity(raw_branches.len()); -// let mut max_match_jump = 0; -// let mut break_jumps: Option> = None; -// -// let switch_pos = instruction.pos; -// for (offset, mtch) in raw_branches { -// // assert that no branch jumps back -// // (it might be allowed, but I'm not prepared to handle this yet) -// if offset <= 0 { -// return Err(FlowError::NonPositiveMatchOffset { switch_pos, offset }); -// } -// let target_pos = switch_pos + offset; -// let Ok(target_ind) = instructions.binary_search_by_key(&target_pos, |ins| ins.pos) -// else { -// return Err(FlowError::InvalidJump { -// at: switch_pos, -// to: target_pos, -// }); -// }; -// // record the jump target -// max_match_jump = max_match_jump.max(target_ind); -// // if the instruction just before this one is a jump -// let prev_intruction = &instructions[target_ind - 1]; -// if let InnerInstructionSpec::Flow(Flow::Goto { -// offsets: jump_offsets, -// _parsed, -// }) = &prev_intruction.spec -// { -// let mut jump_inds = BTreeSet::new(); -// for jump_offset in jump_offsets { -// // find target index -// let break_jump_pos = prev_intruction.pos; -// let break_jump_target = break_jump_pos + jump_offset; -// if let Ok(break_jump_ind) = -// instructions.binary_search_by_key(&break_jump_target, |ins| ins.pos) -// { -// jump_inds.insert(break_jump_ind); -// } else { -// return Err(FlowError::InvalidJump { -// at: break_jump_pos, -// to: break_jump_target, -// }); -// } -// } -// // record target indexes -// if let Some(prev_break_jump_ind) = break_jumps.as_mut() { -// let prev_break_jump_ind: &mut BTreeSet = prev_break_jump_ind; -// prev_break_jump_ind.retain(|ind| jump_inds.contains(&ind)); -// if prev_break_jump_ind.is_empty() { -// return Err(FlowError::IncoherentSwitch { switch_pos }); -// } -// } else { -// break_jumps = Some(jump_inds); -// } -// // take the previous instruction out -// instructions[target_ind - 1].take(); -// // record it's index as a break -// breaks.push(Reverse(target_ind - 1)); -// } -// -// // push the match arm -// inactive_branches.push((Reverse(target_ind), branches.len())); -// branches.push((mtch, Vec::new())); -// } -// -// let past_end_ind: usize = break_jumps -// .map_or(max_match_jump, |mut inds: BTreeSet| { -// inds.pop_last().unwrap() -// }); -// let switch = SwitchData { -// start_ind, -// branches: branches.into_boxed_slice(), -// inactive_branches, -// active_branches, -// breaks, -// past_end_ind, -// }; -// // push to the stack -// switches_stack.push(switch); -// } -// Ok(()) -// } - -struct Match; - -impl InnerInstructionVisitorMut for Match { - fn nodes_enter(&mut self, nodes: &mut [InnerInstruction]) -> Result<(), FlowError> { - // set of positions having jumps to them -- hence they must remain in this scope - let mut forward_jumps = ForwardJumps::new(); - - let mut node_i = 0; - 'nodes: while node_i < nodes.len() { - 'cont: { - let node = &nodes[node_i]; - let InnerInstructionSpec::Flow(flow) = &node.spec else { - break 'cont; - }; - - if !matches!(flow, Flow::LookupSwitch { .. } | Flow::TableSwitch { .. }) { - break 'cont; - } - - let end = ((node_i + 1)..nodes.len()) - .find(|&i| forward_jumps.breaks(nodes[i].pos)) - .unwrap_or(nodes.len()); - - let mut branches = Vec::]>>::new(); - let mut matches = Vec::<(Option, usize)>::new(); - let mut offset_map = BTreeMap::::new(); - let mut process_offset = |mch: Option, offset: i32| -> Result<(), FlowError> { - if let Some(&branch_i) = offset_map.get(&offset) { - matches.push((mch, branch_i)); - return Ok(()); - } - let target_pos = node.pos + offset; - let Ok(target_ind) = nodes.binary_search_by_key(&target_pos, |node| node.pos) - else { - return Err(FlowError::InvalidJump { - at: node.pos, - to: target_pos, - }); - }; - - let branch_i = branches.len(); - offset_map.insert(offset, branch_i); - matches.push((mch, branch_i)); - - let mut branch = nodes[target_ind..end] - .iter() - .map(Clone::clone) - .collect::>(); - - if end < nodes.len() { - branch.push(InnerInstruction { - pos: nodes[end].pos, - spec: InnerInstructionSpec::Nop, - artificial: true, - }); - } - branches.push(branch.into_boxed_slice()); - - Ok(()) - }; - match flow { - Flow::LookupSwitch { default, pairs } => { - process_offset(None, *default)?; - for &LookupPair { r#match, offset } in pairs.iter() { - process_offset(Some(r#match), offset)?; - } - } - Flow::TableSwitch { - default, - low, - high: _, - offsets, - } => { - process_offset(None, *default)?; - for (mch, &offset) in (*low..).zip(offsets.iter()) { - process_offset(Some(mch), offset)?; - } - } - _ => unreachable!(), - } - nodes[node_i].spec = InnerInstructionSpec::Switch { - branches: branches.into_boxed_slice(), - matches: matches.into_boxed_slice().into(), - }; - nodes[node_i].artificial = true; - nodes[(node_i + 1)..end].iter_mut().for_each(|node| { - node.spec = InnerInstructionSpec::Inner; - node.artificial = true; - }); - - forward_jumps.add(&nodes[node_i])?; - node_i = end; - continue 'nodes; - } - forward_jumps.add(&nodes[node_i])?; - node_i += 1; - } - Ok(()) - } -} - -pub(crate) fn parse_switch( - instructions: &mut [InnerInstruction], -) -> Result<(), FlowError> { - // for each match instruction: - // - jump to the offset of the branch. clone all the remaining nodes in the scope to a branch scope - // - erase all the nodes in the currect scope, replace with a single match - InnerInstruction::visit_mut_slice(instructions, &mut Match) -} From 9cc8292492d4809aa7a6c0568b3f17e5817f0a3f Mon Sep 17 00:00:00 2001 From: dzu Date: Tue, 3 Feb 2026 14:50:01 +0000 Subject: [PATCH 3/4] remove chain goto pass as it was not proven to be necessary --- bcode-flow/src/instruction_kind.rs | 18 +--- bcode-flow/src/lib.rs | 166 ++--------------------------- bcode-flow/src/parse_forward.rs | 41 +++---- bcode-flow/src/parse_loops.rs | 10 +- bcode-flow/src/parse_try.rs | 5 +- bcode-flow/src/util.rs | 24 ++--- 6 files changed, 37 insertions(+), 227 deletions(-) diff --git a/bcode-flow/src/instruction_kind.rs b/bcode-flow/src/instruction_kind.rs index 9a0503a..37151dc 100644 --- a/bcode-flow/src/instruction_kind.rs +++ b/bcode-flow/src/instruction_kind.rs @@ -31,10 +31,7 @@ pub(crate) enum Flow { Return(ReturnType), // TODO: unify with ifs Goto { - // invariant: sorted - // TODO: REMOVE - offsets: Vec, - _parsed: bool, + offset: i32, }, /// > ref, ref IfACmp(BinCmp, i16), @@ -65,7 +62,7 @@ impl Flow { | Flow::LookupSwitch { .. } | Flow::TableSwitch { .. } | Flow::Throw => None, - Flow::Goto { offsets, .. } => Some(offsets[0]), + Flow::Goto { offset, .. } => Some(*offset), Flow::IfACmp(_, offset) | Flow::IfICmp(_, offset) | Flow::If(_, offset) @@ -82,11 +79,7 @@ impl Flow { | Flow::LookupSwitch { .. } | Flow::TableSwitch { .. } | Flow::Return(_) => {} - Flow::Goto { offsets, .. } => { - for &offset in offsets { - visitor(offset)?; - } - } + Flow::Goto { offset, .. } => visitor(*offset)?, Flow::IfACmp(_, offset) | Flow::IfICmp(_, offset) | Flow::If(_, offset) @@ -188,10 +181,7 @@ impl InstructionKind { InstructionSpec::Dup2X2 => Self::NonFlow(NonFlow::Dup2X2), InstructionSpec::GetField(field_ref) => Self::NonFlow(NonFlow::GetField(field_ref)), InstructionSpec::GetStatic(field_ref) => Self::NonFlow(NonFlow::GetStatic(field_ref)), - InstructionSpec::Goto(offset) => Self::Flow(Flow::Goto { - offsets: vec![offset], - _parsed: false, - }), + InstructionSpec::Goto(offset) => Self::Flow(Flow::Goto { offset }), InstructionSpec::IfACmp(bin_cmp, offset) => Self::Flow(Flow::IfACmp(bin_cmp, offset)), InstructionSpec::IfICmp(cmp, offset) => Self::Flow(Flow::IfICmp(cmp, offset)), InstructionSpec::If(cmp, offset) => Self::Flow(Flow::If(cmp, offset)), diff --git a/bcode-flow/src/lib.rs b/bcode-flow/src/lib.rs index 11431dd..912ff79 100644 --- a/bcode-flow/src/lib.rs +++ b/bcode-flow/src/lib.rs @@ -9,7 +9,6 @@ extern crate tracing; use alloc::collections::BTreeMap; use alloc::collections::BTreeSet; use alloc::{boxed::Box, sync::Arc, vec, vec::Vec}; -use core::ops::{AddAssign, SubAssign}; use core::{mem, num::NonZeroU8}; use ferrobean_parser_resolve::attribute::Code; use ferrobean_parser_resolve::bcode::tag::LookupPair; @@ -100,8 +99,6 @@ pub enum FlowError { UnusedNegativeHandler(parse_try::NegativeHandlerRange), UnusedHandler(parse_try::HandlerRange), Internal(InternalError), - /// Loop of gotos - GotoLoop, #[cfg(test)] Cancelled, } @@ -485,11 +482,8 @@ impl InnerInstruction { }; visitor.node_enter(self, order)?; let res = match &self.spec { - InnerInstructionSpec::Flow(Flow::Goto { offsets, _parsed }) => { - offsets - .iter() - .copied() - .for_each(|offset| goto_acceptor(self.pos + offset)); + InnerInstructionSpec::Flow(Flow::Goto { offset }) => { + goto_acceptor(self.pos + offset); false } &InnerInstructionSpec::Flow( @@ -1350,13 +1344,8 @@ fn parse_return(instructions: &mut [InnerInstruction]) -> Res } impl InnerInstructionVisitorMut for ParseReturn { fn node_enter(&mut self, node: &mut InnerInstruction) -> Result<(), FlowError> { - if let InnerInstructionSpec::Flow(Flow::Goto { - offsets, - _parsed: _, - }) = &node.spec - && let Some(&tp) = offsets - .iter() - .find_map(|offset| self.returns.get(&(node.pos + offset))) + if let InnerInstructionSpec::Flow(Flow::Goto { offset }) = &node.spec + && let Some(&tp) = self.returns.get(&(node.pos + offset)) { node.spec = InnerInstructionSpec::Return(tp); } @@ -1413,142 +1402,6 @@ fn transform_switch( Ok(()) } -fn parse_chain_goto( - instructions: &mut [InnerInstruction], -) -> Result<(), FlowError> { - #[derive(Debug, Default, Clone, Copy, PartialEq)] - enum GotoState { - #[default] - Initial, - Active, - Computed, - } - struct CollectGotos { - valid_targets: BTreeMap, - gotos: BTreeMap, GotoState)>, - } - impl InnerInstructionVisitor for CollectGotos { - fn nodes_enter(&mut self, nodes: &[InnerInstruction]) -> Result<(), FlowError> { - for node in nodes { - if matches!(node.spec, InnerInstructionSpec::Inner) { - continue; - } - self.valid_targets - .entry(node.pos) - .or_default() - .add_assign(1); - } - Ok(()) - } - - fn nodes_exit(&mut self, nodes: &[InnerInstruction]) -> Result<(), FlowError> { - for node in nodes { - if matches!(node.spec, InnerInstructionSpec::Inner) { - continue; - } - let alloc::collections::btree_map::Entry::Occupied(mut entry) = - self.valid_targets.entry(node.pos) - else { - internal_error!("valid-target-nonexist"); - }; - alloc::collections::btree_map::OccupiedEntry::<'_, i32, u16>::get_mut(&mut entry) - .sub_assign(1); - if *entry.get() == 0 { - entry.remove(); - } - } - Ok(()) - } - - fn node_enter(&mut self, node: &InnerInstruction) -> Result<(), FlowError> { - let InnerInstructionSpec::Flow(Flow::Goto { - offsets, - _parsed: _, - }) = &node.spec - else { - return Ok(()); - }; - for &offset in offsets { - let target = node.pos + offset; - if !self.valid_targets.contains_key(&target) { - return Err(FlowError::InvalidJump { - at: node.pos, - to: target, - }); - } - self.gotos - .entry(node.pos) - .or_default() - .0 - .push(node.pos + offset); - } - Ok(()) - } - } - fn rec(gotos: &mut [(i32, (Vec, GotoState))], ind: usize) -> Result<(), FlowError> { - if gotos[ind].1.1 == GotoState::Active { - return Err(FlowError::GotoLoop); - } - gotos[ind].1.1 = GotoState::Active; - let mut tgts = core::mem::take(&mut gotos[ind].1.0); - for tgt_i in 0..tgts.len() { - if let Ok(tgt1_i) = gotos.binary_search_by_key(&tgts[tgt_i], |(k, _)| *k) { - if tgt1_i == ind { - continue; - } - rec(gotos, tgt1_i)?; - tgts.extend_from_slice(&gotos[tgt1_i].1.0); - } - } - tgts.sort_unstable(); - tgts.dedup(); - gotos[ind].1.0 = tgts; - gotos[ind].1.1 = GotoState::Computed; - Ok(()) - } - struct AssignGotos { - vectors: BTreeMap>, - } - impl InnerInstructionVisitorMut for AssignGotos { - fn node_enter(&mut self, node: &mut InnerInstruction) -> Result<(), FlowError> { - let InnerInstructionSpec::Flow(Flow::Goto { - offsets, - _parsed: _, - }) = &mut node.spec - else { - return Ok(()); - }; - let Some(computed_targets) = self.vectors.get(&node.pos) else { - internal_error!("assign-goto-notargets"); - }; - offsets.clone_from(computed_targets); - for offset in offsets { - offset.sub_assign(node.pos); - } - Ok(()) - } - } - let mut collect = CollectGotos { - valid_targets: BTreeMap::new(), - gotos: BTreeMap::new(), - }; - InnerInstruction::visit_slice(instructions, &mut collect)?; - let mut gotos = collect.gotos.into_iter().collect::>(); - for i in 0..gotos.len() { - rec(&mut gotos, i)?; - } - InnerInstruction::visit_mut_slice( - instructions, - &mut AssignGotos { - vectors: gotos - .into_iter() - .map(|(pos, (vec, _))| (pos, vec)) - .collect(), - }, - )?; - Ok(()) -} - fn parse_nop_goto( instructions: &mut [InnerInstruction], ) -> Result<(), FlowError> { @@ -1559,14 +1412,11 @@ fn parse_nop_goto( instructions: &mut [InnerInstruction], ) -> Result<(), FlowError> { for i in 0..instructions.len() - 1 { - let InnerInstructionSpec::Flow(Flow::Goto { offsets, .. }) = &instructions[i].spec + let InnerInstructionSpec::Flow(Flow::Goto { offset }) = &instructions[i].spec else { continue; }; - if offsets - .binary_search(&(instructions[i + 1].pos - instructions[i].pos)) - .is_ok() - { + if *offset == instructions[i + 1].pos - instructions[i].pos { // that's a nop goto :/ instructions[i].spec = InnerInstructionSpec::NopGoto; } @@ -1602,10 +1452,6 @@ pub fn parse_flow(code: &Code) -> Result, Flo trace!(target: TARGET, inner_instrutions = format_args!("{inner_instrutions:#?}"), "after-transform-switch"); cancel!(); - // parse_chain_goto(&mut inner_instrutions)?; - // trace!(target: TARGET, inner_instrutions = format_args!("{inner_instrutions:#?}"), "after-parse-chain-goto"); - // cancel!(); - parse_return(&mut inner_instrutions)?; trace!(target: TARGET, inner_instrutions = format_args!("{inner_instrutions:#?}"), "after-parse-return"); cancel!(); diff --git a/bcode-flow/src/parse_forward.rs b/bcode-flow/src/parse_forward.rs index 54605b8..3cf1365 100644 --- a/bcode-flow/src/parse_forward.rs +++ b/bcode-flow/src/parse_forward.rs @@ -556,13 +556,8 @@ impl InnerInstructionVisitor for Stage1 { fn node_enter(&mut self, node: &InnerInstruction) -> Result<(), FlowError> { self.unsolved_seg.push(self.unsolved.len()); match &node.spec { - InnerInstructionSpec::Flow(Flow::Goto { - offsets, - _parsed: _, - }) => { - for &offset in offsets { - self.unsolved.push(node.pos + offset); - } + InnerInstructionSpec::Flow(Flow::Goto { offset }) => { + self.unsolved.push(node.pos + offset); } &InnerInstructionSpec::Flow(Flow::If(_, offset)) | &InnerInstructionSpec::Flow(Flow::IfACmp(_, offset)) @@ -872,25 +867,19 @@ impl InnerInstructionVisitorMut for Stage3 { body: _, past_end_pos, } => self.entered_loops.push((node.pos, past_end_pos)), - InnerInstructionSpec::Flow(Flow::Goto { - offsets, - _parsed: _, - }) => { - for &mut offset in offsets { - let tgt = node.pos + offset; - if let Some(&(block_pos, block_past_end_pos)) = self - .entered_loops - .iter() - .rev() - .find(|(_, past_end)| *past_end == tgt) - { - node.spec = InnerInstructionSpec::ForwardBreak { - condition: None, - block_pos, - block_past_end_pos, - }; - break; - } + InnerInstructionSpec::Flow(Flow::Goto { offset }) => { + let tgt = node.pos + *offset; + if let Some(&(block_pos, block_past_end_pos)) = self + .entered_loops + .iter() + .rev() + .find(|(_, past_end)| *past_end == tgt) + { + node.spec = InnerInstructionSpec::ForwardBreak { + condition: None, + block_pos, + block_past_end_pos, + }; } } &mut InnerInstructionSpec::Flow(Flow::If(cmp, offset)) => { diff --git a/bcode-flow/src/parse_loops.rs b/bcode-flow/src/parse_loops.rs index 1173c33..ca94839 100644 --- a/bcode-flow/src/parse_loops.rs +++ b/bcode-flow/src/parse_loops.rs @@ -35,10 +35,8 @@ impl InnerInstructionVisitor for CollectLoops { | Flow::Return(_) | Flow::LookupSwitch { .. } | Flow::TableSwitch { .. } => {} - Flow::Goto { offsets, _parsed } => { - for &offset in offsets { - check_offset!(offset); - } + Flow::Goto { offset } => { + check_offset!(*offset); } &Flow::IfACmp(_, offset) | &Flow::IfICmp(_, offset) @@ -180,9 +178,7 @@ impl InnerInstructionVisitorMut for FormLoops { | Flow::Return(_) | Flow::LookupSwitch { .. } | Flow::TableSwitch { .. } => None, - Flow::Goto { offsets, _parsed } => { - offsets.iter().find_map(|offset| check_offset!(offset)) - } + Flow::Goto { offset } => check_offset!(offset), &Flow::IfACmp(bin_cmp, offset) => { check_offset!(i32::from(offset), Condition::IfACmp(bin_cmp)) } diff --git a/bcode-flow/src/parse_try.rs b/bcode-flow/src/parse_try.rs index e454ffe..60ff722 100644 --- a/bcode-flow/src/parse_try.rs +++ b/bcode-flow/src/parse_try.rs @@ -233,10 +233,7 @@ fn collect_handlers( } body.push(InnerInstruction { pos: jumpto, - spec: InnerInstructionSpec::Flow(Flow::Goto { - offsets: alloc::vec![0], - _parsed: false, - }), + spec: InnerInstructionSpec::Flow(Flow::Goto { offset: 0 }), artificial: true, }); } diff --git a/bcode-flow/src/util.rs b/bcode-flow/src/util.rs index 21028cb..f72f184 100644 --- a/bcode-flow/src/util.rs +++ b/bcode-flow/src/util.rs @@ -38,8 +38,8 @@ impl InnerInstructionVisitor for AddJumps<'_> { }; match flow { Flow::Throw | Flow::Return(_) => {} - Flow::Goto { offsets, _parsed } => { - offsets.iter().copied().for_each(add); + Flow::Goto { offset } => { + add(*offset); } &Flow::IfICmp(_, offset) | &Flow::If(_, offset) @@ -93,8 +93,8 @@ impl InnerInstructionVisitor for RemoveJumps<'_> { } match flow { Flow::Throw | Flow::Return(_) => {} - Flow::Goto { offsets, _parsed } => { - offsets.iter().copied().for_each(rem); + Flow::Goto { offset } => { + rem(*offset); } &Flow::IfICmp(_, offset) | &Flow::If(_, offset) @@ -208,11 +208,8 @@ fn node_traverseable( goto_acceptor(pos); }; let res = match &node.spec { - InnerInstructionSpec::Flow(Flow::Goto { offsets, _parsed }) => { - offsets - .iter() - .copied() - .for_each(|offset| goto_acceptor(node.pos + offset)); + InnerInstructionSpec::Flow(Flow::Goto { offset }) => { + goto_acceptor(node.pos + offset); false } &InnerInstructionSpec::Flow( @@ -768,13 +765,8 @@ pub(crate) fn reachable_from( // match never falls through - it spawns walkers instead break; } - InnerInstructionSpec::Flow(Flow::Goto { - offsets, - _parsed: _, - }) => { - for &offset in offsets { - spawn_walker!(offset); - } + InnerInstructionSpec::Flow(Flow::Goto { offset }) => { + spawn_walker!(offset); // goto never falls through as well break; } From bf853e791dc676aaa887183a191fdab574a3a79f Mon Sep 17 00:00:00 2001 From: dzu Date: Sat, 21 Feb 2026 09:29:53 +0000 Subject: [PATCH 4/4] CI: add dependencies audit --- .forgejo/workflows/audit.yml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 .forgejo/workflows/audit.yml diff --git a/.forgejo/workflows/audit.yml b/.forgejo/workflows/audit.yml new file mode 100644 index 0000000..4c5d892 --- /dev/null +++ b/.forgejo/workflows/audit.yml @@ -0,0 +1,17 @@ +name: Dependencies audit +enable-email-notifications: true + +on: + push: + workflow_dispatch: + + +jobs: + cargo_audit: + runs-on: cargo-audit + steps: + - name: Checkout code + uses: https://git.dzuchun.ing/actions/checkout@v1 + + - name: Cargo audit + uses: https://git.dzuchun.ing/actions/cargo-audit@main