adding floating point arithmetic

This commit is contained in:
Jakub Doka 2024-10-29 13:36:12 +01:00
parent 348d9014e3
commit 80558ea7e6
No known key found for this signature in database
GPG key ID: C6E9A89936B8C143
8 changed files with 238 additions and 68 deletions

View file

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

View file

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

View file

@ -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<u64> {
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);

View file

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

View file

@ -472,6 +472,14 @@ impl<'a, 'b> Parser<'a, 'b> {
radix,
}
}
T::Float => E::Float {
pos,
value: match <f64 as core::str::FromStr>::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 {

View file

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

View file

@ -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)
@ -1183,8 +1208,36 @@ impl regalloc2::Function for Function<'_> {
}
impl TokenKind {
pub fn cmp_against(self) -> Option<u64> {
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<fn(u8, u8, u8) -> 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<fn(u8, u8, u8) -> 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<fn(u8, u8, u8) -> 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<fn(u8, u8, u64) -> EncodedInstr> {
fn imm_binop(self, ty: ty::Id) -> Option<fn(u8, u8, u64) -> 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])
}

View file

@ -0,0 +1,6 @@
main:
LI32 r1, 3212836864w
JALA r0, r31, 0a
code size: 25
ret: 3212836864
status: Ok(())