deferring all null checks after the peepholes

This commit is contained in:
Jakub Doka 2024-11-03 21:13:24 +01:00
parent 798000c756
commit 44fc9c3e2e
No known key found for this signature in database
GPG key ID: C6E9A89936B8C143
8 changed files with 142 additions and 157 deletions

47
f.txt
View file

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

38
f1.txt
View file

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

39
f2.txt
View file

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

View file

@ -1044,6 +1044,28 @@ main := fn(): uint {
### Just Testing Optimizations ### 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 #### const_folding_with_arg
```hb ```hb
main := fn(arg: uint): uint { main := fn(arg: uint): uint {

View file

@ -879,6 +879,46 @@ impl Nodes {
return Some(self.new_const(ty, op.apply_unop(value, is_float))); 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 => { K::If => {
if self[target].ty == ty::Id::VOID { if self[target].ty == ty::Id::VOID {
let &[ctrl, cond] = self[target].inputs.as_slice() else { unreachable!() }; 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)?; write!(out, " {node:>2}-c{:>2}: ", self[node].ralloc_backref)?;
} }
match self[node].kind { match self[node].kind {
Kind::Start => unreachable!(), Kind::Assert { .. } | Kind::Start => unreachable!(),
Kind::End => return Ok(()), 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}"),
@ -1561,6 +1601,11 @@ impl ops::IndexMut<Nid> 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)] #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
#[repr(u8)] #[repr(u8)]
pub enum Kind { pub enum Kind {
@ -1586,6 +1631,11 @@ pub enum Kind {
Return, Return,
// [ctrl] // [ctrl]
Die, Die,
// [ctrl, cond]
Assert {
kind: AssertKind,
pos: Pos,
},
// [ctrl] // [ctrl]
CInt { CInt {
value: i64, value: i64,
@ -1634,6 +1684,7 @@ impl Kind {
| Self::Then | Self::Then
| Self::Else | Self::Else
| Self::Call { .. } | Self::Call { .. }
| Self::Assert { .. }
| Self::If | Self::If
| Self::Region | Self::Region
| Self::Loop | 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.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.finalize(prev_err_len) {
if self.errors.borrow().len() == prev_err_len {
self.ci.emit_body(self.tys, self.files, sig, self.pool); 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].code.append(&mut self.ci.code);
self.tys.ins.funcs[id as usize].relocs.append(&mut self.ci.relocs); 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); 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(<opt>)' if you believe compiler is stupid, \
or explicitly check for null and handle it \
('if <opt> == null { /* handle */ } else { /* use opt */ }')",
);
}
}
}
}
self.errors.borrow().len() == prev_err_len
}
fn ty(&mut self, expr: &Expr) -> ty::Id { fn ty(&mut self, expr: &Expr) -> ty::Id {
self.parse_ty(self.ci.file, expr, None, self.files) 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); let null_check = self.gen_null_check(*opt, ty, TokenKind::Eq);
// TODO: extract the if check int a fucntion // 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]); self.ci.ctrl.set(
let ctrl_ty = self.ci.nodes[ctrl].ty; self.ci.nodes.new_node(
self.ci.nodes.remove(ctrl); ty::Id::VOID,
let oty = mem::replace(&mut opt.ty, ty); Kind::Assert { kind: AssertKind::NullCheck, pos },
match ctrl_ty { [self.ci.ctrl.get(), null_check],
ty::Id::LEFT_UNREACHABLE => { ),
self.unwrap_opt_unchecked(ty, oty, opt); &mut self.ci.nodes,
}
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(<opt>)' if you believe compiler is stupid, \
or explicitly check for null and handle it \
('if <opt> == null { /* handle */ } else { /* use opt */ }')",
); );
} let oty = mem::replace(&mut opt.ty, ty);
} self.unwrap_opt_unchecked(ty, oty, opt);
} }
fn unwrap_opt_unchecked(&mut self, ty: ty::Id, oty: ty::Id, opt: &mut Value) { 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) }); 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(&mut self.pool.nid_stack, self.tys, self.files);
let res = if self.errors.borrow().len() == prev_err_len { let res =
self.emit_and_eval(file, ret, &mut []) if self.finalize(prev_err_len) { self.emit_and_eval(file, ret, &mut []) } else { 1 };
} else {
1
};
self.pool.pop_ci(&mut self.ci); self.pool.pop_ci(&mut self.ci);
self.ci.scope.vars = scope; self.ci.scope.vars = scope;
@ -4291,10 +4351,8 @@ 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(&mut self.pool.nid_stack, self.tys, self.files);
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.finalize(prev_err_len) {
let mut mem = vec![0u8; self.tys.size_of(ret) as usize]; let mut mem = vec![0u8; self.tys.size_of(ret) as usize];
self.emit_and_eval(file, ret, &mut mem); self.emit_and_eval(file, ret, &mut mem);
self.tys.ins.globals[gid as usize].data = mem; self.tys.ins.globals[gid as usize].data = mem;
@ -4388,6 +4446,7 @@ mod tests {
fb_driver; fb_driver;
// Purely Testing Examples; // Purely Testing Examples;
null_check_test;
only_break_loop; only_break_loop;
reading_idk; reading_idk;
nonexistent_ident_import; nonexistent_ident_import;

View file

@ -493,6 +493,7 @@ impl ItemCtx {
} }
} }
Kind::Start Kind::Start
| Kind::Assert { .. }
| Kind::Entry | Kind::Entry
| Kind::Mem | Kind::Mem
| Kind::End | Kind::End
@ -1021,6 +1022,7 @@ 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::Assert { .. } => unreachable!(),
Kind::End | Kind::End |
Kind::Phi | Kind::Arg | Kind::Mem | Kind::Loops => {} 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 => {

View file

View file

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