Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bf853e791d | |||
| 9cc8292492 | |||
| 1e7c7963a5 | |||
| 916ebb401f |
10 changed files with 59 additions and 1027 deletions
17
.forgejo/workflows/audit.yml
Normal file
17
.forgejo/workflows/audit.yml
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
name: Dependencies audit
|
||||||
|
enable-email-notifications: true
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
cargo_audit:
|
||||||
|
runs-on: cargo-audit
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: https://git.dzuchun.ing/actions/checkout@v1
|
||||||
|
|
||||||
|
- name: Cargo audit
|
||||||
|
uses: https://git.dzuchun.ing/actions/cargo-audit@main
|
||||||
|
|
@ -9,5 +9,8 @@ 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,10 +31,7 @@ pub(crate) enum Flow {
|
||||||
Return(ReturnType),
|
Return(ReturnType),
|
||||||
// TODO: unify with ifs
|
// TODO: unify with ifs
|
||||||
Goto {
|
Goto {
|
||||||
// invariant: sorted
|
offset: i32,
|
||||||
// TODO: REMOVE
|
|
||||||
offsets: Vec<i32>,
|
|
||||||
_parsed: bool,
|
|
||||||
},
|
},
|
||||||
/// > ref, ref
|
/// > ref, ref
|
||||||
IfACmp(BinCmp, i16),
|
IfACmp(BinCmp, i16),
|
||||||
|
|
@ -65,7 +62,7 @@ impl Flow {
|
||||||
| Flow::LookupSwitch { .. }
|
| Flow::LookupSwitch { .. }
|
||||||
| Flow::TableSwitch { .. }
|
| Flow::TableSwitch { .. }
|
||||||
| Flow::Throw => None,
|
| Flow::Throw => None,
|
||||||
Flow::Goto { offsets, .. } => Some(offsets[0]),
|
Flow::Goto { offset, .. } => Some(*offset),
|
||||||
Flow::IfACmp(_, offset)
|
Flow::IfACmp(_, offset)
|
||||||
| Flow::IfICmp(_, offset)
|
| Flow::IfICmp(_, offset)
|
||||||
| Flow::If(_, offset)
|
| Flow::If(_, offset)
|
||||||
|
|
@ -82,11 +79,7 @@ impl Flow {
|
||||||
| Flow::LookupSwitch { .. }
|
| Flow::LookupSwitch { .. }
|
||||||
| Flow::TableSwitch { .. }
|
| Flow::TableSwitch { .. }
|
||||||
| Flow::Return(_) => {}
|
| Flow::Return(_) => {}
|
||||||
Flow::Goto { offsets, .. } => {
|
Flow::Goto { offset, .. } => visitor(*offset)?,
|
||||||
for &offset in offsets {
|
|
||||||
visitor(offset)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Flow::IfACmp(_, offset)
|
Flow::IfACmp(_, offset)
|
||||||
| Flow::IfICmp(_, offset)
|
| Flow::IfICmp(_, offset)
|
||||||
| Flow::If(_, offset)
|
| Flow::If(_, offset)
|
||||||
|
|
@ -188,10 +181,7 @@ 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 {
|
InstructionSpec::Goto(offset) => Self::Flow(Flow::Goto { offset }),
|
||||||
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,7 +9,6 @@ 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;
|
||||||
|
|
@ -35,8 +34,6 @@ 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";
|
||||||
|
|
@ -102,14 +99,12 @@ 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(test)]
|
#[cfg(any(test, feature = "cancel"))]
|
||||||
mod cancel {
|
mod cancel {
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
extern crate std;
|
extern crate std;
|
||||||
|
|
@ -130,7 +125,7 @@ pub use cancel::{reset as cancel_reset, trap as cancel_trap};
|
||||||
|
|
||||||
macro_rules! cancel {
|
macro_rules! cancel {
|
||||||
() => {
|
() => {
|
||||||
#[cfg(test)]
|
#[cfg(any(test, feature = "cancel"))]
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|
@ -487,11 +482,8 @@ 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 { offsets, _parsed }) => {
|
InnerInstructionSpec::Flow(Flow::Goto { offset }) => {
|
||||||
offsets
|
goto_acceptor(self.pos + offset);
|
||||||
.iter()
|
|
||||||
.copied()
|
|
||||||
.for_each(|offset| goto_acceptor(self.pos + offset));
|
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
&InnerInstructionSpec::Flow(
|
&InnerInstructionSpec::Flow(
|
||||||
|
|
@ -1352,13 +1344,8 @@ 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 {
|
if let InnerInstructionSpec::Flow(Flow::Goto { offset }) = &node.spec
|
||||||
offsets,
|
&& let Some(&tp) = self.returns.get(&(node.pos + offset))
|
||||||
_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);
|
||||||
}
|
}
|
||||||
|
|
@ -1415,142 +1402,6 @@ 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> {
|
||||||
|
|
@ -1561,14 +1412,11 @@ 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 { offsets, .. }) = &instructions[i].spec
|
let InnerInstructionSpec::Flow(Flow::Goto { offset }) = &instructions[i].spec
|
||||||
else {
|
else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
if offsets
|
if *offset == instructions[i + 1].pos - instructions[i].pos {
|
||||||
.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;
|
||||||
}
|
}
|
||||||
|
|
@ -1604,10 +1452,6 @@ 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!();
|
||||||
|
|
@ -1631,21 +1475,10 @@ 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,14 +556,9 @@ 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 {
|
InnerInstructionSpec::Flow(Flow::Goto { offset }) => {
|
||||||
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))
|
||||||
|
|
@ -872,12 +867,8 @@ 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 {
|
InnerInstructionSpec::Flow(Flow::Goto { offset }) => {
|
||||||
offsets,
|
let tgt = node.pos + *offset;
|
||||||
_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()
|
||||||
|
|
@ -889,8 +880,6 @@ 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)) => {
|
||||||
|
|
|
||||||
|
|
@ -1,421 +0,0 @@
|
||||||
use alloc::{
|
|
||||||
boxed::Box,
|
|
||||||
collections::{BTreeMap, BTreeSet},
|
|
||||||
vec,
|
|
||||||
vec::Vec,
|
|
||||||
};
|
|
||||||
|
|
||||||
use ferrobean_common::TextStorage;
|
|
||||||
|
|
||||||
use crate::{Condition, FlowError, InnerInstruction, InnerInstructionSpec, instruction_kind::Flow};
|
|
||||||
use crate::{InnerInstructionVisitor, InnerInstructionVisitorMut};
|
|
||||||
|
|
||||||
use super::TARGET_IF as TARGET;
|
|
||||||
|
|
||||||
struct Find {
|
|
||||||
// if jump candidates
|
|
||||||
jumps: Vec<(i32, i32)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<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,10 +35,8 @@ impl<S: TextStorage> InnerInstructionVisitor<S> for CollectLoops {
|
||||||
| Flow::Return(_)
|
| Flow::Return(_)
|
||||||
| Flow::LookupSwitch { .. }
|
| Flow::LookupSwitch { .. }
|
||||||
| Flow::TableSwitch { .. } => {}
|
| Flow::TableSwitch { .. } => {}
|
||||||
Flow::Goto { offsets, _parsed } => {
|
Flow::Goto { offset } => {
|
||||||
for &offset in offsets {
|
check_offset!(*offset);
|
||||||
check_offset!(offset);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
&Flow::IfACmp(_, offset)
|
&Flow::IfACmp(_, offset)
|
||||||
| &Flow::IfICmp(_, offset)
|
| &Flow::IfICmp(_, offset)
|
||||||
|
|
@ -180,9 +178,7 @@ impl<S: TextStorage> InnerInstructionVisitorMut<S> for FormLoops {
|
||||||
| Flow::Return(_)
|
| Flow::Return(_)
|
||||||
| Flow::LookupSwitch { .. }
|
| Flow::LookupSwitch { .. }
|
||||||
| Flow::TableSwitch { .. } => None,
|
| Flow::TableSwitch { .. } => None,
|
||||||
Flow::Goto { offsets, _parsed } => {
|
Flow::Goto { offset } => check_offset!(offset),
|
||||||
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))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,364 +0,0 @@
|
||||||
use alloc::collections::BTreeMap;
|
|
||||||
|
|
||||||
use alloc::{boxed::Box, vec::Vec};
|
|
||||||
|
|
||||||
use ferrobean_common::TextStorage;
|
|
||||||
use ferrobean_parser_resolve::bcode::tag::LookupPair;
|
|
||||||
|
|
||||||
use crate::util::ForwardJumps;
|
|
||||||
use crate::{
|
|
||||||
FlowError, InnerInstruction, InnerInstructionSpec, InnerInstructionVisitorMut,
|
|
||||||
instruction_kind::Flow,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::TARGET_MATCH as TARGET;
|
|
||||||
|
|
||||||
// struct SwitchData<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,10 +233,7 @@ fn collect_handlers<S: TextStorage>(
|
||||||
}
|
}
|
||||||
body.push(InnerInstruction {
|
body.push(InnerInstruction {
|
||||||
pos: jumpto,
|
pos: jumpto,
|
||||||
spec: InnerInstructionSpec::Flow(Flow::Goto {
|
spec: InnerInstructionSpec::Flow(Flow::Goto { offset: 0 }),
|
||||||
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 { offsets, _parsed } => {
|
Flow::Goto { offset } => {
|
||||||
offsets.iter().copied().for_each(add);
|
add(*offset);
|
||||||
}
|
}
|
||||||
&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 { offsets, _parsed } => {
|
Flow::Goto { offset } => {
|
||||||
offsets.iter().copied().for_each(rem);
|
rem(*offset);
|
||||||
}
|
}
|
||||||
&Flow::IfICmp(_, offset)
|
&Flow::IfICmp(_, offset)
|
||||||
| &Flow::If(_, offset)
|
| &Flow::If(_, offset)
|
||||||
|
|
@ -208,11 +208,8 @@ 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 { offsets, _parsed }) => {
|
InnerInstructionSpec::Flow(Flow::Goto { offset }) => {
|
||||||
offsets
|
goto_acceptor(node.pos + offset);
|
||||||
.iter()
|
|
||||||
.copied()
|
|
||||||
.for_each(|offset| goto_acceptor(node.pos + offset));
|
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
&InnerInstructionSpec::Flow(
|
&InnerInstructionSpec::Flow(
|
||||||
|
|
@ -768,13 +765,8 @@ 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 {
|
InnerInstructionSpec::Flow(Flow::Goto { offset }) => {
|
||||||
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