From 966d05157ca4bde64d6aedea4f92dce6a2a8f1e2 Mon Sep 17 00:00:00 2001 From: Enrico Zandomeni Borba Date: Tue, 9 Apr 2024 08:33:00 -0400 Subject: [PATCH] f32 operators, reinterpret numeric bits --- src/ast.rs | 17 ++- src/compile.rs | 11 +- src/host/encode.rs | 40 +++--- src/main.rs | 5 +- src/ops.rs | 289 ++++++++++------------------------------ src/ops/float.rs | 111 +++++++++++++++ src/ops/int.rs | 211 +++++++++++++++++++++++++++++ src/run/def.rs | 8 +- src/run/instruction.rs | 55 +++----- src/run/interact.rs | 79 ++--------- src/run/port.rs | 31 ++++- tests/programs/f32.hvmc | 37 ++--- 12 files changed, 503 insertions(+), 391 deletions(-) create mode 100644 src/ops/float.rs create mode 100644 src/ops/int.rs diff --git a/src/ast.rs b/src/ast.rs index 2d5c28a0..201cc246 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -309,15 +309,20 @@ impl<'i> Parser<'i> { Ok(Tree::Ref { nam }) } // Int = "#" Int - // TODO: accept fractional and exponential inputs eg 1.5, 1.5e32 - // F32 = "#f" Int + // F32 = "#" Int "." Int Some('#') => { self.advance_char(); - let is_float = self.consume("f").is_ok(); let is_neg = self.consume("-").is_ok(); let int_val = if is_neg { -(self.parse_int()? as i64) } else { self.parse_int()? as i64 }; - if is_float { Ok(Tree::F32 { val: (int_val as f32).into() }) } else { Ok(Tree::Int { val: int_val }) } + if self.consume(".").is_ok() { + let frac_val = self.parse_int()?; + let frac_len = frac_val.checked_ilog10().unwrap_or(0) + 1; + + Ok(Tree::F32 { val: (int_val as f32 + frac_val as f32 / 10f32.powi(frac_len as i32)).into() }) + } else { + Ok(Tree::Int { val: int_val }) + } } // Op = "<" Op Tree Tree ">" Some('<') => { @@ -386,7 +391,7 @@ impl<'i> Parser<'i> { /// See `ops.rs` for the available operators. fn parse_op(&mut self) -> Result { - let op = self.take_while(|c| "ui0123456789.+-=*/%<>|&^!?$".contains(c)); + let op = self.take_while(|c| "fui0123456789.+-=*/%<>|&^!?$".contains(c)); op.parse().map_err(|_| format!("Unknown operator: {op:?}")) } @@ -538,7 +543,7 @@ impl fmt::Display for Tree { Tree::Var { nam } => write!(f, "{nam}"), Tree::Ref { nam } => write!(f, "@{nam}"), Tree::Int { val } => write!(f, "#{val}"), - Tree::F32 { val } => write!(f, "#f{val}"), + Tree::F32 { val } => write!(f, "#{val}"), Tree::Op { op, rhs, out } => write!(f, "<{op} {rhs} {out}>"), Tree::Mat { zero, succ, out } => write!(f, "?<{zero} {succ} {out}>"), }) diff --git a/src/compile.rs b/src/compile.rs index 83ff2eda..cd9cdc05 100644 --- a/src/compile.rs +++ b/src/compile.rs @@ -23,7 +23,7 @@ fn _compile_host(host: &Host) -> Result { .map(|(raw_name, def)| (raw_name, sanitize_name(raw_name), def)); writeln!(code, "#![allow(non_upper_case_globals, unused_imports)]")?; - writeln!(code, "use crate::{{host::{{Host, DefRef}}, run::*, ops::{{Op, Ty::*, BinOp::*}}}};")?; + writeln!(code, "use crate::{{host::{{Host, DefRef}}, run::*, ops::{{Num::*, Op, Ty::*, BinOp::*}}}};")?; writeln!(code)?; writeln!(code, "pub fn host() -> Host {{")?; @@ -72,11 +72,8 @@ fn compile_def(code: &mut String, host: &Host, name: &str, instr: &[Instruction] Instruction::Op { op, trg, rhs, out } => { writeln!(code, "let ({rhs}, {out}) = net.do_op({op:?}, {trg});") } - Instruction::OpInt { op, trg, rhs, out } => { - writeln!(code, "let {out} = net.do_op_int({op:?}, {trg}, {rhs});") - } - Instruction::OpF32 { op, trg, rhs, out } => { - writeln!(code, "let {out} = net.do_op_float({op:?}, {trg}, {rhs});") + Instruction::OpNum { op, trg, rhs, out } => { + writeln!(code, "let {out} = net.do_op_num({op:?}, {trg}, {rhs:?});") } Instruction::Mat { trg, lft, rgt } => { writeln!(code, "let ({lft}, {rgt}) = net.do_mat({trg});") @@ -99,7 +96,7 @@ fn compile_port(host: &Host, port: &Port) -> String { let name = sanitize_name(&host.back[&port.addr()]); format!("Port::new_ref(unsafe {{ &*DEF_{name} }})") } else if port.tag() == Tag::Int { - format!("Port::new_num({})", port.int()) + format!("Port::new_int({})", port.int()) } else { unreachable!() } diff --git a/src/host/encode.rs b/src/host/encode.rs index dd648a31..4a3930dd 100644 --- a/src/host/encode.rs +++ b/src/host/encode.rs @@ -1,5 +1,9 @@ use super::*; -use crate::{ops::Op, run::Lab, util::maybe_grow}; +use crate::{ + ops::{Num, Op}, + run::Lab, + util::maybe_grow, +}; impl Host { /// Converts an ast net to a list of instructions to create the net. @@ -99,16 +103,21 @@ impl<'a, E: Encoder> State<'a, E> { } self.encoder.link(l, r); } - Tree::Op { op, rhs: lft, out: rgt } => { - if let Tree::Int { val } = &**lft { - let o = self.encoder.op_int(*op, trg, *val); + Tree::Op { op, rhs: lft, out: rgt } => match &**lft { + Tree::Int { val } => { + let o = self.encoder.op_num(*op, trg, Num::Int(*val)); self.visit_tree(rgt, o); - } else { + } + Tree::F32 { val } => { + let o = self.encoder.op_num(*op, trg, Num::Float(val.0)); + self.visit_tree(rgt, o); + } + _ => { let (l, r) = self.encoder.op(*op, trg); self.visit_tree(lft, l); self.visit_tree(rgt, r); } - } + }, Tree::Mat { zero, succ, out } => { let (a, o) = self.encoder.mat(trg); let (z, s) = self.encoder.ctr(0, a); @@ -133,8 +142,7 @@ trait Encoder { fn make_const(&mut self, port: Port) -> Self::Trg; fn ctr(&mut self, lab: Lab, trg: Self::Trg) -> (Self::Trg, Self::Trg); fn op(&mut self, op: Op, trg: Self::Trg) -> (Self::Trg, Self::Trg); - fn op_int(&mut self, op: Op, trg: Self::Trg, rhs: i64) -> Self::Trg; - fn op_float(&mut self, op: Op, trg: Self::Trg, rhs: f32) -> Self::Trg; + fn op_num(&mut self, op: Op, trg: Self::Trg, rhs: Num) -> Self::Trg; fn mat(&mut self, trg: Self::Trg) -> (Self::Trg, Self::Trg); fn wires(&mut self) -> (Self::Trg, Self::Trg, Self::Trg, Self::Trg); } @@ -172,14 +180,9 @@ impl Encoder for InterpretedDef { self.instr.push(Instruction::Op { op, trg, rhs, out }); (rhs, out) } - fn op_int(&mut self, op: Op, trg: Self::Trg, rhs: i64) -> Self::Trg { + fn op_num(&mut self, op: Op, trg: Self::Trg, rhs: Num) -> Self::Trg { let out = self.new_trg_id(); - self.instr.push(Instruction::OpInt { op, trg, rhs, out }); - out - } - fn op_float(&mut self, op: Op, trg: Self::Trg, rhs: f32) -> Self::Trg { - let out = self.new_trg_id(); - self.instr.push(Instruction::OpF32 { op, trg, rhs, out }); + self.instr.push(Instruction::OpNum { op, trg, rhs, out }); out } fn mat(&mut self, trg: Self::Trg) -> (Self::Trg, Self::Trg) { @@ -215,11 +218,8 @@ impl<'a, M: Mode> Encoder for run::Net<'a, M> { fn op(&mut self, op: Op, trg: Self::Trg) -> (Self::Trg, Self::Trg) { self.do_op(op, trg) } - fn op_int(&mut self, op: Op, trg: Self::Trg, rhs: i64) -> Self::Trg { - self.do_op_int(op, trg, rhs) - } - fn op_float(&mut self, op: Op, trg: Self::Trg, rhs: f32) -> Self::Trg { - self.do_op_float(op, trg, rhs) + fn op_num(&mut self, op: Op, trg: Self::Trg, rhs: Num) -> Self::Trg { + self.do_op_num(op, trg, rhs) } fn mat(&mut self, trg: Self::Trg) -> (Self::Trg, Self::Trg) { self.do_mat(trg) diff --git a/src/main.rs b/src/main.rs index 5746f89b..dd120fd6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -467,7 +467,10 @@ fn compile_executable(target: &str, host: Arc>) -> Result<(), } lib main - ops + ops { + int + float + } run { addr allocator diff --git a/src/ops.rs b/src/ops.rs index 54f41400..d84d5084 100644 --- a/src/ops.rs +++ b/src/ops.rs @@ -1,228 +1,69 @@ +mod float; +mod int; + use ordered_float::OrderedFloat; -use crate::util::bi_enum; +use self::{ + float::Op as FloatOp, + int::{Op as IntOp, Ty as IntTy, TypedOp as IntTypedOp}, +}; use std::{ cmp::{Eq, Ord}, fmt::Display, str::FromStr, }; -trait Num: Eq + Ord + Sized { - const ZERO: Self; - const ONE: Self; - - fn add(a: Self, b: Self) -> Self; - fn sub(a: Self, b: Self) -> Self; - fn mul(a: Self, b: Self) -> Self; - fn div(a: Self, b: Self) -> Self; - fn rem(a: Self, b: Self) -> Self; - fn and(a: Self, b: Self) -> Self; - fn or(a: Self, b: Self) -> Self; - fn xor(a: Self, b: Self) -> Self; - fn shl(a: Self, b: Self) -> Self; - fn shr(a: Self, b: Self) -> Self; - - fn from_bool(b: bool) -> Self { - if b { Self::ONE } else { Self::ZERO } - } +#[derive(Clone, Copy, Debug)] +pub enum Num { + Int(i64), + Float(f32), } -macro_rules! impl_num { - ( $($ty:ty),+ ) => { - $( - impl Num for $ty { - const ZERO: Self = 0; - const ONE: Self = 1; - - fn add(a: Self, b: Self) -> Self { a.wrapping_add(b) } - fn sub(a: Self, b: Self) -> Self { a.wrapping_sub(b) } - fn mul(a: Self, b: Self) -> Self { a.wrapping_mul(b) } - fn div(a: Self, b: Self) -> Self { a.checked_div(b).unwrap_or(0) } - fn rem(a: Self, b: Self) -> Self { a.checked_rem(b).unwrap_or(0) } - fn and(a: Self, b: Self) -> Self { a & b } - fn or(a: Self, b: Self) -> Self { a | b } - fn xor(a: Self, b: Self) -> Self { a ^ b } - fn shl(a: Self, b: Self) -> Self { a.wrapping_shl(b as u32) } - fn shr(a: Self, b: Self) -> Self { a.wrapping_shr(b as u32) } - } - )* - } -} - -impl_num! { u8, u16, u32, u64, i8, i16, i32 } - -impl Num for OrderedFloat { - const ZERO: Self = OrderedFloat(0f32); - const ONE: Self = OrderedFloat(1f32); - - fn add(a: Self, b: Self) -> Self { - a + b - } - fn sub(a: Self, b: Self) -> Self { - a - b - } - fn mul(a: Self, b: Self) -> Self { - a * b - } - fn div(a: Self, b: Self) -> Self { - a / b - } - fn rem(a: Self, b: Self) -> Self { - a % b - } - fn and(_: Self, _: Self) -> Self { - unimplemented!("f32 & f32 unsupported") - } - fn or(_: Self, _: Self) -> Self { - unimplemented!("f32 | f32 unsupported") - } - fn xor(_: Self, _: Self) -> Self { - unimplemented!("f32 ^ f32 unsupported") - } - fn shl(_: Self, _: Self) -> Self { - unimplemented!("f32 << f32 unsupported") - } - fn shr(_: Self, _: Self) -> Self { - unimplemented!("f32 >> f32 unsupported") - } -} - -bi_enum! { - #[repr(u8)] - /// Native operations on mixed-width integers (u8, u16, u32, u60, i8, i16, i32). - /// - /// Each operation has a swapped counterpart (accessible with `.swap()`), - /// where the order of the operands is swapped. - /// - /// Operations without an already-named counterpart (e.g. `Add <-> Add` and - /// `Lt <-> Gt`) are suffixed with `$`/`S`: `(-$ 1 2) = (- 2 1) = 1`. - #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] - pub enum BinOp { - "+": Add = 0, - "-": Sub = 1, - "-$": SubS = 2, - "*": Mul = 3, - "/": Div = 4, - "/$": DivS = 5, - "%": Rem = 6, - "%$": RemS = 7, - "==": Eq = 8, - "!=": Ne = 9, - "<": Lt = 10, - ">": Gt = 11, - "<=": Le = 12, - ">=": Ge = 13, - "&": And = 14, - "|": Or = 15, - "^": Xor = 16, - "<<": Shl = 17, - "<<$": ShlS = 18, - ">>": Shr = 19, - ">>$": ShrS = 20, - } -} - -impl BinOp { - /// Returns this operation's swapped counterpart. - /// - /// For all `op, a, b`, `op.swap().op(a, b) == op.op(b, a)`. - #[inline] - fn swap(self) -> Self { - match self { - Self::Add => Self::Add, - Self::Sub => Self::SubS, - Self::SubS => Self::Sub, - Self::Mul => Self::Mul, - Self::Div => Self::DivS, - Self::DivS => Self::Div, - Self::Rem => Self::RemS, - Self::RemS => Self::Rem, - Self::Eq => Self::Eq, - Self::Ne => Self::Ne, - Self::Lt => Self::Gt, - Self::Gt => Self::Lt, - Self::Le => Self::Ge, - Self::Ge => Self::Le, - Self::And => Self::And, - Self::Or => Self::Or, - Self::Xor => Self::Xor, - Self::Shl => Self::ShlS, - Self::ShlS => Self::Shl, - Self::Shr => Self::ShrS, - Self::ShrS => Self::Shr, - } - } - - fn op(self, a: T, b: T) -> T { - match self { - Self::Add => T::add(a, b), - Self::Sub => T::sub(a, b), - Self::SubS => T::sub(b, a), - Self::Mul => T::mul(a, b), - Self::Div => T::div(a, b), - Self::DivS => T::div(b, a), - Self::Rem => T::rem(a, b), - Self::RemS => T::rem(b, a), - Self::Eq => T::from_bool(a == b), - Self::Ne => T::from_bool(a != b), - Self::Lt => T::from_bool(a < b), - Self::Le => T::from_bool(a <= b), - Self::Gt => T::from_bool(a > b), - Self::Ge => T::from_bool(a >= b), - Self::And => T::and(a, b), - Self::Or => T::or(a, b), - Self::Xor => T::xor(a, b), - Self::Shl => T::shl(a, b), - Self::ShlS => T::shl(b, a), - Self::Shr => T::shr(a, b), - Self::ShrS => T::shr(b, a), +impl From for u64 { + fn from(num: Num) -> u64 { + unsafe { + match num { + Num::Int(int) => std::mem::transmute(int), + Num::Float(float) => std::mem::transmute::<_, u32>(float) as u64, + } } } } -bi_enum! { - #[repr(u8)] - #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] - pub enum Ty { - "u8": U8 = 0, - "u16": U16 = 1, - "u32": U32 = 2, - "u60": U60 = 3, - "i8": I8 = 4, - "i16": I16 = 5, - "i32": I32 = 6, - } -} - -#[repr(C)] +/// A numeric operator. +/// +/// Represented as a `u16` with +/// [1 bit for int | float][15 bits variant-dependent] +/// - int: [7 bits type][8 bits operator] +/// - float: [15 bits operator] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct Op { - pub ty: Ty, - pub op: BinOp, -} - -impl Display for Op { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self.ty { - Ty::U60 => write!(f, "{}", self.op), - _ => write!(f, "{}.{}", self.ty, self.op), - } - } +pub enum Op { + Int(IntTypedOp), + Float(FloatOp), } impl TryFrom for Op { type Error = (); fn try_from(value: u16) -> Result { - let [ty, op] = value.to_be_bytes(); + const U15: u16 = 0b1000_0000_0000_0000; - Ok(Self { ty: Ty::try_from(ty)?, op: BinOp::try_from(op)? }) + if U15 & value == 0 { + Ok(Self::Int(IntTypedOp::try_from(value)?)) + } else { + Ok(Self::Float(FloatOp::try_from(U15 ^ value)?)) + } } } impl From for u16 { fn from(op: Op) -> Self { - (op.ty as u16) << 8 | op.op as u16 + const U15: u16 = 0b1000_0000_0000_0000; + + match op { + Op::Int(op) => u16::from(op), + Op::Float(op) => U15 | u16::from(op), + } } } @@ -231,36 +72,50 @@ impl FromStr for Op { fn from_str(s: &str) -> Result { match s.split('.').collect::>().as_slice() { - [ty, op] => Ok(Self { ty: Ty::from_str(ty)?, op: BinOp::from_str(op)? }), - [op] => Ok(Self { ty: Ty::U60, op: BinOp::from_str(op)? }), + ["f32", op] => Ok(Self::Float(FloatOp::from_str(op)?)), + [ty, op] => Ok(Self::Int(IntTypedOp::new(IntTy::from_str(ty)?, IntOp::from_str(op)?))), + [op] => Ok(Self::Int(IntTypedOp::new(IntTy::U60, IntOp::from_str(op)?))), _ => Err(()), } } } -impl Op { - pub fn swap(self) -> Self { - Self { op: self.op.swap(), ty: self.ty } +impl Display for Op { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Int(op) => write!(f, "{op}"), + Self::Float(op) => write!(f, "f32.{op}"), + } } +} - #[inline] - pub fn op_int(self, a: i64, b: i64) -> i64 { - const U60: i64 = 0xFFF_FFFF_FFFF_FFFF; - - match self.ty { - Ty::U8 => self.op.op(a as u8, b as u8) as i64, - Ty::U16 => self.op.op(a as u16, b as u16) as i64, - Ty::U32 => self.op.op(a as u32, b as u32) as i64, - Ty::U60 => self.op.op(a as u64, b as u64) as i64 & U60, - Ty::I8 => self.op.op(a as i8, b as i8) as i64, - Ty::I16 => self.op.op(a as i16, b as i16) as i64, - Ty::I32 => self.op.op(a as i32, b as i32) as i64, +impl Op { + pub fn swap(self) -> Self { + match self { + Self::Int(op) => Self::Int(op.swap()), + Self::Float(op) => Self::Float(op.swap()), } } #[inline] - pub fn op_float(self, a: f32, b: f32) -> f32 { - self.op.op(OrderedFloat(a), OrderedFloat(b)).into() + pub fn op(self, a: u64, b: u64) -> u64 { + // unsafe is only for `std::mem::transmute` + unsafe { + match self { + Self::Int(op) => { + let a: i64 = std::mem::transmute(a); + let b: i64 = std::mem::transmute(b); + + std::mem::transmute(op.op(a, b)) + } + Self::Float(op) => { + let a: OrderedFloat = std::mem::transmute(a as u32); + let b: OrderedFloat = std::mem::transmute(b as u32); + + std::mem::transmute::<_, u32>(op.op(a, b)) as u64 + } + } + } } } diff --git a/src/ops/float.rs b/src/ops/float.rs new file mode 100644 index 00000000..c40b09cb --- /dev/null +++ b/src/ops/float.rs @@ -0,0 +1,111 @@ +use crate::util::bi_enum; + +use ordered_float::OrderedFloat; + +pub trait Float: Eq + Ord + Sized { + const ZERO: Self; + const ONE: Self; + + fn add(a: Self, b: Self) -> Self; + fn sub(a: Self, b: Self) -> Self; + fn mul(a: Self, b: Self) -> Self; + fn div(a: Self, b: Self) -> Self; + fn rem(a: Self, b: Self) -> Self; + + fn from_bool(b: bool) -> Self { + if b { Self::ONE } else { Self::ZERO } + } +} + +impl Float for OrderedFloat { + const ZERO: Self = OrderedFloat(0f32); + const ONE: Self = OrderedFloat(1f32); + + fn add(a: Self, b: Self) -> Self { + a + b + } + fn sub(a: Self, b: Self) -> Self { + a - b + } + fn mul(a: Self, b: Self) -> Self { + a * b + } + fn div(a: Self, b: Self) -> Self { + a / b + } + fn rem(a: Self, b: Self) -> Self { + a % b + } +} + +bi_enum! { + #[repr(u16)] + /// Native operations on floats. + /// + /// Each operation has a swapped counterpart (accessible with `.swap()`), + /// where the order of the operands is swapped. + /// + /// Operations without an already-named counterpart (e.g. `Add <-> Add` and + /// `Lt <-> Gt`) are suffixed with `$`/`S`: `(-$ 1 2) = (- 2 1) = 1`. + #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] + pub enum Op { + "+": Add = 0, + "-": Sub = 1, + "-$": SubS = 2, + "*": Mul = 3, + "/": Div = 4, + "/$": DivS = 5, + "%": Rem = 6, + "%$": RemS = 7, + "==": Eq = 8, + "!=": Ne = 9, + "<": Lt = 10, + ">": Gt = 11, + "<=": Le = 12, + ">=": Ge = 13, + } +} + +impl Op { + /// Returns this operation's swapped counterpart. + /// + /// For all `op, a, b`, `op.swap().op(a, b) == op.op(b, a)`. + #[inline] + pub fn swap(self) -> Self { + match self { + Self::Add => Self::Add, + Self::Sub => Self::SubS, + Self::SubS => Self::Sub, + Self::Mul => Self::Mul, + Self::Div => Self::DivS, + Self::DivS => Self::Div, + Self::Rem => Self::RemS, + Self::RemS => Self::Rem, + Self::Eq => Self::Eq, + Self::Ne => Self::Ne, + Self::Lt => Self::Gt, + Self::Gt => Self::Lt, + Self::Le => Self::Ge, + Self::Ge => Self::Le, + } + } + + pub fn op(self, a: T, b: T) -> T { + match self { + Self::Add => T::add(a, b), + Self::Sub => T::sub(a, b), + Self::SubS => T::sub(b, a), + Self::Mul => T::mul(a, b), + Self::Div => T::div(a, b), + Self::DivS => T::div(b, a), + Self::Rem => T::rem(a, b), + Self::RemS => T::rem(b, a), + Self::Eq => T::from_bool(a == b), + Self::Ne => T::from_bool(a != b), + Self::Lt => T::from_bool(a < b), + Self::Le => T::from_bool(a <= b), + Self::Gt => T::from_bool(a > b), + Self::Ge => T::from_bool(a >= b), + } + } +} diff --git a/src/ops/int.rs b/src/ops/int.rs new file mode 100644 index 00000000..b657b8a7 --- /dev/null +++ b/src/ops/int.rs @@ -0,0 +1,211 @@ +use crate::util::bi_enum; + +use std::fmt::Display; + +pub trait Int: Eq + Ord + Sized { + const ZERO: Self; + const ONE: Self; + + fn add(a: Self, b: Self) -> Self; + fn sub(a: Self, b: Self) -> Self; + fn mul(a: Self, b: Self) -> Self; + fn div(a: Self, b: Self) -> Self; + fn rem(a: Self, b: Self) -> Self; + fn and(a: Self, b: Self) -> Self; + fn or(a: Self, b: Self) -> Self; + fn xor(a: Self, b: Self) -> Self; + fn shl(a: Self, b: Self) -> Self; + fn shr(a: Self, b: Self) -> Self; + + fn from_bool(b: bool) -> Self { + if b { Self::ONE } else { Self::ZERO } + } +} + +macro_rules! impl_int { + ( $($ty:ty),+ ) => { + $( + impl Int for $ty { + const ZERO: Self = 0; + const ONE: Self = 1; + + fn add(a: Self, b: Self) -> Self { a.wrapping_add(b) } + fn sub(a: Self, b: Self) -> Self { a.wrapping_sub(b) } + fn mul(a: Self, b: Self) -> Self { a.wrapping_mul(b) } + fn div(a: Self, b: Self) -> Self { a.checked_div(b).unwrap_or(0) } + fn rem(a: Self, b: Self) -> Self { a.checked_rem(b).unwrap_or(0) } + fn and(a: Self, b: Self) -> Self { a & b } + fn or(a: Self, b: Self) -> Self { a | b } + fn xor(a: Self, b: Self) -> Self { a ^ b } + fn shl(a: Self, b: Self) -> Self { a.wrapping_shl(b as u32) } + fn shr(a: Self, b: Self) -> Self { a.wrapping_shr(b as u32) } + } + )* + } +} + +impl_int! { u8, u16, u32, u64, i8, i16, i32 } + +bi_enum! { + #[repr(u8)] + #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] + pub enum Ty { + "u8": U8 = 0, + "u16": U16 = 1, + "u32": U32 = 2, + "u60": U60 = 3, + "i8": I8 = 4, + "i16": I16 = 5, + "i32": I32 = 6, + } +} + +bi_enum! { + #[repr(u8)] + /// Native operations on mixed-width integers (u8, u16, u32, u60, i8, i16, i32). + /// + /// Each operation has a swapped counterpart (accessible with `.swap()`), + /// where the order of the operands is swapped. + /// + /// Operations without an already-named counterpart (e.g. `Add <-> Add` and + /// `Lt <-> Gt`) are suffixed with `$`/`S`: `(-$ 1 2) = (- 2 1) = 1`. + #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] + pub enum Op { + "+": Add = 0, + "-": Sub = 1, + "-$": SubS = 2, + "*": Mul = 3, + "/": Div = 4, + "/$": DivS = 5, + "%": Rem = 6, + "%$": RemS = 7, + "==": Eq = 8, + "!=": Ne = 9, + "<": Lt = 10, + ">": Gt = 11, + "<=": Le = 12, + ">=": Ge = 13, + "&": And = 14, + "|": Or = 15, + "^": Xor = 16, + "<<": Shl = 17, + "<<$": ShlS = 18, + ">>": Shr = 19, + ">>$": ShrS = 20, + } +} + +impl Op { + /// Returns this operation's swapped counterpart. + /// + /// For all `op, a, b`, `op.swap().op(a, b) == op.op(b, a)`. + #[inline] + fn swap(self) -> Self { + match self { + Self::Add => Self::Add, + Self::Sub => Self::SubS, + Self::SubS => Self::Sub, + Self::Mul => Self::Mul, + Self::Div => Self::DivS, + Self::DivS => Self::Div, + Self::Rem => Self::RemS, + Self::RemS => Self::Rem, + Self::Eq => Self::Eq, + Self::Ne => Self::Ne, + Self::Lt => Self::Gt, + Self::Gt => Self::Lt, + Self::Le => Self::Ge, + Self::Ge => Self::Le, + Self::And => Self::And, + Self::Or => Self::Or, + Self::Xor => Self::Xor, + Self::Shl => Self::ShlS, + Self::ShlS => Self::Shl, + Self::Shr => Self::ShrS, + Self::ShrS => Self::Shr, + } + } + + fn op(self, a: T, b: T) -> T { + match self { + Self::Add => T::add(a, b), + Self::Sub => T::sub(a, b), + Self::SubS => T::sub(b, a), + Self::Mul => T::mul(a, b), + Self::Div => T::div(a, b), + Self::DivS => T::div(b, a), + Self::Rem => T::rem(a, b), + Self::RemS => T::rem(b, a), + Self::Eq => T::from_bool(a == b), + Self::Ne => T::from_bool(a != b), + Self::Lt => T::from_bool(a < b), + Self::Le => T::from_bool(a <= b), + Self::Gt => T::from_bool(a > b), + Self::Ge => T::from_bool(a >= b), + Self::And => T::and(a, b), + Self::Or => T::or(a, b), + Self::Xor => T::xor(a, b), + Self::Shl => T::shl(a, b), + Self::ShlS => T::shl(b, a), + Self::Shr => T::shr(a, b), + Self::ShrS => T::shr(b, a), + } + } +} + +/// A typed integer operator. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct TypedOp { + ty: Ty, + op: Op, +} + +impl TypedOp { + pub fn new(ty: Ty, op: Op) -> Self { + Self { ty, op } + } + + pub fn swap(self) -> Self { + Self { ty: self.ty, op: self.op.swap() } + } + + #[inline] + pub fn op(self, a: i64, b: i64) -> i64 { + const U60: i64 = 0xFFF_FFFF_FFFF_FFFF; + + match self.ty { + Ty::U8 => self.op.op(a as u8, b as u8) as i64, + Ty::U16 => self.op.op(a as u16, b as u16) as i64, + Ty::U32 => self.op.op(a as u32, b as u32) as i64, + Ty::U60 => self.op.op(a as u64, b as u64) as i64 & U60, + Ty::I8 => self.op.op(a as i8, b as i8) as i64, + Ty::I16 => self.op.op(a as i16, b as i16) as i64, + Ty::I32 => self.op.op(a as i32, b as i32) as i64, + } + } +} + +impl TryFrom for TypedOp { + type Error = (); + + fn try_from(value: u16) -> Result { + let [ty, op] = value.to_be_bytes(); + + Ok(Self { ty: Ty::try_from(ty)?, op: Op::try_from(op)? }) + } +} + +impl From for u16 { + fn from(TypedOp { ty, op }: TypedOp) -> Self { + (ty as u16) << 8 | (op as u16) + } +} + +impl Display for TypedOp { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self.ty { + Ty::U60 => write!(f, "{}", self.op), + _ => write!(f, "{}.{}", self.ty, self.op), + } + } +} diff --git a/src/run/def.rs b/src/run/def.rs index 73029f2b..a72e508d 100644 --- a/src/run/def.rs +++ b/src/run/def.rs @@ -258,12 +258,8 @@ impl AsHostedDef for InterpretedDef { trgs.set_trg(rhs, r); trgs.set_trg(out, o); } - Instruction::OpInt { op, trg, rhs: lhs, out } => { - let o = net.do_op_int(op, trgs.get_trg(trg), lhs); - trgs.set_trg(out, o); - } - Instruction::OpF32 { op, trg, rhs: lhs, out } => { - let o = net.do_op_float(op, trgs.get_trg(trg), lhs); + Instruction::OpNum { op, trg, rhs: lhs, out } => { + let o = net.do_op_num(op, trgs.get_trg(trg), lhs); trgs.set_trg(out, o); } Instruction::Mat { trg, lft, rgt } => { diff --git a/src/run/instruction.rs b/src/run/instruction.rs index 9fe12454..fe027d9f 100644 --- a/src/run/instruction.rs +++ b/src/run/instruction.rs @@ -1,3 +1,5 @@ +use crate::ops::Num; + use super::*; /// Each instruction corresponds to a fragment of a net that has a native @@ -54,16 +56,11 @@ pub enum Instruction { /// let (rhs, out) = net.do_op(lab, trg); /// ``` Op { op: Op, trg: TrgId, rhs: TrgId, out: TrgId }, - /// See [`Net::do_op_int`]. - /// ```rust,ignore - /// let out = net.do_op_int(lab, trg, rhs); - /// ``` - OpInt { op: Op, trg: TrgId, rhs: i64, out: TrgId }, - /// See [`Net::do_op_float`]. + /// See [`Net::do_op_num`]. /// ```rust,ignore - /// let out = net.do_op_float(lab, trg, rhs); + /// let out = net.do_op_num(lab, trg, rhs); /// ``` - OpF32 { op: Op, trg: TrgId, rhs: f32, out: TrgId }, + OpNum { op: Op, trg: TrgId, rhs: Num, out: TrgId }, /// See [`Net::do_mat`]. /// ```rust,ignore /// let (lft, rgt) = net.do_mat(trg); @@ -140,10 +137,10 @@ impl<'a, M: Mode> Net<'a, M> { pub(crate) fn do_op(&mut self, op: Op, trg: Trg) -> (Trg, Trg) { trace!(self.tracer, op, trg); let port = trg.target(); - if !M::LAZY && port.tag() == Int { + if !M::LAZY && (port.tag() == Int || port.tag() == F32) { self.free_trg(trg); let n = self.create_node(Op, op.swap().into()); - n.p1.wire().set_target(Port::new_int(port.int())); + n.p1.wire().set_target(port); (Trg::port(n.p0), Trg::port(n.p2)) } else if !M::LAZY && port == Port::ERA { self.free_trg(trg); @@ -157,46 +154,24 @@ impl<'a, M: Mode> Net<'a, M> { /// `trg ~ ` #[inline(always)] - pub(crate) fn do_op_int(&mut self, op: Op, trg: Trg, rhs: i64) -> Trg { + pub(crate) fn do_op_num(&mut self, op: Op, trg: Trg, rhs: Num) -> Trg { let port = trg.target(); - if !M::LAZY && port.tag() == Int { - self.rwts.oper += 1; - self.free_trg(trg); - Trg::port(Port::new_int(op.op_int(port.int(), rhs))) - } else if !M::LAZY && port.tag() == F32 { + if !M::LAZY && (port.tag() == Int || port.tag() == F32) { self.rwts.oper += 1; self.free_trg(trg); - Trg::port(Port::new_float(op.op_float(port.float(), rhs as f32))) - } else if !M::LAZY && port == Port::ERA { - self.free_trg(trg); - Trg::port(Port::ERA) - } else { - let n = self.create_node(Op, op.into()); - self.link_trg_port(trg, n.p0); - n.p1.wire().set_target(Port::new_int(rhs)); - Trg::port(n.p2) - } - } - /// `trg ~ ` - #[inline(always)] - pub(crate) fn do_op_float(&mut self, op: Op, trg: Trg, rhs: f32) -> Trg { - let port = trg.target(); - if !M::LAZY && port.tag() == Int { - self.rwts.oper += 1; - self.free_trg(trg); - Trg::port(Port::new_float(op.op_float(port.int() as f32, rhs))) - } else if !M::LAZY && port.tag() == F32 { - self.rwts.oper += 1; - self.free_trg(trg); - Trg::port(Port::new_float(op.op_float(port.float(), rhs))) + let res = op.op(port.num(), u64::from(rhs)); + match op { + Op::Int(_) => Trg::port(Port::new_num(Tag::Int, res)), + Op::Float(_) => Trg::port(Port::new_num(Tag::F32, res)), + } } else if !M::LAZY && port == Port::ERA { self.free_trg(trg); Trg::port(Port::ERA) } else { let n = self.create_node(Op, op.into()); self.link_trg_port(trg, n.p0); - n.p1.wire().set_target(Port::new_float(rhs)); + n.p1.wire().set_target(Port::from(rhs)); Trg::port(n.p2) } } diff --git a/src/run/interact.rs b/src/run/interact.rs index 3b7e32d2..0884ff6b 100644 --- a/src/run/interact.rs +++ b/src/run/interact.rs @@ -31,10 +31,8 @@ impl<'a, M: Mode> Net<'a, M> { (Ref, _) => self.call(a, b), (_, Ref) => self.call(b, a), // native ops - (Op, Int) => self.op_int(a, b), - (Int, Op) => self.op_int(b, a), - (Op, F32) => self.op_float(a, b), - (F32, Op) => self.op_float(b, a), + (Op, Int | F32) => self.op_num(a, b), + (Int | F32, Op) => self.op_num(b, a), (Mat, Int) => self.mat_int(a, b), (Int, Mat) => self.mat_int(b, a), // todo: what should the semantics of these be? @@ -237,7 +235,7 @@ impl<'a, M: Mode> Net<'a, M> { /// | | | | | /// (m) | a2 | a1 | | a2 /// | - /// --------------------------- | --------------------------- op_int + /// --------------------------- | --------------------------- op_num /// | _ _ _ /// | / \ /// | | (n) | @@ -250,16 +248,21 @@ impl<'a, M: Mode> Net<'a, M> { /// | /// ``` #[inline(never)] - pub fn op_int(&mut self, a: Port, b: Port) { + pub fn op_num(&mut self, a: Port, b: Port) { trace!(self.tracer, a, b); let a = a.consume_node(); let op = unsafe { Op::try_from(a.lab).unwrap_unchecked() }; let a1 = a.p1.load_target(); - if a1.tag() == Int { + if a1.tag() == Int || a1.tag() == F32 { self.rwts.oper += 1; self.half_free(a.p1.addr()); - let out = op.op_int(b.int(), a1.int()); - self.link_wire_port(a.p2, Port::new_int(out)); + + let out = match op { + Op::Int(_) => Port::new_num(Tag::Int, op.op(b.num(), a1.num())), + Op::Float(_) => Port::new_num(Tag::F32, op.op(b.num(), a1.num())), + }; + + self.link_wire_port(a.p2, out); } else { let op = op.swap(); let x = self.create_node(Op, op.into()); @@ -269,62 +272,4 @@ impl<'a, M: Mode> Net<'a, M> { self.link_wire_port(a.p1, x.p0); } } - - /// Interacts a float and a binary numeric operation node. - /// - /// ```text - /// | - /// b (n) | b (n) - /// | | | - /// | | | - /// / \ | / \ - /// a /op \ | a /op \ - /// /_____\ | /_____\ - /// | | | | | - /// (m) | a2 | a1 | | a2 - /// | - /// --------------------------- | --------------------------- op_float - /// | _ _ _ - /// | / \ - /// | | (n) | - /// | _|___|_ | - /// | \ / | - /// | x \op$/ | - /// (n op m) | \ / | - /// | | | | - /// | a2 | a1 | | a2 - /// | - /// ``` - #[inline(never)] - pub fn op_float(&mut self, a: Port, b: Port) { - trace!(self.tracer, a, b); - let a = a.consume_node(); - let op = unsafe { Op::try_from(a.lab).unwrap_unchecked() }; - let a1 = a.p1.load_target(); - - match a1.tag() { - Int => { - self.rwts.oper += 1; - self.half_free(a.p1.addr()); - let out = op.op_float(b.float(), a1.int() as f32); - self.link_wire_port(a.p2, Port::new_float(out)); - } - - F32 => { - self.rwts.oper += 1; - self.half_free(a.p1.addr()); - let out = op.op_float(b.float(), a1.float()); - self.link_wire_port(a.p2, Port::new_float(out)); - } - - _ => { - let op = op.swap(); - let x = self.create_node(Op, op.into()); - trace!(self.tracer, x.p0); - self.link_port_port(x.p1, b); - self.link_wire_port(a.p2, x.p2); - self.link_wire_port(a.p1, x.p0); - } - } - } } diff --git a/src/run/port.rs b/src/run/port.rs index 1b9562da..ad30b35f 100644 --- a/src/run/port.rs +++ b/src/run/port.rs @@ -1,3 +1,5 @@ +use crate::ops::Num; + use super::*; /// A port in the interaction net. @@ -13,7 +15,7 @@ use super::*; /// /// The semantics of these fields depend upon the tag; see the documentation for /// each [`Tag`] variant. -#[derive(Clone, Eq, PartialEq, PartialOrd, Hash, Default)] +#[derive(Clone, Copy, Eq, PartialEq, PartialOrd, Hash, Default)] #[repr(transparent)] #[must_use] pub struct Port(pub u64); @@ -147,7 +149,13 @@ impl Port { /// Creates a new [`F32`] port with a given 60-bit numeric value. #[inline(always)] pub const fn new_float(val: f32) -> Self { - Port((unsafe { std::mem::transmute::<_, u32>(val) as u64 } << 32) | (F32 as u64)) + Port((unsafe { std::mem::transmute::<_, u32>(val) as u64 } << 4) | (F32 as u64)) + } + + /// Creates a new [`Int` | `F32`] port with a given 60-bit numeric value. + #[inline(always)] + pub const fn new_num(tag: Tag, bits: u64) -> Self { + Port((bits << 4) as u64 | (tag as u64)) } /// Creates a new [`Ref`] port corresponding to a given definition. @@ -195,7 +203,15 @@ impl Port { /// Accesses the float value of this port; this is valid for [`F32`] ports. #[inline(always)] pub const fn float(&self) -> f32 { - unsafe { std::mem::transmute((self.0 >> 32) as u32) } + unsafe { std::mem::transmute((self.0 >> 4) as u32) } + } + + /// Accesses the numeric value of this port; this is valid for [`Int` | `F32`] + /// ports. This is meant for numeric operations to defer interpreting this + /// port as an integer or as a float until the operation type is known. + #[inline(always)] + pub const fn num(&self) -> u64 { + self.0 >> 4 } /// Accesses the wire leaving this port; this is valid for [`Var`] ports and @@ -234,3 +250,12 @@ impl Port { self.tag() > Int } } + +impl From for Port { + fn from(num: Num) -> Self { + match num { + Num::Int(int) => Self::new_int(int), + Num::Float(float) => Self::new_float(float), + } + } +} diff --git a/tests/programs/f32.hvmc b/tests/programs/f32.hvmc index 62ac45f1..2236bb6c 100644 --- a/tests/programs/f32.hvmc +++ b/tests/programs/f32.hvmc @@ -1,31 +1,20 @@ -@half = x & #f1 ~ +@half = x & #1.0 ~ @main = x // nan and inf divisions - & x ~ [n x] & #f1 ~ // inf - & x ~ [n x] & #f-1 ~ // -inf - & x ~ [n x] & #f0 ~ // NaN + & x ~ [n x] & #1.0 ~ // inf + & x ~ [n x] & #-1.0 ~ // -inf + & x ~ [n x] & #0.0 ~ // NaN // f32 int - & x ~ [n x] & @half ~ <+ #2 n> // 2.5 - & x ~ [n x] & @half ~ <- #2 n> // -1.5 - & x ~ [n x] & @half ~ <* #2 n> // 1 - & x ~ [n x] & @half ~ // 0.25 - & x ~ [n x] & @half ~ <% #2 n> // 0.5 - & x ~ [n x] & @half ~ <== #2 n> // 0 - & x ~ [n x] & @half ~ // 1 - & x ~ [n x] & @half ~ << #2 n> // 1 - & x ~ [n x] & @half ~ <> #2 n> // 1 - - // f32 f32 - & x ~ [n x] & @half ~ <+ #f2 n> // 2.5 - & x ~ [n x] & @half ~ <- #f2 n> // -1.5 - & x ~ [n x] & @half ~ <* #f2 n> // 1 - & x ~ [n x] & @half ~ // 0.25 - & x ~ [n x] & @half ~ <% #f2 n> // 0.5 - & x ~ [n x] & @half ~ <== #f2 n> // 0 - & x ~ [n x] & @half ~ // 1 - & x ~ [n x] & @half ~ << #f2 n> // 0 - & x ~ [n x] & @half ~ <> #f2 n> // 1 + & x ~ [n x] & @half ~ // 2.5 + & x ~ [n x] & @half ~ // -1.5 + & x ~ [n x] & @half ~ // 1 + & x ~ [n x] & @half ~ // 0.25 + & x ~ [n x] & @half ~ // 0.5 + & x ~ [n x] & @half ~ // 0 + & x ~ [n x] & @half ~ // 1 + & x ~ [n x] & @half ~ // 1 + & x ~ [n x] & @half ~ #2.0 n> // 0 & x ~ *