Skip to content

Commit 4aa11bc

Browse files
committed
chore: comptime optimizations
This PR includes some refactors to our comptime code so it's interpreted faster: 1. a `CHashMap` type is introduced. This is a dummy HashMap that does lookups by traversing an entire list. At first this might seem slower. However, `UHashMap` does a lot of things to get or insert a key (it hashes is, computing the hash using poseidon2, then does a linear probing, etc.), and most of that time is then spent in the interpreter and not actually in the logic. The linear logic ends up being much faster, even for big maps (and here the maps are small, but it still makes a difference). See also [this PR](noir-lang/noir#11384). 2. Use `CtString` more to build function signatures and so on. This is a bit more efficient than working with `Quoted` 3. Eventually I removed `AsStrQuote`. There are two places where we need the length of a `CtString` but then I inlined the code (it's a one-line thing). For `token_contract`, the time to re-check main in LSP goes from 270ms to 220ms. 50ms is not much, but from an IDE-perspective it is. Then `nargo check` for `token_contract` goes from 1.13 seconds to 1.07 seconds. Again, not a huge win, but it's noticeable in the IDE. (this should improve the LSP speed of all contracts, not just token_contract, but that's one of the biggest ones so I always use it to try things on)
1 parent 8e7e4a4 commit 4aa11bc

File tree

15 files changed

+125
-107
lines changed

15 files changed

+125
-107
lines changed

noir-projects/aztec-nr/aztec/src/macros/authorization.nr

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
1-
use crate::macros::utils::{compute_struct_selector, derive_serialize_if_not_implemented, get_trait_impl_method};
2-
use poseidon::poseidon2::Poseidon2Hasher;
3-
use std::{collections::umap::UHashMap, hash::BuildHasherDefault};
1+
use crate::{
2+
macros::utils::{compute_struct_selector, derive_serialize_if_not_implemented, get_trait_impl_method},
3+
utils::cmap::CHashMap,
4+
};
45

56
/// Hashmap that stores authorization structs indexed by their selectors, so no duplicates are inadvertedly created by
67
/// developers
7-
comptime mut global AUTH_TYPES: UHashMap<Field, TypeDefinition, BuildHasherDefault<Poseidon2Hasher>> =
8-
UHashMap::default();
8+
comptime mut global AUTH_TYPES: CHashMap<Field, TypeDefinition> = CHashMap::new();
99

