From 80558ea7e679fae346c4ac821c5dfcb827fab5ca Mon Sep 17 00:00:00 2001 From: Jakub Doka Date: Tue, 29 Oct 2024 13:36:12 +0100 Subject: [PATCH] adding floating point arithmetic --- lang/README.md | 7 + lang/src/fmt.rs | 6 +- lang/src/lexer.rs | 59 +++++-- lang/src/lib.rs | 12 +- lang/src/parser.rs | 13 ++ lang/src/son.rs | 46 +++-- lang/src/son/hbvm.rs | 157 +++++++++++++----- .../son_tests_floating_point_arithmetic.txt | 6 + 8 files changed, 238 insertions(+), 68 deletions(-) create mode 100644 lang/tests/son_tests_floating_point_arithmetic.txt diff --git a/lang/README.md b/lang/README.md index 86d489c..9f28bbc 100644 --- a/lang/README.md +++ b/lang/README.md @@ -51,6 +51,13 @@ main := fn(): uint { } ``` +#### floating_point_arithmetic +```hb +main := fn(): f32 { + return 10. - 20. / 2. + 4. * (2. + 2.) - 4. * 4. + -1. +} +``` + #### functions ```hb main := fn(): uint { diff --git a/lang/src/fmt.rs b/lang/src/fmt.rs index 7c0c0fe..fb371f7 100644 --- a/lang/src/fmt.rs +++ b/lang/src/fmt.rs @@ -366,6 +366,9 @@ impl<'a> Formatter<'a> { let mut buf = [0u8; 64]; f.write_str(display_radix(radix, value as u64, &mut buf)) } + Expr::Float { pos, .. } => { + f.write_str(&self.source[Lexer::restore(self.source, pos).eat().range()]) + } Expr::Bool { value, .. } => f.write_str(if value { "true" } else { "false" }), Expr::Idk { .. } => f.write_str("idk"), Expr::Null { .. } => f.write_str("null"), @@ -475,7 +478,8 @@ pub mod test { let len = crate::fmt::minify(&mut minned); minned.truncate(len); - let ast = parser::Ast::new(ident, minned, &mut Ctx::default(), &mut parser::no_loader); + let mut ctx = Ctx::default(); + let ast = parser::Ast::new(ident, minned, &mut ctx, &mut parser::no_loader); //log::error!( // "{} / {} = {} | {} / {} = {}", // ast.mem.size(), diff --git a/lang/src/lexer.rs b/lang/src/lexer.rs index fbaa25d..1af1760 100644 --- a/lang/src/lexer.rs +++ b/lang/src/lexer.rs @@ -116,6 +116,7 @@ pub enum TokenKind { Ident, Number, + Float, Eof, Ct, @@ -190,7 +191,43 @@ impl TokenKind { matches!(self, S::Eq | S::Ne | S::Bor | S::Xor | S::Band | S::Add | S::Mul) } - pub fn apply_binop(self, a: i64, b: i64) -> i64 { + pub fn is_supported_float_op(self) -> bool { + matches!( + self, + Self::Add + | Self::Sub + | Self::Mul + | Self::Div + | Self::Eq + | Self::Ne + | Self::Le + | Self::Ge + | Self::Lt + | Self::Gt + ) + } + + pub fn apply_binop(self, a: i64, b: i64, float: bool) -> i64 { + if float { + debug_assert!(self.is_supported_float_op()); + let [a, b] = [f64::from_bits(a as _), f64::from_bits(b as _)]; + let res = match self { + Self::Add => a + b, + Self::Sub => a - b, + Self::Mul => a * b, + Self::Div => a / b, + Self::Eq => return (a == b) as i64, + Self::Ne => return (a != b) as i64, + Self::Lt => return (a < b) as i64, + Self::Gt => return (a > b) as i64, + Self::Le => return (a >= b) as i64, + Self::Ge => return (a <= b) as i64, + _ => todo!("floating point op: {self}"), + }; + + return res.to_bits() as _; + } + match self { Self::Add => a.wrapping_add(b), Self::Sub => a.wrapping_sub(b), @@ -214,15 +251,6 @@ impl TokenKind { } } - pub fn cmp_against(self) -> Option { - Some(match self { - TokenKind::Le | TokenKind::Gt => 1, - TokenKind::Ne | TokenKind::Eq => 0, - TokenKind::Ge | TokenKind::Lt => (-1i64) as _, - _ => return None, - }) - } - pub fn is_homogenous(&self) -> bool { self.precedence() != Self::Eq.precedence() && self.precedence() != Self::Gt.precedence() @@ -254,6 +282,7 @@ gen_token_kind! { CtIdent, Ident, Number, + Float, Eof, Directive, #[keywords] @@ -418,7 +447,15 @@ impl<'a> Lexer<'a> { while let Some(b'0'..=b'9') = self.peek() { self.advance(); } - T::Number + + if self.advance_if(b'.') { + while let Some(b'0'..=b'9') = self.peek() { + self.advance(); + } + T::Float + } else { + T::Number + } } b'a'..=b'z' | b'A'..=b'Z' | b'_' | 127.. => { advance_ident(self); diff --git a/lang/src/lib.rs b/lang/src/lib.rs index 14d7833..a301b08 100644 --- a/lang/src/lib.rs +++ b/lang/src/lib.rs @@ -409,6 +409,10 @@ mod ty { } } + pub fn is_float(self) -> bool { + matches!(self.repr(), F32 | F64) || self.is_never() + } + pub fn is_signed(self) -> bool { matches!(self.repr(), I8..=INT) || self.is_never() } @@ -465,8 +469,8 @@ mod ty { Kind::Ptr(_) => 8, Kind::Builtin(VOID) => 0, Kind::Builtin(NEVER) => 0, - Kind::Builtin(INT | UINT) => 8, - Kind::Builtin(I32 | U32 | TYPE) => 4, + Kind::Builtin(INT | UINT | F64) => 8, + Kind::Builtin(I32 | U32 | TYPE | F32) => 4, Kind::Builtin(I16 | U16) => 2, Kind::Builtin(I8 | U8 | BOOL) => 1, _ => return None, @@ -547,7 +551,6 @@ mod ty { };)* } - #[expect(dead_code)] impl Id { $(pub const $name: Self = Kind::Builtin($name).compress();)* } @@ -584,7 +587,8 @@ mod ty { I16; I32; INT; - + F32; + F64; } macro_rules! type_kind { diff --git a/lang/src/parser.rs b/lang/src/parser.rs index 024c592..91de4b5 100644 --- a/lang/src/parser.rs +++ b/lang/src/parser.rs @@ -472,6 +472,14 @@ impl<'a, 'b> Parser<'a, 'b> { radix, } } + T::Float => E::Float { + pos, + value: match ::from_str(self.lexer.slice(token.range())) + { + Ok(f) => f.to_bits(), + Err(e) => self.report(token.start, format_args!("invalid float: {e}"))?, + }, + }, T::LParen => { let expr = self.expr()?; self.expect_advance(T::RParen)?; @@ -811,6 +819,11 @@ generate_expr! { value: i64, radix: Radix, }, + /// `'[0-9]+.[0-9]*'` + Float { + pos: Pos, + value: u64, + }, /// node: precedence defined in `OP` applies /// `Expr OP Expr` BinOp { diff --git a/lang/src/son.rs b/lang/src/son.rs index 8576204..f55eecc 100644 --- a/lang/src/son.rs +++ b/lang/src/son.rs @@ -692,12 +692,16 @@ impl Nodes { }; let ty = self[target].ty; + let is_float = self[lhs].ty.is_float(); + if let (&K::CInt { value: a }, &K::CInt { value: b }) = (&self[lhs].kind, &self[rhs].kind) { - return Some( - self.new_node(ty, K::CInt { value: op.apply_binop(a, b) }, [ctrl]), - ); + return Some(self.new_node( + ty, + K::CInt { value: op.apply_binop(a, b, is_float) }, + [ctrl], + )); } if lhs == rhs { @@ -734,10 +738,11 @@ impl Nodes { && let K::CInt { value: bv } = self[rhs].kind { // (a op #b) op #c => a op (#b op #c) - let new_rhs = - self.new_node_nop(ty, K::CInt { value: op.apply_binop(av, bv) }, [ - ctrl, - ]); + let new_rhs = self.new_node_nop( + ty, + K::CInt { value: op.apply_binop(av, bv, is_float) }, + [ctrl], + ); return Some(self.new_node(ty, K::BinOp { op }, [ctrl, a, new_rhs])); } @@ -2235,6 +2240,11 @@ impl<'a> Codegen<'a> { Kind::CInt { value }, [VOID], )), + Expr::Float { value, .. } => Some(self.ci.nodes.new_node_lit( + ctx.ty.filter(|ty| ty.is_float()).unwrap_or(ty::Id::F32), + Kind::CInt { value: value as _ }, + [VOID], + )), Expr::Ident { id, .. } if let Some(index) = self.ci.scope.vars.iter().rposition(|v| v.id == id) => { @@ -2425,10 +2435,21 @@ impl<'a> Codegen<'a> { Expr::UnOp { pos, op: op @ TokenKind::Sub, val } => { let val = self.expr_ctx(val, Ctx::default().with_ty(ctx.ty.unwrap_or(ty::Id::INT)))?; - if !val.ty.is_integer() { + if val.ty.is_integer() { + Some(self.ci.nodes.new_node_lit(val.ty, Kind::UnOp { op }, [VOID, val.id])) + } else if val.ty.is_float() { + let value = self.ci.nodes.new_node_nop( + val.ty, + Kind::CInt { value: (-1f64).to_bits() as _ }, + [VOID], + ); + Some(self.ci.nodes.new_node_lit(val.ty, Kind::BinOp { op: TokenKind::Mul }, [ + VOID, val.id, value, + ])) + } else { self.report(pos, fa!("cant negate '{}'", self.ty_display(val.ty))); + Value::NEVER } - Some(self.ci.nodes.new_node_lit(val.ty, Kind::UnOp { op }, [VOID, val.id])) } Expr::BinOp { left, op: TokenKind::Decl, right, .. } => { let mut right = self.expr(right)?; @@ -2473,7 +2494,11 @@ impl<'a> Codegen<'a> { self.strip_var(&mut lhs); match lhs.ty.expand() { - _ if lhs.ty.is_pointer() || lhs.ty.is_integer() || lhs.ty == ty::Id::BOOL => { + _ if lhs.ty.is_pointer() + || lhs.ty.is_integer() + || lhs.ty == ty::Id::BOOL + || (lhs.ty.is_float() && op.is_supported_float_op()) => + { self.strip_ptr(&mut lhs); self.ci.nodes.lock(lhs.id); let rhs = self.expr_ctx(right, Ctx::default().with_ty(lhs.ty)); @@ -3924,6 +3949,7 @@ mod tests { // Tour Examples main_fn; arithmetic; + floating_point_arithmetic; functions; comments; if_statements; diff --git a/lang/src/son/hbvm.rs b/lang/src/son/hbvm.rs index efc5b6c..2a82aeb 100644 --- a/lang/src/son/hbvm.rs +++ b/lang/src/son/hbvm.rs @@ -247,7 +247,7 @@ impl ItemCtx { let &[_, cnd] = node.inputs.as_slice() else { unreachable!() }; if let Kind::BinOp { op } = fuc.nodes[cnd].kind && let Some((op, swapped)) = - op.cond_op(fuc.nodes[fuc.nodes[cnd].inputs[1]].ty.is_signed()) + op.cond_op(fuc.nodes[fuc.nodes[cnd].inputs[1]].ty) { let &[lhs, rhs] = allocs else { unreachable!() }; let &[_, lh, rh] = fuc.nodes[cnd].inputs.as_slice() else { @@ -306,6 +306,16 @@ impl ItemCtx { self.emit(instrs::jmp(0)); } } + Kind::CInt { value } if node.ty.is_float() => { + self.emit(match node.ty { + ty::Id::F32 => instrs::li32( + atr(allocs[0]), + (f64::from_bits(value as _) as f32).to_bits(), + ), + ty::Id::F64 => instrs::li64(atr(allocs[0]), value as _), + _ => unreachable!(), + }); + } Kind::CInt { value } => self.emit(match tys.size_of(node.ty) { 1 => instrs::li8(atr(allocs[0]), value as _), 2 => instrs::li16(atr(allocs[0]), value as _), @@ -319,33 +329,48 @@ impl ItemCtx { } Kind::BinOp { .. } if node.lock_rc != 0 => {} Kind::BinOp { op } => { - let &[.., rhs] = node.inputs.as_slice() else { unreachable!() }; + let &[.., lh, rh] = node.inputs.as_slice() else { unreachable!() }; - if let Kind::CInt { value } = fuc.nodes[rhs].kind - && fuc.nodes[rhs].lock_rc != 0 - && let Some(op) = - op.imm_binop(node.ty.is_signed(), fuc.tys.size_of(node.ty)) + if let Kind::CInt { value } = fuc.nodes[rh].kind + && fuc.nodes[rh].lock_rc != 0 + && let Some(op) = op.imm_binop(node.ty) { let &[dst, lhs] = allocs else { unreachable!() }; self.emit(op(atr(dst), atr(lhs), value as _)); } else if let Some(op) = - op.binop(node.ty.is_signed(), fuc.tys.size_of(node.ty)) + op.binop(node.ty).or(op.float_cmp(fuc.nodes[lh].ty)) { let &[dst, lhs, rhs] = allocs else { unreachable!() }; self.emit(op(atr(dst), atr(lhs), atr(rhs))); } else if let Some(against) = op.cmp_against() { - let &[_, lh, rh] = node.inputs.as_slice() else { unreachable!() }; + let op_ty = fuc.nodes[lh].ty; + self.emit(extend(fuc.nodes[lh].ty, fuc.nodes[lh].ty.extend(), 0, 0)); self.emit(extend(fuc.nodes[rh].ty, fuc.nodes[rh].ty.extend(), 1, 1)); - - let signed = fuc.nodes[lh].ty.is_signed(); - let op_fn = if signed { instrs::cmps } else { instrs::cmpu }; let &[dst, lhs, rhs] = allocs else { unreachable!() }; - self.emit(op_fn(atr(dst), atr(lhs), atr(rhs))); - self.emit(instrs::cmpui(atr(dst), atr(dst), against)); - if matches!(op, TokenKind::Eq | TokenKind::Lt | TokenKind::Gt) { + + if op_ty.is_float() && matches!(op, TokenKind::Le | TokenKind::Ge) { + let opop = match op { + TokenKind::Le => TokenKind::Gt, + TokenKind::Ge => TokenKind::Lt, + _ => unreachable!(), + }; + let op_fn = opop.float_cmp(op_ty).unwrap(); + self.emit(op_fn(atr(dst), atr(lhs), atr(rhs))); self.emit(instrs::not(atr(dst), atr(dst))); + } else if op_ty.is_integer() { + let op_fn = + if op_ty.is_signed() { instrs::cmps } else { instrs::cmpu }; + self.emit(op_fn(atr(dst), atr(lhs), atr(rhs))); + self.emit(instrs::cmpui(atr(dst), atr(dst), against)); + if matches!(op, TokenKind::Eq | TokenKind::Lt | TokenKind::Gt) { + self.emit(instrs::not(atr(dst), atr(dst))); + } + } else { + todo!("unhandled operator: {op}"); } + } else { + todo!("unhandled operator: {op}"); } } Kind::Call { args, func } => { @@ -725,7 +750,7 @@ impl<'a> Function<'a> { let &[mut then, mut else_] = node.outputs.as_slice() else { unreachable!() }; if let Kind::BinOp { op } = self.nodes[cond].kind - && let Some((_, swapped)) = op.cond_op(node.ty.is_signed()) + && let Some((_, swapped)) = op.cond_op(node.ty) { if swapped { mem::swap(&mut then, &mut else_); @@ -789,9 +814,9 @@ impl<'a> Function<'a> { if node.outputs.iter().all(|&o| { let ond = &self.nodes[o]; matches!(ond.kind, Kind::BinOp { op } - if op.imm_binop(ond.ty.is_signed(), 8).is_some() + if op.imm_binop(ond.ty).is_some() && self.nodes.is_const(ond.inputs[2]) - && op.cond_op(ond.ty.is_signed()).is_none()) + && op.cond_op(ond.ty).is_none()) }) => { self.nodes.lock(nid) @@ -853,7 +878,7 @@ impl<'a> Function<'a> { self.nodes.lock(nid) } Kind::BinOp { op } - if op.cond_op(node.ty.is_signed()).is_some() + if op.cond_op(node.ty).is_some() && node.outputs.iter().all(|&n| self.nodes[n].kind == Kind::If) => { self.nodes.lock(nid) @@ -955,8 +980,8 @@ impl<'a> Function<'a> { Kind::Stck | Kind::Arg if node.outputs.iter().all(|&n| { matches!(self.nodes[n].kind, Kind::Load - if self.nodes[n].ty.loc(self.tys) == Loc::Reg) - || matches!(self.nodes[n].kind, Kind::Stre + if self.nodes[n].ty.loc(self.tys) == Loc::Reg) + || matches!(self.nodes[n].kind, Kind::Stre if self.nodes[n].ty.loc(self.tys) == Loc::Reg && self.nodes[n].inputs[1] != nid) || matches!(self.nodes[n].kind, Kind::BinOp { op: TokenKind::Add } @@ -1183,8 +1208,36 @@ impl regalloc2::Function for Function<'_> { } impl TokenKind { + pub fn cmp_against(self) -> Option { + Some(match self { + TokenKind::Le | TokenKind::Gt => 1, + TokenKind::Ne | TokenKind::Eq => 0, + TokenKind::Ge | TokenKind::Lt => (-1i64) as _, + _ => return None, + }) + } + + pub fn float_cmp(self, ty: ty::Id) -> Option EncodedInstr> { + if !ty.is_float() { + return None; + } + let size = ty.simple_size().unwrap(); + + let ops = match self { + TokenKind::Gt => [instrs::fcmpgt32, instrs::fcmpgt64], + TokenKind::Lt => [instrs::fcmplt32, instrs::fcmplt64], + _ => return None, + }; + + Some(ops[size.ilog2() as usize - 2]) + } + #[expect(clippy::type_complexity)] - fn cond_op(self, signed: bool) -> Option<(fn(u8, u8, i16) -> EncodedInstr, bool)> { + fn cond_op(self, ty: ty::Id) -> Option<(fn(u8, u8, i16) -> EncodedInstr, bool)> { + if ty.is_float() { + return None; + } + let signed = ty.is_signed(); Some(( match self { Self::Le if signed => instrs::jgts, @@ -1203,31 +1256,45 @@ impl TokenKind { )) } - fn binop(self, signed: bool, size: u32) -> Option EncodedInstr> { - macro_rules! div { ($($op:ident),*) => {[$(|a, b, c| $op(a, 0, b, c)),*]}; } - macro_rules! rem { ($($op:ident),*) => {[$(|a, b, c| $op(0, a, b, c)),*]}; } + fn binop(self, ty: ty::Id) -> Option EncodedInstr> { + let size = ty.simple_size().unwrap(); + if ty.is_integer() || ty == ty::Id::BOOL || ty.is_pointer() { + macro_rules! div { ($($op:ident),*) => {[$(|a, b, c| $op(a, 0, b, c)),*]}; } + macro_rules! rem { ($($op:ident),*) => {[$(|a, b, c| $op(0, a, b, c)),*]}; } + let signed = ty.is_signed(); - let ops = match self { - Self::Add => [add8, add16, add32, add64], - Self::Sub => [sub8, sub16, sub32, sub64], - Self::Mul => [mul8, mul16, mul32, mul64], - Self::Div if signed => div!(dirs8, dirs16, dirs32, dirs64), - Self::Div => div!(diru8, diru16, diru32, diru64), - Self::Mod if signed => rem!(dirs8, dirs16, dirs32, dirs64), - Self::Mod => rem!(diru8, diru16, diru32, diru64), - Self::Band => return Some(and), - Self::Bor => return Some(or), - Self::Xor => return Some(xor), - Self::Shl => [slu8, slu16, slu32, slu64], - Self::Shr if signed => [srs8, srs16, srs32, srs64], - Self::Shr => [sru8, sru16, sru32, sru64], - _ => return None, - }; + let ops = match self { + Self::Add => [add8, add16, add32, add64], + Self::Sub => [sub8, sub16, sub32, sub64], + Self::Mul => [mul8, mul16, mul32, mul64], + Self::Div if signed => div!(dirs8, dirs16, dirs32, dirs64), + Self::Div => div!(diru8, diru16, diru32, diru64), + Self::Mod if signed => rem!(dirs8, dirs16, dirs32, dirs64), + Self::Mod => rem!(diru8, diru16, diru32, diru64), + Self::Band => return Some(and), + Self::Bor => return Some(or), + Self::Xor => return Some(xor), + Self::Shl => [slu8, slu16, slu32, slu64], + Self::Shr if signed => [srs8, srs16, srs32, srs64], + Self::Shr => [sru8, sru16, sru32, sru64], + _ => return None, + }; - Some(ops[size.ilog2() as usize]) + Some(ops[size.ilog2() as usize]) + } else { + debug_assert!(ty.is_float(), "{self} {ty:?}"); + let ops = match self { + Self::Add => [fadd32, fadd64], + Self::Sub => [fsub32, fsub64], + Self::Mul => [fmul32, fmul64], + Self::Div => [fdiv32, fdiv64], + _ => return None, + }; + Some(ops[size.ilog2() as usize - 2]) + } } - fn imm_binop(self, signed: bool, size: u32) -> Option EncodedInstr> { + fn imm_binop(self, ty: ty::Id) -> Option EncodedInstr> { macro_rules! def_op { ($name:ident |$a:ident, $b:ident, $c:ident| $($tt:tt)*) => { macro_rules! $name { @@ -1240,9 +1307,14 @@ impl TokenKind { }; } + if ty.is_float() { + return None; + } + def_op!(basic_op | a, b, c | a, b, c as _); def_op!(sub_op | a, b, c | a, b, c.wrapping_neg() as _); + let signed = ty.is_signed(); let ops = match self { Self::Add => basic_op!(addi8, addi16, addi32, addi64), Self::Sub => sub_op!(addi8, addi16, addi32, addi64), @@ -1256,6 +1328,7 @@ impl TokenKind { _ => return None, }; + let size = ty.simple_size().unwrap(); Some(ops[size.ilog2() as usize]) } diff --git a/lang/tests/son_tests_floating_point_arithmetic.txt b/lang/tests/son_tests_floating_point_arithmetic.txt new file mode 100644 index 0000000..99a9916 --- /dev/null +++ b/lang/tests/son_tests_floating_point_arithmetic.txt @@ -0,0 +1,6 @@ +main: + LI32 r1, 3212836864w + JALA r0, r31, 0a +code size: 25 +ret: 3212836864 +status: Ok(())