chore(aztec-nr): mark emit_event_in_public as #[inline_never] to shrink public dispatch#23161
Conversation
Prevents per-call-site inlining of the public emit helper. On public_fns_with_emit_repro_contract, public_dispatch packed bytecode drops from 5,576 to 4,601 bytes (-975, -17.5%) -- roughly 65 bytes per emit call site across 15 functions. The attribute requires unconstrained, so emit_event_in_public is marked unconstrained and ContractSelfPublic::emit follows. Mirrors the existing pattern on assert_is_initialized_public. CLI --inliner-aggressiveness cannot achieve this on its own: for public_dispatch the noir driver pins aggressiveness to 0, and inlining still fires from the is_simple_function arm. #[inline_never] is the only short-circuit, and it wins even for single-caller helpers.
| /// Public event emission is achieved by emitting public transaction logs. A total of `N+1` fields are emitted, | ||
| /// where `N` is the serialization length of the event. | ||
| pub fn emit<Event>(&mut self, event: Event) | ||
| pub unconstrained fn emit<Event>(&mut self, event: Event) |
There was a problem hiding this comment.
We could just call the inner emit_event_in_public method with unsafe (we need emit_event_in_public to be unconstrained as #[inline_never] is an unconstrained-only attribute in Noir). I figured as this is on ContractSelfPublic it was actually more accurate to have it be marked unconstrained explicitly.
There was a problem hiding this comment.
If we add this here, aren't we forcing all contracts to add a safety tag when emitting events?
There was a problem hiding this comment.
The doc comments on this type state: "Core interface for interacting with aztec-nr contract features in public execution contexts." I was going off the assumption that ContractSelfPublic would only ever be used in the public runtime which is always unconstrained Noir. This looks to be the case for all our tests (which is what I would expect).
There was a problem hiding this comment.
Yes, the fucntions themselves are not because it confuses people. It's a long-standing issue: Noir has two execution modes, but Aztec has three. It makes me profoundly unhappy. Developers are taught that unconstraed == scary, and so public functions being unconstrained seems odd. They're actually constrained, it's just that the constraining happens by the AVM.
| /// Public event emission is achieved by emitting public transaction logs. A total of `N+1` fields are emitted, | ||
| /// where `N` is the serialization length of the event. | ||
| pub fn emit<Event>(&mut self, event: Event) | ||
| pub unconstrained fn emit<Event>(&mut self, event: Event) |
There was a problem hiding this comment.
Yes, the fucntions themselves are not because it confuses people. It's a long-standing issue: Noir has two execution modes, but Aztec has three. It makes me profoundly unhappy. Developers are taught that unconstraed == scary, and so public functions being unconstrained seems odd. They're actually constrained, it's just that the constraining happens by the AVM.
…nk public dispatch (#23161) ## Summary Work towards resolving AztecProtocol/aztec-nr#35 - Mark `emit_event_in_public` `#[inline_never]` (and `unconstrained`, required by the attribute) so it stops being copied into every public call site. - Propagate `unconstrained` to `ContractSelfPublic::emit` so it can still call the helper. - Mirrors the existing precedent on `assert_is_initialized_public` (`aztec-nr/aztec/src/macros/functions/initialization_utils.nr:100`) from #22869 ## Bytecode impact Measured via `nargo compile --inliner-aggressiveness 0` + `bb aztec_process`, reading `public_dispatch` packed bytecode size on `public_fns_with_emit_repro_contract`: | State | AVM bytes | Δ | |-------------------|-----------|----------------| | Baseline | 5,576 | — | | `#[inline_never]` | 4,601 | -975 (-17.5%) | Roughly 65 bytes saved per emit call site across the 15 functions. Real contracts with many public functions emitting the same event shape should see the same per-site savings. ## Additional Context - `--inliner-aggressiveness` cannot achieve this on its own: for `public_dispatch` the noirc driver pins aggressiveness to 0 (`noir/noir-repo/compiler/noirc_driver/src/lib.rs:743-744`) - `ContractSelfPublic::emit` becoming `unconstrained` is a small surface change. All `#[external(\"public\")]` bodies are already unconstrained, so contract code calling `self.emit(...)` directly is unaffected
|
✅ Successfully backported to backport-to-v4-next-staging #23236. |
BEGIN_COMMIT_OVERRIDE feat: package sqlite kv-store backend for stricter browser envs (#23089) fix(pxe): sync target contract before cross-contract utility call (#23225) fix(ci): swap slack_notify args in CLI acceptance test (#23241) feat: optimize get next app tag as sender (#23239) fix(noir): noirfmt nested_utility_contract main.nr (#23246) chore(aztec-nr): mark emit_event_in_public as #[inline_never] to shrink public dispatch (#23161) END_COMMIT_OVERRIDE
Summary
Work towards resolving AztecProtocol/aztec-nr#35
emit_event_in_public#[inline_never](andunconstrained, required by the attribute) so it stops being copied into every public call site.unconstrainedtoContractSelfPublic::emitso it can still call the helper.assert_is_initialized_public(aztec-nr/aztec/src/macros/functions/initialization_utils.nr:100) from chore: prevent inlining public init check #22869Bytecode impact
Measured via
nargo compile --inliner-aggressiveness 0+bb aztec_process, readingpublic_dispatchpacked bytecode size onpublic_fns_with_emit_repro_contract:#[inline_never]Roughly 65 bytes saved per emit call site across the 15 functions. Real contracts with many public functions emitting the same event shape should see the same per-site savings.
Additional Context
--inliner-aggressivenesscannot achieve this on its own: forpublic_dispatchthe noirc driver pins aggressiveness to 0 (noir/noir-repo/compiler/noirc_driver/src/lib.rs:743-744)ContractSelfPublic::emitbecomingunconstrainedis a small surface change. All#[external(\"public\")]bodies are already unconstrained, so contract code callingself.emit(...)directly is unaffected