From 3f9f99ff6567413cddde0f4463760adca6ce3cb8 Mon Sep 17 00:00:00 2001 From: Jakub Doka Date: Thu, 31 Oct 2024 10:36:18 +0100 Subject: [PATCH] adding optional values --- lang/README.md | 10 +- lang/src/lib.rs | 42 ++++- lang/src/son.rs | 225 ++++++++++++++++++++---- lang/src/son/hbvm.rs | 9 +- lang/tests/son_tests_nullable_types.txt | 51 ++++++ 5 files changed, 291 insertions(+), 46 deletions(-) diff --git a/lang/README.md b/lang/README.md index 5edd986..c0f7724 100644 --- a/lang/README.md +++ b/lang/README.md @@ -135,9 +135,6 @@ fib := fn(n: uint): uint { #### pointers ```hb main := fn(): uint { - n := @as(^uint, null) - if n != null return 9001 - a := 1 b := &a @@ -176,7 +173,12 @@ main := fn(): int { if c != null return 42 - return 0 + d := @as(?u16, null) + if decide() d = 0 + + if d == null return 69 + + return d } decide := fn(): bool return true diff --git a/lang/src/lib.rs b/lang/src/lib.rs index b35648d..b9153dd 100644 --- a/lang/src/lib.rs +++ b/lang/src/lib.rs @@ -454,6 +454,7 @@ mod ty { _ if oa == Self::from(NEVER) => ob, _ if ob == Self::from(NEVER) => oa, _ if oa == ob => oa, + _ if ob.is_optional() => ob, _ if oa.is_pointer() && ob.is_pointer() => return None, _ if a.is_signed() && b.is_signed() || a.is_unsigned() && b.is_unsigned() => ob, _ if a.is_unsigned() && b.is_signed() && a.repr() - U8 < b.repr() - I8 => ob, @@ -646,12 +647,12 @@ mod ty { pub enum Kind { Builtin, Struct, - Opt, Ptr, + Slice, + Opt, Func, Global, Module, - Slice, } } @@ -1295,6 +1296,14 @@ impl Types { self.ins.structs[stru as usize].size.set(oiter.offset); oiter.offset } + ty::Kind::Opt(opt) => { + let base = self.ins.opts[opt as usize].base; + if self.nieche_of(base).is_some() { + self.size_of(base) + } else { + self.size_of(base) + self.align_of(base) + } + } _ if let Some(size) = ty.simple_size() => size, ty => unimplemented!("size_of: {:?}", ty), } @@ -1344,10 +1353,27 @@ impl Types { } } - fn opt_flag_field(&self, ty: ty::Id) -> (Offset, ty::Id) { + fn opt_layout(&self, inner_ty: ty::Id) -> OptLayout { + match self.nieche_of(inner_ty) { + Some((_, flag_offset, flag_ty)) => { + OptLayout { flag_ty, flag_offset, payload_offset: 0 } + } + None => OptLayout { + flag_ty: ty::Id::BOOL, + flag_offset: 0, + payload_offset: self.align_of(inner_ty), + }, + } + } + + fn nieche_of(&self, ty: ty::Id) -> Option<(bool, Offset, ty::Id)> { match ty.expand() { - ty::Kind::Ptr(_) => (0, ty::Id::UINT), - _ => todo!("{ty:?}"), + ty::Kind::Ptr(_) => Some((false, 0, ty::Id::UINT)), + // TODO: cache this + ty::Kind::Struct(s) => OffsetIter::new(s, self).into_iter(self).find_map(|(f, off)| { + self.nieche_of(f.ty).map(|(uninit, o, ty)| (uninit, o + off, ty)) + }), + _ => None, } } @@ -1379,6 +1405,12 @@ impl Types { } } +struct OptLayout { + flag_ty: ty::Id, + flag_offset: Offset, + payload_offset: Offset, +} + struct OffsetIter { strct: ty::Struct, offset: Offset, diff --git a/lang/src/son.rs b/lang/src/son.rs index 92b664c..ec81663 100644 --- a/lang/src/son.rs +++ b/lang/src/son.rs @@ -12,8 +12,8 @@ use { task, ty::{self, Arg, ArrayLen, Loc, Tuple}, utils::{BitSet, Vc}, - FTask, Func, Global, Ident, Offset, OffsetIter, Reloc, Sig, StringRef, SymKey, TypeParser, - TypedReloc, Types, + FTask, Func, Global, Ident, Offset, OffsetIter, OptLayout, Reloc, Sig, StringRef, SymKey, + TypeParser, TypedReloc, Types, }, alloc::{string::String, vec::Vec}, core::{ @@ -2320,9 +2320,9 @@ impl<'a> Codegen<'a> { match oty.loc(self.tys) { Loc::Reg => Some(self.ci.nodes.new_const_lit(oty, 0)), Loc::Stack => { - let (off, flag_ty) = self.tys.opt_flag_field(ty); + let OptLayout { flag_ty, flag_offset, .. } = self.tys.opt_layout(ty); let stack = self.new_stack(oty); - let offset = self.offset(stack, off); + let offset = self.offset(stack, flag_offset); let value = self.ci.nodes.new_const(flag_ty, 0); self.store_mem(offset, flag_ty, value); Some(Value::ptr(stack).ty(oty)) @@ -2347,14 +2347,24 @@ impl<'a> Codegen<'a> { } } Expr::Bool { value, .. } => Some(self.ci.nodes.new_const_lit(ty::Id::BOOL, value)), - Expr::Number { value, .. } => Some(self.ci.nodes.new_const_lit( - ctx.ty.filter(|ty| ty.is_integer()).unwrap_or(ty::Id::DEFAULT_INT), - value, - )), - Expr::Float { value, .. } => Some(self.ci.nodes.new_const_lit( - ctx.ty.filter(|ty| ty.is_float()).unwrap_or(ty::Id::F32), - value as i64, - )), + Expr::Number { value, .. } => Some( + self.ci.nodes.new_const_lit( + ctx.ty + .map(|ty| self.tys.inner_of(ty).unwrap_or(ty)) + .filter(|ty| ty.is_integer()) + .unwrap_or(ty::Id::DEFAULT_INT), + value, + ), + ), + Expr::Float { value, .. } => Some( + self.ci.nodes.new_const_lit( + ctx.ty + .map(|ty| self.tys.inner_of(ty).unwrap_or(ty)) + .filter(|ty| ty.is_float()) + .unwrap_or(ty::Id::F32), + value as i64, + ), + ), Expr::Ident { id, .. } if let Some(index) = self.ci.scope.vars.iter().rposition(|v| v.id == id) => { @@ -2534,6 +2544,8 @@ impl<'a> Codegen<'a> { let ctx = Ctx { ty: ctx.ty.map(|ty| self.tys.make_ptr(ty)) }; let mut val = self.expr_ctx(val, ctx)?; + self.unwrap_opt(pos, &mut val); + let Some(base) = self.tys.base_of(val.ty) else { self.report( pos, @@ -2596,6 +2608,25 @@ impl<'a> Codegen<'a> { Some(Value::VOID) } + Expr::BinOp { left: &Expr::Null { pos }, .. } => { + self.report(pos, "'null' must always be no the right side of an expression"); + Value::NEVER + } + Expr::BinOp { + left, + op: op @ (TokenKind::Eq | TokenKind::Ne), + right: Expr::Null { .. }, + .. + } => { + let mut cmped = self.raw_expr(left)?; + self.strip_var(&mut cmped); + + let Some(ty) = self.tys.inner_of(cmped.ty) else { + return Some(self.ci.nodes.new_const_lit(ty::Id::BOOL, 1)); + }; + + Some(Value::new(self.gen_null_check(cmped, ty, op)).ty(ty::BOOL)) + } Expr::BinOp { left, pos, op, right } if !matches!(op, TokenKind::Assign | TokenKind::Decl) => { @@ -2685,17 +2716,27 @@ impl<'a> Codegen<'a> { } Expr::Directive { name: "sizeof", args: [ty], .. } => { let ty = self.ty(ty); - Some(self.ci.nodes.new_const_lit( - ctx.ty.filter(|ty| ty.is_integer()).unwrap_or(ty::Id::DEFAULT_INT), - self.tys.size_of(ty), - )) + Some( + self.ci.nodes.new_const_lit( + ctx.ty + .map(|ty| self.tys.inner_of(ty).unwrap_or(ty)) + .filter(|ty| ty.is_integer()) + .unwrap_or(ty::Id::DEFAULT_INT), + self.tys.size_of(ty), + ), + ) } Expr::Directive { name: "alignof", args: [ty], .. } => { let ty = self.ty(ty); - Some(self.ci.nodes.new_const_lit( - ctx.ty.filter(|ty| ty.is_integer()).unwrap_or(ty::Id::DEFAULT_INT), - self.tys.align_of(ty), - )) + Some( + self.ci.nodes.new_const_lit( + ctx.ty + .map(|ty| self.tys.inner_of(ty).unwrap_or(ty)) + .filter(|ty| ty.is_integer()) + .unwrap_or(ty::Id::DEFAULT_INT), + self.tys.align_of(ty), + ), + ) } Expr::Directive { name: "bitcast", args: [val], pos } => { let mut val = self.raw_expr(val)?; @@ -3929,6 +3970,103 @@ impl<'a> Codegen<'a> { } } + fn wrap_in_opt(&mut self, val: &mut Value) { + debug_assert!(!val.var); + + let oty = self.tys.make_opt(val.ty); + + if let Some((uninit, ..)) = self.tys.nieche_of(val.ty) { + self.strip_ptr(val); + val.ty = oty; + assert!(!uninit, "TODO"); + return; + } + + let OptLayout { flag_ty, flag_offset, payload_offset } = self.tys.opt_layout(val.ty); + + match oty.loc(self.tys) { + Loc::Reg => { + self.strip_ptr(val); + // registers have inverted offsets so that accessing the inner type is a noop + let flag_offset = self.tys.size_of(oty) - flag_offset - 1; + let fill = self.ci.nodes.new_const(oty, 1i64 << (flag_offset * 8 - 1)); + val.id = self + .ci + .nodes + .new_node(oty, Kind::BinOp { op: TokenKind::Bor }, [VOID, val.id, fill]); + val.ty = oty; + } + Loc::Stack if val.ty.loc(self.tys) == Loc::Reg => { + self.strip_ptr(val); + let stack = self.new_stack(oty); + let fill = self.ci.nodes.new_const(flag_ty, 1); + self.store_mem(stack, flag_ty, fill); + let off = self.offset(stack, payload_offset); + self.store_mem(off, val.ty, val.id); + val.id = stack; + val.ptr = true; + val.ty = oty; + } + _ => todo!(), + } + } + + fn unwrap_opt(&mut self, pos: Pos, opt: &mut Value) { + let Some(ty) = self.tys.inner_of(opt.ty) else { return }; + 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); + let oty = mem::replace(&mut opt.ty, ty); + match ctrl_ty { + ty::Id::LEFT_UNREACHABLE => { + if self.tys.nieche_of(ty).is_some() { + return; + } + + let OptLayout { payload_offset, .. } = self.tys.opt_layout(ty); + + match oty.loc(self.tys) { + Loc::Reg => {} + Loc::Stack => { + opt.id = self.offset(opt.id, payload_offset); + } + } + } + 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', \ + there is not nice syntax for bypassing this, sorry", + ); + } + } + } + + fn gen_null_check(&mut self, mut cmped: Value, ty: ty::Id, op: TokenKind) -> Nid { + let OptLayout { flag_ty, flag_offset, .. } = self.tys.opt_layout(ty); + + match cmped.ty.loc(self.tys) { + Loc::Reg => { + self.strip_ptr(&mut cmped); + let inps = [VOID, cmped.id, self.ci.nodes.new_const(cmped.ty, 0)]; + self.ci.nodes.new_node(ty::Id::BOOL, Kind::BinOp { op }, inps) + } + Loc::Stack => { + cmped.id = self.offset(cmped.id, flag_offset); + cmped.ty = flag_ty; + self.strip_ptr(&mut cmped); + let inps = [VOID, cmped.id, self.ci.nodes.new_const(ty, 0)]; + self.ci.nodes.new_node(ty::Id::BOOL, Kind::BinOp { op }, inps) + } + } + } + #[track_caller] fn assert_ty( &mut self, @@ -3940,24 +4078,43 @@ impl<'a> Codegen<'a> { if let Some(upcasted) = src.ty.try_upcast(expected) && upcasted == expected { + if src.ty.is_never() { + return true; + } + if src.ty != upcasted { - debug_assert!( - src.ty.is_integer() || src.ty == ty::Id::NEVER, - "{} {}", - self.ty_display(src.ty), - self.ty_display(upcasted) - ); - debug_assert!( - upcasted.is_integer() || src.ty == ty::Id::NEVER, - "{} {}", - self.ty_display(src.ty), - self.ty_display(upcasted) - ); - self.extend(src, upcasted); + if let Some(inner) = self.tys.inner_of(upcasted) { + if inner != src.ty { + self.assert_ty(pos, src, inner, hint); + } + self.wrap_in_opt(src); + } else { + debug_assert!( + src.ty.is_integer() || src.ty == ty::Id::NEVER, + "{} {}", + self.ty_display(src.ty), + self.ty_display(upcasted) + ); + debug_assert!( + upcasted.is_integer() || src.ty == ty::Id::NEVER, + "{} {}", + self.ty_display(src.ty), + self.ty_display(upcasted) + ); + self.extend(src, upcasted); + } } true } else { + if let Some(inner) = self.tys.inner_of(src.ty) + && inner.try_upcast(expected) == Some(expected) + { + self.unwrap_opt(pos, src); + return self.assert_ty(pos, src, expected, hint); + } + let ty = self.ty_display(src.ty); + let expected = self.ty_display(expected); self.report(pos, fa!("expected {hint} to be of type {expected}, got {ty}")); false @@ -3966,10 +4123,10 @@ impl<'a> Codegen<'a> { fn extend(&mut self, value: &mut Value, to: ty::Id) { self.strip_ptr(value); - value.ty = to; let mask = self.ci.nodes.new_const(to, (1i64 << (self.tys.size_of(value.ty) * 8)) - 1); let inps = [VOID, value.id, mask]; *value = self.ci.nodes.new_node_lit(to, Kind::BinOp { op: TokenKind::Band }, inps); + value.ty = to; } #[track_caller] diff --git a/lang/src/son/hbvm.rs b/lang/src/son/hbvm.rs index 4f73283..248f80a 100644 --- a/lang/src/son/hbvm.rs +++ b/lang/src/son/hbvm.rs @@ -226,17 +226,20 @@ impl ItemCtx { let node = &fuc.nodes[nid]; let mut extend = |base: ty::Id, dest: ty::Id, from: usize, to: usize| { - if base.simple_size() == dest.simple_size() { + let (bsize, dsize) = (tys.size_of(base), tys.size_of(dest)); + debug_assert!(bsize <= 8); + debug_assert!(dsize <= 8); + if bsize == dsize { return Default::default(); } match (base.is_signed(), dest.is_signed()) { (true, true) => { let op = [instrs::sxt8, instrs::sxt16, instrs::sxt32] - [base.simple_size().unwrap().ilog2() as usize]; + [bsize.ilog2() as usize]; op(atr(allocs[to]), atr(allocs[from])) } _ => { - let mask = (1u64 << (base.simple_size().unwrap() * 8)) - 1; + let mask = (1u64 << (bsize * 8)) - 1; instrs::andi(atr(allocs[to]), atr(allocs[from]), mask) } } diff --git a/lang/tests/son_tests_nullable_types.txt b/lang/tests/son_tests_nullable_types.txt index e69de29..c2fe65f 100644 --- a/lang/tests/son_tests_nullable_types.txt +++ b/lang/tests/son_tests_nullable_types.txt @@ -0,0 +1,51 @@ +decide: + LI8 r1, 1b + JALA r0, r31, 0a +main: + ADDI64 r254, r254, -40d + ST r31, r254, 24a, 16h + JAL r31, r0, :decide + LI64 r3, 0d + ANDI r1, r1, 255d + JNE r1, r0, :0 + CP r32, r3 + JMP :1 + 0: ADDI64 r32, r254, 16d + 1: JNE r32, r3, :2 + LI64 r1, 9001d + JMP :3 + 2: JAL r31, r0, :decide + ANDI r1, r1, 255d + JNE r1, r0, :4 + LI8 r6, 1b + ST r6, r254, 0a, 1h + LD r7, r32, 0a, 8h + ST r7, r254, 8a, 8h + JMP :5 + 4: LI8 r1, 0b + ST r1, r254, 0a, 1h + 5: LI64 r6, 0d + LD r7, r254, 0a, 1h + ANDI r7, r7, 255d + JEQ r7, r6, :6 + LI64 r1, 42d + JMP :3 + 6: JAL r31, r0, :decide + LI32 r2, 0w + ANDI r1, r1, 255d + JNE r1, r0, :7 + CP r8, r2 + JMP :8 + 7: LI32 r8, 8388608w + 8: ANDI r8, r8, 4294967295d + ANDI r2, r2, 4294967295d + JNE r8, r2, :9 + LI64 r1, 69d + JMP :3 + 9: ANDI r1, r8, 65535d + 3: LD r31, r254, 24a, 16h + ADDI64 r254, r254, 40d + JALA r0, r31, 0a +code size: 389 +ret: 0 +status: Ok(())