Skip to content

Commit

Permalink
Merge branch 'master' into jz/docs-add-writing-noir
Browse files Browse the repository at this point in the history
  • Loading branch information
jzaki authored Jul 29, 2024
2 parents 95e3496 + 28211a3 commit 8d67f69
Show file tree
Hide file tree
Showing 129 changed files with 3,412 additions and 1,695 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

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

54 changes: 54 additions & 0 deletions acvm-repo/acir/src/native_types/expression/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,60 @@ impl<F: AcirField> Expression<F> {

Expression { mul_terms, linear_combinations, q_c }
}

/// Determine the width of this expression.
/// The width meaning the number of unique witnesses needed for this expression.
pub fn width(&self) -> usize {
let mut width = 0;

for mul_term in &self.mul_terms {
// The coefficient should be non-zero, as this method is ran after the compiler removes all zero coefficient terms
assert_ne!(mul_term.0, F::zero());

let mut found_x = false;
let mut found_y = false;

for term in self.linear_combinations.iter() {
let witness = &term.1;
let x = &mul_term.1;
let y = &mul_term.2;
if witness == x {
found_x = true;
};
if witness == y {
found_y = true;
};
if found_x & found_y {
break;
}
}

// If the multiplication is a squaring then we must assign the two witnesses to separate wires and so we
// can never get a zero contribution to the width.
let multiplication_is_squaring = mul_term.1 == mul_term.2;

let mul_term_width_contribution = if !multiplication_is_squaring && (found_x & found_y)
{
// Both witnesses involved in the multiplication exist elsewhere in the expression.
// They both do not contribute to the width of the expression as this would be double-counting
// due to their appearance in the linear terms.
0
} else if found_x || found_y {
// One of the witnesses involved in the multiplication exists elsewhere in the expression.
// The multiplication then only contributes 1 new witness to the width.
1
} else {
// Worst case scenario, the multiplication is using completely unique witnesses so has a contribution of 2.
2
};

width += mul_term_width_contribution;
}

width += self.linear_combinations.len();

width
}
}

impl<F: AcirField> From<F> for Expression<F> {
Expand Down
65 changes: 1 addition & 64 deletions acvm-repo/acvm/src/compiler/transformers/csat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -415,71 +415,8 @@ fn fits_in_one_identity<F: AcirField>(expr: &Expression<F>, width: usize) -> boo
if expr.mul_terms.len() > 1 {
return false;
};
// A Polynomial with more terms than fan-in cannot fit within a single opcode
if expr.linear_combinations.len() > width {
return false;
}

// A polynomial with no mul term and a fan-in that fits inside of the width can fit into a single opcode
if expr.mul_terms.is_empty() {
return true;
}

// A polynomial with width-2 fan-in terms and a single non-zero mul term can fit into one opcode
// Example: Axy + Dz . Notice, that the mul term places a constraint on the first two terms, but not the last term
// XXX: This would change if our arithmetic polynomial equation was changed to Axyz for example, but for now it is not.
if expr.linear_combinations.len() <= (width - 2) {
return true;
}

// We now know that we have a single mul term. We also know that the mul term must match up with at least one of the other terms
// A polynomial whose mul terms are non zero which do not match up with two terms in the fan-in cannot fit into one opcode
// An example of this is: Axy + Bx + Cy + ...
// Notice how the bivariate monomial xy has two univariate monomials with their respective coefficients
// XXX: note that if x or y is zero, then we could apply a further optimization, but this would be done in another algorithm.
// It would be the same as when we have zero coefficients - Can only work if wire is constrained to be zero publicly
let mul_term = &expr.mul_terms[0];

// The coefficient should be non-zero, as this method is ran after the compiler removes all zero coefficient terms
assert_ne!(mul_term.0, F::zero());

let mut found_x = false;
let mut found_y = false;

for term in expr.linear_combinations.iter() {
let witness = &term.1;
let x = &mul_term.1;
let y = &mul_term.2;
if witness == x {
found_x = true;
};
if witness == y {
found_y = true;
};
if found_x & found_y {
break;
}
}

// If the multiplication is a squaring then we must assign the two witnesses to separate wires and so we
// can never get a zero contribution to the width.
let multiplication_is_squaring = mul_term.1 == mul_term.2;

let mul_term_width_contribution = if !multiplication_is_squaring && (found_x & found_y) {
// Both witnesses involved in the multiplication exist elsewhere in the expression.
// They both do not contribute to the width of the expression as this would be double-counting
// due to their appearance in the linear terms.
0
} else if found_x || found_y {
// One of the witnesses involved in the multiplication exists elsewhere in the expression.
// The multiplication then only contributes 1 new witness to the width.
1
} else {
// Worst case scenario, the multiplication is using completely unique witnesses so has a contribution of 2.
2
};

mul_term_width_contribution + expr.linear_combinations.len() <= width
expr.width() <= width
}

