From a9a1332ac18da6625274bf50cbfa7c11fd6fbf4b Mon Sep 17 00:00:00 2001 From: Enrico Zandomeni Borba Date: Thu, 4 Apr 2024 06:44:18 -0400 Subject: [PATCH] variable-width integer operations --- src/ast.rs | 14 ++- src/compile.rs | 2 +- src/host/encode.rs | 6 +- src/ops.rs | 236 +++++++++++++++++++++++++++--------- src/run/instruction.rs | 10 +- src/run/interact.rs | 13 +- src/run/port.rs | 10 +- src/trace.rs | 2 +- src/transform/eta_reduce.rs | 2 +- tests/numeric.rs | 40 +++--- 10 files changed, 231 insertions(+), 104 deletions(-) diff --git a/src/ast.rs b/src/ast.rs index 3179dcb1..ad89d1bf 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -57,7 +57,7 @@ pub enum Tree { /// A nilary eraser node. Era, /// A native 60-bit integer. - Num { val: u64 }, + Num { val: i64 }, /// A nilary node, referencing a named net. Ref { nam: String }, /// A n-ary interaction combinator. @@ -303,9 +303,15 @@ impl<'i> Parser<'i> { // Num = "#" Int Some('#') => { self.advance_char(); - Ok(Tree::Num { val: self.parse_int()? }) + match self.peek_char() { + Some('-') => { + self.advance_char(); + Ok(Tree::Num { val: -(self.parse_int()? as i64) }) + } + _ => Ok(Tree::Num { val: self.parse_int()? as i64 }), + } } - // Op = "<" Op Tree Tree ">" | "<" Int Op Tree ">" + // Op = "<" Op Tree Tree ">" Some('<') => { self.advance_char(); let op = self.parse_op()?; @@ -372,7 +378,7 @@ impl<'i> Parser<'i> { /// See `ops.rs` for the available operators. fn parse_op(&mut self) -> Result { - let op = self.take_while(|c| "+-=*/%<>|&^!?$".contains(c)); + let op = self.take_while(|c| "ui81632.+-=*/%<>|&^!?$".contains(c)); op.parse().map_err(|_| format!("Unknown operator: {op:?}")) } diff --git a/src/compile.rs b/src/compile.rs index c02c9287..dae5eb9c 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::*}};")?; + writeln!(code, "use crate::{{host::{{Host, DefRef}}, run::*, ops::{{Op, Ty::*, IntOp::*}}}};")?; writeln!(code)?; writeln!(code, "pub fn host() -> Host {{")?; diff --git a/src/host/encode.rs b/src/host/encode.rs index 31bd7717..cff8ce2d 100644 --- a/src/host/encode.rs +++ b/src/host/encode.rs @@ -126,7 +126,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_num(&mut self, op: Op, trg: Self::Trg, rhs: u64) -> Self::Trg; + fn op_num(&mut self, op: Op, trg: Self::Trg, rhs: i64) -> 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); } @@ -164,7 +164,7 @@ impl Encoder for InterpretedDef { self.instr.push(Instruction::Op { op, trg, rhs, out }); (rhs, out) } - fn op_num(&mut self, op: Op, trg: Self::Trg, rhs: u64) -> Self::Trg { + fn op_num(&mut self, op: Op, trg: Self::Trg, rhs: i64) -> Self::Trg { let out = self.new_trg_id(); self.instr.push(Instruction::OpNum { op, trg, rhs, out }); out @@ -202,7 +202,7 @@ 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_num(&mut self, op: Op, trg: Self::Trg, rhs: u64) -> Self::Trg { + fn op_num(&mut self, op: Op, trg: Self::Trg, rhs: i64) -> Self::Trg { self.do_op_num(op, trg, rhs) } fn mat(&mut self, trg: Self::Trg) -> (Self::Trg, Self::Trg) { diff --git a/src/ops.rs b/src/ops.rs index 5f5c3ba4..dd0e88d2 100644 --- a/src/ops.rs +++ b/src/ops.rs @@ -1,30 +1,79 @@ use crate::util::bi_enum; +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 } + } +} + +macro_rules! impl_num { + ( $($ty:ty),+ ) => { + $( + impl Num for $ty { + const ZERO: $ty = 0; + const ONE: $ty = 1; + + fn add(a: $ty, b: $ty) -> $ty { a.wrapping_add(b) } + fn sub(a: $ty, b: $ty) -> $ty { a.wrapping_sub(b) } + fn mul(a: $ty, b: $ty) -> $ty { a.wrapping_mul(b) } + fn div(a: $ty, b: $ty) -> $ty { a.checked_div(b).unwrap_or(0) } + fn rem(a: $ty, b: $ty) -> $ty { a.checked_rem(b).unwrap_or(0) } + fn and(a: $ty, b: $ty) -> $ty { a & b } + fn or(a: $ty, b: $ty) -> $ty { a | b } + fn xor(a: $ty, b: $ty) -> $ty { a ^ b } + fn shl(a: $ty, b: $ty) -> $ty { a.wrapping_shl(b as u32) } + fn shr(a: $ty, b: $ty) -> $ty { a.wrapping_shr(b as u32) } + } + )* + } +} + +impl_num! { u8, u16, u32, u64, i8, i16, i32 } bi_enum! { - #[repr(u16)] - /// A native operation on 60-bit integers. + #[repr(u8)] + /// Native operations on variable-width integers. /// /// 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(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] - pub enum Op { + #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] + pub enum IntOp { "+": Add = 0, "-": Sub = 1, "-$": SubS = 2, "*": Mul = 3, "/": Div = 4, "/$": DivS = 5, - "%": Mod = 6, - "%$": ModS = 7, + "%": Rem = 6, + "%$": RemS = 7, "==": Eq = 8, "!=": Ne = 9, "<": Lt = 10, ">": Gt = 11, - "<=": Lte = 12, - ">=": Gte = 13, + "<=": Le = 12, + ">=": Ge = 13, "&": And = 14, "|": Or = 15, "^": Xor = 16, @@ -35,63 +84,136 @@ bi_enum! { } } -use Op::*; - -impl Op { +impl IntOp { /// 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 { + fn swap(self) -> Self { match self { - Add => Add, - Sub => SubS, - SubS => Sub, - Mul => Mul, - Div => DivS, - DivS => Div, - Mod => ModS, - ModS => Mod, - Eq => Eq, - Ne => Ne, - Lt => Gt, - Gt => Lt, - Lte => Gte, - Gte => Lte, - And => And, - Or => Or, - Xor => Xor, - Shl => ShlS, - ShlS => Shl, - Shr => ShrS, - ShrS => Shr, + 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, } } - #[inline] - pub fn op(self, a: u64, b: u64) -> u64 { - const U60: u64 = 0xFFF_FFFF_FFFF_FFFF; + + fn op(self, a: T, b: T) -> T { match self { - Add => a.wrapping_add(b) & U60, - Sub => a.wrapping_sub(b) & U60, - SubS => b.wrapping_sub(a) & U60, - Mul => a.wrapping_mul(b) & U60, - Div => a.checked_div(b).unwrap_or(0), - DivS => b.checked_div(a).unwrap_or(0), - Mod => a.checked_rem(b).unwrap_or(0), - ModS => b.checked_rem(a).unwrap_or(0), - Eq => (a == b) as u64, - Ne => (a != b) as u64, - Lt => (a < b) as u64, - Gt => (a > b) as u64, - Lte => (a <= b) as u64, - Gte => (a >= b) as u64, - And => a & b, - Or => a | b, - Xor => a ^ b, - Shl => (a << b) & U60, - ShlS => (b << a) & U60, - Shr => a >> b, - ShrS => b >> a, + 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), + } + } +} + +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)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Op { + pub ty: Ty, + pub op: IntOp, +} + +impl From for Op { + fn from(value: u16) -> Self { + unsafe { std::mem::transmute(value) } + } +} + +impl From for u16 { + fn from(op: Op) -> Self { + unsafe { std::mem::transmute(op) } + } +} + +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), + } + } +} + +impl FromStr for Op { + type Err = (); + + fn from_str(s: &str) -> Result { + match s.split('.').collect::>().as_slice() { + [ty, op] => Ok(Self { ty: Ty::from_str(ty)?, op: IntOp::from_str(op)? }), + [op] => Ok(Self { ty: Ty::U60, op: IntOp::from_str(op)? }), + + _ => Err(()), + } + } +} + +impl Op { + pub fn swap(self) -> Self { + Self { op: self.op.swap(), ty: self.ty } + } + + #[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, } } } diff --git a/src/run/instruction.rs b/src/run/instruction.rs index b0090f38..4390eb11 100644 --- a/src/run/instruction.rs +++ b/src/run/instruction.rs @@ -58,7 +58,7 @@ pub enum Instruction { /// ```rust,ignore /// let out = net.do_op_num(lab, trg, rhs); /// ``` - OpNum { op: Op, trg: TrgId, rhs: u64, out: TrgId }, + OpNum { op: Op, trg: TrgId, rhs: i64, out: TrgId }, /// See [`Net::do_mat`]. /// ```rust,ignore /// let (lft, rgt) = net.do_mat(trg); @@ -137,14 +137,14 @@ impl<'a, M: Mode> Net<'a, M> { let port = trg.target(); if !M::LAZY && port.tag() == Num { self.free_trg(trg); - let n = self.create_node(Op, op.swap() as Lab); + let n = self.create_node(Op, op.swap().into()); n.p1.wire().set_target(Port::new_num(port.num())); (Trg::port(n.p0), Trg::port(n.p2)) } else if !M::LAZY && port == Port::ERA { self.free_trg(trg); (Trg::port(Port::ERA), Trg::port(Port::ERA)) } else { - let n = self.create_node(Op, op as Lab); + let n = self.create_node(Op, op.into()); self.link_trg_port(trg, n.p0); (Trg::port(n.p1), Trg::port(n.p2)) } @@ -152,7 +152,7 @@ impl<'a, M: Mode> Net<'a, M> { /// `trg ~ ` #[inline(always)] - pub(crate) fn do_op_num(&mut self, op: Op, trg: Trg, rhs: u64) -> Trg { + pub(crate) fn do_op_num(&mut self, op: Op, trg: Trg, rhs: i64) -> Trg { let port = trg.target(); if !M::LAZY && port.tag() == Num { self.rwts.oper += 1; @@ -162,7 +162,7 @@ impl<'a, M: Mode> Net<'a, M> { self.free_trg(trg); Trg::port(Port::ERA) } else { - let n = self.create_node(Op, op as Lab); + let n = self.create_node(Op, op.into()); self.link_trg_port(trg, n.p0); n.p1.wire().set_target(Port::new_num(rhs)); Trg::port(n.p2) diff --git a/src/run/interact.rs b/src/run/interact.rs index 0781d20f..e7f48c7a 100644 --- a/src/run/interact.rs +++ b/src/run/interact.rs @@ -46,7 +46,7 @@ impl<'a, M: Mode> Net<'a, M> { /// Annihilates two binary agents. /// /// ```text - /// + /// /// a2 | | a1 /// _|___|_ /// \ / @@ -68,7 +68,6 @@ impl<'a, M: Mode> Net<'a, M> { /// / \ /// | | /// b1 | | b2 - /// /// ``` #[inline(never)] pub fn anni2(&mut self, a: Port, b: Port) { @@ -83,7 +82,7 @@ impl<'a, M: Mode> Net<'a, M> { /// Commutes two binary agents. /// /// ```text - /// + /// /// a2 | | a1 /// _|___|_ /// \ / @@ -115,7 +114,6 @@ impl<'a, M: Mode> Net<'a, M> { /// \ / \ / /// | | /// b1 | | b2 - /// /// ``` #[inline(never)] pub fn comm22(&mut self, a: Port, b: Port) { @@ -146,7 +144,7 @@ impl<'a, M: Mode> Net<'a, M> { /// Commutes a nilary agent and a binary agent. /// /// ```text - /// + /// /// a (---) /// | /// | @@ -161,7 +159,6 @@ impl<'a, M: Mode> Net<'a, M> { /// a (---) (---) a /// | | /// b1 | | b2 - /// /// ``` #[inline(never)] pub fn comm02(&mut self, a: Port, b: Port) { @@ -255,7 +252,7 @@ impl<'a, M: Mode> Net<'a, M> { pub fn op_num(&mut self, a: Port, b: Port) { trace!(self.tracer, a, b); let a = a.consume_node(); - let op = unsafe { Op::from_unchecked(a.lab) }; + let op = Op::from(a.lab); let a1 = a.p1.load_target(); if a1.tag() == Num { self.rwts.oper += 1; @@ -264,7 +261,7 @@ impl<'a, M: Mode> Net<'a, M> { self.link_wire_port(a.p2, Port::new_num(out)); } else { let op = op.swap(); - let x = self.create_node(Op, op as u16); + 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); diff --git a/src/run/port.rs b/src/run/port.rs index 34e6da95..d77ad3dc 100644 --- a/src/run/port.rs +++ b/src/run/port.rs @@ -130,8 +130,8 @@ impl Port { /// Creates a new [`Num`] port with a given 60-bit numeric value. #[inline(always)] - pub const fn new_num(val: u64) -> Self { - Port((val << 4) | (Num as u64)) + pub const fn new_num(val: i64) -> Self { + Port((val << 4) as u64 | (Num as u64)) } /// Creates a new [`Ref`] port corresponding to a given definition. @@ -167,13 +167,13 @@ impl Port { /// ports. #[inline(always)] pub fn op(&self) -> Op { - unsafe { Op::from_unchecked(self.lab()) } + self.lab().into() } /// Accesses the numeric value of this port; this is valid for [`Num`] ports. #[inline(always)] - pub const fn num(&self) -> u64 { - self.0 >> 4 + pub const fn num(&self) -> i64 { + (self.0 >> 4) as i64 } /// Accesses the wire leaving this port; this is valid for [`Var`] ports and diff --git a/src/trace.rs b/src/trace.rs index 7bb961d1..8905d1cb 100644 --- a/src/trace.rs +++ b/src/trace.rs @@ -362,7 +362,7 @@ impl TraceArg for Trg { impl TraceArg for Op { fn to_word(&self) -> u64 { - *self as u64 + u16::from(*self) as u64 } fn from_word(word: u64) -> impl Debug { unsafe { Op::try_from(word as u16).unwrap_unchecked() } diff --git a/src/transform/eta_reduce.rs b/src/transform/eta_reduce.rs index b057f50a..11745145 100644 --- a/src/transform/eta_reduce.rs +++ b/src/transform/eta_reduce.rs @@ -75,7 +75,7 @@ impl Net { enum NodeType { Ctr(u16), Var(isize), - Num(u64), + Num(i64), Era, Other, Hole, diff --git a/tests/numeric.rs b/tests/numeric.rs index cfa03578..3560016f 100644 --- a/tests/numeric.rs +++ b/tests/numeric.rs @@ -1,21 +1,23 @@ use hvmc::{ ast::{Book, Net}, - ops::Op, + ops::IntOp, }; use insta::{assert_debug_snapshot, assert_snapshot}; use loaders::*; mod loaders; -fn op_net(lnum: u32, op: Op, rnum: u32) -> Book { - let code = format!("@main = root & #{lnum} ~ <{op} #{rnum} root>"); +fn op_net_u32(lnum: u32, op: IntOp, rnum: u32) -> Book { + let code = format!("@main = root & #{lnum} ~ "); println!("Code: {code:?}"); parse_core(&code) } +use IntOp::*; + #[test] fn test_add() { - let net = op_net(10, Op::Add, 2); + let net = op_net_u32(10, Add, 2); let (rwts, net) = normal(net, Some(128)); assert_snapshot!(Net::to_string(&net), @"#12"); assert_debug_snapshot!(rwts.total(), @"3"); @@ -23,98 +25,98 @@ fn test_add() { #[test] fn test_sub() { - let net = op_net(10, Op::Sub, 2); + let net = op_net_u32(10, Sub, 2); let (_rwts, net) = normal(net, Some(128)); assert_snapshot!(Net::to_string(&net), @"#8"); } #[test] fn test_mul() { - let net = op_net(10, Op::Mul, 2); + let net = op_net_u32(10, Mul, 2); let (_rwts, net) = normal(net, Some(128)); assert_snapshot!(Net::to_string(&net), @"#20"); } #[test] fn test_div() { - let net = op_net(10, Op::Div, 2); + let net = op_net_u32(10, Div, 2); let (_rwts, net) = normal(net, Some(128)); assert_snapshot!(Net::to_string(&net), @"#5"); } #[test] -fn test_mod() { - let net = op_net(10, Op::Mod, 2); +fn test_rem() { + let net = op_net_u32(10, Rem, 2); let (_rwts, net) = normal(net, Some(128)); assert_snapshot!(Net::to_string(&net), @"#0"); } #[test] fn test_eq() { - let net = op_net(10, Op::Eq, 2); + let net = op_net_u32(10, Eq, 2); let (_rwts, net) = normal(net, Some(128)); assert_snapshot!(Net::to_string(&net), @"#0"); } #[test] fn test_ne() { - let net = op_net(10, Op::Ne, 2); + let net = op_net_u32(10, Ne, 2); let (_rwts, net) = normal(net, Some(128)); assert_snapshot!(Net::to_string(&net), @"#1"); } #[test] fn test_lt() { - let net = op_net(10, Op::Lt, 2); + let net = op_net_u32(10, Lt, 2); let (_rwts, net) = normal(net, Some(128)); assert_snapshot!(Net::to_string(&net), @"#0"); } #[test] fn test_gt() { - let net = op_net(10, Op::Gt, 2); + let net = op_net_u32(10, Gt, 2); let (_rwts, net) = normal(net, Some(128)); assert_snapshot!(Net::to_string(&net), @"#1"); } #[test] fn test_and() { - let net = op_net(10, Op::And, 2); + let net = op_net_u32(10, And, 2); let (_rwts, net) = normal(net, Some(128)); assert_snapshot!(Net::to_string(&net), @"#2"); } #[test] fn test_or() { - let net = op_net(10, Op::Or, 2); + let net = op_net_u32(10, Or, 2); let (_rwts, net) = normal(net, Some(128)); assert_snapshot!(Net::to_string(&net), @"#10"); } #[test] fn test_xor() { - let net = op_net(10, Op::Xor, 2); + let net = op_net_u32(10, Xor, 2); let (_rwts, net) = normal(net, Some(128)); assert_snapshot!(Net::to_string(&net), @"#8"); } #[test] fn test_lsh() { - let net = op_net(10, Op::Shl, 2); + let net = op_net_u32(10, Shl, 2); let (_rwts, net) = normal(net, Some(128)); assert_snapshot!(Net::to_string(&net), @"#40"); } #[test] fn test_rsh() { - let net = op_net(10, Op::Shr, 2); + let net = op_net_u32(10, Shr, 2); let (_rwts, net) = normal(net, Some(128)); assert_snapshot!(Net::to_string(&net), @"#2"); } #[test] fn test_div_by_0() { - let net = op_net(9, Op::Div, 0); + let net = op_net_u32(9, Div, 0); let (rwts, net) = normal(net, Some(128)); assert_snapshot!(Net::to_string(&net), @"#0"); assert_debug_snapshot!(rwts.total(), @"3");