mirror of
https://github.com/ProvableHQ/leo.git
synced 2024-11-29 10:03:22 +03:00
Aleo VM interpretation and misc
This commit is contained in:
parent
148183fb96
commit
b2f7fd718b
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -1576,6 +1576,7 @@ dependencies = [
|
||||
"rand_chacha",
|
||||
"snarkvm",
|
||||
"snarkvm-circuit",
|
||||
"snarkvm-synthesizer-program",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -111,7 +111,6 @@ default-features = false
|
||||
version = "1.11.1"
|
||||
|
||||
[workspace.dependencies.snarkvm]
|
||||
# path = "../SnarkVM"
|
||||
version = "1.0.0"
|
||||
|
||||
[workspace.dependencies.serde]
|
||||
|
@ -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
886
interpreter/src/cursor_aleo.rs
Normal file
886
interpreter/src/cursor_aleo.rs
Normal 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!(),
|
||||
}
|
||||
}
|
@ -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())
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user