Repository navigation
feat: Share repeated type annotations during noir_def elaboration - #313
Open
TomAFrench wants to merge 1 commit into
Open
TomAFrench wants to merge 1 commit into
TomAFrench wants to merge 1 commit into
Conversation
Extracted code repeats the same (often enormous) type annotation on every call, builtin, and reference that touches a type, and the DSL re-built and re-elaborated the term for each occurrence. makeNoirType call sites in expression/definition building now go through makeNoirTypeShared, which binds the first occurrence of a sufficiently large type to a fresh identifier and reuses it for every further occurrence; makeFnDecl emits the collected let-bindings around the function tuple, under the generics binders. Since let-bound variables are definitionally transparent, elaboration is unaffected semantically, and the tactic layer zeta-reduces the bindings away when entering a body, so proof goals keep their existing fully-expanded shape. Halves elaboration time (918ms -> 480ms) on a benchmark replicating the type duplication in extracted noir_base64 code, with no change for definitions whose types are below the sharing threshold.
iamrecursion
approved these changes
Oct 12, 2026
Comment on lines
+27
to
+36
| /-- | ||
| Maps the (printed) syntax of a type annotation to the identifier its built term has been bound | ||
| to, so that repeated annotations reuse one binding. See `makeNoirTypeShared`. | ||
| -/ | ||
| sharedTypeCache : Std.HashMap String Lean.Ident := {} | ||
| /-- | ||
| The bindings backing `sharedTypeCache`, in creation order. `wrapSharedTypeLets` emits these as | ||
| `let`s around the finished term. | ||
| -/ | ||
| sharedTypeLets : Array (Lean.Ident × TSyntax `term) := #[] |
Collaborator
There was a problem hiding this comment.
Suggested change
| /-- | |
| Maps the (printed) syntax of a type annotation to the identifier its built term has been bound | |
| to, so that repeated annotations reuse one binding. See `makeNoirTypeShared`. | |
| -/ | |
| sharedTypeCache : Std.HashMap String Lean.Ident := {} | |
| /-- | |
| The bindings backing `sharedTypeCache`, in creation order. `wrapSharedTypeLets` emits these as | |
| `let`s around the finished term. | |
| -/ | |
| sharedTypeLets : Array (Lean.Ident × TSyntax `term) := #[] | |
| /-- | |
| Maps the (printed) syntax of a type annotation to the identifier its built term has been bound | |
| to, so that repeated annotations reuse one binding. See `makeNoirTypeShared`. | |
| -/ | |
| sharedTypeCache : Std.HashMap String Lean.Ident := {} | |
| /-- | |
| The bindings backing `sharedTypeCache`, in creation order. `wrapSharedTypeLets` emits these as | |
| `let`s around the finished term. | |
| -/ | |
| sharedTypeLets : Array (Lean.Ident × TSyntax `term) := #[] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Extracted code repeats the same — often enormous — type annotation on every call, builtin, and reference that touches a type (e.g.
encode_varin extracted noir_base64 repeats its ~15-operator output-length type over a dozen times). The DSL previously re-built and re-elaborated the type term for every occurrence, so a definition's elaboration cost grew with the number of annotations rather than the number of distinct types.This PR shares those terms: within one
noir_def, the first occurrence of a sufficiently large type binds its term to a fresh identifier (#tp_0,#tp_1, …) and every further occurrence elaborates to just that identifier.Benchmark: elaboration time is halved (918ms → 480ms) on a file of ten definitions each repeating a
encode_var-sized type sixteen times. Definitions whose types fall below the sharing threshold are unaffected. The win exceeds the raw re-elaboration saving because sharing also shrinks every downstream term:letInbinder types, unification problems, and metavariable assignments carry a short identifier instead of a copy of the type.How it works
Syntax/State.lean:DSLStategains a cache from printed type syntax to a generated identifier plus the backing bindings in creation order.makeNoirTypeSharedbuilds a type term on first sight and returns the cached identifier on repeats;wrapSharedTypeLetsemits the collectedlets. Types under a size threshold (16 syntax nodes) and types containing context-dependent holes (_) fall back to plainmakeNoirType.Syntax/Builders.lean: the type-bearing call sites in expression building (builtinreturningtypes, funcref signatures, literals, lambdas, trait self types) go through the shared path.makeFnDeclnow builds parameter types, return type, and body in a single DSL-monad run and wraps the result tuple in the collectedlets — inside the generics match, since shared types may mention the generic binders.let-bound variables are definitionally transparent, so elaboration semantics are unchanged.Tactic/Steps.lean: the two body-reduction sites (enter_decl'sreduceOneandreduceBetaIotaMatch) now zeta-reduce the headletbindings, so proof goals keep exactly their existing fully-expanded shape. The change is invisible to proof authors.Validation
LampeandTestsbuilds pass.LenBuiltin,Syntax) prove theorems viaenter_decl/stepsover definitions that do receive shared types (evenArray<u8, M>crosses the threshold), so the proof path over shared bindings is exercised by the suite.#printthat generated definitions bind each large type once and reference it from all use sites, including inferredcallBuiltintype indices.Notes / future work
#printoutput.f<…, EXPR: u32>positions) are not yet shared — they would need a typed ascription to be let-bound, and are the natural follow-up if more elaboration speed is wanted.