start with finding dead stores

This commit is contained in:
nora 2022-04-17 21:42:36 +02:00
parent 039b5ea9c7
commit d5d80f79b7
8 changed files with 181 additions and 56 deletions

View file

@ -20,6 +20,9 @@ insta = "1.14.0"
[profile.release] [profile.release]
debug = true debug = true
[profile.dev]
opt-level = 3
[[bench]] [[bench]]
name = "opts" name = "opts"
harness = false harness = false

View file

@ -1,7 +0,0 @@
++++++++++[>++++++++++<-]>>++++++++++>->>>>>>>>>>>>>>>>-->+++++++[->++\n++++++++<]>[->+>+>+>+<<<<]+++>>+++>>>++++++++[-<
++++<++++<++++>>>]++++\n+[-<++++<++++>>]>>-->++++++[->+++++++++++<]>[->+>+>+>+<<<<]+++++>>+>++\n++++>++++++>++++++++[-<+
+++<++++<++++>>>]++++++[-<+++<+++<+++>>>]>>-->\n---+[-<+]-<[+[->+]-<<->>>+>[-]++[-->++]-->+++[---++[--<++]---->>-<+>[+\n
+++[----<++++]--[>]++[-->++]--<]>++[--+[-<+]->>[-]+++++[---->++++]-->[\n->+<]>>[.>]++[-->++]]-->+++]---+[-<+]->>-[+>>>+[
-<+]->>>++++++++++<<[-\n>+>-[>+>>]>[+[-<+>]>+>>]<<<<<<]>>[-]>>>++++++++++<[->-[>+>>]>[+[-<+>]>\n+>>]<<<<<]>[-]>>[>++++++
[-<++++++++>]<.<<+>+>[-]]<[<[->-<]++++++[->+++\n+++++<]>.[-]]<<++++++[-<++++++++>]<.[-]<<[-<+>]+[-<+]->>]+[-]<<<.>>>+[\n
-<+]-<<]

View file

