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]
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"

View file

@ -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"] }

View file

@ -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
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::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,

View file

@ -75,6 +75,8 @@ pub mod lexer;
#[cfg(feature = "opts")]
mod vc;
pub mod fuzz;
mod debug {
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)];
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)]

View file

@ -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 {
self.report(
body.pos(),
"expected all paths in the fucntion to return \
or the return type to be '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' (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;
}
}

View file

@ -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(())

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
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(())