Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

93 changes: 67 additions & 26 deletions compiler/noirc_evaluator/src/ssa/ir/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ use iter_extended::vecmap;
use noirc_frontend::monomorphization::ast::InlineType;
use serde::{Deserialize, Serialize};

use crate::ssa::ir::instruction::Instruction;
use crate::ssa::ir::post_order::PostOrder;

use super::basic_block::BasicBlockId;
use super::dfg::{DataFlowGraph, GlobalsGraph};
use super::instruction::TerminatorInstruction;
Expand Down Expand Up @@ -229,10 +232,69 @@ impl Function {
.sum()
}

pub fn view(&self) -> FunctionView {
FunctionView(self)
}
}

impl Clone for Function {
fn clone(&self) -> Self {
Function::clone_with_id(self.id(), self)
}
}

impl std::fmt::Display for RuntimeType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
RuntimeType::Acir(inline_type) => write!(f, "acir({inline_type})"),
RuntimeType::Brillig(inline_type) => write!(f, "brillig({inline_type})"),
}
}
}

/// Provide public access to certain aspects of a `Function` without bloating its API.
pub struct FunctionView<'a>(&'a Function);

impl<'a> FunctionView<'a> {
/// Iterate over every Value in this DFG in no particular order, including unused Values,
/// for testing purposes.
pub fn values_iter(&self) -> impl DoubleEndedIterator<Item = (ValueId, &'a Value)> {
self.0.dfg.values_iter()
}

/// Iterate over the blocks in the CFG in reverse-post-order.
pub fn blocks_iter(&self) -> impl ExactSizeIterator<Item = BasicBlockId> {
let post_order = PostOrder::with_function(self.0);
post_order.into_vec_reverse().into_iter()
}

/// Iterate over the successors of a blocks.
pub fn block_successors_iter(
&self,
block_id: BasicBlockId,
) -> impl ExactSizeIterator<Item = BasicBlockId> {
let block = &self.0.dfg[block_id];
block.successors()
}

/// Iterate over the functions called from a block.
pub fn block_callees_iter(&self, block_id: BasicBlockId) -> impl Iterator<Item = FunctionId> {
let block = &self.0.dfg[block_id];
block.instructions().iter().map(|id| &self.0.dfg[*id]).filter_map(|instruction| {
let Instruction::Call { func, .. } = instruction else {
return None;
};
let Value::Function(func) = self.0.dfg[*func] else {
return None;
};
Some(func)
})
}

/// Iterate over the numeric constants in the function.
pub fn constants(&self) -> impl Iterator<Item = (&FieldElement, &NumericType)> {
let local = self.dfg.values_iter();
let global = self.dfg.globals.values_iter();
let local = self.0.dfg.values_iter();
let global = self.0.dfg.globals.values_iter();
local.chain(global).filter_map(|(_, value)| {
if let Value::NumericConstant { constant, typ } = value {
Some((constant, typ))
Expand All @@ -243,41 +305,20 @@ impl Function {
}

pub fn has_data_bus_return_data(&self) -> bool {
self.dfg.data_bus.return_data.is_some()
self.0.dfg.data_bus.return_data.is_some()
}

/// Return the types of the function parameters.
pub fn parameter_types(&self) -> Vec<Type> {
vecmap(self.parameters(), |p| self.dfg.type_of_value(*p))
vecmap(self.0.parameters(), |p| self.0.dfg.type_of_value(*p))
}

/// Return the types of the returned values, if there are any.
pub fn return_types(&self) -> Option<Vec<Type>> {
self.returns().map(|rs| vecmap(rs, |p| self.dfg.type_of_value(*p)))
}
}

impl Clone for Function {
fn clone(&self) -> Self {
Function::clone_with_id(self.id(), self)
self.0.returns().map(|rs| vecmap(rs, |p| self.0.dfg.type_of_value(*p)))
}
}

impl std::fmt::Display for RuntimeType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
RuntimeType::Acir(inline_type) => write!(f, "acir({inline_type})"),
RuntimeType::Brillig(inline_type) => write!(f, "brillig({inline_type})"),
}
}
}

/// Iterate over every Value in this DFG in no particular order, including unused Values,
/// for testing purposes.
pub fn function_values_iter(func: &Function) -> impl DoubleEndedIterator<Item = (ValueId, &Value)> {
func.dfg.values_iter()
}

/// FunctionId is a reference for a function
///
/// This Id is how each function refers to other functions
Expand Down
2 changes: 1 addition & 1 deletion compiler/noirc_evaluator/src/ssa/validation/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@
Value::Function(func_id) => {
let called_function = &self.ssa.functions[func_id];

let parameter_types = called_function.parameter_types();
let parameter_types = called_function.view().parameter_types();
assert_eq!(
arguments.len(),
parameter_types.len(),
Expand Down Expand Up @@ -664,19 +664,19 @@
// message_hash: [u8; N],
// predicate: bool,
// ) -> bool
assert_arguments_length(arguments, 5, "ecdsa_secp_256");

Check warning on line 667 in compiler/noirc_evaluator/src/ssa/validation/mod.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (secp)

let public_key_x = arguments[0];
let public_key_x_type = dfg.type_of_value(public_key_x);
let public_key_x_length =
assert_u8_array(&public_key_x_type, "ecdsa_secp256 public_key_x");

Check warning on line 672 in compiler/noirc_evaluator/src/ssa/validation/mod.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (secp)
assert_array_length(public_key_x_length, 32, "ecdsa_secp256 public_key_x");

Check warning on line 673 in compiler/noirc_evaluator/src/ssa/validation/mod.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (secp)

let public_key_y = arguments[1];
let public_key_y_type = dfg.type_of_value(public_key_y);
let public_key_y_length =
assert_u8_array(&public_key_y_type, "ecdsa_secp256 public_key_y");

Check warning on line 678 in compiler/noirc_evaluator/src/ssa/validation/mod.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (secp)
assert_array_length(public_key_y_length, 32, "ecdsa_secp256 public_key_y");

Check warning on line 679 in compiler/noirc_evaluator/src/ssa/validation/mod.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (secp)

let signature = arguments[2];
let signature_type = dfg.type_of_value(signature);
Expand Down
9 changes: 9 additions & 0 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,15 @@ format-noir:
cargo run -- --program-dir={{justfile_dir()}}/noir_stdlib fmt --check
cd ./test_programs && NARGO="{{justfile_dir()}}/target/debug/nargo" ./format.sh check

# Visualize the CFG after a certain SSA pass and open the Mermaid Live editor.
# This is mostly here for reference: it only works if the pass matches a single unique pass in the pipeline, and there are no errors.
[no-cd]
visualize-ssa-cfg PASS:
open https://mermaid.live/view#$( \
cargo run -q -p nargo_cli -- compile --show-ssa-pass {{PASS}} \
| grep -v After \
| cargo run -q -p noir_ssa_cli -- visualize --url-encode)

# Javascript

# Lints Javascript code
Expand Down
2 changes: 1 addition & 1 deletion tooling/ast_fuzzer/src/input/dictionary.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use noirc_evaluator::ssa::ssa_gen::Ssa;
pub(crate) fn build_dictionary_from_ssa(ssa: &Ssa) -> BTreeSet<FieldElement> {
let mut constants = BTreeSet::new();
for func in ssa.functions.values() {
for (constant, _) in func.constants() {
for (constant, _) in func.view().constants() {
constants.insert(*constant);
}
}
Expand Down
5 changes: 2 additions & 3 deletions tooling/ast_fuzzer/tests/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ use noirc_evaluator::{
brillig::BrilligOptions,
ssa::{
self,
ir::function::function_values_iter,
opt::inlining::MAX_INSTRUCTIONS,
primary_passes,
ssa_gen::{self, Ssa},
Expand Down Expand Up @@ -95,8 +94,8 @@ fn arb_ssa_roundtrip() {
continue;
}
let func2 = &ssa2.functions[&func_id];
let values1 = function_values_iter(&func1).collect::<Vec<_>>();
let values2 = function_values_iter(func2).collect::<Vec<_>>();
let values1 = func1.view().values_iter().collect::<Vec<_>>();
let values2 = func2.view().values_iter().collect::<Vec<_>>();
similar_asserts::assert_eq!(values1, values2);
}

Expand Down
2 changes: 1 addition & 1 deletion tooling/nargo_cli/src/cli/interpret_cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ pub(crate) fn run(args: InterpretCommand, workspace: Workspace) -> Result<(), Cl
// correctness, it's enough if we make sure the flattened values match.
let ssa_return = ssa_return.map(|ssa_return| {
let main_function = &ssa.functions[&ssa.main_id];
if main_function.has_data_bus_return_data() {
if main_function.view().has_data_bus_return_data() {
let values = flatten_databus_values(ssa_return);
vec![Value::array(values, vec![Type::Numeric(NumericType::NativeField)])]
} else {
Expand Down
2 changes: 2 additions & 0 deletions tooling/ssa_cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,11 @@ path = "src/main.rs"
workspace = true

[dependencies]
base64.workspace = true
clap.workspace = true
color-eyre.workspace = true
const_format.workspace = true
serde_json.workspace = true
thiserror.workspace = true
toml.workspace = true
tempfile.workspace = true
Expand Down
108 changes: 108 additions & 0 deletions tooling/ssa_cli/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# SSA CLI

The SSA CLI facilitates experimenting with SSA snippets outside the context of a Noir program. For example we can print the Initial SSA (or any intermediate step) of a Noir program from `nargo [compile|execute|interpret]` using the `--show-ssa-pass` option, and then use the SSA CLI to further `transform`, `interpret` or `visualize` it.


## Example

Take this simple Noir program:

```rust
fn main(c: i32, h: i32) {
assert(c < h);
for t in -10..40 {
show(c, h, t);
}
}

fn show(c: i32, h: i32, t: i32) {
if t < 0 {
println(f"{t}: freezing");
} else if t < c {
println(f"{t}: cold");
} else if t < h {
println(f"{t}: mild");
} else {
println(f"{t}: hot");
}
}
```

Print the initial SSA and save it to a file:
```bash
cargo run -q -p nargo_cli -- compile --silence-warnings --show-ssa-pass Initial \
| tail -n +2 \
> example.ssa
```

The `tail` is just to get rid of the "After Initial SSA:" message.

For quick checks we can also just copy-paste some SSA snippet and use e.g. `pbpaste` to pipe it to the SSA CLI.

### Visualize

We can render the Control Flow Graph of the program to a Markdown file which we can preview in an editor (e.g. using the VS Code [Mermaid Markdown extension](https://marketplace.visualstudio.com/items?itemName=bierner.markdown-mermaid)):

```bash
cat example.ssa \
| cargo run -q -p noir_ssa_cli -- visualize --markdown \
> example.md
code example.md
```

Or we can go directly to the [Mermaid Live Editor](https:://mermaid.live):

```bash
open https://mermaid.live/view#$(cargo run -q -p noir_ssa_cli -- visualize --source-path example.ssa --url-encode)
```

The result should look like [this](https://mermaid.live/view#pako:eNqNlN2ymjAUhV-FyZXOeJj8kQAXvWj7CL2qdDooUZyjwYkwp63ju3cbAZMoZ2S8CGt9e0nI3pzRuqkUytFm33ys69K00Y-vhY7gOnWrrSmPdbTB0fJQ7jQsft2s67XB8QrPZis8nwdi9Pb2xa6IbxCgSUiTkaZTBvMNNoMcNg-DKKj0QQyfRemq0OH-SLQ81c0HLNz9kWf7I-P-iL8_z6C-8fhoZHw0WPEpI_GNBGKSMCYZaTllpL6RQkwaxqQjnfmGBFqGtJyiM6CzkM5GWvgGB5qHNJ-iBdAipMVIM994bDYy9tQjHfTU0y6h0fJodrrdwyBQt1GobRTbKZ_VM6eeufXstXru1HO3nr9Wn_T1vzu9bvSpNTDSqgLdzUpeyxITWcLNEq9lyYks6WbJz7L6SY9je7i9dmvqm8bvmhw0dtf4E40MWj_L9pQHrR81e3KD1nerPY1BgwFGC7Q1uwrlrenUAh2UgU8p3KLzlS9QW6uDKlAOy6o07wUq9AVqjqX-2TSHocw03bZG-abcn-CuO1Zlq77vSniDdwReiDLfmk63KCfMRqD8jP6gnCU8ZiTNBPwol3yB_gKCs5iljHNKGMkwSdhlgf7Z_8RxiomgLOOYC3llLv8BmsiLzg).

The same can be achieved for a Noir project with the following utility command:

```shell
just visualize-ssa-cfg Initial
```

### Transform

We can experiment with applying various SSA passes with the `transform` command, using the `--ssa-pass` option to specify which passes we want to run. The same pass can appear multiple times.

```bash
cargo run -q -p noir_ssa_cli -- transform --source-path example.ssa --ssa-pass Unrolling
```

Use the `list` command to see the available options for `--ssa-pass`.

### Interpret

The SSA can be evaluated with some inputs using the `interpret` command. The inputs can come from a JSON or TOML file, or CLI options. Notably the variable names have to match the SSA, not the original Noir code.

For example if we look at the `example.ssa` above, we see that the variables are called `v0` and `v1`:

```console
$ head example.ssa
acir(inline) fn main f0 {
b0(v0: i32, v1: i32):
v3 = lt v0, v1
constrain v3 == u1 1
jmp b1(i32 -10)
b1(v2: i32):
v7 = lt v2, i32 40
jmpif v7 then: b2, else: b3
b2():
call f1(v0, v1, v2)
```

We can run the SSA for example by passing values encoded as TOML using the `--input-toml` option:

```console
$ cargo run -q -p noir_ssa_cli -- interpret --source-path example.ssa --input-toml "v0=10; v1=30"
-10: freezing
-9: freezing
...
39: hot
--- Interpreter result:
Ok()
---
```
2 changes: 2 additions & 0 deletions tooling/ssa_cli/src/cli/interpret_cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ fn abi_from_ssa(ssa: &Ssa) -> Abi {
let visibility = AbiVisibility::Public;

let parameters = main
.view()
.parameter_types()
.iter()
.enumerate()
Expand All @@ -114,6 +115,7 @@ fn abi_from_ssa(ssa: &Ssa) -> Abi {
.collect();

let return_type = main
.view()
.return_types()
.filter(|ts| !ts.is_empty())
.map(|types| AbiReturnType { abi_type: abi_type_from_multi_ssa(&types), visibility });
Expand Down
Loading
Loading