diff --git a/.forgejo/workflows/audit.yml b/.forgejo/workflows/audit.yml deleted file mode 100644 index 4c5d892..0000000 --- a/.forgejo/workflows/audit.yml +++ /dev/null @@ -1,17 +0,0 @@ -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 diff --git a/bcode-flow/Cargo.toml b/bcode-flow/Cargo.toml index 4e916fa..69d86ff 100644 --- a/bcode-flow/Cargo.toml +++ b/bcode-flow/Cargo.toml @@ -9,8 +9,5 @@ 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/instruction_kind.rs b/bcode-flow/src/instruction_kind.rs index 37151dc..9a0503a 100644 --- a/bcode-flow/src/instruction_kind.rs +++ b/bcode-flow/src/instruction_kind.rs @@ -31,7 +31,10 @@ pub(crate) enum Flow { Return(ReturnType), // TODO: unify with ifs Goto { - offset: i32, + // invariant: sorted + // TODO: REMOVE + offsets: Vec, + _parsed: bool, }, /// > ref, ref IfACmp(BinCmp, i16), @@ -62,7 +65,7 @@ impl Flow { | Flow::LookupSwitch { .. } | Flow::TableSwitch { .. } | Flow::Throw => None, - Flow::Goto { offset, .. } => Some(*offset), + Flow::Goto { offsets, .. } => Some(offsets[0]), Flow::IfACmp(_, offset) | Flow::IfICmp(_, offset) | Flow::If(_, offset) @@ -79,7 +82,11 @@ impl Flow { | Flow::LookupSwitch { .. } | Flow::TableSwitch { .. } | Flow::Return(_) => {} - Flow::Goto { offset, .. } => visitor(*offset)?, + Flow::Goto { offsets, .. } => { + for &offset in offsets { + visitor(offset)?; + } + } Flow::IfACmp(_, offset) | Flow::IfICmp(_, offset) | Flow::If(_, offset) @@ -181,7 +188,10 @@ 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 { offset }), + InstructionSpec::Goto(offset) => Self::Flow(Flow::Goto { + offsets: vec![offset], + _parsed: false, + }), 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 912ff79..4942d45 100644 --- a/bcode-flow/src/lib.rs +++ b/bcode-flow/src/lib.rs @@ -9,6 +9,7 @@ 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; @@ -34,6 +35,8 @@ 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"; @@ -99,12 +102,14 @@ pub enum FlowError { UnusedNegativeHandler(parse_try::NegativeHandlerRange), UnusedHandler(parse_try::HandlerRange), Internal(InternalError), + /// Loop of gotos + GotoLoop, #[cfg(test)] Cancelled, } // TODO: remove; silly me did not explicitly join a killed thread, and got main thread panic as a result >_< -#[cfg(any(test, feature = "cancel"))] +#[cfg(test)] mod cancel { use std::sync::atomic::{AtomicBool, Ordering}; extern crate std; @@ -125,7 +130,7 @@ pub use cancel::{reset as cancel_reset, trap as cancel_trap}; macro_rules! cancel { () => { - #[cfg(any(test, feature = "cancel"))] + #[cfg(test)] if $crate::cancel::CANCEL.load(std::sync::atomic::Ordering::Relaxed) { return Err($crate::FlowError::Cancelled); } @@ -482,8 +487,11 @@ impl InnerInstruction { }; visitor.node_enter(self, order)?; let res = match &self.spec { - InnerInstructionSpec::Flow(Flow::Goto { offset }) => { - goto_acceptor(self.pos + offset); + InnerInstructionSpec::Flow(Flow::Goto { offsets, _parsed }) => { + offsets + .iter() + .copied() + .for_each(|offset| goto_acceptor(self.pos + offset)); false } &InnerInstructionSpec::Flow( @@ -1344,8 +1352,13 @@ 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 { offset }) = &node.spec - && let Some(&tp) = self.returns.get(&(node.pos + offset)) + if let InnerInstructionSpec::Flow(Flow::Goto { + offsets, + _parsed: _, + }) = &node.spec + && let Some(&tp) = offsets + .iter() + .find_map(|offset| self.returns.get(&(node.pos + offset))) { node.spec = InnerInstructionSpec::Return(tp); } @@ -1402,6 +1415,142 @@ 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> { @@ -1412,11 +1561,14 @@ fn parse_nop_goto( instructions: &mut [InnerInstruction], ) -> Result<(), FlowError> { for i in 0..instructions.len() - 1 { - let InnerInstructionSpec::Flow(Flow::Goto { offset }) = &instructions[i].spec + let InnerInstructionSpec::Flow(Flow::Goto { offsets, .. }) = &instructions[i].spec else { continue; }; - if *offset == instructions[i + 1].pos - instructions[i].pos { + if offsets + .binary_search(&(instructions[i + 1].pos - instructions[i].pos)) + .is_ok() + { // that's a nop goto :/ instructions[i].spec = InnerInstructionSpec::NopGoto; } @@ -1452,6 +1604,10 @@ 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!(); @@ -1475,10 +1631,21 @@ 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_forward.rs b/bcode-flow/src/parse_forward.rs index 3cf1365..54605b8 100644 --- a/bcode-flow/src/parse_forward.rs +++ b/bcode-flow/src/parse_forward.rs @@ -556,8 +556,13 @@ 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 { offset }) => { - self.unsolved.push(node.pos + offset); + InnerInstructionSpec::Flow(Flow::Goto { + offsets, + _parsed: _, + }) => { + for &offset in offsets { + self.unsolved.push(node.pos + offset); + } } &InnerInstructionSpec::Flow(Flow::If(_, offset)) | &InnerInstructionSpec::Flow(Flow::IfACmp(_, offset)) @@ -867,19 +872,25 @@ impl InnerInstructionVisitorMut for Stage3 { body: _, past_end_pos, } => self.entered_loops.push((node.pos, past_end_pos)), - 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, - }; + 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; + } } } &mut InnerInstructionSpec::Flow(Flow::If(cmp, offset)) => { diff --git a/bcode-flow/src/parse_if.rs b/bcode-flow/src/parse_if.rs new file mode 100644 index 0000000..4366cae --- /dev/null +++ b/bcode-flow/src/parse_if.rs @@ -0,0 +1,421 @@ +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_loops.rs b/bcode-flow/src/parse_loops.rs index ca94839..1173c33 100644 --- a/bcode-flow/src/parse_loops.rs +++ b/bcode-flow/src/parse_loops.rs @@ -35,8 +35,10 @@ impl InnerInstructionVisitor for CollectLoops { | Flow::Return(_) | Flow::LookupSwitch { .. } | Flow::TableSwitch { .. } => {} - Flow::Goto { offset } => { - check_offset!(*offset); + Flow::Goto { offsets, _parsed } => { + for &offset in offsets { + check_offset!(offset); + } } &Flow::IfACmp(_, offset) | &Flow::IfICmp(_, offset) @@ -178,7 +180,9 @@ impl InnerInstructionVisitorMut for FormLoops { | Flow::Return(_) | Flow::LookupSwitch { .. } | Flow::TableSwitch { .. } => None, - Flow::Goto { offset } => check_offset!(offset), + Flow::Goto { offsets, _parsed } => { + offsets.iter().find_map(|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_switch.rs b/bcode-flow/src/parse_switch.rs new file mode 100644 index 0000000..10cac77 --- /dev/null +++ b/bcode-flow/src/parse_switch.rs @@ -0,0 +1,364 @@ +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) +} diff --git a/bcode-flow/src/parse_try.rs b/bcode-flow/src/parse_try.rs index 60ff722..e454ffe 100644 --- a/bcode-flow/src/parse_try.rs +++ b/bcode-flow/src/parse_try.rs @@ -233,7 +233,10 @@ fn collect_handlers( } body.push(InnerInstruction { pos: jumpto, - spec: InnerInstructionSpec::Flow(Flow::Goto { offset: 0 }), + spec: InnerInstructionSpec::Flow(Flow::Goto { + offsets: alloc::vec![0], + _parsed: false, + }), artificial: true, }); } diff --git a/bcode-flow/src/util.rs b/bcode-flow/src/util.rs index f72f184..21028cb 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 { offset } => { - add(*offset); + Flow::Goto { offsets, _parsed } => { + offsets.iter().copied().for_each(add); } &Flow::IfICmp(_, offset) | &Flow::If(_, offset) @@ -93,8 +93,8 @@ impl InnerInstructionVisitor for RemoveJumps<'_> { } match flow { Flow::Throw | Flow::Return(_) => {} - Flow::Goto { offset } => { - rem(*offset); + Flow::Goto { offsets, _parsed } => { + offsets.iter().copied().for_each(rem); } &Flow::IfICmp(_, offset) | &Flow::If(_, offset) @@ -208,8 +208,11 @@ fn node_traverseable( goto_acceptor(pos); }; let res = match &node.spec { - InnerInstructionSpec::Flow(Flow::Goto { offset }) => { - goto_acceptor(node.pos + offset); + InnerInstructionSpec::Flow(Flow::Goto { offsets, _parsed }) => { + offsets + .iter() + .copied() + .for_each(|offset| goto_acceptor(node.pos + offset)); false } &InnerInstructionSpec::Flow( @@ -765,8 +768,13 @@ pub(crate) fn reachable_from( // match never falls through - it spawns walkers instead break; } - InnerInstructionSpec::Flow(Flow::Goto { offset }) => { - spawn_walker!(offset); + InnerInstructionSpec::Flow(Flow::Goto { + offsets, + _parsed: _, + }) => { + for &offset in offsets { + spawn_walker!(offset); + } // goto never falls through as well break; }