Skip to content

Commit

Permalink
chore: handle emitted pointers on the REPL (#1201)
Browse files Browse the repository at this point in the history
* Remove inner printing from the interpretation of LEM's `Op::Emit`
* Receive and handle emitted pointers in threads spawned in the REPL
* Refactor and document the REPL's evaluation functions
  • Loading branch information
arthurpaulino authored Mar 8, 2024
1 parent b7aa4fe commit 26efa6e
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 60 deletions.
40 changes: 20 additions & 20 deletions src/cli/repl/meta_cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ where
let bindings = repl.store.list(vec![binding]);
let current_env_call = repl.store.list(vec![current_env]);
let expanded = repl.store.list(vec![l, bindings, current_env_call]);
let (expanded_io, ..) = repl.eval_expr(expanded)?;
let expanded_io = repl.eval_expr(expanded)?;
repl.env = expanded_io[0];
println!("{new_name}");
Ok(())
Expand Down Expand Up @@ -117,7 +117,7 @@ where
let bindings = repl.store.list(vec![binding]);
let current_env_call = repl.store.list(vec![current_env]);
let expanded = repl.store.list(vec![l, bindings, current_env_call]);
let (expanded_io, ..) = repl.eval_expr(expanded)?;
let expanded_io = repl.eval_expr(expanded)?;
repl.env = expanded_io[0];
println!("{new_name}");
Ok(())
Expand All @@ -132,7 +132,7 @@ where
example: &["!(assert t)", "!(assert (eq 3 (+ 1 2)))"],
run: |repl, args, _path| {
let first = repl.peek1(args)?;
let (first_io, ..) = repl.eval_expr(first)?;
let first_io = repl.eval_expr(first)?;
if first_io[0].is_nil() {
eprintln!(
"`assert` failed. {} evaluates to nil",
Expand All @@ -152,10 +152,10 @@ where
example: &["!(assert-eq 3 (+ 1 2))"],
run: |repl, args, _path| {
let (first, second) = repl.peek2(args)?;
let (first_io, ..) = repl
let first_io = repl
.eval_expr(first)
.with_context(|| "evaluating first arg")?;
let (second_io, ..) = repl
let second_io = repl
.eval_expr(second)
.with_context(|| "evaluating second arg")?;
let (first_io_expr, second_io_expr) = (&first_io[0], &second_io[0]);
Expand Down Expand Up @@ -188,14 +188,14 @@ where
],
run: |repl, args, _path| {
let (first, second) = repl.peek2(args)?;
let (first_io, ..) = repl
let first_io = repl
.eval_expr(first)
.with_context(|| "evaluating first arg")?;
let Some((expected_emitted, None)) = repl.store.fetch_list(&first_io[0]) else {
bail!("Expectation must be a list")
};
let (.., emitted) = repl
.eval_expr(second)
let emitted = repl
.eval_expr_collecting_emitted(second)
.with_context(|| "evaluating second arg")?;
let (num_expected_emitted, num_emitted) = (expected_emitted.len(), emitted.len());
if num_expected_emitted != num_emitted {
Expand Down Expand Up @@ -224,8 +224,8 @@ where
example: &["!(assert-error (1 1))"],
run: |repl, args, _path| {
let first = repl.peek1(args)?;
let (first_io, ..) = repl.eval_expr_allowing_error_continuation(first)?;
if first_io[2].tag() != &Tag::Cont(ContTag::Error) {
let first_io = repl.eval_expr_allowing_error_continuation(first)?;
if !matches!(first_io[2].tag(), Tag::Cont(ContTag::Error)) {
eprintln!(
"`assert-error` failed. {} doesn't result on evaluation error.",
first.fmt_to_string(&repl.store, &repl.state.borrow())
Expand All @@ -250,7 +250,7 @@ where
],
run: |repl, args, _path| {
let first = repl.peek1(args)?;
let (first_io, ..) = repl.eval_expr(first)?;
let first_io = repl.eval_expr(first)?;
repl.hide(F::NON_HIDING_COMMITMENT_SECRET, first_io[0])
}
};
Expand All @@ -267,10 +267,10 @@ where
],
run: |repl, args, _path| {
let (first, second) = repl.peek2(args)?;
let (first_io, ..) = repl
let first_io = repl
.eval_expr(first)
.with_context(|| "evaluating first arg")?;
let (second_io, ..) = repl
let second_io = repl
.eval_expr(second)
.with_context(|| "evaluating second arg")?;
let (Tag::Expr(ExprTag::Num), RawPtr::Atom(secret)) = first_io[0].parts() else {
Expand Down Expand Up @@ -337,7 +337,7 @@ where
example: &["!(set-env '((a . 1) (b . 2)))", "a"],
run: |repl, args, _path| {
let first = repl.peek1(args)?;
let (first_io, ..) = repl.eval_expr(first)?;
let first_io = repl.eval_expr(first)?;
let env = first_io[0];
if *env.tag() != Tag::Expr(ExprTag::Env) {
return Err(anyhow!("Value must be an environment"));
Expand Down Expand Up @@ -653,7 +653,7 @@ where
run: |repl, args, _path| {
let (expr, path) = repl.peek2(args)?;
let path = get_path(repl, &path)?;
let (io, ..) = repl
let io = repl
.eval_expr(expr)
.with_context(|| "evaluating predicate")?;
let mut z_dag = ZDag::default();
Expand Down Expand Up @@ -736,7 +736,7 @@ where
}

let lambda = repl.store.list(vec![repl.store.intern_lurk_symbol("lambda"), vars, body]);
let (io, ..) = repl.eval_expr_with_env(lambda, repl.store.intern_empty_env())?;
let io = repl.eval_expr_with_env(lambda, repl.store.intern_empty_env())?;
let fun = io[0];
if !fun.is_fun() {
bail!(
Expand Down Expand Up @@ -802,7 +802,7 @@ where
/// * If the backend is not a string or has invalid value
/// * If the reduction count is not a number or can't be converted to `u64`
fn get_fun_backend_and_rc(repl: &Repl<F, C>, ptcl: Ptr) -> Result<(Ptr, Backend, usize)> {
let (io, ..) = repl
let io = repl
.eval_expr(ptcl)
.with_context(|| "evaluating protocol")?;
let ptcl = &io[0];
Expand Down Expand Up @@ -862,7 +862,7 @@ where
.store
.list(vec![*repl.get_apply_fn(), fun, quoted_args]);

let (io, ..) = repl
let io = repl
.eval_expr_with_env(apply_call, repl.store.intern_empty_env())
.with_context(|| "evaluating protocol function call")?;

Expand Down Expand Up @@ -896,7 +896,7 @@ where
fn post_verify_check(repl: &Repl<F, C>, post_verify: Ptr) -> Result<()> {
if !post_verify.is_nil() {
let call = repl.store.list(vec![post_verify]);
let (io, ..) = repl
let io = repl
.eval_expr_with_env(call, repl.store.intern_empty_env())
.with_context(|| "evaluating post-verify call")?;
if io[0].is_nil() {
Expand Down Expand Up @@ -969,7 +969,7 @@ where

let mut args_vec_evaled = Vec::with_capacity(args_vec.len());
for a in args_vec {
let (io, ..) = repl.eval_expr(a)?;
let io = repl.eval_expr(a)?;
args_vec_evaled.push(io[0]);
}

Expand Down
119 changes: 87 additions & 32 deletions src/cli/repl/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,20 @@ use rustyline::{
};
use rustyline_derive::{Completer, Helper, Highlighter, Hinter};
use serde::{de::DeserializeOwned, Serialize};
use std::{cell::OnceCell, collections::HashMap, fs::read_to_string, io::Write, sync::Arc};
use std::{
cell::OnceCell,
collections::HashMap,
fs::read_to_string,
io::Write,
sync::{Arc, Mutex},
thread,
time::Duration,
};
use tracing::info;

use crate::{
coprocessor::Coprocessor,
dual_channel::{dummy_terminal, pair_terminals},
dual_channel::pair_terminals,
field::LurkField,
lang::Lang,
lem::{
Expand Down Expand Up @@ -80,7 +88,7 @@ impl Evaluation {

#[allow(dead_code)]
pub(crate) struct Repl<F: LurkField, C: Coprocessor<F> + Serialize + DeserializeOwned> {
store: Store<F>,
store: Arc<Store<F>>,
state: StateRcCell,
lang: Arc<Lang<F, C>>,
lurk_step: Func,
Expand Down Expand Up @@ -195,7 +203,7 @@ where
let lurk_step = make_eval_step_from_config(&eval_config);
let cprocs = make_cprocs_funcs_from_lang(&lang);
Repl {
store,
store: Arc::new(store),
state: State::init_lurk_state().rccell(),
lang: Arc::new(lang),
lurk_step,
Expand Down Expand Up @@ -229,7 +237,7 @@ where
apply)",
)
.unwrap();
let (io, ..) = self
let io = self
.eval_expr_with_env(ptr, self.store.intern_empty_env())
.unwrap();
io[0]
Expand Down Expand Up @@ -427,22 +435,45 @@ where
}
}

fn eval_expr_with_env(&self, expr: Ptr, env: Ptr) -> Result<(Vec<Ptr>, usize, Vec<Ptr>)> {
#[inline]
fn eval_expr(&self, expr: Ptr) -> Result<Vec<Ptr>> {
self.eval_expr_with_env(expr, self.env)
}

/// Evaluates an expression with an environment while printing emitted pointers
/// and then returns the CEK IO and the number of iterations
fn eval_expr_with_env_printing_emitted(
&self,
expr: Ptr,
env: Ptr,
) -> Result<(Vec<Ptr>, usize)> {
let (t1, t2) = pair_terminals::<Ptr>();
let (ptrs, iterations) = evaluate_simple_with_env::<F, C>(
let store_clone = self.store.clone();
thread::spawn(move || {
t2.iter()
.for_each(|ptr| println!("{}", ptr.fmt_to_string_simple(&store_clone)));
});
let (io, iterations) = evaluate_simple_with_env::<F, C>(
Some(self.lang_setup()),
expr,
env,
&self.store,
self.limit,
&t1,
)?;
let emitted = t2.collect();
match ptrs[2].tag() {
Tag::Cont(ContTag::Terminal) => Ok((ptrs, iterations, emitted)),
t => {
thread::sleep(Duration::from_millis(10)); // wait for last t2 iteration
Ok((io, iterations))
}

/// Evaluates an expression with an environment while printing emitted pointers
/// and then returns the CEK IO if the final continuation is terminal
fn eval_expr_with_env(&self, expr: Ptr, env: Ptr) -> Result<Vec<Ptr>> {
let (io, iterations) = self.eval_expr_with_env_printing_emitted(expr, env)?;
match io[2].tag() {
Tag::Cont(ContTag::Terminal) => Ok(io),
tag => {
let iterations_display = Self::pretty_iterations_display(iterations);
if t == &Tag::Cont(ContTag::Error) {
if matches!(tag, Tag::Cont(ContTag::Error)) {
bail!("Evaluation encountered an error after {iterations_display}")
} else {
bail!("Limit reached after {iterations_display}")
Expand All @@ -451,27 +482,46 @@ where
}
}

#[inline]
fn eval_expr(&self, expr: Ptr) -> Result<(Vec<Ptr>, usize, Vec<Ptr>)> {
self.eval_expr_with_env(expr, self.env)
/// Evaluates an expression with the REPL's environment while printing emitted
/// pointers and then returns the CEK IO if the final continuation is terminal
/// or error
fn eval_expr_allowing_error_continuation(&self, expr: Ptr) -> Result<Vec<Ptr>> {
let (ptrs, iterations) = self.eval_expr_with_env_printing_emitted(expr, self.env)?;
if matches!(ptrs[2].tag(), Tag::Cont(ContTag::Terminal | ContTag::Error)) {
Ok(ptrs)
} else {
bail!(
"Limit reached after {}",
Self::pretty_iterations_display(iterations)
)
}
}

fn eval_expr_allowing_error_continuation(
&mut self,
expr_ptr: Ptr,
) -> Result<(Vec<Ptr>, usize, Vec<Ptr>)> {
/// Evaluates an expression with the REPL's environment while printing emitted
/// pointers and collecting them in a vector. Then returns the emitted pointers
/// if the final continuation is terminal or error
fn eval_expr_collecting_emitted(&self, expr: Ptr) -> Result<Vec<Ptr>> {
let (t1, t2) = pair_terminals::<Ptr>();
let store_clone = self.store.clone();
let emitted = Arc::new(Mutex::new(vec![]));
let emitted_clone = emitted.clone();
thread::spawn(move || {
for ptr in t2.iter() {
println!("{}", ptr.fmt_to_string_simple(&store_clone));
emitted_clone.lock().unwrap().push(ptr);
}
});
let (ptrs, iterations) = evaluate_simple_with_env::<F, C>(
Some(self.lang_setup()),
expr_ptr,
expr,
self.env,
&self.store,
self.limit,
&t1,
)?;
let emitted = t2.collect();
thread::sleep(Duration::from_millis(10)); // wait for last t2 iteration
if matches!(ptrs[2].tag(), Tag::Cont(ContTag::Terminal | ContTag::Error)) {
Ok((ptrs, iterations, emitted))
Ok(emitted.lock().unwrap().to_owned())
} else {
bail!(
"Limit reached after {}",
Expand All @@ -480,23 +530,28 @@ where
}
}

fn eval_expr_and_memoize(&mut self, expr_ptr: Ptr) -> Result<(Vec<Ptr>, usize)> {
/// Evaluates an expression with the REPL's environment while printing emitted
/// pointers and then memoizes the frames and returns the CEK IO and the number
/// of iterations
fn eval_expr_and_memoize(&mut self, expr: Ptr) -> Result<(Vec<Ptr>, usize)> {
let (t1, t2) = pair_terminals::<Ptr>();
let store_clone = self.store.clone();
thread::spawn(move || {
t2.iter()
.for_each(|ptr| println!("{}", ptr.fmt_to_string_simple(&store_clone)));
});
let frames = evaluate_with_env::<F, C>(
Some(self.lang_setup()),
expr_ptr,
expr,
self.env,
&self.store,
self.limit,
&dummy_terminal(),
&t1,
)?;
thread::sleep(Duration::from_millis(10)); // wait for last t2 iteration
let iterations = frames.len();

let Some(last_frames) = frames.last() else {
// TODO: better error decs
bail!("Frames is empty");
};

let output = last_frames.output.clone();
let output = frames.last().expect("frames can't be empty").output.clone();
self.evaluation = Some(Evaluation { frames, iterations });
Ok((output, iterations))
}
Expand All @@ -506,7 +561,7 @@ where
/// # Errors
/// Errors when `hash_expr` doesn't reduce to a Num or Comm pointer
fn get_comm_hash(&mut self, hash_expr: Ptr) -> Result<&F> {
let (io, ..) = self.eval_expr(hash_expr)?;
let io = self.eval_expr(hash_expr)?;
let (Tag::Expr(ExprTag::Num | ExprTag::Comm), RawPtr::Atom(hash_idx)) = io[0].parts()
else {
bail!("Commitment hash expression must reduce to a Num or Comm pointer")
Expand Down
10 changes: 9 additions & 1 deletion src/dual_channel.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
#![deny(missing_docs)]

//! This module implements `ChannelTerminal`, meant to be used in pairs of its
//! instances with crossed `Sender`s and `Receiver` from `mpsc::channel`. This
//! crossing is performed in `pair_terminals`. The idea is that one terminal can
//! send/receive messages to/from the other.
use anyhow::{anyhow, Result};
use std::sync::mpsc::{channel, Receiver, Sender};
use std::sync::mpsc::{channel, Iter, Receiver, Sender};

/// Holds a `Sender` and a `Receiver` which are not expected to be paired with
/// each other
Expand Down Expand Up @@ -33,6 +35,12 @@ impl<T> ChannelTerminal<T> {
pub fn collect(&self) -> Vec<T> {
self.receiver.try_iter().collect()
}

#[inline]
/// Returns a thread-blocking iterator for the received messages
pub fn iter(&self) -> Iter<'_, T> {
self.receiver.iter()
}
}

/// Creates a pair of `ChannelTerminal` with crossed senders and receivers such
Expand Down
Loading

1 comment on commit 26efa6e

@github-actions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Benchmarks

Table of Contents

Overview

This benchmark report shows the Fibonacci GPU benchmark.
NVIDIA L4
Intel(R) Xeon(R) CPU @ 2.20GHz
32 vCPUs
125 GB RAM
Workflow run: https://github.com/lurk-lab/lurk-rs/actions/runs/8206742419

Benchmark Results

LEM Fibonacci Prove - rc = 100

ref=b7aa4fe12f1185c12d00c88053370feccbb218bd ref=26efa6e2916bb319d2fec97f84be5a19d72a7d31
num-100 1.45 s (✅ 1.00x) 1.45 s (✅ 1.00x slower)
num-200 2.77 s (✅ 1.00x) 2.78 s (✅ 1.00x slower)

LEM Fibonacci Prove - rc = 600

ref=b7aa4fe12f1185c12d00c88053370feccbb218bd ref=26efa6e2916bb319d2fec97f84be5a19d72a7d31
num-100 1.85 s (✅ 1.00x) 1.84 s (✅ 1.01x faster)
num-200 3.05 s (✅ 1.00x) 3.06 s (✅ 1.00x slower)

Made with criterion-table

Please sign in to comment.