Merge branch 'add-ast-folder' into array
This commit is contained in:
commit
27a1714294
13 changed files with 1035 additions and 505 deletions
5
zokrates_cli/examples/arrays/return_array.code
Normal file
5
zokrates_cli/examples/arrays/return_array.code
Normal file
|
@ -0,0 +1,5 @@
|
|||
def foo() -> (field[2]):
|
||||
return [1, 2]
|
||||
|
||||
def main() -> (field[2]):
|
||||
return foo()
|
11
zokrates_cli/examples/arrays/slicefrom.code
Normal file
11
zokrates_cli/examples/arrays/slicefrom.code
Normal file
File diff suppressed because one or more lines are too long
7
zokrates_cli/examples/rec.code
Normal file
7
zokrates_cli/examples/rec.code
Normal file
|
@ -0,0 +1,7 @@
|
|||
def get(field[32] array, field index) -> (field):
|
||||
return array[index]
|
||||
|
||||
def main() -> (field):
|
||||
field[32] array = [0, 0, 0, 0, 0, 0, 7, 42, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
||||
field r = get(array, 7)
|
||||
return r
|
File diff suppressed because one or more lines are too long
|
@ -727,6 +727,9 @@ impl Flattener {
|
|||
statements_flattened,
|
||||
expressions[n.to_dec_string().parse::<usize>().unwrap()].clone()
|
||||
).apply_recursive_substitution(&self.substitution)
|
||||
},
|
||||
FieldElementArrayExpression::FunctionCall(size, id, args) => {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -770,6 +773,9 @@ impl Flattener {
|
|||
FieldElementArrayExpression::Value(size, expressions) => {
|
||||
assert_eq!(size, expressions.len());
|
||||
expressions[i].clone()
|
||||
},
|
||||
FieldElementArrayExpression::FunctionCall(size, id, args) => {
|
||||
unimplemented!()
|
||||
}
|
||||
},
|
||||
box FieldElementExpression::Number(T::from(0)),
|
||||
|
@ -807,6 +813,18 @@ impl Flattener {
|
|||
statements_flattened,
|
||||
v
|
||||
)).collect()
|
||||
},
|
||||
FieldElementArrayExpression::FunctionCall(size, ref id, ref param_expressions) => {
|
||||
let exprs_flattened = self.flatten_function_call(
|
||||
functions_flattened,
|
||||
arguments_flattened,
|
||||
statements_flattened,
|
||||
id,
|
||||
vec![Type::FieldElementArray(size)],
|
||||
param_expressions
|
||||
);
|
||||
assert!(exprs_flattened.expressions.len() == size); // outside of MultipleDefinition, FunctionCalls must return a single value
|
||||
exprs_flattened.expressions
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -857,8 +875,8 @@ impl Flattener {
|
|||
|
||||
let rhs = rhs.into_iter().map(|e| e.apply_recursive_substitution(&self.substitution)).collect::<Vec<_>>();
|
||||
|
||||
match rhs.len() {
|
||||
1 => {
|
||||
match expr.get_type() {
|
||||
Type::FieldElement | Type::Boolean => {
|
||||
match assignee {
|
||||
TypedAssignee::Identifier(ref v) => {
|
||||
let debug_name = v.clone().id;
|
||||
|
@ -951,7 +969,7 @@ impl Flattener {
|
|||
}
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
Type::FieldElementArray(..) => {
|
||||
for (index, r) in rhs.into_iter().enumerate() {
|
||||
let debug_name = match assignee {
|
||||
TypedAssignee::Identifier(ref v) => format!("{}_c{}", v.id, index),
|
||||
|
@ -1110,21 +1128,9 @@ impl Flattener {
|
|||
// take each new variable being assigned
|
||||
for v in vars {
|
||||
// determine how many field elements it carries
|
||||
let n = v.get_type().get_primitive_count();
|
||||
|
||||
match n {
|
||||
1 => {
|
||||
let debug_name = v.id;
|
||||
let var = self.use_variable(&debug_name);
|
||||
// handle return of function call
|
||||
let var_to_replace = self.get_latest_var_substitution(&debug_name);
|
||||
if !(var == var_to_replace) && self.variables.contains(&var_to_replace) && !self.substitution.contains_key(&var_to_replace){
|
||||
self.substitution.insert(var_to_replace.clone(),var.clone());
|
||||
}
|
||||
statements_flattened.push(FlatStatement::Definition(var, iterator.next().unwrap()));
|
||||
},
|
||||
n => {
|
||||
for index in 0..n {
|
||||
match v.get_type() {
|
||||
Type::FieldElementArray(size) => {
|
||||
for index in 0..size {
|
||||
let debug_name = format!("{}_c{}", v.id, index);
|
||||
let var = self.use_variable(&debug_name);
|
||||
// handle return of function call
|
||||
|
@ -1134,7 +1140,17 @@ impl Flattener {
|
|||
}
|
||||
statements_flattened.push(FlatStatement::Definition(var, iterator.next().unwrap()));
|
||||
}
|
||||
}
|
||||
},
|
||||
Type::Boolean | Type::FieldElement => {
|
||||
let debug_name = v.id;
|
||||
let var = self.use_variable(&debug_name);
|
||||
// handle return of function call
|
||||
let var_to_replace = self.get_latest_var_substitution(&debug_name);
|
||||
if !(var == var_to_replace) && self.variables.contains(&var_to_replace) && !self.substitution.contains_key(&var_to_replace){
|
||||
self.substitution.insert(var_to_replace.clone(),var.clone());
|
||||
}
|
||||
statements_flattened.push(FlatStatement::Definition(var, iterator.next().unwrap()));
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1218,6 +1234,7 @@ impl Flattener {
|
|||
///
|
||||
/// * `prog` - `Prog`ram that will be flattened.
|
||||
pub fn flatten_program<T: Field>(&mut self, prog: TypedProg<T>) -> FlatProg<T> {
|
||||
|
||||
let mut functions_flattened = Vec::new();
|
||||
|
||||
self.load_stdlib(&mut functions_flattened);
|
||||
|
|
|
@ -489,7 +489,11 @@ impl Checker {
|
|||
let f = &candidates[0];
|
||||
// the return count has to be 1
|
||||
match f.signature.outputs.len() {
|
||||
1 => Ok(FieldElementExpression::FunctionCall(f.id.clone(), arguments_checked).into()),
|
||||
1 => match f.signature.outputs[0] {
|
||||
Type::FieldElement => Ok(FieldElementExpression::FunctionCall(f.id.clone(), arguments_checked).into()),
|
||||
Type::FieldElementArray(size) => Ok(FieldElementArrayExpression::FunctionCall(size, f.id.clone(), arguments_checked).into()),
|
||||
_ => unimplemented!()
|
||||
}
|
||||
n => Err(Error { message: format!("{} returns {} values but is called outside of a definition", f.id, n) })
|
||||
}
|
||||
},
|
||||
|
|
87
zokrates_core/src/static_analysis/dead_code.rs
Normal file
87
zokrates_core/src/static_analysis/dead_code.rs
Normal file
|
@ -0,0 +1,87 @@
|
|||
use std::collections::HashSet;
|
||||
use field::Field;
|
||||
use typed_absy::Folder;
|
||||
use typed_absy::*;
|
||||
use typed_absy::folder::*;
|
||||
use types::{Type, Signature};
|
||||
|
||||
pub struct DeadCode {
|
||||
called: HashSet<String>,
|
||||
}
|
||||
|
||||
impl DeadCode {
|
||||
fn new() -> Self {
|
||||
DeadCode {
|
||||
called: HashSet::new()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clean<T: Field>(p: TypedProg<T>) -> TypedProg<T> {
|
||||
DeadCode::new().fold_program(p)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Field> Folder<T> for DeadCode {
|
||||
fn fold_program(&mut self, p: TypedProg<T>) -> TypedProg<T> {
|
||||
let p = fold_program(self, p);
|
||||
// only keep functions which are being called, or `main`
|
||||
|
||||
TypedProg {
|
||||
functions: p.functions.into_iter().filter(|f| f.id == "main" || self.called.contains(&f.to_slug())).collect(),
|
||||
..p
|
||||
}
|
||||
}
|
||||
|
||||
// add extra statements before the modified statement
|
||||
fn fold_statement(&mut self, s: TypedStatement<T>) -> Vec<TypedStatement<T>> {
|
||||
match s {
|
||||
TypedStatement::MultipleDefinition(variables, elist) => {
|
||||
match elist {
|
||||
TypedExpressionList::FunctionCall(id, exps, types) => {
|
||||
let exps: Vec<_> = exps.into_iter().map(|e| self.fold_expression(e)).collect();
|
||||
|
||||
let signature = Signature::new()
|
||||
.inputs(exps.iter().map(|e| e.get_type()).collect())
|
||||
.outputs(types.clone());
|
||||
|
||||
self.called.insert(format!("{}_{}", id, signature.to_slug()));
|
||||
vec![TypedStatement::MultipleDefinition(variables, TypedExpressionList::FunctionCall(id, exps, types))]
|
||||
}
|
||||
}
|
||||
},
|
||||
s => fold_statement(self, s)
|
||||
}
|
||||
}
|
||||
|
||||
fn fold_field_expression(&mut self, e: FieldElementExpression<T>) -> FieldElementExpression<T> {
|
||||
match e {
|
||||
FieldElementExpression::FunctionCall(id, exps) => {
|
||||
let exps: Vec<_> = exps.into_iter().map(|e| self.fold_expression(e)).collect();
|
||||
|
||||
let signature = Signature::new()
|
||||
.inputs(exps.iter().map(|e| e.get_type()).collect())
|
||||
.outputs(vec![Type::FieldElement]);
|
||||
|
||||
self.called.insert(format!("{}_{}", id, signature.to_slug()));
|
||||
FieldElementExpression::FunctionCall(id, exps)
|
||||
},
|
||||
e => fold_field_expression(self, e)
|
||||
}
|
||||
}
|
||||
|
||||
fn fold_field_array_expression(&mut self, e: FieldElementArrayExpression<T>) -> FieldElementArrayExpression<T> {
|
||||
match e {
|
||||
FieldElementArrayExpression::FunctionCall(size, id, exps) => {
|
||||
let exps: Vec<_> = exps.into_iter().map(|e| self.fold_expression(e)).collect();
|
||||
|
||||
let signature = Signature::new()
|
||||
.inputs(exps.iter().map(|e| e.get_type()).collect())
|
||||
.outputs(vec![Type::FieldElementArray(size)]);
|
||||
|
||||
self.called.insert(format!("{}_{}", id, signature));
|
||||
FieldElementArrayExpression::FunctionCall(size, id, exps)
|
||||
},
|
||||
e => fold_field_array_expression(self, e)
|
||||
}
|
||||
}
|
||||
}
|
194
zokrates_core/src/static_analysis/inline.rs
Normal file
194
zokrates_core/src/static_analysis/inline.rs
Normal file
|
@ -0,0 +1,194 @@
|
|||
use std::collections::HashMap;
|
||||
use field::Field;
|
||||
use typed_absy::Folder;
|
||||
use typed_absy::*;
|
||||
use typed_absy::folder::*;
|
||||
use types::{Type, Signature};
|
||||
|
||||
pub struct Inliner<T: Field> {
|
||||
functions: Vec<TypedFunction<T>>,
|
||||
statements_buffer: Vec<TypedStatement<T>>,
|
||||
context: Vec<(String, usize)>,
|
||||
call_count: HashMap<String, usize>
|
||||
}
|
||||
|
||||
impl<T: Field> Inliner<T> {
|
||||
pub fn new() -> Self {
|
||||
Inliner {
|
||||
functions: vec![],
|
||||
statements_buffer: vec![],
|
||||
context: vec![],
|
||||
call_count: HashMap::new()
|
||||
}
|
||||
}
|
||||
|
||||
fn should_inline(&self, function: &Option<TypedFunction<T>>, _arguments: &Vec<TypedExpression<T>>) -> bool {
|
||||
// we should define a heuristic here
|
||||
// currently it doesn't seem like there's a tradeoff as everything gets inlined in flattening anyway
|
||||
// however, using backends such as bellman, we could avoid flattening and "stream" the computation
|
||||
// at proving time, the tradeoff becomes code size (not inlining keeps only one copy of each function) vs optimisation
|
||||
// (inlining enables constant propagation through function calls, which cannot be achieved by our final optimiser in some cases)
|
||||
match function {
|
||||
Some(..) => true,
|
||||
None => false
|
||||
}
|
||||
}
|
||||
|
||||
// inline a call to `function` taking `expressions` as inputs
|
||||
// this function mutates `self.call` by incrementing the counter for `function`, and mutates `self.context`
|
||||
fn inline_call(&mut self, function: &TypedFunction<T>, expressions: Vec<TypedExpression<T>>) -> Vec<TypedExpression<T>> {
|
||||
self.call_count.entry(function.to_slug()).and_modify(|i| *i += 1).or_insert(1);
|
||||
self.context.push((function.to_slug(), *self.call_count.get(&function.to_slug()).unwrap()));
|
||||
|
||||
// add definitions for the inputs
|
||||
let mut inputs_bindings = function.arguments.iter().zip(expressions)
|
||||
.map(|(a, e)|
|
||||
TypedStatement::Definition(
|
||||
TypedAssignee::Identifier(self.fold_variable(a.id.clone())),
|
||||
e
|
||||
)
|
||||
).collect();
|
||||
self.statements_buffer.append(&mut inputs_bindings);
|
||||
|
||||
// filter out the return statement and keep it aside
|
||||
let (mut statements, ret) : (Vec<_>, Vec<_>) = function.statements.clone().into_iter().flat_map(|s| self.fold_statement(s)).partition(|s| match s {
|
||||
TypedStatement::Return(..) => false,
|
||||
_ => true
|
||||
});
|
||||
|
||||
// add all statements to the buffer
|
||||
self.statements_buffer.append(&mut statements);
|
||||
|
||||
// remove this call from the context
|
||||
self.context.pop();
|
||||
|
||||
match ret[0].clone() {
|
||||
TypedStatement::Return(exprs) => exprs,
|
||||
_ => panic!("")
|
||||
}
|
||||
}
|
||||
|
||||
pub fn inline(prog: TypedProg<T>) -> TypedProg<T> {
|
||||
Inliner::new().fold_program(prog)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Field> Folder<T> for Inliner<T> {
|
||||
// store the list of functions
|
||||
fn fold_program(&mut self, p: TypedProg<T>) -> TypedProg<T> {
|
||||
self.functions = p.functions.clone();
|
||||
fold_program(self, p)
|
||||
}
|
||||
|
||||
// add extra statements before the modified statement
|
||||
fn fold_statement(&mut self, s: TypedStatement<T>) -> Vec<TypedStatement<T>> {
|
||||
let mut statements = match s {
|
||||
TypedStatement::MultipleDefinition(variables, elist) => {
|
||||
match elist {
|
||||
TypedExpressionList::FunctionCall(id, exps, types) => {
|
||||
let variables: Vec<_> = variables.into_iter().map(|a| self.fold_variable(a)).collect();
|
||||
let exps: Vec<_> = exps.into_iter().map(|e| self.fold_expression(e)).collect();
|
||||
|
||||
let passed_signature = Signature::new()
|
||||
.inputs(exps.iter().map(|e| e.get_type()).collect())
|
||||
.outputs(types.clone());
|
||||
|
||||
// find the function
|
||||
let function = self.functions.iter().find(|f| f.id == id && f.signature == passed_signature).cloned();
|
||||
|
||||
match self.should_inline(&function, &exps) {
|
||||
true => {
|
||||
let ret = self.inline_call(&function.unwrap(), exps);
|
||||
variables.into_iter().zip(ret.into_iter()).map(|(v, e)| TypedStatement::Definition(TypedAssignee::Identifier(v), e)).collect()
|
||||
},
|
||||
false => {
|
||||
vec![TypedStatement::MultipleDefinition(variables, TypedExpressionList::FunctionCall(id, exps, types))]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
s => fold_statement(self, s)
|
||||
};
|
||||
|
||||
// add the result of folding to the buffer
|
||||
self.statements_buffer.append(&mut statements);
|
||||
// return the whole buffer
|
||||
self.statements_buffer.drain(..).collect()
|
||||
}
|
||||
|
||||
// prefix all names with the context
|
||||
fn fold_name(&mut self, n: String) -> String {
|
||||
match self.context.len() {
|
||||
0 => n,
|
||||
_ => format!("{}_{}", self
|
||||
.context
|
||||
.iter()
|
||||
.map(|(s, i)|
|
||||
format!("{}_{}", s, i)
|
||||
)
|
||||
.collect::<Vec<_>>()
|
||||
.join("_"), n)
|
||||
}
|
||||
}
|
||||
|
||||
// inline calls which return a field element
|
||||
fn fold_field_expression(&mut self, e: FieldElementExpression<T>) -> FieldElementExpression<T> {
|
||||
match e {
|
||||
FieldElementExpression::FunctionCall(id, exps) => {
|
||||
let exps: Vec<_> = exps.into_iter().map(|e| self.fold_expression(e)).collect();
|
||||
|
||||
let passed_signature = Signature::new()
|
||||
.inputs(exps.iter().map(|e| e.get_type()).collect())
|
||||
.outputs(vec![Type::FieldElement]);
|
||||
|
||||
// find the function
|
||||
let function = self.functions.iter().find(|f| f.id == id && f.signature == passed_signature).cloned();
|
||||
|
||||
match self.should_inline(&function, &exps) {
|
||||
true => {
|
||||
let ret = self.inline_call(&function.unwrap(), exps);
|
||||
// unwrap the result to return a field element
|
||||
match ret[0].clone() {
|
||||
TypedExpression::FieldElement(e) => e,
|
||||
_ => panic!("")
|
||||
}
|
||||
},
|
||||
false => FieldElementExpression::FunctionCall(id, exps)
|
||||
}
|
||||
},
|
||||
// default
|
||||
e => fold_field_expression(self, e)
|
||||
}
|
||||
}
|
||||
|
||||
// inline calls which return a field element array
|
||||
fn fold_field_array_expression(&mut self, e: FieldElementArrayExpression<T>) -> FieldElementArrayExpression<T> {
|
||||
match e {
|
||||
FieldElementArrayExpression::FunctionCall(size, id, exps) => {
|
||||
let exps: Vec<_> = exps.into_iter().map(|e| self.fold_expression(e)).collect();
|
||||
|
||||
let passed_signature = Signature::new()
|
||||
.inputs(exps.iter().map(|e| e.get_type()).collect())
|
||||
.outputs(vec![Type::FieldElementArray(size)]);
|
||||
|
||||
// find the function
|
||||
let function = self.functions.iter().find(|f| f.id == id && f.signature == passed_signature).cloned();
|
||||
|
||||
match self.should_inline(&function, &exps) {
|
||||
true => {
|
||||
let ret = self.inline_call(&function.unwrap(), exps);
|
||||
// unwrap the result to return a field element
|
||||
match ret[0].clone() {
|
||||
TypedExpression::FieldElementArray(e) => e,
|
||||
_ => panic!("")
|
||||
}
|
||||
},
|
||||
false => FieldElementArrayExpression::FunctionCall(size, id, exps)
|
||||
}
|
||||
},
|
||||
// default
|
||||
e => fold_field_array_expression(self, e)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,11 +7,16 @@
|
|||
mod propagation;
|
||||
mod unroll;
|
||||
mod flat_propagation;
|
||||
mod inline;
|
||||
mod dead_code;
|
||||
|
||||
use flat_absy::FlatProg;
|
||||
use field::Field;
|
||||
use typed_absy::TypedProg;
|
||||
use self::unroll::Unroll;
|
||||
use self::unroll::Unroller;
|
||||
use self::inline::Inliner;
|
||||
use self::propagation::Propagator;
|
||||
use self::dead_code::DeadCode;
|
||||
|
||||
pub trait Analyse {
|
||||
fn analyse(self) -> Self;
|
||||
|
@ -19,10 +24,16 @@ pub trait Analyse {
|
|||
|
||||
impl<T: Field> Analyse for TypedProg<T> {
|
||||
fn analyse(self) -> Self {
|
||||
//self.unroll().propagate()
|
||||
let r = self.unroll();
|
||||
let r = r.propagate();
|
||||
print!("propagated! {}", r);
|
||||
// unroll
|
||||
let r = Unroller::unroll(self);
|
||||
//propagate a first time for constants to reach function calls
|
||||
let r = Propagator::propagate(r);
|
||||
// apply inlining strategy
|
||||
let r = Inliner::inline(r);
|
||||
// Propagate again
|
||||
let r = Propagator::propagate(r);
|
||||
// remove unused functions
|
||||
let r = DeadCode::clean(r);
|
||||
r
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,223 +8,39 @@ use absy::variable::Variable;
|
|||
use std::collections::HashMap;
|
||||
use field::Field;
|
||||
use typed_absy::*;
|
||||
use typed_absy::folder::*;
|
||||
|
||||
pub trait Propagate<T: Field> {
|
||||
fn propagate(self, functions: &Vec<TypedFunction<T>>) -> Self;
|
||||
pub struct Propagator<T: Field> {
|
||||
constants: HashMap<TypedAssignee<T>, TypedExpression<T>>,
|
||||
}
|
||||
|
||||
pub trait PropagateWithContext<T: Field> {
|
||||
fn propagate(self, constants: &mut HashMap<TypedAssignee<T>, TypedExpression<T>>, functions: &Vec<TypedFunction<T>>) -> Self;
|
||||
}
|
||||
|
||||
impl<T: Field> PropagateWithContext<T> for TypedExpression<T> {
|
||||
fn propagate(self, constants: &mut HashMap<TypedAssignee<T>, TypedExpression<T>>, functions: &Vec<TypedFunction<T>>) -> TypedExpression<T> {
|
||||
match self {
|
||||
TypedExpression::FieldElement(e) => e.propagate(constants, functions).into(),
|
||||
TypedExpression::Boolean(e) => e.propagate(constants, functions).into(),
|
||||
TypedExpression::FieldElementArray(e) => e.propagate(constants, functions).into(),
|
||||
impl<T: Field> Propagator<T> {
|
||||
fn new() -> Self {
|
||||
Propagator {
|
||||
constants: HashMap::new()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn propagate(p: TypedProg<T>) -> TypedProg<T> {
|
||||
Propagator::new().fold_program(p)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Field> PropagateWithContext<T> for FieldElementExpression<T> {
|
||||
fn propagate(self, constants: &mut HashMap<TypedAssignee<T>, TypedExpression<T>>, functions: &Vec<TypedFunction<T>>) -> FieldElementExpression<T> {
|
||||
match self {
|
||||
FieldElementExpression::Number(n) => FieldElementExpression::Number(n),
|
||||
FieldElementExpression::Identifier(id) => {
|
||||
match constants.get(&TypedAssignee::Identifier(Variable::field_element(id.clone()))) {
|
||||
Some(e) => match e {
|
||||
TypedExpression::FieldElement(e) => e.clone(),
|
||||
_ => panic!("constant stored for a field element should be a field element")
|
||||
},
|
||||
None => FieldElementExpression::Identifier(id)
|
||||
}
|
||||
},
|
||||
FieldElementExpression::Add(box e1, box e2) => {
|
||||
match (e1.propagate(constants, functions), e2.propagate(constants, functions)) {
|
||||
(FieldElementExpression::Number(n1), FieldElementExpression::Number(n2)) => FieldElementExpression::Number(n1 + n2),
|
||||
(e1, e2) => FieldElementExpression::Add(box e1, box e2),
|
||||
}
|
||||
},
|
||||
FieldElementExpression::Sub(box e1, box e2) => {
|
||||
match (e1.propagate(constants, functions), e2.propagate(constants, functions)) {
|
||||
(FieldElementExpression::Number(n1), FieldElementExpression::Number(n2)) => FieldElementExpression::Number(n1 - n2),
|
||||
(e1, e2) => FieldElementExpression::Sub(box e1, box e2),
|
||||
}
|
||||
},
|
||||
FieldElementExpression::Mult(box e1, box e2) => {
|
||||
match (e1.propagate(constants, functions), e2.propagate(constants, functions)) {
|
||||
(FieldElementExpression::Number(n1), FieldElementExpression::Number(n2)) => FieldElementExpression::Number(n1 * n2),
|
||||
(e1, e2) => FieldElementExpression::Mult(box e1, box e2),
|
||||
}
|
||||
},
|
||||
FieldElementExpression::Div(box e1, box e2) => {
|
||||
match (e1.propagate(constants, functions), e2.propagate(constants, functions)) {
|
||||
(FieldElementExpression::Number(n1), FieldElementExpression::Number(n2)) => FieldElementExpression::Number(n1 / n2),
|
||||
(e1, e2) => FieldElementExpression::Div(box e1, box e2),
|
||||
}
|
||||
},
|
||||
FieldElementExpression::Pow(box e1, box e2) => {
|
||||
match (e1.propagate(constants, functions), e2.propagate(constants, functions)) {
|
||||
(FieldElementExpression::Number(n1), FieldElementExpression::Number(n2)) => FieldElementExpression::Number(n1.pow(n2)),
|
||||
(e1, e2) => FieldElementExpression::Pow(box e1, box e2),
|
||||
}
|
||||
},
|
||||
FieldElementExpression::IfElse(box condition, box consequence, box alternative) => {
|
||||
let consequence = consequence.propagate(constants, functions);
|
||||
let alternative = alternative.propagate(constants, functions);
|
||||
match condition.propagate(constants, functions) {
|
||||
BooleanExpression::Value(true) => consequence,
|
||||
BooleanExpression::Value(false) => alternative,
|
||||
c => FieldElementExpression::IfElse(box c, box consequence, box alternative)
|
||||
}
|
||||
},
|
||||
FieldElementExpression::FunctionCall(id, arguments) => {
|
||||
// propagation through function calls is handled after flattening, we only propagate arguments
|
||||
let arguments = arguments.into_iter().map(|a| a.propagate(constants, functions)).collect();
|
||||
|
||||
FieldElementExpression::FunctionCall(id, arguments)
|
||||
}
|
||||
FieldElementExpression::Select(box array, box index) => {
|
||||
let array = array.propagate(constants, functions);
|
||||
let index = index.propagate(constants, functions);
|
||||
|
||||
match (array, index) {
|
||||
(FieldElementArrayExpression::Value(size, v), FieldElementExpression::Number(n)) => {
|
||||
let n_as_usize = n.to_dec_string().parse::<usize>().unwrap();
|
||||
if n_as_usize < size {
|
||||
v[n_as_usize].clone()
|
||||
} else {
|
||||
panic!(format!("out of bounds index ({} >= {}) found during static analysis", n_as_usize, size));
|
||||
}
|
||||
},
|
||||
(FieldElementArrayExpression::Identifier(size, id), FieldElementExpression::Number(n)) => {
|
||||
match constants.get(&TypedAssignee::ArrayElement(box TypedAssignee::Identifier(Variable::field_array(id.clone(), size)), box FieldElementExpression::Number(n.clone()).into())) {
|
||||
Some(e) => match e {
|
||||
TypedExpression::FieldElement(e) => e.clone(),
|
||||
_ => panic!("")
|
||||
},
|
||||
None => FieldElementExpression::Select(box FieldElementArrayExpression::Identifier(size, id), box FieldElementExpression::Number(n))
|
||||
}
|
||||
},
|
||||
(a, i) => FieldElementExpression::Select(box a, box i),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<T: Field> Folder<T> for Propagator<T> {
|
||||
fn fold_function(&mut self, f: TypedFunction<T>) -> TypedFunction<T> {
|
||||
self.constants = HashMap::new();
|
||||
fold_function(self, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Field> PropagateWithContext<T> for FieldElementArrayExpression<T> {
|
||||
fn propagate(self, constants: &mut HashMap<TypedAssignee<T>, TypedExpression<T>>, functions: &Vec<TypedFunction<T>>) -> FieldElementArrayExpression<T> {
|
||||
match self {
|
||||
FieldElementArrayExpression::Identifier(size, id) => {
|
||||
match constants.get(&TypedAssignee::Identifier(Variable::field_array(id.clone(), size))) {
|
||||
Some(e) => match e {
|
||||
TypedExpression::FieldElementArray(e) => e.clone(),
|
||||
_ => panic!("constant stored for an array should be an array")
|
||||
},
|
||||
None => FieldElementArrayExpression::Identifier(size, id)
|
||||
}
|
||||
},
|
||||
FieldElementArrayExpression::Value(size, exprs) => {
|
||||
FieldElementArrayExpression::Value(size, exprs.into_iter().map(|e| e.propagate(constants, functions)).collect())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Field> PropagateWithContext<T> for BooleanExpression<T> {
|
||||
fn propagate(self, constants: &mut HashMap<TypedAssignee<T>, TypedExpression<T>>, functions: &Vec<TypedFunction<T>>) -> BooleanExpression<T> {
|
||||
match self {
|
||||
BooleanExpression::Value(v) => BooleanExpression::Value(v),
|
||||
BooleanExpression::Identifier(id) => {
|
||||
match constants.get(&TypedAssignee::Identifier(Variable::boolean(id.clone()))) {
|
||||
Some(e) => match e {
|
||||
TypedExpression::Boolean(e) => e.clone(),
|
||||
_ => panic!("constant stored for a boolean should be a boolean")
|
||||
},
|
||||
None => BooleanExpression::Identifier(id)
|
||||
}
|
||||
},
|
||||
BooleanExpression::Eq(box e1, box e2) => {
|
||||
let e1 = e1.propagate(constants, functions);
|
||||
let e2 = e2.propagate(constants, functions);
|
||||
|
||||
match (e1, e2) {
|
||||
(FieldElementExpression::Number(n1), FieldElementExpression::Number(n2)) => {
|
||||
BooleanExpression::Value(n1 == n2)
|
||||
}
|
||||
(e1, e2) => BooleanExpression::Eq(box e1, box e2)
|
||||
}
|
||||
}
|
||||
BooleanExpression::Lt(box e1, box e2) => {
|
||||
let e1 = e1.propagate(constants, functions);
|
||||
let e2 = e2.propagate(constants, functions);
|
||||
|
||||
match (e1, e2) {
|
||||
(FieldElementExpression::Number(n1), FieldElementExpression::Number(n2)) => {
|
||||
BooleanExpression::Value(n1 < n2)
|
||||
}
|
||||
(e1, e2) => BooleanExpression::Lt(box e1, box e2)
|
||||
}
|
||||
}
|
||||
BooleanExpression::Le(box e1, box e2) => {
|
||||
let e1 = e1.propagate(constants, functions);
|
||||
let e2 = e2.propagate(constants, functions);
|
||||
|
||||
match (e1, e2) {
|
||||
(FieldElementExpression::Number(n1), FieldElementExpression::Number(n2)) => {
|
||||
BooleanExpression::Value(n1 <= n2)
|
||||
}
|
||||
(e1, e2) => BooleanExpression::Le(box e1, box e2)
|
||||
}
|
||||
}
|
||||
BooleanExpression::Gt(box e1, box e2) => {
|
||||
let e1 = e1.propagate(constants, functions);
|
||||
let e2 = e2.propagate(constants, functions);
|
||||
|
||||
match (e1, e2) {
|
||||
(FieldElementExpression::Number(n1), FieldElementExpression::Number(n2)) => {
|
||||
BooleanExpression::Value(n1 > n2)
|
||||
}
|
||||
(e1, e2) => BooleanExpression::Gt(box e1, box e2)
|
||||
}
|
||||
}
|
||||
BooleanExpression::Ge(box e1, box e2) => {
|
||||
let e1 = e1.propagate(constants, functions);
|
||||
let e2 = e2.propagate(constants, functions);
|
||||
|
||||
match (e1, e2) {
|
||||
(FieldElementExpression::Number(n1), FieldElementExpression::Number(n2)) => {
|
||||
BooleanExpression::Value(n1 >= n2)
|
||||
}
|
||||
(e1, e2) => BooleanExpression::Ge(box e1, box e2)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Field> TypedExpressionList<T> {
|
||||
fn propagate(self, constants: &mut HashMap<TypedAssignee<T>, TypedExpression<T>>, functions: &Vec<TypedFunction<T>>) -> TypedExpressionList<T> {
|
||||
match self {
|
||||
TypedExpressionList::FunctionCall(id, arguments, types) => {
|
||||
TypedExpressionList::FunctionCall(id, arguments.into_iter().map(|e| e.propagate(constants, functions)).collect(), types)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Field> TypedStatement<T> {
|
||||
fn propagate(self, constants: &mut HashMap<TypedAssignee<T>, TypedExpression<T>>, functions: &Vec<TypedFunction<T>>) -> Option<TypedStatement<T>> {
|
||||
match self {
|
||||
fn fold_statement(&mut self, s: TypedStatement<T>) -> Vec<TypedStatement<T>> {
|
||||
let res = match s {
|
||||
TypedStatement::Declaration(v) => Some(TypedStatement::Declaration(v)),
|
||||
TypedStatement::Return(expressions) => Some(TypedStatement::Return(expressions.into_iter().map(|e| e.propagate(constants, functions)).collect())),
|
||||
TypedStatement::Return(expressions) => Some(TypedStatement::Return(expressions.into_iter().map(|e| self.fold_expression(e)).collect())),
|
||||
// propagation to the defined variable if rhs is a constant
|
||||
TypedStatement::Definition(TypedAssignee::Identifier(var), expr) => {
|
||||
match expr.propagate(constants, functions) {
|
||||
match self.fold_expression(expr) {
|
||||
e @ TypedExpression::Boolean(BooleanExpression::Value(..)) | e @ TypedExpression::FieldElement(FieldElementExpression::Number(..)) => {
|
||||
constants.insert(TypedAssignee::Identifier(var), e);
|
||||
self.constants.insert(TypedAssignee::Identifier(var), e);
|
||||
None
|
||||
},
|
||||
TypedExpression::FieldElementArray(FieldElementArrayExpression::Value(size, array)) => {
|
||||
|
@ -234,11 +50,11 @@ impl<T: Field> TypedStatement<T> {
|
|||
}) {
|
||||
true => {
|
||||
// all elements of the array are constants
|
||||
constants.insert(TypedAssignee::Identifier(var), FieldElementArrayExpression::Value(size, array).into());
|
||||
return None;
|
||||
self.constants.insert(TypedAssignee::Identifier(var), FieldElementArrayExpression::Value(size, array).into());
|
||||
None
|
||||
},
|
||||
false => {
|
||||
return Some(TypedStatement::Definition(TypedAssignee::Identifier(var), FieldElementArrayExpression::Value(size, array).into()));
|
||||
Some(TypedStatement::Definition(TypedAssignee::Identifier(var), FieldElementArrayExpression::Value(size, array).into()))
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -249,8 +65,8 @@ impl<T: Field> TypedStatement<T> {
|
|||
},
|
||||
// a[b] = c
|
||||
TypedStatement::Definition(TypedAssignee::ArrayElement(box TypedAssignee::Identifier(var), box index), expr) => {
|
||||
let index = index.propagate(constants, functions);
|
||||
let expr = expr.propagate(constants, functions);
|
||||
let index = self.fold_field_expression(index);
|
||||
let expr = self.fold_expression(expr);
|
||||
|
||||
match (index, expr) {
|
||||
(
|
||||
|
@ -259,7 +75,7 @@ impl<T: Field> TypedStatement<T> {
|
|||
) => {
|
||||
// a[42] = 33
|
||||
// -> store (a[42] -> 33) in the constants, possibly overwriting the previous entry
|
||||
constants.entry(TypedAssignee::Identifier(var)).and_modify(|e| {
|
||||
self.constants.entry(TypedAssignee::Identifier(var)).and_modify(|e| {
|
||||
match *e {
|
||||
TypedExpression::FieldElementArray(FieldElementArrayExpression::Value(size, ref mut v)) => {
|
||||
let n_as_usize = n.to_dec_string().parse::<usize>().unwrap();
|
||||
|
@ -277,7 +93,7 @@ impl<T: Field> TypedStatement<T> {
|
|||
(index, expr) => {
|
||||
// a[42] = e
|
||||
// -> remove a from the constants as one of its elements is not constant
|
||||
constants.remove(&TypedAssignee::Identifier(var.clone()));
|
||||
self.constants.remove(&TypedAssignee::Identifier(var.clone()));
|
||||
Some(TypedStatement::Definition(TypedAssignee::ArrayElement(box TypedAssignee::Identifier(var), box index), expr))
|
||||
}
|
||||
}
|
||||
|
@ -286,42 +102,182 @@ impl<T: Field> TypedStatement<T> {
|
|||
// propagate lhs and rhs for conditions
|
||||
TypedStatement::Condition(e1, e2) => {
|
||||
// could stop execution here if condition is known to fail
|
||||
Some(TypedStatement::Condition(e1.propagate(constants, functions), e2.propagate(constants, functions)))
|
||||
Some(TypedStatement::Condition(self.fold_expression(e1), self.fold_expression(e2)))
|
||||
},
|
||||
// we unrolled for loops in the previous step
|
||||
TypedStatement::For(..) => panic!("for loop is unexpected, it should have been unrolled"),
|
||||
TypedStatement::MultipleDefinition(variables, expression_list) => {
|
||||
let expression_list = expression_list.propagate(constants, functions);
|
||||
let expression_list = self.fold_expression_list(expression_list);
|
||||
Some(TypedStatement::MultipleDefinition(variables, expression_list))
|
||||
}
|
||||
};
|
||||
match res {
|
||||
Some(v) => vec![v],
|
||||
None => vec![]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Field> Propagate<T> for TypedFunction<T> {
|
||||
fn propagate(self, functions: &Vec<TypedFunction<T>>) -> TypedFunction<T> {
|
||||
fn fold_field_expression(&mut self, e: FieldElementExpression<T>) -> FieldElementExpression<T> {
|
||||
match e {
|
||||
FieldElementExpression::Identifier(id) => {
|
||||
match self.constants.get(&TypedAssignee::Identifier(Variable::field_element(id.clone()))) {
|
||||
Some(e) => match e {
|
||||
TypedExpression::FieldElement(e) => e.clone(),
|
||||
_ => panic!("constant stored for a field element should be a field element")
|
||||
},
|
||||
None => FieldElementExpression::Identifier(id)
|
||||
}
|
||||
},
|
||||
FieldElementExpression::Add(box e1, box e2) => {
|
||||
match (self.fold_field_expression(e1), self.fold_field_expression(e2)) {
|
||||
(FieldElementExpression::Number(n1), FieldElementExpression::Number(n2)) => FieldElementExpression::Number(n1 + n2),
|
||||
(e1, e2) => FieldElementExpression::Add(box e1, box e2),
|
||||
}
|
||||
},
|
||||
FieldElementExpression::Sub(box e1, box e2) => {
|
||||
match (self.fold_field_expression(e1), self.fold_field_expression(e2)) {
|
||||
(FieldElementExpression::Number(n1), FieldElementExpression::Number(n2)) => FieldElementExpression::Number(n1 - n2),
|
||||
(e1, e2) => FieldElementExpression::Sub(box e1, box e2),
|
||||
}
|
||||
},
|
||||
FieldElementExpression::Mult(box e1, box e2) => {
|
||||
match (self.fold_field_expression(e1), self.fold_field_expression(e2)) {
|
||||
(FieldElementExpression::Number(n1), FieldElementExpression::Number(n2)) => FieldElementExpression::Number(n1 * n2),
|
||||
(e1, e2) => FieldElementExpression::Mult(box e1, box e2),
|
||||
}
|
||||
},
|
||||
FieldElementExpression::Div(box e1, box e2) => {
|
||||
match (self.fold_field_expression(e1), self.fold_field_expression(e2)) {
|
||||
(FieldElementExpression::Number(n1), FieldElementExpression::Number(n2)) => FieldElementExpression::Number(n1 / n2),
|
||||
(e1, e2) => FieldElementExpression::Div(box e1, box e2),
|
||||
}
|
||||
},
|
||||
FieldElementExpression::Pow(box e1, box e2) => {
|
||||
match (self.fold_field_expression(e1), self.fold_field_expression(e2)) {
|
||||
(FieldElementExpression::Number(n1), FieldElementExpression::Number(n2)) => FieldElementExpression::Number(n1.pow(n2)),
|
||||
(e1, e2) => FieldElementExpression::Pow(box e1, box e2),
|
||||
}
|
||||
},
|
||||
FieldElementExpression::IfElse(box condition, box consequence, box alternative) => {
|
||||
let consequence = self.fold_field_expression(consequence);
|
||||
let alternative = self.fold_field_expression(alternative);
|
||||
match self.fold_boolean_expression(condition) {
|
||||
BooleanExpression::Value(true) => consequence,
|
||||
BooleanExpression::Value(false) => alternative,
|
||||
c => FieldElementExpression::IfElse(box c, box consequence, box alternative)
|
||||
}
|
||||
},
|
||||
FieldElementExpression::Select(box array, box index) => {
|
||||
let array = self.fold_field_array_expression(array);
|
||||
let index = self.fold_field_expression(index);
|
||||
|
||||
let mut constants = HashMap::new();
|
||||
|
||||
TypedFunction {
|
||||
statements: self.statements.into_iter().filter_map(|s| s.propagate(&mut constants, functions)).collect(),
|
||||
..self
|
||||
match (array, index) {
|
||||
(FieldElementArrayExpression::Value(size, v), FieldElementExpression::Number(n)) => {
|
||||
let n_as_usize = n.to_dec_string().parse::<usize>().unwrap();
|
||||
if n_as_usize < size {
|
||||
v[n_as_usize].clone()
|
||||
} else {
|
||||
panic!(format!("out of bounds index ({} >= {}) found during static analysis", n_as_usize, size));
|
||||
}
|
||||
},
|
||||
(FieldElementArrayExpression::Identifier(size, id), FieldElementExpression::Number(n)) => {
|
||||
match self.constants.get(&TypedAssignee::ArrayElement(box TypedAssignee::Identifier(Variable::field_array(id.clone(), size)), box FieldElementExpression::Number(n.clone()).into())) {
|
||||
Some(e) => match e {
|
||||
TypedExpression::FieldElement(e) => e.clone(),
|
||||
_ => panic!("")
|
||||
},
|
||||
None => FieldElementExpression::Select(box FieldElementArrayExpression::Identifier(size, id), box FieldElementExpression::Number(n))
|
||||
}
|
||||
},
|
||||
(a, i) => FieldElementExpression::Select(box a, box i),
|
||||
}
|
||||
},
|
||||
e => fold_field_expression(self, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Field> TypedProg<T> {
|
||||
pub fn propagate(self) -> TypedProg<T> {
|
||||
let mut functions = vec![];
|
||||
|
||||
for f in self.functions {
|
||||
let fun = f.propagate(&mut functions);
|
||||
functions.push(fun);
|
||||
fn fold_field_array_expression(&mut self, e: FieldElementArrayExpression<T>) -> FieldElementArrayExpression<T> {
|
||||
match e {
|
||||
FieldElementArrayExpression::Identifier(size, id) => {
|
||||
match self.constants.get(&TypedAssignee::Identifier(Variable::field_array(id.clone(), size))) {
|
||||
Some(e) => match e {
|
||||
TypedExpression::FieldElementArray(e) => e.clone(),
|
||||
_ => panic!("constant stored for an array should be an array")
|
||||
},
|
||||
None => FieldElementArrayExpression::Identifier(size, id)
|
||||
}
|
||||
},
|
||||
e => fold_field_array_expression(self, e)
|
||||
}
|
||||
}
|
||||
|
||||
TypedProg {
|
||||
functions,
|
||||
..self
|
||||
fn fold_boolean_expression(&mut self, e: BooleanExpression<T>) -> BooleanExpression<T> {
|
||||
match e {
|
||||
BooleanExpression::Identifier(id) => {
|
||||
match self.constants.get(&TypedAssignee::Identifier(Variable::boolean(id.clone()))) {
|
||||
Some(e) => match e {
|
||||
TypedExpression::Boolean(e) => e.clone(),
|
||||
_ => panic!("constant stored for a boolean should be a boolean")
|
||||
},
|
||||
None => BooleanExpression::Identifier(id)
|
||||
}
|
||||
},
|
||||
BooleanExpression::Eq(box e1, box e2) => {
|
||||
let e1 = self.fold_field_expression(e1);
|
||||
let e2 = self.fold_field_expression(e2);
|
||||
|
||||
match (e1, e2) {
|
||||
(FieldElementExpression::Number(n1), FieldElementExpression::Number(n2)) => {
|
||||
BooleanExpression::Value(n1 == n2)
|
||||
}
|
||||
(e1, e2) => BooleanExpression::Eq(box e1, box e2)
|
||||
}
|
||||
}
|
||||
BooleanExpression::Lt(box e1, box e2) => {
|
||||
let e1 = self.fold_field_expression(e1);
|
||||
let e2 = self.fold_field_expression(e2);
|
||||
|
||||
match (e1, e2) {
|
||||
(FieldElementExpression::Number(n1), FieldElementExpression::Number(n2)) => {
|
||||
BooleanExpression::Value(n1 < n2)
|
||||
}
|
||||
(e1, e2) => BooleanExpression::Lt(box e1, box e2)
|
||||
}
|
||||
}
|
||||
BooleanExpression::Le(box e1, box e2) => {
|
||||
let e1 = self.fold_field_expression(e1);
|
||||
let e2 = self.fold_field_expression(e2);
|
||||
|
||||
match (e1, e2) {
|
||||
(FieldElementExpression::Number(n1), FieldElementExpression::Number(n2)) => {
|
||||
BooleanExpression::Value(n1 <= n2)
|
||||
}
|
||||
(e1, e2) => BooleanExpression::Le(box e1, box e2)
|
||||
}
|
||||
}
|
||||
BooleanExpression::Gt(box e1, box e2) => {
|
||||
let e1 = self.fold_field_expression(e1);
|
||||
let e2 = self.fold_field_expression(e2);
|
||||
|
||||
match (e1, e2) {
|
||||
(FieldElementExpression::Number(n1), FieldElementExpression::Number(n2)) => {
|
||||
BooleanExpression::Value(n1 > n2)
|
||||
}
|
||||
(e1, e2) => BooleanExpression::Gt(box e1, box e2)
|
||||
}
|
||||
}
|
||||
BooleanExpression::Ge(box e1, box e2) => {
|
||||
let e1 = self.fold_field_expression(e1);
|
||||
let e2 = self.fold_field_expression(e2);
|
||||
|
||||
match (e1, e2) {
|
||||
(FieldElementExpression::Number(n1), FieldElementExpression::Number(n2)) => {
|
||||
BooleanExpression::Value(n1 >= n2)
|
||||
}
|
||||
(e1, e2) => BooleanExpression::Ge(box e1, box e2)
|
||||
}
|
||||
},
|
||||
e => fold_boolean_expression(self, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -346,7 +302,7 @@ mod tests {
|
|||
box FieldElementExpression::Number(FieldPrime::from(3))
|
||||
);
|
||||
|
||||
assert_eq!(e.propagate(&mut HashMap::new(), &mut vec![]), FieldElementExpression::Number(FieldPrime::from(5)));
|
||||
assert_eq!(Propagator::new().fold_field_expression(e), FieldElementExpression::Number(FieldPrime::from(5)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -356,7 +312,7 @@ mod tests {
|
|||
box FieldElementExpression::Number(FieldPrime::from(2))
|
||||
);
|
||||
|
||||
assert_eq!(e.propagate(&mut HashMap::new(), &mut vec![]), FieldElementExpression::Number(FieldPrime::from(1)));
|
||||
assert_eq!(Propagator::new().fold_field_expression(e), FieldElementExpression::Number(FieldPrime::from(1)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -366,7 +322,7 @@ mod tests {
|
|||
box FieldElementExpression::Number(FieldPrime::from(2))
|
||||
);
|
||||
|
||||
assert_eq!(e.propagate(&mut HashMap::new(), &mut vec![]), FieldElementExpression::Number(FieldPrime::from(6)));
|
||||
assert_eq!(Propagator::new().fold_field_expression(e), FieldElementExpression::Number(FieldPrime::from(6)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -376,7 +332,7 @@ mod tests {
|
|||
box FieldElementExpression::Number(FieldPrime::from(2))
|
||||
);
|
||||
|
||||
assert_eq!(e.propagate(&mut HashMap::new(), &mut vec![]), FieldElementExpression::Number(FieldPrime::from(3)));
|
||||
assert_eq!(Propagator::new().fold_field_expression(e), FieldElementExpression::Number(FieldPrime::from(3)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -386,7 +342,7 @@ mod tests {
|
|||
box FieldElementExpression::Number(FieldPrime::from(3))
|
||||
);
|
||||
|
||||
assert_eq!(e.propagate(&mut HashMap::new(), &mut vec![]), FieldElementExpression::Number(FieldPrime::from(8)));
|
||||
assert_eq!(Propagator::new().fold_field_expression(e), FieldElementExpression::Number(FieldPrime::from(8)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -397,7 +353,7 @@ mod tests {
|
|||
box FieldElementExpression::Number(FieldPrime::from(3))
|
||||
);
|
||||
|
||||
assert_eq!(e.propagate(&mut HashMap::new(), &mut vec![]), FieldElementExpression::Number(FieldPrime::from(2)));
|
||||
assert_eq!(Propagator::new().fold_field_expression(e), FieldElementExpression::Number(FieldPrime::from(2)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -408,7 +364,7 @@ mod tests {
|
|||
box FieldElementExpression::Number(FieldPrime::from(3))
|
||||
);
|
||||
|
||||
assert_eq!(e.propagate(&mut HashMap::new(), &mut vec![]), FieldElementExpression::Number(FieldPrime::from(3)));
|
||||
assert_eq!(Propagator::new().fold_field_expression(e), FieldElementExpression::Number(FieldPrime::from(3)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -421,7 +377,7 @@ mod tests {
|
|||
),
|
||||
);
|
||||
|
||||
assert_eq!(e.propagate(&mut HashMap::new(), &mut vec![]), FieldElementExpression::Number(FieldPrime::from(3)));
|
||||
assert_eq!(Propagator::new().fold_field_expression(e), FieldElementExpression::Number(FieldPrime::from(3)));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -441,8 +397,8 @@ mod tests {
|
|||
box FieldElementExpression::Number(FieldPrime::from(2))
|
||||
);
|
||||
|
||||
assert_eq!(e_true.propagate(&mut HashMap::new(), &mut vec![]), BooleanExpression::Value(true));
|
||||
assert_eq!(e_false.propagate(&mut HashMap::new(), &mut vec![]), BooleanExpression::Value(false));
|
||||
assert_eq!(Propagator::new().fold_boolean_expression(e_true), BooleanExpression::Value(true));
|
||||
assert_eq!(Propagator::new().fold_boolean_expression(e_false), BooleanExpression::Value(false));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -457,8 +413,8 @@ mod tests {
|
|||
box FieldElementExpression::Number(FieldPrime::from(2))
|
||||
);
|
||||
|
||||
assert_eq!(e_true.propagate(&mut HashMap::new(), &mut vec![]), BooleanExpression::Value(true));
|
||||
assert_eq!(e_false.propagate(&mut HashMap::new(), &mut vec![]), BooleanExpression::Value(false));
|
||||
assert_eq!(Propagator::new().fold_boolean_expression(e_true), BooleanExpression::Value(true));
|
||||
assert_eq!(Propagator::new().fold_boolean_expression(e_false), BooleanExpression::Value(false));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -473,8 +429,8 @@ mod tests {
|
|||
box FieldElementExpression::Number(FieldPrime::from(2))
|
||||
);
|
||||
|
||||
assert_eq!(e_true.propagate(&mut HashMap::new(), &mut vec![]), BooleanExpression::Value(true));
|
||||
assert_eq!(e_false.propagate(&mut HashMap::new(), &mut vec![]), BooleanExpression::Value(false));
|
||||
assert_eq!(Propagator::new().fold_boolean_expression(e_true), BooleanExpression::Value(true));
|
||||
assert_eq!(Propagator::new().fold_boolean_expression(e_false), BooleanExpression::Value(false));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -489,8 +445,8 @@ mod tests {
|
|||
box FieldElementExpression::Number(FieldPrime::from(5))
|
||||
);
|
||||
|
||||
assert_eq!(e_true.propagate(&mut HashMap::new(), &mut vec![]), BooleanExpression::Value(true));
|
||||
assert_eq!(e_false.propagate(&mut HashMap::new(), &mut vec![]), BooleanExpression::Value(false));
|
||||
assert_eq!(Propagator::new().fold_boolean_expression(e_true), BooleanExpression::Value(true));
|
||||
assert_eq!(Propagator::new().fold_boolean_expression(e_false), BooleanExpression::Value(false));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -505,8 +461,8 @@ mod tests {
|
|||
box FieldElementExpression::Number(FieldPrime::from(5))
|
||||
);
|
||||
|
||||
assert_eq!(e_true.propagate(&mut HashMap::new(), &mut vec![]), BooleanExpression::Value(true));
|
||||
assert_eq!(e_false.propagate(&mut HashMap::new(), &mut vec![]), BooleanExpression::Value(false));
|
||||
assert_eq!(Propagator::new().fold_boolean_expression(e_true), BooleanExpression::Value(true));
|
||||
assert_eq!(Propagator::new().fold_boolean_expression(e_false), BooleanExpression::Value(false));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -544,11 +500,10 @@ mod tests {
|
|||
FieldElementExpression::Number(FieldPrime::from(42)).into()
|
||||
);
|
||||
|
||||
let mut constants = HashMap::new();
|
||||
let mut functions = vec![];
|
||||
let mut p = Propagator::new();
|
||||
|
||||
declaration.propagate(&mut constants, &mut functions);
|
||||
definition.propagate(&mut constants, &mut functions);
|
||||
p.fold_statement(declaration);
|
||||
p.fold_statement(definition);
|
||||
let expected_value: TypedExpression<FieldPrime> = FieldElementArrayExpression::Value(
|
||||
2,
|
||||
vec![
|
||||
|
@ -556,11 +511,11 @@ mod tests {
|
|||
FieldElementExpression::Number(FieldPrime::from(22))
|
||||
]).into();
|
||||
|
||||
assert_eq!(constants.get(&TypedAssignee::Identifier(
|
||||
assert_eq!(p.constants.get(&TypedAssignee::Identifier(
|
||||
Variable::field_array("a", 2)
|
||||
)).unwrap(), &expected_value);
|
||||
|
||||
overwrite.propagate(&mut constants, &mut functions);
|
||||
p.fold_statement(overwrite);
|
||||
let expected_value: TypedExpression<FieldPrime> = FieldElementArrayExpression::Value(
|
||||
2,
|
||||
vec![
|
||||
|
@ -568,7 +523,7 @@ mod tests {
|
|||
FieldElementExpression::Number(FieldPrime::from(42))
|
||||
]).into();
|
||||
|
||||
assert_eq!(constants.get(&TypedAssignee::Identifier(
|
||||
assert_eq!(p.constants.get(&TypedAssignee::Identifier(
|
||||
Variable::field_array("a", 2)
|
||||
)).unwrap(), &expected_value);
|
||||
}
|
||||
|
@ -597,13 +552,12 @@ mod tests {
|
|||
FieldElementExpression::Number(FieldPrime::from(42)).into()
|
||||
);
|
||||
|
||||
let mut constants = HashMap::new();
|
||||
let mut functions = vec![];
|
||||
let mut p = Propagator::new();
|
||||
|
||||
declaration.propagate(&mut constants, &mut functions);
|
||||
overwrite.propagate(&mut constants, &mut functions);
|
||||
p.fold_statement(declaration);
|
||||
p.fold_statement(overwrite);
|
||||
|
||||
assert_eq!(constants.get(&TypedAssignee::Identifier(
|
||||
assert_eq!(p.constants.get(&TypedAssignee::Identifier(
|
||||
Variable::field_array("a", 2)
|
||||
)), None);
|
||||
}
|
||||
|
|
|
@ -4,111 +4,80 @@
|
|||
//! @author Thibaut Schaeffer <thibaut@schaeff.fr>
|
||||
//! @date 2018
|
||||
|
||||
use absy::parameter::Parameter;
|
||||
use absy::variable::Variable;
|
||||
use std::collections::HashMap;
|
||||
use field::Field;
|
||||
use typed_absy::*;
|
||||
use typed_absy::folder::*;
|
||||
use types::Type;
|
||||
|
||||
pub trait Unroll {
|
||||
fn unroll(self) -> Self;
|
||||
pub struct Unroller {
|
||||
substitution: HashMap<String, usize>
|
||||
}
|
||||
|
||||
pub trait UnrollWithContext<T: Field> {
|
||||
fn unroll(self, substitution: &mut HashMap<String, usize>) -> Self;
|
||||
}
|
||||
|
||||
impl<T: Field> TypedExpression<T> {
|
||||
fn unroll(self, substitution: &HashMap<String, usize>) -> TypedExpression<T> {
|
||||
match self {
|
||||
TypedExpression::FieldElement(e) => e.unroll(substitution).into(),
|
||||
TypedExpression::Boolean(e) => e.unroll(substitution).into(),
|
||||
TypedExpression::FieldElementArray(e) => e.unroll(substitution).into(),
|
||||
impl Unroller {
|
||||
fn new() -> Self {
|
||||
Unroller {
|
||||
substitution: HashMap::new()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Field> FieldElementExpression<T> {
|
||||
fn unroll(self, substitution: &HashMap<String, usize>) -> FieldElementExpression<T> {
|
||||
match self {
|
||||
FieldElementExpression::Identifier(id) => FieldElementExpression::Identifier(format!("{}_{}", id, substitution.get(&id).unwrap().clone())),
|
||||
FieldElementExpression::Number(n) => FieldElementExpression::Number(n),
|
||||
FieldElementExpression::Add(box e1, box e2) => FieldElementExpression::Add(box e1.unroll(substitution), box e2.unroll(substitution)),
|
||||
FieldElementExpression::Sub(box e1, box e2) => FieldElementExpression::Sub(box e1.unroll(substitution), box e2.unroll(substitution)),
|
||||
FieldElementExpression::Mult(box e1, box e2) => FieldElementExpression::Mult(box e1.unroll(substitution), box e2.unroll(substitution)),
|
||||
FieldElementExpression::Div(box e1, box e2) => FieldElementExpression::Div(box e1.unroll(substitution), box e2.unroll(substitution)),
|
||||
FieldElementExpression::Pow(box e1, box e2) => FieldElementExpression::Div(box e1.unroll(substitution), box e2.unroll(substitution)),
|
||||
FieldElementExpression::IfElse(box cond, box cons, box alt) => FieldElementExpression::IfElse(box cond.unroll(substitution), box cons.unroll(substitution), box alt.unroll(substitution)),
|
||||
FieldElementExpression::FunctionCall(id, args) => FieldElementExpression::FunctionCall(id, args.into_iter().map(|a| a.unroll(substitution)).collect()),
|
||||
FieldElementExpression::Select(box array, box index) => FieldElementExpression::Select(box array.unroll(substitution), box index.unroll(substitution)),
|
||||
fn get_current_ssa_variable(&self, v: Variable) -> Variable {
|
||||
Variable {
|
||||
id: format!("{}_{}", v.id, self.substitution.get(&v.id).unwrap()),
|
||||
..v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Field> FieldElementArrayExpression<T> {
|
||||
fn unroll(self, substitution: &HashMap<String, usize>) -> FieldElementArrayExpression<T> {
|
||||
match self {
|
||||
FieldElementArrayExpression::Identifier(size, id) => FieldElementArrayExpression::Identifier(size, format!("{}_{}", id, substitution.get(&id).unwrap().clone())),
|
||||
FieldElementArrayExpression::Value(size, v) => FieldElementArrayExpression::Value(size, v.into_iter().map(|e| e.unroll(substitution)).collect()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Field> BooleanExpression<T> {
|
||||
fn unroll(self, substitution: &HashMap<String, usize>) -> BooleanExpression<T> {
|
||||
match self {
|
||||
BooleanExpression::Identifier(id) => BooleanExpression::Identifier(format!("{}_{}", id, substitution.get(&id).unwrap().clone())),
|
||||
BooleanExpression::Value(v) => BooleanExpression::Value(v),
|
||||
BooleanExpression::Eq(box e1, box e2) => BooleanExpression::Eq(box e1.unroll(substitution), box e2.unroll(substitution)),
|
||||
BooleanExpression::Lt(box e1, box e2) => BooleanExpression::Lt(box e1.unroll(substitution), box e2.unroll(substitution)),
|
||||
BooleanExpression::Le(box e1, box e2) => BooleanExpression::Le(box e1.unroll(substitution), box e2.unroll(substitution)),
|
||||
BooleanExpression::Gt(box e1, box e2) => BooleanExpression::Gt(box e1.unroll(substitution), box e2.unroll(substitution)),
|
||||
BooleanExpression::Ge(box e1, box e2) => BooleanExpression::Ge(box e1.unroll(substitution), box e2.unroll(substitution)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Field> TypedExpressionList<T> {
|
||||
fn unroll(self, substitution: &HashMap<String, usize>) -> TypedExpressionList<T> {
|
||||
match self {
|
||||
TypedExpressionList::FunctionCall(id, arguments, types) => {
|
||||
TypedExpressionList::FunctionCall(id, arguments.into_iter().map(|a| a.unroll(substitution)).collect(), types)
|
||||
fn issue_next_ssa_variable(&mut self, v: Variable) -> Variable {
|
||||
let res = match self.substitution.get(&v.id) {
|
||||
Some(i) => {
|
||||
Variable { id: format!("{}_{}", v.id, i + 1), ..v}
|
||||
},
|
||||
None => {
|
||||
Variable { id: format!("{}_{}", v.id, 0), ..v}
|
||||
}
|
||||
}
|
||||
};
|
||||
self.substitution.entry(v.id)
|
||||
.and_modify(|e| { *e += 1 })
|
||||
.or_insert(0);
|
||||
res
|
||||
}
|
||||
|
||||
pub fn unroll<T: Field>(p: TypedProg<T>) -> TypedProg<T> {
|
||||
Unroller::new().fold_program(p)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl<T: Field> TypedStatement<T> {
|
||||
fn unroll(self, substitution: &mut HashMap<String, usize>) -> Vec<TypedStatement<T>> {
|
||||
match self {
|
||||
impl<T: Field> Folder<T> for Unroller {
|
||||
fn fold_statement(&mut self, s: TypedStatement<T>) -> Vec<TypedStatement<T>> {
|
||||
match s {
|
||||
TypedStatement::Declaration(_) => {
|
||||
vec![]
|
||||
},
|
||||
TypedStatement::Definition(TypedAssignee::Identifier(variable), expr) => {
|
||||
let expr = expr.unroll(substitution);
|
||||
let expr = self.fold_expression(expr);
|
||||
|
||||
let res = match substitution.get(&variable.id) {
|
||||
Some(i) => {
|
||||
vec![TypedStatement::Definition(TypedAssignee::Identifier(Variable { id: format!("{}_{}", variable.id, i + 1), ..variable}), expr)]
|
||||
},
|
||||
None => {
|
||||
vec![TypedStatement::Definition(TypedAssignee::Identifier(Variable { id: format!("{}_{}", variable.id, 0), ..variable}), expr)]
|
||||
}
|
||||
};
|
||||
substitution.entry(variable.id)
|
||||
.and_modify(|e| { *e += 1 })
|
||||
.or_insert(0);
|
||||
res
|
||||
vec![TypedStatement::Definition(TypedAssignee::Identifier(self.issue_next_ssa_variable(variable)), expr)]
|
||||
},
|
||||
TypedStatement::Definition(TypedAssignee::ArrayElement(box TypedAssignee::Identifier(variable), box index), expr) => {
|
||||
let expr = expr.unroll(substitution);
|
||||
let index = index.unroll(substitution);
|
||||
TypedStatement::Definition(TypedAssignee::ArrayElement(array @ box TypedAssignee::Identifier(..), box index), expr) => {
|
||||
let expr = self.fold_expression(expr);
|
||||
let index = self.fold_field_expression(index);
|
||||
let current_array = self.fold_assignee(*array.clone());
|
||||
|
||||
let array_size = match variable.get_type() {
|
||||
let current_ssa_variable = match current_array {
|
||||
TypedAssignee::Identifier(v) => v,
|
||||
_ => panic!("assignee should be an identifier")
|
||||
};
|
||||
|
||||
let original_variable = match *array {
|
||||
TypedAssignee::Identifier(v) => v,
|
||||
_ => panic!("assignee should be an identifier")
|
||||
};
|
||||
|
||||
let array_size = match original_variable.get_type() {
|
||||
Type::FieldElementArray(size) => size,
|
||||
_ => panic!("array identifier has to be a field element array")
|
||||
_ => panic!("array identifier should be a field element array")
|
||||
};
|
||||
|
||||
let expr = match expr {
|
||||
|
@ -116,56 +85,35 @@ impl<T: Field> TypedStatement<T> {
|
|||
_ => panic!("right side of array element definition must be a field element")
|
||||
};
|
||||
|
||||
let res = match substitution.get(&variable.id) {
|
||||
Some(ssa_id) => {
|
||||
let new_array = FieldElementArrayExpression::Value(array_size, (0..array_size)
|
||||
.map(|i|
|
||||
FieldElementExpression::IfElse(
|
||||
box BooleanExpression::Eq(
|
||||
box index.clone(),
|
||||
box FieldElementExpression::Number(T::from(i))
|
||||
),
|
||||
box expr.clone(),
|
||||
box FieldElementExpression::Select(
|
||||
box FieldElementArrayExpression::Identifier(array_size, format!("{}_{}", variable.id, ssa_id)),
|
||||
box FieldElementExpression::Number(T::from(i))
|
||||
)
|
||||
)
|
||||
let new_variable = self.issue_next_ssa_variable(original_variable);
|
||||
|
||||
let new_array = FieldElementArrayExpression::Value(array_size, (0..array_size)
|
||||
.map(|i|
|
||||
FieldElementExpression::IfElse(
|
||||
box BooleanExpression::Eq(
|
||||
box index.clone(),
|
||||
box FieldElementExpression::Number(T::from(i))
|
||||
),
|
||||
box expr.clone(),
|
||||
box FieldElementExpression::Select(
|
||||
box FieldElementArrayExpression::Identifier(array_size, current_ssa_variable.id.clone()),
|
||||
box FieldElementExpression::Number(T::from(i))
|
||||
)
|
||||
.collect());
|
||||
vec![
|
||||
TypedStatement::Definition(TypedAssignee::Identifier(Variable { id: format!("{}_{}", variable.id, ssa_id + 1), ..variable.clone()}), new_array.into()),
|
||||
]
|
||||
},
|
||||
None => {
|
||||
vec![TypedStatement::Definition(TypedAssignee::Identifier(Variable { id: format!("{}_{}", variable.id, 0), ..variable}), expr.into())]
|
||||
}
|
||||
};
|
||||
substitution.entry(variable.id)
|
||||
.and_modify(|e| { *e += 1 })
|
||||
.or_insert(0);
|
||||
res
|
||||
)
|
||||
)
|
||||
.collect()
|
||||
);
|
||||
|
||||
vec![
|
||||
TypedStatement::Definition(TypedAssignee::Identifier(new_variable), new_array.into()),
|
||||
]
|
||||
},
|
||||
TypedStatement::MultipleDefinition(variables, exprs) => {
|
||||
let exprs = exprs.unroll(substitution);
|
||||
let variables = variables.into_iter().map(|v| {
|
||||
let res = match substitution.get(&v.id) {
|
||||
Some(i) => {
|
||||
Variable { id: format!("{}_{}", v.id, i + 1), ..v}
|
||||
},
|
||||
None => {
|
||||
Variable { id: format!("{}_{}", v.id, 0), ..v}
|
||||
}
|
||||
};
|
||||
substitution.entry(v.id)
|
||||
.and_modify(|e| { *e += 1 })
|
||||
.or_insert(0);
|
||||
res
|
||||
}).collect();
|
||||
let exprs = self.fold_expression_list(exprs);
|
||||
let variables = variables.into_iter().map(|v| self.issue_next_ssa_variable(v)).collect();
|
||||
|
||||
vec![TypedStatement::MultipleDefinition(variables, exprs)]
|
||||
},
|
||||
TypedStatement::Condition(e1, e2) => vec![TypedStatement::Condition(e1.unroll(substitution), e2.unroll(substitution))],
|
||||
TypedStatement::For(v, from, to, stats) => {
|
||||
let mut values: Vec<T> = vec![];
|
||||
let mut current = from;
|
||||
|
@ -182,49 +130,25 @@ impl<T: Field> TypedStatement<T> {
|
|||
],
|
||||
stats.clone()
|
||||
].into_iter().flat_map(|x| x)
|
||||
}).flat_map(|x| x).flat_map(|x| x.unroll(substitution)).collect();
|
||||
}).flat_map(|x| x).flat_map(|x| self.fold_statement(x)).collect();
|
||||
|
||||
res
|
||||
}
|
||||
TypedStatement::Return(exprs) => {
|
||||
vec![TypedStatement::Return(exprs.into_iter().map(|e| e.unroll(substitution)).collect())]
|
||||
},
|
||||
_ => vec![self]
|
||||
s => fold_statement(self, s)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Field> Unroll for TypedFunction<T> {
|
||||
fn unroll(self) -> TypedFunction<T> {
|
||||
|
||||
let mut substitution = HashMap::new();
|
||||
|
||||
let arguments = self.arguments.into_iter().map(|p|
|
||||
Parameter {
|
||||
id: Variable {
|
||||
id: format!("{}_{}", p.id.id.clone(), substitution.entry(p.id.id)
|
||||
.and_modify(|e| { *e += 1 })
|
||||
.or_insert(0)),
|
||||
..p.id
|
||||
},
|
||||
..p
|
||||
}
|
||||
).collect();
|
||||
|
||||
TypedFunction {
|
||||
arguments: arguments,
|
||||
statements: self.statements.into_iter().flat_map(|s| s.unroll(&mut substitution)).collect(),
|
||||
..self
|
||||
fn fold_function(&mut self, f: TypedFunction<T>) -> TypedFunction<T> {
|
||||
self.substitution = HashMap::new();
|
||||
for arg in &f.arguments {
|
||||
self.substitution.insert(arg.id.id.clone(), 0);
|
||||
}
|
||||
|
||||
fold_function(self, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Field> Unroll for TypedProg<T> {
|
||||
fn unroll(self) -> TypedProg<T> {
|
||||
TypedProg {
|
||||
functions: self.functions.into_iter().map(|f| f.unroll()).collect(),
|
||||
..self
|
||||
}
|
||||
fn fold_name(&mut self, n: String) -> String {
|
||||
format!("{}_{}", n, self.substitution.get(&n).unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -239,6 +163,18 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn for_loop() {
|
||||
|
||||
// for field i in 2..5
|
||||
// field foo = i
|
||||
|
||||
// should be unrolled to
|
||||
// i_0 = 2
|
||||
// foo_0 = i_0
|
||||
// i_1 = 3
|
||||
// foo_1 = i_1
|
||||
// i_2 = 4
|
||||
// foo_2 = i_2
|
||||
|
||||
let s = TypedStatement::For(Variable::field_element("i"), FieldPrime::from(2), FieldPrime::from(5), vec![
|
||||
TypedStatement::Declaration(Variable::field_element("foo")),
|
||||
TypedStatement::Definition(TypedAssignee::Identifier(Variable::field_element("foo")), FieldElementExpression::Identifier(String::from("i")).into())]
|
||||
|
@ -255,7 +191,9 @@ mod tests {
|
|||
TypedStatement::Definition(TypedAssignee::Identifier(Variable::field_element("foo_2")), FieldElementExpression::Identifier(String::from("i_2")).into()),
|
||||
];
|
||||
|
||||
assert_eq!(s.unroll(&mut HashMap::new()), expected);
|
||||
let mut u = Unroller::new();
|
||||
|
||||
assert_eq!(u.fold_statement(s), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -271,19 +209,19 @@ mod tests {
|
|||
// a_1 = 6
|
||||
// a_1
|
||||
|
||||
let mut substitution = HashMap::new();
|
||||
let mut u = Unroller::new();
|
||||
|
||||
let s: TypedStatement<FieldPrime> = TypedStatement::Declaration(Variable::field_element("a"));
|
||||
assert_eq!(s.unroll(&mut substitution), vec![]);
|
||||
assert_eq!(u.fold_statement(s), vec![]);
|
||||
|
||||
let s = TypedStatement::Definition(TypedAssignee::Identifier(Variable::field_element("a")), FieldElementExpression::Number(FieldPrime::from(5)).into());
|
||||
assert_eq!(s.unroll(&mut substitution), vec![TypedStatement::Definition(TypedAssignee::Identifier(Variable::field_element("a_0")), FieldElementExpression::Number(FieldPrime::from(5)).into())]);
|
||||
assert_eq!(u.fold_statement(s), vec![TypedStatement::Definition(TypedAssignee::Identifier(Variable::field_element("a_0")), FieldElementExpression::Number(FieldPrime::from(5)).into())]);
|
||||
|
||||
let s = TypedStatement::Definition(TypedAssignee::Identifier(Variable::field_element("a")), FieldElementExpression::Number(FieldPrime::from(6)).into());
|
||||
assert_eq!(s.unroll(&mut substitution), vec![TypedStatement::Definition(TypedAssignee::Identifier(Variable::field_element("a_1")), FieldElementExpression::Number(FieldPrime::from(6)).into())]);
|
||||
assert_eq!(u.fold_statement(s), vec![TypedStatement::Definition(TypedAssignee::Identifier(Variable::field_element("a_1")), FieldElementExpression::Number(FieldPrime::from(6)).into())]);
|
||||
|
||||
let e: FieldElementExpression<FieldPrime> = FieldElementExpression::Identifier(String::from("a"));
|
||||
assert_eq!(e.unroll(&mut substitution), FieldElementExpression::Identifier(String::from("a_1")));
|
||||
assert_eq!(u.fold_field_expression(e), FieldElementExpression::Identifier(String::from("a_1")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -297,13 +235,13 @@ mod tests {
|
|||
// a_0 = 5
|
||||
// a_1 = a_0 + 1
|
||||
|
||||
let mut substitution = HashMap::new();
|
||||
let mut u = Unroller::new();
|
||||
|
||||
let s: TypedStatement<FieldPrime> = TypedStatement::Declaration(Variable::field_element("a"));
|
||||
assert_eq!(s.unroll(&mut substitution), vec![]);
|
||||
assert_eq!(u.fold_statement(s), vec![]);
|
||||
|
||||
let s = TypedStatement::Definition(TypedAssignee::Identifier(Variable::field_element("a")), FieldElementExpression::Number(FieldPrime::from(5)).into());
|
||||
assert_eq!(s.unroll(&mut substitution), vec![TypedStatement::Definition(TypedAssignee::Identifier(Variable::field_element("a_0")), FieldElementExpression::Number(FieldPrime::from(5)).into())]);
|
||||
assert_eq!(u.fold_statement(s), vec![TypedStatement::Definition(TypedAssignee::Identifier(Variable::field_element("a_0")), FieldElementExpression::Number(FieldPrime::from(5)).into())]);
|
||||
|
||||
let s = TypedStatement::Definition(
|
||||
TypedAssignee::Identifier(Variable::field_element("a")),
|
||||
|
@ -313,7 +251,7 @@ mod tests {
|
|||
).into()
|
||||
);
|
||||
assert_eq!(
|
||||
s.unroll(&mut substitution),
|
||||
u.fold_statement(s),
|
||||
vec![
|
||||
TypedStatement::Definition(
|
||||
TypedAssignee::Identifier(Variable::field_element("a_1")),
|
||||
|
@ -339,13 +277,13 @@ mod tests {
|
|||
// a_0 = 2
|
||||
// a_1 = foo(a_0)
|
||||
|
||||
let mut substitution = HashMap::new();
|
||||
let mut u = Unroller::new();
|
||||
|
||||
let s: TypedStatement<FieldPrime> = TypedStatement::Declaration(Variable::field_element("a"));
|
||||
assert_eq!(s.unroll(&mut substitution), vec![]);
|
||||
assert_eq!(u.fold_statement(s), vec![]);
|
||||
|
||||
let s = TypedStatement::Definition(TypedAssignee::Identifier(Variable::field_element("a")), FieldElementExpression::Number(FieldPrime::from(2)).into());
|
||||
assert_eq!(s.unroll(&mut substitution), vec![TypedStatement::Definition(TypedAssignee::Identifier(Variable::field_element("a_0")), FieldElementExpression::Number(FieldPrime::from(2)).into())]);
|
||||
assert_eq!(u.fold_statement(s), vec![TypedStatement::Definition(TypedAssignee::Identifier(Variable::field_element("a_0")), FieldElementExpression::Number(FieldPrime::from(2)).into())]);
|
||||
|
||||
let s: TypedStatement<FieldPrime> = TypedStatement::MultipleDefinition(
|
||||
vec![Variable::field_element("a")],
|
||||
|
@ -356,7 +294,7 @@ mod tests {
|
|||
)
|
||||
);
|
||||
assert_eq!(
|
||||
s.unroll(&mut substitution),
|
||||
u.fold_statement(s),
|
||||
vec![
|
||||
TypedStatement::MultipleDefinition(
|
||||
vec![Variable::field_element("a_1")],
|
||||
|
@ -369,5 +307,91 @@ mod tests {
|
|||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn incremental_array_definition() {
|
||||
|
||||
// field[2] a = [1, 1]
|
||||
// a[1] = 2
|
||||
|
||||
// should be turned into
|
||||
// a_0 = [1, 1]
|
||||
// a_1 = [if 0 == 1 then 2 else a_0[0], if 1 == 1 then 2 else a_0[1]]
|
||||
|
||||
let mut u = Unroller::new();
|
||||
|
||||
let s: TypedStatement<FieldPrime> = TypedStatement::Declaration(Variable::field_array("a", 2));
|
||||
assert_eq!(u.fold_statement(s), vec![]);
|
||||
|
||||
let s = TypedStatement::Definition(
|
||||
TypedAssignee::Identifier(
|
||||
Variable::field_array("a", 2)
|
||||
),
|
||||
FieldElementArrayExpression::Value(
|
||||
2,
|
||||
vec![
|
||||
FieldElementExpression::Number(FieldPrime::from(1)),
|
||||
FieldElementExpression::Number(FieldPrime::from(1))
|
||||
]
|
||||
).into()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
u.fold_statement(s),
|
||||
vec![
|
||||
TypedStatement::Definition(
|
||||
TypedAssignee::Identifier(
|
||||
Variable::field_array("a_0", 2)
|
||||
),
|
||||
FieldElementArrayExpression::Value(
|
||||
2,
|
||||
vec![
|
||||
FieldElementExpression::Number(FieldPrime::from(1)),
|
||||
FieldElementExpression::Number(FieldPrime::from(1))
|
||||
]
|
||||
).into()
|
||||
)
|
||||
]
|
||||
);
|
||||
|
||||
let s: TypedStatement<FieldPrime> = TypedStatement::Definition(
|
||||
TypedAssignee::ArrayElement(box TypedAssignee::Identifier(Variable::field_array("a", 2)), box FieldElementExpression::Number(FieldPrime::from(1))),
|
||||
FieldElementExpression::Number(FieldPrime::from(2)).into()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
u.fold_statement(s),
|
||||
vec![
|
||||
TypedStatement::Definition(
|
||||
TypedAssignee::Identifier(Variable::field_array("a_1", 2)),
|
||||
FieldElementArrayExpression::Value(
|
||||
2,
|
||||
vec![
|
||||
FieldElementExpression::IfElse(
|
||||
box BooleanExpression::Eq(
|
||||
box FieldElementExpression::Number(FieldPrime::from(1)), box FieldElementExpression::Number(FieldPrime::from(0))
|
||||
),
|
||||
box FieldElementExpression::Number(FieldPrime::from(2)),
|
||||
box FieldElementExpression::Select(
|
||||
box FieldElementArrayExpression::Identifier(2, String::from("a_0")),
|
||||
box FieldElementExpression::Number(FieldPrime::from(0))
|
||||
),
|
||||
),
|
||||
FieldElementExpression::IfElse(
|
||||
box BooleanExpression::Eq(
|
||||
box FieldElementExpression::Number(FieldPrime::from(1)), box FieldElementExpression::Number(FieldPrime::from(1))
|
||||
),
|
||||
box FieldElementExpression::Number(FieldPrime::from(2)),
|
||||
box FieldElementExpression::Select(
|
||||
box FieldElementArrayExpression::Identifier(2, String::from("a_0")),
|
||||
box FieldElementExpression::Number(FieldPrime::from(1))
|
||||
),
|
||||
),
|
||||
]
|
||||
).into()
|
||||
)
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
189
zokrates_core/src/typed_absy/folder.rs
Normal file
189
zokrates_core/src/typed_absy/folder.rs
Normal file
|
@ -0,0 +1,189 @@
|
|||
// Generic walk through a typed AST. Not mutating in place
|
||||
|
||||
use absy::variable::Variable;
|
||||
use field::Field;
|
||||
use typed_absy::*;
|
||||
|
||||
pub trait Folder<T: Field> : Sized {
|
||||
fn fold_program(&mut self, p: TypedProg<T>) -> TypedProg<T> {
|
||||
fold_program(self, p)
|
||||
}
|
||||
|
||||
fn fold_function(&mut self, f: TypedFunction<T>) -> TypedFunction<T> {
|
||||
fold_function(self, f)
|
||||
}
|
||||
|
||||
fn fold_parameter(&mut self, p: Parameter) -> Parameter {
|
||||
Parameter {
|
||||
id: self.fold_variable(p.id),
|
||||
..p
|
||||
}
|
||||
}
|
||||
|
||||
fn fold_name(&mut self, n: String) -> String { n }
|
||||
|
||||
fn fold_variable(&mut self, v: Variable) -> Variable {
|
||||
Variable {
|
||||
id: self.fold_name(v.id),
|
||||
..v
|
||||
}
|
||||
}
|
||||
|
||||
fn fold_assignee(&mut self, a: TypedAssignee<T>) -> TypedAssignee<T> {
|
||||
match a {
|
||||
TypedAssignee::Identifier(v) => TypedAssignee::Identifier(self.fold_variable(v)),
|
||||
TypedAssignee::ArrayElement(box a, box index) => TypedAssignee::ArrayElement(box self.fold_assignee(a), box self.fold_field_expression(index))
|
||||
}
|
||||
}
|
||||
|
||||
fn fold_statement(&mut self, s: TypedStatement<T>) -> Vec<TypedStatement<T>> {
|
||||
fold_statement(self, s)
|
||||
}
|
||||
|
||||
fn fold_expression(&mut self, e: TypedExpression<T>) -> TypedExpression<T> {
|
||||
match e {
|
||||
TypedExpression::FieldElement(e) => self.fold_field_expression(e).into(),
|
||||
TypedExpression::Boolean(e) => self.fold_boolean_expression(e).into(),
|
||||
TypedExpression::FieldElementArray(e) => self.fold_field_array_expression(e).into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn fold_expression_list(&mut self, es: TypedExpressionList<T>) -> TypedExpressionList<T> {
|
||||
match es {
|
||||
TypedExpressionList::FunctionCall(id, arguments, types) => {
|
||||
TypedExpressionList::FunctionCall(id, arguments.into_iter().map(|a| self.fold_expression(a)).collect(), types)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn fold_field_expression(&mut self, e: FieldElementExpression<T>) -> FieldElementExpression<T> {
|
||||
fold_field_expression(self, e)
|
||||
}
|
||||
fn fold_boolean_expression(&mut self, e: BooleanExpression<T>) -> BooleanExpression<T> {
|
||||
fold_boolean_expression(self, e)
|
||||
}
|
||||
fn fold_field_array_expression(&mut self, e: FieldElementArrayExpression<T>) -> FieldElementArrayExpression<T> {
|
||||
fold_field_array_expression(self, e)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fold_program<T: Field, F: Folder<T>>(f: &mut F, p: TypedProg<T>) -> TypedProg<T> {
|
||||
TypedProg {
|
||||
functions: p.functions.into_iter().map(|fun| f.fold_function(fun)).collect(),
|
||||
..p
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fold_statement<T: Field, F: Folder<T>>(f: &mut F, s: TypedStatement<T>) -> Vec<TypedStatement<T>> {
|
||||
let res = match s {
|
||||
TypedStatement::Return(expressions) => TypedStatement::Return(expressions.into_iter().map(|e| f.fold_expression(e)).collect()),
|
||||
TypedStatement::Definition(a, e) => TypedStatement::Definition(f.fold_assignee(a), f.fold_expression(e)),
|
||||
TypedStatement::Declaration(v) => TypedStatement::Declaration(f.fold_variable(v)),
|
||||
TypedStatement::Condition(left, right) => TypedStatement::Condition(f.fold_expression(left), f.fold_expression(right)),
|
||||
TypedStatement::For(v, from, to, statements) => TypedStatement::For(f.fold_variable(v), from, to, statements.into_iter().flat_map(|s| f.fold_statement(s)).collect()),
|
||||
TypedStatement::MultipleDefinition(variables, elist) => TypedStatement::MultipleDefinition(variables.into_iter().map(|v| f.fold_variable(v)).collect(), f.fold_expression_list(elist)),
|
||||
};
|
||||
vec![res]
|
||||
}
|
||||
|
||||
pub fn fold_field_array_expression<T: Field, F: Folder<T>>(f: &mut F, e: FieldElementArrayExpression<T>) -> FieldElementArrayExpression<T> {
|
||||
match e {
|
||||
FieldElementArrayExpression::Identifier(size, id) => {
|
||||
FieldElementArrayExpression::Identifier(size, f.fold_name(id))
|
||||
},
|
||||
FieldElementArrayExpression::Value(size, exprs) => {
|
||||
FieldElementArrayExpression::Value(size, exprs.into_iter().map(|e| f.fold_field_expression(e)).collect())
|
||||
}
|
||||
FieldElementArrayExpression::FunctionCall(size, id, exps) => {
|
||||
let exps = exps.into_iter().map(|e| f.fold_expression(e)).collect();
|
||||
FieldElementArrayExpression::FunctionCall(size, id, exps)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fold_field_expression<T: Field, F: Folder<T>>(f: &mut F, e: FieldElementExpression<T>) -> FieldElementExpression<T> {
|
||||
match e {
|
||||
FieldElementExpression::Number(n) => FieldElementExpression::Number(n),
|
||||
FieldElementExpression::Identifier(id) => FieldElementExpression::Identifier(f.fold_name(id)),
|
||||
FieldElementExpression::Add(box e1, box e2) => {
|
||||
let e1 = f.fold_field_expression(e1);
|
||||
let e2 = f.fold_field_expression(e2);
|
||||
FieldElementExpression::Add(box e1, box e2)
|
||||
},
|
||||
FieldElementExpression::Sub(box e1, box e2) => {
|
||||
let e1 = f.fold_field_expression(e1);
|
||||
let e2 = f.fold_field_expression(e2);
|
||||
FieldElementExpression::Sub(box e1, box e2)
|
||||
},
|
||||
FieldElementExpression::Mult(box e1, box e2) => {
|
||||
let e1 = f.fold_field_expression(e1);
|
||||
let e2 = f.fold_field_expression(e2);
|
||||
FieldElementExpression::Mult(box e1, box e2)
|
||||
},
|
||||
FieldElementExpression::Div(box e1, box e2) => {
|
||||
let e1 = f.fold_field_expression(e1);
|
||||
let e2 = f.fold_field_expression(e2);
|
||||
FieldElementExpression::Div(box e1, box e2)
|
||||
},
|
||||
FieldElementExpression::Pow(box e1, box e2) => {
|
||||
let e1 = f.fold_field_expression(e1);
|
||||
let e2 = f.fold_field_expression(e2);
|
||||
FieldElementExpression::Pow(box e1, box e2)
|
||||
},
|
||||
FieldElementExpression::IfElse(box cond, box cons, box alt) => {
|
||||
let cond = f.fold_boolean_expression(cond);
|
||||
let cons = f.fold_field_expression(cons);
|
||||
let alt = f.fold_field_expression(alt);
|
||||
FieldElementExpression::IfElse(box cond, box cons, box alt)
|
||||
},
|
||||
FieldElementExpression::FunctionCall(id, exps) => {
|
||||
let exps = exps.into_iter().map(|e| f.fold_expression(e)).collect();
|
||||
FieldElementExpression::FunctionCall(id, exps)
|
||||
},
|
||||
FieldElementExpression::Select(box array, box index) => {
|
||||
let array = f.fold_field_array_expression(array);
|
||||
let index = f.fold_field_expression(index);
|
||||
FieldElementExpression::Select(box array, box index)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fold_boolean_expression<T: Field, F: Folder<T>>(f: &mut F, e: BooleanExpression<T>) -> BooleanExpression<T> {
|
||||
match e {
|
||||
BooleanExpression::Value(v) => BooleanExpression::Value(v),
|
||||
BooleanExpression::Identifier(id) => BooleanExpression::Identifier(f.fold_name(id)),
|
||||
BooleanExpression::Eq(box e1, box e2) => {
|
||||
let e1 = f.fold_field_expression(e1);
|
||||
let e2 = f.fold_field_expression(e2);
|
||||
BooleanExpression::Eq(box e1, box e2)
|
||||
}
|
||||
BooleanExpression::Lt(box e1, box e2) => {
|
||||
let e1 = f.fold_field_expression(e1);
|
||||
let e2 = f.fold_field_expression(e2);
|
||||
BooleanExpression::Lt(box e1, box e2)
|
||||
}
|
||||
BooleanExpression::Le(box e1, box e2) => {
|
||||
let e1 = f.fold_field_expression(e1);
|
||||
let e2 = f.fold_field_expression(e2);
|
||||
BooleanExpression::Le(box e1, box e2)
|
||||
}
|
||||
BooleanExpression::Gt(box e1, box e2) => {
|
||||
let e1 = f.fold_field_expression(e1);
|
||||
let e2 = f.fold_field_expression(e2);
|
||||
BooleanExpression::Gt(box e1, box e2)
|
||||
}
|
||||
BooleanExpression::Ge(box e1, box e2) => {
|
||||
let e1 = f.fold_field_expression(e1);
|
||||
let e2 = f.fold_field_expression(e2);
|
||||
BooleanExpression::Ge(box e1, box e2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fold_function<T: Field, F: Folder<T>>(f: &mut F, fun: TypedFunction<T>) -> TypedFunction<T> {
|
||||
TypedFunction {
|
||||
arguments: fun.arguments.into_iter().map(|a| f.fold_parameter(a)).collect(),
|
||||
statements: fun.statements.into_iter().flat_map(|s| f.fold_statement(s)).collect(),
|
||||
..fun
|
||||
}
|
||||
}
|
|
@ -5,6 +5,8 @@
|
|||
//! @author Jacob Eberhardt <jacob.eberhardt@tu-berlin.de>
|
||||
//! @date 2017
|
||||
|
||||
pub mod folder;
|
||||
|
||||
use types::Signature;
|
||||
use absy::parameter::Parameter;
|
||||
use absy::variable::Variable;
|
||||
|
@ -15,6 +17,8 @@ use imports::Import;
|
|||
use flat_absy::*;
|
||||
use types::Type;
|
||||
|
||||
pub use self::folder::Folder;
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, PartialEq)]
|
||||
pub struct TypedProg<T: Field> {
|
||||
/// Functions of the program
|
||||
|
@ -309,6 +313,7 @@ impl<T: Field> Typed for TypedExpression<T> {
|
|||
TypedExpression::FieldElement(_) => Type::FieldElement,
|
||||
TypedExpression::FieldElementArray(FieldElementArrayExpression::Identifier(n, _)) => Type::FieldElementArray(n),
|
||||
TypedExpression::FieldElementArray(FieldElementArrayExpression::Value(n, _)) => Type::FieldElementArray(n),
|
||||
TypedExpression::FieldElementArray(FieldElementArrayExpression::FunctionCall(n, ..)) => Type::FieldElementArray(n),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -361,12 +366,13 @@ pub enum BooleanExpression<T: Field> {
|
|||
pub enum FieldElementArrayExpression<T: Field> {
|
||||
Identifier(usize, String),
|
||||
Value(usize, Vec<FieldElementExpression<T>>),
|
||||
FunctionCall(usize, String, Vec<TypedExpression<T>>),
|
||||
}
|
||||
|
||||
impl<T: Field> FieldElementArrayExpression<T> {
|
||||
pub fn size(&self) -> usize {
|
||||
match *self {
|
||||
FieldElementArrayExpression::Identifier(s, _) | FieldElementArrayExpression::Value(s, _) => s
|
||||
FieldElementArrayExpression::Identifier(s, _) | FieldElementArrayExpression::Value(s, _) | FieldElementArrayExpression::FunctionCall(s, ..) => s
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -424,6 +430,16 @@ impl<T: Field> fmt::Display for FieldElementArrayExpression<T> {
|
|||
match *self {
|
||||
FieldElementArrayExpression::Identifier(_, ref var) => write!(f, "{}", var),
|
||||
FieldElementArrayExpression::Value(_, ref values) => write!(f, "[{}]", values.iter().map(|o| o.to_string()).collect::<Vec<String>>().join(", ")),
|
||||
FieldElementArrayExpression::FunctionCall(_, ref i, ref p) => {
|
||||
try!(write!(f, "{}(", i,));
|
||||
for (i, param) in p.iter().enumerate() {
|
||||
try!(write!(f, "{}", param));
|
||||
if i < p.len() - 1 {
|
||||
try!(write!(f, ", "));
|
||||
}
|
||||
}
|
||||
write!(f, ")")
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -468,6 +484,11 @@ impl<T: Field> fmt::Debug for FieldElementArrayExpression<T> {
|
|||
match *self {
|
||||
FieldElementArrayExpression::Identifier(_, ref var) => write!(f, "{:?}", var),
|
||||
FieldElementArrayExpression::Value(_, ref values) => write!(f, "{:?}", values),
|
||||
FieldElementArrayExpression::FunctionCall(_, ref i, ref p) => {
|
||||
try!(write!(f, "FunctionCall({:?}, (", i));
|
||||
try!(f.debug_list().entries(p.iter()).finish());
|
||||
write!(f, ")")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -500,3 +521,9 @@ impl<T: Field> fmt::Debug for TypedExpressionList<T> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Field> TypedFunction<T> {
|
||||
pub fn to_slug(&self) -> String {
|
||||
format!("{}_{}", self.id, self.signature.to_slug())
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue