1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
// Copyright (C) 2019-2026 Provable Inc.
// This file is part of the Leo library.
// The Leo library is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// The Leo library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
//! Inlines function calls at their call sites across every program reachable from the top-level
//! compilation unit.
//!
//! Aleo `call` can target either a `function` (entry point) or a `closure`, in the callee's own
//! program or another program. So top-level `Variant::Fn` closures can be emitted as standalone
//! Aleo closures and called across programs just like entry points — the heuristics in step 4
//! below decide whether to inline them or keep them as closures. What must always be inlined is
//! anything Aleo's bytecode can't represent as a top-level closure: submodule functions (Aleo
//! resources are flat identifiers, not paths), library functions (libraries aren't deployable),
//! and `final fn`s (on-chain-only code that can't be called via `call`).
//!
//! See <https://en.wikipedia.org/wiki/Inline_expansion> for background on inlining in general.
//!
//! ### Example
//!
//! ```leo
//! function main(flag: u8, value: u8) -> u8 {
//! $var$0 = flag == 0u8;
//! $var$1 = foo(value);
//! value$2 = $var$1;
//! value$3 = $var$0 ? value$2 : value;
//! return value$3;
//! }
//!
//! inline foo(x: u8) -> u8 {
//! $var$4 = x * x;
//! return $var$4;
//! }
//! ```
//!
//! produces
//!
//! ```leo
//! inline foo(x: u8) -> u8 {
//! $var$4 = x * x;
//! return $var$4;
//! }
//!
//! function main(flag: u8, value: u8) -> u8 {
//! $var$0 = flag == 0u8;
//! $var$4$5 = value * value;
//! $var$1 = $var$4$5;
//! value$2 = $var$1;
//! value$3 = $var$0 ? value$2 : value;
//! return value$3;
//! }
//! ```
//!
//! ### Algorithm
//!
//! The pass runs in two phases. Monomorphization is expected to have already specialized every
//! const generic, so this pass sees only regular parameters.
//!
//! **Phase 1 — Analysis.** `AnalysisVisitor` walks the AST and records every function called
//! from a `Fn`/`FinalFn`/`Finalize`/`View` body or a constructor. The resulting `always_inline`
//! set force-inlines any function that reaches an on-chain context (or a view body, which cannot
//! contain a `call` instruction) even transitively.
//!
//! **Phase 2 — Transform.**
//!
//! 1. **Seed** `function_map` with every function reachable from the top-level `Program` — its
//! own scopes and modules plus the contents of every `FromLeo` and `FromLibrary` stub.
//! Current-program inserts come last so they override any stub placeholders. `FromAleo`
//! stubs are skipped; their bodies live in Aleo bytecode and are not available to inline.
//! 2. **Walk** the call graph in post-order so each callee is already reconstructed when its
//! callers are visited. `self.program` is set to each callee's own program during traversal
//! so `reconstruct_call` interprets cross-program edges from the callee's perspective.
//! 3. **At each call site**, emit a direct Aleo `call` if the callee is a cross-program entry
//! point (inlining would lose its transition semantics) — otherwise consult step 4. When
//! inlining is chosen, substitute parameters for arguments via `Replacer` and run
//! `SsaFormingVisitor` with `rename_defs` enabled so inlined definitions don't shadow caller
//! bindings. The call expression is replaced with the inlined block's return value (or a
//! unit expression if there was none).
//! 4. **Force-inline** if any of: the callee is a submodule function (same program or cross-
//! program — Aleo resources are flat identifiers), a library function, a `final fn`, a `Fn`
//! called from an on-chain context, a `Fn` taking more than 16 args, or a `Fn` whose
//! signature names any optional types. Otherwise inline conditionally when the callee has a
//! single caller, no args, or only empty-typed args — a top-level `Variant::Fn` that doesn't
//! trip any of those heuristics stays as a closure and is invoked via `call`, whether the
//! call site is in the same program or a different one. An `@no_inline` annotation
//! suppresses the conditional cases and emits a warning if a mandatory rule would have
//! overridden it.
//! 5. **Carry through** external definitions the DFS did not process so `FromLeo` stub
//! assembly can pick them up; drop current-program leftovers as dead code.
//! 6. **Assemble stubs** from `reconstructed_functions`. Top-level `Variant::Fn`s are kept in
//! the stub — same-program callers (inside the stub's own entry points) and cross-program
//! callers (in the compilation unit) both emit direct `call`s into them, so removing them
//! would leave dangling references in the emitted bytecode.
use cratePass;
use AnalysisVisitor;
use IndexMap;
use ;
use Result;
use Symbol;
use TransformVisitor;
;