Skip to content

chore(aztec-nr): mark emit_event_in_public as #[inline_never] to shrink public dispatch#23161

Merged
nventuro merged 1 commit into
merge-train/fairiesfrom
mv/inline-never-emit-event-in-public
May 13, 2026
Merged

chore(aztec-nr): mark emit_event_in_public as #[inline_never] to shrink public dispatch#23161
nventuro merged 1 commit into
merge-train/fairiesfrom
mv/inline-never-emit-event-in-public

Conversation

@vezenovm
Copy link
Copy Markdown
Contributor

@vezenovm vezenovm commented May 11, 2026

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 chore: prevent inlining public init check #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

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.
@vezenovm vezenovm marked this pull request as ready for review May 11, 2026 18:18
@vezenovm vezenovm requested a review from nventuro as a code owner May 11, 2026 18:18
@vezenovm vezenovm requested review from benesjan, dbanks12 and nchamo May 11, 2026 18:19
/// 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)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this required?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we add this here, aren't we forcing all contracts to add a safety tag when emitting events?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@nventuro nventuro merged commit dcd1f65 into merge-train/fairies May 13, 2026
40 of 46 checks passed
@nventuro nventuro deleted the mv/inline-never-emit-event-in-public branch May 13, 2026 19:25
AztecBot pushed a commit that referenced this pull request May 13, 2026
…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
@AztecBot
Copy link
Copy Markdown
Collaborator

✅ Successfully backported to backport-to-v4-next-staging #23236.

AztecBot added a commit that referenced this pull request May 14, 2026
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants