Skip to main content

solar_codegen/transform/
inline.rs

1//! Function inlining optimization pass.
2//!
3//! This module provides heuristics and analysis for deciding when to inline function calls.
4//! Inlining small functions saves JUMP gas and enables further optimizations.
5//!
6//! ## Inlining Heuristics
7//!
8//! A function is considered inlineable if:
9//! - Body is < N instructions (configurable, default 10)
10//! - Only called once (always inline)
11//! - Called in hot loop (inline if small)
12//! - No complex control flow (single return path preferred)
13//! - Not recursive
14//!
15//! ## Optimization Levels
16//!
17//! - `O0`: No inlining
18//! - `O1`: Conservative inlining (very small functions, single-call)
19//! - `O2`: Aggressive inlining (larger threshold, loop-aware)
20
21use crate::{
22    analysis::LoopAnalyzer,
23    mir::{
24        BlockId, Function, FunctionId as MirFunctionId, Immediate, InstKind, Instruction, MirType,
25        Module, Terminator, Value, ValueId,
26    },
27    pass::ModulePass,
28};
29use alloy_primitives::U256;
30use smallvec::SmallVec;
31use solar_data_structures::map::{FxHashMap, FxHashSet};
32use solar_sema::hir::{self, FunctionId, StmtKind};
33
34/// Optimization level for the compiler.
35#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
36#[repr(u8)]
37pub enum OptLevel {
38    /// No optimizations.
39    O0 = 0,
40    /// Conservative optimizations (default).
41    #[default]
42    O1 = 1,
43    /// Aggressive optimizations.
44    O2 = 2,
45    /// Maximum optimizations (size may increase).
46    O3 = 3,
47}
48
49impl OptLevel {
50    /// Returns true if any optimizations are enabled.
51    #[must_use]
52    pub fn is_optimizing(self) -> bool {
53        self != Self::O0
54    }
55
56    /// Returns true if inlining is enabled at this optimization level.
57    #[must_use]
58    pub fn inline_enabled(self) -> bool {
59        self >= Self::O1
60    }
61}
62
63/// Configuration for function inlining.
64#[derive(Clone, Debug)]
65pub struct InlineConfig {
66    /// Maximum number of statements in a function to consider for inlining.
67    /// Functions with more statements than this are not inlined.
68    pub max_statements: usize,
69    /// Maximum number of statements for aggressive inlining.
70    pub max_statements_aggressive: usize,
71    /// Always inline functions called only once.
72    pub inline_single_call: bool,
73    /// Inline threshold multiplier for functions called in loops.
74    pub loop_inline_multiplier: f32,
75    /// Maximum inline depth (to prevent excessive code bloat).
76    pub max_inline_depth: usize,
77}
78
79impl Default for InlineConfig {
80    fn default() -> Self {
81        Self::for_opt_level(OptLevel::O1)
82    }
83}
84
85impl InlineConfig {
86    /// Creates an inline configuration for the given optimization level.
87    #[must_use]
88    pub fn for_opt_level(level: OptLevel) -> Self {
89        match level {
90            OptLevel::O0 => Self {
91                max_statements: 0,
92                max_statements_aggressive: 0,
93                inline_single_call: false,
94                loop_inline_multiplier: 1.0,
95                max_inline_depth: 0,
96            },
97            OptLevel::O1 => Self {
98                max_statements: 5,
99                max_statements_aggressive: 10,
100                inline_single_call: true,
101                loop_inline_multiplier: 1.5,
102                max_inline_depth: 16,
103            },
104            OptLevel::O2 => Self {
105                max_statements: 10,
106                max_statements_aggressive: 20,
107                inline_single_call: true,
108                loop_inline_multiplier: 2.0,
109                max_inline_depth: 32,
110            },
111            OptLevel::O3 => Self {
112                max_statements: 20,
113                max_statements_aggressive: 50,
114                inline_single_call: true,
115                loop_inline_multiplier: 3.0,
116                max_inline_depth: 64,
117            },
118        }
119    }
120
121    /// Returns true if inlining is disabled.
122    #[must_use]
123    pub fn is_disabled(&self) -> bool {
124        self.max_statements == 0 && !self.inline_single_call
125    }
126}
127
128/// Inlining decision for a function.
129#[derive(Clone, Copy, Debug, PartialEq, Eq)]
130pub enum InlineDecision {
131    /// Always inline this function.
132    Always,
133    /// Inline if call site is in a hot context (e.g., loop).
134    IfHot,
135    /// Never inline this function.
136    Never,
137}
138
139/// Analysis result for a function's inlineability.
140#[derive(Clone, Debug)]
141pub struct FunctionInlineInfo {
142    /// Number of statements in the function body.
143    pub statement_count: usize,
144    /// Number of call sites for this function.
145    pub call_count: usize,
146    /// Whether the function is recursive (directly or indirectly).
147    pub is_recursive: bool,
148    /// Whether the function has complex control flow.
149    pub has_complex_control_flow: bool,
150    /// Whether the function has multiple return points.
151    pub has_multiple_returns: bool,
152    /// The inlining decision for this function.
153    pub decision: InlineDecision,
154}
155
156impl Default for FunctionInlineInfo {
157    fn default() -> Self {
158        Self {
159            statement_count: 0,
160            call_count: 0,
161            is_recursive: false,
162            has_complex_control_flow: false,
163            has_multiple_returns: false,
164            decision: InlineDecision::Never,
165        }
166    }
167}
168
169/// Analyzes functions for inlining opportunities.
170pub struct InlineAnalyzer<'a> {
171    /// The HIR context.
172    hir: &'a hir::Hir<'a>,
173    /// Inline configuration.
174    config: InlineConfig,
175    /// Analysis results for each function.
176    info: FxHashMap<FunctionId, FunctionInlineInfo>,
177    /// Call graph: maps caller -> set of callees.
178    call_graph: FxHashMap<FunctionId, FxHashSet<FunctionId>>,
179}
180
181impl<'a> InlineAnalyzer<'a> {
182    /// Creates a new inline analyzer.
183    pub fn new(hir: &'a hir::Hir<'a>, config: InlineConfig) -> Self {
184        Self { hir, config, info: FxHashMap::default(), call_graph: FxHashMap::default() }
185    }
186
187    /// Analyzes all functions and returns inlining decisions.
188    pub fn analyze(&mut self) -> &FxHashMap<FunctionId, FunctionInlineInfo> {
189        if self.config.is_disabled() {
190            return &self.info;
191        }
192
193        // First pass: collect basic info for each function
194        for func_id in self.hir.function_ids() {
195            let info = self.analyze_function(func_id);
196            self.info.insert(func_id, info);
197        }
198
199        // Second pass: detect recursive functions
200        self.detect_recursion();
201
202        // Third pass: count call sites
203        self.count_call_sites();
204
205        // Fourth pass: make inlining decisions
206        self.make_decisions();
207
208        &self.info
209    }
210
211    /// Analyzes a single function.
212    fn analyze_function(&mut self, func_id: FunctionId) -> FunctionInlineInfo {
213        let func = self.hir.function(func_id);
214        let mut info = FunctionInlineInfo::default();
215
216        if let Some(body) = &func.body {
217            info.statement_count = self.count_statements(body);
218            info.has_multiple_returns = self.count_returns(body) > 1;
219            info.has_complex_control_flow = self.has_complex_control_flow(body);
220
221            // Build call graph
222            let callees = self.collect_callees(body);
223            if !callees.is_empty() {
224                self.call_graph.insert(func_id, callees);
225            }
226        }
227
228        info
229    }
230
231    /// Counts statements in a block (recursive).
232    fn count_statements(&self, block: &hir::Block<'_>) -> usize {
233        let mut count = 0;
234        for stmt in block.stmts {
235            count += self.count_statement(stmt);
236        }
237        count
238    }
239
240    /// Counts a single statement (may be nested).
241    fn count_statement(&self, stmt: &hir::Stmt<'_>) -> usize {
242        match &stmt.kind {
243            StmtKind::Block(block) | StmtKind::UncheckedBlock(block) => {
244                self.count_statements(block)
245            }
246            StmtKind::If(_, then_stmt, else_stmt) => {
247                let mut count = 1;
248                count += self.count_statement(then_stmt);
249                if let Some(else_s) = else_stmt {
250                    count += self.count_statement(else_s);
251                }
252                count
253            }
254            StmtKind::Loop(block, _) => 1 + self.count_statements(block),
255            StmtKind::Try(try_stmt) => {
256                let mut count = 1;
257                for clause in try_stmt.clauses {
258                    count += self.count_statements(&clause.block);
259                }
260                count
261            }
262            _ => 1,
263        }
264    }
265
266    /// Counts return statements in a block.
267    fn count_returns(&self, block: &hir::Block<'_>) -> usize {
268        let mut count = 0;
269        for stmt in block.stmts {
270            count += self.count_returns_stmt(stmt);
271        }
272        count
273    }
274
275    /// Counts returns in a statement.
276    fn count_returns_stmt(&self, stmt: &hir::Stmt<'_>) -> usize {
277        match &stmt.kind {
278            StmtKind::Return(_) => 1,
279            StmtKind::Block(block) | StmtKind::UncheckedBlock(block) => self.count_returns(block),
280            StmtKind::If(_, then_stmt, else_stmt) => {
281                let mut count = self.count_returns_stmt(then_stmt);
282                if let Some(else_s) = else_stmt {
283                    count += self.count_returns_stmt(else_s);
284                }
285                count
286            }
287            StmtKind::Loop(block, _) => self.count_returns(block),
288            StmtKind::Try(try_stmt) => {
289                let mut count = 0;
290                for clause in try_stmt.clauses {
291                    count += self.count_returns(&clause.block);
292                }
293                count
294            }
295            _ => 0,
296        }
297    }
298
299    /// Checks if a block has complex control flow.
300    fn has_complex_control_flow(&self, block: &hir::Block<'_>) -> bool {
301        for stmt in block.stmts {
302            if self.stmt_has_complex_control_flow(stmt) {
303                return true;
304            }
305        }
306        false
307    }
308
309    /// Checks if a statement has complex control flow.
310    fn stmt_has_complex_control_flow(&self, stmt: &hir::Stmt<'_>) -> bool {
311        match &stmt.kind {
312            // Loops add complexity
313            StmtKind::Loop(_, _) => true,
314            // Try/catch is complex
315            StmtKind::Try(_) => true,
316            // Nested blocks may have complex control flow
317            StmtKind::Block(block) | StmtKind::UncheckedBlock(block) => {
318                self.has_complex_control_flow(block)
319            }
320            // Deeply nested if-else is considered complex
321            StmtKind::If(_, then_stmt, else_stmt) => {
322                self.stmt_has_complex_control_flow(then_stmt)
323                    || else_stmt.is_some_and(|s| self.stmt_has_complex_control_flow(s))
324            }
325            _ => false,
326        }
327    }
328
329    /// Collects all function callees in a block.
330    fn collect_callees(&self, block: &hir::Block<'_>) -> FxHashSet<FunctionId> {
331        let mut callees = FxHashSet::default();
332        for stmt in block.stmts {
333            self.collect_callees_stmt(stmt, &mut callees);
334        }
335        callees
336    }
337
338    /// Collects callees from a statement.
339    fn collect_callees_stmt(&self, stmt: &hir::Stmt<'_>, callees: &mut FxHashSet<FunctionId>) {
340        match &stmt.kind {
341            StmtKind::Expr(expr) => self.collect_callees_expr(expr, callees),
342            StmtKind::Block(block) | StmtKind::UncheckedBlock(block) => {
343                for s in block.stmts {
344                    self.collect_callees_stmt(s, callees);
345                }
346            }
347            StmtKind::If(cond, then_stmt, else_stmt) => {
348                self.collect_callees_expr(cond, callees);
349                self.collect_callees_stmt(then_stmt, callees);
350                if let Some(else_s) = else_stmt {
351                    self.collect_callees_stmt(else_s, callees);
352                }
353            }
354            StmtKind::Loop(block, _) => {
355                for s in block.stmts {
356                    self.collect_callees_stmt(s, callees);
357                }
358            }
359            StmtKind::Return(Some(expr)) | StmtKind::Revert(expr) | StmtKind::Emit(expr) => {
360                self.collect_callees_expr(expr, callees);
361            }
362            StmtKind::DeclSingle(var_id) => {
363                let var = self.hir.variable(*var_id);
364                if let Some(init) = var.initializer {
365                    self.collect_callees_expr(init, callees);
366                }
367            }
368            _ => {}
369        }
370    }
371
372    /// Collects callees from an expression.
373    fn collect_callees_expr(&self, expr: &hir::Expr<'_>, callees: &mut FxHashSet<FunctionId>) {
374        match &expr.kind {
375            hir::ExprKind::Call(callee, args, _) => {
376                // Check if callee is a function reference
377                if let hir::ExprKind::Ident(res_slice) = &callee.kind {
378                    for res in res_slice.iter() {
379                        if let hir::Res::Item(hir::ItemId::Function(func_id)) = res {
380                            callees.insert(*func_id);
381                        }
382                    }
383                }
384                self.collect_callees_expr(callee, callees);
385                for arg in args.kind.exprs() {
386                    self.collect_callees_expr(arg, callees);
387                }
388            }
389            hir::ExprKind::Binary(lhs, _, rhs) => {
390                self.collect_callees_expr(lhs, callees);
391                self.collect_callees_expr(rhs, callees);
392            }
393            hir::ExprKind::Unary(_, operand) => {
394                self.collect_callees_expr(operand, callees);
395            }
396            hir::ExprKind::Ternary(cond, then_expr, else_expr) => {
397                self.collect_callees_expr(cond, callees);
398                self.collect_callees_expr(then_expr, callees);
399                self.collect_callees_expr(else_expr, callees);
400            }
401            hir::ExprKind::Member(base, _) => {
402                self.collect_callees_expr(base, callees);
403            }
404            hir::ExprKind::Index(base, idx) => {
405                self.collect_callees_expr(base, callees);
406                if let Some(i) = idx {
407                    self.collect_callees_expr(i, callees);
408                }
409            }
410            hir::ExprKind::Array(elems) => {
411                for elem in elems.iter() {
412                    self.collect_callees_expr(elem, callees);
413                }
414            }
415            hir::ExprKind::Tuple(elems) => {
416                for elem in elems.iter().flatten() {
417                    self.collect_callees_expr(elem, callees);
418                }
419            }
420            hir::ExprKind::Assign(lhs, _, rhs) => {
421                self.collect_callees_expr(lhs, callees);
422                self.collect_callees_expr(rhs, callees);
423            }
424            _ => {}
425        }
426    }
427
428    /// Detects recursive functions using DFS on the call graph.
429    fn detect_recursion(&mut self) {
430        let func_ids: Vec<_> = self.info.keys().copied().collect();
431        for func_id in func_ids {
432            if self.is_recursive(func_id, &mut FxHashSet::default())
433                && let Some(info) = self.info.get_mut(&func_id)
434            {
435                info.is_recursive = true;
436            }
437        }
438    }
439
440    /// Checks if a function is recursive (directly or indirectly).
441    fn is_recursive(&self, func_id: FunctionId, visited: &mut FxHashSet<FunctionId>) -> bool {
442        if !visited.insert(func_id) {
443            return true; // Cycle detected
444        }
445
446        if let Some(callees) = self.call_graph.get(&func_id) {
447            for &callee in callees {
448                if self.is_recursive(callee, visited) {
449                    return true;
450                }
451            }
452        }
453
454        visited.remove(&func_id);
455        false
456    }
457
458    /// Counts call sites for each function.
459    fn count_call_sites(&mut self) {
460        // Count how many times each function is called
461        let mut call_counts: FxHashMap<FunctionId, usize> = FxHashMap::default();
462
463        for callees in self.call_graph.values() {
464            for &callee in callees {
465                *call_counts.entry(callee).or_default() += 1;
466            }
467        }
468
469        for (func_id, count) in call_counts {
470            if let Some(info) = self.info.get_mut(&func_id) {
471                info.call_count = count;
472            }
473        }
474    }
475
476    /// Makes inlining decisions for all functions.
477    fn make_decisions(&mut self) {
478        let func_ids: Vec<_> = self.info.keys().copied().collect();
479        for func_id in func_ids {
480            let decision = self.decide_inline(func_id);
481            if let Some(info) = self.info.get_mut(&func_id) {
482                info.decision = decision;
483            }
484        }
485    }
486
487    /// Decides whether to inline a function.
488    fn decide_inline(&self, func_id: FunctionId) -> InlineDecision {
489        let Some(info) = self.info.get(&func_id) else {
490            return InlineDecision::Never;
491        };
492
493        // Never inline recursive functions
494        if info.is_recursive {
495            return InlineDecision::Never;
496        }
497
498        // Check if function is external/public - don't inline entry points
499        let func = self.hir.function(func_id);
500        if matches!(func.visibility, hir::Visibility::External | hir::Visibility::Public) {
501            return InlineDecision::Never;
502        }
503
504        // Always inline single-call functions if enabled
505        if self.config.inline_single_call && info.call_count == 1 {
506            return InlineDecision::Always;
507        }
508
509        // Check statement count threshold
510        if info.statement_count <= self.config.max_statements {
511            // Small function - always inline
512            return InlineDecision::Always;
513        }
514
515        // Medium-sized functions - inline in hot contexts
516        if info.statement_count <= self.config.max_statements_aggressive
517            && !info.has_complex_control_flow
518        {
519            return InlineDecision::IfHot;
520        }
521
522        InlineDecision::Never
523    }
524
525    /// Returns the inlining decision for a function.
526    #[must_use]
527    pub fn get_decision(&self, func_id: FunctionId) -> InlineDecision {
528        self.info.get(&func_id).map_or(InlineDecision::Never, |i| i.decision)
529    }
530
531    /// Returns the inline info for a function.
532    #[must_use]
533    pub fn get_info(&self, func_id: FunctionId) -> Option<&FunctionInlineInfo> {
534        self.info.get(&func_id)
535    }
536}
537
538/// Statistics about inlining performed.
539#[derive(Clone, Debug, Default)]
540pub struct InlineStats {
541    /// Number of functions analyzed.
542    pub functions_analyzed: usize,
543    /// Number of functions marked for always-inline.
544    pub always_inline: usize,
545    /// Number of functions marked for conditional inline.
546    pub if_hot_inline: usize,
547    /// Number of functions that won't be inlined.
548    pub never_inline: usize,
549    /// Number of recursive functions detected.
550    pub recursive_functions: usize,
551}
552
553impl InlineStats {
554    /// Collects statistics from an analyzer.
555    pub fn from_analyzer(analyzer: &InlineAnalyzer<'_>) -> Self {
556        let mut stats = Self::default();
557
558        for info in analyzer.info.values() {
559            stats.functions_analyzed += 1;
560
561            match info.decision {
562                InlineDecision::Always => stats.always_inline += 1,
563                InlineDecision::IfHot => stats.if_hot_inline += 1,
564                InlineDecision::Never => stats.never_inline += 1,
565            }
566
567            if info.is_recursive {
568                stats.recursive_functions += 1;
569            }
570        }
571
572        stats
573    }
574}
575
576/// Configuration for MIR-level internal-call inlining.
577#[derive(Clone, Debug)]
578pub struct MirInlineConfig {
579    /// Maximum instruction count for ordinary inline candidates.
580    pub max_instructions: usize,
581    /// Maximum instruction count for functions that have exactly one call site.
582    pub max_single_call_instructions: usize,
583    /// Hard sanity limit for single-call-site callees. These bypass the normal
584    /// size and block caps because function DCE removes their original body.
585    pub max_single_call_sanity_instructions: usize,
586    /// Maximum number of blocks to clone from one callee.
587    pub max_blocks: usize,
588    /// Whether a single call site may use the larger threshold.
589    pub inline_single_call: bool,
590    /// Maximum estimated runtime bytecode growth for a cold call site.
591    pub max_cold_code_growth: usize,
592    /// Maximum estimated runtime bytecode growth for a call site inside a loop.
593    pub max_hot_code_growth: usize,
594    /// Maximum number of instructions a single caller may gain from inlining
595    /// multi-use callees, bounding total code growth per function.
596    pub max_caller_inlined_instructions: usize,
597    /// Minimum estimated internal-call protocol gas saved before inlining.
598    pub min_call_savings: u64,
599}
600
601impl Default for MirInlineConfig {
602    fn default() -> Self {
603        Self {
604            max_instructions: 96,
605            max_single_call_instructions: 96,
606            max_single_call_sanity_instructions: 4096,
607            max_blocks: 10,
608            inline_single_call: true,
609            max_cold_code_growth: 256,
610            max_hot_code_growth: 512,
611            max_caller_inlined_instructions: 64,
612            min_call_savings: 120,
613        }
614    }
615}
616
617/// Statistics for MIR-level inlining.
618#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
619pub struct MirInlineStats {
620    /// Number of internal call sites considered.
621    pub call_sites: usize,
622    /// Number of call sites inlined.
623    pub inlined: usize,
624    /// Number of call sites skipped because the callee was not inlineable.
625    pub skipped: usize,
626}
627
628#[derive(Clone, Copy, Debug, Default)]
629struct MirInlineSummary {
630    instruction_count: usize,
631    block_count: usize,
632    return_count: usize,
633    param_count: usize,
634    estimated_code_size: usize,
635    estimated_runtime_gas: u64,
636    internal_frame_size: u64,
637    has_internal_call: bool,
638    has_phi: bool,
639    has_external_call: bool,
640    has_storage_write: bool,
641    has_log: bool,
642    has_control_flow: bool,
643    has_unsupported_terminator: bool,
644    is_entry_point: bool,
645    is_constructor: bool,
646}
647
648/// Module-level MIR internal-call inliner.
649///
650/// This pass clones small internal/private callees into their callers. Each
651/// inline expansion gets a fresh internal-frame range so copied local slots do
652/// not overlap caller locals.
653pub struct MirInliner {
654    config: MirInlineConfig,
655}
656
657/// Module pass for metadata-backed MIR inlining.
658pub struct InlinePass;
659
660impl ModulePass for InlinePass {
661    fn name(&self) -> &str {
662        "inline"
663    }
664
665    fn run(&mut self, module: &mut Module) -> bool {
666        MirInliner::default().run(module).inlined != 0
667    }
668}
669
670impl Default for MirInliner {
671    fn default() -> Self {
672        Self::new(MirInlineConfig::default())
673    }
674}
675
676impl MirInliner {
677    /// Creates a new MIR inliner with the given configuration.
678    #[must_use]
679    pub fn new(config: MirInlineConfig) -> Self {
680        Self { config }
681    }
682
683    /// Runs the inliner over the whole module.
684    pub fn run(&mut self, module: &mut Module) -> MirInlineStats {
685        let mut stats = MirInlineStats::default();
686        let mut summaries = self.summarize_module(module);
687        let mut call_counts = self.call_counts(module);
688        let recursive_functions = self.recursive_functions(module);
689
690        for caller_id in module.functions.indices().collect::<Vec<_>>() {
691            let loop_depths = block_loop_depths(module.function(caller_id));
692            // Bound how much each caller may grow from inlining so a function
693            // calling many internal helpers (e.g. a large verifier) cannot
694            // balloon past the deployable code-size limit.
695            let base_instructions =
696                summaries.get(&caller_id).map(|s| s.instruction_count).unwrap_or_default();
697            let mut cursor = (0, 0);
698            while let Some(site) =
699                self.find_next_call(module.function(caller_id), cursor, &loop_depths)
700            {
701                stats.call_sites += 1;
702                cursor = (site.block.index(), site.inst_index + 1);
703
704                let Some(summary) = summaries.get(&site.callee).copied() else {
705                    stats.skipped += 1;
706                    continue;
707                };
708                let call_count = call_counts.get(&site.callee).copied().unwrap_or_default();
709                let grew_too_much = summaries.get(&caller_id).is_some_and(|s| {
710                    s.instruction_count.saturating_sub(base_instructions)
711                        > self.config.max_caller_inlined_instructions
712                });
713                if grew_too_much
714                    || recursive_functions.contains(&site.callee)
715                    || !self.is_inlineable(caller_id, site, summary, call_count)
716                {
717                    stats.skipped += 1;
718                    continue;
719                }
720
721                let callee = module.function(site.callee).clone();
722                let caller = module.function_mut(caller_id);
723                if inline_call(caller, site.block, site.inst_index, &callee) {
724                    stats.inlined += 1;
725                    summaries.insert(caller_id, summarize_function(module.function(caller_id)));
726                    call_counts = self.call_counts(module);
727                    cursor = (site.block.index(), 0);
728                } else {
729                    stats.skipped += 1;
730                }
731            }
732        }
733
734        stats
735    }
736
737    fn summarize_module(&self, module: &Module) -> FxHashMap<MirFunctionId, MirInlineSummary> {
738        module
739            .functions
740            .iter_enumerated()
741            .map(|(id, func)| (id, summarize_function(func)))
742            .collect()
743    }
744
745    fn call_counts(&self, module: &Module) -> FxHashMap<MirFunctionId, usize> {
746        let mut counts = FxHashMap::default();
747        for func in module.functions.iter() {
748            for block in func.blocks.iter() {
749                for &inst_id in &block.instructions {
750                    if let InstKind::InternalCall { function, .. } = func.instructions[inst_id].kind
751                    {
752                        *counts.entry(function).or_default() += 1;
753                    }
754                }
755            }
756        }
757        counts
758    }
759
760    fn recursive_functions(&self, module: &Module) -> FxHashSet<MirFunctionId> {
761        let mut recursive = FxHashSet::default();
762        for func_id in module.functions.indices() {
763            let mut visiting = FxHashSet::default();
764            if self.function_reaches(module, func_id, func_id, &mut visiting) {
765                recursive.insert(func_id);
766            }
767        }
768        recursive
769    }
770
771    fn function_reaches(
772        &self,
773        module: &Module,
774        current: MirFunctionId,
775        target: MirFunctionId,
776        visiting: &mut FxHashSet<MirFunctionId>,
777    ) -> bool {
778        if !visiting.insert(current) {
779            return false;
780        }
781
782        for callee in self.function_callees(module.function(current)) {
783            if callee == target || self.function_reaches(module, callee, target, visiting) {
784                return true;
785            }
786        }
787
788        false
789    }
790
791    fn function_callees(&self, func: &Function) -> Vec<MirFunctionId> {
792        let mut callees = Vec::new();
793        for block in func.blocks.iter() {
794            for &inst_id in &block.instructions {
795                if let InstKind::InternalCall { function, .. } = func.instructions[inst_id].kind {
796                    callees.push(function);
797                }
798            }
799        }
800        callees
801    }
802
803    fn find_next_call(
804        &self,
805        func: &Function,
806        start: (usize, usize),
807        loop_depths: &FxHashMap<BlockId, usize>,
808    ) -> Option<CallSite> {
809        for (block, bb) in func.blocks.iter_enumerated().skip(start.0) {
810            let start_inst = if block.index() == start.0 { start.1 } else { 0 };
811            for (inst_index, &inst_id) in bb.instructions.iter().enumerate().skip(start_inst) {
812                if let InstKind::InternalCall { function, ref args, returns } =
813                    func.instructions[inst_id].kind
814                {
815                    return Some(CallSite {
816                        block,
817                        inst_index,
818                        callee: function,
819                        args_len: args.len(),
820                        returns: returns as usize,
821                        loop_depth: loop_depths.get(&block).copied().unwrap_or_default(),
822                    });
823                }
824            }
825        }
826        None
827    }
828
829    fn is_inlineable(
830        &self,
831        caller: MirFunctionId,
832        site: CallSite,
833        summary: MirInlineSummary,
834        call_count: usize,
835    ) -> bool {
836        let single_call = self.config.inline_single_call && call_count == 1;
837
838        if caller == site.callee
839            || summary.is_entry_point
840            || summary.is_constructor
841            || summary.has_phi
842            || summary.has_unsupported_terminator
843            || summary.return_count == 0
844        {
845            return false;
846        }
847
848        if single_call {
849            if summary.instruction_count > self.config.max_single_call_sanity_instructions {
850                return false;
851            }
852        } else if summary.block_count > self.config.max_blocks
853            || summary.instruction_count > self.config.max_instructions
854        {
855            return false;
856        }
857
858        // Multi-use stateful callees are usually not worth cloning unless the
859        // call is hot or the body is no larger than the internal-call protocol
860        // it replaces. Single-call callees disappear from emitted runtime
861        // bytecode after inlining, so they are allowed through the normal
862        // code-growth check below.
863        if !single_call
864            && site.loop_depth == 0
865            && (summary.has_storage_write || summary.has_external_call || summary.has_log)
866            && summary.estimated_code_size
867                > estimated_internal_call_code_size(site)
868                    + estimated_internal_return_code_size(summary, site)
869        {
870            return false;
871        }
872
873        let code_growth = estimated_inline_code_growth(summary, site, single_call);
874        let max_growth = if site.loop_depth > 0 {
875            self.config.max_hot_code_growth
876        } else {
877            self.config.max_cold_code_growth
878        };
879        if code_growth > max_growth {
880            return false;
881        }
882
883        let savings = estimated_internal_call_savings(site, summary);
884        savings >= self.config.min_call_savings
885    }
886}
887
888#[derive(Clone, Copy)]
889struct CallSite {
890    block: BlockId,
891    inst_index: usize,
892    callee: MirFunctionId,
893    args_len: usize,
894    returns: usize,
895    loop_depth: usize,
896}
897
898fn summarize_function(func: &Function) -> MirInlineSummary {
899    let mut summary = MirInlineSummary {
900        block_count: func.blocks.len(),
901        param_count: func.params.len(),
902        internal_frame_size: func.internal_frame_size,
903        is_entry_point: func.is_public()
904            || func.attributes.is_fallback
905            || func.attributes.is_receive
906            || func.selector.is_some(),
907        is_constructor: func.attributes.is_constructor,
908        ..MirInlineSummary::default()
909    };
910
911    for block in func.blocks.iter() {
912        summary.instruction_count += block.instructions.len();
913        for &inst_id in &block.instructions {
914            let inst_cost = estimate_inst_cost(&func.instructions[inst_id].kind);
915            summary.estimated_code_size += inst_cost.code_size;
916            summary.estimated_runtime_gas += inst_cost.runtime_gas;
917            match &func.instructions[inst_id].kind {
918                InstKind::InternalCall { .. } => summary.has_internal_call = true,
919                InstKind::Phi(_) => summary.has_phi = true,
920                InstKind::Call { .. }
921                | InstKind::StaticCall { .. }
922                | InstKind::DelegateCall { .. }
923                | InstKind::Create(..)
924                | InstKind::Create2(..) => {
925                    summary.has_external_call = true;
926                }
927                InstKind::SStore(..) | InstKind::TStore(..) => summary.has_storage_write = true,
928                InstKind::Log0(..)
929                | InstKind::Log1(..)
930                | InstKind::Log2(..)
931                | InstKind::Log3(..)
932                | InstKind::Log4(..) => summary.has_log = true,
933                _ => {}
934            }
935        }
936        match block.terminator.as_ref() {
937            Some(term @ Terminator::Return { .. }) => {
938                summary.return_count += 1;
939                let term_cost = estimate_terminator_cost(term);
940                summary.estimated_code_size += term_cost.code_size;
941                summary.estimated_runtime_gas += term_cost.runtime_gas;
942            }
943            Some(term @ Terminator::Revert { .. }) => {
944                let term_cost = estimate_terminator_cost(term);
945                summary.estimated_code_size += term_cost.code_size;
946                summary.estimated_runtime_gas += term_cost.runtime_gas;
947            }
948            // A void internal function returns via `Stop` (the backend lowers it
949            // to an internal return). Treat it as a return point so void callees
950            // can be inlined.
951            Some(Terminator::Stop) if func.returns.is_empty() => {
952                summary.return_count += 1;
953            }
954            Some(Terminator::Jump(_))
955            | Some(Terminator::Branch { .. })
956            | Some(Terminator::Switch { .. }) => {
957                summary.has_control_flow = true;
958                let term_cost = estimate_terminator_cost(block.terminator.as_ref().unwrap());
959                summary.estimated_code_size += term_cost.code_size;
960                summary.estimated_runtime_gas += term_cost.runtime_gas;
961            }
962            Some(Terminator::ReturnData { .. })
963            | Some(Terminator::Stop)
964            | Some(Terminator::SelfDestruct { .. })
965            | None => summary.has_unsupported_terminator = true,
966            Some(Terminator::Invalid) => {}
967        }
968    }
969
970    summary
971}
972
973#[derive(Clone, Copy, Debug, Default)]
974struct MirCost {
975    runtime_gas: u64,
976    code_size: usize,
977}
978
979fn estimate_inst_cost(kind: &InstKind) -> MirCost {
980    let (runtime_gas, code_size) = match kind {
981        InstKind::Add(..)
982        | InstKind::Sub(..)
983        | InstKind::Lt(..)
984        | InstKind::Gt(..)
985        | InstKind::SLt(..)
986        | InstKind::SGt(..)
987        | InstKind::Eq(..)
988        | InstKind::IsZero(..)
989        | InstKind::And(..)
990        | InstKind::Or(..)
991        | InstKind::Xor(..)
992        | InstKind::Not(..)
993        | InstKind::Byte(..)
994        | InstKind::Shl(..)
995        | InstKind::Shr(..)
996        | InstKind::Sar(..)
997        | InstKind::SignExtend(..)
998        | InstKind::MLoad(..)
999        | InstKind::MStore(..)
1000        | InstKind::MStore8(..)
1001        | InstKind::CalldataLoad(..)
1002        | InstKind::CalldataSize
1003        | InstKind::Caller
1004        | InstKind::CallValue
1005        | InstKind::Origin
1006        | InstKind::GasPrice
1007        | InstKind::Coinbase
1008        | InstKind::Timestamp
1009        | InstKind::BlockNumber
1010        | InstKind::PrevRandao
1011        | InstKind::GasLimit
1012        | InstKind::ChainId
1013        | InstKind::Address
1014        | InstKind::SelfBalance
1015        | InstKind::Gas
1016        | InstKind::BaseFee
1017        | InstKind::BlobBaseFee => (3, 1),
1018        InstKind::Mul(..)
1019        | InstKind::Div(..)
1020        | InstKind::SDiv(..)
1021        | InstKind::Mod(..)
1022        | InstKind::SMod(..) => (5, 1),
1023        InstKind::Exp(..) => (50, 1),
1024        InstKind::AddMod(..) | InstKind::MulMod(..) => (8, 1),
1025        InstKind::SLoad(..) | InstKind::TLoad(..) => (100, 1),
1026        InstKind::SStore(..) | InstKind::TStore(..) => (5_000, 1),
1027        InstKind::MCopy(..)
1028        | InstKind::CalldataCopy(..)
1029        | InstKind::CodeCopy(..)
1030        | InstKind::ExtCodeCopy(..)
1031        | InstKind::ReturnDataCopy(..) => (12, 1),
1032        InstKind::MSize | InstKind::CodeSize | InstKind::ReturnDataSize => (2, 1),
1033        InstKind::InternalFrameAddr(_) => (6, 3),
1034        // PUSH32 placeholder patched at deploy time.
1035        InstKind::LoadImmutable(_) => (3, 33),
1036        InstKind::ExtCodeSize(..)
1037        | InstKind::ExtCodeHash(..)
1038        | InstKind::Balance(..)
1039        | InstKind::BlockHash(..)
1040        | InstKind::BlobHash(..)
1041        | InstKind::Keccak256(..) => (30, 1),
1042        InstKind::Call { .. } | InstKind::StaticCall { .. } | InstKind::DelegateCall { .. } => {
1043            (700, 1)
1044        }
1045        InstKind::InternalCall { args, returns, .. } => {
1046            let returns = *returns as usize;
1047            (80 + ((args.len() + returns) as u64) * 20, 16 + (args.len() + returns) * 4)
1048        }
1049        InstKind::Create(..) | InstKind::Create2(..) => (32_000, 1),
1050        InstKind::Log0(..) => (375, 1),
1051        InstKind::Log1(..) => (750, 1),
1052        InstKind::Log2(..) => (1_125, 1),
1053        InstKind::Log3(..) => (1_500, 1),
1054        InstKind::Log4(..) => (1_875, 1),
1055        InstKind::Phi(_) | InstKind::Select(..) => (3, 1),
1056    };
1057    MirCost { runtime_gas, code_size }
1058}
1059
1060fn estimate_terminator_cost(term: &Terminator) -> MirCost {
1061    let (runtime_gas, code_size) = match term {
1062        Terminator::Jump(_) => (8, 3),
1063        Terminator::Branch { .. } => (13, 4),
1064        Terminator::Switch { cases, .. } => (13 + (cases.len() as u64) * 10, 4 + cases.len() * 4),
1065        Terminator::Return { values } => (20 + (values.len() as u64) * 12, 8),
1066        Terminator::Revert { .. } | Terminator::ReturnData { .. } => (20, 4),
1067        Terminator::Stop => (0, 1),
1068        Terminator::SelfDestruct { .. } => (5_000, 1),
1069        Terminator::Invalid => (0, 1),
1070    };
1071    MirCost { runtime_gas, code_size }
1072}
1073
1074fn estimated_internal_call_savings(site: CallSite, summary: MirInlineSummary) -> u64 {
1075    let frame_words =
1076        (summary.internal_frame_size / 32) + 2 + (site.args_len + site.returns) as u64;
1077    let protocol = 90 + ((site.args_len + site.returns) as u64) * 24 + frame_words * 6;
1078    let return_protocol = 24 + (summary.param_count as u64 + site.returns as u64) * 8;
1079    let loop_multiplier = (site.loop_depth as u64).saturating_add(1);
1080    (protocol + return_protocol) * loop_multiplier
1081}
1082
1083fn estimated_internal_call_code_size(site: CallSite) -> usize {
1084    18 + (site.args_len + site.returns) * 5
1085}
1086
1087fn estimated_internal_return_code_size(summary: MirInlineSummary, site: CallSite) -> usize {
1088    8 + (summary.param_count + site.returns) * 4
1089}
1090
1091fn estimated_inline_code_growth(
1092    summary: MirInlineSummary,
1093    site: CallSite,
1094    single_call: bool,
1095) -> usize {
1096    let removed_call = estimated_internal_call_code_size(site);
1097    if single_call {
1098        let removed_callee =
1099            summary.estimated_code_size + estimated_internal_return_code_size(summary, site);
1100        summary.estimated_code_size.saturating_sub(removed_call + removed_callee)
1101    } else {
1102        summary.estimated_code_size.saturating_sub(removed_call)
1103    }
1104}
1105
1106fn block_loop_depths(func: &Function) -> FxHashMap<BlockId, usize> {
1107    let mut analyzer = LoopAnalyzer::new();
1108    let loop_info = analyzer.analyze(func);
1109    let mut depths = FxHashMap::default();
1110    for loop_data in loop_info.all_loops() {
1111        for &block in &loop_data.blocks {
1112            *depths.entry(block).or_default() += 1;
1113        }
1114    }
1115    depths
1116}
1117
1118fn inline_call(
1119    caller: &mut Function,
1120    call_block: BlockId,
1121    call_inst_index: usize,
1122    callee: &Function,
1123) -> bool {
1124    let snapshot = caller.clone();
1125    if inline_call_impl(caller, call_block, call_inst_index, callee).is_some() {
1126        true
1127    } else {
1128        *caller = snapshot;
1129        false
1130    }
1131}
1132
1133fn inline_call_impl(
1134    caller: &mut Function,
1135    call_block: BlockId,
1136    call_inst_index: usize,
1137    callee: &Function,
1138) -> Option<()> {
1139    let call_inst = caller.blocks[call_block].instructions[call_inst_index];
1140    let InstKind::InternalCall { args, returns, .. } = caller.instructions[call_inst].kind.clone()
1141    else {
1142        return None;
1143    };
1144    let returns = returns as usize;
1145    if returns != callee.returns.len() {
1146        return None;
1147    }
1148
1149    let call_result = caller.inst_result_value(call_inst);
1150    if returns > 0 && call_result.is_none() {
1151        return None;
1152    }
1153
1154    let continuation = caller.alloc_block();
1155    let old_terminator = caller.blocks[call_block].terminator.take();
1156    let old_successors = old_terminator.as_ref().map(Terminator::successors).unwrap_or_default();
1157    let suffix = {
1158        let block = &mut caller.blocks[call_block];
1159        block.instructions.split_off(call_inst_index + 1)
1160    };
1161    caller.blocks[call_block].instructions.pop();
1162    caller.blocks[continuation].instructions = suffix;
1163    caller.blocks[continuation].terminator = old_terminator;
1164    redirect_phi_predecessors(caller, &old_successors, call_block, continuation);
1165
1166    let frame_base = caller.internal_frame_size;
1167    caller.internal_frame_size += callee.internal_frame_size;
1168
1169    let mut cloner = InlineCloner::new(caller, callee, frame_base, &args);
1170    let cloned_entry = cloner.clone_blocks(continuation)?;
1171    cloner.caller.blocks[call_block].terminator = Some(Terminator::Jump(cloned_entry));
1172
1173    let mut replacements = FxHashMap::default();
1174    if returns > 0 {
1175        let return_values = build_return_values(
1176            cloner.caller,
1177            continuation,
1178            &callee.returns,
1179            &cloner.return_edges,
1180        )?;
1181        replacements.insert(call_result?, return_values[0]);
1182        insert_extra_return_stores(cloner.caller, continuation, &return_values[1..]);
1183    }
1184
1185    cloner.caller.replace_uses(&replacements);
1186    recompute_cfg(cloner.caller);
1187    prune_phi_incoming_to_predecessors(cloner.caller);
1188    Some(())
1189}
1190
1191struct InlineCloner<'a> {
1192    caller: &'a mut Function,
1193    callee: &'a Function,
1194    frame_base: u64,
1195    value_map: FxHashMap<ValueId, ValueId>,
1196    block_map: FxHashMap<BlockId, BlockId>,
1197    return_edges: Vec<(BlockId, SmallVec<[ValueId; 2]>)>,
1198}
1199
1200impl<'a> InlineCloner<'a> {
1201    fn new(
1202        caller: &'a mut Function,
1203        callee: &'a Function,
1204        frame_base: u64,
1205        args: &[ValueId],
1206    ) -> Self {
1207        let mut value_map = FxHashMap::default();
1208        for (callee_value, value) in callee.values.iter_enumerated() {
1209            if let Value::Arg { index, .. } = value
1210                && let Some(&arg) = args.get(*index as usize)
1211            {
1212                value_map.insert(callee_value, arg);
1213            }
1214        }
1215        Self {
1216            caller,
1217            callee,
1218            frame_base,
1219            value_map,
1220            block_map: FxHashMap::default(),
1221            return_edges: Vec::new(),
1222        }
1223    }
1224
1225    fn clone_blocks(&mut self, continuation: BlockId) -> Option<BlockId> {
1226        for block_id in self.callee.blocks.indices() {
1227            self.block_map.insert(block_id, self.caller.alloc_block());
1228        }
1229
1230        for (callee_block, block) in self.callee.blocks.iter_enumerated() {
1231            let caller_block = self.block_map[&callee_block];
1232            let mut instructions = Vec::with_capacity(block.instructions.len());
1233            for &inst_id in &block.instructions {
1234                let inst = self.callee.instructions[inst_id].clone();
1235                let kind = self.clone_inst_kind(inst.kind)?;
1236                let new_inst = self.caller.alloc_inst(Instruction::new(kind, inst.result_ty));
1237                instructions.push(new_inst);
1238                if let Some(callee_result) = self.callee.inst_result_value(inst_id) {
1239                    let new_result = self.caller.alloc_value(Value::Inst(new_inst));
1240                    self.value_map.insert(callee_result, new_result);
1241                }
1242            }
1243            self.caller.blocks[caller_block].instructions = instructions;
1244        }
1245
1246        for (callee_block, block) in self.callee.blocks.iter_enumerated() {
1247            let caller_block = self.block_map[&callee_block];
1248            let term =
1249                self.clone_terminator(block.terminator.as_ref()?, caller_block, continuation)?;
1250            self.caller.blocks[caller_block].terminator = Some(term);
1251        }
1252
1253        Some(self.block_map[&self.callee.entry_block])
1254    }
1255
1256    fn clone_value(&mut self, value: ValueId) -> Option<ValueId> {
1257        if let Some(&mapped) = self.value_map.get(&value) {
1258            return Some(mapped);
1259        }
1260
1261        let cloned = match self.callee.values[value].clone() {
1262            Value::Immediate(imm) => self.caller.alloc_value(Value::Immediate(imm)),
1263            Value::Undef(ty) => self.caller.alloc_value(Value::Undef(ty)),
1264            Value::Error(guar) => self.caller.alloc_value(Value::Error(guar)),
1265            Value::Arg { .. } | Value::Inst(_) => return None,
1266        };
1267        self.value_map.insert(value, cloned);
1268        Some(cloned)
1269    }
1270
1271    fn clone_block(&self, block: BlockId) -> Option<BlockId> {
1272        self.block_map.get(&block).copied()
1273    }
1274
1275    #[allow(clippy::too_many_lines)]
1276    fn clone_inst_kind(&mut self, kind: InstKind) -> Option<InstKind> {
1277        Some(match kind {
1278            InstKind::Add(a, b) => InstKind::Add(self.clone_value(a)?, self.clone_value(b)?),
1279            InstKind::Sub(a, b) => InstKind::Sub(self.clone_value(a)?, self.clone_value(b)?),
1280            InstKind::Mul(a, b) => InstKind::Mul(self.clone_value(a)?, self.clone_value(b)?),
1281            InstKind::Div(a, b) => InstKind::Div(self.clone_value(a)?, self.clone_value(b)?),
1282            InstKind::SDiv(a, b) => InstKind::SDiv(self.clone_value(a)?, self.clone_value(b)?),
1283            InstKind::Mod(a, b) => InstKind::Mod(self.clone_value(a)?, self.clone_value(b)?),
1284            InstKind::SMod(a, b) => InstKind::SMod(self.clone_value(a)?, self.clone_value(b)?),
1285            InstKind::Exp(a, b) => InstKind::Exp(self.clone_value(a)?, self.clone_value(b)?),
1286            InstKind::AddMod(a, b, c) => {
1287                InstKind::AddMod(self.clone_value(a)?, self.clone_value(b)?, self.clone_value(c)?)
1288            }
1289            InstKind::MulMod(a, b, c) => {
1290                InstKind::MulMod(self.clone_value(a)?, self.clone_value(b)?, self.clone_value(c)?)
1291            }
1292            InstKind::And(a, b) => InstKind::And(self.clone_value(a)?, self.clone_value(b)?),
1293            InstKind::Or(a, b) => InstKind::Or(self.clone_value(a)?, self.clone_value(b)?),
1294            InstKind::Xor(a, b) => InstKind::Xor(self.clone_value(a)?, self.clone_value(b)?),
1295            InstKind::Not(a) => InstKind::Not(self.clone_value(a)?),
1296            InstKind::Shl(a, b) => InstKind::Shl(self.clone_value(a)?, self.clone_value(b)?),
1297            InstKind::Shr(a, b) => InstKind::Shr(self.clone_value(a)?, self.clone_value(b)?),
1298            InstKind::Sar(a, b) => InstKind::Sar(self.clone_value(a)?, self.clone_value(b)?),
1299            InstKind::Byte(a, b) => InstKind::Byte(self.clone_value(a)?, self.clone_value(b)?),
1300            InstKind::Lt(a, b) => InstKind::Lt(self.clone_value(a)?, self.clone_value(b)?),
1301            InstKind::Gt(a, b) => InstKind::Gt(self.clone_value(a)?, self.clone_value(b)?),
1302            InstKind::SLt(a, b) => InstKind::SLt(self.clone_value(a)?, self.clone_value(b)?),
1303            InstKind::SGt(a, b) => InstKind::SGt(self.clone_value(a)?, self.clone_value(b)?),
1304            InstKind::Eq(a, b) => InstKind::Eq(self.clone_value(a)?, self.clone_value(b)?),
1305            InstKind::IsZero(a) => InstKind::IsZero(self.clone_value(a)?),
1306            InstKind::MLoad(a) => InstKind::MLoad(self.clone_value(a)?),
1307            InstKind::MStore(a, b) => InstKind::MStore(self.clone_value(a)?, self.clone_value(b)?),
1308            InstKind::MStore8(a, b) => {
1309                InstKind::MStore8(self.clone_value(a)?, self.clone_value(b)?)
1310            }
1311            InstKind::MSize => InstKind::MSize,
1312            InstKind::MCopy(a, b, c) => {
1313                InstKind::MCopy(self.clone_value(a)?, self.clone_value(b)?, self.clone_value(c)?)
1314            }
1315            InstKind::SLoad(a) => InstKind::SLoad(self.clone_value(a)?),
1316            InstKind::SStore(a, b) => InstKind::SStore(self.clone_value(a)?, self.clone_value(b)?),
1317            InstKind::TLoad(a) => InstKind::TLoad(self.clone_value(a)?),
1318            InstKind::TStore(a, b) => InstKind::TStore(self.clone_value(a)?, self.clone_value(b)?),
1319            InstKind::CalldataLoad(a) => InstKind::CalldataLoad(self.clone_value(a)?),
1320            InstKind::CalldataCopy(a, b, c) => InstKind::CalldataCopy(
1321                self.clone_value(a)?,
1322                self.clone_value(b)?,
1323                self.clone_value(c)?,
1324            ),
1325            InstKind::CalldataSize => InstKind::CalldataSize,
1326            InstKind::InternalFrameAddr(offset) => {
1327                InstKind::InternalFrameAddr(self.frame_base + offset)
1328            }
1329            InstKind::CodeSize => InstKind::CodeSize,
1330            InstKind::LoadImmutable(offset) => InstKind::LoadImmutable(offset),
1331            InstKind::CodeCopy(a, b, c) => {
1332                InstKind::CodeCopy(self.clone_value(a)?, self.clone_value(b)?, self.clone_value(c)?)
1333            }
1334            InstKind::ExtCodeSize(a) => InstKind::ExtCodeSize(self.clone_value(a)?),
1335            InstKind::ExtCodeCopy(a, b, c, d) => InstKind::ExtCodeCopy(
1336                self.clone_value(a)?,
1337                self.clone_value(b)?,
1338                self.clone_value(c)?,
1339                self.clone_value(d)?,
1340            ),
1341            InstKind::ExtCodeHash(a) => InstKind::ExtCodeHash(self.clone_value(a)?),
1342            InstKind::ReturnDataSize => InstKind::ReturnDataSize,
1343            InstKind::ReturnDataCopy(a, b, c) => InstKind::ReturnDataCopy(
1344                self.clone_value(a)?,
1345                self.clone_value(b)?,
1346                self.clone_value(c)?,
1347            ),
1348            InstKind::Caller => InstKind::Caller,
1349            InstKind::CallValue => InstKind::CallValue,
1350            InstKind::Origin => InstKind::Origin,
1351            InstKind::GasPrice => InstKind::GasPrice,
1352            InstKind::BlockHash(a) => InstKind::BlockHash(self.clone_value(a)?),
1353            InstKind::Coinbase => InstKind::Coinbase,
1354            InstKind::Timestamp => InstKind::Timestamp,
1355            InstKind::BlockNumber => InstKind::BlockNumber,
1356            InstKind::PrevRandao => InstKind::PrevRandao,
1357            InstKind::GasLimit => InstKind::GasLimit,
1358            InstKind::ChainId => InstKind::ChainId,
1359            InstKind::Address => InstKind::Address,
1360            InstKind::Balance(a) => InstKind::Balance(self.clone_value(a)?),
1361            InstKind::SelfBalance => InstKind::SelfBalance,
1362            InstKind::Gas => InstKind::Gas,
1363            InstKind::BaseFee => InstKind::BaseFee,
1364            InstKind::BlobBaseFee => InstKind::BlobBaseFee,
1365            InstKind::BlobHash(a) => InstKind::BlobHash(self.clone_value(a)?),
1366            InstKind::Keccak256(a, b) => {
1367                InstKind::Keccak256(self.clone_value(a)?, self.clone_value(b)?)
1368            }
1369            InstKind::Call { gas, addr, value, args_offset, args_size, ret_offset, ret_size } => {
1370                InstKind::Call {
1371                    gas: self.clone_value(gas)?,
1372                    addr: self.clone_value(addr)?,
1373                    value: self.clone_value(value)?,
1374                    args_offset: self.clone_value(args_offset)?,
1375                    args_size: self.clone_value(args_size)?,
1376                    ret_offset: self.clone_value(ret_offset)?,
1377                    ret_size: self.clone_value(ret_size)?,
1378                }
1379            }
1380            InstKind::StaticCall { gas, addr, args_offset, args_size, ret_offset, ret_size } => {
1381                InstKind::StaticCall {
1382                    gas: self.clone_value(gas)?,
1383                    addr: self.clone_value(addr)?,
1384                    args_offset: self.clone_value(args_offset)?,
1385                    args_size: self.clone_value(args_size)?,
1386                    ret_offset: self.clone_value(ret_offset)?,
1387                    ret_size: self.clone_value(ret_size)?,
1388                }
1389            }
1390            InstKind::DelegateCall { gas, addr, args_offset, args_size, ret_offset, ret_size } => {
1391                InstKind::DelegateCall {
1392                    gas: self.clone_value(gas)?,
1393                    addr: self.clone_value(addr)?,
1394                    args_offset: self.clone_value(args_offset)?,
1395                    args_size: self.clone_value(args_size)?,
1396                    ret_offset: self.clone_value(ret_offset)?,
1397                    ret_size: self.clone_value(ret_size)?,
1398                }
1399            }
1400            InstKind::InternalCall { function, args, returns } => InstKind::InternalCall {
1401                function,
1402                args: args
1403                    .into_iter()
1404                    .map(|arg| self.clone_value(arg))
1405                    .collect::<Option<Vec<_>>>()?
1406                    .into(),
1407                returns,
1408            },
1409            InstKind::Create(a, b, c) => {
1410                InstKind::Create(self.clone_value(a)?, self.clone_value(b)?, self.clone_value(c)?)
1411            }
1412            InstKind::Create2(a, b, c, d) => InstKind::Create2(
1413                self.clone_value(a)?,
1414                self.clone_value(b)?,
1415                self.clone_value(c)?,
1416                self.clone_value(d)?,
1417            ),
1418            InstKind::Log0(a, b) => InstKind::Log0(self.clone_value(a)?, self.clone_value(b)?),
1419            InstKind::Log1(a, b, c) => {
1420                InstKind::Log1(self.clone_value(a)?, self.clone_value(b)?, self.clone_value(c)?)
1421            }
1422            InstKind::Log2(a, b, c, d) => InstKind::Log2(
1423                self.clone_value(a)?,
1424                self.clone_value(b)?,
1425                self.clone_value(c)?,
1426                self.clone_value(d)?,
1427            ),
1428            InstKind::Log3(a, b, c, d, e) => InstKind::Log3(
1429                self.clone_value(a)?,
1430                self.clone_value(b)?,
1431                self.clone_value(c)?,
1432                self.clone_value(d)?,
1433                self.clone_value(e)?,
1434            ),
1435            InstKind::Log4(a, b, c, d, e, f) => InstKind::Log4(
1436                self.clone_value(a)?,
1437                self.clone_value(b)?,
1438                self.clone_value(c)?,
1439                self.clone_value(d)?,
1440                self.clone_value(e)?,
1441                self.clone_value(f)?,
1442            ),
1443            InstKind::Phi(_) => return None,
1444            InstKind::Select(a, b, c) => {
1445                InstKind::Select(self.clone_value(a)?, self.clone_value(b)?, self.clone_value(c)?)
1446            }
1447            InstKind::SignExtend(a, b) => {
1448                InstKind::SignExtend(self.clone_value(a)?, self.clone_value(b)?)
1449            }
1450        })
1451    }
1452
1453    fn clone_terminator(
1454        &mut self,
1455        term: &Terminator,
1456        cloned_block: BlockId,
1457        continuation: BlockId,
1458    ) -> Option<Terminator> {
1459        Some(match term {
1460            Terminator::Jump(target) => Terminator::Jump(self.clone_block(*target)?),
1461            Terminator::Branch { condition, then_block, else_block } => Terminator::Branch {
1462                condition: self.clone_value(*condition)?,
1463                then_block: self.clone_block(*then_block)?,
1464                else_block: self.clone_block(*else_block)?,
1465            },
1466            Terminator::Switch { value, default, cases } => Terminator::Switch {
1467                value: self.clone_value(*value)?,
1468                default: self.clone_block(*default)?,
1469                cases: cases
1470                    .iter()
1471                    .map(|(value, block)| {
1472                        Some((self.clone_value(*value)?, self.clone_block(*block)?))
1473                    })
1474                    .collect::<Option<Vec<_>>>()?,
1475            },
1476            Terminator::Return { values } => {
1477                let mapped = values
1478                    .iter()
1479                    .map(|value| self.clone_value(*value))
1480                    .collect::<Option<SmallVec<[ValueId; 2]>>>()?;
1481                self.return_edges.push((cloned_block, mapped));
1482                Terminator::Jump(continuation)
1483            }
1484            // A void callee's `Stop` is an internal return with no values.
1485            Terminator::Stop if self.callee.returns.is_empty() => {
1486                self.return_edges.push((cloned_block, SmallVec::new()));
1487                Terminator::Jump(continuation)
1488            }
1489            Terminator::Revert { offset, size } => Terminator::Revert {
1490                offset: self.clone_value(*offset)?,
1491                size: self.clone_value(*size)?,
1492            },
1493            Terminator::ReturnData { .. } | Terminator::Stop | Terminator::SelfDestruct { .. } => {
1494                return None;
1495            }
1496            Terminator::Invalid => Terminator::Invalid,
1497        })
1498    }
1499}
1500
1501fn build_return_values(
1502    caller: &mut Function,
1503    continuation: BlockId,
1504    return_tys: &[MirType],
1505    return_edges: &[(BlockId, SmallVec<[ValueId; 2]>)],
1506) -> Option<Vec<ValueId>> {
1507    let mut values = Vec::with_capacity(return_tys.len());
1508    for (index, &ty) in return_tys.iter().enumerate() {
1509        let incoming = return_edges
1510            .iter()
1511            .map(|(block, edge_values)| Some((*block, *edge_values.get(index)?)))
1512            .collect::<Option<Vec<_>>>()?;
1513        let phi = caller.alloc_inst(Instruction::new(InstKind::Phi(incoming), Some(ty)));
1514        caller.blocks[continuation].instructions.insert(index, phi);
1515        values.push(caller.alloc_value(Value::Inst(phi)));
1516    }
1517    Some(values)
1518}
1519
1520fn insert_extra_return_stores(caller: &mut Function, continuation: BlockId, values: &[ValueId]) {
1521    if values.is_empty() {
1522        return;
1523    }
1524
1525    // Insert the stores right after the continuation block's leading phis.
1526    let phi_count = caller.blocks[continuation]
1527        .instructions
1528        .iter()
1529        .take_while(|&&inst_id| matches!(caller.instructions[inst_id].kind, InstKind::Phi(_)))
1530        .count();
1531
1532    for (index, &value) in values.iter().enumerate() {
1533        let offset = caller
1534            .alloc_value(Value::Immediate(Immediate::uint256(U256::from((index as u64 + 1) * 32))));
1535        let store = caller.alloc_inst(Instruction::new(InstKind::MStore(offset, value), None));
1536        caller.blocks[continuation].instructions.insert(phi_count + index, store);
1537    }
1538}
1539
1540fn redirect_phi_predecessors(
1541    func: &mut Function,
1542    successors: &[BlockId],
1543    old_pred: BlockId,
1544    new_pred: BlockId,
1545) {
1546    if successors.is_empty() {
1547        return;
1548    }
1549
1550    for &succ in successors {
1551        for &inst_id in &func.blocks[succ].instructions {
1552            if let InstKind::Phi(incoming) = &mut func.instructions[inst_id].kind {
1553                for (pred, _) in incoming {
1554                    if *pred == old_pred {
1555                        *pred = new_pred;
1556                    }
1557                }
1558            }
1559        }
1560    }
1561}
1562
1563fn recompute_cfg(func: &mut Function) {
1564    let mut edges = Vec::new();
1565    for (block, bb) in func.blocks.iter_enumerated() {
1566        if let Some(term) = &bb.terminator {
1567            edges.push((block, term.successors()));
1568        }
1569    }
1570
1571    for block in func.blocks.iter_mut() {
1572        block.predecessors.clear();
1573    }
1574
1575    for (block, successors) in edges {
1576        for succ in successors {
1577            func.blocks[succ].predecessors.push(block);
1578        }
1579    }
1580}
1581
1582fn prune_phi_incoming_to_predecessors(func: &mut Function) {
1583    for block_id in func.blocks.indices() {
1584        let predecessors = func.blocks[block_id].predecessors.clone();
1585        for &inst_id in &func.blocks[block_id].instructions {
1586            if let InstKind::Phi(incoming) = &mut func.instructions[inst_id].kind {
1587                incoming.retain(|(pred, _)| predecessors.contains(pred));
1588            }
1589        }
1590    }
1591}
1592
1593#[cfg(test)]
1594mod tests {
1595    use super::*;
1596    use crate::mir::FunctionBuilder;
1597    use solar_interface::Ident;
1598    use solar_sema::hir::Visibility;
1599
1600    #[test]
1601    fn inlines_loop_return_through_continuation_phi() {
1602        let mut module = Module::new(Ident::DUMMY);
1603
1604        let mut callee = Function::new(Ident::DUMMY);
1605        {
1606            let mut builder = FunctionBuilder::new(&mut callee);
1607            let limit = builder.add_param(MirType::uint256());
1608            builder.add_return(MirType::uint256());
1609
1610            let header = builder.create_block();
1611            let body = builder.create_block();
1612            let exit = builder.create_block();
1613
1614            let frame = builder.internal_frame_addr(0);
1615            let zero = builder.imm_u64(0);
1616            builder.mstore(frame, zero);
1617            builder.jump(header);
1618
1619            builder.switch_to_block(header);
1620            let frame = builder.internal_frame_addr(0);
1621            let current = builder.mload(frame);
1622            let cond = builder.lt(current, limit);
1623            builder.branch(cond, body, exit);
1624
1625            builder.switch_to_block(body);
1626            let frame = builder.internal_frame_addr(0);
1627            let current = builder.mload(frame);
1628            let one = builder.imm_u64(1);
1629            let next = builder.add(current, one);
1630            let frame = builder.internal_frame_addr(0);
1631            builder.mstore(frame, next);
1632            builder.jump(header);
1633
1634            builder.switch_to_block(exit);
1635            let frame = builder.internal_frame_addr(0);
1636            let result = builder.mload(frame);
1637            builder.ret([result]);
1638        }
1639        callee.internal_frame_size = 32;
1640        let callee_id = module.add_function(callee);
1641
1642        let mut caller = Function::new(Ident::DUMMY);
1643        caller.attributes.visibility = Visibility::Public;
1644        {
1645            let mut builder = FunctionBuilder::new(&mut caller);
1646            let limit = builder.imm_u64(4);
1647            let value = builder.internal_call(callee_id, vec![limit], MirType::uint256(), 1);
1648            let one = builder.imm_u64(1);
1649            let result = builder.add(value, one);
1650            builder.ret([result]);
1651        }
1652        let caller_id = module.add_function(caller);
1653
1654        let mut inliner = MirInliner::default();
1655        let stats = inliner.run(&mut module);
1656        assert_eq!(stats.inlined, 1);
1657
1658        let caller = module.function(caller_id);
1659        assert!(caller.blocks.iter().all(|block| {
1660            block.instructions.iter().all(|&inst| {
1661                !matches!(caller.instructions[inst].kind, InstKind::InternalCall { .. })
1662            })
1663        }));
1664        assert!(caller.blocks.iter().any(|block| {
1665            block
1666                .instructions
1667                .iter()
1668                .any(|&inst| matches!(caller.instructions[inst].kind, InstKind::Phi(_)))
1669        }));
1670    }
1671}