1
0
Fork 0
mirror of synced 2025-09-23 12:18:44 +00:00
ZoKrates/zokrates_core/src/compile.rs

476 lines
13 KiB
Rust

//! Module containing the complete compilation pipeline.
//!
//! @file compile.rs
//! @author Thibaut Schaeffer <thibaut@schaeff.fr>
//! @date 2018
use crate::absy::{Module, OwnedModuleId, Program};
use crate::flatten::FlattenerIterator;
use crate::imports::{self, Importer};
use crate::ir;
use crate::macros;
use crate::semantics::{self, Checker};
use crate::static_analysis;
use crate::static_analysis::Analyse;
use crate::typed_absy::abi::Abi;
use crate::zir::ZirProgram;
use macros::process_macros;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fmt;
use std::io;
use std::io::Write;
use std::path::{Path, PathBuf};
use typed_arena::Arena;
use zokrates_common::Resolver;
use zokrates_field::Field;
use zokrates_pest_ast as pest;
#[derive(Debug)]
pub struct CompilationArtifacts<I> {
pub prog: ir::ProgIterator<I>,
pub abi: Abi,
}
impl<I> CompilationArtifacts<I> {
pub fn prog(self) -> ir::ProgIterator<I> {
self.prog
}
pub fn abi(&self) -> &Abi {
&self.abi
}
}
#[derive(Debug)]
pub struct CompileErrors(pub Vec<CompileError>);
impl From<CompileError> for CompileErrors {
fn from(e: CompileError) -> CompileErrors {
CompileErrors(vec![e])
}
}
#[derive(Debug)]
pub enum CompileErrorInner {
ParserError(pest::Error),
ImportError(imports::Error),
MacroError(macros::Error),
SemanticError(semantics::ErrorInner),
ReadError(io::Error),
AnalysisError(static_analysis::Error),
}
impl CompileErrorInner {
pub fn in_file(self, context: &Path) -> CompileError {
CompileError {
value: self,
file: context.to_path_buf(),
}
}
}
#[derive(Debug)]
pub struct CompileError {
file: PathBuf,
value: CompileErrorInner,
}
impl CompileError {
pub fn file(&self) -> &PathBuf {
&self.file
}
pub fn value(&self) -> &CompileErrorInner {
&self.value
}
}
impl CompileErrors {
pub fn with_context(self, file: PathBuf) -> Self {
CompileErrors(
self.0
.into_iter()
.map(|e| CompileError {
file: file.clone(),
..e
})
.collect(),
)
}
}
impl From<pest::Error> for CompileErrorInner {
fn from(error: pest::Error) -> Self {
CompileErrorInner::ParserError(error)
}
}
impl From<imports::Error> for CompileErrorInner {
fn from(error: imports::Error) -> Self {
CompileErrorInner::ImportError(error)
}
}
impl From<io::Error> for CompileErrorInner {
fn from(error: io::Error) -> Self {
CompileErrorInner::ReadError(error)
}
}
impl From<macros::Error> for CompileErrorInner {
fn from(error: macros::Error) -> Self {
CompileErrorInner::MacroError(error)
}
}
impl From<semantics::Error> for CompileError {
fn from(error: semantics::Error) -> Self {
CompileError {
value: CompileErrorInner::SemanticError(error.inner),
file: error.module_id,
}
}
}
impl From<static_analysis::Error> for CompileErrorInner {
fn from(error: static_analysis::Error) -> Self {
CompileErrorInner::AnalysisError(error)
}
}
impl fmt::Display for CompileErrorInner {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
CompileErrorInner::ParserError(ref e) => write!(f, "\n\t{}", e),
CompileErrorInner::MacroError(ref e) => write!(f, "\n\t{}", e),
CompileErrorInner::SemanticError(ref e) => {
let location = e
.pos()
.map(|p| format!("{}", p.0))
.unwrap_or_else(|| "".to_string());
write!(f, "{}\n\t{}", location, e.message())
}
CompileErrorInner::ReadError(ref e) => write!(f, "\n\t{}", e),
CompileErrorInner::ImportError(ref e) => {
let location = e
.pos()
.map(|p| format!("{}", p.0))
.unwrap_or_else(|| "".to_string());
write!(f, "{}\n\t{}", location, e.message())
}
CompileErrorInner::AnalysisError(ref e) => write!(f, "\n\t{}", e),
}
}
}
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
pub struct CompileConfig {
#[serde(default)]
pub allow_unconstrained_variables: bool,
#[serde(default)]
pub isolate_branches: bool,
}
impl CompileConfig {
pub fn allow_unconstrained_variables(mut self, flag: bool) -> Self {
self.allow_unconstrained_variables = flag;
self
}
pub fn isolate_branches(mut self, flag: bool) -> Self {
self.isolate_branches = flag;
self
}
}
type FilePath = PathBuf;
pub fn compile<'ast, T: Field, E: Into<imports::Error>>(
source: String,
location: FilePath,
resolver: Option<&dyn Resolver<E>>,
config: CompileConfig,
arena: &'ast Arena<String>,
) -> Result<CompilationArtifacts<impl Iterator<Item = ir::Statement<T>> + 'ast>, CompileErrors> {
let (typed_ast, abi): (crate::zir::ZirProgram<'_, T>, _) =
check_with_arena(source, location.clone(), resolver, &config, arena)?;
// flatten input program
log::debug!("Flatten");
let program_flattened = FlattenerIterator::from_function_and_config(typed_ast.main, config);
// // constant propagation after call resolution
// log::debug!("Propagate flat program");
// let program_flattened = program_flattened.propagate();
// convert to ir
log::debug!("Convert to IR");
let ir_prog = ir::ProgIterator {
arguments: program_flattened
.arguments_flattened
.clone()
.into_iter()
.map(|a| a.into())
.collect(),
return_count: 0,
statements: ir::from_flat::from_flat(program_flattened),
};
// optimize
log::debug!("Optimise IR");
let optimized_ir_prog = ir_prog.optimize();
// analyse ir (check constraints)
// log::debug!("Analyse IR");
// let optimized_ir_prog = optimized_ir_prog
// .analyse()
// .map_err(|e| CompileErrorInner::from(e).in_file(location.as_path()))?;
Ok(CompilationArtifacts {
prog: optimized_ir_prog,
abi,
})
}
pub fn check<T: Field, E: Into<imports::Error>>(
source: String,
location: FilePath,
resolver: Option<&dyn Resolver<E>>,
config: &CompileConfig,
) -> Result<(), CompileErrors> {
let arena = Arena::new();
check_with_arena::<T, _>(source, location, resolver, config, &arena).map(|_| ())
}
fn check_with_arena<'ast, T: Field, E: Into<imports::Error>>(
source: String,
location: FilePath,
resolver: Option<&dyn Resolver<E>>,
config: &CompileConfig,
arena: &'ast Arena<String>,
) -> Result<(ZirProgram<'ast, T>, Abi), CompileErrors> {
let source = arena.alloc(source);
log::debug!("Parse program with entry file {}", location.display());
let compiled = parse_program::<T, E>(source, location, resolver, arena)?;
log::debug!("Check semantics");
// check semantics
let typed_ast = Checker::check(compiled)
.map_err(|errors| CompileErrors(errors.into_iter().map(CompileError::from).collect()))?;
log::trace!("\n{}", typed_ast);
let main_module = typed_ast.main.clone();
log::debug!("Run static analysis");
// analyse (unroll and constant propagation)
typed_ast
.analyse(config)
.map_err(|e| CompileErrors(vec![CompileErrorInner::from(e).in_file(&main_module)]))
}
pub fn parse_program<'ast, T: Field, E: Into<imports::Error>>(
source: &'ast str,
location: FilePath,
resolver: Option<&dyn Resolver<E>>,
arena: &'ast Arena<String>,
) -> Result<Program<'ast>, CompileErrors> {
let mut modules = HashMap::new();
let main = parse_module::<T, E>(source, location.clone(), resolver, &mut modules, arena)?;
modules.insert(location.clone(), main);
Ok(Program {
main: location,
modules,
})
}
pub fn parse_module<'ast, T: Field, E: Into<imports::Error>>(
source: &'ast str,
location: FilePath,
resolver: Option<&dyn Resolver<E>>,
modules: &mut HashMap<OwnedModuleId, Module<'ast>>,
arena: &'ast Arena<String>,
) -> Result<Module<'ast>, CompileErrors> {
log::debug!("Generate pest AST for {}", location.display());
let ast = pest::generate_ast(source)
.map_err(|e| CompileErrors::from(CompileErrorInner::from(e).in_file(&location)))?;
log::debug!("Process macros for {}", location.display());
let ast = process_macros::<T>(ast)
.map_err(|e| CompileErrors::from(CompileErrorInner::from(e).in_file(&location)))?;
log::debug!("Generate absy for {}", location.display());
let module_without_imports: Module = Module::from(ast);
log::debug!("Apply imports to absy for {}", location.display());
Importer::apply_imports::<T, E>(
module_without_imports,
location.clone(),
resolver,
modules,
arena,
)
}
#[cfg(test)]
mod test {
use super::*;
use zokrates_field::Bn128Field;
#[test]
fn no_resolver_with_imports() {
let source = r#"
import "./path/to/file" as foo
def main() -> field:
return foo()
"#
.to_string();
let res: Result<CompilationArtifacts<Bn128Field>, CompileErrors> = compile(
source,
"./path/to/file".into(),
None::<&dyn Resolver<io::Error>>,
&CompileConfig::default(),
);
assert!(res.unwrap_err().0[0]
.value()
.to_string()
.contains(&"Cannot resolve import without a resolver"));
}
#[test]
fn no_resolver_without_imports() {
let source = r#"
def main() -> field:
return 1
"#
.to_string();
let res: Result<CompilationArtifacts<Bn128Field>, CompileErrors> = compile(
source,
"./path/to/file".into(),
None::<&dyn Resolver<io::Error>>,
&CompileConfig::default(),
);
assert!(res.is_ok());
}
mod abi {
use super::*;
use crate::typed_absy::abi::*;
use crate::typed_absy::types::*;
#[test]
fn use_struct_declaration_types() {
// when importing types and renaming them, we use the canonical struct names in the ABI
// // main.zok
// from foo import Foo as FooMain
//
// // foo.zok
// from bar import Bar as BarFoo
// struct Foo { BarFoo b }
//
// // bar.zok
// struct Bar { field a }
// Expected resolved type for FooMain:
// Foo { Bar b }
let main = r#"
from "foo" import Foo as FooMain
def main(FooMain f):
return
"#;
struct CustomResolver;
impl<E> Resolver<E> for CustomResolver {
fn resolve(
&self,
_: PathBuf,
import_location: PathBuf,
) -> Result<(String, PathBuf), E> {
let loc = import_location.display().to_string();
if loc == "main" {
Ok((
r#"
from "foo" import Foo as FooMain
def main(FooMain f):
return
"#
.into(),
"main".into(),
))
} else if loc == "foo" {
Ok((
r#"
from "bar" import Bar as BarFoo
struct Foo {
BarFoo b
}
"#
.into(),
"foo".into(),
))
} else if loc == "bar" {
Ok((
r#"
struct Bar { field a }
"#
.into(),
"bar".into(),
))
} else {
unreachable!()
}
}
}
let artifacts = compile::<Bn128Field, io::Error>(
main.to_string(),
"main".into(),
Some(&CustomResolver),
&CompileConfig::default(),
)
.unwrap();
assert_eq!(
artifacts.abi,
Abi {
inputs: vec![AbiInput {
name: "f".into(),
public: true,
ty: ConcreteType::Struct(ConcreteStructType::new(
"foo".into(),
"Foo".into(),
vec![],
vec![ConcreteStructMember {
id: "b".into(),
ty: box ConcreteType::Struct(ConcreteStructType::new(
"bar".into(),
"Bar".into(),
vec![],
vec![ConcreteStructMember {
id: "a".into(),
ty: box ConcreteType::FieldElement
}]
))
}]
))
}],
outputs: vec![]
}
);
}
}
}