#[cfg(test)]
Expand Down
7 changes: 4 additions & 3 deletions aztec_macros/src/transforms/functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ use crate::{
ast_utils::{
assignment, assignment_with_type, call, cast, expression, ident, ident_path,
index_array, make_eq, make_statement, make_type, method_call, mutable_assignment,
mutable_reference, path, return_type, variable, variable_ident, variable_path,
mutable_reference, path, path_segment, return_type, variable, variable_ident,
variable_path,
},
errors::AztecMacroError,
},
Expand Down Expand Up @@ -722,8 +723,8 @@ fn add_struct_to_hasher(identifier: &Ident, hasher_name: &str) -> Statement {
fn str_to_bytes(identifier: &Ident) -> (Statement, Ident) {
// let identifier_as_bytes = identifier.as_bytes();
let var = variable_ident(identifier.clone());
let contents = if let ExpressionKind::Variable(p, _) = &var.kind {
p.segments.first().cloned().unwrap_or_else(|| panic!("No segments")).0.contents
let contents = if let ExpressionKind::Variable(p) = &var.kind {
p.first_name()
} else {
panic!("Unexpected identifier type")
};
Expand Down
16 changes: 6 additions & 10 deletions aztec_macros/src/transforms/note_interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ use crate::{
utils::{
ast_utils::{
check_trait_method_implemented, ident, ident_path, is_custom_attribute, make_type,
path_segment,
},
errors::AztecMacroError,
hir_utils::{fetch_notes, get_contract_module_data, inject_global},
Expand All @@ -45,8 +46,8 @@ pub fn generate_note_interface_impl(module: &mut SortedModule) -> Result<(), Azt
.iter_mut()
.find(|trait_impl| {
if let UnresolvedTypeData::Named(struct_path, _, _) = &trait_impl.object_type.typ {
struct_path.last_segment() == note_struct.name
&& trait_impl.trait_name.last_segment().0.contents == "NoteInterface"
struct_path.last_ident() == note_struct.name
&& trait_impl.trait_name.last_name() == "NoteInterface"
} else {
false
}
Expand All @@ -61,7 +62,7 @@ pub fn generate_note_interface_impl(module: &mut SortedModule) -> Result<(), Azt
let note_interface_impl_span: Option<Span> = trait_impl.object_type.span;
// Look for the note struct implementation, generate a default one if it doesn't exist (in order to append methods to it)
let existing_impl = module.impls.iter_mut().find(|r#impl| match &r#impl.object_type.typ {
UnresolvedTypeData::Named(path, _, _) => path.last_segment().eq(&note_struct.name),
UnresolvedTypeData::Named(path, _, _) => path.last_ident().eq(&note_struct.name),
_ => false,
});
let note_impl = if let Some(note_impl) = existing_impl {
Expand All @@ -73,7 +74,6 @@ pub fn generate_note_interface_impl(module: &mut SortedModule) -> Result<(), Azt
generics: vec![],
methods: vec![],
where_clause: vec![],
is_comptime: false,
};
module.impls.push(default_impl.clone());
module.impls.last_mut().unwrap()
Expand All @@ -85,9 +85,7 @@ pub fn generate_note_interface_impl(module: &mut SortedModule) -> Result<(), Azt
.trait_generics
.iter()
.map(|gen| match gen.typ.clone() {
UnresolvedTypeData::Named(path, _, _) => {
Ok(path.last_segment().0.contents.to_string())
}
UnresolvedTypeData::Named(path, _, _) => Ok(path.last_name().to_string()),
UnresolvedTypeData::Expression(UnresolvedTypeExpression::Constant(val, _)) => {
Ok(val.to_string())
}
Expand All @@ -106,9 +104,7 @@ pub fn generate_note_interface_impl(module: &mut SortedModule) -> Result<(), Azt
// Automatically inject the header field if it's not present
let (header_field_name, _) = if let Some(existing_header) =
note_struct.fields.iter().find(|(_, field_type)| match &field_type.typ {
UnresolvedTypeData::Named(path, _, _) => {
path.last_segment().0.contents == "NoteHeader"
}
UnresolvedTypeData::Named(path, _, _) => path.last_name() == "NoteHeader",
_ => false,
}) {
existing_header.clone()
Expand Down
13 changes: 5 additions & 8 deletions aztec_macros/src/transforms/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use crate::{
utils::{
ast_utils::{
call, expression, ident, ident_path, is_custom_attribute, lambda, make_statement,
make_type, pattern, return_type, variable, variable_path,
make_type, path_segment, pattern, return_type, variable, variable_path,
},
errors::AztecMacroError,
hir_utils::{
Expand Down Expand Up @@ -59,7 +59,7 @@ fn inject_context_in_storage_field(field: &mut UnresolvedType) -> Result<(), Azt
vec![],
false,
)));
match path.segments.last().unwrap().0.contents.as_str() {
match path.last_name() {
"Map" => inject_context_in_storage_field(&mut generics[1]),
_ => Ok(()),
}
Expand Down Expand Up @@ -106,9 +106,7 @@ pub fn check_for_storage_implementation(
storage_struct_name: &String,
) -> bool {
module.impls.iter().any(|r#impl| match &r#impl.object_type.typ {
UnresolvedTypeData::Named(path, _, _) => {
path.segments.last().is_some_and(|segment| segment.0.contents == *storage_struct_name)
}
UnresolvedTypeData::Named(path, _, _) => path.last_name() == *storage_struct_name,
_ => false,
})
}
Expand All @@ -123,8 +121,8 @@ pub fn generate_storage_field_constructor(
match typ {
UnresolvedTypeData::Named(path, generics, _) => {
let mut new_path = path.clone().to_owned();
new_path.segments.push(ident("new"));
match path.segments.last().unwrap().0.contents.as_str() {
new_path.segments.push(path_segment("new"));
match path.last_name() {
"Map" => Ok(call(
variable_path(new_path),
vec![
Expand Down Expand Up @@ -248,7 +246,6 @@ pub fn generate_storage_implementation(
methods: vec![(init, Span::default())],

where_clause: vec![],
is_comptime: false,
};
module.impls.push(storage_impl);

Expand Down
18 changes: 11 additions & 7 deletions aztec_macros/src/utils/ast_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ use noirc_errors::{Span, Spanned};
use noirc_frontend::ast::{
BinaryOpKind, CallExpression, CastExpression, Expression, ExpressionKind, FunctionReturnType,
Ident, IndexExpression, InfixExpression, Lambda, LetStatement, MemberAccessExpression,
MethodCallExpression, NoirTraitImpl, Path, Pattern, PrefixExpression, Statement, StatementKind,
TraitImplItem, UnaryOp, UnresolvedType, UnresolvedTypeData,
MethodCallExpression, NoirTraitImpl, Path, PathSegment, Pattern, PrefixExpression, Statement,
StatementKind, TraitImplItem, UnaryOp, UnresolvedType, UnresolvedTypeData,
};
use noirc_frontend::token::SecondaryAttribute;

Expand All @@ -18,6 +18,10 @@ pub fn ident_path(name: &str) -> Path {
Path::from_ident(ident(name))
}

pub fn path_segment(name: &str) -> PathSegment {
PathSegment::from(ident(name))
}

pub fn path(ident: Ident) -> Path {
Path::from_ident(ident)
}
Expand All @@ -27,15 +31,15 @@ pub fn expression(kind: ExpressionKind) -> Expression {
}

pub fn variable(name: &str) -> Expression {
expression(ExpressionKind::Variable(ident_path(name), None))
expression(ExpressionKind::Variable(ident_path(name)))
}

pub fn variable_ident(identifier: Ident) -> Expression {
expression(ExpressionKind::Variable(path(identifier), None))
expression(ExpressionKind::Variable(path(identifier)))
}

pub fn variable_path(path: Path) -> Expression {
expression(ExpressionKind::Variable(path, None))
expression(ExpressionKind::Variable(path))
}

pub fn method_call(
Expand Down Expand Up @@ -149,7 +153,7 @@ macro_rules! chained_path {
{
let mut base_path = ident_path($base);
$(
base_path.segments.push(ident($tail));
base_path.segments.push(path_segment($tail));
)*
base_path
}
Expand All @@ -163,7 +167,7 @@ macro_rules! chained_dep {
let mut base_path = ident_path($base);
base_path.kind = PathKind::Plain;
$(
base_path.segments.push(ident($tail));
base_path.segments.push(path_segment($tail));
)*
base_path
}
Expand Down
1 change: 1 addition & 0 deletions aztec_macros/src/utils/hir_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@ pub fn inject_global(
let global_id = context.def_interner.push_empty_global(
name.clone(),
module_id,
*crate_id,
file_id,
global.attributes.clone(),
false,
Expand Down
2 changes: 1 addition & 1 deletion compiler/noirc_driver/src/abi_gen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ fn to_abi_visibility(value: Visibility) -> AbiVisibility {
match value {
Visibility::Public => AbiVisibility::Public,
Visibility::Private => AbiVisibility::Private,
Visibility::DataBus => AbiVisibility::DataBus,
Visibility::CallData(_) | Visibility::ReturnData => AbiVisibility::DataBus,
}
}

Expand Down
17 changes: 17 additions & 0 deletions compiler/noirc_driver/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,12 @@ pub struct CompileOptions {
#[arg(long, value_parser = parse_expression_width)]
pub expression_width: Option<ExpressionWidth>,

/// Generate ACIR with the target backend expression width.
/// The default is to generate ACIR without a bound and split expressions after code generation.
/// Activating this flag can sometimes provide optimizations for certain programs.
#[arg(long, default_value = "false")]
pub bounded_codegen: bool,

/// Force a full recompilation.
#[arg(long = "force")]
pub force_compile: bool,
Expand Down Expand Up @@ -512,6 +518,12 @@ fn compile_contract_inner(
}
}

/// Default expression width used for Noir compilation.
/// The ACVM native type `ExpressionWidth` has its own default which should always be unbounded,
/// while we can sometimes expect the compilation target width to change.
/// Thus, we set it separately here rather than trying to alter the default derivation of the type.
pub const DEFAULT_EXPRESSION_WIDTH: ExpressionWidth = ExpressionWidth::Bounded { width: 4 };

/// Compile the current crate using `main_function` as the entrypoint.
///
/// This function assumes [`check_crate`] is called beforehand.
Expand Down Expand Up @@ -550,6 +562,11 @@ pub fn compile_no_check(
enable_brillig_logging: options.show_brillig,
force_brillig_output: options.force_brillig,
print_codegen_timings: options.benchmark_codegen,
expression_width: if options.bounded_codegen {
options.expression_width.unwrap_or(DEFAULT_EXPRESSION_WIDTH)
} else {
ExpressionWidth::default()
},
};

let SsaProgramArtifact { program, debug, warnings, names, error_types, .. } =
Expand Down
Loading

0 comments on commit 8d67f69

Please sign in to comment.