diff --git a/f.txt b/f.txt deleted file mode 100644 index 5ca0531e..00000000 --- a/f.txt +++ /dev/null @@ -1,47 +0,0 @@ -start: 0 - 46-c65535: +: [0, 38, 45] [47] - 45-c65535: &: [0, 43, 44] [46] - 44-c65535: cint: #255 [0] [45] - 43-c65535: load: [0, 42, 23] [45] - 42-c65535: +: [0, 22, 18] [43] - 39-c65535: stre: [0, 0, 36, 37] [47] - 39-c65535: stre: [0, 0, 36, 37] [47] - 37-c65535: stre: [0, 35, 36, 3] [38, 39] - 36-c65535: stck: [0, 3] [37, 38, 39] - 35-c65535: load: [0, 24, 34] [37] - 34-c65535: stre: [0, 31, 33, 30] [35, 47] - 33-c65535: +: [0, 24, 32] [34] - 32-c65535: cint: #16 [0] [33] - 31-c65535: cint: #4 [0] [34] - 30-c65535: stre: [0, 27, 29, 26] [34] - 29-c65535: +: [0, 24, 28] [30] - 28-c65535: cint: #8 [0] [29] - 27-c65535: cint: #2 [0] [30] - 26-c65535: stre: [0, 25, 24, 3] [30] - 25-c65535: cint: #1 [0] [26] - 24-c65535: stck: [0, 3] [26, 29, 33, 35] - 23-c65535: stre: [0, 21, 22, 3] [43, 47] - 22-c65535: stck: [0, 3] [23, 42] - 21-c65535: load: [0, 6, 20] [23] - 20-c65535: stre: [0, 17, 19, 14] [21, 47] - 19-c65535: +: [0, 6, 18] [20] - 18-c65535: cint: #3 [0] [19, 42] - 17-c65535: cint: #1 [0] [20] - 14-c65535: stre: [0, 5, 13, 11] [20] - 13-c65535: +: [0, 6, 12] [14] - 12-c65535: cint: #2 [0] [13] - 11-c65535: stre: [0, 7, 10, 8] [14] - 10-c65535: +: [0, 6, 9] [11] - 9-c65535: cint: #1 [0] [10] - 8-c65535: stre: [0, 7, 6, 3] [11] - 7-c65535: cint: #0 [0] [8, 11] - 6-c65535: stck: [0, 3] [8, 10, 13, 19, 21] - 5-c65535: cint: #511 [0] [14] - 4-c65535: loops: [0] [] - 3-c65535: mem: [0] [6, 8, 22, 23, 24, 26, 36, 37, 47] - 2-c65535: ctrl: entry [0] [38] -b2: 0 0 [38] - 38-c65535: call: 1 0 [2, 36, 37] [46, 47] - 47-c65535: ret: [38, 46, 3, 20, 23, 34, 39] [1] - - diff --git a/f1.txt b/f1.txt deleted file mode 100644 index e885f9e6..00000000 --- a/f1.txt +++ /dev/null @@ -1,38 +0,0 @@ - -start: 0 - 34-c65535: stre: [0, 31, 51, 30] [39, 47, 38] - 51-c65535: +: [0, 36, 32] [34] - 32-c65535: cint: #16 [0] [51] - 31-c65535: cint: #4 [0] [34] - 30-c65535: stre: [0, 27, 50, 26] [34] - 50-c65535: +: [0, 36, 28] [30] - 28-c65535: cint: #8 [0] [50] - 27-c65535: cint: #2 [0] [30] - 26-c65535: stre: [0, 25, 36, 3] [30] - 25-c65535: cint: #1 [0] [26, 46] - 39-c65535: stre: [0, 0, 36, 34] [47] - 36-c65535: stck: [0, 3] [51, 38, 39, 26, 50] - 22-c65535: stck: [0, 3] [49, 42, 8, 48] - 39-c65535: stre: [0, 0, 36, 34] [47] - 20-c65535: stre: [0, 17, 42, 14] [47, 47] - 46-c65535: +: [0, 38, 25] [47] - 18-c65535: cint: #3 [0] [42] - 17-c65535: cint: #1 [0] [20] - 14-c65535: stre: [0, 5, 49, 11] [20] - 49-c65535: +: [0, 22, 12] [14] - 12-c65535: cint: #2 [0] [49] - 11-c65535: stre: [0, 7, 48, 8] [14] - 48-c65535: +: [0, 22, 9] [11] - 9-c65535: cint: #1 [0] [48] - 8-c65535: stre: [0, 7, 22, 3] [11] - 7-c65535: cint: #0 [0] [8, 11] - 42-c65535: +: [0, 22, 18] [20] - 5-c65535: cint: #511 [0] [14] - 4-c65535: loops: [0] [] - 3-c65535: mem: [0] [47, 8, 22, 26, 36] - 2-c65535: ctrl: entry [0] [38] -b2: 0 0 [38] - 38-c65535: call: 1 0 [2, 36, 34] [46, 47] - 47-c65535: ret: [38, 46, 3, 20, 20, 34, 39] [1] - - diff --git a/f2.txt b/f2.txt deleted file mode 100644 index cf1429e1..00000000 --- a/f2.txt +++ /dev/null @@ -1,39 +0,0 @@ -start: 0 - 42-c65535: +: [0, 22, 18] [43, 20] - 36-c65535: stck: [0, 3] [51, 38, 39, 26, 50] - 39-c65535: stre: [0, 0, 36, 34] [47] - 34-c65535: stre: [0, 31, 51, 30] [39, 47, 38] - 51-c65535: +: [0, 36, 32] [34] - 32-c65535: cint: #16 [0] [51] - 31-c65535: cint: #4 [0] [34] - 30-c65535: stre: [0, 27, 50, 26] [34] - 50-c65535: +: [0, 36, 28] [30] - 28-c65535: cint: #8 [0] [50] - 27-c65535: cint: #2 [0] [30] - 26-c65535: stre: [0, 25, 36, 3] [30] - 25-c65535: cint: #1 [0] [26] - 39-c65535: stre: [0, 0, 36, 34] [47] - 45-c65535: &: [0, 43, 44] [46] - 22-c65535: stck: [0, 3] [49, 42, 8, 48] - 44-c65535: cint: #255 [0] [45] - 20-c65535: stre: [0, 17, 42, 14] [47, 47, 43] - 46-c65535: +: [0, 38, 45] [47] - 18-c65535: cint: #3 [0] [42] - 17-c65535: cint: #1 [0] [20] - 14-c65535: stre: [0, 5, 49, 11] [20] - 49-c65535: +: [0, 22, 12] [14] - 12-c65535: cint: #2 [0] [49] - 11-c65535: stre: [0, 7, 48, 8] [14] - 48-c65535: +: [0, 22, 9] [11] - 9-c65535: cint: #1 [0] [48] - 8-c65535: stre: [0, 7, 22, 3] [11] - 7-c65535: cint: #0 [0] [8, 11] - 43-c65535: load: [0, 42, 20] [45] - 5-c65535: cint: #511 [0] [14] - 4-c65535: loops: [0] [] - 3-c65535: mem: [0] [47, 8, 22, 26, 36] - 2-c65535: ctrl: entry [0] [38] -b2: 0 0 [38] - 38-c65535: call: 1 0 [2, 36, 34] [46, 47] - 47-c65535: ret: [38, 46, 3, 20, 20, 34, 39] [1] - diff --git a/lang/README.md b/lang/README.md index eed92352..90606ca4 100644 --- a/lang/README.md +++ b/lang/README.md @@ -1044,6 +1044,28 @@ main := fn(): uint { ### Just Testing Optimizations +#### null_check_test +```hb +get_ptr := fn(): ?^uint { + value := 0 + return &value +} + +main := fn(): uint { + ptr := get_ptr() + + if ptr == null { + return 0 + } + + loop if *ptr != 10 { + *ptr += 1 + } else break + + return *ptr +} +``` + #### const_folding_with_arg ```hb main := fn(arg: uint): uint { diff --git a/lang/src/son.rs b/lang/src/son.rs index fb8617f4..087d485c 100644 --- a/lang/src/son.rs +++ b/lang/src/son.rs @@ -879,6 +879,46 @@ impl Nodes { return Some(self.new_const(ty, op.apply_unop(value, is_float))); } } + K::Assert { kind, pos } => { + if self[target].ty == ty::Id::VOID { + let &[ctrl, cond] = self[target].inputs.as_slice() else { unreachable!() }; + if let K::CInt { value } = self[cond].kind { + let ty = if value != 0 { + ty::Id::NEVER + } else { + return Some(ctrl); + }; + return Some(self.new_node_nop(ty, K::Assert { kind, pos }, [ctrl, cond])); + } + + 'b: { + let mut cursor = ctrl; + loop { + if cursor == ENTRY { + break 'b; + } + + // TODO: do more inteligent checks on the condition + if self[cursor].kind == Kind::Else + && self[self[cursor].inputs[0]].inputs[1] == cond + { + return Some(ctrl); + } + if self[cursor].kind == Kind::Then + && self[self[cursor].inputs[0]].inputs[1] == cond + { + return Some(self.new_node_nop( + ty::Id::NEVER, + K::Assert { kind, pos }, + [ctrl, cond], + )); + } + + cursor = self.idom(cursor); + } + } + } + } K::If => { if self[target].ty == ty::Id::VOID { let &[ctrl, cond] = self[target].inputs.as_slice() else { unreachable!() }; @@ -1295,7 +1335,7 @@ impl Nodes { write!(out, " {node:>2}-c{:>2}: ", self[node].ralloc_backref)?; } match self[node].kind { - Kind::Start => unreachable!(), + Kind::Assert { .. } | Kind::Start => unreachable!(), Kind::End => return Ok(()), Kind::If => write!(out, " if: "), Kind::Region | Kind::Loop => writeln!(out, " goto: {node}"), @@ -1561,6 +1601,11 @@ impl ops::IndexMut for Nodes { } } +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum AssertKind { + NullCheck, +} + #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Default)] #[repr(u8)] pub enum Kind { @@ -1586,6 +1631,11 @@ pub enum Kind { Return, // [ctrl] Die, + // [ctrl, cond] + Assert { + kind: AssertKind, + pos: Pos, + }, // [ctrl] CInt { value: i64, @@ -1634,6 +1684,7 @@ impl Kind { | Self::Then | Self::Else | Self::Call { .. } + | Self::Assert { .. } | Self::If | Self::Region | Self::Loop @@ -3981,9 +4032,7 @@ impl<'a> Codegen<'a> { self.ci.scope.vars.drain(..).for_each(|v| v.remove_ignore_arg(&mut self.ci.nodes)); - self.ci.finalize(&mut self.pool.nid_stack, self.tys, self.files); - - if self.errors.borrow().len() == prev_err_len { + if self.finalize(prev_err_len) { self.ci.emit_body(self.tys, self.files, sig, self.pool); self.tys.ins.funcs[id as usize].code.append(&mut self.ci.code); self.tys.ins.funcs[id as usize].relocs.append(&mut self.ci.relocs); @@ -3992,6 +4041,32 @@ impl<'a> Codegen<'a> { self.pool.pop_ci(&mut self.ci); } + fn finalize(&mut self, prev_err_len: usize) -> bool { + self.ci.finalize(&mut self.pool.nid_stack, self.tys, self.files); + for (_, node) in self.ci.nodes.iter() { + if let Kind::Assert { kind: AssertKind::NullCheck, pos } = node.kind { + match node.ty { + ty::Id::NEVER => { + self.report( + pos, + "the value is always null, some checks might need to be inverted", + ); + } + _ => { + self.report( + pos, + "can't prove the value is not 'null', \ + use '@unwrap()' if you believe compiler is stupid, \ + or explicitly check for null and handle it \ + ('if == null { /* handle */ } else { /* use opt */ }')", + ); + } + } + } + } + self.errors.borrow().len() == prev_err_len + } + fn ty(&mut self, expr: &Expr) -> ty::Id { self.parse_ty(self.ci.file, expr, None, self.files) } @@ -4096,27 +4171,16 @@ impl<'a> Codegen<'a> { let null_check = self.gen_null_check(*opt, ty, TokenKind::Eq); // TODO: extract the if check int a fucntion - let ctrl = self.ci.nodes.new_node(ty::Id::VOID, Kind::If, [self.ci.ctrl.get(), null_check]); - let ctrl_ty = self.ci.nodes[ctrl].ty; - self.ci.nodes.remove(ctrl); + self.ci.ctrl.set( + self.ci.nodes.new_node( + ty::Id::VOID, + Kind::Assert { kind: AssertKind::NullCheck, pos }, + [self.ci.ctrl.get(), null_check], + ), + &mut self.ci.nodes, + ); let oty = mem::replace(&mut opt.ty, ty); - match ctrl_ty { - ty::Id::LEFT_UNREACHABLE => { - self.unwrap_opt_unchecked(ty, oty, opt); - } - ty::Id::RIGHT_UNREACHABLE => { - self.report(pos, "the value is always null, some checks might need to be inverted"); - } - _ => { - self.report( - pos, - "can't prove the value is not 'null', \ - use '@unwrap()' if you believe compiler is stupid, \ - or explicitly check for null and handle it \ - ('if == null { /* handle */ } else { /* use opt */ }')", - ); - } - } + self.unwrap_opt_unchecked(ty, oty, opt); } fn unwrap_opt_unchecked(&mut self, ty: ty::Id, oty: ty::Id, opt: &mut Value) { @@ -4247,13 +4311,9 @@ 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(&mut self.pool.nid_stack, self.tys, self.files); - let res = if self.errors.borrow().len() == prev_err_len { - self.emit_and_eval(file, ret, &mut []) - } else { - 1 - }; + let res = + if self.finalize(prev_err_len) { self.emit_and_eval(file, ret, &mut []) } else { 1 }; self.pool.pop_ci(&mut self.ci); self.ci.scope.vars = scope; @@ -4291,10 +4351,8 @@ impl TypeParser for Codegen<'_> { self.expr(&(Expr::Return { pos: expr.pos(), val: Some(expr) })); - self.ci.finalize(&mut self.pool.nid_stack, self.tys, self.files); - let ret = self.ci.ret.expect("for return type to be infered"); - if self.errors.borrow().len() == prev_err_len { + if self.finalize(prev_err_len) { let mut mem = vec![0u8; self.tys.size_of(ret) as usize]; self.emit_and_eval(file, ret, &mut mem); self.tys.ins.globals[gid as usize].data = mem; @@ -4388,6 +4446,7 @@ mod tests { fb_driver; // Purely Testing Examples; + null_check_test; only_break_loop; reading_idk; nonexistent_ident_import; diff --git a/lang/src/son/hbvm.rs b/lang/src/son/hbvm.rs index 4411cd4c..ddfce3fd 100644 --- a/lang/src/son/hbvm.rs +++ b/lang/src/son/hbvm.rs @@ -493,6 +493,7 @@ impl ItemCtx { } } Kind::Start + | Kind::Assert { .. } | Kind::Entry | Kind::Mem | Kind::End @@ -1021,6 +1022,7 @@ impl<'a> Function<'a> { let ops = vec![self.drg(nid)]; self.add_instr(nid, ops); } + Kind::Assert { .. } => unreachable!(), Kind::End | Kind::Phi | Kind::Arg | Kind::Mem | Kind::Loops => {} Kind::Load { .. } if node.ty.loc(self.tys) == Loc::Stack => { diff --git a/lang/tests/son_tests_inlining_loops.txt b/lang/tests/son_tests_inlining_loops.txt new file mode 100644 index 00000000..e69de29b diff --git a/lang/tests/son_tests_null_check_test.txt b/lang/tests/son_tests_null_check_test.txt new file mode 100644 index 00000000..d3bf60f0 --- /dev/null +++ b/lang/tests/son_tests_null_check_test.txt @@ -0,0 +1,26 @@ +get_ptr: + ADDI64 r254, r254, -8d + ADDI64 r1, r254, 0d + ADDI64 r254, r254, 8d + JALA r0, r31, 0a +main: + ADDI64 r254, r254, -8d + ST r31, r254, 0a, 8h + JAL r31, r0, :get_ptr + LI64 r3, 0d + JNE r1, r3, :0 + LI64 r1, 0d + JMP :1 + 0: LI64 r10, 10d + CP r2, r1 + 2: LD r1, r2, 0a, 8h + JEQ r1, r10, :1 + ADDI64 r3, r1, 1d + ST r3, r2, 0a, 8h + JMP :2 + 1: LD r31, r254, 0a, 8h + ADDI64 r254, r254, 8d + JALA r0, r31, 0a +code size: 208 +ret: 10 +status: Ok(())