Merge branch 'profile' into urcrypt

This commit is contained in:
Alex Shelkovnykov 2023-11-20 00:32:41 -05:00
commit b1b46f908c
12 changed files with 615 additions and 383 deletions

View File

@ -7,15 +7,13 @@ use crate::jets::cold::Cold;
use crate::jets::hot::Hot;
use crate::jets::warm::Warm;
use crate::jets::JetErr;
use crate::jets::form::util::scow;
use crate::jets::bits::util::rap;
use crate::mem::unifying_equality;
use crate::mem::NockStack;
use crate::mug::met3_usize;
use crate::newt::Newt;
use crate::noun;
use crate::noun::{Atom, Cell, DirectAtom, IndirectAtom, Noun, Slots, D, T};
use crate::noun::{Atom, Cell, IndirectAtom, Noun, Slots, D, T};
use crate::serf::TERMINATOR;
use crate::trace::{write_nock_trace, TraceInfo, TraceStack};
use ares_macros::tas;
use assert_no_alloc::assert_no_alloc;
use bitvec::prelude::{BitSlice, Lsb0};
@ -23,11 +21,7 @@ use either::Either::*;
use std::result;
use std::sync::atomic::Ordering;
use std::sync::Arc;
use std::fs::File;
use std::time::Instant;
use json::object;
use std::io::Write;
crate::gdb!();
@ -274,18 +268,6 @@ pub struct Context {
pub trace_info: Option<TraceInfo>,
}
pub struct TraceInfo {
pub file: File,
pub pid: u32,
pub process_start: Instant,
}
pub struct TraceStack {
pub start: Instant,
pub path: Noun,
pub next: *const TraceStack,
}
#[derive(Clone, Copy, Debug)]
pub enum Error {
ScryBlocked(Noun), // path
@ -325,9 +307,9 @@ pub fn interpret(context: &mut Context, mut subject: Noun, formula: Noun) -> Res
// Setup stack for Nock computation
unsafe {
context.stack.frame_push(2);
// Bottom of mean stack
*(context.stack.local_noun_pointer(0)) = D(0);
// Bottom of trace stack
*(context.stack.local_noun_pointer(1) as *mut *const TraceStack) = std::ptr::null();
@ -352,8 +334,9 @@ pub fn interpret(context: &mut Context, mut subject: Noun, formula: Noun) -> Res
let work: NockWork = *context.stack.top();
match work {
NockWork::Done => {
let stack = &mut context.stack;
write_trace(context);
let stack = &mut context.stack;
debug_assertions(stack, orig_subject);
debug_assertions(stack, subject);
debug_assertions(stack, res);
@ -370,62 +353,13 @@ pub fn interpret(context: &mut Context, mut subject: Noun, formula: Noun) -> Res
break Ok(res);
}
NockWork::Ret => {
let stack = &mut context.stack;
write_trace(context);
let stack = &mut context.stack;
debug_assertions(stack, orig_subject);
debug_assertions(stack, subject);
debug_assertions(stack, res);
// Write fast-hinted traces to file
if let Some(ref mut info) = &mut context.trace_info {
let mut trace_stack = *(stack.local_noun_pointer(1) as *const *const TraceStack);
let now = Instant::now();
assert_no_alloc::permit_alloc(|| {
loop {
if trace_stack.is_null() { break; }
let ts = (*trace_stack).start.saturating_duration_since(info.process_start).as_micros() as f64;
let dur = now.saturating_duration_since((*trace_stack).start).as_micros() as f64;
// Don't write out traces less than 33us
// (same threshhold used in vere)
if dur < 33.0 {
trace_stack = (*trace_stack).next;
continue;
}
let pc = path_to_cord(stack, (*trace_stack).path);
let pclen = met3_usize(pc);
if let Ok(pc_str) = std::str::from_utf8(&pc.as_bytes()[0..pclen]) {
let obj = object!{
cat: "nock",
name: pc_str,
ph: "X",
pid: info.pid,
tid: 2,
ts: ts,
dur: dur,
};
if let Err(e) = obj.write(&mut info.file) {
eprintln!("Error writing trace to file: {:?}", e);
break;
};
if let Err(e) = info.file.write(",\n".as_bytes()) {
eprintln!("Error writing trace to file: {:?}", e);
break;
};
}
trace_stack = (*trace_stack).next;
}
});
if let Err(e) = info.file.sync_data() {
eprintln!("Error syncing trace file: {:?}", e);
};
}
stack.preserve(&mut context.cache);
stack.preserve(&mut context.cold);
stack.preserve(&mut context.warm);
@ -696,17 +630,12 @@ pub fn interpret(context: &mut Context, mut subject: Noun, formula: Noun) -> Res
if kale.tail {
stack.pop::<NockWork>();
if let Some(_info) = &context.trace_info {
// We could trace on 2 as well, but 2 only comes from Hoon via
// '.*', so we can assume it's never directly used to invoke
// jetted code.
if context.trace_info.is_some() {
if let Some(path) = context.cold.matches(stack, &mut res) {
// Push onto the tracing stack
let trace_stack = *(stack.local_noun_pointer(1) as *const *const TraceStack);
let new_trace_entry = stack.struct_alloc(1);
*new_trace_entry = TraceStack {
path,
start: Instant::now(),
next: trace_stack,
};
*(stack.local_noun_pointer(1) as *mut *const TraceStack) = new_trace_entry;
append_trace(stack, path);
};
};
@ -726,20 +655,14 @@ pub fn interpret(context: &mut Context, mut subject: Noun, formula: Noun) -> Res
*stack.push() = NockWork::Ret;
push_formula(stack, formula, true)?;
if let Some(_info) = &context.trace_info {
// We could trace on 2 as well, but 2 only comes from Hoon via
// '.*', so we can assume it's never directly used to invoke
// jetted code.
if context.trace_info.is_some() {
if let Some(path) = context.cold.matches(stack, &mut res) {
// Push onto the tracing stack
let trace_stack = *(stack.local_noun_pointer(1) as *const *const TraceStack);
let new_trace_entry = stack.struct_alloc(1);
*new_trace_entry = TraceStack {
path,
start: Instant::now(),
next: trace_stack,
};
*(stack.local_noun_pointer(1) as *mut *const TraceStack) = new_trace_entry;
append_trace(stack, path);
};
};
}
} else {
// Axis into core must be atom
@ -1172,7 +1095,7 @@ fn push_formula(stack: &mut NockStack, formula: Noun, tail: bool) -> result::Res
Ok(())
}
pub fn exit(context: &mut Context, virtual_frame: *const u64, error: Error) -> Error {
fn exit(context: &mut Context, virtual_frame: *const u64, error: Error) -> Error {
unsafe {
let stack = &mut context.stack;
@ -1302,81 +1225,30 @@ pub fn inc(stack: &mut NockStack, atom: Atom) -> Atom {
}
}
fn path_to_cord(stack: &mut NockStack, path: Noun) -> Atom {
let mut cursor = path;
let mut length = 0usize;
// count how much size we need
loop {
if let Ok(c) = cursor.as_cell() {
unsafe {
match c.head().as_either_atom_cell() {
Left(a) => {
length += 1;
length += met3_usize(a);
},
Right(ch) => {
if let Ok(nm) = ch.head().as_atom() {
if let Ok(kv) = ch.tail().as_atom() {
let kvt = scow(stack, DirectAtom::new_unchecked(tas!(b"ud")), kv).expect("scow should succeed in path_to_cord");
let kvc = rap(stack, 3, kvt).expect("rap should succeed in path_to_cord");
length += 1;
length += met3_usize(nm);
length += met3_usize(kvc);
}
}
}
}
}
cursor = c.tail();
} else {
break;
}
}
// reset cursor, then actually write the path
cursor = path;
let mut idx = 0;
let (mut deres, buffer) = unsafe { IndirectAtom::new_raw_mut_bytes(stack, length) };
let slash = (b"/")[0];
loop {
if let Ok(c) = cursor.as_cell() {
unsafe {
match c.head().as_either_atom_cell() {
Left(a) => {
buffer[idx] = slash;
idx += 1;
let bytelen = met3_usize(a);
buffer[idx..idx+bytelen].copy_from_slice(&a.as_bytes()[0..bytelen]);
idx += bytelen;
},
Right(ch) => {
if let Ok(nm) = ch.head().as_atom() {
if let Ok(kv) = ch.tail().as_atom() {
let kvt = scow(stack, DirectAtom::new_unchecked(tas!(b"ud")), kv).expect("scow should succeed in path_to_cord");
let kvc = rap(stack, 3, kvt).expect("rap should succeed in path_to_cord");
buffer[idx] = slash;
idx += 1;
let nmlen = met3_usize(nm);
buffer[idx..idx+nmlen].copy_from_slice(&nm.as_bytes()[0..nmlen]);
idx += nmlen;
let kvclen = met3_usize(kvc);
buffer[idx..idx+kvclen].copy_from_slice(&kvc.as_bytes()[0..kvclen]);
idx += kvclen;
}
}
},
}
}
cursor = c.tail();
} else {
break;
}
};
/// Push onto the tracing stack
fn append_trace(stack: &mut NockStack, path: Noun) {
unsafe {
deres.normalize_as_atom()
let trace_stack = *(stack.local_noun_pointer(1) as *const *const TraceStack);
let new_trace_entry = stack.struct_alloc(1);
*new_trace_entry = TraceStack {
path,
start: Instant::now(),
next: trace_stack,
};
*(stack.local_noun_pointer(1) as *mut *const TraceStack) = new_trace_entry;
}
}
/// Write fast-hinted traces to trace file
unsafe fn write_trace(context: &mut Context) {
if let Some(ref mut info) = &mut context.trace_info {
let trace_stack = *(context.stack.local_noun_pointer(1) as *mut *const TraceStack);
// Abort writing to trace file if we encountered an error. This should
// result in a well-formed partial trace file.
if let Err(e) = write_nock_trace(&mut context.stack, info, trace_stack) {
eprintln!("\rserf: error writing nock trace to file: {:?}", e);
context.trace_info = None;
}
}
}
@ -1688,4 +1560,3 @@ mod hint {
newt.slog(stack, 0u64, tank);
}
}

View File

@ -53,15 +53,15 @@ pub enum JetErr {
Fail(Error), // Error; do not retry
}
impl From<noun::Error> for JetErr {
fn from(_err: noun::Error) -> Self {
Self::Fail(Error::Deterministic(D(0)))
impl From<Error> for JetErr {
fn from(err: Error) -> Self {
Self::Fail(err)
}
}
impl From<Error> for JetErr {
fn from(e: Error) -> Self {
Self::Fail(e)
impl From<noun::Error> for JetErr {
fn from(_err: noun::Error) -> Self {
Self::Fail(Error::Deterministic(D(0)))
}
}
@ -355,6 +355,14 @@ pub mod util {
}
}
pub fn assert_jet_size(context: &mut Context, jet: Jet, sam: Noun, siz: usize) {
let sam = T(&mut context.stack, &[D(0), sam, D(0)]);
let res = assert_no_alloc(|| jet(context, sam).unwrap());
assert!(res.is_atom(), "jet result not atom");
let res_siz = res.atom().unwrap().size();
assert!(siz == res_siz, "got: {}, need: {}", res_siz, siz);
}
pub fn assert_common_jet(
context: &mut Context,
jet: Jet,
@ -386,5 +394,16 @@ pub mod util {
let sam = T(&mut context.stack, &sam);
assert_jet_err(context, jet, sam, err);
}
pub fn assert_common_jet_size(
context: &mut Context,
jet: Jet,
sam: &[fn(&mut NockStack) -> Noun],
siz: usize,
) {
let sam: Vec<Noun> = sam.iter().map(|f| f(&mut context.stack)).collect();
let sam = T(&mut context.stack, &sam);
assert_jet_size(context, jet, sam, siz)
}
}
}

View File

@ -130,7 +130,7 @@ pub fn jet_lsh(context: &mut Context, subject: Noun) -> Result {
let (bloq, step) = bite(slot(arg, 2)?)?;
let a = slot(arg, 3)?.as_atom()?;
util::lsh(&mut context.stack, bloq, step, a).map(|a| a.as_noun())
util::lsh(&mut context.stack, bloq, step, a)
}
pub fn jet_met(_context: &mut Context, subject: Noun) -> Result {
@ -148,7 +148,6 @@ pub fn jet_rap(context: &mut Context, subject: Noun) -> Result {
Ok(util::rap(&mut context.stack, bloq, original_list)?.as_noun())
}
pub fn jet_rep(context: &mut Context, subject: Noun) -> Result {
let arg = slot(subject, 6)?;
let (bloq, step) = bite(slot(arg, 2)?)?;
@ -306,11 +305,10 @@ pub fn jet_mix(context: &mut Context, subject: Noun) -> Result {
}
pub mod util {
use crate::jets;
use crate::jets::util::*;
use crate::jets::JetErr;
use crate::jets::{JetErr, Result};
use crate::mem::NockStack;
use crate::noun::{Atom, Cell, DirectAtom, IndirectAtom, D, Noun};
use crate::noun::{Atom, Cell, DirectAtom, IndirectAtom, Noun, D};
use std::cmp;
use std::result;
@ -327,22 +325,17 @@ pub mod util {
}
}
pub fn lsh(
stack: &mut NockStack,
bloq: usize,
step: usize,
a: Atom,
) -> result::Result<Atom, JetErr> {
pub fn lsh(stack: &mut NockStack, bloq: usize, step: usize, a: Atom) -> Result {
let len = met(bloq, a);
if len == 0 {
return Ok(D(0).as_atom()?);
return Ok(D(0));
}
let new_size = bits_to_word(checked_add(a.bit_size(), checked_left_shift(bloq, step)?)?)?;
unsafe {
let (mut atom, dest) = IndirectAtom::new_raw_mut_bitslice(stack, new_size);
chop(bloq, 0, len, step, dest, a.as_bitslice())?;
Ok(atom.normalize_as_atom())
Ok(atom.normalize_as_atom().as_noun())
}
}
@ -358,7 +351,7 @@ pub mod util {
}
}
pub fn rip(stack: &mut NockStack, bloq: usize, step: usize, atom: Atom) -> jets::Result {
pub fn rip(stack: &mut NockStack, bloq: usize, step: usize, atom: Atom) -> Result {
let len = (met(bloq, atom) + step - 1) / step;
let mut list = D(0);
for i in (0..len).rev() {
@ -387,20 +380,24 @@ pub mod util {
}
}
pub fn rap(stack: &mut NockStack, bloq: usize, original_list: Noun) -> Result<Atom, JetErr> {
pub fn rap(
stack: &mut NockStack,
bloq: usize,
original_list: Noun,
) -> result::Result<Atom, JetErr> {
let mut len = 0usize;
let mut list = original_list;
loop {
if unsafe { list.raw_equals(D(0)) } {
break;
}
let cell = list.as_cell()?;
len = checked_add(len, met(bloq, cell.head().as_atom()?))?;
list = cell.tail();
}
if len == 0 {
Ok(Atom::new(stack, 0))
} else {
@ -409,21 +406,21 @@ pub mod util {
IndirectAtom::new_raw_mut_bitslice(stack, bite_to_word(bloq, len)?);
let mut pos = 0;
let mut list = original_list;
loop {
if list.raw_equals(D(0)) {
break;
}
let cell = list.as_cell()?;
let atom = cell.head().as_atom()?;
let step = met(bloq, atom);
chop(bloq, 0, step, pos, new_slice, atom.as_bitslice())?;
pos += step;
list = cell.tail();
}
Ok(new_indirect.normalize_as_atom())
}
}

View File

@ -274,8 +274,8 @@ struct ColdMem {
/// value: possible registered paths for core
///
/// Identical nock can exist in multiple places, so the outermost battery
/// yield multiple paths. Instead of matching on the entire core in the Hamt
/// (which would require iterating through every possible pait), we match
/// yields multiple paths. Instead of matching on the entire core in the Hamt
/// (which would require iterating through every possible pair), we match
/// the outermost battery to a path, then compare the core to the registered
/// cores for that path.
battery_to_paths: Hamt<NounList>,
@ -348,15 +348,16 @@ impl Cold {
unsafe {
let paths = (*(self.0)).battery_to_paths.lookup(stack, &mut battery)?;
for path in paths {
if let Some(batteries_list) = (*(self.0)).path_to_batteries.lookup(stack, &mut (*path)) {
if let Some(batteries_list) =
(*(self.0)).path_to_batteries.lookup(stack, &mut (*path))
{
if let Some(_batt) = batteries_list.matches(stack, *core) {
return Some(*path);
}
}
};
return None;
}
}
};
None
}
/// register a core, return a boolean of whether we actually needed to register (false ->

View File

@ -9,12 +9,12 @@ crate::gdb!();
pub fn jet_flop(context: &mut Context, subject: Noun) -> Result {
let sam = slot(subject, 6)?;
Ok(util::flop(&mut context.stack, sam)?)
util::flop(&mut context.stack, sam)
}
pub fn jet_lent(_context: &mut Context, subject: Noun) -> Result {
let tape = slot(subject, 6)?;
util::lent(tape).map(|x| D(x as u64))
let list = slot(subject, 6)?;
util::lent(list).map(|x| D(x as u64))
}
pub fn jet_zing(context: &mut Context, subject: Noun) -> Result {
@ -26,14 +26,13 @@ pub fn jet_zing(context: &mut Context, subject: Noun) -> Result {
pub mod util {
use crate::interpreter::Error;
use crate::jets;
use crate::jets::JetErr;
use crate::jets::{JetErr, Result};
use crate::mem::NockStack;
use crate::noun::{Cell, Noun, D, T};
use std::result::Result;
use std::result;
/// Reverse order of list
pub fn flop(stack: &mut NockStack, noun: Noun) -> Result<Noun, Error> {
pub fn flop(stack: &mut NockStack, noun: Noun) -> Result {
let mut list = noun;
let mut tsil = D(0);
loop {
@ -49,7 +48,7 @@ pub mod util {
Ok(tsil)
}
pub fn lent(tape: Noun) -> Result<usize, JetErr> {
pub fn lent(tape: Noun) -> result::Result<usize, JetErr> {
let mut len = 0usize;
let mut list = tape;
loop {
@ -68,50 +67,29 @@ pub mod util {
Ok(len)
}
pub fn zing(stack: &mut NockStack, mut list: Noun) -> jets::Result {
pub fn zing(stack: &mut NockStack, mut list: Noun) -> Result {
unsafe {
let (mut new_cell, mut new_memory) = Cell::new_raw_mut(stack);
#[allow(unused_assignments)]
let (mut cell, mut memory) = (new_cell, new_memory);
let mut res: Noun = D(0);
let mut flag = false;
loop {
if list.raw_equals(D(0)) {
break;
}
let mut dest = &mut res as *mut Noun;
while !list.raw_equals(D(0)) {
let pair = list.as_cell()?;
let mut sub_list = pair.head();
loop {
if sub_list.raw_equals(D(0)) {
break;
}
let elem = sub_list.as_cell()?;
let head = elem.head();
if flag {
(new_cell, new_memory) = Cell::new_raw_mut(stack);
(*memory).tail = new_cell.as_noun();
memory = new_memory;
} else {
(cell, memory) = Cell::new_raw_mut(stack);
res = cell.as_noun();
flag = true;
}
(*memory).head = head;
sub_list = elem.tail();
}
let mut sublist = pair.head();
list = pair.tail();
while !sublist.raw_equals(D(0)) {
let it = sublist.as_cell()?;
let i = it.head();
sublist = it.tail();
let (new_cell, new_memory) = Cell::new_raw_mut(stack);
(*new_memory).head = i;
*dest = new_cell.as_noun();
dest = &mut (*new_memory).tail;
}
}
if flag {
(*memory).tail = D(0);
}
*dest = D(0);
Ok(res)
}
}

View File

@ -307,6 +307,7 @@ pub mod util {
}
}
/// Less than
pub fn lth(stack: &mut NockStack, a: Atom, b: Atom) -> Noun {
if let (Ok(a), Ok(b)) = (a.as_direct(), b.as_direct()) {
if a.data() < b.data() {

View File

@ -1,18 +1,17 @@
/** Tree jets
*/
use crate::interpreter::{Context, Error};
use crate::jets::bits;
use crate::jets::math;
use crate::jets::util::slot;
use crate::jets::bits::util::*;
use crate::jets::util::*;
use crate::jets::{JetErr, Result};
use crate::noun::{Noun, D};
use crate::noun::{IndirectAtom, Noun, D};
crate::gdb!();
pub fn jet_cap(_context: &mut Context, subject: Noun) -> Result {
let arg = slot(subject, 6)?;
let tom = arg.as_atom()?;
let met = bits::util::met(0, tom);
let met = met(0, tom);
unsafe {
if met < 2 {
@ -27,43 +26,53 @@ pub fn jet_cap(_context: &mut Context, subject: Noun) -> Result {
pub fn jet_mas(context: &mut Context, subject: Noun) -> Result {
let stack = &mut context.stack;
let arg = slot(subject, 6)?;
let tom = arg.as_atom()?;
let met = bits::util::met(0, tom);
let tom = slot(subject, 6)?.as_atom()?;
let met = met(0, tom);
if met < 2 {
Err(JetErr::Fail(Error::Deterministic(D(0))))
} else {
let c = bits::util::bex(stack, met - 1);
let d = bits::util::bex(stack, met - 2);
let e = math::util::sub(stack, tom, c)?;
Ok(bits::util::con(stack, e, d).as_noun())
let out_bits = met - 1;
let out_words = (out_bits + 63) >> 6;
let (mut indirect_out, out_bs) =
unsafe { IndirectAtom::new_raw_mut_bitslice(stack, out_words) };
out_bs.set(met - 2, true); // Set MSB
if met > 2 {
out_bs[0..(met - 2)].copy_from_bitslice(&tom.as_bitslice()[0..(met - 2)]);
};
unsafe { Ok(indirect_out.normalize_as_atom().as_noun()) }
}
}
pub fn jet_peg(context: &mut Context, subject: Noun) -> Result {
let stack = &mut context.stack;
let arg = slot(subject, 6)?;
let a = slot(arg, 2)?;
let b = slot(arg, 3)?;
let a = slot(arg, 2)?.as_atom()?;
let b = slot(arg, 3)?.as_atom()?;
unsafe {
if a.raw_equals(D(0)) {
if a.as_noun().raw_equals(D(0)) {
return Err(JetErr::Fail(Error::Deterministic(D(0))));
}
// XX: Import jet mistmatch from Vere
if b.raw_equals(D(0)) {
return Err(JetErr::Fail(Error::Deterministic(D(0))));
}
};
};
let c = bits::util::met(0, b.as_atom()?);
let d = c - 1;
let e = bits::util::lsh(stack, 0, d, D(1).as_atom()?)?;
let f = math::util::sub(stack, b.as_atom()?, e)?;
let g = bits::util::lsh(stack, 0, d, a.as_atom()?)?;
Ok(math::util::add(stack, f, g).as_noun())
if b.as_noun().raw_equals(D(0)) {
return Err(JetErr::Fail(Error::Deterministic(D(0))));
};
}
let a_bits = met(0, a);
let b_bits = met(0, b);
let out_bits = a_bits + b_bits - 1;
let out_words = (out_bits + 63) >> 6; // bits to 8-byte words
let (mut indirect_out, out_bs) =
unsafe { IndirectAtom::new_raw_mut_bitslice(stack, out_words) };
out_bs[0..b_bits - 1].copy_from_bitslice(&b.as_bitslice()[0..b_bits - 1]);
out_bs[b_bits - 1..out_bits].copy_from_bitslice(&a.as_bitslice()[0..a_bits]);
unsafe { Ok(indirect_out.normalize_as_atom().as_noun()) }
}
#[cfg(test)]
@ -92,10 +101,18 @@ mod tests {
D(0x4000000000000000)
}
fn atom_64(stack: &mut NockStack) -> Noun {
A(stack, &ubig!(_0x8000000000000000))
}
fn atom_65(stack: &mut NockStack) -> Noun {
A(stack, &ubig!(_0x10000000000000000))
}
fn atom_66(stack: &mut NockStack) -> Noun {
A(stack, &ubig!(_0x20000000000000000))
}
fn pos_2(_stack: &mut NockStack) -> Noun {
D(2)
}
@ -127,10 +144,16 @@ mod tests {
#[test]
fn test_mas() {
let c = &mut init_context();
let a63 = atom_63(&mut c.stack);
let a64 = atom_64(&mut c.stack);
let a65 = atom_65(&mut c.stack);
let a66 = atom_66(&mut c.stack);
// Test invalid input
assert_jet_err(c, jet_mas, D(0), JetErr::Fail(Error::Deterministic(D(0))));
assert_jet_err(c, jet_mas, D(1), JetErr::Fail(Error::Deterministic(D(0))));
// Test direct
assert_jet(c, jet_mas, D(2), D(1));
assert_jet(c, jet_mas, D(3), D(1));
assert_jet(c, jet_mas, D(4), D(2));
@ -138,6 +161,16 @@ mod tests {
assert_jet(c, jet_mas, D(6), D(2));
assert_jet(c, jet_mas, D(7), D(3));
assert_jet(c, jet_mas, D(8), D(4));
// Test indirect
assert_jet(c, jet_mas, a64, a63);
assert_jet(c, jet_mas, a65, a64);
assert_jet(c, jet_mas, a66, a65);
// Test allocation
assert_jet_size(c, jet_mas, D(2), 1_usize);
assert_jet_size(c, jet_mas, a65, 1_usize);
assert_jet_size(c, jet_mas, a66, 2_usize);
}
#[test]
@ -174,5 +207,10 @@ mod tests {
&[atom_65, atom_65],
ubig!(_0x100000000000000000000000000000000),
);
// Test allocation
assert_common_jet_size(c, jet_peg, &[atom_65, atom_1], 2_usize);
assert_common_jet_size(c, jet_peg, &[atom_1, atom_65], 2_usize);
assert_common_jet_size(c, jet_peg, &[atom_65, atom_65], 3_usize);
}
}

View File

@ -3,6 +3,7 @@ extern crate num_derive;
extern crate lazy_static;
#[macro_use]
extern crate static_assertions;
pub mod hamt;
pub mod interpreter;
pub mod jets;
pub mod mem;
@ -11,9 +12,9 @@ pub mod newt;
pub mod noun;
pub mod serf;
//pub mod bytecode;
pub mod hamt;
pub mod serialization;
pub mod snapshot;
pub mod trace;
/** Introduce useful functions for debugging
*

View File

@ -104,4 +104,3 @@ fn main() -> io::Result<()> {
};
Ok(())
}

View File

@ -40,7 +40,7 @@ const FORWARDING_MASK: u64 = CELL_MASK;
/** Loobeans */
pub const YES: Noun = D(0);
pub const NO: Noun = D(1);
pub const NONE: Noun = unsafe { DirectAtom::new_unchecked(tas!(b"mormagic")).as_noun() };
pub const NONE: Noun = unsafe { DirectAtom::new_unchecked(tas!(b"MORMAGIC")).as_noun() };
#[cfg(feature = "check_acyclic")]
#[macro_export]
@ -464,6 +464,25 @@ impl IndirectAtom {
UBig::from_le_bytes_stack(stack, self.as_bytes())
}
pub unsafe fn as_u64(self) -> Result<u64> {
if self.size() == 1 {
Ok(*(self.data_pointer()))
} else {
Err(Error::NotRepresentable)
}
}
/** Produce a SoftFloat-compatible ordered pair of 64-bit words */
pub fn as_u64_pair(self) -> Result<[u64; 2]> {
if self.size() <= 2 {
let u128_array = &mut [0u64; 2];
u128_array.copy_from_slice(&(self.as_slice()[0..2]));
Ok(*u128_array)
} else {
Err(Error::NotRepresentable)
}
}
/** Ensure that the size does not contain any trailing 0 words */
pub unsafe fn normalize(&mut self) -> &Self {
let mut index = self.size() - 1;
@ -738,6 +757,26 @@ impl Atom {
}
}
pub fn as_u64(self) -> Result<u64> {
if self.is_direct() {
Ok(unsafe { self.direct.data() })
} else {
unsafe { self.indirect.as_u64() }
}
}
/** Produce a SoftFloat-compatible ordered pair of 64-bit words */
pub unsafe fn as_u64_pair(self) -> Result<[u64; 2]> {
if self.is_direct() {
let u128_array = &mut [0u64; 2];
u128_array[0] = self.as_direct()?.data();
u128_array[1] = 0x0_u64;
Ok(*u128_array)
} else {
unsafe { self.indirect.as_u64_pair() }
}
}
pub fn as_bitslice(&self) -> &BitSlice<u64, Lsb0> {
if self.is_indirect() {
unsafe { self.indirect.as_bitslice() }

View File

@ -1,30 +1,28 @@
use crate::hamt::Hamt;
use crate::interpreter;
use crate::interpreter::{inc, interpret, Error, TraceInfo};
use crate::interpreter::{inc, interpret, Error};
use crate::jets::cold::Cold;
use crate::jets::hot::Hot;
use crate::jets::list::util::{lent, zing};
use crate::jets::nock::util::mook;
use crate::jets::warm::Warm;
use crate::mem::NockStack;
use crate::mug::mug_u32;
use crate::mug::*;
use crate::newt::Newt;
use crate::noun::{Cell, Noun, Slots, D, T};
use crate::noun::{Atom, Cell, DirectAtom, Noun, Slots, D, T};
use crate::snapshot::double_jam::DoubleJam;
use crate::snapshot::Snapshot;
use crate::trace::*;
use ares_macros::tas;
use signal_hook;
use signal_hook::consts::SIGINT;
use std::fs::create_dir_all;
use std::io::{self, Write};
use std::io;
use std::path::PathBuf;
use std::result::Result;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::time::Instant;
use std::fs::File;
use json::object;
crate::gdb!();
@ -195,88 +193,26 @@ pub fn serf() -> io::Result<()> {
snap_path.push("chk");
create_dir_all(&snap_path)?;
let wag: u32 = std::env::args().nth(4).ok_or(io::Error::new(io::ErrorKind::Other, "no flag bitmap"))?.parse().or(Err(io::Error::new(io::ErrorKind::Other, "flag bitmap is not integer")))?;
let wag: u32 = std::env::args()
.nth(4)
.ok_or(io::Error::new(io::ErrorKind::Other, "no flag bitmap"))?
.parse()
.or(Err(io::Error::new(
io::ErrorKind::Other,
"flag bitmap is not integer",
)))?;
let trace_info =
if wag & FLAG_TRACE != 0 {
let mut trace_dir_path = pier_path.clone();
trace_dir_path.push(".urb");
trace_dir_path.push("put");
trace_dir_path.push("trace");
create_dir_all(&trace_dir_path)?;
let mut trace_idx = 0u32;
loop {
let mut trace_path = trace_dir_path.clone();
trace_path.push(format!("{}.json", trace_idx));
if trace_path.exists() {
trace_idx += 1;
} else {
let mut file = File::create(trace_path)?;
let process_start = Instant::now();
let pid = std::process::id();
file.write("[ ".as_bytes())?;
// write metadata to trace file
(object!{
name: "process_name",
ph: "M",
pid: pid,
args: object! { name: "urbit", },
}).write(&mut file)?;
file.write(",\n".as_bytes())?;
(object!{
name: "thread_name",
ph: "M",
pid: pid,
tid: 1,
args: object!{ name: "Event Processing", },
}).write(&mut file)?;
file.write(",\n".as_bytes())?;
(object!{
name: "thread_sort_index",
ph: "M",
pid: pid,
tid: 1,
args: object!{ sort_index: 1, },
}).write(&mut file)?;
file.write(",\n".as_bytes())?;
(object!{
name: "thread_name",
ph: "M",
pid: pid,
tid: 2,
args: object!{ name: "Nock", },
}).write(&mut file)?;
file.write(",\n".as_bytes())?;
(object!{
name: "thread_sort_index",
ph: "M",
pid: pid,
tid: 2,
args: object!{ sort_index: 2, },
}).write(&mut file)?;
file.write(",\n".as_bytes())?;
file.sync_data()?;
break Some(TraceInfo {
file,
pid,
process_start,
});
}
}
} else {
None
};
let mut trace_info = if wag & FLAG_TRACE != 0 {
create_trace_file(pier_path).ok()
} else {
None
};
if let Some(ref mut info) = trace_info.as_mut() {
if let Err(e) = write_metadata(info) {
eprintln!("\rError initializing trace file: {:?}", e);
trace_info = None;
}
}
let mut context = Context::new(&snap_path, trace_info);
context.ripe();
@ -307,9 +243,8 @@ pub fn serf() -> io::Result<()> {
context.live();
}
tas!(b"peek") => {
let sam = slot(writ, 7)?;
let res =
slam(&mut context, PEEK_AXIS, sam).expect("peek error handling unimplemented");
let ovo = slot(writ, 7)?;
let res = peek(&mut context, ovo);
context.peek_done(res);
}
tas!(b"play") => {
@ -354,6 +289,20 @@ fn slam(context: &mut Context, axis: u64, ovo: Noun) -> Result<Noun, Error> {
interpret(&mut context.nock_context, sub, fol)
}
fn peek(context: &mut Context, ovo: Noun) -> Noun {
if context.nock_context.trace_info.is_some() {
// XX: way too many cases in the input to pull the actual vane, care, and path out
let trace_name = "peek";
let start = Instant::now();
let slam_res = slam(context, PEEK_AXIS, ovo);
write_serf_trace_safe(&mut context.nock_context.trace_info, trace_name, start);
slam_res.expect("peek error handling unimplemented")
} else {
slam(context, PEEK_AXIS, ovo).expect("peek error handling unimplemented")
}
}
fn goof(context: &mut Context, traces: Noun) -> Noun {
let trace = zing(&mut context.nock_context.stack, traces).unwrap();
let tone = Cell::new(&mut context.nock_context.stack, D(2), trace);
@ -366,9 +315,25 @@ fn goof(context: &mut Context, traces: Noun) -> Noun {
T(&mut context.nock_context.stack, &[D(tas!(b"exit")), tang])
}
/** Run slam, process stack trace to tang if error */
fn soft(context: &mut Context, ovo: Noun) -> Result<Noun, Noun> {
match slam(context, POKE_AXIS, ovo) {
/** Run slam; process stack trace to tang if error.
* Generate tracing events, if JSON tracing enabled.
*/
fn soft(context: &mut Context, ovo: Noun, trace_name: Option<String>) -> Result<Noun, Noun> {
let slam_res = if context.nock_context.trace_info.is_some() {
let start = Instant::now();
let slam_res = slam(context, POKE_AXIS, ovo);
write_serf_trace_safe(
&mut context.nock_context.trace_info,
trace_name.as_ref().unwrap(),
start,
);
slam_res
} else {
slam(context, POKE_AXIS, ovo)
};
match slam_res {
Ok(res) => Ok(res),
Err(error) => match error {
Error::Deterministic(trace) | Error::NonDeterministic(trace) => {
@ -385,7 +350,18 @@ fn play_life(context: &mut Context, eve: Noun) {
let stack = &mut context.nock_context.stack;
let sub = T(stack, &[D(0), D(3)]);
let lyf = T(stack, &[D(2), sub, D(0), D(2)]);
match interpret(&mut context.nock_context, eve, lyf) {
let res = if context.nock_context.trace_info.is_some() {
let trace_name = "boot";
let start = Instant::now();
let boot_res = interpret(&mut context.nock_context, eve, lyf);
write_serf_trace_safe(&mut context.nock_context.trace_info, trace_name, start);
boot_res
} else {
interpret(&mut context.nock_context, eve, lyf)
};
match res {
Ok(gat) => {
let eved = lent(eve).expect("serf: play: boot event number failure") as u64;
let arvo = slot(gat, 7).expect("serf: play: lifecycle didn't return initial Arvo");
@ -399,7 +375,7 @@ fn play_life(context: &mut Context, eve: Noun) {
context.play_bail(goof);
}
Error::ScryBlocked(_) | Error::ScryCrashed(_) => {
panic!("serf: soft: .^ invalid outside of virtual Nock")
panic!("serf: play: .^ invalid outside of virtual Nock")
}
},
}
@ -409,7 +385,13 @@ fn play_list(context: &mut Context, mut lit: Noun) {
let mut eve = context.event_num;
while let Ok(cell) = lit.as_cell() {
let ovo = cell.head();
match soft(context, ovo) {
let trace_name = if context.nock_context.trace_info.is_some() {
Some(format!("play [{}]", eve))
} else {
None
};
match soft(context, ovo, trace_name) {
Ok(res) => {
let arvo = res
.as_cell()
@ -429,7 +411,21 @@ fn play_list(context: &mut Context, mut lit: Noun) {
}
fn work(context: &mut Context, job: Noun) {
match soft(context, job) {
let trace_name = if context.nock_context.trace_info.is_some() {
// XX: good luck making this safe AND rust idiomatic!
let wire = job.slot(6).expect("serf: work: job missing wire");
let vent = job
.slot(14)
.expect("serf: work: job missing event tag")
.as_atom()
.expect("serf: work: event tag not atom");
Some(work_trace_name(&mut context.nock_context.stack, wire, vent))
} else {
None
};
match soft(context, job, trace_name) {
Ok(res) => {
let cell = res.as_cell().expect("serf: work: +slam returned atom");
let fec = cell.head();
@ -452,21 +448,31 @@ fn work_swap(context: &mut Context, job: Noun, goof: Noun) {
clear_interrupt();
let stack = &mut context.nock_context.stack;
// crud = [+(now) [%$ %arvo ~] [%crud goof ovo]]
// crud ovo = [+(now) [%$ %arvo ~] [%crud goof ovo]]
let job_cell = job.as_cell().expect("serf: work: job not a cell");
let job_now = job_cell.head().as_atom().expect("serf: work: now not atom");
let now = inc(stack, job_now).as_noun();
let wire = T(stack, &[D(0), D(tas!(b"arvo")), D(0)]);
let crud = T(stack, &[now, wire, D(tas!(b"crud")), goof, job_cell.tail()]);
let crud = DirectAtom::new_panic(tas!(b"crud"));
let ovo = T(stack, &[now, wire, crud.as_noun(), goof, job_cell.tail()]);
let trace_name = if context.nock_context.trace_info.is_some() {
Some(work_trace_name(
&mut context.nock_context.stack,
wire,
crud.as_atom(),
))
} else {
None
};
match soft(context, crud) {
match soft(context, ovo, trace_name) {
Ok(res) => {
let cell = res.as_cell().expect("serf: work: crud +slam returned atom");
let fec = cell.head();
let eve = context.event_num;
context.event_update(eve + 1, cell.tail());
context.work_swap(crud, fec);
context.work_swap(ovo, fec);
}
Err(goof_crud) => {
work_bail(context, &[goof_crud, goof]);
@ -481,6 +487,31 @@ fn work_bail(context: &mut Context, goofs: &[Noun]) {
context.work_bail(lud);
}
fn work_trace_name(stack: &mut NockStack, wire: Noun, vent: Atom) -> String {
let wpc = path_to_cord(stack, wire);
let wpc_len = met3_usize(wpc);
let wpc_bytes = &wpc.as_bytes()[0..wpc_len];
let wpc_str = match std::str::from_utf8(wpc_bytes) {
Ok(valid) => valid,
Err(error) => {
let (valid, _) = wpc_bytes.split_at(error.valid_up_to());
unsafe { std::str::from_utf8_unchecked(valid) }
}
};
let vc_len = met3_usize(vent);
let vc_bytes = &vent.as_bytes()[0..vc_len];
let vc_str = match std::str::from_utf8(vc_bytes) {
Ok(valid) => valid,
Err(error) => {
let (valid, _) = vc_bytes.split_at(error.valid_up_to());
unsafe { std::str::from_utf8_unchecked(valid) }
}
};
format!("work [{} {}]", wpc_str, vc_str)
}
fn slot(noun: Noun, axis: u64) -> io::Result<Noun> {
noun.slot(axis)
.map_err(|_e| io::Error::new(io::ErrorKind::InvalidInput, "Bad axis"))

257
rust/ares/src/trace.rs Normal file
View File

@ -0,0 +1,257 @@
use crate::jets::bits::util::rap;
use crate::jets::form::util::scow;
use crate::mem::NockStack;
use crate::mug::met3_usize;
use crate::noun::{Atom, DirectAtom, IndirectAtom, Noun};
use ares_macros::tas;
use either::Either::*;
use json::object;
use std::fs::{create_dir_all, File};
use std::io::{Error, Write};
use std::path::PathBuf;
use std::result::Result;
use std::time::Instant;
crate::gdb!();
pub struct TraceInfo {
pub file: File,
pub pid: u32,
pub process_start: Instant,
}
pub struct TraceStack {
pub start: Instant,
pub path: Noun,
pub next: *const TraceStack,
}
pub fn create_trace_file(pier_path: PathBuf) -> Result<TraceInfo, Error> {
let mut trace_dir_path = pier_path.clone();
trace_dir_path.push(".urb");
trace_dir_path.push("put");
trace_dir_path.push("trace");
create_dir_all(&trace_dir_path)?;
let trace_path: PathBuf;
let mut trace_idx = 0u32;
loop {
let mut prospective_path = trace_dir_path.clone();
prospective_path.push(format!("{}.json", trace_idx));
if prospective_path.exists() {
trace_idx += 1;
} else {
trace_path = prospective_path.clone();
break;
}
}
let file = File::create(trace_path)?;
let process_start = Instant::now();
let pid = std::process::id();
Ok(TraceInfo {
file,
pid,
process_start,
})
}
/// Write metadata to trace file
pub fn write_metadata(info: &mut TraceInfo) -> Result<(), Error> {
info.file.write_all("[ ".as_bytes())?;
(object! {
name: "process_name",
ph: "M",
pid: info.pid,
args: object! { name: "urbit", },
})
.write(&mut info.file)?;
info.file.write_all(",\n".as_bytes())?;
(object! {
name: "thread_name",
ph: "M",
pid: info.pid,
tid: 1,
args: object!{ name: "Event Processing", },
})
.write(&mut info.file)?;
info.file.write_all(",\n".as_bytes())?;
(object! {
name: "thread_sort_index",
ph: "M",
pid: info.pid,
tid: 1,
args: object!{ sort_index: 1, },
})
.write(&mut info.file)?;
info.file.write_all(",\n".as_bytes())?;
info.file.sync_data()?;
Ok(())
}
/// Abort writing to trace file if an error is encountered.
///
/// This should result in a well-formed partial trace file.
pub fn write_serf_trace_safe(info: &mut Option<TraceInfo>, name: &str, start: Instant) {
if let Err(e) = write_serf_trace(info.as_mut().unwrap(), name, start) {
eprintln!("\rserf: error writing event trace to file: {:?}", e);
*info = None;
}
}
pub fn write_serf_trace(info: &mut TraceInfo, name: &str, start: Instant) -> Result<(), Error> {
let ts = start
.saturating_duration_since(info.process_start)
.as_micros() as f64;
let dur = Instant::now().saturating_duration_since(start).as_micros() as f64;
assert_no_alloc::permit_alloc(|| {
let obj = object! {
cat: "event",
name: name,
ph: "X",
pid: info.pid,
tid: 1,
ts: ts,
dur: dur,
};
obj.write(&mut info.file)
})?;
info.file.write_all(",\n".as_bytes())?;
Ok(())
}
pub unsafe fn write_nock_trace(
stack: &mut NockStack,
info: &mut TraceInfo,
mut trace_stack: *const TraceStack,
) -> Result<(), Error> {
let now = Instant::now();
while !trace_stack.is_null() {
let ts = (*trace_stack)
.start
.saturating_duration_since(info.process_start)
.as_micros() as f64;
let dur = now
.saturating_duration_since((*trace_stack).start)
.as_micros() as f64;
// Don't write out traces less than 33us
// (same threshhold used in vere)
if dur < 33.0 {
trace_stack = (*trace_stack).next;
continue;
}
let pc = path_to_cord(stack, (*trace_stack).path);
let pc_len = met3_usize(pc);
let pc_bytes = &pc.as_bytes()[0..pc_len];
let pc_str = match std::str::from_utf8(pc_bytes) {
Ok(valid) => valid,
Err(error) => {
let (valid, _) = pc_bytes.split_at(error.valid_up_to());
unsafe { std::str::from_utf8_unchecked(valid) }
}
};
assert_no_alloc::permit_alloc(|| {
let obj = object! {
cat: "nock",
name: pc_str,
ph: "X",
pid: info.pid,
tid: 1,
ts: ts,
dur: dur,
};
obj.write(&mut info.file)
})?;
info.file.write_all(",\n".as_bytes())?;
trace_stack = (*trace_stack).next;
}
info.file.sync_data()?;
Ok(())
}
// XX: Need Rust string interpolation helper that doesn't allocate
pub fn path_to_cord(stack: &mut NockStack, path: Noun) -> Atom {
let mut cursor = path;
let mut length = 0usize;
// count how much size we need
while let Ok(c) = cursor.as_cell() {
unsafe {
match c.head().as_either_atom_cell() {
Left(a) => {
length += 1;
length += met3_usize(a);
}
Right(ch) => {
if let Ok(nm) = ch.head().as_atom() {
if let Ok(kv) = ch.tail().as_atom() {
let kvt = scow(stack, DirectAtom::new_unchecked(tas!(b"ud")), kv)
.expect("scow should succeed in path_to_cord");
let kvc =
rap(stack, 3, kvt).expect("rap should succeed in path_to_cord");
length += 1;
length += met3_usize(nm);
length += met3_usize(kvc);
}
}
}
}
}
cursor = c.tail();
}
// reset cursor, then actually write the path
cursor = path;
let mut idx = 0;
let (mut deres, buffer) = unsafe { IndirectAtom::new_raw_mut_bytes(stack, length) };
let slash = (b"/")[0];
while let Ok(c) = cursor.as_cell() {
unsafe {
match c.head().as_either_atom_cell() {
Left(a) => {
buffer[idx] = slash;
idx += 1;
let bytelen = met3_usize(a);
buffer[idx..idx + bytelen].copy_from_slice(&a.as_bytes()[0..bytelen]);
idx += bytelen;
}
Right(ch) => {
if let Ok(nm) = ch.head().as_atom() {
if let Ok(kv) = ch.tail().as_atom() {
let kvt = scow(stack, DirectAtom::new_unchecked(tas!(b"ud")), kv)
.expect("scow should succeed in path_to_cord");
let kvc =
rap(stack, 3, kvt).expect("rap should succeed in path_to_cord");
buffer[idx] = slash;
idx += 1;
let nmlen = met3_usize(nm);
buffer[idx..idx + nmlen].copy_from_slice(&nm.as_bytes()[0..nmlen]);
idx += nmlen;
let kvclen = met3_usize(kvc);
buffer[idx..idx + kvclen].copy_from_slice(&kvc.as_bytes()[0..kvclen]);
idx += kvclen;
}
}
}
}
}
cursor = c.tail();
}
unsafe { deres.normalize_as_atom() }
}