From e4f44d705cfef1ef4f2a2fdbff2a02e5fa6bd1fd Mon Sep 17 00:00:00 2001 From: Philip Monk Date: Sat, 11 Feb 2023 02:39:27 -0700 Subject: [PATCH 01/10] [ares] convenient noun constructors --- rust/ares/src/noun.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/rust/ares/src/noun.rs b/rust/ares/src/noun.rs index 923487e..abea7d8 100644 --- a/rust/ares/src/noun.rs +++ b/rust/ares/src/noun.rs @@ -150,6 +150,11 @@ impl fmt::Debug for DirectAtom { } } +#[allow(non_snake_case)] +pub fn D(n: u64) -> Noun { + DirectAtom::new_panic(n).as_noun() +} + /** An indirect atom. * * Indirect atoms represent atoms above DIRECT_MAX as a tagged pointer to a memory buffer @@ -387,6 +392,19 @@ impl Cell { } } + pub fn new_tuple(allocator: &mut dyn NounAllocator, tup: &[Noun]) -> Cell { + if tup.len() < 2 { + panic!("Cannot create tuple with fewer than 2 elements"); + } + + let len = tup.len(); + let mut cell = Cell::new(allocator, tup[len-2], tup[len-1]); + for i in (0..len-2).rev() { + cell = Cell::new(allocator, tup[i], cell.as_noun()); + } + cell + } + pub unsafe fn new_raw_mut(allocator: &mut dyn NounAllocator) -> (Cell, *mut CellMemory) { let memory = allocator.alloc_cell(); (*memory).metadata = 0; From 1d6e4c74b2ba8fb4240625a766221b182b2aa557 Mon Sep 17 00:00:00 2001 From: Philip Monk Date: Fri, 10 Feb 2023 23:44:06 -0700 Subject: [PATCH 02/10] [ares] add basic ipc handler --- rust/ares/src/lib.rs | 1 + rust/ares/src/main.rs | 61 ++++++++++- rust/ares/src/newt.rs | 248 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 308 insertions(+), 2 deletions(-) create mode 100644 rust/ares/src/newt.rs diff --git a/rust/ares/src/lib.rs b/rust/ares/src/lib.rs index 8f2ff19..7608bd8 100644 --- a/rust/ares/src/lib.rs +++ b/rust/ares/src/lib.rs @@ -4,6 +4,7 @@ pub mod interpreter; pub mod jets; pub mod mem; pub mod mug; +pub mod newt; pub mod noun; pub mod serialization; diff --git a/rust/ares/src/main.rs b/rust/ares/src/main.rs index b1ab57a..9b39273 100644 --- a/rust/ares/src/main.rs +++ b/rust/ares/src/main.rs @@ -1,7 +1,9 @@ -use ares::interpreter::interpret; +use ares::interpreter::{interpret, raw_slot}; use ares::mem::NockStack; -use ares::noun::IndirectAtom; +use ares::newt::Newt; +use ares::noun::{IndirectAtom, D}; use ares::serialization::{cue, jam}; +use ares_macros::tas; use memmap::Mmap; use memmap::MmapMut; use std::env; @@ -14,6 +16,10 @@ use std::ptr::write_bytes; fn main() -> io::Result<()> { let filename = env::args().nth(1).expect("Must provide input filename"); + if filename == "serf" { + return serf(); + } + let output_filename = format!("{}.out", filename.clone()); let f = File::open(filename)?; let in_len = f.metadata()?.len(); @@ -53,3 +59,54 @@ fn main() -> io::Result<()> { }; Ok(()) } + +/** + * This is suitable for talking to the king process. To test, change the arg_c[0] line in + * u3_lord_init in vere to point at this binary and start vere like normal. + */ +fn serf() -> io::Result<()> { + let mut newt = Newt::new(); + newt.ripe(0, 0); + + let mut eve = 0; + + // Can't use for loop because it borrows newt + loop { + let writ = if let Some(writ) = newt.next() { + writ + } else { + break; + }; + + let tag = raw_slot(writ, 2).as_direct().unwrap(); + match tag.data() { + tas!(b"live") => { + newt.live(); + } + tas!(b"peek") => { + newt.peek_done(D(0)); + } + tas!(b"play") => { + eve = raw_slot(writ, 6).as_direct().unwrap().data(); + let mut lit = raw_slot(writ, 7); + loop { + if let Ok(cell) = lit.as_cell() { + eve += 1; + lit = cell.tail(); + } else { + break; + } + } + newt.play_done(0); + } + tas!(b"work") => { + newt.flog(D(tas!(b"working"))); + newt.work_done(eve, 0, D(0)); + eve += 1; + } + _ => panic!("got message with unknown tag {:?}", tag), + }; + } + + Ok(()) +} diff --git a/rust/ares/src/newt.rs b/rust/ares/src/newt.rs new file mode 100644 index 0000000..db4cedf --- /dev/null +++ b/rust/ares/src/newt.rs @@ -0,0 +1,248 @@ +/** Newt: IPC to the king + * + * This manages an IPC connection to the king over stdin and stdout. The protocol is jammed nouns, + * with the following schema: + * + * |% + * :: +writ: from king to serf + * :: + * +$ writ + * $% $: %live + * $% [%cram eve=@] + * [%exit cod=@] + * [%save eve=@] + * [%meld ~] + * [%pack ~] + * == == + * [%peek mil=@ sam=*] :: gang (each path $%([%once @tas @tas path] [%beam @tas beam])) + * [%play eve=@ lit=(list ?((pair @da ovum) *))] + * [%work mil=@ job=(pair @da ovum)] + * == + * :: +plea: from serf to king + * :: + * +$ plea + * $% [%live ~] + * [%ripe [pro=%1 hon=@ nok=@] eve=@ mug=@] + * [%slog pri=@ tank] + * [%flog cord] + * $: %peek + * $% [%done dat=(unit (cask))] + * [%bail dud=goof] + * == == + * $: %play + * $% [%done mug=@] + * [%bail eve=@ mug=@ dud=goof] + * == == + * $: %work + * $% [%done eve=@ mug=@ fec=(list ovum)] + * [%swap eve=@ mug=@ job=(pair @da ovum) fec=(list ovum)] + * [%bail lud=(list goof)] + * == == + * == + * -- + * + * NB: stdin and stdout are generally buffered, and there's no officially supported way to work + * around that: https://github.com/rust-lang/rust/issues/58326. + * + * We use stdin and stdout with File::from_raw_fd(0) and File::from_raw_fd(1), which seems to get + * around this. We tested that using io::Stdout requires flushing while this doesn't, but we + * haven't tested the same for stdin. + * + * It's important to not use io::Stdin and io::Stdout directly. All printfs should use stderr. + */ +use crate::mem::NockStack; +use crate::noun::{Cell, IndirectAtom, Noun, D}; +use crate::serialization::{cue, jam}; +use ares_macros::tas; +use either::Either; +use std::io::{Read, Write}; +use std::os::unix::prelude::FromRawFd; +use std::ptr::{copy_nonoverlapping, write_bytes}; + +pub struct Newt { + stack: NockStack, + input: std::fs::File, + output: std::fs::File, +} + +impl Newt { + pub fn new() -> Newt { + Newt { + stack: NockStack::new(8 << 10 << 10, 0), + input: unsafe { std::fs::File::from_raw_fd(0) }, + output: unsafe { std::fs::File::from_raw_fd(1) }, + } + } + + /** Write a noun to the newt. + * + * NB: we write 64-bit words, while vere writes bytes. The extra zero bytes shouldn't be a + * problem. + */ + fn write_noun(&mut self, noun: Noun) { + let atom = jam(&mut self.stack, noun); + let size = atom.size() << 3; + let mut buf = vec![0 as u8; size + 5]; + buf[1] = size as u8; + buf[2] = (size >> 8) as u8; + buf[3] = (size >> 16) as u8; + buf[4] = (size >> 24) as u8; + match atom.as_either() { + Either::Left(direct) => unsafe { + copy_nonoverlapping( + &direct.data() as *const u64 as *const u8, + buf.as_mut_ptr().add(5) as *mut u8, + size, + ); + }, + Either::Right(indirect) => unsafe { + // REVIEW: is this safe/the right way to do this? + copy_nonoverlapping( + indirect.data_pointer() as *const u8, + buf.as_mut_ptr().add(5) as *mut u8, + size, + ); + }, + }; + self.output.write_all(&buf).unwrap(); + } + + /** Send %ripe, the first event. */ + pub fn ripe(&mut self, eve: u64, mug: u64) { + let version = Cell::new_tuple( + &mut self.stack, + &[ + D(1), // newt protocol + D(139), // hoon kelvin + D(4), // nock kelvin + ], + ) + .as_noun(); + let ripe = Cell::new_tuple( + &mut self.stack, + &[D(tas!(b"ripe")), version, D(eve), D(mug)], + ) + .as_noun(); + self.write_noun(ripe); + } + + /** Send %live, acknowledging. */ + pub fn live(&mut self) { + let live = Cell::new_tuple(&mut self.stack, &[D(tas!(b"live")), D(0)]).as_noun(); + self.write_noun(live); + } + + /** Send %slog, pretty-printed debug output. */ + pub fn slog(&mut self, pri: u64, tank: Noun) { + let slog = Cell::new_tuple(&mut self.stack, &[D(tas!(b"slog")), D(pri), tank]).as_noun(); + self.write_noun(slog); + } + + /** Send %flog, raw debug output. */ + pub fn flog(&mut self, cord: Noun) { + let flog = Cell::new_tuple(&mut self.stack, &[D(tas!(b"flog")), cord]).as_noun(); + self.write_noun(flog); + } + + /** Send %peek %done, successfully scried. */ + pub fn peek_done(&mut self, dat: Noun) { + let peek = + Cell::new_tuple(&mut self.stack, &[D(tas!(b"peek")), D(tas!(b"done")), dat]).as_noun(); + self.write_noun(peek); + } + + /** Send %peek %bail, unsuccessfully scried. */ + pub fn peek_bail(&mut self, dud: Noun) { + let peek = + Cell::new_tuple(&mut self.stack, &[D(tas!(b"peek")), D(tas!(b"bail")), dud]).as_noun(); + self.write_noun(peek); + } + + /** Send %play %done, successfully replayed events. */ + pub fn play_done(&mut self, mug: u64) { + let play = Cell::new_tuple( + &mut self.stack, + &[D(tas!(b"play")), D(tas!(b"done")), D(mug)], + ) + .as_noun(); + self.write_noun(play); + } + + /** Send %play %bail, failed to replay events. */ + pub fn play_bail(&mut self, eve: u64, mug: u64, dud: Noun) { + let play = Cell::new_tuple( + &mut self.stack, + &[D(tas!(b"play")), D(tas!(b"bail")), D(eve), D(mug), dud], + ) + .as_noun(); + self.write_noun(play); + } + + /** Send %work %done, successfully ran event. */ + pub fn work_done(&mut self, eve: u64, mug: u64, fec: Noun) { + let work = Cell::new_tuple( + &mut self.stack, + &[D(tas!(b"work")), D(tas!(b"done")), D(eve), D(mug), fec], + ) + .as_noun(); + self.write_noun(work); + } + + /** Send %work %swap, successfully replaced failed event. */ + pub fn work_swap(&mut self, eve: u64, mug: u64, job: Noun, fec: Noun) { + let work = Cell::new_tuple( + &mut self.stack, + &[D(tas!(b"work")), D(tas!(b"swap")), D(eve), D(mug), job, fec], + ) + .as_noun(); + self.write_noun(work); + } + + /** Send %work %bail, failed to run event. */ + pub fn work_bail(&mut self, lud: Noun) { + let work = + Cell::new_tuple(&mut self.stack, &[D(tas!(b"work")), D(tas!(b"bail")), lud]).as_noun(); + self.write_noun(work); + } +} + +impl Iterator for Newt { + type Item = Noun; + + /** Fetch next message. */ + fn next(&mut self) -> Option { + let mut header: Vec = Vec::with_capacity(5); + header.resize(5, 0); + if let Err(err) = self.input.read_exact(&mut header) { + if err.kind() == std::io::ErrorKind::UnexpectedEof { + return None; + } else { + panic!("Error reading header: {}", err); + } + } + + let byte_len = u32::from_le_bytes([header[1], header[2], header[3], header[4]]) as usize; + + // Would be nice to copy directly into an indirect atom, but I don't know how to do that + // when these are aligned to bytes + let mut body: Vec = Vec::with_capacity(byte_len); + body.resize(byte_len, 0); + if let Err(err) = self.input.read_exact(&mut body) { + if err.kind() == std::io::ErrorKind::UnexpectedEof { + return None; + } else { + panic!("Error reading body: {}", err); + } + } + + let atom = unsafe { + let word_len = (byte_len + 7) >> 3; + let (mut atom, dest) = IndirectAtom::new_raw_mut(&mut self.stack, word_len as usize); + write_bytes(dest.add(word_len as usize - 1), 0, 8); + copy_nonoverlapping(body.as_ptr(), dest as *mut u8, byte_len); + atom.normalize_as_atom() + }; + + Some(cue(&mut self.stack, atom)) + } +} From 51b4735711c19d4b6b2daff8242a4c0d1b3fd623 Mon Sep 17 00:00:00 2001 From: Philip Monk Date: Sat, 11 Feb 2023 17:24:38 -0700 Subject: [PATCH 03/10] [ares] fill out basic lifecycle With these changes, you can boot the included "baby" pill -- a trivial pill which requires no jets and prints the text "effect" on every event. --- hoon/scaffolding/baby.hoon | 48 +++++++++++++++++ rust/ares/src/interpreter.rs | 80 +++++++++++++++++++++------- rust/ares/src/lib.rs | 1 + rust/ares/src/main.rs | 60 ++------------------- rust/ares/src/newt.rs | 95 ++++++++++++++-------------------- rust/ares/src/noun.rs | 4 +- rust/ares/src/serf.rs | 93 +++++++++++++++++++++++++++++++++ rust/ares/test_data/baby.pill | Bin 0 -> 552 bytes 8 files changed, 247 insertions(+), 134 deletions(-) create mode 100644 hoon/scaffolding/baby.hoon create mode 100644 rust/ares/src/serf.rs create mode 100644 rust/ares/test_data/baby.pill diff --git a/hoon/scaffolding/baby.hoon b/hoon/scaffolding/baby.hoon new file mode 100644 index 0000000..787f736 --- /dev/null +++ b/hoon/scaffolding/baby.hoon @@ -0,0 +1,48 @@ +:: A trivial working pill which requires no jets +!. +=> ~ +=/ core + != + => ~ => + |% + +$ card (cask) + ++ cask |$ [a] (pair mark a) + +$ knot @ta + ++ list |$ [item] $@(~ [i=item t=(list item)]) + +$ mark @tas + +$ ovum [=wire =card] + ++ pair |$ [head tail] [p=head q=tail] + +$ path (list knot) + +$ wire path + -- => + :: + |% + ++ load !! + ++ peek _~ + ++ wish !! + ++ poke + |= [now=@da ovo=ovum] + ^- ^ + :: ~> %slog.[0 'got'] + :: ~> %slog.[0 -.card.ovo] + =/ fec [//term/1 %blit [%put "effect"] [%nel ~] ~] + [[fec ~] ..poke] + -- + :: + |= [now=@da ovo=ovum] + ^- * + .(+> +:(poke now ovo)) +:: +|% +++ aeon + ^- * + => *[arvo=* epic=*] + != + |- ^- * + ?@ epic arvo + %= $ + epic +.epic + arvo .*([arvo -.epic] [%9 2 %10 [6 %0 3] %0 2]) + == +-- +[%pill %baby [aeon .*(0 core) ~] ~ ~] diff --git a/rust/ares/src/interpreter.rs b/rust/ares/src/interpreter.rs index 1b62935..9f5ece1 100644 --- a/rust/ares/src/interpreter.rs +++ b/rust/ares/src/interpreter.rs @@ -2,6 +2,7 @@ use self::NockWork::*; use crate::jets; use crate::mem::unifying_equality; use crate::mem::NockStack; +use crate::newt::Newt; use crate::noun::{Atom, Cell, DirectAtom, IndirectAtom, Noun}; use ares_macros::tas; use bitvec::prelude::{BitSlice, Lsb0}; @@ -64,7 +65,12 @@ fn noun_to_work(noun: Noun) -> NockWork { } } -pub fn interpret(stack: &mut NockStack, mut subject: Noun, formula: Noun) -> Noun { +pub fn interpret( + stack: &mut NockStack, + newt: &mut Option<&mut Newt>, + mut subject: Noun, + formula: Noun, +) -> Noun { let mut res = unsafe { DirectAtom::new_unchecked(0).as_atom().as_noun() }; stack.push(1); unsafe { @@ -314,30 +320,27 @@ pub fn interpret(stack: &mut NockStack, mut subject: Noun, formula: Noun) -> Nou Nock11ComputeHint => unsafe { let hint = *stack.local_noun_pointer(1); if let Ok(hint_cell) = hint.as_cell() { - // match %sham hints, which are scaffolding until we have a real jet dashboard - if hint_cell - .head() - .raw_equals(DirectAtom::new_unchecked(tas!(b"sham")).as_noun()) - { - if let Ok(jet_formula) = hint_cell.tail().as_cell() { - let jet_name = jet_formula.tail(); - if let Ok(jet) = jets::get_jet(jet_name) { - res = jet(stack, subject); - stack.pop(&mut res); - continue; - } - } + if let Ok(found) = match_pre_hint(stack, subject, hint_cell) { + res = found; + stack.pop(&mut res); + } else { + *(stack.local_noun_pointer(0)) = work_to_noun(Nock11ComputeResult); + push_formula(stack, hint_cell.tail()); } - *(stack.local_noun_pointer(0)) = work_to_noun(Nock11ComputeResult); - push_formula(stack, hint_cell.tail()); } else { panic!("IMPOSSIBLE: tried to compute a dynamic hint but hint is an atom"); } }, Nock11ComputeResult => unsafe { - *(stack.local_noun_pointer(0)) = work_to_noun(Nock11Done); - let formula = *stack.local_noun_pointer(2); - push_formula(stack, formula); + let hint = *stack.local_noun_pointer(1); + if let Ok(found) = match_post_hint(stack, newt, subject, hint, res) { + res = found; + stack.pop(&mut res); + } else { + *(stack.local_noun_pointer(0)) = work_to_noun(Nock11Done); + let formula = *stack.local_noun_pointer(2); + push_formula(stack, formula); + } }, Nock11Done => { stack.pop(&mut res); @@ -517,7 +520,7 @@ fn push_formula(stack: &mut NockStack, formula: Noun) { } } } else { - panic!("Bad formula: atoms are not formulas"); + panic!("Bad formula: atoms are not formulas: {:?}", formula); } } @@ -619,3 +622,40 @@ fn inc(stack: &mut NockStack, atom: Atom) -> Atom { } } } + +fn match_pre_hint(stack: &mut NockStack, subject: Noun, cell: Cell) -> Result { + let direct = cell.head().as_direct()?; + match direct.data() { + // %sham hints are scaffolding until we have a real jet dashboard + tas!(b"sham") => { + let jet_formula = cell.tail().as_cell()?; + let jet = jets::get_jet(jet_formula.tail())?; + return Ok(jet(stack, subject)); + } + _ => Err(()), + } +} + +fn match_post_hint( + stack: &mut NockStack, + newt: &mut Option<&mut Newt>, + _subject: Noun, + hint: Noun, + res: Noun, +) -> Result { + let direct = hint.as_cell()?.head().as_direct()?; + match direct.data() { + tas!(b"slog") => { + let slog_cell = res.as_cell()?; + let pri = slog_cell.head().as_direct()?.data(); + let tank = slog_cell.tail(); + if let Some(not) = newt { + not.slog(stack, pri, tank); + } else { + println!("slog: {:?} {:?}", pri, tank); + } + Err(()) + } + _ => Err(()), + } +} diff --git a/rust/ares/src/lib.rs b/rust/ares/src/lib.rs index 7608bd8..cf4b37c 100644 --- a/rust/ares/src/lib.rs +++ b/rust/ares/src/lib.rs @@ -6,6 +6,7 @@ pub mod mem; pub mod mug; pub mod newt; pub mod noun; +pub mod serf; pub mod serialization; #[cfg(test)] diff --git a/rust/ares/src/main.rs b/rust/ares/src/main.rs index 9b39273..fa8cafb 100644 --- a/rust/ares/src/main.rs +++ b/rust/ares/src/main.rs @@ -1,9 +1,8 @@ -use ares::interpreter::{interpret, raw_slot}; +use ares::interpreter::interpret; use ares::mem::NockStack; -use ares::newt::Newt; -use ares::noun::{IndirectAtom, D}; +use ares::noun::IndirectAtom; +use ares::serf::serf; use ares::serialization::{cue, jam}; -use ares_macros::tas; use memmap::Mmap; use memmap::MmapMut; use std::env; @@ -37,7 +36,7 @@ fn main() -> io::Result<()> { let input_cell = input .as_cell() .expect("Input must be jam of subject/formula pair"); - let result = interpret(&mut stack, input_cell.head(), input_cell.tail()); + let result = interpret(&mut stack, &mut None, input_cell.head(), input_cell.tail()); if let Ok(atom) = result.as_atom() { println!("Result: {:?}", atom); } @@ -59,54 +58,3 @@ fn main() -> io::Result<()> { }; Ok(()) } - -/** - * This is suitable for talking to the king process. To test, change the arg_c[0] line in - * u3_lord_init in vere to point at this binary and start vere like normal. - */ -fn serf() -> io::Result<()> { - let mut newt = Newt::new(); - newt.ripe(0, 0); - - let mut eve = 0; - - // Can't use for loop because it borrows newt - loop { - let writ = if let Some(writ) = newt.next() { - writ - } else { - break; - }; - - let tag = raw_slot(writ, 2).as_direct().unwrap(); - match tag.data() { - tas!(b"live") => { - newt.live(); - } - tas!(b"peek") => { - newt.peek_done(D(0)); - } - tas!(b"play") => { - eve = raw_slot(writ, 6).as_direct().unwrap().data(); - let mut lit = raw_slot(writ, 7); - loop { - if let Ok(cell) = lit.as_cell() { - eve += 1; - lit = cell.tail(); - } else { - break; - } - } - newt.play_done(0); - } - tas!(b"work") => { - newt.flog(D(tas!(b"working"))); - newt.work_done(eve, 0, D(0)); - eve += 1; - } - _ => panic!("got message with unknown tag {:?}", tag), - }; - } - - Ok(()) -} diff --git a/rust/ares/src/newt.rs b/rust/ares/src/newt.rs index db4cedf..c590830 100644 --- a/rust/ares/src/newt.rs +++ b/rust/ares/src/newt.rs @@ -60,7 +60,6 @@ use std::os::unix::prelude::FromRawFd; use std::ptr::{copy_nonoverlapping, write_bytes}; pub struct Newt { - stack: NockStack, input: std::fs::File, output: std::fs::File, } @@ -68,7 +67,6 @@ pub struct Newt { impl Newt { pub fn new() -> Newt { Newt { - stack: NockStack::new(8 << 10 << 10, 0), input: unsafe { std::fs::File::from_raw_fd(0) }, output: unsafe { std::fs::File::from_raw_fd(1) }, } @@ -79,8 +77,8 @@ impl Newt { * NB: we write 64-bit words, while vere writes bytes. The extra zero bytes shouldn't be a * problem. */ - fn write_noun(&mut self, noun: Noun) { - let atom = jam(&mut self.stack, noun); + fn write_noun(&mut self, stack: &mut NockStack, noun: Noun) { + let atom = jam(stack, noun); let size = atom.size() << 3; let mut buf = vec![0 as u8; size + 5]; buf[1] = size as u8; @@ -108,9 +106,9 @@ impl Newt { } /** Send %ripe, the first event. */ - pub fn ripe(&mut self, eve: u64, mug: u64) { + pub fn ripe(&mut self, stack: &mut NockStack, eve: u64, mug: u64) { let version = Cell::new_tuple( - &mut self.stack, + stack, &[ D(1), // newt protocol D(139), // hoon kelvin @@ -118,99 +116,84 @@ impl Newt { ], ) .as_noun(); - let ripe = Cell::new_tuple( - &mut self.stack, - &[D(tas!(b"ripe")), version, D(eve), D(mug)], - ) - .as_noun(); - self.write_noun(ripe); + let ripe = Cell::new_tuple(stack, &[D(tas!(b"ripe")), version, D(eve), D(mug)]).as_noun(); + self.write_noun(stack, ripe); } /** Send %live, acknowledging. */ - pub fn live(&mut self) { - let live = Cell::new_tuple(&mut self.stack, &[D(tas!(b"live")), D(0)]).as_noun(); - self.write_noun(live); + pub fn live(&mut self, stack: &mut NockStack) { + let live = Cell::new_tuple(stack, &[D(tas!(b"live")), D(0)]).as_noun(); + self.write_noun(stack, live); } /** Send %slog, pretty-printed debug output. */ - pub fn slog(&mut self, pri: u64, tank: Noun) { - let slog = Cell::new_tuple(&mut self.stack, &[D(tas!(b"slog")), D(pri), tank]).as_noun(); - self.write_noun(slog); + pub fn slog(&mut self, stack: &mut NockStack, pri: u64, tank: Noun) { + let slog = Cell::new_tuple(stack, &[D(tas!(b"slog")), D(pri), tank]).as_noun(); + self.write_noun(stack, slog); } /** Send %flog, raw debug output. */ - pub fn flog(&mut self, cord: Noun) { - let flog = Cell::new_tuple(&mut self.stack, &[D(tas!(b"flog")), cord]).as_noun(); - self.write_noun(flog); + pub fn flog(&mut self, stack: &mut NockStack, cord: Noun) { + let flog = Cell::new_tuple(stack, &[D(tas!(b"flog")), cord]).as_noun(); + self.write_noun(stack, flog); } /** Send %peek %done, successfully scried. */ - pub fn peek_done(&mut self, dat: Noun) { - let peek = - Cell::new_tuple(&mut self.stack, &[D(tas!(b"peek")), D(tas!(b"done")), dat]).as_noun(); - self.write_noun(peek); + pub fn peek_done(&mut self, stack: &mut NockStack, dat: Noun) { + let peek = Cell::new_tuple(stack, &[D(tas!(b"peek")), D(tas!(b"done")), dat]).as_noun(); + self.write_noun(stack, peek); } /** Send %peek %bail, unsuccessfully scried. */ - pub fn peek_bail(&mut self, dud: Noun) { - let peek = - Cell::new_tuple(&mut self.stack, &[D(tas!(b"peek")), D(tas!(b"bail")), dud]).as_noun(); - self.write_noun(peek); + pub fn peek_bail(&mut self, stack: &mut NockStack, dud: Noun) { + let peek = Cell::new_tuple(stack, &[D(tas!(b"peek")), D(tas!(b"bail")), dud]).as_noun(); + self.write_noun(stack, peek); } /** Send %play %done, successfully replayed events. */ - pub fn play_done(&mut self, mug: u64) { - let play = Cell::new_tuple( - &mut self.stack, - &[D(tas!(b"play")), D(tas!(b"done")), D(mug)], - ) - .as_noun(); - self.write_noun(play); + pub fn play_done(&mut self, stack: &mut NockStack, mug: u64) { + let play = Cell::new_tuple(stack, &[D(tas!(b"play")), D(tas!(b"done")), D(mug)]).as_noun(); + self.write_noun(stack, play); } /** Send %play %bail, failed to replay events. */ - pub fn play_bail(&mut self, eve: u64, mug: u64, dud: Noun) { + pub fn play_bail(&mut self, stack: &mut NockStack, eve: u64, mug: u64, dud: Noun) { let play = Cell::new_tuple( - &mut self.stack, + stack, &[D(tas!(b"play")), D(tas!(b"bail")), D(eve), D(mug), dud], ) .as_noun(); - self.write_noun(play); + self.write_noun(stack, play); } /** Send %work %done, successfully ran event. */ - pub fn work_done(&mut self, eve: u64, mug: u64, fec: Noun) { + pub fn work_done(&mut self, stack: &mut NockStack, eve: u64, mug: u64, fec: Noun) { let work = Cell::new_tuple( - &mut self.stack, + stack, &[D(tas!(b"work")), D(tas!(b"done")), D(eve), D(mug), fec], ) .as_noun(); - self.write_noun(work); + self.write_noun(stack, work); } /** Send %work %swap, successfully replaced failed event. */ - pub fn work_swap(&mut self, eve: u64, mug: u64, job: Noun, fec: Noun) { + pub fn work_swap(&mut self, stack: &mut NockStack, eve: u64, mug: u64, job: Noun, fec: Noun) { let work = Cell::new_tuple( - &mut self.stack, + stack, &[D(tas!(b"work")), D(tas!(b"swap")), D(eve), D(mug), job, fec], ) .as_noun(); - self.write_noun(work); + self.write_noun(stack, work); } /** Send %work %bail, failed to run event. */ - pub fn work_bail(&mut self, lud: Noun) { - let work = - Cell::new_tuple(&mut self.stack, &[D(tas!(b"work")), D(tas!(b"bail")), lud]).as_noun(); - self.write_noun(work); + pub fn work_bail(&mut self, stack: &mut NockStack, lud: Noun) { + let work = Cell::new_tuple(stack, &[D(tas!(b"work")), D(tas!(b"bail")), lud]).as_noun(); + self.write_noun(stack, work); } -} - -impl Iterator for Newt { - type Item = Noun; /** Fetch next message. */ - fn next(&mut self) -> Option { + pub fn next(&mut self, stack: &mut NockStack) -> Option { let mut header: Vec = Vec::with_capacity(5); header.resize(5, 0); if let Err(err) = self.input.read_exact(&mut header) { @@ -237,12 +220,12 @@ impl Iterator for Newt { let atom = unsafe { let word_len = (byte_len + 7) >> 3; - let (mut atom, dest) = IndirectAtom::new_raw_mut(&mut self.stack, word_len as usize); + let (mut atom, dest) = IndirectAtom::new_raw_mut(stack, word_len as usize); write_bytes(dest.add(word_len as usize - 1), 0, 8); copy_nonoverlapping(body.as_ptr(), dest as *mut u8, byte_len); atom.normalize_as_atom() }; - Some(cue(&mut self.stack, atom)) + Some(cue(stack, atom)) } } diff --git a/rust/ares/src/noun.rs b/rust/ares/src/noun.rs index abea7d8..a0e7f34 100644 --- a/rust/ares/src/noun.rs +++ b/rust/ares/src/noun.rs @@ -398,8 +398,8 @@ impl Cell { } let len = tup.len(); - let mut cell = Cell::new(allocator, tup[len-2], tup[len-1]); - for i in (0..len-2).rev() { + let mut cell = Cell::new(allocator, tup[len - 2], tup[len - 1]); + for i in (0..len - 2).rev() { cell = Cell::new(allocator, tup[i], cell.as_noun()); } cell diff --git a/rust/ares/src/serf.rs b/rust/ares/src/serf.rs new file mode 100644 index 0000000..f84438b --- /dev/null +++ b/rust/ares/src/serf.rs @@ -0,0 +1,93 @@ +use crate::interpreter::{interpret, raw_slot}; +use crate::mem::NockStack; +use crate::newt::Newt; +use crate::noun::{Cell, Noun, D}; +use ares_macros::tas; +use std::io; + +#[allow(dead_code)] +const LOAD_AXIS: u64 = 4; +const PEEK_AXIS: u64 = 22; +const POKE_AXIS: u64 = 23; +#[allow(dead_code)] +const WISH_AXIS: u64 = 10; + +/** + * This is suitable for talking to the king process. To test, change the arg_c[0] line in + * u3_lord_init in vere to point at this binary and start vere like normal. + */ +pub fn serf() -> io::Result<()> { + let mut stack = NockStack::new(8 << 10 << 10, 0); + let mut newt = Newt::new(); + newt.ripe(&mut stack, 0, 0); + + let mut eve = 0; + let mut cor = D(0); + + // Can't use for loop because it borrows newt + loop { + let writ = if let Some(writ) = newt.next(&mut stack) { + writ + } else { + break; + }; + + let tag = raw_slot(writ, 2).as_direct().unwrap(); + match tag.data() { + tas!(b"live") => { + newt.live(&mut stack); + } + tas!(b"peek") => { + let sam = raw_slot(writ, 7); + let res = slam(&mut stack, &mut newt, cor, PEEK_AXIS, sam); + newt.peek_done(&mut stack, res); + } + tas!(b"play") => { + if eve == 0 { + // apply lifecycle to first batch + let lit = raw_slot(writ, 7); + let sub = Cell::new_tuple(&mut stack, &[D(0), D(3)]).as_noun(); + let lyf = Cell::new_tuple(&mut stack, &[D(2), sub, D(0), D(2)]).as_noun(); + let gat = interpret(&mut stack, &mut Some(&mut newt), lit, lyf); + cor = raw_slot(gat, 7); + } else { + panic!("partial replay not implemented"); + } + eve = raw_slot(writ, 6).as_direct().unwrap().data(); + let mut lit = raw_slot(writ, 7); + loop { + if let Ok(cell) = lit.as_cell() { + eve += 1; + lit = cell.tail(); + } else { + break; + } + } + newt.play_done(&mut stack, 0); + } + tas!(b"work") => { + let ovo = raw_slot(writ, 7); + let res = slam(&mut stack, &mut newt, cor, POKE_AXIS, ovo) + .as_cell() + .unwrap(); + let fec = res.head(); + cor = res.tail(); + + eve += 1; + + newt.work_done(&mut stack, eve - 1, 0, fec); + } + _ => panic!("got message with unknown tag {:?}", tag), + }; + } + + Ok(()) +} + +pub fn slam(stack: &mut NockStack, newt: &mut Newt, cor: Noun, axis: u64, ovo: Noun) -> Noun { + let pul = Cell::new_tuple(stack, &[D(9), D(axis), D(0), D(2)]).as_noun(); + let sam = Cell::new_tuple(stack, &[D(6), D(0), D(7)]).as_noun(); + let fol = Cell::new_tuple(stack, &[D(8), pul, D(9), D(2), D(10), sam, D(0), D(2)]).as_noun(); + let sub = Cell::new_tuple(stack, &[cor, ovo]).as_noun(); + interpret(stack, &mut Some(newt), sub, fol) +} diff --git a/rust/ares/test_data/baby.pill b/rust/ares/test_data/baby.pill new file mode 100644 index 0000000000000000000000000000000000000000..10079a63f54242cc6b4ba2aab0fb28988549a3ee GIT binary patch literal 552 zcmZRS*X!+-{c!Zqkx!y5-G}wwIH}he9-nr@(I`bgw?r|U<5o+BjEXbc#Y282O2VH` zIvPDOsL<{ZtNX!vuqkDObxXYgl0}5FW7cep~7+jXp zde+vpQ{jfds-un@K1e$iaa>i1*und`Kw`b~1-@o;4l(|vnS}ytEST+LXU14(LY~+0i8c>6L^|fPd>Px_<`-@1jj8K4(HEU>1gx==(NKXypt50GZOj17TenM+~aCG z>U=athK0MS^}fKQhvF*DHxobbF<0z3$p7_^K?Qdc@4_gc?CuTRwk&T2=J7gOhn#T+ zX>3aP7@#Z8evk{S(a~r}xx^lDpuJgGCvz+t;*3cLUl>$0r<_oTy5VeV*>ip2JbAYF z6Mh37$zj%#RS0zQEs#)tp@5DDTWR|Q9kj+yPIA1esM20((Q`fVgBVNpiF>k} z%DbBrR73n$#LBQ}b4&}U^DyCUQr#kNl-yob2lU$mU5KGN9Ab6O9&9HK+7DXac>tDk z+@M&!-2y88rY+_aSCi@){?29KxG>@nQ)#YmpCFU>!8tAGM(b^XRn8a8T3!Vhf`g#_ J;A-ciJOCK+ Date: Sat, 11 Feb 2023 23:04:39 -0700 Subject: [PATCH 04/10] [ares] add simplest possible checkpoints These checkpoints are just a jam file of your state, with the event number and a checksum. If you crash while writing the checkpoint, it will be corrupt. Obviously, these snapshots will be slow to write on a real ship as well. --- rust/ares/src/serf.rs | 138 +++++++++++++++++++++++++++++++++++------- 1 file changed, 116 insertions(+), 22 deletions(-) diff --git a/rust/ares/src/serf.rs b/rust/ares/src/serf.rs index f84438b..d059695 100644 --- a/rust/ares/src/serf.rs +++ b/rust/ares/src/serf.rs @@ -1,9 +1,18 @@ use crate::interpreter::{interpret, raw_slot}; use crate::mem::NockStack; +use crate::mug::{mug, mug_u32}; use crate::newt::Newt; -use crate::noun::{Cell, Noun, D}; +use crate::noun::{Cell, IndirectAtom, Noun, D}; +use crate::serialization::{cue, jam}; use ares_macros::tas; +use memmap::Mmap; +use memmap::MmapMut; +use std::fs::{create_dir_all, File, OpenOptions}; use std::io; +use std::mem; +use std::path::PathBuf; +use std::ptr::copy_nonoverlapping; +use std::ptr::write_bytes; #[allow(dead_code)] const LOAD_AXIS: u64 = 4; @@ -17,16 +26,26 @@ const WISH_AXIS: u64 = 10; * u3_lord_init in vere to point at this binary and start vere like normal. */ pub fn serf() -> io::Result<()> { - let mut stack = NockStack::new(8 << 10 << 10, 0); - let mut newt = Newt::new(); - newt.ripe(&mut stack, 0, 0); + let pier_path_string = std::env::args() + .nth(2) + .ok_or(io::Error::new(io::ErrorKind::Other, "no pier path"))?; + let mut pier_path = PathBuf::from(pier_path_string); + pier_path.push(".urb"); + pier_path.push("chk"); + create_dir_all(&pier_path)?; + pier_path.push("snapshot.jam"); - let mut eve = 0; - let mut cor = D(0); + let ref mut stack = NockStack::new(8 << 10 << 10, 0); + let ref mut newt = Newt::new(); + + let (mut eve, mut cor) = load(stack, &pier_path).unwrap_or((0, D(0))); + let mug = mug_u32(stack, cor); + + newt.ripe(stack, eve, mug as u64); // Can't use for loop because it borrows newt loop { - let writ = if let Some(writ) = newt.next(&mut stack) { + let writ = if let Some(writ) = newt.next(stack) { writ } else { break; @@ -35,47 +54,67 @@ pub fn serf() -> io::Result<()> { let tag = raw_slot(writ, 2).as_direct().unwrap(); match tag.data() { tas!(b"live") => { - newt.live(&mut stack); + let inner = raw_slot(writ, 6); + match inner.as_direct().unwrap().data() { + tas!(b"cram") => eprintln!("cram"), + tas!(b"exit") => eprintln!("exit"), + tas!(b"save") => { + // XX what is eve for? + eprintln!("save"); + save(stack, &pier_path, eve, cor); + } + tas!(b"meld") => eprintln!("meld"), + tas!(b"pack") => eprintln!("pack"), + _ => eprintln!("unknown live"), + } + newt.live(stack); } tas!(b"peek") => { let sam = raw_slot(writ, 7); - let res = slam(&mut stack, &mut newt, cor, PEEK_AXIS, sam); - newt.peek_done(&mut stack, res); + let res = slam(stack, newt, cor, PEEK_AXIS, sam); + newt.peek_done(stack, res); } tas!(b"play") => { - if eve == 0 { + let run = if eve == 0 { // apply lifecycle to first batch let lit = raw_slot(writ, 7); - let sub = Cell::new_tuple(&mut stack, &[D(0), D(3)]).as_noun(); - let lyf = Cell::new_tuple(&mut stack, &[D(2), sub, D(0), D(2)]).as_noun(); - let gat = interpret(&mut stack, &mut Some(&mut newt), lit, lyf); + let sub = Cell::new_tuple(stack, &[D(0), D(3)]).as_noun(); + let lyf = Cell::new_tuple(stack, &[D(2), sub, D(0), D(2)]).as_noun(); + let gat = interpret(stack, &mut Some(newt), lit, lyf); cor = raw_slot(gat, 7); + false } else { - panic!("partial replay not implemented"); - } - eve = raw_slot(writ, 6).as_direct().unwrap().data(); + true + }; + + // do we need to assert something here? + // eve = raw_slot(writ, 6).as_direct().unwrap().data(); + let mut lit = raw_slot(writ, 7); loop { if let Ok(cell) = lit.as_cell() { + if run { + let ovo = cell.head(); + let res = slam(stack, newt, cor, POKE_AXIS, ovo).as_cell().unwrap(); + cor = res.tail(); + } eve += 1; lit = cell.tail(); } else { break; } } - newt.play_done(&mut stack, 0); + newt.play_done(stack, 0); } tas!(b"work") => { let ovo = raw_slot(writ, 7); - let res = slam(&mut stack, &mut newt, cor, POKE_AXIS, ovo) - .as_cell() - .unwrap(); + let res = slam(stack, newt, cor, POKE_AXIS, ovo).as_cell().unwrap(); let fec = res.head(); cor = res.tail(); eve += 1; - newt.work_done(&mut stack, eve - 1, 0, fec); + newt.work_done(stack, eve, 0, fec); } _ => panic!("got message with unknown tag {:?}", tag), }; @@ -91,3 +130,58 @@ pub fn slam(stack: &mut NockStack, newt: &mut Newt, cor: Noun, axis: u64, ovo: N let sub = Cell::new_tuple(stack, &[cor, ovo]).as_noun(); interpret(stack, &mut Some(newt), sub, fol) } + +fn save(stack: &mut NockStack, snap_path: &PathBuf, eve: u64, cor: Noun) { + let state = Cell::new(stack, D(eve), cor).as_noun(); + let mugged = mug(stack, state).as_noun(); + let snapshot = Cell::new(stack, mugged, state).as_noun(); + + let jammed = jam(stack, snapshot); + let f_out = OpenOptions::new() + .read(true) + .write(true) + .create(true) + .open(snap_path) + .unwrap(); + + f_out.set_len((jammed.size() << 3) as u64).unwrap(); + unsafe { + let mut out_map = MmapMut::map_mut(&f_out).unwrap(); + copy_nonoverlapping( + jammed.data_pointer() as *mut u8, + out_map.as_mut_ptr(), + jammed.size() << 3, + ); + out_map.flush().unwrap(); + }; +} + +fn load(stack: &mut NockStack, snap_path: &PathBuf) -> io::Result<(u64, Noun)> { + let f_in = File::open(snap_path)?; + + let in_len = f_in.metadata().unwrap().len(); + let jammed = unsafe { + let in_map = Mmap::map(&f_in).unwrap(); + let word_len = (in_len + 7) >> 3; + let (mut atom, dest) = IndirectAtom::new_raw_mut(stack, word_len as usize); + write_bytes(dest.add(word_len as usize - 1), 0, 8); + copy_nonoverlapping(in_map.as_ptr(), dest as *mut u8, in_len as usize); + mem::drop(in_map); + atom.normalize_as_atom() + }; + + let snapshot = cue(stack, jammed).as_cell().unwrap(); + let state = snapshot.tail().as_cell().unwrap(); + assert!( + unsafe { + mug(stack, state.as_noun()) + .as_noun() + .raw_equals(snapshot.head()) + }, + "snapshot is corrupt" + ); + let eve = state.head().as_direct().unwrap().data(); + let cor = state.tail(); + + Ok((eve, cor)) +} From b9abb0efe89a8eef2f9b3eccceefd1b36941b310 Mon Sep 17 00:00:00 2001 From: Philip Monk Date: Sun, 12 Feb 2023 14:55:05 -0700 Subject: [PATCH 05/10] [ares] extend jam perstistence to use two snapshots --- rust/ares/src/serf.rs | 128 ++++++++++++++++++++++++++++-------------- 1 file changed, 85 insertions(+), 43 deletions(-) diff --git a/rust/ares/src/serf.rs b/rust/ares/src/serf.rs index d059695..72b1d60 100644 --- a/rust/ares/src/serf.rs +++ b/rust/ares/src/serf.rs @@ -26,22 +26,24 @@ const WISH_AXIS: u64 = 10; * u3_lord_init in vere to point at this binary and start vere like normal. */ pub fn serf() -> io::Result<()> { - let pier_path_string = std::env::args() + let snap_path_string = std::env::args() .nth(2) .ok_or(io::Error::new(io::ErrorKind::Other, "no pier path"))?; - let mut pier_path = PathBuf::from(pier_path_string); - pier_path.push(".urb"); - pier_path.push("chk"); - create_dir_all(&pier_path)?; - pier_path.push("snapshot.jam"); + let mut snap_path = PathBuf::from(snap_path_string); + snap_path.push(".urb"); + snap_path.push("chk"); + create_dir_all(&snap_path)?; let ref mut stack = NockStack::new(8 << 10 << 10, 0); let ref mut newt = Newt::new(); + let mut snap_number; // Last valid snapshot number. + let mut event_number; + let mut arvo; - let (mut eve, mut cor) = load(stack, &pier_path).unwrap_or((0, D(0))); - let mug = mug_u32(stack, cor); + (event_number, arvo, snap_number) = load(stack, snap_path.clone()).unwrap_or((0, D(0), 0)); + let mug = mug_u32(stack, arvo); - newt.ripe(stack, eve, mug as u64); + newt.ripe(stack, event_number, mug as u64); // Can't use for loop because it borrows newt loop { @@ -61,7 +63,8 @@ pub fn serf() -> io::Result<()> { tas!(b"save") => { // XX what is eve for? eprintln!("save"); - save(stack, &pier_path, eve, cor); + snap_number = if snap_number == 0 { 1 } else { 0 }; + save(stack, snap_path.clone(), snap_number, event_number, arvo); } tas!(b"meld") => eprintln!("meld"), tas!(b"pack") => eprintln!("pack"), @@ -71,34 +74,34 @@ pub fn serf() -> io::Result<()> { } tas!(b"peek") => { let sam = raw_slot(writ, 7); - let res = slam(stack, newt, cor, PEEK_AXIS, sam); + let res = slam(stack, newt, arvo, PEEK_AXIS, sam); newt.peek_done(stack, res); } tas!(b"play") => { - let run = if eve == 0 { + let run = if event_number == 0 { // apply lifecycle to first batch let lit = raw_slot(writ, 7); let sub = Cell::new_tuple(stack, &[D(0), D(3)]).as_noun(); let lyf = Cell::new_tuple(stack, &[D(2), sub, D(0), D(2)]).as_noun(); let gat = interpret(stack, &mut Some(newt), lit, lyf); - cor = raw_slot(gat, 7); + arvo = raw_slot(gat, 7); false } else { true }; // do we need to assert something here? - // eve = raw_slot(writ, 6).as_direct().unwrap().data(); + // event_number = raw_slot(writ, 6).as_direct().unwrap().data(); let mut lit = raw_slot(writ, 7); loop { if let Ok(cell) = lit.as_cell() { if run { let ovo = cell.head(); - let res = slam(stack, newt, cor, POKE_AXIS, ovo).as_cell().unwrap(); - cor = res.tail(); + let res = slam(stack, newt, arvo, POKE_AXIS, ovo).as_cell().unwrap(); + arvo = res.tail(); } - eve += 1; + event_number += 1; lit = cell.tail(); } else { break; @@ -108,13 +111,13 @@ pub fn serf() -> io::Result<()> { } tas!(b"work") => { let ovo = raw_slot(writ, 7); - let res = slam(stack, newt, cor, POKE_AXIS, ovo).as_cell().unwrap(); + let res = slam(stack, newt, arvo, POKE_AXIS, ovo).as_cell().unwrap(); let fec = res.head(); - cor = res.tail(); + arvo = res.tail(); - eve += 1; + event_number += 1; - newt.work_done(stack, eve, 0, fec); + newt.work_done(stack, event_number, 0, fec); } _ => panic!("got message with unknown tag {:?}", tag), }; @@ -123,30 +126,38 @@ pub fn serf() -> io::Result<()> { Ok(()) } -pub fn slam(stack: &mut NockStack, newt: &mut Newt, cor: Noun, axis: u64, ovo: Noun) -> Noun { +pub fn slam(stack: &mut NockStack, newt: &mut Newt, arvo: Noun, axis: u64, ovo: Noun) -> Noun { let pul = Cell::new_tuple(stack, &[D(9), D(axis), D(0), D(2)]).as_noun(); let sam = Cell::new_tuple(stack, &[D(6), D(0), D(7)]).as_noun(); let fol = Cell::new_tuple(stack, &[D(8), pul, D(9), D(2), D(10), sam, D(0), D(2)]).as_noun(); - let sub = Cell::new_tuple(stack, &[cor, ovo]).as_noun(); + let sub = Cell::new_tuple(stack, &[arvo, ovo]).as_noun(); interpret(stack, &mut Some(newt), sub, fol) } -fn save(stack: &mut NockStack, snap_path: &PathBuf, eve: u64, cor: Noun) { - let state = Cell::new(stack, D(eve), cor).as_noun(); +fn save( + stack: &mut NockStack, + mut snap_path: PathBuf, + snap_number: u8, + event_number: u64, + arvo: Noun, +) { + snap_path.push(format!("snapshot-{}.snap", snap_number)); + + let state = Cell::new(stack, D(event_number), arvo).as_noun(); let mugged = mug(stack, state).as_noun(); let snapshot = Cell::new(stack, mugged, state).as_noun(); let jammed = jam(stack, snapshot); - let f_out = OpenOptions::new() + let f = OpenOptions::new() .read(true) .write(true) .create(true) .open(snap_path) .unwrap(); - f_out.set_len((jammed.size() << 3) as u64).unwrap(); + f.set_len((jammed.size() << 3) as u64).unwrap(); unsafe { - let mut out_map = MmapMut::map_mut(&f_out).unwrap(); + let mut out_map = MmapMut::map_mut(&f).unwrap(); copy_nonoverlapping( jammed.data_pointer() as *mut u8, out_map.as_mut_ptr(), @@ -156,12 +167,41 @@ fn save(stack: &mut NockStack, snap_path: &PathBuf, eve: u64, cor: Noun) { }; } -fn load(stack: &mut NockStack, snap_path: &PathBuf) -> io::Result<(u64, Noun)> { - let f_in = File::open(snap_path)?; +fn load(stack: &mut NockStack, snap_path: PathBuf) -> io::Result<(u64, Noun, u8)> { + let res0 = load_snapshot(stack, snap_path.clone(), 0); + let res1 = load_snapshot(stack, snap_path.clone(), 1); - let in_len = f_in.metadata().unwrap().len(); + match (res0, res1) { + (Ok((event_number_0, arvo_0)), Ok((event_number_1, arvo_1))) => { + if event_number_0 > event_number_1 { + Ok((event_number_0, arvo_0, 0)) + } else { + Ok((event_number_1, arvo_1, 1)) + } + } + (Ok((event_number_0, arvo_0)), Err(_)) => Ok((event_number_0, arvo_0, 0)), + (Err(_), Ok((event_number_1, arvo_1))) => Ok((event_number_1, arvo_1, 1)), + (Err(_), Err(_)) => Err(io::Error::new( + io::ErrorKind::NotFound, + "no valid snapshot found", + )), + } +} + +fn load_snapshot( + stack: &mut NockStack, + mut snap_path: PathBuf, + number: u8, +) -> io::Result<(u64, Noun)> { + snap_path.push(format!("snapshot-{}.snap", number)); + + eprintln!("\rload: snapshot at {:?}", snap_path); + + let f = File::open(snap_path)?; + + let in_len = f.metadata().unwrap().len(); let jammed = unsafe { - let in_map = Mmap::map(&f_in).unwrap(); + let in_map = Mmap::map(&f).unwrap(); let word_len = (in_len + 7) >> 3; let (mut atom, dest) = IndirectAtom::new_raw_mut(stack, word_len as usize); write_bytes(dest.add(word_len as usize - 1), 0, 8); @@ -172,16 +212,18 @@ fn load(stack: &mut NockStack, snap_path: &PathBuf) -> io::Result<(u64, Noun)> { let snapshot = cue(stack, jammed).as_cell().unwrap(); let state = snapshot.tail().as_cell().unwrap(); - assert!( - unsafe { - mug(stack, state.as_noun()) - .as_noun() - .raw_equals(snapshot.head()) - }, - "snapshot is corrupt" - ); - let eve = state.head().as_direct().unwrap().data(); - let cor = state.tail(); + if !unsafe { + snapshot + .head() + .raw_equals(mug(stack, state.as_noun()).as_noun()) + } { + return Err(io::Error::new( + io::ErrorKind::Other, + "snapshot checksum failed", + )) + } + let event_number = state.head().as_direct().unwrap().data(); + let arvo = state.tail(); - Ok((eve, cor)) + Ok((event_number, arvo)) } From f16c1940987aced530f6b0ea217bc2e4215ca8d9 Mon Sep 17 00:00:00 2001 From: Philip Monk Date: Sun, 12 Feb 2023 17:10:26 -0700 Subject: [PATCH 06/10] [ares] refactor snapshotting Especially, don't require cueing the snapshot to verify the checksum and get the event number. --- rust/ares/src/lib.rs | 1 + rust/ares/src/mem.rs | 8 +-- rust/ares/src/serf.rs | 109 ++--------------------------- rust/ares/src/snapshot.rs | 140 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 148 insertions(+), 110 deletions(-) create mode 100644 rust/ares/src/snapshot.rs diff --git a/rust/ares/src/lib.rs b/rust/ares/src/lib.rs index cf4b37c..8dfc53a 100644 --- a/rust/ares/src/lib.rs +++ b/rust/ares/src/lib.rs @@ -8,6 +8,7 @@ pub mod newt; pub mod noun; pub mod serf; pub mod serialization; +pub mod snapshot; #[cfg(test)] mod tests { diff --git a/rust/ares/src/mem.rs b/rust/ares/src/mem.rs index bb88e29..001672c 100644 --- a/rust/ares/src/mem.rs +++ b/rust/ares/src/mem.rs @@ -661,12 +661,8 @@ pub unsafe fn unifying_equality(stack: &mut NockStack, a: *mut Noun, b: *mut Nou return true; }; // If the nouns have cached mugs which are disequal we have nothing to do - if let (Ok(a_alloc), Ok(b_alloc)) = ( - (*a).as_allocated(), - (*b).as_allocated(), - ) { - if let (Some(a_mug), Some(b_mug)) = (a_alloc.get_cached_mug(), b_alloc.get_cached_mug()) - { + if let (Ok(a_alloc), Ok(b_alloc)) = ((*a).as_allocated(), (*b).as_allocated()) { + if let (Some(a_mug), Some(b_mug)) = (a_alloc.get_cached_mug(), b_alloc.get_cached_mug()) { if a_mug != b_mug { return false; }; diff --git a/rust/ares/src/serf.rs b/rust/ares/src/serf.rs index 72b1d60..844e695 100644 --- a/rust/ares/src/serf.rs +++ b/rust/ares/src/serf.rs @@ -1,18 +1,13 @@ use crate::interpreter::{interpret, raw_slot}; use crate::mem::NockStack; -use crate::mug::{mug, mug_u32}; +use crate::mug::mug_u32; use crate::newt::Newt; -use crate::noun::{Cell, IndirectAtom, Noun, D}; -use crate::serialization::{cue, jam}; +use crate::noun::{Cell, Noun, D}; +use crate::snapshot::{load, save}; use ares_macros::tas; -use memmap::Mmap; -use memmap::MmapMut; -use std::fs::{create_dir_all, File, OpenOptions}; +use std::fs::create_dir_all; use std::io; -use std::mem; use std::path::PathBuf; -use std::ptr::copy_nonoverlapping; -use std::ptr::write_bytes; #[allow(dead_code)] const LOAD_AXIS: u64 = 4; @@ -36,7 +31,7 @@ pub fn serf() -> io::Result<()> { let ref mut stack = NockStack::new(8 << 10 << 10, 0); let ref mut newt = Newt::new(); - let mut snap_number; // Last valid snapshot number. + let mut snap_number; // Last valid snapshot number. let mut event_number; let mut arvo; @@ -133,97 +128,3 @@ pub fn slam(stack: &mut NockStack, newt: &mut Newt, arvo: Noun, axis: u64, ovo: let sub = Cell::new_tuple(stack, &[arvo, ovo]).as_noun(); interpret(stack, &mut Some(newt), sub, fol) } - -fn save( - stack: &mut NockStack, - mut snap_path: PathBuf, - snap_number: u8, - event_number: u64, - arvo: Noun, -) { - snap_path.push(format!("snapshot-{}.snap", snap_number)); - - let state = Cell::new(stack, D(event_number), arvo).as_noun(); - let mugged = mug(stack, state).as_noun(); - let snapshot = Cell::new(stack, mugged, state).as_noun(); - - let jammed = jam(stack, snapshot); - let f = OpenOptions::new() - .read(true) - .write(true) - .create(true) - .open(snap_path) - .unwrap(); - - f.set_len((jammed.size() << 3) as u64).unwrap(); - unsafe { - let mut out_map = MmapMut::map_mut(&f).unwrap(); - copy_nonoverlapping( - jammed.data_pointer() as *mut u8, - out_map.as_mut_ptr(), - jammed.size() << 3, - ); - out_map.flush().unwrap(); - }; -} - -fn load(stack: &mut NockStack, snap_path: PathBuf) -> io::Result<(u64, Noun, u8)> { - let res0 = load_snapshot(stack, snap_path.clone(), 0); - let res1 = load_snapshot(stack, snap_path.clone(), 1); - - match (res0, res1) { - (Ok((event_number_0, arvo_0)), Ok((event_number_1, arvo_1))) => { - if event_number_0 > event_number_1 { - Ok((event_number_0, arvo_0, 0)) - } else { - Ok((event_number_1, arvo_1, 1)) - } - } - (Ok((event_number_0, arvo_0)), Err(_)) => Ok((event_number_0, arvo_0, 0)), - (Err(_), Ok((event_number_1, arvo_1))) => Ok((event_number_1, arvo_1, 1)), - (Err(_), Err(_)) => Err(io::Error::new( - io::ErrorKind::NotFound, - "no valid snapshot found", - )), - } -} - -fn load_snapshot( - stack: &mut NockStack, - mut snap_path: PathBuf, - number: u8, -) -> io::Result<(u64, Noun)> { - snap_path.push(format!("snapshot-{}.snap", number)); - - eprintln!("\rload: snapshot at {:?}", snap_path); - - let f = File::open(snap_path)?; - - let in_len = f.metadata().unwrap().len(); - let jammed = unsafe { - let in_map = Mmap::map(&f).unwrap(); - let word_len = (in_len + 7) >> 3; - let (mut atom, dest) = IndirectAtom::new_raw_mut(stack, word_len as usize); - write_bytes(dest.add(word_len as usize - 1), 0, 8); - copy_nonoverlapping(in_map.as_ptr(), dest as *mut u8, in_len as usize); - mem::drop(in_map); - atom.normalize_as_atom() - }; - - let snapshot = cue(stack, jammed).as_cell().unwrap(); - let state = snapshot.tail().as_cell().unwrap(); - if !unsafe { - snapshot - .head() - .raw_equals(mug(stack, state.as_noun()).as_noun()) - } { - return Err(io::Error::new( - io::ErrorKind::Other, - "snapshot checksum failed", - )) - } - let event_number = state.head().as_direct().unwrap().data(); - let arvo = state.tail(); - - Ok((event_number, arvo)) -} diff --git a/rust/ares/src/snapshot.rs b/rust/ares/src/snapshot.rs new file mode 100644 index 0000000..cbe1055 --- /dev/null +++ b/rust/ares/src/snapshot.rs @@ -0,0 +1,140 @@ +/** Jam-based snapshotting + * + * This is a simple checkpoint system that should be safe but has (very) poor performance. This is + * intended as a working placeholder until the real PMA is hooked up. + * + * This keeps two files, .urb/chk/snapshot-0.jam and .urbit/chk/snapshot-1.jam. Each of these + * contains 64 bits for a mug checksum, then 64 bits for the event number, then a jam of the state. + * We alternate between writing these two files, so that at least one is always valid. + * + * When we start up, we read both files and pick the one with the higher event number. If either + * is corrupted, we use the other. + */ +use crate::mem::NockStack; +use crate::mug::mug_u32; +use crate::noun::{IndirectAtom, Noun}; +use crate::serialization::{cue, jam}; +use either::Either; +use memmap::Mmap; +use memmap::MmapMut; +use std::fs::{File, OpenOptions}; +use std::io; +use std::mem; +use std::path::PathBuf; +use std::ptr::copy_nonoverlapping; +use std::ptr::write_bytes; + +pub fn save( + stack: &mut NockStack, + mut snap_path: PathBuf, + snap_number: u8, + event_number: u64, + arvo: Noun, +) { + snap_path.push(format!("snapshot-{}.snap", snap_number)); + + let jammed_arvo = jam(stack, arvo); + let state = unsafe { + let (mut state, dest) = IndirectAtom::new_raw_mut(stack, jammed_arvo.size() + 1); + dest.write(event_number); + match jammed_arvo.as_either() { + Either::Left(direct) => { + copy_nonoverlapping(&direct.data() as *const u64, dest.add(1), 1); + } + Either::Right(indirect) => { + copy_nonoverlapping(indirect.data_pointer(), dest.add(1), jammed_arvo.size()); + } + }; + state.normalize_as_atom() + }; + + let mugged = mug_u32(stack, state.as_noun()); + + let f = OpenOptions::new() + .read(true) + .write(true) + .create(true) + .open(snap_path) + .unwrap(); + + f.set_len(((state.size() + 1) << 3) as u64).unwrap(); + unsafe { + let mut out_map = MmapMut::map_mut(&f).unwrap(); + let out_ptr = out_map.as_mut_ptr(); + out_ptr.add(0).write(mugged as u8); + out_ptr.add(1).write((mugged >> 8) as u8); + out_ptr.add(2).write((mugged >> 16) as u8); + out_ptr.add(3).write((mugged >> 24) as u8); + copy_nonoverlapping( + state.data_pointer() as *mut u8, + out_ptr.add(8), + state.size() << 3, + ); + out_map.flush().unwrap(); + }; +} + +pub fn load(stack: &mut NockStack, snap_path: PathBuf) -> io::Result<(u64, Noun, u8)> { + let res0 = load_snapshot(stack, snap_path.clone(), 0); + let res1 = load_snapshot(stack, snap_path.clone(), 1); + + match (res0, res1) { + (Ok((event_number_0, arvo_0)), Ok((event_number_1, arvo_1))) => { + if event_number_0 > event_number_1 { + Ok((event_number_0, arvo_0, 0)) + } else { + Ok((event_number_1, arvo_1, 1)) + } + } + (Ok((event_number_0, arvo_0)), Err(_)) => Ok((event_number_0, arvo_0, 0)), + (Err(_), Ok((event_number_1, arvo_1))) => Ok((event_number_1, arvo_1, 1)), + (Err(_), Err(_)) => Err(io::Error::new( + io::ErrorKind::NotFound, + "no valid snapshot found", + )), + } +} + +fn load_snapshot( + stack: &mut NockStack, + mut snap_path: PathBuf, + number: u8, +) -> io::Result<(u64, Noun)> { + snap_path.push(format!("snapshot-{}.snap", number)); + + eprintln!("\rload: snapshot at {:?}", snap_path); + + let f = File::open(snap_path)?; + + let in_len = f.metadata().unwrap().len() - 8; + let word_len = (in_len + 7) >> 3; + let (event_number, jammed) = unsafe { + let in_map = Mmap::map(&f).unwrap(); + let in_ptr = in_map.as_ptr(); + let (mut state, dest) = IndirectAtom::new_raw_mut(stack, word_len as usize); + let mugged = (*in_ptr.add(0) as u32) + | ((*in_ptr.add(1) as u32) << 8) + | ((*in_ptr.add(2) as u32) << 16) + | ((*in_ptr.add(3) as u32) << 24); + write_bytes(dest.add(word_len as usize - 1), 0, 8); + copy_nonoverlapping(in_ptr.add(8), dest as *mut u8, in_len as usize); + mem::drop(in_map); + state.normalize(); // know it's not direct because first word is event number + + if mug_u32(stack, state.as_noun()) != mugged { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + "snapshot checksum mismatch", + )); + } + + let event_number = *state.data_pointer(); + let jammed = + IndirectAtom::new_raw(stack, word_len as usize - 1, state.data_pointer().add(1)); + (event_number, jammed) + }; + + let arvo = cue(stack, jammed.as_atom()); + + Ok((event_number, arvo)) +} From dd4dc625bdbfc5d706f55a96afde59b247bb6d3a Mon Sep 17 00:00:00 2001 From: Philip Monk Date: Sun, 12 Feb 2023 17:59:46 -0700 Subject: [PATCH 07/10] [ares] harden snapshotting --- rust/ares/src/serf.rs | 10 +++---- rust/ares/src/snapshot.rs | 58 ++++++++++++++++++++++++--------------- 2 files changed, 40 insertions(+), 28 deletions(-) diff --git a/rust/ares/src/serf.rs b/rust/ares/src/serf.rs index 844e695..08fba47 100644 --- a/rust/ares/src/serf.rs +++ b/rust/ares/src/serf.rs @@ -31,11 +31,10 @@ pub fn serf() -> io::Result<()> { let ref mut stack = NockStack::new(8 << 10 << 10, 0); let ref mut newt = Newt::new(); - let mut snap_number; // Last valid snapshot number. let mut event_number; let mut arvo; - (event_number, arvo, snap_number) = load(stack, snap_path.clone()).unwrap_or((0, D(0), 0)); + (event_number, arvo) = load(stack, snap_path.clone()).unwrap_or((0, D(0))); let mug = mug_u32(stack, arvo); newt.ripe(stack, event_number, mug as u64); @@ -58,8 +57,7 @@ pub fn serf() -> io::Result<()> { tas!(b"save") => { // XX what is eve for? eprintln!("save"); - snap_number = if snap_number == 0 { 1 } else { 0 }; - save(stack, snap_path.clone(), snap_number, event_number, arvo); + save(stack, snap_path.clone(), event_number, arvo); } tas!(b"meld") => eprintln!("meld"), tas!(b"pack") => eprintln!("pack"), @@ -121,10 +119,10 @@ pub fn serf() -> io::Result<()> { Ok(()) } -pub fn slam(stack: &mut NockStack, newt: &mut Newt, arvo: Noun, axis: u64, ovo: Noun) -> Noun { +pub fn slam(stack: &mut NockStack, newt: &mut Newt, core: Noun, axis: u64, ovo: Noun) -> Noun { let pul = Cell::new_tuple(stack, &[D(9), D(axis), D(0), D(2)]).as_noun(); let sam = Cell::new_tuple(stack, &[D(6), D(0), D(7)]).as_noun(); let fol = Cell::new_tuple(stack, &[D(8), pul, D(9), D(2), D(10), sam, D(0), D(2)]).as_noun(); - let sub = Cell::new_tuple(stack, &[arvo, ovo]).as_noun(); + let sub = Cell::new_tuple(stack, &[core, ovo]).as_noun(); interpret(stack, &mut Some(newt), sub, fol) } diff --git a/rust/ares/src/snapshot.rs b/rust/ares/src/snapshot.rs index cbe1055..cbae793 100644 --- a/rust/ares/src/snapshot.rs +++ b/rust/ares/src/snapshot.rs @@ -24,13 +24,14 @@ use std::path::PathBuf; use std::ptr::copy_nonoverlapping; use std::ptr::write_bytes; -pub fn save( - stack: &mut NockStack, - mut snap_path: PathBuf, - snap_number: u8, - event_number: u64, - arvo: Noun, -) { +pub fn save(stack: &mut NockStack, mut snap_path: PathBuf, event_number: u64, arvo: Noun) { + // Find the latest valid snapshot, and write to the other file. + let prev_snap = if let Ok((prev_snap, _, _)) = latest_snapshot(stack, snap_path.clone()) { + prev_snap + } else { + 0 + }; + let snap_number = if prev_snap == 0 { 1 } else { 0 }; snap_path.push(format!("snapshot-{}.snap", snap_number)); let jammed_arvo = jam(stack, arvo); @@ -71,23 +72,41 @@ pub fn save( state.size() << 3, ); out_map.flush().unwrap(); + + // This appears to match c3/portable.h: fdatasync for linux, fcntl with F_FULLFSYNC for for + // macos, and fsync for some other platforms. + f.sync_data().unwrap(); }; } -pub fn load(stack: &mut NockStack, snap_path: PathBuf) -> io::Result<(u64, Noun, u8)> { +pub fn load(stack: &mut NockStack, snap_path: PathBuf) -> io::Result<(u64, Noun)> { + let (_num, event_number, state) = latest_snapshot(stack, snap_path)?; + + let jammed_arvo = + unsafe { IndirectAtom::new_raw(stack, state.size() - 1, state.data_pointer().add(1)) }; + + let arvo = cue(stack, jammed_arvo.as_atom()); + + Ok((event_number, arvo)) +} + +fn latest_snapshot( + stack: &mut NockStack, + snap_path: PathBuf, +) -> io::Result<(u8, u64, IndirectAtom)> { let res0 = load_snapshot(stack, snap_path.clone(), 0); let res1 = load_snapshot(stack, snap_path.clone(), 1); match (res0, res1) { - (Ok((event_number_0, arvo_0)), Ok((event_number_1, arvo_1))) => { + (Ok((event_number_0, state_0)), Ok((event_number_1, state_1))) => { if event_number_0 > event_number_1 { - Ok((event_number_0, arvo_0, 0)) + Ok((0, event_number_0, state_0)) } else { - Ok((event_number_1, arvo_1, 1)) + Ok((1, event_number_1, state_1)) } } - (Ok((event_number_0, arvo_0)), Err(_)) => Ok((event_number_0, arvo_0, 0)), - (Err(_), Ok((event_number_1, arvo_1))) => Ok((event_number_1, arvo_1, 1)), + (Ok((event_number_0, state_0)), Err(_)) => Ok((0, event_number_0, state_0)), + (Err(_), Ok((event_number_1, state_1))) => Ok((1, event_number_1, state_1)), (Err(_), Err(_)) => Err(io::Error::new( io::ErrorKind::NotFound, "no valid snapshot found", @@ -99,7 +118,7 @@ fn load_snapshot( stack: &mut NockStack, mut snap_path: PathBuf, number: u8, -) -> io::Result<(u64, Noun)> { +) -> io::Result<(u64, IndirectAtom)> { snap_path.push(format!("snapshot-{}.snap", number)); eprintln!("\rload: snapshot at {:?}", snap_path); @@ -108,7 +127,7 @@ fn load_snapshot( let in_len = f.metadata().unwrap().len() - 8; let word_len = (in_len + 7) >> 3; - let (event_number, jammed) = unsafe { + let (event_number, state) = unsafe { let in_map = Mmap::map(&f).unwrap(); let in_ptr = in_map.as_ptr(); let (mut state, dest) = IndirectAtom::new_raw_mut(stack, word_len as usize); @@ -128,13 +147,8 @@ fn load_snapshot( )); } - let event_number = *state.data_pointer(); - let jammed = - IndirectAtom::new_raw(stack, word_len as usize - 1, state.data_pointer().add(1)); - (event_number, jammed) + (*state.data_pointer(), state) }; - let arvo = cue(stack, jammed.as_atom()); - - Ok((event_number, arvo)) + Ok((event_number, state)) } From ff59ca8ba91c5e19b779039b31e7725e884222af Mon Sep 17 00:00:00 2001 From: Philip Monk Date: Mon, 13 Feb 2023 10:39:08 -0700 Subject: [PATCH 08/10] [ares] rename new_tuple -> T, make D const --- rust/ares/src/newt.rs | 38 +++++++++++++++++--------------------- rust/ares/src/noun.rs | 9 +++++++-- rust/ares/src/serf.rs | 14 +++++++------- 3 files changed, 31 insertions(+), 30 deletions(-) diff --git a/rust/ares/src/newt.rs b/rust/ares/src/newt.rs index c590830..cc18536 100644 --- a/rust/ares/src/newt.rs +++ b/rust/ares/src/newt.rs @@ -51,7 +51,7 @@ * It's important to not use io::Stdin and io::Stdout directly. All printfs should use stderr. */ use crate::mem::NockStack; -use crate::noun::{Cell, IndirectAtom, Noun, D}; +use crate::noun::{T, IndirectAtom, Noun, D}; use crate::serialization::{cue, jam}; use ares_macros::tas; use either::Either; @@ -107,88 +107,84 @@ impl Newt { /** Send %ripe, the first event. */ pub fn ripe(&mut self, stack: &mut NockStack, eve: u64, mug: u64) { - let version = Cell::new_tuple( + let version = T( stack, &[ D(1), // newt protocol D(139), // hoon kelvin D(4), // nock kelvin ], - ) - .as_noun(); - let ripe = Cell::new_tuple(stack, &[D(tas!(b"ripe")), version, D(eve), D(mug)]).as_noun(); + ); + let ripe = T(stack, &[D(tas!(b"ripe")), version, D(eve), D(mug)]); self.write_noun(stack, ripe); } /** Send %live, acknowledging. */ pub fn live(&mut self, stack: &mut NockStack) { - let live = Cell::new_tuple(stack, &[D(tas!(b"live")), D(0)]).as_noun(); + let live = T(stack, &[D(tas!(b"live")), D(0)]); self.write_noun(stack, live); } /** Send %slog, pretty-printed debug output. */ pub fn slog(&mut self, stack: &mut NockStack, pri: u64, tank: Noun) { - let slog = Cell::new_tuple(stack, &[D(tas!(b"slog")), D(pri), tank]).as_noun(); + let slog = T(stack, &[D(tas!(b"slog")), D(pri), tank]); self.write_noun(stack, slog); } /** Send %flog, raw debug output. */ pub fn flog(&mut self, stack: &mut NockStack, cord: Noun) { - let flog = Cell::new_tuple(stack, &[D(tas!(b"flog")), cord]).as_noun(); + let flog = T(stack, &[D(tas!(b"flog")), cord]); self.write_noun(stack, flog); } /** Send %peek %done, successfully scried. */ pub fn peek_done(&mut self, stack: &mut NockStack, dat: Noun) { - let peek = Cell::new_tuple(stack, &[D(tas!(b"peek")), D(tas!(b"done")), dat]).as_noun(); + let peek = T(stack, &[D(tas!(b"peek")), D(tas!(b"done")), dat]); self.write_noun(stack, peek); } /** Send %peek %bail, unsuccessfully scried. */ pub fn peek_bail(&mut self, stack: &mut NockStack, dud: Noun) { - let peek = Cell::new_tuple(stack, &[D(tas!(b"peek")), D(tas!(b"bail")), dud]).as_noun(); + let peek = T(stack, &[D(tas!(b"peek")), D(tas!(b"bail")), dud]); self.write_noun(stack, peek); } /** Send %play %done, successfully replayed events. */ pub fn play_done(&mut self, stack: &mut NockStack, mug: u64) { - let play = Cell::new_tuple(stack, &[D(tas!(b"play")), D(tas!(b"done")), D(mug)]).as_noun(); + let play = T(stack, &[D(tas!(b"play")), D(tas!(b"done")), D(mug)]); self.write_noun(stack, play); } /** Send %play %bail, failed to replay events. */ pub fn play_bail(&mut self, stack: &mut NockStack, eve: u64, mug: u64, dud: Noun) { - let play = Cell::new_tuple( + let play = T( stack, &[D(tas!(b"play")), D(tas!(b"bail")), D(eve), D(mug), dud], - ) - .as_noun(); + ); self.write_noun(stack, play); } /** Send %work %done, successfully ran event. */ pub fn work_done(&mut self, stack: &mut NockStack, eve: u64, mug: u64, fec: Noun) { - let work = Cell::new_tuple( + let work = T( stack, &[D(tas!(b"work")), D(tas!(b"done")), D(eve), D(mug), fec], - ) - .as_noun(); + ); self.write_noun(stack, work); } /** Send %work %swap, successfully replaced failed event. */ pub fn work_swap(&mut self, stack: &mut NockStack, eve: u64, mug: u64, job: Noun, fec: Noun) { - let work = Cell::new_tuple( + let work = T( stack, &[D(tas!(b"work")), D(tas!(b"swap")), D(eve), D(mug), job, fec], - ) - .as_noun(); + ); self.write_noun(stack, work); } /** Send %work %bail, failed to run event. */ pub fn work_bail(&mut self, stack: &mut NockStack, lud: Noun) { - let work = Cell::new_tuple(stack, &[D(tas!(b"work")), D(tas!(b"bail")), lud]).as_noun(); + let work = T(stack, &[D(tas!(b"work")), D(tas!(b"bail")), lud]); self.write_noun(stack, work); } diff --git a/rust/ares/src/noun.rs b/rust/ares/src/noun.rs index a0e7f34..68022e8 100644 --- a/rust/ares/src/noun.rs +++ b/rust/ares/src/noun.rs @@ -131,7 +131,7 @@ impl DirectAtom { Atom { direct: self } } - pub fn as_noun(self) -> Noun { + pub const fn as_noun(self) -> Noun { Noun { direct: self } } @@ -151,10 +151,15 @@ impl fmt::Debug for DirectAtom { } #[allow(non_snake_case)] -pub fn D(n: u64) -> Noun { +pub const fn D(n: u64) -> Noun { DirectAtom::new_panic(n).as_noun() } +#[allow(non_snake_case)] +pub fn T(allocator: &mut dyn NounAllocator, tup: &[Noun]) -> Noun { + Cell::new_tuple(allocator, tup).as_noun() +} + /** An indirect atom. * * Indirect atoms represent atoms above DIRECT_MAX as a tagged pointer to a memory buffer diff --git a/rust/ares/src/serf.rs b/rust/ares/src/serf.rs index 08fba47..dfe5819 100644 --- a/rust/ares/src/serf.rs +++ b/rust/ares/src/serf.rs @@ -2,7 +2,7 @@ use crate::interpreter::{interpret, raw_slot}; use crate::mem::NockStack; use crate::mug::mug_u32; use crate::newt::Newt; -use crate::noun::{Cell, Noun, D}; +use crate::noun::{T, Noun, D}; use crate::snapshot::{load, save}; use ares_macros::tas; use std::fs::create_dir_all; @@ -74,8 +74,8 @@ pub fn serf() -> io::Result<()> { let run = if event_number == 0 { // apply lifecycle to first batch let lit = raw_slot(writ, 7); - let sub = Cell::new_tuple(stack, &[D(0), D(3)]).as_noun(); - let lyf = Cell::new_tuple(stack, &[D(2), sub, D(0), D(2)]).as_noun(); + let sub = T(stack, &[D(0), D(3)]); + let lyf = T(stack, &[D(2), sub, D(0), D(2)]); let gat = interpret(stack, &mut Some(newt), lit, lyf); arvo = raw_slot(gat, 7); false @@ -120,9 +120,9 @@ pub fn serf() -> io::Result<()> { } pub fn slam(stack: &mut NockStack, newt: &mut Newt, core: Noun, axis: u64, ovo: Noun) -> Noun { - let pul = Cell::new_tuple(stack, &[D(9), D(axis), D(0), D(2)]).as_noun(); - let sam = Cell::new_tuple(stack, &[D(6), D(0), D(7)]).as_noun(); - let fol = Cell::new_tuple(stack, &[D(8), pul, D(9), D(2), D(10), sam, D(0), D(2)]).as_noun(); - let sub = Cell::new_tuple(stack, &[core, ovo]).as_noun(); + let pul = T(stack, &[D(9), D(axis), D(0), D(2)]); + let sam = T(stack, &[D(6), D(0), D(7)]); + let fol = T(stack, &[D(8), pul, D(9), D(2), D(10), sam, D(0), D(2)]); + let sub = T(stack, &[core, ovo]); interpret(stack, &mut Some(newt), sub, fol) } From fe3c0895b23b4658142baca601476853426e12d3 Mon Sep 17 00:00:00 2001 From: Philip Monk Date: Mon, 13 Feb 2023 10:57:13 -0700 Subject: [PATCH 09/10] [ares] comments and formatting --- rust/ares/src/interpreter.rs | 5 ++++- rust/ares/src/newt.rs | 2 +- rust/ares/src/serf.rs | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/rust/ares/src/interpreter.rs b/rust/ares/src/interpreter.rs index 9f5ece1..23a81ed 100644 --- a/rust/ares/src/interpreter.rs +++ b/rust/ares/src/interpreter.rs @@ -65,9 +65,10 @@ fn noun_to_work(noun: Noun) -> NockWork { } } +/** Interpret nock */ pub fn interpret( stack: &mut NockStack, - newt: &mut Option<&mut Newt>, + newt: &mut Option<&mut Newt>, // For printing slogs; if None, print to stdout mut subject: Noun, formula: Noun, ) -> Noun { @@ -623,6 +624,7 @@ fn inc(stack: &mut NockStack, atom: Atom) -> Atom { } } +/** Match hints which apply before the formula is evaluated */ fn match_pre_hint(stack: &mut NockStack, subject: Noun, cell: Cell) -> Result { let direct = cell.head().as_direct()?; match direct.data() { @@ -636,6 +638,7 @@ fn match_pre_hint(stack: &mut NockStack, subject: Noun, cell: Cell) -> Result, diff --git a/rust/ares/src/newt.rs b/rust/ares/src/newt.rs index cc18536..eef6f2c 100644 --- a/rust/ares/src/newt.rs +++ b/rust/ares/src/newt.rs @@ -51,7 +51,7 @@ * It's important to not use io::Stdin and io::Stdout directly. All printfs should use stderr. */ use crate::mem::NockStack; -use crate::noun::{T, IndirectAtom, Noun, D}; +use crate::noun::{IndirectAtom, Noun, D, T}; use crate::serialization::{cue, jam}; use ares_macros::tas; use either::Either; diff --git a/rust/ares/src/serf.rs b/rust/ares/src/serf.rs index dfe5819..7f26ebf 100644 --- a/rust/ares/src/serf.rs +++ b/rust/ares/src/serf.rs @@ -2,7 +2,7 @@ use crate::interpreter::{interpret, raw_slot}; use crate::mem::NockStack; use crate::mug::mug_u32; use crate::newt::Newt; -use crate::noun::{T, Noun, D}; +use crate::noun::{Noun, D, T}; use crate::snapshot::{load, save}; use ares_macros::tas; use std::fs::create_dir_all; From 634680aa2461abb3b712dd4a47be1194b57ed2f9 Mon Sep 17 00:00:00 2001 From: Philip Monk Date: Mon, 13 Feb 2023 11:13:21 -0700 Subject: [PATCH 10/10] [ares] copy directly into atom in newt --- rust/ares/src/newt.rs | 26 +++++++++----------------- rust/ares/src/noun.rs | 14 ++++++++++++++ 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/rust/ares/src/newt.rs b/rust/ares/src/newt.rs index eef6f2c..f3cbf71 100644 --- a/rust/ares/src/newt.rs +++ b/rust/ares/src/newt.rs @@ -57,7 +57,7 @@ use ares_macros::tas; use either::Either; use std::io::{Read, Write}; use std::os::unix::prelude::FromRawFd; -use std::ptr::{copy_nonoverlapping, write_bytes}; +use std::ptr::copy_nonoverlapping; pub struct Newt { input: std::fs::File, @@ -202,23 +202,15 @@ impl Newt { let byte_len = u32::from_le_bytes([header[1], header[2], header[3], header[4]]) as usize; - // Would be nice to copy directly into an indirect atom, but I don't know how to do that - // when these are aligned to bytes - let mut body: Vec = Vec::with_capacity(byte_len); - body.resize(byte_len, 0); - if let Err(err) = self.input.read_exact(&mut body) { - if err.kind() == std::io::ErrorKind::UnexpectedEof { - return None; - } else { - panic!("Error reading body: {}", err); - } - } - let atom = unsafe { - let word_len = (byte_len + 7) >> 3; - let (mut atom, dest) = IndirectAtom::new_raw_mut(stack, word_len as usize); - write_bytes(dest.add(word_len as usize - 1), 0, 8); - copy_nonoverlapping(body.as_ptr(), dest as *mut u8, byte_len); + let (mut atom, dest) = IndirectAtom::new_raw_mut_bytes(stack, byte_len); + if let Err(err) = self.input.read_exact(dest) { + if err.kind() == std::io::ErrorKind::UnexpectedEof { + return None; + } else { + panic!("Error reading body: {}", err); + } + } atom.normalize_as_atom() }; diff --git a/rust/ares/src/noun.rs b/rust/ares/src/noun.rs index 68022e8..3a0e9bf 100644 --- a/rust/ares/src/noun.rs +++ b/rust/ares/src/noun.rs @@ -261,6 +261,20 @@ impl IndirectAtom { ) } + /** Make an indirect atom that can be written into as a slice of bytes. The constraints of + * [new_raw_mut_zeroed] also apply here + * + * Note: size is bytes, not words + */ + pub unsafe fn new_raw_mut_bytes<'a>( + allocator: &mut dyn NounAllocator, + size: usize, + ) -> (Self, &'a mut [u8]) { + 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)) + } + /** Size of an indirect atom in 64-bit words */ pub fn size(&self) -> usize { unsafe { *(self.to_raw_pointer().add(1)) as usize }