1use 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#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
36#[repr(u8)]
37pub enum OptLevel {
38 O0 = 0,
40 #[default]
42 O1 = 1,
43 O2 = 2,
45 O3 = 3,
47}
48
49impl OptLevel {
50 #[must_use]
52 pub fn is_optimizing(self) -> bool {
53 self != Self::O0
54 }
55
56 #[must_use]
58 pub fn inline_enabled(self) -> bool {
59 self >= Self::O1
60 }
61}
62
63#[derive(Clone, Debug)]
65pub struct InlineConfig {
66 pub max_statements: usize,
69 pub max_statements_aggressive: usize,
71 pub inline_single_call: bool,
73 pub loop_inline_multiplier: f32,
75 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 #[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 #[must_use]
123 pub fn is_disabled(&self) -> bool {
124 self.max_statements == 0 && !self.inline_single_call
125 }
126}
127
128#[derive(Clone, Copy, Debug, PartialEq, Eq)]
130pub enum InlineDecision {
131 Always,
133 IfHot,
135 Never,
137}
138
139#[derive(Clone, Debug)]
141pub struct FunctionInlineInfo {
142 pub statement_count: usize,
144 pub call_count: usize,
146 pub is_recursive: bool,
148 pub has_complex_control_flow: bool,
150 pub has_multiple_returns: bool,
152 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
169pub struct InlineAnalyzer<'a> {
171 hir: &'a hir::Hir<'a>,
173 config: InlineConfig,
175 info: FxHashMap<FunctionId, FunctionInlineInfo>,
177 call_graph: FxHashMap<FunctionId, FxHashSet<FunctionId>>,
179}
180
181impl<'a> InlineAnalyzer<'a> {
182 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 pub fn analyze(&mut self) -> &FxHashMap<FunctionId, FunctionInlineInfo> {
189 if self.config.is_disabled() {
190 return &self.info;
191 }
192
193 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 self.detect_recursion();
201
202 self.count_call_sites();
204
205 self.make_decisions();
207
208 &self.info
209 }
210
211 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 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 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 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 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 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 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 fn stmt_has_complex_control_flow(&self, stmt: &hir::Stmt<'_>) -> bool {
311 match &stmt.kind {
312 StmtKind::Loop(_, _) => true,
314 StmtKind::Try(_) => true,
316 StmtKind::Block(block) | StmtKind::UncheckedBlock(block) => {
318 self.has_complex_control_flow(block)
319 }
320 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 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 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 fn collect_callees_expr(&self, expr: &hir::Expr<'_>, callees: &mut FxHashSet<FunctionId>) {
374 match &expr.kind {
375 hir::ExprKind::Call(callee, args, _) => {
376 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 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 fn is_recursive(&self, func_id: FunctionId, visited: &mut FxHashSet<FunctionId>) -> bool {
442 if !visited.insert(func_id) {
443 return true; }
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 fn count_call_sites(&mut self) {
460 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 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 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 if info.is_recursive {
495 return InlineDecision::Never;
496 }
497
498 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 if self.config.inline_single_call && info.call_count == 1 {
506 return InlineDecision::Always;
507 }
508
509 if info.statement_count <= self.config.max_statements {
511 return InlineDecision::Always;
513 }
514
515 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 #[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 #[must_use]
533 pub fn get_info(&self, func_id: FunctionId) -> Option<&FunctionInlineInfo> {
534 self.info.get(&func_id)
535 }
536}
537
538#[derive(Clone, Debug, Default)]
540pub struct InlineStats {
541 pub functions_analyzed: usize,
543 pub always_inline: usize,
545 pub if_hot_inline: usize,
547 pub never_inline: usize,
549 pub recursive_functions: usize,
551}
552
553impl InlineStats {
554 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#[derive(Clone, Debug)]
578pub struct MirInlineConfig {
579 pub max_instructions: usize,
581 pub max_single_call_instructions: usize,
583 pub max_single_call_sanity_instructions: usize,
586 pub max_blocks: usize,
588 pub inline_single_call: bool,
590 pub max_cold_code_growth: usize,
592 pub max_hot_code_growth: usize,
594 pub max_caller_inlined_instructions: usize,
597 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#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
619pub struct MirInlineStats {
620 pub call_sites: usize,
622 pub inlined: usize,
624 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
648pub struct MirInliner {
654 config: MirInlineConfig,
655}
656
657pub 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 #[must_use]
679 pub fn new(config: MirInlineConfig) -> Self {
680 Self { config }
681 }
682
683 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 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 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 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 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 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 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}