Skip to content

Commit

Permalink
feat: add MVP nargo export command (#3870)
Browse files Browse the repository at this point in the history
# Description

## Problem\*

Resolves <!-- Link to GitHub Issue -->

## Summary\*

This PR adds the skeleton of a `nargo export` command which compiles all
of the functions in a library crate with the `#[export]` attribute into
an `export` directory. This is intended to be then fed into
`noir-codegen` in order to generate a TS library for executing these
functions.

## Additional Context



## Documentation\*

Check one:
- [ ] No documentation needed.
- [ ] Documentation included in this PR.
- [ ] **[Exceptional Case]** Documentation to be submitted in a separate
PR.

# PR Checklist\*

- [ ] I have tested the changes locally.
- [ ] I have formatted the changes with [Prettier](https://prettier.io/)
and/or `cargo fmt` on default settings.

---------

Co-authored-by: kevaundray <[email protected]>
  • Loading branch information
TomAFrench and kevaundray authored Jan 5, 2024
1 parent d731103 commit fbb51ed
Show file tree
Hide file tree
Showing 16 changed files with 205 additions and 21 deletions.
16 changes: 15 additions & 1 deletion .github/workflows/test-js-packages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,7 @@ jobs:
run: yarn workspace @noir-lang/noir_wasm test:browser

test-noir-codegen:
needs: [build-acvm-js, build-noirc-abi]
needs: [build-acvm-js, build-noirc-abi, build-nargo]
name: noir_codegen
runs-on: ubuntu-latest
timeout-minutes: 30
Expand All @@ -328,6 +328,12 @@ jobs:
- name: Checkout
uses: actions/checkout@v4

- name: Download nargo binary
uses: actions/download-artifact@v3
with:
name: nargo
path: ./nargo

- name: Download acvm_js package artifact
uses: actions/download-artifact@v3
with:
Expand All @@ -339,6 +345,14 @@ jobs:
with:
name: noirc_abi_wasm
path: ./tooling/noirc_abi_wasm

- name: Set nargo on PATH
run: |
nargo_binary="${{ github.workspace }}/nargo/nargo"
chmod +x $nargo_binary
echo "$(dirname $nargo_binary)" >> $GITHUB_PATH
export PATH="$PATH:$(dirname $nargo_binary)"
nargo -V
- name: Install Yarn dependencies
uses: ./.github/actions/setup
Expand Down
23 changes: 23 additions & 0 deletions compiler/noirc_frontend/src/hir/def_map/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Item = FuncId> + '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<Contract> {
Expand Down
13 changes: 13 additions & 0 deletions compiler/noirc_frontend/src/hir/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,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()
}

/// Return a Vec of all `contract` declarations in the source code and the functions they contain
pub fn get_all_contracts(&self, crate_id: &CrateId) -> Vec<Contract> {
self.def_map(crate_id)
Expand Down
5 changes: 4 additions & 1 deletion compiler/noirc_frontend/src/lexer/token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -588,6 +589,7 @@ pub enum SecondaryAttribute {
// the entry point.
ContractLibraryMethod,
Event,
Export,
Field(String),
Custom(String),
}
Expand All @@ -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})]"),
}
}
Expand All @@ -625,7 +628,7 @@ impl AsRef<str> for SecondaryAttribute {
SecondaryAttribute::Deprecated(None) => "",
SecondaryAttribute::Custom(string) | SecondaryAttribute::Field(string) => string,
SecondaryAttribute::ContractLibraryMethod => "",
SecondaryAttribute::Event => "",
SecondaryAttribute::Event | SecondaryAttribute::Export => "",
}
}
}
Expand Down
3 changes: 2 additions & 1 deletion cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
"bincode",
"bindgen",
"bitand",
"bitxor",
"bitor",
"bitxor",
"blackbox",
"bridgekeeper",
"brillig",
Expand All @@ -29,6 +29,7 @@
"chumsky",
"clippy",
"codegen",
"codegenned",
"codegens",
"Codespaces",
"codespan",
Expand Down
2 changes: 2 additions & 0 deletions tooling/nargo/src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ pub const PROOFS_DIR: &str = "proofs";
pub const SRC_DIR: &str = "src";
/// The directory to store circuits' serialized ACIR representations.
pub const TARGET_DIR: &str = "target";
/// The directory to store serialized ACIR representations of exported library functions.
pub const EXPORT_DIR: &str = "export";

// Files
/// The file from which Nargo pulls prover inputs
Expand Down
6 changes: 5 additions & 1 deletion tooling/nargo/src/workspace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use std::{
};

use crate::{
constants::{CONTRACT_DIR, PROOFS_DIR, TARGET_DIR},
constants::{CONTRACT_DIR, EXPORT_DIR, PROOFS_DIR, TARGET_DIR},
package::Package,
};

Expand Down Expand Up @@ -40,6 +40,10 @@ impl Workspace {
pub fn target_directory_path(&self) -> PathBuf {
self.root_dir.join(TARGET_DIR)
}

pub fn export_directory_path(&self) -> PathBuf {
self.root_dir.join(EXPORT_DIR)
}
}

pub enum IntoIter<'a, T> {
Expand Down
2 changes: 1 addition & 1 deletion tooling/nargo_cli/src/cli/compile_cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ fn compile_contract(
Ok((optimized_contract, warnings))
}

