handling infinite loops properly

This commit is contained in:
Jakub Doka 2024-10-27 11:32:34 +01:00
parent 9c90adbfe8
commit ce7bb001da
No known key found for this signature in database
GPG key ID: C6E9A89936B8C143
12 changed files with 358 additions and 197 deletions

View file

@ -23,7 +23,7 @@ hbjit = { path = "jit" }
[profile.release] [profile.release]
lto = true lto = true
#debug = true debug = true
#strip = true #strip = true
codegen-units = 1 codegen-units = 1
panic = "abort" panic = "abort"
@ -42,3 +42,9 @@ inherits = "dev"
opt-level = "z" opt-level = "z"
strip = "debuginfo" strip = "debuginfo"
panic = "abort" panic = "abort"
[profile.fuzz]
inherits = "dev"
debug = true
opt-level = 3
panic = "abort"

View file

@ -7,6 +7,10 @@ edition = "2021"
name = "hbc" name = "hbc"
path = "src/main.rs" path = "src/main.rs"
[[bin]]
name = "fuzz"
path = "src/fuzz_main.rs"
[dependencies] [dependencies]
hashbrown = { version = "0.15.0", default-features = false, features = ["raw-entry", "allocator-api2"] } hashbrown = { version = "0.15.0", default-features = false, features = ["raw-entry", "allocator-api2"] }
hbbytecode = { workspace = true, features = ["disasm"] } hbbytecode = { workspace = true, features = ["disasm"] }

View file

@ -1011,6 +1011,11 @@ main := fn(): uint {
return 6 return 6
} }
infinite_loop()
return 0
}
infinite_loop := fn(): void {
f := 0 f := 0
loop { loop {
if f == 1 { if f == 1 {
@ -1124,3 +1129,15 @@ main := fn(): uint {
return 1 return 1
} }
``` ```
#### infinite_loop_after_peephole
```hb
main := fn(): uint {
n := 0
f := 0
loop if n != 0 break else {
f += 1
}
return f
}
```

139
lang/src/fuzz.rs Normal file
View file

@ -0,0 +1,139 @@
use {
crate::{
lexer::TokenKind,
parser,
son::{Codegen, CodegenCtx},
},
core::{fmt::Write, hash::BuildHasher, ops::Range},
std::string::String,
};
#[derive(Default)]
struct Rand(pub u64);
impl Rand {
pub fn next(&mut self) -> u64 {
self.0 = crate::FnvBuildHasher::default().hash_one(self.0);
self.0
}
pub fn range(&mut self, min: u64, max: u64) -> u64 {
self.next() % (max - min) + min
}
fn bool(&mut self) -> bool {
self.next() % 2 == 0
}
}
#[derive(Default)]
struct FuncGen {
rand: Rand,
buf: String,
vars: u64,
}
impl FuncGen {
fn gen(&mut self, seed: u64) -> &str {
self.rand = Rand(seed);
self.buf.clear();
self.buf.push_str("main := fn(): void ");
self.block().unwrap();
&self.buf
}
fn block(&mut self) -> core::fmt::Result {
let prev_vars = self.vars;
self.buf.push('{');
for _ in 0..self.rand.range(1, 10) {
self.stmt()?;
}
self.buf.push('}');
self.vars = prev_vars;
Ok(())
}
fn stmt(&mut self) -> core::fmt::Result {
match self.rand.range(0, 100) {
0..4 => _ = self.block(),
4..10 => {
write!(self.buf, "var{} := ", self.vars)?;
self.expr()?;
self.vars += 1;
}
10..20 if self.vars != 0 => {
write!(self.buf, "var{} = ", self.rand.range(0, self.vars))?;
self.expr()?;
}
20..23 => {
self.buf.push_str("if ");
self.expr()?;
self.block()?;
if self.rand.bool() {
self.buf.push_str(" else ");
self.block()?;
}
}
_ => {
self.buf.push_str("return ");
self.expr()?;
}
}
self.buf.push(';');
Ok(())
}
fn expr(&mut self) -> core::fmt::Result {
match self.rand.range(0, 100) {
0..80 => {
write!(self.buf, "{}", self.rand.next())
}
80..90 if self.vars != 0 => {
write!(self.buf, "var{}", self.rand.range(0, self.vars))
}
80..100 => {
self.expr()?;
let ops = [
TokenKind::Add,
TokenKind::Sub,
TokenKind::Mul,
TokenKind::Div,
TokenKind::Shl,
TokenKind::Eq,
TokenKind::Ne,
TokenKind::Lt,
TokenKind::Gt,
TokenKind::Le,
TokenKind::Ge,
TokenKind::Band,
TokenKind::Bor,
TokenKind::Xor,
TokenKind::Mod,
TokenKind::Shr,
];
let op = ops[self.rand.range(0, ops.len() as u64) as usize];
write!(self.buf, " {op} ")?;
self.expr()
}
_ => unreachable!(),
}
}
}
pub fn fuzz(seed_range: Range<u64>) {
let mut gen = FuncGen::default();
let mut ctx = CodegenCtx::default();
for i in seed_range {
ctx.clear();
let src = gen.gen(i);
let parsed = parser::Ast::new("fuzz", src, &mut ctx.parser, &mut parser::no_loader);
assert!(ctx.parser.errors.get_mut().is_empty());
let mut cdg = Codegen::new(core::slice::from_ref(&parsed), &mut ctx);
cdg.generate(0);
}
}

3
lang/src/fuzz_main.rs Normal file
View file

@ -0,0 +1,3 @@
fn main() {
hblang::fuzz::fuzz(0..1000000);
}

View file

@ -277,6 +277,7 @@ impl TokenKind {
Self::Add => a.wrapping_add(b), Self::Add => a.wrapping_add(b),
Self::Sub => a.wrapping_sub(b), Self::Sub => a.wrapping_sub(b),
Self::Mul => a.wrapping_mul(b), Self::Mul => a.wrapping_mul(b),
Self::Div if b == 0 => 0,
Self::Div => a.wrapping_div(b), Self::Div => a.wrapping_div(b),
Self::Shl => a.wrapping_shl(b as _), Self::Shl => a.wrapping_shl(b as _),
Self::Eq => (a == b) as i64, Self::Eq => (a == b) as i64,

View file

@ -75,6 +75,8 @@ pub mod lexer;
#[cfg(feature = "opts")] #[cfg(feature = "opts")]
mod vc; mod vc;
pub mod fuzz;
mod debug { mod debug {
pub fn panicking() -> bool { pub fn panicking() -> bool {

View file

@ -1147,8 +1147,18 @@ fn report_to(file: &str, path: &str, pos: Pos, msg: &dyn fmt::Display, out: &mut
..file[pos as usize..].find('\n').map_or(file.len(), |i| i + pos as usize)]; ..file[pos as usize..].find('\n').map_or(file.len(), |i| i + pos as usize)];
col += line.matches('\t').count() * 3; col += line.matches('\t').count() * 3;
_ = writeln!(out, "{}", line.replace("\t", " ")); for char in line.chars() {
_ = writeln!(out, "{}^", " ".repeat(col - 1)); if char == '\t' {
_ = out.write_str(" ");
} else {
_ = out.write_char(char);
}
}
_ = out.write_char('\n');
for _ in 0..col - 1 {
_ = out.write_str(" ");
}
_ = out.write_str("^\n");
} }
#[derive(PartialEq, Eq, Hash)] #[derive(PartialEq, Eq, Hash)]

View file

@ -35,6 +35,8 @@ const VOID: Nid = 0;
const NEVER: Nid = 1; const NEVER: Nid = 1;
const ENTRY: Nid = 2; const ENTRY: Nid = 2;
const MEM: Nid = 3; const MEM: Nid = 3;
const LOOPS: Nid = 4;
const ARG_START: usize = 3;
type Nid = u16; type Nid = u16;
@ -154,23 +156,23 @@ impl Nodes {
fn graphviz_in_browser(&self, tys: &Types, files: &[parser::Ast]) { fn graphviz_in_browser(&self, tys: &Types, files: &[parser::Ast]) {
#[cfg(all(debug_assertions, feature = "std"))] #[cfg(all(debug_assertions, feature = "std"))]
{ {
let out = &mut String::new(); // let out = &mut String::new();
_ = self.graphviz_low(tys, files, out); // _ = self.graphviz_low(tys, files, out);
if !std::process::Command::new("brave") // if !std::process::Command::new("brave")
.arg(format!("https://dreampuf.github.io/GraphvizOnline/#{out}")) // .arg(format!("https://dreampuf.github.io/GraphvizOnline/#{out}"))
.status() // .status()
.unwrap() // .unwrap()
.success() // .success()
{ // {
log::error!("{out}"); // log::error!("{out}");
} // }
} }
} }
fn gcm(&mut self) { fn gcm(&mut self) {
fix_loops(self);
self.visited.clear(self.values.len()); self.visited.clear(self.values.len());
push_up(self); push_up(self);
// TODO: handle infinte loops
self.visited.clear(self.values.len()); self.visited.clear(self.values.len());
push_down(self, VOID); push_down(self, VOID);
} }
@ -313,13 +315,12 @@ impl Nodes {
None None
} }
fn iter_peeps(&mut self, mut fuel: usize) { fn iter_peeps(&mut self, mut fuel: usize, stack: &mut Vec<Nid>) {
self.lock(NEVER); stack.clear();
let mut stack = self self.iter()
.iter()
.filter_map(|(id, node)| node.kind.is_peeped().then_some(id)) .filter_map(|(id, node)| node.kind.is_peeped().then_some(id))
.collect::<Vec<_>>(); .collect_into(stack);
stack.iter().for_each(|&s| self.lock(s)); stack.iter().for_each(|&s| self.lock(s));
while fuel != 0 while fuel != 0
@ -340,8 +341,6 @@ impl Nodes {
stack.iter().skip(prev_len).for_each(|&n| self.lock(n)); stack.iter().skip(prev_len).for_each(|&n| self.lock(n));
} }
} }
self.unlock(NEVER);
} }
fn peephole(&mut self, target: Nid) -> Option<Nid> { fn peephole(&mut self, target: Nid) -> Option<Nid> {
@ -524,6 +523,11 @@ impl Nodes {
return Some(ctrl); return Some(ctrl);
} }
K::Call { .. } | K::Return => {
if self[target].inputs[0] == NEVER {
return Some(NEVER);
}
}
K::Phi => { K::Phi => {
let &[ctrl, lhs, rhs] = self[target].inputs.as_slice() else { unreachable!() }; let &[ctrl, lhs, rhs] = self[target].inputs.as_slice() else { unreachable!() };
@ -667,7 +671,7 @@ impl Nodes {
} }
match self[node].kind { match self[node].kind {
Kind::Start => unreachable!(), Kind::Start => unreachable!(),
Kind::End => unreachable!(), Kind::End => return Ok(()),
Kind::If => write!(out, " if: "), Kind::If => write!(out, " if: "),
Kind::Region | Kind::Loop => writeln!(out, " goto: {node}"), Kind::Region | Kind::Loop => writeln!(out, " goto: {node}"),
Kind::Return => write!(out, " ret: "), Kind::Return => write!(out, " ret: "),
@ -692,6 +696,7 @@ impl Nodes {
Kind::Load => write!(out, "load: "), Kind::Load => write!(out, "load: "),
Kind::Stre => write!(out, "stre: "), Kind::Stre => write!(out, "stre: "),
Kind::Mem => write!(out, " mem: "), Kind::Mem => write!(out, " mem: "),
Kind::Loops => write!(out, " loops: "),
Kind::Extend => write!(out, " ext: "), Kind::Extend => write!(out, " ext: "),
}?; }?;
@ -819,10 +824,16 @@ impl Nodes {
log::error!("{} {} {:?}", node.lock_rc, 0, node.kind); log::error!("{} {} {:?}", node.lock_rc, 0, node.kind);
failed = true; failed = true;
} }
if !matches!(node.kind, Kind::End | Kind::Mem | Kind::Arg) && node.outputs.is_empty() { if !matches!(node.kind, Kind::End | Kind::Mem | Kind::Arg | Kind::Loops)
&& node.outputs.is_empty()
{
log::error!("outputs are empry {id} {:?}", node.kind); log::error!("outputs are empry {id} {:?}", node.kind);
failed = true; failed = true;
} }
if node.inputs.first() == Some(&NEVER) && id != NEVER {
log::error!("is unreachable but still present {id} {:?}", node.kind);
failed = true;
}
} }
if failed { if failed {
@ -1021,7 +1032,10 @@ pub enum Kind {
Start, Start,
// [ctrl] // [ctrl]
Entry, Entry,
// [VOID]
Mem, Mem,
// [VOID]
Loops,
// [terms...] // [terms...]
End, End,
// [ctrl, cond] // [ctrl, cond]
@ -1070,7 +1084,7 @@ pub enum Kind {
impl Kind { impl Kind {
fn is_pinned(&self) -> bool { fn is_pinned(&self) -> bool {
self.is_cfg() || matches!(self, Self::Phi | Self::Arg | Self::Mem) self.is_cfg() || matches!(self, Self::Phi | Self::Arg | Self::Mem | Self::Loops)
} }
fn is_cfg(&self) -> bool { fn is_cfg(&self) -> bool {
@ -1140,7 +1154,7 @@ impl Node {
fn is_not_gvnd(&self) -> bool { fn is_not_gvnd(&self) -> bool {
(self.kind == Kind::Phi && self.inputs[2] == 0) (self.kind == Kind::Phi && self.inputs[2] == 0)
|| matches!(self.kind, Kind::Arg | Kind::Stck) || matches!(self.kind, Kind::Arg | Kind::Stck | Kind::End)
} }
fn is_mem(&self) -> bool { fn is_mem(&self) -> bool {
@ -1368,16 +1382,20 @@ impl ItemCtx {
let mem = self.nodes.new_node(ty::Id::VOID, Kind::Mem, [VOID]); let mem = self.nodes.new_node(ty::Id::VOID, Kind::Mem, [VOID]);
debug_assert_eq!(mem, MEM); debug_assert_eq!(mem, MEM);
self.nodes.lock(mem); self.nodes.lock(mem);
let loops = self.nodes.new_node(ty::Id::VOID, Kind::Loops, [VOID]);
debug_assert_eq!(loops, LOOPS);
self.nodes.lock(loops);
self.scope.store = Variable::new(0, ty::Id::VOID, false, MEM, &mut self.nodes); self.scope.store = Variable::new(0, ty::Id::VOID, false, MEM, &mut self.nodes);
} }
fn finalize(&mut self) { fn finalize(&mut self, stack: &mut Vec<Nid>) {
self.scope.clear(&mut self.nodes); self.scope.clear(&mut self.nodes);
self.nodes.unlock(NEVER);
mem::take(&mut self.ctrl).soft_remove(&mut self.nodes); mem::take(&mut self.ctrl).soft_remove(&mut self.nodes);
self.nodes.unlock(MEM);
self.nodes.eliminate_stack_temporaries(); self.nodes.eliminate_stack_temporaries();
self.nodes.iter_peeps(1000); self.nodes.iter_peeps(1000, stack);
self.nodes.unlock(MEM);
self.nodes.unlock(NEVER);
self.nodes.unlock(LOOPS);
} }
fn emit(&mut self, instr: (usize, [u8; instrs::MAX_SIZE])) { fn emit(&mut self, instr: (usize, [u8; instrs::MAX_SIZE])) {
@ -1438,7 +1456,7 @@ impl ItemCtx {
let (retl, mut parama) = tys.parama(sig.ret); let (retl, mut parama) = tys.parama(sig.ret);
let mut typs = sig.args.args(); let mut typs = sig.args.args();
let mut args = fuc.nodes[VOID].outputs[2..].iter(); let mut args = fuc.nodes[VOID].outputs[ARG_START..].iter();
while let Some(aty) = typs.next(tys) { while let Some(aty) = typs.next(tys) {
let Arg::Value(ty) = aty else { continue }; let Arg::Value(ty) = aty else { continue };
let Some(loc) = parama.next(ty, tys) else { continue }; let Some(loc) = parama.next(ty, tys) else { continue };
@ -1710,6 +1728,7 @@ impl ItemCtx {
| Kind::Entry | Kind::Entry
| Kind::Mem | Kind::Mem
| Kind::End | Kind::End
| Kind::Loops
| Kind::Then | Kind::Then
| Kind::Else | Kind::Else
| Kind::Phi | Kind::Phi
@ -1852,6 +1871,7 @@ struct Pool {
cis: Vec<ItemCtx>, cis: Vec<ItemCtx>,
used_cis: usize, used_cis: usize,
ralloc: Regalloc, ralloc: Regalloc,
nid_stack: Vec<Nid>,
} }
impl Pool { impl Pool {
@ -2840,7 +2860,7 @@ impl<'a> Codegen<'a> {
let prev_file = mem::replace(&mut self.ci.file, file); let prev_file = mem::replace(&mut self.ci.file, file);
self.ci.inline_depth += 1; self.ci.inline_depth += 1;
if self.expr(body).is_some() && sig.ret == ty::Id::VOID { if self.expr(body).is_some() && sig.ret != ty::Id::VOID {
self.report( self.report(
body.pos(), body.pos(),
"expected all paths in the fucntion to return \ "expected all paths in the fucntion to return \
@ -3052,7 +3072,11 @@ impl<'a> Codegen<'a> {
} }
Expr::Loop { body, .. } => { Expr::Loop { body, .. } => {
self.ci.ctrl.set( self.ci.ctrl.set(
self.ci.nodes.new_node(ty::Id::VOID, Kind::Loop, [self.ci.ctrl.get(); 2]), self.ci.nodes.new_node(ty::Id::VOID, Kind::Loop, [
self.ci.ctrl.get(),
self.ci.ctrl.get(),
LOOPS,
]),
&mut self.ci.nodes, &mut self.ci.nodes,
); );
self.ci.loops.push(Loop { self.ci.loops.push(Loop {
@ -3121,6 +3145,7 @@ impl<'a> Codegen<'a> {
} }
scope.clear(&mut self.ci.nodes); scope.clear(&mut self.ci.nodes);
self.ci.ctrl.set(NEVER, &mut self.ci.nodes); self.ci.ctrl.set(NEVER, &mut self.ci.nodes);
return None; return None;
}; };
@ -3182,9 +3207,9 @@ impl<'a> Codegen<'a> {
self.ci.nodes.new_node(ty::Id::VOID, Kind::If, [self.ci.ctrl.get(), cnd.id]); self.ci.nodes.new_node(ty::Id::VOID, Kind::If, [self.ci.ctrl.get(), cnd.id]);
'b: { 'b: {
let branch = match self.tof(if_node).expand().inner() { let branch = match self.ci.nodes[if_node].ty {
ty::LEFT_UNREACHABLE => else_, ty::Id::LEFT_UNREACHABLE => else_,
ty::RIGHT_UNREACHABLE => Some(then), ty::Id::RIGHT_UNREACHABLE => Some(then),
_ => break 'b, _ => break 'b,
}; };
@ -3471,11 +3496,6 @@ impl<'a> Codegen<'a> {
None None
} }
#[inline(always)]
fn tof(&self, id: Nid) -> ty::Id {
self.ci.nodes[id].ty
}
fn complete_call_graph(&mut self) -> bool { fn complete_call_graph(&mut self) -> bool {
let prev_err_len = self.errors.borrow().len(); let prev_err_len = self.errors.borrow().len();
while self.ci.task_base < self.tys.tasks.len() while self.ci.task_base < self.tys.tasks.len()
@ -3535,17 +3555,24 @@ impl<'a> Codegen<'a> {
} }
} }
if self.expr(body).is_some() && sig.ret == ty::Id::VOID { if self.expr(body).is_some() {
self.report( if sig.ret == ty::Id::VOID {
body.pos(), self.expr(&Expr::Return { pos: body.pos(), val: None });
"expected all paths in the fucntion to return \ } else {
or the return type to be 'void'", self.report(
); body.pos(),
fa!(
"expected all paths in the fucntion to return \
or the return type to be 'void' (return type is '{}')",
self.ty_display(sig.ret),
),
);
}
} }
self.ci.scope.vars.drain(..).for_each(|v| v.remove_ignore_arg(&mut self.ci.nodes)); self.ci.scope.vars.drain(..).for_each(|v| v.remove_ignore_arg(&mut self.ci.nodes));
self.ci.finalize(); self.ci.finalize(&mut self.pool.nid_stack);
if self.errors.borrow().len() == prev_err_len { if self.errors.borrow().len() == prev_err_len {
self.ci.emit_body(self.tys, self.files, sig, &mut self.pool.ralloc); self.ci.emit_body(self.tys, self.files, sig, &mut self.pool.ralloc);
@ -3675,7 +3702,7 @@ impl TypeParser for Codegen<'_> {
self.expr(&Expr::Return { pos: expr.pos(), val: Some(expr) }); self.expr(&Expr::Return { pos: expr.pos(), val: Some(expr) });
scope = mem::take(&mut self.ci.scope.vars); scope = mem::take(&mut self.ci.scope.vars);
self.ci.finalize(); self.ci.finalize(&mut self.pool.nid_stack);
let res = if self.errors.borrow().len() == prev_err_len { let res = if self.errors.borrow().len() == prev_err_len {
self.emit_and_eval(file, ret, &mut []) self.emit_and_eval(file, ret, &mut [])
@ -3719,7 +3746,7 @@ impl TypeParser for Codegen<'_> {
self.expr(&(Expr::Return { pos: expr.pos(), val: Some(expr) })); self.expr(&(Expr::Return { pos: expr.pos(), val: Some(expr) }));
self.ci.finalize(); self.ci.finalize(&mut self.pool.nid_stack);
let ret = self.ci.ret.expect("for return type to be infered"); let ret = self.ci.ret.expect("for return type to be infered");
if self.errors.borrow().len() == prev_err_len { if self.errors.borrow().len() == prev_err_len {
@ -3881,7 +3908,6 @@ impl<'a> Function<'a> {
debug_assert_matches!(self.nodes[node.outputs[0]].kind, Kind::Entry); debug_assert_matches!(self.nodes[node.outputs[0]].kind, Kind::Entry);
self.emit_node(node.outputs[0], VOID) self.emit_node(node.outputs[0], VOID)
} }
Kind::End => {}
Kind::If => { Kind::If => {
self.nodes[nid].ralloc_backref = self.nodes[prev].ralloc_backref; self.nodes[nid].ralloc_backref = self.nodes[prev].ralloc_backref;
@ -3974,7 +4000,7 @@ impl<'a> Function<'a> {
let (ret, mut parama) = self.tys.parama(self.sig.ret); let (ret, mut parama) = self.tys.parama(self.sig.ret);
let mut typs = self.sig.args.args(); let mut typs = self.sig.args.args();
#[allow(clippy::unnecessary_to_owned)] #[allow(clippy::unnecessary_to_owned)]
let mut args = self.nodes[VOID].outputs[2..].to_owned().into_iter(); let mut args = self.nodes[VOID].outputs[ARG_START..].to_owned().into_iter();
while let Some(ty) = typs.next_value(self.tys) { while let Some(ty) = typs.next_value(self.tys) {
let arg = args.next().unwrap(); let arg = args.next().unwrap();
match parama.next(ty, self.tys) { match parama.next(ty, self.tys) {
@ -4136,7 +4162,8 @@ impl<'a> Function<'a> {
let ops = vec![self.drg(nid)]; let ops = vec![self.drg(nid)];
self.add_instr(nid, ops); self.add_instr(nid, ops);
} }
Kind::Phi | Kind::Arg | Kind::Mem => {} Kind::End |
Kind::Phi | Kind::Arg | Kind::Mem | Kind::Loops => {}
Kind::Load { .. } if node.ty.loc(self.tys) == Loc::Stack => { Kind::Load { .. } if node.ty.loc(self.tys) == Loc::Stack => {
self.nodes.lock(nid) self.nodes.lock(nid)
} }
@ -4414,6 +4441,27 @@ fn idepth(nodes: &mut Nodes, target: Nid) -> IDomDepth {
nodes[target].depth nodes[target].depth
} }
fn fix_loops(nodes: &mut Nodes) {
'o: for l in nodes[LOOPS].outputs.clone() {
let mut cursor = nodes[l].inputs[1];
while cursor != l {
if nodes[cursor].kind == Kind::If
&& nodes[cursor]
.outputs
.clone()
.into_iter()
.any(|b| loop_depth(b, nodes) < loop_depth(cursor, nodes))
{
continue 'o;
}
cursor = idom(nodes, cursor);
}
nodes[l].outputs.push(NEVER);
nodes[NEVER].inputs.push(l);
}
}
fn push_up(nodes: &mut Nodes) { fn push_up(nodes: &mut Nodes) {
fn collect_rpo(node: Nid, nodes: &mut Nodes, rpo: &mut Vec<Nid>) { fn collect_rpo(node: Nid, nodes: &mut Nodes, rpo: &mut Vec<Nid>) {
if !nodes.is_cfg(node) || !nodes.visited.set(node) { if !nodes.is_cfg(node) || !nodes.visited.set(node) {
@ -4491,13 +4539,15 @@ fn push_up(nodes: &mut Nodes) {
nodes nodes
.iter() .iter()
.map(|(n, _)| n) .map(|(n, _)| n)
.filter(|&n| !nodes.visited.get(n) && !matches!(nodes[n].kind, Kind::Arg | Kind::Mem)) .filter(|&n| !nodes.visited.get(n)
&& !matches!(nodes[n].kind, Kind::Arg | Kind::Mem | Kind::Loops))
.collect::<Vec<_>>(), .collect::<Vec<_>>(),
vec![], vec![],
"{:?}", "{:?}",
nodes nodes
.iter() .iter()
.filter(|&(n, nod)| !nodes.visited.get(n) && !matches!(nod.kind, Kind::Arg | Kind::Mem)) .filter(|&(n, nod)| !nodes.visited.get(n)
&& !matches!(nod.kind, Kind::Arg | Kind::Mem | Kind::Loops))
.collect::<Vec<_>>() .collect::<Vec<_>>()
); );
} }
@ -4609,100 +4659,11 @@ fn common_dom(mut a: Nid, mut b: Nid, nodes: &mut Nodes) -> Nid {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use { use {
super::{Codegen, CodegenCtx}, super::CodegenCtx,
crate::{
lexer::TokenKind,
parser::{self},
},
alloc::{string::String, vec::Vec}, alloc::{string::String, vec::Vec},
core::{fmt::Write, hash::BuildHasher, ops::Range}, core::fmt::Write,
}; };
#[derive(Default)]
struct Rand(pub u64);
impl Rand {
pub fn next(&mut self) -> u64 {
self.0 = crate::FnvBuildHasher::default().hash_one(self.0);
self.0
}
pub fn range(&mut self, min: u64, max: u64) -> u64 {
self.next() % (max - min) + min
}
}
#[derive(Default)]
struct FuncGen {
rand: Rand,
buf: String,
}
impl FuncGen {
fn gen(&mut self, seed: u64) -> &str {
self.rand = Rand(seed);
self.buf.clear();
self.buf.push_str("main := fn(): void { return ");
self.expr().unwrap();
self.buf.push('}');
&self.buf
}
fn expr(&mut self) -> core::fmt::Result {
match self.rand.range(0, 100) {
0..80 => {
write!(self.buf, "{}", self.rand.next())
}
80..100 => {
self.expr()?;
let ops = [
TokenKind::Add,
TokenKind::Sub,
TokenKind::Mul,
TokenKind::Div,
TokenKind::Shl,
TokenKind::Eq,
TokenKind::Ne,
TokenKind::Lt,
TokenKind::Gt,
TokenKind::Le,
TokenKind::Ge,
TokenKind::Band,
TokenKind::Bor,
TokenKind::Xor,
TokenKind::Mod,
TokenKind::Shr,
];
let op = ops[self.rand.range(0, ops.len() as u64) as usize];
write!(self.buf, " {op} ")?;
self.expr()
}
_ => unreachable!(),
}
}
}
fn fuzz(seed_range: Range<u64>) {
let mut gen = FuncGen::default();
let mut ctx = CodegenCtx::default();
for i in seed_range {
ctx.clear();
let src = gen.gen(i);
let parsed = parser::Ast::new("fuzz", src, &mut ctx.parser, &mut parser::no_loader);
let mut cdg = Codegen::new(core::slice::from_ref(&parsed), &mut ctx);
cdg.generate(0);
}
}
#[test]
#[ignore]
fn fuzz_test() {
_ = log::set_logger(&crate::fs::Logger);
log::set_max_level(log::LevelFilter::Info);
fuzz(0..10000);
}
fn generate(ident: &'static str, input: &'static str, output: &mut String) { fn generate(ident: &'static str, input: &'static str, output: &mut String) {
_ = log::set_logger(&crate::fs::Logger); _ = log::set_logger(&crate::fs::Logger);
log::set_max_level(log::LevelFilter::Info); log::set_max_level(log::LevelFilter::Info);
@ -4795,5 +4756,6 @@ mod tests {
conditional_stores; conditional_stores;
loop_stores; loop_stores;
dead_code_in_loop; dead_code_in_loop;
infinite_loop_after_peephole;
} }
} }

View file

@ -15,56 +15,65 @@ continue_and_state_change:
4: ADDI64 r2, r2, 1d 4: ADDI64 r2, r2, 1d
3: JMP :6 3: JMP :6
5: JALA r0, r31, 0a 5: JALA r0, r31, 0a
infinite_loop:
ADDI64 r254, r254, -24d
ST r31, r254, 0a, 24h
LI64 r32, 1d
LI64 r33, 0d
CP r1, r33
1: JNE r1, r32, :0
JMP :0
0: CP r2, r33
JAL r31, r0, :continue_and_state_change
JMP :1
LD r31, r254, 0a, 24h
ADDI64 r254, r254, 24d
JALA r0, r31, 0a
main: main:
ADDI64 r254, r254, -64d ADDI64 r254, r254, -56d
ST r31, r254, 0a, 64h ST r31, r254, 0a, 56h
LI64 r32, 0d LI64 r32, 0d
CP r2, r32 CP r2, r32
JAL r31, r0, :multiple_breaks JAL r31, r0, :multiple_breaks
LI64 r6, 1d CP r2, r1
LI64 r7, 3d LI64 r1, 3d
JEQ r1, r7, :0 JEQ r2, r1, :0
CP r1, r6 LI64 r1, 1d
JMP :1 JMP :1
0: CP r33, r6 0: CP r33, r1
CP r34, r7 LI64 r34, 4d
LI64 r35, 4d CP r2, r34
CP r2, r35
JAL r31, r0, :multiple_breaks JAL r31, r0, :multiple_breaks
CP r36, r35 CP r35, r34
LI64 r37, 10d LI64 r36, 10d
JEQ r1, r37, :2 JEQ r1, r36, :2
LI64 r1, 2d LI64 r1, 2d
JMP :1 JMP :1
2: CP r2, r32 2: CP r2, r32
JAL r31, r0, :state_change_in_break JAL r31, r0, :state_change_in_break
JEQ r1, r32, :3 JEQ r1, r32, :3
CP r1, r34 CP r1, r33
JMP :1 JMP :1
3: CP r2, r36 3: CP r2, r35
JAL r31, r0, :state_change_in_break JAL r31, r0, :state_change_in_break
JEQ r1, r37, :4 JEQ r1, r36, :4
CP r1, r36 CP r1, r35
JMP :1 JMP :1
4: CP r2, r37 4: CP r2, r36
JAL r31, r0, :continue_and_state_change JAL r31, r0, :continue_and_state_change
JEQ r1, r37, :5 JEQ r1, r36, :5
LI64 r1, 5d LI64 r1, 5d
JMP :1 JMP :1
5: CP r2, r34 5: CP r2, r33
JAL r31, r0, :continue_and_state_change JAL r31, r0, :continue_and_state_change
JEQ r1, r32, :6 JEQ r1, r32, :6
LI64 r1, 6d LI64 r1, 6d
JMP :1 JMP :1
6: CP r1, r32 6: CP r37, r32
CP r38, r33 JAL r31, r0, :infinite_loop
8: JNE r1, r38, :7 CP r1, r37
JMP :7 1: LD r31, r254, 0a, 56h
7: CP r2, r32 ADDI64 r254, r254, 56d
JAL r31, r0, :continue_and_state_change
JMP :8
1: LD r31, r254, 0a, 64h
ADDI64 r254, r254, 64d
JALA r0, r31, 0a JALA r0, r31, 0a
multiple_breaks: multiple_breaks:
LI64 r6, 3d LI64 r6, 3d
@ -91,6 +100,6 @@ state_change_in_break:
JMP :4 JMP :4
3: JALA r0, r31, 0a 3: JALA r0, r31, 0a
timed out timed out
code size: 582 code size: 668
ret: 10 ret: 10
status: Ok(()) status: Ok(())

View file

@ -0,0 +1,9 @@
main:
LI64 r2, 0d
0: ADDI64 r2, r2, 1d
JMP :0
JALA r0, r31, 0a
timed out
code size: 45
ret: 0
status: Ok(())

View file

@ -7,30 +7,29 @@ main:
CP r9, r4 CP r9, r4
6: JNE r9, r6, :0 6: JNE r9, r6, :0
LI64 r6, 2d LI64 r6, 2d
CP r7, r4 ADDI64 r7, r254, 32d
4: JNE r7, r8, :1 CP r9, r4
4: JNE r9, r8, :1
LD r1, r254, 0a, 8h LD r1, r254, 0a, 8h
JMP :2 JMP :2
1: CP r10, r4 1: MUL64 r12, r9, r6
5: ADD64 r9, r7, r8 ADD64 r9, r9, r8
JNE r10, r6, :3 SUB64 r10, r6, r9
CP r7, r9 MUL64 r10, r10, r6
CP r3, r4
5: JNE r3, r6, :3
JMP :4 JMP :4
3: ADD64 r3, r10, r8 3: ADD64 r11, r3, r8
MUL64 r12, r7, r6 ADD64 r1, r12, r3
SUB64 r11, r6, r9 MULI64 r1, r1, 8d
ADD64 r9, r12, r10 ADD64 r2, r10, r3
MUL64 r11, r11, r6 ADD64 r1, r5, r1
MULI64 r9, r9, 8d MULI64 r2, r2, 8d
ADD64 r11, r11, r10 ADD64 r2, r5, r2
ADD64 r9, r5, r9 BMC r1, r7, 8h
MULI64 r11, r11, 8d BMC r2, r1, 8h
ADDI64 r10, r254, 32d BMC r7, r2, 8h
ADD64 r11, r5, r11 CP r3, r11
BMC r9, r10, 8h
BMC r11, r9, 8h
BMC r10, r11, 8h
CP r10, r3
JMP :5 JMP :5
0: ADD64 r2, r9, r8 0: ADD64 r2, r9, r8
MULI64 r12, r9, 8d MULI64 r12, r9, 8d
@ -40,6 +39,6 @@ main:
JMP :6 JMP :6
2: ADDI64 r254, r254, 40d 2: ADDI64 r254, r254, 40d
JALA r0, r31, 0a JALA r0, r31, 0a
code size: 274 code size: 271
ret: 2 ret: 2
status: Ok(()) status: Ok(())