From 4c1e7420444fc77667283db632112633e6d58809 Mon Sep 17 00:00:00 2001 From: Philip Monk Date: Mon, 13 Feb 2023 21:03:22 -0700 Subject: [PATCH] [jets] import ubig, add "add" jet --- rust/ares/Cargo.lock | 34 ++++++++++++++ rust/ares/Cargo.toml | 1 + rust/ares/src/jets.rs | 65 ++------------------------ rust/ares/src/jets_math.rs | 95 ++++++++++++++++++++++++++++++++++++++ rust/ares/src/lib.rs | 1 + rust/ares/src/noun.rs | 58 ++++++++++++++++++++++- 6 files changed, 191 insertions(+), 63 deletions(-) create mode 100644 rust/ares/src/jets_math.rs diff --git a/rust/ares/Cargo.lock b/rust/ares/Cargo.lock index 19b07a1..93df99f 100644 --- a/rust/ares/Cargo.lock +++ b/rust/ares/Cargo.lock @@ -16,6 +16,7 @@ dependencies = [ "bitvec", "criterion", "either", + "ibig", "intmap", "libc", "memmap", @@ -254,6 +255,18 @@ dependencies = [ "libc", ] +[[package]] +name = "ibig" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1fcc7f316b2c079dde77564a1360639c1a956a23fa96122732e416cb10717bb" +dependencies = [ + "cfg-if", + "num-traits", + "rand", + "static_assertions", +] + [[package]] name = "indexmap" version = "1.9.2" @@ -439,6 +452,21 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + [[package]] name = "rayon" version = "1.6.1" @@ -528,6 +556,12 @@ dependencies = [ "serde", ] +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "syn" version = "1.0.98" diff --git a/rust/ares/Cargo.toml b/rust/ares/Cargo.toml index 02c73d9..3a1cce5 100644 --- a/rust/ares/Cargo.toml +++ b/rust/ares/Cargo.toml @@ -17,6 +17,7 @@ intmap = "1.1.0" num-traits = "0.2" num-derive = "0.3" criterion = "0.4" +ibig = "0.3.6" [[bin]] name = "cue_pill" diff --git a/rust/ares/src/jets.rs b/rust/ares/src/jets.rs index 30c60f2..a1e5800 100644 --- a/rust/ares/src/jets.rs +++ b/rust/ares/src/jets.rs @@ -1,9 +1,9 @@ use crate::interpreter::raw_slot; +use crate::jets_math::*; use crate::mem::NockStack; use crate::mug::mug; -use crate::noun::{DirectAtom, IndirectAtom, Noun}; +use crate::noun::{Noun}; use ares_macros::tas; -use either::Either::*; /// Return Err if the computation crashed or should punt to Nock pub type Jet = fn(&mut NockStack, Noun) -> Result; @@ -25,83 +25,26 @@ impl From for () { fn from(_: JetErr) -> Self {} } -use JetErr::*; - pub fn get_jet(jet_name: Noun) -> Result { match jet_name.as_direct()?.data() { tas!(b"dec") => Ok(jet_dec), + tas!(b"add") => Ok(jet_add), tas!(b"cut") => Ok(jet_cut), tas!(b"mug") => Ok(jet_mug), _ => { // eprintln!("Unknown jet: {:?}", jet_name); Err(()) - } + }, } } pub fn get_jet_test_mode(jet_name: Noun) -> bool { match jet_name.as_direct().unwrap().data() { - tas!(b"dec") => true, tas!(b"cut") => true, _ => false, } } -fn jet_dec(stack: &mut NockStack, subject: Noun) -> Result { - let arg = raw_slot(subject, 6); - if let Ok(atom) = arg.as_atom() { - match atom.as_either() { - Left(direct) => { - if direct.data() == 0 { - Err(Deterministic) - } else { - Ok(unsafe { DirectAtom::new_unchecked(direct.data() - 1) }.as_noun()) - } - } - Right(indirect) => { - let indirect_slice = indirect.as_bitslice(); - match indirect_slice.first_one() { - None => { - panic!("Decrementing 0 stored as an indirect atom"); - } - Some(first_one) => { - let (mut new_indirect, new_slice) = - unsafe { IndirectAtom::new_raw_mut_bitslice(stack, indirect.size()) }; - if first_one > 0 { - new_slice[..first_one].fill(true); - } - new_slice.set(first_one, false); - new_slice[first_one + 1..] - .copy_from_bitslice(&indirect_slice[first_one + 1..]); - let res = unsafe { new_indirect.normalize_as_atom() }; - Ok(res.as_noun()) - } - } - } - } - } else { - Err(Deterministic) - } -} - -fn jet_cut(stack: &mut NockStack, subject: Noun) -> Result { - let arg = raw_slot(subject, 6); - let bloq = raw_slot(arg, 2).as_direct()?.data(); - let start = raw_slot(arg, 12).as_direct()?.data(); - let run = raw_slot(arg, 13).as_direct()?.data(); - let atom = raw_slot(arg, 7).as_atom()?; - let slice = atom.as_bitslice(); - let unit = 1 << bloq; - let new_indirect = unsafe { - let (mut new_indirect, new_slice) = - IndirectAtom::new_raw_mut_bitslice(stack, ((run * unit + 63) >> 6) as usize); - new_slice[..(unit * run) as usize] - .copy_from_bitslice(&slice[(unit * start) as usize..(unit * (start + run)) as usize]); - new_indirect.normalize_as_atom() - }; - Ok(new_indirect.as_noun()) -} - fn jet_mug(stack: &mut NockStack, subject: Noun) -> Result { let arg = raw_slot(subject, 6); Ok(mug(stack, arg).as_noun()) diff --git a/rust/ares/src/jets_math.rs b/rust/ares/src/jets_math.rs new file mode 100644 index 0000000..6cbc780 --- /dev/null +++ b/rust/ares/src/jets_math.rs @@ -0,0 +1,95 @@ +/** Math jets + * + * We use ibig for math operations. This is a pure rust library, and it is very convenient to use. + * If they're noticeably, fater, we may want to use gmp or a library wrapping it, such as rug. + * + * In any case, it's important to ensure that the library only allocates on the nock stack. Gmp + * has mp_set_memory_functions. I don't know if rug does any allocation on top of that. ibig does + * not appear to support custom allocation functions, but we could probably patch it. If we're + * patching it, we might even be able to avoid copying the input and output at all, which might + * give a greater performance advantage than using gmp anyway. + * + * Another approach is use a global custom allocator. This is fairly involved, but it would allow + * us to use any library without worrying whether it allocates. + */ + +use crate::interpreter::raw_slot; +use crate::jets::{JetErr, JetErr::*}; +use crate::mem::NockStack; +use crate::noun::{Atom, DirectAtom, IndirectAtom, Noun}; +use either::Either::*; + +pub fn jet_dec(stack: &mut NockStack, subject: Noun) -> Result { + let arg = raw_slot(subject, 6); + if let Ok(atom) = arg.as_atom() { + match atom.as_either() { + Left(direct) => { + if direct.data() == 0 { + Err(Deterministic) + } else { + Ok(unsafe { DirectAtom::new_unchecked(direct.data() - 1) }.as_noun()) + } + } + Right(indirect) => { + let indirect_slice = indirect.as_bitslice(); + match indirect_slice.first_one() { + None => { + panic!("Decrementing 0 stored as an indirect atom"); + } + Some(first_one) => { + let (mut new_indirect, new_slice) = + unsafe { IndirectAtom::new_raw_mut_bitslice(stack, indirect.size()) }; + if first_one > 0 { + new_slice[..first_one].fill(true); + } + new_slice.set(first_one, false); + new_slice[first_one + 1..] + .copy_from_bitslice(&indirect_slice[first_one + 1..]); + let res = unsafe { new_indirect.normalize_as_atom() }; + Ok(res.as_noun()) + } + } + } + } + } else { + Err(Deterministic) + } +} + +pub fn jet_cut(stack: &mut NockStack, subject: Noun) -> Result { + let arg = raw_slot(subject, 6); + let bloq = raw_slot(arg, 2).as_direct()?.data(); + let start = raw_slot(arg, 12).as_direct()?.data(); + let run = raw_slot(arg, 13).as_direct()?.data(); + let atom = raw_slot(arg, 7).as_atom()?; + let slice = atom.as_bitslice(); + let unit = 1 << bloq; + let new_indirect = unsafe { + let (mut new_indirect, new_slice) = + IndirectAtom::new_raw_mut_bitslice(stack, ((run * unit + 63) >> 6) as usize); + new_slice[..(unit * run) as usize] + .copy_from_bitslice(&slice[(unit * start) as usize..(unit * (start + run)) as usize]); + new_indirect.normalize_as_atom() + }; + Ok(new_indirect.as_noun()) +} + +pub fn jet_add(stack: &mut NockStack, subject: Noun) -> Result { + eprintln!("\radd"); + let arg = raw_slot(subject, 6); + let a = raw_slot(arg, 2).as_atom()?; + let b = raw_slot(arg, 3).as_atom()?; + + let res = match (a.as_direct(), b.as_direct()) { + (Ok(a), Ok(b)) => { + Atom::new(stack, a.data() + b.data()) + } + (_, _) => { + let a_int = a.as_ubig(); + let b_int = b.as_ubig(); + let res = a_int + b_int; + Atom::from_ubig(stack, &res) + } + }; + Ok(res.as_noun()) +} diff --git a/rust/ares/src/lib.rs b/rust/ares/src/lib.rs index 8dfc53a..b0377e8 100644 --- a/rust/ares/src/lib.rs +++ b/rust/ares/src/lib.rs @@ -2,6 +2,7 @@ extern crate num_derive; pub mod interpreter; pub mod jets; +pub mod jets_math; pub mod mem; pub mod mug; pub mod newt; diff --git a/rust/ares/src/noun.rs b/rust/ares/src/noun.rs index 3a0e9bf..ad67d7d 100644 --- a/rust/ares/src/noun.rs +++ b/rust/ares/src/noun.rs @@ -4,6 +4,7 @@ use intmap::IntMap; use std::fmt; use std::ptr; use std::slice::{from_raw_parts, from_raw_parts_mut}; +use ibig::UBig; /** Tag for a direct atom. */ const DIRECT_TAG: u64 = 0x0; @@ -131,6 +132,10 @@ impl DirectAtom { Atom { direct: self } } + pub fn as_ubig(self) -> UBig { + UBig::from(self.0) + } + pub const fn as_noun(self) -> Noun { Noun { direct: self } } @@ -210,7 +215,7 @@ impl IndirectAtom { /** Make an indirect atom by copying from other memory. * - * The size is specified in 64 bit words, not in bytes. + * Note: size is in 64-bit words, not bytes. */ pub unsafe fn new_raw( allocator: &mut dyn NounAllocator, @@ -222,6 +227,20 @@ impl IndirectAtom { *(indirect.normalize()) } + /** Make an indirect atom by copying from other memory. + * + * Note: size is bytes, not words + */ + pub unsafe fn new_raw_bytes( + allocator: &mut dyn NounAllocator, + size: usize, + data: *const u8, + ) -> Self { + let (mut indirect, buffer) = Self::new_raw_mut_bytes(allocator, size); + ptr::copy_nonoverlapping(data, buffer.as_mut_ptr(), size); + *(indirect.normalize()) + } + /** Make an indirect atom that can be written into. Return the atom (which should not be used * until it is written and normalized) and a mutable pointer which is the data buffer for the * indirect atom, to be written into. @@ -270,7 +289,7 @@ impl IndirectAtom { allocator: &mut dyn NounAllocator, size: usize, ) -> (Self, &'a mut [u8]) { - let word_size = (size + 7) << 3; + let word_size = (size + 7) >> 3; let (noun, ptr) = Self::new_raw_mut_zeroed(allocator, word_size); (noun, from_raw_parts_mut(ptr as *mut u8, size)) } @@ -298,6 +317,10 @@ impl IndirectAtom { BitSlice::from_slice(self.as_slice()) } + pub fn as_ubig(self) -> UBig { + UBig::from_le_bytes(self.as_bytes()) + } + /** Ensure that the size does not contain any trailing 0 words */ pub unsafe fn normalize(&mut self) -> &Self { let mut index = self.size() - 1; @@ -497,6 +520,29 @@ impl Atom { unsafe { IndirectAtom::new_raw(allocator, 1, &value).as_atom() } } } + + // to_le_bytes and new_raw are copies. We should be able to do this completely without copies + // if we integrate with ibig properly. + pub fn from_ubig(allocator: &mut dyn NounAllocator, big: &UBig) -> Atom { + let bit_size = big.bit_len(); + let buffer = big.to_le_bytes(); + if bit_size < 64 { + let value: u64 = if bit_size == 0 { 0 } else { + buffer[0] as u64 + | if bit_size <= 8 { 0 } else { (buffer[1] as u64) << 8 + | if bit_size <= 16 { 0 } else { (buffer[2] as u64) << 16 + | if bit_size <= 24 { 0 } else { (buffer[3] as u64) << 24 + | if bit_size <= 32 { 0 } else { (buffer[4] as u64) << 32 + | if bit_size <= 40 { 0 } else { (buffer[5] as u64) << 40 + | if bit_size <= 48 { 0 } else { (buffer[6] as u64) << 48 + | if bit_size <= 56 { 0 } else { (buffer[7] as u64) << 56 } } } } } } } }; + unsafe { DirectAtom::new_unchecked(value).as_atom() } + } else { + let byte_size = (big.bit_len() + 7) >> 3; + unsafe { IndirectAtom::new_raw_bytes(allocator, byte_size, buffer.as_ptr()).as_atom() } + } + } + pub fn is_direct(&self) -> bool { unsafe { is_direct_atom(self.raw) } } @@ -537,6 +583,14 @@ impl Atom { } } + pub fn as_ubig(&self) -> UBig { + if self.is_indirect() { + unsafe { self.indirect.as_ubig() } + } else { + unsafe { self.direct.as_ubig() } + } + } + pub fn size(&self) -> usize { match self.as_either() { Either::Left(_direct) => 1,