fn save_program(
pub(super) fn save_program(
program: CompiledProgram,
package: &Package,
circuit_dir: &Path,
Expand Down
120 changes: 120 additions & 0 deletions tooling/nargo_cli/src/cli/export_cmd.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
use nargo::errors::CompileError;
use noirc_errors::FileDiagnostic;
use rayon::prelude::*;

use fm::FileManager;
use iter_extended::try_vecmap;
use nargo::insert_all_files_for_workspace_into_file_manager;
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, file_manager_with_stdlib, CompileOptions, CompiledProgram,
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::compile_cmd::report_errors;
use super::fs::program::save_program_to_file;
use super::NargoConfig;

/// Exports functions marked with #[export] attribute
#[derive(Debug, Clone, Args)]
pub(crate) struct ExportCommand {
/// The name of the package to compile
#[clap(long, conflicts_with = "workspace")]
package: Option<CrateName>,

/// 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 mut workspace_file_manager = file_manager_with_stdlib(&workspace.root_dir);
insert_all_files_for_workspace_into_file_manager(&workspace, &mut workspace_file_manager);

let library_packages: Vec<_> =
workspace.into_iter().filter(|package| package.is_library()).collect();

library_packages
.par_iter()
.map(|package| {
compile_exported_functions(
&workspace_file_manager,
&workspace,
package,
&args.compile_options,
)
})
.collect()
}

fn compile_exported_functions(
file_manager: &FileManager,
workspace: &Workspace,
package: &Package,
compile_options: &CompileOptions,
) -> Result<(), CliError> {
let (mut context, crate_id) = prepare_package(file_manager, 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 = try_vecmap(
exported_functions,
|(function_name, function_id)| -> Result<(String, CompiledProgram), CompileError> {
// TODO: We should to refactor how to deal with compilation errors to avoid this.
let program = compile_no_check(&context, compile_options, function_id, None, false)
.map_err(|error| vec![FileDiagnostic::from(error)]);

let program = report_errors(
program.map(|program| (program, Vec::new())),
file_manager,
compile_options.deny_warnings,
compile_options.silence_warnings,
)?;

Ok((function_name, program))
},
)?;

let export_dir = workspace.export_directory_path();
for (function_name, program) in exported_programs {
save_program_to_file(&program.into(), &function_name.parse().unwrap(), &export_dir);
}
Ok(())
}
4 changes: 4 additions & 0 deletions tooling/nargo_cli/src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ mod compile_cmd;
mod dap_cmd;
mod debug_cmd;
mod execute_cmd;
mod export_cmd;
mod fmt_cmd;
mod info_cmd;
mod init_cmd;
Expand Down Expand Up @@ -69,6 +70,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),
Expand Down Expand Up @@ -109,6 +112,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),
Expand Down
1 change: 0 additions & 1 deletion tooling/noir_codegen/.gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
crs
lib

!test/*/target
test/codegen
4 changes: 2 additions & 2 deletions tooling/noir_codegen/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@
"dev": "tsc-multi --watch",
"build": "tsc",
"test": "yarn test:codegen && yarn test:node && yarn test:clean",
"test:codegen": "tsx src/main.ts ./test/assert_lt/target/** --out-dir ./test/codegen",
"test:codegen": "nargo export --program-dir=./test/test_lib && tsx src/main.ts ./test/test_lib/export/** --out-dir ./test/codegen",
"test:node": "mocha --timeout 25000 --exit --config ./.mocharc.json",
"test:clean": "rm -rf ./test/codegen",
"test:clean": "rm -rf ./test/codegen ./test/test_lib/export",
"prettier": "prettier 'src/**/*.ts'",
"prettier:fix": "prettier --write 'src/**/*.ts' 'test/**/*.ts'",
"lint": "NODE_NO_WARNINGS=1 eslint . --ext .ts --ignore-path ./.eslintignore --max-warnings 0",
Expand Down
1 change: 0 additions & 1 deletion tooling/noir_codegen/test/assert_lt/target/assert_lt.json

This file was deleted.

8 changes: 5 additions & 3 deletions tooling/noir_codegen/test/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { expect } from 'chai';
import { assert_lt, MyStruct, u64, ForeignCallHandler } from './codegen/index.js';
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore File is codegenned at test time.
import { exported_function_foo, MyStruct, u64, ForeignCallHandler } from './codegen/index.js';

it('codegens a callable function', async () => {
const my_struct = { foo: true, bar: ['12345', '12345', '12345'] };

const [sum, constant, struct]: [u64, u64, MyStruct] = await assert_lt(
const [sum, constant, struct]: [u64, u64, MyStruct] = await exported_function_foo(
'2',
'3',
[0, 0, 0, 0, 0],
Expand Down Expand Up @@ -35,7 +37,7 @@ it('allows passing a custom foreign call handler', async () => {

const my_struct = { foo: true, bar: ['12345', '12345', '12345'] };

const [sum, constant, struct]: [u64, u64, MyStruct] = await assert_lt(
const [sum, constant, struct]: [u64, u64, MyStruct] = await exported_function_foo(
'2',
'3',
[0, 0, 0, 0, 0],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[package]
name = "assert_lt"
type = "bin"
name = "test_lib"
type = "lib"
authors = [""]
[dependencies]
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,8 @@ struct NestedStruct {
baz: u64
}

fn main(
x: u64,
y: pub u64,
array: [u8; 5],
my_struct: NestedStruct,
string: str<5>
) -> pub (u64, u64, MyStruct) {
#[export]
fn exported_function_foo(x: u64, y: u64, array: [u8; 5], my_struct: NestedStruct, string: str<5>) -> (u64, u64, MyStruct) {
assert(array.len() == 5);
assert(my_struct.foo.foo);
assert(string == "12345");
Expand All @@ -24,3 +19,8 @@ fn main(
assert(x < y);
(x + y, 3, my_struct.foo)
}

#[export]
fn exported_function_bar(my_struct: NestedStruct) -> (u64) {
my_struct.baz
}

0 comments on commit fbb51ed

Please sign in to comment.