From 4705b35389f1ab0bb35f6aaaf13a64e8f72f4114 Mon Sep 17 00:00:00 2001 From: TomAFrench Date: Wed, 1 Nov 2023 19:27:08 +0000 Subject: [PATCH] feat: add mvp `nargo export` command --- .../noirc_frontend/src/hir/def_map/mod.rs | 23 +++++ compiler/noirc_frontend/src/hir/mod.rs | 13 +++ compiler/noirc_frontend/src/lexer/token.rs | 5 +- tooling/nargo_cli/src/cli/compile_cmd.rs | 2 +- tooling/nargo_cli/src/cli/export_cmd.rs | 98 +++++++++++++++++++ tooling/nargo_cli/src/cli/mod.rs | 4 + 6 files changed, 143 insertions(+), 2 deletions(-) create mode 100644 tooling/nargo_cli/src/cli/export_cmd.rs diff --git a/compiler/noirc_frontend/src/hir/def_map/mod.rs b/compiler/noirc_frontend/src/hir/def_map/mod.rs index 20f05532ce4..9c46ef35854 100644 --- a/compiler/noirc_frontend/src/hir/def_map/mod.rs +++ b/compiler/noirc_frontend/src/hir/def_map/mod.rs @@ -173,6 +173,29 @@ impl CrateDefMap { }) }) } + + /// Go through all modules in this crate, and find all functions in + /// each module with the #[export] attribute + pub fn get_all_exported_functions<'a>( + &'a self, + interner: &'a NodeInterner, + ) -> impl Iterator + 'a { + self.modules.iter().flat_map(|(_, module)| { + module.value_definitions().filter_map(|id| { + if let Some(func_id) = id.as_function() { + let attributes = interner.function_attributes(&func_id); + if attributes.secondary.contains(&SecondaryAttribute::Export) { + Some(func_id) + } else { + None + } + } else { + None + } + }) + }) + } + /// Go through all modules in this crate, find all `contract ... { ... }` declarations, /// and collect them all into a Vec. pub fn get_all_contracts(&self, interner: &NodeInterner) -> Vec { diff --git a/compiler/noirc_frontend/src/hir/mod.rs b/compiler/noirc_frontend/src/hir/mod.rs index adeca7cf2ba..bfa49527975 100644 --- a/compiler/noirc_frontend/src/hir/mod.rs +++ b/compiler/noirc_frontend/src/hir/mod.rs @@ -181,6 +181,19 @@ impl Context { .collect() } + pub fn get_all_exported_functions_in_crate(&self, crate_id: &CrateId) -> Vec<(String, FuncId)> { + let interner = &self.def_interner; + let def_map = self.def_map(crate_id).expect("The local crate should be analyzed already"); + + def_map + .get_all_exported_functions(interner) + .map(|function_id| { + let function_name = self.function_name(&function_id).to_owned(); + (function_name, function_id) + }) + .collect() + } + /// Returns the [Location] of the definition of the given Ident found at [Span] of the given [FileId]. /// Returns [None] when definition is not found. pub fn get_definition_location_from(&self, location: Location) -> Option { diff --git a/compiler/noirc_frontend/src/lexer/token.rs b/compiler/noirc_frontend/src/lexer/token.rs index e6542c643ad..ab131ccd880 100644 --- a/compiler/noirc_frontend/src/lexer/token.rs +++ b/compiler/noirc_frontend/src/lexer/token.rs @@ -510,6 +510,7 @@ impl Attribute { Attribute::Secondary(SecondaryAttribute::ContractLibraryMethod) } ["event"] => Attribute::Secondary(SecondaryAttribute::Event), + ["export"] => Attribute::Secondary(SecondaryAttribute::Export), ["deprecated", name] => { if !name.starts_with('"') && !name.ends_with('"') { return Err(LexerErrorKind::MalformedFuncAttribute { @@ -588,6 +589,7 @@ pub enum SecondaryAttribute { // the entry point. ContractLibraryMethod, Event, + Export, Field(String), Custom(String), } @@ -602,6 +604,7 @@ impl fmt::Display for SecondaryAttribute { SecondaryAttribute::Custom(ref k) => write!(f, "#[{k}]"), SecondaryAttribute::ContractLibraryMethod => write!(f, "#[contract_library_method]"), SecondaryAttribute::Event => write!(f, "#[event]"), + SecondaryAttribute::Export => write!(f, "#[export]"), SecondaryAttribute::Field(ref k) => write!(f, "#[field({k})]"), } } @@ -625,7 +628,7 @@ impl AsRef for SecondaryAttribute { SecondaryAttribute::Deprecated(None) => "", SecondaryAttribute::Custom(string) | SecondaryAttribute::Field(string) => string, SecondaryAttribute::ContractLibraryMethod => "", - SecondaryAttribute::Event => "", + SecondaryAttribute::Event | SecondaryAttribute::Export => "", } } } diff --git a/tooling/nargo_cli/src/cli/compile_cmd.rs b/tooling/nargo_cli/src/cli/compile_cmd.rs index 5ee053c5088..58b4728d1ec 100644 --- a/tooling/nargo_cli/src/cli/compile_cmd.rs +++ b/tooling/nargo_cli/src/cli/compile_cmd.rs @@ -222,7 +222,7 @@ fn compile_contract( (context.file_manager, Ok((optimized_contract, warnings))) } -fn save_program( +pub(super) fn save_program( program: CompiledProgram, package: &Package, circuit_dir: &Path, diff --git a/tooling/nargo_cli/src/cli/export_cmd.rs b/tooling/nargo_cli/src/cli/export_cmd.rs new file mode 100644 index 00000000000..372c9e2a985 --- /dev/null +++ b/tooling/nargo_cli/src/cli/export_cmd.rs @@ -0,0 +1,98 @@ +use iter_extended::vecmap; +use nargo::artifacts::program::PreprocessedProgram; +use nargo::package::Package; +use nargo::prepare_package; +use nargo::workspace::Workspace; +use nargo_toml::{get_package_manifest, resolve_workspace_from_toml, PackageSelection}; +use noirc_driver::compile_no_check; +use noirc_driver::CompileOptions; +use noirc_driver::CompiledProgram; +use noirc_driver::NOIR_ARTIFACT_VERSION_STRING; +use noirc_frontend::graph::CrateName; + +use clap::Args; + +use crate::backends::Backend; +use crate::errors::CliError; + +use super::check_cmd::check_crate_and_report_errors; + +use super::fs::program::save_program_to_file; +use super::NargoConfig; + +/// Compile the program and its secret execution trace into ACIR format +#[derive(Debug, Clone, Args)] +pub(crate) struct ExportCommand { + /// The name of the package to compile + #[clap(long, conflicts_with = "workspace")] + package: Option, + + /// Compile all packages in the workspace + #[clap(long, conflicts_with = "package")] + workspace: bool, + + #[clap(flatten)] + compile_options: CompileOptions, +} + +pub(crate) fn run( + _backend: &Backend, + args: ExportCommand, + config: NargoConfig, +) -> Result<(), CliError> { + let toml_path = get_package_manifest(&config.program_dir)?; + let default_selection = + if args.workspace { PackageSelection::All } else { PackageSelection::DefaultOrAll }; + let selection = args.package.map_or(default_selection, PackageSelection::Selected); + + let workspace = resolve_workspace_from_toml( + &toml_path, + selection, + Some(NOIR_ARTIFACT_VERSION_STRING.to_owned()), + )?; + + let library_packages: Vec<_> = + workspace.into_iter().filter(|package| package.is_library()).collect(); + + compile_program(&workspace, library_packages[0], &args.compile_options)?; + + Ok(()) +} + +fn compile_program( + workspace: &Workspace, + package: &Package, + compile_options: &CompileOptions, +) -> Result<(), CliError> { + let (mut context, crate_id) = prepare_package(package); + check_crate_and_report_errors( + &mut context, + crate_id, + compile_options.deny_warnings, + compile_options.disable_macros, + compile_options.silence_warnings, + )?; + + let exported_functions = context.get_all_exported_functions_in_crate(&crate_id); + + let exported_programs = + vecmap(exported_functions, |(function_name, function_id)| -> (String, CompiledProgram) { + let program = compile_no_check(&context, compile_options, function_id, None, false) + .expect("heyooo"); + + (function_name, program) + }); + + let export_dir = workspace.target_directory_path().parent().unwrap().join("export"); + for (function_name, program) in exported_programs { + let preprocessed_program = PreprocessedProgram { + hash: program.hash, + abi: program.abi, + noir_version: program.noir_version, + bytecode: program.circuit, + }; + + save_program_to_file(&preprocessed_program, &function_name.parse().unwrap(), &export_dir); + } + Ok(()) +} diff --git a/tooling/nargo_cli/src/cli/mod.rs b/tooling/nargo_cli/src/cli/mod.rs index 448e28fb6a7..af36586d385 100644 --- a/tooling/nargo_cli/src/cli/mod.rs +++ b/tooling/nargo_cli/src/cli/mod.rs @@ -16,6 +16,7 @@ mod codegen_verifier_cmd; mod compile_cmd; mod debug_cmd; mod execute_cmd; +mod export_cmd; mod fmt_cmd; mod info_cmd; mod init_cmd; @@ -68,6 +69,8 @@ enum NargoCommand { Init(init_cmd::InitCommand), Execute(execute_cmd::ExecuteCommand), #[command(hide = true)] // Hidden while the feature is being built out + Export(export_cmd::ExportCommand), + #[command(hide = true)] // Hidden while the feature is being built out Debug(debug_cmd::DebugCommand), Prove(prove_cmd::ProveCommand), Verify(verify_cmd::VerifyCommand), @@ -105,6 +108,7 @@ pub(crate) fn start_cli() -> eyre::Result<()> { NargoCommand::Compile(args) => compile_cmd::run(&backend, args, config), NargoCommand::Debug(args) => debug_cmd::run(&backend, args, config), NargoCommand::Execute(args) => execute_cmd::run(&backend, args, config), + NargoCommand::Export(args) => export_cmd::run(&backend, args, config), NargoCommand::Prove(args) => prove_cmd::run(&backend, args, config), NargoCommand::Verify(args) => verify_cmd::run(&backend, args, config), NargoCommand::Test(args) => test_cmd::run(&backend, args, config),