1010
comptime fn register_authorization(authorization_selector: Field, authorization_struct: TypeDefinition) {
11-
if AUTH_TYPES.contains_key(authorization_selector) {
12-
let existing_authorization = AUTH_TYPES.get(authorization_selector).unwrap().name();
11+
let existing_authorization = AUTH_TYPES.get(authorization_selector);
12+
if existing_authorization.is_some() {
13+
let existing_authorization = existing_authorization.unwrap().name();
1314
let authorization_name = authorization_struct.name();
1415
panic(
1516
f"Selector collision detected between authorizations '{authorization_name}' and '{existing_authorization}'",

noir-projects/aztec-nr/aztec/src/macros/calls_generation/external_functions_stubs.nr

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// of manually serializing arguments and creating call interfaces, stubs allow natural syntax, e.g. for ! enqueuing
33
// calls to public functions: ! ! ExternalContract.at(address).some_method(arg1, arg2).enqueue()
44

5-
use crate::macros::utils::{AsStrQuote, compute_fn_selector};
5+
use crate::macros::utils::compute_fn_selector;
66
use crate::protocol::meta::utils::derive_serialization_quotes;
77
use std::meta::unquote;
88

@@ -36,7 +36,7 @@ comptime fn create_stub_base(f: FunctionDefinition) -> (Quoted, Quoted, Quoted,
3636
derive_serialization_quotes(fn_parameters, false);
3737
let serialized_args_array_len: u32 = unquote!(quote { ($serialized_args_array_len_quote) as u32 });
3838

39-
let (fn_name_str, _) = fn_name.as_str_quote();
39+
let fn_name_str = f"\"{fn_name}\"".quoted_contents();
4040
let fn_name_len: u32 = unquote!(quote { $fn_name_str.as_bytes().len()});
4141
let fn_selector: Field = compute_fn_selector(f);
4242

noir-projects/aztec-nr/aztec/src/macros/dispatch.nr

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
use crate::macros::internals_functions_generation::external_functions_registry::get_public_functions;
22
use crate::protocol::meta::utils::get_params_len_quote;
3+
use crate::utils::cmap::CHashMap;
34
use super::utils::compute_fn_selector;
4-
use poseidon::poseidon2::Poseidon2Hasher;
5-
use std::{collections::umap::UHashMap, hash::BuildHasherDefault, panic};
5+
use std::panic;
66

77
/// Returns an `fn public_dispatch(...)` function for the given module that's assumed to be an Aztec contract.
88
pub comptime fn generate_public_dispatch(m: Module) -> Quoted {
99
let functions = get_public_functions(m);
1010

1111
let unit = get_type::<()>();
1212

13-
let seen_selectors = &mut UHashMap::<Field, Quoted, BuildHasherDefault<Poseidon2Hasher>>::default();
13+
let seen_selectors = &mut CHashMap::<Field, Quoted>::new();
1414

1515
let ifs = functions.map(|function: FunctionDefinition| {
1616
let parameters = function.parameters();
@@ -21,8 +21,9 @@ pub comptime fn generate_public_dispatch(m: Module) -> Quoted {
2121

2222
// Since function selectors are computed as the first 4 bytes of the hash of the function signature, it's
2323
// possible to have collisions. With the following check, we ensure it doesn't happen within the same contract.
24-
if seen_selectors.contains_key(selector) {
25-
let existing_fn = seen_selectors.get(selector).unwrap();
24+
let existing_fn = seen_selectors.get(selector);
25+
if existing_fn.is_some() {
26+
let existing_fn = existing_fn.unwrap();
2627
panic(
2728
f"Public function selector collision detected between functions '{fn_name}' and '{existing_fn}'",
2829
);

noir-projects/aztec-nr/aztec/src/macros/events.nr

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
use crate::macros::utils::{compute_struct_selector, derive_serialize_if_not_implemented, get_trait_impl_method};
2-
use poseidon::poseidon2::Poseidon2Hasher;
3-
use std::{collections::umap::UHashMap, hash::BuildHasherDefault, panic};
1+
use crate::{
2+
macros::utils::{compute_struct_selector, derive_serialize_if_not_implemented, get_trait_impl_method},
3+
utils::cmap::CHashMap,
4+
};
5+
use std::panic;
46

57
/// A map from event selector to event name indicating whether the event selector has already been seen during the
68
/// contract compilation - prevents event selector collisions.
7-
pub comptime mut global EVENT_SELECTORS: UHashMap<Field, Quoted, BuildHasherDefault<Poseidon2Hasher>> =
8-
UHashMap::default();
9+
pub comptime mut global EVENT_SELECTORS: CHashMap<Field, Quoted> = CHashMap::new();
910

1011
comptime fn generate_event_interface_and_get_selector(s: TypeDefinition) -> (Quoted, Field) {
1112
let name = s.name();
@@ -31,8 +32,9 @@ comptime fn generate_event_interface_and_get_selector(s: TypeDefinition) -> (Quo
3132
}
3233

3334
comptime fn register_event_selector(event_selector: Field, event_name: Quoted) {
34-
if EVENT_SELECTORS.contains_key(event_selector) {
35-
let existing_event = EVENT_SELECTORS.get(event_selector).unwrap();
35+
let existing_event = EVENT_SELECTORS.get(event_selector);
36+
if existing_event.is_some() {
37+
let existing_event = existing_event.unwrap();
3638
panic(
3739
f"Event selector collision detected between events '{event_name}' and '{existing_event}'",
3840
);
Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
use poseidon::poseidon2::Poseidon2Hasher;
2-
use std::{collections::umap::UHashMap, hash::BuildHasherDefault};
1+
use crate::utils::cmap::CHashMap;
32

43
/// Registers the functions that have the `#[authorize_once(...)]` macro, and the parameters the macro was invoked
54
/// with. This is used to later inject the authorization check (see ./utils.nr -> create_authorize_once_check) via the
65
/// #[external("private")] or #[external("public")] macros. The `#[authorize_once(...)]` macro is not used directly to
76
/// inject the check because it has to be placed after context instantiation but before the function body, which only
87
/// the aforementioned macros can do.
9-
pub(crate) comptime mut global AUTHORIZE_ONCE_REGISTRY: UHashMap<FunctionDefinition, (CtString, CtString), BuildHasherDefault<Poseidon2Hasher>> =
10-
UHashMap::default();
8+
pub(crate) comptime mut global AUTHORIZE_ONCE_REGISTRY: CHashMap<FunctionDefinition, (CtString, CtString)> =
9+
CHashMap::new();

noir-projects/aztec-nr/aztec/src/macros/internals_functions_generation/external/helpers.nr

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ use crate::macros::{
22
functions::auth_registry::AUTHORIZE_ONCE_REGISTRY,
33
utils::{is_fn_initializer, is_fn_only_self, is_fn_view},
44
};
5-
use std::meta::ctstring::AsCtString;
65

76
/// Gathers all attributes relevant to the function's ABI and returns a quote that can be applied to the newly
87
/// generated function. We apply the abi marker attributes instead of the original ones (e.g. abi_view instead of view)
@@ -98,7 +97,6 @@ pub(crate) comptime fn create_authorize_once_check(f: FunctionDefinition, is_pri
9897
quote { aztec::authwit::auth::assert_current_call_valid_authwit_public }
9998
};
10099
let invalid_nonce_message = f"Invalid authwit nonce. When '{from_arg_name}' and 'msg_sender' are the same, '{nonce_arg_name}' must be zero"
101-
.as_ctstring()
102100
.as_quoted_str();
103101
quote {
104102
if (!$from_arg_name_quoted.eq(self.msg_sender())) {

noir-projects/aztec-nr/aztec/src/macros/internals_functions_generation/external/private.nr

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use crate::macros::{
66
},
77
};
88
use crate::protocol::meta::utils::derive_serialization_quotes;
9-
use std::meta::{ctstring::AsCtString, type_of};
9+
use std::meta::type_of;
1010

1111
pub(crate) comptime fn generate_private_external(f: FunctionDefinition) -> Quoted {
1212
let module_has_initializer = module_has_initializer(f.module());
@@ -70,8 +70,7 @@ pub(crate) comptime fn generate_private_external(f: FunctionDefinition) -> Quote
7070
};
7171

7272
let view_check = if is_fn_view(f) {
73-
let assertion_message =
74-
f"Function {original_function_name} can only be called statically".as_ctstring().as_quoted_str();
73+
let assertion_message = f"Function {original_function_name} can only be called statically".as_quoted_str();
7574
quote { assert(self.context.inputs.call_context.is_static_call, $assertion_message); }
7675
} else {
7776
quote {}

noir-projects/aztec-nr/aztec/src/macros/internals_functions_generation/external/public.nr

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ use crate::macros::{
55
module_has_initializer, module_has_storage,
66
},
77
};
8-
use std::meta::ctstring::AsCtString;
98

109
pub(crate) comptime fn generate_public_external(f: FunctionDefinition) -> Quoted {
1110
let module_has_initializer = module_has_initializer(f.module());
@@ -71,8 +70,7 @@ pub(crate) comptime fn generate_public_external(f: FunctionDefinition) -> Quoted
7170
};
7271

7372
let view_check = if is_fn_view(f) {
74-
let assertion_message =
75-
f"Function {original_function_name} can only be called statically".as_ctstring().as_quoted_str();
73+
let assertion_message = f"Function {original_function_name} can only be called statically".as_quoted_str();
7674
quote { assert(self.context.is_static_call(), $assertion_message); }
7775
} else {
7876
quote {}

noir-projects/aztec-nr/aztec/src/macros/internals_functions_generation/external_functions_registry.nr

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,14 @@
66
//! macro, because at that point we do not have the information about whether a given external function is private or
77
//! public. We only see the internal attribute itself, not the "private" or "public" argument.
88

9-
use poseidon::poseidon2::Poseidon2Hasher;
10-
use std::{collections::umap::UHashMap, hash::BuildHasherDefault};
9+
use crate::utils::cmap::CHashMap;
1110

1211
// Key is a contract module, value is an array of function definitions.
13-
comptime mut global PRIVATE_REGISTRY: UHashMap<Module, [FunctionDefinition], BuildHasherDefault<Poseidon2Hasher>> =
14-
UHashMap::default();
15-
comptime mut global PUBLIC_REGISTRY: UHashMap<Module, [FunctionDefinition], BuildHasherDefault<Poseidon2Hasher>> =
16-
UHashMap::default();
17-
comptime mut global UTILITY_REGISTRY: UHashMap<Module, [FunctionDefinition], BuildHasherDefault<Poseidon2Hasher>> =
18-
UHashMap::default();
19-
20-
comptime fn add_to_registry(
21-
registry: &mut UHashMap<Module, [FunctionDefinition], BuildHasherDefault<Poseidon2Hasher>>,
22-
f: FunctionDefinition,
23-
) {
12+
comptime mut global PRIVATE_REGISTRY: CHashMap<Module, [FunctionDefinition]> = CHashMap::new();
13+
comptime mut global PUBLIC_REGISTRY: CHashMap<Module, [FunctionDefinition]> = CHashMap::new();
14+
comptime mut global UTILITY_REGISTRY: CHashMap<Module, [FunctionDefinition]> = CHashMap::new();
15+
16+
comptime fn add_to_registry(registry: &mut CHashMap<Module, [FunctionDefinition]>, f: FunctionDefinition) {
2417
let module = f.module();
2518
let current_functions = registry.get(module);
2619
let functions_to_insert = if current_functions.is_some() {

noir-projects/aztec-nr/aztec/src/macros/internals_functions_generation/internal_functions_registry.nr

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,13 @@
55
//! macro, because at that point we do not have the information about whether a given internal function is private or
66
//! public. We only see the internal attribute itself, not the "private" or "public" argument.
77

8-
use poseidon::poseidon2::Poseidon2Hasher;
9-
use std::{collections::umap::UHashMap, hash::BuildHasherDefault};
8+
use crate::utils::cmap::CHashMap;
109

1110
// Key is a contract module, value is an array of function definitions.
12-
comptime mut global PRIVATE_INTERNAL_REGISTRY: UHashMap<Module, [FunctionDefinition], BuildHasherDefault<Poseidon2Hasher>> =
13-
UHashMap::default();
14-
comptime mut global PUBLIC_INTERNAL_REGISTRY: UHashMap<Module, [FunctionDefinition], BuildHasherDefault<Poseidon2Hasher>> =
15-
UHashMap::default();
11+
comptime mut global PRIVATE_INTERNAL_REGISTRY: CHashMap<Module, [FunctionDefinition]> = CHashMap::new();
12+
comptime mut global PUBLIC_INTERNAL_REGISTRY: CHashMap<Module, [FunctionDefinition]> = CHashMap::new();
1613

17-
comptime fn add_to_registry(
18-
registry: &mut UHashMap<Module, [FunctionDefinition], BuildHasherDefault<Poseidon2Hasher>>,
19-
f: FunctionDefinition,
20-
) {
14+
comptime fn add_to_registry(registry: &mut CHashMap<Module, [FunctionDefinition]>, f: FunctionDefinition) {
2115
let module = f.module();
2216
let current_functions = registry.get(module);
2317
let functions_to_insert = if current_functions.is_some() {

0 commit comments

Comments
 (0)