Compare commits
10 changed files with 1027 additions and 59 deletions
|
|
@ -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
|
|
||||||
|
|
@ -9,8 +9,5 @@ ferrobean-parser-raw.workspace = true
|
||||||
ferrobean-common.workspace = true
|
ferrobean-common.workspace = true
|
||||||
tracing.workspace = true
|
tracing.workspace = true
|
||||||
|
|
||||||
[features]
|
|
||||||
cancel = []
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
test-utils = {workspace = true, features = ["resolve"]}
|
test-utils = {workspace = true, features = ["resolve"]}
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,10 @@ pub(crate) enum Flow {
|
||||||
Return(ReturnType),
|
Return(ReturnType),
|
||||||
// TODO: unify with ifs
|
// TODO: unify with ifs
|
||||||
Goto {
|
Goto {
|
||||||
offset: i32,
|
// invariant: sorted
|
||||||
|
// TODO: REMOVE
|
||||||
|
offsets: Vec<i32>,
|
||||||
|
_parsed: bool,
|
||||||
},
|
},
|
||||||
/// > ref, ref
|
/// > ref, ref
|
||||||
IfACmp(BinCmp, i16),
|
IfACmp(BinCmp, i16),
|
||||||
|
|
@ -62,7 +65,7 @@ impl Flow {
|
||||||
| Flow::LookupSwitch { .. }
|
| Flow::LookupSwitch { .. }
|
||||||
| Flow::TableSwitch { .. }
|
| Flow::TableSwitch { .. }
|
||||||
| Flow::Throw => None,
|
| Flow::Throw => None,
|
||||||
Flow::Goto { offset, .. } => Some(*offset),
|
Flow::Goto { offsets, .. } => Some(offsets[0]),
|
||||||
Flow::IfACmp(_, offset)
|
Flow::IfACmp(_, offset)
|
||||||
| Flow::IfICmp(_, offset)
|
| Flow::IfICmp(_, offset)
|
||||||
| Flow::If(_, offset)
|
| Flow::If(_, offset)
|
||||||
|
|
@ -79,7 +82,11 @@ impl Flow {
|
||||||
| Flow::LookupSwitch { .. }
|
| Flow::LookupSwitch { .. }
|
||||||
| Flow::TableSwitch { .. }
|
| Flow::TableSwitch { .. }
|
||||||
| Flow::Return(_) => {}
|
| Flow::Return(_) => {}
|
||||||
Flow::Goto { offset, .. } => visitor(*offset)?,
|
Flow::Goto { offsets, .. } => {
|
||||||
|
for &offset in offsets {
|
||||||
|
visitor(offset)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
Flow::IfACmp(_, offset)
|
Flow::IfACmp(_, offset)
|
||||||
| Flow::IfICmp(_, offset)
|
| Flow::IfICmp(_, offset)
|
||||||
| Flow::If(_, offset)
|
| Flow::If(_, offset)
|
||||||
|
|
@ -181,7 +188,10 @@ impl<S: TextStorage> InstructionKind<S> {
|
||||||
InstructionSpec::Dup2X2 => Self::NonFlow(NonFlow::Dup2X2),
|
InstructionSpec::Dup2X2 => Self::NonFlow(NonFlow::Dup2X2),
|
||||||
InstructionSpec::GetField(field_ref) => Self::NonFlow(NonFlow::GetField(field_ref)),
|
InstructionSpec::GetField(field_ref) => Self::NonFlow(NonFlow::GetField(field_ref)),
|
||||||
InstructionSpec::GetStatic(field_ref) => Self::NonFlow(NonFlow::GetStatic(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::IfACmp(bin_cmp, offset) => Self::Flow(Flow::IfACmp(bin_cmp, offset)),
|
||||||
InstructionSpec::IfICmp(cmp, offset) => Self::Flow(Flow::IfICmp(cmp, offset)),
|
InstructionSpec::IfICmp(cmp, offset) => Self::Flow(Flow::IfICmp(cmp, offset)),
|
||||||
InstructionSpec::If(cmp, offset) => Self::Flow(Flow::If(cmp, offset)),
|
InstructionSpec::If(cmp, offset) => Self::Flow(Flow::If(cmp, offset)),
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ extern crate tracing;
|
||||||
use alloc::collections::BTreeMap;
|
use alloc::collections::BTreeMap;
|
||||||
use alloc::collections::BTreeSet;
|
use alloc::collections::BTreeSet;
|
||||||
use alloc::{boxed::Box, sync::Arc, vec, vec::Vec};
|
use alloc::{boxed::Box, sync::Arc, vec, vec::Vec};
|
||||||
|
use core::ops::{AddAssign, SubAssign};
|
||||||
use core::{mem, num::NonZeroU8};
|
use core::{mem, num::NonZeroU8};
|
||||||
use ferrobean_parser_resolve::attribute::Code;
|
use ferrobean_parser_resolve::attribute::Code;
|
||||||
use ferrobean_parser_resolve::bcode::tag::LookupPair;
|
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: &str = "ferrobean-top-loop";
|
||||||
const TARGET_LOOP: &str = "ferrobean-loops";
|
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_FWD: &str = "ferrobean-forward";
|
||||||
const TARGET_TRY: &str = "ferrobean-try";
|
const TARGET_TRY: &str = "ferrobean-try";
|
||||||
const TARGET_PRUNE: &str = "ferrobean-prune";
|
const TARGET_PRUNE: &str = "ferrobean-prune";
|
||||||
|
|
@ -99,12 +102,14 @@ pub enum FlowError {
|
||||||
UnusedNegativeHandler(parse_try::NegativeHandlerRange),
|
UnusedNegativeHandler(parse_try::NegativeHandlerRange),
|
||||||
UnusedHandler(parse_try::HandlerRange),
|
UnusedHandler(parse_try::HandlerRange),
|
||||||
Internal(InternalError),
|
Internal(InternalError),
|
||||||
|
/// Loop of gotos
|
||||||
|
GotoLoop,
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
Cancelled,
|
Cancelled,
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: remove; silly me did not explicitly join a killed thread, and got main thread panic as a result >_<
|
// 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 {
|
mod cancel {
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
extern crate std;
|
extern crate std;
|
||||||
|
|
@ -125,7 +130,7 @@ pub use cancel::{reset as cancel_reset, trap as cancel_trap};
|
||||||
|
|
||||||
macro_rules! cancel {
|
macro_rules! cancel {
|
||||||
() => {
|
() => {
|
||||||
#[cfg(any(test, feature = "cancel"))]
|
#[cfg(test)]
|
||||||
if $crate::cancel::CANCEL.load(std::sync::atomic::Ordering::Relaxed) {
|
if $crate::cancel::CANCEL.load(std::sync::atomic::Ordering::Relaxed) {
|
||||||
return Err($crate::FlowError::Cancelled);
|
return Err($crate::FlowError::Cancelled);
|
||||||
}
|
}
|
||||||
|
|
@ -482,8 +487,11 @@ impl<S: TextStorage> InnerInstruction<S> {
|
||||||
};
|
};
|
||||||
visitor.node_enter(self, order)?;
|
visitor.node_enter(self, order)?;
|
||||||
let res = match &self.spec {
|
let res = match &self.spec {
|
||||||
InnerInstructionSpec::Flow(Flow::Goto { offset }) => {
|
InnerInstructionSpec::Flow(Flow::Goto { offsets, _parsed }) => {
|
||||||
goto_acceptor(self.pos + offset);
|
offsets
|
||||||
|
.iter()
|
||||||
|
.copied()
|
||||||
|
.for_each(|offset| goto_acceptor(self.pos + offset));
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
&InnerInstructionSpec::Flow(
|
&InnerInstructionSpec::Flow(
|
||||||
|
|
@ -1344,8 +1352,13 @@ fn parse_return<S: TextStorage>(instructions: &mut [InnerInstruction<S>]) -> Res
|
||||||
}
|
}
|
||||||
impl<S: TextStorage> InnerInstructionVisitorMut<S> for ParseReturn {
|
impl<S: TextStorage> InnerInstructionVisitorMut<S> for ParseReturn {
|
||||||
fn node_enter(&mut self, node: &mut InnerInstruction<S>) -> Result<(), FlowError> {
|
fn node_enter(&mut self, node: &mut InnerInstruction<S>) -> Result<(), FlowError> {
|
||||||
if let InnerInstructionSpec::Flow(Flow::Goto { offset }) = &node.spec
|
if let InnerInstructionSpec::Flow(Flow::Goto {
|
||||||
&& let Some(&tp) = self.returns.get(&(node.pos + offset))
|
offsets,
|
||||||
|
_parsed: _,
|
||||||
|
}) = &node.spec
|
||||||
|
&& let Some(&tp) = offsets
|
||||||
|
.iter()
|
||||||
|
.find_map(|offset| self.returns.get(&(node.pos + offset)))
|
||||||
{
|
{
|
||||||
node.spec = InnerInstructionSpec::Return(tp);
|
node.spec = InnerInstructionSpec::Return(tp);
|
||||||
}
|
}
|
||||||
|
|
@ -1402,6 +1415,142 @@ fn transform_switch<S: TextStorage>(
|
||||||
Ok(())
|
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>(
|
fn parse_nop_goto<S: TextStorage>(
|
||||||
instructions: &mut [InnerInstruction<S>],
|
instructions: &mut [InnerInstruction<S>],
|
||||||
) -> Result<(), FlowError> {
|
) -> Result<(), FlowError> {
|
||||||
|
|
@ -1412,11 +1561,14 @@ fn parse_nop_goto<S: TextStorage>(
|
||||||
instructions: &mut [InnerInstruction<S>],
|
instructions: &mut [InnerInstruction<S>],
|
||||||
) -> Result<(), FlowError> {
|
) -> Result<(), FlowError> {
|
||||||
for i in 0..instructions.len() - 1 {
|
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 {
|
else {
|
||||||
continue;
|
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 :/
|
// that's a nop goto :/
|
||||||
instructions[i].spec = InnerInstructionSpec::NopGoto;
|
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");
|
trace!(target: TARGET, inner_instrutions = format_args!("{inner_instrutions:#?}"), "after-transform-switch");
|
||||||
cancel!();
|
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)?;
|
parse_return(&mut inner_instrutions)?;
|
||||||
trace!(target: TARGET, inner_instrutions = format_args!("{inner_instrutions:#?}"), "after-parse-return");
|
trace!(target: TARGET, inner_instrutions = format_args!("{inner_instrutions:#?}"), "after-parse-return");
|
||||||
cancel!();
|
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");
|
// trace!(target: TARGET, inner_instrutions = format_args!("{inner_instrutions:#?}"), "after-prune");
|
||||||
// cancel!();
|
// 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)?;
|
parse_loops::parse_loops(&mut inner_instrutions)?;
|
||||||
trace!(target: TARGET, inner_instrutions = format_args!("{inner_instrutions:#?}"), "after-parse-loops");
|
trace!(target: TARGET, inner_instrutions = format_args!("{inner_instrutions:#?}"), "after-parse-loops");
|
||||||
cancel!();
|
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
|
// no inner prune should be done up until this point
|
||||||
parse_forward::parse_forward(&mut inner_instrutions)?;
|
parse_forward::parse_forward(&mut inner_instrutions)?;
|
||||||
trace!(target: TARGET, inner_instrutions = format_args!("{inner_instrutions:#?}"), "after-parse-forward");
|
trace!(target: TARGET, inner_instrutions = format_args!("{inner_instrutions:#?}"), "after-parse-forward");
|
||||||
|
|
|
||||||
|
|
@ -556,9 +556,14 @@ impl<S: TextStorage> InnerInstructionVisitor<S> for Stage1 {
|
||||||
fn node_enter(&mut self, node: &InnerInstruction<S>) -> Result<(), FlowError> {
|
fn node_enter(&mut self, node: &InnerInstruction<S>) -> Result<(), FlowError> {
|
||||||
self.unsolved_seg.push(self.unsolved.len());
|
self.unsolved_seg.push(self.unsolved.len());
|
||||||
match &node.spec {
|
match &node.spec {
|
||||||
InnerInstructionSpec::Flow(Flow::Goto { offset }) => {
|
InnerInstructionSpec::Flow(Flow::Goto {
|
||||||
|
offsets,
|
||||||
|
_parsed: _,
|
||||||
|
}) => {
|
||||||
|
for &offset in offsets {
|
||||||
self.unsolved.push(node.pos + offset);
|
self.unsolved.push(node.pos + offset);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
&InnerInstructionSpec::Flow(Flow::If(_, offset))
|
&InnerInstructionSpec::Flow(Flow::If(_, offset))
|
||||||
| &InnerInstructionSpec::Flow(Flow::IfACmp(_, offset))
|
| &InnerInstructionSpec::Flow(Flow::IfACmp(_, offset))
|
||||||
| &InnerInstructionSpec::Flow(Flow::IfICmp(_, offset))
|
| &InnerInstructionSpec::Flow(Flow::IfICmp(_, offset))
|
||||||
|
|
@ -867,8 +872,12 @@ impl<S: TextStorage> InnerInstructionVisitorMut<S> for Stage3 {
|
||||||
body: _,
|
body: _,
|
||||||
past_end_pos,
|
past_end_pos,
|
||||||
} => self.entered_loops.push((node.pos, past_end_pos)),
|
} => self.entered_loops.push((node.pos, past_end_pos)),
|
||||||
InnerInstructionSpec::Flow(Flow::Goto { offset }) => {
|
InnerInstructionSpec::Flow(Flow::Goto {
|
||||||
let tgt = node.pos + *offset;
|
offsets,
|
||||||
|
_parsed: _,
|
||||||
|
}) => {
|
||||||
|
for &mut offset in offsets {
|
||||||
|
let tgt = node.pos + offset;
|
||||||
if let Some(&(block_pos, block_past_end_pos)) = self
|
if let Some(&(block_pos, block_past_end_pos)) = self
|
||||||
.entered_loops
|
.entered_loops
|
||||||
.iter()
|
.iter()
|
||||||
|
|
@ -880,6 +889,8 @@ impl<S: TextStorage> InnerInstructionVisitorMut<S> for Stage3 {
|
||||||
block_pos,
|
block_pos,
|
||||||
block_past_end_pos,
|
block_past_end_pos,
|
||||||
};
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&mut InnerInstructionSpec::Flow(Flow::If(cmp, offset)) => {
|
&mut InnerInstructionSpec::Flow(Flow::If(cmp, offset)) => {
|
||||||
|
|
|
||||||
421
bcode-flow/src/parse_if.rs
Normal file
421
bcode-flow/src/parse_if.rs
Normal 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(())
|
||||||
|
}
|
||||||
|
|
@ -35,8 +35,10 @@ impl<S: TextStorage> InnerInstructionVisitor<S> for CollectLoops {
|
||||||
| Flow::Return(_)
|
| Flow::Return(_)
|
||||||
| Flow::LookupSwitch { .. }
|
| Flow::LookupSwitch { .. }
|
||||||
| Flow::TableSwitch { .. } => {}
|
| Flow::TableSwitch { .. } => {}
|
||||||
Flow::Goto { offset } => {
|
Flow::Goto { offsets, _parsed } => {
|
||||||
check_offset!(*offset);
|
for &offset in offsets {
|
||||||
|
check_offset!(offset);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
&Flow::IfACmp(_, offset)
|
&Flow::IfACmp(_, offset)
|
||||||
| &Flow::IfICmp(_, offset)
|
| &Flow::IfICmp(_, offset)
|
||||||
|
|
@ -178,7 +180,9 @@ impl<S: TextStorage> InnerInstructionVisitorMut<S> for FormLoops {
|
||||||
| Flow::Return(_)
|
| Flow::Return(_)
|
||||||
| Flow::LookupSwitch { .. }
|
| Flow::LookupSwitch { .. }
|
||||||
| Flow::TableSwitch { .. } => None,
|
| 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) => {
|
&Flow::IfACmp(bin_cmp, offset) => {
|
||||||
check_offset!(i32::from(offset), Condition::IfACmp(bin_cmp))
|
check_offset!(i32::from(offset), Condition::IfACmp(bin_cmp))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
364
bcode-flow/src/parse_switch.rs
Normal file
364
bcode-flow/src/parse_switch.rs
Normal 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)
|
||||||
|
}
|
||||||
|
|
@ -233,7 +233,10 @@ fn collect_handlers<S: TextStorage>(
|
||||||
}
|
}
|
||||||
body.push(InnerInstruction {
|
body.push(InnerInstruction {
|
||||||
pos: jumpto,
|
pos: jumpto,
|
||||||
spec: InnerInstructionSpec::Flow(Flow::Goto { offset: 0 }),
|
spec: InnerInstructionSpec::Flow(Flow::Goto {
|
||||||
|
offsets: alloc::vec![0],
|
||||||
|
_parsed: false,
|
||||||
|
}),
|
||||||
artificial: true,
|
artificial: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,8 +38,8 @@ impl<S: TextStorage> InnerInstructionVisitor<S> for AddJumps<'_> {
|
||||||
};
|
};
|
||||||
match flow {
|
match flow {
|
||||||
Flow::Throw | Flow::Return(_) => {}
|
Flow::Throw | Flow::Return(_) => {}
|
||||||
Flow::Goto { offset } => {
|
Flow::Goto { offsets, _parsed } => {
|
||||||
add(*offset);
|
offsets.iter().copied().for_each(add);
|
||||||
}
|
}
|
||||||
&Flow::IfICmp(_, offset)
|
&Flow::IfICmp(_, offset)
|
||||||
| &Flow::If(_, offset)
|
| &Flow::If(_, offset)
|
||||||
|
|
@ -93,8 +93,8 @@ impl<S: TextStorage> InnerInstructionVisitor<S> for RemoveJumps<'_> {
|
||||||
}
|
}
|
||||||
match flow {
|
match flow {
|
||||||
Flow::Throw | Flow::Return(_) => {}
|
Flow::Throw | Flow::Return(_) => {}
|
||||||
Flow::Goto { offset } => {
|
Flow::Goto { offsets, _parsed } => {
|
||||||
rem(*offset);
|
offsets.iter().copied().for_each(rem);
|
||||||
}
|
}
|
||||||
&Flow::IfICmp(_, offset)
|
&Flow::IfICmp(_, offset)
|
||||||
| &Flow::If(_, offset)
|
| &Flow::If(_, offset)
|
||||||
|
|
@ -208,8 +208,11 @@ fn node_traverseable<S: TextStorage>(
|
||||||
goto_acceptor(pos);
|
goto_acceptor(pos);
|
||||||
};
|
};
|
||||||
let res = match &node.spec {
|
let res = match &node.spec {
|
||||||
InnerInstructionSpec::Flow(Flow::Goto { offset }) => {
|
InnerInstructionSpec::Flow(Flow::Goto { offsets, _parsed }) => {
|
||||||
goto_acceptor(node.pos + offset);
|
offsets
|
||||||
|
.iter()
|
||||||
|
.copied()
|
||||||
|
.for_each(|offset| goto_acceptor(node.pos + offset));
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
&InnerInstructionSpec::Flow(
|
&InnerInstructionSpec::Flow(
|
||||||
|
|
@ -765,8 +768,13 @@ pub(crate) fn reachable_from<S: TextStorage>(
|
||||||
// match never falls through - it spawns walkers instead
|
// match never falls through - it spawns walkers instead
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
InnerInstructionSpec::Flow(Flow::Goto { offset }) => {
|
InnerInstructionSpec::Flow(Flow::Goto {
|
||||||
|
offsets,
|
||||||
|
_parsed: _,
|
||||||
|
}) => {
|
||||||
|
for &offset in offsets {
|
||||||
spawn_walker!(offset);
|
spawn_walker!(offset);
|
||||||
|
}
|
||||||
// goto never falls through as well
|
// goto never falls through as well
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue