handling infinite loops properly
This commit is contained in:
parent
9c90adbfe8
commit
ce7bb001da
|
@ -23,7 +23,7 @@ hbjit = { path = "jit" }
|
|||
|
||||
[profile.release]
|
||||
lto = true
|
||||
#debug = true
|
||||
debug = true
|
||||
#strip = true
|
||||
codegen-units = 1
|
||||
panic = "abort"
|
||||
|
@ -42,3 +42,9 @@ inherits = "dev"
|
|||
opt-level = "z"
|
||||
strip = "debuginfo"
|
||||
panic = "abort"
|
||||
|
||||
[profile.fuzz]
|
||||
inherits = "dev"
|
||||
debug = true
|
||||
opt-level = 3
|
||||
panic = "abort"
|
||||
|
|
|
@ -7,6 +7,10 @@ edition = "2021"
|
|||
name = "hbc"
|
||||
path = "src/main.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "fuzz"
|
||||
path = "src/fuzz_main.rs"
|
||||
|
||||
[dependencies]
|
||||
hashbrown = { version = "0.15.0", default-features = false, features = ["raw-entry", "allocator-api2"] }
|
||||
hbbytecode = { workspace = true, features = ["disasm"] }
|
||||
|
|
|
@ -1011,6 +1011,11 @@ main := fn(): uint {
|
|||
return 6
|
||||
}
|
||||
|
||||
infinite_loop()
|
||||
return 0
|
||||
}
|
||||
|
||||
infinite_loop := fn(): void {
|
||||
f := 0
|
||||
loop {
|
||||
if f == 1 {
|
||||
|
@ -1124,3 +1129,15 @@ main := fn(): uint {
|
|||
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
139
lang/src/fuzz.rs
Normal 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
3
lang/src/fuzz_main.rs
Normal file
|
@ -0,0 +1,3 @@
|
|||
fn main() {
|
||||
hblang::fuzz::fuzz(0..1000000);
|
||||
}
|
|
@ -277,6 +277,7 @@ impl TokenKind {
|
|||
Self::Add => a.wrapping_add(b),
|
||||
Self::Sub => a.wrapping_sub(b),
|
||||
Self::Mul => a.wrapping_mul(b),
|
||||
Self::Div if b == 0 => 0,
|
||||
Self::Div => a.wrapping_div(b),
|
||||
Self::Shl => a.wrapping_shl(b as _),
|
||||
Self::Eq => (a == b) as i64,
|
||||
|
|
|
@ -75,6 +75,8 @@ pub mod lexer;
|
|||
#[cfg(feature = "opts")]
|
||||
mod vc;
|
||||
|
||||
pub mod fuzz;
|
||||
|
||||
mod debug {
|
||||
|
||||
pub fn panicking() -> bool {
|
||||
|
|
|
@ -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)];
|
||||
col += line.matches('\t').count() * 3;
|
||||
|
||||
_ = writeln!(out, "{}", line.replace("\t", " "));
|
||||
_ = writeln!(out, "{}^", " ".repeat(col - 1));
|
||||
for char in line.chars() {
|
||||
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)]
|
||||
|
|
238
lang/src/son.rs
238
lang/src/son.rs
|
@ -35,6 +35,8 @@ const VOID: Nid = 0;
|
|||
const NEVER: Nid = 1;
|
||||
const ENTRY: Nid = 2;
|
||||
const MEM: Nid = 3;
|
||||
const LOOPS: Nid = 4;
|
||||
const ARG_START: usize = 3;
|
||||
|
||||
type Nid = u16;
|
||||
|
||||
|
@ -154,23 +156,23 @@ impl Nodes {
|
|||
fn graphviz_in_browser(&self, tys: &Types, files: &[parser::Ast]) {
|
||||
#[cfg(all(debug_assertions, feature = "std"))]
|
||||
{
|
||||
let out = &mut String::new();
|
||||
_ = self.graphviz_low(tys, files, out);
|
||||
if !std::process::Command::new("brave")
|
||||
.arg(format!("https://dreampuf.github.io/GraphvizOnline/#{out}"))
|
||||
.status()
|
||||
.unwrap()
|
||||
.success()
|
||||
{
|
||||
log::error!("{out}");
|
||||
}
|
||||
// let out = &mut String::new();
|
||||
// _ = self.graphviz_low(tys, files, out);
|
||||
// if !std::process::Command::new("brave")
|
||||
// .arg(format!("https://dreampuf.github.io/GraphvizOnline/#{out}"))
|
||||
// .status()
|
||||
// .unwrap()
|
||||
// .success()
|
||||
// {
|
||||
// log::error!("{out}");
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
fn gcm(&mut self) {
|
||||
fix_loops(self);
|
||||
self.visited.clear(self.values.len());
|
||||
push_up(self);
|
||||
// TODO: handle infinte loops
|
||||
self.visited.clear(self.values.len());
|
||||
push_down(self, VOID);
|
||||
}
|
||||
|
@ -313,13 +315,12 @@ impl Nodes {
|
|||
None
|
||||
}
|
||||
|
||||
fn iter_peeps(&mut self, mut fuel: usize) {
|
||||
self.lock(NEVER);
|
||||
fn iter_peeps(&mut self, mut fuel: usize, stack: &mut Vec<Nid>) {
|
||||
stack.clear();
|
||||
|
||||
let mut stack = self
|
||||
.iter()
|
||||
self.iter()
|
||||
.filter_map(|(id, node)| node.kind.is_peeped().then_some(id))
|
||||
.collect::<Vec<_>>();
|
||||
.collect_into(stack);
|
||||
stack.iter().for_each(|&s| self.lock(s));
|
||||
|
||||
while fuel != 0
|
||||
|
@ -340,8 +341,6 @@ impl Nodes {
|
|||
stack.iter().skip(prev_len).for_each(|&n| self.lock(n));
|
||||
}
|
||||
}
|
||||
|
||||
self.unlock(NEVER);
|
||||
}
|
||||
|
||||
fn peephole(&mut self, target: Nid) -> Option<Nid> {
|
||||
|
@ -524,6 +523,11 @@ impl Nodes {
|
|||
|
||||
return Some(ctrl);
|
||||
}
|
||||
K::Call { .. } | K::Return => {
|
||||
if self[target].inputs[0] == NEVER {
|
||||
return Some(NEVER);
|
||||
}
|
||||
}
|
||||
K::Phi => {
|
||||
let &[ctrl, lhs, rhs] = self[target].inputs.as_slice() else { unreachable!() };
|
||||
|
||||
|
@ -667,7 +671,7 @@ impl Nodes {
|
|||
}
|
||||
match self[node].kind {
|
||||
Kind::Start => unreachable!(),
|
||||
Kind::End => unreachable!(),
|
||||
Kind::End => return Ok(()),
|
||||
Kind::If => write!(out, " if: "),
|
||||
Kind::Region | Kind::Loop => writeln!(out, " goto: {node}"),
|
||||
Kind::Return => write!(out, " ret: "),
|
||||
|
@ -692,6 +696,7 @@ impl Nodes {
|
|||
Kind::Load => write!(out, "load: "),
|
||||
Kind::Stre => write!(out, "stre: "),
|
||||
Kind::Mem => write!(out, " mem: "),
|
||||
Kind::Loops => write!(out, " loops: "),
|
||||
Kind::Extend => write!(out, " ext: "),
|
||||
}?;
|
||||
|
||||
|
@ -819,10 +824,16 @@ impl Nodes {
|
|||
log::error!("{} {} {:?}", node.lock_rc, 0, node.kind);
|
||||
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);
|
||||
failed = true;
|
||||
}
|
||||
if node.inputs.first() == Some(&NEVER) && id != NEVER {
|
||||
log::error!("is unreachable but still present {id} {:?}", node.kind);
|
||||
failed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if failed {
|
||||
|
@ -1021,7 +1032,10 @@ pub enum Kind {
|
|||
Start,
|
||||
// [ctrl]
|
||||
Entry,
|
||||
// [VOID]
|
||||
Mem,
|
||||
// [VOID]
|
||||
Loops,
|
||||
// [terms...]
|
||||
End,
|
||||
// [ctrl, cond]
|
||||
|
@ -1070,7 +1084,7 @@ pub enum Kind {
|
|||
|
||||
impl Kind {
|
||||
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 {
|
||||
|
@ -1140,7 +1154,7 @@ impl Node {
|
|||
|
||||
fn is_not_gvnd(&self) -> bool {
|
||||
(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 {
|
||||
|
@ -1368,16 +1382,20 @@ impl ItemCtx {
|
|||
let mem = self.nodes.new_node(ty::Id::VOID, Kind::Mem, [VOID]);
|
||||
debug_assert_eq!(mem, 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);
|
||||
}
|
||||
|
||||
fn finalize(&mut self) {
|
||||
fn finalize(&mut self, stack: &mut Vec<Nid>) {
|
||||
self.scope.clear(&mut self.nodes);
|
||||
self.nodes.unlock(NEVER);
|
||||
mem::take(&mut self.ctrl).soft_remove(&mut self.nodes);
|
||||
self.nodes.unlock(MEM);
|
||||
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])) {
|
||||
|
@ -1438,7 +1456,7 @@ impl ItemCtx {
|
|||
|
||||
let (retl, mut parama) = tys.parama(sig.ret);
|
||||
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) {
|
||||
let Arg::Value(ty) = aty else { continue };
|
||||
let Some(loc) = parama.next(ty, tys) else { continue };
|
||||
|
@ -1710,6 +1728,7 @@ impl ItemCtx {
|
|||
| Kind::Entry
|
||||
| Kind::Mem
|
||||
| Kind::End
|
||||
| Kind::Loops
|
||||
| Kind::Then
|
||||
| Kind::Else
|
||||
| Kind::Phi
|
||||
|
@ -1852,6 +1871,7 @@ struct Pool {
|
|||
cis: Vec<ItemCtx>,
|
||||
used_cis: usize,
|
||||
ralloc: Regalloc,
|
||||
nid_stack: Vec<Nid>,
|
||||
}
|
||||
|
||||
impl Pool {
|
||||
|
@ -2840,7 +2860,7 @@ impl<'a> Codegen<'a> {
|
|||
let prev_file = mem::replace(&mut self.ci.file, file);
|
||||
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(
|
||||
body.pos(),
|
||||
"expected all paths in the fucntion to return \
|
||||
|
@ -3052,7 +3072,11 @@ impl<'a> Codegen<'a> {
|
|||
}
|
||||
Expr::Loop { body, .. } => {
|
||||
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,
|
||||
);
|
||||
self.ci.loops.push(Loop {
|
||||
|
@ -3121,6 +3145,7 @@ impl<'a> Codegen<'a> {
|
|||
}
|
||||
scope.clear(&mut self.ci.nodes);
|
||||
self.ci.ctrl.set(NEVER, &mut self.ci.nodes);
|
||||
|
||||
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]);
|
||||
|
||||
'b: {
|
||||
let branch = match self.tof(if_node).expand().inner() {
|
||||
ty::LEFT_UNREACHABLE => else_,
|
||||
ty::RIGHT_UNREACHABLE => Some(then),
|
||||
let branch = match self.ci.nodes[if_node].ty {
|
||||
ty::Id::LEFT_UNREACHABLE => else_,
|
||||
ty::Id::RIGHT_UNREACHABLE => Some(then),
|
||||
_ => break 'b,
|
||||
};
|
||||
|
||||
|
@ -3471,11 +3496,6 @@ impl<'a> Codegen<'a> {
|
|||
None
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn tof(&self, id: Nid) -> ty::Id {
|
||||
self.ci.nodes[id].ty
|
||||
}
|
||||
|
||||
fn complete_call_graph(&mut self) -> bool {
|
||||
let prev_err_len = self.errors.borrow().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() {
|
||||
if sig.ret == ty::Id::VOID {
|
||||
self.expr(&Expr::Return { pos: body.pos(), val: None });
|
||||
} else {
|
||||
self.report(
|
||||
body.pos(),
|
||||
fa!(
|
||||
"expected all paths in the fucntion to return \
|
||||
or the return type to be 'void'",
|
||||
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.finalize();
|
||||
self.ci.finalize(&mut self.pool.nid_stack);
|
||||
|
||||
if self.errors.borrow().len() == prev_err_len {
|
||||
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) });
|
||||
|
||||
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 {
|
||||
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.ci.finalize();
|
||||
self.ci.finalize(&mut self.pool.nid_stack);
|
||||
|
||||
let ret = self.ci.ret.expect("for return type to be infered");
|
||||
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);
|
||||
self.emit_node(node.outputs[0], VOID)
|
||||
}
|
||||
Kind::End => {}
|
||||
Kind::If => {
|
||||
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 mut typs = self.sig.args.args();
|
||||
#[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) {
|
||||
let arg = args.next().unwrap();
|
||||
match parama.next(ty, self.tys) {
|
||||
|
@ -4136,7 +4162,8 @@ impl<'a> Function<'a> {
|
|||
let ops = vec![self.drg(nid)];
|
||||
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 => {
|
||||
self.nodes.lock(nid)
|
||||
}
|
||||
|
@ -4414,6 +4441,27 @@ fn idepth(nodes: &mut Nodes, target: Nid) -> IDomDepth {
|
|||
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 collect_rpo(node: Nid, nodes: &mut Nodes, rpo: &mut Vec<Nid>) {
|
||||
if !nodes.is_cfg(node) || !nodes.visited.set(node) {
|
||||
|
@ -4491,13 +4539,15 @@ fn push_up(nodes: &mut Nodes) {
|
|||
nodes
|
||||
.iter()
|
||||
.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<_>>(),
|
||||
vec![],
|
||||
"{:?}",
|
||||
nodes
|
||||
.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<_>>()
|
||||
);
|
||||
}
|
||||
|
@ -4609,100 +4659,11 @@ fn common_dom(mut a: Nid, mut b: Nid, nodes: &mut Nodes) -> Nid {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use {
|
||||
super::{Codegen, CodegenCtx},
|
||||
crate::{
|
||||
lexer::TokenKind,
|
||||
parser::{self},
|
||||
},
|
||||
super::CodegenCtx,
|
||||
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) {
|
||||
_ = log::set_logger(&crate::fs::Logger);
|
||||
log::set_max_level(log::LevelFilter::Info);
|
||||
|
@ -4795,5 +4756,6 @@ mod tests {
|
|||
conditional_stores;
|
||||
loop_stores;
|
||||
dead_code_in_loop;
|
||||
infinite_loop_after_peephole;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,56 +15,65 @@ continue_and_state_change:
|
|||
4: ADDI64 r2, r2, 1d
|
||||
3: JMP :6
|
||||
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:
|
||||
ADDI64 r254, r254, -64d
|
||||
ST r31, r254, 0a, 64h
|
||||
ADDI64 r254, r254, -56d
|
||||
ST r31, r254, 0a, 56h
|
||||
LI64 r32, 0d
|
||||
CP r2, r32
|
||||
JAL r31, r0, :multiple_breaks
|
||||
LI64 r6, 1d
|
||||
LI64 r7, 3d
|
||||
JEQ r1, r7, :0
|
||||
CP r1, r6
|
||||
CP r2, r1
|
||||
LI64 r1, 3d
|
||||
JEQ r2, r1, :0
|
||||
LI64 r1, 1d
|
||||
JMP :1
|
||||
0: CP r33, r6
|
||||
CP r34, r7
|
||||
LI64 r35, 4d
|
||||
CP r2, r35
|
||||
0: CP r33, r1
|
||||
LI64 r34, 4d
|
||||
CP r2, r34
|
||||
JAL r31, r0, :multiple_breaks
|
||||
CP r36, r35
|
||||
LI64 r37, 10d
|
||||
JEQ r1, r37, :2
|
||||
CP r35, r34
|
||||
LI64 r36, 10d
|
||||
JEQ r1, r36, :2
|
||||
LI64 r1, 2d
|
||||
JMP :1
|
||||
2: CP r2, r32
|
||||
JAL r31, r0, :state_change_in_break
|
||||
JEQ r1, r32, :3
|
||||
CP r1, r34
|
||||
CP r1, r33
|
||||
JMP :1
|
||||
3: CP r2, r36
|
||||
3: CP r2, r35
|
||||
JAL r31, r0, :state_change_in_break
|
||||
JEQ r1, r37, :4
|
||||
CP r1, r36
|
||||
JEQ r1, r36, :4
|
||||
CP r1, r35
|
||||
JMP :1
|
||||
4: CP r2, r37
|
||||
4: CP r2, r36
|
||||
JAL r31, r0, :continue_and_state_change
|
||||
JEQ r1, r37, :5
|
||||
JEQ r1, r36, :5
|
||||
LI64 r1, 5d
|
||||
JMP :1
|
||||
5: CP r2, r34
|
||||
5: CP r2, r33
|
||||
JAL r31, r0, :continue_and_state_change
|
||||
JEQ r1, r32, :6
|
||||
LI64 r1, 6d
|
||||
JMP :1
|
||||
6: CP r1, r32
|
||||
CP r38, r33
|
||||
8: JNE r1, r38, :7
|
||||
JMP :7
|
||||
7: CP r2, r32
|
||||
JAL r31, r0, :continue_and_state_change
|
||||
JMP :8
|
||||
1: LD r31, r254, 0a, 64h
|
||||
ADDI64 r254, r254, 64d
|
||||
6: CP r37, r32
|
||||
JAL r31, r0, :infinite_loop
|
||||
CP r1, r37
|
||||
1: LD r31, r254, 0a, 56h
|
||||
ADDI64 r254, r254, 56d
|
||||
JALA r0, r31, 0a
|
||||
multiple_breaks:
|
||||
LI64 r6, 3d
|
||||
|
@ -91,6 +100,6 @@ state_change_in_break:
|
|||
JMP :4
|
||||
3: JALA r0, r31, 0a
|
||||
timed out
|
||||
code size: 582
|
||||
code size: 668
|
||||
ret: 10
|
||||
status: Ok(())
|
||||
|
|
9
lang/tests/son_tests_infinite_loop_after_peephole.txt
Normal file
9
lang/tests/son_tests_infinite_loop_after_peephole.txt
Normal 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(())
|
|
@ -7,30 +7,29 @@ main:
|
|||
CP r9, r4
|
||||
6: JNE r9, r6, :0
|
||||
LI64 r6, 2d
|
||||
CP r7, r4
|
||||
4: JNE r7, r8, :1
|
||||
ADDI64 r7, r254, 32d
|
||||
CP r9, r4
|
||||
4: JNE r9, r8, :1
|
||||
LD r1, r254, 0a, 8h
|
||||
JMP :2
|
||||
1: CP r10, r4
|
||||
5: ADD64 r9, r7, r8
|
||||
JNE r10, r6, :3
|
||||
CP r7, r9
|
||||
1: MUL64 r12, r9, r6
|
||||
ADD64 r9, r9, r8
|
||||
SUB64 r10, r6, r9
|
||||
MUL64 r10, r10, r6
|
||||
CP r3, r4
|
||||
5: JNE r3, r6, :3
|
||||
JMP :4
|
||||
3: ADD64 r3, r10, r8
|
||||
MUL64 r12, r7, r6
|
||||
SUB64 r11, r6, r9
|
||||
ADD64 r9, r12, r10
|
||||
MUL64 r11, r11, r6
|
||||
MULI64 r9, r9, 8d
|
||||
ADD64 r11, r11, r10
|
||||
ADD64 r9, r5, r9
|
||||
MULI64 r11, r11, 8d
|
||||
ADDI64 r10, r254, 32d
|
||||
ADD64 r11, r5, r11
|
||||
BMC r9, r10, 8h
|
||||
BMC r11, r9, 8h
|
||||
BMC r10, r11, 8h
|
||||
CP r10, r3
|
||||
3: ADD64 r11, r3, r8
|
||||
ADD64 r1, r12, r3
|
||||
MULI64 r1, r1, 8d
|
||||
ADD64 r2, r10, r3
|
||||
ADD64 r1, r5, r1
|
||||
MULI64 r2, r2, 8d
|
||||
ADD64 r2, r5, r2
|
||||
BMC r1, r7, 8h
|
||||
BMC r2, r1, 8h
|
||||
BMC r7, r2, 8h
|
||||
CP r3, r11
|
||||
JMP :5
|
||||
0: ADD64 r2, r9, r8
|
||||
MULI64 r12, r9, 8d
|
||||
|
@ -40,6 +39,6 @@ main:
|
|||
JMP :6
|
||||
2: ADDI64 r254, r254, 40d
|
||||
JALA r0, r31, 0a
|
||||
code size: 274
|
||||
code size: 271
|
||||
ret: 2
|
||||
status: Ok(())
|
||||
|
|
Loading…
Reference in a new issue