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

File diff suppressed because it is too large Load Diff

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,25 +299,76 @@ 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> {
let span = self.current_span()?;
if span == Default::default() {
return None;
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;
}
with_session_globals(|s| {
let source_file = s.source_map.find_source_file(span.lo)?;
let first_span = Span::new(source_file.start_pos, span.lo);
let last_span = Span::new(span.hi, source_file.end_pos);
Some(format!(
"{}{}{}",
s.source_map.contents_of_span(first_span)?,
s.source_map.contents_of_span(span)?.red(),
s.source_map.contents_of_span(last_span)?,
))
})
}
with_session_globals(|s| {
let source_file = s.source_map.find_source_file(span.lo)?;
let first_span = Span::new(source_file.start_pos, span.lo);
let last_span = Span::new(span.hi, source_file.end_pos);
Some(format!(
"{}{}{}",
s.source_map.contents_of_span(first_span)?,
s.source_map.contents_of_span(span)?.red(),
s.source_map.contents_of_span(last_span)?,
))
})
}
fn current_program_and_line(&self) -> Option<(String, usize)> {
@ -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,28 +86,45 @@ 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)?;
// Retrieve all local dependencies in post order
let main_sym = Symbol::intern(&program_id.name().to_string());
let mut retriever = Retriever::<N>::new(
main_sym,
&package_path,
&home_path,
context.get_endpoint(&command.compiler_options.endpoint)?.to_string(),
)
.map_err(|err| UtilError::failed_to_retrieve_dependencies(err, Default::default()))?;
let mut local_dependencies =
retriever.retrieve().map_err(|err| UtilError::failed_to_retrieve_dependencies(err, Default::default()))?;
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(
main_sym,
&package_path,
&home_path,
context.get_endpoint(&command.compiler_options.endpoint)?.to_string(),
)
.map_err(|err| UtilError::failed_to_retrieve_dependencies(err, Default::default()))?;
let mut local_dependencies =
retriever.retrieve().map_err(|err| UtilError::failed_to_retrieve_dependencies(err, Default::default()))?;
// Push the main program at the end of the list.
local_dependencies.push(main_sym);
// Push the main program at the end of the list.
local_dependencies.push(main_sym);
let paths: Vec<PathBuf> = local_dependencies
.into_iter()
.map(|dependency| {
let base_path = retriever.get_context(&dependency).full_path();
base_path.join("src/main.leo")
})
.collect();
let paths: Vec<PathBuf> = local_dependencies
.into_iter()
.map(|dependency| {
let base_path = retriever.get_context(&dependency).full_path();
base_path.join("src/main.leo")
})
.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)
}
}