[jets] import ubig, add "add" jet

This commit is contained in:
Philip Monk 2023-02-13 21:03:22 -07:00
parent a527ba7aaa
commit 4c1e742044
6 changed files with 191 additions and 63 deletions

34
rust/ares/Cargo.lock generated
View File

@ -16,6 +16,7 @@ dependencies = [
"bitvec", "bitvec",
"criterion", "criterion",
"either", "either",
"ibig",
"intmap", "intmap",
"libc", "libc",
"memmap", "memmap",
@ -254,6 +255,18 @@ dependencies = [
"libc", "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]] [[package]]
name = "indexmap" name = "indexmap"
version = "1.9.2" version = "1.9.2"
@ -439,6 +452,21 @@ version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" 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]] [[package]]
name = "rayon" name = "rayon"
version = "1.6.1" version = "1.6.1"
@ -528,6 +556,12 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "static_assertions"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]] [[package]]
name = "syn" name = "syn"
version = "1.0.98" version = "1.0.98"

View File

@ -17,6 +17,7 @@ intmap = "1.1.0"
num-traits = "0.2" num-traits = "0.2"
num-derive = "0.3" num-derive = "0.3"
criterion = "0.4" criterion = "0.4"
ibig = "0.3.6"
[[bin]] [[bin]]
name = "cue_pill" name = "cue_pill"

View File

@ -1,9 +1,9 @@
use crate::interpreter::raw_slot; use crate::interpreter::raw_slot;
use crate::jets_math::*;
use crate::mem::NockStack; use crate::mem::NockStack;
use crate::mug::mug; use crate::mug::mug;
use crate::noun::{DirectAtom, IndirectAtom, Noun}; use crate::noun::{Noun};
use ares_macros::tas; use ares_macros::tas;
use either::Either::*;
/// Return Err if the computation crashed or should punt to Nock /// Return Err if the computation crashed or should punt to Nock
pub type Jet = fn(&mut NockStack, Noun) -> Result<Noun, JetErr>; pub type Jet = fn(&mut NockStack, Noun) -> Result<Noun, JetErr>;
@ -25,83 +25,26 @@ impl From<JetErr> for () {
fn from(_: JetErr) -> Self {} fn from(_: JetErr) -> Self {}
} }
use JetErr::*;
pub fn get_jet(jet_name: Noun) -> Result<Jet, ()> { pub fn get_jet(jet_name: Noun) -> Result<Jet, ()> {
match jet_name.as_direct()?.data() { match jet_name.as_direct()?.data() {
tas!(b"dec") => Ok(jet_dec), tas!(b"dec") => Ok(jet_dec),
tas!(b"add") => Ok(jet_add),
tas!(b"cut") => Ok(jet_cut), tas!(b"cut") => Ok(jet_cut),
tas!(b"mug") => Ok(jet_mug), tas!(b"mug") => Ok(jet_mug),
_ => { _ => {
// eprintln!("Unknown jet: {:?}", jet_name); // eprintln!("Unknown jet: {:?}", jet_name);
Err(()) Err(())
} },
} }
} }
pub fn get_jet_test_mode(jet_name: Noun) -> bool { pub fn get_jet_test_mode(jet_name: Noun) -> bool {
match jet_name.as_direct().unwrap().data() { match jet_name.as_direct().unwrap().data() {
tas!(b"dec") => true,
tas!(b"cut") => true, tas!(b"cut") => true,
_ => false, _ => false,
} }
} }
fn jet_dec(stack: &mut NockStack, subject: Noun) -> Result<Noun, JetErr> {
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<Noun, JetErr> {
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<Noun, JetErr> { fn jet_mug(stack: &mut NockStack, subject: Noun) -> Result<Noun, JetErr> {
let arg = raw_slot(subject, 6); let arg = raw_slot(subject, 6);
Ok(mug(stack, arg).as_noun()) Ok(mug(stack, arg).as_noun())

View File

@ -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<Noun, JetErr> {
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<Noun, JetErr> {
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<Noun, JetErr> {
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())
}

View File

@ -2,6 +2,7 @@
extern crate num_derive; extern crate num_derive;
pub mod interpreter; pub mod interpreter;
pub mod jets; pub mod jets;
pub mod jets_math;
pub mod mem; pub mod mem;
pub mod mug; pub mod mug;
pub mod newt; pub mod newt;

View File

@ -4,6 +4,7 @@ use intmap::IntMap;
use std::fmt; use std::fmt;
use std::ptr; use std::ptr;
use std::slice::{from_raw_parts, from_raw_parts_mut}; use std::slice::{from_raw_parts, from_raw_parts_mut};
use ibig::UBig;
/** Tag for a direct atom. */ /** Tag for a direct atom. */
const DIRECT_TAG: u64 = 0x0; const DIRECT_TAG: u64 = 0x0;
@ -131,6 +132,10 @@ impl DirectAtom {
Atom { direct: self } Atom { direct: self }
} }
pub fn as_ubig(self) -> UBig {
UBig::from(self.0)
}
pub const fn as_noun(self) -> Noun { pub const fn as_noun(self) -> Noun {
Noun { direct: self } Noun { direct: self }
} }
@ -210,7 +215,7 @@ impl IndirectAtom {
/** Make an indirect atom by copying from other memory. /** 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( pub unsafe fn new_raw(
allocator: &mut dyn NounAllocator, allocator: &mut dyn NounAllocator,
@ -222,6 +227,20 @@ impl IndirectAtom {
*(indirect.normalize()) *(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 /** 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 * until it is written and normalized) and a mutable pointer which is the data buffer for the
* indirect atom, to be written into. * indirect atom, to be written into.
@ -270,7 +289,7 @@ impl IndirectAtom {
allocator: &mut dyn NounAllocator, allocator: &mut dyn NounAllocator,
size: usize, size: usize,
) -> (Self, &'a mut [u8]) { ) -> (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); let (noun, ptr) = Self::new_raw_mut_zeroed(allocator, word_size);
(noun, from_raw_parts_mut(ptr as *mut u8, size)) (noun, from_raw_parts_mut(ptr as *mut u8, size))
} }
@ -298,6 +317,10 @@ impl IndirectAtom {
BitSlice::from_slice(self.as_slice()) 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 */ /** Ensure that the size does not contain any trailing 0 words */
pub unsafe fn normalize(&mut self) -> &Self { pub unsafe fn normalize(&mut self) -> &Self {
let mut index = self.size() - 1; let mut index = self.size() - 1;
@ -497,6 +520,29 @@ impl Atom {
unsafe { IndirectAtom::new_raw(allocator, 1, &value).as_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 { pub fn is_direct(&self) -> bool {
unsafe { is_direct_atom(self.raw) } 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 { pub fn size(&self) -> usize {
match self.as_either() { match self.as_either() {
Either::Left(_direct) => 1, Either::Left(_direct) => 1,