From 66d2a07f0fedb04422c218cbe8d6fb080efac994 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Wed, 25 Sep 2024 15:18:43 -0300 Subject: [PATCH 01/36] feat: visibility for type aliases (#6058) # Description ## Problem Part of #4515 ## Summary ## Additional Context ## Documentation Check one: - [ ] No documentation needed. - [x] Documentation included in this PR. - [ ] **[For Experimental Features]** Documentation to be submitted in a separate PR. # PR Checklist - [x] I have tested the changes locally. - [x] I have formatted the changes with [Prettier](https://prettier.io/) and/or `cargo fmt` on default settings. --------- Co-authored-by: Tom French <15848336+TomAFrench@users.noreply.github.com> --- compiler/noirc_frontend/src/ast/type_alias.rs | 6 +- compiler/noirc_frontend/src/elaborator/mod.rs | 93 +++++++++++++++++++ .../src/hir/def_collector/dc_mod.rs | 22 ++++- .../src/hir/def_map/module_data.rs | 3 +- .../src/hir/resolution/errors.rs | 9 ++ compiler/noirc_frontend/src/parser/parser.rs | 23 +++-- compiler/noirc_frontend/src/tests.rs | 4 +- .../noirc_frontend/src/tests/unused_items.rs | 74 +++++++++++++++ compiler/noirc_frontend/src/usage_tracker.rs | 4 +- docs/docs/noir/concepts/data_types/index.md | 8 ++ noir_stdlib/src/meta/mod.nr | 2 +- 11 files changed, 230 insertions(+), 18 deletions(-) diff --git a/compiler/noirc_frontend/src/ast/type_alias.rs b/compiler/noirc_frontend/src/ast/type_alias.rs index 3228765170e..b279d86f19e 100644 --- a/compiler/noirc_frontend/src/ast/type_alias.rs +++ b/compiler/noirc_frontend/src/ast/type_alias.rs @@ -1,4 +1,4 @@ -use super::{Ident, UnresolvedGenerics, UnresolvedType}; +use super::{Ident, ItemVisibility, UnresolvedGenerics, UnresolvedType}; use iter_extended::vecmap; use noirc_errors::Span; use std::fmt::Display; @@ -9,6 +9,7 @@ pub struct NoirTypeAlias { pub name: Ident, pub generics: UnresolvedGenerics, pub typ: UnresolvedType, + pub visibility: ItemVisibility, pub span: Span, } @@ -17,9 +18,10 @@ impl NoirTypeAlias { name: Ident, generics: UnresolvedGenerics, typ: UnresolvedType, + visibility: ItemVisibility, span: Span, ) -> NoirTypeAlias { - NoirTypeAlias { name, generics, typ, span } + NoirTypeAlias { name, generics, typ, visibility, span } } } diff --git a/compiler/noirc_frontend/src/elaborator/mod.rs b/compiler/noirc_frontend/src/elaborator/mod.rs index c36231bcf3b..9dd76885d61 100644 --- a/compiler/noirc_frontend/src/elaborator/mod.rs +++ b/compiler/noirc_frontend/src/elaborator/mod.rs @@ -1131,13 +1131,106 @@ impl<'context> Elaborator<'context> { self.file = alias.file_id; self.local_module = alias.module_id; + let name = &alias.type_alias_def.name; + let visibility = alias.type_alias_def.visibility; + let span = alias.type_alias_def.typ.span; + let generics = self.add_generics(&alias.type_alias_def.generics); self.current_item = Some(DependencyId::Alias(alias_id)); let typ = self.resolve_type(alias.type_alias_def.typ); + + if visibility != ItemVisibility::Private { + self.check_aliased_type_is_not_more_private(name, visibility, &typ, span); + } + self.interner.set_type_alias(alias_id, typ, generics); self.generics.clear(); } + fn check_aliased_type_is_not_more_private( + &mut self, + name: &Ident, + visibility: ItemVisibility, + typ: &Type, + span: Span, + ) { + match typ { + Type::Struct(struct_type, generics) => { + let struct_type = struct_type.borrow(); + let struct_module_id = struct_type.id.module_id(); + + // We only check this in types in the same crate. If it's in a different crate + // then it's either accessible (all good) or it's not, in which case a different + // error will happen somewhere else, but no need to error again here. + if struct_module_id.krate == self.crate_id { + // Go to the struct's parent module + let module_data = self.get_module(struct_module_id); + let parent_local_id = + module_data.parent.expect("Expected struct module parent to exist"); + let parent_module_id = + ModuleId { krate: struct_module_id.krate, local_id: parent_local_id }; + let parent_module_data = self.get_module(parent_module_id); + + // Find the struct in the parent module so we can know its visibility + let per_ns = parent_module_data.find_name(&struct_type.name); + if let Some((_, aliased_visibility, _)) = per_ns.types { + if aliased_visibility < visibility { + self.push_err(ResolverError::TypeIsMorePrivateThenItem { + typ: struct_type.name.to_string(), + item: name.to_string(), + span, + }); + } + } + } + + for generic in generics { + self.check_aliased_type_is_not_more_private(name, visibility, generic, span); + } + } + Type::Tuple(types) => { + for typ in types { + self.check_aliased_type_is_not_more_private(name, visibility, typ, span); + } + } + Type::Alias(alias_type, generics) => { + self.check_aliased_type_is_not_more_private( + name, + visibility, + &alias_type.borrow().get_type(generics), + span, + ); + } + Type::Function(args, return_type, env, _) => { + for arg in args { + self.check_aliased_type_is_not_more_private(name, visibility, arg, span); + } + self.check_aliased_type_is_not_more_private(name, visibility, return_type, span); + self.check_aliased_type_is_not_more_private(name, visibility, env, span); + } + Type::MutableReference(typ) | Type::Array(_, typ) | Type::Slice(typ) => { + self.check_aliased_type_is_not_more_private(name, visibility, typ, span); + } + Type::InfixExpr(left, _op, right) => { + self.check_aliased_type_is_not_more_private(name, visibility, left, span); + self.check_aliased_type_is_not_more_private(name, visibility, right, span); + } + Type::FieldElement + | Type::Integer(..) + | Type::Bool + | Type::String(..) + | Type::FmtString(..) + | Type::Unit + | Type::Quoted(..) + | Type::TypeVariable(..) + | Type::Forall(..) + | Type::TraitAsType(..) + | Type::Constant(..) + | Type::NamedGeneric(..) + | Type::Error => (), + } + } + fn collect_struct_definitions(&mut self, structs: &BTreeMap) { // This is necessary to avoid cloning the entire struct map // when adding checks after each struct field is resolved. diff --git a/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs b/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs index f50a0608fab..132b62f84b7 100644 --- a/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs +++ b/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs @@ -306,6 +306,7 @@ impl<'a> ModCollector<'a> { let doc_comments = type_alias.doc_comments; let type_alias = type_alias.item; let name = type_alias.name.clone(); + let visibility = type_alias.visibility; // And store the TypeId -> TypeAlias mapping somewhere it is reachable let unresolved = UnresolvedTypeAlias { @@ -327,8 +328,19 @@ impl<'a> ModCollector<'a> { context.def_interner.set_doc_comments(ReferenceId::Alias(type_alias_id), doc_comments); // Add the type alias to scope so its path can be looked up later - let result = self.def_collector.def_map.modules[self.module_id.0] - .declare_type_alias(name.clone(), type_alias_id); + let result = self.def_collector.def_map.modules[self.module_id.0].declare_type_alias( + name.clone(), + visibility, + type_alias_id, + ); + + let parent_module_id = ModuleId { krate, local_id: self.module_id }; + context.def_interner.usage_tracker.add_unused_item( + parent_module_id, + name.clone(), + UnusedItem::TypeAlias(type_alias_id), + visibility, + ); if let Err((first_def, second_def)) = result { let err = DefCollectorErrorKind::Duplicate { @@ -526,7 +538,11 @@ impl<'a> ModCollector<'a> { TraitItem::Type { name } => { if let Err((first_def, second_def)) = self.def_collector.def_map.modules [trait_id.0.local_id.0] - .declare_type_alias(name.clone(), TypeAliasId::dummy_id()) + .declare_type_alias( + name.clone(), + ItemVisibility::Public, + TypeAliasId::dummy_id(), + ) { let error = DefCollectorErrorKind::Duplicate { typ: DuplicateType::TraitAssociatedType, diff --git a/compiler/noirc_frontend/src/hir/def_map/module_data.rs b/compiler/noirc_frontend/src/hir/def_map/module_data.rs index 645d8650c7e..5bbff85079e 100644 --- a/compiler/noirc_frontend/src/hir/def_map/module_data.rs +++ b/compiler/noirc_frontend/src/hir/def_map/module_data.rs @@ -112,9 +112,10 @@ impl ModuleData { pub fn declare_type_alias( &mut self, name: Ident, + visibility: ItemVisibility, id: TypeAliasId, ) -> Result<(), (Ident, Ident)> { - self.declare(name, ItemVisibility::Public, id.into(), None) + self.declare(name, visibility, id.into(), None) } pub fn declare_trait( diff --git a/compiler/noirc_frontend/src/hir/resolution/errors.rs b/compiler/noirc_frontend/src/hir/resolution/errors.rs index 1e7f29527e2..cd3acbdc0f1 100644 --- a/compiler/noirc_frontend/src/hir/resolution/errors.rs +++ b/compiler/noirc_frontend/src/hir/resolution/errors.rs @@ -130,6 +130,8 @@ pub enum ResolverError { MutatingComptimeInNonComptimeContext { name: String, span: Span }, #[error("Failed to parse `{statement}` as an expression")] InvalidInternedStatementInExpr { statement: String, span: Span }, + #[error("Type `{typ}` is more private than item `{item}`")] + TypeIsMorePrivateThenItem { typ: String, item: String, span: Span }, } impl ResolverError { @@ -529,6 +531,13 @@ impl<'a> From<&'a ResolverError> for Diagnostic { *span, ) }, + ResolverError::TypeIsMorePrivateThenItem { typ, item, span } => { + Diagnostic::simple_warning( + format!("Type `{typ}` is more private than item `{item}`"), + String::new(), + *span, + ) + }, } } } diff --git a/compiler/noirc_frontend/src/parser/parser.rs b/compiler/noirc_frontend/src/parser/parser.rs index 0eb49ae975a..3f9a1d7a08b 100644 --- a/compiler/noirc_frontend/src/parser/parser.rs +++ b/compiler/noirc_frontend/src/parser/parser.rs @@ -290,14 +290,21 @@ fn contract( fn type_alias_definition() -> impl NoirParser { use self::Keyword::Type; - let p = ignore_then_commit(keyword(Type), ident()); - let p = then_commit(p, function::generics()); - let p = then_commit_ignore(p, just(Token::Assign)); - let p = then_commit(p, parse_type()); - - p.map_with_span(|((name, generics), typ), span| { - TopLevelStatementKind::TypeAlias(NoirTypeAlias { name, generics, typ, span }) - }) + item_visibility() + .then_ignore(keyword(Type)) + .then(ident()) + .then(function::generics()) + .then_ignore(just(Token::Assign)) + .then(parse_type()) + .map_with_span(|(((visibility, name), generics), typ), span| { + TopLevelStatementKind::TypeAlias(NoirTypeAlias { + name, + generics, + typ, + visibility, + span, + }) + }) } fn self_parameter() -> impl NoirParser { diff --git a/compiler/noirc_frontend/src/tests.rs b/compiler/noirc_frontend/src/tests.rs index cb291902ae2..c8e94d2c3e0 100644 --- a/compiler/noirc_frontend/src/tests.rs +++ b/compiler/noirc_frontend/src/tests.rs @@ -2659,8 +2659,8 @@ fn incorrect_generic_count_on_struct_impl() { #[test] fn incorrect_generic_count_on_type_alias() { let src = r#" - struct Foo {} - type Bar = Foo; + pub struct Foo {} + pub type Bar = Foo; fn main() {} "#; diff --git a/compiler/noirc_frontend/src/tests/unused_items.rs b/compiler/noirc_frontend/src/tests/unused_items.rs index da0a40d93cf..13787dd6955 100644 --- a/compiler/noirc_frontend/src/tests/unused_items.rs +++ b/compiler/noirc_frontend/src/tests/unused_items.rs @@ -171,3 +171,77 @@ fn silences_unused_variable_warning() { "#; assert_no_errors(src); } + +#[test] +fn errors_on_unused_type_alias() { + let src = r#" + type Foo = Field; + type Bar = Field; + pub fn bar(_: Bar) {} + fn main() {} + "#; + + let errors = get_program_errors(src); + assert_eq!(errors.len(), 1); + + let CompilationError::ResolverError(ResolverError::UnusedItem { ident, item_type }) = + &errors[0].0 + else { + panic!("Expected an unused item error"); + }; + + assert_eq!(ident.to_string(), "Foo"); + assert_eq!(*item_type, "type alias"); +} + +#[test] +fn errors_if_type_alias_aliases_more_private_type() { + let src = r#" + struct Foo {} + pub type Bar = Foo; + pub fn no_unused_warnings(_b: Bar) { + let _ = Foo {}; + } + fn main() {} + "#; + + let errors = get_program_errors(src); + assert_eq!(errors.len(), 1); + + let CompilationError::ResolverError(ResolverError::TypeIsMorePrivateThenItem { + typ, item, .. + }) = &errors[0].0 + else { + panic!("Expected an unused item error"); + }; + + assert_eq!(typ, "Foo"); + assert_eq!(item, "Bar"); +} + +#[test] +fn errors_if_type_alias_aliases_more_private_type_in_generic() { + let src = r#" + pub struct Generic { value: T } + struct Foo {} + pub type Bar = Generic; + pub fn no_unused_warnings(_b: Bar) { + let _ = Foo {}; + let _ = Generic { value: 1 }; + } + fn main() {} + "#; + + let errors = get_program_errors(src); + assert_eq!(errors.len(), 1); + + let CompilationError::ResolverError(ResolverError::TypeIsMorePrivateThenItem { + typ, item, .. + }) = &errors[0].0 + else { + panic!("Expected an unused item error"); + }; + + assert_eq!(typ, "Foo"); + assert_eq!(item, "Bar"); +} diff --git a/compiler/noirc_frontend/src/usage_tracker.rs b/compiler/noirc_frontend/src/usage_tracker.rs index b6f41dc72f2..23c97370b46 100644 --- a/compiler/noirc_frontend/src/usage_tracker.rs +++ b/compiler/noirc_frontend/src/usage_tracker.rs @@ -4,7 +4,7 @@ use crate::{ ast::{Ident, ItemVisibility}, hir::def_map::ModuleId, macros_api::StructId, - node_interner::{FuncId, TraitId}, + node_interner::{FuncId, TraitId, TypeAliasId}, }; #[derive(Debug)] @@ -13,6 +13,7 @@ pub enum UnusedItem { Function(FuncId), Struct(StructId), Trait(TraitId), + TypeAlias(TypeAliasId), } impl UnusedItem { @@ -22,6 +23,7 @@ impl UnusedItem { UnusedItem::Function(_) => "function", UnusedItem::Struct(_) => "struct", UnusedItem::Trait(_) => "trait", + UnusedItem::TypeAlias(_) => "type alias", } } } diff --git a/docs/docs/noir/concepts/data_types/index.md b/docs/docs/noir/concepts/data_types/index.md index 3eadb2dc8a4..11f51e2b65a 100644 --- a/docs/docs/noir/concepts/data_types/index.md +++ b/docs/docs/noir/concepts/data_types/index.md @@ -105,6 +105,14 @@ type Bad2 = Bad1; // ^^^^^^^^^^^ 'Bad2' recursively depends on itself: Bad2 -> Bad1 -> Bad2 ``` +By default, like functions, type aliases are private to the module the exist in. You can use `pub` +to make the type alias public or `pub(crate)` to make it public to just its crate: + +```rust +// This type alias is now public +pub type Id = u8; +``` + ## Wildcard Type Noir can usually infer the type of the variable from the context, so specifying the type of a variable is only required when it cannot be inferred. However, specifying a complex type can be tedious, especially when it has multiple generic arguments. Often some of the generic types can be inferred from the context, and Noir only needs a hint to properly infer the other types. We can partially specify a variable's type by using `_` as a marker, indicating where we still want the compiler to infer the type. diff --git a/noir_stdlib/src/meta/mod.nr b/noir_stdlib/src/meta/mod.nr index 1d787ebcdc1..21d80e76bfc 100644 --- a/noir_stdlib/src/meta/mod.nr +++ b/noir_stdlib/src/meta/mod.nr @@ -36,7 +36,7 @@ use crate::hash::poseidon2::Poseidon2Hasher; // A derive function is one that given a struct definition can // create us a quoted trait impl from it. -type DeriveFunction = fn(StructDefinition) -> Quoted; +pub type DeriveFunction = fn(StructDefinition) -> Quoted; // We'll keep a global HANDLERS map to keep track of the derive handler for each trait comptime mut global HANDLERS: UHashMap> = UHashMap::default(); From c679bc6bbd291b6264820dd497b37279116a1cd2 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Wed, 25 Sep 2024 17:05:14 -0300 Subject: [PATCH 02/36] fix: (LSP) make goto and hover work well for attributes (#6152) # Description ## Problem Hovering or using "Go to definition" on an attribute that was linked to a function that generated code wasn't working well (it would jump to one of the generated definitions instead of to the attribute function). ## Summary Before (note how the docs are for the generated code, not the attr function): ![lsp-hover-before](https://github.com/user-attachments/assets/2cb325c9-b73f-4f1c-a885-b842fd276c68) After: ![lsp-hover-after](https://github.com/user-attachments/assets/d75a849e-8410-4e71-9d71-324c19714964) ## Additional Context ## Documentation Check one: - [x] No documentation needed. - [ ] Documentation included in this PR. - [ ] **[For Experimental Features]** Documentation to be submitted in a separate PR. # PR Checklist - [x] I have tested the changes locally. - [x] I have formatted the changes with [Prettier](https://prettier.io/) and/or `cargo fmt` on default settings. --- Cargo.lock | 1 + compiler/noirc_frontend/src/node_interner.rs | 2 +- compiler/noirc_frontend/src/usage_tracker.rs | 6 +- .../src/main.nr | 7 ++ tooling/lsp/Cargo.toml | 1 + tooling/lsp/src/attribute_reference_finder.rs | 115 ++++++++++++++++++ tooling/lsp/src/lib.rs | 1 + tooling/lsp/src/requests/goto_definition.rs | 64 ++++++++-- tooling/lsp/src/requests/hover.rs | 61 ++++++++-- .../go_to_definition/src/main.nr | 7 ++ .../test_programs/workspace/two/src/lib.nr | 7 ++ 11 files changed, 241 insertions(+), 31 deletions(-) create mode 100644 tooling/lsp/src/attribute_reference_finder.rs diff --git a/Cargo.lock b/Cargo.lock index 6a469bd67f4..cdc1d75a421 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2753,6 +2753,7 @@ version = "0.34.0" dependencies = [ "acvm", "async-lsp", + "chumsky", "codespan-lsp", "convert_case 0.6.0", "fm", diff --git a/compiler/noirc_frontend/src/node_interner.rs b/compiler/noirc_frontend/src/node_interner.rs index 75178df319d..23ca3387f09 100644 --- a/compiler/noirc_frontend/src/node_interner.rs +++ b/compiler/noirc_frontend/src/node_interner.rs @@ -676,7 +676,7 @@ impl Default for NodeInterner { auto_import_names: HashMap::default(), comptime_scopes: vec![HashMap::default()], trait_impl_associated_types: HashMap::default(), - usage_tracker: UsageTracker::new(), + usage_tracker: UsageTracker::default(), doc_comments: HashMap::default(), } } diff --git a/compiler/noirc_frontend/src/usage_tracker.rs b/compiler/noirc_frontend/src/usage_tracker.rs index 23c97370b46..4224ba62df9 100644 --- a/compiler/noirc_frontend/src/usage_tracker.rs +++ b/compiler/noirc_frontend/src/usage_tracker.rs @@ -28,16 +28,12 @@ impl UnusedItem { } } -#[derive(Debug)] +#[derive(Debug, Default)] pub struct UsageTracker { unused_items: HashMap>, } impl UsageTracker { - pub(crate) fn new() -> Self { - Self { unused_items: HashMap::new() } - } - pub(crate) fn add_unused_item( &mut self, module_id: ModuleId, diff --git a/test_programs/compile_success_empty/field_or_integer_static_trait_method/src/main.nr b/test_programs/compile_success_empty/field_or_integer_static_trait_method/src/main.nr index f5a54c82ce5..64eb564cda1 100644 --- a/test_programs/compile_success_empty/field_or_integer_static_trait_method/src/main.nr +++ b/test_programs/compile_success_empty/field_or_integer_static_trait_method/src/main.nr @@ -23,3 +23,10 @@ fn main() { let value: Field = Field::read(data); assert_eq(value, 10); } + +#[attr] +pub fn foo() {} + +comptime fn attr(_: FunctionDefinition) -> Quoted { + quote { pub fn hello() {} } +} diff --git a/tooling/lsp/Cargo.toml b/tooling/lsp/Cargo.toml index c15895d801f..209f2afe4a4 100644 --- a/tooling/lsp/Cargo.toml +++ b/tooling/lsp/Cargo.toml @@ -14,6 +14,7 @@ workspace = true [dependencies] acvm.workspace = true +chumsky.workspace = true codespan-lsp.workspace = true lsp-types.workspace = true nargo.workspace = true diff --git a/tooling/lsp/src/attribute_reference_finder.rs b/tooling/lsp/src/attribute_reference_finder.rs new file mode 100644 index 00000000000..f08c8073a79 --- /dev/null +++ b/tooling/lsp/src/attribute_reference_finder.rs @@ -0,0 +1,115 @@ +/// If the cursor is on an custom attribute, this struct will try to resolve its +/// underlying function and return a ReferenceId to it. +/// This is needed in hover and go-to-definition because when an annotation generates +/// code, that code ends up residing in the attribute definition (it ends up having the +/// attribute's span) so using the usual graph to locate what points to that location +/// will give not only the attribute function but also any type generated by it. +use std::collections::BTreeMap; + +use chumsky::Parser; +use fm::FileId; +use noirc_errors::Span; +use noirc_frontend::{ + ast::{AttributeTarget, Visitor}, + graph::CrateId, + hir::{ + def_map::{CrateDefMap, LocalModuleId, ModuleId}, + resolution::path_resolver::{PathResolver, StandardPathResolver}, + }, + lexer::Lexer, + node_interner::ReferenceId, + parser::{path_no_turbofish, ParsedSubModule}, + token::CustomAttribute, + usage_tracker::UsageTracker, + ParsedModule, +}; + +use crate::modules::module_def_id_to_reference_id; + +pub(crate) struct AttributeReferenceFinder<'a> { + byte_index: usize, + /// The module ID in scope. This might change as we traverse the AST + /// if we are analyzing something inside an inline module declaration. + module_id: ModuleId, + def_maps: &'a BTreeMap, + reference_id: Option, +} + +impl<'a> AttributeReferenceFinder<'a> { + #[allow(clippy::too_many_arguments)] + pub(crate) fn new( + file: FileId, + byte_index: usize, + krate: CrateId, + def_maps: &'a BTreeMap, + ) -> Self { + // Find the module the current file belongs to + let def_map = &def_maps[&krate]; + let local_id = if let Some((module_index, _)) = + def_map.modules().iter().find(|(_, module_data)| module_data.location.file == file) + { + LocalModuleId(module_index) + } else { + def_map.root() + }; + let module_id = ModuleId { krate, local_id }; + Self { byte_index, module_id, def_maps, reference_id: None } + } + + pub(crate) fn find(&mut self, parsed_module: &ParsedModule) -> Option { + parsed_module.accept(self); + + self.reference_id + } + + fn includes_span(&self, span: Span) -> bool { + span.start() as usize <= self.byte_index && self.byte_index <= span.end() as usize + } +} + +impl<'a> Visitor for AttributeReferenceFinder<'a> { + fn visit_parsed_submodule(&mut self, parsed_sub_module: &ParsedSubModule, _span: Span) -> bool { + // Switch `self.module_id` to the submodule + let previous_module_id = self.module_id; + + let def_map = &self.def_maps[&self.module_id.krate]; + if let Some(module_data) = def_map.modules().get(self.module_id.local_id.0) { + if let Some(child_module) = module_data.children.get(&parsed_sub_module.name) { + self.module_id = ModuleId { krate: self.module_id.krate, local_id: *child_module }; + } + } + + parsed_sub_module.accept_children(self); + + // Restore the old module before continuing + self.module_id = previous_module_id; + + false + } + + fn visit_custom_attribute(&mut self, attribute: &CustomAttribute, _target: AttributeTarget) { + if !self.includes_span(attribute.contents_span) { + return; + } + + let name = match attribute.contents.split_once('(') { + Some((left, _right)) => left.to_string(), + None => attribute.contents.to_string(), + }; + let (tokens, _) = Lexer::lex(&name); + + let parser = path_no_turbofish(); + let Ok(path) = parser.parse(tokens) else { + return; + }; + + let resolver = StandardPathResolver::new(self.module_id); + let mut usage_tracker = UsageTracker::default(); + let Ok(result) = resolver.resolve(self.def_maps, path, &mut usage_tracker, &mut None) + else { + return; + }; + + self.reference_id = Some(module_def_id_to_reference_id(result.module_def_id)); + } +} diff --git a/tooling/lsp/src/lib.rs b/tooling/lsp/src/lib.rs index 39d4c3faa61..771f67e1fa2 100644 --- a/tooling/lsp/src/lib.rs +++ b/tooling/lsp/src/lib.rs @@ -62,6 +62,7 @@ use serde_json::Value as JsonValue; use thiserror::Error; use tower::Service; +mod attribute_reference_finder; mod modules; mod notifications; mod requests; diff --git a/tooling/lsp/src/requests/goto_definition.rs b/tooling/lsp/src/requests/goto_definition.rs index 6538e64dc90..a2443ea165d 100644 --- a/tooling/lsp/src/requests/goto_definition.rs +++ b/tooling/lsp/src/requests/goto_definition.rs @@ -1,8 +1,11 @@ use std::future::{self, Future}; +use crate::attribute_reference_finder::AttributeReferenceFinder; +use crate::utils; use crate::{types::GotoDefinitionResult, LspState}; use async_lsp::ResponseError; +use fm::PathString; use lsp_types::request::GotoTypeDefinitionParams; use lsp_types::{GotoDefinitionParams, GotoDefinitionResponse}; @@ -29,21 +32,42 @@ fn on_goto_definition_inner( params: GotoDefinitionParams, return_type_location_instead: bool, ) -> Result { + let uri = params.text_document_position_params.text_document.uri.clone(); + let position = params.text_document_position_params.position; process_request(state, params.text_document_position_params, |args| { - args.interner - .get_definition_location_from(args.location, return_type_location_instead) - .or_else(|| { - args.interner - .reference_at_location(args.location) - .map(|reference| args.interner.reference_location(reference)) - }) - .and_then(|found_location| { - let file_id = found_location.file; - let definition_position = - to_lsp_location(args.files, file_id, found_location.span)?; - let response = GotoDefinitionResponse::from(definition_position).to_owned(); - Some(response) + let path = PathString::from_path(uri.to_file_path().unwrap()); + let reference_id = args.files.get_file_id(&path).and_then(|file_id| { + utils::position_to_byte_index(args.files, file_id, &position).and_then(|byte_index| { + let file = args.files.get_file(file_id).unwrap(); + let source = file.source(); + let (parsed_module, _errors) = noirc_frontend::parse_program(source); + + let mut finder = AttributeReferenceFinder::new( + file_id, + byte_index, + args.crate_id, + args.def_maps, + ); + finder.find(&parsed_module) }) + }); + let location = if let Some(reference_id) = reference_id { + Some(args.interner.reference_location(reference_id)) + } else { + args.interner + .get_definition_location_from(args.location, return_type_location_instead) + .or_else(|| { + args.interner + .reference_at_location(args.location) + .map(|reference| args.interner.reference_location(reference)) + }) + }; + location.and_then(|found_location| { + let file_id = found_location.file; + let definition_position = to_lsp_location(args.files, file_id, found_location.span)?; + let response = GotoDefinitionResponse::from(definition_position).to_owned(); + Some(response) + }) }) } @@ -249,4 +273,18 @@ mod goto_definition_tests { ) .await; } + + #[test] + async fn goto_attribute_function() { + expect_goto( + "go_to_definition", + Position { line: 31, character: 3 }, // "attr" + "src/main.nr", + Range { + start: Position { line: 34, character: 12 }, + end: Position { line: 34, character: 16 }, + }, + ) + .await; + } } diff --git a/tooling/lsp/src/requests/hover.rs b/tooling/lsp/src/requests/hover.rs index 46d2a5cfc8f..2628c9b2ab6 100644 --- a/tooling/lsp/src/requests/hover.rs +++ b/tooling/lsp/src/requests/hover.rs @@ -1,7 +1,7 @@ use std::future::{self, Future}; use async_lsp::ResponseError; -use fm::FileMap; +use fm::{FileMap, PathString}; use lsp_types::{Hover, HoverContents, HoverParams, MarkupContent, MarkupKind}; use noirc_frontend::{ ast::Visibility, @@ -15,7 +15,10 @@ use noirc_frontend::{ Generics, Shared, StructType, Type, TypeAlias, TypeBinding, TypeVariable, }; -use crate::{modules::module_full_path, LspState}; +use crate::{ + attribute_reference_finder::AttributeReferenceFinder, modules::module_full_path, utils, + LspState, +}; use super::{process_request, to_lsp_location, ProcessRequestCallbackArgs}; @@ -23,18 +26,41 @@ pub(crate) fn on_hover_request( state: &mut LspState, params: HoverParams, ) -> impl Future, ResponseError>> { + let uri = params.text_document_position_params.text_document.uri.clone(); + let position = params.text_document_position_params.position; let result = process_request(state, params.text_document_position_params, |args| { - args.interner.reference_at_location(args.location).and_then(|reference| { - let location = args.interner.reference_location(reference); - let lsp_location = to_lsp_location(args.files, location.file, location.span); - format_reference(reference, &args).map(|formatted| Hover { - range: lsp_location.map(|location| location.range), - contents: HoverContents::Markup(MarkupContent { - kind: MarkupKind::Markdown, - value: formatted, - }), + let path = PathString::from_path(uri.to_file_path().unwrap()); + args.files + .get_file_id(&path) + .and_then(|file_id| { + utils::position_to_byte_index(args.files, file_id, &position).and_then( + |byte_index| { + let file = args.files.get_file(file_id).unwrap(); + let source = file.source(); + let (parsed_module, _errors) = noirc_frontend::parse_program(source); + + let mut finder = AttributeReferenceFinder::new( + file_id, + byte_index, + args.crate_id, + args.def_maps, + ); + finder.find(&parsed_module) + }, + ) + }) + .or_else(|| args.interner.reference_at_location(args.location)) + .and_then(|reference| { + let location = args.interner.reference_location(reference); + let lsp_location = to_lsp_location(args.files, location.file, location.span); + format_reference(reference, &args).map(|formatted| Hover { + range: lsp_location.map(|location| location.range), + contents: HoverContents::Markup(MarkupContent { + kind: MarkupKind::Markdown, + value: formatted, + }), + }) }) - }) }); future::ready(result) @@ -918,4 +944,15 @@ mod hover_tests { ) .await; } + + #[test] + async fn hover_on_attribute_function() { + assert_hover( + "workspace", + "two/src/lib.nr", + Position { line: 54, character: 2 }, + " two\n fn attr(_: FunctionDefinition) -> Quoted", + ) + .await; + } } diff --git a/tooling/lsp/test_programs/go_to_definition/src/main.nr b/tooling/lsp/test_programs/go_to_definition/src/main.nr index 4550324c182..cb04fe04eaa 100644 --- a/tooling/lsp/test_programs/go_to_definition/src/main.nr +++ b/tooling/lsp/test_programs/go_to_definition/src/main.nr @@ -28,3 +28,10 @@ trait Trait { } use dependency::something; + +#[attr] +pub fn foo() {} + +comptime fn attr(_: FunctionDefinition) -> Quoted { + quote { pub fn hello() {} } +} \ No newline at end of file diff --git a/tooling/lsp/test_programs/workspace/two/src/lib.nr b/tooling/lsp/test_programs/workspace/two/src/lib.nr index 3f0f0f117b7..c6b516d88ab 100644 --- a/tooling/lsp/test_programs/workspace/two/src/lib.nr +++ b/tooling/lsp/test_programs/workspace/two/src/lib.nr @@ -51,3 +51,10 @@ use std::collections::bounded_vec::BoundedVec; fn instantiate_generic() { let x: BoundedVec = BoundedVec::new(); } + +#[attr] +pub fn foo() {} + +comptime fn attr(_: FunctionDefinition) -> Quoted { + quote { pub fn hello() {} } +} \ No newline at end of file From bcb438b0816fbe08344535545612d32b4730af79 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Wed, 25 Sep 2024 17:34:52 -0300 Subject: [PATCH 03/36] feat: detect unconstructed structs (#6061) # Description ## Problem Fixes #6055 ## Summary This is similar to how it works in Rust. ## Additional Context ## Documentation Check one: - [ ] No documentation needed. - [x] Documentation included in this PR. - [ ] **[For Experimental Features]** Documentation to be submitted in a separate PR. # PR Checklist - [x] I have tested the changes locally. - [x] I have formatted the changes with [Prettier](https://prettier.io/) and/or `cargo fmt` on default settings. --- .../src/elaborator/expressions.rs | 8 ++ compiler/noirc_frontend/src/elaborator/mod.rs | 67 +++++++-- .../src/hir/comptime/interpreter/builtin.rs | 10 +- .../src/hir/def_collector/dc_crate.rs | 2 +- .../noirc_frontend/src/hir/def_map/mod.rs | 8 +- .../src/hir/resolution/errors.rs | 31 ++-- .../src/hir/resolution/import.rs | 4 +- compiler/noirc_frontend/src/node_interner.rs | 6 + compiler/noirc_frontend/src/tests.rs | 132 ++++++++++++++++-- .../noirc_frontend/src/tests/unused_items.rs | 30 ++-- compiler/noirc_frontend/src/usage_tracker.rs | 25 +++- noir_stdlib/src/meta/mod.nr | 10 +- tooling/lsp/src/modules.rs | 9 -- .../requests/code_action/import_or_qualify.rs | 4 +- .../src/requests/completion/auto_import.rs | 4 +- .../src/trait_impl_method_stub_generator.rs | 7 +- 16 files changed, 272 insertions(+), 85 deletions(-) diff --git a/compiler/noirc_frontend/src/elaborator/expressions.rs b/compiler/noirc_frontend/src/elaborator/expressions.rs index 9a74e77215a..46a22bb232f 100644 --- a/compiler/noirc_frontend/src/elaborator/expressions.rs +++ b/compiler/noirc_frontend/src/elaborator/expressions.rs @@ -535,6 +535,8 @@ impl<'context> Elaborator<'context> { } }; + self.mark_struct_as_constructed(r#type.clone()); + let turbofish_span = last_segment.turbofish_span(); let struct_generics = self.resolve_struct_turbofish_generics( @@ -564,6 +566,12 @@ impl<'context> Elaborator<'context> { (expr, Type::Struct(struct_type, generics)) } + pub(super) fn mark_struct_as_constructed(&mut self, struct_type: Shared) { + let struct_type = struct_type.borrow(); + let parent_module_id = struct_type.id.parent_module_id(self.def_maps); + self.interner.usage_tracker.mark_as_used(parent_module_id, &struct_type.name); + } + /// Resolve all the fields of a struct constructor expression. /// Ensures all fields are present, none are repeated, and all /// are part of the struct. diff --git a/compiler/noirc_frontend/src/elaborator/mod.rs b/compiler/noirc_frontend/src/elaborator/mod.rs index 9dd76885d61..c5e457c405b 100644 --- a/compiler/noirc_frontend/src/elaborator/mod.rs +++ b/compiler/noirc_frontend/src/elaborator/mod.rs @@ -95,7 +95,7 @@ pub struct Elaborator<'context> { pub(crate) interner: &'context mut NodeInterner, - def_maps: &'context mut DefMaps, + pub(crate) def_maps: &'context mut DefMaps, pub(crate) file: FileId, @@ -759,6 +759,10 @@ impl<'context> Elaborator<'context> { type_span, ); + if is_entry_point { + self.mark_parameter_type_as_used(&typ); + } + let pattern = self.elaborate_pattern_and_store_ids( pattern, typ.clone(), @@ -837,6 +841,57 @@ impl<'context> Elaborator<'context> { self.current_item = None; } + fn mark_parameter_type_as_used(&mut self, typ: &Type) { + match typ { + Type::Array(_n, typ) => self.mark_parameter_type_as_used(typ), + Type::Slice(typ) => self.mark_parameter_type_as_used(typ), + Type::Tuple(types) => { + for typ in types { + self.mark_parameter_type_as_used(typ); + } + } + Type::Struct(struct_type, generics) => { + self.mark_struct_as_constructed(struct_type.clone()); + for generic in generics { + self.mark_parameter_type_as_used(generic); + } + } + Type::Alias(alias_type, generics) => { + self.mark_parameter_type_as_used(&alias_type.borrow().get_type(generics)); + } + Type::MutableReference(typ) => { + self.mark_parameter_type_as_used(typ); + } + Type::InfixExpr(left, _op, right) => { + self.mark_parameter_type_as_used(left); + self.mark_parameter_type_as_used(right); + } + Type::FieldElement + | Type::Integer(..) + | Type::Bool + | Type::String(_) + | Type::FmtString(_, _) + | Type::Unit + | Type::Quoted(..) + | Type::Constant(..) + | Type::TraitAsType(..) + | Type::TypeVariable(..) + | Type::NamedGeneric(..) + | Type::Function(..) + | Type::Forall(..) + | Type::Error => (), + } + + if let Type::Alias(alias_type, generics) = typ { + self.mark_parameter_type_as_used(&alias_type.borrow().get_type(generics)); + return; + } + + if let Type::Struct(struct_type, _generics) = typ { + self.mark_struct_as_constructed(struct_type.clone()); + } + } + fn run_function_lints(&mut self, func: &FuncMeta, modifiers: &FunctionModifiers) { self.run_lint(|_| lints::inlining_attributes(func, modifiers).map(Into::into)); self.run_lint(|_| lints::missing_pub(func, modifiers).map(Into::into)); @@ -1163,15 +1218,9 @@ impl<'context> Elaborator<'context> { // then it's either accessible (all good) or it's not, in which case a different // error will happen somewhere else, but no need to error again here. if struct_module_id.krate == self.crate_id { - // Go to the struct's parent module - let module_data = self.get_module(struct_module_id); - let parent_local_id = - module_data.parent.expect("Expected struct module parent to exist"); - let parent_module_id = - ModuleId { krate: struct_module_id.krate, local_id: parent_local_id }; - let parent_module_data = self.get_module(parent_module_id); - // Find the struct in the parent module so we can know its visibility + let parent_module_id = struct_type.id.parent_module_id(self.def_maps); + let parent_module_data = self.get_module(parent_module_id); let per_ns = parent_module_data.find_name(&struct_type.name); if let Some((_, aliased_visibility, _)) = per_ns.types { if aliased_visibility < visibility { diff --git a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs index c21ca353e07..59fc186173a 100644 --- a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs +++ b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs @@ -30,7 +30,6 @@ use crate::{ InterpreterError, Value, }, def_collector::dc_crate::CollectedItems, - def_map::ModuleId, }, hir_def::function::FunctionBody, macros_api::{HirExpression, HirLiteral, Ident, ModuleDefId, NodeInterner, Signedness}, @@ -505,14 +504,7 @@ fn struct_def_module( ) -> IResult { let self_argument = check_one_argument(arguments, location)?; let struct_id = get_struct(self_argument)?; - let struct_module_id = struct_id.module_id(); - - // A struct's module is its own module. To get the module where its defined we need - // to look for its parent. - let module_data = interpreter.elaborator.get_module(struct_module_id); - let parent_local_id = module_data.parent.expect("Expected struct module parent to exist"); - let parent = ModuleId { krate: struct_module_id.krate, local_id: parent_local_id }; - + let parent = struct_id.parent_module_id(interpreter.elaborator.def_maps); Ok(Value::ModuleDefinition(parent)) } diff --git a/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs b/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs index d365e5807c2..faf72e86fb4 100644 --- a/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs +++ b/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs @@ -498,7 +498,7 @@ impl DefCollector { let ident = ident.clone(); let error = CompilationError::ResolverError(ResolverError::UnusedItem { ident, - item_type: unused_item.item_type(), + item: *unused_item, }); (error, module.location.file) }) diff --git a/compiler/noirc_frontend/src/hir/def_map/mod.rs b/compiler/noirc_frontend/src/hir/def_map/mod.rs index d810e95218c..9afe897a167 100644 --- a/compiler/noirc_frontend/src/hir/def_map/mod.rs +++ b/compiler/noirc_frontend/src/hir/def_map/mod.rs @@ -42,12 +42,16 @@ impl ModuleId { pub fn dummy_id() -> ModuleId { ModuleId { krate: CrateId::dummy_id(), local_id: LocalModuleId::dummy_id() } } -} -impl ModuleId { pub fn module(self, def_maps: &DefMaps) -> &ModuleData { &def_maps[&self.krate].modules()[self.local_id.0] } + + /// Returns this module's parent, if there's any. + pub fn parent(self, def_maps: &DefMaps) -> Option { + let module_data = &def_maps[&self.krate].modules()[self.local_id.0]; + module_data.parent.map(|local_id| ModuleId { krate: self.krate, local_id }) + } } pub type DefMaps = BTreeMap; diff --git a/compiler/noirc_frontend/src/hir/resolution/errors.rs b/compiler/noirc_frontend/src/hir/resolution/errors.rs index cd3acbdc0f1..f3c61a7fbe2 100644 --- a/compiler/noirc_frontend/src/hir/resolution/errors.rs +++ b/compiler/noirc_frontend/src/hir/resolution/errors.rs @@ -2,7 +2,10 @@ pub use noirc_errors::Span; use noirc_errors::{CustomDiagnostic as Diagnostic, FileDiagnostic}; use thiserror::Error; -use crate::{ast::Ident, hir::comptime::InterpreterError, parser::ParserError, Type}; +use crate::{ + ast::Ident, hir::comptime::InterpreterError, parser::ParserError, usage_tracker::UnusedItem, + Type, +}; use super::import::PathResolutionError; @@ -20,8 +23,8 @@ pub enum ResolverError { DuplicateDefinition { name: String, first_span: Span, second_span: Span }, #[error("Unused variable")] UnusedVariable { ident: Ident }, - #[error("Unused {item_type}")] - UnusedItem { ident: Ident, item_type: &'static str }, + #[error("Unused {}", item.item_type())] + UnusedItem { ident: Ident, item: UnusedItem }, #[error("Could not find variable in this scope")] VariableNotDeclared { name: String, span: Span }, #[error("path is not an identifier")] @@ -166,14 +169,24 @@ impl<'a> From<&'a ResolverError> for Diagnostic { diagnostic.unnecessary = true; diagnostic } - ResolverError::UnusedItem { ident, item_type } => { + ResolverError::UnusedItem { ident, item} => { let name = &ident.0.contents; + let item_type = item.item_type(); - let mut diagnostic = Diagnostic::simple_warning( - format!("unused {item_type} {name}"), - format!("unused {item_type}"), - ident.span(), - ); + let mut diagnostic = + if let UnusedItem::Struct(..) = item { + Diagnostic::simple_warning( + format!("{item_type} `{name}` is never constructed"), + format!("{item_type} is never constructed"), + ident.span(), + ) + } else { + Diagnostic::simple_warning( + format!("unused {item_type} {name}"), + format!("unused {item_type}"), + ident.span(), + ) + }; diagnostic.unnecessary = true; diagnostic } diff --git a/compiler/noirc_frontend/src/hir/resolution/import.rs b/compiler/noirc_frontend/src/hir/resolution/import.rs index 938da0a879f..73a1b1ccb7c 100644 --- a/compiler/noirc_frontend/src/hir/resolution/import.rs +++ b/compiler/noirc_frontend/src/hir/resolution/import.rs @@ -289,7 +289,7 @@ fn resolve_name_in_module( return Err(PathResolutionError::Unresolved(first_segment.clone())); } - usage_tracker.mark_as_used(current_mod_id, first_segment); + usage_tracker.mark_as_referenced(current_mod_id, first_segment); let mut warning: Option = None; for (index, (last_segment, current_segment)) in @@ -356,7 +356,7 @@ fn resolve_name_in_module( return Err(PathResolutionError::Unresolved(current_segment.clone())); } - usage_tracker.mark_as_used(current_mod_id, current_segment); + usage_tracker.mark_as_referenced(current_mod_id, current_segment); current_ns = found_ns; } diff --git a/compiler/noirc_frontend/src/node_interner.rs b/compiler/noirc_frontend/src/node_interner.rs index 23ca3387f09..a95282a1ec9 100644 --- a/compiler/noirc_frontend/src/node_interner.rs +++ b/compiler/noirc_frontend/src/node_interner.rs @@ -23,6 +23,7 @@ use crate::graph::CrateId; use crate::hir::comptime; use crate::hir::def_collector::dc_crate::CompilationError; use crate::hir::def_collector::dc_crate::{UnresolvedStruct, UnresolvedTrait, UnresolvedTypeAlias}; +use crate::hir::def_map::DefMaps; use crate::hir::def_map::{LocalModuleId, ModuleId}; use crate::hir::type_check::generics::TraitGenerics; use crate::hir_def::traits::NamedType; @@ -489,6 +490,11 @@ impl StructId { pub fn local_module_id(self) -> LocalModuleId { self.0.local_id } + + /// Returns the module where this struct is defined. + pub fn parent_module_id(self, def_maps: &DefMaps) -> ModuleId { + self.module_id().parent(def_maps).expect("Expected struct module parent to exist") + } } #[derive(Debug, Eq, PartialEq, Hash, Copy, Clone, PartialOrd, Ord)] diff --git a/compiler/noirc_frontend/src/tests.rs b/compiler/noirc_frontend/src/tests.rs index c8e94d2c3e0..4c91c90bc31 100644 --- a/compiler/noirc_frontend/src/tests.rs +++ b/compiler/noirc_frontend/src/tests.rs @@ -195,8 +195,10 @@ fn check_trait_implementation_duplicate_method() { x + 2 * y } } - - fn main() {}"; + + fn main() { + let _ = Foo { bar: 1, array: [2, 3] }; // silence Foo never constructed warning + }"; let errors = get_program_errors(src); assert!(!has_parser_error(&errors)); @@ -237,6 +239,7 @@ fn check_trait_wrong_method_return_type() { } fn main() { + let _ = Foo {}; // silence Foo never constructed warning } "; let errors = get_program_errors(src); @@ -279,6 +282,7 @@ fn check_trait_wrong_method_return_type2() { } fn main() { + let _ = Foo { bar: 1, array: [2, 3] }; // silence Foo never constructed warning }"; let errors = get_program_errors(src); assert!(!has_parser_error(&errors)); @@ -401,6 +405,7 @@ fn check_trait_wrong_method_name() { } fn main() { + let _ = Foo { bar: 1, array: [2, 3] }; // silence Foo never constructed warning }"; let compilation_errors = get_program_errors(src); assert!(!has_parser_error(&compilation_errors)); @@ -639,8 +644,10 @@ fn check_impl_struct_not_trait() { Self { bar: x, array: [x,y] } } } - - fn main() {} + + fn main() { + let _ = Default { x: 1, z: 1 }; // silence Default never constructed warning + } "; let errors = get_program_errors(src); assert!(!has_parser_error(&errors)); @@ -719,6 +726,7 @@ fn check_trait_duplicate_implementation() { impl Default for Foo { } fn main() { + let _ = Foo { bar: 1 }; // silence Foo never constructed warning } "; let errors = get_program_errors(src); @@ -757,6 +765,7 @@ fn check_trait_duplicate_implementation_with_alias() { } fn main() { + let _ = MyStruct {}; // silence MyStruct never constructed warning } "; let errors = get_program_errors(src); @@ -1549,7 +1558,9 @@ fn struct_numeric_generic_in_function() { inner: u64 } - pub fn bar() { } + pub fn bar() { + let _ = Foo { inner: 1 }; // silence Foo never constructed warning + } "#; let errors = get_program_errors(src); assert_eq!(errors.len(), 1); @@ -1724,7 +1735,7 @@ fn numeric_generic_used_in_nested_type_fails() { a: Field, b: Bar, } - struct Bar { + pub struct Bar { inner: N } "#; @@ -1879,6 +1890,10 @@ fn constant_used_with_numeric_generic() { [self.value] } } + + fn main() { + let _ = ValueNote { value: 1 }; // silence ValueNote never constructed warning + } "#; assert_no_errors(src); } @@ -1985,6 +2000,10 @@ fn numeric_generics_value_kind_mismatch_u32_u64() { self.len += 1; } } + + fn main() { + let _ = BoundedVec { storage: [1], len: 1 }; // silence never constructed warning + } "#; let errors = get_program_errors(src); assert_eq!(errors.len(), 1); @@ -2060,6 +2079,10 @@ fn impl_stricter_than_trait_no_trait_method_constraints() { process_array(serialize_thing(self)) } } + + fn main() { + let _ = MyType { a: 1, b: 1 }; // silence MyType never constructed warning + } "#; let errors = get_program_errors(src); @@ -2150,6 +2173,10 @@ fn impl_stricter_than_trait_different_object_generics() { fn tuple_bad() where (Option, Option): MyTrait { } } + + fn main() { + let _ = OtherOption { inner: Option { inner: 1 } }; // silence unused warnings + } "#; let errors = get_program_errors(src); @@ -2211,6 +2238,10 @@ fn impl_stricter_than_trait_different_trait() { // types are the same. fn bar() where Option: OtherDefault {} } + + fn main() { + let _ = Option { inner: 1 }; // silence Option never constructed warning + } "#; let errors = get_program_errors(src); @@ -2248,6 +2279,10 @@ fn trait_impl_where_clause_stricter_pass() { fn bad_foo() where A: OtherTrait { } } + + fn main() { + let _ = Option { inner: 1 }; // silence Option never constructed warning + } "#; let errors = get_program_errors(src); @@ -2333,6 +2368,10 @@ fn impl_not_found_for_inner_impl() { process_array(serialize_thing(self)) } } + + fn main() { + let _ = MyType { a: 1, b: 1 }; // silence MyType never constructed warning + } "#; let errors = get_program_errors(src); @@ -2639,7 +2678,9 @@ fn incorrect_generic_count_on_struct_impl() { let src = r#" struct Foo {} impl Foo {} - fn main() {} + fn main() { + let _ = Foo {}; // silence Foo never constructed warning + } "#; let errors = get_program_errors(src); @@ -2661,7 +2702,9 @@ fn incorrect_generic_count_on_type_alias() { let src = r#" pub struct Foo {} pub type Bar = Foo; - fn main() {} + fn main() { + let _ = Foo {}; // silence Foo never constructed warning + } "#; let errors = get_program_errors(src); @@ -2693,7 +2736,9 @@ fn uses_self_type_for_struct_function_call() { } } - fn main() {} + fn main() { + let _ = S {}; // silence S never constructed warning + } "#; assert_no_errors(src); } @@ -2743,7 +2788,9 @@ fn uses_self_type_in_trait_where_clause() { } - fn main() {} + fn main() { + let _ = Bar {}; // silence Bar never constructed warning + } "#; let errors = get_program_errors(src); @@ -2903,6 +2950,8 @@ fn as_trait_path_syntax_resolves_outside_impl() { // AsTraitPath syntax is a bit silly when associated types // are explicitly specified let _: i64 = 1 as >::Assoc; + + let _ = Bar {}; // silence Bar never constructed warning } "#; @@ -2934,6 +2983,8 @@ fn as_trait_path_syntax_no_impl() { fn main() { let _: i64 = 1 as >::Assoc; + + let _ = Bar {}; // silence Bar never constructed warning } "#; @@ -3047,7 +3098,9 @@ fn errors_once_on_unused_import_that_is_not_accessible() { struct Foo {} } use moo::Foo; - fn main() {} + fn main() { + let _ = Foo {}; + } "#; let errors = get_program_errors(src); @@ -3088,3 +3141,60 @@ fn trait_unconstrained_methods_typechecked_correctly() { println!("{errors:?}"); assert_eq!(errors.len(), 0); } + +#[test] +fn errors_if_type_alias_aliases_more_private_type() { + let src = r#" + struct Foo {} + pub type Bar = Foo; + + pub fn no_unused_warnings(_b: Bar) { + let _ = Foo {}; + } + + fn main() {} + "#; + + let errors = get_program_errors(src); + assert_eq!(errors.len(), 1); + + let CompilationError::ResolverError(ResolverError::TypeIsMorePrivateThenItem { + typ, item, .. + }) = &errors[0].0 + else { + panic!("Expected an unused item error"); + }; + + assert_eq!(typ, "Foo"); + assert_eq!(item, "Bar"); +} + +#[test] +fn errors_if_type_alias_aliases_more_private_type_in_generic() { + let src = r#" + pub struct Generic { value: T } + + struct Foo {} + pub type Bar = Generic; + + pub fn no_unused_warnings(_b: Bar) { + let _ = Foo {}; + let _ = Generic { value: 1 }; + } + + fn main() {} + "#; + + let errors = get_program_errors(src); + assert_eq!(errors.len(), 1); + + let CompilationError::ResolverError(ResolverError::TypeIsMorePrivateThenItem { + typ, item, .. + }) = &errors[0].0 + else { + panic!("Expected an unused item error"); + }; + + assert_eq!(typ, "Foo"); + assert_eq!(item, "Bar"); +} diff --git a/compiler/noirc_frontend/src/tests/unused_items.rs b/compiler/noirc_frontend/src/tests/unused_items.rs index 13787dd6955..be3b2996e5a 100644 --- a/compiler/noirc_frontend/src/tests/unused_items.rs +++ b/compiler/noirc_frontend/src/tests/unused_items.rs @@ -31,14 +31,13 @@ fn errors_on_unused_private_import() { let errors = get_program_errors(src); assert_eq!(errors.len(), 1); - let CompilationError::ResolverError(ResolverError::UnusedItem { ident, item_type }) = - &errors[0].0 + let CompilationError::ResolverError(ResolverError::UnusedItem { ident, item }) = &errors[0].0 else { panic!("Expected an unused item error"); }; assert_eq!(ident.to_string(), "bar"); - assert_eq!(*item_type, "import"); + assert_eq!(item.item_type(), "import"); } #[test] @@ -67,14 +66,13 @@ fn errors_on_unused_pub_crate_import() { let errors = get_program_errors(src); assert_eq!(errors.len(), 1); - let CompilationError::ResolverError(ResolverError::UnusedItem { ident, item_type }) = - &errors[0].0 + let CompilationError::ResolverError(ResolverError::UnusedItem { ident, item }) = &errors[0].0 else { panic!("Expected an unused item error"); }; assert_eq!(ident.to_string(), "bar"); - assert_eq!(*item_type, "import"); + assert_eq!(item.item_type(), "import"); } #[test] @@ -99,14 +97,13 @@ fn errors_on_unused_function() { let errors = get_program_errors(src); assert_eq!(errors.len(), 1); - let CompilationError::ResolverError(ResolverError::UnusedItem { ident, item_type }) = - &errors[0].0 + let CompilationError::ResolverError(ResolverError::UnusedItem { ident, item }) = &errors[0].0 else { panic!("Expected an unused item error"); }; assert_eq!(ident.to_string(), "foo"); - assert_eq!(*item_type, "function"); + assert_eq!(item.item_type(), "function"); } #[test] @@ -123,14 +120,13 @@ fn errors_on_unused_struct() { let errors = get_program_errors(src); assert_eq!(errors.len(), 1); - let CompilationError::ResolverError(ResolverError::UnusedItem { ident, item_type }) = - &errors[0].0 + let CompilationError::ResolverError(ResolverError::UnusedItem { ident, item }) = &errors[0].0 else { panic!("Expected an unused item error"); }; assert_eq!(ident.to_string(), "Foo"); - assert_eq!(*item_type, "struct"); + assert_eq!(item.item_type(), "struct"); } #[test] @@ -151,14 +147,13 @@ fn errors_on_unused_trait() { let errors = get_program_errors(src); assert_eq!(errors.len(), 1); - let CompilationError::ResolverError(ResolverError::UnusedItem { ident, item_type }) = - &errors[0].0 + let CompilationError::ResolverError(ResolverError::UnusedItem { ident, item }) = &errors[0].0 else { panic!("Expected an unused item error"); }; assert_eq!(ident.to_string(), "Foo"); - assert_eq!(*item_type, "trait"); + assert_eq!(item.item_type(), "trait"); } #[test] @@ -184,14 +179,13 @@ fn errors_on_unused_type_alias() { let errors = get_program_errors(src); assert_eq!(errors.len(), 1); - let CompilationError::ResolverError(ResolverError::UnusedItem { ident, item_type }) = - &errors[0].0 + let CompilationError::ResolverError(ResolverError::UnusedItem { ident, item }) = &errors[0].0 else { panic!("Expected an unused item error"); }; assert_eq!(ident.to_string(), "Foo"); - assert_eq!(*item_type, "type alias"); + assert_eq!(item.item_type(), "type alias"); } #[test] diff --git a/compiler/noirc_frontend/src/usage_tracker.rs b/compiler/noirc_frontend/src/usage_tracker.rs index 4224ba62df9..cd1041cc36f 100644 --- a/compiler/noirc_frontend/src/usage_tracker.rs +++ b/compiler/noirc_frontend/src/usage_tracker.rs @@ -7,7 +7,7 @@ use crate::{ node_interner::{FuncId, TraitId, TypeAliasId}, }; -#[derive(Debug)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum UnusedItem { Import, Function(FuncId), @@ -47,8 +47,29 @@ impl UsageTracker { } } + /// Marks an item as being referenced. This doesn't always makes the item as used. For example + /// if a struct is referenced it will still be considered unused unless it's constructed somewhere. + pub(crate) fn mark_as_referenced(&mut self, current_mod_id: ModuleId, name: &Ident) { + let Some(items) = self.unused_items.get_mut(¤t_mod_id) else { + return; + }; + + let Some(unused_item) = items.get(name) else { + return; + }; + + if let UnusedItem::Struct(_) = unused_item { + return; + } + + items.remove(name); + } + + /// Marks an item as being used. pub(crate) fn mark_as_used(&mut self, current_mod_id: ModuleId, name: &Ident) { - self.unused_items.entry(current_mod_id).or_default().remove(name); + if let Some(items) = self.unused_items.get_mut(¤t_mod_id) { + items.remove(name); + }; } pub(crate) fn unused_items(&self) -> &HashMap> { diff --git a/noir_stdlib/src/meta/mod.nr b/noir_stdlib/src/meta/mod.nr index 21d80e76bfc..0dc2e5fa714 100644 --- a/noir_stdlib/src/meta/mod.nr +++ b/noir_stdlib/src/meta/mod.nr @@ -243,11 +243,15 @@ mod tests { } // docs:end:big-derive-usage-example + impl DoNothing for Bar { + fn do_nothing(_: Self) {} + } + // This function is just to remove unused warnings fn remove_unused_warnings() { - let _: Bar = crate::panic::panic(f""); - let _: MyStruct = crate::panic::panic(f""); - let _: MyOtherStruct = crate::panic::panic(f""); + let _: Bar = Bar { x: 1, y: [2, 3] }; + let _: MyStruct = MyStruct { my_field: 1 }; + let _: MyOtherStruct = MyOtherStruct { my_other_field: 2 }; let _ = derive_do_nothing(crate::panic::panic(f"")); let _ = derive_do_nothing_alt(crate::panic::panic(f"")); remove_unused_warnings(); diff --git a/tooling/lsp/src/modules.rs b/tooling/lsp/src/modules.rs index c8f7430c997..f1eff3b5a7d 100644 --- a/tooling/lsp/src/modules.rs +++ b/tooling/lsp/src/modules.rs @@ -18,15 +18,6 @@ pub(crate) fn get_parent_module( interner.reference_module(reference_id).copied() } -pub(crate) fn get_parent_module_id( - def_maps: &BTreeMap, - module_id: ModuleId, -) -> Option { - let crate_def_map = &def_maps[&module_id.krate]; - let module_data = &crate_def_map.modules()[module_id.local_id.0]; - module_data.parent.map(|parent| ModuleId { krate: module_id.krate, local_id: parent }) -} - pub(crate) fn module_def_id_to_reference_id(module_def_id: ModuleDefId) -> ReferenceId { match module_def_id { ModuleDefId::ModuleId(id) => ReferenceId::Module(id), diff --git a/tooling/lsp/src/requests/code_action/import_or_qualify.rs b/tooling/lsp/src/requests/code_action/import_or_qualify.rs index 03a953bde85..0d97ccde2ed 100644 --- a/tooling/lsp/src/requests/code_action/import_or_qualify.rs +++ b/tooling/lsp/src/requests/code_action/import_or_qualify.rs @@ -7,7 +7,7 @@ use noirc_frontend::{ use crate::{ byte_span_to_range, - modules::{get_parent_module_id, relative_module_full_path, relative_module_id_path}, + modules::{relative_module_full_path, relative_module_id_path}, }; use super::CodeActionFinder; @@ -28,7 +28,7 @@ impl<'a> CodeActionFinder<'a> { return; } - let current_module_parent_id = get_parent_module_id(self.def_maps, self.module_id); + let current_module_parent_id = self.module_id.parent(self.def_maps); // The Path doesn't resolve to anything so it means it's an error and maybe we // can suggest an import or to fully-qualify the path. diff --git a/tooling/lsp/src/requests/completion/auto_import.rs b/tooling/lsp/src/requests/completion/auto_import.rs index 2713ae252bf..20b126a248d 100644 --- a/tooling/lsp/src/requests/completion/auto_import.rs +++ b/tooling/lsp/src/requests/completion/auto_import.rs @@ -1,7 +1,7 @@ use lsp_types::{Position, Range, TextEdit}; use noirc_frontend::macros_api::ModuleDefId; -use crate::modules::{get_parent_module_id, relative_module_full_path, relative_module_id_path}; +use crate::modules::{relative_module_full_path, relative_module_id_path}; use super::{ kinds::{FunctionCompletionKind, FunctionKind, RequestedItems}, @@ -17,7 +17,7 @@ impl<'a> NodeFinder<'a> { requested_items: RequestedItems, function_completion_kind: FunctionCompletionKind, ) { - let current_module_parent_id = get_parent_module_id(self.def_maps, self.module_id); + let current_module_parent_id = self.module_id.parent(self.def_maps); for (name, entries) in self.interner.get_auto_import_names() { if !name_matches(name, prefix) { diff --git a/tooling/lsp/src/trait_impl_method_stub_generator.rs b/tooling/lsp/src/trait_impl_method_stub_generator.rs index ae12bac4c06..56d2e5e1ea1 100644 --- a/tooling/lsp/src/trait_impl_method_stub_generator.rs +++ b/tooling/lsp/src/trait_impl_method_stub_generator.rs @@ -197,12 +197,7 @@ impl<'a> TraitImplMethodStubGenerator<'a> { } } - let module_id = struct_type.id.module_id(); - let module_data = &self.def_maps[&module_id.krate].modules()[module_id.local_id.0]; - let parent_module_local_id = module_data.parent.unwrap(); - let parent_module_id = - ModuleId { krate: module_id.krate, local_id: parent_module_local_id }; - + let parent_module_id = struct_type.id.parent_module_id(self.def_maps); let current_module_parent_id = current_module_data .parent .map(|parent| ModuleId { krate: self.module_id.krate, local_id: parent }); From 68f3022fcdaab6e379e43091b3242e6ea51cff26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Rodr=C3=ADguez?= Date: Thu, 26 Sep 2024 01:47:52 +0200 Subject: [PATCH 04/36] fix: Do not duplicate constant arrays in brillig (#6155) # Description ## Problem\* Avoids array duplication in inserter for brillig runtimes. ## Summary\* After https://github.com/noir-lang/noir/pull/6122 the inhibitor in the deduplication process for brillig should not be necessary anymore. ## Additional Context ## Documentation\* Check one: - [x] No documentation needed. - [ ] Documentation included in this PR. - [ ] **[For Experimental Features]** 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. --- .../noirc_evaluator/src/ssa/ir/function_inserter.rs | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/compiler/noirc_evaluator/src/ssa/ir/function_inserter.rs b/compiler/noirc_evaluator/src/ssa/ir/function_inserter.rs index 9221a925aa8..991ff22c902 100644 --- a/compiler/noirc_evaluator/src/ssa/ir/function_inserter.rs +++ b/compiler/noirc_evaluator/src/ssa/ir/function_inserter.rs @@ -5,7 +5,7 @@ use crate::ssa::ir::types::Type; use super::{ basic_block::BasicBlockId, dfg::{CallStack, InsertInstructionResult}, - function::{Function, RuntimeType}, + function::Function, instruction::{Instruction, InstructionId}, value::ValueId, }; @@ -46,14 +46,7 @@ impl<'f> FunctionInserter<'f> { if let Some(fetched_value) = self.const_arrays.get(&(new_array.clone(), typ.clone())) { - // Arrays in ACIR are immutable, but in Brillig arrays are copy-on-write - // so for function's with a Brillig runtime we make sure to check that value - // in our constants array map matches the resolved array value id. - if matches!(self.function.runtime(), RuntimeType::Acir(_)) { - return *fetched_value; - } else if *fetched_value == value { - return value; - } + return *fetched_value; }; let new_array_clone = new_array.clone(); From 742e8ae575aeea951057e1590c4c91c1406ced23 Mon Sep 17 00:00:00 2001 From: Maxim Vezenov Date: Thu, 26 Sep 2024 12:42:40 -0400 Subject: [PATCH 05/36] chore(ci): Update gates diff action to not post Brillig sizes report with no changes (#6157) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit …mment when not needed # Description ## Problem\* Resolves No issue as found while making the public functions size report on aztec-packages. ## Summary\* We currently repost the Brillig opcode report even when there have been no changes. This updates the noir-gates-diff to the most recent commit on the noir-gates-diff `main` branch which has the fix. ## Additional Context ## Documentation\* Check one: - [ ] No documentation needed. - [ ] Documentation included in this PR. - [ ] **[For Experimental Features]** 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. --- .github/workflows/gates_report_brillig.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gates_report_brillig.yml b/.github/workflows/gates_report_brillig.yml index 4e12a6fcbca..e7ec30923f0 100644 --- a/.github/workflows/gates_report_brillig.yml +++ b/.github/workflows/gates_report_brillig.yml @@ -74,7 +74,7 @@ jobs: - name: Compare Brillig bytecode size reports id: brillig_bytecode_diff - uses: noir-lang/noir-gates-diff@3fb844067b25d1b59727ea600b614503b33503f4 + uses: noir-lang/noir-gates-diff@d88f7523b013b9edd3f31c5cfddaef87a3fe1b48 with: report: gates_report_brillig.json header: | From 180bfc99944cd42b3f44048213458d1399687cef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Rodr=C3=ADguez?= Date: Thu, 26 Sep 2024 18:44:15 +0200 Subject: [PATCH 06/36] feat: Hoist constant allocation outside of loops (#6158) # Description ## Problem\* Avoids the runtime cost of reinitializing constants inside loops. ## Summary\* ## Additional Context ## Documentation\* Check one: - [x] No documentation needed. - [ ] Documentation included in this PR. - [ ] **[For Experimental Features]** Documentation to be submitted in a separate PR. # PR Checklist\* - [x] I have tested the changes locally. - [x] I have formatted the changes with [Prettier](https://prettier.io/) and/or `cargo fmt` on default settings. --- .../brillig_gen/constant_allocation.rs | 115 +++++++++++++++--- .../brillig/brillig_gen/variable_liveness.rs | 20 +-- 2 files changed, 110 insertions(+), 25 deletions(-) diff --git a/compiler/noirc_evaluator/src/brillig/brillig_gen/constant_allocation.rs b/compiler/noirc_evaluator/src/brillig/brillig_gen/constant_allocation.rs index cf484fa5038..f9ded224b33 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_gen/constant_allocation.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_gen/constant_allocation.rs @@ -1,7 +1,7 @@ //! This module analyzes the usage of constants in a given function and decides an allocation point for them. //! The allocation point will be the common dominator of all the places where the constant is used. //! By allocating in the common dominator, we can cache the constants for all subsequent uses. -use fxhash::FxHashMap as HashMap; +use fxhash::{FxHashMap as HashMap, FxHashSet as HashSet}; use crate::ssa::ir::{ basic_block::BasicBlockId, @@ -26,20 +26,23 @@ pub(crate) struct ConstantAllocation { constant_usage: HashMap>>, allocation_points: HashMap>>, dominator_tree: DominatorTree, + blocks_within_loops: HashSet, } impl ConstantAllocation { pub(crate) fn from_function(func: &Function) -> Self { let cfg = ControlFlowGraph::with_function(func); let post_order = PostOrder::with_function(func); - let dominator_tree = DominatorTree::with_cfg_and_post_order(&cfg, &post_order); + let mut dominator_tree = DominatorTree::with_cfg_and_post_order(&cfg, &post_order); + let blocks_within_loops = find_all_blocks_within_loops(func, &cfg, &mut dominator_tree); let mut instance = ConstantAllocation { constant_usage: HashMap::default(), allocation_points: HashMap::default(), dominator_tree, + blocks_within_loops, }; instance.collect_constant_usage(func); - instance.decide_allocation_points(); + instance.decide_allocation_points(func); instance } @@ -95,16 +98,16 @@ impl ConstantAllocation { } } - fn decide_allocation_points(&mut self) { + fn decide_allocation_points(&mut self, func: &Function) { for (constant_id, usage_in_blocks) in self.constant_usage.iter() { let block_ids: Vec<_> = usage_in_blocks.iter().map(|(block_id, _)| *block_id).collect(); - let common_dominator = self.common_dominator(&block_ids); + let allocation_point = self.decide_allocation_point(*constant_id, &block_ids, func); - // If the common dominator is one of the places where it's used, we take the first usage in the common dominator. - // Otherwise, we allocate it at the terminator of the common dominator. + // If the allocation point is one of the places where it's used, we take the first usage in the allocation point. + // Otherwise, we allocate it at the terminator of the allocation point. let location = if let Some(locations_in_common_dominator) = - usage_in_blocks.get(&common_dominator) + usage_in_blocks.get(&allocation_point) { *locations_in_common_dominator .first() @@ -114,7 +117,7 @@ impl ConstantAllocation { }; self.allocation_points - .entry(common_dominator) + .entry(allocation_point) .or_default() .entry(location) .or_default() @@ -122,21 +125,97 @@ impl ConstantAllocation { } } - fn common_dominator(&self, block_ids: &[BasicBlockId]) -> BasicBlockId { - if block_ids.len() == 1 { - return block_ids[0]; - } - - let mut common_dominator = block_ids[0]; + fn decide_allocation_point( + &self, + constant_id: ValueId, + blocks_where_is_used: &[BasicBlockId], + func: &Function, + ) -> BasicBlockId { + // Find the common dominator of all the blocks where the constant is used. + let common_dominator = if blocks_where_is_used.len() == 1 { + blocks_where_is_used[0] + } else { + let mut common_dominator = blocks_where_is_used[0]; + + for block_id in blocks_where_is_used.iter().skip(1) { + common_dominator = + self.dominator_tree.common_dominator(common_dominator, *block_id); + } - for block_id in block_ids.iter().skip(1) { - common_dominator = self.dominator_tree.common_dominator(common_dominator, *block_id); + common_dominator + }; + // If the value only contains constants, it's safe to hoist outside of any loop + if func.dfg.is_constant(constant_id) { + self.exit_loops(common_dominator) + } else { + common_dominator } + } - common_dominator + /// Returns the nearest dominator that is outside of any loop. + fn exit_loops(&self, block: BasicBlockId) -> BasicBlockId { + let mut current_block = block; + while self.blocks_within_loops.contains(¤t_block) { + current_block = self + .dominator_tree + .immediate_dominator(current_block) + .expect("No dominator found when trying to allocate a constant outside of a loop"); + } + current_block } } pub(crate) fn is_constant_value(id: ValueId, dfg: &DataFlowGraph) -> bool { matches!(&dfg[dfg.resolve(id)], Value::NumericConstant { .. } | Value::Array { .. }) } + +/// For a given function, finds all the blocks that are within loops +fn find_all_blocks_within_loops( + func: &Function, + cfg: &ControlFlowGraph, + dominator_tree: &mut DominatorTree, +) -> HashSet { + let mut blocks_in_loops = HashSet::default(); + for block_id in func.reachable_blocks() { + let block = &func.dfg[block_id]; + let successors = block.successors(); + for successor_id in successors { + if dominator_tree.dominates(successor_id, block_id) { + blocks_in_loops.extend(find_blocks_in_loop(successor_id, block_id, cfg)); + } + } + } + + blocks_in_loops +} + +/// Return each block that is in a loop starting in the given header block. +/// Expects back_edge_start -> header to be the back edge of the loop. +fn find_blocks_in_loop( + header: BasicBlockId, + back_edge_start: BasicBlockId, + cfg: &ControlFlowGraph, +) -> HashSet { + let mut blocks = HashSet::default(); + blocks.insert(header); + + let mut insert = |block, stack: &mut Vec| { + if !blocks.contains(&block) { + blocks.insert(block); + stack.push(block); + } + }; + + // Starting from the back edge of the loop, each predecessor of this block until + // the header is within the loop. + let mut stack = vec![]; + insert(back_edge_start, &mut stack); + + while let Some(block) = stack.pop() { + for predecessor in cfg.predecessors(block) { + insert(predecessor, &mut stack); + } + } + + blocks +} diff --git a/compiler/noirc_evaluator/src/brillig/brillig_gen/variable_liveness.rs b/compiler/noirc_evaluator/src/brillig/brillig_gen/variable_liveness.rs index 73e88cee676..92595292bf0 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_gen/variable_liveness.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_gen/variable_liveness.rs @@ -570,28 +570,34 @@ mod test { let liveness = VariableLiveness::from_function(func, &constants); assert!(liveness.get_live_in(&func.entry_block()).is_empty()); - assert_eq!(liveness.get_live_in(&b1), &FxHashSet::from_iter([v0, v1, v3, v4].into_iter())); + assert_eq!( + liveness.get_live_in(&b1), + &FxHashSet::from_iter([v0, v1, v3, v4, twenty_seven, one].into_iter()) + ); assert_eq!(liveness.get_live_in(&b3), &FxHashSet::from_iter([v3].into_iter())); - assert_eq!(liveness.get_live_in(&b2), &FxHashSet::from_iter([v0, v1, v3, v4].into_iter())); + assert_eq!( + liveness.get_live_in(&b2), + &FxHashSet::from_iter([v0, v1, v3, v4, twenty_seven, one].into_iter()) + ); assert_eq!( liveness.get_live_in(&b4), - &FxHashSet::from_iter([v0, v1, v3, v4, v6, v7].into_iter()) + &FxHashSet::from_iter([v0, v1, v3, v4, v6, v7, twenty_seven, one].into_iter()) ); assert_eq!( liveness.get_live_in(&b6), - &FxHashSet::from_iter([v0, v1, v3, v4, one].into_iter()) + &FxHashSet::from_iter([v0, v1, v3, v4, twenty_seven, one].into_iter()) ); assert_eq!( liveness.get_live_in(&b5), - &FxHashSet::from_iter([v0, v1, v3, v4, v6, v7, one].into_iter()) + &FxHashSet::from_iter([v0, v1, v3, v4, v6, v7, twenty_seven, one].into_iter()) ); assert_eq!( liveness.get_live_in(&b7), - &FxHashSet::from_iter([v0, v1, v3, v4, v6, v7, one].into_iter()) + &FxHashSet::from_iter([v0, v1, v3, v4, v6, v7, twenty_seven, one].into_iter()) ); assert_eq!( liveness.get_live_in(&b8), - &FxHashSet::from_iter([v0, v1, v3, v4, v6, v7, one].into_iter()) + &FxHashSet::from_iter([v0, v1, v3, v4, v6, v7, twenty_seven, one].into_iter()) ); let block_3 = &func.dfg[b3]; From 33d6c8c77a2a0dc670da8811549ae4f5feaa4894 Mon Sep 17 00:00:00 2001 From: Tom French <15848336+TomAFrench@users.noreply.github.com> Date: Thu, 26 Sep 2024 18:04:21 +0100 Subject: [PATCH 07/36] chore: deprecate various items in stdlib (#6156) # Description ## Problem\* Resolves ## Summary\* This PR deprecates various items in the stdlib. Where an external library is fulfilling the role of a stdlib function we point off to that external lib. ## Additional Context ## Documentation\* Check one: - [ ] No documentation needed. - [ ] Documentation included in this PR. - [ ] **[For Experimental Features]** 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. --- noir_stdlib/src/ec/consts/te.nr | 1 + noir_stdlib/src/ec/tecurve.nr | 1 + noir_stdlib/src/hash/mimc.nr | 2 ++ noir_stdlib/src/hash/sha256.nr | 2 +- noir_stdlib/src/schnorr.nr | 13 ++++++++++--- noir_stdlib/src/sha256.nr | 11 ++++++++++- noir_stdlib/src/sha512.nr | 6 +++++- 7 files changed, 30 insertions(+), 6 deletions(-) diff --git a/noir_stdlib/src/ec/consts/te.nr b/noir_stdlib/src/ec/consts/te.nr index 349e518cdb2..e82302eadee 100644 --- a/noir_stdlib/src/ec/consts/te.nr +++ b/noir_stdlib/src/ec/consts/te.nr @@ -8,6 +8,7 @@ pub struct BabyJubjub { } #[field(bn254)] +#[deprecated = "It's recommmended to use the external noir-edwards library (https://github.com/noir-lang/noir-edwards)"] pub fn baby_jubjub() -> BabyJubjub { BabyJubjub { // Baby Jubjub (ERC-2494) parameters in affine representation diff --git a/noir_stdlib/src/ec/tecurve.nr b/noir_stdlib/src/ec/tecurve.nr index b306873806d..f9cdf83aab9 100644 --- a/noir_stdlib/src/ec/tecurve.nr +++ b/noir_stdlib/src/ec/tecurve.nr @@ -27,6 +27,7 @@ mod affine { impl Point { // Point constructor + #[deprecated = "It's recommmended to use the external noir-edwards library (https://github.com/noir-lang/noir-edwards)"] pub fn new(x: Field, y: Field) -> Self { Self { x, y } } diff --git a/noir_stdlib/src/hash/mimc.nr b/noir_stdlib/src/hash/mimc.nr index 8c43536d18b..1bcc9e3fcc7 100644 --- a/noir_stdlib/src/hash/mimc.nr +++ b/noir_stdlib/src/hash/mimc.nr @@ -6,6 +6,7 @@ use crate::default::Default; // You must use constants generated for the native field // Rounds number should be ~ log(p)/log(exp) // For 254 bit primes, exponent 7 and 91 rounds seems to be recommended +#[deprecated = "It's recommmended to use the external MiMC library (https://github.com/noir-lang/mimc)"] fn mimc(x: Field, k: Field, constants: [Field; N], exp: Field) -> Field { //round 0 let mut t = x + k; @@ -116,6 +117,7 @@ global MIMC_BN254_CONSTANTS: [Field; MIMC_BN254_ROUNDS] = [ //mimc implementation with hardcoded parameters for BN254 curve. #[field(bn254)] +#[deprecated = "It's recommmended to use the external MiMC library (https://github.com/noir-lang/mimc)"] pub fn mimc_bn254(array: [Field; N]) -> Field { let exponent = 7; let mut r = 0; diff --git a/noir_stdlib/src/hash/sha256.nr b/noir_stdlib/src/hash/sha256.nr index e712019b5cc..413c26d6f6b 100644 --- a/noir_stdlib/src/hash/sha256.nr +++ b/noir_stdlib/src/hash/sha256.nr @@ -8,7 +8,7 @@ use crate::runtime::is_unconstrained; pub fn sha256(input: [u8; N]) -> [u8; 32] // docs:end:sha256 { - crate::sha256::digest(input) + digest(input) } #[foreign(sha256_compression)] diff --git a/noir_stdlib/src/schnorr.nr b/noir_stdlib/src/schnorr.nr index 336041fec19..e24aabf3cda 100644 --- a/noir_stdlib/src/schnorr.nr +++ b/noir_stdlib/src/schnorr.nr @@ -1,4 +1,3 @@ -use crate::collections::vec::Vec; use crate::embedded_curve_ops::{EmbeddedCurvePoint, EmbeddedCurveScalar}; #[foreign(schnorr_verify)] @@ -23,7 +22,11 @@ pub fn verify_signature_slice( // docs:end:schnorr_verify_slice {} -pub fn verify_signature_noir(public_key: EmbeddedCurvePoint, signature: [u8; 64], message: [u8; N]) -> bool { +pub fn verify_signature_noir( + public_key: EmbeddedCurvePoint, + signature: [u8; 64], + message: [u8; N] +) -> bool { //scalar lo/hi from bytes let sig_s = EmbeddedCurveScalar::from_bytes(signature, 0); let sig_e = EmbeddedCurveScalar::from_bytes(signature, 32); @@ -42,7 +45,11 @@ pub fn verify_signature_noir(public_key: EmbeddedCurvePoint, signatu is_ok } -pub fn assert_valid_signature(public_key: EmbeddedCurvePoint, signature: [u8; 64], message: [u8; N]) { +pub fn assert_valid_signature( + public_key: EmbeddedCurvePoint, + signature: [u8; 64], + message: [u8; N] +) { //scalar lo/hi from bytes let sig_s = EmbeddedCurveScalar::from_bytes(signature, 0); let sig_e = EmbeddedCurveScalar::from_bytes(signature, 32); diff --git a/noir_stdlib/src/sha256.nr b/noir_stdlib/src/sha256.nr index ce217f7a689..7679e517317 100644 --- a/noir_stdlib/src/sha256.nr +++ b/noir_stdlib/src/sha256.nr @@ -1,2 +1,11 @@ // This file is kept for backwards compatibility. -pub use crate::hash::sha256::{digest, sha256_var}; + +#[deprecated = "replace with std::hash::sha256::digest"] +pub fn digest(msg: [u8; N]) -> [u8; 32] { + crate::hash::sha256::digest(msg) +} + +#[deprecated = "replace with std::hash::sha256::sha256_var"] +pub fn sha256_var(msg: [u8; N], message_size: u64) -> [u8; 32] { + crate::hash::sha256::sha256_var(msg, message_size) +} diff --git a/noir_stdlib/src/sha512.nr b/noir_stdlib/src/sha512.nr index b474e27b416..0a8a5bf4760 100644 --- a/noir_stdlib/src/sha512.nr +++ b/noir_stdlib/src/sha512.nr @@ -1,2 +1,6 @@ // This file is kept for backwards compatibility. -pub use crate::hash::sha512::digest; + +#[deprecated = "replace with std::hash::sha512::digest"] +pub fn digest(msg: [u8; N]) -> [u8; 64] { + crate::hash::sha512::digest(msg) +} From 59c41182faa19d1cb8c9be5c11d50636fc17dad7 Mon Sep 17 00:00:00 2001 From: Maxim Vezenov Date: Thu, 26 Sep 2024 14:44:19 -0400 Subject: [PATCH 08/36] feat(perf): Remove useless paired RC instructions within a block during DIE (#6160) # Description ## Problem\* Resolves Part of general effort to reduce Brillig bytecode sizes. ## Summary\* We currently have a pass for removing paired `inc_rc` and `dec_rc` instructions as long as there is no array set to an array of the same type between them. However, this is pass only checks the first block for inc_rc instructions and the return block for dec_rc instructions. This is because during SSA gen we add inc_rc instructions on array function parameters and dec_rc parameters when we exit the function scope, so this pass still allows us to get rid of a lot of instructions. However, function scope entry and exit are not the only places we add inc_rc instructions. And once we inline functions and go through other SSA opts, it is common to have inc_rc and dec_rc instructions through a block. e.g. This is the SSA of the return block of `execution_success/poseidon2`: ``` b4(): v17 = load v10 v19 = call poseidon2_permutation(v17, u32 4) inc_rc v19 dec_rc v19 inc_rc v19 v20 = array_get v19, index u32 0 dec_rc v19 constrain v20 == v1 return ``` All of those inc_rc/dec_rc pairs are useless and we should be able to remove them. This PR removes paired inc_rc/dec_rc instructions per block when there is not an array set to an array of the same type between them. The return block of `execution_success/poseidon2` after this PR is the following: ``` b4(): v17 = load v10 v19 = call poseidon2_permutation(v17, u32 4) v20 = array_get v19, index u32 0 constrain v20 == v1 return ``` ## Additional Context Right now I just focused on removing these inc_rc/dec_rc instructions per block for simplicity. Removing them across blocks is more complex and that work can come in a follow-up. ## Documentation\* Check one: - [X] No documentation needed. - [ ] Documentation included in this PR. - [ ] **[For Experimental Features]** Documentation to be submitted in a separate PR. # PR Checklist\* - [X] I have tested the changes locally. - [X] I have formatted the changes with [Prettier](https://prettier.io/) and/or `cargo fmt` on default settings. --- compiler/noirc_evaluator/src/ssa/opt/die.rs | 143 +++++++++++++++++++- compiler/noirc_evaluator/src/ssa/opt/rc.rs | 37 ++--- 2 files changed, 161 insertions(+), 19 deletions(-) diff --git a/compiler/noirc_evaluator/src/ssa/opt/die.rs b/compiler/noirc_evaluator/src/ssa/opt/die.rs index 7356998ceb8..02737f5645b 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/die.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/die.rs @@ -1,7 +1,6 @@ //! Dead Instruction Elimination (DIE) pass: Removes any instruction without side-effects for //! which the results are unused. -use std::collections::HashSet; - +use fxhash::{FxHashMap as HashMap, FxHashSet as HashSet}; use im::Vector; use noirc_errors::Location; @@ -18,6 +17,8 @@ use crate::ssa::{ ssa_gen::{Ssa, SSA_WORD_SIZE}, }; +use super::rc::{pop_rc_for, RcInstruction}; + impl Ssa { /// Performs Dead Instruction Elimination (DIE) to remove any instructions with /// unused results. @@ -105,6 +106,16 @@ impl Context { let instructions_len = block.instructions().len(); + // We can track IncrementRc instructions per block to determine whether they are useless. + // IncrementRc and DecrementRc instructions are normally side effectual instructions, but we remove + // them if their value is not used anywhere in the function. However, even when their value is used, their existence + // is pointless logic if there is no array set between the increment and the decrement of the reference counter. + // We track per block whether an IncrementRc instruction has a paired DecrementRc instruction + // with the same value but no array set in between. + // If we see an inc/dec RC pair within a block we can safely remove both instructions. + let mut inc_rcs: HashMap> = HashMap::default(); + let mut inc_rcs_to_remove = HashSet::default(); + // Indexes of instructions that might be out of bounds. // We'll remove those, but before that we'll insert bounds checks for them. let mut possible_index_out_of_bounds_indexes = Vec::new(); @@ -131,6 +142,17 @@ impl Context { }); } } + + self.track_inc_rcs_to_remove( + *instruction_id, + function, + &mut inc_rcs, + &mut inc_rcs_to_remove, + ); + } + + for id in inc_rcs_to_remove { + self.instructions_to_remove.insert(id); } // If there are some instructions that might trigger an out of bounds error, @@ -155,6 +177,45 @@ impl Context { false } + fn track_inc_rcs_to_remove( + &self, + instruction_id: InstructionId, + function: &Function, + inc_rcs: &mut HashMap>, + inc_rcs_to_remove: &mut HashSet, + ) { + let instruction = &function.dfg[instruction_id]; + // DIE loops over a block in reverse order, so we insert an RC instruction for possible removal + // when we see a DecrementRc and check whether it was possibly mutated when we see an IncrementRc. + match instruction { + Instruction::IncrementRc { value } => { + if let Some(inc_rc) = pop_rc_for(*value, function, inc_rcs) { + if !inc_rc.possibly_mutated { + inc_rcs_to_remove.insert(inc_rc.id); + inc_rcs_to_remove.insert(instruction_id); + } + } + } + Instruction::DecrementRc { value } => { + let typ = function.dfg.type_of_value(*value); + + // We assume arrays aren't mutated until we find an array_set + let inc_rc = + RcInstruction { id: instruction_id, array: *value, possibly_mutated: false }; + inc_rcs.entry(typ).or_default().push(inc_rc); + } + Instruction::ArraySet { array, .. } => { + let typ = function.dfg.type_of_value(*array); + if let Some(inc_rcs) = inc_rcs.get_mut(&typ) { + for inc_rc in inc_rcs { + inc_rc.possibly_mutated = true; + } + } + } + _ => {} + } + } + /// Returns true if an instruction can be removed. /// /// An instruction can be removed as long as it has no side-effects, and none of its result @@ -509,10 +570,12 @@ fn apply_side_effects( #[cfg(test)] mod test { + use std::sync::Arc; + use crate::ssa::{ function_builder::FunctionBuilder, ir::{ - instruction::{BinaryOp, Intrinsic}, + instruction::{BinaryOp, Instruction, Intrinsic}, map::Id, types::Type, }, @@ -642,4 +705,78 @@ mod test { assert_eq!(main.dfg[main.entry_block()].instructions().len(), 1); } + + #[test] + fn remove_useless_paired_rcs_even_when_used() { + // acir(inline) fn main f0 { + // b0(v0: [Field; 2]): + // inc_rc v0 + // v2 = array_get v0, index u32 0 + // dec_rc v0 + // return v2 + // } + let main_id = Id::test_new(0); + + // Compiling main + let mut builder = FunctionBuilder::new("main".into(), main_id); + let v0 = builder.add_parameter(Type::Array(Arc::new(vec![Type::field()]), 2)); + builder.increment_array_reference_count(v0); + let zero = builder.numeric_constant(0u128, Type::unsigned(32)); + let v1 = builder.insert_array_get(v0, zero, Type::field()); + builder.decrement_array_reference_count(v0); + builder.terminate_with_return(vec![v1]); + + let ssa = builder.finish(); + let main = ssa.main(); + + // The instruction count never includes the terminator instruction + assert_eq!(main.dfg[main.entry_block()].instructions().len(), 3); + + // Expected output: + // + // acir(inline) fn main f0 { + // b0(v0: [Field; 2]): + // v2 = array_get v0, index u32 0 + // return v2 + // } + let ssa = ssa.dead_instruction_elimination(); + let main = ssa.main(); + + let instructions = main.dfg[main.entry_block()].instructions(); + assert_eq!(instructions.len(), 1); + assert!(matches!(&main.dfg[instructions[0]], Instruction::ArrayGet { .. })); + } + + #[test] + fn keep_paired_rcs_with_array_set() { + // acir(inline) fn main f0 { + // b0(v0: [Field; 2]): + // inc_rc v0 + // v2 = array_set v0, index u32 0, value u32 0 + // dec_rc v0 + // return v2 + // } + let main_id = Id::test_new(0); + + // Compiling main + let mut builder = FunctionBuilder::new("main".into(), main_id); + let v0 = builder.add_parameter(Type::Array(Arc::new(vec![Type::field()]), 2)); + builder.increment_array_reference_count(v0); + let zero = builder.numeric_constant(0u128, Type::unsigned(32)); + let v1 = builder.insert_array_set(v0, zero, zero); + builder.decrement_array_reference_count(v0); + builder.terminate_with_return(vec![v1]); + + let ssa = builder.finish(); + let main = ssa.main(); + + // The instruction count never includes the terminator instruction + assert_eq!(main.dfg[main.entry_block()].instructions().len(), 3); + + // We expect the output to be unchanged + let ssa = ssa.dead_instruction_elimination(); + let main = ssa.main(); + + assert_eq!(main.dfg[main.entry_block()].instructions().len(), 3); + } } diff --git a/compiler/noirc_evaluator/src/ssa/opt/rc.rs b/compiler/noirc_evaluator/src/ssa/opt/rc.rs index 1750f2d80a5..06025fd9e8b 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/rc.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/rc.rs @@ -1,4 +1,4 @@ -use std::collections::{HashMap, HashSet}; +use fxhash::{FxHashMap as HashMap, FxHashSet as HashSet}; use crate::ssa::{ ir::{ @@ -35,13 +35,13 @@ struct Context { // // The type of the array being operated on is recorded. // If an array_set to that array type is encountered, that is also recorded. - inc_rcs: HashMap>, + inc_rcs: HashMap>, } -struct IncRc { - id: InstructionId, - array: ValueId, - possibly_mutated: bool, +pub(crate) struct RcInstruction { + pub(crate) id: InstructionId, + pub(crate) array: ValueId, + pub(crate) possibly_mutated: bool, } /// This function is very simplistic for now. It takes advantage of the fact that dec_rc @@ -80,7 +80,8 @@ impl Context { let typ = function.dfg.type_of_value(*value); // We assume arrays aren't mutated until we find an array_set - let inc_rc = IncRc { id: *instruction, array: *value, possibly_mutated: false }; + let inc_rc = + RcInstruction { id: *instruction, array: *value, possibly_mutated: false }; self.inc_rcs.entry(typ).or_default().push(inc_rc); } } @@ -107,11 +108,11 @@ impl Context { /// is not possibly mutated, then we can remove them both. Returns each such pair. fn find_rcs_to_remove(&mut self, function: &Function) -> HashSet { let last_block = function.find_last_block(); - let mut to_remove = HashSet::new(); + let mut to_remove = HashSet::default(); for instruction in function.dfg[last_block].instructions() { if let Instruction::DecrementRc { value } = &function.dfg[*instruction] { - if let Some(inc_rc) = self.pop_rc_for(*value, function) { + if let Some(inc_rc) = pop_rc_for(*value, function, &mut self.inc_rcs) { if !inc_rc.possibly_mutated { to_remove.insert(inc_rc.id); to_remove.insert(*instruction); @@ -122,16 +123,20 @@ impl Context { to_remove } +} - /// Finds and pops the IncRc for the given array value if possible. - fn pop_rc_for(&mut self, value: ValueId, function: &Function) -> Option { - let typ = function.dfg.type_of_value(value); +/// Finds and pops the IncRc for the given array value if possible. +pub(crate) fn pop_rc_for( + value: ValueId, + function: &Function, + inc_rcs: &mut HashMap>, +) -> Option { + let typ = function.dfg.type_of_value(value); - let rcs = self.inc_rcs.get_mut(&typ)?; - let position = rcs.iter().position(|inc_rc| inc_rc.array == value)?; + let rcs = inc_rcs.get_mut(&typ)?; + let position = rcs.iter().position(|inc_rc| inc_rc.array == value)?; - Some(rcs.remove(position)) - } + Some(rcs.remove(position)) } fn remove_instructions(to_remove: HashSet, function: &mut Function) { From 103b54db8a5a81ecf76381fe99320c1e1f606898 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Thu, 26 Sep 2024 16:26:01 -0300 Subject: [PATCH 09/36] feat: visibility for globals (#6161) # Description ## Problem Part of #4515 ## Summary ## Additional Context I left all of the stdlib globals as private except one which needed to be `pub(crate)` to avoid a warning. I don't know if some of these globals should actually be public. ## Documentation Check one: - [ ] No documentation needed. - [x] Documentation included in this PR. - [ ] **[For Experimental Features]** Documentation to be submitted in a separate PR. # PR Checklist - [x] I have tested the changes locally. - [x] I have formatted the changes with [Prettier](https://prettier.io/) and/or `cargo fmt` on default settings. --------- Co-authored-by: Tom French <15848336+TomAFrench@users.noreply.github.com> --- compiler/noirc_frontend/src/ast/visitor.rs | 2 +- .../noirc_frontend/src/elaborator/comptime.rs | 3 +- .../src/hir/def_collector/dc_mod.rs | 18 +++++++-- .../src/hir/def_map/module_data.rs | 9 ++++- compiler/noirc_frontend/src/parser/mod.rs | 37 ++++++++++++------- compiler/noirc_frontend/src/parser/parser.rs | 18 +++++++-- compiler/noirc_frontend/src/tests.rs | 4 +- .../noirc_frontend/src/tests/unused_items.rs | 23 ++++++++++++ compiler/noirc_frontend/src/usage_tracker.rs | 4 +- docs/docs/noir/concepts/globals.md | 10 +++++ noir_stdlib/src/field/bn254.nr | 2 +- tooling/nargo_fmt/src/visitor/item.rs | 2 +- 12 files changed, 104 insertions(+), 28 deletions(-) diff --git a/compiler/noirc_frontend/src/ast/visitor.rs b/compiler/noirc_frontend/src/ast/visitor.rs index 6ce8a70a0c5..ed4d17cadd8 100644 --- a/compiler/noirc_frontend/src/ast/visitor.rs +++ b/compiler/noirc_frontend/src/ast/visitor.rs @@ -500,7 +500,7 @@ impl Item { noir_trait_impl.accept(self.span, visitor); } ItemKind::Impl(type_impl) => type_impl.accept(self.span, visitor), - ItemKind::Global(let_statement) => { + ItemKind::Global(let_statement, _visibility) => { if visitor.visit_global(let_statement, self.span) { let_statement.accept(visitor); } diff --git a/compiler/noirc_frontend/src/elaborator/comptime.rs b/compiler/noirc_frontend/src/elaborator/comptime.rs index ca441758322..560be895628 100644 --- a/compiler/noirc_frontend/src/elaborator/comptime.rs +++ b/compiler/noirc_frontend/src/elaborator/comptime.rs @@ -439,11 +439,12 @@ impl<'context> Elaborator<'context> { resolved_trait_generics: Vec::new(), }); } - TopLevelStatementKind::Global(global) => { + TopLevelStatementKind::Global(global, visibility) => { let (global, error) = dc_mod::collect_global( self.interner, self.def_maps.get_mut(&self.crate_id).unwrap(), Documented::new(global, item.doc_comments), + visibility, self.file, self.local_module, self.crate_id, diff --git a/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs b/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs index 132b62f84b7..162d39e0adb 100644 --- a/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs +++ b/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs @@ -139,15 +139,16 @@ impl<'a> ModCollector<'a> { fn collect_globals( &mut self, context: &mut Context, - globals: Vec>, + globals: Vec<(Documented, ItemVisibility)>, crate_id: CrateId, ) -> Vec<(CompilationError, fm::FileId)> { let mut errors = vec![]; - for global in globals { + for (global, visibility) in globals { let (global, error) = collect_global( &mut context.def_interner, &mut self.def_collector.def_map, global, + visibility, self.file_id, self.module_id, crate_id, @@ -515,7 +516,7 @@ impl<'a> ModCollector<'a> { if let Err((first_def, second_def)) = self.def_collector.def_map.modules [trait_id.0.local_id.0] - .declare_global(name.clone(), global_id) + .declare_global(name.clone(), ItemVisibility::Public, global_id) { let error = DefCollectorErrorKind::Duplicate { typ: DuplicateType::TraitAssociatedConst, @@ -1160,6 +1161,7 @@ pub(crate) fn collect_global( interner: &mut NodeInterner, def_map: &mut CrateDefMap, global: Documented, + visibility: ItemVisibility, file_id: FileId, module_id: LocalModuleId, crate_id: CrateId, @@ -1180,7 +1182,15 @@ pub(crate) fn collect_global( ); // Add the statement to the scope so its path can be looked up later - let result = def_map.modules[module_id.0].declare_global(name, global_id); + let result = def_map.modules[module_id.0].declare_global(name.clone(), visibility, global_id); + + let parent_module_id = ModuleId { krate: crate_id, local_id: module_id }; + interner.usage_tracker.add_unused_item( + parent_module_id, + name, + UnusedItem::Global(global_id), + visibility, + ); let error = result.err().map(|(first_def, second_def)| { let err = diff --git a/compiler/noirc_frontend/src/hir/def_map/module_data.rs b/compiler/noirc_frontend/src/hir/def_map/module_data.rs index 5bbff85079e..149b1977925 100644 --- a/compiler/noirc_frontend/src/hir/def_map/module_data.rs +++ b/compiler/noirc_frontend/src/hir/def_map/module_data.rs @@ -96,8 +96,13 @@ impl ModuleData { self.definitions.remove_definition(name); } - pub fn declare_global(&mut self, name: Ident, id: GlobalId) -> Result<(), (Ident, Ident)> { - self.declare(name, ItemVisibility::Public, id.into(), None) + pub fn declare_global( + &mut self, + name: Ident, + visibility: ItemVisibility, + id: GlobalId, + ) -> Result<(), (Ident, Ident)> { + self.declare(name, visibility, id.into(), None) } pub fn declare_struct( diff --git a/compiler/noirc_frontend/src/parser/mod.rs b/compiler/noirc_frontend/src/parser/mod.rs index 968af82a8b3..1b6d573560d 100644 --- a/compiler/noirc_frontend/src/parser/mod.rs +++ b/compiler/noirc_frontend/src/parser/mod.rs @@ -47,7 +47,7 @@ pub enum TopLevelStatementKind { Impl(TypeImpl), TypeAlias(NoirTypeAlias), SubModule(ParsedSubModule), - Global(LetStatement), + Global(LetStatement, ItemVisibility), InnerAttribute(SecondaryAttribute), Error, } @@ -64,7 +64,7 @@ impl TopLevelStatementKind { TopLevelStatementKind::Impl(i) => Some(ItemKind::Impl(i)), TopLevelStatementKind::TypeAlias(t) => Some(ItemKind::TypeAlias(t)), TopLevelStatementKind::SubModule(s) => Some(ItemKind::Submodules(s)), - TopLevelStatementKind::Global(c) => Some(ItemKind::Global(c)), + TopLevelStatementKind::Global(c, visibility) => Some(ItemKind::Global(c, visibility)), TopLevelStatementKind::InnerAttribute(a) => Some(ItemKind::InnerAttribute(a)), TopLevelStatementKind::Error => None, } @@ -249,7 +249,7 @@ pub struct SortedModule { pub trait_impls: Vec, pub impls: Vec, pub type_aliases: Vec>, - pub globals: Vec>, + pub globals: Vec<(Documented, ItemVisibility)>, /// Module declarations like `mod foo;` pub module_decls: Vec>, @@ -271,7 +271,7 @@ impl std::fmt::Display for SortedModule { write!(f, "{import}")?; } - for global_const in &self.globals { + for (global_const, _visibility) in &self.globals { write!(f, "{global_const}")?; } @@ -321,7 +321,9 @@ impl ParsedModule { ItemKind::TypeAlias(type_alias) => { module.push_type_alias(type_alias, item.doc_comments); } - ItemKind::Global(global) => module.push_global(global, item.doc_comments), + ItemKind::Global(global, visibility) => { + module.push_global(global, visibility, item.doc_comments); + } ItemKind::ModuleDecl(mod_name) => { module.push_module_decl(mod_name, item.doc_comments); } @@ -354,7 +356,7 @@ pub enum ItemKind { TraitImpl(NoirTraitImpl), Impl(TypeImpl), TypeAlias(NoirTypeAlias), - Global(LetStatement), + Global(LetStatement, ItemVisibility), ModuleDecl(ModuleDeclaration), Submodules(ParsedSubModule), InnerAttribute(SecondaryAttribute), @@ -438,8 +440,13 @@ impl SortedModule { self.submodules.push(Documented::new(submodule, doc_comments)); } - fn push_global(&mut self, global: LetStatement, doc_comments: Vec) { - self.globals.push(Documented::new(global, doc_comments)); + fn push_global( + &mut self, + global: LetStatement, + visibility: ItemVisibility, + doc_comments: Vec, + ) { + self.globals.push((Documented::new(global, doc_comments), visibility)); } } @@ -526,11 +533,10 @@ impl std::fmt::Display for TopLevelStatementKind { TopLevelStatementKind::Function(fun) => fun.fmt(f), TopLevelStatementKind::Module(m) => m.fmt(f), TopLevelStatementKind::Import(tree, visibility) => { - if visibility == &ItemVisibility::Private { - write!(f, "use {tree}") - } else { - write!(f, "{visibility} use {tree}") + if visibility != &ItemVisibility::Private { + write!(f, "{visibility} ")?; } + write!(f, "use {tree}") } TopLevelStatementKind::Trait(t) => t.fmt(f), TopLevelStatementKind::TraitImpl(i) => i.fmt(f), @@ -538,7 +544,12 @@ impl std::fmt::Display for TopLevelStatementKind { TopLevelStatementKind::Impl(i) => i.fmt(f), TopLevelStatementKind::TypeAlias(t) => t.fmt(f), TopLevelStatementKind::SubModule(s) => s.fmt(f), - TopLevelStatementKind::Global(c) => c.fmt(f), + TopLevelStatementKind::Global(c, visibility) => { + if visibility != &ItemVisibility::Private { + write!(f, "{visibility} ")?; + } + c.fmt(f) + } TopLevelStatementKind::InnerAttribute(a) => write!(f, "#![{}]", a), TopLevelStatementKind::Error => write!(f, "error"), } diff --git a/compiler/noirc_frontend/src/parser/parser.rs b/compiler/noirc_frontend/src/parser/parser.rs index 3f9a1d7a08b..d902f0a3b5a 100644 --- a/compiler/noirc_frontend/src/parser/parser.rs +++ b/compiler/noirc_frontend/src/parser/parser.rs @@ -220,6 +220,7 @@ fn implementation() -> impl NoirParser { /// global_declaration: 'global' ident global_type_annotation '=' literal fn global_declaration() -> impl NoirParser { let p = attributes::attributes() + .then(item_visibility()) .then(maybe_comp_time()) .then(spanned(keyword(Keyword::Mut)).or_not()) .then_ignore(keyword(Keyword::Global).labelled(ParsingRuleLabel::Global)) @@ -229,7 +230,9 @@ fn global_declaration() -> impl NoirParser { let p = then_commit_ignore(p, just(Token::Assign)); let p = then_commit(p, expression()); p.validate( - |(((((attributes, comptime), mutable), mut pattern), r#type), expression), span, emit| { + |((((((attributes, visibility), comptime), mutable), mut pattern), r#type), expression), + span, + emit| { let global_attributes = attributes::validate_secondary_attributes(attributes, span, emit); @@ -239,10 +242,19 @@ fn global_declaration() -> impl NoirParser { let span = mut_span.merge(pattern.span()); pattern = Pattern::Mutable(Box::new(pattern), span, false); } - LetStatement { pattern, r#type, comptime, expression, attributes: global_attributes } + ( + LetStatement { + pattern, + r#type, + comptime, + expression, + attributes: global_attributes, + }, + visibility, + ) }, ) - .map(TopLevelStatementKind::Global) + .map(|(let_statement, visibility)| TopLevelStatementKind::Global(let_statement, visibility)) } /// submodule: 'mod' ident '{' module '}' diff --git a/compiler/noirc_frontend/src/tests.rs b/compiler/noirc_frontend/src/tests.rs index 4c91c90bc31..9b55e689eed 100644 --- a/compiler/noirc_frontend/src/tests.rs +++ b/compiler/noirc_frontend/src/tests.rs @@ -1376,7 +1376,9 @@ fn ban_mutable_globals() { // Mutable globals are only allowed in a comptime context let src = r#" mut global FOO: Field = 0; - fn main() {} + fn main() { + let _ = FOO; // silence FOO never used warning + } "#; assert_eq!(get_program_errors(src).len(), 1); } diff --git a/compiler/noirc_frontend/src/tests/unused_items.rs b/compiler/noirc_frontend/src/tests/unused_items.rs index be3b2996e5a..a4d379b6358 100644 --- a/compiler/noirc_frontend/src/tests/unused_items.rs +++ b/compiler/noirc_frontend/src/tests/unused_items.rs @@ -239,3 +239,26 @@ fn errors_if_type_alias_aliases_more_private_type_in_generic() { assert_eq!(typ, "Foo"); assert_eq!(item, "Bar"); } + +#[test] +fn warns_on_unused_global() { + let src = r#" + global foo = 1; + global bar = 1; + + fn main() { + let _ = bar; + } + "#; + + let errors = get_program_errors(src); + assert_eq!(errors.len(), 1); + + let CompilationError::ResolverError(ResolverError::UnusedItem { ident, item }) = &errors[0].0 + else { + panic!("Expected an unused item warning"); + }; + + assert_eq!(ident.to_string(), "foo"); + assert_eq!(item.item_type(), "global"); +} diff --git a/compiler/noirc_frontend/src/usage_tracker.rs b/compiler/noirc_frontend/src/usage_tracker.rs index cd1041cc36f..275ca1f964b 100644 --- a/compiler/noirc_frontend/src/usage_tracker.rs +++ b/compiler/noirc_frontend/src/usage_tracker.rs @@ -4,7 +4,7 @@ use crate::{ ast::{Ident, ItemVisibility}, hir::def_map::ModuleId, macros_api::StructId, - node_interner::{FuncId, TraitId, TypeAliasId}, + node_interner::{FuncId, GlobalId, TraitId, TypeAliasId}, }; #[derive(Debug, Copy, Clone, PartialEq, Eq)] @@ -14,6 +14,7 @@ pub enum UnusedItem { Struct(StructId), Trait(TraitId), TypeAlias(TypeAliasId), + Global(GlobalId), } impl UnusedItem { @@ -24,6 +25,7 @@ impl UnusedItem { UnusedItem::Struct(_) => "struct", UnusedItem::Trait(_) => "trait", UnusedItem::TypeAlias(_) => "type alias", + UnusedItem::Global(_) => "global", } } } diff --git a/docs/docs/noir/concepts/globals.md b/docs/docs/noir/concepts/globals.md index 97a92a86e72..1145c55dfc7 100644 --- a/docs/docs/noir/concepts/globals.md +++ b/docs/docs/noir/concepts/globals.md @@ -70,3 +70,13 @@ fn foo() -> [Field; 100] { ... } This is usually fine since Noir will generally optimize any function call that does not refer to a program input into a constant. It should be kept in mind however, if the called function performs side-effects like `println`, as these will still occur on each use. + +### Visibility + +By default, like functions, globals are private to the module the exist in. You can use `pub` +to make the global public or `pub(crate)` to make it public to just its crate: + +```rust +// This global is now public +pub global N = 5; +``` \ No newline at end of file diff --git a/noir_stdlib/src/field/bn254.nr b/noir_stdlib/src/field/bn254.nr index 0aa5ca0717b..8ff62062d5c 100644 --- a/noir_stdlib/src/field/bn254.nr +++ b/noir_stdlib/src/field/bn254.nr @@ -4,7 +4,7 @@ use crate::runtime::is_unconstrained; global PLO: Field = 53438638232309528389504892708671455233; global PHI: Field = 64323764613183177041862057485226039389; -global TWO_POW_128: Field = 0x100000000000000000000000000000000; +pub(crate) global TWO_POW_128: Field = 0x100000000000000000000000000000000; // Decomposes a single field into two 16 byte fields. fn compute_decomposition(x: Field) -> (Field, Field) { diff --git a/tooling/nargo_fmt/src/visitor/item.rs b/tooling/nargo_fmt/src/visitor/item.rs index 7d5e1886072..b2a689919bd 100644 --- a/tooling/nargo_fmt/src/visitor/item.rs +++ b/tooling/nargo_fmt/src/visitor/item.rs @@ -276,7 +276,7 @@ impl super::FmtVisitor<'_> { ItemKind::Struct(_) | ItemKind::Trait(_) | ItemKind::TypeAlias(_) - | ItemKind::Global(_) + | ItemKind::Global(..) | ItemKind::ModuleDecl(_) | ItemKind::InnerAttribute(_) => { self.push_rewrite(self.slice(span).to_string(), span); From 12cb80a214fd81eb7619413a6d0663369be38512 Mon Sep 17 00:00:00 2001 From: Maxim Vezenov Date: Thu, 26 Sep 2024 17:09:10 -0400 Subject: [PATCH 10/36] feat(perf): Handle array set optimization across blocks for Brillig functions (#6153) # Description ## Problem\* Resolves Part of general effort to reduce Brillig bytecode sizes ## Summary\* We now go through all blocks of Brillig functions to determine whether we can mutate an array directly rather than copy it. We are still pretty conservative with our checks to make sure that we do not inadvertently mark the wrong array set mutable. - If we have seen a last array set whose array comes from a Load result we do not make that array set mutable unless we are in a return block. We can expand upon the cases in which we mark an array loaded from a reference as mutable in follow-ups. - If the array is used in the terminator of a block we do not mark its array set mutable. ## Additional Context ## Documentation\* Check one: - [X] No documentation needed. - [ ] Documentation included in this PR. - [ ] **[For Experimental Features]** Documentation to be submitted in a separate PR. # PR Checklist\* - [X] I have tested the changes locally. - [X] I have formatted the changes with [Prettier](https://prettier.io/) and/or `cargo fmt` on default settings. --------- Co-authored-by: Tom French <15848336+TomAFrench@users.noreply.github.com> --- .../noirc_evaluator/src/ssa/opt/array_set.rs | 198 ++++++++++++++++-- 1 file changed, 179 insertions(+), 19 deletions(-) diff --git a/compiler/noirc_evaluator/src/ssa/opt/array_set.rs b/compiler/noirc_evaluator/src/ssa/opt/array_set.rs index 491a17adb66..6d48b8c0d67 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/array_set.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/array_set.rs @@ -2,12 +2,13 @@ use crate::ssa::{ ir::{ basic_block::BasicBlockId, dfg::DataFlowGraph, - instruction::{Instruction, InstructionId}, + instruction::{Instruction, InstructionId, TerminatorInstruction}, types::Type::{Array, Slice}, + value::ValueId, }, ssa_gen::Ssa, }; -use fxhash::{FxHashMap as HashMap, FxHashSet}; +use fxhash::{FxHashMap as HashMap, FxHashSet as HashSet}; impl Ssa { /// Map arrays with the last instruction that uses it @@ -16,28 +17,42 @@ impl Ssa { #[tracing::instrument(level = "trace", skip(self))] pub(crate) fn array_set_optimization(mut self) -> Self { for func in self.functions.values_mut() { - let mut reachable_blocks = func.reachable_blocks(); - let block = if !func.runtime().is_entry_point() { + let reachable_blocks = func.reachable_blocks(); + + if !func.runtime().is_entry_point() { assert_eq!(reachable_blocks.len(), 1, "Expected there to be 1 block remaining in Acir function for array_set optimization"); - reachable_blocks.pop_first().unwrap() - } else { - // We only apply the array set optimization in the return block of Brillig functions - func.find_last_block() - }; + } + let mut array_to_last_use = HashMap::default(); + let mut instructions_to_update = HashSet::default(); + let mut arrays_from_load = HashSet::default(); - let instructions_to_update = analyze_last_uses(&func.dfg, block); - make_mutable(&mut func.dfg, block, instructions_to_update); + for block in reachable_blocks.iter() { + analyze_last_uses( + &func.dfg, + *block, + &mut array_to_last_use, + &mut instructions_to_update, + &mut arrays_from_load, + ); + } + for block in reachable_blocks { + make_mutable(&mut func.dfg, block, &instructions_to_update); + } } self } } -/// Returns the set of ArraySet instructions that can be made mutable +/// Builds the set of ArraySet instructions that can be made mutable /// because their input value is unused elsewhere afterward. -fn analyze_last_uses(dfg: &DataFlowGraph, block_id: BasicBlockId) -> FxHashSet { +fn analyze_last_uses( + dfg: &DataFlowGraph, + block_id: BasicBlockId, + array_to_last_use: &mut HashMap, + instructions_that_can_be_made_mutable: &mut HashSet, + arrays_from_load: &mut HashSet, +) { let block = &dfg[block_id]; - let mut array_to_last_use = HashMap::default(); - let mut instructions_that_can_be_made_mutable = FxHashSet::default(); for instruction_id in block.instructions() { match &dfg[*instruction_id] { @@ -54,7 +69,22 @@ fn analyze_last_uses(dfg: &DataFlowGraph, block_id: BasicBlockId) -> FxHashSet { for argument in arguments { @@ -68,18 +98,22 @@ fn analyze_last_uses(dfg: &DataFlowGraph, block_id: BasicBlockId) -> FxHashSet { + let result = dfg.instruction_results(*instruction_id)[0]; + if matches!(dfg.type_of_value(result), Array { .. } | Slice { .. }) { + arrays_from_load.insert(result); + } + } _ => (), } } - - instructions_that_can_be_made_mutable } /// Make each ArraySet instruction in `instructions_to_update` mutable. fn make_mutable( dfg: &mut DataFlowGraph, block_id: BasicBlockId, - instructions_to_update: FxHashSet, + instructions_to_update: &HashSet, ) { if instructions_to_update.is_empty() { return; @@ -105,3 +139,129 @@ fn make_mutable( *dfg[block_id].instructions_mut() = instructions; } + +#[cfg(test)] +mod tests { + use std::sync::Arc; + + use im::vector; + + use crate::ssa::{ + function_builder::FunctionBuilder, + ir::{ + function::RuntimeType, + instruction::{BinaryOp, Instruction}, + map::Id, + types::Type, + }, + }; + + #[test] + fn array_set_in_loop_with_conditional_clone() { + // We want to make sure that we do not mark a single array set mutable which is loaded + // from and cloned in a loop. If the array is inadvertently marked mutable, and is cloned in a previous iteration + // of the loop, its clone will also be altered. + // + // acir(inline) fn main f0 { + // b0(): + // v2 = allocate + // store [Field 0, Field 0, Field 0, Field 0, Field 0] at v2 + // v3 = allocate + // store [Field 0, Field 0, Field 0, Field 0, Field 0] at v3 + // jmp b1(u32 0) + // b1(v5: u32): + // v7 = lt v5, u32 5 + // jmpif v7 then: b3, else: b2 + // b3(): + // v8 = eq v5, u32 5 + // jmpif v8 then: b4, else: b5 + // b4(): + // v9 = load v2 + // store v9 at v3 + // jmp b5() + // b5(): + // v10 = load v2 + // v12 = array_set v10, index v5, value Field 20 + // store v12 at v2 + // v14 = add v5, u32 1 + // jmp b1(v14) + // b2(): + // return + // } + let main_id = Id::test_new(0); + let mut builder = FunctionBuilder::new("main".into(), main_id); + builder.set_runtime(RuntimeType::Brillig); + + let array_type = Type::Array(Arc::new(vec![Type::field()]), 5); + let zero = builder.field_constant(0u128); + let array_constant = + builder.array_constant(vector![zero, zero, zero, zero, zero], array_type.clone()); + + let v2 = builder.insert_allocate(array_type.clone()); + + builder.insert_store(v2, array_constant); + + let v3 = builder.insert_allocate(array_type.clone()); + builder.insert_store(v3, array_constant); + + let b1 = builder.insert_block(); + let zero_u32 = builder.numeric_constant(0u128, Type::unsigned(32)); + builder.terminate_with_jmp(b1, vec![zero_u32]); + + // Loop header + builder.switch_to_block(b1); + let v5 = builder.add_block_parameter(b1, Type::unsigned(32)); + let five = builder.numeric_constant(5u128, Type::unsigned(32)); + let v7 = builder.insert_binary(v5, BinaryOp::Lt, five); + + let b2 = builder.insert_block(); + let b3 = builder.insert_block(); + let b4 = builder.insert_block(); + let b5 = builder.insert_block(); + builder.terminate_with_jmpif(v7, b3, b2); + + // Loop body + // b3 is the if statement conditional + builder.switch_to_block(b3); + let two = builder.numeric_constant(5u128, Type::unsigned(32)); + let v8 = builder.insert_binary(v5, BinaryOp::Eq, two); + builder.terminate_with_jmpif(v8, b4, b5); + + // b4 is the rest of the loop after the if statement + builder.switch_to_block(b4); + let v9 = builder.insert_load(v2, array_type.clone()); + builder.insert_store(v3, v9); + builder.terminate_with_jmp(b5, vec![]); + + builder.switch_to_block(b5); + let v10 = builder.insert_load(v2, array_type.clone()); + let twenty = builder.field_constant(20u128); + let v12 = builder.insert_array_set(v10, v5, twenty); + builder.insert_store(v2, v12); + let one = builder.numeric_constant(1u128, Type::unsigned(32)); + let v14 = builder.insert_binary(v5, BinaryOp::Add, one); + builder.terminate_with_jmp(b1, vec![v14]); + + builder.switch_to_block(b2); + builder.terminate_with_return(vec![]); + + let ssa = builder.finish(); + // We expect the same result as above + let ssa = ssa.array_set_optimization(); + + let main = ssa.main(); + assert_eq!(main.reachable_blocks().len(), 6); + + let array_set_instructions = main.dfg[b5] + .instructions() + .iter() + .filter(|instruction| matches!(&main.dfg[**instruction], Instruction::ArraySet { .. })) + .collect::>(); + + assert_eq!(array_set_instructions.len(), 1); + if let Instruction::ArraySet { mutable, .. } = &main.dfg[*array_set_instructions[0]] { + // The single array set should not be marked mutable + assert!(!mutable); + } + } +} From fd03ea0824835e1d1ae6c1777ae3e1dcfff6ad7d Mon Sep 17 00:00:00 2001 From: Tom French <15848336+TomAFrench@users.noreply.github.com> Date: Fri, 27 Sep 2024 00:43:02 +0100 Subject: [PATCH 11/36] chore: enable tests on aztec-nr and contracts (#6162) # Description ## Problem\* Resolves ## Summary\* Testing these again now that aztec-nr doesn't have 40k warnings. ## Additional Context ## Documentation\* Check one: - [ ] No documentation needed. - [ ] Documentation included in this PR. - [ ] **[For Experimental Features]** 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. --- .github/workflows/mirror-external_libs.yml | 8 -------- .github/workflows/test-js-packages.yml | 14 +++++--------- 2 files changed, 5 insertions(+), 17 deletions(-) delete mode 100644 .github/workflows/mirror-external_libs.yml diff --git a/.github/workflows/mirror-external_libs.yml b/.github/workflows/mirror-external_libs.yml deleted file mode 100644 index e577ac0ed92..00000000000 --- a/.github/workflows/mirror-external_libs.yml +++ /dev/null @@ -1,8 +0,0 @@ -name: Mirror Repositories -on: - workflow_dispatch: {} -jobs: - lint: - runs-on: ubuntu-latest - steps: - - run: echo Dummy workflow TODO \ No newline at end of file diff --git a/.github/workflows/test-js-packages.yml b/.github/workflows/test-js-packages.yml index e45a482cc59..ff894f061ee 100644 --- a/.github/workflows/test-js-packages.yml +++ b/.github/workflows/test-js-packages.yml @@ -519,16 +519,12 @@ jobs: fail-fast: false matrix: project: - # Disabled as these are currently failing with many visibility errors - # - { repo: AztecProtocol/aztec-nr, path: ./ } - # - { repo: AztecProtocol/aztec-packages, path: ./noir-projects/noir-contracts } - # Disabled as aztec-packages requires a setup-step in order to generate a `Nargo.toml` - #- { repo: AztecProtocol/aztec-packages, path: ./noir-projects/noir-protocol-circuits } + # Disabled as these are currently failing with many visibility errors + - { repo: AztecProtocol/aztec-nr, path: ./ } + - { repo: AztecProtocol/aztec-packages, path: ./noir-projects/noir-contracts } + # Disabled as aztec-packages requires a setup-step in order to generate a `Nargo.toml` + #- { repo: AztecProtocol/aztec-packages, path: ./noir-projects/noir-protocol-circuits } - { repo: zac-williamson/noir-edwards, path: ./, ref: 037e44b2ee8557c51f6aef9bb9d63ea9e32722d1 } - # TODO: Enable these once they're passing against master again. - # - { repo: zac-williamson/noir-bignum, path: ./, ref: 030c2acce1e6b97c44a3bbbf3429ed96f20d72d3 } - # - { repo: vlayer-xyz/monorepo, path: ./, ref: ee46af88c025863872234eb05d890e1e447907cb } - # - { repo: hashcloak/noir-bigint, path: ./, ref: 940ddba3a5201b508e7b37a2ef643551afcf5ed8 } name: Check external repo - ${{ matrix.project.repo }} steps: - name: Checkout From 82b89c421da80b719922416d574c1bbaa73d55b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Rodr=C3=ADguez?= Date: Fri, 27 Sep 2024 12:14:55 +0200 Subject: [PATCH 12/36] fix: Pass radix directly to the blackbox (#6164) # Description ## Problem\* Avoids creating an extra constant for to radix when we already have a variable for it. ## Summary\* ## Additional Context ## Documentation\* Check one: - [x] No documentation needed. - [ ] Documentation included in this PR. - [ ] **[For Experimental Features]** Documentation to be submitted in a separate PR. # PR Checklist\* - [x] I have tested the changes locally. - [x] I have formatted the changes with [Prettier](https://prettier.io/) and/or `cargo fmt` on default settings. --- .../src/brillig/brillig_gen/brillig_block.rs | 15 ++++++--------- .../src/brillig/brillig_ir/codegen_intrinsic.rs | 8 +++----- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs index 8929122f515..e764c81b023 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs @@ -553,14 +553,7 @@ impl<'block> BrilligBlock<'block> { let results = dfg.instruction_results(instruction_id); let source = self.convert_ssa_single_addr_value(arguments[0], dfg); - - let radix: u32 = dfg - .get_numeric_constant(arguments[1]) - .expect("Radix should be known") - .try_to_u64() - .expect("Radix should fit in u64") - .try_into() - .expect("Radix should be u32"); + let radix = self.convert_ssa_single_addr_value(arguments[1], dfg); let target_array = self .variables @@ -595,13 +588,17 @@ impl<'block> BrilligBlock<'block> { ) .extract_array(); + let two = self.brillig_context.make_usize_constant_instruction(2_usize.into()); + self.brillig_context.codegen_to_radix( source, target_array, - 2, + two, matches!(endianness, Endian::Big), true, ); + + self.brillig_context.deallocate_single_addr(two); } // `Intrinsic::AsWitness` is used to provide hints to acir-gen on optimal expression splitting. diff --git a/compiler/noirc_evaluator/src/brillig/brillig_ir/codegen_intrinsic.rs b/compiler/noirc_evaluator/src/brillig/brillig_ir/codegen_intrinsic.rs index c9c31267d7b..5f4781788f5 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_ir/codegen_intrinsic.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_ir/codegen_intrinsic.rs @@ -68,21 +68,20 @@ impl BrilligContext< &mut self, source_field: SingleAddrVariable, target_array: BrilligArray, - radix: u32, + radix: SingleAddrVariable, big_endian: bool, output_bits: bool, // If true will generate bit limbs, if false will generate byte limbs ) { assert!(source_field.bit_size == F::max_num_bits()); + assert!(radix.bit_size == 32); self.codegen_initialize_array(target_array); let heap_array = self.codegen_brillig_array_to_heap_array(target_array); - let radix_var = self.make_constant_instruction(F::from(radix as u128), 32); - self.black_box_op_instruction(BlackBoxOp::ToRadix { input: source_field.address, - radix: radix_var.address, + radix: radix.address, output: heap_array, output_bits, }); @@ -93,6 +92,5 @@ impl BrilligContext< self.deallocate_single_addr(items_len); } self.deallocate_register(heap_array.pointer); - self.deallocate_register(radix_var.address); } } From 877b806ee02cb640472c6bb2b1ed7bc76b861a9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Venturo?= Date: Fri, 27 Sep 2024 08:34:10 -0300 Subject: [PATCH 13/36] feat: expose `derived_generators` and `pedersen_commitment_with_separator` from the stdlib (#6154) These are functions that we need to import directly in `aztec-nr`, from discussion with @vezenovm there doesn't seem to be a good reason to not export them. --------- Co-authored-by: Tom French <15848336+TomAFrench@users.noreply.github.com> --- noir_stdlib/src/hash/mod.nr | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/noir_stdlib/src/hash/mod.nr b/noir_stdlib/src/hash/mod.nr index 93bce3c20e1..c69c3f9c49e 100644 --- a/noir_stdlib/src/hash/mod.nr +++ b/noir_stdlib/src/hash/mod.nr @@ -36,7 +36,7 @@ pub fn pedersen_hash_with_separator(input: [Field; N], separator: u3 __pedersen_hash_with_separator(input, separator) } -fn pedersen_commitment_with_separator(input: [Field; N], separator: u32) -> EmbeddedCurvePoint { +pub fn pedersen_commitment_with_separator(input: [Field; N], separator: u32) -> EmbeddedCurvePoint { let value = __pedersen_commitment_with_separator(input, separator); if (value[0] == 0) & (value[1] == 0) { EmbeddedCurvePoint { x: 0, y: 0, is_infinite: true } @@ -88,7 +88,7 @@ fn __pedersen_hash_with_separator(input: [Field; N], separator: u32) fn __pedersen_commitment_with_separator(input: [Field; N], separator: u32) -> [Field; 2] {} #[field(bn254)] -fn derive_generators(domain_separator_bytes: [u8; M], starting_index: u32) -> [EmbeddedCurvePoint; N] { +pub fn derive_generators(domain_separator_bytes: [u8; M], starting_index: u32) -> [EmbeddedCurvePoint; N] { crate::assert_constant(domain_separator_bytes); // TODO(https://github.com/noir-lang/noir/issues/5672): Add back assert_constant on starting_index __derive_generators(domain_separator_bytes, starting_index) @@ -102,10 +102,10 @@ fn __derive_generators( ) -> [EmbeddedCurvePoint; N] {} #[field(bn254)] - // Same as from_field but: - // does not assert the limbs are 128 bits - // does not assert the decomposition does not overflow the EmbeddedCurveScalar - fn from_field_unsafe(scalar: Field) -> EmbeddedCurveScalar { +// Same as from_field but: +// does not assert the limbs are 128 bits +// does not assert the decomposition does not overflow the EmbeddedCurveScalar +fn from_field_unsafe(scalar: Field) -> EmbeddedCurveScalar { let (xlo, xhi) = unsafe { crate::field::bn254::decompose_hint(scalar) }; @@ -419,4 +419,3 @@ fn assert_pedersen() { } ); } - From fcdbcb91afb18771cbb5ee48628e171845f22f5f Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Fri, 27 Sep 2024 14:22:27 -0300 Subject: [PATCH 14/36] feat: visibility for modules (#6165) # Description ## Problem Part of #4515 ## Summary ## Additional Context I made most of the stdlib modules public. These are all except the test modules and some modules that I thought were just implementation details of some other modules. I'm adding very short documentation for each of these. I'm thinking that once we get all visibility done it should be documented in its own section, with examples. ## Documentation Check one: - [ ] No documentation needed. - [x] Documentation included in this PR. - [ ] **[For Experimental Features]** Documentation to be submitted in a separate PR. # PR Checklist - [x] I have tested the changes locally. - [x] I have formatted the changes with [Prettier](https://prettier.io/) and/or `cargo fmt` on default settings. --------- Co-authored-by: Tom French <15848336+TomAFrench@users.noreply.github.com> --- compiler/noirc_frontend/src/ast/statement.rs | 1 + .../src/hir/def_collector/dc_mod.rs | 9 +- .../src/hir/def_map/module_data.rs | 3 +- compiler/noirc_frontend/src/parser/mod.rs | 3 + compiler/noirc_frontend/src/parser/parser.rs | 18 ++- compiler/noirc_frontend/src/tests.rs | 81 +------------ .../noirc_frontend/src/tests/visibility.rs | 113 ++++++++++++++++++ .../noir/modules_packages_crates/modules.md | 12 +- noir_stdlib/src/collections/mod.nr | 8 +- noir_stdlib/src/ec/consts/mod.nr | 2 +- noir_stdlib/src/ec/mod.nr | 8 +- noir_stdlib/src/ec/montcurve.nr | 4 +- noir_stdlib/src/ec/swcurve.nr | 4 +- noir_stdlib/src/ec/tecurve.nr | 4 +- noir_stdlib/src/field/mod.nr | 2 +- noir_stdlib/src/hash/mod.nr | 17 ++- noir_stdlib/src/hash/poseidon/bn254.nr | 4 +- noir_stdlib/src/hash/poseidon/mod.nr | 2 +- noir_stdlib/src/lib.nr | 62 +++++----- noir_stdlib/src/meta/mod.nr | 28 ++--- noir_stdlib/src/ops/mod.nr | 4 +- .../comptime_module/src/main.nr | 6 +- .../code_action/remove_unused_import.rs | 4 +- tooling/lsp/src/requests/completion/tests.rs | 30 ++--- tooling/nargo_fmt/src/visitor/item.rs | 8 +- tooling/nargo_fmt/tests/expected/module.nr | 4 + tooling/nargo_fmt/tests/input/module.nr | 4 + 27 files changed, 260 insertions(+), 185 deletions(-) create mode 100644 compiler/noirc_frontend/src/tests/visibility.rs diff --git a/compiler/noirc_frontend/src/ast/statement.rs b/compiler/noirc_frontend/src/ast/statement.rs index 38cf2cb1d80..4abea8cebb4 100644 --- a/compiler/noirc_frontend/src/ast/statement.rs +++ b/compiler/noirc_frontend/src/ast/statement.rs @@ -294,6 +294,7 @@ pub trait Recoverable { #[derive(Debug, PartialEq, Eq, Clone)] pub struct ModuleDeclaration { + pub visibility: ItemVisibility, pub ident: Ident, pub outer_attributes: Vec, } diff --git a/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs b/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs index 162d39e0adb..b530e023152 100644 --- a/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs +++ b/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs @@ -381,6 +381,7 @@ impl<'a> ModCollector<'a> { let trait_id = match self.push_child_module( context, &name, + ItemVisibility::Public, Location::new(name.span(), self.file_id), Vec::new(), Vec::new(), @@ -612,6 +613,7 @@ impl<'a> ModCollector<'a> { match self.push_child_module( context, &submodule.name, + submodule.visibility, Location::new(submodule.name.span(), file_id), submodule.outer_attributes.clone(), submodule.contents.inner_attributes.clone(), @@ -711,6 +713,7 @@ impl<'a> ModCollector<'a> { match self.push_child_module( context, &mod_decl.ident, + mod_decl.visibility, Location::new(Span::empty(0), child_file_id), mod_decl.outer_attributes.clone(), ast.inner_attributes.clone(), @@ -761,6 +764,7 @@ impl<'a> ModCollector<'a> { &mut self, context: &mut Context, mod_name: &Ident, + visibility: ItemVisibility, mod_location: Location, outer_attributes: Vec, inner_attributes: Vec, @@ -772,6 +776,7 @@ impl<'a> ModCollector<'a> { &mut self.def_collector.def_map, self.module_id, mod_name, + visibility, mod_location, outer_attributes, inner_attributes, @@ -806,6 +811,7 @@ fn push_child_module( def_map: &mut CrateDefMap, parent: LocalModuleId, mod_name: &Ident, + visibility: ItemVisibility, mod_location: Location, outer_attributes: Vec, inner_attributes: Vec, @@ -840,7 +846,7 @@ fn push_child_module( // the struct name. if add_to_parent_scope { if let Err((first_def, second_def)) = - modules[parent.0].declare_child_module(mod_name.to_owned(), mod_id) + modules[parent.0].declare_child_module(mod_name.to_owned(), visibility, mod_id) { let err = DefCollectorErrorKind::Duplicate { typ: DuplicateType::Module, @@ -952,6 +958,7 @@ pub fn collect_struct( def_map, module_id, &name, + ItemVisibility::Public, location, Vec::new(), Vec::new(), diff --git a/compiler/noirc_frontend/src/hir/def_map/module_data.rs b/compiler/noirc_frontend/src/hir/def_map/module_data.rs index 149b1977925..ff36bcf27d6 100644 --- a/compiler/noirc_frontend/src/hir/def_map/module_data.rs +++ b/compiler/noirc_frontend/src/hir/def_map/module_data.rs @@ -135,9 +135,10 @@ impl ModuleData { pub fn declare_child_module( &mut self, name: Ident, + visibility: ItemVisibility, child_id: ModuleId, ) -> Result<(), (Ident, Ident)> { - self.declare(name, ItemVisibility::Public, child_id.into(), None) + self.declare(name, visibility, child_id.into(), None) } pub fn find_func_with_name(&self, name: &Ident) -> Option { diff --git a/compiler/noirc_frontend/src/parser/mod.rs b/compiler/noirc_frontend/src/parser/mod.rs index 1b6d573560d..ad6dd1459e9 100644 --- a/compiler/noirc_frontend/src/parser/mod.rs +++ b/compiler/noirc_frontend/src/parser/mod.rs @@ -366,6 +366,7 @@ pub enum ItemKind { /// These submodules always share the same file as some larger ParsedModule #[derive(Clone, Debug)] pub struct ParsedSubModule { + pub visibility: ItemVisibility, pub name: Ident, pub contents: ParsedModule, pub outer_attributes: Vec, @@ -375,6 +376,7 @@ pub struct ParsedSubModule { impl ParsedSubModule { pub fn into_sorted(self) -> SortedSubModule { SortedSubModule { + visibility: self.visibility, name: self.name, contents: self.contents.into_sorted(), outer_attributes: self.outer_attributes, @@ -398,6 +400,7 @@ impl std::fmt::Display for SortedSubModule { #[derive(Clone)] pub struct SortedSubModule { pub name: Ident, + pub visibility: ItemVisibility, pub contents: SortedModule, pub outer_attributes: Vec, pub is_contract: bool, diff --git a/compiler/noirc_frontend/src/parser/parser.rs b/compiler/noirc_frontend/src/parser/parser.rs index d902f0a3b5a..df656dc5a7d 100644 --- a/compiler/noirc_frontend/src/parser/parser.rs +++ b/compiler/noirc_frontend/src/parser/parser.rs @@ -262,14 +262,16 @@ fn submodule( module_parser: impl NoirParser, ) -> impl NoirParser { attributes() + .then(item_visibility()) .then_ignore(keyword(Keyword::Mod)) .then(ident()) .then_ignore(just(Token::LeftBrace)) .then(module_parser) .then_ignore(just(Token::RightBrace)) - .validate(|((attributes, name), contents), span, emit| { + .validate(|(((attributes, visibility), name), contents), span, emit| { let attributes = validate_secondary_attributes(attributes, span, emit); TopLevelStatementKind::SubModule(ParsedSubModule { + visibility, name, contents, outer_attributes: attributes, @@ -283,14 +285,16 @@ fn contract( module_parser: impl NoirParser, ) -> impl NoirParser { attributes() + .then(item_visibility()) .then_ignore(keyword(Keyword::Contract)) .then(ident()) .then_ignore(just(Token::LeftBrace)) .then(module_parser) .then_ignore(just(Token::RightBrace)) - .validate(|((attributes, name), contents), span, emit| { + .validate(|(((attributes, visibility), name), contents), span, emit| { let attributes = validate_secondary_attributes(attributes, span, emit); TopLevelStatementKind::SubModule(ParsedSubModule { + visibility, name, contents, outer_attributes: attributes, @@ -431,10 +435,14 @@ fn optional_type_annotation<'a>() -> impl NoirParser + 'a { } fn module_declaration() -> impl NoirParser { - attributes().then_ignore(keyword(Keyword::Mod)).then(ident()).validate( - |(attributes, ident), span, emit| { + attributes().then(item_visibility()).then_ignore(keyword(Keyword::Mod)).then(ident()).validate( + |((attributes, visibility), ident), span, emit| { let attributes = validate_secondary_attributes(attributes, span, emit); - TopLevelStatementKind::Module(ModuleDeclaration { ident, outer_attributes: attributes }) + TopLevelStatementKind::Module(ModuleDeclaration { + visibility, + ident, + outer_attributes: attributes, + }) }, ) } diff --git a/compiler/noirc_frontend/src/tests.rs b/compiler/noirc_frontend/src/tests.rs index 9b55e689eed..63013036ca9 100644 --- a/compiler/noirc_frontend/src/tests.rs +++ b/compiler/noirc_frontend/src/tests.rs @@ -6,6 +6,7 @@ mod name_shadowing; mod references; mod turbofish; mod unused_items; +mod visibility; // XXX: These tests repeat a lot of code // what we should do is have test cases which are passed to a test harness @@ -3092,29 +3093,6 @@ fn use_numeric_generic_in_trait_method() { assert_eq!(errors.len(), 0); } -#[test] -fn errors_once_on_unused_import_that_is_not_accessible() { - // Tests that we don't get an "unused import" here given that the import is not accessible - let src = r#" - mod moo { - struct Foo {} - } - use moo::Foo; - fn main() { - let _ = Foo {}; - } - "#; - - let errors = get_program_errors(src); - assert_eq!(errors.len(), 1); - assert!(matches!( - errors[0].0, - CompilationError::DefinitionError(DefCollectorErrorKind::PathResolutionError( - PathResolutionError::Private { .. } - )) - )); -} - #[test] fn trait_unconstrained_methods_typechecked_correctly() { // This test checks that we properly track whether a method has been declared as unconstrained on the trait definition @@ -3143,60 +3121,3 @@ fn trait_unconstrained_methods_typechecked_correctly() { println!("{errors:?}"); assert_eq!(errors.len(), 0); } - -#[test] -fn errors_if_type_alias_aliases_more_private_type() { - let src = r#" - struct Foo {} - pub type Bar = Foo; - - pub fn no_unused_warnings(_b: Bar) { - let _ = Foo {}; - } - - fn main() {} - "#; - - let errors = get_program_errors(src); - assert_eq!(errors.len(), 1); - - let CompilationError::ResolverError(ResolverError::TypeIsMorePrivateThenItem { - typ, item, .. - }) = &errors[0].0 - else { - panic!("Expected an unused item error"); - }; - - assert_eq!(typ, "Foo"); - assert_eq!(item, "Bar"); -} - -#[test] -fn errors_if_type_alias_aliases_more_private_type_in_generic() { - let src = r#" - pub struct Generic { value: T } - - struct Foo {} - pub type Bar = Generic; - - pub fn no_unused_warnings(_b: Bar) { - let _ = Foo {}; - let _ = Generic { value: 1 }; - } - - fn main() {} - "#; - - let errors = get_program_errors(src); - assert_eq!(errors.len(), 1); - - let CompilationError::ResolverError(ResolverError::TypeIsMorePrivateThenItem { - typ, item, .. - }) = &errors[0].0 - else { - panic!("Expected an unused item error"); - }; - - assert_eq!(typ, "Foo"); - assert_eq!(item, "Bar"); -} diff --git a/compiler/noirc_frontend/src/tests/visibility.rs b/compiler/noirc_frontend/src/tests/visibility.rs new file mode 100644 index 00000000000..e6c2680ea19 --- /dev/null +++ b/compiler/noirc_frontend/src/tests/visibility.rs @@ -0,0 +1,113 @@ +use crate::{ + hir::{ + def_collector::{dc_crate::CompilationError, errors::DefCollectorErrorKind}, + resolution::{errors::ResolverError, import::PathResolutionError}, + }, + tests::get_program_errors, +}; + +#[test] +fn errors_once_on_unused_import_that_is_not_accessible() { + // Tests that we don't get an "unused import" here given that the import is not accessible + let src = r#" + mod moo { + struct Foo {} + } + use moo::Foo; + fn main() { + let _ = Foo {}; + } + "#; + + let errors = get_program_errors(src); + assert_eq!(errors.len(), 1); + assert!(matches!( + errors[0].0, + CompilationError::DefinitionError(DefCollectorErrorKind::PathResolutionError( + PathResolutionError::Private { .. } + )) + )); +} + +#[test] +fn errors_if_type_alias_aliases_more_private_type() { + let src = r#" + struct Foo {} + pub type Bar = Foo; + + pub fn no_unused_warnings(_b: Bar) { + let _ = Foo {}; + } + + fn main() {} + "#; + + let errors = get_program_errors(src); + assert_eq!(errors.len(), 1); + + let CompilationError::ResolverError(ResolverError::TypeIsMorePrivateThenItem { + typ, item, .. + }) = &errors[0].0 + else { + panic!("Expected an unused item error"); + }; + + assert_eq!(typ, "Foo"); + assert_eq!(item, "Bar"); +} + +#[test] +fn errors_if_type_alias_aliases_more_private_type_in_generic() { + let src = r#" + pub struct Generic { value: T } + + struct Foo {} + pub type Bar = Generic; + + pub fn no_unused_warnings(_b: Bar) { + let _ = Foo {}; + let _ = Generic { value: 1 }; + } + + fn main() {} + "#; + + let errors = get_program_errors(src); + assert_eq!(errors.len(), 1); + + let CompilationError::ResolverError(ResolverError::TypeIsMorePrivateThenItem { + typ, item, .. + }) = &errors[0].0 + else { + panic!("Expected an unused item error"); + }; + + assert_eq!(typ, "Foo"); + assert_eq!(item, "Bar"); +} + +#[test] +fn errors_if_trying_to_access_public_function_inside_private_module() { + let src = r#" + mod foo { + mod bar { + pub fn baz() {} + } + } + fn main() { + foo::bar::baz() + } + "#; + + let errors = get_program_errors(src); + assert_eq!(errors.len(), 2); // There's a bug that duplicates this error + + let CompilationError::ResolverError(ResolverError::PathResolutionError( + PathResolutionError::Private(ident), + )) = &errors[0].0 + else { + panic!("Expected a private error"); + }; + + assert_eq!(ident.to_string(), "bar"); +} diff --git a/docs/docs/noir/modules_packages_crates/modules.md b/docs/docs/noir/modules_packages_crates/modules.md index d21b009be3b..05399c38b4c 100644 --- a/docs/docs/noir/modules_packages_crates/modules.md +++ b/docs/docs/noir/modules_packages_crates/modules.md @@ -208,4 +208,14 @@ fn main() { } ``` -In this example, the module `some_module` re-exports two public names defined in `foo`. \ No newline at end of file +In this example, the module `some_module` re-exports two public names defined in `foo`. + +### Visibility + +By default, like functions, modules are private to the module (or crate) the exist in. You can use `pub` +to make the module public or `pub(crate)` to make it public to just its crate: + +```rust +// This module is now public and can be seen by other crates. +pub mod foo; +``` \ No newline at end of file diff --git a/noir_stdlib/src/collections/mod.nr b/noir_stdlib/src/collections/mod.nr index 29f3e8cc854..d11e9f2febb 100644 --- a/noir_stdlib/src/collections/mod.nr +++ b/noir_stdlib/src/collections/mod.nr @@ -1,4 +1,4 @@ -mod vec; -mod bounded_vec; -mod map; -mod umap; +pub mod vec; +pub mod bounded_vec; +pub mod map; +pub mod umap; diff --git a/noir_stdlib/src/ec/consts/mod.nr b/noir_stdlib/src/ec/consts/mod.nr index f4d67e7a92c..73c594c6a26 100644 --- a/noir_stdlib/src/ec/consts/mod.nr +++ b/noir_stdlib/src/ec/consts/mod.nr @@ -1 +1 @@ -mod te; +pub mod te; diff --git a/noir_stdlib/src/ec/mod.nr b/noir_stdlib/src/ec/mod.nr index 3c1ba87eb9f..112f39f74eb 100644 --- a/noir_stdlib/src/ec/mod.nr +++ b/noir_stdlib/src/ec/mod.nr @@ -2,10 +2,10 @@ // Overview // ======== // The following three elliptic curve representations are admissible: -mod tecurve; // Twisted Edwards curves -mod swcurve; // Elliptic curves in Short Weierstrass form -mod montcurve; // Montgomery curves -mod consts; // Commonly used curve presets +pub mod tecurve; // Twisted Edwards curves +pub mod swcurve; // Elliptic curves in Short Weierstrass form +pub mod montcurve; // Montgomery curves +pub mod consts; // Commonly used curve presets // // Note that Twisted Edwards and Montgomery curves are (birationally) equivalent, so that // they may be freely converted between one another, whereas Short Weierstrass curves are diff --git a/noir_stdlib/src/ec/montcurve.nr b/noir_stdlib/src/ec/montcurve.nr index 395e8528b45..b8077b6b639 100644 --- a/noir_stdlib/src/ec/montcurve.nr +++ b/noir_stdlib/src/ec/montcurve.nr @@ -1,4 +1,4 @@ -mod affine { +pub mod affine { // Affine representation of Montgomery curves // Points are represented by two-dimensional Cartesian coordinates. // All group operations are induced by those of the corresponding Twisted Edwards curve. @@ -210,7 +210,7 @@ mod affine { } } } -mod curvegroup { +pub mod curvegroup { // Affine representation of Montgomery curves // Points are represented by three-dimensional projective (homogeneous) coordinates. // All group operations are induced by those of the corresponding Twisted Edwards curve. diff --git a/noir_stdlib/src/ec/swcurve.nr b/noir_stdlib/src/ec/swcurve.nr index 839069e1fd8..9c40ddd1adc 100644 --- a/noir_stdlib/src/ec/swcurve.nr +++ b/noir_stdlib/src/ec/swcurve.nr @@ -1,4 +1,4 @@ -mod affine { +pub mod affine { // Affine representation of Short Weierstrass curves // Points are represented by two-dimensional Cartesian coordinates. // Group operations are implemented in terms of those in CurveGroup (in this case, extended Twisted Edwards) coordinates @@ -186,7 +186,7 @@ mod affine { } } -mod curvegroup { +pub mod curvegroup { // CurveGroup representation of Weierstrass curves // Points are represented by three-dimensional Jacobian coordinates. // See for details. diff --git a/noir_stdlib/src/ec/tecurve.nr b/noir_stdlib/src/ec/tecurve.nr index f9cdf83aab9..c37b7c94a54 100644 --- a/noir_stdlib/src/ec/tecurve.nr +++ b/noir_stdlib/src/ec/tecurve.nr @@ -1,4 +1,4 @@ -mod affine { +pub mod affine { // Affine coordinate representation of Twisted Edwards curves // Points are represented by two-dimensional Cartesian coordinates. // Group operations are implemented in terms of those in CurveGroup (in this case, extended Twisted Edwards) coordinates @@ -193,7 +193,7 @@ mod affine { } } } -mod curvegroup { +pub mod curvegroup { // CurveGroup coordinate representation of Twisted Edwards curves // Points are represented by four-dimensional projective coordinates, viz. extended Twisted Edwards coordinates. // See section 3 of for details. diff --git a/noir_stdlib/src/field/mod.nr b/noir_stdlib/src/field/mod.nr index e1d08c6c230..93245e18072 100644 --- a/noir_stdlib/src/field/mod.nr +++ b/noir_stdlib/src/field/mod.nr @@ -1,4 +1,4 @@ -mod bn254; +pub mod bn254; use bn254::lt as bn254_lt; impl Field { diff --git a/noir_stdlib/src/hash/mod.nr b/noir_stdlib/src/hash/mod.nr index c69c3f9c49e..1b37a07f6c9 100644 --- a/noir_stdlib/src/hash/mod.nr +++ b/noir_stdlib/src/hash/mod.nr @@ -1,9 +1,9 @@ -mod poseidon; -mod mimc; -mod poseidon2; -mod keccak; -mod sha256; -mod sha512; +pub mod poseidon; +pub mod mimc; +pub mod poseidon2; +pub mod keccak; +pub mod sha256; +pub mod sha512; use crate::default::Default; use crate::uint128::U128; @@ -96,10 +96,7 @@ pub fn derive_generators(domain_separator_bytes: [u8; M] #[builtin(derive_pedersen_generators)] #[field(bn254)] -fn __derive_generators( - domain_separator_bytes: [u8; M], - starting_index: u32 -) -> [EmbeddedCurvePoint; N] {} +fn __derive_generators(domain_separator_bytes: [u8; M], starting_index: u32) -> [EmbeddedCurvePoint; N] {} #[field(bn254)] // Same as from_field but: diff --git a/noir_stdlib/src/hash/poseidon/bn254.nr b/noir_stdlib/src/hash/poseidon/bn254.nr index a1c78a9b31c..ce4b904ed5c 100644 --- a/noir_stdlib/src/hash/poseidon/bn254.nr +++ b/noir_stdlib/src/hash/poseidon/bn254.nr @@ -1,6 +1,6 @@ // Instantiations of Poseidon constants, permutations and sponge for prime field of the same order as BN254 -mod perm; -mod consts; +pub mod perm; +pub mod consts; use crate::hash::poseidon::absorb; diff --git a/noir_stdlib/src/hash/poseidon/mod.nr b/noir_stdlib/src/hash/poseidon/mod.nr index 8a1025ada71..9553d352d28 100644 --- a/noir_stdlib/src/hash/poseidon/mod.nr +++ b/noir_stdlib/src/hash/poseidon/mod.nr @@ -1,4 +1,4 @@ -mod bn254; // Instantiations of Poseidon for prime field of the same order as BN254 +pub mod bn254; // Instantiations of Poseidon for prime field of the same order as BN254 use crate::hash::Hasher; use crate::default::Default; diff --git a/noir_stdlib/src/lib.nr b/noir_stdlib/src/lib.nr index 714079d306a..3d1dd3e90eb 100644 --- a/noir_stdlib/src/lib.nr +++ b/noir_stdlib/src/lib.nr @@ -1,34 +1,34 @@ -mod hash; -mod aes128; -mod array; -mod slice; -mod merkle; -mod schnorr; -mod ecdsa_secp256k1; -mod ecdsa_secp256r1; -mod eddsa; -mod embedded_curve_ops; -mod sha256; -mod sha512; -mod field; -mod ec; -mod collections; -mod compat; -mod convert; -mod option; -mod string; -mod test; -mod cmp; -mod ops; -mod default; -mod prelude; -mod uint128; -mod bigint; -mod runtime; -mod meta; -mod append; -mod mem; -mod panic; +pub mod hash; +pub mod aes128; +pub mod array; +pub mod slice; +pub mod merkle; +pub mod schnorr; +pub mod ecdsa_secp256k1; +pub mod ecdsa_secp256r1; +pub mod eddsa; +pub mod embedded_curve_ops; +pub mod sha256; +pub mod sha512; +pub mod field; +pub mod ec; +pub mod collections; +pub mod compat; +pub mod convert; +pub mod option; +pub mod string; +pub mod test; +pub mod cmp; +pub mod ops; +pub mod default; +pub mod prelude; +pub mod uint128; +pub mod bigint; +pub mod runtime; +pub mod meta; +pub mod append; +pub mod mem; +pub mod panic; // Oracle calls are required to be wrapped in an unconstrained function // Thus, the only argument to the `println` oracle is expected to always be an ident diff --git a/noir_stdlib/src/meta/mod.nr b/noir_stdlib/src/meta/mod.nr index 0dc2e5fa714..378f0df1ba8 100644 --- a/noir_stdlib/src/meta/mod.nr +++ b/noir_stdlib/src/meta/mod.nr @@ -1,17 +1,17 @@ -mod ctstring; -mod expr; -mod format_string; -mod function_def; -mod module; -mod op; -mod struct_def; -mod trait_constraint; -mod trait_def; -mod trait_impl; -mod typ; -mod typed_expr; -mod quoted; -mod unresolved_type; +pub mod ctstring; +pub mod expr; +pub mod format_string; +pub mod function_def; +pub mod module; +pub mod op; +pub mod struct_def; +pub mod trait_constraint; +pub mod trait_def; +pub mod trait_impl; +pub mod typ; +pub mod typed_expr; +pub mod quoted; +pub mod unresolved_type; /// Calling unquote as a macro (via `unquote!(arg)`) will unquote /// its argument. Since this is the effect `!` already does, `unquote` diff --git a/noir_stdlib/src/ops/mod.nr b/noir_stdlib/src/ops/mod.nr index 6cf20432468..bf908ea4b27 100644 --- a/noir_stdlib/src/ops/mod.nr +++ b/noir_stdlib/src/ops/mod.nr @@ -1,5 +1,5 @@ -mod arith; -mod bit; +pub(crate) mod arith; +pub(crate) mod bit; pub use arith::{Add, Sub, Mul, Div, Rem, Neg}; pub use bit::{Not, BitOr, BitAnd, BitXor, Shl, Shr}; diff --git a/test_programs/compile_success_empty/comptime_module/src/main.nr b/test_programs/compile_success_empty/comptime_module/src/main.nr index 9443b59fb0a..4c48c1b1a76 100644 --- a/test_programs/compile_success_empty/comptime_module/src/main.nr +++ b/test_programs/compile_success_empty/comptime_module/src/main.nr @@ -4,7 +4,7 @@ mod foo { pub fn x() {} pub fn y() {} - struct Struct1 {} + pub struct Struct1 {} } contract bar {} @@ -61,6 +61,8 @@ comptime fn add_function(m: Module) { } fn main() { + let _ = foo::Struct1 {}; + comptime { // Check Module::is_contract @@ -101,7 +103,7 @@ fn main() { // docs:start:as_module_example mod baz { - mod qux {} + pub mod qux {} } #[test] diff --git a/tooling/lsp/src/requests/code_action/remove_unused_import.rs b/tooling/lsp/src/requests/code_action/remove_unused_import.rs index f1e12d64ef5..f5f0b520149 100644 --- a/tooling/lsp/src/requests/code_action/remove_unused_import.rs +++ b/tooling/lsp/src/requests/code_action/remove_unused_import.rs @@ -189,7 +189,7 @@ mod tests { let title = "Remove the whole `use` item"; let src = r#" - mod moo { + pub(crate) mod moo { pub fn foo() {} } @@ -202,7 +202,7 @@ mod tests { "#; let expected = r#" - mod moo { + pub(crate) mod moo { pub fn foo() {} } diff --git a/tooling/lsp/src/requests/completion/tests.rs b/tooling/lsp/src/requests/completion/tests.rs index 68cb3870f63..147fb5be4f3 100644 --- a/tooling/lsp/src/requests/completion/tests.rs +++ b/tooling/lsp/src/requests/completion/tests.rs @@ -149,8 +149,8 @@ mod completion_tests { async fn test_use_second_segment() { let src = r#" mod foo { - mod bar {} - mod baz {} + pub mod bar {} + pub mod baz {} } use foo::>|< "#; @@ -163,8 +163,8 @@ mod completion_tests { async fn test_use_second_segment_after_typing() { let src = r#" mod foo { - mod bar {} - mod brave {} + pub mod bar {} + pub mod brave {} } use foo::ba>|< "#; @@ -239,7 +239,7 @@ mod completion_tests { async fn test_use_in_tree_after_letter() { let src = r#" mod foo { - mod bar {} + pub mod bar {} } use foo::{b>|<} "#; @@ -251,8 +251,8 @@ mod completion_tests { async fn test_use_in_tree_after_colons() { let src = r#" mod foo { - mod bar { - mod baz {} + pub mod bar { + pub mod baz {} } } use foo::{bar::>|<} @@ -265,8 +265,8 @@ mod completion_tests { async fn test_use_in_tree_after_colons_after_another_segment() { let src = r#" mod foo { - mod bar {} - mod qux {} + pub mod bar {} + pub mod qux {} } use foo::{bar, q>|<} "#; @@ -350,7 +350,7 @@ mod completion_tests { async fn test_complete_path_after_colons_shows_submodule() { let src = r#" mod foo { - mod bar {} + pub mod bar {} } fn main() { @@ -364,7 +364,7 @@ mod completion_tests { async fn test_complete_path_after_colons_and_letter_shows_submodule() { let src = r#" mod foo { - mod qux {} + pub mod qux {} } fn main() { @@ -1809,8 +1809,8 @@ mod completion_tests { async fn test_suggests_pub_use() { let src = r#" mod bar { - mod baz { - mod coco {} + pub mod baz { + pub mod coco {} } pub use baz::coco; @@ -1827,8 +1827,8 @@ mod completion_tests { async fn test_auto_import_suggests_pub_use_for_module() { let src = r#" mod bar { - mod baz { - mod coco {} + pub mod baz { + pub mod coco {} } pub use baz::coco as foobar; diff --git a/tooling/nargo_fmt/src/visitor/item.rs b/tooling/nargo_fmt/src/visitor/item.rs index b2a689919bd..2feae4b390c 100644 --- a/tooling/nargo_fmt/src/visitor/item.rs +++ b/tooling/nargo_fmt/src/visitor/item.rs @@ -7,7 +7,7 @@ use crate::{ visitor::expr::{format_seq, NewlineMode}, }; use noirc_frontend::{ - ast::{NoirFunction, TraitImplItemKind, Visibility}, + ast::{ItemVisibility, NoirFunction, TraitImplItemKind, Visibility}, macros_api::UnresolvedTypeData, }; use noirc_frontend::{ @@ -179,8 +179,12 @@ impl super::FmtVisitor<'_> { let after_brace = self.span_after(span, Token::LeftBrace).start(); self.last_position = after_brace; - let keyword = if module.is_contract { "contract" } else { "mod" }; + let visibility = module.visibility; + if visibility != ItemVisibility::Private { + self.push_str(&format!("{visibility} ")); + } + let keyword = if module.is_contract { "contract" } else { "mod" }; self.push_str(&format!("{keyword} {name} ")); if module.contents.items.is_empty() { diff --git a/tooling/nargo_fmt/tests/expected/module.nr b/tooling/nargo_fmt/tests/expected/module.nr index 0a051a1b50f..1d153b02078 100644 --- a/tooling/nargo_fmt/tests/expected/module.nr +++ b/tooling/nargo_fmt/tests/expected/module.nr @@ -33,3 +33,7 @@ mod a { #![inner] } } + +pub(crate) mod one {} + +pub mod two {} diff --git a/tooling/nargo_fmt/tests/input/module.nr b/tooling/nargo_fmt/tests/input/module.nr index 0a051a1b50f..3e01b81bcf0 100644 --- a/tooling/nargo_fmt/tests/input/module.nr +++ b/tooling/nargo_fmt/tests/input/module.nr @@ -33,3 +33,7 @@ mod a { #![inner] } } + +pub(crate) mod one { } + +pub mod two { } From eec3a6152493e56866ec5338ff52f823c530778e Mon Sep 17 00:00:00 2001 From: Maxim Vezenov Date: Fri, 27 Sep 2024 13:29:36 -0400 Subject: [PATCH 15/36] feat(ssa): Simplify signed casts (#6166) # Description ## Problem\* Resolves Small optimization I noticed while working on other tasks. ## Summary\* We currently simplify when casting to a field or an unsigned type. We can also simplify when casting to a signed type. This is an initial version that is quite conservative in the simplification. If we we have a constant that is below `2^(bit_size-1)` when casting to a signed type we return the constant with the new signed type. I also added the case of signed -> field as this can be the same as going unsigned -> field. ## Additional Context ## Documentation\* Check one: - [X] No documentation needed. - [ ] Documentation included in this PR. - [ ] **[For Experimental Features]** Documentation to be submitted in a separate PR. # PR Checklist\* - [X] I have tested the changes locally. - [X] I have formatted the changes with [Prettier](https://prettier.io/) and/or `cargo fmt` on default settings. --- .../src/ssa/ir/instruction/cast.rs | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/compiler/noirc_evaluator/src/ssa/ir/instruction/cast.rs b/compiler/noirc_evaluator/src/ssa/ir/instruction/cast.rs index d0ed5a1fa9c..ed588def1d7 100644 --- a/compiler/noirc_evaluator/src/ssa/ir/instruction/cast.rs +++ b/compiler/noirc_evaluator/src/ssa/ir/instruction/cast.rs @@ -27,10 +27,10 @@ pub(super) fn simplify_cast( SimplifiedTo(value) } ( - Type::Numeric(NumericType::Unsigned { .. }), + Type::Numeric(NumericType::Unsigned { .. } | NumericType::Signed { .. }), Type::Numeric(NumericType::NativeField), ) => { - // Unsigned -> Field: redefine same constant as Field + // Unsigned/Signed -> Field: redefine same constant as Field SimplifiedTo(dfg.make_constant(constant, dst_typ.clone())) } ( @@ -48,6 +48,24 @@ pub(super) fn simplify_cast( let truncated = FieldElement::from_be_bytes_reduce(&truncated.to_bytes_be()); SimplifiedTo(dfg.make_constant(truncated, dst_typ.clone())) } + ( + Type::Numeric( + NumericType::NativeField + | NumericType::Unsigned { .. } + | NumericType::Signed { .. }, + ), + Type::Numeric(NumericType::Signed { bit_size }), + ) => { + // Field/Unsigned -> signed + // We only simplify to signed when we are below the maximum signed integer of the destination type. + let integer_modulus = BigUint::from(2u128).pow(*bit_size - 1); + let constant_uint: BigUint = BigUint::from_bytes_be(&constant.to_be_bytes()); + if constant_uint < integer_modulus { + SimplifiedTo(dfg.make_constant(constant, dst_typ.clone())) + } else { + None + } + } _ => None, } } else if *dst_typ == dfg.type_of_value(value) { From 3fc62cda328957a966e95b812d88c02a32d82e51 Mon Sep 17 00:00:00 2001 From: Tom French <15848336+TomAFrench@users.noreply.github.com> Date: Mon, 30 Sep 2024 14:38:29 +0100 Subject: [PATCH 16/36] chore: remove `DefCollectorErrorKind::MacroError` (#6174) # Description ## Problem\* Resolves ## Summary\* This PR follows up on #6087 by removing the error variant which is provided for `aztec_macros` to return. ## Additional Context ## Documentation\* Check one: - [x] No documentation needed. - [ ] Documentation included in this PR. - [ ] **[For Experimental Features]** Documentation to be submitted in a separate PR. # PR Checklist\* - [x] I have tested the changes locally. - [x] I have formatted the changes with [Prettier](https://prettier.io/) and/or `cargo fmt` on default settings. --- .../noirc_frontend/src/hir/def_collector/errors.rs | 13 ------------- compiler/noirc_frontend/src/lib.rs | 1 - 2 files changed, 14 deletions(-) diff --git a/compiler/noirc_frontend/src/hir/def_collector/errors.rs b/compiler/noirc_frontend/src/hir/def_collector/errors.rs index f931a7cdf41..2f47505db94 100644 --- a/compiler/noirc_frontend/src/hir/def_collector/errors.rs +++ b/compiler/noirc_frontend/src/hir/def_collector/errors.rs @@ -71,8 +71,6 @@ pub enum DefCollectorErrorKind { "Either the type or the trait must be from the same crate as the trait implementation" )] TraitImplOrphaned { span: Span }, - #[error("macro error : {0:?}")] - MacroError(MacroError), #[error("The only supported types of numeric generics are integers, fields, and booleans")] UnsupportedNumericGenericType { ident: Ident, typ: UnresolvedTypeData }, #[error("impl has stricter requirements than trait")] @@ -86,14 +84,6 @@ pub enum DefCollectorErrorKind { }, } -/// An error struct that macro processors can return. -#[derive(Debug, Clone)] -pub struct MacroError { - pub primary_message: String, - pub secondary_message: Option, - pub span: Option, -} - impl DefCollectorErrorKind { pub fn into_file_diagnostic(&self, file: fm::FileId) -> FileDiagnostic { Diagnostic::from(self).in_file(file) @@ -276,9 +266,6 @@ impl<'a> From<&'a DefCollectorErrorKind> for Diagnostic { "Either the type or the trait must be from the same crate as the trait implementation".into(), *span, ), - DefCollectorErrorKind::MacroError(macro_error) => { - Diagnostic::simple_error(macro_error.primary_message.clone(), macro_error.secondary_message.clone().unwrap_or_default(), macro_error.span.unwrap_or_default()) - }, DefCollectorErrorKind::UnsupportedNumericGenericType { ident, typ } => { let name = &ident.0.contents; diff --git a/compiler/noirc_frontend/src/lib.rs b/compiler/noirc_frontend/src/lib.rs index b2d7c297b8c..4391c760701 100644 --- a/compiler/noirc_frontend/src/lib.rs +++ b/compiler/noirc_frontend/src/lib.rs @@ -45,7 +45,6 @@ pub mod macros_api { pub use noirc_errors::Span; pub use crate::graph::CrateId; - pub use crate::hir::def_collector::errors::MacroError; pub use crate::hir_def::expr::{HirExpression, HirLiteral}; pub use crate::hir_def::stmt::HirStatement; pub use crate::node_interner::{NodeInterner, StructId}; From dcd3c5202473ec2819371b311debff3e35e0ee58 Mon Sep 17 00:00:00 2001 From: Tom French <15848336+TomAFrench@users.noreply.github.com> Date: Mon, 30 Sep 2024 14:39:35 +0100 Subject: [PATCH 17/36] chore: split `test_program`s into modules (#6101) # Description ## Problem\* Resolves ## Summary\* A small change to how we generate the testcases so it's easier to see at a glance which folder the test comes from in the nextest output. ## Additional Context ## Documentation\* Check one: - [x] No documentation needed. - [ ] Documentation included in this PR. - [ ] **[For Experimental Features]** Documentation to be submitted in a separate PR. # PR Checklist\* - [x] I have tested the changes locally. - [x] I have formatted the changes with [Prettier](https://prettier.io/) and/or `cargo fmt` on default settings. --- tooling/nargo_cli/build.rs | 84 +++++++++++++++++++++++++++++++++----- 1 file changed, 73 insertions(+), 11 deletions(-) diff --git a/tooling/nargo_cli/build.rs b/tooling/nargo_cli/build.rs index 7469c8be0f8..9f694080cf5 100644 --- a/tooling/nargo_cli/build.rs +++ b/tooling/nargo_cli/build.rs @@ -81,7 +81,6 @@ fn read_test_cases( fn generate_test_case( test_file: &mut File, - test_type: &str, test_name: &str, test_dir: &std::path::Display, test_content: &str, @@ -90,7 +89,7 @@ fn generate_test_case( test_file, r#" #[test] -fn {test_type}_{test_name}() {{ +fn test_{test_name}() {{ let test_program_dir = PathBuf::from("{test_dir}"); let mut nargo = Command::cargo_bin("nargo").unwrap(); @@ -105,12 +104,19 @@ fn {test_type}_{test_name}() {{ fn generate_execution_success_tests(test_file: &mut File, test_data_dir: &Path) { let test_type = "execution_success"; let test_cases = read_test_cases(test_data_dir, test_type); + + writeln!( + test_file, + "mod {test_type} {{ + use super::*; + " + ) + .unwrap(); for (test_name, test_dir) in test_cases { let test_dir = test_dir.display(); generate_test_case( test_file, - test_type, &test_name, &test_dir, r#" @@ -122,7 +128,6 @@ fn generate_execution_success_tests(test_file: &mut File, test_data_dir: &Path) if !IGNORED_BRILLIG_TESTS.contains(&test_name.as_str()) { generate_test_case( test_file, - test_type, &format!("{test_name}_brillig"), &test_dir, r#" @@ -132,17 +137,25 @@ fn generate_execution_success_tests(test_file: &mut File, test_data_dir: &Path) ); } } + writeln!(test_file, "}}").unwrap(); } fn generate_execution_failure_tests(test_file: &mut File, test_data_dir: &Path) { let test_type = "execution_failure"; let test_cases = read_test_cases(test_data_dir, test_type); + + writeln!( + test_file, + "mod {test_type} {{ + use super::*; + " + ) + .unwrap(); for (test_name, test_dir) in test_cases { let test_dir = test_dir.display(); generate_test_case( test_file, - test_type, &test_name, &test_dir, r#" @@ -151,17 +164,25 @@ fn generate_execution_failure_tests(test_file: &mut File, test_data_dir: &Path) nargo.assert().failure().stderr(predicate::str::contains("The application panicked (crashed).").not());"#, ); } + writeln!(test_file, "}}").unwrap(); } fn generate_noir_test_success_tests(test_file: &mut File, test_data_dir: &Path) { let test_type = "noir_test_success"; let test_cases = read_test_cases(test_data_dir, "noir_test_success"); + + writeln!( + test_file, + "mod {test_type} {{ + use super::*; + " + ) + .unwrap(); for (test_name, test_dir) in test_cases { let test_dir = test_dir.display(); generate_test_case( test_file, - test_type, &test_name, &test_dir, r#" @@ -170,16 +191,24 @@ fn generate_noir_test_success_tests(test_file: &mut File, test_data_dir: &Path) nargo.assert().success();"#, ); } + writeln!(test_file, "}}").unwrap(); } fn generate_noir_test_failure_tests(test_file: &mut File, test_data_dir: &Path) { let test_type = "noir_test_failure"; let test_cases = read_test_cases(test_data_dir, test_type); + + writeln!( + test_file, + "mod {test_type} {{ + use super::*; + " + ) + .unwrap(); for (test_name, test_dir) in test_cases { let test_dir = test_dir.display(); generate_test_case( test_file, - test_type, &test_name, &test_dir, r#" @@ -188,11 +217,20 @@ fn generate_noir_test_failure_tests(test_file: &mut File, test_data_dir: &Path) nargo.assert().failure();"#, ); } + writeln!(test_file, "}}").unwrap(); } fn generate_compile_success_empty_tests(test_file: &mut File, test_data_dir: &Path) { let test_type = "compile_success_empty"; let test_cases = read_test_cases(test_data_dir, test_type); + + writeln!( + test_file, + "mod {test_type} {{ + use super::*; + " + ) + .unwrap(); for (test_name, test_dir) in test_cases { let test_dir = test_dir.display(); @@ -213,7 +251,6 @@ fn generate_compile_success_empty_tests(test_file: &mut File, test_data_dir: &Pa generate_test_case( test_file, - test_type, &test_name, &test_dir, &format!( @@ -224,17 +261,25 @@ fn generate_compile_success_empty_tests(test_file: &mut File, test_data_dir: &Pa ), ); } + writeln!(test_file, "}}").unwrap(); } fn generate_compile_success_contract_tests(test_file: &mut File, test_data_dir: &Path) { let test_type = "compile_success_contract"; let test_cases = read_test_cases(test_data_dir, test_type); + + writeln!( + test_file, + "mod {test_type} {{ + use super::*; + " + ) + .unwrap(); for (test_name, test_dir) in test_cases { let test_dir = test_dir.display(); generate_test_case( test_file, - test_type, &test_name, &test_dir, r#" @@ -242,18 +287,26 @@ fn generate_compile_success_contract_tests(test_file: &mut File, test_data_dir: nargo.assert().success();"#, ); } + writeln!(test_file, "}}").unwrap(); } /// Generate tests for checking that the contract compiles and there are no "bugs" in stderr fn generate_compile_success_no_bug_tests(test_file: &mut File, test_data_dir: &Path) { let test_type = "compile_success_no_bug"; let test_cases = read_test_cases(test_data_dir, test_type); + + writeln!( + test_file, + "mod {test_type} {{ + use super::*; + " + ) + .unwrap(); for (test_name, test_dir) in test_cases { let test_dir = test_dir.display(); generate_test_case( test_file, - test_type, &test_name, &test_dir, r#" @@ -261,17 +314,25 @@ fn generate_compile_success_no_bug_tests(test_file: &mut File, test_data_dir: &P nargo.assert().success().stderr(predicate::str::contains("bug:").not());"#, ); } + writeln!(test_file, "}}").unwrap(); } fn generate_compile_failure_tests(test_file: &mut File, test_data_dir: &Path) { let test_type = "compile_failure"; let test_cases = read_test_cases(test_data_dir, test_type); + + writeln!( + test_file, + "mod {test_type} {{ + use super::*; + " + ) + .unwrap(); for (test_name, test_dir) in test_cases { let test_dir = test_dir.display(); generate_test_case( test_file, - test_type, &test_name, &test_dir, r#"nargo.arg("compile").arg("--force"); @@ -279,4 +340,5 @@ fn generate_compile_failure_tests(test_file: &mut File, test_data_dir: &Path) { nargo.assert().failure().stderr(predicate::str::contains("The application panicked (crashed).").not());"#, ); } + writeln!(test_file, "}}").unwrap(); } From dec98747197442f6c2a15e6543c5d453dff4b967 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Mon, 30 Sep 2024 12:50:23 -0300 Subject: [PATCH 18/36] feat: let `Module::functions` and `Module::structs` return them in definition order (#6178) # Description ## Problem The above functions would return items in different order depending on the run. ## Summary ## Additional Context ## Documentation Check one: - [x] No documentation needed. - [ ] Documentation included in this PR. - [ ] **[For Experimental Features]** Documentation to be submitted in a separate PR. # PR Checklist - [x] I have tested the changes locally. - [x] I have formatted the changes with [Prettier](https://prettier.io/) and/or `cargo fmt` on default settings. --- .../src/hir/comptime/interpreter/builtin.rs | 12 ++++++++---- .../comptime_module/src/main.nr | 16 ++++++++++++++-- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs index 59fc186173a..9960486120e 100644 --- a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs +++ b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs @@ -2458,10 +2458,12 @@ fn module_functions( let module_id = get_module(self_argument)?; let module_data = interpreter.elaborator.get_module(module_id); let func_ids = module_data - .value_definitions() + .definitions() + .definitions() + .iter() .filter_map(|module_def_id| { if let ModuleDefId::FunctionId(func_id) = module_def_id { - Some(Value::FunctionDefinition(func_id)) + Some(Value::FunctionDefinition(*func_id)) } else { None } @@ -2482,10 +2484,12 @@ fn module_structs( let module_id = get_module(self_argument)?; let module_data = interpreter.elaborator.get_module(module_id); let struct_ids = module_data - .type_definitions() + .definitions() + .definitions() + .iter() .filter_map(|module_def_id| { if let ModuleDefId::TypeId(id) = module_def_id { - Some(Value::StructDefinition(id)) + Some(Value::StructDefinition(*id)) } else { None } diff --git a/test_programs/compile_success_empty/comptime_module/src/main.nr b/test_programs/compile_success_empty/comptime_module/src/main.nr index 4c48c1b1a76..5099f3b7acb 100644 --- a/test_programs/compile_success_empty/comptime_module/src/main.nr +++ b/test_programs/compile_success_empty/comptime_module/src/main.nr @@ -3,8 +3,11 @@ mod foo { #![some_attribute] pub fn x() {} pub fn y() {} + pub fn z() {} pub struct Struct1 {} + pub struct Struct2 {} + pub struct Struct3 {} } contract bar {} @@ -75,11 +78,20 @@ fn main() { let another_module = quote { another_module }.as_module().unwrap(); // Check Module::functions - assert_eq(foo.functions().len(), 2); + let foo_functions = foo.functions(); + assert_eq(foo_functions.len(), 3); + assert_eq(foo_functions[0].name(), quote { x }); + assert_eq(foo_functions[1].name(), quote { y }); + assert_eq(foo_functions[2].name(), quote { z }); + assert_eq(bar.functions().len(), 0); // Check Module::structs - assert_eq(foo.structs().len(), 1); + let foo_structs = foo.structs(); + assert_eq(foo_structs.len(), 3); + assert_eq(foo_structs[0].name(), quote { Struct1 }); + assert_eq(foo_structs[1].name(), quote { Struct2 }); + assert_eq(foo_structs[2].name(), quote { Struct3 }); assert_eq(bar.structs().len(), 0); // Check Module::name From d26d20fece6153e4c4f9e06578d0685623f0ec4a Mon Sep 17 00:00:00 2001 From: josh crites Date: Mon, 30 Sep 2024 15:27:05 -0400 Subject: [PATCH 19/36] chore(docs): Add link to more info about proving backend to Solidity verifier page (#5754) # Description ## Problem\* Resolves https://github.com/noir-lang/noir/issues/5711 ## Summary\* ## Additional Context ## Documentation\* Check one: - [ ] No documentation needed. - [x] Documentation included in this PR. - [ ] **[For Experimental Features]** 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: Tom French <15848336+TomAFrench@users.noreply.github.com> --- docs/docs/how_to/how-to-solidity-verifier.md | 2 ++ .../version-v0.32.0/how_to/how-to-solidity-verifier.md | 2 ++ .../version-v0.33.0/how_to/how-to-solidity-verifier.md | 2 ++ 3 files changed, 6 insertions(+) diff --git a/docs/docs/how_to/how-to-solidity-verifier.md b/docs/docs/how_to/how-to-solidity-verifier.md index 3bb96c66795..a8169595b3d 100644 --- a/docs/docs/how_to/how-to-solidity-verifier.md +++ b/docs/docs/how_to/how-to-solidity-verifier.md @@ -57,6 +57,8 @@ bb contract replacing `` with the name of your Noir project. A new `contract` folder would then be generated in your project directory, containing the Solidity file `contract.sol`. It can be deployed to any EVM blockchain acting as a verifier smart contract. +You can find more information about `bb` and the default Noir proving backend on [this page](../getting_started/hello_noir/index.md#proving-backend). + :::info It is possible to generate verifier contracts of Noir programs for other smart contract platforms as long as the proving backend supplies an implementation. diff --git a/docs/versioned_docs/version-v0.32.0/how_to/how-to-solidity-verifier.md b/docs/versioned_docs/version-v0.32.0/how_to/how-to-solidity-verifier.md index 3bb96c66795..a8169595b3d 100644 --- a/docs/versioned_docs/version-v0.32.0/how_to/how-to-solidity-verifier.md +++ b/docs/versioned_docs/version-v0.32.0/how_to/how-to-solidity-verifier.md @@ -57,6 +57,8 @@ bb contract replacing `` with the name of your Noir project. A new `contract` folder would then be generated in your project directory, containing the Solidity file `contract.sol`. It can be deployed to any EVM blockchain acting as a verifier smart contract. +You can find more information about `bb` and the default Noir proving backend on [this page](../getting_started/hello_noir/index.md#proving-backend). + :::info It is possible to generate verifier contracts of Noir programs for other smart contract platforms as long as the proving backend supplies an implementation. diff --git a/docs/versioned_docs/version-v0.33.0/how_to/how-to-solidity-verifier.md b/docs/versioned_docs/version-v0.33.0/how_to/how-to-solidity-verifier.md index c800d91ac69..063538f1fc2 100644 --- a/docs/versioned_docs/version-v0.33.0/how_to/how-to-solidity-verifier.md +++ b/docs/versioned_docs/version-v0.33.0/how_to/how-to-solidity-verifier.md @@ -57,6 +57,8 @@ bb contract replacing `` with the name of your Noir project. A new `contract` folder would then be generated in your project directory, containing the Solidity file `contract.sol`. It can be deployed to any EVM blockchain acting as a verifier smart contract. +You can find more information about `bb` and the default Noir proving backend on [this page](../getting_started/hello_noir/index.md#proving-backend). + :::info It is possible to generate verifier contracts of Noir programs for other smart contract platforms as long as the proving backend supplies an implementation. From a19544247fffaf5d2fe0d6d45013f833576f7c61 Mon Sep 17 00:00:00 2001 From: Maxim Vezenov Date: Mon, 30 Sep 2024 16:24:51 -0400 Subject: [PATCH 20/36] feat(perf): Remove inc_rc instructions for arrays which are never mutably borrowed (#6168) # Description ## Problem\* Part of general effort to reduce Brillig bytecode sizes ## Summary\* An array can be borrowed without being mutated. We code gen inc_rc instructions quite pessimistically on every borrow or reassignment of an array. If we know that the array is never mutated, it should be safe to remove these inc_rc instructions. During DIE we now track all inc_rc instructions in a block and if that array is never used in an array set or a store, we mark its inc_rc instruction for removal. ## Additional Context ## Documentation\* Check one: - [X] No documentation needed. - [ ] Documentation included in this PR. - [ ] **[For Experimental Features]** Documentation to be submitted in a separate PR. # PR Checklist\* - [X] I have tested the changes locally. - [X] I have formatted the changes with [Prettier](https://prettier.io/) and/or `cargo fmt` on default settings. --------- Co-authored-by: Tom French <15848336+TomAFrench@users.noreply.github.com> --- compiler/noirc_evaluator/src/ssa/opt/die.rs | 200 ++++++++++++++++++-- 1 file changed, 187 insertions(+), 13 deletions(-) diff --git a/compiler/noirc_evaluator/src/ssa/opt/die.rs b/compiler/noirc_evaluator/src/ssa/opt/die.rs index 02737f5645b..ae55f85d897 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/die.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/die.rs @@ -113,8 +113,12 @@ impl Context { // We track per block whether an IncrementRc instruction has a paired DecrementRc instruction // with the same value but no array set in between. // If we see an inc/dec RC pair within a block we can safely remove both instructions. - let mut inc_rcs: HashMap> = HashMap::default(); - let mut inc_rcs_to_remove = HashSet::default(); + let mut rcs_with_possible_pairs: HashMap> = HashMap::default(); + let mut rc_pairs_to_remove = HashSet::default(); + // We also separately track all IncrementRc instructions and all arrays which have been mutably borrowed. + // If an array has not been mutably borrowed we can then safely remove all IncrementRc instructions on that array. + let mut inc_rcs: HashMap> = HashMap::default(); + let mut borrowed_arrays: HashSet = HashSet::default(); // Indexes of instructions that might be out of bounds. // We'll remove those, but before that we'll insert bounds checks for them. @@ -146,14 +150,29 @@ impl Context { self.track_inc_rcs_to_remove( *instruction_id, function, + &mut rcs_with_possible_pairs, + &mut rc_pairs_to_remove, &mut inc_rcs, - &mut inc_rcs_to_remove, + &mut borrowed_arrays, ); } - for id in inc_rcs_to_remove { - self.instructions_to_remove.insert(id); - } + let non_mutated_arrays = + inc_rcs + .keys() + .filter_map(|value| { + if !borrowed_arrays.contains(value) { + Some(&inc_rcs[value]) + } else { + None + } + }) + .flatten() + .copied() + .collect::>(); + + self.instructions_to_remove.extend(non_mutated_arrays); + self.instructions_to_remove.extend(rc_pairs_to_remove); // If there are some instructions that might trigger an out of bounds error, // first add constrain checks. Then run the DIE pass again, which will remove those @@ -181,36 +200,50 @@ impl Context { &self, instruction_id: InstructionId, function: &Function, - inc_rcs: &mut HashMap>, + rcs_with_possible_pairs: &mut HashMap>, inc_rcs_to_remove: &mut HashSet, + inc_rcs: &mut HashMap>, + borrowed_arrays: &mut HashSet, ) { let instruction = &function.dfg[instruction_id]; // DIE loops over a block in reverse order, so we insert an RC instruction for possible removal // when we see a DecrementRc and check whether it was possibly mutated when we see an IncrementRc. match instruction { Instruction::IncrementRc { value } => { - if let Some(inc_rc) = pop_rc_for(*value, function, inc_rcs) { + if let Some(inc_rc) = pop_rc_for(*value, function, rcs_with_possible_pairs) { if !inc_rc.possibly_mutated { inc_rcs_to_remove.insert(inc_rc.id); inc_rcs_to_remove.insert(instruction_id); } } + + inc_rcs.entry(*value).or_default().insert(instruction_id); } Instruction::DecrementRc { value } => { let typ = function.dfg.type_of_value(*value); // We assume arrays aren't mutated until we find an array_set - let inc_rc = + let dec_rc = RcInstruction { id: instruction_id, array: *value, possibly_mutated: false }; - inc_rcs.entry(typ).or_default().push(inc_rc); + rcs_with_possible_pairs.entry(typ).or_default().push(dec_rc); } Instruction::ArraySet { array, .. } => { let typ = function.dfg.type_of_value(*array); - if let Some(inc_rcs) = inc_rcs.get_mut(&typ) { - for inc_rc in inc_rcs { - inc_rc.possibly_mutated = true; + if let Some(dec_rcs) = rcs_with_possible_pairs.get_mut(&typ) { + for dec_rc in dec_rcs { + dec_rc.possibly_mutated = true; } } + + borrowed_arrays.insert(*array); + } + Instruction::Store { value, .. } => { + // We are very conservative and say that any store of an array value means it has the potential + // to be mutated. This is done due to the tracking of mutable borrows still being per block. + let typ = function.dfg.type_of_value(*value); + if matches!(&typ, Type::Array(..) | Type::Slice(..)) { + borrowed_arrays.insert(*value); + } } _ => {} } @@ -572,6 +605,8 @@ fn apply_side_effects( mod test { use std::sync::Arc; + use im::vector; + use crate::ssa::{ function_builder::FunctionBuilder, ir::{ @@ -779,4 +814,143 @@ mod test { assert_eq!(main.dfg[main.entry_block()].instructions().len(), 3); } + + #[test] + fn keep_inc_rc_on_borrowed_array_store() { + // acir(inline) fn main f0 { + // b0(): + // v2 = allocate + // inc_rc [u32 0, u32 0] + // store [u32 0, u32 0] at v2 + // inc_rc [u32 0, u32 0] + // jmp b1() + // b1(): + // v3 = load v2 + // v5 = array_set v3, index u32 0, value u32 1 + // return v5 + // } + let main_id = Id::test_new(0); + + // Compiling main + let mut builder = FunctionBuilder::new("main".into(), main_id); + let zero = builder.numeric_constant(0u128, Type::unsigned(32)); + let array_type = Type::Array(Arc::new(vec![Type::unsigned(32)]), 2); + let array = builder.array_constant(vector![zero, zero], array_type.clone()); + let v2 = builder.insert_allocate(array_type.clone()); + builder.increment_array_reference_count(array); + builder.insert_store(v2, array); + builder.increment_array_reference_count(array); + + let b1 = builder.insert_block(); + builder.terminate_with_jmp(b1, vec![]); + builder.switch_to_block(b1); + + let v3 = builder.insert_load(v2, array_type); + let one = builder.numeric_constant(1u128, Type::unsigned(32)); + let v5 = builder.insert_array_set(v3, zero, one); + builder.terminate_with_return(vec![v5]); + + let ssa = builder.finish(); + let main = ssa.main(); + + // The instruction count never includes the terminator instruction + assert_eq!(main.dfg[main.entry_block()].instructions().len(), 4); + assert_eq!(main.dfg[b1].instructions().len(), 2); + + // We expect the output to be unchanged + let ssa = ssa.dead_instruction_elimination(); + let main = ssa.main(); + + assert_eq!(main.dfg[main.entry_block()].instructions().len(), 4); + assert_eq!(main.dfg[b1].instructions().len(), 2); + } + + #[test] + fn keep_inc_rc_on_borrowed_array_set() { + // acir(inline) fn main f0 { + // b0(v0: [u32; 2]): + // inc_rc v0 + // v3 = array_set v0, index u32 0, value u32 1 + // inc_rc v0 + // inc_rc v0 + // inc_rc v0 + // v4 = array_get v3, index u32 1 + // return v4 + // } + let main_id = Id::test_new(0); + + // Compiling main + let mut builder = FunctionBuilder::new("main".into(), main_id); + let array_type = Type::Array(Arc::new(vec![Type::unsigned(32)]), 2); + let v0 = builder.add_parameter(array_type.clone()); + builder.increment_array_reference_count(v0); + let zero = builder.numeric_constant(0u128, Type::unsigned(32)); + let one = builder.numeric_constant(1u128, Type::unsigned(32)); + let v3 = builder.insert_array_set(v0, zero, one); + builder.increment_array_reference_count(v0); + builder.increment_array_reference_count(v0); + builder.increment_array_reference_count(v0); + + let v4 = builder.insert_array_get(v3, one, Type::unsigned(32)); + + builder.terminate_with_return(vec![v4]); + + let ssa = builder.finish(); + let main = ssa.main(); + + // The instruction count never includes the terminator instruction + assert_eq!(main.dfg[main.entry_block()].instructions().len(), 6); + + // We expect the output to be unchanged + let ssa = ssa.dead_instruction_elimination(); + let main = ssa.main(); + + assert_eq!(main.dfg[main.entry_block()].instructions().len(), 6); + } + + #[test] + fn remove_inc_rcs_that_are_never_mutably_borrowed() { + // acir(inline) fn main f0 { + // b0(v0: [Field; 2]): + // inc_rc v0 + // inc_rc v0 + // inc_rc v0 + // v2 = array_get v0, index u32 0 + // inc_rc v0 + // return v2 + // } + let main_id = Id::test_new(0); + + // Compiling main + let mut builder = FunctionBuilder::new("main".into(), main_id); + let v0 = builder.add_parameter(Type::Array(Arc::new(vec![Type::field()]), 2)); + builder.increment_array_reference_count(v0); + builder.increment_array_reference_count(v0); + builder.increment_array_reference_count(v0); + + let zero = builder.numeric_constant(0u128, Type::unsigned(32)); + let v2 = builder.insert_array_get(v0, zero, Type::field()); + builder.increment_array_reference_count(v0); + builder.terminate_with_return(vec![v2]); + + let ssa = builder.finish(); + let main = ssa.main(); + + // The instruction count never includes the terminator instruction + assert_eq!(main.dfg[main.entry_block()].instructions().len(), 5); + + // Expected output: + // + // acir(inline) fn main f0 { + // b0(v0: [Field; 2]): + // v2 = array_get v0, index u32 0 + // return v2 + // } + let ssa = ssa.dead_instruction_elimination(); + let main = ssa.main(); + + let instructions = main.dfg[main.entry_block()].instructions(); + assert_eq!(instructions.len(), 1); + assert!(matches!(&main.dfg[instructions[0]], Instruction::ArrayGet { .. })); + } } From 1ac980bb6d227d0cc52162b0251deae92aa0ea86 Mon Sep 17 00:00:00 2001 From: Tom French <15848336+TomAFrench@users.noreply.github.com> Date: Mon, 30 Sep 2024 21:39:19 +0100 Subject: [PATCH 21/36] chore: reexport `CrateName` through `nargo` (#6177) # Description ## Problem\* Resolves ## Summary\* Ideally `nargo_cli` would not need to directly hook into `noirc_frontend` but a number of imports it needs are not reexported by the packages which are supposed to be the entrypoints to the noir compiler (`noirc_driver` and `nargo`). This PR tackles a couple of cases of these issues. ## Additional Context ## Documentation\* Check one: - [x] No documentation needed. - [ ] Documentation included in this PR. - [ ] **[For Experimental Features]** Documentation to be submitted in a separate PR. # PR Checklist\* - [x] I have tested the changes locally. - [x] I have formatted the changes with [Prettier](https://prettier.io/) and/or `cargo fmt` on default settings. --- compiler/noirc_driver/src/lib.rs | 2 +- tooling/nargo/src/errors.rs | 2 +- tooling/nargo/src/package.rs | 2 +- tooling/nargo_cli/src/cli/check_cmd.rs | 14 +++++++------- tooling/nargo_cli/src/cli/compile_cmd.rs | 4 +--- tooling/nargo_cli/src/cli/debug_cmd.rs | 3 +-- tooling/nargo_cli/src/cli/execute_cmd.rs | 3 +-- tooling/nargo_cli/src/cli/fs/program.rs | 2 +- tooling/nargo_cli/src/cli/info_cmd.rs | 3 +-- tooling/nargo_cli/src/cli/init_cmd.rs | 3 +-- tooling/nargo_cli/src/cli/new_cmd.rs | 3 +-- tooling/nargo_cli/src/cli/test_cmd.rs | 11 +++++------ 12 files changed, 22 insertions(+), 30 deletions(-) diff --git a/compiler/noirc_driver/src/lib.rs b/compiler/noirc_driver/src/lib.rs index 74916d65264..1d69e435738 100644 --- a/compiler/noirc_driver/src/lib.rs +++ b/compiler/noirc_driver/src/lib.rs @@ -14,7 +14,6 @@ use noirc_evaluator::create_program; use noirc_evaluator::errors::RuntimeError; use noirc_evaluator::ssa::SsaProgramArtifact; use noirc_frontend::debug::build_debug_crate_file; -use noirc_frontend::graph::{CrateId, CrateName}; use noirc_frontend::hir::def_map::{Contract, CrateDefMap}; use noirc_frontend::hir::Context; use noirc_frontend::monomorphization::{ @@ -35,6 +34,7 @@ use debug::filter_relevant_files; pub use contract::{CompiledContract, CompiledContractOutputs, ContractFunction}; pub use debug::DebugFile; +pub use noirc_frontend::graph::{CrateId, CrateName}; pub use program::CompiledProgram; const STD_CRATE_NAME: &str = "std"; diff --git a/tooling/nargo/src/errors.rs b/tooling/nargo/src/errors.rs index 313f29517d6..5256f28e36c 100644 --- a/tooling/nargo/src/errors.rs +++ b/tooling/nargo/src/errors.rs @@ -15,7 +15,7 @@ use noirc_errors::{ pub use noirc_errors::Location; -use noirc_frontend::graph::CrateName; +use noirc_driver::CrateName; use noirc_printable_type::ForeignCallError; use thiserror::Error; diff --git a/tooling/nargo/src/package.rs b/tooling/nargo/src/package.rs index f55ca5550a3..cd1d5e2a84f 100644 --- a/tooling/nargo/src/package.rs +++ b/tooling/nargo/src/package.rs @@ -1,7 +1,7 @@ use std::{collections::BTreeMap, fmt::Display, path::PathBuf}; use acvm::acir::circuit::ExpressionWidth; -use noirc_frontend::graph::CrateName; +pub use noirc_driver::CrateName; use crate::constants::PROVER_INPUT_FILE; diff --git a/tooling/nargo_cli/src/cli/check_cmd.rs b/tooling/nargo_cli/src/cli/check_cmd.rs index 65a09dcdd11..9059f1dd8e8 100644 --- a/tooling/nargo_cli/src/cli/check_cmd.rs +++ b/tooling/nargo_cli/src/cli/check_cmd.rs @@ -4,18 +4,18 @@ use clap::Args; use fm::FileManager; use iter_extended::btree_map; use nargo::{ - errors::CompileError, insert_all_files_for_workspace_into_file_manager, ops::report_errors, - package::Package, parse_all, prepare_package, + errors::CompileError, + insert_all_files_for_workspace_into_file_manager, + ops::report_errors, + package::{CrateName, Package}, + parse_all, prepare_package, }; use nargo_toml::{get_package_manifest, resolve_workspace_from_toml, PackageSelection}; use noirc_abi::{AbiParameter, AbiType, MAIN_RETURN_NAME}; use noirc_driver::{ - check_crate, compute_function_abi, CompileOptions, NOIR_ARTIFACT_VERSION_STRING, -}; -use noirc_frontend::{ - graph::{CrateId, CrateName}, - hir::{Context, ParsedFiles}, + check_crate, compute_function_abi, CompileOptions, CrateId, NOIR_ARTIFACT_VERSION_STRING, }; +use noirc_frontend::hir::{Context, ParsedFiles}; use super::fs::write_to_file; use super::NargoConfig; diff --git a/tooling/nargo_cli/src/cli/compile_cmd.rs b/tooling/nargo_cli/src/cli/compile_cmd.rs index ae96f6436e2..0ad07c91ff4 100644 --- a/tooling/nargo_cli/src/cli/compile_cmd.rs +++ b/tooling/nargo_cli/src/cli/compile_cmd.rs @@ -5,7 +5,7 @@ use std::time::Duration; use acvm::acir::circuit::ExpressionWidth; use fm::FileManager; use nargo::ops::{collect_errors, compile_contract, compile_program, report_errors}; -use nargo::package::Package; +use nargo::package::{CrateName, Package}; use nargo::workspace::Workspace; use nargo::{insert_all_files_for_workspace_into_file_manager, parse_all}; use nargo_toml::{get_package_manifest, resolve_workspace_from_toml, PackageSelection}; @@ -13,8 +13,6 @@ use noirc_driver::DEFAULT_EXPRESSION_WIDTH; use noirc_driver::NOIR_ARTIFACT_VERSION_STRING; use noirc_driver::{CompilationResult, CompileOptions, CompiledContract}; -use noirc_frontend::graph::CrateName; - use clap::Args; use noirc_frontend::hir::ParsedFiles; use notify::{EventKind, RecursiveMode, Watcher}; diff --git a/tooling/nargo_cli/src/cli/debug_cmd.rs b/tooling/nargo_cli/src/cli/debug_cmd.rs index 0a593e09c17..e837f297475 100644 --- a/tooling/nargo_cli/src/cli/debug_cmd.rs +++ b/tooling/nargo_cli/src/cli/debug_cmd.rs @@ -9,7 +9,7 @@ use fm::FileManager; use nargo::constants::PROVER_INPUT_FILE; use nargo::errors::CompileError; use nargo::ops::{compile_program, compile_program_with_debug_instrumenter, report_errors}; -use nargo::package::Package; +use nargo::package::{CrateName, Package}; use nargo::workspace::Workspace; use nargo::{insert_all_files_for_workspace_into_file_manager, parse_all}; use nargo_toml::{get_package_manifest, resolve_workspace_from_toml, PackageSelection}; @@ -19,7 +19,6 @@ use noirc_driver::{ file_manager_with_stdlib, CompileOptions, CompiledProgram, NOIR_ARTIFACT_VERSION_STRING, }; use noirc_frontend::debug::DebugInstrumenter; -use noirc_frontend::graph::CrateName; use noirc_frontend::hir::ParsedFiles; use super::compile_cmd::get_target_width; diff --git a/tooling/nargo_cli/src/cli/execute_cmd.rs b/tooling/nargo_cli/src/cli/execute_cmd.rs index c145ae9dbca..8dc71b1c7e5 100644 --- a/tooling/nargo_cli/src/cli/execute_cmd.rs +++ b/tooling/nargo_cli/src/cli/execute_cmd.rs @@ -8,13 +8,12 @@ use clap::Args; use nargo::constants::PROVER_INPUT_FILE; use nargo::errors::try_to_diagnose_runtime_error; use nargo::ops::DefaultForeignCallExecutor; -use nargo::package::Package; +use nargo::package::{CrateName, Package}; use nargo_toml::{get_package_manifest, resolve_workspace_from_toml, PackageSelection}; use noirc_abi::input_parser::{Format, InputValue}; use noirc_abi::InputMap; use noirc_artifacts::debug::DebugArtifact; use noirc_driver::{CompileOptions, CompiledProgram, NOIR_ARTIFACT_VERSION_STRING}; -use noirc_frontend::graph::CrateName; use super::compile_cmd::compile_workspace_full; use super::fs::{inputs::read_inputs_from_file, witness::save_witness_to_dir}; diff --git a/tooling/nargo_cli/src/cli/fs/program.rs b/tooling/nargo_cli/src/cli/fs/program.rs index 323cd2c6a06..0b30d23db2b 100644 --- a/tooling/nargo_cli/src/cli/fs/program.rs +++ b/tooling/nargo_cli/src/cli/fs/program.rs @@ -1,7 +1,7 @@ use std::path::{Path, PathBuf}; +use nargo::package::CrateName; use noirc_artifacts::{contract::ContractArtifact, program::ProgramArtifact}; -use noirc_frontend::graph::CrateName; use crate::errors::FilesystemError; diff --git a/tooling/nargo_cli/src/cli/info_cmd.rs b/tooling/nargo_cli/src/cli/info_cmd.rs index 7e4a8b4117d..f2e44fa893c 100644 --- a/tooling/nargo_cli/src/cli/info_cmd.rs +++ b/tooling/nargo_cli/src/cli/info_cmd.rs @@ -3,12 +3,11 @@ use std::collections::HashMap; use acvm::acir::circuit::ExpressionWidth; use clap::Args; use iter_extended::vecmap; -use nargo::package::Package; +use nargo::package::{CrateName, Package}; use nargo_toml::{get_package_manifest, resolve_workspace_from_toml, PackageSelection}; use noirc_artifacts::{debug::DebugArtifact, program::ProgramArtifact}; use noirc_driver::{CompileOptions, NOIR_ARTIFACT_VERSION_STRING}; use noirc_errors::{debug_info::OpCodesCount, Location}; -use noirc_frontend::graph::CrateName; use prettytable::{row, table, Row}; use rayon::prelude::*; use serde::Serialize; diff --git a/tooling/nargo_cli/src/cli/init_cmd.rs b/tooling/nargo_cli/src/cli/init_cmd.rs index dd3af97ecd6..c69775d3323 100644 --- a/tooling/nargo_cli/src/cli/init_cmd.rs +++ b/tooling/nargo_cli/src/cli/init_cmd.rs @@ -4,9 +4,8 @@ use super::fs::{create_named_dir, write_to_file}; use super::NargoConfig; use clap::Args; use nargo::constants::{PKG_FILE, SRC_DIR}; -use nargo::package::PackageType; +use nargo::package::{CrateName, PackageType}; use noirc_driver::NOIRC_VERSION; -use noirc_frontend::graph::CrateName; use std::path::PathBuf; /// Create a Noir project in the current directory. diff --git a/tooling/nargo_cli/src/cli/new_cmd.rs b/tooling/nargo_cli/src/cli/new_cmd.rs index 21951f27260..db9257b8aa0 100644 --- a/tooling/nargo_cli/src/cli/new_cmd.rs +++ b/tooling/nargo_cli/src/cli/new_cmd.rs @@ -2,8 +2,7 @@ use crate::errors::CliError; use super::{init_cmd::initialize_project, NargoConfig}; use clap::Args; -use nargo::package::PackageType; -use noirc_frontend::graph::CrateName; +use nargo::package::{CrateName, PackageType}; use std::path::PathBuf; /// Create a Noir project in a new directory. diff --git a/tooling/nargo_cli/src/cli/test_cmd.rs b/tooling/nargo_cli/src/cli/test_cmd.rs index 8b3c0e29c7d..7b0201226ef 100644 --- a/tooling/nargo_cli/src/cli/test_cmd.rs +++ b/tooling/nargo_cli/src/cli/test_cmd.rs @@ -5,15 +5,14 @@ use bn254_blackbox_solver::Bn254BlackBoxSolver; use clap::Args; use fm::FileManager; use nargo::{ - insert_all_files_for_workspace_into_file_manager, ops::TestStatus, package::Package, parse_all, - prepare_package, + insert_all_files_for_workspace_into_file_manager, + ops::TestStatus, + package::{CrateName, Package}, + parse_all, prepare_package, }; use nargo_toml::{get_package_manifest, resolve_workspace_from_toml, PackageSelection}; use noirc_driver::{check_crate, CompileOptions, NOIR_ARTIFACT_VERSION_STRING}; -use noirc_frontend::{ - graph::CrateName, - hir::{FunctionNameMatch, ParsedFiles}, -}; +use noirc_frontend::hir::{FunctionNameMatch, ParsedFiles}; use rayon::prelude::{IntoParallelIterator, ParallelBridge, ParallelIterator}; use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor}; From be9dcfe56d808b1bd5ef552d41274705b2df7062 Mon Sep 17 00:00:00 2001 From: Maxim Vezenov Date: Mon, 30 Sep 2024 18:23:11 -0400 Subject: [PATCH 22/36] feat(perf): Remove redundant inc rc without instructions between (#6183) # Description ## Problem\* Part of general effort to reduce Brillig bytecode sizes. ## Summary\* We often have patterns like such (example from `brillig_rc_regression_6123`): ``` brillig fn main f0 { b0(): inc_rc [Field 0, Field 0] inc_rc [Field 0, Field 0] inc_rc [Field 0, Field 0] inc_rc [Field 0, Field 0] inc_rc [Field 0, Field 0] v4 = allocate store [Field 0, Field 0] at v4 v5 = allocate store u32 0 at v5 inc_rc [Field 0, Field 0] v7 = allocate store [Field 0, Field 0] at v7 inc_rc [Field 0, Field 0] inc_rc [Field 0, Field 0] jmp b1(u32 0) ``` It is usually due to each impl method that mutably borrows self placing inc RCs on the internal arrays of the struct for which it is implementing the method. Without any instructions in between these `inc_rc` instructions, we can safely collapse the inc_rcs into a single one. The SSA was `brillig_rc_regression_6123` now looks like such: ``` brillig fn main f0 { b0(): inc_rc [Field 0, Field 0] v4 = allocate store [Field 0, Field 0] at v4 v5 = allocate store u32 0 at v5 inc_rc [Field 0, Field 0] v7 = allocate store [Field 0, Field 0] at v7 inc_rc [Field 0, Field 0] jmp b1(u32 0) ``` This PR does a slight refactor as the `track_inc_rcs_to_remove` was starting to take a lot of parameters. I made a new `RcTracker` struct. Its state is simply updated per instruction as previously and then the instructions it marks for removal are including at the end of performing DIE on a block. ## Additional Context ## Documentation\* Check one: - [X] No documentation needed. - [ ] Documentation included in this PR. - [ ] **[For Experimental Features]** Documentation to be submitted in a separate PR. # PR Checklist\* - [X] I have tested the changes locally. - [X] I have formatted the changes with [Prettier](https://prettier.io/) and/or `cargo fmt` on default settings. --- compiler/noirc_evaluator/src/ssa/opt/die.rs | 210 +++++++++++--------- 1 file changed, 119 insertions(+), 91 deletions(-) diff --git a/compiler/noirc_evaluator/src/ssa/opt/die.rs b/compiler/noirc_evaluator/src/ssa/opt/die.rs index ae55f85d897..54af2e9ad5d 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/die.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/die.rs @@ -106,19 +106,7 @@ impl Context { let instructions_len = block.instructions().len(); - // We can track IncrementRc instructions per block to determine whether they are useless. - // IncrementRc and DecrementRc instructions are normally side effectual instructions, but we remove - // them if their value is not used anywhere in the function. However, even when their value is used, their existence - // is pointless logic if there is no array set between the increment and the decrement of the reference counter. - // We track per block whether an IncrementRc instruction has a paired DecrementRc instruction - // with the same value but no array set in between. - // If we see an inc/dec RC pair within a block we can safely remove both instructions. - let mut rcs_with_possible_pairs: HashMap> = HashMap::default(); - let mut rc_pairs_to_remove = HashSet::default(); - // We also separately track all IncrementRc instructions and all arrays which have been mutably borrowed. - // If an array has not been mutably borrowed we can then safely remove all IncrementRc instructions on that array. - let mut inc_rcs: HashMap> = HashMap::default(); - let mut borrowed_arrays: HashSet = HashSet::default(); + let mut rc_tracker = RcTracker::default(); // Indexes of instructions that might be out of bounds. // We'll remove those, but before that we'll insert bounds checks for them. @@ -147,32 +135,11 @@ impl Context { } } - self.track_inc_rcs_to_remove( - *instruction_id, - function, - &mut rcs_with_possible_pairs, - &mut rc_pairs_to_remove, - &mut inc_rcs, - &mut borrowed_arrays, - ); + rc_tracker.track_inc_rcs_to_remove(*instruction_id, function); } - let non_mutated_arrays = - inc_rcs - .keys() - .filter_map(|value| { - if !borrowed_arrays.contains(value) { - Some(&inc_rcs[value]) - } else { - None - } - }) - .flatten() - .copied() - .collect::>(); - - self.instructions_to_remove.extend(non_mutated_arrays); - self.instructions_to_remove.extend(rc_pairs_to_remove); + self.instructions_to_remove.extend(rc_tracker.get_non_mutated_arrays()); + self.instructions_to_remove.extend(rc_tracker.rc_pairs_to_remove); // If there are some instructions that might trigger an out of bounds error, // first add constrain checks. Then run the DIE pass again, which will remove those @@ -196,59 +163,6 @@ impl Context { false } - fn track_inc_rcs_to_remove( - &self, - instruction_id: InstructionId, - function: &Function, - rcs_with_possible_pairs: &mut HashMap>, - inc_rcs_to_remove: &mut HashSet, - inc_rcs: &mut HashMap>, - borrowed_arrays: &mut HashSet, - ) { - let instruction = &function.dfg[instruction_id]; - // DIE loops over a block in reverse order, so we insert an RC instruction for possible removal - // when we see a DecrementRc and check whether it was possibly mutated when we see an IncrementRc. - match instruction { - Instruction::IncrementRc { value } => { - if let Some(inc_rc) = pop_rc_for(*value, function, rcs_with_possible_pairs) { - if !inc_rc.possibly_mutated { - inc_rcs_to_remove.insert(inc_rc.id); - inc_rcs_to_remove.insert(instruction_id); - } - } - - inc_rcs.entry(*value).or_default().insert(instruction_id); - } - Instruction::DecrementRc { value } => { - let typ = function.dfg.type_of_value(*value); - - // We assume arrays aren't mutated until we find an array_set - let dec_rc = - RcInstruction { id: instruction_id, array: *value, possibly_mutated: false }; - rcs_with_possible_pairs.entry(typ).or_default().push(dec_rc); - } - Instruction::ArraySet { array, .. } => { - let typ = function.dfg.type_of_value(*array); - if let Some(dec_rcs) = rcs_with_possible_pairs.get_mut(&typ) { - for dec_rc in dec_rcs { - dec_rc.possibly_mutated = true; - } - } - - borrowed_arrays.insert(*array); - } - Instruction::Store { value, .. } => { - // We are very conservative and say that any store of an array value means it has the potential - // to be mutated. This is done due to the tracking of mutable borrows still being per block. - let typ = function.dfg.type_of_value(*value); - if matches!(&typ, Type::Array(..) | Type::Slice(..)) { - borrowed_arrays.insert(*value); - } - } - _ => {} - } - } - /// Returns true if an instruction can be removed. /// /// An instruction can be removed as long as it has no side-effects, and none of its result @@ -601,6 +515,103 @@ fn apply_side_effects( (lhs, rhs) } +#[derive(Default)] +struct RcTracker { + // We can track IncrementRc instructions per block to determine whether they are useless. + // IncrementRc and DecrementRc instructions are normally side effectual instructions, but we remove + // them if their value is not used anywhere in the function. However, even when their value is used, their existence + // is pointless logic if there is no array set between the increment and the decrement of the reference counter. + // We track per block whether an IncrementRc instruction has a paired DecrementRc instruction + // with the same value but no array set in between. + // If we see an inc/dec RC pair within a block we can safely remove both instructions. + rcs_with_possible_pairs: HashMap>, + rc_pairs_to_remove: HashSet, + // We also separately track all IncrementRc instructions and all arrays which have been mutably borrowed. + // If an array has not been mutably borrowed we can then safely remove all IncrementRc instructions on that array. + inc_rcs: HashMap>, + mut_borrowed_arrays: HashSet, + // The SSA often creates patterns where after simplifications we end up with repeat + // IncrementRc instructions on the same value. We track whether the previous instruction was an IncrementRc, + // and if the current instruction is also an IncrementRc on the same value we remove the current instruction. + // `None` if the previous instruction was anything other than an IncrementRc + previous_inc_rc: Option, +} + +impl RcTracker { + fn track_inc_rcs_to_remove(&mut self, instruction_id: InstructionId, function: &Function) { + let instruction = &function.dfg[instruction_id]; + + if let Instruction::IncrementRc { value } = instruction { + if let Some(previous_value) = self.previous_inc_rc { + if previous_value == *value { + self.rc_pairs_to_remove.insert(instruction_id); + } + } + self.previous_inc_rc = Some(*value); + } else { + self.previous_inc_rc = None; + } + + // DIE loops over a block in reverse order, so we insert an RC instruction for possible removal + // when we see a DecrementRc and check whether it was possibly mutated when we see an IncrementRc. + match instruction { + Instruction::IncrementRc { value } => { + if let Some(inc_rc) = + pop_rc_for(*value, function, &mut self.rcs_with_possible_pairs) + { + if !inc_rc.possibly_mutated { + self.rc_pairs_to_remove.insert(inc_rc.id); + self.rc_pairs_to_remove.insert(instruction_id); + } + } + + self.inc_rcs.entry(*value).or_default().insert(instruction_id); + } + Instruction::DecrementRc { value } => { + let typ = function.dfg.type_of_value(*value); + + // We assume arrays aren't mutated until we find an array_set + let dec_rc = + RcInstruction { id: instruction_id, array: *value, possibly_mutated: false }; + self.rcs_with_possible_pairs.entry(typ).or_default().push(dec_rc); + } + Instruction::ArraySet { array, .. } => { + let typ = function.dfg.type_of_value(*array); + if let Some(dec_rcs) = self.rcs_with_possible_pairs.get_mut(&typ) { + for dec_rc in dec_rcs { + dec_rc.possibly_mutated = true; + } + } + + self.mut_borrowed_arrays.insert(*array); + } + Instruction::Store { value, .. } => { + // We are very conservative and say that any store of an array value means it has the potential + // to be mutated. This is done due to the tracking of mutable borrows still being per block. + let typ = function.dfg.type_of_value(*value); + if matches!(&typ, Type::Array(..) | Type::Slice(..)) { + self.mut_borrowed_arrays.insert(*value); + } + } + _ => {} + } + } + + fn get_non_mutated_arrays(&self) -> HashSet { + self.inc_rcs + .keys() + .filter_map(|value| { + if !self.mut_borrowed_arrays.contains(value) { + Some(&self.inc_rcs[value]) + } else { + None + } + }) + .flatten() + .copied() + .collect() + } +} #[cfg(test)] mod test { use std::sync::Arc; @@ -902,10 +913,27 @@ mod test { assert_eq!(main.dfg[main.entry_block()].instructions().len(), 6); // We expect the output to be unchanged + // Expected output: + // + // acir(inline) fn main f0 { + // b0(v0: [u32; 2]): + // inc_rc v0 + // v3 = array_set v0, index u32 0, value u32 1 + // inc_rc v0 + // v4 = array_get v3, index u32 1 + // return v4 + // } let ssa = ssa.dead_instruction_elimination(); let main = ssa.main(); - assert_eq!(main.dfg[main.entry_block()].instructions().len(), 6); + let instructions = main.dfg[main.entry_block()].instructions(); + // We expect only the repeated inc_rc instructions to be collapsed into a single inc_rc. + assert_eq!(instructions.len(), 4); + + assert!(matches!(&main.dfg[instructions[0]], Instruction::IncrementRc { .. })); + assert!(matches!(&main.dfg[instructions[1]], Instruction::ArraySet { .. })); + assert!(matches!(&main.dfg[instructions[2]], Instruction::IncrementRc { .. })); + assert!(matches!(&main.dfg[instructions[3]], Instruction::ArrayGet { .. })); } #[test] From 79f895490632a8751cc1ce6e05a862e28810cc3f Mon Sep 17 00:00:00 2001 From: Michael J Klein Date: Tue, 1 Oct 2024 08:14:38 -0400 Subject: [PATCH 23/36] chore: rename `DefinitionKind::GenericType` (#6182) # Description ## Problem\* Resolves https://github.com/noir-lang/noir/issues/6181 ## Summary\* ## Additional Context ## Documentation\* Check one: - [x] No documentation needed. - [ ] Documentation included in this PR. - [ ] **[For Experimental Features]** Documentation to be submitted in a separate PR. # PR Checklist\* - [x] I have tested the changes locally. - [x] I have formatted the changes with [Prettier](https://prettier.io/) and/or `cargo fmt` on default settings. --- compiler/noirc_frontend/src/elaborator/mod.rs | 2 +- compiler/noirc_frontend/src/elaborator/patterns.rs | 2 +- compiler/noirc_frontend/src/hir/comptime/interpreter.rs | 2 +- compiler/noirc_frontend/src/monomorphization/mod.rs | 2 +- compiler/noirc_frontend/src/node_interner.rs | 4 ++-- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/compiler/noirc_frontend/src/elaborator/mod.rs b/compiler/noirc_frontend/src/elaborator/mod.rs index c5e457c405b..c9195fdc267 100644 --- a/compiler/noirc_frontend/src/elaborator/mod.rs +++ b/compiler/noirc_frontend/src/elaborator/mod.rs @@ -360,7 +360,7 @@ impl<'context> Elaborator<'context> { // Introduce all numeric generics into scope for generic in &all_generics { if let Kind::Numeric(typ) = &generic.kind { - let definition = DefinitionKind::GenericType(generic.type_var.clone()); + let definition = DefinitionKind::NumericGeneric(generic.type_var.clone()); let ident = Ident::new(generic.name.to_string(), generic.span); let hir_ident = self.add_variable_decl( ident, false, // mutable diff --git a/compiler/noirc_frontend/src/elaborator/patterns.rs b/compiler/noirc_frontend/src/elaborator/patterns.rs index 6ed59a61e4e..56b7eb30b3b 100644 --- a/compiler/noirc_frontend/src/elaborator/patterns.rs +++ b/compiler/noirc_frontend/src/elaborator/patterns.rs @@ -557,7 +557,7 @@ impl<'context> Elaborator<'context> { self.interner.add_global_reference(global_id, hir_ident.location); } - DefinitionKind::GenericType(_) => { + DefinitionKind::NumericGeneric(_) => { // Initialize numeric generics to a polymorphic integer type in case // they're used in expressions. We must do this here since type_check_variable // does not check definition kinds and otherwise expects parameters to diff --git a/compiler/noirc_frontend/src/hir/comptime/interpreter.rs b/compiler/noirc_frontend/src/hir/comptime/interpreter.rs index e920073b453..16f154e3ec0 100644 --- a/compiler/noirc_frontend/src/hir/comptime/interpreter.rs +++ b/compiler/noirc_frontend/src/hir/comptime/interpreter.rs @@ -582,7 +582,7 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { Ok(value) } } - DefinitionKind::GenericType(type_variable) => { + DefinitionKind::NumericGeneric(type_variable) => { let value = match &*type_variable.borrow() { TypeBinding::Unbound(_) => None, TypeBinding::Bound(binding) => binding.evaluate_to_u32(), diff --git a/compiler/noirc_frontend/src/monomorphization/mod.rs b/compiler/noirc_frontend/src/monomorphization/mod.rs index 12cc3b55b1f..fea15b26c5b 100644 --- a/compiler/noirc_frontend/src/monomorphization/mod.rs +++ b/compiler/noirc_frontend/src/monomorphization/mod.rs @@ -916,7 +916,7 @@ impl<'interner> Monomorphizer<'interner> { ast::Expression::Ident(ident) } }, - DefinitionKind::GenericType(type_variable) => { + DefinitionKind::NumericGeneric(type_variable) => { let value = match &*type_variable.borrow() { TypeBinding::Unbound(_) => { unreachable!("Unbound type variable used in expression") diff --git a/compiler/noirc_frontend/src/node_interner.rs b/compiler/noirc_frontend/src/node_interner.rs index a95282a1ec9..8d665ceacbf 100644 --- a/compiler/noirc_frontend/src/node_interner.rs +++ b/compiler/noirc_frontend/src/node_interner.rs @@ -586,7 +586,7 @@ pub enum DefinitionKind { /// Generic types in functions (T, U in `fn foo(...)` are declared as variables /// in scope in case they resolve to numeric generics later. - GenericType(TypeVariable), + NumericGeneric(TypeVariable), } impl DefinitionKind { @@ -601,7 +601,7 @@ impl DefinitionKind { DefinitionKind::Function(_) => None, DefinitionKind::Global(_) => None, DefinitionKind::Local(id) => *id, - DefinitionKind::GenericType(_) => None, + DefinitionKind::NumericGeneric(_) => None, } } } From b280a79cf8a4fd2a97200e5436e0ec7cb7134711 Mon Sep 17 00:00:00 2001 From: guipublic <47281315+guipublic@users.noreply.github.com> Date: Tue, 1 Oct 2024 14:41:43 +0200 Subject: [PATCH 24/36] fix: ensure to_bytes returns the canonical decomposition (#6084) # Description ## Problem\* Resolves #5846 ## Summary\* Enforce canonical decomposition for to_bytes ## Additional Context I also added from_bytes methods since they are often requested ## Documentation\* I updated the comments, I assume the doc is auto generated but I am not sure. Let me know if I need to modify it manually. Check one: - [ ] No documentation needed. - [X] Documentation included in this PR. - [ ] **[For Experimental Features]** Documentation to be submitted in a separate PR. # PR Checklist\* - [X] I have tested the changes locally. - [X] I have formatted the changes with [Prettier](https://prettier.io/) and/or `cargo fmt` on default settings. --- noir_stdlib/src/field/mod.nr | 99 +++++++++++++++---- .../execution_success/to_be_bytes/src/main.nr | 1 + .../execution_success/to_le_bytes/src/main.nr | 9 +- 3 files changed, 84 insertions(+), 25 deletions(-) diff --git a/noir_stdlib/src/field/mod.nr b/noir_stdlib/src/field/mod.nr index 93245e18072..d5a6193db3b 100644 --- a/noir_stdlib/src/field/mod.nr +++ b/noir_stdlib/src/field/mod.nr @@ -1,5 +1,6 @@ pub mod bn254; use bn254::lt as bn254_lt; +use crate::runtime::is_unconstrained; impl Field { /// Asserts that `self` can be represented in `bit_size` bits. @@ -49,39 +50,71 @@ impl Field { pub fn to_be_bits(self: Self) -> [u1; N] {} // docs:end:to_be_bits - /// Decomposes `self` into its little endian byte decomposition as a `[u8]` slice of length `byte_size`. - /// This slice will be zero padded should not all bytes be necessary to represent `self`. + /// Decomposes `self` into its little endian byte decomposition as a `[u8;N]` array + /// This array will be zero padded should not all bytes be necessary to represent `self`. /// /// # Failures - /// Causes a constraint failure for `Field` values exceeding `2^{8*byte_size}` as the resulting slice will not - /// be able to represent the original `Field`. + /// The length N of the array must be big enough to contain all the bytes of the 'self', + /// and no more than the number of bytes required to represent the field modulus /// /// # Safety - /// Values of `byte_size` equal to or greater than the number of bytes necessary to represent the `Field` modulus - /// (e.g. 32 for the BN254 field) allow for multiple byte decompositions. This is due to how the `Field` will - /// wrap around due to overflow when verifying the decomposition. + /// The result is ensured to be the canonical decomposition of the field element // docs:start:to_le_bytes pub fn to_le_bytes(self: Self) -> [u8; N] { - self.to_le_radix(256) + // docs:end:to_le_bytes + // Compute the byte decomposition + let bytes = self.to_le_radix(256); + + if !is_unconstrained() { + // Ensure that the byte decomposition does not overflow the modulus + let p = modulus_le_bytes(); + assert(bytes.len() <= p.len()); + let mut ok = bytes.len() != p.len(); + for i in 0..N { + if !ok { + if (bytes[N - 1 - i] != p[N - 1 - i]) { + assert(bytes[N - 1 - i] < p[N - 1 - i]); + ok = true; + } + } + } + assert(ok); + } + bytes } - // docs:end:to_le_bytes - /// Decomposes `self` into its big endian byte decomposition as a `[u8]` slice of length `byte_size`. - /// This slice will be zero padded should not all bytes be necessary to represent `self`. + /// Decomposes `self` into its big endian byte decomposition as a `[u8;N]` array of length required to represent the field modulus + /// This array will be zero padded should not all bytes be necessary to represent `self`. /// /// # Failures - /// Causes a constraint failure for `Field` values exceeding `2^{8*byte_size}` as the resulting slice will not - /// be able to represent the original `Field`. + /// The length N of the array must be big enough to contain all the bytes of the 'self', + /// and no more than the number of bytes required to represent the field modulus /// /// # Safety - /// Values of `byte_size` equal to or greater than the number of bytes necessary to represent the `Field` modulus - /// (e.g. 32 for the BN254 field) allow for multiple byte decompositions. This is due to how the `Field` will - /// wrap around due to overflow when verifying the decomposition. + /// The result is ensured to be the canonical decomposition of the field element // docs:start:to_be_bytes pub fn to_be_bytes(self: Self) -> [u8; N] { - self.to_be_radix(256) + // docs:end:to_be_bytes + // Compute the byte decomposition + let bytes = self.to_be_radix(256); + + if !is_unconstrained() { + // Ensure that the byte decomposition does not overflow the modulus + let p = modulus_be_bytes(); + assert(bytes.len() <= p.len()); + let mut ok = bytes.len() != p.len(); + for i in 0..N { + if !ok { + if (bytes[i] != p[i]) { + assert(bytes[i] < p[i]); + ok = true; + } + } + } + assert(ok); + } + bytes } - // docs:end:to_be_bytes // docs:start:to_le_radix pub fn to_le_radix(self: Self, radix: u32) -> [u8; N] { @@ -130,6 +163,32 @@ impl Field { lt_fallback(self, another) } } + + /// Convert a little endian byte array to a field element. + /// If the provided byte array overflows the field modulus then the Field will silently wrap around. + pub fn from_le_bytes(bytes: [u8; N]) -> Field { + let mut v = 1; + let mut result = 0; + + for i in 0..N { + result += (bytes[i] as Field) * v; + v = v * 256; + } + result + } + + /// Convert a big endian byte array to a field element. + /// If the provided byte array overflows the field modulus then the Field will silently wrap around. + pub fn from_be_bytes(bytes: [u8; N]) -> Field { + let mut v = 1; + let mut result = 0; + + for i in 0..N { + result += (bytes[N-1-i] as Field) * v; + v = v * 256; + } + result + } } #[builtin(modulus_num_bits)] @@ -207,6 +266,7 @@ mod tests { let field = 2; let bits: [u8; 8] = field.to_be_bytes(); assert_eq(bits, [0, 0, 0, 0, 0, 0, 0, 2]); + assert_eq(Field::from_be_bytes::<8>(bits), field); } // docs:end:to_be_bytes_example @@ -216,6 +276,7 @@ mod tests { let field = 2; let bits: [u8; 8] = field.to_le_bytes(); assert_eq(bits, [2, 0, 0, 0, 0, 0, 0, 0]); + assert_eq(Field::from_le_bytes::<8>(bits), field); } // docs:end:to_le_bytes_example @@ -225,6 +286,7 @@ mod tests { let field = 2; let bits: [u8; 8] = field.to_be_radix(256); assert_eq(bits, [0, 0, 0, 0, 0, 0, 0, 2]); + assert_eq(Field::from_be_bytes::<8>(bits), field); } // docs:end:to_be_radix_example @@ -234,6 +296,7 @@ mod tests { let field = 2; let bits: [u8; 8] = field.to_le_radix(256); assert_eq(bits, [2, 0, 0, 0, 0, 0, 0, 0]); + assert_eq(Field::from_le_bytes::<8>(bits), field); } // docs:end:to_le_radix_example } diff --git a/test_programs/execution_success/to_be_bytes/src/main.nr b/test_programs/execution_success/to_be_bytes/src/main.nr index 809d8ad4563..062f9f763d5 100644 --- a/test_programs/execution_success/to_be_bytes/src/main.nr +++ b/test_programs/execution_success/to_be_bytes/src/main.nr @@ -8,5 +8,6 @@ fn main(x: Field) -> pub [u8; 31] { if (bytes[30] != 60) | (bytes[29] != 33) | (bytes[28] != 31) { assert(false); } + assert(Field::from_be_bytes::<31>(bytes) == x); bytes } diff --git a/test_programs/execution_success/to_le_bytes/src/main.nr b/test_programs/execution_success/to_le_bytes/src/main.nr index 4e232b025aa..867551b6dbd 100644 --- a/test_programs/execution_success/to_le_bytes/src/main.nr +++ b/test_programs/execution_success/to_le_bytes/src/main.nr @@ -2,17 +2,12 @@ fn main(x: Field, cond: bool) -> pub [u8; 31] { // The result of this byte array will be little-endian let byte_array: [u8; 31] = x.to_le_bytes(); assert(byte_array.len() == 31); - - let mut bytes = [0; 31]; - for i in 0..31 { - bytes[i] = byte_array[i]; - } - + assert(Field::from_le_bytes::<31>(byte_array) == x); if cond { // We've set x = "2040124" so we shouldn't be able to represent this as a single byte. let bad_byte_array: [u8; 1] = x.to_le_bytes(); assert_eq(bad_byte_array.len(), 1); } - bytes + byte_array } From 7077b0194c9d58a1f353ed3b9ae54a28429f4825 Mon Sep 17 00:00:00 2001 From: Akosh Farkash Date: Tue, 1 Oct 2024 17:00:16 +0100 Subject: [PATCH 25/36] chore: Remove macros_api module (#6190) # Description ## Problem Resolves #6176 ## Summary Removes the `noir_frontend::macros_api` module and fixes the `use` statements where it appeared. ## Additional Context See the linked ticket for why it's not needed any more. ## Documentation\* Check one: - [x] No documentation needed. - [ ] Documentation included in this PR. - [ ] **[For Experimental Features]** Documentation to be submitted in a separate PR. # PR Checklist - [x] I have tested the changes locally. - [x] I have formatted the changes with [Prettier](https://prettier.io/) and/or `cargo fmt` on default settings. --- Cargo.lock | 1 + compiler/noirc_driver/src/abi_gen.rs | 8 ++- compiler/noirc_frontend/src/ast/expression.rs | 5 +- compiler/noirc_frontend/src/ast/statement.rs | 8 +-- compiler/noirc_frontend/src/ast/traits.rs | 2 +- .../noirc_frontend/src/elaborator/comptime.rs | 10 ++-- .../src/elaborator/expressions.rs | 14 +++-- .../noirc_frontend/src/elaborator/lints.rs | 9 ++-- compiler/noirc_frontend/src/elaborator/mod.rs | 52 +++++++------------ .../noirc_frontend/src/elaborator/patterns.rs | 7 +-- .../noirc_frontend/src/elaborator/scope.rs | 6 +-- .../src/elaborator/statements.rs | 7 ++- .../src/elaborator/trait_impls.rs | 3 +- .../noirc_frontend/src/elaborator/traits.rs | 10 ++-- .../noirc_frontend/src/elaborator/types.rs | 19 ++++--- .../noirc_frontend/src/elaborator/unquote.rs | 2 +- .../src/hir/comptime/display.rs | 3 +- .../src/hir/comptime/hir_to_display_ast.rs | 5 +- .../src/hir/comptime/interpreter.rs | 10 ++-- .../src/hir/comptime/interpreter/builtin.rs | 14 +++-- .../interpreter/builtin/builtin_helpers.rs | 3 +- .../src/hir/comptime/interpreter/foreign.rs | 2 +- .../noirc_frontend/src/hir/comptime/value.rs | 14 ++--- .../src/hir/def_collector/dc_crate.rs | 4 +- .../src/hir/def_collector/dc_mod.rs | 9 ++-- .../src/hir/type_check/errors.rs | 8 +-- .../src/hir/type_check/generics.rs | 3 +- .../noirc_frontend/src/hir_def/function.rs | 5 +- compiler/noirc_frontend/src/hir_def/stmt.rs | 2 +- compiler/noirc_frontend/src/lib.rs | 28 ---------- compiler/noirc_frontend/src/locations.rs | 5 +- compiler/noirc_frontend/src/node_interner.rs | 13 ++--- .../src/parser/parser/attributes.rs | 3 +- .../src/parser/parser/function.rs | 4 +- .../src/parser/parser/lambdas.rs | 3 +- .../noirc_frontend/src/parser/parser/path.rs | 5 +- .../src/parser/parser/primitives.rs | 5 +- .../src/parser/parser/traits.rs | 5 +- compiler/noirc_frontend/src/usage_tracker.rs | 3 +- tooling/lsp/src/modules.rs | 5 +- tooling/lsp/src/requests/code_action.rs | 2 +- .../requests/code_action/import_or_qualify.rs | 2 +- tooling/lsp/src/requests/code_lens_request.rs | 4 +- tooling/lsp/src/requests/completion.rs | 4 +- .../src/requests/completion/auto_import.rs | 2 +- .../requests/completion/completion_items.rs | 5 +- tooling/lsp/src/requests/hover.rs | 8 ++- tooling/lsp/src/requests/inlay_hint.rs | 3 +- tooling/lsp/src/requests/mod.rs | 2 +- tooling/lsp/src/requests/signature_help.rs | 3 +- .../src/trait_impl_method_stub_generator.rs | 4 +- tooling/nargo_fmt/Cargo.toml | 1 + tooling/nargo_fmt/src/items.rs | 2 +- tooling/nargo_fmt/src/rewrite/expr.rs | 3 +- tooling/nargo_fmt/src/visitor/item.rs | 5 +- tooling/nargo_fmt/src/visitor/stmt.rs | 2 +- 56 files changed, 163 insertions(+), 213 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cdc1d75a421..4b56812e96d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2619,6 +2619,7 @@ name = "nargo_fmt" version = "0.34.0" dependencies = [ "bytecount", + "noirc_errors", "noirc_frontend", "serde", "similar-asserts", diff --git a/compiler/noirc_driver/src/abi_gen.rs b/compiler/noirc_driver/src/abi_gen.rs index be89b24fee5..fcae136349d 100644 --- a/compiler/noirc_driver/src/abi_gen.rs +++ b/compiler/noirc_driver/src/abi_gen.rs @@ -9,8 +9,12 @@ use noirc_abi::{ use noirc_frontend::ast::{Signedness, Visibility}; use noirc_frontend::{ hir::Context, - hir_def::{expr::HirArrayLiteral, function::Param, stmt::HirPattern, types::Type}, - macros_api::{HirExpression, HirLiteral}, + hir_def::{ + expr::{HirArrayLiteral, HirExpression, HirLiteral}, + function::Param, + stmt::HirPattern, + types::Type, + }, node_interner::{FuncId, NodeInterner}, }; use noirc_frontend::{TypeBinding, TypeVariableKind}; diff --git a/compiler/noirc_frontend/src/ast/expression.rs b/compiler/noirc_frontend/src/ast/expression.rs index 7b0a6d028de..095356682fd 100644 --- a/compiler/noirc_frontend/src/ast/expression.rs +++ b/compiler/noirc_frontend/src/ast/expression.rs @@ -6,8 +6,9 @@ use crate::ast::{ UnresolvedTraitConstraint, UnresolvedType, UnresolvedTypeData, Visibility, }; use crate::hir::def_collector::errors::DefCollectorErrorKind; -use crate::macros_api::StructId; -use crate::node_interner::{ExprId, InternedExpressionKind, InternedStatementKind, QuotedTypeId}; +use crate::node_interner::{ + ExprId, InternedExpressionKind, InternedStatementKind, QuotedTypeId, StructId, +}; use crate::token::{Attributes, FunctionAttribute, Token, Tokens}; use crate::{Kind, Type}; use acvm::{acir::AcirField, FieldElement}; diff --git a/compiler/noirc_frontend/src/ast/statement.rs b/compiler/noirc_frontend/src/ast/statement.rs index 4abea8cebb4..2d835482082 100644 --- a/compiler/noirc_frontend/src/ast/statement.rs +++ b/compiler/noirc_frontend/src/ast/statement.rs @@ -10,12 +10,14 @@ use super::{ BlockExpression, ConstructorExpression, Expression, ExpressionKind, GenericTypeArgs, IndexExpression, ItemVisibility, MemberAccessExpression, MethodCallExpression, UnresolvedType, }; +use crate::ast::UnresolvedTypeData; use crate::elaborator::types::SELF_TYPE_NAME; use crate::lexer::token::SpannedToken; -use crate::macros_api::{NodeInterner, SecondaryAttribute, UnresolvedTypeData}; -use crate::node_interner::{InternedExpressionKind, InternedPattern, InternedStatementKind}; +use crate::node_interner::{ + InternedExpressionKind, InternedPattern, InternedStatementKind, NodeInterner, +}; use crate::parser::{ParserError, ParserErrorReason}; -use crate::token::Token; +use crate::token::{SecondaryAttribute, Token}; /// This is used when an identifier fails to parse in the parser. /// Instead of failing the parse, we can often recover using this diff --git a/compiler/noirc_frontend/src/ast/traits.rs b/compiler/noirc_frontend/src/ast/traits.rs index 3df9939dc70..d2fa95e4f5a 100644 --- a/compiler/noirc_frontend/src/ast/traits.rs +++ b/compiler/noirc_frontend/src/ast/traits.rs @@ -7,8 +7,8 @@ use crate::ast::{ BlockExpression, Expression, FunctionReturnType, Ident, NoirFunction, Path, UnresolvedGenerics, UnresolvedType, }; -use crate::macros_api::SecondaryAttribute; use crate::node_interner::TraitId; +use crate::token::SecondaryAttribute; use super::{Documented, GenericTypeArgs, ItemVisibility}; diff --git a/compiler/noirc_frontend/src/elaborator/comptime.rs b/compiler/noirc_frontend/src/elaborator/comptime.rs index 560be895628..4d6095724f4 100644 --- a/compiler/noirc_frontend/src/elaborator/comptime.rs +++ b/compiler/noirc_frontend/src/elaborator/comptime.rs @@ -6,7 +6,7 @@ use iter_extended::vecmap; use noirc_errors::{Location, Span}; use crate::{ - ast::Documented, + ast::{Documented, Expression, ExpressionKind}, hir::{ comptime::{Interpreter, InterpreterError, Value}, def_collector::{ @@ -19,13 +19,11 @@ use crate::{ def_map::{LocalModuleId, ModuleId}, resolution::errors::ResolverError, }, - hir_def::expr::HirIdent, + hir_def::expr::{HirExpression, HirIdent}, lexer::Lexer, - macros_api::{ - Expression, ExpressionKind, HirExpression, NodeInterner, SecondaryAttribute, StructId, - }, - node_interner::{DefinitionKind, DependencyId, FuncId, TraitId}, + node_interner::{DefinitionKind, DependencyId, FuncId, NodeInterner, StructId, TraitId}, parser::{self, TopLevelStatement, TopLevelStatementKind}, + token::SecondaryAttribute, Type, TypeBindings, UnificationError, }; diff --git a/compiler/noirc_frontend/src/elaborator/expressions.rs b/compiler/noirc_frontend/src/elaborator/expressions.rs index 46a22bb232f..cbd72788c85 100644 --- a/compiler/noirc_frontend/src/elaborator/expressions.rs +++ b/compiler/noirc_frontend/src/elaborator/expressions.rs @@ -5,8 +5,10 @@ use rustc_hash::FxHashSet as HashSet; use crate::{ ast::{ - ArrayLiteral, ConstructorExpression, IfExpression, InfixExpression, Lambda, UnaryOp, - UnresolvedTypeData, UnresolvedTypeExpression, + ArrayLiteral, BlockExpression, CallExpression, CastExpression, ConstructorExpression, + Expression, ExpressionKind, Ident, IfExpression, IndexExpression, InfixExpression, Lambda, + Literal, MemberAccessExpression, MethodCallExpression, PrefixExpression, StatementKind, + UnaryOp, UnresolvedTypeData, UnresolvedTypeExpression, }, hir::{ comptime::{self, InterpreterError}, @@ -17,16 +19,12 @@ use crate::{ expr::{ HirArrayLiteral, HirBinaryOp, HirBlockExpression, HirCallExpression, HirCastExpression, HirConstructorExpression, HirExpression, HirIfExpression, HirIndexExpression, - HirInfixExpression, HirLambda, HirMemberAccess, HirMethodCallExpression, + HirInfixExpression, HirLambda, HirLiteral, HirMemberAccess, HirMethodCallExpression, HirPrefixExpression, }, + stmt::HirStatement, traits::TraitConstraint, }, - macros_api::{ - BlockExpression, CallExpression, CastExpression, Expression, ExpressionKind, HirLiteral, - HirStatement, Ident, IndexExpression, Literal, MemberAccessExpression, - MethodCallExpression, PrefixExpression, StatementKind, - }, node_interner::{DefinitionKind, ExprId, FuncId, InternedStatementKind, TraitMethodId}, token::Tokens, Kind, QuotedType, Shared, StructType, Type, diff --git a/compiler/noirc_frontend/src/elaborator/lints.rs b/compiler/noirc_frontend/src/elaborator/lints.rs index 8253921d305..c0a18d219b7 100644 --- a/compiler/noirc_frontend/src/elaborator/lints.rs +++ b/compiler/noirc_frontend/src/elaborator/lints.rs @@ -1,14 +1,15 @@ use crate::{ - ast::{FunctionKind, Ident}, + ast::{FunctionKind, Ident, NoirFunction, Signedness, UnaryOp, Visibility}, graph::CrateId, hir::{ resolution::errors::{PubPosition, ResolverError}, type_check::TypeCheckError, }, - hir_def::{expr::HirIdent, function::FuncMeta}, - macros_api::{ - HirExpression, HirLiteral, NodeInterner, NoirFunction, Signedness, UnaryOp, Visibility, + hir_def::{ + expr::{HirExpression, HirIdent, HirLiteral}, + function::FuncMeta, }, + node_interner::NodeInterner, node_interner::{DefinitionKind, ExprId, FuncId, FunctionModifiers}, Type, }; diff --git a/compiler/noirc_frontend/src/elaborator/mod.rs b/compiler/noirc_frontend/src/elaborator/mod.rs index c9195fdc267..5f1b97f5eed 100644 --- a/compiler/noirc_frontend/src/elaborator/mod.rs +++ b/compiler/noirc_frontend/src/elaborator/mod.rs @@ -3,56 +3,42 @@ use std::{ rc::Rc, }; +use crate::ast::ItemVisibility; use crate::{ - ast::{FunctionKind, GenericTypeArgs, UnresolvedTraitConstraint}, + ast::{ + BlockExpression, FunctionKind, GenericTypeArgs, Ident, NoirFunction, NoirStruct, Param, + Path, Pattern, TraitBound, UnresolvedGeneric, UnresolvedGenerics, + UnresolvedTraitConstraint, UnresolvedTypeData, + }, + graph::CrateId, hir::{ def_collector::dc_crate::{ - filter_literal_globals, CompilationError, ImplMap, UnresolvedGlobal, UnresolvedStruct, - UnresolvedTypeAlias, + filter_literal_globals, CompilationError, ImplMap, UnresolvedFunctions, + UnresolvedGlobal, UnresolvedStruct, UnresolvedTraitImpl, UnresolvedTypeAlias, }, - def_map::DefMaps, + def_collector::{dc_crate::CollectedItems, errors::DefCollectorErrorKind}, + def_map::{DefMaps, ModuleData}, + def_map::{LocalModuleId, ModuleDefId, ModuleId, MAIN_FUNCTION}, resolution::errors::ResolverError, + resolution::import::PathResolution, scope::ScopeForest as GenericScopeForest, type_check::{generics::TraitGenerics, TypeCheckError}, + Context, }, + hir_def::traits::TraitImpl, hir_def::{ expr::{HirCapturedVar, HirIdent}, - function::FunctionBody, + function::{FuncMeta, FunctionBody, HirFunction}, traits::TraitConstraint, types::{Generics, Kind, ResolvedGeneric}, }, - macros_api::{ - BlockExpression, Ident, NodeInterner, NoirFunction, NoirStruct, Pattern, - SecondaryAttribute, StructId, - }, node_interner::{ - DefinitionKind, DependencyId, ExprId, FuncId, FunctionModifiers, GlobalId, ReferenceId, - TraitId, TypeAliasId, + DefinitionKind, DependencyId, ExprId, FuncId, FunctionModifiers, GlobalId, NodeInterner, + ReferenceId, StructId, TraitId, TraitImplId, TypeAliasId, }, - token::CustomAttribute, + token::{CustomAttribute, SecondaryAttribute}, Shared, Type, TypeVariable, }; -use crate::{ - ast::{TraitBound, UnresolvedGeneric, UnresolvedGenerics}, - graph::CrateId, - hir::{ - def_collector::{dc_crate::CollectedItems, errors::DefCollectorErrorKind}, - def_map::{LocalModuleId, ModuleDefId, ModuleId, MAIN_FUNCTION}, - resolution::import::PathResolution, - Context, - }, - hir_def::function::{FuncMeta, HirFunction}, - macros_api::{Param, Path, UnresolvedTypeData}, - node_interner::TraitImplId, -}; -use crate::{ - hir::{ - def_collector::dc_crate::{UnresolvedFunctions, UnresolvedTraitImpl}, - def_map::ModuleData, - }, - hir_def::traits::TraitImpl, - macros_api::ItemVisibility, -}; mod comptime; mod expressions; diff --git a/compiler/noirc_frontend/src/elaborator/patterns.rs b/compiler/noirc_frontend/src/elaborator/patterns.rs index 56b7eb30b3b..91c4b17a857 100644 --- a/compiler/noirc_frontend/src/elaborator/patterns.rs +++ b/compiler/noirc_frontend/src/elaborator/patterns.rs @@ -3,17 +3,18 @@ use noirc_errors::{Location, Span}; use rustc_hash::FxHashSet as HashSet; use crate::{ - ast::{TypePath, UnresolvedType, ERROR_IDENT}, + ast::{ + Expression, ExpressionKind, Ident, Path, Pattern, TypePath, UnresolvedType, ERROR_IDENT, + }, hir::{ def_collector::dc_crate::CompilationError, resolution::errors::ResolverError, type_check::{Source, TypeCheckError}, }, hir_def::{ - expr::{HirIdent, HirMethodReference, ImplKind}, + expr::{HirExpression, HirIdent, HirMethodReference, ImplKind}, stmt::HirPattern, }, - macros_api::{Expression, ExpressionKind, HirExpression, Ident, Path, Pattern}, node_interner::{DefinitionId, DefinitionKind, ExprId, FuncId, GlobalId, TraitImplKind}, ResolvedGeneric, Shared, StructType, Type, TypeBindings, }; diff --git a/compiler/noirc_frontend/src/elaborator/scope.rs b/compiler/noirc_frontend/src/elaborator/scope.rs index 8e746256142..0fb5a58035a 100644 --- a/compiler/noirc_frontend/src/elaborator/scope.rs +++ b/compiler/noirc_frontend/src/elaborator/scope.rs @@ -1,11 +1,10 @@ use noirc_errors::{Location, Spanned}; -use crate::ast::{PathKind, ERROR_IDENT}; +use crate::ast::{Ident, Path, PathKind, ERROR_IDENT}; use crate::hir::def_map::{LocalModuleId, ModuleId}; use crate::hir::resolution::import::{PathResolution, PathResolutionResult}; use crate::hir::resolution::path_resolver::{PathResolver, StandardPathResolver}; use crate::hir::scope::{Scope as GenericScope, ScopeTree as GenericScopeTree}; -use crate::macros_api::Ident; use crate::{ hir::{ def_map::{ModuleDefId, TryFromModuleDefId}, @@ -15,8 +14,7 @@ use crate::{ expr::{HirCapturedVar, HirIdent}, traits::Trait, }, - macros_api::{Path, StructId}, - node_interner::{DefinitionId, TraitId}, + node_interner::{DefinitionId, StructId, TraitId}, Shared, StructType, }; use crate::{Type, TypeAlias}; diff --git a/compiler/noirc_frontend/src/elaborator/statements.rs b/compiler/noirc_frontend/src/elaborator/statements.rs index 55b641ca3d4..204a7f9cd75 100644 --- a/compiler/noirc_frontend/src/elaborator/statements.rs +++ b/compiler/noirc_frontend/src/elaborator/statements.rs @@ -3,7 +3,8 @@ use noirc_errors::{Location, Span, Spanned}; use crate::{ ast::{ AssignStatement, BinaryOpKind, ConstrainKind, ConstrainStatement, Expression, - ExpressionKind, InfixExpression, LValue, + ExpressionKind, ForLoopStatement, ForRange, InfixExpression, LValue, LetStatement, Path, + Statement, StatementKind, }, hir::{ resolution::errors::ResolverError, @@ -13,11 +14,9 @@ use crate::{ expr::HirIdent, stmt::{ HirAssignStatement, HirConstrainStatement, HirForStatement, HirLValue, HirLetStatement, + HirStatement, }, }, - macros_api::{ - ForLoopStatement, ForRange, HirStatement, LetStatement, Path, Statement, StatementKind, - }, node_interner::{DefinitionId, DefinitionKind, GlobalId, StmtId}, Type, }; diff --git a/compiler/noirc_frontend/src/elaborator/trait_impls.rs b/compiler/noirc_frontend/src/elaborator/trait_impls.rs index aa7e1cb89c5..2f9c927fae6 100644 --- a/compiler/noirc_frontend/src/elaborator/trait_impls.rs +++ b/compiler/noirc_frontend/src/elaborator/trait_impls.rs @@ -1,8 +1,7 @@ use crate::{ - ast::UnresolvedTypeExpression, + ast::{Ident, UnresolvedType, UnresolvedTypeData, UnresolvedTypeExpression}, graph::CrateId, hir::def_collector::{dc_crate::UnresolvedTraitImpl, errors::DefCollectorErrorKind}, - macros_api::{Ident, UnresolvedType, UnresolvedTypeData}, node_interner::TraitImplId, ResolvedGeneric, }; diff --git a/compiler/noirc_frontend/src/elaborator/traits.rs b/compiler/noirc_frontend/src/elaborator/traits.rs index d7c8769620d..21e2edf3822 100644 --- a/compiler/noirc_frontend/src/elaborator/traits.rs +++ b/compiler/noirc_frontend/src/elaborator/traits.rs @@ -5,15 +5,13 @@ use noirc_errors::{Location, Span}; use crate::{ ast::{ - FunctionKind, TraitItem, UnresolvedGeneric, UnresolvedGenerics, UnresolvedTraitConstraint, + BlockExpression, FunctionDefinition, FunctionKind, FunctionReturnType, Ident, + ItemVisibility, NoirFunction, TraitItem, UnresolvedGeneric, UnresolvedGenerics, + UnresolvedTraitConstraint, UnresolvedType, }, hir::{def_collector::dc_crate::UnresolvedTrait, type_check::TypeCheckError}, hir_def::{function::Parameters, traits::TraitFunction}, - macros_api::{ - BlockExpression, FunctionDefinition, FunctionReturnType, Ident, ItemVisibility, - NodeInterner, NoirFunction, UnresolvedType, - }, - node_interner::{FuncId, ReferenceId, TraitId}, + node_interner::{FuncId, NodeInterner, ReferenceId, TraitId}, Kind, ResolvedGeneric, Type, TypeBindings, TypeVariableKind, }; diff --git a/compiler/noirc_frontend/src/elaborator/types.rs b/compiler/noirc_frontend/src/elaborator/types.rs index 264b83956f8..80a9133d0d7 100644 --- a/compiler/noirc_frontend/src/elaborator/types.rs +++ b/compiler/noirc_frontend/src/elaborator/types.rs @@ -7,8 +7,9 @@ use rustc_hash::FxHashMap as HashMap; use crate::{ ast::{ - AsTraitPath, BinaryOpKind, GenericTypeArgs, IntegerBitSize, UnresolvedGeneric, - UnresolvedGenerics, UnresolvedTypeExpression, + AsTraitPath, BinaryOpKind, GenericTypeArgs, Ident, IntegerBitSize, Path, PathKind, + Signedness, UnaryOp, UnresolvedGeneric, UnresolvedGenerics, UnresolvedType, + UnresolvedTypeData, UnresolvedTypeExpression, }, hir::{ comptime::{Interpreter, Value}, @@ -22,20 +23,18 @@ use crate::{ }, hir_def::{ expr::{ - HirBinaryOp, HirCallExpression, HirMemberAccess, HirMethodReference, - HirPrefixExpression, + HirBinaryOp, HirCallExpression, HirExpression, HirLiteral, HirMemberAccess, + HirMethodReference, HirPrefixExpression, }, function::{FuncMeta, Parameters}, + stmt::HirStatement, traits::{NamedType, TraitConstraint}, }, - macros_api::{ - HirExpression, HirLiteral, HirStatement, Ident, NodeInterner, Path, PathKind, - SecondaryAttribute, Signedness, UnaryOp, UnresolvedType, UnresolvedTypeData, - }, node_interner::{ - DefinitionKind, DependencyId, ExprId, FuncId, GlobalId, ImplSearchErrorKind, TraitId, - TraitImplKind, TraitMethodId, + DefinitionKind, DependencyId, ExprId, FuncId, GlobalId, ImplSearchErrorKind, NodeInterner, + TraitId, TraitImplKind, TraitMethodId, }, + token::SecondaryAttribute, Generics, Kind, ResolvedGeneric, Type, TypeBinding, TypeBindings, TypeVariable, TypeVariableKind, UnificationError, }; diff --git a/compiler/noirc_frontend/src/elaborator/unquote.rs b/compiler/noirc_frontend/src/elaborator/unquote.rs index fd7e02df905..982ad3d2e1f 100644 --- a/compiler/noirc_frontend/src/elaborator/unquote.rs +++ b/compiler/noirc_frontend/src/elaborator/unquote.rs @@ -1,5 +1,5 @@ use crate::{ - macros_api::Path, + ast::Path, token::{SpannedToken, Token, Tokens}, }; diff --git a/compiler/noirc_frontend/src/hir/comptime/display.rs b/compiler/noirc_frontend/src/hir/comptime/display.rs index 105f6e09395..3f2ecb395d0 100644 --- a/compiler/noirc_frontend/src/hir/comptime/display.rs +++ b/compiler/noirc_frontend/src/hir/comptime/display.rs @@ -13,8 +13,7 @@ use crate::{ UnresolvedTypeData, }, hir_def::traits::TraitConstraint, - macros_api::NodeInterner, - node_interner::InternedStatementKind, + node_interner::{InternedStatementKind, NodeInterner}, token::{Keyword, Token}, Type, }; diff --git a/compiler/noirc_frontend/src/hir/comptime/hir_to_display_ast.rs b/compiler/noirc_frontend/src/hir/comptime/hir_to_display_ast.rs index 4344d19829a..90deef2deb7 100644 --- a/compiler/noirc_frontend/src/hir/comptime/hir_to_display_ast.rs +++ b/compiler/noirc_frontend/src/hir/comptime/hir_to_display_ast.rs @@ -9,10 +9,11 @@ use crate::ast::{ UnresolvedType, UnresolvedTypeData, UnresolvedTypeExpression, }; use crate::ast::{ConstrainStatement, Expression, Statement, StatementKind}; -use crate::hir_def::expr::{HirArrayLiteral, HirBlockExpression, HirExpression, HirIdent}; +use crate::hir_def::expr::{ + HirArrayLiteral, HirBlockExpression, HirExpression, HirIdent, HirLiteral, +}; use crate::hir_def::stmt::{HirLValue, HirPattern, HirStatement}; use crate::hir_def::types::{Type, TypeBinding}; -use crate::macros_api::HirLiteral; use crate::node_interner::{ExprId, NodeInterner, StmtId}; // TODO: diff --git a/compiler/noirc_frontend/src/hir/comptime/interpreter.rs b/compiler/noirc_frontend/src/hir/comptime/interpreter.rs index 16f154e3ec0..473e1f25e34 100644 --- a/compiler/noirc_frontend/src/hir/comptime/interpreter.rs +++ b/compiler/noirc_frontend/src/hir/comptime/interpreter.rs @@ -8,14 +8,13 @@ use iter_extended::try_vecmap; use noirc_errors::Location; use rustc_hash::FxHashMap as HashMap; -use crate::ast::{BinaryOpKind, FunctionKind, IntegerBitSize, Signedness}; +use crate::ast::{BinaryOpKind, FunctionKind, IntegerBitSize, Signedness, UnaryOp}; use crate::elaborator::Elaborator; use crate::graph::CrateId; use crate::hir::def_map::ModuleId; use crate::hir::type_check::TypeCheckError; use crate::hir_def::expr::ImplKind; use crate::hir_def::function::FunctionBody; -use crate::macros_api::UnaryOp; use crate::monomorphization::{ perform_impl_bindings, perform_instantiation_bindings, resolve_trait_method, undo_instantiation_bindings, @@ -27,16 +26,15 @@ use crate::{ expr::{ HirArrayLiteral, HirBlockExpression, HirCallExpression, HirCastExpression, HirConstructorExpression, HirExpression, HirIdent, HirIfExpression, HirIndexExpression, - HirInfixExpression, HirLambda, HirMemberAccess, HirMethodCallExpression, + HirInfixExpression, HirLambda, HirLiteral, HirMemberAccess, HirMethodCallExpression, HirPrefixExpression, }, stmt::{ HirAssignStatement, HirConstrainStatement, HirForStatement, HirLValue, HirLetStatement, - HirPattern, + HirPattern, HirStatement, }, }, - macros_api::{HirLiteral, HirStatement, NodeInterner}, - node_interner::{DefinitionId, DefinitionKind, ExprId, FuncId, StmtId}, + node_interner::{DefinitionId, DefinitionKind, ExprId, FuncId, NodeInterner, StmtId}, Shared, Type, TypeBinding, TypeBindings, TypeVariableKind, }; diff --git a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs index 9960486120e..4c81d4c3ed3 100644 --- a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs +++ b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs @@ -20,8 +20,9 @@ use rustc_hash::FxHashMap as HashMap; use crate::{ ast::{ ArrayLiteral, BlockExpression, ConstrainKind, Expression, ExpressionKind, ForRange, - FunctionKind, FunctionReturnType, IntegerBitSize, LValue, Literal, Pattern, Statement, - StatementKind, UnaryOp, UnresolvedType, UnresolvedTypeData, Visibility, + FunctionKind, FunctionReturnType, Ident, IntegerBitSize, LValue, Literal, Pattern, + Signedness, Statement, StatementKind, UnaryOp, UnresolvedType, UnresolvedTypeData, + Visibility, }, hir::{ comptime::{ @@ -30,10 +31,13 @@ use crate::{ InterpreterError, Value, }, def_collector::dc_crate::CollectedItems, + def_map::ModuleDefId, }, - hir_def::function::FunctionBody, - macros_api::{HirExpression, HirLiteral, Ident, ModuleDefId, NodeInterner, Signedness}, - node_interner::{DefinitionKind, TraitImplKind}, + hir_def::{ + expr::{HirExpression, HirLiteral}, + function::FunctionBody, + }, + node_interner::{DefinitionKind, NodeInterner, TraitImplKind}, parser, token::{Attribute, SecondaryAttribute, Token}, Kind, QuotedType, ResolvedGeneric, Shared, Type, TypeVariable, diff --git a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin/builtin_helpers.rs b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin/builtin_helpers.rs index 20303e49e15..a355b23b74f 100644 --- a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin/builtin_helpers.rs +++ b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin/builtin_helpers.rs @@ -24,8 +24,7 @@ use crate::{ function::{FuncMeta, FunctionBody}, stmt::HirPattern, }, - macros_api::{NodeInterner, StructId}, - node_interner::{FuncId, TraitId, TraitImplId}, + node_interner::{FuncId, NodeInterner, StructId, TraitId, TraitImplId}, parser::NoirParser, token::{SecondaryAttribute, Token, Tokens}, QuotedType, Type, diff --git a/compiler/noirc_frontend/src/hir/comptime/interpreter/foreign.rs b/compiler/noirc_frontend/src/hir/comptime/interpreter/foreign.rs index 5ae60bb4d00..d1ab6a1dabd 100644 --- a/compiler/noirc_frontend/src/hir/comptime/interpreter/foreign.rs +++ b/compiler/noirc_frontend/src/hir/comptime/interpreter/foreign.rs @@ -6,7 +6,7 @@ use noirc_errors::Location; use crate::{ hir::comptime::{errors::IResult, InterpreterError, Value}, - macros_api::NodeInterner, + node_interner::NodeInterner, }; use super::builtin::builtin_helpers::{ diff --git a/compiler/noirc_frontend/src/hir/comptime/value.rs b/compiler/noirc_frontend/src/hir/comptime/value.rs index f01e188e498..4c968234f04 100644 --- a/compiler/noirc_frontend/src/hir/comptime/value.rs +++ b/compiler/noirc_frontend/src/hir/comptime/value.rs @@ -9,16 +9,16 @@ use strum_macros::Display; use crate::{ ast::{ - ArrayLiteral, BlockExpression, ConstructorExpression, Ident, IntegerBitSize, LValue, - Pattern, Signedness, Statement, StatementKind, UnresolvedType, UnresolvedTypeData, + ArrayLiteral, BlockExpression, ConstructorExpression, Expression, ExpressionKind, Ident, + IntegerBitSize, LValue, Literal, Path, Pattern, Signedness, Statement, StatementKind, + UnresolvedType, UnresolvedTypeData, }, hir::{def_map::ModuleId, type_check::generics::TraitGenerics}, - hir_def::expr::{HirArrayLiteral, HirConstructorExpression, HirIdent, HirLambda, ImplKind}, - macros_api::{ - Expression, ExpressionKind, HirExpression, HirLiteral, Literal, NodeInterner, Path, - StructId, + hir_def::expr::{ + HirArrayLiteral, HirConstructorExpression, HirExpression, HirIdent, HirLambda, HirLiteral, + ImplKind, }, - node_interner::{ExprId, FuncId, StmtId, TraitId, TraitImplId}, + node_interner::{ExprId, FuncId, NodeInterner, StmtId, StructId, TraitId, TraitImplId}, parser::{self, NoirParser, TopLevelStatement}, token::{SpannedToken, Token, Tokens}, Kind, QuotedType, Shared, Type, TypeBindings, diff --git a/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs b/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs index faf72e86fb4..267c9bc84b8 100644 --- a/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs +++ b/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs @@ -14,7 +14,7 @@ use crate::{Generics, Type}; use crate::hir::resolution::import::{resolve_import, ImportDirective, PathResolution}; use crate::hir::Context; -use crate::macros_api::Expression; +use crate::ast::Expression; use crate::node_interner::{ FuncId, GlobalId, ModuleAttributes, NodeInterner, ReferenceId, StructId, TraitId, TraitImplId, TypeAliasId, @@ -507,7 +507,7 @@ impl DefCollector { } fn add_import_reference( - def_id: crate::macros_api::ModuleDefId, + def_id: crate::hir::def_map::ModuleDefId, name: &Ident, interner: &mut NodeInterner, file_id: FileId, diff --git a/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs b/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs index b530e023152..dff63e045fe 100644 --- a/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs +++ b/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs @@ -11,13 +11,12 @@ use num_traits::Num; use rustc_hash::FxHashMap as HashMap; use crate::ast::{ - Documented, FunctionDefinition, Ident, ItemVisibility, LetStatement, ModuleDeclaration, - NoirFunction, NoirStruct, NoirTrait, NoirTraitImpl, NoirTypeAlias, Pattern, TraitImplItemKind, - TraitItem, TypeImpl, + Documented, Expression, FunctionDefinition, Ident, ItemVisibility, LetStatement, + ModuleDeclaration, NoirFunction, NoirStruct, NoirTrait, NoirTraitImpl, NoirTypeAlias, Pattern, + TraitImplItemKind, TraitItem, TypeImpl, UnresolvedType, UnresolvedTypeData, }; use crate::hir::resolution::errors::ResolverError; -use crate::macros_api::{Expression, NodeInterner, StructId, UnresolvedType, UnresolvedTypeData}; -use crate::node_interner::{ModuleAttributes, ReferenceId}; +use crate::node_interner::{ModuleAttributes, NodeInterner, ReferenceId, StructId}; use crate::token::SecondaryAttribute; use crate::usage_tracker::UnusedItem; use crate::{ diff --git a/compiler/noirc_frontend/src/hir/type_check/errors.rs b/compiler/noirc_frontend/src/hir/type_check/errors.rs index 00e73e682e8..54699792901 100644 --- a/compiler/noirc_frontend/src/hir/type_check/errors.rs +++ b/compiler/noirc_frontend/src/hir/type_check/errors.rs @@ -5,14 +5,14 @@ use noirc_errors::CustomDiagnostic as Diagnostic; use noirc_errors::Span; use thiserror::Error; -use crate::ast::ConstrainKind; -use crate::ast::{BinaryOpKind, FunctionReturnType, IntegerBitSize, Signedness}; +use crate::ast::{ + BinaryOpKind, ConstrainKind, FunctionReturnType, Ident, IntegerBitSize, Signedness, +}; use crate::hir::resolution::errors::ResolverError; use crate::hir_def::expr::HirBinaryOp; use crate::hir_def::traits::TraitConstraint; use crate::hir_def::types::Type; -use crate::macros_api::Ident; -use crate::macros_api::NodeInterner; +use crate::node_interner::NodeInterner; #[derive(Error, Debug, Clone, PartialEq, Eq)] pub enum Source { diff --git a/compiler/noirc_frontend/src/hir/type_check/generics.rs b/compiler/noirc_frontend/src/hir/type_check/generics.rs index b86e2350279..86fc2d25d4e 100644 --- a/compiler/noirc_frontend/src/hir/type_check/generics.rs +++ b/compiler/noirc_frontend/src/hir/type_check/generics.rs @@ -4,8 +4,7 @@ use iter_extended::vecmap; use crate::{ hir_def::traits::NamedType, - macros_api::NodeInterner, - node_interner::{FuncId, TraitId, TypeAliasId}, + node_interner::{FuncId, NodeInterner, TraitId, TypeAliasId}, ResolvedGeneric, StructType, Type, }; diff --git a/compiler/noirc_frontend/src/hir_def/function.rs b/compiler/noirc_frontend/src/hir_def/function.rs index 39c87607446..6ecfdefe996 100644 --- a/compiler/noirc_frontend/src/hir_def/function.rs +++ b/compiler/noirc_frontend/src/hir_def/function.rs @@ -5,11 +5,10 @@ use noirc_errors::{Location, Span}; use super::expr::{HirBlockExpression, HirExpression, HirIdent}; use super::stmt::HirPattern; use super::traits::TraitConstraint; -use crate::ast::{FunctionKind, FunctionReturnType, Visibility}; +use crate::ast::{BlockExpression, FunctionKind, FunctionReturnType, Visibility}; use crate::graph::CrateId; use crate::hir::def_map::LocalModuleId; -use crate::macros_api::{BlockExpression, StructId}; -use crate::node_interner::{ExprId, NodeInterner, TraitId, TraitImplId}; +use crate::node_interner::{ExprId, NodeInterner, StructId, TraitId, TraitImplId}; use crate::token::CustomAttribute; use crate::{ResolvedGeneric, Type}; diff --git a/compiler/noirc_frontend/src/hir_def/stmt.rs b/compiler/noirc_frontend/src/hir_def/stmt.rs index 0b4dbeb3006..b97e99583bb 100644 --- a/compiler/noirc_frontend/src/hir_def/stmt.rs +++ b/compiler/noirc_frontend/src/hir_def/stmt.rs @@ -1,7 +1,7 @@ use super::expr::HirIdent; use crate::ast::Ident; -use crate::macros_api::SecondaryAttribute; use crate::node_interner::{ExprId, StmtId}; +use crate::token::SecondaryAttribute; use crate::Type; use fm::FileId; use noirc_errors::{Location, Span}; diff --git a/compiler/noirc_frontend/src/lib.rs b/compiler/noirc_frontend/src/lib.rs index 4391c760701..9d98b125e32 100644 --- a/compiler/noirc_frontend/src/lib.rs +++ b/compiler/noirc_frontend/src/lib.rs @@ -36,31 +36,3 @@ pub use hir_def::types::*; // Unit tests that involve all modules pub mod tests; - -// API for experimental macros feature -pub mod macros_api { - - pub use acvm::FieldElement; - pub use fm::FileId; - pub use noirc_errors::Span; - - pub use crate::graph::CrateId; - pub use crate::hir_def::expr::{HirExpression, HirLiteral}; - pub use crate::hir_def::stmt::HirStatement; - pub use crate::node_interner::{NodeInterner, StructId}; - pub use crate::parser::{parse_program, SortedModule}; - pub use crate::token::SecondaryAttribute; - - pub use crate::ast::{ - BlockExpression, CallExpression, CastExpression, Documented, Expression, ExpressionKind, - FunctionReturnType, Ident, IndexExpression, ItemVisibility, LetStatement, Literal, - MemberAccessExpression, MethodCallExpression, NoirFunction, Path, PathKind, Pattern, - Statement, UnresolvedType, UnresolvedTypeData, Visibility, - }; - pub use crate::ast::{ - ForLoopStatement, ForRange, FunctionDefinition, ImportStatement, NoirStruct, Param, - PrefixExpression, Signedness, StatementKind, TypeImpl, UnaryOp, - }; - pub use crate::hir::{def_map::ModuleDefId, Context as HirContext}; - pub use crate::{StructType, Type}; -} diff --git a/compiler/noirc_frontend/src/locations.rs b/compiler/noirc_frontend/src/locations.rs index cba667d5dcb..65adf4ca9c4 100644 --- a/compiler/noirc_frontend/src/locations.rs +++ b/compiler/noirc_frontend/src/locations.rs @@ -6,8 +6,9 @@ use rustc_hash::FxHashMap as HashMap; use crate::{ ast::{FunctionDefinition, ItemVisibility}, hir::def_map::{ModuleDefId, ModuleId}, - macros_api::{NodeInterner, StructId}, - node_interner::{DefinitionId, FuncId, GlobalId, ReferenceId, TraitId, TypeAliasId}, + node_interner::{ + DefinitionId, FuncId, GlobalId, NodeInterner, ReferenceId, StructId, TraitId, TypeAliasId, + }, }; use petgraph::prelude::NodeIndex as PetGraphIndex; diff --git a/compiler/noirc_frontend/src/node_interner.rs b/compiler/noirc_frontend/src/node_interner.rs index 8d665ceacbf..295678c1412 100644 --- a/compiler/noirc_frontend/src/node_interner.rs +++ b/compiler/noirc_frontend/src/node_interner.rs @@ -13,22 +13,17 @@ use petgraph::prelude::DiGraph; use petgraph::prelude::NodeIndex as PetGraphIndex; use rustc_hash::FxHashMap as HashMap; -use crate::ast::ExpressionKind; -use crate::ast::Ident; -use crate::ast::LValue; -use crate::ast::Pattern; -use crate::ast::StatementKind; -use crate::ast::UnresolvedTypeData; +use crate::ast::{ + ExpressionKind, Ident, LValue, Pattern, StatementKind, UnaryOp, UnresolvedTypeData, +}; use crate::graph::CrateId; use crate::hir::comptime; use crate::hir::def_collector::dc_crate::CompilationError; use crate::hir::def_collector::dc_crate::{UnresolvedStruct, UnresolvedTrait, UnresolvedTypeAlias}; use crate::hir::def_map::DefMaps; -use crate::hir::def_map::{LocalModuleId, ModuleId}; +use crate::hir::def_map::{LocalModuleId, ModuleDefId, ModuleId}; use crate::hir::type_check::generics::TraitGenerics; use crate::hir_def::traits::NamedType; -use crate::macros_api::ModuleDefId; -use crate::macros_api::UnaryOp; use crate::usage_tracker::UnusedItem; use crate::usage_tracker::UsageTracker; use crate::QuotedType; diff --git a/compiler/noirc_frontend/src/parser/parser/attributes.rs b/compiler/noirc_frontend/src/parser/parser/attributes.rs index 66d0ca29ca6..dc363248d72 100644 --- a/compiler/noirc_frontend/src/parser/parser/attributes.rs +++ b/compiler/noirc_frontend/src/parser/parser/attributes.rs @@ -2,9 +2,8 @@ use chumsky::Parser; use noirc_errors::Span; use crate::{ - macros_api::SecondaryAttribute, parser::{NoirParser, ParserError, ParserErrorReason}, - token::{Attribute, Attributes, Token, TokenKind}, + token::{Attribute, Attributes, SecondaryAttribute, Token, TokenKind}, }; use super::primitives::token_kind; diff --git a/compiler/noirc_frontend/src/parser/parser/function.rs b/compiler/noirc_frontend/src/parser/parser/function.rs index dc8b968ea7a..7b1f67a48bd 100644 --- a/compiler/noirc_frontend/src/parser/parser/function.rs +++ b/compiler/noirc_frontend/src/parser/parser/function.rs @@ -14,9 +14,9 @@ use crate::{ }; use crate::{ ast::{ - FunctionDefinition, FunctionReturnType, ItemVisibility, NoirFunction, Param, Visibility, + FunctionDefinition, FunctionReturnType, ItemVisibility, NoirFunction, Param, + UnresolvedTypeData, Visibility, }, - macros_api::UnresolvedTypeData, parser::{ParserError, ParserErrorReason}, }; use crate::{ diff --git a/compiler/noirc_frontend/src/parser/parser/lambdas.rs b/compiler/noirc_frontend/src/parser/parser/lambdas.rs index 5ef0b918375..68b5724edc6 100644 --- a/compiler/noirc_frontend/src/parser/parser/lambdas.rs +++ b/compiler/noirc_frontend/src/parser/parser/lambdas.rs @@ -1,8 +1,7 @@ use chumsky::{primitive::just, Parser}; use super::{parse_type, pattern}; -use crate::ast::{Expression, ExpressionKind, Lambda, Pattern, UnresolvedType}; -use crate::macros_api::UnresolvedTypeData; +use crate::ast::{Expression, ExpressionKind, Lambda, Pattern, UnresolvedType, UnresolvedTypeData}; use crate::{ parser::{labels::ParsingRuleLabel, parameter_name_recovery, parameter_recovery, NoirParser}, token::Token, diff --git a/compiler/noirc_frontend/src/parser/parser/path.rs b/compiler/noirc_frontend/src/parser/parser/path.rs index 1c9c24f5376..4babd0f6730 100644 --- a/compiler/noirc_frontend/src/parser/parser/path.rs +++ b/compiler/noirc_frontend/src/parser/parser/path.rs @@ -1,5 +1,6 @@ -use crate::ast::{AsTraitPath, Ident, Path, PathKind, PathSegment, TypePath, UnresolvedType}; -use crate::macros_api::ExpressionKind; +use crate::ast::{ + AsTraitPath, ExpressionKind, Ident, Path, PathKind, PathSegment, TypePath, UnresolvedType, +}; use crate::parser::{NoirParser, ParserError, ParserErrorReason}; use crate::token::{Keyword, Token}; diff --git a/compiler/noirc_frontend/src/parser/parser/primitives.rs b/compiler/noirc_frontend/src/parser/parser/primitives.rs index 7fcca89f70c..5a040f23619 100644 --- a/compiler/noirc_frontend/src/parser/parser/primitives.rs +++ b/compiler/noirc_frontend/src/parser/parser/primitives.rs @@ -1,7 +1,8 @@ use chumsky::prelude::*; -use crate::ast::{ExpressionKind, GenericTypeArgs, Ident, PathSegment, UnaryOp}; -use crate::macros_api::{StatementKind, UnresolvedType}; +use crate::ast::{ + ExpressionKind, GenericTypeArgs, Ident, PathSegment, StatementKind, UnaryOp, UnresolvedType, +}; use crate::parser::ParserErrorReason; use crate::{ parser::{labels::ParsingRuleLabel, ExprParser, NoirParser, ParserError}, diff --git a/compiler/noirc_frontend/src/parser/parser/traits.rs b/compiler/noirc_frontend/src/parser/parser/traits.rs index b95319f6da0..78453d7f7a2 100644 --- a/compiler/noirc_frontend/src/parser/parser/traits.rs +++ b/compiler/noirc_frontend/src/parser/parser/traits.rs @@ -10,10 +10,9 @@ use super::{ }; use crate::ast::{ - Documented, Expression, ItemVisibility, NoirTrait, NoirTraitImpl, TraitBound, TraitImplItem, - TraitImplItemKind, TraitItem, UnresolvedTraitConstraint, UnresolvedType, + Documented, Expression, ItemVisibility, NoirTrait, NoirTraitImpl, Pattern, TraitBound, + TraitImplItem, TraitImplItemKind, TraitItem, UnresolvedTraitConstraint, UnresolvedType, }; -use crate::macros_api::Pattern; use crate::parser::spanned; use crate::{ parser::{ diff --git a/compiler/noirc_frontend/src/usage_tracker.rs b/compiler/noirc_frontend/src/usage_tracker.rs index 275ca1f964b..0a112c6937d 100644 --- a/compiler/noirc_frontend/src/usage_tracker.rs +++ b/compiler/noirc_frontend/src/usage_tracker.rs @@ -3,8 +3,7 @@ use std::collections::HashMap; use crate::{ ast::{Ident, ItemVisibility}, hir::def_map::ModuleId, - macros_api::StructId, - node_interner::{FuncId, GlobalId, TraitId, TypeAliasId}, + node_interner::{FuncId, GlobalId, StructId, TraitId, TypeAliasId}, }; #[derive(Debug, Copy, Clone, PartialEq, Eq)] diff --git a/tooling/lsp/src/modules.rs b/tooling/lsp/src/modules.rs index f1eff3b5a7d..9f9a826d6ca 100644 --- a/tooling/lsp/src/modules.rs +++ b/tooling/lsp/src/modules.rs @@ -3,9 +3,8 @@ use std::collections::BTreeMap; use noirc_frontend::{ ast::ItemVisibility, graph::{CrateId, Dependency}, - hir::def_map::{CrateDefMap, ModuleId}, - macros_api::{ModuleDefId, NodeInterner}, - node_interner::ReferenceId, + hir::def_map::{CrateDefMap, ModuleDefId, ModuleId}, + node_interner::{NodeInterner, ReferenceId}, }; use crate::visibility::is_visible; diff --git a/tooling/lsp/src/requests/code_action.rs b/tooling/lsp/src/requests/code_action.rs index 64eccab8947..9299dc76368 100644 --- a/tooling/lsp/src/requests/code_action.rs +++ b/tooling/lsp/src/requests/code_action.rs @@ -15,7 +15,7 @@ use noirc_frontend::{ ast::{ConstructorExpression, ItemVisibility, NoirTraitImpl, Path, UseTree, Visitor}, graph::CrateId, hir::def_map::{CrateDefMap, LocalModuleId, ModuleId}, - macros_api::NodeInterner, + node_interner::NodeInterner, }; use noirc_frontend::{ parser::{Item, ItemKind, ParsedSubModule}, diff --git a/tooling/lsp/src/requests/code_action/import_or_qualify.rs b/tooling/lsp/src/requests/code_action/import_or_qualify.rs index 0d97ccde2ed..769f801a6f1 100644 --- a/tooling/lsp/src/requests/code_action/import_or_qualify.rs +++ b/tooling/lsp/src/requests/code_action/import_or_qualify.rs @@ -2,7 +2,7 @@ use lsp_types::{Position, Range, TextEdit}; use noirc_errors::Location; use noirc_frontend::{ ast::{Ident, Path}, - macros_api::ModuleDefId, + hir::def_map::ModuleDefId, }; use crate::{ diff --git a/tooling/lsp/src/requests/code_lens_request.rs b/tooling/lsp/src/requests/code_lens_request.rs index 4565569e67b..42f2af3a7bf 100644 --- a/tooling/lsp/src/requests/code_lens_request.rs +++ b/tooling/lsp/src/requests/code_lens_request.rs @@ -89,8 +89,8 @@ fn on_code_lens_request_inner( } pub(crate) fn collect_lenses_for_package( - context: &noirc_frontend::macros_api::HirContext, - crate_id: noirc_frontend::macros_api::CrateId, + context: &noirc_frontend::hir::Context, + crate_id: noirc_frontend::graph::CrateId, workspace: &Workspace, package: &Package, file_path: Option<&std::path::PathBuf>, diff --git a/tooling/lsp/src/requests/completion.rs b/tooling/lsp/src/requests/completion.rs index 588f5b18f1b..2db1665b639 100644 --- a/tooling/lsp/src/requests/completion.rs +++ b/tooling/lsp/src/requests/completion.rs @@ -24,9 +24,9 @@ use noirc_frontend::{ UseTreeKind, Visitor, }, graph::{CrateId, Dependency}, - hir::def_map::{CrateDefMap, LocalModuleId, ModuleId}, + hir::def_map::{CrateDefMap, LocalModuleId, ModuleDefId, ModuleId}, hir_def::traits::Trait, - macros_api::{ModuleDefId, NodeInterner}, + node_interner::NodeInterner, node_interner::ReferenceId, parser::{Item, ItemKind, ParsedSubModule}, token::{CustomAttribute, Token, Tokens}, diff --git a/tooling/lsp/src/requests/completion/auto_import.rs b/tooling/lsp/src/requests/completion/auto_import.rs index 20b126a248d..e2dd582f2f3 100644 --- a/tooling/lsp/src/requests/completion/auto_import.rs +++ b/tooling/lsp/src/requests/completion/auto_import.rs @@ -1,5 +1,5 @@ use lsp_types::{Position, Range, TextEdit}; -use noirc_frontend::macros_api::ModuleDefId; +use noirc_frontend::hir::def_map::ModuleDefId; use crate::modules::{relative_module_full_path, relative_module_id_path}; diff --git a/tooling/lsp/src/requests/completion/completion_items.rs b/tooling/lsp/src/requests/completion/completion_items.rs index 809988c34a5..f281f5e3abf 100644 --- a/tooling/lsp/src/requests/completion/completion_items.rs +++ b/tooling/lsp/src/requests/completion/completion_items.rs @@ -4,10 +4,9 @@ use lsp_types::{ }; use noirc_frontend::{ ast::AttributeTarget, - hir::def_map::ModuleId, + hir::def_map::{ModuleDefId, ModuleId}, hir_def::{function::FuncMeta, stmt::HirPattern}, - macros_api::{ModuleDefId, StructId}, - node_interner::{FuncId, GlobalId, ReferenceId, TraitId, TypeAliasId}, + node_interner::{FuncId, GlobalId, ReferenceId, StructId, TraitId, TypeAliasId}, QuotedType, Type, }; diff --git a/tooling/lsp/src/requests/hover.rs b/tooling/lsp/src/requests/hover.rs index 2628c9b2ab6..25a401f488e 100644 --- a/tooling/lsp/src/requests/hover.rs +++ b/tooling/lsp/src/requests/hover.rs @@ -7,11 +7,15 @@ use noirc_frontend::{ ast::Visibility, elaborator::types::try_eval_array_length_id, hir::def_map::ModuleId, - hir_def::{expr::HirArrayLiteral, stmt::HirPattern, traits::Trait}, - macros_api::{HirExpression, HirLiteral, NodeInterner, StructId}, + hir_def::{ + expr::{HirArrayLiteral, HirExpression, HirLiteral}, + stmt::HirPattern, + traits::Trait, + }, node_interner::{ DefinitionId, DefinitionKind, ExprId, FuncId, GlobalId, ReferenceId, TraitId, TypeAliasId, }, + node_interner::{NodeInterner, StructId}, Generics, Shared, StructType, Type, TypeAlias, TypeBinding, TypeVariable, }; diff --git a/tooling/lsp/src/requests/inlay_hint.rs b/tooling/lsp/src/requests/inlay_hint.rs index 2eef4f6e262..ea73cc688ef 100644 --- a/tooling/lsp/src/requests/inlay_hint.rs +++ b/tooling/lsp/src/requests/inlay_hint.rs @@ -15,8 +15,7 @@ use noirc_frontend::{ UnresolvedTypeData, Visitor, }, hir_def::stmt::HirPattern, - macros_api::NodeInterner, - node_interner::ReferenceId, + node_interner::{NodeInterner, ReferenceId}, parser::{Item, ParsedSubModule}, Type, TypeBinding, TypeVariable, TypeVariableKind, }; diff --git a/tooling/lsp/src/requests/mod.rs b/tooling/lsp/src/requests/mod.rs index 576d026081d..597d8355468 100644 --- a/tooling/lsp/src/requests/mod.rs +++ b/tooling/lsp/src/requests/mod.rs @@ -18,7 +18,7 @@ use nargo_fmt::Config; use noirc_frontend::graph::CrateId; use noirc_frontend::hir::def_map::CrateDefMap; -use noirc_frontend::{graph::Dependency, macros_api::NodeInterner}; +use noirc_frontend::{graph::Dependency, node_interner::NodeInterner}; use serde::{Deserialize, Serialize}; use crate::{ diff --git a/tooling/lsp/src/requests/signature_help.rs b/tooling/lsp/src/requests/signature_help.rs index b075fea1d1e..c0d40656c19 100644 --- a/tooling/lsp/src/requests/signature_help.rs +++ b/tooling/lsp/src/requests/signature_help.rs @@ -12,8 +12,7 @@ use noirc_frontend::{ MethodCallExpression, Statement, Visitor, }, hir_def::{function::FuncMeta, stmt::HirPattern}, - macros_api::NodeInterner, - node_interner::ReferenceId, + node_interner::{NodeInterner, ReferenceId}, parser::Item, ParsedModule, Type, }; diff --git a/tooling/lsp/src/trait_impl_method_stub_generator.rs b/tooling/lsp/src/trait_impl_method_stub_generator.rs index 56d2e5e1ea1..4fe039e7bd7 100644 --- a/tooling/lsp/src/trait_impl_method_stub_generator.rs +++ b/tooling/lsp/src/trait_impl_method_stub_generator.rs @@ -3,13 +3,13 @@ use std::collections::BTreeMap; use noirc_frontend::{ ast::NoirTraitImpl, graph::CrateId, + hir::def_map::ModuleDefId, hir::{ def_map::{CrateDefMap, ModuleId}, type_check::generics::TraitGenerics, }, hir_def::{function::FuncMeta, stmt::HirPattern, traits::Trait}, - macros_api::{ModuleDefId, NodeInterner}, - node_interner::{FunctionModifiers, ReferenceId}, + node_interner::{FunctionModifiers, NodeInterner, ReferenceId}, Kind, ResolvedGeneric, Type, TypeVariableKind, }; diff --git a/tooling/nargo_fmt/Cargo.toml b/tooling/nargo_fmt/Cargo.toml index 9868f259097..1e4d93b3125 100644 --- a/tooling/nargo_fmt/Cargo.toml +++ b/tooling/nargo_fmt/Cargo.toml @@ -11,6 +11,7 @@ workspace = true [dependencies] bytecount = "0.6.3" +noirc_errors.workspace = true noirc_frontend.workspace = true serde.workspace = true toml.workspace = true diff --git a/tooling/nargo_fmt/src/items.rs b/tooling/nargo_fmt/src/items.rs index 57757982e83..e68be7cdc95 100644 --- a/tooling/nargo_fmt/src/items.rs +++ b/tooling/nargo_fmt/src/items.rs @@ -1,4 +1,4 @@ -use noirc_frontend::macros_api::Span; +use noirc_errors::Span; use crate::{ utils::{comment_len, find_comment_end}, diff --git a/tooling/nargo_fmt/src/rewrite/expr.rs b/tooling/nargo_fmt/src/rewrite/expr.rs index 873b5c87056..98b50e92476 100644 --- a/tooling/nargo_fmt/src/rewrite/expr.rs +++ b/tooling/nargo_fmt/src/rewrite/expr.rs @@ -1,8 +1,9 @@ +use noirc_errors::Span; use noirc_frontend::ast::{ ArrayLiteral, BlockExpression, Expression, ExpressionKind, Literal, Path, PathKind, UnaryOp, UnresolvedType, }; -use noirc_frontend::{macros_api::Span, token::Token}; +use noirc_frontend::token::Token; use crate::rewrite; use crate::visitor::{ diff --git a/tooling/nargo_fmt/src/visitor/item.rs b/tooling/nargo_fmt/src/visitor/item.rs index 2feae4b390c..12ace814369 100644 --- a/tooling/nargo_fmt/src/visitor/item.rs +++ b/tooling/nargo_fmt/src/visitor/item.rs @@ -6,9 +6,8 @@ use crate::{ }, visitor::expr::{format_seq, NewlineMode}, }; -use noirc_frontend::{ - ast::{ItemVisibility, NoirFunction, TraitImplItemKind, Visibility}, - macros_api::UnresolvedTypeData, +use noirc_frontend::ast::{ + ItemVisibility, NoirFunction, TraitImplItemKind, UnresolvedTypeData, Visibility, }; use noirc_frontend::{ hir::resolution::errors::Span, diff --git a/tooling/nargo_fmt/src/visitor/stmt.rs b/tooling/nargo_fmt/src/visitor/stmt.rs index 8908aabd87c..7298be641d9 100644 --- a/tooling/nargo_fmt/src/visitor/stmt.rs +++ b/tooling/nargo_fmt/src/visitor/stmt.rs @@ -1,6 +1,6 @@ use std::iter::zip; -use noirc_frontend::macros_api::Span; +use noirc_errors::Span; use noirc_frontend::ast::{ConstrainKind, ConstrainStatement, ForRange, Statement, StatementKind}; From 85c502c9fa69b151fdff1a97b5a97ad78cb599ab Mon Sep 17 00:00:00 2001 From: Michael J Klein Date: Tue, 1 Oct 2024 12:00:44 -0400 Subject: [PATCH 26/36] feat: refactor SSA passes to run on individual functions (#6072) # Description ## Problem\* ~Resolves https://github.com/noir-lang/noir/issues/5839~ Refactors passes that only act on individual functions into `impl Function { fn do_pass() }` ## Summary\* ## Additional Context ## Documentation\* Check one: - [x] No documentation needed. - [ ] Documentation included in this PR. - [ ] **[For Experimental Features]** Documentation to be submitted in a separate PR. # PR Checklist\* - [x] I have tested the changes locally. - [x] I have formatted the changes with [Prettier](https://prettier.io/) and/or `cargo fmt` on default settings. --- .../noirc_evaluator/src/ssa/opt/array_set.rs | 49 ++++++++------ .../src/ssa/opt/as_slice_length.rs | 10 ++- .../src/ssa/opt/assert_constant.rs | 33 ++++++---- .../src/ssa/opt/constant_folding.rs | 28 ++++---- compiler/noirc_evaluator/src/ssa/opt/die.rs | 60 ++++++++--------- .../noirc_evaluator/src/ssa/opt/mem2reg.rs | 14 ++-- compiler/noirc_evaluator/src/ssa/opt/rc.rs | 40 ++++++------ .../src/ssa/opt/remove_bit_shifts.rs | 28 ++++---- .../src/ssa/opt/remove_enable_side_effects.rs | 22 ++++--- .../src/ssa/opt/remove_if_else.rs | 18 +++-- .../src/ssa/opt/resolve_is_unconstrained.rs | 50 +++++++------- .../src/ssa/opt/simplify_cfg.rs | 65 ++++++++++--------- .../noirc_evaluator/src/ssa/opt/unrolling.rs | 47 ++++++++++++-- 13 files changed, 275 insertions(+), 189 deletions(-) diff --git a/compiler/noirc_evaluator/src/ssa/opt/array_set.rs b/compiler/noirc_evaluator/src/ssa/opt/array_set.rs index 6d48b8c0d67..267a2c105f2 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/array_set.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/array_set.rs @@ -2,6 +2,7 @@ use crate::ssa::{ ir::{ basic_block::BasicBlockId, dfg::DataFlowGraph, + function::Function, instruction::{Instruction, InstructionId, TerminatorInstruction}, types::Type::{Array, Slice}, value::ValueId, @@ -17,32 +18,38 @@ impl Ssa { #[tracing::instrument(level = "trace", skip(self))] pub(crate) fn array_set_optimization(mut self) -> Self { for func in self.functions.values_mut() { - let reachable_blocks = func.reachable_blocks(); - - if !func.runtime().is_entry_point() { - assert_eq!(reachable_blocks.len(), 1, "Expected there to be 1 block remaining in Acir function for array_set optimization"); - } - let mut array_to_last_use = HashMap::default(); - let mut instructions_to_update = HashSet::default(); - let mut arrays_from_load = HashSet::default(); - - for block in reachable_blocks.iter() { - analyze_last_uses( - &func.dfg, - *block, - &mut array_to_last_use, - &mut instructions_to_update, - &mut arrays_from_load, - ); - } - for block in reachable_blocks { - make_mutable(&mut func.dfg, block, &instructions_to_update); - } + func.array_set_optimization(); } self } } +impl Function { + pub(crate) fn array_set_optimization(&mut self) { + let reachable_blocks = self.reachable_blocks(); + + if !self.runtime().is_entry_point() { + assert_eq!(reachable_blocks.len(), 1, "Expected there to be 1 block remaining in Acir function for array_set optimization"); + } + let mut array_to_last_use = HashMap::default(); + let mut instructions_to_update = HashSet::default(); + let mut arrays_from_load = HashSet::default(); + + for block in reachable_blocks.iter() { + analyze_last_uses( + &self.dfg, + *block, + &mut array_to_last_use, + &mut instructions_to_update, + &mut arrays_from_load, + ); + } + for block in reachable_blocks { + make_mutable(&mut self.dfg, block, &instructions_to_update); + } + } +} + /// Builds the set of ArraySet instructions that can be made mutable /// because their input value is unused elsewhere afterward. fn analyze_last_uses( diff --git a/compiler/noirc_evaluator/src/ssa/opt/as_slice_length.rs b/compiler/noirc_evaluator/src/ssa/opt/as_slice_length.rs index 69eab1da0ed..59917e8589b 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/as_slice_length.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/as_slice_length.rs @@ -20,13 +20,19 @@ impl Ssa { #[tracing::instrument(level = "trace", skip(self))] pub(crate) fn as_slice_optimization(mut self) -> Self { for func in self.functions.values_mut() { - let known_slice_lengths = known_slice_lengths(func); - replace_known_slice_lengths(func, known_slice_lengths); + func.as_slice_optimization(); } self } } +impl Function { + pub(crate) fn as_slice_optimization(&mut self) { + let known_slice_lengths = known_slice_lengths(self); + replace_known_slice_lengths(self, known_slice_lengths); + } +} + fn known_slice_lengths(func: &Function) -> HashMap { let mut known_slice_lengths = HashMap::default(); for block_id in func.reachable_blocks() { diff --git a/compiler/noirc_evaluator/src/ssa/opt/assert_constant.rs b/compiler/noirc_evaluator/src/ssa/opt/assert_constant.rs index ae0681a55ff..348c78683a0 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/assert_constant.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/assert_constant.rs @@ -26,22 +26,31 @@ impl Ssa { mut self, ) -> Result { for function in self.functions.values_mut() { - for block in function.reachable_blocks() { - // Unfortunately we can't just use instructions.retain(...) here since - // check_instruction can also return an error - let instructions = function.dfg[block].take_instructions(); - let mut filtered_instructions = Vec::with_capacity(instructions.len()); + function.evaluate_static_assert_and_assert_constant()?; + } + Ok(self) + } +} - for instruction in instructions { - if check_instruction(function, instruction)? { - filtered_instructions.push(instruction); - } - } +impl Function { + pub(crate) fn evaluate_static_assert_and_assert_constant( + &mut self, + ) -> Result<(), RuntimeError> { + for block in self.reachable_blocks() { + // Unfortunately we can't just use instructions.retain(...) here since + // check_instruction can also return an error + let instructions = self.dfg[block].take_instructions(); + let mut filtered_instructions = Vec::with_capacity(instructions.len()); - *function.dfg[block].instructions_mut() = filtered_instructions; + for instruction in instructions { + if check_instruction(self, instruction)? { + filtered_instructions.push(instruction); + } } + + *self.dfg[block].instructions_mut() = filtered_instructions; } - Ok(self) + Ok(()) } } diff --git a/compiler/noirc_evaluator/src/ssa/opt/constant_folding.rs b/compiler/noirc_evaluator/src/ssa/opt/constant_folding.rs index ff9a63c8d79..ea422fdff09 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/constant_folding.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/constant_folding.rs @@ -44,7 +44,7 @@ impl Ssa { #[tracing::instrument(level = "trace", skip(self))] pub(crate) fn fold_constants(mut self) -> Ssa { for function in self.functions.values_mut() { - constant_fold(function, false); + function.constant_fold(false); } self } @@ -57,25 +57,27 @@ impl Ssa { #[tracing::instrument(level = "trace", skip(self))] pub(crate) fn fold_constants_using_constraints(mut self) -> Ssa { for function in self.functions.values_mut() { - constant_fold(function, true); + function.constant_fold(true); } self } } -/// The structure of this pass is simple: -/// Go through each block and re-insert all instructions. -fn constant_fold(function: &mut Function, use_constraint_info: bool) { - let mut context = Context { use_constraint_info, ..Default::default() }; - context.block_queue.push(function.entry_block()); +impl Function { + /// The structure of this pass is simple: + /// Go through each block and re-insert all instructions. + pub(crate) fn constant_fold(&mut self, use_constraint_info: bool) { + let mut context = Context { use_constraint_info, ..Default::default() }; + context.block_queue.push(self.entry_block()); - while let Some(block) = context.block_queue.pop() { - if context.visited_blocks.contains(&block) { - continue; - } + while let Some(block) = context.block_queue.pop() { + if context.visited_blocks.contains(&block) { + continue; + } - context.visited_blocks.insert(block); - context.fold_constants_in_block(function, block); + context.visited_blocks.insert(block); + context.fold_constants_in_block(self, block); + } } } diff --git a/compiler/noirc_evaluator/src/ssa/opt/die.rs b/compiler/noirc_evaluator/src/ssa/opt/die.rs index 54af2e9ad5d..beca7c41e5c 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/die.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/die.rs @@ -25,44 +25,46 @@ impl Ssa { #[tracing::instrument(level = "trace", skip(self))] pub(crate) fn dead_instruction_elimination(mut self) -> Ssa { for function in self.functions.values_mut() { - dead_instruction_elimination(function, true); + function.dead_instruction_elimination(true); } self } } -/// Removes any unused instructions in the reachable blocks of the given function. -/// -/// The blocks of the function are iterated in post order, such that any blocks containing -/// instructions that reference results from an instruction in another block are evaluated first. -/// If we did not iterate blocks in this order we could not safely say whether or not the results -/// of its instructions are needed elsewhere. -fn dead_instruction_elimination(function: &mut Function, insert_out_of_bounds_checks: bool) { - let mut context = Context::default(); - for call_data in &function.dfg.data_bus.call_data { - context.mark_used_instruction_results(&function.dfg, call_data.array_id); - } +impl Function { + /// Removes any unused instructions in the reachable blocks of the given function. + /// + /// The blocks of the function are iterated in post order, such that any blocks containing + /// instructions that reference results from an instruction in another block are evaluated first. + /// If we did not iterate blocks in this order we could not safely say whether or not the results + /// of its instructions are needed elsewhere. + pub(crate) fn dead_instruction_elimination(&mut self, insert_out_of_bounds_checks: bool) { + let mut context = Context::default(); + for call_data in &self.dfg.data_bus.call_data { + context.mark_used_instruction_results(&self.dfg, call_data.array_id); + } - let mut inserted_out_of_bounds_checks = false; + let mut inserted_out_of_bounds_checks = false; - let blocks = PostOrder::with_function(function); - for block in blocks.as_slice() { - inserted_out_of_bounds_checks |= context.remove_unused_instructions_in_block( - function, - *block, - insert_out_of_bounds_checks, - ); - } + let blocks = PostOrder::with_function(self); + for block in blocks.as_slice() { + inserted_out_of_bounds_checks |= context.remove_unused_instructions_in_block( + self, + *block, + insert_out_of_bounds_checks, + ); + } - // If we inserted out of bounds check, let's run the pass again with those new - // instructions (we don't want to remove those checks, or instructions that are - // dependencies of those checks) - if inserted_out_of_bounds_checks { - dead_instruction_elimination(function, false); - return; - } + // If we inserted out of bounds check, let's run the pass again with those new + // instructions (we don't want to remove those checks, or instructions that are + // dependencies of those checks) + if inserted_out_of_bounds_checks { + self.dead_instruction_elimination(false); + return; + } - context.remove_rc_instructions(&mut function.dfg); + context.remove_rc_instructions(&mut self.dfg); + } } /// Per function context for tracking unused values and which instructions to remove. diff --git a/compiler/noirc_evaluator/src/ssa/opt/mem2reg.rs b/compiler/noirc_evaluator/src/ssa/opt/mem2reg.rs index 68c04e3b4b4..165e0e36b71 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/mem2reg.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/mem2reg.rs @@ -123,16 +123,22 @@ impl Ssa { #[tracing::instrument(level = "trace", skip(self))] pub(crate) fn mem2reg(mut self) -> Ssa { for function in self.functions.values_mut() { - let mut context = PerFunctionContext::new(function); - context.mem2reg(); - context.remove_instructions(); - context.update_data_bus(); + function.mem2reg(); } self } } +impl Function { + pub(crate) fn mem2reg(&mut self) { + let mut context = PerFunctionContext::new(self); + context.mem2reg(); + context.remove_instructions(); + context.update_data_bus(); + } +} + struct PerFunctionContext<'f> { cfg: ControlFlowGraph, post_order: PostOrder, diff --git a/compiler/noirc_evaluator/src/ssa/opt/rc.rs b/compiler/noirc_evaluator/src/ssa/opt/rc.rs index 06025fd9e8b..c879f6c8fff 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/rc.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/rc.rs @@ -22,7 +22,7 @@ impl Ssa { #[tracing::instrument(level = "trace", skip(self))] pub(crate) fn remove_paired_rc(mut self) -> Ssa { for function in self.functions.values_mut() { - remove_paired_rc(function); + function.remove_paired_rc(); } self } @@ -44,26 +44,28 @@ pub(crate) struct RcInstruction { pub(crate) possibly_mutated: bool, } -/// This function is very simplistic for now. It takes advantage of the fact that dec_rc -/// instructions are currently issued only at the end of a function for parameters and will -/// only check the first and last block for inc & dec rc instructions to be removed. The rest -/// of the function is still checked for array_set instructions. -/// -/// This restriction lets this function largely ignore merging intermediate results from other -/// blocks and handling loops. -fn remove_paired_rc(function: &mut Function) { - // `dec_rc` is only issued for parameters currently so we can speed things - // up a bit by skipping any functions without them. - if !contains_array_parameter(function) { - return; - } +impl Function { + /// This function is very simplistic for now. It takes advantage of the fact that dec_rc + /// instructions are currently issued only at the end of a function for parameters and will + /// only check the first and last block for inc & dec rc instructions to be removed. The rest + /// of the function is still checked for array_set instructions. + /// + /// This restriction lets this function largely ignore merging intermediate results from other + /// blocks and handling loops. + pub(crate) fn remove_paired_rc(&mut self) { + // `dec_rc` is only issued for parameters currently so we can speed things + // up a bit by skipping any functions without them. + if !contains_array_parameter(self) { + return; + } - let mut context = Context::default(); + let mut context = Context::default(); - context.find_rcs_in_entry_block(function); - context.scan_for_array_sets(function); - let to_remove = context.find_rcs_to_remove(function); - remove_instructions(to_remove, function); + context.find_rcs_in_entry_block(self); + context.scan_for_array_sets(self); + let to_remove = context.find_rcs_to_remove(self); + remove_instructions(to_remove, self); + } } fn contains_array_parameter(function: &mut Function) -> bool { diff --git a/compiler/noirc_evaluator/src/ssa/opt/remove_bit_shifts.rs b/compiler/noirc_evaluator/src/ssa/opt/remove_bit_shifts.rs index 984c0de04ca..4b2d753f072 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/remove_bit_shifts.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/remove_bit_shifts.rs @@ -21,24 +21,30 @@ impl Ssa { #[tracing::instrument(level = "trace", skip(self))] pub(crate) fn remove_bit_shifts(mut self) -> Ssa { for function in self.functions.values_mut() { - remove_bit_shifts(function); + function.remove_bit_shifts(); } self } } -/// The structure of this pass is simple: -/// Go through each block and re-insert all instructions. -fn remove_bit_shifts(function: &mut Function) { - if let RuntimeType::Brillig = function.runtime() { - return; - } +impl Function { + /// The structure of this pass is simple: + /// Go through each block and re-insert all instructions. + pub(crate) fn remove_bit_shifts(&mut self) { + if let RuntimeType::Brillig = self.runtime() { + return; + } - let block = function.entry_block(); - let mut context = - Context { function, new_instructions: Vec::new(), block, call_stack: CallStack::default() }; + let block = self.entry_block(); + let mut context = Context { + function: self, + new_instructions: Vec::new(), + block, + call_stack: CallStack::default(), + }; - context.remove_bit_shifts(); + context.remove_bit_shifts(); + } } struct Context<'f> { diff --git a/compiler/noirc_evaluator/src/ssa/opt/remove_enable_side_effects.rs b/compiler/noirc_evaluator/src/ssa/opt/remove_enable_side_effects.rs index a56786b2603..c611ab682bf 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/remove_enable_side_effects.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/remove_enable_side_effects.rs @@ -29,23 +29,25 @@ impl Ssa { #[tracing::instrument(level = "trace", skip(self))] pub(crate) fn remove_enable_side_effects(mut self) -> Ssa { for function in self.functions.values_mut() { - remove_enable_side_effects(function); + function.remove_enable_side_effects(); } self } } -fn remove_enable_side_effects(function: &mut Function) { - let mut context = Context::default(); - context.block_queue.push(function.entry_block()); +impl Function { + pub(crate) fn remove_enable_side_effects(&mut self) { + let mut context = Context::default(); + context.block_queue.push(self.entry_block()); - while let Some(block) = context.block_queue.pop() { - if context.visited_blocks.contains(&block) { - continue; - } + while let Some(block) = context.block_queue.pop() { + if context.visited_blocks.contains(&block) { + continue; + } - context.visited_blocks.insert(block); - context.remove_enable_side_effects_in_block(function, block); + context.visited_blocks.insert(block); + context.remove_enable_side_effects_in_block(self, block); + } } } diff --git a/compiler/noirc_evaluator/src/ssa/opt/remove_if_else.rs b/compiler/noirc_evaluator/src/ssa/opt/remove_if_else.rs index 8d6225afabf..9f01800bca6 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/remove_if_else.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/remove_if_else.rs @@ -28,17 +28,23 @@ impl Ssa { #[tracing::instrument(level = "trace", skip(self))] pub(crate) fn remove_if_else(mut self) -> Ssa { for function in self.functions.values_mut() { - // This should match the check in flatten_cfg - if let crate::ssa::ir::function::RuntimeType::Brillig = function.runtime() { - continue; - } - - Context::default().remove_if_else(function); + function.remove_if_else(); } self } } +impl Function { + pub(crate) fn remove_if_else(&mut self) { + // This should match the check in flatten_cfg + if let crate::ssa::ir::function::RuntimeType::Brillig = self.runtime() { + // skip + } else { + Context::default().remove_if_else(self); + } + } +} + #[derive(Default)] struct Context { slice_sizes: HashMap, diff --git a/compiler/noirc_evaluator/src/ssa/opt/resolve_is_unconstrained.rs b/compiler/noirc_evaluator/src/ssa/opt/resolve_is_unconstrained.rs index 2c9e33ae528..1768cbddec3 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/resolve_is_unconstrained.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/resolve_is_unconstrained.rs @@ -17,40 +17,42 @@ impl Ssa { #[tracing::instrument(level = "trace", skip(self))] pub(crate) fn resolve_is_unconstrained(mut self) -> Self { for func in self.functions.values_mut() { - replace_is_unconstrained_result(func); + func.replace_is_unconstrained_result(); } self } } -fn replace_is_unconstrained_result(func: &mut Function) { - let mut is_unconstrained_calls = HashSet::default(); - // Collect all calls to is_unconstrained - for block_id in func.reachable_blocks() { - for &instruction_id in func.dfg[block_id].instructions() { - let target_func = match &func.dfg[instruction_id] { - Instruction::Call { func, .. } => *func, - _ => continue, - }; +impl Function { + pub(crate) fn replace_is_unconstrained_result(&mut self) { + let mut is_unconstrained_calls = HashSet::default(); + // Collect all calls to is_unconstrained + for block_id in self.reachable_blocks() { + for &instruction_id in self.dfg[block_id].instructions() { + let target_func = match &self.dfg[instruction_id] { + Instruction::Call { func, .. } => *func, + _ => continue, + }; - if let Value::Intrinsic(Intrinsic::IsUnconstrained) = &func.dfg[target_func] { - is_unconstrained_calls.insert(instruction_id); + if let Value::Intrinsic(Intrinsic::IsUnconstrained) = &self.dfg[target_func] { + is_unconstrained_calls.insert(instruction_id); + } } } - } - for instruction_id in is_unconstrained_calls { - let call_returns = func.dfg.instruction_results(instruction_id); - let original_return_id = call_returns[0]; + for instruction_id in is_unconstrained_calls { + let call_returns = self.dfg.instruction_results(instruction_id); + let original_return_id = call_returns[0]; - // We replace the result with a fresh id. This will be unused, so the DIE pass will remove the leftover intrinsic call. - func.dfg.replace_result(instruction_id, original_return_id); + // We replace the result with a fresh id. This will be unused, so the DIE pass will remove the leftover intrinsic call. + self.dfg.replace_result(instruction_id, original_return_id); - let is_within_unconstrained = func.dfg.make_constant( - FieldElement::from(matches!(func.runtime(), RuntimeType::Brillig)), - Type::bool(), - ); - // Replace all uses of the original return value with the constant - func.dfg.set_value_from_id(original_return_id, is_within_unconstrained); + let is_within_unconstrained = self.dfg.make_constant( + FieldElement::from(matches!(self.runtime(), RuntimeType::Brillig)), + Type::bool(), + ); + // Replace all uses of the original return value with the constant + self.dfg.set_value_from_id(original_return_id, is_within_unconstrained); + } } } diff --git a/compiler/noirc_evaluator/src/ssa/opt/simplify_cfg.rs b/compiler/noirc_evaluator/src/ssa/opt/simplify_cfg.rs index 6887873dbc3..b77bc8c72f3 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/simplify_cfg.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/simplify_cfg.rs @@ -36,48 +36,51 @@ impl Ssa { #[tracing::instrument(level = "trace", skip(self))] pub(crate) fn simplify_cfg(mut self) -> Self { for function in self.functions.values_mut() { - simplify_function(function); + function.simplify_function(); } self } } -/// Simplify a function's cfg by going through each block to check for any simple blocks that can -/// be inlined into their predecessor. -fn simplify_function(function: &mut Function) { - let mut cfg = ControlFlowGraph::with_function(function); - let mut stack = vec![function.entry_block()]; - let mut visited = HashSet::new(); - - while let Some(block) = stack.pop() { - if visited.insert(block) { - stack.extend(function.dfg[block].successors().filter(|block| !visited.contains(block))); - } +impl Function { + /// Simplify a function's cfg by going through each block to check for any simple blocks that can + /// be inlined into their predecessor. + pub(crate) fn simplify_function(&mut self) { + let mut cfg = ControlFlowGraph::with_function(self); + let mut stack = vec![self.entry_block()]; + let mut visited = HashSet::new(); + + while let Some(block) = stack.pop() { + if visited.insert(block) { + stack.extend(self.dfg[block].successors().filter(|block| !visited.contains(block))); + } - // This call is before try_inline_into_predecessor so that if it succeeds in changing a - // jmpif into a jmp, the block may then be inlined entirely into its predecessor in try_inline_into_predecessor. - check_for_constant_jmpif(function, block, &mut cfg); + // This call is before try_inline_into_predecessor so that if it succeeds in changing a + // jmpif into a jmp, the block may then be inlined entirely into its predecessor in try_inline_into_predecessor. + check_for_constant_jmpif(self, block, &mut cfg); - let mut predecessors = cfg.predecessors(block); + let mut predecessors = cfg.predecessors(block); - if predecessors.len() == 1 { - let predecessor = predecessors.next().expect("Already checked length of predecessors"); - drop(predecessors); + if predecessors.len() == 1 { + let predecessor = + predecessors.next().expect("Already checked length of predecessors"); + drop(predecessors); - // If the block has only 1 predecessor, we can safely remove its block parameters - remove_block_parameters(function, block, predecessor); + // If the block has only 1 predecessor, we can safely remove its block parameters + remove_block_parameters(self, block, predecessor); - // Note: this function relies on `remove_block_parameters` being called first. - // Otherwise the inlined block will refer to parameters that no longer exist. - // - // If successful, `block` will be empty and unreachable after this call, so any - // optimizations performed after this point on the same block should check if - // the inlining here was successful before continuing. - try_inline_into_predecessor(function, &mut cfg, block, predecessor); - } else { - drop(predecessors); + // Note: this function relies on `remove_block_parameters` being called first. + // Otherwise the inlined block will refer to parameters that no longer exist. + // + // If successful, `block` will be empty and unreachable after this call, so any + // optimizations performed after this point on the same block should check if + // the inlining here was successful before continuing. + try_inline_into_predecessor(self, &mut cfg, block, predecessor); + } else { + drop(predecessors); - check_for_double_jmp(function, block, &mut cfg); + check_for_double_jmp(self, block, &mut cfg); + } } } } diff --git a/compiler/noirc_evaluator/src/ssa/opt/unrolling.rs b/compiler/noirc_evaluator/src/ssa/opt/unrolling.rs index f3e11e04e3a..d6ed11ddf0e 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/unrolling.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/unrolling.rs @@ -74,16 +74,49 @@ impl Ssa { pub(crate) fn try_to_unroll_loops(mut self) -> (Ssa, Vec) { let mut errors = vec![]; for function in self.functions.values_mut() { - // Loop unrolling in brillig can lead to a code explosion currently. This can - // also be true for ACIR, but we have no alternative to unrolling in ACIR. - // Brillig also generally prefers smaller code rather than faster code. - if function.runtime() == RuntimeType::Brillig { - continue; + function.try_to_unroll_loops(&mut errors); + } + (self, errors) + } +} + +impl Function { + // TODO(https://github.com/noir-lang/noir/issues/6192): are both this and + // TODO: Ssa::unroll_loops_iteratively needed? Likely able to be combined + pub(crate) fn unroll_loops_iteratively(&mut self) -> Result<(), RuntimeError> { + // Try to unroll loops first: + let mut unroll_errors = vec![]; + self.try_to_unroll_loops(&mut unroll_errors); + + // Keep unrolling until no more errors are found + while !unroll_errors.is_empty() { + let prev_unroll_err_count = unroll_errors.len(); + + // Simplify the SSA before retrying + + // Do a mem2reg after the last unroll to aid simplify_cfg + self.mem2reg(); + self.simplify_function(); + // Do another mem2reg after simplify_cfg to aid the next unroll + self.mem2reg(); + + // Unroll again + self.try_to_unroll_loops(&mut unroll_errors); + // If we didn't manage to unroll any more loops, exit + if unroll_errors.len() >= prev_unroll_err_count { + return Err(unroll_errors.swap_remove(0)); } + } + Ok(()) + } - errors.extend(find_all_loops(function).unroll_each_loop(function)); + pub(crate) fn try_to_unroll_loops(&mut self, errors: &mut Vec) { + // Loop unrolling in brillig can lead to a code explosion currently. This can + // also be true for ACIR, but we have no alternative to unrolling in ACIR. + // Brillig also generally prefers smaller code rather than faster code. + if self.runtime() != RuntimeType::Brillig { + errors.extend(find_all_loops(self).unroll_each_loop(self)); } - (self, errors) } } From a1b50466bfd8c44d50440e00ecb50e29425471e5 Mon Sep 17 00:00:00 2001 From: Maxim Vezenov Date: Tue, 1 Oct 2024 12:45:35 -0400 Subject: [PATCH 27/36] feat(perf): Simplify the cfg after DIE (#6184) # Description ## Problem\* Part of general effort to reduce Brillig bytecode sizes. ## Summary\* While working other SSA opts, I noticed in a couple tests we had simplified blocks down to only their terminator, but those empty blocks still existed in our final SSA. It looks to be due to previous passes, and specifically DIE simplifying down to an empty block which was not caught in the initial CFG simplification. I pushed this PR wanted to see how much of a benefit simplifying at the end would be across our tests. It is a whole extra SSA pass, however, of all our passes, simplify CFG shouldn't be very heavy and wouldn't be too bad to run again if it provides decent benefits. ## Additional Context ## Documentation\* Check one: - [X] No documentation needed. - [ ] Documentation included in this PR. - [ ] **[For Experimental Features]** Documentation to be submitted in a separate PR. # PR Checklist\* - [X] I have tested the changes locally. - [X] I have formatted the changes with [Prettier](https://prettier.io/) and/or `cargo fmt` on default settings. --- compiler/noirc_evaluator/src/ssa.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/noirc_evaluator/src/ssa.rs b/compiler/noirc_evaluator/src/ssa.rs index ad6645df228..efc7c6018c1 100644 --- a/compiler/noirc_evaluator/src/ssa.rs +++ b/compiler/noirc_evaluator/src/ssa.rs @@ -118,6 +118,7 @@ pub(crate) fn optimize_into_acir( .run_pass(Ssa::remove_enable_side_effects, "After EnableSideEffectsIf removal:") .run_pass(Ssa::fold_constants_using_constraints, "After Constraint Folding:") .run_pass(Ssa::dead_instruction_elimination, "After Dead Instruction Elimination:") + .run_pass(Ssa::simplify_cfg, "After Simplifying:") .run_pass(Ssa::array_set_optimization, "After Array Set Optimizations:") .finish(); From 6440e183085160d77563b4e735ccaaf199e21693 Mon Sep 17 00:00:00 2001 From: Michael J Klein Date: Tue, 1 Oct 2024 18:10:12 -0400 Subject: [PATCH 28/36] feat!: Syncing TypeVariableKind with Kind (#6094) # Description ## Problem\* Resolves https://github.com/noir-lang/noir/issues/6067 ## Summary\* - Combines `TypeVariableKind` and `Kind` (now only `Kind`) - Adds `Kind` arguments where needed - Adds more `Kind` checks ## Additional Context ## Documentation\* Check one: - [x] No documentation needed. - [ ] Documentation included in this PR. - [ ] **[For Experimental Features]** Documentation to be submitted in a separate PR. # PR Checklist\* - [x] I have tested the changes locally. - [x] I have formatted the changes with [Prettier](https://prettier.io/) and/or `cargo fmt` on default settings. --------- Co-authored-by: Tom French <15848336+TomAFrench@users.noreply.github.com> Co-authored-by: Maxim Vezenov --- compiler/noirc_driver/src/abi_gen.rs | 20 +- compiler/noirc_frontend/src/ast/expression.rs | 16 +- compiler/noirc_frontend/src/elaborator/mod.rs | 57 +- .../noirc_frontend/src/elaborator/patterns.rs | 9 +- .../src/elaborator/trait_impls.rs | 10 +- .../noirc_frontend/src/elaborator/traits.rs | 13 +- .../noirc_frontend/src/elaborator/types.rs | 134 ++-- .../src/hir/comptime/hir_to_display_ast.rs | 10 +- .../src/hir/comptime/interpreter.rs | 51 +- .../src/hir/comptime/interpreter/builtin.rs | 16 +- .../src/hir/def_collector/dc_crate.rs | 9 +- .../src/hir/def_collector/dc_mod.rs | 9 +- .../src/hir/def_collector/errors.rs | 29 +- compiler/noirc_frontend/src/hir/mod.rs | 17 +- .../src/hir/resolution/errors.rs | 19 +- compiler/noirc_frontend/src/hir_def/traits.rs | 4 +- compiler/noirc_frontend/src/hir_def/types.rs | 632 ++++++++++-------- .../src/hir_def/types/arithmetic.rs | 11 +- .../src/monomorphization/mod.rs | 78 ++- compiler/noirc_frontend/src/node_interner.rs | 54 +- compiler/noirc_frontend/src/tests.rs | 118 +++- compiler/noirc_frontend/src/tests/imports.rs | 17 + .../src/tests/metaprogramming.rs | 11 + tooling/lsp/src/requests/completion.rs | 10 +- tooling/lsp/src/requests/hover.rs | 4 +- tooling/lsp/src/requests/inlay_hint.rs | 23 +- .../src/trait_impl_method_stub_generator.rs | 16 +- 27 files changed, 851 insertions(+), 546 deletions(-) diff --git a/compiler/noirc_driver/src/abi_gen.rs b/compiler/noirc_driver/src/abi_gen.rs index fcae136349d..ad54d0f7d6b 100644 --- a/compiler/noirc_driver/src/abi_gen.rs +++ b/compiler/noirc_driver/src/abi_gen.rs @@ -7,6 +7,7 @@ use noirc_abi::{ Abi, AbiErrorType, AbiParameter, AbiReturnType, AbiType, AbiValue, AbiVisibility, Sign, }; use noirc_frontend::ast::{Signedness, Visibility}; +use noirc_frontend::TypeBinding; use noirc_frontend::{ hir::Context, hir_def::{ @@ -17,7 +18,6 @@ use noirc_frontend::{ }, node_interner::{FuncId, NodeInterner}, }; -use noirc_frontend::{TypeBinding, TypeVariableKind}; /// Arranges a function signature and a generated circuit's return witnesses into a /// `noirc_abi::Abi`. @@ -72,13 +72,18 @@ pub(super) fn abi_type_from_hir_type(context: &Context, typ: &Type) -> AbiType { AbiType::Integer { sign, width: (*bit_width).into() } } - Type::TypeVariable(binding, TypeVariableKind::IntegerOrField) - | Type::TypeVariable(binding, TypeVariableKind::Integer) => match &*binding.borrow() { - TypeBinding::Bound(typ) => abi_type_from_hir_type(context, typ), - TypeBinding::Unbound(_) => { - abi_type_from_hir_type(context, &Type::default_int_or_field_type()) + Type::TypeVariable(binding) => { + if binding.is_integer() || binding.is_integer_or_field() { + match &*binding.borrow() { + TypeBinding::Bound(typ) => abi_type_from_hir_type(context, typ), + TypeBinding::Unbound(_id, _kind) => { + abi_type_from_hir_type(context, &Type::default_int_or_field_type()) + } + } + } else { + unreachable!("{typ} cannot be used in the abi") } - }, + } Type::Bool => AbiType::Boolean, Type::String(size) => { let size = size @@ -106,7 +111,6 @@ pub(super) fn abi_type_from_hir_type(context: &Context, typ: &Type) -> AbiType { | Type::Constant(..) | Type::InfixExpr(..) | Type::TraitAsType(..) - | Type::TypeVariable(_, _) | Type::NamedGeneric(..) | Type::Forall(..) | Type::Quoted(_) diff --git a/compiler/noirc_frontend/src/ast/expression.rs b/compiler/noirc_frontend/src/ast/expression.rs index 095356682fd..362f94171d3 100644 --- a/compiler/noirc_frontend/src/ast/expression.rs +++ b/compiler/noirc_frontend/src/ast/expression.rs @@ -1,11 +1,12 @@ use std::borrow::Cow; use std::fmt::Display; +use thiserror::Error; + use crate::ast::{ Ident, ItemVisibility, Path, Pattern, Recoverable, Statement, StatementKind, UnresolvedTraitConstraint, UnresolvedType, UnresolvedTypeData, Visibility, }; -use crate::hir::def_collector::errors::DefCollectorErrorKind; use crate::node_interner::{ ExprId, InternedExpressionKind, InternedStatementKind, QuotedTypeId, StructId, }; @@ -76,6 +77,13 @@ pub enum UnresolvedGeneric { Resolved(QuotedTypeId, Span), } +#[derive(Error, PartialEq, Eq, Debug, Clone)] +#[error("The only supported types of numeric generics are integers, fields, and booleans")] +pub struct UnsupportedNumericGenericType { + pub ident: Ident, + pub typ: UnresolvedTypeData, +} + impl UnresolvedGeneric { pub fn span(&self) -> Span { match self { @@ -85,7 +93,7 @@ impl UnresolvedGeneric { } } - pub fn kind(&self) -> Result { + pub fn kind(&self) -> Result { match self { UnresolvedGeneric::Variable(_) => Ok(Kind::Normal), UnresolvedGeneric::Numeric { typ, .. } => { @@ -101,14 +109,14 @@ impl UnresolvedGeneric { fn resolve_numeric_kind_type( &self, typ: &UnresolvedType, - ) -> Result { + ) -> Result { use crate::ast::UnresolvedTypeData::{FieldElement, Integer}; match typ.typ { FieldElement => Ok(Type::FieldElement), Integer(sign, bits) => Ok(Type::Integer(sign, bits)), // Only fields and integers are supported for numeric kinds - _ => Err(DefCollectorErrorKind::UnsupportedNumericGenericType { + _ => Err(UnsupportedNumericGenericType { ident: self.ident().clone(), typ: typ.typ.clone(), }), diff --git a/compiler/noirc_frontend/src/elaborator/mod.rs b/compiler/noirc_frontend/src/elaborator/mod.rs index 5f1b97f5eed..bc1976febd4 100644 --- a/compiler/noirc_frontend/src/elaborator/mod.rs +++ b/compiler/noirc_frontend/src/elaborator/mod.rs @@ -8,7 +8,7 @@ use crate::{ ast::{ BlockExpression, FunctionKind, GenericTypeArgs, Ident, NoirFunction, NoirStruct, Param, Path, Pattern, TraitBound, UnresolvedGeneric, UnresolvedGenerics, - UnresolvedTraitConstraint, UnresolvedTypeData, + UnresolvedTraitConstraint, UnresolvedTypeData, UnsupportedNumericGenericType, }, graph::CrateId, hir::{ @@ -345,8 +345,9 @@ impl<'context> Elaborator<'context> { fn introduce_generics_into_scope(&mut self, all_generics: Vec) { // Introduce all numeric generics into scope for generic in &all_generics { - if let Kind::Numeric(typ) = &generic.kind { - let definition = DefinitionKind::NumericGeneric(generic.type_var.clone()); + if let Kind::Numeric(typ) = &generic.kind() { + let definition = + DefinitionKind::NumericGeneric(generic.type_var.clone(), typ.clone()); let ident = Ident::new(generic.name.to_string(), generic.span); let hir_ident = self.add_variable_decl( ident, false, // mutable @@ -461,9 +462,9 @@ impl<'context> Elaborator<'context> { let context = self.function_context.pop().expect("Imbalanced function_context pushes"); for typ in context.type_variables { - if let Type::TypeVariable(variable, kind) = typ.follow_bindings() { + if let Type::TypeVariable(variable) = typ.follow_bindings() { let msg = "TypeChecker should only track defaultable type vars"; - variable.bind(kind.default_type().expect(msg)); + variable.bind(variable.kind().default_type().expect(msg)); } } @@ -501,11 +502,12 @@ impl<'context> Elaborator<'context> { trait_constraints: &mut Vec, ) -> Type { let new_generic_id = self.interner.next_type_variable_id(); - let new_generic = TypeVariable::unbound(new_generic_id); + + let new_generic = TypeVariable::unbound(new_generic_id, Kind::Normal); generics.push(new_generic.clone()); let name = format!("impl {trait_path}"); - let generic_type = Type::NamedGeneric(new_generic, Rc::new(name), Kind::Normal); + let generic_type = Type::NamedGeneric(new_generic, Rc::new(name)); let trait_bound = TraitBound { trait_path, trait_id: None, trait_generics }; if let Some(new_constraint) = self.resolve_trait_bound(&trait_bound, generic_type.clone()) { @@ -520,19 +522,20 @@ impl<'context> Elaborator<'context> { pub fn add_generics(&mut self, generics: &UnresolvedGenerics) -> Generics { vecmap(generics, |generic| { let mut is_error = false; - let (type_var, name, kind) = match self.resolve_generic(generic) { + let (type_var, name) = match self.resolve_generic(generic) { Ok(values) => values, Err(error) => { self.push_err(error); is_error = true; let id = self.interner.next_type_variable_id(); - (TypeVariable::unbound(id), Rc::new("(error)".into()), Kind::Normal) + let kind = self.resolve_generic_kind(generic); + (TypeVariable::unbound(id, kind), Rc::new("(error)".into())) } }; let span = generic.span(); let name_owned = name.as_ref().clone(); - let resolved_generic = ResolvedGeneric { name, type_var, kind, span }; + let resolved_generic = ResolvedGeneric { name, type_var, span }; // Check for name collisions of this generic // Checking `is_error` here prevents DuplicateDefinition errors when @@ -557,25 +560,22 @@ impl<'context> Elaborator<'context> { fn resolve_generic( &mut self, generic: &UnresolvedGeneric, - ) -> Result<(TypeVariable, Rc, Kind), ResolverError> { + ) -> Result<(TypeVariable, Rc), ResolverError> { // Map the generic to a fresh type variable match generic { UnresolvedGeneric::Variable(_) | UnresolvedGeneric::Numeric { .. } => { let id = self.interner.next_type_variable_id(); - let typevar = TypeVariable::unbound(id); - let ident = generic.ident(); - let kind = self.resolve_generic_kind(generic); + let typevar = TypeVariable::unbound(id, kind); + let ident = generic.ident(); let name = Rc::new(ident.0.contents.clone()); - Ok((typevar, name, kind)) + Ok((typevar, name)) } // An already-resolved generic is only possible if it is the result of a // previous macro call being inserted into a generics list. UnresolvedGeneric::Resolved(id, span) => { match self.interner.get_quoted_type(*id).follow_bindings() { - Type::NamedGeneric(type_variable, name, kind) => { - Ok((type_variable, name, kind)) - } + Type::NamedGeneric(type_variable, name) => Ok((type_variable.clone(), name)), other => Err(ResolverError::MacroResultInGenericsListNotAGeneric { span: *span, typ: other.clone(), @@ -590,17 +590,21 @@ impl<'context> Elaborator<'context> { /// sure only primitive numeric types are being used. pub(super) fn resolve_generic_kind(&mut self, generic: &UnresolvedGeneric) -> Kind { if let UnresolvedGeneric::Numeric { ident, typ } = generic { - let typ = typ.clone(); - let typ = if typ.is_type_expression() { - self.resolve_type_inner(typ, &Kind::Numeric(Box::new(Type::default_int_type()))) + let unresolved_typ = typ.clone(); + let typ = if unresolved_typ.is_type_expression() { + self.resolve_type_inner( + unresolved_typ.clone(), + &Kind::Numeric(Box::new(Type::default_int_type())), + ) } else { - self.resolve_type(typ.clone()) + self.resolve_type(unresolved_typ.clone()) }; if !matches!(typ, Type::FieldElement | Type::Integer(_, _)) { - let unsupported_typ_err = ResolverError::UnsupportedNumericGenericType { - ident: ident.clone(), - typ: typ.clone(), - }; + let unsupported_typ_err = + ResolverError::UnsupportedNumericGenericType(UnsupportedNumericGenericType { + ident: ident.clone(), + typ: unresolved_typ.typ.clone(), + }); self.push_err(unsupported_typ_err); } Kind::Numeric(Box::new(typ)) @@ -735,6 +739,7 @@ impl<'context> Elaborator<'context> { UnresolvedTypeData::TraitAsType(path, args) => { self.desugar_impl_trait_arg(path, args, &mut generics, &mut trait_constraints) } + // Function parameters have Kind::Normal _ => self.resolve_type_inner(typ, &Kind::Normal), }; diff --git a/compiler/noirc_frontend/src/elaborator/patterns.rs b/compiler/noirc_frontend/src/elaborator/patterns.rs index 91c4b17a857..132d1988b78 100644 --- a/compiler/noirc_frontend/src/elaborator/patterns.rs +++ b/compiler/noirc_frontend/src/elaborator/patterns.rs @@ -16,7 +16,7 @@ use crate::{ stmt::HirPattern, }, node_interner::{DefinitionId, DefinitionKind, ExprId, FuncId, GlobalId, TraitImplKind}, - ResolvedGeneric, Shared, StructType, Type, TypeBindings, + Kind, ResolvedGeneric, Shared, StructType, Type, TypeBindings, }; use super::{Elaborator, ResolverMeta}; @@ -489,7 +489,7 @@ impl<'context> Elaborator<'context> { ) -> Vec { let generics_with_types = generics.iter().zip(turbofish_generics); vecmap(generics_with_types, |(generic, unresolved_type)| { - self.resolve_type_inner(unresolved_type, &generic.kind) + self.resolve_type_inner(unresolved_type, &generic.kind()) }) } @@ -558,13 +558,14 @@ impl<'context> Elaborator<'context> { self.interner.add_global_reference(global_id, hir_ident.location); } - DefinitionKind::NumericGeneric(_) => { + DefinitionKind::NumericGeneric(_, ref numeric_typ) => { // Initialize numeric generics to a polymorphic integer type in case // they're used in expressions. We must do this here since type_check_variable // does not check definition kinds and otherwise expects parameters to // already be typed. if self.interner.definition_type(hir_ident.id) == Type::Error { - let typ = Type::polymorphic_integer_or_field(self.interner); + let type_var_kind = Kind::Numeric(numeric_typ.clone()); + let typ = self.type_variable_with_kind(type_var_kind); self.interner.push_definition_type(hir_ident.id, typ); } } diff --git a/compiler/noirc_frontend/src/elaborator/trait_impls.rs b/compiler/noirc_frontend/src/elaborator/trait_impls.rs index 2f9c927fae6..858cfa5cdd6 100644 --- a/compiler/noirc_frontend/src/elaborator/trait_impls.rs +++ b/compiler/noirc_frontend/src/elaborator/trait_impls.rs @@ -153,11 +153,15 @@ impl<'context> Elaborator<'context> { // Substitute each generic on the trait function with the corresponding generic on the impl function for ( ResolvedGeneric { type_var: trait_fn_generic, .. }, - ResolvedGeneric { name, type_var: impl_fn_generic, kind, .. }, + ResolvedGeneric { name, type_var: impl_fn_generic, .. }, ) in method.direct_generics.iter().zip(&override_meta.direct_generics) { - let arg = Type::NamedGeneric(impl_fn_generic.clone(), name.clone(), kind.clone()); - bindings.insert(trait_fn_generic.id(), (trait_fn_generic.clone(), arg)); + let trait_fn_kind = trait_fn_generic.kind(); + let arg = Type::NamedGeneric(impl_fn_generic.clone(), name.clone()); + bindings.insert( + trait_fn_generic.id(), + (trait_fn_generic.clone(), trait_fn_kind.clone(), arg), + ); } let mut substituted_method_ids = HashSet::default(); diff --git a/compiler/noirc_frontend/src/elaborator/traits.rs b/compiler/noirc_frontend/src/elaborator/traits.rs index 21e2edf3822..b4042bd3e31 100644 --- a/compiler/noirc_frontend/src/elaborator/traits.rs +++ b/compiler/noirc_frontend/src/elaborator/traits.rs @@ -12,7 +12,7 @@ use crate::{ hir::{def_collector::dc_crate::UnresolvedTrait, type_check::TypeCheckError}, hir_def::{function::Parameters, traits::TraitFunction}, node_interner::{FuncId, NodeInterner, ReferenceId, TraitId}, - Kind, ResolvedGeneric, Type, TypeBindings, TypeVariableKind, + ResolvedGeneric, Type, TypeBindings, }; use super::Elaborator; @@ -79,8 +79,7 @@ impl<'context> Elaborator<'context> { self.recover_generics(|this| { let the_trait = this.interner.get_trait(trait_id); let self_typevar = the_trait.self_type_typevar.clone(); - let self_type = - Type::TypeVariable(self_typevar.clone(), TypeVariableKind::Normal); + let self_type = Type::TypeVariable(self_typevar.clone()); let name_span = the_trait.name.span(); this.add_existing_generic( @@ -90,7 +89,6 @@ impl<'context> Elaborator<'context> { name: Rc::new("Self".to_owned()), type_var: self_typevar, span: name_span, - kind: Kind::Normal, }, ); this.self_type = Some(self_type.clone()); @@ -279,11 +277,12 @@ pub(crate) fn check_trait_impl_method_matches_declaration( // Substitute each generic on the trait function with the corresponding generic on the impl function for ( ResolvedGeneric { type_var: trait_fn_generic, .. }, - ResolvedGeneric { name, type_var: impl_fn_generic, kind, .. }, + ResolvedGeneric { name, type_var: impl_fn_generic, .. }, ) in trait_fn_meta.direct_generics.iter().zip(&meta.direct_generics) { - let arg = Type::NamedGeneric(impl_fn_generic.clone(), name.clone(), kind.clone()); - bindings.insert(trait_fn_generic.id(), (trait_fn_generic.clone(), arg)); + let trait_fn_kind = trait_fn_generic.kind(); + let arg = Type::NamedGeneric(impl_fn_generic.clone(), name.clone()); + bindings.insert(trait_fn_generic.id(), (trait_fn_generic.clone(), trait_fn_kind, arg)); } let (declaration_type, _) = trait_fn_meta.typ.instantiate_with_bindings(bindings, interner); diff --git a/compiler/noirc_frontend/src/elaborator/types.rs b/compiler/noirc_frontend/src/elaborator/types.rs index 80a9133d0d7..35cd5145579 100644 --- a/compiler/noirc_frontend/src/elaborator/types.rs +++ b/compiler/noirc_frontend/src/elaborator/types.rs @@ -36,7 +36,7 @@ use crate::{ }, token::SecondaryAttribute, Generics, Kind, ResolvedGeneric, Type, TypeBinding, TypeBindings, TypeVariable, - TypeVariableKind, UnificationError, + UnificationError, }; use super::{lints, Elaborator}; @@ -125,7 +125,7 @@ impl<'context> Elaborator<'context> { let env = Box::new(self.resolve_type_inner(*env, kind)); match *env { - Type::Unit | Type::Tuple(_) | Type::NamedGeneric(_, _, _) => { + Type::Unit | Type::Tuple(_) | Type::NamedGeneric(_, _) => { Type::Function(args, ret, env, unconstrained) } _ => { @@ -168,14 +168,10 @@ impl<'context> Elaborator<'context> { _ => (), } - if !kind.matches_opt(resolved_type.kind()) { + if !kind.unifies(&resolved_type.kind()) { let expected_typ_err = CompilationError::TypeError(TypeCheckError::TypeKindMismatch { expected_kind: kind.to_string(), - expr_kind: resolved_type - .kind() - .as_ref() - .map(Kind::to_string) - .unwrap_or("unknown".to_string()), + expr_kind: resolved_type.kind().to_string(), expr_span: span, }); self.errors.push((expected_typ_err, self.file)); @@ -229,7 +225,7 @@ impl<'context> Elaborator<'context> { return self_type; } } else if name == WILDCARD_TYPE { - return self.interner.next_type_variable(); + return self.interner.next_type_variable_with_kind(Kind::Any); } } else if let Some(typ) = self.lookup_associated_type_on_self(&path) { if !args.is_empty() { @@ -332,7 +328,7 @@ impl<'context> Elaborator<'context> { let ordered_args = expected_kinds.iter().zip(args.ordered_args); let ordered = - vecmap(ordered_args, |(generic, typ)| self.resolve_type_inner(typ, &generic.kind)); + vecmap(ordered_args, |(generic, typ)| self.resolve_type_inner(typ, &generic.kind())); let mut associated = Vec::new(); @@ -376,7 +372,7 @@ impl<'context> Elaborator<'context> { let expected = required_args.remove(index); seen_args.insert(name.0.contents.clone(), name.span()); - let typ = self.resolve_type_inner(typ, &expected.kind); + let typ = self.resolve_type_inner(typ, &expected.kind()); resolved.push(NamedType { name, typ }); } @@ -395,7 +391,7 @@ impl<'context> Elaborator<'context> { let name = path.last_name(); if let Some(generic) = self.find_generic(name) { let generic = generic.clone(); - return Some(Type::NamedGeneric(generic.type_var, generic.name, generic.kind)); + return Some(Type::NamedGeneric(generic.type_var, generic.name)); } } else if let Some(typ) = self.lookup_associated_type_on_self(path) { return Some(typ); @@ -451,7 +447,7 @@ impl<'context> Elaborator<'context> { }); return Type::Error; } - if let Some(result) = op.function(lhs, rhs) { + if let Some(result) = op.function(lhs, rhs, &lhs_kind) { Type::Constant(result, lhs_kind) } else { self.push_err(ResolverError::OverflowInType { lhs, op, rhs, span }); @@ -469,15 +465,13 @@ impl<'context> Elaborator<'context> { } fn check_kind(&mut self, typ: Type, expected_kind: &Kind, span: Span) -> Type { - if let Some(kind) = typ.kind() { - if !kind.unifies(expected_kind) { - self.push_err(TypeCheckError::TypeKindMismatch { - expected_kind: expected_kind.to_string(), - expr_kind: kind.to_string(), - expr_span: span, - }); - return Type::Error; - } + if !typ.kind().unifies(expected_kind) { + self.push_err(TypeCheckError::TypeKindMismatch { + expected_kind: expected_kind.to_string(), + expr_kind: typ.kind().to_string(), + expr_span: span, + }); + return Type::Error; } typ } @@ -579,7 +573,7 @@ impl<'context> Elaborator<'context> { } for constraint in self.trait_bounds.clone() { - if let Type::NamedGeneric(_, name, _) = &constraint.typ { + if let Type::NamedGeneric(_, name) = &constraint.typ { // if `path` is `T::method_name`, we're looking for constraint of the form `T: SomeTrait` if path.segments[0].ident.0.contents != name.as_str() { continue; @@ -696,11 +690,21 @@ impl<'context> Elaborator<'context> { typ } + /// Return a fresh integer type variable and log it + /// in self.type_variables to default it later. + pub(super) fn type_variable_with_kind(&mut self, type_var_kind: Kind) -> Type { + let typ = Type::type_variable_with_kind(self.interner, type_var_kind); + self.push_type_variable(typ.clone()); + typ + } + /// Translates a (possibly Unspecified) UnresolvedType to a Type. /// Any UnresolvedType::Unspecified encountered are replaced with fresh type variables. pub(super) fn resolve_inferred_type(&mut self, typ: UnresolvedType) -> Type { match &typ.typ { - UnresolvedTypeData::Unspecified => self.interner.next_type_variable(), + UnresolvedTypeData::Unspecified => { + self.interner.next_type_variable_with_kind(Kind::Any) + } _ => self.resolve_type(typ), } } @@ -789,7 +793,7 @@ impl<'context> Elaborator<'context> { // Could do a single unification for the entire function type, but matching beforehand // lets us issue a more precise error on the individual argument that fails to type check. match function { - Type::TypeVariable(binding, TypeVariableKind::Normal) => { + Type::TypeVariable(binding) if binding.kind() == Kind::Normal => { if let TypeBinding::Bound(typ) = &*binding.borrow() { return self.bind_function_type(typ.clone(), args, span); } @@ -800,7 +804,8 @@ impl<'context> Elaborator<'context> { let expected = Type::Function(args, Box::new(ret.clone()), Box::new(env_type), false); - if let Err(error) = binding.try_bind(expected, span) { + let expected_kind = expected.kind(); + if let Err(error) = binding.try_bind(expected, &expected_kind, span) { self.push_err(error); } ret @@ -820,16 +825,14 @@ impl<'context> Elaborator<'context> { pub(super) fn check_cast(&mut self, from: &Type, to: &Type, span: Span) -> Type { match from.follow_bindings() { - Type::Integer(..) - | Type::FieldElement - | Type::TypeVariable(_, TypeVariableKind::IntegerOrField) - | Type::TypeVariable(_, TypeVariableKind::Integer) - | Type::Bool => (), + Type::Integer(..) | Type::FieldElement | Type::Bool => (), - Type::TypeVariable(_, _) => { + Type::TypeVariable(var) if var.is_integer() || var.is_integer_or_field() => (), + + Type::TypeVariable(_) => { // NOTE: in reality the expected type can also include bool, but for the compiler's simplicity // we only allow integer types. If a bool is in `from` it will need an explicit type annotation. - let expected = Type::polymorphic_integer_or_field(self.interner); + let expected = self.polymorphic_integer_or_field(); self.unify(from, &expected, || TypeCheckError::InvalidCast { from: from.clone(), span, @@ -877,8 +880,8 @@ impl<'context> Elaborator<'context> { // Matches on TypeVariable must be first to follow any type // bindings. - (TypeVariable(var, _), other) | (other, TypeVariable(var, _)) => { - if let TypeBinding::Bound(binding) = &*var.borrow() { + (TypeVariable(var), other) | (other, TypeVariable(var)) => { + if let TypeBinding::Bound(ref binding) = &*var.borrow() { return self.comparator_operand_type_rules(other, binding, op, span); } @@ -942,14 +945,14 @@ impl<'context> Elaborator<'context> { span, }); - let use_impl = !lhs_type.is_numeric(); + let use_impl = !lhs_type.is_numeric_value(); // If this operator isn't valid for fields we have to possibly narrow - // TypeVariableKind::IntegerOrField to TypeVariableKind::Integer. + // Kind::IntegerOrField to Kind::Integer. // Doing so also ensures a type error if Field is used. // The is_numeric check is to allow impls for custom types to bypass this. - if !op.kind.is_valid_for_field_type() && lhs_type.is_numeric() { - let target = Type::polymorphic_integer(self.interner); + if !op.kind.is_valid_for_field_type() && lhs_type.is_numeric_value() { + let target = self.polymorphic_integer(); use crate::ast::BinaryOpKind::*; use TypeCheckError::*; @@ -990,22 +993,22 @@ impl<'context> Elaborator<'context> { // Matches on TypeVariable must be first so that we follow any type // bindings. - (TypeVariable(int, _), other) | (other, TypeVariable(int, _)) => { + (TypeVariable(int), other) | (other, TypeVariable(int)) => { if op.kind == BinaryOpKind::ShiftLeft || op.kind == BinaryOpKind::ShiftRight { self.unify( rhs_type, &Type::Integer(Signedness::Unsigned, IntegerBitSize::Eight), || TypeCheckError::InvalidShiftSize { span }, ); - let use_impl = if lhs_type.is_numeric() { - let integer_type = Type::polymorphic_integer(self.interner); + let use_impl = if lhs_type.is_numeric_value() { + let integer_type = self.polymorphic_integer(); self.bind_type_variables_for_infix(lhs_type, op, &integer_type, span) } else { true }; return Ok((lhs_type.clone(), use_impl)); } - if let TypeBinding::Bound(binding) = &*int.borrow() { + if let TypeBinding::Bound(ref binding) = &*int.borrow() { return self.infix_operand_type_rules(binding, op, other, span); } let use_impl = self.bind_type_variables_for_infix(lhs_type, op, rhs_type, span); @@ -1090,21 +1093,21 @@ impl<'context> Elaborator<'context> { // Matches on TypeVariable must be first so that we follow any type // bindings. - TypeVariable(int, _) => { - if let TypeBinding::Bound(binding) = &*int.borrow() { + TypeVariable(int) => { + if let TypeBinding::Bound(ref binding) = &*int.borrow() { return self.prefix_operand_type_rules(op, binding, span); } // The `!` prefix operator is not valid for Field, so if this is a numeric // type we constrain it to just (non-Field) integer types. - if matches!(op, crate::ast::UnaryOp::Not) && rhs_type.is_numeric() { + if matches!(op, crate::ast::UnaryOp::Not) && rhs_type.is_numeric_value() { let integer_type = Type::polymorphic_integer(self.interner); self.unify(rhs_type, &integer_type, || { TypeCheckError::InvalidUnaryOp { kind: rhs_type.to_string(), span } }); } - Ok((rhs_type.clone(), !rhs_type.is_numeric())) + Ok((rhs_type.clone(), !rhs_type.is_numeric_value())) } Integer(sign_x, bit_width_x) => { if *op == UnaryOp::Minus && *sign_x == Signedness::Unsigned { @@ -1186,7 +1189,11 @@ impl<'context> Elaborator<'context> { let object_type = object_type.substitute(&bindings); bindings.insert( the_trait.self_type_typevar.id(), - (the_trait.self_type_typevar.clone(), object_type.clone()), + ( + the_trait.self_type_typevar.clone(), + the_trait.self_type_typevar.kind(), + object_type.clone(), + ), ); self.interner.select_impl_for_expression( expr_id, @@ -1267,7 +1274,7 @@ impl<'context> Elaborator<'context> { }); None } - Type::NamedGeneric(_, _, _) => { + Type::NamedGeneric(_, _) => { self.lookup_method_in_trait_constraints(object_type, method_name, span) } // Mutable references to another type should resolve to methods of their element type. @@ -1283,7 +1290,7 @@ impl<'context> Elaborator<'context> { Type::Error => None, // The type variable must be unbound at this point since follow_bindings was called - Type::TypeVariable(_, TypeVariableKind::Normal) => { + Type::TypeVariable(var) if var.kind() == Kind::Normal => { self.push_err(TypeCheckError::TypeAnnotationsNeededForMethodCall { span }); None } @@ -1630,9 +1637,9 @@ impl<'context> Elaborator<'context> { | Type::Bool | Type::Unit | Type::Error - | Type::TypeVariable(_, _) + | Type::TypeVariable(_) | Type::Constant(..) - | Type::NamedGeneric(_, _, _) + | Type::NamedGeneric(_, _) | Type::Quoted(_) | Type::Forall(_, _) => (), @@ -1646,7 +1653,7 @@ impl<'context> Elaborator<'context> { } Type::Array(length, element_type) => { - if let Type::NamedGeneric(type_variable, name, _) = length.as_ref() { + if let Type::NamedGeneric(type_variable, name) = length.as_ref() { found.insert(name.to_string(), type_variable.clone()); } Self::find_numeric_generics_in_type(element_type, found); @@ -1671,7 +1678,7 @@ impl<'context> Elaborator<'context> { Type::Struct(struct_type, generics) => { for (i, generic) in generics.iter().enumerate() { - if let Type::NamedGeneric(type_variable, name, _) = generic { + if let Type::NamedGeneric(type_variable, name) = generic { if struct_type.borrow().generics[i].is_numeric() { found.insert(name.to_string(), type_variable.clone()); } @@ -1682,7 +1689,7 @@ impl<'context> Elaborator<'context> { } Type::Alias(alias, generics) => { for (i, generic) in generics.iter().enumerate() { - if let Type::NamedGeneric(type_variable, name, _) = generic { + if let Type::NamedGeneric(type_variable, name) = generic { if alias.borrow().generics[i].is_numeric() { found.insert(name.to_string(), type_variable.clone()); } @@ -1693,12 +1700,12 @@ impl<'context> Elaborator<'context> { } Type::MutableReference(element) => Self::find_numeric_generics_in_type(element, found), Type::String(length) => { - if let Type::NamedGeneric(type_variable, name, _) = length.as_ref() { + if let Type::NamedGeneric(type_variable, name) = length.as_ref() { found.insert(name.to_string(), type_variable.clone()); } } Type::FmtString(length, fields) => { - if let Type::NamedGeneric(type_variable, name, _) = length.as_ref() { + if let Type::NamedGeneric(type_variable, name) = length.as_ref() { found.insert(name.to_string(), type_variable.clone()); } Self::find_numeric_generics_in_type(fields, found); @@ -1751,7 +1758,10 @@ impl<'context> Elaborator<'context> { for (param, arg) in the_trait.generics.iter().zip(&constraint.trait_generics.ordered) { // Avoid binding t = t if !arg.occurs(param.type_var.id()) { - bindings.insert(param.type_var.id(), (param.type_var.clone(), arg.clone())); + bindings.insert( + param.type_var.id(), + (param.type_var.clone(), param.kind(), arg.clone()), + ); } } @@ -1770,7 +1780,10 @@ impl<'context> Elaborator<'context> { // Avoid binding t = t if !arg.typ.occurs(param.type_var.id()) { - bindings.insert(param.type_var.id(), (param.type_var.clone(), arg.typ.clone())); + bindings.insert( + param.type_var.id(), + (param.type_var.clone(), param.kind(), arg.typ.clone()), + ); } } @@ -1779,7 +1792,8 @@ impl<'context> Elaborator<'context> { // to specify a redundant type annotation. if assumed { let self_type = the_trait.self_type_typevar.clone(); - bindings.insert(self_type.id(), (self_type, constraint.typ.clone())); + let kind = the_trait.self_type_typevar.kind(); + bindings.insert(self_type.id(), (self_type, kind, constraint.typ.clone())); } } } diff --git a/compiler/noirc_frontend/src/hir/comptime/hir_to_display_ast.rs b/compiler/noirc_frontend/src/hir/comptime/hir_to_display_ast.rs index 90deef2deb7..63c32bc7e5f 100644 --- a/compiler/noirc_frontend/src/hir/comptime/hir_to_display_ast.rs +++ b/compiler/noirc_frontend/src/hir/comptime/hir_to_display_ast.rs @@ -316,10 +316,10 @@ impl Type { let name = Path::from_ident(type_def.name.clone()); UnresolvedTypeData::Named(name, generics, false) } - Type::TypeVariable(binding, kind) => match &*binding.borrow() { + Type::TypeVariable(binding) => match &*binding.borrow() { TypeBinding::Bound(typ) => return typ.to_display_ast(), - TypeBinding::Unbound(id) => { - let name = format!("var_{:?}_{}", kind, id); + TypeBinding::Unbound(id, type_var_kind) => { + let name = format!("var_{:?}_{}", type_var_kind, id); let path = Path::from_single(name, Span::empty(0)); let expression = UnresolvedTypeExpression::Variable(path); UnresolvedTypeData::Expression(expression) @@ -334,7 +334,7 @@ impl Type { let name = Path::from_single(name.as_ref().clone(), Span::default()); UnresolvedTypeData::TraitAsType(name, generics) } - Type::NamedGeneric(_var, name, _kind) => { + Type::NamedGeneric(_var, name) => { let name = Path::from_single(name.as_ref().clone(), Span::default()); UnresolvedTypeData::Named(name, GenericTypeArgs::default(), true) } @@ -374,7 +374,7 @@ impl Type { match self.follow_bindings() { Type::Constant(length, _kind) => UnresolvedTypeExpression::Constant(length, span), - Type::NamedGeneric(_var, name, _kind) => { + Type::NamedGeneric(_var, name) => { let path = Path::from_single(name.as_ref().clone(), span); UnresolvedTypeExpression::Variable(path) } diff --git a/compiler/noirc_frontend/src/hir/comptime/interpreter.rs b/compiler/noirc_frontend/src/hir/comptime/interpreter.rs index 473e1f25e34..b981dcb348f 100644 --- a/compiler/noirc_frontend/src/hir/comptime/interpreter.rs +++ b/compiler/noirc_frontend/src/hir/comptime/interpreter.rs @@ -33,9 +33,10 @@ use crate::{ HirAssignStatement, HirConstrainStatement, HirForStatement, HirLValue, HirLetStatement, HirPattern, HirStatement, }, + types::Kind, }, node_interner::{DefinitionId, DefinitionKind, ExprId, FuncId, NodeInterner, StmtId}, - Shared, Type, TypeBinding, TypeBindings, TypeVariableKind, + Shared, Type, TypeBinding, TypeBindings, }; use super::errors::{IResult, InterpreterError}; @@ -60,7 +61,7 @@ pub struct Interpreter<'local, 'interner> { /// Since the interpreter monomorphizes as it interprets, we can bind over the same generic /// multiple times. Without this map, when one of these inner functions exits we would /// unbind the generic completely instead of resetting it to its previous binding. - bound_generics: Vec>, + bound_generics: Vec>, } #[allow(unused)] @@ -87,7 +88,8 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { // To match the monomorphizer, we need to call follow_bindings on each of // the instantiation bindings before we unbind the generics from the previous function. // This is because the instantiation bindings refer to variables from the call site. - for (_, binding) in instantiation_bindings.values_mut() { + for (_, kind, binding) in instantiation_bindings.values_mut() { + *kind = kind.follow_bindings(); *binding = binding.follow_bindings(); } @@ -96,7 +98,8 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { let mut impl_bindings = perform_impl_bindings(self.elaborator.interner, trait_method, function, location)?; - for (_, binding) in impl_bindings.values_mut() { + for (_, kind, binding) in impl_bindings.values_mut() { + *kind = kind.follow_bindings(); *binding = binding.follow_bindings(); } @@ -333,8 +336,8 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { fn unbind_generics_from_previous_function(&mut self) { if let Some(bindings) = self.bound_generics.last() { - for var in bindings.keys() { - var.unbind(var.id()); + for (var, (_, kind)) in bindings { + var.unbind(var.id(), kind.clone()); } } // Push a new bindings list for the current function @@ -346,7 +349,7 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { self.bound_generics.pop(); if let Some(bindings) = self.bound_generics.last() { - for (var, binding) in bindings { + for (var, (binding, _kind)) in bindings { var.force_bind(binding.clone()); } } @@ -358,12 +361,12 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { .last_mut() .expect("remember_bindings called with no bound_generics on the stack"); - for (var, binding) in main_bindings.values() { - bound_generics.insert(var.clone(), binding.follow_bindings()); + for (var, kind, binding) in main_bindings.values() { + bound_generics.insert(var.clone(), (binding.follow_bindings(), kind.clone())); } - for (var, binding) in impl_bindings.values() { - bound_generics.insert(var.clone(), binding.follow_bindings()); + for (var, kind, binding) in impl_bindings.values() { + bound_generics.insert(var.clone(), (binding.follow_bindings(), kind.clone())); } } @@ -580,9 +583,9 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { Ok(value) } } - DefinitionKind::NumericGeneric(type_variable) => { + DefinitionKind::NumericGeneric(type_variable, numeric_typ) => { let value = match &*type_variable.borrow() { - TypeBinding::Unbound(_) => None, + TypeBinding::Unbound(_, _) => None, TypeBinding::Bound(binding) => binding.evaluate_to_u32(), }; @@ -591,7 +594,7 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { self.evaluate_integer((value as u128).into(), false, id) } else { let location = self.elaborator.interner.expr_location(&id); - let typ = Type::TypeVariable(type_variable.clone(), TypeVariableKind::Normal); + let typ = Type::TypeVariable(type_variable.clone()); Err(InterpreterError::NonIntegerArrayLength { typ, location }) } } @@ -750,14 +753,18 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { Ok(Value::I64(value)) } } - } else if let Type::TypeVariable(variable, TypeVariableKind::IntegerOrField) = &typ { - Ok(Value::Field(value)) - } else if let Type::TypeVariable(variable, TypeVariableKind::Integer) = &typ { - let value: u64 = value - .try_to_u64() - .ok_or(InterpreterError::IntegerOutOfRangeForType { value, typ, location })?; - let value = if is_negative { 0u64.wrapping_sub(value) } else { value }; - Ok(Value::U64(value)) + } else if let Type::TypeVariable(variable) = &typ { + if variable.is_integer_or_field() { + Ok(Value::Field(value)) + } else if variable.is_integer() { + let value: u64 = value + .try_to_u64() + .ok_or(InterpreterError::IntegerOutOfRangeForType { value, typ, location })?; + let value = if is_negative { 0u64.wrapping_sub(value) } else { value }; + Ok(Value::U64(value)) + } else { + Err(InterpreterError::NonIntegerIntegerLiteral { typ, location }) + } } else { Err(InterpreterError::NonIntegerIntegerLiteral { typ, location }) } diff --git a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs index 4c81d4c3ed3..48827c087f9 100644 --- a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs +++ b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs @@ -404,11 +404,11 @@ fn struct_def_add_generic( } } - let type_var = TypeVariable::unbound(interner.next_type_variable_id()); + let type_var_kind = Kind::Normal; + let type_var = TypeVariable::unbound(interner.next_type_variable_id(), type_var_kind); let span = generic_location.span; - let kind = Kind::Normal; - let typ = Type::NamedGeneric(type_var.clone(), name.clone(), kind.clone()); - let new_generic = ResolvedGeneric { name, type_var, span, kind }; + let typ = Type::NamedGeneric(type_var.clone(), name.clone()); + let new_generic = ResolvedGeneric { name, type_var, span }; the_struct.generics.push(new_generic); Ok(Value::Type(typ)) @@ -426,7 +426,7 @@ fn struct_def_as_type( let struct_def = struct_def_rc.borrow(); let generics = vecmap(&struct_def.generics, |generic| { - Type::NamedGeneric(generic.type_var.clone(), generic.name.clone(), generic.kind.clone()) + Type::NamedGeneric(generic.type_var.clone(), generic.name.clone()) }); drop(struct_def); @@ -1196,14 +1196,14 @@ fn zeroed(return_type: Type) -> IResult { Ok(Value::Pointer(Shared::new(element), false)) } // Optimistically assume we can resolve this type later or that the value is unused - Type::TypeVariable(_, _) + Type::TypeVariable(_) | Type::Forall(_, _) | Type::Constant(..) | Type::InfixExpr(..) | Type::Quoted(_) | Type::Error | Type::TraitAsType(..) - | Type::NamedGeneric(_, _, _) => Ok(Value::Zeroed(return_type)), + | Type::NamedGeneric(_, _) => Ok(Value::Zeroed(return_type)), } } @@ -2079,7 +2079,7 @@ fn fmtstr_quoted_contents( // fn fresh_type_variable() -> Type fn fresh_type_variable(interner: &NodeInterner) -> IResult { - Ok(Value::Type(interner.next_type_variable())) + Ok(Value::Type(interner.next_type_variable_with_kind(Kind::Any))) } // fn add_attribute(self, attribute: str) diff --git a/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs b/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs index 267c9bc84b8..4d348d998b7 100644 --- a/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs +++ b/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs @@ -23,7 +23,7 @@ use crate::node_interner::{ use crate::ast::{ ExpressionKind, GenericTypeArgs, Ident, ItemVisibility, LetStatement, Literal, NoirFunction, NoirStruct, NoirTrait, NoirTypeAlias, Path, PathKind, PathSegment, UnresolvedGenerics, - UnresolvedTraitConstraint, UnresolvedType, + UnresolvedTraitConstraint, UnresolvedType, UnsupportedNumericGenericType, }; use crate::parser::{ParserError, SortedModule}; @@ -231,12 +231,19 @@ impl From for CompilationError { CompilationError::ResolverError(value) } } + impl From for CompilationError { fn from(value: TypeCheckError) -> Self { CompilationError::TypeError(value) } } +impl From for CompilationError { + fn from(value: UnsupportedNumericGenericType) -> Self { + Self::ResolverError(value.into()) + } +} + impl DefCollector { pub fn new(def_map: CrateDefMap) -> DefCollector { DefCollector { diff --git a/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs b/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs index dff63e045fe..0e20ec3a304 100644 --- a/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs +++ b/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs @@ -530,8 +530,10 @@ impl<'a> ModCollector<'a> { associated_types.push(ResolvedGeneric { name: Rc::new(name.to_string()), - type_var: TypeVariable::unbound(type_variable_id), - kind: Kind::Numeric(Box::new(typ)), + type_var: TypeVariable::unbound( + type_variable_id, + Kind::Numeric(Box::new(typ)), + ), span: name.span(), }); } @@ -555,8 +557,7 @@ impl<'a> ModCollector<'a> { let type_variable_id = context.def_interner.next_type_variable_id(); associated_types.push(ResolvedGeneric { name: Rc::new(name.to_string()), - type_var: TypeVariable::unbound(type_variable_id), - kind: Kind::Normal, + type_var: TypeVariable::unbound(type_variable_id, Kind::Normal), span: name.span(), }); } diff --git a/compiler/noirc_frontend/src/hir/def_collector/errors.rs b/compiler/noirc_frontend/src/hir/def_collector/errors.rs index 2f47505db94..d72f493092d 100644 --- a/compiler/noirc_frontend/src/hir/def_collector/errors.rs +++ b/compiler/noirc_frontend/src/hir/def_collector/errors.rs @@ -1,4 +1,4 @@ -use crate::ast::{Ident, ItemVisibility, Path, UnresolvedTypeData}; +use crate::ast::{Ident, ItemVisibility, Path, UnsupportedNumericGenericType}; use crate::hir::resolution::import::PathResolutionError; use crate::hir::type_check::generics::TraitGenerics; @@ -71,8 +71,6 @@ pub enum DefCollectorErrorKind { "Either the type or the trait must be from the same crate as the trait implementation" )] TraitImplOrphaned { span: Span }, - #[error("The only supported types of numeric generics are integers, fields, and booleans")] - UnsupportedNumericGenericType { ident: Ident, typ: UnresolvedTypeData }, #[error("impl has stricter requirements than trait")] ImplIsStricterThanTrait { constraint_typ: crate::Type, @@ -82,6 +80,8 @@ pub enum DefCollectorErrorKind { trait_method_name: String, trait_method_span: Span, }, + #[error("{0}")] + UnsupportedNumericGenericType(#[from] UnsupportedNumericGenericType), } impl DefCollectorErrorKind { @@ -90,6 +90,19 @@ impl DefCollectorErrorKind { } } +impl<'a> From<&'a UnsupportedNumericGenericType> for Diagnostic { + fn from(error: &'a UnsupportedNumericGenericType) -> Diagnostic { + let name = &error.ident.0.contents; + let typ = &error.typ; + + Diagnostic::simple_error( + format!("{name} has a type of {typ}. The only supported numeric generic types are `u1`, `u8`, `u16`, and `u32`."), + "Unsupported numeric generic type".to_string(), + error.ident.0.span(), + ) + } +} + impl fmt::Display for DuplicateType { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { @@ -266,15 +279,6 @@ impl<'a> From<&'a DefCollectorErrorKind> for Diagnostic { "Either the type or the trait must be from the same crate as the trait implementation".into(), *span, ), - DefCollectorErrorKind::UnsupportedNumericGenericType { ident, typ } => { - let name = &ident.0.contents; - - Diagnostic::simple_error( - format!("{name} has a type of {typ}. The only supported types of numeric generics are integers and fields"), - "Unsupported numeric generic type".to_string(), - ident.0.span(), - ) - } DefCollectorErrorKind::ImplIsStricterThanTrait { constraint_typ, constraint_name, constraint_generics, constraint_span, trait_method_name, trait_method_span } => { let constraint = format!("{}{}", constraint_name, constraint_generics); @@ -286,6 +290,7 @@ impl<'a> From<&'a DefCollectorErrorKind> for Diagnostic { diag.add_secondary(format!("definition of `{trait_method_name}` from trait"), *trait_method_span); diag } + DefCollectorErrorKind::UnsupportedNumericGenericType(err) => err.into(), } } } diff --git a/compiler/noirc_frontend/src/hir/mod.rs b/compiler/noirc_frontend/src/hir/mod.rs index c631edfa889..015b7deb6e0 100644 --- a/compiler/noirc_frontend/src/hir/mod.rs +++ b/compiler/noirc_frontend/src/hir/mod.rs @@ -11,7 +11,7 @@ use crate::graph::{CrateGraph, CrateId}; use crate::hir_def::function::FuncMeta; use crate::node_interner::{FuncId, NodeInterner, StructId}; use crate::parser::ParserError; -use crate::{Generics, Kind, ParsedModule, ResolvedGeneric, Type, TypeVariable}; +use crate::{Generics, Kind, ParsedModule, ResolvedGeneric, TypeVariable}; use def_collector::dc_crate::CompilationError; use def_map::{Contract, CrateDefMap}; use fm::{FileId, FileManager}; @@ -280,19 +280,20 @@ impl Context<'_, '_> { vecmap(generics, |generic| { // Map the generic to a fresh type variable let id = interner.next_type_variable_id(); - let type_var = TypeVariable::unbound(id); + + let type_var_kind = generic.kind().unwrap_or_else(|err| { + errors.push((err.into(), file_id)); + // When there's an error, unify with any other kinds + Kind::Any + }); + let type_var = TypeVariable::unbound(id, type_var_kind); let ident = generic.ident(); let span = ident.0.span(); // Check for name collisions of this generic let name = Rc::new(ident.0.contents.clone()); - let kind = generic.kind().unwrap_or_else(|err| { - errors.push((err.into(), file_id)); - Kind::Numeric(Box::new(Type::Error)) - }); - - ResolvedGeneric { name, type_var, kind, span } + ResolvedGeneric { name, type_var, span } }) } diff --git a/compiler/noirc_frontend/src/hir/resolution/errors.rs b/compiler/noirc_frontend/src/hir/resolution/errors.rs index f3c61a7fbe2..4e9520ad761 100644 --- a/compiler/noirc_frontend/src/hir/resolution/errors.rs +++ b/compiler/noirc_frontend/src/hir/resolution/errors.rs @@ -3,7 +3,10 @@ use noirc_errors::{CustomDiagnostic as Diagnostic, FileDiagnostic}; use thiserror::Error; use crate::{ - ast::Ident, hir::comptime::InterpreterError, parser::ParserError, usage_tracker::UnusedItem, + ast::{Ident, UnsupportedNumericGenericType}, + hir::comptime::InterpreterError, + parser::ParserError, + usage_tracker::UnusedItem, Type, }; @@ -103,8 +106,6 @@ pub enum ResolverError { NoPredicatesAttributeOnUnconstrained { ident: Ident }, #[error("#[fold] attribute is only allowed on constrained functions")] FoldAttributeOnUnconstrained { ident: Ident }, - #[error("The only supported types of numeric generics are integers, fields, and booleans")] - UnsupportedNumericGenericType { ident: Ident, typ: Type }, #[error("expected type, found numeric generic parameter")] NumericGenericUsedForType { name: String, span: Span }, #[error("Invalid array length construction")] @@ -133,6 +134,8 @@ pub enum ResolverError { MutatingComptimeInNonComptimeContext { name: String, span: Span }, #[error("Failed to parse `{statement}` as an expression")] InvalidInternedStatementInExpr { statement: String, span: Span }, + #[error("{0}")] + UnsupportedNumericGenericType(#[from] UnsupportedNumericGenericType), #[error("Type `{typ}` is more private than item `{item}`")] TypeIsMorePrivateThenItem { typ: String, item: String, span: Span }, } @@ -443,15 +446,6 @@ impl<'a> From<&'a ResolverError> for Diagnostic { diag.add_note("The `#[fold]` attribute specifies whether a constrained function should be treated as a separate circuit rather than inlined into the program entry point".to_owned()); diag } - ResolverError::UnsupportedNumericGenericType { ident , typ } => { - let name = &ident.0.contents; - - Diagnostic::simple_error( - format!("{name} has a type of {typ}. The only supported types of numeric generics are integers, fields, and booleans."), - "Unsupported numeric generic type".to_string(), - ident.0.span(), - ) - } ResolverError::NumericGenericUsedForType { name, span } => { Diagnostic::simple_error( format!("expected type, found numeric generic parameter {name}"), @@ -544,6 +538,7 @@ impl<'a> From<&'a ResolverError> for Diagnostic { *span, ) }, + ResolverError::UnsupportedNumericGenericType(err) => err.into(), ResolverError::TypeIsMorePrivateThenItem { typ, item, span } => { Diagnostic::simple_warning( format!("Type `{typ}` is more private than item `{item}`"), diff --git a/compiler/noirc_frontend/src/hir_def/traits.rs b/compiler/noirc_frontend/src/hir_def/traits.rs index 0572ba403a1..3859db26e39 100644 --- a/compiler/noirc_frontend/src/hir_def/traits.rs +++ b/compiler/noirc_frontend/src/hir_def/traits.rs @@ -3,12 +3,12 @@ use rustc_hash::FxHashMap as HashMap; use crate::ast::{Ident, NoirFunction}; use crate::hir::type_check::generics::TraitGenerics; +use crate::ResolvedGeneric; use crate::{ graph::CrateId, node_interner::{FuncId, TraitId, TraitMethodId}, Generics, Type, TypeBindings, TypeVariable, }; -use crate::{ResolvedGeneric, TypeVariableKind}; use fm::FileId; use noirc_errors::{Location, Span}; @@ -168,7 +168,7 @@ impl Trait { }); TraitConstraint { - typ: Type::TypeVariable(self.self_type_typevar.clone(), TypeVariableKind::Normal), + typ: Type::TypeVariable(self.self_type_typevar.clone()), trait_generics: TraitGenerics { ordered, named }, trait_id: self.id, span, diff --git a/compiler/noirc_frontend/src/hir_def/types.rs b/compiler/noirc_frontend/src/hir_def/types.rs index c170d2cc08f..8f20fe1c685 100644 --- a/compiler/noirc_frontend/src/hir_def/types.rs +++ b/compiler/noirc_frontend/src/hir_def/types.rs @@ -78,7 +78,7 @@ pub enum Type { /// is a process that replaces each NamedGeneric in a generic function with a TypeVariable. /// Doing this at each call site of a generic function is how they can be called with /// different argument types each time. - TypeVariable(TypeVariable, TypeVariableKind), + TypeVariable(TypeVariable), /// `impl Trait` when used in a type position. /// These are only matched based on the TraitId. The trait name parameter is only @@ -87,7 +87,7 @@ pub enum Type { /// NamedGenerics are the 'T' or 'U' in a user-defined generic function /// like `fn foo(...) {}`. Unlike TypeVariables, they cannot be bound over. - NamedGeneric(TypeVariable, Rc, Kind), + NamedGeneric(TypeVariable, Rc), /// A functions with arguments, a return type and environment. /// the environment should be `Unit` by default, @@ -134,7 +134,25 @@ pub enum Type { /// is expected (such as in an array length position) are expected to be of kind `Kind::Numeric`. #[derive(PartialEq, Eq, Clone, Hash, Debug, PartialOrd, Ord)] pub enum Kind { + /// Can bind to any type + // TODO(https://github.com/noir-lang/noir/issues/6194): evaluate need for and usage of + Any, + + /// Can bind to any type, except Type::Constant and Type::InfixExpr Normal, + + /// A generic integer or field type. This is a more specific kind of TypeVariable + /// that can only be bound to Type::Field, Type::Integer, or other polymorphic integers. + /// This is the type of undecorated integer literals like `46`. Typing them in this way + /// allows them to be polymorphic over the actual integer/field type used without requiring + /// type annotations on each integer literal. + IntegerOrField, + + /// A generic integer type. This is a more specific kind of TypeVariable + /// that can only be bound to Type::Integer, or other polymorphic integers. + Integer, + + /// Can bind to a Type::Constant or Type::InfixExpr of the given kind Numeric(Box), } @@ -150,18 +168,34 @@ impl Kind { matches!(self, Self::Numeric { .. }) } - pub(crate) fn matches_opt(&self, other: Option) -> bool { - other.as_ref().map_or(true, |other_kind| self.unifies(other_kind)) - } - pub(crate) fn u32() -> Self { Self::Numeric(Box::new(Type::Integer(Signedness::Unsigned, IntegerBitSize::ThirtyTwo))) } + pub(crate) fn follow_bindings(&self) -> Self { + match self { + Self::Any => Self::Any, + Self::Normal => Self::Normal, + Self::Integer => Self::Integer, + Self::IntegerOrField => Self::IntegerOrField, + Self::Numeric(typ) => Self::Numeric(Box::new(typ.follow_bindings())), + } + } + /// Unifies this kind with the other. Returns true on success pub(crate) fn unifies(&self, other: &Kind) -> bool { match (self, other) { - (Kind::Normal, Kind::Normal) => true, + // Kind::Any unifies with everything + (Kind::Any, _) | (_, Kind::Any) => true, + + // Kind::Normal unifies with Kind::Integer and Kind::IntegerOrField + (Kind::Normal, Kind::Integer | Kind::IntegerOrField) + | (Kind::Integer | Kind::IntegerOrField, Kind::Normal) => true, + + // Kind::Integer unifies with Kind::IntegerOrField + (Kind::Integer | Kind::IntegerOrField, Kind::Integer | Kind::IntegerOrField) => true, + + // Kind::Numeric unifies along its Type argument (Kind::Numeric(lhs), Kind::Numeric(rhs)) => { let mut bindings = TypeBindings::new(); let unifies = lhs.try_unify(rhs, &mut bindings).is_ok(); @@ -170,7 +204,9 @@ impl Kind { } unifies } - _ => false, + + // everything unifies with itself + (lhs, rhs) => lhs == rhs, } } @@ -181,12 +217,27 @@ impl Kind { Err(UnificationError) } } + + /// Returns the default type this type variable should be bound to if it is still unbound + /// during monomorphization. + pub(crate) fn default_type(&self) -> Option { + match self { + Kind::Any => None, + Kind::IntegerOrField => Some(Type::default_int_or_field_type()), + Kind::Integer => Some(Type::default_int_type()), + Kind::Normal => None, + Kind::Numeric(typ) => Some(*typ.clone()), + } + } } impl std::fmt::Display for Kind { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { + Kind::Any => write!(f, "any"), Kind::Normal => write!(f, "normal"), + Kind::Integer => write!(f, "int"), + Kind::IntegerOrField => write!(f, "intOrField"), Kind::Numeric(typ) => write!(f, "numeric {}", typ), } } @@ -209,10 +260,10 @@ pub enum QuotedType { CtString, } -/// A list of TypeVariableIds to bind to a type. Storing the +/// A list of (TypeVariableId, Kind)'s to bind to a type. Storing the /// TypeVariable in addition to the matching TypeVariableId allows /// the binding to later be undone if needed. -pub type TypeBindings = HashMap; +pub type TypeBindings = HashMap; /// Represents a struct type in the type system. Each instance of this /// rust struct will be shared across all Type::Struct variants that represent @@ -236,7 +287,7 @@ pub struct StructType { /// Corresponds to generic lists such as `` in the source program. /// Used mainly for resolved types which no longer need information such -/// as names or kinds. +/// as names or kinds pub type GenericTypeVars = Vec; /// Corresponds to generic lists such as `` with additional @@ -248,17 +299,20 @@ pub type Generics = Vec; pub struct ResolvedGeneric { pub name: Rc, pub type_var: TypeVariable, - pub kind: Kind, pub span: Span, } impl ResolvedGeneric { pub fn as_named_generic(self) -> Type { - Type::NamedGeneric(self.type_var, self.name, self.kind) + Type::NamedGeneric(self.type_var, self.name) + } + + pub fn kind(&self) -> Kind { + self.type_var.kind() } pub(crate) fn is_numeric(&self) -> bool { - self.kind.is_numeric() + self.kind().is_numeric() } } @@ -326,7 +380,12 @@ impl StructType { .generics .iter() .zip(generic_args) - .map(|(old, new)| (old.type_var.id(), (old.type_var.clone(), new.clone()))) + .map(|(old, new)| { + ( + old.type_var.id(), + (old.type_var.clone(), old.type_var.kind(), new.clone()), + ) + }) .collect(); (typ.substitute(&substitutions), i) @@ -342,7 +401,9 @@ impl StructType { .generics .iter() .zip(generic_args) - .map(|(old, new)| (old.type_var.id(), (old.type_var.clone(), new.clone()))) + .map(|(old, new)| { + (old.type_var.id(), (old.type_var.clone(), old.type_var.kind(), new.clone())) + }) .collect(); vecmap(&self.fields, |(name, typ)| { @@ -380,7 +441,7 @@ impl StructType { /// Instantiate this struct type, returning a Vec of the new generic args (in /// the same order as self.generics) pub fn instantiate(&self, interner: &mut NodeInterner) -> Vec { - vecmap(&self.generics, |_| interner.next_type_variable()) + vecmap(&self.generics, |generic| interner.next_type_variable_with_kind(generic.kind())) } } @@ -454,7 +515,9 @@ impl TypeAlias { .generics .iter() .zip(generic_args) - .map(|(old, new)| (old.type_var.id(), (old.type_var.clone(), new.clone()))) + .map(|(old, new)| { + (old.type_var.id(), (old.type_var.clone(), old.type_var.kind(), new.clone())) + }) .collect(); self.typ.substitute(&substitutions) @@ -527,31 +590,14 @@ pub enum BinaryTypeOperator { Modulo, } -#[derive(Debug, PartialEq, Eq, Clone, Hash, PartialOrd, Ord)] -pub enum TypeVariableKind { - /// Can bind to any type - Normal, - - /// A generic integer or field type. This is a more specific kind of TypeVariable - /// that can only be bound to Type::Field, Type::Integer, or other polymorphic integers. - /// This is the type of undecorated integer literals like `46`. Typing them in this way - /// allows them to be polymorphic over the actual integer/field type used without requiring - /// type annotations on each integer literal. - IntegerOrField, - - /// A generic integer type. This is a more specific kind of TypeVariable - /// that can only be bound to Type::Integer, or other polymorphic integers. - Integer, -} - /// A TypeVariable is a mutable reference that is either /// bound to some type, or unbound with a given TypeVariableId. #[derive(PartialEq, Eq, Clone, Hash, PartialOrd, Ord)] pub struct TypeVariable(TypeVariableId, Shared); impl TypeVariable { - pub fn unbound(id: TypeVariableId) -> Self { - TypeVariable(id, Shared::new(TypeBinding::Unbound(id))) + pub fn unbound(id: TypeVariableId, type_var_kind: Kind) -> Self { + TypeVariable(id, Shared::new(TypeBinding::Unbound(id, type_var_kind))) } pub fn id(&self) -> TypeVariableId { @@ -568,19 +614,27 @@ impl TypeVariable { TypeBinding::Bound(binding) => { unreachable!("TypeVariable::bind, cannot bind bound var {} to {}", binding, typ) } - TypeBinding::Unbound(id) => *id, + TypeBinding::Unbound(id, _) => *id, }; assert!(!typ.occurs(id), "{self:?} occurs within {typ:?}"); *self.1.borrow_mut() = TypeBinding::Bound(typ); } - pub fn try_bind(&self, binding: Type, span: Span) -> Result<(), TypeCheckError> { + pub fn try_bind(&self, binding: Type, kind: &Kind, span: Span) -> Result<(), TypeCheckError> { + if !binding.kind().unifies(kind) { + return Err(TypeCheckError::TypeKindMismatch { + expected_kind: format!("{}", kind), + expr_kind: format!("{}", binding.kind()), + expr_span: span, + }); + } + let id = match &*self.1.borrow() { TypeBinding::Bound(binding) => { unreachable!("Expected unbound, found bound to {binding}") } - TypeBinding::Unbound(id) => *id, + TypeBinding::Unbound(id, _) => *id, }; if binding.occurs(id) { @@ -599,18 +653,47 @@ impl TypeVariable { /// Unbind this type variable, setting it to Unbound(id). /// /// This is generally a logic error to use outside of monomorphization. - pub fn unbind(&self, id: TypeVariableId) { - *self.1.borrow_mut() = TypeBinding::Unbound(id); + pub fn unbind(&self, id: TypeVariableId, type_var_kind: Kind) { + *self.1.borrow_mut() = TypeBinding::Unbound(id, type_var_kind); } /// Forcibly bind a type variable to a new type - even if the type /// variable is already bound to a different type. This generally /// a logic error to use outside of monomorphization. + /// + /// Asserts that the given type is compatible with the given Kind pub fn force_bind(&self, typ: Type) { if !typ.occurs(self.id()) { *self.1.borrow_mut() = TypeBinding::Bound(typ); } } + + pub fn kind(&self) -> Kind { + match &*self.borrow() { + TypeBinding::Bound(binding) => binding.kind(), + TypeBinding::Unbound(_, type_var_kind) => type_var_kind.clone(), + } + } + + /// Check that if bound, it's an integer + /// and if unbound, that it's a Kind::Integer + pub fn is_integer(&self) -> bool { + match &*self.borrow() { + TypeBinding::Bound(binding) => matches!(binding, Type::Integer(..)), + TypeBinding::Unbound(_, type_var_kind) => matches!(type_var_kind, Kind::Integer), + } + } + + /// Check that if bound, it's an integer or field + /// and if unbound, that it's a Kind::IntegerOrField + pub fn is_integer_or_field(&self) -> bool { + match &*self.borrow() { + TypeBinding::Bound(binding) => { + matches!(binding, Type::Integer(..) | Type::FieldElement) + } + TypeBinding::Unbound(_, type_var_kind) => matches!(type_var_kind, Kind::IntegerOrField), + } + } } /// TypeBindings are the mutable insides of a TypeVariable. @@ -618,12 +701,12 @@ impl TypeVariable { #[derive(Clone, PartialEq, Eq, Hash, Debug, PartialOrd, Ord)] pub enum TypeBinding { Bound(Type), - Unbound(TypeVariableId), + Unbound(TypeVariableId, Kind), } impl TypeBinding { pub fn is_unbound(&self) -> bool { - matches!(self, TypeBinding::Unbound(_)) + matches!(self, TypeBinding::Unbound(_, _)) } } @@ -647,22 +730,18 @@ impl std::fmt::Display for Type { Signedness::Signed => write!(f, "i{num_bits}"), Signedness::Unsigned => write!(f, "u{num_bits}"), }, - Type::TypeVariable(var, TypeVariableKind::Normal) => write!(f, "{}", var.borrow()), - Type::TypeVariable(binding, TypeVariableKind::Integer) => { - if let TypeBinding::Unbound(_) = &*binding.borrow() { - write!(f, "{}", Type::default_int_type()) - } else { - write!(f, "{}", binding.borrow()) - } - } - Type::TypeVariable(binding, TypeVariableKind::IntegerOrField) => { - if let TypeBinding::Unbound(_) = &*binding.borrow() { - // Show a Field by default if this TypeVariableKind::IntegerOrField is unbound, since that is - // what they bind to by default anyway. It is less confusing than displaying it - // as a generic. - write!(f, "Field") - } else { - write!(f, "{}", binding.borrow()) + Type::TypeVariable(var) => { + let binding = &var.1; + match &*binding.borrow() { + TypeBinding::Unbound(_, type_var_kind) => match type_var_kind { + Kind::Any | Kind::Normal => write!(f, "{}", var.borrow()), + Kind::Integer => write!(f, "{}", Type::default_int_type()), + Kind::IntegerOrField => write!(f, "Field"), + Kind::Numeric(_typ) => write!(f, "_"), + }, + TypeBinding::Bound(binding) => { + write!(f, "{}", binding) + } } } Type::Struct(s, args) => { @@ -695,10 +774,10 @@ impl std::fmt::Display for Type { } Type::Unit => write!(f, "()"), Type::Error => write!(f, "error"), - Type::NamedGeneric(binding, name, _) => match &*binding.borrow() { + Type::NamedGeneric(binding, name) => match &*binding.borrow() { TypeBinding::Bound(binding) => binding.fmt(f), - TypeBinding::Unbound(_) if name.is_empty() => write!(f, "_"), - TypeBinding::Unbound(_) => write!(f, "{name}"), + TypeBinding::Unbound(_, _) if name.is_empty() => write!(f, "_"), + TypeBinding::Unbound(_, _) => write!(f, "{name}"), }, Type::Constant(x, _kind) => write!(f, "{x}"), Type::Forall(typevars, typ) => { @@ -759,7 +838,7 @@ impl std::fmt::Display for TypeBinding { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { TypeBinding::Bound(typ) => typ.fmt(f), - TypeBinding::Unbound(id) => id.fmt(f), + TypeBinding::Unbound(id, _) => id.fmt(f), } } } @@ -795,23 +874,25 @@ impl Type { Type::Integer(Signedness::Unsigned, IntegerBitSize::ThirtyTwo) } + pub fn type_variable_with_kind(interner: &NodeInterner, type_var_kind: Kind) -> Type { + let id = interner.next_type_variable_id(); + let var = TypeVariable::unbound(id, type_var_kind); + Type::TypeVariable(var) + } + pub fn type_variable(id: TypeVariableId) -> Type { - let var = TypeVariable::unbound(id); - Type::TypeVariable(var, TypeVariableKind::Normal) + let var = TypeVariable::unbound(id, Kind::Normal); + Type::TypeVariable(var) } - pub fn polymorphic_integer_or_field(interner: &mut NodeInterner) -> Type { - let id = interner.next_type_variable_id(); - let kind = TypeVariableKind::IntegerOrField; - let var = TypeVariable::unbound(id); - Type::TypeVariable(var, kind) + pub fn polymorphic_integer_or_field(interner: &NodeInterner) -> Type { + let type_var_kind = Kind::IntegerOrField; + Self::type_variable_with_kind(interner, type_var_kind) } - pub fn polymorphic_integer(interner: &mut NodeInterner) -> Type { - let id = interner.next_type_variable_id(); - let kind = TypeVariableKind::Integer; - let var = TypeVariable::unbound(id); - Type::TypeVariable(var, kind) + pub fn polymorphic_integer(interner: &NodeInterner) -> Type { + let type_var_kind = Kind::Integer; + Self::type_variable_with_kind(interner, type_var_kind) } /// A bit of an awkward name for this function - this function returns @@ -820,9 +901,9 @@ impl Type { /// they shouldn't be bound over until monomorphization. pub fn is_bindable(&self) -> bool { match self { - Type::TypeVariable(binding, _) => match &*binding.borrow() { + Type::TypeVariable(binding) => match &*binding.borrow() { TypeBinding::Bound(binding) => binding.is_bindable(), - TypeBinding::Unbound(_) => true, + TypeBinding::Unbound(_, _) => true, }, Type::Alias(alias, args) => alias.borrow().get_type(args).is_bindable(), _ => false, @@ -849,21 +930,35 @@ impl Type { matches!(self.follow_bindings(), Type::Integer(Signedness::Unsigned, _)) } - pub fn is_numeric(&self) -> bool { + /// While Kind::is_numeric refers to numeric _types_, + /// this method checks for numeric _values_ + pub fn is_numeric_value(&self) -> bool { + use Kind as K; use Type::*; - use TypeVariableKind as K; - matches!( - self.follow_bindings(), - FieldElement | Integer(..) | Bool | TypeVariable(_, K::Integer | K::IntegerOrField) - ) + match self.follow_bindings() { + FieldElement => true, + Integer(..) => true, + Bool => true, + TypeVariable(var) => match &*var.borrow() { + TypeBinding::Bound(typ) => typ.is_numeric_value(), + TypeBinding::Unbound(_, type_var_kind) => { + matches!(type_var_kind, K::Integer | K::IntegerOrField) + } + }, + _ => false, + } } pub fn find_numeric_type_vars(&self, found_names: &mut Vec) { - // Return whether the named generic has a TypeKind::Numeric and save its name + // Return whether the named generic has a Kind::Numeric and save its name let named_generic_is_numeric = |typ: &Type, found_names: &mut Vec| { - if let Type::NamedGeneric(_, name, Kind::Numeric { .. }) = typ { - found_names.push(name.to_string()); - true + if let Type::NamedGeneric(var, name) = typ { + if var.kind().is_numeric() { + found_names.push(name.to_string()); + true + } else { + false + } } else { false } @@ -879,13 +974,13 @@ impl Type { | Type::Forall(_, _) | Type::Quoted(_) => {} - Type::TypeVariable(type_var, _) => { + Type::TypeVariable(type_var) => { if let TypeBinding::Bound(typ) = &*type_var.borrow() { named_generic_is_numeric(typ, found_names); } } - Type::NamedGeneric(_, _, _) => { + Type::NamedGeneric(_, _) => { named_generic_is_numeric(self, found_names); } @@ -962,8 +1057,8 @@ impl Type { | Type::Error => true, Type::FmtString(_, _) - | Type::TypeVariable(_, _) - | Type::NamedGeneric(_, _, _) + | Type::TypeVariable(_) + | Type::NamedGeneric(_, _) | Type::Function(_, _, _, _) | Type::MutableReference(_) | Type::Forall(_, _) @@ -1005,8 +1100,8 @@ impl Type { | Type::Bool | Type::Unit | Type::Constant(_, _) - | Type::TypeVariable(_, _) - | Type::NamedGeneric(_, _, _) + | Type::TypeVariable(_) + | Type::NamedGeneric(_, _) | Type::InfixExpr(..) | Type::Error => true, @@ -1054,7 +1149,7 @@ impl Type { | Type::InfixExpr(..) | Type::Error => true, - Type::TypeVariable(type_var, _) | Type::NamedGeneric(type_var, _, _) => { + Type::TypeVariable(type_var) | Type::NamedGeneric(type_var, _) => { if let TypeBinding::Bound(typ) = &*type_var.borrow() { typ.is_valid_for_unconstrained_boundary() } else { @@ -1094,10 +1189,10 @@ impl Type { pub fn generic_count(&self) -> usize { match self { Type::Forall(generics, _) => generics.len(), - Type::TypeVariable(type_variable, _) | Type::NamedGeneric(type_variable, _, _) => { + Type::TypeVariable(type_variable) | Type::NamedGeneric(type_variable, _) => { match &*type_variable.borrow() { TypeBinding::Bound(binding) => binding.generic_count(), - TypeBinding::Unbound(_) => 0, + TypeBinding::Unbound(_, _) => 0, } } _ => 0, @@ -1105,9 +1200,10 @@ impl Type { } /// Takes a monomorphic type and generalizes it over each of the type variables in the - /// given type bindings, ignoring what each type variable is bound to in the TypeBindings. + /// given type bindings, ignoring what each type variable is bound to in the TypeBindings + /// and their Kind's pub(crate) fn generalize_from_substitutions(self, type_bindings: TypeBindings) -> Type { - let polymorphic_type_vars = vecmap(type_bindings, |(_, (type_var, _))| type_var); + let polymorphic_type_vars = vecmap(type_bindings, |(_, (type_var, _kind, _))| type_var); Type::Forall(polymorphic_type_vars, Box::new(self)) } @@ -1130,15 +1226,15 @@ impl Type { } } - pub(crate) fn kind(&self) -> Option { + pub(crate) fn kind(&self) -> Kind { match self { - Type::NamedGeneric(_, _, kind) => Some(kind.clone()), - Type::Constant(_, kind) => Some(kind.clone()), - Type::TypeVariable(var, _) => match *var.borrow() { + Type::NamedGeneric(var, _) => var.kind(), + Type::Constant(_, kind) => kind.clone(), + Type::TypeVariable(var) => match &*var.borrow() { TypeBinding::Bound(ref typ) => typ.kind(), - TypeBinding::Unbound(_) => None, + TypeBinding::Unbound(_, ref type_var_kind) => type_var_kind.clone(), }, - Type::InfixExpr(lhs, _op, rhs) => Some(lhs.infix_kind(rhs)), + Type::InfixExpr(lhs, _op, rhs) => lhs.infix_kind(rhs), Type::FieldElement | Type::Array(..) | Type::Slice(..) @@ -1155,26 +1251,18 @@ impl Type { | Type::MutableReference(..) | Type::Forall(..) | Type::Quoted(..) - | Type::Error => Some(Kind::Normal), + | Type::Error => Kind::Normal, } } - /// if both Kind's are equal to Some(_), return that Kind, - /// otherwise return a Kind error - /// if both Kind's are None, default to u32 - /// if exactly one Kind is None, return the other one + /// Unifies self and other kinds or fails with a Kind error fn infix_kind(&self, other: &Self) -> Kind { - match (self.kind(), other.kind()) { - (Some(self_kind), Some(other_kind)) => { - if self_kind == other_kind { - self_kind - } else { - Kind::Numeric(Box::new(Type::Error)) - } - } - (None, None) => Kind::u32(), - (Some(self_kind), None) => self_kind, - (None, Some(other_kind)) => other_kind, + let self_kind = self.kind(); + let other_kind = other.kind(); + if self_kind.unifies(&other_kind) { + self_kind + } else { + Kind::Numeric(Box::new(Type::Error)) } } @@ -1203,9 +1291,9 @@ impl Type { .expect("Cannot have variable sized strings as a parameter to main"), Type::FmtString(_, _) | Type::Unit - | Type::TypeVariable(_, _) + | Type::TypeVariable(_) | Type::TraitAsType(..) - | Type::NamedGeneric(_, _, _) + | Type::NamedGeneric(_, _) | Type::Function(_, _, _, _) | Type::MutableReference(_) | Type::Forall(_, _) @@ -1261,71 +1349,62 @@ impl Type { ) -> Result<(), UnificationError> { let target_id = match &*var.borrow() { TypeBinding::Bound(_) => unreachable!(), - TypeBinding::Unbound(id) => *id, + TypeBinding::Unbound(id, _) => *id, }; + if !self.kind().unifies(&Kind::IntegerOrField) { + return Err(UnificationError); + } + let this = self.substitute(bindings).follow_bindings(); match &this { Type::Integer(..) => { - bindings.insert(target_id, (var.clone(), this)); + bindings.insert(target_id, (var.clone(), Kind::Integer, this)); Ok(()) } Type::FieldElement if !only_integer => { - bindings.insert(target_id, (var.clone(), this)); + bindings.insert(target_id, (var.clone(), Kind::IntegerOrField, this)); Ok(()) } - Type::TypeVariable(self_var, TypeVariableKind::IntegerOrField) => { + Type::TypeVariable(self_var) => { let borrow = self_var.borrow(); match &*borrow { TypeBinding::Bound(typ) => { typ.try_bind_to_polymorphic_int(var, bindings, only_integer) } // Avoid infinitely recursive bindings - TypeBinding::Unbound(id) if *id == target_id => Ok(()), - TypeBinding::Unbound(new_target_id) => { + TypeBinding::Unbound(ref id, _) if *id == target_id => Ok(()), + TypeBinding::Unbound(ref new_target_id, Kind::IntegerOrField) => { + let type_var_kind = Kind::IntegerOrField; if only_integer { + let var_clone = var.clone(); + Kind::Integer.unify(&type_var_kind)?; // Integer is more specific than IntegerOrField so we bind the type // variable to Integer instead. - let clone = Type::TypeVariable(var.clone(), TypeVariableKind::Integer); - bindings.insert(*new_target_id, (self_var.clone(), clone)); + let clone = Type::TypeVariable(var_clone); + bindings + .insert(*new_target_id, (self_var.clone(), type_var_kind, clone)); } else { - bindings.insert(target_id, (var.clone(), this.clone())); + bindings.insert( + target_id, + (var.clone(), Kind::IntegerOrField, this.clone()), + ); } Ok(()) } - } - } - Type::TypeVariable(self_var, TypeVariableKind::Integer) => { - let borrow = self_var.borrow(); - match &*borrow { - TypeBinding::Bound(typ) => { - typ.try_bind_to_polymorphic_int(var, bindings, only_integer) - } - // Avoid infinitely recursive bindings - TypeBinding::Unbound(id) if *id == target_id => Ok(()), - TypeBinding::Unbound(_) => { - bindings.insert(target_id, (var.clone(), this.clone())); + TypeBinding::Unbound(_new_target_id, Kind::Integer) => { + Kind::Integer.unify(&Kind::Integer)?; + bindings.insert(target_id, (var.clone(), Kind::Integer, this.clone())); Ok(()) } - } - } - Type::TypeVariable(self_var, TypeVariableKind::Normal) => { - let borrow = self_var.borrow(); - match &*borrow { - TypeBinding::Bound(typ) => { - typ.try_bind_to_polymorphic_int(var, bindings, only_integer) - } - // Avoid infinitely recursive bindings - TypeBinding::Unbound(id) if *id == target_id => Ok(()), - TypeBinding::Unbound(new_target_id) => { + TypeBinding::Unbound(new_target_id, ref type_var_kind) => { + let var_clone = var.clone(); // Bind to the most specific type variable kind - let clone_kind = if only_integer { - TypeVariableKind::Integer - } else { - TypeVariableKind::IntegerOrField - }; - let clone = Type::TypeVariable(var.clone(), clone_kind); - bindings.insert(*new_target_id, (self_var.clone(), clone)); + let clone_kind = + if only_integer { Kind::Integer } else { Kind::IntegerOrField }; + clone_kind.unify(type_var_kind)?; + let clone = Type::TypeVariable(var_clone); + bindings.insert(*new_target_id, (self_var.clone(), clone_kind, clone)); Ok(()) } } @@ -1335,7 +1414,7 @@ impl Type { } /// Try to bind the given type variable to self. Although the given type variable - /// is expected to be of TypeVariableKind::Normal, this binding can still fail + /// is expected to be of Kind::Normal, this binding can still fail /// if the given type variable occurs within `self` as that would create a recursive type. /// /// If successful, the binding is placed in the @@ -1344,18 +1423,23 @@ impl Type { &self, var: &TypeVariable, bindings: &mut TypeBindings, + kind: Kind, ) -> Result<(), UnificationError> { let target_id = match &*var.borrow() { TypeBinding::Bound(_) => unreachable!(), - TypeBinding::Unbound(id) => *id, + TypeBinding::Unbound(id, _) => *id, }; + if !self.kind().unifies(&kind) { + return Err(UnificationError); + } + let this = self.substitute(bindings).follow_bindings(); - if let Some(binding) = this.get_inner_type_variable() { + if let Some((binding, kind)) = this.get_inner_type_variable() { match &*binding.borrow() { - TypeBinding::Bound(typ) => return typ.try_bind_to(var, bindings), + TypeBinding::Bound(typ) => return typ.try_bind_to(var, bindings, kind), // Don't recursively bind the same id to itself - TypeBinding::Unbound(id) if *id == target_id => return Ok(()), + TypeBinding::Unbound(id, _) if *id == target_id => return Ok(()), _ => (), } } @@ -1365,14 +1449,15 @@ impl Type { if this.occurs(target_id) { Err(UnificationError) } else { - bindings.insert(target_id, (var.clone(), this.clone())); + bindings.insert(target_id, (var.clone(), this.kind(), this.clone())); Ok(()) } } - fn get_inner_type_variable(&self) -> Option> { + fn get_inner_type_variable(&self) -> Option<(Shared, Kind)> { match self { - Type::TypeVariable(var, _) | Type::NamedGeneric(var, _, _) => Some(var.1.clone()), + Type::TypeVariable(var) => Some((var.1.clone(), var.kind())), + Type::NamedGeneric(var, _) => Some((var.1.clone(), var.kind())), _ => None, } } @@ -1398,7 +1483,6 @@ impl Type { bindings: &mut TypeBindings, ) -> Result<(), UnificationError> { use Type::*; - use TypeVariableKind as Kind; let lhs = match self { Type::InfixExpr(..) => Cow::Owned(self.canonicalize()), @@ -1418,27 +1502,36 @@ impl Type { alias.try_unify(other, bindings) } - (TypeVariable(var, Kind::IntegerOrField), other) - | (other, TypeVariable(var, Kind::IntegerOrField)) => { - other.try_unify_to_type_variable(var, bindings, |bindings| { - let only_integer = false; - other.try_bind_to_polymorphic_int(var, bindings, only_integer) - }) - } - - (TypeVariable(var, Kind::Integer), other) - | (other, TypeVariable(var, Kind::Integer)) => { - other.try_unify_to_type_variable(var, bindings, |bindings| { - let only_integer = true; - other.try_bind_to_polymorphic_int(var, bindings, only_integer) - }) - } - - (TypeVariable(var, Kind::Normal), other) | (other, TypeVariable(var, Kind::Normal)) => { - other.try_unify_to_type_variable(var, bindings, |bindings| { - other.try_bind_to(var, bindings) - }) - } + (TypeVariable(var), other) | (other, TypeVariable(var)) => match &*var.borrow() { + TypeBinding::Bound(typ) => { + if typ.is_numeric_value() { + other.try_unify_to_type_variable(var, bindings, |bindings| { + let only_integer = matches!(typ, Type::Integer(..)); + other.try_bind_to_polymorphic_int(var, bindings, only_integer) + }) + } else { + other.try_unify_to_type_variable(var, bindings, |bindings| { + other.try_bind_to(var, bindings, typ.kind()) + }) + } + } + TypeBinding::Unbound(_id, Kind::IntegerOrField) => other + .try_unify_to_type_variable(var, bindings, |bindings| { + let only_integer = false; + other.try_bind_to_polymorphic_int(var, bindings, only_integer) + }), + TypeBinding::Unbound(_id, Kind::Integer) => { + other.try_unify_to_type_variable(var, bindings, |bindings| { + let only_integer = true; + other.try_bind_to_polymorphic_int(var, bindings, only_integer) + }) + } + TypeBinding::Unbound(_id, type_var_kind) => { + other.try_unify_to_type_variable(var, bindings, |bindings| { + other.try_bind_to(var, bindings, type_var_kind.clone()) + }) + } + }, (Array(len_a, elem_a), Array(len_b, elem_b)) => { len_a.try_unify(len_b, bindings)?; @@ -1479,7 +1572,7 @@ impl Type { } } - (NamedGeneric(binding, _, _), other) | (other, NamedGeneric(binding, _, _)) + (NamedGeneric(binding, _), other) | (other, NamedGeneric(binding, _)) if !binding.borrow().is_unbound() => { if let TypeBinding::Bound(link) = &*binding.borrow() { @@ -1489,13 +1582,13 @@ impl Type { } } - (NamedGeneric(binding_a, name_a, kind_a), NamedGeneric(binding_b, name_b, kind_b)) => { + (NamedGeneric(binding_a, name_a), NamedGeneric(binding_b, name_b)) => { // Bound NamedGenerics are caught by the check above assert!(binding_a.borrow().is_unbound()); assert!(binding_b.borrow().is_unbound()); if name_a == name_b { - kind_a.unify(kind_b) + binding_a.kind().unify(&binding_b.kind()) } else { Err(UnificationError) } @@ -1541,8 +1634,9 @@ impl Type { } (Constant(value, kind), other) | (other, Constant(value, kind)) => { + // TODO(https://github.com/noir-lang/noir/pull/6137): replace evaluate_to_u32 if let Some(other_value) = other.evaluate_to_u32() { - if *value == other_value && kind.matches_opt(other.kind()) { + if *value == other_value && kind.unifies(&other.kind()) { Ok(()) } else { Err(UnificationError) @@ -1583,18 +1677,23 @@ impl Type { bindings: &mut TypeBindings, // Bind the type variable to a type. This is factored out since depending on the - // TypeVariableKind, there are different methods to check whether the variable can + // Kind, there are different methods to check whether the variable can // bind to the given type or not. bind_variable: impl FnOnce(&mut TypeBindings) -> Result<(), UnificationError>, ) -> Result<(), UnificationError> { match &*type_variable.borrow() { // If it is already bound, unify against what it is bound to TypeBinding::Bound(link) => link.try_unify(self, bindings), - TypeBinding::Unbound(id) => { + TypeBinding::Unbound(id, _) => { // We may have already "bound" this type variable in this call to // try_unify, so check those bindings as well. match bindings.get(id) { - Some((_, binding)) => binding.clone().try_unify(self, bindings), + Some((_, kind, binding)) => { + if !kind.unifies(&binding.kind()) { + return Err(UnificationError); + } + binding.clone().try_unify(self, bindings) + } // Otherwise, bind it None => bind_variable(bindings), @@ -1697,7 +1796,7 @@ impl Type { /// Apply the given type bindings, making them permanently visible for each /// clone of each type variable bound. pub fn apply_type_bindings(bindings: TypeBindings) { - for (type_variable, binding) in bindings.values() { + for (type_variable, _kind, binding) in bindings.values() { type_variable.bind(binding.clone()); } } @@ -1705,7 +1804,7 @@ impl Type { /// If this type is a Type::Constant (used in array lengths), or is bound /// to a Type::Constant, return the constant as a u32. pub fn evaluate_to_u32(&self) -> Option { - if let Some(binding) = self.get_inner_type_variable() { + if let Some((binding, _kind)) = self.get_inner_type_variable() { if let TypeBinding::Bound(binding) = &*binding.borrow() { return binding.evaluate_to_u32(); } @@ -1713,11 +1812,11 @@ impl Type { match self.canonicalize() { Type::Array(len, _elem) => len.evaluate_to_u32(), - Type::Constant(x, _) => Some(x), + Type::Constant(x, _kind) => Some(x), Type::InfixExpr(lhs, op, rhs) => { - let lhs = lhs.evaluate_to_u32()?; - let rhs = rhs.evaluate_to_u32()?; - op.function(lhs, rhs) + let lhs_u32 = lhs.evaluate_to_u32()?; + let rhs_u32 = rhs.evaluate_to_u32()?; + op.function(lhs_u32, rhs_u32, &lhs.infix_kind(&rhs)) } _ => None, } @@ -1771,9 +1870,9 @@ impl Type { match self { Type::Forall(typevars, typ) => { for var in typevars { - bindings - .entry(var.id()) - .or_insert_with(|| (var.clone(), interner.next_type_variable())); + bindings.entry(var.id()).or_insert_with(|| { + (var.clone(), var.kind(), interner.next_type_variable_with_kind(var.kind())) + }); } let instantiated = typ.force_substitute(&bindings); @@ -1792,8 +1891,8 @@ impl Type { let replacements = typevars .iter() .map(|var| { - let new = interner.next_type_variable(); - (var.id(), (var.clone(), new)) + let new = interner.next_type_variable_with_kind(var.kind()); + (var.id(), (var.clone(), var.kind(), new)) }) .collect(); @@ -1827,7 +1926,7 @@ impl Type { let replacements = typevars .iter() .zip(bindings) - .map(|(var, binding)| (var.id(), (var.clone(), binding))) + .map(|(var, binding)| (var.id(), (var.clone(), var.kind(), binding))) .collect(); let instantiated = typ.substitute(&replacements); @@ -1839,9 +1938,7 @@ impl Type { fn type_variable_id(&self) -> Option { match self { - Type::TypeVariable(variable, _) | Type::NamedGeneric(variable, _, _) => { - Some(variable.0) - } + Type::TypeVariable(variable) | Type::NamedGeneric(variable, _) => Some(variable.0), _ => None, } } @@ -1894,15 +1991,24 @@ impl Type { // type variables that have already been bound over. // This is needed for monomorphizing trait impl methods. match type_bindings.get(&binding.0) { - Some((_, replacement)) if substitute_bound_typevars => { + Some((_, _kind, replacement)) if substitute_bound_typevars => { recur_on_binding(binding.0, replacement) } _ => match &*binding.borrow() { TypeBinding::Bound(binding) => { binding.substitute_helper(type_bindings, substitute_bound_typevars) } - TypeBinding::Unbound(id) => match type_bindings.get(id) { - Some((_, replacement)) => recur_on_binding(binding.0, replacement), + TypeBinding::Unbound(id, _) => match type_bindings.get(id) { + Some((_, kind, replacement)) => { + assert!( + kind.unifies(&replacement.kind()), + "while substituting (unbound): expected kind of unbound TypeVariable ({:?}) to match the kind of its binding ({:?})", + kind, + replacement.kind() + ); + + recur_on_binding(binding.0, replacement) + } None => self.clone(), }, }, @@ -1928,7 +2034,7 @@ impl Type { let fields = fields.substitute_helper(type_bindings, substitute_bound_typevars); Type::FmtString(Box::new(size), Box::new(fields)) } - Type::NamedGeneric(binding, _, _) | Type::TypeVariable(binding, _) => { + Type::NamedGeneric(binding, _) | Type::TypeVariable(binding) => { substitute_binding(binding) } // Do not substitute_helper fields, it can lead to infinite recursion @@ -2017,12 +2123,12 @@ impl Type { || args.named.iter().any(|arg| arg.typ.occurs(target_id)) } Type::Tuple(fields) => fields.iter().any(|field| field.occurs(target_id)), - Type::NamedGeneric(type_var, _, _) | Type::TypeVariable(type_var, _) => { + Type::NamedGeneric(type_var, _) | Type::TypeVariable(type_var) => { match &*type_var.borrow() { TypeBinding::Bound(binding) => { type_var.id() == target_id || binding.occurs(target_id) } - TypeBinding::Unbound(id) => *id == target_id, + TypeBinding::Unbound(id, _) => *id == target_id, } } Type::Forall(typevars, typ) => { @@ -2075,7 +2181,7 @@ impl Type { def.borrow().get_type(args).follow_bindings() } Tuple(args) => Tuple(vecmap(args, |arg| arg.follow_bindings())), - TypeVariable(var, _) | NamedGeneric(var, _, _) => { + TypeVariable(var) | NamedGeneric(var, _) => { if let TypeBinding::Bound(typ) = &*var.borrow() { return typ.follow_bindings(); } @@ -2114,7 +2220,7 @@ impl Type { } pub fn from_generics(generics: &GenericTypeVars) -> Vec { - vecmap(generics, |var| Type::TypeVariable(var.clone(), TypeVariableKind::Normal)) + vecmap(generics, |var| Type::TypeVariable(var.clone())) } /// Replace any `Type::NamedGeneric` in this type with a `Type::TypeVariable` @@ -2156,12 +2262,11 @@ impl Type { typ.replace_named_generics_with_type_variables(); *self = typ; } - Type::TypeVariable(var, _) => { + Type::TypeVariable(var) => { let var = var.borrow(); if let TypeBinding::Bound(binding) = &*var { - let mut binding = binding.clone(); + let binding = binding.clone(); drop(var); - binding.replace_named_generics_with_type_variables(); *self = binding; } } @@ -2173,7 +2278,7 @@ impl Type { generic.typ.replace_named_generics_with_type_variables(); } } - Type::NamedGeneric(var, _, _) => { + Type::NamedGeneric(var, _) => { let type_binding = var.borrow(); if let TypeBinding::Bound(binding) = &*type_binding { let mut binding = binding.clone(); @@ -2182,7 +2287,7 @@ impl Type { *self = binding; } else { drop(type_binding); - *self = Type::TypeVariable(var.clone(), TypeVariableKind::Normal); + *self = Type::TypeVariable(var.clone()); } } Type::Function(args, ret, env, _unconstrained) => { @@ -2244,7 +2349,9 @@ fn convert_array_expression_to_slice( impl BinaryTypeOperator { /// Perform the actual rust numeric operation associated with this operator - pub fn function(self, a: u32, b: u32) -> Option { + // TODO(https://github.com/noir-lang/noir/pull/6137): the Kind is included + // since it'll be needed for size checks + pub fn function(self, a: u32, b: u32, _kind: &Kind) -> Option { match self { BinaryTypeOperator::Addition => a.checked_add(b), BinaryTypeOperator::Subtraction => a.checked_sub(b), @@ -2270,18 +2377,6 @@ impl BinaryTypeOperator { } } -impl TypeVariableKind { - /// Returns the default type this type variable should be bound to if it is still unbound - /// during monomorphization. - pub(crate) fn default_type(&self) -> Option { - match self { - TypeVariableKind::IntegerOrField => Some(Type::default_int_or_field_type()), - TypeVariableKind::Integer => Some(Type::default_int_type()), - TypeVariableKind::Normal => None, - } - } -} - impl From for PrintableType { fn from(value: Type) -> Self { Self::from(&value) @@ -2309,16 +2404,15 @@ impl From<&Type> for PrintableType { } Signedness::Signed => PrintableType::SignedInteger { width: (*bit_width).into() }, }, - Type::TypeVariable(binding, TypeVariableKind::Integer) => match &*binding.borrow() { + Type::TypeVariable(binding) => match &*binding.borrow() { TypeBinding::Bound(typ) => typ.into(), - TypeBinding::Unbound(_) => Type::default_int_type().into(), - }, - Type::TypeVariable(binding, TypeVariableKind::IntegerOrField) => { - match &*binding.borrow() { - TypeBinding::Bound(typ) => typ.into(), - TypeBinding::Unbound(_) => Type::default_int_or_field_type().into(), + TypeBinding::Unbound(_, Kind::Integer) => Type::default_int_type().into(), + TypeBinding::Unbound(_, Kind::IntegerOrField) => { + Type::default_int_or_field_type().into() } - } + TypeBinding::Unbound(_, Kind::Numeric(typ)) => (*typ.clone()).into(), + TypeBinding::Unbound(_, Kind::Any | Kind::Normal) => unreachable!(), + }, Type::Bool => PrintableType::Boolean, Type::String(size) => { let size = size.evaluate_to_u32().expect("Cannot print variable sized strings"); @@ -2337,7 +2431,6 @@ impl From<&Type> for PrintableType { Type::Alias(alias, args) => alias.borrow().get_type(args).into(), Type::TraitAsType(..) => unreachable!(), Type::Tuple(types) => PrintableType::Tuple { types: vecmap(types, |typ| typ.into()) }, - Type::TypeVariable(_, _) => unreachable!(), Type::NamedGeneric(..) => unreachable!(), Type::Forall(..) => unreachable!(), Type::Function(arguments, return_type, env, unconstrained) => PrintableType::Function { @@ -2371,12 +2464,18 @@ impl std::fmt::Debug for Type { Signedness::Signed => write!(f, "i{num_bits}"), Signedness::Unsigned => write!(f, "u{num_bits}"), }, - Type::TypeVariable(var, TypeVariableKind::Normal) => write!(f, "{:?}", var), - Type::TypeVariable(binding, TypeVariableKind::IntegerOrField) => { - write!(f, "IntOrField{:?}", binding) - } - Type::TypeVariable(binding, TypeVariableKind::Integer) => { - write!(f, "Int{:?}", binding) + Type::TypeVariable(var) => { + let binding = &var.1; + if let TypeBinding::Unbound(_, type_var_kind) = &*binding.borrow() { + match type_var_kind { + Kind::Any | Kind::Normal => write!(f, "{:?}", var), + Kind::IntegerOrField => write!(f, "IntOrField{:?}", binding), + Kind::Integer => write!(f, "Int{:?}", binding), + Kind::Numeric(typ) => write!(f, "Numeric({:?}: {:?})", binding, typ), + } + } else { + write!(f, "{}", binding.borrow()) + } } Type::Struct(s, args) => { let args = vecmap(args, |arg| format!("{:?}", arg)); @@ -2406,8 +2505,8 @@ impl std::fmt::Debug for Type { } Type::Unit => write!(f, "()"), Type::Error => write!(f, "error"), - Type::NamedGeneric(binding, name, kind) => match kind { - Kind::Normal => { + Type::NamedGeneric(binding, name) => match binding.kind() { + Kind::Any | Kind::Normal | Kind::Integer | Kind::IntegerOrField => { write!(f, "{}{:?}", name, binding) } Kind::Numeric(typ) => { @@ -2467,7 +2566,8 @@ impl std::fmt::Debug for StructType { impl std::hash::Hash for Type { fn hash(&self, state: &mut H) { - if let Some(variable) = self.get_inner_type_variable() { + if let Some((variable, kind)) = self.get_inner_type_variable() { + kind.hash(state); if let TypeBinding::Bound(typ) = &*variable.borrow() { typ.hash(state); return; @@ -2503,7 +2603,7 @@ impl std::hash::Hash for Type { alias.hash(state); args.hash(state); } - Type::TypeVariable(var, _) | Type::NamedGeneric(var, ..) => var.hash(state), + Type::TypeVariable(var) | Type::NamedGeneric(var, ..) => var.hash(state), Type::TraitAsType(trait_id, _, args) => { trait_id.hash(state); args.hash(state); @@ -2532,13 +2632,19 @@ impl std::hash::Hash for Type { impl PartialEq for Type { fn eq(&self, other: &Self) -> bool { - if let Some(variable) = self.get_inner_type_variable() { + if let Some((variable, kind)) = self.get_inner_type_variable() { + if kind != other.kind() { + return false; + } if let TypeBinding::Bound(typ) = &*variable.borrow() { return typ == other; } } - if let Some(variable) = other.get_inner_type_variable() { + if let Some((variable, other_kind)) = other.get_inner_type_variable() { + if self.kind() != other_kind { + return false; + } if let TypeBinding::Bound(typ) = &*variable.borrow() { return self == typ; } @@ -2592,8 +2698,8 @@ impl PartialEq for Type { // still want them to be equal for canonicalization checks in arithmetic generics. // Without this we'd fail the `serialize` test. ( - NamedGeneric(lhs_var, _, _) | TypeVariable(lhs_var, _), - NamedGeneric(rhs_var, _, _) | TypeVariable(rhs_var, _), + NamedGeneric(lhs_var, _) | TypeVariable(lhs_var), + NamedGeneric(rhs_var, _) | TypeVariable(rhs_var), ) => lhs_var.id() == rhs_var.id(), _ => false, } diff --git a/compiler/noirc_frontend/src/hir_def/types/arithmetic.rs b/compiler/noirc_frontend/src/hir_def/types/arithmetic.rs index 54b4c27a1f3..af94ef27535 100644 --- a/compiler/noirc_frontend/src/hir_def/types/arithmetic.rs +++ b/compiler/noirc_frontend/src/hir_def/types/arithmetic.rs @@ -20,8 +20,9 @@ impl Type { if let (Some(lhs_u32), Some(rhs_u32)) = (lhs.evaluate_to_u32(), rhs.evaluate_to_u32()) { - if let Some(result) = op.function(lhs_u32, rhs_u32) { - return Type::Constant(result, lhs.infix_kind(&rhs)); + let kind = lhs.infix_kind(&rhs); + if let Some(result) = op.function(lhs_u32, rhs_u32, &kind) { + return Type::Constant(result, kind); } } @@ -68,7 +69,7 @@ impl Type { queue.push(*rhs); } Type::Constant(new_constant, new_constant_kind) => { - if let Some(result) = op.function(constant, new_constant) { + if let Some(result) = op.function(constant, new_constant, &new_constant_kind) { constant = result; } else { let constant = Type::Constant(new_constant, new_constant_kind); @@ -205,7 +206,7 @@ impl Type { if l_op == Subtraction { op = op.inverse()?; } - let result = op.function(l_const, r_const)?; + let result = op.function(l_const, r_const, &lhs.infix_kind(rhs))?; let constant = Type::Constant(result, lhs.infix_kind(rhs)); Some(Type::InfixExpr(l_type, l_op, Box::new(constant))) } @@ -218,7 +219,7 @@ impl Type { if op == Division && (r_const == 0 || l_const % r_const != 0) { None } else { - let result = op.function(l_const, r_const)?; + let result = op.function(l_const, r_const, &lhs.infix_kind(rhs))?; let constant = Box::new(Type::Constant(result, lhs.infix_kind(rhs))); Some(Type::InfixExpr(l_type, l_op, constant)) } diff --git a/compiler/noirc_frontend/src/monomorphization/mod.rs b/compiler/noirc_frontend/src/monomorphization/mod.rs index fea15b26c5b..63ef807d898 100644 --- a/compiler/noirc_frontend/src/monomorphization/mod.rs +++ b/compiler/noirc_frontend/src/monomorphization/mod.rs @@ -21,7 +21,7 @@ use crate::{ types, }, node_interner::{self, DefinitionKind, NodeInterner, StmtId, TraitImplKind, TraitMethodId}, - Type, TypeBinding, TypeBindings, + Kind, Type, TypeBinding, TypeBindings, }; use acvm::{acir::AcirField, FieldElement}; use iter_extended::{btree_map, try_vecmap, vecmap}; @@ -857,7 +857,14 @@ impl<'interner> Monomorphizer<'interner> { // Ensure all instantiation bindings are bound. // This ensures even unused type variables like `fn foo() {}` have concrete types if let Some(bindings) = self.interner.try_get_instantiation_bindings(expr_id) { - for (_, binding) in bindings.values() { + for (_, kind, binding) in bindings.values() { + match kind { + Kind::Any => (), + Kind::Normal => (), + Kind::Integer => (), + Kind::IntegerOrField => (), + Kind::Numeric(typ) => Self::check_type(typ, ident.location)?, + } Self::check_type(binding, ident.location)?; } } @@ -916,20 +923,31 @@ impl<'interner> Monomorphizer<'interner> { ast::Expression::Ident(ident) } }, - DefinitionKind::NumericGeneric(type_variable) => { + DefinitionKind::NumericGeneric(type_variable, numeric_typ) => { let value = match &*type_variable.borrow() { - TypeBinding::Unbound(_) => { + TypeBinding::Unbound(_, _) => { unreachable!("Unbound type variable used in expression") } TypeBinding::Bound(binding) => binding.evaluate_to_u32().unwrap_or_else(|| { panic!("Non-numeric type variable used in expression expecting a value") }), }; - - let value = FieldElement::from(value as u128); let location = self.interner.id_location(expr_id); + + if !Kind::Numeric(numeric_typ.clone()) + .unifies(&Kind::Numeric(Box::new(typ.clone()))) + { + let message = "ICE: Generic's kind does not match expected type"; + return Err(MonomorphizationError::InternalError { location, message }); + } + let typ = Self::convert_type(&typ, ident.location)?; - ast::Expression::Literal(ast::Literal::Integer(value, false, typ, location)) + ast::Expression::Literal(ast::Literal::Integer( + (value as u128).into(), + false, + typ, + location, + )) } }; @@ -967,8 +985,8 @@ impl<'interner> Monomorphizer<'interner> { HirType::TraitAsType(..) => { unreachable!("All TraitAsType should be replaced before calling convert_type"); } - HirType::NamedGeneric(binding, _, _) => { - if let TypeBinding::Bound(binding) = &*binding.borrow() { + HirType::NamedGeneric(binding, _) => { + if let TypeBinding::Bound(ref binding) = &*binding.borrow() { return Self::convert_type(binding, location); } @@ -979,15 +997,18 @@ impl<'interner> Monomorphizer<'interner> { ast::Type::Field } - HirType::TypeVariable(binding, kind) => { - if let TypeBinding::Bound(binding) = &*binding.borrow() { - return Self::convert_type(binding, location); - } + HirType::TypeVariable(ref binding) => { + let type_var_kind = match &*binding.borrow() { + TypeBinding::Bound(ref binding) => { + return Self::convert_type(binding, location); + } + TypeBinding::Unbound(_, ref type_var_kind) => type_var_kind.clone(), + }; // Default any remaining unbound type variables. // This should only happen if the variable in question is unused // and within a larger generic type. - let default = match kind.default_type() { + let default = match type_var_kind.default_type() { Some(typ) => typ, None => return Err(MonomorphizationError::NoDefaultType { location }), }; @@ -1085,23 +1106,26 @@ impl<'interner> Monomorphizer<'interner> { HirType::FmtString(_size, fields) => Self::check_type(fields.as_ref(), location), HirType::Array(_length, element) => Self::check_type(element.as_ref(), location), HirType::Slice(element) => Self::check_type(element.as_ref(), location), - HirType::NamedGeneric(binding, _, _) => { - if let TypeBinding::Bound(binding) = &*binding.borrow() { + HirType::NamedGeneric(binding, _) => { + if let TypeBinding::Bound(ref binding) = &*binding.borrow() { return Self::check_type(binding, location); } Ok(()) } - HirType::TypeVariable(binding, kind) => { - if let TypeBinding::Bound(binding) = &*binding.borrow() { - return Self::check_type(binding, location); - } + HirType::TypeVariable(ref binding) => { + let type_var_kind = match &*binding.borrow() { + TypeBinding::Bound(binding) => { + return Self::check_type(binding, location); + } + TypeBinding::Unbound(_, ref type_var_kind) => type_var_kind.clone(), + }; // Default any remaining unbound type variables. // This should only happen if the variable in question is unused // and within a larger generic type. - let default = match kind.default_type() { + let default = match type_var_kind.default_type() { Some(typ) => typ, None => return Err(MonomorphizationError::NoDefaultType { location }), }; @@ -1441,9 +1465,9 @@ impl<'interner> Monomorphizer<'interner> { fn follow_bindings(&self, bindings: &TypeBindings) -> TypeBindings { bindings .iter() - .map(|(id, (var, binding))| { + .map(|(id, (var, kind, binding))| { let binding2 = binding.follow_bindings(); - (*id, (var.clone(), binding2)) + (*id, (var.clone(), kind.clone(), binding2)) }) .collect() } @@ -1910,14 +1934,14 @@ fn unwrap_struct_type( } pub fn perform_instantiation_bindings(bindings: &TypeBindings) { - for (var, binding) in bindings.values() { + for (var, _kind, binding) in bindings.values() { var.force_bind(binding.clone()); } } pub fn undo_instantiation_bindings(bindings: TypeBindings) { - for (id, (var, _)) in bindings { - var.unbind(id); + for (id, (var, kind, _)) in bindings { + var.unbind(id, kind); } } @@ -1944,7 +1968,7 @@ pub fn perform_impl_bindings( interner.function_meta(&impl_method).typ.unwrap_forall().1.clone(); // Make each NamedGeneric in this type bindable by replacing it with a TypeVariable - // with the same internal id and binding. + // with the same internal id, binding. trait_method_type.replace_named_generics_with_type_variables(); impl_method_type.replace_named_generics_with_type_variables(); diff --git a/compiler/noirc_frontend/src/node_interner.rs b/compiler/noirc_frontend/src/node_interner.rs index 295678c1412..6a7096c10c2 100644 --- a/compiler/noirc_frontend/src/node_interner.rs +++ b/compiler/noirc_frontend/src/node_interner.rs @@ -34,7 +34,7 @@ use crate::hir_def::expr::HirIdent; use crate::hir_def::stmt::HirLetStatement; use crate::hir_def::traits::TraitImpl; use crate::hir_def::traits::{Trait, TraitConstraint}; -use crate::hir_def::types::{StructType, Type}; +use crate::hir_def::types::{Kind, StructType, Type}; use crate::hir_def::{ expr::HirExpression, function::{FuncMeta, HirFunction}, @@ -44,7 +44,7 @@ use crate::locations::LocationIndices; use crate::token::{Attributes, SecondaryAttribute}; use crate::GenericTypeVars; use crate::Generics; -use crate::{Shared, TypeAlias, TypeBindings, TypeVariable, TypeVariableId, TypeVariableKind}; +use crate::{Shared, TypeAlias, TypeBindings, TypeVariable, TypeVariableId}; /// An arbitrary number to limit the recursion depth when searching for trait impls. /// This is needed to stop recursing for cases such as `impl Foo for T where T: Eq` @@ -581,7 +581,7 @@ pub enum DefinitionKind { /// Generic types in functions (T, U in `fn foo(...)` are declared as variables /// in scope in case they resolve to numeric generics later. - NumericGeneric(TypeVariable), + NumericGeneric(TypeVariable, Box), } impl DefinitionKind { @@ -596,7 +596,7 @@ impl DefinitionKind { DefinitionKind::Function(_) => None, DefinitionKind::Global(_) => None, DefinitionKind::Local(id) => *id, - DefinitionKind::NumericGeneric(_) => None, + DefinitionKind::NumericGeneric(_, _) => None, } } } @@ -728,7 +728,7 @@ impl NodeInterner { crate_id: unresolved_trait.crate_id, location: Location::new(unresolved_trait.trait_def.span, unresolved_trait.file_id), generics, - self_type_typevar: TypeVariable::unbound(self.next_type_variable_id()), + self_type_typevar: TypeVariable::unbound(self.next_type_variable_id(), Kind::Normal), methods: Vec::new(), method_ids: unresolved_trait.method_ids.clone(), associated_types, @@ -1331,6 +1331,10 @@ impl NodeInterner { Type::type_variable(self.next_type_variable_id()) } + pub fn next_type_variable_with_kind(&self, kind: Kind) -> Type { + Type::type_variable_with_kind(self, kind) + } + pub fn store_instantiation_bindings( &mut self, expr_id: ExprId, @@ -1732,7 +1736,16 @@ impl NodeInterner { // Replace each generic with a fresh type variable let substitutions = impl_generics .into_iter() - .map(|typevar| (typevar.id(), (typevar, self.next_type_variable()))) + .map(|typevar| { + let typevar_kind = typevar.kind(); + let typevar_id = typevar.id(); + let substitution = ( + typevar, + typevar_kind.clone(), + self.next_type_variable_with_kind(typevar_kind), + ); + (typevar_id, substitution) + }) .collect(); let instantiated_object_type = object_type.substitute(&substitutions); @@ -2223,11 +2236,17 @@ impl NodeInterner { let trait_generics = the_trait.generics.clone(); let self_type_var = the_trait.self_type_typevar.clone(); - bindings.insert(self_type_var.id(), (self_type_var, impl_self_type)); + bindings.insert( + self_type_var.id(), + (self_type_var.clone(), self_type_var.kind(), impl_self_type), + ); for (trait_generic, trait_impl_generic) in trait_generics.iter().zip(trait_impl_generics) { let type_var = trait_generic.type_var.clone(); - bindings.insert(type_var.id(), (type_var, trait_impl_generic.clone())); + bindings.insert( + type_var.id(), + (type_var, trait_generic.kind(), trait_impl_generic.clone()), + ); } // Now that the normal bindings are added, we still need to bind the associated types @@ -2236,7 +2255,10 @@ impl NodeInterner { for (trait_type, impl_type) in trait_associated_types.iter().zip(impl_associated_types) { let type_variable = trait_type.type_var.clone(); - bindings.insert(type_variable.id(), (type_variable, impl_type.typ.clone())); + bindings.insert( + type_variable.id(), + (type_variable, trait_type.kind(), impl_type.typ.clone()), + ); } bindings @@ -2357,22 +2379,26 @@ fn get_type_method_key(typ: &Type) -> Option { Type::Array(_, _) => Some(Array), Type::Slice(_) => Some(Slice), Type::Integer(_, _) => Some(FieldOrInt), - Type::TypeVariable(_, TypeVariableKind::IntegerOrField) => Some(FieldOrInt), - Type::TypeVariable(_, TypeVariableKind::Integer) => Some(FieldOrInt), + Type::TypeVariable(var) => { + if var.is_integer() || var.is_integer_or_field() { + Some(FieldOrInt) + } else { + None + } + } Type::Bool => Some(Bool), Type::String(_) => Some(String), Type::FmtString(_, _) => Some(FmtString), Type::Unit => Some(Unit), Type::Tuple(_) => Some(Tuple), Type::Function(_, _, _, _) => Some(Function), - Type::NamedGeneric(_, _, _) => Some(Generic), + Type::NamedGeneric(_, _) => Some(Generic), Type::Quoted(quoted) => Some(Quoted(*quoted)), Type::MutableReference(element) => get_type_method_key(element), Type::Alias(alias, _) => get_type_method_key(&alias.borrow().typ), // We do not support adding methods to these types - Type::TypeVariable(_, _) - | Type::Forall(_, _) + Type::Forall(_, _) | Type::Constant(..) | Type::Error | Type::Struct(_, _) diff --git a/compiler/noirc_frontend/src/tests.rs b/compiler/noirc_frontend/src/tests.rs index 63013036ca9..f7ef9955550 100644 --- a/compiler/noirc_frontend/src/tests.rs +++ b/compiler/noirc_frontend/src/tests.rs @@ -1586,9 +1586,7 @@ fn struct_numeric_generic_in_struct() { assert_eq!(errors.len(), 1); assert!(matches!( errors[0].0, - CompilationError::DefinitionError( - DefCollectorErrorKind::UnsupportedNumericGenericType { .. } - ), + CompilationError::ResolverError(ResolverError::UnsupportedNumericGenericType(_)), )); } @@ -1641,7 +1639,6 @@ fn bool_generic_as_loop_bound() { "#; let errors = get_program_errors(src); assert_eq!(errors.len(), 3); - assert!(matches!( errors[0].0, CompilationError::ResolverError(ResolverError::UnsupportedNumericGenericType { .. }), @@ -1706,7 +1703,7 @@ fn normal_generic_as_array_length() { #[test] fn numeric_generic_as_param_type() { let src = r#" - pub fn foo(x: I) -> I { + pub fn foo(x: I) -> I { let _q: I = 5; x } @@ -1731,6 +1728,68 @@ fn numeric_generic_as_param_type() { )); } +#[test] +fn numeric_generic_as_unused_param_type() { + let src = r#" + pub fn foo(_x: I) { } + "#; + let errors = get_program_errors(src); + assert_eq!(errors.len(), 1); + assert!(matches!( + errors[0].0, + CompilationError::TypeError(TypeCheckError::TypeKindMismatch { .. }), + )); +} + +#[test] +fn numeric_generic_as_unused_trait_fn_param_type() { + let src = r#" + trait Foo { + fn foo(_x: I) { } + } + "#; + let errors = get_program_errors(src); + assert_eq!(errors.len(), 2); + assert!(matches!( + errors[0].0, + CompilationError::TypeError(TypeCheckError::TypeKindMismatch { .. }), + )); + // Foo is unused + assert!(matches!( + errors[1].0, + CompilationError::ResolverError(ResolverError::UnusedItem { .. }), + )); +} + +#[test] +fn numeric_generic_as_return_type() { + let src = r#" + // std::mem::zeroed() without stdlib + trait Zeroed { + fn zeroed(self) -> T; + } + + fn foo(x: T) -> I where T: Zeroed { + x.zeroed() + } + + fn main() {} + "#; + let errors = get_program_errors(src); + assert_eq!(errors.len(), 2); + + // Error from the return type + assert!(matches!( + errors[0].0, + CompilationError::TypeError(TypeCheckError::TypeKindMismatch { .. }), + )); + // foo is unused + assert!(matches!( + errors[1].0, + CompilationError::ResolverError(ResolverError::UnusedItem { .. }), + )); +} + #[test] fn numeric_generic_used_in_nested_type_fails() { let src = r#" @@ -2385,23 +2444,6 @@ fn impl_not_found_for_inner_impl() { )); } -#[test] -fn no_super() { - let src = "use super::some_func;"; - let errors = get_program_errors(src); - assert_eq!(errors.len(), 1); - - let CompilationError::DefinitionError(DefCollectorErrorKind::PathResolutionError( - PathResolutionError::NoSuper(span), - )) = &errors[0].0 - else { - panic!("Expected a 'no super' error, got {:?}", errors[0].0); - }; - - assert_eq!(span.start(), 4); - assert_eq!(span.end(), 9); -} - #[test] fn cannot_call_unconstrained_function_outside_of_unsafe() { let src = r#" @@ -3035,6 +3077,35 @@ fn infer_globals_to_u32_from_type_use() { assert_eq!(errors.len(), 0); } +#[test] +fn struct_array_len() { + let src = r#" + struct Array { + inner: [T; N], + } + + impl Array { + pub fn len(self) -> u32 { + N as u32 + } + } + + fn main(xs: [Field; 2]) { + let ys = Array { + inner: xs, + }; + assert(ys.len() == 2); + } + "#; + + let errors = get_program_errors(src); + assert_eq!(errors.len(), 1); + assert!(matches!( + errors[0].0, + CompilationError::ResolverError(ResolverError::UnusedVariable { .. }) + )); +} + #[test] fn non_u32_in_array_length() { let src = r#" @@ -3084,7 +3155,8 @@ fn use_numeric_generic_in_trait_method() { } fn main() { - let _ = Bar{}.foo([1,2,3]); + let bytes: [u8; 3] = [1,2,3]; + let _ = Bar{}.foo(bytes); } "#; diff --git a/compiler/noirc_frontend/src/tests/imports.rs b/compiler/noirc_frontend/src/tests/imports.rs index dfdc60e15e4..5ebc5b3bdbd 100644 --- a/compiler/noirc_frontend/src/tests/imports.rs +++ b/compiler/noirc_frontend/src/tests/imports.rs @@ -21,6 +21,23 @@ fn use_super() { assert_no_errors(src); } +#[test] +fn no_super() { + let src = "use super::some_func;"; + let errors = get_program_errors(src); + assert_eq!(errors.len(), 1); + + let CompilationError::DefinitionError(DefCollectorErrorKind::PathResolutionError( + PathResolutionError::NoSuper(span), + )) = &errors[0].0 + else { + panic!("Expected a 'no super' error, got {:?}", errors[0].0); + }; + + assert_eq!(span.start(), 4); + assert_eq!(span.end(), 9); +} + #[test] fn use_super_in_path() { let src = r#" diff --git a/compiler/noirc_frontend/src/tests/metaprogramming.rs b/compiler/noirc_frontend/src/tests/metaprogramming.rs index d980cba5cfd..ec52310b3d6 100644 --- a/compiler/noirc_frontend/src/tests/metaprogramming.rs +++ b/compiler/noirc_frontend/src/tests/metaprogramming.rs @@ -2,6 +2,17 @@ use crate::hir::def_collector::dc_crate::CompilationError; use super::get_program_errors; +// Regression for #5388 +#[test] +fn comptime_let() { + let src = r#"fn main() { + comptime let my_var = 2; + assert_eq(my_var, 2); + }"#; + let errors = get_program_errors(src); + assert_eq!(errors.len(), 0); +} + #[test] fn comptime_type_in_runtime_code() { let source = "pub fn foo(_f: FunctionDefinition) {}"; diff --git a/tooling/lsp/src/requests/completion.rs b/tooling/lsp/src/requests/completion.rs index 2db1665b639..2882cb143bf 100644 --- a/tooling/lsp/src/requests/completion.rs +++ b/tooling/lsp/src/requests/completion.rs @@ -581,8 +581,8 @@ impl<'a> NodeFinder<'a> { Type::Tuple(types) => { self.complete_tuple_fields(types, self_prefix); } - Type::TypeVariable(var, _) | Type::NamedGeneric(var, _, _) => { - if let TypeBinding::Bound(typ) = &*var.borrow() { + Type::TypeVariable(var) | Type::NamedGeneric(var, _) => { + if let TypeBinding::Bound(ref typ) = &*var.borrow() { return self.complete_type_fields_and_methods( typ, prefix, @@ -1662,8 +1662,8 @@ fn get_field_type(typ: &Type, name: &str) -> Option { } } Type::Alias(alias_type, generics) => Some(alias_type.borrow().get_type(generics)), - Type::TypeVariable(var, _) | Type::NamedGeneric(var, _, _) => { - if let TypeBinding::Bound(typ) = &*var.borrow() { + Type::TypeVariable(var) | Type::NamedGeneric(var, _) => { + if let TypeBinding::Bound(ref typ) = &*var.borrow() { get_field_type(typ, name) } else { None @@ -1680,7 +1680,7 @@ fn get_array_element_type(typ: Type) -> Option { let typ = alias_type.borrow().get_type(&generics); get_array_element_type(typ) } - Type::TypeVariable(var, _) | Type::NamedGeneric(var, _, _) => { + Type::TypeVariable(var) | Type::NamedGeneric(var, _) => { if let TypeBinding::Bound(typ) = &*var.borrow() { get_array_element_type(typ.clone()) } else { diff --git a/tooling/lsp/src/requests/hover.rs b/tooling/lsp/src/requests/hover.rs index 25a401f488e..7b1fa7352a6 100644 --- a/tooling/lsp/src/requests/hover.rs +++ b/tooling/lsp/src/requests/hover.rs @@ -530,7 +530,7 @@ impl<'a> TypeLinksGatherer<'a> { self.gather_type_links(generic); } } - Type::TypeVariable(var, _) => { + Type::TypeVariable(var) => { self.gather_type_variable_links(var); } Type::TraitAsType(trait_id, _, generics) => { @@ -543,7 +543,7 @@ impl<'a> TypeLinksGatherer<'a> { self.gather_type_links(&named_type.typ); } } - Type::NamedGeneric(var, _, _) => { + Type::NamedGeneric(var, _) => { self.gather_type_variable_links(var); } Type::Function(args, return_type, env, _) => { diff --git a/tooling/lsp/src/requests/inlay_hint.rs b/tooling/lsp/src/requests/inlay_hint.rs index ea73cc688ef..e119ee0d5b6 100644 --- a/tooling/lsp/src/requests/inlay_hint.rs +++ b/tooling/lsp/src/requests/inlay_hint.rs @@ -17,7 +17,7 @@ use noirc_frontend::{ hir_def::stmt::HirPattern, node_interner::{NodeInterner, ReferenceId}, parser::{Item, ParsedSubModule}, - Type, TypeBinding, TypeVariable, TypeVariableKind, + Kind, Type, TypeBinding, TypeVariable, }; use crate::{utils, LspState}; @@ -459,19 +459,14 @@ fn push_type_parts(typ: &Type, parts: &mut Vec, files: &File parts.push(string_part("&mut ")); push_type_parts(typ, parts, files); } - Type::TypeVariable(var, TypeVariableKind::Normal) => { - push_type_variable_parts(var, parts, files); - } - Type::TypeVariable(binding, TypeVariableKind::Integer) => { - if let TypeBinding::Unbound(_) = &*binding.borrow() { - push_type_parts(&Type::default_int_type(), parts, files); - } else { - push_type_variable_parts(binding, parts, files); - } - } - Type::TypeVariable(binding, TypeVariableKind::IntegerOrField) => { - if let TypeBinding::Unbound(_) = &*binding.borrow() { - parts.push(string_part("Field")); + Type::TypeVariable(binding) => { + if let TypeBinding::Unbound(_, kind) = &*binding.borrow() { + match kind { + Kind::Any | Kind::Normal => push_type_variable_parts(binding, parts, files), + Kind::Integer => push_type_parts(&Type::default_int_type(), parts, files), + Kind::IntegerOrField => parts.push(string_part("Field")), + Kind::Numeric(ref typ) => push_type_parts(typ, parts, files), + } } else { push_type_variable_parts(binding, parts, files); } diff --git a/tooling/lsp/src/trait_impl_method_stub_generator.rs b/tooling/lsp/src/trait_impl_method_stub_generator.rs index 4fe039e7bd7..14b40858bb1 100644 --- a/tooling/lsp/src/trait_impl_method_stub_generator.rs +++ b/tooling/lsp/src/trait_impl_method_stub_generator.rs @@ -10,7 +10,7 @@ use noirc_frontend::{ }, hir_def::{function::FuncMeta, stmt::HirPattern, traits::Trait}, node_interner::{FunctionModifiers, NodeInterner, ReferenceId}, - Kind, ResolvedGeneric, Type, TypeVariableKind, + Kind, ResolvedGeneric, Type, }; use crate::modules::relative_module_id_path; @@ -290,7 +290,7 @@ impl<'a> TraitImplMethodStubGenerator<'a> { self.string.push_str(&trait_.name.0.contents); self.append_trait_generics(trait_generics); } - Type::TypeVariable(typevar, _) => { + Type::TypeVariable(typevar) => { if typevar.id() == self.trait_.self_type_typevar.id() { self.string.push_str("Self"); return; @@ -323,8 +323,8 @@ impl<'a> TraitImplMethodStubGenerator<'a> { self.string.push_str("error"); } - Type::NamedGeneric(typevar, _name, _kind) => { - self.append_type(&Type::TypeVariable(typevar.clone(), TypeVariableKind::Normal)); + Type::NamedGeneric(typevar, _name) => { + self.append_type(&Type::TypeVariable(typevar.clone())); } Type::Function(args, ret, env, unconstrained) => { if *unconstrained { @@ -437,9 +437,11 @@ impl<'a> TraitImplMethodStubGenerator<'a> { } fn append_resolved_generic(&mut self, generic: &ResolvedGeneric) { - match &generic.kind { - Kind::Normal => self.string.push_str(&generic.name), - Kind::Numeric(typ) => { + match &generic.kind() { + Kind::Any | Kind::Normal | Kind::Integer | Kind::IntegerOrField => { + self.string.push_str(&generic.name); + } + Kind::Numeric(ref typ) => { self.string.push_str("let "); self.string.push_str(&generic.name); self.string.push_str(": "); From 2303615815a2a60de8ac3dd53349f85201660917 Mon Sep 17 00:00:00 2001 From: Tom French <15848336+TomAFrench@users.noreply.github.com> Date: Wed, 2 Oct 2024 10:30:06 +0100 Subject: [PATCH 29/36] feat: skip `remove_enable_side_effects` pass on brillig functions (#6199) # Description ## Problem\* Resolves ## Summary\* PR described in comment in code. ## Additional Context ## Documentation\* Check one: - [x] No documentation needed. - [ ] Documentation included in this PR. - [ ] **[For Experimental Features]** Documentation to be submitted in a separate PR. # PR Checklist\* - [x] I have tested the changes locally. - [x] I have formatted the changes with [Prettier](https://prettier.io/) and/or `cargo fmt` on default settings. --- .../src/ssa/opt/remove_enable_side_effects.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/compiler/noirc_evaluator/src/ssa/opt/remove_enable_side_effects.rs b/compiler/noirc_evaluator/src/ssa/opt/remove_enable_side_effects.rs index c611ab682bf..daae2cb08ce 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/remove_enable_side_effects.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/remove_enable_side_effects.rs @@ -16,7 +16,7 @@ use crate::ssa::{ ir::{ basic_block::BasicBlockId, dfg::DataFlowGraph, - function::Function, + function::{Function, RuntimeType}, instruction::{BinaryOp, Instruction, Intrinsic}, types::Type, value::Value, @@ -37,6 +37,11 @@ impl Ssa { impl Function { pub(crate) fn remove_enable_side_effects(&mut self) { + if matches!(self.runtime(), RuntimeType::Brillig) { + // Brillig functions do not make use of the `EnableSideEffects` instruction so are unaffected by this pass. + return; + } + let mut context = Context::default(); context.block_queue.push(self.entry_block()); From 054e48b76e7b083feb500d30c54912f9db57c565 Mon Sep 17 00:00:00 2001 From: josh crites Date: Wed, 2 Oct 2024 10:43:07 -0400 Subject: [PATCH 30/36] fix(docs): Rename recursion.md to recursion.mdx (#6195) # Description Update the recursion page to mdx file extension, so the Blackbox Function component is correctly rendered. Also updates the black box fns component to take a path, since the component on different pages wasnt being linked to correctly. ## Problem\* Resolves ## Summary\* ## Additional Context ## Documentation\* Check one: - [ ] No documentation needed. - [ ] Documentation included in this PR. - [ ] **[For Experimental Features]** 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. --- docs/docs/explainers/explainer-recursion.md | 2 +- docs/docs/how_to/how-to-recursion.md | 2 +- docs/docs/noir/standard_library/black_box_fns.md | 2 +- .../cryptographic_primitives/ciphers.mdx | 4 ++-- .../ecdsa_sig_verification.mdx | 10 +++++----- .../cryptographic_primitives/eddsa.mdx | 4 ++-- .../embedded_curve_ops.mdx | 4 ++-- .../cryptographic_primitives/hashes.mdx | 14 +++++++------- .../cryptographic_primitives/schnorr.mdx | 6 +++--- .../{recursion.md => recursion.mdx} | 4 ++-- docs/src/components/Notes/_blackbox.jsx | 12 ++++++++++++ docs/src/components/Notes/_blackbox.mdx | 5 ----- .../explainers/explainer-recursion.md | 2 +- .../version-v0.32.0/how_to/how-to-recursion.md | 2 +- .../noir/standard_library/black_box_fns.md | 2 +- .../cryptographic_primitives/ciphers.mdx | 4 ++-- .../ecdsa_sig_verification.mdx | 10 +++++----- .../cryptographic_primitives/eddsa.mdx | 4 ++-- .../embedded_curve_ops.mdx | 4 ++-- .../cryptographic_primitives/hashes.mdx | 14 +++++++------- .../cryptographic_primitives/schnorr.mdx | 6 +++--- .../{recursion.md => recursion.mdx} | 4 ++-- .../explainers/explainer-recursion.md | 2 +- .../version-v0.33.0/how_to/how-to-recursion.md | 2 +- .../noir/standard_library/black_box_fns.md | 2 +- .../cryptographic_primitives/ciphers.mdx | 4 ++-- .../ecdsa_sig_verification.mdx | 10 +++++----- .../cryptographic_primitives/eddsa.mdx | 4 ++-- .../embedded_curve_ops.mdx | 4 ++-- .../cryptographic_primitives/hashes.mdx | 14 +++++++------- .../cryptographic_primitives/schnorr.mdx | 6 +++--- .../noir/standard_library/recursion.mdx} | 4 ++-- .../explainers/explainer-recursion.md | 2 +- .../version-v0.34.0/how_to/how-to-recursion.md | 2 +- .../noir/standard_library/black_box_fns.md | 2 +- .../cryptographic_primitives/ciphers.mdx | 4 ++-- .../ecdsa_sig_verification.mdx | 10 +++++----- .../cryptographic_primitives/eddsa.mdx | 4 ++-- .../embedded_curve_ops.mdx | 4 ++-- .../cryptographic_primitives/hashes.mdx | 14 +++++++------- .../cryptographic_primitives/schnorr.mdx | 6 +++--- .../noir/standard_library/recursion.mdx} | 4 ++-- 42 files changed, 116 insertions(+), 109 deletions(-) rename docs/docs/noir/standard_library/{recursion.md => recursion.mdx} (96%) create mode 100644 docs/src/components/Notes/_blackbox.jsx delete mode 100644 docs/src/components/Notes/_blackbox.mdx rename docs/versioned_docs/version-v0.32.0/noir/standard_library/{recursion.md => recursion.mdx} (96%) rename docs/versioned_docs/{version-v0.34.0/noir/standard_library/recursion.md => version-v0.33.0/noir/standard_library/recursion.mdx} (96%) rename docs/versioned_docs/{version-v0.33.0/noir/standard_library/recursion.md => version-v0.34.0/noir/standard_library/recursion.mdx} (96%) diff --git a/docs/docs/explainers/explainer-recursion.md b/docs/docs/explainers/explainer-recursion.md index 18846176ca7..df8529ef4e0 100644 --- a/docs/docs/explainers/explainer-recursion.md +++ b/docs/docs/explainers/explainer-recursion.md @@ -111,7 +111,7 @@ He might find it more efficient to generate a proof for that setup phase separat ## What params do I need -As you can see in the [recursion reference](noir/standard_library/recursion.md), a simple recursive proof requires: +As you can see in the [recursion reference](noir/standard_library/recursion.mdx), a simple recursive proof requires: - The proof to verify - The Verification Key of the circuit that generated the proof diff --git a/docs/docs/how_to/how-to-recursion.md b/docs/docs/how_to/how-to-recursion.md index 71f02fa5435..c8c4dc9f5b4 100644 --- a/docs/docs/how_to/how-to-recursion.md +++ b/docs/docs/how_to/how-to-recursion.md @@ -25,7 +25,7 @@ This guide shows you how to use recursive proofs in your NoirJS app. For the sak - You already have a NoirJS app. If you don't, please visit the [NoirJS tutorial](../tutorials/noirjs_app.md) and the [reference](../reference/NoirJS/noir_js/index.md). - You are familiar with what are recursive proofs and you have read the [recursion explainer](../explainers/explainer-recursion.md) -- You already built a recursive circuit following [the reference](../noir/standard_library/recursion.md), and understand how it works. +- You already built a recursive circuit following [the reference](../noir/standard_library/recursion.mdx), and understand how it works. It is also assumed that you're not using `noir_wasm` for compilation, and instead you've used [`nargo compile`](../reference/nargo_commands.md) to generate the `json` you're now importing into your project. However, the guide should work just the same if you're using `noir_wasm`. diff --git a/docs/docs/noir/standard_library/black_box_fns.md b/docs/docs/noir/standard_library/black_box_fns.md index d5694250f05..d6079ab182c 100644 --- a/docs/docs/noir/standard_library/black_box_fns.md +++ b/docs/docs/noir/standard_library/black_box_fns.md @@ -25,7 +25,7 @@ Here is a list of the current black box functions: - XOR - RANGE - [Keccak256](./cryptographic_primitives/hashes.mdx#keccak256) -- [Recursive proof verification](./recursion.md) +- [Recursive proof verification](./recursion.mdx) Most black box functions are included as part of the Noir standard library, however `AND`, `XOR` and `RANGE` are used as part of the Noir language syntax. For instance, using the bitwise operator `&` will invoke the `AND` black box function. diff --git a/docs/docs/noir/standard_library/cryptographic_primitives/ciphers.mdx b/docs/docs/noir/standard_library/cryptographic_primitives/ciphers.mdx index 0103791d2e4..d2ceb63175a 100644 --- a/docs/docs/noir/standard_library/cryptographic_primitives/ciphers.mdx +++ b/docs/docs/noir/standard_library/cryptographic_primitives/ciphers.mdx @@ -7,7 +7,7 @@ keywords: sidebar_position: 0 --- -import BlackBoxInfo from '@site/src/components/Notes/_blackbox.mdx'; +import BlackBoxInfo from '@site/src/components/Notes/_blackbox'; ## aes128 @@ -25,4 +25,4 @@ fn main() { ``` - \ No newline at end of file + \ No newline at end of file diff --git a/docs/docs/noir/standard_library/cryptographic_primitives/ecdsa_sig_verification.mdx b/docs/docs/noir/standard_library/cryptographic_primitives/ecdsa_sig_verification.mdx index 6787c9f46a1..d46bdc0729b 100644 --- a/docs/docs/noir/standard_library/cryptographic_primitives/ecdsa_sig_verification.mdx +++ b/docs/docs/noir/standard_library/cryptographic_primitives/ecdsa_sig_verification.mdx @@ -5,7 +5,7 @@ keywords: [cryptographic primitives, Noir project, ecdsa, secp256k1, secp256r1, sidebar_position: 3 --- -import BlackBoxInfo from '@site/src/components/Notes/_blackbox.mdx'; +import BlackBoxInfo from '@site/src/components/Notes/_blackbox'; Noir supports ECDSA signatures verification over the secp256k1 and secp256r1 curves. @@ -25,7 +25,7 @@ fn main(hashed_message : [u8;32], pub_key_x : [u8;32], pub_key_y : [u8;32], sign } ``` - + ## ecdsa_secp256k1::verify_signature_slice @@ -33,7 +33,7 @@ Verifier for ECDSA Secp256k1 signatures where the message is a slice. #include_code ecdsa_secp256k1_slice noir_stdlib/src/ecdsa_secp256k1.nr rust - + ## ecdsa_secp256r1::verify_signature @@ -51,7 +51,7 @@ fn main(hashed_message : [u8;32], pub_key_x : [u8;32], pub_key_y : [u8;32], sign } ``` - + ## ecdsa_secp256r1::verify_signature @@ -59,4 +59,4 @@ Verifier for ECDSA Secp256r1 signatures where the message is a slice. #include_code ecdsa_secp256r1_slice noir_stdlib/src/ecdsa_secp256r1.nr rust - + diff --git a/docs/docs/noir/standard_library/cryptographic_primitives/eddsa.mdx b/docs/docs/noir/standard_library/cryptographic_primitives/eddsa.mdx index 1ad42a5ac96..b283de693c8 100644 --- a/docs/docs/noir/standard_library/cryptographic_primitives/eddsa.mdx +++ b/docs/docs/noir/standard_library/cryptographic_primitives/eddsa.mdx @@ -5,7 +5,7 @@ keywords: [cryptographic primitives, Noir project, eddsa, signatures] sidebar_position: 5 --- -import BlackBoxInfo from '@site/src/components/Notes/_blackbox.mdx'; +import BlackBoxInfo from '@site/src/components/Notes/_blackbox'; ## eddsa::eddsa_poseidon_verify @@ -23,7 +23,7 @@ use std::hash::poseidon2::Poseidon2Hasher; eddsa_verify::(pub_key_a.x, pub_key_a.y, s_a, r8_a.x, r8_a.y, msg); ``` - + ## eddsa::eddsa_to_pub diff --git a/docs/docs/noir/standard_library/cryptographic_primitives/embedded_curve_ops.mdx b/docs/docs/noir/standard_library/cryptographic_primitives/embedded_curve_ops.mdx index f1122fc37d5..e10688857a6 100644 --- a/docs/docs/noir/standard_library/cryptographic_primitives/embedded_curve_ops.mdx +++ b/docs/docs/noir/standard_library/cryptographic_primitives/embedded_curve_ops.mdx @@ -5,7 +5,7 @@ keywords: [cryptographic primitives, Noir project, scalar multiplication] sidebar_position: 1 --- -import BlackBoxInfo from '@site/src/components/Notes/_blackbox.mdx'; +import BlackBoxInfo from '@site/src/components/Notes/_blackbox'; The following functions perform operations over the embedded curve whose coordinates are defined by the configured noir field. For the BN254 scalar field, this is BabyJubJub or Grumpkin. @@ -74,4 +74,4 @@ fn main() { } ``` - + diff --git a/docs/docs/noir/standard_library/cryptographic_primitives/hashes.mdx b/docs/docs/noir/standard_library/cryptographic_primitives/hashes.mdx index d2a8204bccb..c33ce34e4d1 100644 --- a/docs/docs/noir/standard_library/cryptographic_primitives/hashes.mdx +++ b/docs/docs/noir/standard_library/cryptographic_primitives/hashes.mdx @@ -8,7 +8,7 @@ keywords: sidebar_position: 0 --- -import BlackBoxInfo from '@site/src/components/Notes/_blackbox.mdx'; +import BlackBoxInfo from '@site/src/components/Notes/_blackbox'; ## sha256 @@ -28,7 +28,7 @@ fn main() { ``` - + ## blake2s @@ -45,7 +45,7 @@ fn main() { } ``` - + ## blake3 @@ -62,7 +62,7 @@ fn main() { } ``` - + ## pedersen_hash @@ -74,7 +74,7 @@ example: #include_code pedersen-hash test_programs/execution_success/pedersen_hash/src/main.nr rust - + ## pedersen_commitment @@ -86,7 +86,7 @@ example: #include_code pedersen-commitment test_programs/execution_success/pedersen_commitment/src/main.nr rust - + ## keccak256 @@ -100,7 +100,7 @@ example: #include_code keccak256 test_programs/execution_success/keccak256/src/main.nr rust - + ## poseidon diff --git a/docs/docs/noir/standard_library/cryptographic_primitives/schnorr.mdx b/docs/docs/noir/standard_library/cryptographic_primitives/schnorr.mdx index 2c9eb18cd34..286a0ac6c7d 100644 --- a/docs/docs/noir/standard_library/cryptographic_primitives/schnorr.mdx +++ b/docs/docs/noir/standard_library/cryptographic_primitives/schnorr.mdx @@ -5,7 +5,7 @@ keywords: [cryptographic primitives, Noir project, schnorr, signatures] sidebar_position: 2 --- -import BlackBoxInfo from '@site/src/components/Notes/_blackbox.mdx'; +import BlackBoxInfo from '@site/src/components/Notes/_blackbox'; ## schnorr::verify_signature @@ -34,7 +34,7 @@ const signature = Array.from( ... ``` - + ## schnorr::verify_signature_slice @@ -43,4 +43,4 @@ where the message is a slice. #include_code schnorr_verify_slice noir_stdlib/src/schnorr.nr rust - + diff --git a/docs/docs/noir/standard_library/recursion.md b/docs/docs/noir/standard_library/recursion.mdx similarity index 96% rename from docs/docs/noir/standard_library/recursion.md rename to docs/docs/noir/standard_library/recursion.mdx index 7f4dcebf084..60414a2fa51 100644 --- a/docs/docs/noir/standard_library/recursion.md +++ b/docs/docs/noir/standard_library/recursion.mdx @@ -4,7 +4,7 @@ description: Learn about how to write recursive proofs in Noir. keywords: [recursion, recursive proofs, verification_key, verify_proof] --- -import BlackBoxInfo from '@site/src/components/Notes/_blackbox.mdx'; +import BlackBoxInfo from '@site/src/components/Notes/_blackbox'; Noir supports recursively verifying proofs, meaning you verify the proof of a Noir program in another Noir program. This enables creating proofs of arbitrary size by doing step-wise verification of smaller components of a large proof. @@ -35,7 +35,7 @@ By incorporating this attribute directly in the circuit's definition, tooling li pub fn verify_proof(verification_key: [Field], proof: [Field], public_inputs: [Field], key_hash: Field) {} ``` - + ## Example usage diff --git a/docs/src/components/Notes/_blackbox.jsx b/docs/src/components/Notes/_blackbox.jsx new file mode 100644 index 00000000000..ae3c5987cb6 --- /dev/null +++ b/docs/src/components/Notes/_blackbox.jsx @@ -0,0 +1,12 @@ +import Link from '@docusaurus/Link'; + +export default function BlackBoxInfo({ to }) { + return ( +
+

+ This is a black box function. Read this section to learn more about black box functions in + Noir. +

+
+ ); +} diff --git a/docs/src/components/Notes/_blackbox.mdx b/docs/src/components/Notes/_blackbox.mdx deleted file mode 100644 index 514ca00a7e7..00000000000 --- a/docs/src/components/Notes/_blackbox.mdx +++ /dev/null @@ -1,5 +0,0 @@ -:::info - -This is a black box function. Read [this section](/docs/noir/standard_library/black_box_fns) to learn more about black box functions in Noir. - -::: diff --git a/docs/versioned_docs/version-v0.32.0/explainers/explainer-recursion.md b/docs/versioned_docs/version-v0.32.0/explainers/explainer-recursion.md index 18846176ca7..df8529ef4e0 100644 --- a/docs/versioned_docs/version-v0.32.0/explainers/explainer-recursion.md +++ b/docs/versioned_docs/version-v0.32.0/explainers/explainer-recursion.md @@ -111,7 +111,7 @@ He might find it more efficient to generate a proof for that setup phase separat ## What params do I need -As you can see in the [recursion reference](noir/standard_library/recursion.md), a simple recursive proof requires: +As you can see in the [recursion reference](noir/standard_library/recursion.mdx), a simple recursive proof requires: - The proof to verify - The Verification Key of the circuit that generated the proof diff --git a/docs/versioned_docs/version-v0.32.0/how_to/how-to-recursion.md b/docs/versioned_docs/version-v0.32.0/how_to/how-to-recursion.md index 71f02fa5435..c8c4dc9f5b4 100644 --- a/docs/versioned_docs/version-v0.32.0/how_to/how-to-recursion.md +++ b/docs/versioned_docs/version-v0.32.0/how_to/how-to-recursion.md @@ -25,7 +25,7 @@ This guide shows you how to use recursive proofs in your NoirJS app. For the sak - You already have a NoirJS app. If you don't, please visit the [NoirJS tutorial](../tutorials/noirjs_app.md) and the [reference](../reference/NoirJS/noir_js/index.md). - You are familiar with what are recursive proofs and you have read the [recursion explainer](../explainers/explainer-recursion.md) -- You already built a recursive circuit following [the reference](../noir/standard_library/recursion.md), and understand how it works. +- You already built a recursive circuit following [the reference](../noir/standard_library/recursion.mdx), and understand how it works. It is also assumed that you're not using `noir_wasm` for compilation, and instead you've used [`nargo compile`](../reference/nargo_commands.md) to generate the `json` you're now importing into your project. However, the guide should work just the same if you're using `noir_wasm`. diff --git a/docs/versioned_docs/version-v0.32.0/noir/standard_library/black_box_fns.md b/docs/versioned_docs/version-v0.32.0/noir/standard_library/black_box_fns.md index d5694250f05..d6079ab182c 100644 --- a/docs/versioned_docs/version-v0.32.0/noir/standard_library/black_box_fns.md +++ b/docs/versioned_docs/version-v0.32.0/noir/standard_library/black_box_fns.md @@ -25,7 +25,7 @@ Here is a list of the current black box functions: - XOR - RANGE - [Keccak256](./cryptographic_primitives/hashes.mdx#keccak256) -- [Recursive proof verification](./recursion.md) +- [Recursive proof verification](./recursion.mdx) Most black box functions are included as part of the Noir standard library, however `AND`, `XOR` and `RANGE` are used as part of the Noir language syntax. For instance, using the bitwise operator `&` will invoke the `AND` black box function. diff --git a/docs/versioned_docs/version-v0.32.0/noir/standard_library/cryptographic_primitives/ciphers.mdx b/docs/versioned_docs/version-v0.32.0/noir/standard_library/cryptographic_primitives/ciphers.mdx index d75e50d4b89..d6a5e1a79eb 100644 --- a/docs/versioned_docs/version-v0.32.0/noir/standard_library/cryptographic_primitives/ciphers.mdx +++ b/docs/versioned_docs/version-v0.32.0/noir/standard_library/cryptographic_primitives/ciphers.mdx @@ -7,7 +7,7 @@ keywords: sidebar_position: 0 --- -import BlackBoxInfo from '@site/src/components/Notes/_blackbox.mdx'; +import BlackBoxInfo from '@site/src/components/Notes/_blackbox'; ## aes128 @@ -29,4 +29,4 @@ fn main() { ``` - \ No newline at end of file + \ No newline at end of file diff --git a/docs/versioned_docs/version-v0.32.0/noir/standard_library/cryptographic_primitives/ecdsa_sig_verification.mdx b/docs/versioned_docs/version-v0.32.0/noir/standard_library/cryptographic_primitives/ecdsa_sig_verification.mdx index 8520071e95f..4c22e70e8de 100644 --- a/docs/versioned_docs/version-v0.32.0/noir/standard_library/cryptographic_primitives/ecdsa_sig_verification.mdx +++ b/docs/versioned_docs/version-v0.32.0/noir/standard_library/cryptographic_primitives/ecdsa_sig_verification.mdx @@ -5,7 +5,7 @@ keywords: [cryptographic primitives, Noir project, ecdsa, secp256k1, secp256r1, sidebar_position: 3 --- -import BlackBoxInfo from '@site/src/components/Notes/_blackbox.mdx'; +import BlackBoxInfo from '@site/src/components/Notes/_blackbox'; Noir supports ECDSA signatures verification over the secp256k1 and secp256r1 curves. @@ -34,7 +34,7 @@ fn main(hashed_message : [u8;32], pub_key_x : [u8;32], pub_key_y : [u8;32], sign } ``` - + ## ecdsa_secp256k1::verify_signature_slice @@ -51,7 +51,7 @@ pub fn verify_signature_slice( >
Source code: noir_stdlib/src/ecdsa_secp256k1.nr#L13-L20 - + ## ecdsa_secp256r1::verify_signature @@ -78,7 +78,7 @@ fn main(hashed_message : [u8;32], pub_key_x : [u8;32], pub_key_y : [u8;32], sign } ``` - + ## ecdsa_secp256r1::verify_signature @@ -95,4 +95,4 @@ pub fn verify_signature_slice( > Source code: noir_stdlib/src/ecdsa_secp256r1.nr#L13-L20 - + diff --git a/docs/versioned_docs/version-v0.32.0/noir/standard_library/cryptographic_primitives/eddsa.mdx b/docs/versioned_docs/version-v0.32.0/noir/standard_library/cryptographic_primitives/eddsa.mdx index 1ad42a5ac96..ef4386052eb 100644 --- a/docs/versioned_docs/version-v0.32.0/noir/standard_library/cryptographic_primitives/eddsa.mdx +++ b/docs/versioned_docs/version-v0.32.0/noir/standard_library/cryptographic_primitives/eddsa.mdx @@ -5,7 +5,7 @@ keywords: [cryptographic primitives, Noir project, eddsa, signatures] sidebar_position: 5 --- -import BlackBoxInfo from '@site/src/components/Notes/_blackbox.mdx'; +import BlackBoxInfo from '@site/src/components/Notes/_blackbox'; ## eddsa::eddsa_poseidon_verify @@ -23,7 +23,7 @@ use std::hash::poseidon2::Poseidon2Hasher; eddsa_verify::(pub_key_a.x, pub_key_a.y, s_a, r8_a.x, r8_a.y, msg); ``` - + ## eddsa::eddsa_to_pub diff --git a/docs/versioned_docs/version-v0.32.0/noir/standard_library/cryptographic_primitives/embedded_curve_ops.mdx b/docs/versioned_docs/version-v0.32.0/noir/standard_library/cryptographic_primitives/embedded_curve_ops.mdx index 0230f6a8ab9..68d033e9d60 100644 --- a/docs/versioned_docs/version-v0.32.0/noir/standard_library/cryptographic_primitives/embedded_curve_ops.mdx +++ b/docs/versioned_docs/version-v0.32.0/noir/standard_library/cryptographic_primitives/embedded_curve_ops.mdx @@ -5,7 +5,7 @@ keywords: [cryptographic primitives, Noir project, scalar multiplication] sidebar_position: 1 --- -import BlackBoxInfo from '@site/src/components/Notes/_blackbox.mdx'; +import BlackBoxInfo from '@site/src/components/Notes/_blackbox'; The following functions perform operations over the embedded curve whose coordinates are defined by the configured noir field. For the BN254 scalar field, this is BabyJubJub or Grumpkin. @@ -95,4 +95,4 @@ fn main() { } ``` - + diff --git a/docs/versioned_docs/version-v0.32.0/noir/standard_library/cryptographic_primitives/hashes.mdx b/docs/versioned_docs/version-v0.32.0/noir/standard_library/cryptographic_primitives/hashes.mdx index dadff87bb69..ddcfbb2175f 100644 --- a/docs/versioned_docs/version-v0.32.0/noir/standard_library/cryptographic_primitives/hashes.mdx +++ b/docs/versioned_docs/version-v0.32.0/noir/standard_library/cryptographic_primitives/hashes.mdx @@ -8,7 +8,7 @@ keywords: sidebar_position: 0 --- -import BlackBoxInfo from '@site/src/components/Notes/_blackbox.mdx'; +import BlackBoxInfo from '@site/src/components/Notes/_blackbox'; ## sha256 @@ -36,7 +36,7 @@ fn main() { ``` - + ## blake2s @@ -57,7 +57,7 @@ fn main() { } ``` - + ## blake3 @@ -78,7 +78,7 @@ fn main() { } ``` - + ## pedersen_hash @@ -101,7 +101,7 @@ fn main(x: Field, y: Field, expected_hash: Field) { > Source code: test_programs/execution_success/pedersen_hash/src/main.nr#L1-L7 - + ## pedersen_commitment @@ -125,7 +125,7 @@ fn main(x: Field, y: Field, expected_commitment: std::embedded_curve_ops::Embedd > Source code: test_programs/execution_success/pedersen_commitment/src/main.nr#L1-L8 - + ## keccak256 @@ -164,7 +164,7 @@ fn main(x: Field, result: [u8; 32]) { > Source code: test_programs/execution_success/keccak256/src/main.nr#L1-L21 - + ## poseidon diff --git a/docs/versioned_docs/version-v0.32.0/noir/standard_library/cryptographic_primitives/schnorr.mdx b/docs/versioned_docs/version-v0.32.0/noir/standard_library/cryptographic_primitives/schnorr.mdx index a32138daaa6..00e7f257612 100644 --- a/docs/versioned_docs/version-v0.32.0/noir/standard_library/cryptographic_primitives/schnorr.mdx +++ b/docs/versioned_docs/version-v0.32.0/noir/standard_library/cryptographic_primitives/schnorr.mdx @@ -5,7 +5,7 @@ keywords: [cryptographic primitives, Noir project, schnorr, signatures] sidebar_position: 2 --- -import BlackBoxInfo from '@site/src/components/Notes/_blackbox.mdx'; +import BlackBoxInfo from '@site/src/components/Notes/_blackbox'; ## schnorr::verify_signature @@ -43,7 +43,7 @@ const signature = Array.from( ... ``` - + ## schnorr::verify_signature_slice @@ -61,4 +61,4 @@ pub fn verify_signature_slice( > Source code: noir_stdlib/src/schnorr.nr#L13-L20 - + diff --git a/docs/versioned_docs/version-v0.32.0/noir/standard_library/recursion.md b/docs/versioned_docs/version-v0.32.0/noir/standard_library/recursion.mdx similarity index 96% rename from docs/versioned_docs/version-v0.32.0/noir/standard_library/recursion.md rename to docs/versioned_docs/version-v0.32.0/noir/standard_library/recursion.mdx index 8cfb37fc52d..8fdb8e8f514 100644 --- a/docs/versioned_docs/version-v0.32.0/noir/standard_library/recursion.md +++ b/docs/versioned_docs/version-v0.32.0/noir/standard_library/recursion.mdx @@ -4,7 +4,7 @@ description: Learn about how to write recursive proofs in Noir. keywords: [recursion, recursive proofs, verification_key, verify_proof] --- -import BlackBoxInfo from '@site/src/components/Notes/_blackbox.mdx'; +import BlackBoxInfo from '@site/src/components/Notes/_blackbox'; Noir supports recursively verifying proofs, meaning you verify the proof of a Noir program in another Noir program. This enables creating proofs of arbitrary size by doing step-wise verification of smaller components of a large proof. @@ -35,7 +35,7 @@ By incorporating this attribute directly in the circuit's definition, tooling li pub fn verify_proof(verification_key: [Field], proof: [Field], public_inputs: [Field], key_hash: Field) {} ``` - + ## Example usage diff --git a/docs/versioned_docs/version-v0.33.0/explainers/explainer-recursion.md b/docs/versioned_docs/version-v0.33.0/explainers/explainer-recursion.md index 18846176ca7..df8529ef4e0 100644 --- a/docs/versioned_docs/version-v0.33.0/explainers/explainer-recursion.md +++ b/docs/versioned_docs/version-v0.33.0/explainers/explainer-recursion.md @@ -111,7 +111,7 @@ He might find it more efficient to generate a proof for that setup phase separat ## What params do I need -As you can see in the [recursion reference](noir/standard_library/recursion.md), a simple recursive proof requires: +As you can see in the [recursion reference](noir/standard_library/recursion.mdx), a simple recursive proof requires: - The proof to verify - The Verification Key of the circuit that generated the proof diff --git a/docs/versioned_docs/version-v0.33.0/how_to/how-to-recursion.md b/docs/versioned_docs/version-v0.33.0/how_to/how-to-recursion.md index 71f02fa5435..c8c4dc9f5b4 100644 --- a/docs/versioned_docs/version-v0.33.0/how_to/how-to-recursion.md +++ b/docs/versioned_docs/version-v0.33.0/how_to/how-to-recursion.md @@ -25,7 +25,7 @@ This guide shows you how to use recursive proofs in your NoirJS app. For the sak - You already have a NoirJS app. If you don't, please visit the [NoirJS tutorial](../tutorials/noirjs_app.md) and the [reference](../reference/NoirJS/noir_js/index.md). - You are familiar with what are recursive proofs and you have read the [recursion explainer](../explainers/explainer-recursion.md) -- You already built a recursive circuit following [the reference](../noir/standard_library/recursion.md), and understand how it works. +- You already built a recursive circuit following [the reference](../noir/standard_library/recursion.mdx), and understand how it works. It is also assumed that you're not using `noir_wasm` for compilation, and instead you've used [`nargo compile`](../reference/nargo_commands.md) to generate the `json` you're now importing into your project. However, the guide should work just the same if you're using `noir_wasm`. diff --git a/docs/versioned_docs/version-v0.33.0/noir/standard_library/black_box_fns.md b/docs/versioned_docs/version-v0.33.0/noir/standard_library/black_box_fns.md index d5694250f05..d6079ab182c 100644 --- a/docs/versioned_docs/version-v0.33.0/noir/standard_library/black_box_fns.md +++ b/docs/versioned_docs/version-v0.33.0/noir/standard_library/black_box_fns.md @@ -25,7 +25,7 @@ Here is a list of the current black box functions: - XOR - RANGE - [Keccak256](./cryptographic_primitives/hashes.mdx#keccak256) -- [Recursive proof verification](./recursion.md) +- [Recursive proof verification](./recursion.mdx) Most black box functions are included as part of the Noir standard library, however `AND`, `XOR` and `RANGE` are used as part of the Noir language syntax. For instance, using the bitwise operator `&` will invoke the `AND` black box function. diff --git a/docs/versioned_docs/version-v0.33.0/noir/standard_library/cryptographic_primitives/ciphers.mdx b/docs/versioned_docs/version-v0.33.0/noir/standard_library/cryptographic_primitives/ciphers.mdx index d75e50d4b89..d6a5e1a79eb 100644 --- a/docs/versioned_docs/version-v0.33.0/noir/standard_library/cryptographic_primitives/ciphers.mdx +++ b/docs/versioned_docs/version-v0.33.0/noir/standard_library/cryptographic_primitives/ciphers.mdx @@ -7,7 +7,7 @@ keywords: sidebar_position: 0 --- -import BlackBoxInfo from '@site/src/components/Notes/_blackbox.mdx'; +import BlackBoxInfo from '@site/src/components/Notes/_blackbox'; ## aes128 @@ -29,4 +29,4 @@ fn main() { ``` - \ No newline at end of file + \ No newline at end of file diff --git a/docs/versioned_docs/version-v0.33.0/noir/standard_library/cryptographic_primitives/ecdsa_sig_verification.mdx b/docs/versioned_docs/version-v0.33.0/noir/standard_library/cryptographic_primitives/ecdsa_sig_verification.mdx index 8520071e95f..4c22e70e8de 100644 --- a/docs/versioned_docs/version-v0.33.0/noir/standard_library/cryptographic_primitives/ecdsa_sig_verification.mdx +++ b/docs/versioned_docs/version-v0.33.0/noir/standard_library/cryptographic_primitives/ecdsa_sig_verification.mdx @@ -5,7 +5,7 @@ keywords: [cryptographic primitives, Noir project, ecdsa, secp256k1, secp256r1, sidebar_position: 3 --- -import BlackBoxInfo from '@site/src/components/Notes/_blackbox.mdx'; +import BlackBoxInfo from '@site/src/components/Notes/_blackbox'; Noir supports ECDSA signatures verification over the secp256k1 and secp256r1 curves. @@ -34,7 +34,7 @@ fn main(hashed_message : [u8;32], pub_key_x : [u8;32], pub_key_y : [u8;32], sign } ``` - + ## ecdsa_secp256k1::verify_signature_slice @@ -51,7 +51,7 @@ pub fn verify_signature_slice( > Source code: noir_stdlib/src/ecdsa_secp256k1.nr#L13-L20 - + ## ecdsa_secp256r1::verify_signature @@ -78,7 +78,7 @@ fn main(hashed_message : [u8;32], pub_key_x : [u8;32], pub_key_y : [u8;32], sign } ``` - + ## ecdsa_secp256r1::verify_signature @@ -95,4 +95,4 @@ pub fn verify_signature_slice( > Source code: noir_stdlib/src/ecdsa_secp256r1.nr#L13-L20 - + diff --git a/docs/versioned_docs/version-v0.33.0/noir/standard_library/cryptographic_primitives/eddsa.mdx b/docs/versioned_docs/version-v0.33.0/noir/standard_library/cryptographic_primitives/eddsa.mdx index 1ad42a5ac96..b283de693c8 100644 --- a/docs/versioned_docs/version-v0.33.0/noir/standard_library/cryptographic_primitives/eddsa.mdx +++ b/docs/versioned_docs/version-v0.33.0/noir/standard_library/cryptographic_primitives/eddsa.mdx @@ -5,7 +5,7 @@ keywords: [cryptographic primitives, Noir project, eddsa, signatures] sidebar_position: 5 --- -import BlackBoxInfo from '@site/src/components/Notes/_blackbox.mdx'; +import BlackBoxInfo from '@site/src/components/Notes/_blackbox'; ## eddsa::eddsa_poseidon_verify @@ -23,7 +23,7 @@ use std::hash::poseidon2::Poseidon2Hasher; eddsa_verify::(pub_key_a.x, pub_key_a.y, s_a, r8_a.x, r8_a.y, msg); ``` - + ## eddsa::eddsa_to_pub diff --git a/docs/versioned_docs/version-v0.33.0/noir/standard_library/cryptographic_primitives/embedded_curve_ops.mdx b/docs/versioned_docs/version-v0.33.0/noir/standard_library/cryptographic_primitives/embedded_curve_ops.mdx index 719549bc418..69e0265c81a 100644 --- a/docs/versioned_docs/version-v0.33.0/noir/standard_library/cryptographic_primitives/embedded_curve_ops.mdx +++ b/docs/versioned_docs/version-v0.33.0/noir/standard_library/cryptographic_primitives/embedded_curve_ops.mdx @@ -5,7 +5,7 @@ keywords: [cryptographic primitives, Noir project, scalar multiplication] sidebar_position: 1 --- -import BlackBoxInfo from '@site/src/components/Notes/_blackbox.mdx'; +import BlackBoxInfo from '@site/src/components/Notes/_blackbox'; The following functions perform operations over the embedded curve whose coordinates are defined by the configured noir field. For the BN254 scalar field, this is BabyJubJub or Grumpkin. @@ -92,4 +92,4 @@ fn main() { } ``` - + diff --git a/docs/versioned_docs/version-v0.33.0/noir/standard_library/cryptographic_primitives/hashes.mdx b/docs/versioned_docs/version-v0.33.0/noir/standard_library/cryptographic_primitives/hashes.mdx index 63a4a2afd53..797ff8cc22c 100644 --- a/docs/versioned_docs/version-v0.33.0/noir/standard_library/cryptographic_primitives/hashes.mdx +++ b/docs/versioned_docs/version-v0.33.0/noir/standard_library/cryptographic_primitives/hashes.mdx @@ -8,7 +8,7 @@ keywords: sidebar_position: 0 --- -import BlackBoxInfo from '@site/src/components/Notes/_blackbox.mdx'; +import BlackBoxInfo from '@site/src/components/Notes/_blackbox'; ## sha256 @@ -36,7 +36,7 @@ fn main() { ``` - + ## blake2s @@ -57,7 +57,7 @@ fn main() { } ``` - + ## blake3 @@ -78,7 +78,7 @@ fn main() { } ``` - + ## pedersen_hash @@ -101,7 +101,7 @@ fn main(x: Field, y: Field, expected_hash: Field) { > Source code: test_programs/execution_success/pedersen_hash/src/main.nr#L1-L7 - + ## pedersen_commitment @@ -125,7 +125,7 @@ fn main(x: Field, y: Field, expected_commitment: std::embedded_curve_ops::Embedd > Source code: test_programs/execution_success/pedersen_commitment/src/main.nr#L1-L8 - + ## keccak256 @@ -164,7 +164,7 @@ fn main(x: Field, result: [u8; 32]) { > Source code: test_programs/execution_success/keccak256/src/main.nr#L1-L21 - + ## poseidon diff --git a/docs/versioned_docs/version-v0.33.0/noir/standard_library/cryptographic_primitives/schnorr.mdx b/docs/versioned_docs/version-v0.33.0/noir/standard_library/cryptographic_primitives/schnorr.mdx index a32138daaa6..00e7f257612 100644 --- a/docs/versioned_docs/version-v0.33.0/noir/standard_library/cryptographic_primitives/schnorr.mdx +++ b/docs/versioned_docs/version-v0.33.0/noir/standard_library/cryptographic_primitives/schnorr.mdx @@ -5,7 +5,7 @@ keywords: [cryptographic primitives, Noir project, schnorr, signatures] sidebar_position: 2 --- -import BlackBoxInfo from '@site/src/components/Notes/_blackbox.mdx'; +import BlackBoxInfo from '@site/src/components/Notes/_blackbox'; ## schnorr::verify_signature @@ -43,7 +43,7 @@ const signature = Array.from( ... ``` - + ## schnorr::verify_signature_slice @@ -61,4 +61,4 @@ pub fn verify_signature_slice( > Source code: noir_stdlib/src/schnorr.nr#L13-L20 - + diff --git a/docs/versioned_docs/version-v0.34.0/noir/standard_library/recursion.md b/docs/versioned_docs/version-v0.33.0/noir/standard_library/recursion.mdx similarity index 96% rename from docs/versioned_docs/version-v0.34.0/noir/standard_library/recursion.md rename to docs/versioned_docs/version-v0.33.0/noir/standard_library/recursion.mdx index 7f4dcebf084..60414a2fa51 100644 --- a/docs/versioned_docs/version-v0.34.0/noir/standard_library/recursion.md +++ b/docs/versioned_docs/version-v0.33.0/noir/standard_library/recursion.mdx @@ -4,7 +4,7 @@ description: Learn about how to write recursive proofs in Noir. keywords: [recursion, recursive proofs, verification_key, verify_proof] --- -import BlackBoxInfo from '@site/src/components/Notes/_blackbox.mdx'; +import BlackBoxInfo from '@site/src/components/Notes/_blackbox'; Noir supports recursively verifying proofs, meaning you verify the proof of a Noir program in another Noir program. This enables creating proofs of arbitrary size by doing step-wise verification of smaller components of a large proof. @@ -35,7 +35,7 @@ By incorporating this attribute directly in the circuit's definition, tooling li pub fn verify_proof(verification_key: [Field], proof: [Field], public_inputs: [Field], key_hash: Field) {} ``` - + ## Example usage diff --git a/docs/versioned_docs/version-v0.34.0/explainers/explainer-recursion.md b/docs/versioned_docs/version-v0.34.0/explainers/explainer-recursion.md index 18846176ca7..df8529ef4e0 100644 --- a/docs/versioned_docs/version-v0.34.0/explainers/explainer-recursion.md +++ b/docs/versioned_docs/version-v0.34.0/explainers/explainer-recursion.md @@ -111,7 +111,7 @@ He might find it more efficient to generate a proof for that setup phase separat ## What params do I need -As you can see in the [recursion reference](noir/standard_library/recursion.md), a simple recursive proof requires: +As you can see in the [recursion reference](noir/standard_library/recursion.mdx), a simple recursive proof requires: - The proof to verify - The Verification Key of the circuit that generated the proof diff --git a/docs/versioned_docs/version-v0.34.0/how_to/how-to-recursion.md b/docs/versioned_docs/version-v0.34.0/how_to/how-to-recursion.md index 71f02fa5435..c8c4dc9f5b4 100644 --- a/docs/versioned_docs/version-v0.34.0/how_to/how-to-recursion.md +++ b/docs/versioned_docs/version-v0.34.0/how_to/how-to-recursion.md @@ -25,7 +25,7 @@ This guide shows you how to use recursive proofs in your NoirJS app. For the sak - You already have a NoirJS app. If you don't, please visit the [NoirJS tutorial](../tutorials/noirjs_app.md) and the [reference](../reference/NoirJS/noir_js/index.md). - You are familiar with what are recursive proofs and you have read the [recursion explainer](../explainers/explainer-recursion.md) -- You already built a recursive circuit following [the reference](../noir/standard_library/recursion.md), and understand how it works. +- You already built a recursive circuit following [the reference](../noir/standard_library/recursion.mdx), and understand how it works. It is also assumed that you're not using `noir_wasm` for compilation, and instead you've used [`nargo compile`](../reference/nargo_commands.md) to generate the `json` you're now importing into your project. However, the guide should work just the same if you're using `noir_wasm`. diff --git a/docs/versioned_docs/version-v0.34.0/noir/standard_library/black_box_fns.md b/docs/versioned_docs/version-v0.34.0/noir/standard_library/black_box_fns.md index d5694250f05..d6079ab182c 100644 --- a/docs/versioned_docs/version-v0.34.0/noir/standard_library/black_box_fns.md +++ b/docs/versioned_docs/version-v0.34.0/noir/standard_library/black_box_fns.md @@ -25,7 +25,7 @@ Here is a list of the current black box functions: - XOR - RANGE - [Keccak256](./cryptographic_primitives/hashes.mdx#keccak256) -- [Recursive proof verification](./recursion.md) +- [Recursive proof verification](./recursion.mdx) Most black box functions are included as part of the Noir standard library, however `AND`, `XOR` and `RANGE` are used as part of the Noir language syntax. For instance, using the bitwise operator `&` will invoke the `AND` black box function. diff --git a/docs/versioned_docs/version-v0.34.0/noir/standard_library/cryptographic_primitives/ciphers.mdx b/docs/versioned_docs/version-v0.34.0/noir/standard_library/cryptographic_primitives/ciphers.mdx index d75e50d4b89..d6a5e1a79eb 100644 --- a/docs/versioned_docs/version-v0.34.0/noir/standard_library/cryptographic_primitives/ciphers.mdx +++ b/docs/versioned_docs/version-v0.34.0/noir/standard_library/cryptographic_primitives/ciphers.mdx @@ -7,7 +7,7 @@ keywords: sidebar_position: 0 --- -import BlackBoxInfo from '@site/src/components/Notes/_blackbox.mdx'; +import BlackBoxInfo from '@site/src/components/Notes/_blackbox'; ## aes128 @@ -29,4 +29,4 @@ fn main() { ``` - \ No newline at end of file + \ No newline at end of file diff --git a/docs/versioned_docs/version-v0.34.0/noir/standard_library/cryptographic_primitives/ecdsa_sig_verification.mdx b/docs/versioned_docs/version-v0.34.0/noir/standard_library/cryptographic_primitives/ecdsa_sig_verification.mdx index 8520071e95f..4c22e70e8de 100644 --- a/docs/versioned_docs/version-v0.34.0/noir/standard_library/cryptographic_primitives/ecdsa_sig_verification.mdx +++ b/docs/versioned_docs/version-v0.34.0/noir/standard_library/cryptographic_primitives/ecdsa_sig_verification.mdx @@ -5,7 +5,7 @@ keywords: [cryptographic primitives, Noir project, ecdsa, secp256k1, secp256r1, sidebar_position: 3 --- -import BlackBoxInfo from '@site/src/components/Notes/_blackbox.mdx'; +import BlackBoxInfo from '@site/src/components/Notes/_blackbox'; Noir supports ECDSA signatures verification over the secp256k1 and secp256r1 curves. @@ -34,7 +34,7 @@ fn main(hashed_message : [u8;32], pub_key_x : [u8;32], pub_key_y : [u8;32], sign } ``` - + ## ecdsa_secp256k1::verify_signature_slice @@ -51,7 +51,7 @@ pub fn verify_signature_slice( > Source code: noir_stdlib/src/ecdsa_secp256k1.nr#L13-L20 - + ## ecdsa_secp256r1::verify_signature @@ -78,7 +78,7 @@ fn main(hashed_message : [u8;32], pub_key_x : [u8;32], pub_key_y : [u8;32], sign } ``` - + ## ecdsa_secp256r1::verify_signature @@ -95,4 +95,4 @@ pub fn verify_signature_slice( > Source code: noir_stdlib/src/ecdsa_secp256r1.nr#L13-L20 - + diff --git a/docs/versioned_docs/version-v0.34.0/noir/standard_library/cryptographic_primitives/eddsa.mdx b/docs/versioned_docs/version-v0.34.0/noir/standard_library/cryptographic_primitives/eddsa.mdx index 1ad42a5ac96..b283de693c8 100644 --- a/docs/versioned_docs/version-v0.34.0/noir/standard_library/cryptographic_primitives/eddsa.mdx +++ b/docs/versioned_docs/version-v0.34.0/noir/standard_library/cryptographic_primitives/eddsa.mdx @@ -5,7 +5,7 @@ keywords: [cryptographic primitives, Noir project, eddsa, signatures] sidebar_position: 5 --- -import BlackBoxInfo from '@site/src/components/Notes/_blackbox.mdx'; +import BlackBoxInfo from '@site/src/components/Notes/_blackbox'; ## eddsa::eddsa_poseidon_verify @@ -23,7 +23,7 @@ use std::hash::poseidon2::Poseidon2Hasher; eddsa_verify::(pub_key_a.x, pub_key_a.y, s_a, r8_a.x, r8_a.y, msg); ``` - + ## eddsa::eddsa_to_pub diff --git a/docs/versioned_docs/version-v0.34.0/noir/standard_library/cryptographic_primitives/embedded_curve_ops.mdx b/docs/versioned_docs/version-v0.34.0/noir/standard_library/cryptographic_primitives/embedded_curve_ops.mdx index 8ded020bf27..2da1e34f008 100644 --- a/docs/versioned_docs/version-v0.34.0/noir/standard_library/cryptographic_primitives/embedded_curve_ops.mdx +++ b/docs/versioned_docs/version-v0.34.0/noir/standard_library/cryptographic_primitives/embedded_curve_ops.mdx @@ -5,7 +5,7 @@ keywords: [cryptographic primitives, Noir project, scalar multiplication] sidebar_position: 1 --- -import BlackBoxInfo from '@site/src/components/Notes/_blackbox.mdx'; +import BlackBoxInfo from '@site/src/components/Notes/_blackbox'; The following functions perform operations over the embedded curve whose coordinates are defined by the configured noir field. For the BN254 scalar field, this is BabyJubJub or Grumpkin. @@ -92,4 +92,4 @@ fn main() { } ``` - + diff --git a/docs/versioned_docs/version-v0.34.0/noir/standard_library/cryptographic_primitives/hashes.mdx b/docs/versioned_docs/version-v0.34.0/noir/standard_library/cryptographic_primitives/hashes.mdx index 2581690e034..d6640d26f25 100644 --- a/docs/versioned_docs/version-v0.34.0/noir/standard_library/cryptographic_primitives/hashes.mdx +++ b/docs/versioned_docs/version-v0.34.0/noir/standard_library/cryptographic_primitives/hashes.mdx @@ -8,7 +8,7 @@ keywords: sidebar_position: 0 --- -import BlackBoxInfo from '@site/src/components/Notes/_blackbox.mdx'; +import BlackBoxInfo from '@site/src/components/Notes/_blackbox'; ## sha256 @@ -36,7 +36,7 @@ fn main() { ``` - + ## blake2s @@ -57,7 +57,7 @@ fn main() { } ``` - + ## blake3 @@ -78,7 +78,7 @@ fn main() { } ``` - + ## pedersen_hash @@ -101,7 +101,7 @@ fn main(x: Field, y: Field, expected_hash: Field) { > Source code: test_programs/execution_success/pedersen_hash/src/main.nr#L1-L7 - + ## pedersen_commitment @@ -125,7 +125,7 @@ fn main(x: Field, y: Field, expected_commitment: std::embedded_curve_ops::Embedd > Source code: test_programs/execution_success/pedersen_commitment/src/main.nr#L1-L8 - + ## keccak256 @@ -164,7 +164,7 @@ fn main(x: Field, result: [u8; 32]) { > Source code: test_programs/execution_success/keccak256/src/main.nr#L1-L21 - + ## poseidon diff --git a/docs/versioned_docs/version-v0.34.0/noir/standard_library/cryptographic_primitives/schnorr.mdx b/docs/versioned_docs/version-v0.34.0/noir/standard_library/cryptographic_primitives/schnorr.mdx index a32138daaa6..00e7f257612 100644 --- a/docs/versioned_docs/version-v0.34.0/noir/standard_library/cryptographic_primitives/schnorr.mdx +++ b/docs/versioned_docs/version-v0.34.0/noir/standard_library/cryptographic_primitives/schnorr.mdx @@ -5,7 +5,7 @@ keywords: [cryptographic primitives, Noir project, schnorr, signatures] sidebar_position: 2 --- -import BlackBoxInfo from '@site/src/components/Notes/_blackbox.mdx'; +import BlackBoxInfo from '@site/src/components/Notes/_blackbox'; ## schnorr::verify_signature @@ -43,7 +43,7 @@ const signature = Array.from( ... ``` - + ## schnorr::verify_signature_slice @@ -61,4 +61,4 @@ pub fn verify_signature_slice( > Source code: noir_stdlib/src/schnorr.nr#L13-L20 - + diff --git a/docs/versioned_docs/version-v0.33.0/noir/standard_library/recursion.md b/docs/versioned_docs/version-v0.34.0/noir/standard_library/recursion.mdx similarity index 96% rename from docs/versioned_docs/version-v0.33.0/noir/standard_library/recursion.md rename to docs/versioned_docs/version-v0.34.0/noir/standard_library/recursion.mdx index 7f4dcebf084..60414a2fa51 100644 --- a/docs/versioned_docs/version-v0.33.0/noir/standard_library/recursion.md +++ b/docs/versioned_docs/version-v0.34.0/noir/standard_library/recursion.mdx @@ -4,7 +4,7 @@ description: Learn about how to write recursive proofs in Noir. keywords: [recursion, recursive proofs, verification_key, verify_proof] --- -import BlackBoxInfo from '@site/src/components/Notes/_blackbox.mdx'; +import BlackBoxInfo from '@site/src/components/Notes/_blackbox'; Noir supports recursively verifying proofs, meaning you verify the proof of a Noir program in another Noir program. This enables creating proofs of arbitrary size by doing step-wise verification of smaller components of a large proof. @@ -35,7 +35,7 @@ By incorporating this attribute directly in the circuit's definition, tooling li pub fn verify_proof(verification_key: [Field], proof: [Field], public_inputs: [Field], key_hash: Field) {} ``` - + ## Example usage From 594ec91de55c4cf191d7cdc94a00bb16711cd430 Mon Sep 17 00:00:00 2001 From: Maxim Vezenov Date: Wed, 2 Oct 2024 10:49:59 -0400 Subject: [PATCH 31/36] fix(ssa): Check if result of array set is used in value of another array set (#6197) # Description ## Problem\* Resolves ## Summary\* Fix for failure in the `test_barycentric` of the `blob` lib in `noir-protocol-circuits`. The test now passes when Brillig's `MAX_STACK_SIZE` is increased to 32k. I included an additional map to track whether an array set result was used as a value in another array set. We can not allow for inner nested array sets to be mutable across blocks as the full nested array may be used in another block. We could add additional logic to check whether the parent array set will be marked mutable, but this seems overly complex for little benefit at the moment as this fix showed a quite small size regression. ## Additional Context ## Documentation\* Check one: - [X] No documentation needed. - [ ] Documentation included in this PR. - [ ] **[For Experimental Features]** Documentation to be submitted in a separate PR. # PR Checklist\* - [X] I have tested the changes locally. - [X] I have formatted the changes with [Prettier](https://prettier.io/) and/or `cargo fmt` on default settings. --- .../noirc_evaluator/src/ssa/opt/array_set.rs | 91 ++++++++++++------- 1 file changed, 57 insertions(+), 34 deletions(-) diff --git a/compiler/noirc_evaluator/src/ssa/opt/array_set.rs b/compiler/noirc_evaluator/src/ssa/opt/array_set.rs index 267a2c105f2..e7d765949b9 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/array_set.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/array_set.rs @@ -2,7 +2,7 @@ use crate::ssa::{ ir::{ basic_block::BasicBlockId, dfg::DataFlowGraph, - function::Function, + function::{Function, RuntimeType}, instruction::{Instruction, InstructionId, TerminatorInstruction}, types::Type::{Array, Slice}, value::ValueId, @@ -34,14 +34,17 @@ impl Function { let mut array_to_last_use = HashMap::default(); let mut instructions_to_update = HashSet::default(); let mut arrays_from_load = HashSet::default(); + let mut inner_nested_arrays = HashMap::default(); for block in reachable_blocks.iter() { analyze_last_uses( &self.dfg, *block, + matches!(self.runtime(), RuntimeType::Brillig), &mut array_to_last_use, &mut instructions_to_update, &mut arrays_from_load, + &mut inner_nested_arrays, ); } for block in reachable_blocks { @@ -55,9 +58,11 @@ impl Function { fn analyze_last_uses( dfg: &DataFlowGraph, block_id: BasicBlockId, + is_brillig_func: bool, array_to_last_use: &mut HashMap, instructions_that_can_be_made_mutable: &mut HashSet, arrays_from_load: &mut HashSet, + inner_nested_arrays: &mut HashMap, ) { let block = &dfg[block_id]; @@ -70,12 +75,22 @@ fn analyze_last_uses( instructions_that_can_be_made_mutable.remove(&existing); } } - Instruction::ArraySet { array, .. } => { + Instruction::ArraySet { array, value, .. } => { let array = dfg.resolve(*array); if let Some(existing) = array_to_last_use.insert(array, *instruction_id) { instructions_that_can_be_made_mutable.remove(&existing); } + if is_brillig_func { + let value = dfg.resolve(*value); + + if let Some(existing) = inner_nested_arrays.get(&value) { + instructions_that_can_be_made_mutable.remove(existing); + } + let result = dfg.instruction_results(*instruction_id)[0]; + inner_nested_arrays.insert(result, *instruction_id); + } + // If the array we are setting does not come from a load we can safely mark it mutable. // If the array comes from a load we may potentially being mutating an array at a reference // that is loaded from by other values. @@ -169,29 +184,31 @@ mod tests { // from and cloned in a loop. If the array is inadvertently marked mutable, and is cloned in a previous iteration // of the loop, its clone will also be altered. // - // acir(inline) fn main f0 { + // brillig fn main f0 { // b0(): - // v2 = allocate - // store [Field 0, Field 0, Field 0, Field 0, Field 0] at v2 // v3 = allocate - // store [Field 0, Field 0, Field 0, Field 0, Field 0] at v3 + // store [[Field 0, Field 0, Field 0, Field 0, Field 0], [Field 0, Field 0, Field 0, Field 0, Field 0]] at v3 + // v4 = allocate + // store [[Field 0, Field 0, Field 0, Field 0, Field 0], [Field 0, Field 0, Field 0, Field 0, Field 0]] at v4 // jmp b1(u32 0) - // b1(v5: u32): - // v7 = lt v5, u32 5 - // jmpif v7 then: b3, else: b2 + // b1(v6: u32): + // v8 = lt v6, u32 5 + // jmpif v8 then: b3, else: b2 // b3(): - // v8 = eq v5, u32 5 - // jmpif v8 then: b4, else: b5 + // v9 = eq v6, u32 5 + // jmpif v9 then: b4, else: b5 // b4(): - // v9 = load v2 - // store v9 at v3 + // v10 = load v3 + // store v10 at v4 // jmp b5() // b5(): - // v10 = load v2 - // v12 = array_set v10, index v5, value Field 20 - // store v12 at v2 - // v14 = add v5, u32 1 - // jmp b1(v14) + // v11 = load v3 + // v13 = array_get v11, index Field 0 + // v14 = array_set v13, index v6, value Field 20 + // v15 = array_set v11, index v6, value v14 + // store v15 at v3 + // v17 = add v6, u32 1 + // jmp b1(v17) // b2(): // return // } @@ -203,13 +220,16 @@ mod tests { let zero = builder.field_constant(0u128); let array_constant = builder.array_constant(vector![zero, zero, zero, zero, zero], array_type.clone()); + let nested_array_type = Type::Array(Arc::new(vec![array_type.clone()]), 2); + let nested_array_constant = builder + .array_constant(vector![array_constant, array_constant], nested_array_type.clone()); - let v2 = builder.insert_allocate(array_type.clone()); + let v3 = builder.insert_allocate(array_type.clone()); - builder.insert_store(v2, array_constant); + builder.insert_store(v3, nested_array_constant); - let v3 = builder.insert_allocate(array_type.clone()); - builder.insert_store(v3, array_constant); + let v4 = builder.insert_allocate(array_type.clone()); + builder.insert_store(v4, nested_array_constant); let b1 = builder.insert_block(); let zero_u32 = builder.numeric_constant(0u128, Type::unsigned(32)); @@ -219,35 +239,38 @@ mod tests { builder.switch_to_block(b1); let v5 = builder.add_block_parameter(b1, Type::unsigned(32)); let five = builder.numeric_constant(5u128, Type::unsigned(32)); - let v7 = builder.insert_binary(v5, BinaryOp::Lt, five); + let v8 = builder.insert_binary(v5, BinaryOp::Lt, five); let b2 = builder.insert_block(); let b3 = builder.insert_block(); let b4 = builder.insert_block(); let b5 = builder.insert_block(); - builder.terminate_with_jmpif(v7, b3, b2); + builder.terminate_with_jmpif(v8, b3, b2); // Loop body // b3 is the if statement conditional builder.switch_to_block(b3); let two = builder.numeric_constant(5u128, Type::unsigned(32)); - let v8 = builder.insert_binary(v5, BinaryOp::Eq, two); - builder.terminate_with_jmpif(v8, b4, b5); + let v9 = builder.insert_binary(v5, BinaryOp::Eq, two); + builder.terminate_with_jmpif(v9, b4, b5); // b4 is the rest of the loop after the if statement builder.switch_to_block(b4); - let v9 = builder.insert_load(v2, array_type.clone()); - builder.insert_store(v3, v9); + let v10 = builder.insert_load(v3, nested_array_type.clone()); + builder.insert_store(v4, v10); builder.terminate_with_jmp(b5, vec![]); builder.switch_to_block(b5); - let v10 = builder.insert_load(v2, array_type.clone()); + let v11 = builder.insert_load(v3, nested_array_type.clone()); let twenty = builder.field_constant(20u128); - let v12 = builder.insert_array_set(v10, v5, twenty); - builder.insert_store(v2, v12); + let v13 = builder.insert_array_get(v11, zero, array_type.clone()); + let v14 = builder.insert_array_set(v13, v5, twenty); + let v15 = builder.insert_array_set(v11, v5, v14); + + builder.insert_store(v3, v15); let one = builder.numeric_constant(1u128, Type::unsigned(32)); - let v14 = builder.insert_binary(v5, BinaryOp::Add, one); - builder.terminate_with_jmp(b1, vec![v14]); + let v17 = builder.insert_binary(v5, BinaryOp::Add, one); + builder.terminate_with_jmp(b1, vec![v17]); builder.switch_to_block(b2); builder.terminate_with_return(vec![]); @@ -265,7 +288,7 @@ mod tests { .filter(|instruction| matches!(&main.dfg[**instruction], Instruction::ArraySet { .. })) .collect::>(); - assert_eq!(array_set_instructions.len(), 1); + assert_eq!(array_set_instructions.len(), 2); if let Instruction::ArraySet { mutable, .. } = &main.dfg[*array_set_instructions[0]] { // The single array set should not be marked mutable assert!(!mutable); From b4712c5ba50ef38789978522afcd251ffbcf8780 Mon Sep 17 00:00:00 2001 From: Tom French <15848336+TomAFrench@users.noreply.github.com> Date: Wed, 2 Oct 2024 16:31:01 +0100 Subject: [PATCH 32/36] feat: remove orphaned blocks from cfg to improve `simplify_cfg` pass. (#6198) # Description ## Problem\* Resolves ## Summary\* Inspired by https://github.com/noir-lang/noir/pull/6184, I noticed that after simplifying a constant jmpif we wouldn't inline the block following the inlined block into the first block, as an example consider the SSA ``` After Dead Instruction Elimination: brillig fn main f0 { b0(v0: Field, v1: Field): constrain v0 == Field 1 constrain v1 == Field 0 jmpif u1 0 then: b2, else: b1 b2(): v8 = add v0, Field 1 jmp b3(v0, v8) b3(v2: Field, v3: Field): v9 = add v0, Field 1 constrain v2 == v9 constrain v3 == v0 constrain v0 == Field 1 constrain v1 == Field 0 return b1(): v7 = add v0, Field 1 jmp b3(v7, v0) } ``` Here we can see that we never enter `b2`, instead going `b0 -> b1 -> b3`. However when the simplify_cfg pass looks at the predecessors of `b3` it sees both `b1` **and `b2`** despite `b2` not being reachable anymore. It then doesn't inline `b3` into its sole predecessor. On master we'll simplify the CFG for the SSA to give the program: ``` After Simplifying: brillig fn main f0 { b0(v0: Field, v1: Field): constrain v0 == Field 1 constrain v1 == Field 0 v6 = add v0, Field 1 jmp b1(v6, v0) b1(v2: Field, v3: Field): v7 = add v0, Field 1 constrain v2 == v7 constrain v3 == v0 constrain v0 == Field 1 constrain v1 == Field 0 return } ``` This PR checks the previous successors of any block which has it's CFG recomputed to check if they've been left orphaned, if so we remove it from the CFG entirely. We now instead fully simplify the CFG to give the program: ``` After Simplifying: brillig fn main f0 { b0(v0: Field, v1: Field): constrain v0 == Field 1 constrain v1 == Field 0 v4 = add v0, Field 1 v5 = add v0, Field 1 constrain v4 == v5 constrain v0 == Field 1 constrain v1 == Field 0 return } ``` ## Additional Context ## Documentation\* Check one: - [x] No documentation needed. - [ ] Documentation included in this PR. - [ ] **[For Experimental Features]** Documentation to be submitted in a separate PR. # PR Checklist\* - [x] I have tested the changes locally. - [x] I have formatted the changes with [Prettier](https://prettier.io/) and/or `cargo fmt` on default settings. --- compiler/noirc_evaluator/src/ssa/ir/cfg.rs | 2 +- .../noirc_evaluator/src/ssa/opt/simplify_cfg.rs | 14 +++++++++++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/compiler/noirc_evaluator/src/ssa/ir/cfg.rs b/compiler/noirc_evaluator/src/ssa/ir/cfg.rs index b9166bf1d56..38e6efa5b9a 100644 --- a/compiler/noirc_evaluator/src/ssa/ir/cfg.rs +++ b/compiler/noirc_evaluator/src/ssa/ir/cfg.rs @@ -59,7 +59,7 @@ impl ControlFlowGraph { /// Clears out a given block's successors. This also removes the given block from /// being a predecessor of any of its previous successors. - fn invalidate_block_successors(&mut self, basic_block_id: BasicBlockId) { + pub(crate) fn invalidate_block_successors(&mut self, basic_block_id: BasicBlockId) { let node = self .data .get_mut(&basic_block_id) diff --git a/compiler/noirc_evaluator/src/ssa/opt/simplify_cfg.rs b/compiler/noirc_evaluator/src/ssa/opt/simplify_cfg.rs index b77bc8c72f3..46941775c5e 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/simplify_cfg.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/simplify_cfg.rs @@ -60,7 +60,6 @@ impl Function { check_for_constant_jmpif(self, block, &mut cfg); let mut predecessors = cfg.predecessors(block); - if predecessors.len() == 1 { let predecessor = predecessors.next().expect("Already checked length of predecessors"); @@ -99,14 +98,23 @@ fn check_for_constant_jmpif( }) = function.dfg[block].terminator() { if let Some(constant) = function.dfg.get_numeric_constant(*condition) { - let destination = - if constant.is_zero() { *else_destination } else { *then_destination }; + let (destination, unchosen_destination) = if constant.is_zero() { + (*else_destination, *then_destination) + } else { + (*then_destination, *else_destination) + }; let arguments = Vec::new(); let call_stack = call_stack.clone(); let jmp = TerminatorInstruction::Jmp { destination, arguments, call_stack }; function.dfg[block].set_terminator(jmp); cfg.recompute_block(function, block); + + // If `block` was the only predecessor to `unchosen_destination` then it's no long reachable through the CFG, + // we can then invalidate it successors as it's an invalid predecessor. + if cfg.predecessors(unchosen_destination).len() == 0 { + cfg.invalidate_block_successors(unchosen_destination); + } } } } From 268f2a0240c507646c65c932748d1bdf062d00b1 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Wed, 2 Oct 2024 12:37:13 -0300 Subject: [PATCH 33/36] fix: type variables by default should have Any kind (#6203) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description ## Problem Resolves #6202 ## Summary Or maybe this isn't the fix. On top of `Kind::Normal` it says: ``` /// Can bind to any type, except Type::Constant and Type::InfixExpr Normal, ``` Does that exclude numeric generics? If not, then maybe the fix is somewhere else 🤔 ## Additional Context ## Documentation Check one: - [x] No documentation needed. - [ ] Documentation included in this PR. - [ ] **[For Experimental Features]** Documentation to be submitted in a separate PR. # PR Checklist - [x] I have tested the changes locally. - [x] I have formatted the changes with [Prettier](https://prettier.io/) and/or `cargo fmt` on default settings. --- compiler/noirc_frontend/src/hir_def/types.rs | 2 +- compiler/noirc_frontend/src/tests/turbofish.rs | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/compiler/noirc_frontend/src/hir_def/types.rs b/compiler/noirc_frontend/src/hir_def/types.rs index 8f20fe1c685..b8c98428bb0 100644 --- a/compiler/noirc_frontend/src/hir_def/types.rs +++ b/compiler/noirc_frontend/src/hir_def/types.rs @@ -881,7 +881,7 @@ impl Type { } pub fn type_variable(id: TypeVariableId) -> Type { - let var = TypeVariable::unbound(id, Kind::Normal); + let var = TypeVariable::unbound(id, Kind::Any); Type::TypeVariable(var) } diff --git a/compiler/noirc_frontend/src/tests/turbofish.rs b/compiler/noirc_frontend/src/tests/turbofish.rs index 43d536fd196..b1156b20eb0 100644 --- a/compiler/noirc_frontend/src/tests/turbofish.rs +++ b/compiler/noirc_frontend/src/tests/turbofish.rs @@ -196,3 +196,21 @@ fn turbofish_in_struct_pattern_generic_count_mismatch() { assert_eq!(*expected, 1); assert_eq!(*found, 2); } + +#[test] +fn numeric_turbofish() { + let src = r#" + struct Reader { + } + + impl Reader { + fn read(_self: Self) {} + } + + fn main() { + let reader: Reader<1234> = Reader {}; + let _ = reader.read::<1234>(); + } + "#; + assert_no_errors(src); +} From 2eb4a2cc782e333be1fcdb3e2b3e6540505a2d18 Mon Sep 17 00:00:00 2001 From: Maxim Vezenov Date: Wed, 2 Oct 2024 11:57:47 -0400 Subject: [PATCH 34/36] chore(refactor): Array set optimization context struct for analysis (#6204) # Description ## Problem\* Resolves ## Summary\* The `analyze_last_uses` method was starting to have multiple different state variables. This PR just creates a separate context struct for analyze the mutable array sets across a block in hopes of making it simpler to add future additions. ## Additional Context ## Documentation\* Check one: - [X] No documentation needed. - [ ] Documentation included in this PR. - [ ] **[For Experimental Features]** Documentation to be submitted in a separate PR. # PR Checklist\* - [X] I have tested the changes locally. - [X] I have formatted the changes with [Prettier](https://prettier.io/) and/or `cargo fmt` on default settings. --- .../noirc_evaluator/src/ssa/opt/array_set.rs | 157 ++++++++++-------- 1 file changed, 85 insertions(+), 72 deletions(-) diff --git a/compiler/noirc_evaluator/src/ssa/opt/array_set.rs b/compiler/noirc_evaluator/src/ssa/opt/array_set.rs index e7d765949b9..b2fe137c8bc 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/array_set.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/array_set.rs @@ -1,3 +1,5 @@ +use std::mem; + use crate::ssa::{ ir::{ basic_block::BasicBlockId, @@ -31,102 +33,113 @@ impl Function { if !self.runtime().is_entry_point() { assert_eq!(reachable_blocks.len(), 1, "Expected there to be 1 block remaining in Acir function for array_set optimization"); } - let mut array_to_last_use = HashMap::default(); - let mut instructions_to_update = HashSet::default(); - let mut arrays_from_load = HashSet::default(); - let mut inner_nested_arrays = HashMap::default(); + + let mut context = Context::new(&self.dfg, matches!(self.runtime(), RuntimeType::Brillig)); for block in reachable_blocks.iter() { - analyze_last_uses( - &self.dfg, - *block, - matches!(self.runtime(), RuntimeType::Brillig), - &mut array_to_last_use, - &mut instructions_to_update, - &mut arrays_from_load, - &mut inner_nested_arrays, - ); + context.analyze_last_uses(*block); } + + let instructions_to_update = mem::take(&mut context.instructions_that_can_be_made_mutable); for block in reachable_blocks { make_mutable(&mut self.dfg, block, &instructions_to_update); } } } -/// Builds the set of ArraySet instructions that can be made mutable -/// because their input value is unused elsewhere afterward. -fn analyze_last_uses( - dfg: &DataFlowGraph, - block_id: BasicBlockId, - is_brillig_func: bool, - array_to_last_use: &mut HashMap, - instructions_that_can_be_made_mutable: &mut HashSet, - arrays_from_load: &mut HashSet, - inner_nested_arrays: &mut HashMap, -) { - let block = &dfg[block_id]; +struct Context<'f> { + dfg: &'f DataFlowGraph, + is_brillig_runtime: bool, + array_to_last_use: HashMap, + instructions_that_can_be_made_mutable: HashSet, + arrays_from_load: HashSet, + inner_nested_arrays: HashMap, +} - for instruction_id in block.instructions() { - match &dfg[*instruction_id] { - Instruction::ArrayGet { array, .. } => { - let array = dfg.resolve(*array); +impl<'f> Context<'f> { + fn new(dfg: &'f DataFlowGraph, is_brillig_runtime: bool) -> Self { + Context { + dfg, + is_brillig_runtime, + array_to_last_use: HashMap::default(), + instructions_that_can_be_made_mutable: HashSet::default(), + arrays_from_load: HashSet::default(), + inner_nested_arrays: HashMap::default(), + } + } - if let Some(existing) = array_to_last_use.insert(array, *instruction_id) { - instructions_that_can_be_made_mutable.remove(&existing); - } - } - Instruction::ArraySet { array, value, .. } => { - let array = dfg.resolve(*array); + /// Builds the set of ArraySet instructions that can be made mutable + /// because their input value is unused elsewhere afterward. + fn analyze_last_uses(&mut self, block_id: BasicBlockId) { + let block = &self.dfg[block_id]; - if let Some(existing) = array_to_last_use.insert(array, *instruction_id) { - instructions_that_can_be_made_mutable.remove(&existing); - } - if is_brillig_func { - let value = dfg.resolve(*value); + for instruction_id in block.instructions() { + match &self.dfg[*instruction_id] { + Instruction::ArrayGet { array, .. } => { + let array = self.dfg.resolve(*array); - if let Some(existing) = inner_nested_arrays.get(&value) { - instructions_that_can_be_made_mutable.remove(existing); + if let Some(existing) = self.array_to_last_use.insert(array, *instruction_id) { + self.instructions_that_can_be_made_mutable.remove(&existing); } - let result = dfg.instruction_results(*instruction_id)[0]; - inner_nested_arrays.insert(result, *instruction_id); } + Instruction::ArraySet { array, value, .. } => { + let array = self.dfg.resolve(*array); - // If the array we are setting does not come from a load we can safely mark it mutable. - // If the array comes from a load we may potentially being mutating an array at a reference - // that is loaded from by other values. - let terminator = dfg[block_id].unwrap_terminator(); - // If we are in a return block we are not concerned about the array potentially being mutated again. - let is_return_block = matches!(terminator, TerminatorInstruction::Return { .. }); - // We also want to check that the array is not part of the terminator arguments, as this means it is used again. - let mut array_in_terminator = false; - terminator.for_each_value(|value| { - if value == array { - array_in_terminator = true; + if let Some(existing) = self.array_to_last_use.insert(array, *instruction_id) { + self.instructions_that_can_be_made_mutable.remove(&existing); + } + if self.is_brillig_runtime { + let value = self.dfg.resolve(*value); + + if let Some(existing) = self.inner_nested_arrays.get(&value) { + self.instructions_that_can_be_made_mutable.remove(existing); + } + let result = self.dfg.instruction_results(*instruction_id)[0]; + self.inner_nested_arrays.insert(result, *instruction_id); } - }); - if (!arrays_from_load.contains(&array) || is_return_block) && !array_in_terminator { - instructions_that_can_be_made_mutable.insert(*instruction_id); - } - } - Instruction::Call { arguments, .. } => { - for argument in arguments { - if matches!(dfg.type_of_value(*argument), Array { .. } | Slice { .. }) { - let argument = dfg.resolve(*argument); - if let Some(existing) = array_to_last_use.insert(argument, *instruction_id) + // If the array we are setting does not come from a load we can safely mark it mutable. + // If the array comes from a load we may potentially being mutating an array at a reference + // that is loaded from by other values. + let terminator = self.dfg[block_id].unwrap_terminator(); + // If we are in a return block we are not concerned about the array potentially being mutated again. + let is_return_block = + matches!(terminator, TerminatorInstruction::Return { .. }); + // We also want to check that the array is not part of the terminator arguments, as this means it is used again. + let mut array_in_terminator = false; + terminator.for_each_value(|value| { + if value == array { + array_in_terminator = true; + } + }); + if (!self.arrays_from_load.contains(&array) || is_return_block) + && !array_in_terminator + { + self.instructions_that_can_be_made_mutable.insert(*instruction_id); + } + } + Instruction::Call { arguments, .. } => { + for argument in arguments { + if matches!(self.dfg.type_of_value(*argument), Array { .. } | Slice { .. }) { - instructions_that_can_be_made_mutable.remove(&existing); + let argument = self.dfg.resolve(*argument); + + if let Some(existing) = + self.array_to_last_use.insert(argument, *instruction_id) + { + self.instructions_that_can_be_made_mutable.remove(&existing); + } } } } - } - Instruction::Load { .. } => { - let result = dfg.instruction_results(*instruction_id)[0]; - if matches!(dfg.type_of_value(result), Array { .. } | Slice { .. }) { - arrays_from_load.insert(result); + Instruction::Load { .. } => { + let result = self.dfg.instruction_results(*instruction_id)[0]; + if matches!(self.dfg.type_of_value(result), Array { .. } | Slice { .. }) { + self.arrays_from_load.insert(result); + } } + _ => (), } - _ => (), } } } From dfeb1c51c564ec345978a9a0efef3e4e96ab638a Mon Sep 17 00:00:00 2001 From: Maxim Vezenov Date: Wed, 2 Oct 2024 14:29:49 -0400 Subject: [PATCH 35/36] feat(perf): Optimize array set from get (#6207) # Description ## Problem\* Part of general effort to reduce Brillig bytecode sizes ## Summary\* We often times (usually when working with nested arrays) run into patterns where we fetch an inner nested array dynamically, array set a different array in the nested array, and the reset the other unchanged nested array at the same index. These array sets can be optimized out. Here is an example of what happens on master in SSA. Looking at `nested_array_dynamic` with `--force-brillig`. Inside `b14` you will see the following: ``` v114 = array_get v110, index v113 v115 = add v113, u32 1 v116 = add v113, u32 2 v117 = array_get v110, index v116 v118 = array_set v110, index v113, value v114 ``` After this PR `v118 = array_set v110, index v113, value v114` will simplify to `v110`. If `v114` is unused anywhere else in the function (which in this case it is) it will be removed by DIE. ``` v114 = add v113, u32 1 v115 = add v113, u32 2 v116 = array_get v110, index v115 ``` ## Additional Context Similarly to `try_optimize_array_get_from_previous_set` we should be able to follow past `array_sets` when optimizing these `array_sets`. e.g. if we see a pattern like this: ``` v116 = array_get v110, index v115 v120 = array_set v110, index v114, value [Field 100, Field 101, Field 102] v122 = array_set mut v120, index v115, value v116 ``` We know that we performed `array_set` on the same `v110` array, however, it was at a different index. So even though `v120` is different than `v110` we can simplify `v122 = array_set mut v120, index v115, value v116` to `v120`. I am working on this in a follow-up, as the simple change in this PR provides decent benefits on its own. ## Documentation\* Check one: - [X] No documentation needed. - [ ] Documentation included in this PR. - [ ] **[For Experimental Features]** Documentation to be submitted in a separate PR. # PR Checklist\* - [X] I have tested the changes locally. - [X] I have formatted the changes with [Prettier](https://prettier.io/) and/or `cargo fmt` on default settings. --- .../src/brillig/brillig_ir/entry_point.rs | 2 +- .../noirc_evaluator/src/ssa/ir/instruction.rs | 30 ++++++++++++++++--- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/compiler/noirc_evaluator/src/brillig/brillig_ir/entry_point.rs b/compiler/noirc_evaluator/src/brillig/brillig_ir/entry_point.rs index ff9b5ea67eb..c28e9c66011 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_ir/entry_point.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_ir/entry_point.rs @@ -9,7 +9,7 @@ use super::{ }; use acvm::acir::{brillig::MemoryAddress, AcirField}; -pub(crate) const MAX_STACK_SIZE: usize = 2048; +pub(crate) const MAX_STACK_SIZE: usize = 32768; pub(crate) const MAX_SCRATCH_SPACE: usize = 64; impl BrilligContext { diff --git a/compiler/noirc_evaluator/src/ssa/ir/instruction.rs b/compiler/noirc_evaluator/src/ssa/ir/instruction.rs index e30707effed..676bb48c4d9 100644 --- a/compiler/noirc_evaluator/src/ssa/ir/instruction.rs +++ b/compiler/noirc_evaluator/src/ssa/ir/instruction.rs @@ -630,9 +630,9 @@ impl Instruction { } } Instruction::ArraySet { array, index, value, .. } => { - let array = dfg.get_array_constant(*array); - let index = dfg.get_numeric_constant(*index); - if let (Some((array, element_type)), Some(index)) = (array, index) { + let array_const = dfg.get_array_constant(*array); + let index_const = dfg.get_numeric_constant(*index); + if let (Some((array, element_type)), Some(index)) = (array_const, index_const) { let index = index.try_to_u32().expect("Expected array index to fit in u32") as usize; @@ -641,7 +641,8 @@ impl Instruction { return SimplifiedTo(new_array); } } - None + + try_optimize_array_set_from_previous_get(dfg, *array, *index, *value) } Instruction::Truncate { value, bit_size, max_bit_size } => { if bit_size == max_bit_size { @@ -817,6 +818,27 @@ fn try_optimize_array_get_from_previous_set( SimplifyResult::None } +fn try_optimize_array_set_from_previous_get( + dfg: &DataFlowGraph, + array_id: ValueId, + target_index: ValueId, + target_value: ValueId, +) -> SimplifyResult { + match &dfg[target_value] { + Value::Instruction { instruction, .. } => match &dfg[*instruction] { + Instruction::ArrayGet { array, index } => { + if *array == array_id && *index == target_index { + SimplifyResult::SimplifiedTo(array_id) + } else { + SimplifyResult::None + } + } + _ => SimplifyResult::None, + }, + _ => SimplifyResult::None, + } +} + pub(crate) type ErrorType = HirType; pub(crate) fn error_selector_from_type(typ: &ErrorType) -> ErrorSelector { From 76eec710ff73e5e45fdddcd41ae2cd74e879cfa5 Mon Sep 17 00:00:00 2001 From: saleel Date: Thu, 3 Oct 2024 01:18:52 +0530 Subject: [PATCH 36/36] fix: ignore compression of blocks after msg.len in sha256_var (#6206) # Description Fix an issue where `sha256_var` produces wrong results for messages with larger paddings ## Problem\* Resolves #6163 ## Summary\* `h = sha256_compression(msg_u8_to_u32(msg_block), h)` was run for bytes after length as well. This block is moved to `if msg_start < message_size` block ## Documentation\* Check one: - [ ] No documentation needed. - [ ] Documentation included in this PR. - [ ] **[For Experimental Features]** 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. --- noir_stdlib/src/hash/sha256.nr | 36 ++++++++++++++++++- .../sha256_var_padding_regression/Nargo.toml | 7 ++++ .../sha256_var_padding_regression/Prover.toml | 2 ++ .../sha256_var_padding_regression/src/main.nr | 29 +++++++++++++++ 4 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 test_programs/execution_success/sha256_var_padding_regression/Nargo.toml create mode 100644 test_programs/execution_success/sha256_var_padding_regression/Prover.toml create mode 100644 test_programs/execution_success/sha256_var_padding_regression/src/main.nr diff --git a/noir_stdlib/src/hash/sha256.nr b/noir_stdlib/src/hash/sha256.nr index 413c26d6f6b..081f7deb0fa 100644 --- a/noir_stdlib/src/hash/sha256.nr +++ b/noir_stdlib/src/hash/sha256.nr @@ -110,7 +110,7 @@ pub fn sha256_var(msg: [u8; N], message_size: u64) -> [u8; 32] { // If the block is filled, compress it. // An un-filled block is handled after this loop. - if msg_byte_ptr == BLOCK_SIZE { + if (msg_start < message_size) & (msg_byte_ptr == BLOCK_SIZE) { h = sha256_compression(msg_u8_to_u32(msg_block), h); } } @@ -335,4 +335,38 @@ mod tests { ]; assert_eq(sha256_var(input, input.len() as u64), result); } + + #[test] + fn same_msg_len_variable_padding() { + let input = [ + 29, 81, 165, 84, 243, 114, 101, 37, 242, 146, 127, 99, 69, 145, 39, 72, 213, 39, 253, 179, 218, 37, 217, 201, 172, 93, 198, 50, 249, 70, 15, 30, 162, 112, 187, 40, 140, 9, 236, 53, 32, 44, 38, 163, 113, 254, 192, 197, 44, 89, 71, 130, 169, 242, 17, 211, 214, 72, 19, 178, 186, 168, 147, 127, 99, 101, 252, 227, 8, 147, 150, 85, 97, 158, 17, 107, 218, 244, 82, 113, 247, 91, 208, 214, 60, 244, 87, 137, 173, 201, 130, 18, 66, 56, 198, 149, 207, 189, 175, 120, 123, 224, 177, 167, 251, 159, 143, 110, 68, 183, 189, 70, 126, 32, 35, 164, 44, 30, 44, 12, 65, 18, 62, 239, 242, 2, 248, 104, 2, 178, 64, 28, 126, 36, 137, 24, 14, 116, 91, 98, 90, 159, 218, 102, 45, 11, 110, 223, 245, 184, 52, 99, 59, 245, 136, 175, 3, 72, 164, 146, 145, 116, 22, 66, 24, 49, 193, 121, 3, 60, 37, 41, 97, 3, 190, 66, 195, 225, 63, 46, 3, 118, 4, 208, 15, 1, 40, 254, 235, 151, 123, 70, 180, 170, 44, 172, 90, 4, 254, 53, 239, 116, 246, 67, 56, 129, 61, 22, 169, 213, 65, 27, 216, 116, 162, 239, 214, 207, 126, 177, 20, 100, 25, 48, 143, 84, 215, 70, 197, 53, 65, 70, 86, 172, 61, 62, 9, 212, 167, 169, 133, 41, 126, 213, 196, 33, 192, 238, 0, 63, 246, 215, 58, 128, 110, 101, 92, 3, 170, 214, 130, 149, 52, 81, 125, 118, 233, 3, 118, 193, 104, 207, 120, 115, 77, 253, 191, 122, 0, 107, 164, 207, 113, 81, 169, 36, 201, 228, 74, 134, 131, 218, 178, 35, 30, 216, 101, 2, 103, 174, 87, 95, 50, 50, 215, 157, 5, 210, 188, 54, 211, 78, 45, 199, 96, 121, 241, 241, 176, 226, 194, 134, 130, 89, 217, 210, 186, 32, 140, 39, 91, 103, 212, 26, 87, 32, 72, 144, 228, 230, 117, 99, 188, 50, 15, 69, 79, 179, 50, 12, 106, 86, 218, 101, 73, 142, 243, 29, 250, 122, 228, 233, 29, 255, 22, 121, 114, 125, 103, 41, 250, 241, 179, 126, 158, 198, 116, 209, 65, 94, 98, 228, 175, 169, 96, 3, 9, 233, 133, 214, 55, 161, 164, 103, 80, 85, 24, 186, 64, 167, 92, 131, 53, 101, 202, 47, 25, 104, 118, 155, 14, 12, 12, 25, 116, 45, 221, 249, 28, 246, 212, 200, 157, 167, 169, 56, 197, 181, 4, 245, 146, 1, 140, 234, 191, 212, 228, 125, 87, 81, 86, 119, 30, 63, 129, 143, 32, 96 + ]; + + // Prepare inputs of different lengths + let mut input_511 = [0; 511]; + let mut input_512 = [0; 512]; // Next block + let mut input_575 = [0; 575]; + let mut input_576 = [0; 576]; // Next block + for i in 0..input.len() { + input_511[i] = input[i]; + input_512[i] = input[i]; + input_575[i] = input[i]; + input_576[i] = input[i]; + } + + // Compute hashes of all inputs (with same message length) + let fixed_length_hash = super::sha256(input); + let var_full_length_hash = sha256_var(input, input.len() as u64); + let var_length_hash_511 = sha256_var(input_511, input.len() as u64); + let var_length_hash_512 = sha256_var(input_512, input.len() as u64); + let var_length_hash_575 = sha256_var(input_575, input.len() as u64); + let var_length_hash_576 = sha256_var(input_576, input.len() as u64); + + // All of the above should have produced the same hash + assert_eq(var_full_length_hash, fixed_length_hash); + assert_eq(var_length_hash_511, fixed_length_hash); + assert_eq(var_length_hash_512, fixed_length_hash); + assert_eq(var_length_hash_575, fixed_length_hash); + assert_eq(var_length_hash_576, fixed_length_hash); + } } diff --git a/test_programs/execution_success/sha256_var_padding_regression/Nargo.toml b/test_programs/execution_success/sha256_var_padding_regression/Nargo.toml new file mode 100644 index 00000000000..a80677c585d --- /dev/null +++ b/test_programs/execution_success/sha256_var_padding_regression/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "sha256_var_padding_regression" +type = "bin" +authors = [""] +compiler_version = ">=0.34.0" + +[dependencies] \ No newline at end of file diff --git a/test_programs/execution_success/sha256_var_padding_regression/Prover.toml b/test_programs/execution_success/sha256_var_padding_regression/Prover.toml new file mode 100644 index 00000000000..7b20e870128 --- /dev/null +++ b/test_programs/execution_success/sha256_var_padding_regression/Prover.toml @@ -0,0 +1,2 @@ +preimage = [29, 81, 165, 84, 243, 114, 101, 37, 242, 146, 127, 99, 69, 145, 39, 72, 213, 39, 253, 179, 218, 37, 217, 201, 172, 93, 198, 50, 249, 70, 15, 30, 162, 112, 187, 40, 140, 9, 236, 53, 32, 44, 38, 163, 113, 254, 192, 197, 44, 89, 71, 130, 169, 242, 17, 211, 214, 72, 19, 178, 186, 168, 147, 127, 99, 101, 252, 227, 8, 147, 150, 85, 97, 158, 17, 107, 218, 244, 82, 113, 247, 91, 208, 214, 60, 244, 87, 137, 173, 201, 130, 18, 66, 56, 198, 149, 207, 189, 175, 120, 123, 224, 177, 167, 251, 159, 143, 110, 68, 183, 189, 70, 126, 32, 35, 164, 44, 30, 44, 12, 65, 18, 62, 239, 242, 2, 248, 104, 2, 178, 64, 28, 126, 36, 137, 24, 14, 116, 91, 98, 90, 159, 218, 102, 45, 11, 110, 223, 245, 184, 52, 99, 59, 245, 136, 175, 3, 72, 164, 146, 145, 116, 22, 66, 24, 49, 193, 121, 3, 60, 37, 41, 97, 3, 190, 66, 195, 225, 63, 46, 3, 118, 4, 208, 15, 1, 40, 254, 235, 151, 123, 70, 180, 170, 44, 172, 90, 4, 254, 53, 239, 116, 246, 67, 56, 129, 61, 22, 169, 213, 65, 27, 216, 116, 162, 239, 214, 207, 126, 177, 20, 100, 25, 48, 143, 84, 215, 70, 197, 53, 65, 70, 86, 172, 61, 62, 9, 212, 167, 169, 133, 41, 126, 213, 196, 33, 192, 238, 0, 63, 246, 215, 58, 128, 110, 101, 92, 3, 170, 214, 130, 149, 52, 81, 125, 118, 233, 3, 118, 193, 104, 207, 120, 115, 77, 253, 191, 122, 0, 107, 164, 207, 113, 81, 169, 36, 201, 228, 74, 134, 131, 218, 178, 35, 30, 216, 101, 2, 103, 174, 87, 95, 50, 50, 215, 157, 5, 210, 188, 54, 211, 78, 45, 199, 96, 121, 241, 241, 176, 226, 194, 134, 130, 89, 217, 210, 186, 32, 140, 39, 91, 103, 212, 26, 87, 32, 72, 144, 228, 230, 117, 99, 188, 50, 15, 69, 79, 179, 50, 12, 106, 86, 218, 101, 73, 142, 243, 29, 250, 122, 228, 233, 29, 255, 22, 121, 114, 125, 103, 41, 250, 241, 179, 126, 158, 198, 116, 209, 65, 94, 98, 228, 175, 169, 96, 3, 9, 233, 133, 214, 55, 161, 164, 103, 80, 85, 24, 186, 64, 167, 92, 131, 53, 101, 202, 47, 25, 104, 118, 155, 14, 12, 12, 25, 116, 45, 221, 249, 28, 246, 212, 200, 157, 167, 169, 56, 197, 181, 4, 245, 146, 1, 140, 234, 191, 212, 228, 125, 87, 81, 86, 119, 30, 63, 129, 143, 32, 96] +result = [205, 74, 73, 134, 202, 93, 199, 152, 171, 244, 133, 193, 132, 40, 42, 9, 248, 11, 99, 200, 135, 58, 220, 227, 45, 253, 183, 241, 69, 69, 80, 219] \ No newline at end of file diff --git a/test_programs/execution_success/sha256_var_padding_regression/src/main.nr b/test_programs/execution_success/sha256_var_padding_regression/src/main.nr new file mode 100644 index 00000000000..13f87a0efc5 --- /dev/null +++ b/test_programs/execution_success/sha256_var_padding_regression/src/main.nr @@ -0,0 +1,29 @@ +// Test to check sha256_var produces same results irrespective of number of padding bytes after message.length +// Ref: https://github.com/noir-lang/noir/issues/6163, https://gist.github.com/jp4g/d5953faae9eadb2909357474f7901e58 +fn main(preimage: [u8; 448], result: [u8; 32]) { + // Construct arrays of different lengths + let mut preimage_511 = [0; 511]; + let mut preimage_512 = [0; 512]; // Next block + let mut preimage_575 = [0; 575]; + let mut preimage_576 = [0; 576]; // Next block + for i in 0..preimage.len() { + preimage_511[i] = preimage[i]; + preimage_512[i] = preimage[i]; + preimage_575[i] = preimage[i]; + preimage_576[i] = preimage[i]; + } + let fixed_length_hash = std::hash::sha256::digest(preimage); + let var_full_length_hash = std::hash::sha256::sha256_var(preimage, preimage.len() as u64); + let var_length_hash_511 = std::hash::sha256::sha256_var(preimage_511, preimage.len() as u64); + let var_length_hash_512 = std::hash::sha256::sha256_var(preimage_512, preimage.len() as u64); + let var_length_hash_575 = std::hash::sha256::sha256_var(preimage_575, preimage.len() as u64); + let var_length_hash_576 = std::hash::sha256::sha256_var(preimage_576, preimage.len() as u64); + + // All of the above should have produced the same hash (result) + assert(fixed_length_hash == result); + assert(var_full_length_hash == result); + assert(var_length_hash_511 == result); + assert(var_length_hash_512 == result); + assert(var_length_hash_575 == result); + assert(var_length_hash_576 == result); +}