snag and roll jets (#269)

* snag

* roll and call site caching logic

* site: propagate errors

* interpreter: use flog for string interpolation

* yak shaving, reorder jets alphabetically

* jets: flip sample in snag tests
This commit is contained in:
~litlep-nibbyt 2024-09-20 20:47:43 -04:00 committed by GitHub
parent 74fad40bdc
commit 299292a876
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 205 additions and 132 deletions

View File

@ -12,7 +12,7 @@ sword_guard = { path = "../sword_guard" }
sword_crypto = { path = "../sword_crypto" }
sword_macros = { path = "../sword_macros" }
sword_pma = { path = "../sword_pma" }
assert_no_alloc = { path = "../assert_no_alloc" }
assert_no_alloc = { path = "../assert_no_alloc", features=["warn_debug"]}
murmur3 = { path = "../murmur3" }
bitvec = "1.0.0"
criterion = "0.4"

View File

@ -1563,9 +1563,7 @@ mod hint {
slogger.slog(stack, 0, cell.head());
list = cell.tail();
} else {
let stack = &mut context.stack;
let tape = tape(stack, "serf: %hela: list ends without ~");
slog_leaf(stack, slogger, tape);
flog!(context, "serf: %hela: list ends without ~");
break;
}
}
@ -1609,37 +1607,36 @@ mod hint {
tas!(b"fast") => {
if !cfg!(feature = "sham_hints") {
if let Some(clue) = hint {
let cold_res: cold::Result = {
let chum = clue.slot(2).ok()?;
let mut parent = clue.slot(6).ok()?;
loop {
if let Ok(parent_cell) = parent.as_cell() {
if unsafe { parent_cell.head().raw_equals(D(11)) } {
match parent.slot(7) {
Ok(noun) => {
parent = noun;
}
Err(_) => {
return None;
}
let chum = clue.slot(2).ok()?;
let mut parent = clue.slot(6).ok()?;
loop {
if let Ok(parent_cell) = parent.as_cell() {
if unsafe { parent_cell.head().raw_equals(D(11)) } {
match parent.slot(7) {
Ok(noun) => {
parent = noun;
}
Err(_) => {
return None;
}
} else {
break;
}
} else {
return None;
break;
}
} else {
return None;
}
let parent_formula_op = parent.slot(2).ok()?.atom()?.direct()?;
let parent_formula_ax = parent.slot(3).ok()?.atom()?;
}
let parent_formula_op = parent.slot(2).ok()?.atom()?.direct()?;
let parent_formula_ax = parent.slot(3).ok()?.atom()?;
let cold_res: cold::Result = {
if parent_formula_op.data() == 1 {
if parent_formula_ax.direct()?.data() == 0 {
cold.register(stack, res, parent_formula_ax, chum)
} else {
// XX: Need better message in slog; need better slogging tools
// format!("invalid root parent axis: {} {}", chum, parent_formula_ax)
// XX: flog! is ideal, but it runs afoul of the borrow checker
// flog!(context, "invalid root parent formula: {} {}", chum, parent);
let tape = tape(
stack,
"serf: cold: register: invalid root parent axis",
@ -1655,25 +1652,15 @@ mod hint {
match cold_res {
Ok(true) => context.warm = Warm::init(stack, cold, hot),
Err(cold::Error::NoParent) => {
// XX: Need better message in slog; need better slogging tools
// format!("could not find parent battery at given axis: {} {}", chum, parent_formula_ax)
let tape = tape(
stack,
"serf: cold: register: could not find parent battery at given axis",
);
slog_leaf(stack, slogger, tape);
flog!(context, "serf: cold: register: could not match parent battery at given axis: {} {}", chum, parent_formula_ax);
}
Err(cold::Error::BadNock) => {
// XX: Need better message in slog; need better slogging tools
// format!("bad clue formula: {}", clue)
let tape = tape(stack, "serf: cold: register: bad clue formula");
slog_leaf(stack, slogger, tape);
flog!(context, "serf: cold: register: bad clue formula: {}", clue);
}
_ => {}
}
} else {
let tape = tape(stack, "serf: cold: register: no clue for %fast");
slog_leaf(stack, slogger, tape);
flog!(context, "serf: cold: register: no clue for %fast");
}
}
}

View File

@ -94,6 +94,16 @@ pub const URBIT_HOT_STATE: &[HotEntry] = &[
1,
jet_lent,
),
(
&[K_139, Left(b"one"), Left(b"two"), Left(b"roll")],
1,
jet_roll,
),
(
&[K_139, Left(b"one"), Left(b"two"), Left(b"snag")],
1,
jet_snag,
),
(
&[K_139, Left(b"one"), Left(b"two"), Left(b"snip")],
1,

View File

@ -1,11 +1,10 @@
/** Text processing jets
*/
use crate::interpreter::{interpret, Context};
use crate::interpreter::Context;
use crate::jets::util::{slot, BAIL_FAIL};
use crate::jets::Result;
use crate::noun::{Cell, Noun, D, T};
use bitvec::order::Lsb0;
use bitvec::slice::BitSlice;
use crate::site::{site_slam, Site};
crate::gdb!();
@ -19,11 +18,71 @@ pub fn jet_lent(_context: &mut Context, subject: Noun) -> Result {
util::lent(list).map(|x| D(x as u64))
}
pub fn jet_roll(context: &mut Context, subject: Noun) -> Result {
let sample = slot(subject, 6)?;
let mut list = slot(sample, 2)?;
let mut gate = slot(sample, 3)?;
let mut prod = slot(gate, 13)?;
let site = Site::new(context, &mut gate);
loop {
if let Ok(list_cell) = list.as_cell() {
list = list_cell.tail();
let sam = T(&mut context.stack, &[list_cell.head(), prod]);
prod = site_slam(context, &site, sam)?;
} else {
if unsafe { !list.raw_equals(D(0)) } {
return Err(BAIL_FAIL);
}
return Ok(prod);
}
}
}
pub fn jet_snag(_context: &mut Context, subject: Noun) -> Result {
let sam = slot(subject, 6)?;
let index = slot(sam, 2)?;
let list = slot(sam, 3)?;
util::snag(list, index)
}
pub fn jet_snip(context: &mut Context, subject: Noun) -> Result {
let list = slot(subject, 6)?;
util::snip(&mut context.stack, list)
}
pub fn jet_turn(context: &mut Context, subject: Noun) -> Result {
let sample = slot(subject, 6)?;
let mut list = slot(sample, 2)?;
let mut gate = slot(sample, 3)?;
let mut res = D(0);
let mut dest: *mut Noun = &mut res; // Mutable pointer because we cannot guarantee initialized
// Since the gate doesn't change, we can do a single jet check and use that through the whole
// loop
let site = Site::new(context, &mut gate);
loop {
if let Ok(list_cell) = list.as_cell() {
list = list_cell.tail();
unsafe {
let (new_cell, new_mem) = Cell::new_raw_mut(&mut context.stack);
(*new_mem).head = site_slam(context, &site, list_cell.head())?;
*dest = new_cell.as_noun();
dest = &mut (*new_mem).tail;
}
} else {
if unsafe { !list.raw_equals(D(0)) } {
return Err(BAIL_FAIL);
}
unsafe {
*dest = D(0);
};
return Ok(res);
}
}
}
pub fn jet_zing(context: &mut Context, subject: Noun) -> Result {
let list = slot(subject, 6)?;
let stack = &mut context.stack;
@ -31,97 +90,6 @@ pub fn jet_zing(context: &mut Context, subject: Noun) -> Result {
util::zing(stack, list)
}
pub fn jet_turn(context: &mut Context, subject: Noun) -> Result {
let sample = slot(subject, 6)?;
let mut list = slot(sample, 2)?;
let mut gate = slot(sample, 3)?;
let mut gate_battery = slot(gate, 2)?;
let gate_context = slot(gate, 7)?;
let mut res = D(0);
let mut dest: *mut Noun = &mut res; // Mutable pointer because we cannot guarantee initialized
// Since the gate doesn't change, we can do a single jet check and use that through the whole
// loop
if let Some((jet, _path)) = context
.warm
.find_jet(&mut context.stack, &mut gate, &mut gate_battery)
.filter(|(_jet, mut path)| {
// check that 7 is a prefix of the parent battery axis,
// to ensure that the sample (axis 6) is not part of the jet match.
//
// XX TODO this check is pessimized since there could be multiple ways to match the
// jet and we only actually match one of them, but we check all of them and run
// unjetted if any have an axis outside 7.
let axis_7_bits: &BitSlice<u64, Lsb0> = BitSlice::from_element(&7u64);
let batteries_list = context.cold.find(&mut context.stack, &mut path);
let mut ret = true;
for mut batteries in batteries_list {
if let Some((_battery, parent_axis)) = batteries.next() {
let parent_axis_prefix_bits = &parent_axis.as_bitslice()[0..3];
if parent_axis_prefix_bits == axis_7_bits {
continue;
} else {
ret = false;
break;
}
} else {
ret = false;
break;
}
}
ret
})
{
loop {
if let Ok(list_cell) = list.as_cell() {
list = list_cell.tail();
let element_subject = T(
&mut context.stack,
&[gate_battery, list_cell.head(), gate_context],
);
unsafe {
let (new_cell, new_mem) = Cell::new_raw_mut(&mut context.stack);
(*new_mem).head = jet(context, element_subject)?;
*dest = new_cell.as_noun();
dest = &mut (*new_mem).tail;
}
} else {
if unsafe { !list.raw_equals(D(0)) } {
return Err(BAIL_FAIL);
}
unsafe {
*dest = D(0);
};
return Ok(res);
}
}
} else {
loop {
if let Ok(list_cell) = list.as_cell() {
list = list_cell.tail();
let element_subject = T(
&mut context.stack,
&[gate_battery, list_cell.head(), gate_context],
);
unsafe {
let (new_cell, new_mem) = Cell::new_raw_mut(&mut context.stack);
(*new_mem).head = interpret(context, element_subject, gate_battery)?;
*dest = new_cell.as_noun();
dest = &mut (*new_mem).tail;
}
} else {
if unsafe { !list.raw_equals(D(0)) } {
return Err(BAIL_FAIL);
}
unsafe {
*dest = D(0);
};
return Ok(res);
}
}
}
}
pub mod util {
use crate::jets::util::BAIL_EXIT;
use crate::jets::{JetErr, Result};
@ -165,6 +133,22 @@ pub mod util {
Ok(len)
}
pub fn snag(tape: Noun, index: Noun) -> Result {
let mut list = tape;
let mut idx = index.as_atom()?.as_u64()? as usize;
loop {
if unsafe { list.raw_equals(D(0)) } {
return Err(BAIL_EXIT);
}
let cell = list.as_cell()?;
if idx == 0 {
return Ok(cell.head());
}
idx -= 1;
list = cell.tail();
}
}
pub fn snip(stack: &mut NockStack, tape: Noun) -> Result {
let mut ret = D(0);
let mut dest = &mut ret as *mut Noun;
@ -283,6 +267,24 @@ mod tests {
assert_jet_err(c, jet_lent, sam, BAIL_EXIT);
}
#[test]
fn test_snag() {
let c = &mut init_context();
let list1 = T(&mut c.stack, &[D(1), D(2), D(3), D(0)]);
let sam = T(&mut c.stack, &[D(1), list1]);
assert_jet(c, jet_snag, sam, D(2));
let list2 = T(&mut c.stack, &[D(1), D(0)]);
let sam = T(&mut c.stack, &[D(0), list2]);
assert_jet(c, jet_snag, sam, D(1));
let sam = T(&mut c.stack, &[D(3), list1]);
assert_jet_err(c, jet_snag, sam, BAIL_EXIT);
let sam = T(&mut c.stack, &[D(0), D(0)]);
assert_jet_err(c, jet_snag, sam, BAIL_EXIT);
}
#[test]
fn test_snip() {
let c = &mut init_context();

View File

@ -13,6 +13,7 @@ pub mod mug;
pub mod newt;
pub mod noun;
pub mod serf;
pub mod site;
//pub mod bytecode;
pub mod persist;
pub mod serialization;

73
rust/sword/src/site.rs Normal file
View File

@ -0,0 +1,73 @@
/** Call site of a kick (Nock 9), used to cache call targets. */
use bitvec::order::Lsb0;
use bitvec::slice::BitSlice;
use crate::interpreter::{interpret, Context};
use crate::jets::util::slot;
use crate::jets::{Jet, JetErr};
use crate::noun::{Noun, D, T};
/// Return Err if the computation crashed or should punt to Nock
pub type Result = std::result::Result<Noun, JetErr>;
pub struct Site {
pub battery: Noun, // battery
pub context: Noun, // context
pub jet: Option<Jet>, // jet driver
pub path: Noun, // label
}
impl Site {
/// Prepare a locally cached gate to call repeatedly.
pub fn new(ctx: &mut Context, core: &mut Noun) -> Site {
let mut battery = slot(*core, 2).unwrap();
let context = slot(*core, 7).unwrap();
let warm_result = ctx
.warm
.find_jet(&mut ctx.stack, core, &mut battery)
.filter(|(_jet, mut path)| {
// check that 7 is a prefix of the parent battery axis,
// to ensure that the sample (axis 6) is not part of the jet match.
//
// XX TODO this check is pessimized since there could be multiple ways to match the
// jet and we only actually match one of them, but we check all of them and run
// unjetted if any have an axis outside 7.
let axis_7_bits: &BitSlice<u64, Lsb0> = BitSlice::from_element(&7u64);
let batteries_list = ctx.cold.find(&mut ctx.stack, &mut path);
let mut ret = true;
for mut batteries in batteries_list {
if let Some((_battery, parent_axis)) = batteries.next() {
let parent_axis_prefix_bits = &parent_axis.as_bitslice()[0..3];
if parent_axis_prefix_bits == axis_7_bits {
continue;
} else {
ret = false;
break;
}
} else {
ret = false;
break;
}
}
ret
});
Site {
battery,
context,
jet: warm_result.map(|(jet, _)| jet),
path: warm_result.map(|(_, path)| path).unwrap_or(D(0)),
}
}
}
/// Slam a cached call site.
pub fn site_slam(ctx: &mut Context, site: &Site, sample: Noun) -> Result {
let subject = T(&mut ctx.stack, &[site.battery, sample, site.context]);
if site.jet.is_some() {
let jet = site.jet.unwrap();
jet(ctx, subject)
} else {
Ok(interpret(ctx, subject, site.battery)?)
}
}