Aleo VM interpretation and misc

This commit is contained in:
Michael Benfield 2024-11-19 10:04:53 -08:00
parent 148183fb96
commit b2f7fd718b
8 changed files with 2368 additions and 1163 deletions

1
Cargo.lock generated
View File

@ -1576,6 +1576,7 @@ dependencies = [
"rand_chacha",
"snarkvm",
"snarkvm-circuit",
"snarkvm-synthesizer-program",
]
[[package]]

View File

@ -111,7 +111,6 @@ default-features = false
version = "1.11.1"
[workspace.dependencies.snarkvm]
# path = "../SnarkVM"
version = "1.0.0"
[workspace.dependencies.serde]

View File

@ -18,11 +18,14 @@ license = "GPL-3.0"
edition = "2021"
rust-version = "1.82.0"
[dependencies.snarkvm]
workspace = true
[dependencies.snarkvm-circuit]
version = "1.0.0"
[dependencies.snarkvm]
workspace = true
[dependencies.snarkvm-synthesizer-program]
version = "1.0.0"
[dependencies.leo-ast]
workspace = true

View File

@ -38,7 +38,10 @@ use leo_span::{Span, Symbol, sym};
use snarkvm::prelude::{
CastLossy as _,
Closure as SvmClosure,
Double as _,
Finalize as SvmFinalize,
Function as SvmFunctionParam,
Inverse as _,
Network as _,
Pow as _,
@ -49,16 +52,20 @@ use snarkvm::prelude::{
ToBits,
};
use indexmap::IndexSet;
use indexmap::{IndexMap, IndexSet};
use rand::Rng as _;
use rand_chacha::{ChaCha20Rng, rand_core::SeedableRng};
use std::{cmp::Ordering, collections::HashMap, fmt, mem, str::FromStr as _};
pub type Closure = SvmClosure<TestnetV0>;
pub type Finalize = SvmFinalize<TestnetV0>;
pub type SvmFunction = SvmFunctionParam<TestnetV0>;
/// Names associated to values in a function being executed.
#[derive(Clone, Debug)]
struct FunctionContext {
pub struct FunctionContext {
program: Symbol,
caller: SvmAddress,
pub caller: SvmAddress,
names: HashMap<Symbol, Value>,
accumulated_futures: Future,
is_async: bool,
@ -117,7 +124,7 @@ impl ContextStack {
self.last_mut().unwrap().names.insert(symbol, value);
}
fn add_future(&mut self, future: Future) {
pub fn add_future(&mut self, future: Future) {
assert!(self.current_len > 0);
self.contexts[self.current_len - 1].accumulated_futures.0.extend(future.0);
}
@ -128,11 +135,11 @@ impl ContextStack {
self.last().unwrap().is_async
}
fn current_program(&self) -> Option<Symbol> {
pub fn current_program(&self) -> Option<Symbol> {
self.last().map(|c| c.program)
}
fn last(&self) -> Option<&FunctionContext> {
pub fn last(&self) -> Option<&FunctionContext> {
self.len().checked_sub(1).and_then(|i| self.contexts.get(i))
}
@ -141,8 +148,15 @@ impl ContextStack {
}
}
/// A Leo construct to be evauated.
#[derive(Copy, Clone, Debug)]
pub enum AleoContext<'a> {
Closure(&'a Closure),
Function(&'a SvmFunction),
Finalize(&'a Finalize),
}
/// A Leo construct to be evauated.
#[derive(Clone, Debug)]
pub enum Element<'a> {
/// A Leo statement.
Statement(&'a Statement),
@ -163,6 +177,12 @@ pub enum Element<'a> {
function_body: bool,
},
AleoExecution {
context: AleoContext<'a>,
registers: IndexMap<u64, Value>,
instruction_index: usize,
},
DelayedCall(GlobalId),
}
@ -173,7 +193,7 @@ impl Element<'_> {
Statement(statement) => statement.span(),
Expression(expression) => expression.span(),
Block { block, .. } => block.span(),
DelayedCall(..) => Default::default(),
AleoExecution { .. } | DelayedCall(..) => Default::default(),
}
}
}
@ -201,6 +221,13 @@ impl fmt::Display for GlobalId {
}
}
#[derive(Clone, Debug)]
pub enum FunctionVariant<'a> {
Leo(&'a Function),
AleoClosure(&'a Closure),
AleoFunction(&'a SvmFunction),
}
/// Tracks the current execution state - a cursor into the running program.
#[derive(Clone, Debug)]
pub struct Cursor<'a> {
@ -213,7 +240,7 @@ pub struct Cursor<'a> {
pub values: Vec<Value>,
/// All functions (or transitions or inlines) in any program being interpreted.
pub functions: HashMap<GlobalId, &'a Function>,
pub functions: HashMap<GlobalId, FunctionVariant<'a>>,
/// Consts are stored here.
pub globals: HashMap<GlobalId, Value>,
@ -227,13 +254,13 @@ pub struct Cursor<'a> {
pub contexts: ContextStack,
signer: SvmAddress,
pub signer: SvmAddress,
rng: ChaCha20Rng,
block_height: u32,
pub block_height: u32,
really_async: bool,
pub really_async: bool,
}
impl<'a> Cursor<'a> {
@ -255,6 +282,23 @@ impl<'a> Cursor<'a> {
}
}
pub fn increment_step(&mut self) {
let Some(Frame { step, .. }) = self.frames.last_mut() else {
panic!("frame expected");
};
*step += 1;
}
fn new_caller(&self) -> SvmAddress {
if let Some(function_context) = self.contexts.last() {
let program_id = ProgramID::<TestnetV0>::from_str(&format!("{}.aleo", function_context.program))
.expect("should be able to create ProgramID");
program_id.to_address().expect("should be able to convert to address")
} else {
self.signer
}
}
fn pop_value(&mut self) -> Result<Value> {
match self.values.pop() {
Some(v) => Ok(v),
@ -292,7 +336,7 @@ impl<'a> Cursor<'a> {
self.mappings.get_mut(&GlobalId { program, name })
}
fn lookup_function(&self, program: Symbol, name: Symbol) -> Option<&'a Function> {
fn lookup_function(&self, program: Symbol, name: Symbol) -> Option<FunctionVariant<'a>> {
self.functions.get(&GlobalId { program, name }).cloned()
}
@ -652,6 +696,359 @@ impl<'a> Cursor<'a> {
let span = function.span();
let value = self.evaluate_core_function(core_function, &function.arguments, span)?;
Some(value)
}
Expression::Array(array) if step == 0 => {
array.elements.iter().rev().for_each(push!());
None
}
Expression::Array(array) if step == 1 => {
let len = self.values.len();
let array_values = self.values.drain(len - array.elements.len()..).collect();
Some(Value::Array(array_values))
}
Expression::Binary(binary) if step == 0 => {
push!()(&binary.right);
push!()(&binary.left);
None
}
Expression::Binary(binary) if step == 1 => {
let rhs = self.pop_value()?;
let lhs = self.pop_value()?;
Some(evaluate_binary(binary.span, binary.op, lhs, rhs)?)
}
Expression::Call(call) if step == 0 => {
call.arguments.iter().rev().for_each(push!());
None
}
Expression::Call(call) if step == 1 => {
let len = self.values.len();
let (program, name) = match &*call.function {
Expression::Identifier(id) => {
let program = call.program.unwrap_or_else(|| {
self.contexts.current_program().expect("there should be a current program")
});
(program, id.name)
}
Expression::Locator(locator) => (locator.program.name.name, locator.name),
_ => tc_fail!(),
};
// It's a bit cheesy to collect the arguments into a Vec first, but it's the easiest way
// to handle lifetimes here.
let arguments: Vec<Value> = self.values.drain(len - call.arguments.len()..).collect();
self.do_call(
program,
name,
arguments.into_iter(),
false, // finalize
call.span(),
)?;
None
}
Expression::Call(_call) if step == 2 => Some(self.pop_value()?),
Expression::Cast(cast) if step == 0 => {
push!()(&*cast.expression);
None
}
Expression::Cast(cast) if step == 1 => {
let span = cast.span();
let arg = self.pop_value()?;
match arg.cast(&cast.type_) {
Some(value) => Some(value),
None => return Err(InterpreterHalt::new_spanned("cast failure".to_string(), span).into()),
}
}
Expression::Err(_) => todo!(),
Expression::Identifier(identifier) if step == 0 => {
Some(self.lookup(identifier.name).expect_tc(identifier.span())?)
}
Expression::Literal(literal) if step == 0 => Some(match literal {
Literal::Boolean(b, ..) => Value::Bool(*b),
Literal::Integer(IntegerType::U8, s, ..) => Value::U8(s.parse().expect_tc(literal.span())?),
Literal::Integer(IntegerType::U16, s, ..) => Value::U16(s.parse().expect_tc(literal.span())?),
Literal::Integer(IntegerType::U32, s, ..) => Value::U32(s.parse().expect_tc(literal.span())?),
Literal::Integer(IntegerType::U64, s, ..) => Value::U64(s.parse().expect_tc(literal.span())?),
Literal::Integer(IntegerType::U128, s, ..) => Value::U128(s.parse().expect_tc(literal.span())?),
Literal::Integer(IntegerType::I8, s, ..) => Value::I8(s.parse().expect_tc(literal.span())?),
Literal::Integer(IntegerType::I16, s, ..) => Value::I16(s.parse().expect_tc(literal.span())?),
Literal::Integer(IntegerType::I32, s, ..) => Value::I32(s.parse().expect_tc(literal.span())?),
Literal::Integer(IntegerType::I64, s, ..) => Value::I64(s.parse().expect_tc(literal.span())?),
Literal::Integer(IntegerType::I128, s, ..) => Value::I128(s.parse().expect_tc(literal.span())?),
Literal::Field(s, ..) => Value::Field(format!("{s}field").parse().expect_tc(literal.span())?),
Literal::Group(group_literal) => match &**group_literal {
GroupLiteral::Single(s, ..) => Value::Group(format!("{s}group").parse().expect_tc(literal.span())?),
GroupLiteral::Tuple(_group_tuple) => todo!(),
},
Literal::Address(s, ..) => Value::Address(s.parse().expect_tc(literal.span())?),
Literal::Scalar(s, ..) => Value::Scalar(format!("{s}scalar").parse().expect_tc(literal.span())?),
Literal::String(..) => tc_fail!(),
}),
Expression::Locator(_locator) => todo!(),
Expression::Struct(struct_) if step == 0 => {
struct_.members.iter().rev().flat_map(|init| init.expression.as_ref()).for_each(push!());
None
}
Expression::Struct(struct_) if step == 1 => {
// Collect all the key/value pairs into a HashMap.
let mut contents_tmp = HashMap::with_capacity(struct_.members.len());
for initializer in struct_.members.iter() {
let name = initializer.identifier.name;
let value = if initializer.expression.is_some() {
self.pop_value()?
} else {
self.lookup(name).expect_tc(struct_.span())?
};
contents_tmp.insert(name, value);
}
// And now put them into an IndexMap in the correct order.
let program = self.contexts.current_program().expect("there should be a current program");
let id = GlobalId { program, name: struct_.name.name };
let struct_type = self.structs.get(&id).expect_tc(struct_.span())?;
let contents = struct_type
.iter()
.map(|sym| (*sym, contents_tmp.remove(sym).expect("we just inserted this")))
.collect();
Some(Value::Struct(StructContents { name: struct_.name.name, contents }))
}
Expression::Ternary(ternary) if step == 0 => {
push!()(&*ternary.condition);
None
}
Expression::Ternary(ternary) if step == 1 => {
let condition = self.pop_value()?;
match condition {
Value::Bool(true) => push!()(&*ternary.if_true),
Value::Bool(false) => push!()(&*ternary.if_false),
_ => tc_fail!(),
}
None
}
Expression::Ternary(_) if step == 2 => Some(self.pop_value()?),
Expression::Tuple(tuple) if step == 0 => {
tuple.elements.iter().rev().for_each(push!());
None
}
Expression::Tuple(tuple) if step == 1 => {
let len = self.values.len();
let tuple_values = self.values.drain(len - tuple.elements.len()..).collect();
Some(Value::Tuple(tuple_values))
}
Expression::Unary(unary) if step == 0 => {
push!()(&*unary.receiver);
None
}
Expression::Unary(unary) if step == 1 => {
let value = self.pop_value()?;
Some(evaluate_unary(unary.span, unary.op, value)?)
}
Expression::Unit(_) if step == 0 => Some(Value::Unit),
_ => unreachable!(),
} {
assert_eq!(self.frames.len(), len);
self.frames.pop();
self.values.push(value);
Ok(true)
} else {
self.frames[len - 1].step += 1;
Ok(false)
}
}
/// Execute one step of the current element.
///
/// Many Leo constructs require multiple steps. For instance, when executing a conditional,
/// the first step will push the condition expression to the stack. Once that has executed
/// and we've returned to the conditional, we push the `then` or `otherwise` block to the
/// stack. Once that has executed and we've returned to the conditional, the final step
/// does nothing.
pub fn step(&mut self) -> Result<StepResult> {
if self.frames.is_empty() {
return Err(InterpreterHalt::new("no execution frames available".into()).into());
}
let Frame { element, step, user_initiated } = self.frames.last().expect("there should be a frame");
let user_initiated = *user_initiated;
match element {
Element::Block { block, function_body } => {
let finished = self.step_block(block, *function_body, *step);
Ok(StepResult { finished, value: None })
}
Element::Statement(statement) => {
let finished = self.step_statement(statement, *step)?;
Ok(StepResult { finished, value: None })
}
Element::Expression(expression) => {
let finished = self.step_expression(expression, *step)?;
let value = match (finished, user_initiated) {
(false, _) => None,
(true, false) => self.values.last().cloned(),
(true, true) => self.values.pop(),
};
let maybe_future = if let Some(Value::Tuple(vals)) = &value { vals.last() } else { value.as_ref() };
if let Some(Value::Future(future)) = &maybe_future {
if user_initiated && !future.0.is_empty() {
self.futures.push(future.clone());
}
}
Ok(StepResult { finished, value })
}
Element::AleoExecution { .. } => {
self.step_aleo()?;
Ok(StepResult { finished: true, value: None })
}
Element::DelayedCall(gid) if *step == 0 => {
match self.lookup_function(gid.program, gid.name).expect("function should exist") {
FunctionVariant::Leo(function) => {
assert!(function.variant == Variant::AsyncFunction);
let len = self.values.len();
let values_iter = self.values.drain(len - function.input.len()..);
self.contexts.push(
gid.program,
self.signer,
true, // is_async
);
let param_names = function.input.iter().map(|input| input.identifier.name);
for (name, value) in param_names.zip(values_iter) {
self.contexts.set(name, value);
}
self.frames.last_mut().unwrap().step = 1;
self.frames.push(Frame {
step: 0,
element: Element::Block { block: &function.block, function_body: true },
user_initiated: false,
});
Ok(StepResult { finished: false, value: None })
}
FunctionVariant::AleoFunction(function) => {
let Some(finalize_f) = function.finalize_logic() else {
panic!("must have finalize logic for a delayed call");
};
let len = self.values.len();
let values_iter = self.values.drain(len - finalize_f.inputs().len()..);
self.contexts.push(
gid.program,
self.signer,
true, // is_async
);
self.frames.last_mut().unwrap().step = 1;
self.frames.push(Frame {
step: 0,
element: Element::AleoExecution {
context: AleoContext::Finalize(finalize_f),
registers: values_iter.enumerate().map(|(i, v)| (i as u64, v)).collect(),
instruction_index: 0,
},
user_initiated: false,
});
Ok(StepResult { finished: false, value: None })
}
FunctionVariant::AleoClosure(..) => panic!("A call to a closure can't be delayed"),
}
}
Element::DelayedCall(_gid) => {
assert_eq!(*step, 1);
let value = self.values.pop();
let Some(Value::Future(future)) = value else {
panic!("Delayed calls should always be to async functions");
};
if !future.0.is_empty() {
self.futures.push(future.clone());
}
self.frames.pop();
Ok(StepResult { finished: true, value: Some(Value::Future(future)) })
}
}
}
pub fn do_call(
&mut self,
function_program: Symbol,
function_name: Symbol,
arguments: impl Iterator<Item = Value>,
finalize: bool,
span: Span,
) -> Result<()> {
let Some(function_variant) = self.lookup_function(function_program, function_name) else {
halt!(span, "unknown function {function_program}.aleo/{function_name}");
};
match function_variant {
FunctionVariant::Leo(function) => {
let caller = if matches!(function.variant, Variant::Transition | Variant::AsyncTransition) {
self.new_caller()
} else {
self.signer
};
if self.really_async && function.variant == Variant::AsyncFunction {
// Don't actually run the call now.
let async_ex = AsyncExecution {
function: GlobalId { name: function_name, program: function_program },
arguments: arguments.collect(),
};
self.values.push(Value::Future(Future(vec![async_ex])));
} else {
let is_async = function.variant == Variant::AsyncFunction;
self.contexts.push(function_program, caller, is_async);
let param_names = function.input.iter().map(|input| input.identifier.name);
for (name, value) in param_names.zip(arguments) {
self.contexts.set(name, value);
}
self.frames.push(Frame {
step: 0,
element: Element::Block { block: &function.block, function_body: true },
user_initiated: false,
});
}
}
FunctionVariant::AleoClosure(closure) => {
self.contexts.push(function_program, self.signer, false);
let context = AleoContext::Closure(closure);
self.frames.push(Frame {
step: 0,
element: Element::AleoExecution {
context,
registers: arguments.enumerate().map(|(i, v)| (i as u64, v)).collect(),
instruction_index: 0,
},
user_initiated: false,
});
}
FunctionVariant::AleoFunction(function) => {
let caller = self.new_caller();
self.contexts.push(function_program, caller, false);
let context = if finalize {
let Some(finalize_f) = function.finalize_logic() else {
panic!("finalize call with no finalize logic");
};
AleoContext::Finalize(finalize_f)
} else {
AleoContext::Function(function)
};
self.frames.push(Frame {
step: 0,
element: Element::AleoExecution {
context,
registers: arguments.enumerate().map(|(i, v)| (i as u64, v)).collect(),
instruction_index: 0,
},
user_initiated: false,
});
}
}
Ok(())
}
pub fn evaluate_core_function(
&mut self,
core_function: CoreFunction,
arguments: &[Expression],
span: Span,
) -> Result<Value> {
macro_rules! apply {
($func: expr, $value: ident, $to: ident) => {{
let v = self.pop_value()?;
@ -1616,20 +2013,20 @@ impl<'a> Cursor<'a> {
),
CoreFunction::MappingGet => {
let key = self.values.pop().expect_tc(span)?;
let (program, name) = match &function.arguments[0] {
let (program, name) = match &arguments[0] {
Expression::Identifier(id) => (None, id.name),
Expression::Locator(locator) => (Some(locator.program.name.name), locator.name),
_ => tc_fail!(),
};
match self.lookup_mapping(program, name).and_then(|mapping| mapping.get(&key)) {
Some(v) => v.clone(),
None => halt!(function.span(), "map lookup failure"),
None => halt!(span, "map lookup failure"),
}
}
CoreFunction::MappingGetOrUse => {
let use_value = self.values.pop().expect_tc(span)?;
let key = self.values.pop().expect_tc(span)?;
let (program, name) = match &function.arguments[0] {
let (program, name) = match &arguments[0] {
Expression::Identifier(id) => (None, id.name),
Expression::Locator(locator) => (Some(locator.program.name.name), locator.name),
_ => tc_fail!(),
@ -1642,7 +2039,7 @@ impl<'a> Cursor<'a> {
CoreFunction::MappingSet => {
let value = self.pop_value()?;
let key = self.pop_value()?;
let (program, name) = match &function.arguments[0] {
let (program, name) = match &arguments[0] {
Expression::Identifier(id) => (None, id.name),
Expression::Locator(locator) => (Some(locator.program.name.name), locator.name),
_ => tc_fail!(),
@ -1656,7 +2053,7 @@ impl<'a> Cursor<'a> {
}
CoreFunction::MappingRemove => {
let key = self.pop_value()?;
let (program, name) = match &function.arguments[0] {
let (program, name) = match &arguments[0] {
Expression::Identifier(id) => (None, id.name),
Expression::Locator(locator) => (Some(locator.program.name.name), locator.name),
_ => tc_fail!(),
@ -1670,7 +2067,7 @@ impl<'a> Cursor<'a> {
}
CoreFunction::MappingContains => {
let key = self.pop_value()?;
let (program, name) = match &function.arguments[0] {
let (program, name) = match &arguments[0] {
Expression::Identifier(id) => (None, id.name),
Expression::Locator(locator) => (Some(locator.program.name.name), locator.name),
_ => tc_fail!(),
@ -1702,261 +2099,8 @@ impl<'a> Cursor<'a> {
Value::Unit
}
};
Some(value)
}
Expression::Array(array) if step == 0 => {
array.elements.iter().rev().for_each(push!());
None
}
Expression::Array(array) if step == 1 => {
let len = self.values.len();
let array_values = self.values.drain(len - array.elements.len()..).collect();
Some(Value::Array(array_values))
}
Expression::Binary(binary) if step == 0 => {
push!()(&binary.right);
push!()(&binary.left);
None
}
Expression::Binary(binary) if step == 1 => {
let rhs = self.pop_value()?;
let lhs = self.pop_value()?;
Some(evaluate_binary(binary.span, binary.op, lhs, rhs)?)
}
Expression::Call(call) if step == 0 => {
call.arguments.iter().rev().for_each(push!());
None
}
Expression::Call(call) if step == 1 => {
let len = self.values.len();
let (program, name) = match &*call.function {
Expression::Identifier(id) => {
let program = call.program.unwrap_or_else(|| {
self.contexts.current_program().expect("there should be a current program")
});
(program, id.name)
}
Expression::Locator(locator) => (locator.program.name.name, locator.name),
_ => tc_fail!(),
};
let function = self.lookup_function(program, name).expect_tc(call.span())?;
let values_iter = self.values.drain(len - call.arguments.len()..);
if self.really_async && function.variant == Variant::AsyncFunction {
// Don't actually run the call now.
let async_ex =
AsyncExecution { function: GlobalId { name, program }, arguments: values_iter.collect() };
self.values.push(Value::Future(Future(vec![async_ex])));
} else {
let is_async = function.variant == Variant::AsyncFunction;
let caller = if matches!(function.variant, Variant::Transition | Variant::AsyncTransition) {
if let Some(function_context) = self.contexts.last() {
let program_id = ProgramID::<TestnetV0>::from_str(&format!("{}", function_context.program))
.expect("should be able to create ProgramID");
program_id.to_address().expect("should be able to convert to address")
} else {
self.signer
}
} else {
self.signer
};
self.contexts.push(program, caller, is_async);
let param_names = function.input.iter().map(|input| input.identifier.name);
for (name, value) in param_names.zip(values_iter) {
self.contexts.set(name, value);
}
self.frames.push(Frame {
step: 0,
element: Element::Block { block: &function.block, function_body: true },
user_initiated: false,
});
}
None
}
Expression::Call(_call) if step == 2 => Some(self.pop_value()?),
Expression::Cast(cast) if step == 0 => {
push!()(&*cast.expression);
None
}
Expression::Cast(cast) if step == 1 => {
let span = cast.span();
let arg = self.pop_value()?;
match arg.cast(&cast.type_) {
Some(value) => Some(value),
None => return Err(InterpreterHalt::new_spanned("cast failure".to_string(), span).into()),
}
}
Expression::Err(_) => todo!(),
Expression::Identifier(identifier) if step == 0 => {
Some(self.lookup(identifier.name).expect_tc(identifier.span())?)
}
Expression::Literal(literal) if step == 0 => Some(match literal {
Literal::Boolean(b, ..) => Value::Bool(*b),
Literal::Integer(IntegerType::U8, s, ..) => Value::U8(s.parse().expect_tc(literal.span())?),
Literal::Integer(IntegerType::U16, s, ..) => Value::U16(s.parse().expect_tc(literal.span())?),
Literal::Integer(IntegerType::U32, s, ..) => Value::U32(s.parse().expect_tc(literal.span())?),
Literal::Integer(IntegerType::U64, s, ..) => Value::U64(s.parse().expect_tc(literal.span())?),
Literal::Integer(IntegerType::U128, s, ..) => Value::U128(s.parse().expect_tc(literal.span())?),
Literal::Integer(IntegerType::I8, s, ..) => Value::I8(s.parse().expect_tc(literal.span())?),
Literal::Integer(IntegerType::I16, s, ..) => Value::I16(s.parse().expect_tc(literal.span())?),
Literal::Integer(IntegerType::I32, s, ..) => Value::I32(s.parse().expect_tc(literal.span())?),
Literal::Integer(IntegerType::I64, s, ..) => Value::I64(s.parse().expect_tc(literal.span())?),
Literal::Integer(IntegerType::I128, s, ..) => Value::I128(s.parse().expect_tc(literal.span())?),
Literal::Field(s, ..) => Value::Field(format!("{s}field").parse().expect_tc(literal.span())?),
Literal::Group(group_literal) => match &**group_literal {
GroupLiteral::Single(s, ..) => Value::Group(format!("{s}group").parse().expect_tc(literal.span())?),
GroupLiteral::Tuple(_group_tuple) => todo!(),
},
Literal::Address(s, ..) => Value::Address(s.parse().expect_tc(literal.span())?),
Literal::Scalar(s, ..) => Value::Scalar(format!("{s}scalar").parse().expect_tc(literal.span())?),
Literal::String(..) => tc_fail!(),
}),
Expression::Locator(_locator) => todo!(),
Expression::Struct(struct_) if step == 0 => {
struct_.members.iter().rev().flat_map(|init| init.expression.as_ref()).for_each(push!());
None
}
Expression::Struct(struct_) if step == 1 => {
// Collect all the key/value pairs into a HashMap.
let mut contents_tmp = HashMap::with_capacity(struct_.members.len());
for initializer in struct_.members.iter() {
let name = initializer.identifier.name;
let value = if initializer.expression.is_some() {
self.pop_value()?
} else {
self.lookup(name).expect_tc(struct_.span())?
};
contents_tmp.insert(name, value);
}
// And now put them into an IndexMap in the correct order.
let program = self.contexts.current_program().expect("there should be a current program");
let id = GlobalId { program, name: struct_.name.name };
let struct_type = self.structs.get(&id).expect_tc(struct_.span())?;
let contents = struct_type
.iter()
.map(|sym| (*sym, contents_tmp.remove(sym).expect("we just inserted this")))
.collect();
Some(Value::Struct(StructContents { name: struct_.name.name, contents }))
}
Expression::Ternary(ternary) if step == 0 => {
push!()(&*ternary.condition);
None
}
Expression::Ternary(ternary) if step == 1 => {
let condition = self.pop_value()?;
match condition {
Value::Bool(true) => push!()(&*ternary.if_true),
Value::Bool(false) => push!()(&*ternary.if_false),
_ => tc_fail!(),
}
None
}
Expression::Ternary(_) if step == 2 => Some(self.pop_value()?),
Expression::Tuple(tuple) if step == 0 => {
tuple.elements.iter().rev().for_each(push!());
None
}
Expression::Tuple(tuple) if step == 1 => {
let len = self.values.len();
let tuple_values = self.values.drain(len - tuple.elements.len()..).collect();
Some(Value::Tuple(tuple_values))
}
Expression::Unary(unary) if step == 0 => {
push!()(&*unary.receiver);
None
}
Expression::Unary(unary) if step == 1 => {
let value = self.pop_value()?;
Some(evaluate_unary(unary.span, unary.op, value)?)
}
Expression::Unit(_) if step == 0 => Some(Value::Unit),
_ => unreachable!(),
} {
assert_eq!(self.frames.len(), len);
self.frames.pop();
self.values.push(value);
Ok(true)
} else {
self.frames[len - 1].step += 1;
Ok(false)
}
}
/// Execute one step of the current element.
///
/// Many Leo constructs require multiple steps. For instance, when executing a conditional,
/// the first step will push the condition expression to the stack. Once that has executed
/// and we've returned to the conditional, we push the `then` or `otherwise` block to the
/// stack. Once that has executed and we've returned to the conditional, the final step
/// does nothing.
pub fn step(&mut self) -> Result<StepResult> {
if self.frames.is_empty() {
return Err(InterpreterHalt::new("no execution frames available".into()).into());
}
let Frame { element, step, user_initiated } = self.frames.last().expect("there should be a frame");
let user_initiated = *user_initiated;
match element {
Element::Block { block, function_body } => {
let finished = self.step_block(block, *function_body, *step);
Ok(StepResult { finished, value: None })
}
Element::Statement(statement) => {
let finished = self.step_statement(statement, *step)?;
Ok(StepResult { finished, value: None })
}
Element::Expression(expression) => {
let finished = self.step_expression(expression, *step)?;
let value = match (finished, user_initiated) {
(false, _) => None,
(true, false) => self.values.last().cloned(),
(true, true) => self.values.pop(),
};
let maybe_future = if let Some(Value::Tuple(vals)) = &value { vals.last() } else { value.as_ref() };
if let Some(Value::Future(future)) = &maybe_future {
if user_initiated && !future.0.is_empty() {
self.futures.push(future.clone());
}
}
Ok(StepResult { finished, value })
}
Element::DelayedCall(gid) if *step == 0 => {
let function = self.lookup_function(gid.program, gid.name).expect("function should exist");
assert!(function.variant == Variant::AsyncFunction);
let len = self.values.len();
let values_iter = self.values.drain(len - function.input.len()..);
self.contexts.push(
gid.program,
self.signer,
true, // is_async
);
let param_names = function.input.iter().map(|input| input.identifier.name);
for (name, value) in param_names.zip(values_iter) {
self.contexts.set(name, value);
}
self.frames.last_mut().unwrap().step = 1;
self.frames.push(Frame {
step: 0,
element: Element::Block { block: &function.block, function_body: true },
user_initiated: false,
});
Ok(StepResult { finished: false, value: None })
}
Element::DelayedCall(_gid) => {
assert_eq!(*step, 1);
let value = self.values.pop();
let Some(Value::Future(future)) = value else {
panic!("Delayed calls should always be to async functions");
};
if !future.0.is_empty() {
self.futures.push(future.clone());
}
self.frames.pop();
Ok(StepResult { finished: true, value: Some(Value::Future(future)) })
}
}
Ok(value)
}
}
@ -1970,7 +2114,7 @@ pub struct StepResult {
}
/// Evaluate a binary operation.
fn evaluate_binary(span: Span, op: BinaryOperation, lhs: Value, rhs: Value) -> Result<Value> {
pub fn evaluate_binary(span: Span, op: BinaryOperation, lhs: Value, rhs: Value) -> Result<Value> {
let value = match op {
BinaryOperation::Add => {
let Some(value) = (match (lhs, rhs) {
@ -2389,7 +2533,7 @@ fn evaluate_binary(span: Span, op: BinaryOperation, lhs: Value, rhs: Value) -> R
}
/// Evaluate a unary operation.
fn evaluate_unary(span: Span, op: UnaryOperation, value: Value) -> Result<Value> {
pub fn evaluate_unary(span: Span, op: UnaryOperation, value: Value) -> Result<Value> {
let value_result = match op {
UnaryOperation::Abs => match value {
Value::I8(x) => {

View File

@ -0,0 +1,886 @@
// Copyright (C) 2019-2024 Aleo Systems Inc.
// This file is part of the Leo library.
// The Leo library is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// The Leo library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
use super::*;
use leo_ast::{BinaryOperation, CoreFunction, IntegerType, Type, UnaryOperation};
use snarkvm::{
prelude::{Boolean, Identifier, Literal, LiteralType, PlaintextType, Register, TestnetV0, integers::Integer},
synthesizer::{Command, Instruction},
};
use snarkvm_synthesizer_program::{CallOperator, CastType, Operand};
use std::mem;
impl Cursor<'_> {
fn get_register(&self, reg: Register<TestnetV0>) -> &Value {
let Some(Frame { element: Element::AleoExecution { registers, .. }, .. }) = self.frames.last() else {
panic!();
};
match reg {
Register::Locator(index) => {
registers.get(&index).expect("valid .aleo code doesn't access undefined registers")
}
Register::Access(_, _) => todo!(),
}
}
fn set_register(&mut self, reg: Register<TestnetV0>, value: Value) {
let Some(Frame { element: Element::AleoExecution { registers, .. }, .. }) = self.frames.last_mut() else {
panic!();
};
match reg {
Register::Locator(index) => {
registers.insert(index, value);
}
Register::Access(_, _) => todo!(),
}
}
fn instructions_len(&self) -> usize {
let Some(Frame { element: Element::AleoExecution { context, .. }, .. }) = self.frames.last() else {
panic!();
};
match context {
AleoContext::Closure(closure) => closure.instructions().len(),
AleoContext::Function(function) => function.instructions().len(),
AleoContext::Finalize(finalize) => finalize.commands().len(),
}
}
fn increment_instruction_index(&mut self) {
let Some(Frame { element: Element::AleoExecution { instruction_index, .. }, .. }) = self.frames.last_mut()
else {
panic!();
};
*instruction_index += 1;
}
fn execution_complete(&self) -> bool {
let Some(Frame { element: Element::AleoExecution { instruction_index, .. }, .. }) = self.frames.last() else {
panic!();
};
*instruction_index >= self.instructions_len()
}
fn next_instruction(&self) -> Option<&Instruction<TestnetV0>> {
let Some(Frame { element: Element::AleoExecution { instruction_index, context, .. }, .. }) = self.frames.last()
else {
panic!();
};
match context {
AleoContext::Closure(closure) => closure.instructions().get(*instruction_index),
AleoContext::Function(function) => function.instructions().get(*instruction_index),
AleoContext::Finalize(_) => None,
}
}
fn next_command(&self) -> Option<&Command<TestnetV0>> {
let Some(Frame { element: Element::AleoExecution { instruction_index, context, .. }, .. }) = self.frames.last()
else {
panic!();
};
match context {
AleoContext::Closure(_) | AleoContext::Function(_) => None,
AleoContext::Finalize(finalize) => finalize.commands().get(*instruction_index),
}
}
fn operand_value(&self, operand: &Operand<TestnetV0>) -> Value {
match operand {
Operand::Literal(literal) => match literal {
Literal::Address(x) => Value::Address(*x),
Literal::Boolean(x) => Value::Bool(**x),
Literal::Field(x) => Value::Field(*x),
Literal::Group(x) => Value::Group(*x),
Literal::I8(x) => Value::I8(**x),
Literal::I16(x) => Value::I16(**x),
Literal::I32(x) => Value::I32(**x),
Literal::I64(x) => Value::I64(**x),
Literal::I128(x) => Value::I128(**x),
Literal::U8(x) => Value::U8(**x),
Literal::U16(x) => Value::U16(**x),
Literal::U32(x) => Value::U32(**x),
Literal::U64(x) => Value::U64(**x),
Literal::U128(x) => Value::U128(**x),
Literal::Scalar(x) => Value::Scalar(*x),
Literal::Signature(_) => todo!(),
Literal::String(_) => todo!(),
},
Operand::Register(register) => self.get_register(register.clone()).clone(),
Operand::ProgramID(_) => todo!(),
Operand::Signer => Value::Address(self.signer),
Operand::Caller => {
if let Some(function_context) = self.contexts.last() {
Value::Address(function_context.caller)
} else {
Value::Address(self.signer)
}
}
Operand::BlockHeight => Value::U32(self.block_height),
Operand::NetworkID => todo!(),
}
}
fn step_aleo_instruction(&mut self, instruction: Instruction<TestnetV0>) -> Result<()> {
// The Aleo VM code is a linear sequence of instructions, so we don't need to keep
// a stack of Elements (except for calls). Just run instructions in order.
use Instruction::*;
let Some(Frame { step, .. }) = self.frames.last() else {
panic!("frame expected");
};
macro_rules! unary {
($svm_op: expr, $op: ident) => {{
let operand = self.operand_value(&$svm_op.operands()[0]);
let value = evaluate_unary(Default::default(), UnaryOperation::$op, operand)?;
self.increment_instruction_index();
(value, $svm_op.destinations()[0].clone())
}};
}
macro_rules! binary {
($svm_op: expr, $op: ident) => {{
let operand0 = self.operand_value(&$svm_op.operands()[0]);
let operand1 = self.operand_value(&$svm_op.operands()[1]);
let value = evaluate_binary(Default::default(), BinaryOperation::$op, operand0, operand1)?;
self.increment_instruction_index();
(value, $svm_op.destinations()[0].clone())
}};
}
macro_rules! commit_function {
($commit: expr,
$to_address: ident,
$to_field: ident,
$to_group: ident,
) => {{
let core_function = match $commit.destination_type() {
LiteralType::Address => CoreFunction::$to_address,
LiteralType::Field => CoreFunction::$to_field,
LiteralType::Group => CoreFunction::$to_group,
_ => panic!("invalid commit destination type"),
};
let randomizer_value = self.operand_value(&$commit.operands()[0]);
let operand_value = self.operand_value(&$commit.operands()[1]);
self.values.push(randomizer_value);
self.values.push(operand_value);
let value = self.evaluate_core_function(core_function, &[], Span::default())?;
self.increment_instruction_index();
(value, $commit.destinations()[0].clone())
}};
}
macro_rules! hash_function {
($hash: expr,
$to_address: ident,
$to_field: ident,
$to_group: ident,
$to_i8: ident,
$to_i16: ident,
$to_i32: ident,
$to_i64: ident,
$to_i128: ident,
$to_u8: ident,
$to_u16: ident,
$to_u32: ident,
$to_u64: ident,
$to_u128: ident,
$to_scalar: ident,
) => {{
let core_function = match $hash.destination_type() {
PlaintextType::Literal(LiteralType::Address) => CoreFunction::$to_address,
PlaintextType::Literal(LiteralType::Field) => CoreFunction::$to_field,
PlaintextType::Literal(LiteralType::Group) => CoreFunction::$to_group,
PlaintextType::Literal(LiteralType::I8) => CoreFunction::$to_i8,
PlaintextType::Literal(LiteralType::I16) => CoreFunction::$to_i16,
PlaintextType::Literal(LiteralType::I32) => CoreFunction::$to_i32,
PlaintextType::Literal(LiteralType::I64) => CoreFunction::$to_i64,
PlaintextType::Literal(LiteralType::I128) => CoreFunction::$to_i128,
PlaintextType::Literal(LiteralType::U8) => CoreFunction::$to_u8,
PlaintextType::Literal(LiteralType::U16) => CoreFunction::$to_u16,
PlaintextType::Literal(LiteralType::U32) => CoreFunction::$to_u32,
PlaintextType::Literal(LiteralType::U64) => CoreFunction::$to_u64,
PlaintextType::Literal(LiteralType::U128) => CoreFunction::$to_u128,
PlaintextType::Literal(LiteralType::Scalar) => CoreFunction::$to_scalar,
_ => panic!("invalid hash destination type"),
};
let operand_value = self.operand_value(&$hash.operands()[0]);
self.values.push(operand_value);
let value = self.evaluate_core_function(core_function, &[], Span::default())?;
self.increment_instruction_index();
(value, $hash.destinations()[0].clone())
}};
}
let (value, destination) = match instruction {
Abs(abs) => unary!(abs, Abs),
AbsWrapped(abs_wrapped) => unary!(abs_wrapped, AbsWrapped),
Add(add) => binary!(add, Add),
AddWrapped(add_wrapped) => binary!(add_wrapped, AddWrapped),
And(and) => binary!(and, BitwiseAnd),
AssertEq(assert_eq) => {
let operand0 = self.operand_value(&assert_eq.operands()[0]);
let operand1 = self.operand_value(&assert_eq.operands()[1]);
if operand0.neq(&operand1) {
halt_no_span!("assertion failure: {operand0} != {operand1}");
}
self.increment_instruction_index();
return Ok(());
}
AssertNeq(assert_neq) => {
let operand0 = self.operand_value(&assert_neq.operands()[0]);
let operand1 = self.operand_value(&assert_neq.operands()[1]);
if operand0.eq(&operand1) {
halt_no_span!("assertion failure: {operand0} != {operand1}");
}
self.increment_instruction_index();
return Ok(());
}
Async(async_) if *step == 0 => {
let program = self.contexts.current_program().expect("there should be a program");
let name = snarkvm_identifier_to_symbol(async_.function_name());
let arguments: Vec<Value> = async_.operands().iter().map(|op| self.operand_value(op)).collect();
if self.really_async {
let async_ex = AsyncExecution { function: GlobalId { name, program }, arguments };
(Value::Future(Future(vec![async_ex])), async_.destinations()[0].clone())
} else {
self.do_call(
program,
name,
arguments.into_iter(),
true, // finalize
Span::default(),
)?;
self.increment_step();
return Ok(());
}
}
Call(call) if *step == 0 => {
let (program, name) = match call.operator() {
CallOperator::Locator(locator) => (
snarkvm_identifier_to_symbol(locator.resource()),
snarkvm_identifier_to_symbol(locator.program_id().name()),
),
CallOperator::Resource(id) => (
snarkvm_identifier_to_symbol(id),
self.contexts.current_program().expect("there should be a program"),
),
};
let arguments: Vec<Value> = call.operands().iter().map(|op| self.operand_value(op)).collect();
self.do_call(
program,
name,
arguments.into_iter(),
false, // finalize
Span::default(),
)?;
self.increment_step();
return Ok(());
}
Async(async_) if *step == 1 => {
// We've done a call, and the result is on the value stack.
self.values.pop();
self.set_register(async_.destinations()[0].clone(), Value::Future(Future(Vec::new())));
self.increment_instruction_index();
return Ok(());
}
Call(call) if *step == 1 => {
// We've done a call, and the result is on the value stack.
let Some(result) = self.values.pop() else {
panic!("should have a result");
};
if call.destinations().len() == 1 {
self.set_register(call.destinations()[0].clone(), result);
} else {
let Value::Tuple(tuple) = result else {
panic!("function returning multiple values should create a tuple");
};
for (dest, value) in call.destinations().iter().zip(tuple.into_iter()) {
self.set_register(dest.clone(), value);
}
}
self.increment_instruction_index();
return Ok(());
}
Call(_) | Async(_) => unreachable!("all cases covered above"),
Cast(cast) => {
let destination = cast.destinations()[0].clone();
self.increment_instruction_index();
let make_struct = |program, name_identifier| {
let name = snarkvm_identifier_to_symbol(name_identifier);
let id = GlobalId { program, name };
let struct_type = self.structs.get(&id).expect("struct type should exist");
let operands = cast.operands().iter().map(|op| self.operand_value(op));
Value::Struct(StructContents {
name,
contents: struct_type.iter().cloned().zip(operands).collect(),
})
};
match cast.cast_type() {
CastType::GroupXCoordinate => {
let Value::Group(g) = self.operand_value(&cast.operands()[0]) else {
tc_fail!();
};
let value = Value::Field(g.to_x_coordinate());
(value, destination)
}
CastType::GroupYCoordinate => {
let Value::Group(g) = self.operand_value(&cast.operands()[0]) else {
tc_fail!();
};
let value = Value::Field(g.to_y_coordinate());
(value, destination)
}
CastType::Plaintext(PlaintextType::Array(_array)) => {
let value = Value::Array(cast.operands().iter().map(|op| self.operand_value(op)).collect());
(value, destination)
}
CastType::Plaintext(PlaintextType::Literal(literal_type)) => {
let operand = self.operand_value(&cast.operands()[0]);
let value = match operand.cast(&snarkvm_literal_type_to_type(*literal_type)) {
Some(value) => value,
None => halt_no_span!("cast failure"),
};
(value, destination)
}
CastType::Record(struct_name) | CastType::Plaintext(PlaintextType::Struct(struct_name)) => {
let program = self.contexts.current_program().expect("there should be a current program");
let value = make_struct(program, struct_name);
(value, destination)
}
CastType::ExternalRecord(locator) => {
let program = snarkvm_identifier_to_symbol(locator.program_id().name());
let value = make_struct(program, locator.name());
(value, destination)
}
}
}
CastLossy(cast_lossy) => {
match cast_lossy.cast_type() {
CastType::Plaintext(PlaintextType::Literal(literal_type)) => {
// This is the only variant supported for lossy casts.
let operand = self.operand_value(&cast_lossy.operands()[0]);
let operand_literal = value_to_snarkvm_literal(operand);
let result_literal = match operand_literal.cast_lossy(*literal_type) {
Ok(result_literal) => result_literal,
Err(_) => halt_no_span!("cast failure"),
};
let destination = cast_lossy.destinations()[0].clone();
self.increment_instruction_index();
(snarkvm_literal_to_value(result_literal), destination)
}
_ => tc_fail!(),
}
}
CommitBHP256(commit) => {
commit_function!(commit, BHP256CommitToAddress, BHP256CommitToField, BHP256CommitToGroup,)
}
CommitBHP512(commit) => {
commit_function!(commit, BHP512CommitToAddress, BHP512CommitToField, BHP512CommitToGroup,)
}
CommitBHP768(commit) => {
commit_function!(commit, BHP768CommitToAddress, BHP768CommitToField, BHP768CommitToGroup,)
}
CommitBHP1024(commit) => {
commit_function!(commit, BHP1024CommitToAddress, BHP1024CommitToField, BHP1024CommitToGroup,)
}
CommitPED64(commit) => {
commit_function!(commit, Pedersen64CommitToAddress, Pedersen64CommitToField, Pedersen64CommitToGroup,)
}
CommitPED128(commit) => {
commit_function!(commit, Pedersen128CommitToAddress, Pedersen128CommitToField, Pedersen128CommitToGroup,)
}
Div(div) => binary!(div, Div),
DivWrapped(div_wrapped) => binary!(div_wrapped, DivWrapped),
Double(double) => unary!(double, Double),
GreaterThan(gt) => binary!(gt, Gt),
GreaterThanOrEqual(gte) => binary!(gte, Gte),
HashBHP256(hash) => hash_function!(
hash,
BHP256HashToAddress,
BHP256HashToField,
BHP256HashToGroup,
BHP256HashToI8,
BHP256HashToI16,
BHP256HashToI32,
BHP256HashToI64,
BHP256HashToI128,
BHP256HashToU8,
BHP256HashToU16,
BHP256HashToU32,
BHP256HashToU64,
BHP256HashToU128,
BHP256HashToScalar,
),
HashBHP512(hash) => hash_function!(
hash,
BHP512HashToAddress,
BHP512HashToField,
BHP512HashToGroup,
BHP512HashToI8,
BHP512HashToI16,
BHP512HashToI32,
BHP512HashToI64,
BHP512HashToI128,
BHP512HashToU8,
BHP512HashToU16,
BHP512HashToU32,
BHP512HashToU64,
BHP512HashToU128,
BHP512HashToScalar,
),
HashBHP768(hash) => hash_function!(
hash,
BHP768HashToAddress,
BHP768HashToField,
BHP768HashToGroup,
BHP768HashToI8,
BHP768HashToI16,
BHP768HashToI32,
BHP768HashToI64,
BHP768HashToI128,
BHP768HashToU8,
BHP768HashToU16,
BHP768HashToU32,
BHP768HashToU64,
BHP768HashToU128,
BHP768HashToScalar,
),
HashBHP1024(hash) => hash_function!(
hash,
BHP1024HashToAddress,
BHP1024HashToField,
BHP1024HashToGroup,
BHP1024HashToI8,
BHP1024HashToI16,
BHP1024HashToI32,
BHP1024HashToI64,
BHP1024HashToI128,
BHP1024HashToU8,
BHP1024HashToU16,
BHP1024HashToU32,
BHP1024HashToU64,
BHP1024HashToU128,
BHP1024HashToScalar,
),
HashKeccak256(hash) => hash_function!(
hash,
Keccak256HashToAddress,
Keccak256HashToField,
Keccak256HashToGroup,
Keccak256HashToI8,
Keccak256HashToI16,
Keccak256HashToI32,
Keccak256HashToI64,
Keccak256HashToI128,
Keccak256HashToU8,
Keccak256HashToU16,
Keccak256HashToU32,
Keccak256HashToU64,
Keccak256HashToU128,
Keccak256HashToScalar,
),
HashKeccak384(hash) => hash_function!(
hash,
Keccak384HashToAddress,
Keccak384HashToField,
Keccak384HashToGroup,
Keccak384HashToI8,
Keccak384HashToI16,
Keccak384HashToI32,
Keccak384HashToI64,
Keccak384HashToI128,
Keccak384HashToU8,
Keccak384HashToU16,
Keccak384HashToU32,
Keccak384HashToU64,
Keccak384HashToU128,
Keccak384HashToScalar,
),
HashKeccak512(hash) => hash_function!(
hash,
Keccak512HashToAddress,
Keccak512HashToField,
Keccak512HashToGroup,
Keccak512HashToI8,
Keccak512HashToI16,
Keccak512HashToI32,
Keccak512HashToI64,
Keccak512HashToI128,
Keccak512HashToU8,
Keccak512HashToU16,
Keccak512HashToU32,
Keccak512HashToU64,
Keccak512HashToU128,
Keccak512HashToScalar,
),
HashPED64(hash) => hash_function!(
hash,
Pedersen64HashToAddress,
Pedersen64HashToField,
Pedersen64HashToGroup,
Pedersen64HashToI8,
Pedersen64HashToI16,
Pedersen64HashToI32,
Pedersen64HashToI64,
Pedersen64HashToI128,
Pedersen64HashToU8,
Pedersen64HashToU16,
Pedersen64HashToU32,
Pedersen64HashToU64,
Pedersen64HashToU128,
Pedersen64HashToScalar,
),
HashPED128(hash) => hash_function!(
hash,
Pedersen128HashToAddress,
Pedersen128HashToField,
Pedersen128HashToGroup,
Pedersen128HashToI8,
Pedersen128HashToI16,
Pedersen128HashToI32,
Pedersen128HashToI64,
Pedersen128HashToI128,
Pedersen128HashToU8,
Pedersen128HashToU16,
Pedersen128HashToU32,
Pedersen128HashToU64,
Pedersen128HashToU128,
Pedersen128HashToScalar,
),
HashPSD2(hash) => hash_function!(
hash,
Poseidon2HashToAddress,
Poseidon2HashToField,
Poseidon2HashToGroup,
Poseidon2HashToI8,
Poseidon2HashToI16,
Poseidon2HashToI32,
Poseidon2HashToI64,
Poseidon2HashToI128,
Poseidon2HashToU8,
Poseidon2HashToU16,
Poseidon2HashToU32,
Poseidon2HashToU64,
Poseidon2HashToU128,
Poseidon2HashToScalar,
),
HashPSD4(hash) => hash_function!(
hash,
Poseidon4HashToAddress,
Poseidon4HashToField,
Poseidon4HashToGroup,
Poseidon4HashToI8,
Poseidon4HashToI16,
Poseidon4HashToI32,
Poseidon4HashToI64,
Poseidon4HashToI128,
Poseidon4HashToU8,
Poseidon4HashToU16,
Poseidon4HashToU32,
Poseidon4HashToU64,
Poseidon4HashToU128,
Poseidon4HashToScalar,
),
HashPSD8(hash) => hash_function!(
hash,
Poseidon8HashToAddress,
Poseidon8HashToField,
Poseidon8HashToGroup,
Poseidon8HashToI8,
Poseidon8HashToI16,
Poseidon8HashToI32,
Poseidon8HashToI64,
Poseidon8HashToI128,
Poseidon8HashToU8,
Poseidon8HashToU16,
Poseidon8HashToU32,
Poseidon8HashToU64,
Poseidon8HashToU128,
Poseidon8HashToScalar,
),
HashSha3_256(hash) => hash_function!(
hash,
SHA3_256HashToAddress,
SHA3_256HashToField,
SHA3_256HashToGroup,
SHA3_256HashToI8,
SHA3_256HashToI16,
SHA3_256HashToI32,
SHA3_256HashToI64,
SHA3_256HashToI128,
SHA3_256HashToU8,
SHA3_256HashToU16,
SHA3_256HashToU32,
SHA3_256HashToU64,
SHA3_256HashToU128,
SHA3_256HashToScalar,
),
HashSha3_384(hash) => hash_function!(
hash,
SHA3_384HashToAddress,
SHA3_384HashToField,
SHA3_384HashToGroup,
SHA3_384HashToI8,
SHA3_384HashToI16,
SHA3_384HashToI32,
SHA3_384HashToI64,
SHA3_384HashToI128,
SHA3_384HashToU8,
SHA3_384HashToU16,
SHA3_384HashToU32,
SHA3_384HashToU64,
SHA3_384HashToU128,
SHA3_384HashToScalar,
),
HashSha3_512(hash) => hash_function!(
hash,
SHA3_512HashToAddress,
SHA3_512HashToField,
SHA3_512HashToGroup,
SHA3_512HashToI8,
SHA3_512HashToI16,
SHA3_512HashToI32,
SHA3_512HashToI64,
SHA3_512HashToI128,
SHA3_512HashToU8,
SHA3_512HashToU16,
SHA3_512HashToU32,
SHA3_512HashToU64,
SHA3_512HashToU128,
SHA3_512HashToScalar,
),
HashManyPSD2(_) | HashManyPSD4(_) | HashManyPSD8(_) => panic!("these instructions don't exist yet"),
Inv(inv) => unary!(inv, Inverse),
IsEq(eq) => binary!(eq, Eq),
IsNeq(neq) => binary!(neq, Neq),
LessThan(lt) => binary!(lt, Lt),
LessThanOrEqual(lte) => binary!(lte, Lte),
Modulo(modulo) => binary!(modulo, Mod),
Mul(mul) => binary!(mul, Mul),
MulWrapped(mul_wrapped) => binary!(mul_wrapped, MulWrapped),
Nand(nand) => binary!(nand, Nand),
Neg(neg) => unary!(neg, Negate),
Nor(nor) => binary!(nor, Nor),
Not(not) => unary!(not, Not),
Or(or) => binary!(or, BitwiseOr),
Pow(pow) => binary!(pow, Pow),
PowWrapped(pow_wrapped) => binary!(pow_wrapped, PowWrapped),
Rem(rem) => binary!(rem, Rem),
RemWrapped(rem_wrapped) => binary!(rem_wrapped, RemWrapped),
Shl(shl) => binary!(shl, Shl),
ShlWrapped(shl_wrapped) => binary!(shl_wrapped, ShlWrapped),
Shr(shr) => binary!(shr, Shr),
ShrWrapped(shr_wrapped) => binary!(shr_wrapped, ShrWrapped),
SignVerify(_) => todo!(),
Square(square) => unary!(square, Square),
SquareRoot(sqrt) => unary!(sqrt, SquareRoot),
Sub(sub) => binary!(sub, Sub),
SubWrapped(sub_wrapped) => binary!(sub_wrapped, SubWrapped),
Ternary(ternary) => {
let condition = self.operand_value(&ternary.operands()[0]);
let result = match condition {
Value::Bool(true) => &ternary.operands()[1],
Value::Bool(false) => &ternary.operands()[2],
_ => panic!(),
};
self.increment_instruction_index();
(self.operand_value(result), ternary.destinations()[0].clone())
}
Xor(xor) => binary!(xor, Xor),
};
self.set_register(destination, value);
Ok(())
}
fn outputs(&self) -> Vec<Value> {
let Some(Frame { element, .. }) = self.frames.last() else {
panic!("frame expected");
};
let Element::AleoExecution { context, .. } = element else {
panic!("aleo execution expected");
};
let mut result = match context {
AleoContext::Closure(closure) => {
closure.outputs().iter().map(|output| self.operand_value(output.operand())).collect()
}
AleoContext::Function(function) => {
function.outputs().iter().map(|output| self.operand_value(output.operand())).collect()
}
AleoContext::Finalize(_finalize) => Vec::new(),
};
if result.is_empty() {
result.push(Value::Unit);
}
result
}
fn step_aleo_command(&mut self, command: Command<TestnetV0>) -> Result<()> {
use Command::*;
match command {
Instruction(instruction) => self.step_aleo_instruction(instruction)?,
Await(await_) => {
let Value::Future(future) = self.get_register(await_.register().clone()) else {
halt_no_span!("attempted to await a non-future");
};
self.contexts.add_future(future.clone());
self.increment_instruction_index();
}
Contains(_) => todo!(),
Get(_) => todo!(),
GetOrUse(_) => todo!(),
RandChaCha(_) => todo!(),
Remove(_) => todo!(),
Set(_) => todo!(),
BranchEq(branch_eq) => {
let first = self.operand_value(branch_eq.first());
let second = self.operand_value(branch_eq.second());
if first.eq(&second) {
self.branch(branch_eq.position());
} else {
self.increment_instruction_index();
}
}
BranchNeq(branch_neq) => {
let first = self.operand_value(branch_neq.first());
let second = self.operand_value(branch_neq.second());
if first.neq(&second) {
self.branch(branch_neq.position());
} else {
self.increment_instruction_index();
}
}
Position(_) => {}
}
Ok(())
}
fn branch(&mut self, label: &Identifier<TestnetV0>) {
let Some(Frame {
element: Element::AleoExecution { instruction_index, context: AleoContext::Finalize(finalize), .. },
..
}) = self.frames.last_mut()
else {
panic!();
};
for (i, cmd) in finalize.commands().iter().enumerate() {
if let Command::Position(position) = cmd {
if position.name() == label {
*instruction_index = i;
return;
}
}
}
panic!("branch to nonexistent label {}", label);
}
pub fn step_aleo(&mut self) -> Result<()> {
if let Some(command) = self.next_command().cloned() {
self.step_aleo_command(command)?;
} else if let Some(instruction) = self.next_instruction().cloned() {
self.step_aleo_instruction(instruction)?;
}
if self.execution_complete() {
let mut outputs = self.outputs();
self.frames.pop();
if outputs.len() > 1 {
self.values.push(Value::Tuple(outputs));
} else {
self.values.push(mem::take(&mut outputs[0]));
}
}
Ok(())
}
}
fn snarkvm_literal_type_to_type(snarkvm_type: LiteralType) -> Type {
use Type::*;
match snarkvm_type {
LiteralType::Address => Address,
LiteralType::Boolean => Boolean,
LiteralType::Field => Field,
LiteralType::Group => Group,
LiteralType::I8 => Integer(IntegerType::I8),
LiteralType::I16 => Integer(IntegerType::I16),
LiteralType::I32 => Integer(IntegerType::I32),
LiteralType::I64 => Integer(IntegerType::I64),
LiteralType::I128 => Integer(IntegerType::I128),
LiteralType::U8 => Integer(IntegerType::U8),
LiteralType::U16 => Integer(IntegerType::U16),
LiteralType::U32 => Integer(IntegerType::U32),
LiteralType::U64 => Integer(IntegerType::U64),
LiteralType::U128 => Integer(IntegerType::U128),
LiteralType::Scalar => Scalar,
LiteralType::Signature => todo!(),
LiteralType::String => todo!(),
}
}
fn snarkvm_literal_to_value(literal: Literal<TestnetV0>) -> Value {
match literal {
Literal::Address(x) => Value::Address(x),
Literal::Boolean(x) => Value::Bool(*x),
Literal::Field(x) => Value::Field(x),
Literal::Group(x) => Value::Group(x),
Literal::I8(x) => Value::I8(*x),
Literal::I16(x) => Value::I16(*x),
Literal::I32(x) => Value::I32(*x),
Literal::I64(x) => Value::I64(*x),
Literal::I128(x) => Value::I128(*x),
Literal::U8(x) => Value::U8(*x),
Literal::U16(x) => Value::U16(*x),
Literal::U32(x) => Value::U32(*x),
Literal::U64(x) => Value::U64(*x),
Literal::U128(x) => Value::U128(*x),
Literal::Scalar(x) => Value::Scalar(x),
Literal::Signature(_) | Literal::String(_) => tc_fail!(),
}
}
fn value_to_snarkvm_literal(value: Value) -> Literal<TestnetV0> {
match value {
Value::Bool(x) => Literal::Boolean(Boolean::new(x)),
Value::U8(x) => Literal::U8(Integer::new(x)),
Value::U16(x) => Literal::U16(Integer::new(x)),
Value::U32(x) => Literal::U32(Integer::new(x)),
Value::U64(x) => Literal::U64(Integer::new(x)),
Value::U128(x) => Literal::U128(Integer::new(x)),
Value::I8(x) => Literal::I8(Integer::new(x)),
Value::I16(x) => Literal::I16(Integer::new(x)),
Value::I32(x) => Literal::I32(Integer::new(x)),
Value::I64(x) => Literal::I64(Integer::new(x)),
Value::I128(x) => Literal::I128(Integer::new(x)),
Value::Group(x) => Literal::Group(x),
Value::Field(x) => Literal::Field(x),
Value::Scalar(x) => Literal::Scalar(x),
Value::Address(x) => Literal::Address(x),
Value::Array(_) | Value::Tuple(_) | Value::Unit | Value::Future(_) | Value::Struct(_) => tc_fail!(),
}
}

View File

@ -18,12 +18,12 @@ use leo_ast::{AccessExpression, AssertVariant, Ast, ConsoleFunction, Expression,
use leo_errors::{CompilerError, InterpreterHalt, LeoError, Result, emitter::Handler};
use leo_span::{Span, source_map::FileName, symbol::with_session_globals};
use snarkvm::prelude::TestnetV0;
use snarkvm::prelude::{Program, TestnetV0};
use colored::*;
use std::{
collections::HashMap,
fmt::Display,
fmt::{Display, Write as _},
fs,
path::{Path, PathBuf},
};
@ -34,6 +34,8 @@ use util::*;
mod cursor;
use cursor::*;
mod cursor_aleo;
mod value;
use value::*;
@ -61,6 +63,7 @@ enum InterpreterAction {
LeoInterpretOver(String),
RunFuture(usize),
Breakpoint(Breakpoint),
PrintRegister(u64),
Into,
Over,
Step,
@ -68,12 +71,18 @@ enum InterpreterAction {
}
impl Interpreter {
fn new<'a, P: 'a + AsRef<Path>>(
source_files: impl IntoIterator<Item = &'a P>,
fn new<'a, P: 'a + AsRef<Path>, Q: 'a + AsRef<Path>>(
leo_source_files: impl IntoIterator<Item = &'a P>,
aleo_source_files: impl IntoIterator<Item = &'a Q>,
signer: SvmAddress,
block_height: u32,
) -> Result<Self> {
Self::new_impl(&mut source_files.into_iter().map(|p| p.as_ref()), signer, block_height)
Self::new_impl(
&mut leo_source_files.into_iter().map(|p| p.as_ref()),
&mut aleo_source_files.into_iter().map(|p| p.as_ref()),
signer,
block_height,
)
}
fn get_ast(path: &Path, handler: &Handler, node_builder: &NodeBuilder) -> Result<Ast> {
@ -83,7 +92,12 @@ impl Interpreter {
leo_parser::parse_ast::<TestnetV0>(handler, node_builder, &text, source_file.start_pos)
}
fn new_impl(source_files: &mut dyn Iterator<Item = &Path>, signer: SvmAddress, block_height: u32) -> Result<Self> {
fn new_impl(
leo_source_files: &mut dyn Iterator<Item = &Path>,
aleo_source_files: &mut dyn Iterator<Item = &Path>,
signer: SvmAddress,
block_height: u32,
) -> Result<Self> {
let handler = Handler::default();
let node_builder = Default::default();
let mut cursor: Cursor<'_> = Cursor::new(
@ -92,14 +106,14 @@ impl Interpreter {
block_height,
);
let mut filename_to_program = HashMap::new();
for path in source_files {
for path in leo_source_files {
let ast = Self::get_ast(path, &handler, &node_builder)?;
// TODO: This leak is silly.
let ast = Box::leak(Box::new(ast));
for (&program, scope) in ast.ast.program_scopes.iter() {
filename_to_program.insert(path.to_path_buf(), program.to_string());
for (name, function) in scope.functions.iter() {
cursor.functions.insert(GlobalId { program, name: *name }, function);
cursor.functions.insert(GlobalId { program, name: *name }, FunctionVariant::Leo(function));
}
for (name, composite) in scope.structs.iter() {
@ -126,6 +140,46 @@ impl Interpreter {
}
}
for path in aleo_source_files {
let aleo_program = Self::get_aleo_program(path)?;
// TODO: Another goofy leak.
let aleo_program = Box::leak(Box::new(aleo_program));
let program = snarkvm_identifier_to_symbol(aleo_program.id().name());
filename_to_program.insert(path.to_path_buf(), program.to_string());
for (name, struct_type) in aleo_program.structs().iter() {
cursor.structs.insert(
GlobalId { program, name: snarkvm_identifier_to_symbol(name) },
struct_type.members().keys().map(snarkvm_identifier_to_symbol).collect(),
);
}
for (name, record_type) in aleo_program.records().iter() {
cursor.structs.insert(
GlobalId { program, name: snarkvm_identifier_to_symbol(name) },
record_type.entries().keys().map(snarkvm_identifier_to_symbol).collect(),
);
}
for (name, _mapping) in aleo_program.mappings().iter() {
cursor.mappings.insert(GlobalId { program, name: snarkvm_identifier_to_symbol(name) }, HashMap::new());
}
for (name, function) in aleo_program.functions().iter() {
cursor.functions.insert(
GlobalId { program, name: snarkvm_identifier_to_symbol(name) },
FunctionVariant::AleoFunction(function),
);
}
for (name, closure) in aleo_program.closures().iter() {
cursor.functions.insert(
GlobalId { program, name: snarkvm_identifier_to_symbol(name) },
FunctionVariant::AleoClosure(closure),
);
}
}
Ok(Interpreter {
cursor,
handler,
@ -136,6 +190,12 @@ impl Interpreter {
})
}
fn get_aleo_program(path: &Path) -> Result<Program<TestnetV0>> {
let text = fs::read_to_string(path).map_err(|e| CompilerError::file_read_error(path, e))?;
let program = text.parse()?;
Ok(program)
}
fn action(&mut self, act: InterpreterAction) -> Result<Option<Value>> {
use InterpreterAction::*;
@ -196,6 +256,19 @@ impl Interpreter {
StepResult { finished: false, value: None }
}
PrintRegister(register_index) => {
let Some(Frame { element: Element::AleoExecution { registers, .. }, .. }) = self.cursor.frames.last()
else {
halt_no_span!("cannot print register - not currently interpreting Aleo VM code");
};
if let Some(value) = registers.get(register_index) {
StepResult { finished: false, value: Some(value.clone()) }
} else {
halt_no_span!("no such register {register_index}");
}
}
Run => {
while !self.cursor.frames.is_empty() {
if let Some((program, line)) = self.current_program_and_line() {
@ -226,10 +299,60 @@ impl Interpreter {
Element::Expression(expression) => format!("{expression}"),
Element::Block { block, .. } => format!("{block}"),
Element::DelayedCall(gid) => format!("Delayed call to {gid}"),
Element::AleoExecution { context, instruction_index, .. } => match context {
AleoContext::Closure(closure) => closure.instructions().get(instruction_index).map(|i| format!("{i}")),
AleoContext::Function(function) => {
function.instructions().get(instruction_index).map(|i| format!("{i}"))
}
AleoContext::Finalize(finalize) => finalize.commands().get(instruction_index).map(|i| format!("{i}")),
}
.unwrap_or_else(|| "...".to_string()),
})
}
fn view_current_in_context(&self) -> Option<impl Display> {
if let Some(Frame { element: Element::AleoExecution { context, instruction_index, .. }, .. }) =
self.cursor.frames.last()
{
// For Aleo VM code, there are no spans; just print out the code without referring to the source code.
fn write_all<I: Display>(items: impl Iterator<Item = I>, instruction_index: usize) -> String {
let mut result = String::new();
for (i, item) in items.enumerate() {
if i == instruction_index {
let temp = format!(" {item}").red();
writeln!(&mut result, "{temp}").expect("write");
} else {
writeln!(&mut result, " {item}").expect("write");
}
}
result
}
let (heading, inputs, instructions, outputs) = match context {
AleoContext::Closure(closure) => (
format!("closure {}\n", closure.name()),
write_all(closure.inputs().iter(), usize::MAX),
write_all(closure.instructions().iter(), *instruction_index),
write_all(closure.outputs().iter(), usize::MAX),
),
AleoContext::Function(function) => (
format!("function {}\n", function.name()),
write_all(function.inputs().iter(), usize::MAX),
write_all(function.instructions().iter(), *instruction_index),
write_all(function.outputs().iter(), usize::MAX),
),
AleoContext::Finalize(finalize) => (
format!("finalize {}\n", finalize.name()),
write_all(finalize.inputs().iter(), usize::MAX),
write_all(finalize.commands().iter(), *instruction_index),
"".to_string(),
),
};
Some(format!("{heading}{inputs}{instructions}{outputs}"))
} else {
// For Leo code, we use spans to print the original source code.
let span = self.current_span()?;
if span == Default::default() {
return None;
@ -246,6 +369,7 @@ impl Interpreter {
))
})
}
}
fn current_program_and_line(&self) -> Option<(String, usize)> {
if let Some(span) = self.current_span() {
@ -396,8 +520,13 @@ fn kill_span_expression(expression: &mut Expression) {
/// Load all the Leo source files indicated and open the interpreter
/// to commands from the user.
pub fn interpret(filenames: &[PathBuf], signer: SvmAddress, block_height: u32) -> Result<()> {
let mut interpreter = Interpreter::new(filenames.iter(), signer, block_height)?;
pub fn interpret(
leo_filenames: &[PathBuf],
aleo_filenames: &[PathBuf],
signer: SvmAddress,
block_height: u32,
) -> Result<()> {
let mut interpreter = Interpreter::new(leo_filenames.iter(), aleo_filenames.iter(), signer, block_height)?;
let mut buffer = String::new();
println!("{}", INSTRUCTIONS);
loop {
@ -437,6 +566,15 @@ pub fn interpret(filenames: &[PathBuf], signer: SvmAddress, block_height: u32) -
InterpreterAction::Breakpoint(breakpoint)
} else if let Some(rest) = s.strip_prefix("#into ").or(s.strip_prefix("#i ")) {
InterpreterAction::LeoInterpretInto(rest.trim().into())
} else if let Some(rest) = s.strip_prefix("#print ").or(s.strip_prefix("#p ")) {
let trimmed = rest.trim();
let without_r = trimmed.strip_prefix("r").unwrap_or(trimmed);
if let Ok(num) = without_r.parse::<u64>() {
InterpreterAction::PrintRegister(num)
} else {
println!("failed to parse register number {trimmed}");
continue;
}
} else {
InterpreterAction::LeoInterpretOver(s.trim().into())
}

View File

@ -15,7 +15,9 @@
// along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
use leo_errors::{InterpreterHalt, Result};
use leo_span::Span;
use leo_span::{Span, Symbol};
use snarkvm::prelude::{Identifier, TestnetV0};
#[macro_export]
macro_rules! tc_fail {
@ -24,6 +26,13 @@ macro_rules! tc_fail {
};
}
#[macro_export]
macro_rules! halt_no_span {
($($x:tt)*) => {
return Err(InterpreterHalt::new(format!($($x)*)).into())
}
}
#[macro_export]
macro_rules! halt {
($span: expr) => {
@ -59,3 +68,8 @@ impl<T, U: std::fmt::Debug> ExpectTc for Result<T, U> {
self.map_err(|_e| InterpreterHalt::new_spanned("type failure".into(), span).into())
}
}
pub fn snarkvm_identifier_to_symbol(id: &Identifier<TestnetV0>) -> Symbol {
let s = id.to_string();
Symbol::intern(&s)
}

View File

@ -30,6 +30,9 @@ use super::*;
/// Debugs an Aleo program through the interpreter.
#[derive(Parser, Debug)]
pub struct LeoDebug {
#[arg(long, help = "Use these source files instead of finding source files through the project structure.", num_args = 1..)]
pub(crate) paths: Vec<String>,
#[arg(long, help = "The block height, accessible via block.height.", default_value = "0")]
pub(crate) block_height: u32,
@ -83,6 +86,7 @@ fn handle_debug<N: Network>(command: &LeoDebug, context: Context) -> Result<()>
let private_key = context.get_private_key(&None)?;
let address = Address::try_from(&private_key)?;
if command.paths.is_empty() {
// Retrieve all local dependencies in post order
let main_sym = Symbol::intern(&program_id.name().to_string());
let mut retriever = Retriever::<N>::new(
@ -106,5 +110,21 @@ fn handle_debug<N: Network>(command: &LeoDebug, context: Context) -> Result<()>
})
.collect();
leo_interpreter::interpret(&paths, address, command.block_height)
leo_interpreter::interpret(&paths, &[], address, command.block_height)
} else {
let leo_paths: Vec<PathBuf> = command
.paths
.iter()
.filter(|path_str| path_str.ends_with(".leo"))
.map(|path_str| path_str.into())
.collect();
let aleo_paths: Vec<PathBuf> = command
.paths
.iter()
.filter(|path_str| !path_str.ends_with(".leo"))
.map(|path_str| path_str.into())
.collect();
leo_interpreter::interpret(&leo_paths, &aleo_paths, address, command.block_height)
}
}