@ -158,7 +158,18 @@ mod tests {
#[test] #[test]
fn fizzbuzz() { fn fizzbuzz() {
let str = include_str!("fizzbuzz.bf"); let str = include_str!("../benches/fizzbuzz.bf");
let mut stdout = Vec::new();
let stdin = [];
super::run(str, &mut stdout, stdin.as_slice(), &Args::default()).unwrap();
insta::assert_debug_snapshot!(String::from_utf8(stdout));
}
#[test]
fn mandelbrot() {
let str = include_str!("../benches/mandelbrot.bf");
let mut stdout = Vec::new(); let mut stdout = Vec::new();
let stdin = []; let stdin = [];

View file

@ -24,7 +24,7 @@ use bumpalo::Bump;
use crate::{ use crate::{
hir::{Hir, StmtKind as HirStmtKind}, hir::{Hir, StmtKind as HirStmtKind},
mir::state::{Load, MemoryState, Store}, mir::state::{MemoryState, Store},
parse::Span, parse::Span,
BumpVec, BumpVec,
}; };
@ -50,20 +50,26 @@ impl Debug for Stmt<'_> {
} }
} }
type Offset = i32;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
enum StmtKind<'mir> { enum StmtKind<'mir> {
/// Add or sub, the value has the valid range -255..=255 /// Add or sub, the value has the valid range -255..=255
AddSub(i32, i16, Store), AddSub {
offset: Offset,
n: i16,
store: Store,
},
/// Sets the current cell to 0 and adds that value of the cell to another cell at `offset` /// Sets the current cell to 0 and adds that value of the cell to another cell at `offset`
MoveAddTo { MoveAddTo {
offset: i32, offset: Offset,
store_set_null: Store, store_set_null: Store,
store_move: Store, store_move: Store,
}, },
/// Left or Right pointer move (`<>`) /// Left or Right pointer move (`<>`)
PointerMove(i32), PointerMove(Offset),
Loop(Mir<'mir>, Load), Loop(Mir<'mir>),
Out(Load), Out,
In(Store), In(Store),
SetN(u8, Store), SetN(u8, Store),
} }
@ -80,21 +86,27 @@ fn hir_to_mir<'mir>(alloc: &'mir Bump, hir: &Hir<'_>) -> Mir<'mir> {
let mut stmts = Vec::new_in(alloc); let mut stmts = Vec::new_in(alloc);
let iter = hir.stmts.iter().map(|hir_stmt| { let iter = hir.stmts.iter().map(|hir_stmt| {
let kind = match *hir_stmt.kind() { let kind = match *hir_stmt.kind() {
HirStmtKind::Add(offset, n) => StmtKind::AddSub(offset, i16::from(n), Store::unknown()), HirStmtKind::Add(offset, n) => StmtKind::AddSub {
HirStmtKind::Sub(offset, n) => { offset,
StmtKind::AddSub(offset, -i16::from(n), Store::unknown()) n: i16::from(n),
} store: Store::dead(),
},
HirStmtKind::Sub(offset, n) => StmtKind::AddSub {
offset,
n: -i16::from(n),
store: Store::dead(),
},
HirStmtKind::MoveAddTo { offset } => StmtKind::MoveAddTo { HirStmtKind::MoveAddTo { offset } => StmtKind::MoveAddTo {
offset, offset,
store_set_null: Store::unknown(), store_set_null: Store::dead(),
store_move: Store::unknown(), store_move: Store::dead(),
}, },
HirStmtKind::Right(n) => StmtKind::PointerMove(i32::try_from(n).unwrap()), HirStmtKind::Right(n) => StmtKind::PointerMove(i32::try_from(n).unwrap()),
HirStmtKind::Left(n) => StmtKind::PointerMove(-i32::try_from(n).unwrap()), HirStmtKind::Left(n) => StmtKind::PointerMove(-i32::try_from(n).unwrap()),
HirStmtKind::Loop(ref body) => StmtKind::Loop(hir_to_mir(alloc, body), Load::Unknown), HirStmtKind::Loop(ref body) => StmtKind::Loop(hir_to_mir(alloc, body)),
HirStmtKind::Out => StmtKind::Out(Load::Unknown), HirStmtKind::Out => StmtKind::Out,
HirStmtKind::In => StmtKind::In(Store::unknown()), HirStmtKind::In => StmtKind::In(Store::dead()),
HirStmtKind::SetN(n) => StmtKind::SetN(n, Store::unknown()), HirStmtKind::SetN(n) => StmtKind::SetN(n, Store::dead()),
}; };
Stmt { Stmt {
kind, kind,

View file

@ -1,9 +1,11 @@
use std::collections::{hash_map::Entry, HashMap};
use bumpalo::Bump; use bumpalo::Bump;
use tracing::info; use tracing::info;
use crate::mir::{ use crate::mir::{
state::{CellState, MemoryState, MemoryStateChange}, state::{CellState, MemoryState, MemoryStateChange, Store},
Mir, StmtKind, Mir, Offset, StmtKind,
}; };
/// this pass fills out as much state info for all statements as possible /// this pass fills out as much state info for all statements as possible
@ -11,6 +13,7 @@ use crate::mir::{
pub fn passes<'mir>(alloc: &'mir Bump, mir: &mut Mir<'mir>) { pub fn passes<'mir>(alloc: &'mir Bump, mir: &mut Mir<'mir>) {
pass_fill_state_info(alloc, mir); pass_fill_state_info(alloc, mir);
pass_const_propagation(mir); pass_const_propagation(mir);
pass_dead_store_elimination(mir);
} }
/// this pass fills out as much state info for all statements as possible /// this pass fills out as much state info for all statements as possible
#[tracing::instrument(skip(alloc, mir))] #[tracing::instrument(skip(alloc, mir))]
@ -27,7 +30,7 @@ fn pass_fill_state_info_inner<'mir>(
) { ) {
for stmt in &mut mir.stmts { for stmt in &mut mir.stmts {
let state = match &mut stmt.kind { let state = match &mut stmt.kind {
StmtKind::AddSub(offset, n, store) => { StmtKind::AddSub { offset, n, store } => {
let prev_state = outer.state_for_offset(*offset); let prev_state = outer.state_for_offset(*offset);
let new_state = match prev_state { let new_state = match prev_state {
CellState::WrittenToKnown(_, prev_n) => { CellState::WrittenToKnown(_, prev_n) => {
@ -65,7 +68,7 @@ fn pass_fill_state_info_inner<'mir>(
StmtKind::PointerMove(n) => { StmtKind::PointerMove(n) => {
MemoryState::single(alloc, outer, MemoryStateChange::Move(*n)) MemoryState::single(alloc, outer, MemoryStateChange::Move(*n))
} }
StmtKind::Loop(body, _) => { StmtKind::Loop(body) => {
// TODO: we can get a lot smarter here and get huge benefits; we don't yet // TODO: we can get a lot smarter here and get huge benefits; we don't yet
pass_fill_state_info_inner(alloc, body, MemoryState::empty(alloc)); pass_fill_state_info_inner(alloc, body, MemoryState::empty(alloc));
MemoryState::double( MemoryState::double(
@ -80,7 +83,7 @@ fn pass_fill_state_info_inner<'mir>(
}, },
) )
} }
StmtKind::Out(_) => outer, StmtKind::Out => outer,
StmtKind::In(store) => MemoryState::single( StmtKind::In(store) => MemoryState::single(
alloc, alloc,
outer, outer,
@ -103,6 +106,82 @@ fn pass_fill_state_info_inner<'mir>(
} }
} }
/// This pass eliminates dead stores. It should probably be run multiple times between other passes
/// for cleanup
#[tracing::instrument(skip(mir))]
fn pass_dead_store_elimination(mir: &mut Mir<'_>) {
pass_dead_store_elimination_mark_dead_stores(mir)
}
fn pass_dead_store_elimination_mark_dead_stores(mir: &mut Mir<'_>) {
fn mark_store(
potential_dead_stores: &mut HashMap<Offset, Store>,
offset: Offset,
store: &Store,
) {
match potential_dead_stores.entry(offset) {
Entry::Occupied(mut entry) => {
let old = entry.insert(store.clone());
if old.is_maybe_dead() {
// it's certainly dead
info!("We have a dead one!!!");
old.mark_dead();
} else {
// it's alive and well, drop it and keep it marked alive
drop(old);
}
}
Entry::Vacant(entry) => {
entry.insert(store.clone());
}
}
}
let mut potential_dead_stores = HashMap::new();
let mut current_offset = 0;
for stmt in &mir.stmts {
match &stmt.kind {
StmtKind::AddSub { store, offset, .. } => {
mark_store(&mut potential_dead_stores, current_offset + offset, store);
}
StmtKind::MoveAddTo {
offset,
store_move,
store_set_null,
} => {
mark_store(&mut potential_dead_stores, current_offset, store_set_null);
mark_store(
&mut potential_dead_stores,
current_offset + offset,
store_move,
);
}
StmtKind::PointerMove(offset) => {
current_offset -= offset; // ???
}
StmtKind::Loop(_) | StmtKind::Out => {
let store = potential_dead_stores.get(&current_offset);
if let Some(store) = store {
store.add_load();
}
}
StmtKind::In(store) | StmtKind::SetN(_, store) => {
mark_store(&mut potential_dead_stores, current_offset, store);
}
}
if stmt.state.has_forget_delta() {
// they might all have loads now
for store in potential_dead_stores.values_mut() {
// TODO STOP: WE MUTATE THE STATE HERE!!! ALL COOL DEAD STORES WILL BE CLOBBERED
store.clobber();
}
}
}
}
// test pass
#[tracing::instrument(skip(mir))] #[tracing::instrument(skip(mir))]
fn pass_const_propagation(mir: &mut Mir<'_>) { fn pass_const_propagation(mir: &mut Mir<'_>) {
pass_const_propagation_inner(mir) pass_const_propagation_inner(mir)
@ -111,15 +190,13 @@ fn pass_const_propagation(mir: &mut Mir<'_>) {
fn pass_const_propagation_inner(mir: &mut Mir<'_>) { fn pass_const_propagation_inner(mir: &mut Mir<'_>) {
for stmt in &mut mir.stmts { for stmt in &mut mir.stmts {
match &mut stmt.kind { match &mut stmt.kind {
StmtKind::Out(_) => { StmtKind::Out => {
let state = stmt.state.state_for_offset(0); let state = stmt.state.state_for_offset(0);
info!(?state, "We got the state of the output 😳😳😳");
// we could now insert a `SetN` before the `Out`, to mark the previous store // we could now insert a `SetN` before the `Out`, to mark the previous store
// as dead. // as dead.
} }
StmtKind::Loop(body, _) => { StmtKind::Loop(body) => {
let state = stmt.state.state_for_offset(0); let state = stmt.state.state_for_offset(0);
info!(?state, "We got the state of the output 😳😳😳");
// we could now insert a `SetN` before the `Loop`, to mark the previous store // we could now insert a `SetN` before the `Loop`, to mark the previous store
// as dead. // as dead.
pass_const_propagation_inner(body); pass_const_propagation_inner(body);

View file

@ -9,7 +9,7 @@ use std::{
use bumpalo::Bump; use bumpalo::Bump;
use crate::BumpVec; use crate::{mir::Offset, BumpVec};
/// The known state of a cell in the MIR /// The known state of a cell in the MIR
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -28,16 +28,19 @@ pub enum CellState {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum MemoryStateChange { pub enum MemoryStateChange {
/// A cell value was changed to a new state. /// A cell value was changed to a new state.
Change { offset: i32, new_state: CellState }, Change {
offset: Offset,
new_state: CellState,
},
/// The pointer was moved. This affects the `offset` calculations from previous states. /// The pointer was moved. This affects the `offset` calculations from previous states.
Move(i32), Move(Offset),
/// Forget everything about the memory state. This currently happens after each loop, since /// Forget everything about the memory state. This currently happens after each loop, since
/// the loop is opaque and might clobber everything. /// the loop is opaque and might clobber everything.
Forget, Forget,
/// Load a value from memory. This is not a direct change of the memory itself, but it does /// Load a value from memory. This is not a direct change of the memory itself, but it does
/// change the state in that it marks the corresponding store, if any, as alive. Loads should /// change the state in that it marks the corresponding store, if any, as alive. Loads should
/// be eliminated whenever possible, to remove as many dead stores as possible. /// be eliminated whenever possible, to remove as many dead stores as possible.
Load(Option<Store>), Load { offset: Offset },
} }
/// The known state of memory at a specific instance in the instruction sequence /// The known state of memory at a specific instance in the instruction sequence
@ -78,9 +81,17 @@ impl<'mir> MemoryState<'mir> {
Self(Rc::new(RefCell::new(MemoryStateInner { prev, deltas }))) Self(Rc::new(RefCell::new(MemoryStateInner { prev, deltas })))
} }
pub fn state_for_offset(&self, offset: i32) -> CellState { pub fn state_for_offset(&self, offset: Offset) -> CellState {
self.0.borrow().state_for_offset(offset) self.0.borrow().state_for_offset(offset)
} }
pub fn has_forget_delta(&self) -> bool {
self.0
.borrow()
.deltas
.iter()
.any(|d| matches!(d, MemoryStateChange::Forget))
}
} }
impl Debug for MemoryState<'_> { impl Debug for MemoryState<'_> {
@ -94,13 +105,13 @@ impl Debug for MemoryState<'_> {
/// The known state of memory relative to the pointer /// The known state of memory relative to the pointer
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct MemoryStateInner<'mir> { struct MemoryStateInner<'mir> {
prev: Option<MemoryState<'mir>>, prev: Option<MemoryState<'mir>>,
deltas: BumpVec<'mir, MemoryStateChange>, deltas: BumpVec<'mir, MemoryStateChange>,
} }
impl<'mir> MemoryStateInner<'mir> { impl<'mir> MemoryStateInner<'mir> {
pub fn state_for_offset(&self, offset: i32) -> CellState { fn state_for_offset(&self, offset: Offset) -> CellState {
let mut offset = offset; let mut offset = offset;
for delta in &self.deltas { for delta in &self.deltas {
match delta { match delta {
@ -128,8 +139,8 @@ impl<'mir> MemoryStateInner<'mir> {
pub struct Store(Rc<Cell<StoreInner>>); pub struct Store(Rc<Cell<StoreInner>>);
impl Store { impl Store {
pub fn unknown() -> Self { pub fn dead() -> Self {
StoreKind::Unknown.into() StoreKind::Dead.into()
} }
pub fn id(&self) -> u64 { pub fn id(&self) -> u64 {
@ -138,18 +149,38 @@ impl Store {
pub fn add_load(&self) { pub fn add_load(&self) {
let old = self.inner(); let old = self.inner();
let new_kind = match old.kind { let kind = match old.kind {
StoreKind::Unknown => StoreKind::UsedAtLeast(NonZeroU32::new(1).unwrap()), StoreKind::Unknown => StoreKind::UsedAtLeast(NonZeroU32::new(1).unwrap()),
StoreKind::UsedExact(n) => StoreKind::UsedExact(n.checked_add(1).unwrap()), StoreKind::UsedExact(n) => StoreKind::UsedExact(n.checked_add(1).unwrap()),
StoreKind::UsedAtLeast(n) => StoreKind::UsedAtLeast(n.checked_add(1).unwrap()), StoreKind::UsedAtLeast(n) => StoreKind::UsedAtLeast(n.checked_add(1).unwrap()),
StoreKind::Dead => StoreKind::UsedExact(NonZeroU32::new(1).unwrap()), StoreKind::Dead => StoreKind::UsedExact(NonZeroU32::new(1).unwrap()),
}; };
self.0.set(StoreInner { id: old.id, kind })
}
pub fn is_maybe_dead(&self) -> bool {
matches!(self.inner().kind, StoreKind::Dead | StoreKind::Unknown)
}
pub fn mark_dead(&self) {
let old = self.inner();
self.0.set(StoreInner { self.0.set(StoreInner {
id: old.id, id: old.id,
kind: new_kind, kind: StoreKind::Dead,
}) })
} }
pub fn clobber(&self) {
let old = self.inner();
let kind = match old.kind {
StoreKind::Unknown => StoreKind::Unknown,
StoreKind::UsedExact(n) => StoreKind::UsedAtLeast(n),
StoreKind::UsedAtLeast(n) => StoreKind::UsedAtLeast(n),
StoreKind::Dead => StoreKind::Unknown,
};
self.0.set(StoreInner { id: old.id, kind })
}
fn inner(&self) -> StoreInner { fn inner(&self) -> StoreInner {
self.0.get() self.0.get()
} }
@ -157,7 +188,7 @@ impl Store {
impl Debug for Store { impl Debug for Store {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
self.inner().fmt(f) self.inner().kind.fmt(f)
} }
} }
@ -169,7 +200,7 @@ struct StoreInner {
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
enum StoreKind { enum StoreKind {
/// No information is known about uses of the store /// No information is known about uses of the store, it has probably been clobbered
Unknown, Unknown,
/// The exact amount of subsequent loads is known about the store, and it's this /// The exact amount of subsequent loads is known about the store, and it's this
UsedExact(NonZeroU32), UsedExact(NonZeroU32),
@ -187,12 +218,3 @@ impl From<StoreKind> for Store {
}))) })))
} }
} }
/// A load from memory and from which store it was acquired
#[derive(Debug, Clone)]
pub enum Load {
/// It is not known from which `Store` this was loaded
Unknown,
/// The load was acquired from this `Store`. The `Store` must either be `UsedExact` or `UsedAtLeast`
KnownStore(Store),
}

View file

@ -0,0 +1,7 @@
---
source: src/lib.rs
expression: "String::from_utf8(stdout)"
---
Ok(
"AAAAAAAABBBBBBBBCCCCCCCCCCCCCCCCCCDDDDEFEEDDDCCCCCBBBBBBBBBBBBBBB\nAAAAAAABBBBBBCCCCCCCCCCCCCCCCCDDDDDDEEFIKGGGDDDDDCCCCBBBBBBBBBBBB\nAAAAAABBBBCCCCCCCCCCCCCCCCCDDDDDDDEEEFGHKPIGFEDDDDDCCCCCBBBBBBBBB\nAAAAABBBCCCCCCCCCCCCCCCCCDDDDDDDEEEFGPVT Q[HEEEEDDDCCCCCCBBBBBBB\nAAAABBCCCCCCCCCCCCCCCCDDDDDDDEEFFFGGHK HGFFEEEDDDCCCCCBBBBBB\nAAABBCCCCCCCCCCCCCCCDDDDDEEEFGK MJJ NR YS L HHGIJFDDCCCCCCBBBB\nAAABCCCCCCCCCCCCCDDDEEEEEEFFFHI MGEDDCCCCCCBBB\nAABCCCCCCCCCCCDDEEEEEEEEFFFGY Q MHGEEDCCCCCCCBB\nAACCCCCCDDDDDEEFLHGGHMHGGGHIR QLHEDDCCCCCCCB\nABCCDDDDDDEEEEFGIKU RLJJL IFEDDCCCCCCCB\nACDDDDDDEEEEEGGHOS QR JFEDDDCCCCCCC\nADDDDDEFFFGGHKOPS GEEDDDCCCCCCC\nA PJGFEEDDDCCCCCCC\nADDDDDEFFFGGHKOPS GEEDDDCCCCCCC\nACDDDDDDEEEEEGGHOS QR JFEDDDCCCCCCC\nABCCDDDDDDEEEEFGIKU RLJJL IFEDDCCCCCCCB\nAACCCCCCDDDDDEEFLHGGHMHGGGHIR QLHEDDCCCCCCCB\nAABCCCCCCCCCCCDDEEEEEEEEFFFGY Q MHGEEDCCCCCCCBB\nAAABCCCCCCCCCCCCCDDDEEEEEEFFFHI MGEDDCCCCCCBBB\nAAABBCCCCCCCCCCCCCCCDDDDDEEEFGK MJJ NR YS L HHGIJFDDCCCCCCBBBB\nAAAABBCCCCCCCCCCCCCCCCDDDDDDDEEFFFGGHK HGFFEEEDDDCCCCCBBBBBB\nAAAAABBBCCCCCCCCCCCCCCCCCDDDDDDDEEEFGPVT Q[HEEEEDDDCCCCCCBBBBBBB\nAAAAAABBBBCCCCCCCCCCCCCCCCCDDDDDDDEEEFGHKPIGFEDDDDDCCCCCBBBBBBBBB\nAAAAAAABBBBBBCCCCCCCCCCCCCCCCCDDDDDDEEFIKGGGDDDDDCCCCBBBBBBBBBBBB\n",
)

View file

@ -1 +1 @@
++[<] +><+