Compare commits

..
10 changed files with 1027 additions and 59 deletions

View file

@ -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

View file

@ -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"]}

View file

@ -31,7 +31,10 @@ pub(crate) enum Flow {
Return(ReturnType),
// TODO: unify with ifs
Goto {
offset: i32,
// invariant: sorted
// TODO: REMOVE
offsets: Vec<i32>,
_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<S: TextStorage> InstructionKind<S> {
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)),

View file

@ -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<S: TextStorage> InnerInstruction<S> {
};
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<S: TextStorage>(instructions: &mut [InnerInstruction<S>]) -> Res
}
impl<S: TextStorage> InnerInstructionVisitorMut<S> for ParseReturn {
fn node_enter(&mut self, node: &mut InnerInstruction<S>) -> 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<S: TextStorage>(
Ok(())
}
fn parse_chain_goto<S: TextStorage>(
instructions: &mut [InnerInstruction<S>],
) -> Result<(), FlowError> {
#[derive(Debug, Default, Clone, Copy, PartialEq)]
enum GotoState {
#[default]
Initial,
Active,
Computed,
}
struct CollectGotos {
valid_targets: BTreeMap<i32, u16>,
gotos: BTreeMap<i32, (Vec<i32>, GotoState)>,
}
impl<S: TextStorage> InnerInstructionVisitor<S> for CollectGotos {
fn nodes_enter(&mut self, nodes: &[InnerInstruction<S>]) -> 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<S>]) -> 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<S>) -> 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<i32>, 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<i32, Vec<i32>>,
}
impl<S: TextStorage> InnerInstructionVisitorMut<S> for AssignGotos {
fn node_enter(&mut self, node: &mut InnerInstruction<S>) -> 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::<Vec<_>>();
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<S: TextStorage>(
instructions: &mut [InnerInstruction<S>],
) -> Result<(), FlowError> {
@ -1412,11 +1561,14 @@ fn parse_nop_goto<S: TextStorage>(
instructions: &mut [InnerInstruction<S>],
) -> 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<S: TextStorage>(code: &Code<S>) -> Result<Instructions<S>, 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<S: TextStorage>(code: &Code<S>) -> Result<Instructions<S>, 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");

View file

@ -556,9 +556,14 @@ impl<S: TextStorage> InnerInstructionVisitor<S> for Stage1 {
fn node_enter(&mut self, node: &InnerInstruction<S>) -> Result<(), FlowError> {
self.unsolved_seg.push(self.unsolved.len());
match &node.spec {
InnerInstructionSpec::Flow(Flow::Goto { 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))
| &InnerInstructionSpec::Flow(Flow::IfICmp(_, offset))
@ -867,8 +872,12 @@ impl<S: TextStorage> InnerInstructionVisitorMut<S> 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;
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()
@ -880,6 +889,8 @@ impl<S: TextStorage> InnerInstructionVisitorMut<S> for Stage3 {
block_pos,
block_past_end_pos,
};
break;
}
}
}
&mut InnerInstructionSpec::Flow(Flow::If(cmp, offset)) => {

421
bcode-flow/src/parse_if.rs Normal file
View file

@ -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<S: TextStorage> InnerInstructionVisitor<S> for Find {
fn node_enter(&mut self, node: &InnerInstruction<S>) -> 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<SuperIf>,
}
impl<S: TextStorage> InnerInstructionVisitor<S> for Parse {
fn nodes_enter(&mut self, nodes: &[InnerInstruction<S>]) -> 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::<Vec<_>>();
// 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<SuperIf>,
// TODO: maybe use `Rc` instead of `Box`
superif_jump_map: BTreeMap<i32, (i32, usize)>,
}
impl<S: TextStorage> InnerInstructionVisitorMut<S> for TransformSuperIf {
fn nodes_enter(&mut self, nodes: &mut [InnerInstruction<S>]) -> 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::<Vec<_>>()
.into_boxed_slice();
if condition.is_empty() {
internal_error!("empty-condition");
}
// populate the branches
let mut branches: Box<[Vec<InnerInstruction<S>>]> =
vec![vec![]; superif.destinations.len()].into_boxed_slice();
let mut active_brances = BTreeSet::<usize>::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::<Vec<_>>()
.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<S>) -> 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::<Condition>::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<S: TextStorage> InnerInstructionVisitorMut<S> for ConditionRevolve0 {
fn node_enter(&mut self, node: &mut InnerInstruction<S>) -> 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<S: TextStorage>(instructions: &mut [InnerInstruction<S>]) -> 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(())
}

View file

@ -35,8 +35,10 @@ impl<S: TextStorage> InnerInstructionVisitor<S> 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<S: TextStorage> InnerInstructionVisitorMut<S> 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))
}

View file

@ -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<S: TextStorage> {
// start_ind: usize,
// #[allow(clippy::type_complexity)]
// branches: Box<[(Option<i32>, Vec<InnerInstruction<S>>)]>,
// inactive_branches: BinaryHeap<(
// /* start index */ Reverse<usize>,
// /* branch index */ usize,
// )>,
// active_branches: Vec<usize>,
// breaks: BinaryHeap<Reverse<usize>>,
// past_end_ind: usize,
// }
// impl<S: TextStorage> SwitchData<S> {
// fn push_instruction(&mut self, instruction: InnerInstruction<S>) {
// 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<S: TextStorage>(
// instructions: &mut [InnerInstruction<S>],
// ) -> 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::<SwitchData<S>>::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::<Vec<_>>()
// .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<i32>)>::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<i32>)>::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<i32>, Vec<InnerInstruction<S>>)> =
// Vec::with_capacity(raw_branches.len());
// let mut inactive_branches: BinaryHeap<(
// /* start index */ Reverse<usize>,
// /* branch index */ usize,
// )> = BinaryHeap::with_capacity(raw_branches.len());
// let active_branches: Vec<usize> = Vec::with_capacity(raw_branches.len());
// let mut breaks: BinaryHeap<Reverse<usize>> = BinaryHeap::with_capacity(raw_branches.len());
// let mut max_match_jump = 0;
// let mut break_jumps: Option<BTreeSet<usize>> = 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<usize> = 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<usize>| {
// 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<S: TextStorage> InnerInstructionVisitorMut<S> for Match {
fn nodes_enter(&mut self, nodes: &mut [InnerInstruction<S>]) -> 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::<Box<[InnerInstruction<S>]>>::new();
let mut matches = Vec::<(Option<i32>, usize)>::new();
let mut offset_map = BTreeMap::<i32, usize>::new();
let mut process_offset = |mch: Option<i32>, 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::<Vec<_>>();
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<S: TextStorage>(
instructions: &mut [InnerInstruction<S>],
) -> 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)
}

View file

@ -233,7 +233,10 @@ fn collect_handlers<S: TextStorage>(
}
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,
});
}

View file

@ -38,8 +38,8 @@ impl<S: TextStorage> InnerInstructionVisitor<S> 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<S: TextStorage> InnerInstructionVisitor<S> 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<S: TextStorage>(
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<S: TextStorage>(
// match never falls through - it spawns walkers instead
break;
}
InnerInstructionSpec::Flow(Flow::Goto { offset }) => {
InnerInstructionSpec::Flow(Flow::Goto {
offsets,
_parsed: _,
}) => {
for &offset in offsets {
spawn_walker!(offset);
}
// goto never falls through as well
break;
}