1use super::{
10 assembler::{Assembler, AssemblerConfig, DeferredConst, ImmutableRef, Label, op},
11 stack::{
12 MAX_STACK_ACCESS, ScheduledOp, SpillSlot, StackModel, StackOp, StackScheduler, TargetSlot,
13 },
14};
15use crate::{
16 IMMUTABLE_SCRATCH_BASE,
17 analysis::{
18 CallGraphInfo, CfgInfo, CopyDest, CopySource, Liveness, Loop, LoopAnalyzer, ParallelCopy,
19 PhiEliminator,
20 },
21 mir::{BlockId, Function, FunctionId, InstId, InstKind, MirType, Module, Terminator, ValueId},
22 pass::{AnalysisManager, LivenessAnalysis, PipelineOptions, run_default_pipeline_with_options},
23};
24use alloy_primitives::U256;
25use solar_config::{EvmVersion, OptimizationMode};
26use solar_data_structures::map::{FxHashMap, FxHashSet};
27use solar_interface::Session;
28
29const INTERNAL_FRAME_PTR_SLOT: u64 = 0xa0;
33const LOW_MEMORY_START: u64 = 0x80;
34const CONSTRUCTOR_FREE_MEMORY_START: u64 = 0x4000;
35const CONSTRUCTOR_SPILL_BASE: u64 = 0x1000;
36const LINEAR_SELECTOR_DISPATCH_THRESHOLD: usize = 64;
37const STACK_PHI_LAYOUT_LIMIT: usize = 8;
38
39#[derive(Clone, Copy, Debug, Default)]
41pub struct EvmCodegenConfig {
42 pub evm_version: EvmVersion,
44 pub optimization: OptimizationMode,
46 pub mir_print_after_each: bool,
48 pub evm_ir_stack_schedule: bool,
55 pub evm_ir_layout_passes: bool,
57}
58
59impl EvmCodegenConfig {
60 #[must_use]
62 pub fn from_session(sess: &Session) -> Self {
63 Self {
64 evm_version: sess.opts.evm_version,
65 optimization: sess.opts.optimization,
66 mir_print_after_each: sess.opts.unstable.mir_print_after_each,
67 evm_ir_stack_schedule: false,
70 evm_ir_layout_passes: false,
71 }
72 }
73
74 fn assembler_config(self) -> AssemblerConfig {
75 AssemblerConfig {
76 evm_version: self.evm_version,
77 optimization: self.optimization,
78 evm_ir_stack_schedule: self.evm_ir_stack_schedule,
79 evm_ir_layout_passes: self.evm_ir_layout_passes,
80 }
81 }
82}
83
84impl From<&Session> for EvmCodegenConfig {
85 fn from(sess: &Session) -> Self {
86 Self::from_session(sess)
87 }
88}
89
90impl From<solar_sema::Gcx<'_>> for EvmCodegenConfig {
91 fn from(gcx: solar_sema::Gcx<'_>) -> Self {
92 Self::from_session(gcx.sess)
93 }
94}
95
96#[derive(Clone, Copy, Debug)]
99struct StackEffect {
100 pops: usize,
102 pushes: usize,
104}
105
106#[derive(Clone, Copy, Debug)]
108enum StackPush {
109 #[allow(dead_code)]
111 None,
112 Tracked(ValueId),
114 Unknown,
116}
117
118#[derive(Clone, Copy, Debug)]
119struct SelectorDispatchEntry {
120 selector: u32,
121 label: Label,
122}
123
124#[derive(Clone, Debug, Default)]
125struct StackPhiPlan {
126 entries: FxHashMap<BlockId, Vec<ValueId>>,
127 edges: FxHashMap<BlockId, StackPhiEdge>,
128 edge_sources: FxHashMap<BlockId, FxHashSet<ValueId>>,
129}
130
131#[derive(Clone, Debug)]
132struct StackPhiEdge {
133 sources: Vec<ValueId>,
134 results: Vec<ValueId>,
135}
136
137impl StackPhiPlan {
138 fn analyze(func: &Function) -> Self {
139 StackPhiPlanner::new(func).plan()
140 }
141}
142
143struct StackPhiPlanner<'a> {
144 func: &'a Function,
145 loops: Vec<Loop>,
146 header_results: FxHashMap<BlockId, Vec<ValueId>>,
147}
148
149impl<'a> StackPhiPlanner<'a> {
150 fn new(func: &'a Function) -> Self {
151 let mut loop_analyzer = LoopAnalyzer::new();
152 let loop_info = loop_analyzer.analyze(func);
153 let mut loops: Vec<_> = loop_info.all_loops().cloned().collect();
154 loops.sort_by_key(|loop_info| loop_info.header.index());
155
156 let mut planner = Self { func, loops, header_results: FxHashMap::default() };
157 planner.collect_header_results();
158 planner
159 }
160
161 fn plan(&self) -> StackPhiPlan {
162 let mut plan = StackPhiPlan::default();
163 for loop_info in &self.loops {
164 self.plan_loop(loop_info, &mut plan);
165 }
166 plan
167 }
168
169 fn collect_header_results(&mut self) {
170 for loop_info in &self.loops {
171 let block = &self.func.blocks[loop_info.header];
172 let phi_insts = self.leading_phi_insts(block);
173 if let Some(results) = self.phi_result_values(&phi_insts) {
174 self.header_results.insert(loop_info.header, results);
175 }
176 }
177 }
178
179 fn plan_loop(&self, loop_info: &Loop, plan: &mut StackPhiPlan) {
180 let Some(preheader) = loop_info.preheader else {
181 return;
182 };
183 let [latch] = loop_info.back_edges.as_slice() else {
184 return;
185 };
186 if !matches!(self.func.blocks[preheader].terminator, Some(Terminator::Jump(target)) if target == loop_info.header)
187 || !matches!(self.func.blocks[*latch].terminator, Some(Terminator::Jump(target)) if target == loop_info.header)
188 {
189 return;
190 }
191 if plan.edges.contains_key(&preheader) || plan.edges.contains_key(latch) {
192 return;
193 }
194
195 let block = &self.func.blocks[loop_info.header];
196 let phi_insts = self.leading_phi_insts(block);
197 if phi_insts.is_empty() || phi_insts.len() > STACK_PHI_LAYOUT_LIMIT {
198 return;
199 }
200
201 let Some(results) = self.phi_result_values(&phi_insts) else {
202 return;
203 };
204 if results.len() > STACK_PHI_LAYOUT_LIMIT {
205 return;
206 }
207
208 let carry_through = self.carry_through_values(loop_info);
209 if carry_through.len() + results.len() > STACK_PHI_LAYOUT_LIMIT {
210 return;
211 }
212
213 let mut entry = carry_through.clone();
214 entry.extend(results.iter().copied());
215
216 let predecessors = [preheader, *latch];
217 let mut edges = Vec::with_capacity(predecessors.len());
218 for pred in predecessors {
219 let Some(phi_sources) = self.phi_sources_for_pred(&phi_insts, pred) else {
220 return;
221 };
222 let mut sources = carry_through.clone();
223 sources.extend(phi_sources);
224 debug_assert_eq!(sources.len(), entry.len());
225 edges.push((pred, sources));
226 }
227
228 plan.entries.insert(loop_info.header, entry.clone());
229 for (pred, sources) in edges {
230 plan.edge_sources.insert(pred, sources.iter().copied().collect());
231 plan.edges.insert(pred, StackPhiEdge { sources, results: entry.clone() });
232 }
233 }
234
235 fn leading_phi_insts(&self, block: &crate::mir::BasicBlock) -> Vec<InstId> {
236 block
237 .instructions
238 .iter()
239 .copied()
240 .take_while(|&inst| matches!(self.func.instructions[inst].kind, InstKind::Phi(_)))
241 .collect()
242 }
243
244 fn carry_through_values(&self, loop_info: &Loop) -> Vec<ValueId> {
245 let mut carry_through = Vec::new();
246 for outer in &self.loops {
247 if outer.header == loop_info.header || !outer.blocks.contains(&loop_info.header) {
248 continue;
249 }
250 let Some(results) = self.header_results.get(&outer.header) else {
251 continue;
252 };
253 for &value in results {
254 if carry_through.contains(&value)
255 || !self.value_used_in_blocks(&loop_info.blocks, value)
256 {
257 continue;
258 }
259 carry_through.push(value);
260 }
261 }
262 carry_through
263 }
264
265 fn value_used_in_blocks(&self, blocks: &FxHashSet<BlockId>, value: ValueId) -> bool {
266 for &block_id in blocks {
267 let block = &self.func.blocks[block_id];
268 for &inst_id in &block.instructions {
269 if matches!(self.func.instructions[inst_id].kind, InstKind::Phi(_)) {
270 continue;
271 }
272 if self.func.instructions[inst_id].kind.operands().contains(&value) {
273 return true;
274 }
275 }
276 if block.terminator.as_ref().is_some_and(|term| term.operands().contains(&value)) {
277 return true;
278 }
279 }
280 false
281 }
282
283 fn phi_result_values(&self, phi_insts: &[InstId]) -> Option<Vec<ValueId>> {
284 phi_insts.iter().map(|&inst| self.func.inst_result_value(inst)).collect()
285 }
286
287 fn phi_sources_for_pred(&self, phi_insts: &[InstId], pred: BlockId) -> Option<Vec<ValueId>> {
288 phi_insts
289 .iter()
290 .map(|&inst| {
291 let InstKind::Phi(incoming) = &self.func.instructions[inst].kind else {
292 return None;
293 };
294 incoming.iter().find_map(|&(block, value)| (block == pred).then_some(value))
295 })
296 .collect()
297 }
298}
299
300pub struct EvmCodegen {
302 asm: Assembler,
304 scheduler: StackScheduler,
306 block_labels: FxHashMap<BlockId, Label>,
308 function_labels: FxHashMap<FunctionId, Label>,
310 function_static_frame_sizes: FxHashMap<FunctionId, u64>,
315 function_spill_sizes: FxHashMap<FunctionId, u64>,
317 pending_frame_size_consts: Vec<(DeferredConst, FunctionId, u64)>,
319 restorable_internal_frames: FxHashSet<FunctionId>,
321 block_copies: FxHashMap<BlockId, Vec<ParallelCopy>>,
323 stack_phi_sources: FxHashMap<BlockId, FxHashSet<ValueId>>,
325 runtime_immutable_refs: Vec<ImmutableRef>,
327 in_constructor: bool,
330 constructor_param_count: u32,
332 in_internal_function: bool,
334 optimization: OptimizationMode,
336 mir_print_after_each: bool,
338}
339
340impl EvmCodegen {
341 #[must_use]
343 pub fn new(config: impl Into<EvmCodegenConfig>) -> Self {
344 let config = config.into();
345 Self {
346 asm: Assembler::with_config(config.assembler_config()),
347 scheduler: StackScheduler::new(),
348 block_labels: FxHashMap::default(),
349 function_labels: FxHashMap::default(),
350 function_static_frame_sizes: FxHashMap::default(),
351 function_spill_sizes: FxHashMap::default(),
352 pending_frame_size_consts: Vec::new(),
353 restorable_internal_frames: FxHashSet::default(),
354 block_copies: FxHashMap::default(),
355 stack_phi_sources: FxHashMap::default(),
356 runtime_immutable_refs: Vec::new(),
357 in_constructor: false,
358 constructor_param_count: 0,
359 in_internal_function: false,
360 optimization: config.optimization,
361 mir_print_after_each: config.mir_print_after_each,
362 }
363 }
364
365 fn emit_stack_op(&mut self, op: StackOp) {
373 self.asm.emit_op(op.opcode());
374 match op {
375 StackOp::Dup(n) => self.scheduler.stack.dup(n),
376 StackOp::Swap(n) => self.scheduler.stack.swap(n),
377 StackOp::Pop => {
378 self.scheduler.stack.pop();
379 }
380 }
381 }
382
383 fn emit_op_with_effect(&mut self, opcode: u8, effect: StackEffect, push: StackPush) {
392 #[cfg(debug_assertions)]
393 let before = self.scheduler.depth();
394
395 self.asm.emit_op(opcode);
396
397 for _ in 0..effect.pops {
399 self.scheduler.stack.pop();
400 }
401
402 match (effect.pushes, push) {
404 (0, StackPush::None) => {}
405 (1, StackPush::Tracked(v)) => self.scheduler.stack.push(v),
406 (1, StackPush::Unknown) => self.scheduler.stack.push_unknown(),
407 (n, _) if n > 1 => {
408 for _ in 0..n {
410 self.scheduler.stack.push_unknown();
411 }
412 }
413 _ => {}
414 }
415
416 #[cfg(debug_assertions)]
417 {
418 let expected = before + effect.pushes - effect.pops;
419 debug_assert_eq!(
420 self.scheduler.depth(),
421 expected,
422 "Stack model drift after opcode 0x{:02x}: expected depth {}, got {}",
423 opcode,
424 expected,
425 self.scheduler.depth()
426 );
427 }
428 }
429
430 pub fn generate_module(&mut self, module: &mut Module) -> Vec<u8> {
435 if module.is_interface {
436 return Vec::new();
437 }
438 self.run_optimization_passes(module);
439 self.generate_runtime_code(module)
442 }
443
444 pub fn generate_deployment_bytecode(&mut self, module: &mut Module) -> (Vec<u8>, Vec<u8>) {
450 if module.is_interface {
451 return (Vec::new(), Vec::new());
452 }
453 self.run_optimization_passes(module);
454 let runtime_code = self.generate_runtime_code(module);
456 let runtime_len = runtime_code.len();
457 let immutable_refs = std::mem::take(&mut self.runtime_immutable_refs);
458
459 let copy_base = if !immutable_refs.is_empty() && runtime_len as u64 > IMMUTABLE_SCRATCH_BASE
464 {
465 IMMUTABLE_SCRATCH_BASE + module.immutable_data_len() as u64
466 } else {
467 0
468 };
469
470 let mut deploy_code_len = 0usize;
474 let mut constructor_arg_offset = runtime_len;
475 let mut constructor_code = self.generate_constructor_code(module, Some(runtime_len));
476 for _ in 0..8 {
477 let postlude = self.build_deployment_postlude(
478 deploy_code_len,
479 runtime_len,
480 copy_base,
481 &immutable_refs,
482 );
483 let next_deploy_code_len = constructor_code.len() + postlude.len();
484 let next_arg_offset = next_deploy_code_len + runtime_len;
485 if next_deploy_code_len == deploy_code_len && next_arg_offset == constructor_arg_offset
486 {
487 break;
488 }
489 deploy_code_len = next_deploy_code_len;
490 constructor_arg_offset = next_arg_offset;
491 constructor_code = self.generate_constructor_code(module, Some(constructor_arg_offset));
492 }
493
494 let postlude = self.build_deployment_postlude(
505 deploy_code_len,
506 runtime_len,
507 copy_base,
508 &immutable_refs,
509 );
510
511 let mut deploy_bytecode = Vec::new();
513
514 deploy_bytecode.extend_from_slice(&constructor_code);
516 deploy_bytecode.extend_from_slice(&postlude);
517
518 deploy_bytecode.extend_from_slice(&runtime_code);
520
521 (deploy_bytecode, runtime_code)
524 }
525
526 fn build_deployment_postlude(
527 &mut self,
528 deploy_code_len: usize,
529 runtime_len: usize,
530 copy_base: u64,
531 immutable_refs: &[ImmutableRef],
532 ) -> Vec<u8> {
533 self.asm.clear();
534
535 self.asm.emit_push(U256::from(runtime_len as u64));
537 self.asm.emit_op(op::dup(1));
538 self.asm.emit_push(U256::from(deploy_code_len as u64));
539 self.asm.emit_push(U256::from(copy_base));
540 self.asm.emit_op(op::CODECOPY);
541
542 for r in immutable_refs {
545 self.asm.emit_push(U256::from(IMMUTABLE_SCRATCH_BASE + u64::from(r.id)));
546 self.asm.emit_op(op::MLOAD);
547 self.asm.emit_push(U256::from(copy_base + r.code_offset as u64 + 1));
548 self.asm.emit_op(op::MSTORE);
549 }
550
551 self.asm.emit_push(U256::from(copy_base));
553 self.asm.emit_op(op::RETURN);
554 self.asm.assemble().bytecode
555 }
556
557 fn generate_constructor_code(
563 &mut self,
564 module: &Module,
565 constructor_arg_offset: Option<usize>,
566 ) -> Vec<u8> {
567 let constructor =
569 module.functions.iter_enumerated().find(|(_, f)| f.attributes.is_constructor);
570
571 if let Some((ctor_id, ctor)) = constructor {
572 self.asm.clear();
574
575 self.block_labels.clear();
577 self.block_copies.clear();
578 self.function_labels.clear();
579 self.function_static_frame_sizes.clear();
580 self.function_spill_sizes.clear();
581 self.pending_frame_size_consts.clear();
582 self.restorable_internal_frames.clear();
583 self.stack_phi_sources.clear();
584
585 for (func_id, func) in module.functions.iter_enumerated() {
586 self.function_static_frame_sizes.insert(func_id, func.internal_frame_size);
587 if !func.params.iter().chain(&func.returns).any(|ty| matches!(ty, MirType::MemPtr))
588 {
589 self.restorable_internal_frames.insert(func_id);
590 }
591 }
592
593 let call_graph = CallGraphInfo::new(module);
594 let internal_targets = call_graph.reachable_bodies_from(std::iter::once(ctor_id));
595 for &func_id in &internal_targets {
596 self.function_labels.insert(func_id, self.asm.new_label());
597 }
598
599 let constructor_free_memory_start = self.asm.new_deferred_const();
604 self.asm.emit_push_deferred(constructor_free_memory_start);
605 self.asm.emit_push(U256::from(0x40));
606 self.asm.emit_op(op::MSTORE);
607
608 self.in_constructor = true;
610 self.constructor_param_count = ctor.params.len() as u32;
611
612 if !ctor.params.is_empty() {
616 let arg_offset = constructor_arg_offset.unwrap_or(0);
617 self.asm.emit_push(U256::from(arg_offset));
618 self.asm.emit_op(op::CODESIZE);
619 self.asm.emit_op(op::SUB); self.asm.emit_push(U256::from(arg_offset)); self.asm.emit_push(U256::from(0x80)); self.asm.emit_op(op::CODECOPY);
623 }
624
625 if !internal_targets.is_empty() {
626 let constructor_entry = self.asm.new_label();
627 self.asm.emit_push_label(constructor_entry);
628 self.asm.emit_op(op::JUMP);
629
630 for (func_id, func) in module.functions.iter_enumerated() {
631 if !internal_targets.contains(&func_id) {
632 continue;
633 }
634 let label = self.function_labels[&func_id];
635 self.asm.define_label(label);
636 self.in_internal_function = true;
637 self.generate_function_body(func);
638 self.in_internal_function = false;
639 self.record_function_spill_size(func_id);
640 }
641
642 self.asm.define_label(constructor_entry);
643 }
644
645 self.generate_function_body(ctor);
647 let constructor_spill_size = self.record_function_spill_size(ctor_id);
648 self.asm.set_deferred_const(
649 constructor_free_memory_start,
650 U256::from(Self::constructor_free_memory_start(constructor_spill_size)),
651 );
652
653 self.resolve_pending_frame_size_consts(module);
654
655 self.in_constructor = false;
657 self.constructor_param_count = 0;
658
659 let mut bytecode = self.asm.assemble().bytecode;
660
661 if bytecode.last() == Some(&op::STOP) {
663 bytecode.pop();
664 }
665
666 bytecode
667 } else {
668 Vec::new()
669 }
670 }
671
672 fn run_optimization_passes(&mut self, module: &mut Module) {
674 if self.optimization == OptimizationMode::None {
675 return;
676 }
677 run_default_pipeline_with_options(
678 module,
679 PipelineOptions {
680 print_after_each: self.mir_print_after_each,
681 ..PipelineOptions::default()
682 },
683 );
684 }
685
686 fn generate_runtime_code(&mut self, module: &Module) -> Vec<u8> {
688 self.asm.clear();
689 self.block_labels.clear();
690 self.function_labels.clear();
691 self.function_static_frame_sizes.clear();
692 self.function_spill_sizes.clear();
693 self.pending_frame_size_consts.clear();
694 self.restorable_internal_frames.clear();
695 self.block_copies.clear();
696 self.stack_phi_sources.clear();
697
698 if !module.functions.is_empty() {
699 self.generate_dispatcher(module);
701 }
702
703 let result = self.asm.assemble();
704 self.runtime_immutable_refs = result.immutable_refs;
705 result.bytecode
706 }
707
708 fn generate_dispatcher(&mut self, module: &Module) {
722 let receive_idx =
725 module.functions.iter().position(|f| f.attributes.is_receive && Self::has_body(f));
726 let fallback_idx =
727 module.functions.iter().position(|f| f.attributes.is_fallback && Self::has_body(f));
728
729 let call_graph = CallGraphInfo::new(module);
730 let internal_targets = call_graph.reachable_bodies_from(
731 module
732 .functions
733 .iter_enumerated()
734 .filter_map(|(func_id, func)| Self::is_external_entry(func).then_some(func_id)),
735 );
736
737 for (func_id, func) in module.functions.iter_enumerated() {
738 self.function_static_frame_sizes.insert(func_id, func.internal_frame_size);
739 if !func.params.iter().chain(&func.returns).any(|ty| matches!(ty, MirType::MemPtr)) {
740 self.restorable_internal_frames.insert(func_id);
741 }
742 }
743
744 let mut func_labels: Vec<Option<Label>> = Vec::new();
746 for (func_id, func) in module.functions.iter_enumerated() {
747 let external = Self::is_external_entry(func);
748 let needs_body =
749 external || (Self::has_body(func) && internal_targets.contains(&func_id));
750 let label = needs_body.then(|| self.asm.new_label());
751 if let Some(label) = label {
752 self.function_labels.insert(func_id, label);
753 }
754 func_labels.push(label);
755 }
756 let revert_label = self.asm.new_label();
757 self.asm.mark_label_cold(revert_label);
758 let has_calldata_label = self.asm.new_label();
759 let all_external_entries_reject_value =
760 module.functions.iter().any(Self::is_external_entry)
761 && module
762 .functions
763 .iter()
764 .filter(|func| Self::is_external_entry(func))
765 .all(Self::rejects_callvalue);
766
767 if all_external_entries_reject_value {
768 self.emit_callvalue_check(revert_label);
769 }
770
771 self.asm.emit_op(op::CALLDATASIZE);
773 self.asm.emit_push_label(has_calldata_label);
774 self.asm.emit_op(op::JUMPI);
775
776 if let Some(recv_idx) = receive_idx {
780 self.asm.emit_push_label(func_labels[recv_idx].expect("receive label missing"));
781 self.asm.emit_op(op::JUMP);
782 } else if let Some(fb_idx) = fallback_idx {
783 self.asm.emit_push_label(func_labels[fb_idx].expect("fallback label missing"));
784 self.asm.emit_op(op::JUMP);
785 } else {
786 self.asm.emit_push_label(revert_label);
787 self.asm.emit_op(op::JUMP);
788 }
789
790 self.asm.define_label(has_calldata_label);
792
793 self.asm.emit_push(U256::ZERO);
795 self.asm.emit_op(op::CALLDATALOAD);
796 self.asm.emit_push(U256::from(0xe0));
797 self.asm.emit_op(op::SHR);
798
799 let mut selectors: Vec<_> = module
800 .functions
801 .iter()
802 .enumerate()
803 .filter_map(|(i, func)| {
804 if !Self::is_external_entry(func) {
805 return None;
806 }
807 let selector = func.selector?;
808 Some(SelectorDispatchEntry {
809 selector: u32::from_be_bytes(selector),
810 label: func_labels[i].expect("selector label missing"),
811 })
812 })
813 .collect();
814 selectors.sort_by_key(|entry| entry.selector);
815
816 let fallback_label =
817 fallback_idx.map(|idx| func_labels[idx].expect("fallback label missing"));
818 self.emit_selector_dispatch(&selectors, fallback_label, revert_label);
819
820 for (func_id, func) in module.functions.iter_enumerated() {
822 if !Self::is_external_entry(func) {
823 continue;
824 }
825 let Some(label) = func_labels[func_id.index()] else { continue };
826 self.asm.define_label(label);
827
828 if func.selector.is_some() {
830 self.asm.emit_op(op::POP);
831 }
832
833 if !all_external_entries_reject_value {
834 self.emit_payable_check(func, revert_label);
835 }
836
837 let free_memory_start = self.emit_external_free_memory_start();
838
839 self.in_internal_function = false;
841 self.generate_function_body(func);
842
843 let spill_size = self.record_function_spill_size(func_id);
844 self.asm.set_deferred_const(
845 free_memory_start,
846 U256::from(Self::external_spill_base(func) + spill_size),
847 );
848 }
849
850 for (func_id, func) in module.functions.iter_enumerated() {
853 if Self::is_external_entry(func) || !Self::has_body(func) {
854 continue;
855 }
856 let Some(label) = func_labels[func_id.index()] else { continue };
857 self.asm.define_label(label);
858 self.in_internal_function = true;
859 self.generate_function_body(func);
860 self.in_internal_function = false;
861 self.record_function_spill_size(func_id);
862 }
863
864 self.asm.define_label(revert_label);
866 self.asm.emit_push(U256::ZERO);
867 self.asm.emit_push(U256::ZERO);
868 self.asm.emit_op(op::REVERT);
869
870 self.resolve_pending_frame_size_consts(module);
871 }
872
873 fn record_function_spill_size(&mut self, func_id: FunctionId) -> u64 {
875 let spill_size = u64::from(self.scheduler.spills.spill_area_size());
876 self.function_spill_sizes.insert(func_id, spill_size);
877 spill_size
878 }
879
880 fn resolve_pending_frame_size_consts(&mut self, module: &Module) {
886 for (id, callee, static_size) in std::mem::take(&mut self.pending_frame_size_consts) {
887 let spill_size =
888 self.function_spill_sizes.get(&callee).copied().unwrap_or_else(|| {
889 Self::conservative_spill_frame_size(&module.functions[callee])
890 });
891 self.asm.set_deferred_const(id, U256::from(static_size + spill_size));
892 }
893 }
894
895 fn is_external_entry(func: &Function) -> bool {
896 Self::has_body(func)
897 && (func.selector.is_some()
898 || func.attributes.is_receive
899 || func.attributes.is_fallback)
900 }
901
902 fn has_body(func: &Function) -> bool {
903 !func.attributes.is_constructor && !func.blocks.is_empty()
904 }
905
906 fn emit_selector_dispatch(
907 &mut self,
908 selectors: &[SelectorDispatchEntry],
909 fallback_label: Option<Label>,
910 revert_label: Label,
911 ) {
912 if selectors.len() <= LINEAR_SELECTOR_DISPATCH_THRESHOLD {
913 self.emit_linear_selector_dispatch(selectors, fallback_label, revert_label);
914 } else {
915 self.emit_binary_selector_dispatch(selectors, fallback_label, revert_label);
916 }
917 }
918
919 fn emit_linear_selector_dispatch(
920 &mut self,
921 selectors: &[SelectorDispatchEntry],
922 fallback_label: Option<Label>,
923 revert_label: Label,
924 ) {
925 for entry in selectors {
926 self.emit_selector_eq_jump(*entry);
927 }
928 self.emit_selector_dispatch_miss(fallback_label, revert_label);
929 }
930
931 fn emit_binary_selector_dispatch(
932 &mut self,
933 selectors: &[SelectorDispatchEntry],
934 fallback_label: Option<Label>,
935 revert_label: Label,
936 ) {
937 if selectors.len() <= LINEAR_SELECTOR_DISPATCH_THRESHOLD {
938 self.emit_linear_selector_dispatch(selectors, fallback_label, revert_label);
939 return;
940 }
941
942 let mid = selectors.len() / 2;
943 let left_label = self.asm.new_label();
944
945 self.asm.emit_op(op::dup(1));
948 self.asm.emit_push(U256::from(selectors[mid].selector));
949 self.asm.emit_op(op::GT);
950 self.asm.emit_push_label(left_label);
951 self.asm.emit_op(op::JUMPI);
952
953 self.emit_binary_selector_dispatch(&selectors[mid..], fallback_label, revert_label);
954
955 self.asm.define_label(left_label);
956 self.emit_binary_selector_dispatch(&selectors[..mid], fallback_label, revert_label);
957 }
958
959 fn emit_selector_eq_jump(&mut self, entry: SelectorDispatchEntry) {
960 self.asm.emit_op(op::dup(1));
961 self.asm.emit_push(U256::from(entry.selector));
962 self.asm.emit_op(op::EQ);
963 self.asm.emit_push_label(entry.label);
964 self.asm.emit_op(op::JUMPI);
965 }
966
967 fn emit_selector_dispatch_miss(&mut self, fallback_label: Option<Label>, revert_label: Label) {
968 if let Some(fallback_label) = fallback_label {
969 self.asm.emit_op(op::POP);
970 self.asm.emit_push_label(fallback_label);
971 self.asm.emit_op(op::JUMP);
972 } else {
973 self.asm.emit_push_label(revert_label);
974 self.asm.emit_op(op::JUMP);
975 }
976 }
977
978 fn emit_payable_check(&mut self, func: &Function, revert_label: Label) {
981 if Self::rejects_callvalue(func) {
982 self.emit_callvalue_check(revert_label);
983 }
984 }
985
986 fn rejects_callvalue(func: &Function) -> bool {
987 use solar_sema::hir::StateMutability;
988
989 matches!(
990 func.attributes.state_mutability,
991 StateMutability::NonPayable | StateMutability::View | StateMutability::Pure
992 )
993 }
994
995 fn emit_callvalue_check(&mut self, revert_label: Label) {
996 self.asm.emit_op(op::CALLVALUE);
997 self.asm.emit_push_label(revert_label);
998 self.asm.emit_op(op::JUMPI);
999 }
1000
1001 #[allow(dead_code)]
1003 fn generate_function(&mut self, func: &Function) {
1004 self.generate_function_body(func);
1005 }
1006
1007 fn generate_function_body(&mut self, func: &Function) {
1009 let mut am = AnalysisManager::new();
1013 let liveness: &Liveness = am.get_or_compute(&LivenessAnalysis, func);
1014
1015 let phi_result = PhiEliminator::analyze(func);
1017 for (block_id, copies) in phi_result.block_copies {
1018 self.block_copies.insert(block_id, copies.copies);
1019 }
1020 let stack_phi_plan = StackPhiPlan::analyze(func);
1021 self.stack_phi_sources = stack_phi_plan.edge_sources.clone();
1022
1023 self.scheduler = StackScheduler::new();
1025
1026 self.preallocate_cross_block_spills(func, liveness);
1027
1028 self.block_labels.clear();
1030 for block_id in func.blocks.indices() {
1031 let label = self.asm.new_label();
1032 if Self::block_is_cold(func, block_id) {
1033 self.asm.mark_label_cold(label);
1034 }
1035 self.block_labels.insert(block_id, label);
1036 }
1037
1038 let block_order = Self::block_layout_order(func);
1040 let block_pos: FxHashMap<BlockId, usize> =
1041 block_order.iter().enumerate().map(|(pos, &b)| (b, pos)).collect();
1042 let mut block_entry_stacks: FxHashMap<BlockId, StackModel> = FxHashMap::default();
1046 let mut preserved_fallthrough: Option<BlockId> = None;
1047 for (pos, &block_id) in block_order.iter().enumerate() {
1048 let block = &func.blocks[block_id];
1049 let fallthrough = block_order.get(pos + 1).copied();
1050 let entered_by_preserved_fallthrough = preserved_fallthrough == Some(block_id);
1051 preserved_fallthrough = None;
1052
1053 let label = self.block_labels[&block_id];
1054 if !entered_by_preserved_fallthrough
1055 && (block_id != func.entry_block || !block.predecessors.is_empty())
1056 {
1057 self.asm.define_label(label);
1058 }
1059
1060 if !entered_by_preserved_fallthrough {
1066 if let Some(entry_stack) = block_entry_stacks.remove(&block_id) {
1067 self.scheduler.stack = entry_stack;
1068 self.mark_live_in_spills(func, liveness, block_id);
1070 } else if let Some(entry) = stack_phi_plan.entries.get(&block_id) {
1071 self.set_stack_to_values(entry);
1072 self.mark_live_in_spills(func, liveness, block_id);
1073 } else {
1074 self.scheduler.clear_stack();
1075 self.mark_live_in_spills(func, liveness, block_id);
1076 }
1077 }
1078
1079 for (inst_idx, &inst_id) in block.instructions.iter().enumerate() {
1081 let inst = &func.instructions[inst_id];
1082
1083 if matches!(inst.kind, InstKind::Phi(_)) {
1085 continue;
1086 }
1087
1088 let result_value = func.inst_result_value(inst_id);
1090
1091 self.generate_inst(func, &inst.kind, liveness, block_id, inst_idx, result_value);
1093 if let Some(result) = result_value {
1094 self.spill_reserved_result_if_live(func, liveness, block_id, inst_idx, result);
1095 }
1096 }
1097
1098 let stack_phi_preserved = stack_phi_plan.edges.get(&block_id).is_some_and(|edge| {
1099 if !self.can_prepare_stack_phi_edge(func, edge) {
1100 return false;
1101 }
1102 self.spill_live_out_values_except(func, liveness, block_id, &edge.sources);
1103 self.pop_stack_values_not_needed_by(&edge.sources);
1104 self.try_emit_stack_phi_edge(func, edge)
1105 });
1106
1107 if stack_phi_preserved {
1111 self.block_copies.remove(&block_id);
1112 } else if let Some(copies) = self.block_copies.remove(&block_id) {
1113 let mut temps = FxHashMap::default();
1114 for copy in &copies {
1115 self.generate_copy(func, copy, &mut temps);
1116 }
1117 }
1118
1119 let preserve_stack_to_fallthrough =
1120 self.can_preserve_stack_fallthrough(func, block_id, fallthrough);
1121
1122 let preserve_jump_target = (!preserve_stack_to_fallthrough)
1126 .then(|| self.single_pred_jump_target(func, block_id, fallthrough))
1127 .flatten()
1128 .filter(|target| block_pos.get(target).copied() > Some(pos));
1129
1130 let preserve_branch_targets =
1134 if !preserve_stack_to_fallthrough && preserve_jump_target.is_none() {
1135 self.branch_preserve_targets(func, liveness, block_id, pos, &block_pos)
1136 } else {
1137 Vec::new()
1138 };
1139
1140 let preserve_stack = preserve_stack_to_fallthrough
1141 || preserve_jump_target.is_some()
1142 || !preserve_branch_targets.is_empty()
1143 || stack_phi_preserved;
1144
1145 if !preserve_stack {
1148 self.spill_live_out_values(func, liveness, block_id);
1149 }
1150
1151 if let Some(term) = &block.terminator {
1153 self.generate_terminator(func, term, fallthrough, preserve_stack);
1154 }
1155 if preserve_stack_to_fallthrough {
1156 preserved_fallthrough = fallthrough;
1157 } else if let Some(target) = preserve_jump_target {
1158 block_entry_stacks.insert(target, self.scheduler.stack.clone());
1159 }
1160 for target in preserve_branch_targets {
1161 block_entry_stacks.insert(target, self.scheduler.stack.clone());
1162 }
1163 }
1164 }
1165
1166 fn single_pred_jump_target(
1170 &self,
1171 func: &Function,
1172 block_id: BlockId,
1173 fallthrough: Option<BlockId>,
1174 ) -> Option<BlockId> {
1175 let Some(Terminator::Jump(target)) = func.blocks[block_id].terminator.as_ref() else {
1176 return None;
1177 };
1178 if Some(*target) == fallthrough
1179 || func.blocks[*target].predecessors.as_slice() != [block_id]
1180 {
1181 return None;
1182 }
1183 let has_phi = func.blocks[*target]
1184 .instructions
1185 .iter()
1186 .any(|&inst| matches!(func.instructions[inst].kind, InstKind::Phi(_)));
1187 (!has_phi).then_some(*target)
1188 }
1189
1190 fn branch_preserve_targets(
1198 &self,
1199 func: &Function,
1200 liveness: &Liveness,
1201 block_id: BlockId,
1202 pos: usize,
1203 block_pos: &FxHashMap<BlockId, usize>,
1204 ) -> Vec<BlockId> {
1205 let Some(Terminator::Branch { condition, then_block, else_block }) =
1206 func.blocks[block_id].terminator.as_ref()
1207 else {
1208 return Vec::new();
1209 };
1210
1211 if self.scheduler.stack.depth() <= 1 || self.scheduler.stack.top() != Some(*condition) {
1212 return Vec::new();
1213 }
1214
1215 let Some(carried) = self
1216 .scheduler
1217 .stack
1218 .iter()
1219 .skip(1)
1220 .map(|slot| {
1221 let value = slot?;
1222 liveness.live_out(block_id).contains(value).then_some(value)
1223 })
1224 .collect::<Option<Vec<_>>>()
1225 else {
1226 return Vec::new();
1227 };
1228 if carried.len() > STACK_PHI_LAYOUT_LIMIT {
1229 return Vec::new();
1230 }
1231
1232 let targets = [*then_block, *else_block];
1233 let mut live_in_any_target = FxHashSet::default();
1234 for target in targets {
1235 live_in_any_target.extend(liveness.live_in(target).iter());
1236 }
1237 if carried.iter().any(|value| !live_in_any_target.contains(value)) {
1238 return Vec::new();
1239 }
1240
1241 for target in targets {
1242 if target == block_id
1243 || func.blocks[target].predecessors.as_slice() != [block_id]
1244 || block_pos.get(&target).copied() <= Some(pos)
1245 || func.blocks[target]
1246 .instructions
1247 .iter()
1248 .any(|&inst| matches!(func.instructions[inst].kind, InstKind::Phi(_)))
1249 {
1250 return Vec::new();
1251 }
1252 }
1253
1254 targets.into()
1255 }
1256
1257 fn block_is_cold(func: &Function, block_id: BlockId) -> bool {
1260 matches!(
1261 func.blocks[block_id].terminator,
1262 Some(Terminator::Revert { .. } | Terminator::Invalid)
1263 )
1264 }
1265
1266 fn block_layout_order(func: &Function) -> Vec<BlockId> {
1267 let cfg = CfgInfo::new(func);
1268 let reachable = cfg.reachable();
1269 let mut order = Vec::with_capacity(func.blocks.len());
1270 let mut placed = FxHashSet::default();
1271
1272 Self::append_layout_chain(func, func.entry_block, reachable, &mut placed, &mut order);
1273 for block_id in func.blocks.indices() {
1274 if reachable.contains(&block_id) {
1275 Self::append_layout_chain(func, block_id, reachable, &mut placed, &mut order);
1276 }
1277 }
1278
1279 order
1280 }
1281
1282 fn append_layout_chain(
1283 func: &Function,
1284 mut block_id: BlockId,
1285 reachable: &FxHashSet<BlockId>,
1286 placed: &mut FxHashSet<BlockId>,
1287 order: &mut Vec<BlockId>,
1288 ) {
1289 loop {
1290 if !reachable.contains(&block_id) || !placed.insert(block_id) {
1291 return;
1292 }
1293 order.push(block_id);
1294
1295 let Some(Terminator::Jump(target)) = func.blocks[block_id].terminator.as_ref() else {
1296 return;
1297 };
1298 if placed.contains(target) || func.blocks[*target].predecessors.as_slice() != [block_id]
1299 {
1300 return;
1301 }
1302
1303 block_id = *target;
1304 }
1305 }
1306
1307 fn set_stack_to_values(&mut self, values: &[ValueId]) {
1308 self.scheduler.stack.clear();
1309 for &value in values.iter().rev() {
1310 self.scheduler.stack.push(value);
1311 }
1312 }
1313
1314 fn try_emit_stack_phi_edge(&mut self, func: &Function, edge: &StackPhiEdge) -> bool {
1315 if edge.sources.len() != edge.results.len()
1316 || edge.sources.is_empty()
1317 || edge.sources.len() > STACK_PHI_LAYOUT_LIMIT
1318 {
1319 return false;
1320 }
1321 if !self.stack_contains_only_phi_sources(&edge.sources) {
1322 return false;
1323 }
1324
1325 for &source in Self::missing_stack_phi_sources(&self.scheduler.stack, &edge.sources).iter()
1326 {
1327 if !self.scheduler.can_emit_value(source, func) {
1328 return false;
1329 }
1330 self.emit_operand(func, source);
1331 }
1332 if !self.stack_contains_only_phi_sources(&edge.sources) {
1333 return false;
1334 }
1335
1336 let target: Vec<_> = edge.sources.iter().copied().map(TargetSlot::Value).collect();
1337 let shuffle = self.scheduler.shuffle_to_layout(&target);
1338 for op in shuffle.ops {
1339 self.asm.emit_op(op.opcode());
1340 }
1341
1342 if self.scheduler.depth() != edge.sources.len() {
1343 return false;
1344 }
1345 self.set_stack_to_values(&edge.results);
1346 true
1347 }
1348
1349 fn can_prepare_stack_phi_edge(&self, func: &Function, edge: &StackPhiEdge) -> bool {
1350 if edge.sources.len() != edge.results.len()
1351 || edge.sources.is_empty()
1352 || edge.sources.len() > STACK_PHI_LAYOUT_LIMIT
1353 {
1354 return false;
1355 }
1356
1357 let present =
1358 Self::stack_phi_source_counts_after_trim(&self.scheduler.stack, &edge.sources);
1359 if present.len() > STACK_PHI_LAYOUT_LIMIT {
1360 return false;
1361 }
1362
1363 let mut seen = Self::value_counts(present);
1364 for &source in &edge.sources {
1365 if let Some(count) = seen.get_mut(&source)
1366 && *count > 0
1367 {
1368 *count -= 1;
1369 continue;
1370 }
1371 if !self.scheduler.can_emit_value(source, func) {
1372 return false;
1373 }
1374 }
1375 true
1376 }
1377
1378 fn stack_phi_source_counts_after_trim(stack: &StackModel, sources: &[ValueId]) -> Vec<ValueId> {
1379 let mut remaining = Self::value_counts(sources.iter().copied());
1380 let mut kept = Vec::new();
1381 for value in stack.iter().flatten() {
1382 if let Some(count) = remaining.get_mut(&value)
1383 && *count > 0
1384 {
1385 *count -= 1;
1386 kept.push(value);
1387 }
1388 }
1389 kept
1390 }
1391
1392 fn stack_contains_only_phi_sources(&self, sources: &[ValueId]) -> bool {
1393 let mut remaining = Self::value_counts(sources.iter().copied());
1394 for slot in self.scheduler.stack.iter() {
1395 let Some(value) = slot else {
1396 return false;
1397 };
1398 let Some(count) = remaining.get_mut(&value) else {
1399 return false;
1400 };
1401 if *count == 0 {
1402 return false;
1403 }
1404 *count -= 1;
1405 }
1406 true
1407 }
1408
1409 fn missing_stack_phi_sources(stack: &StackModel, sources: &[ValueId]) -> Vec<ValueId> {
1410 let mut needed = Self::value_counts(sources.iter().copied());
1411 for value in stack.iter().flatten() {
1412 if let Some(count) = needed.get_mut(&value)
1413 && *count > 0
1414 {
1415 *count -= 1;
1416 }
1417 }
1418
1419 let mut missing = Vec::new();
1420 for &source in sources {
1421 if let Some(count) = needed.get_mut(&source)
1422 && *count > 0
1423 {
1424 missing.push(source);
1425 *count -= 1;
1426 }
1427 }
1428 missing
1429 }
1430
1431 fn value_counts(values: impl IntoIterator<Item = ValueId>) -> FxHashMap<ValueId, usize> {
1432 let mut counts = FxHashMap::default();
1433 for value in values {
1434 *counts.entry(value).or_insert(0) += 1;
1435 }
1436 counts
1437 }
1438
1439 fn can_preserve_stack_fallthrough(
1440 &self,
1441 func: &Function,
1442 block_id: BlockId,
1443 fallthrough: Option<BlockId>,
1444 ) -> bool {
1445 let Some(Terminator::Jump(target)) = func.blocks[block_id].terminator.as_ref() else {
1446 return false;
1447 };
1448 if Some(*target) != fallthrough {
1449 return false;
1450 }
1451
1452 func.blocks[*target].predecessors.as_slice() == [block_id]
1455 }
1456
1457 fn is_stack_phi_source(&self, block: BlockId, value: ValueId) -> bool {
1458 self.stack_phi_sources.get(&block).is_some_and(|sources| sources.contains(&value))
1459 }
1460
1461 fn preallocate_cross_block_spills(&mut self, func: &Function, liveness: &Liveness) {
1468 for val in Self::cross_block_spill_values(func, liveness) {
1469 self.scheduler.spills.allocate(val);
1470 }
1471 }
1472
1473 fn cross_block_spill_values(func: &Function, liveness: &Liveness) -> FxHashSet<ValueId> {
1474 let mut values = FxHashSet::default();
1475 for block_id in func.blocks.indices() {
1476 for val in liveness.live_in(block_id).iter().chain(liveness.live_out(block_id).iter()) {
1477 if matches!(func.value(val), crate::mir::Value::Inst(_)) {
1478 values.insert(val);
1479 }
1480 }
1481 for &inst_id in &func.blocks[block_id].instructions {
1482 if matches!(func.instructions[inst_id].kind, InstKind::Phi(_))
1483 && let Some(val) = func.inst_result_value(inst_id)
1484 {
1485 values.insert(val);
1486 }
1487 }
1488 }
1489 values
1490 }
1491
1492 fn spill_live_out_values(&mut self, func: &Function, liveness: &Liveness, block_id: BlockId) {
1495 let live_out = liveness.live_out(block_id);
1496
1497 for val in live_out.iter() {
1498 self.spill_value_if_needed(func, val);
1499 }
1500 }
1501
1502 fn spill_live_out_values_except(
1503 &mut self,
1504 func: &Function,
1505 liveness: &Liveness,
1506 block_id: BlockId,
1507 exempt: &[ValueId],
1508 ) {
1509 let exempt: FxHashSet<_> = exempt.iter().copied().collect();
1510 for val in liveness.live_out(block_id).iter() {
1511 if !exempt.contains(&val) {
1512 self.spill_value_if_needed(func, val);
1513 }
1514 }
1515 }
1516
1517 fn pop_stack_values_not_needed_by(&mut self, needed: &[ValueId]) {
1518 while let Some(depth) = self.first_stack_value_not_needed_by(needed) {
1519 if depth > 0 {
1520 self.emit_stack_op(StackOp::Swap(depth as u8));
1521 }
1522 self.emit_stack_op(StackOp::Pop);
1523 }
1524 }
1525
1526 fn first_stack_value_not_needed_by(&self, needed: &[ValueId]) -> Option<usize> {
1527 let mut remaining = Self::value_counts(needed.iter().copied());
1528 for (depth, slot) in self.scheduler.stack.iter().enumerate() {
1529 let Some(value) = slot else {
1530 return Some(depth);
1531 };
1532 let Some(count) = remaining.get_mut(&value) else {
1533 return Some(depth);
1534 };
1535 if *count == 0 {
1536 return Some(depth);
1537 }
1538 *count -= 1;
1539 }
1540 None
1541 }
1542
1543 fn mark_live_in_spills(&mut self, func: &Function, liveness: &Liveness, block_id: BlockId) {
1544 for val in liveness.live_in(block_id).iter() {
1548 if !self.scheduler.stack.contains(val) && self.scheduler.spills.get(val).is_some() {
1549 self.scheduler.spills.mark_reloadable(val);
1550 }
1551 }
1552 for &inst_id in &func.blocks[block_id].instructions {
1553 if matches!(func.instructions[inst_id].kind, InstKind::Phi(_))
1554 && let Some(val) = func.inst_result_value(inst_id)
1555 && !self.scheduler.stack.contains(val)
1556 && self.scheduler.spills.get(val).is_some()
1557 {
1558 self.scheduler.spills.mark_reloadable(val);
1559 }
1560 }
1561 }
1562
1563 fn spill_values_before_stack_clear(&mut self, func: &Function, values: &[ValueId]) {
1564 for &value in values {
1565 self.spill_value_if_needed(func, value);
1566 }
1567 }
1568
1569 fn spill_value_if_needed(&mut self, func: &Function, val: ValueId) {
1572 match func.value(val) {
1574 crate::mir::Value::Immediate(_) | crate::mir::Value::Arg { .. } => return,
1575 _ => {}
1576 }
1577
1578 if self.scheduler.spills.is_stored(val) {
1579 return;
1580 }
1581
1582 if let Some(depth) = self.scheduler.stack.find(val) {
1583 let slot = self.scheduler.spills.allocate(val);
1584 if depth >= MAX_STACK_ACCESS {
1585 self.spill_deep_stack_value(func, val, slot, depth);
1586 return;
1587 }
1588
1589 self.spill_accessible_stack_value(func, val, slot, depth);
1590 }
1591 }
1592
1593 fn spill_value_to_reserved_slot(&mut self, func: &Function, val: ValueId) -> bool {
1594 if Self::is_rematerializable_value(func, val) || self.scheduler.spills.get(val).is_none() {
1595 return false;
1596 }
1597
1598 let Some(depth) = self.scheduler.stack.find(val) else {
1599 return false;
1600 };
1601 let slot = self.scheduler.spills.allocate(val);
1602 if depth >= MAX_STACK_ACCESS {
1603 self.spill_deep_stack_value(func, val, slot, depth);
1604 } else {
1605 self.spill_accessible_stack_value(func, val, slot, depth);
1606 }
1607 true
1608 }
1609
1610 fn spill_reserved_result_if_live(
1611 &mut self,
1612 func: &Function,
1613 liveness: &Liveness,
1614 block: BlockId,
1615 inst_idx: usize,
1616 value: ValueId,
1617 ) {
1618 if self.scheduler.spills.get(value).is_none()
1623 || !self.scheduler.spills.is_stored(value)
1624 || liveness.is_dead_after(value, block, inst_idx)
1625 {
1626 return;
1627 }
1628
1629 self.spill_value_to_reserved_slot(func, value);
1630 }
1631
1632 fn spill_accessible_stack_value(
1633 &mut self,
1634 func: &Function,
1635 val: ValueId,
1636 slot: SpillSlot,
1637 depth: usize,
1638 ) {
1639 debug_assert!(depth < MAX_STACK_ACCESS);
1640
1641 let dup_n = (depth + 1) as u8;
1646 self.asm.emit_op(op::dup(dup_n));
1647 self.scheduler.stack.dup(dup_n);
1648
1649 self.store_stack_top_to_spill(func, val, slot);
1650 }
1651
1652 fn spill_deep_stack_value(
1653 &mut self,
1654 func: &Function,
1655 val: ValueId,
1656 slot: SpillSlot,
1657 depth: usize,
1658 ) {
1659 debug_assert!(depth >= MAX_STACK_ACCESS);
1660
1661 let mut saved_above = Vec::with_capacity(depth + 1 - MAX_STACK_ACCESS);
1662 for _ in 0..(depth + 1 - MAX_STACK_ACCESS) {
1663 let Some(top) = self.scheduler.stack.top() else {
1664 panic!("cannot spill deep stack value {val:?}: untracked stack entry above it");
1665 };
1666 let top_slot = self.scheduler.spills.allocate(top);
1667 if self.scheduler.spills.is_reloadable(top) {
1668 self.emit_stack_op(StackOp::Pop);
1669 } else {
1670 self.store_stack_top_to_spill(func, top, top_slot);
1671 }
1672 saved_above.push((top, top_slot));
1673 }
1674
1675 let Some(accessible_depth) = self.scheduler.stack.find(val) else {
1676 panic!("cannot spill deep stack value {val:?}: value disappeared while exposing it");
1677 };
1678 self.spill_accessible_stack_value(func, val, slot, accessible_depth);
1679
1680 for (saved, saved_slot) in saved_above.into_iter().rev() {
1681 self.emit_spill_slot_addr(func, saved_slot);
1682 self.asm.emit_op(op::MLOAD);
1683 self.scheduler.stack.push(saved);
1684 }
1685 }
1686
1687 fn store_stack_top_to_spill(&mut self, func: &Function, value: ValueId, slot: SpillSlot) {
1688 self.emit_spill_slot_addr(func, slot);
1691 self.scheduler.stack.push_unknown();
1692
1693 self.asm.emit_op(op::MSTORE);
1694 self.scheduler.stack.pop();
1696 self.scheduler.stack.pop();
1697 self.scheduler.spills.mark_stored(value);
1698 }
1699
1700 fn spill_live_out_operands(
1703 &mut self,
1704 func: &Function,
1705 liveness: &Liveness,
1706 block_id: BlockId,
1707 operands: &[ValueId],
1708 ) {
1709 let live_out = liveness.live_out(block_id);
1710
1711 for &op in operands {
1712 if live_out.contains(op) && !self.is_stack_phi_source(block_id, op) {
1713 self.spill_value_if_needed(func, op);
1714 }
1715 }
1716 }
1717
1718 fn is_rematerializable_value(func: &Function, value: ValueId) -> bool {
1719 matches!(func.value(value), crate::mir::Value::Immediate(_) | crate::mir::Value::Arg { .. })
1720 }
1721
1722 fn is_cheap_recomputable_value(func: &Function, value: ValueId) -> bool {
1723 let crate::mir::Value::Inst(inst_id) = func.value(value) else {
1724 return false;
1725 };
1726 matches!(
1727 func.instruction(*inst_id).kind,
1728 InstKind::Add(_, _)
1729 | InstKind::Sub(_, _)
1730 | InstKind::Mul(_, _)
1731 | InstKind::And(_, _)
1732 | InstKind::Or(_, _)
1733 | InstKind::Xor(_, _)
1734 | InstKind::Shl(_, _)
1735 | InstKind::Shr(_, _)
1736 | InstKind::Sar(_, _)
1737 )
1738 }
1739
1740 fn spill_top_value_if_live(
1741 &mut self,
1742 func: &Function,
1743 liveness: &Liveness,
1744 block: BlockId,
1745 inst_idx: usize,
1746 value: ValueId,
1747 ) {
1748 if Self::is_rematerializable_value(func, value) {
1749 return;
1750 }
1751
1752 let has_reserved_cross_block_slot = self.scheduler.spills.get(value).is_some();
1753 if liveness.is_dead_after(value, block, inst_idx) && !has_reserved_cross_block_slot {
1754 return;
1755 }
1756
1757 debug_assert_eq!(self.scheduler.stack.top(), Some(value));
1758 if !self.spill_value_to_reserved_slot(func, value) {
1759 self.spill_value_if_needed(func, value);
1760 }
1761 }
1762
1763 fn generate_inst(
1765 &mut self,
1766 func: &Function,
1767 kind: &InstKind,
1768 liveness: &Liveness,
1769 block: BlockId,
1770 inst_idx: usize,
1771 result_value: Option<ValueId>,
1772 ) {
1773 let operands = kind.operands();
1776 self.spill_live_out_operands(func, liveness, block, &operands);
1777
1778 match kind {
1779 InstKind::Add(a, b) => self.emit_binary_op_with_result(
1781 func,
1782 *a,
1783 *b,
1784 op::ADD,
1785 result_value,
1786 liveness,
1787 block,
1788 inst_idx,
1789 ),
1790 InstKind::Sub(a, b) => self.emit_binary_op_with_result(
1791 func,
1792 *a,
1793 *b,
1794 op::SUB,
1795 result_value,
1796 liveness,
1797 block,
1798 inst_idx,
1799 ),
1800 InstKind::Mul(a, b) => self.emit_binary_op_with_result(
1801 func,
1802 *a,
1803 *b,
1804 op::MUL,
1805 result_value,
1806 liveness,
1807 block,
1808 inst_idx,
1809 ),
1810 InstKind::Div(a, b) => self.emit_binary_op_with_result(
1811 func,
1812 *a,
1813 *b,
1814 op::DIV,
1815 result_value,
1816 liveness,
1817 block,
1818 inst_idx,
1819 ),
1820 InstKind::SDiv(a, b) => self.emit_binary_op_with_result(
1821 func,
1822 *a,
1823 *b,
1824 op::SDIV,
1825 result_value,
1826 liveness,
1827 block,
1828 inst_idx,
1829 ),
1830 InstKind::Mod(a, b) => self.emit_binary_op_with_result(
1831 func,
1832 *a,
1833 *b,
1834 op::MOD,
1835 result_value,
1836 liveness,
1837 block,
1838 inst_idx,
1839 ),
1840 InstKind::SMod(a, b) => self.emit_binary_op_with_result(
1841 func,
1842 *a,
1843 *b,
1844 op::SMOD,
1845 result_value,
1846 liveness,
1847 block,
1848 inst_idx,
1849 ),
1850 InstKind::Exp(a, b) => self.emit_binary_op_with_result(
1851 func,
1852 *a,
1853 *b,
1854 op::EXP,
1855 result_value,
1856 liveness,
1857 block,
1858 inst_idx,
1859 ),
1860
1861 InstKind::And(a, b) => self.emit_binary_op_with_result(
1863 func,
1864 *a,
1865 *b,
1866 op::AND,
1867 result_value,
1868 liveness,
1869 block,
1870 inst_idx,
1871 ),
1872 InstKind::Or(a, b) => self.emit_binary_op_with_result(
1873 func,
1874 *a,
1875 *b,
1876 op::OR,
1877 result_value,
1878 liveness,
1879 block,
1880 inst_idx,
1881 ),
1882 InstKind::Xor(a, b) => self.emit_binary_op_with_result(
1883 func,
1884 *a,
1885 *b,
1886 op::XOR,
1887 result_value,
1888 liveness,
1889 block,
1890 inst_idx,
1891 ),
1892 InstKind::Not(a) => self.emit_unary_op_with_result(
1893 func,
1894 *a,
1895 op::NOT,
1896 result_value,
1897 liveness,
1898 block,
1899 inst_idx,
1900 ),
1901 InstKind::Shl(shift, val) => self.emit_binary_op_with_result(
1902 func,
1903 *shift,
1904 *val,
1905 op::SHL,
1906 result_value,
1907 liveness,
1908 block,
1909 inst_idx,
1910 ),
1911 InstKind::Shr(shift, val) => self.emit_binary_op_with_result(
1912 func,
1913 *shift,
1914 *val,
1915 op::SHR,
1916 result_value,
1917 liveness,
1918 block,
1919 inst_idx,
1920 ),
1921 InstKind::Sar(shift, val) => self.emit_binary_op_with_result(
1922 func,
1923 *shift,
1924 *val,
1925 op::SAR,
1926 result_value,
1927 liveness,
1928 block,
1929 inst_idx,
1930 ),
1931 InstKind::Byte(i, x) => self.emit_binary_op_with_result(
1932 func,
1933 *i,
1934 *x,
1935 op::BYTE,
1936 result_value,
1937 liveness,
1938 block,
1939 inst_idx,
1940 ),
1941
1942 InstKind::Lt(a, b) => self.emit_binary_op_with_result(
1944 func,
1945 *a,
1946 *b,
1947 op::LT,
1948 result_value,
1949 liveness,
1950 block,
1951 inst_idx,
1952 ),
1953 InstKind::Gt(a, b) => self.emit_binary_op_with_result(
1954 func,
1955 *a,
1956 *b,
1957 op::GT,
1958 result_value,
1959 liveness,
1960 block,
1961 inst_idx,
1962 ),
1963 InstKind::SLt(a, b) => self.emit_binary_op_with_result(
1964 func,
1965 *a,
1966 *b,
1967 op::SLT,
1968 result_value,
1969 liveness,
1970 block,
1971 inst_idx,
1972 ),
1973 InstKind::SGt(a, b) => self.emit_binary_op_with_result(
1974 func,
1975 *a,
1976 *b,
1977 op::SGT,
1978 result_value,
1979 liveness,
1980 block,
1981 inst_idx,
1982 ),
1983 InstKind::Eq(a, b) => self.emit_binary_op_with_result(
1984 func,
1985 *a,
1986 *b,
1987 op::EQ,
1988 result_value,
1989 liveness,
1990 block,
1991 inst_idx,
1992 ),
1993 InstKind::IsZero(a) => self.emit_unary_op_with_result(
1994 func,
1995 *a,
1996 op::ISZERO,
1997 result_value,
1998 liveness,
1999 block,
2000 inst_idx,
2001 ),
2002
2003 InstKind::MLoad(addr) => self.emit_unary_op_with_result(
2008 func,
2009 *addr,
2010 op::MLOAD,
2011 result_value,
2012 liveness,
2013 block,
2014 inst_idx,
2015 ),
2016 InstKind::MStore(addr, val) => self.emit_store_op_live_aware(
2017 func,
2018 *addr,
2019 *val,
2020 op::MSTORE,
2021 liveness,
2022 block,
2023 inst_idx,
2024 ),
2025 InstKind::MStore8(addr, val) => self.emit_store_op_live_aware(
2026 func,
2027 *addr,
2028 *val,
2029 op::MSTORE8,
2030 liveness,
2031 block,
2032 inst_idx,
2033 ),
2034 InstKind::MSize => {
2035 self.asm.emit_op(op::MSIZE);
2036 self.scheduler.instruction_executed(0, result_value);
2037 }
2038
2039 InstKind::SLoad(slot) => self.emit_unary_op_with_result(
2041 func,
2042 *slot,
2043 op::SLOAD,
2044 result_value,
2045 liveness,
2046 block,
2047 inst_idx,
2048 ),
2049 InstKind::SStore(slot, val) => self.emit_store_op_live_aware(
2050 func,
2051 *slot,
2052 *val,
2053 op::SSTORE,
2054 liveness,
2055 block,
2056 inst_idx,
2057 ),
2058 InstKind::TLoad(slot) => self.emit_unary_op_with_result(
2059 func,
2060 *slot,
2061 op::TLOAD,
2062 result_value,
2063 liveness,
2064 block,
2065 inst_idx,
2066 ),
2067 InstKind::TStore(slot, val) => self.emit_store_op_live_aware(
2068 func,
2069 *slot,
2070 *val,
2071 op::TSTORE,
2072 liveness,
2073 block,
2074 inst_idx,
2075 ),
2076
2077 InstKind::CalldataLoad(off) => self.emit_unary_op_with_result(
2079 func,
2080 *off,
2081 op::CALLDATALOAD,
2082 result_value,
2083 liveness,
2084 block,
2085 inst_idx,
2086 ),
2087 InstKind::CalldataSize => {
2088 self.asm.emit_op(op::CALLDATASIZE);
2089 self.scheduler.instruction_executed(0, result_value);
2090 }
2091
2092 InstKind::Keccak256(off, len) => self.emit_binary_op_with_result(
2094 func,
2095 *off,
2096 *len,
2097 op::KECCAK256,
2098 result_value,
2099 liveness,
2100 block,
2101 inst_idx,
2102 ),
2103
2104 InstKind::Caller => {
2106 self.asm.emit_op(op::CALLER);
2107 self.scheduler.instruction_executed(0, result_value);
2108 }
2109 InstKind::CallValue => {
2110 self.asm.emit_op(op::CALLVALUE);
2111 self.scheduler.instruction_executed(0, result_value);
2112 }
2113 InstKind::Address => {
2114 self.asm.emit_op(op::ADDRESS);
2115 self.scheduler.instruction_executed(0, result_value);
2116 }
2117 InstKind::Origin => {
2118 self.asm.emit_op(op::ORIGIN);
2119 self.scheduler.instruction_executed(0, result_value);
2120 }
2121 InstKind::GasPrice => {
2122 self.asm.emit_op(op::GASPRICE);
2123 self.scheduler.instruction_executed(0, result_value);
2124 }
2125 InstKind::Gas => {
2126 self.asm.emit_op(op::GAS);
2127 self.scheduler.instruction_executed(0, result_value);
2128 }
2129 InstKind::Timestamp => {
2130 self.asm.emit_op(op::TIMESTAMP);
2131 self.scheduler.instruction_executed(0, result_value);
2132 }
2133 InstKind::BlockNumber => {
2134 self.asm.emit_op(op::NUMBER);
2135 self.scheduler.instruction_executed(0, result_value);
2136 }
2137 InstKind::Coinbase => {
2138 self.asm.emit_op(op::COINBASE);
2139 self.scheduler.instruction_executed(0, result_value);
2140 }
2141 InstKind::ChainId => {
2142 self.asm.emit_op(op::CHAINID);
2143 self.scheduler.instruction_executed(0, result_value);
2144 }
2145 InstKind::SelfBalance => {
2146 self.asm.emit_op(op::SELFBALANCE);
2147 self.scheduler.instruction_executed(0, result_value);
2148 }
2149 InstKind::BaseFee => {
2150 self.asm.emit_op(op::BASEFEE);
2151 self.scheduler.instruction_executed(0, result_value);
2152 }
2153 InstKind::BlobBaseFee => {
2154 self.asm.emit_op(op::BLOBBASEFEE);
2155 self.scheduler.instruction_executed(0, result_value);
2156 }
2157 InstKind::GasLimit => {
2158 self.asm.emit_op(op::GASLIMIT);
2159 self.scheduler.instruction_executed(0, result_value);
2160 }
2161 InstKind::PrevRandao => {
2162 self.asm.emit_op(op::PREVRANDAO);
2163 self.scheduler.instruction_executed(0, result_value);
2164 }
2165 InstKind::Balance(addr) => self.emit_unary_op_with_result(
2166 func,
2167 *addr,
2168 op::BALANCE,
2169 result_value,
2170 liveness,
2171 block,
2172 inst_idx,
2173 ),
2174 InstKind::BlockHash(num) => self.emit_unary_op_with_result(
2175 func,
2176 *num,
2177 op::BLOCKHASH,
2178 result_value,
2179 liveness,
2180 block,
2181 inst_idx,
2182 ),
2183 InstKind::BlobHash(idx) => self.emit_unary_op_with_result(
2184 func,
2185 *idx,
2186 op::BLOBHASH,
2187 result_value,
2188 liveness,
2189 block,
2190 inst_idx,
2191 ),
2192 InstKind::ExtCodeSize(addr) => self.emit_unary_op_with_result(
2193 func,
2194 *addr,
2195 op::EXTCODESIZE,
2196 result_value,
2197 liveness,
2198 block,
2199 inst_idx,
2200 ),
2201 InstKind::ExtCodeHash(addr) => self.emit_unary_op_with_result(
2202 func,
2203 *addr,
2204 op::EXTCODEHASH,
2205 result_value,
2206 liveness,
2207 block,
2208 inst_idx,
2209 ),
2210 InstKind::CodeSize => {
2211 self.asm.emit_op(op::CODESIZE);
2212 self.scheduler.instruction_executed(0, result_value);
2213 }
2214 InstKind::LoadImmutable(offset) => {
2215 if self.in_constructor {
2216 self.asm.emit_push(U256::from(IMMUTABLE_SCRATCH_BASE + u64::from(*offset)));
2219 self.asm.emit_op(op::MLOAD);
2220 } else {
2221 self.asm.emit_push_immutable(*offset);
2222 }
2223 self.scheduler.instruction_executed(0, result_value);
2224 }
2225 InstKind::ReturnDataSize => {
2226 self.asm.emit_op(op::RETURNDATASIZE);
2227 self.scheduler.instruction_executed(0, result_value);
2228 }
2229
2230 InstKind::AddMod(a, b, n) => {
2232 self.emit_ternary_op(func, *a, *b, *n, op::ADDMOD, result_value)
2233 }
2234 InstKind::MulMod(a, b, n) => {
2235 self.emit_ternary_op(func, *a, *b, *n, op::MULMOD, result_value)
2236 }
2237
2238 InstKind::Select(cond, true_val, false_val) => {
2240 self.emit_value(func, *false_val); self.emit_operand(func, *true_val); self.emit_operand(func, *cond); self.emit_stack_op(StackOp::Dup(3));
2255 self.emit_stack_op(StackOp::Dup(3));
2257 self.emit_op_with_effect(
2259 op::SUB,
2260 StackEffect { pops: 2, pushes: 1 },
2261 StackPush::Unknown,
2262 );
2263 self.emit_op_with_effect(
2265 op::MUL,
2266 StackEffect { pops: 2, pushes: 1 },
2267 StackPush::Unknown,
2268 );
2269 self.emit_stack_op(StackOp::Swap(1));
2271 self.emit_stack_op(StackOp::Pop);
2273 let push = result_value.map_or(StackPush::Unknown, StackPush::Tracked);
2275 self.emit_op_with_effect(op::ADD, StackEffect { pops: 2, pushes: 1 }, push);
2276 }
2277
2278 InstKind::SignExtend(b, x) => self.emit_binary_op_with_result(
2280 func,
2281 *b,
2282 *x,
2283 op::SIGNEXTEND,
2284 result_value,
2285 liveness,
2286 block,
2287 inst_idx,
2288 ),
2289
2290 InstKind::Phi(_) => {}
2292
2293 InstKind::Create(value, offset, size) => {
2295 self.emit_value(func, *size);
2296 self.emit_operand(func, *offset);
2297 self.emit_operand(func, *value);
2298 self.asm.emit_op(op::CREATE);
2299 self.scheduler.instruction_executed(3, result_value);
2301 }
2302
2303 InstKind::Create2(value, offset, size, salt) => {
2304 self.emit_value(func, *value);
2307 self.emit_operand(func, *offset);
2308 self.emit_operand(func, *size);
2309 self.emit_operand(func, *salt);
2310 self.asm.emit_op(op::CREATE2);
2311 self.scheduler.instruction_executed(4, result_value);
2313 }
2314
2315 InstKind::Call { gas, addr, value, args_offset, args_size, ret_offset, ret_size } => {
2320 self.emit_value_fresh(func, *ret_size);
2325 self.emit_value_fresh(func, *ret_offset);
2326 self.emit_value_fresh(func, *args_size);
2327 self.emit_value_fresh(func, *args_offset);
2328 self.emit_value_fresh(func, *value);
2329 self.emit_value_fresh(func, *addr);
2330 self.emit_value_fresh(func, *gas);
2331
2332 let push = result_value.map_or(StackPush::Unknown, StackPush::Tracked);
2334 self.emit_op_with_effect(op::CALL, StackEffect { pops: 7, pushes: 1 }, push);
2335 }
2336
2337 InstKind::StaticCall { gas, addr, args_offset, args_size, ret_offset, ret_size } => {
2338 self.emit_value_fresh(func, *ret_size);
2340 self.emit_value_fresh(func, *ret_offset);
2341 self.emit_value_fresh(func, *args_size);
2342 self.emit_value_fresh(func, *args_offset);
2343 self.emit_value_fresh(func, *addr);
2344 self.emit_value_fresh(func, *gas);
2345 let push = result_value.map_or(StackPush::Unknown, StackPush::Tracked);
2347 self.emit_op_with_effect(op::STATICCALL, StackEffect { pops: 6, pushes: 1 }, push);
2348 }
2349
2350 InstKind::DelegateCall { gas, addr, args_offset, args_size, ret_offset, ret_size } => {
2351 self.emit_value_fresh(func, *ret_size);
2353 self.emit_value_fresh(func, *ret_offset);
2354 self.emit_value_fresh(func, *args_size);
2355 self.emit_value_fresh(func, *args_offset);
2356 self.emit_value_fresh(func, *addr);
2357 self.emit_value_fresh(func, *gas);
2358 let push = result_value.map_or(StackPush::Unknown, StackPush::Tracked);
2360 self.emit_op_with_effect(
2361 op::DELEGATECALL,
2362 StackEffect { pops: 6, pushes: 1 },
2363 push,
2364 );
2365 }
2366
2367 InstKind::InternalCall { function, args, returns } => {
2368 self.emit_internal_call(
2369 func,
2370 *function,
2371 args,
2372 *returns as usize,
2373 result_value,
2374 liveness,
2375 block,
2376 inst_idx,
2377 );
2378 }
2379
2380 InstKind::InternalFrameAddr(offset) => {
2381 self.emit_current_internal_frame_addr(*offset);
2382 if let Some(result) = result_value {
2383 self.scheduler.stack.push(result);
2384 }
2385 }
2386
2387 InstKind::Log0(offset, size) => {
2389 self.emit_value(func, *size);
2391 self.emit_operand(func, *offset);
2392 self.asm.emit_op(op::LOG0);
2393 self.scheduler.instruction_executed(2, None);
2394 }
2395 InstKind::Log1(offset, size, topic1) => {
2396 self.emit_value(func, *topic1);
2398 self.emit_operand(func, *size);
2399 self.emit_operand(func, *offset);
2400 self.asm.emit_op(op::LOG1);
2401 self.scheduler.instruction_executed(3, None);
2402 }
2403 InstKind::Log2(offset, size, topic1, topic2) => {
2404 self.emit_value(func, *topic2);
2406 self.emit_operand(func, *topic1);
2407 self.emit_operand(func, *size);
2408 self.emit_operand(func, *offset);
2409 self.asm.emit_op(op::LOG2);
2410 self.scheduler.instruction_executed(4, None);
2411 }
2412 InstKind::Log3(offset, size, topic1, topic2, topic3) => {
2413 self.emit_value(func, *topic3);
2415 self.emit_operand(func, *topic2);
2416 self.emit_operand(func, *topic1);
2417 self.emit_operand(func, *size);
2418 self.emit_operand(func, *offset);
2419 self.asm.emit_op(op::LOG3);
2420 self.scheduler.instruction_executed(5, None);
2421 }
2422 InstKind::Log4(offset, size, topic1, topic2, topic3, topic4) => {
2423 self.emit_value(func, *topic4);
2425 self.emit_operand(func, *topic3);
2426 self.emit_operand(func, *topic2);
2427 self.emit_operand(func, *topic1);
2428 self.emit_operand(func, *size);
2429 self.emit_operand(func, *offset);
2430 self.asm.emit_op(op::LOG4);
2431 self.scheduler.instruction_executed(6, None);
2432 }
2433
2434 InstKind::CalldataCopy(dest, offset, size) => {
2436 self.emit_copy_op_live_aware(
2438 func,
2439 &[*size, *offset, *dest],
2440 op::CALLDATACOPY,
2441 liveness,
2442 block,
2443 inst_idx,
2444 );
2445 }
2446
2447 InstKind::CodeCopy(dest, offset, size) => {
2448 self.emit_copy_op_live_aware(
2450 func,
2451 &[*size, *offset, *dest],
2452 op::CODECOPY,
2453 liveness,
2454 block,
2455 inst_idx,
2456 );
2457 }
2458
2459 InstKind::ReturnDataCopy(dest, offset, size) => {
2460 self.emit_copy_op_live_aware(
2462 func,
2463 &[*size, *offset, *dest],
2464 op::RETURNDATACOPY,
2465 liveness,
2466 block,
2467 inst_idx,
2468 );
2469 }
2470
2471 InstKind::MCopy(dest, src, size) => {
2472 self.emit_copy_op_live_aware(
2474 func,
2475 &[*size, *src, *dest],
2476 op::MCOPY,
2477 liveness,
2478 block,
2479 inst_idx,
2480 );
2481 }
2482
2483 InstKind::ExtCodeCopy(addr, dest, offset, size) => {
2484 self.emit_copy_op_live_aware(
2486 func,
2487 &[*size, *offset, *dest, *addr],
2488 op::EXTCODECOPY,
2489 liveness,
2490 block,
2491 inst_idx,
2492 );
2493 }
2494 }
2495
2496 if let Some(result) = result_value
2497 && liveness.live_out(block).contains(result)
2498 && !self.is_stack_phi_source(block, result)
2499 {
2500 self.spill_value_if_needed(func, result);
2501 }
2502
2503 let dead_ops = self.scheduler.drop_dead_values(liveness, block, inst_idx);
2505 for op in dead_ops {
2506 self.asm.emit_op(op.opcode());
2507 }
2508 #[cfg(debug_assertions)]
2509 {
2510 debug_assert!(self.scheduler.depth() <= 1024);
2511 }
2512 }
2513
2514 fn emit_new_internal_frame_base_tracked(&mut self) {
2515 self.asm.emit_push(U256::from(0x40));
2516 self.asm.emit_op(op::MLOAD);
2517 self.scheduler.stack.push_unknown();
2518 }
2519
2520 fn emit_internal_frame_store_from_top_preserving_base(&mut self, offset: u64) {
2521 self.emit_stack_op(StackOp::Dup(2));
2522 if offset != 0 {
2523 self.asm.emit_push(U256::from(offset));
2524 self.scheduler.stack.push_unknown();
2525 self.emit_op_with_effect(
2526 op::ADD,
2527 StackEffect { pops: 2, pushes: 1 },
2528 StackPush::Unknown,
2529 );
2530 }
2531 self.asm.emit_op(op::MSTORE);
2532 self.scheduler.instruction_executed(2, None);
2533 }
2534
2535 fn emit_store_frame_base_to_current_frame_slot(&mut self) {
2536 self.emit_stack_op(StackOp::Dup(1));
2537 self.asm.emit_push(U256::from(INTERNAL_FRAME_PTR_SLOT));
2538 self.scheduler.stack.push_unknown();
2539 self.asm.emit_op(op::MSTORE);
2540 self.scheduler.instruction_executed(2, None);
2541 }
2542
2543 fn emit_store_new_free_pointer_from_frame_base(&mut self, frame_size: DeferredConst) {
2544 self.asm.emit_push_deferred(frame_size);
2545 self.scheduler.stack.push_unknown();
2546 self.emit_op_with_effect(op::ADD, StackEffect { pops: 2, pushes: 1 }, StackPush::Unknown);
2547 self.asm.emit_push(U256::from(0x40));
2548 self.scheduler.stack.push_unknown();
2549 self.asm.emit_op(op::MSTORE);
2550 self.scheduler.instruction_executed(2, None);
2551 }
2552
2553 fn emit_current_internal_frame_addr(&mut self, offset: u64) {
2554 self.asm.emit_push(U256::from(INTERNAL_FRAME_PTR_SLOT));
2555 self.asm.emit_op(op::MLOAD);
2556 if offset != 0 {
2557 self.asm.emit_push(U256::from(offset));
2558 self.asm.emit_op(op::ADD);
2559 }
2560 }
2561
2562 fn conservative_spill_frame_size(func: &Function) -> u64 {
2563 func.values.len() as u64 * 32
2567 }
2568
2569 fn external_spill_base(func: &Function) -> u64 {
2570 let low_memory_start = if Self::uses_internal_frame_slot(func) {
2571 INTERNAL_FRAME_PTR_SLOT + 32
2572 } else {
2573 LOW_MEMORY_START
2574 };
2575 low_memory_start + func.internal_frame_size.max(func.external_static_return_size)
2576 }
2577
2578 fn constructor_free_memory_start(spill_size: u64) -> u64 {
2579 CONSTRUCTOR_FREE_MEMORY_START.max(CONSTRUCTOR_SPILL_BASE + spill_size)
2580 }
2581
2582 fn uses_internal_frame_slot(func: &Function) -> bool {
2583 func.instructions.iter().any(|inst| matches!(inst.kind, InstKind::InternalCall { .. }))
2584 }
2585
2586 fn emit_external_free_memory_start(&mut self) -> DeferredConst {
2587 let id = self.asm.new_deferred_const();
2588 self.asm.emit_push_deferred(id);
2589 self.asm.emit_push(U256::from(0x40));
2590 self.asm.emit_op(op::MSTORE);
2591 id
2592 }
2593
2594 fn emit_spill_slot_addr(&mut self, func: &Function, slot: SpillSlot) {
2595 if self.in_internal_function {
2596 let spill_base =
2597 64 + (func.params.len() as u64) * 32 + (func.returns.len() as u64) * 32;
2598 self.emit_current_internal_frame_addr(
2599 spill_base + func.internal_frame_size + u64::from(slot.offset) * 32,
2600 );
2601 } else if self.in_constructor {
2602 self.asm.emit_push(U256::from(slot.byte_offset()));
2603 } else {
2604 self.asm.emit_push(U256::from(
2605 Self::external_spill_base(func) + u64::from(slot.offset) * 32,
2606 ));
2607 }
2608 }
2609
2610 fn emit_internal_arg_load(&mut self, index: u32) {
2611 self.emit_current_internal_frame_addr(64 + u64::from(index) * 32);
2612 self.asm.emit_op(op::MLOAD);
2613 }
2614
2615 #[allow(clippy::too_many_arguments)]
2616 fn emit_internal_call(
2617 &mut self,
2618 func: &Function,
2619 callee: FunctionId,
2620 args: &[ValueId],
2621 returns: usize,
2622 result: Option<ValueId>,
2623 liveness: &Liveness,
2624 block: BlockId,
2625 inst_idx: usize,
2626 ) {
2627 let Some(&callee_label) = self.function_labels.get(&callee) else {
2628 return;
2629 };
2630 let return_label = self.asm.new_label();
2631 let static_local_frame_size =
2632 self.function_static_frame_sizes.get(&callee).copied().unwrap_or_default();
2633 let static_frame_size = 64 + ((args.len() + returns) as u64) * 32 + static_local_frame_size;
2636 let frame_size = self.asm.new_deferred_const();
2637 self.pending_frame_size_consts.push((frame_size, callee, static_frame_size));
2638
2639 self.spill_live_stack_values(func, liveness, block, inst_idx);
2645
2646 self.emit_new_internal_frame_base_tracked();
2647
2648 self.asm.emit_push_label(return_label);
2650 self.scheduler.stack.push_unknown();
2651 self.emit_internal_frame_store_from_top_preserving_base(0);
2652
2653 self.asm.emit_push(U256::from(INTERNAL_FRAME_PTR_SLOT));
2655 self.asm.emit_op(op::MLOAD);
2656 self.scheduler.stack.push_unknown();
2657 self.emit_internal_frame_store_from_top_preserving_base(32);
2658
2659 for (i, &arg) in args.iter().enumerate() {
2660 self.emit_operand(func, arg);
2661 self.emit_internal_frame_store_from_top_preserving_base(64 + (i as u64) * 32);
2662 }
2663
2664 self.emit_store_frame_base_to_current_frame_slot();
2666
2667 self.emit_store_new_free_pointer_from_frame_base(frame_size);
2669
2670 self.pop_all_stack_values();
2671 self.scheduler.clear_stack();
2672
2673 self.asm.emit_push_label(callee_label);
2674 self.asm.emit_op(op::JUMP);
2675
2676 self.asm.define_label(return_label);
2677 self.scheduler.clear_stack();
2678
2679 if let Some(result) = result
2680 && returns > 0
2681 {
2682 self.emit_current_internal_frame_addr(64 + (args.len() as u64) * 32);
2683 self.asm.emit_op(op::MLOAD);
2684 self.scheduler.stack.push(result);
2685 }
2686
2687 for i in 1..returns {
2694 self.emit_current_internal_frame_addr(64 + (args.len() as u64) * 32 + (i as u64) * 32);
2695 self.asm.emit_op(op::MLOAD);
2696 self.asm.emit_push(U256::from((i as u64) * 32));
2697 self.asm.emit_op(op::MSTORE);
2698 }
2699
2700 if self.restorable_internal_frames.contains(&callee) {
2710 self.emit_current_internal_frame_addr(0);
2711 self.asm.emit_push(U256::from(0x40));
2712 self.asm.emit_op(op::MSTORE);
2713 }
2714
2715 self.emit_current_internal_frame_addr(32);
2717 self.asm.emit_op(op::MLOAD);
2718 self.asm.emit_push(U256::from(INTERNAL_FRAME_PTR_SLOT));
2719 self.asm.emit_op(op::MSTORE);
2720 }
2721
2722 fn spill_live_stack_values(
2723 &mut self,
2724 func: &Function,
2725 liveness: &Liveness,
2726 block: BlockId,
2727 inst_idx: usize,
2728 ) {
2729 let stack_values: Vec<_> = self.scheduler.stack.iter().flatten().collect();
2730 for value in stack_values {
2731 if !liveness.is_dead_after(value, block, inst_idx) {
2732 self.spill_value_if_needed(func, value);
2733 }
2734 }
2735 }
2736
2737 fn emit_value(&mut self, func: &Function, val: ValueId) {
2739 self.emit_value_impl(func, val, true);
2740 }
2741
2742 fn emit_operand(&mut self, func: &Function, val: ValueId) {
2744 self.emit_value_impl(func, val, false);
2745 }
2746
2747 fn emit_value_impl(&mut self, func: &Function, val: ValueId, claim_top: bool) {
2748 if let Some(depth) = self.scheduler.stack.find(val)
2749 && depth >= MAX_STACK_ACCESS
2750 && !self.scheduler.spills.is_reloadable(val)
2751 && !matches!(
2752 func.value(val),
2753 crate::mir::Value::Immediate(_) | crate::mir::Value::Arg { .. }
2754 )
2755 {
2756 let slot = self.scheduler.spills.allocate(val);
2757 self.spill_deep_stack_value(func, val, slot, depth);
2758 }
2759
2760 if self.scheduler.stack.find(val).is_none()
2761 && self.scheduler.spills.get(val).is_some()
2762 && !self.scheduler.spills.is_stored(val)
2763 && Self::is_cheap_recomputable_value(func, val)
2764 {
2765 self.emit_value_fresh(func, val);
2766 return;
2767 }
2768
2769 let ops = if claim_top {
2770 self.scheduler.ensure_on_top(val, func)
2771 } else {
2772 self.scheduler.ensure_operand_on_top(val, func)
2773 }
2774 .to_vec();
2775 for op in ops {
2776 match op {
2777 ScheduledOp::Stack(stack_op) => {
2778 self.asm.emit_op(stack_op.opcode());
2779 }
2780 ScheduledOp::PushImmediate(imm) => {
2781 self.asm.emit_push(imm);
2782 }
2783 ScheduledOp::LoadSpill(slot) => {
2784 self.emit_spill_slot_addr(func, slot);
2786 self.asm.emit_op(op::MLOAD);
2787 }
2788 ScheduledOp::SaveSpill(slot) => {
2789 self.emit_spill_slot_addr(func, slot);
2791 self.asm.emit_op(op::MSTORE);
2792 }
2793 ScheduledOp::LoadArg(index) => {
2794 if self.in_internal_function {
2795 self.asm.emit_push(U256::from(INTERNAL_FRAME_PTR_SLOT));
2796 self.asm.emit_op(op::MLOAD);
2797 self.asm.emit_push(U256::from(64 + u64::from(index) * 32));
2798 self.asm.emit_op(op::ADD);
2799 self.asm.emit_op(op::MLOAD);
2800 } else if self.in_constructor {
2801 let offset = 0x80 + (index as u64) * 32;
2804 self.asm.emit_push(U256::from(offset));
2805 self.asm.emit_op(op::MLOAD);
2806 } else {
2807 let offset = 4 + (index as u64) * 32;
2811 self.asm.emit_push(U256::from(offset));
2812 self.asm.emit_op(op::CALLDATALOAD);
2813 }
2814 }
2815 }
2816 }
2817 }
2818
2819 fn emit_value_fresh(&mut self, func: &Function, val: ValueId) {
2823 match func.value(val) {
2824 crate::mir::Value::Immediate(imm) => {
2825 if let Some(u256) = imm.as_u256() {
2826 self.asm.emit_push(u256);
2827 self.scheduler.stack.push(val);
2828 }
2829 }
2830 crate::mir::Value::Arg { index, .. } => {
2831 if self.in_internal_function {
2832 self.emit_internal_arg_load(*index);
2833 } else if self.in_constructor {
2834 let offset = 0x80 + (*index as u64) * 32;
2835 self.asm.emit_push(U256::from(offset));
2836 self.asm.emit_op(op::MLOAD);
2837 } else {
2838 let offset = 4 + (*index as u64) * 32;
2839 self.asm.emit_push(U256::from(offset));
2840 self.asm.emit_op(op::CALLDATALOAD);
2841 }
2842 self.scheduler.stack.push(val);
2843 }
2844 crate::mir::Value::Inst(inst_id) => {
2845 if let Some(slot) = self.scheduler.spills.get(val)
2848 && self.scheduler.spills.is_stored(val)
2849 {
2850 self.emit_spill_slot_addr(func, slot);
2852 self.asm.emit_op(op::MLOAD);
2853 self.scheduler.stack.push(val);
2854 } else {
2855 let inst_kind = &func.instruction(*inst_id).kind;
2859 match inst_kind {
2860 crate::mir::InstKind::Gas => {
2861 self.asm.emit_op(op::GAS);
2862 self.scheduler.stack.push(val);
2863 }
2864 crate::mir::InstKind::CallValue => {
2865 self.asm.emit_op(op::CALLVALUE);
2866 self.scheduler.stack.push(val);
2867 }
2868 crate::mir::InstKind::Caller => {
2869 self.asm.emit_op(op::CALLER);
2870 self.scheduler.stack.push(val);
2871 }
2872 crate::mir::InstKind::Origin => {
2873 self.asm.emit_op(op::ORIGIN);
2874 self.scheduler.stack.push(val);
2875 }
2876 crate::mir::InstKind::CalldataSize => {
2877 self.asm.emit_op(op::CALLDATASIZE);
2878 self.scheduler.stack.push(val);
2879 }
2880 crate::mir::InstKind::InternalFrameAddr(offset) => {
2881 self.emit_current_internal_frame_addr(*offset);
2882 self.scheduler.stack.push(val);
2883 }
2884 crate::mir::InstKind::Timestamp => {
2885 self.asm.emit_op(op::TIMESTAMP);
2886 self.scheduler.stack.push(val);
2887 }
2888 crate::mir::InstKind::BlockNumber => {
2889 self.asm.emit_op(op::NUMBER);
2890 self.scheduler.stack.push(val);
2891 }
2892 crate::mir::InstKind::MLoad(offset) => {
2893 self.emit_value_fresh(func, *offset);
2902 self.asm.emit_op(op::MLOAD);
2903 self.scheduler.stack.pop();
2905 self.scheduler.stack.push(val);
2906 }
2907 crate::mir::InstKind::Keccak256(offset, size) => {
2908 self.emit_value_fresh(func, *size);
2912 self.emit_value_fresh(func, *offset);
2913 self.asm.emit_op(op::KECCAK256);
2914 self.scheduler.stack.pop();
2916 self.scheduler.stack.pop();
2917 self.scheduler.stack.push(val);
2918 }
2919 crate::mir::InstKind::Add(a, b) => {
2920 self.emit_fresh_binary(func, val, *a, *b, op::ADD, true);
2921 }
2922 crate::mir::InstKind::Sub(a, b) => {
2923 self.emit_fresh_binary(func, val, *a, *b, op::SUB, false);
2924 }
2925 crate::mir::InstKind::Mul(a, b) => {
2926 self.emit_fresh_binary(func, val, *a, *b, op::MUL, true);
2927 }
2928 crate::mir::InstKind::And(a, b) => {
2929 self.emit_fresh_binary(func, val, *a, *b, op::AND, true);
2930 }
2931 crate::mir::InstKind::Or(a, b) => {
2932 self.emit_fresh_binary(func, val, *a, *b, op::OR, true);
2933 }
2934 crate::mir::InstKind::Xor(a, b) => {
2935 self.emit_fresh_binary(func, val, *a, *b, op::XOR, true);
2936 }
2937 crate::mir::InstKind::Shl(shift, value) => {
2938 self.emit_fresh_binary(func, val, *shift, *value, op::SHL, false);
2939 }
2940 crate::mir::InstKind::Shr(shift, value) => {
2941 self.emit_fresh_binary(func, val, *shift, *value, op::SHR, false);
2942 }
2943 crate::mir::InstKind::Sar(shift, value) => {
2944 self.emit_fresh_binary(func, val, *shift, *value, op::SAR, false);
2945 }
2946 crate::mir::InstKind::SLoad(slot) => {
2947 self.emit_value_fresh(func, *slot);
2952 self.asm.emit_op(op::SLOAD);
2953 self.scheduler.stack.pop();
2954 self.scheduler.stack.push(val);
2955 }
2956 other => {
2957 panic!(
2962 "emit_value_fresh: unhandled instruction kind {other:?} for value {val:?}. \
2963 CALL operands should be immediates, spilled values, GAS, or MLOAD."
2964 );
2965 }
2966 }
2967 }
2968 }
2969 crate::mir::Value::Undef(_) => {
2970 panic!(
2972 "emit_value_fresh: unexpected undef value {val:?}. \
2973 CALL operands should be concrete values."
2974 );
2975 }
2976 crate::mir::Value::Error(_) => {
2977 panic!("emit_value_fresh: error sentinel {val:?} reached the backend");
2979 }
2980 }
2981 }
2982
2983 fn emit_fresh_binary(
2984 &mut self,
2985 func: &Function,
2986 result: ValueId,
2987 a: ValueId,
2988 b: ValueId,
2989 opcode: u8,
2990 commutative: bool,
2991 ) {
2992 if commutative {
2993 self.emit_value_fresh(func, a);
2994 self.emit_value_fresh(func, b);
2995 } else {
2996 self.emit_value_fresh(func, b);
2999 self.emit_value_fresh(func, a);
3000 }
3001 self.asm.emit_op(opcode);
3002 self.scheduler.stack.pop();
3003 self.scheduler.stack.pop();
3004 self.scheduler.stack.push(result);
3005 }
3006
3007 #[allow(clippy::too_many_arguments)]
3010 fn emit_binary_op_with_result(
3011 &mut self,
3012 func: &Function,
3013 a: ValueId,
3014 b: ValueId,
3015 opcode: u8,
3016 result: Option<ValueId>,
3017 liveness: &Liveness,
3018 block: BlockId,
3019 inst_idx: usize,
3020 ) {
3021 let a_is_live = !liveness.is_dead_after(a, block, inst_idx);
3023
3024 if a == b {
3026 self.emit_value(func, a);
3027 self.spill_top_value_if_live(func, liveness, block, inst_idx, a);
3028 self.asm.emit_op(op::DUP1);
3030 self.scheduler.stack.dup(1);
3031 self.asm.emit_op(opcode);
3032 self.scheduler.instruction_executed(2, result);
3033 return;
3034 }
3035
3036 let a_can_emit = self.scheduler.can_emit_value(a, func);
3038 let b_can_emit = self.scheduler.can_emit_value(b, func);
3039 let has_untracked = self.scheduler.has_untracked_on_top();
3040 let has_untracked_at_1 = self.scheduler.has_untracked_at_depth(1);
3041
3042 if !a_can_emit && b_can_emit && has_untracked {
3043 self.emit_value(func, b);
3045 self.spill_top_value_if_live(func, liveness, block, inst_idx, b);
3046 self.asm.emit_op(op::SWAP1);
3047 self.scheduler.stack_swapped();
3048 } else if a_can_emit && !b_can_emit && has_untracked {
3049 self.emit_value(func, a);
3051 if a_is_live && !Self::is_rematerializable_value(func, a) {
3053 self.spill_value_if_needed(func, a);
3054 }
3055 } else if !a_can_emit && b_can_emit && has_untracked_at_1 {
3056 self.asm.emit_op(op::SWAP1);
3059 self.scheduler.stack_swapped();
3060 } else {
3061 self.emit_value(func, b);
3063 self.spill_top_value_if_live(func, liveness, block, inst_idx, b);
3064 self.emit_value(func, a);
3065 if a_is_live && !Self::is_rematerializable_value(func, a) {
3067 self.spill_value_if_needed(func, a);
3068 }
3069 }
3070
3071 self.asm.emit_op(opcode);
3072 self.scheduler.instruction_executed(2, result);
3073 }
3074
3075 #[allow(clippy::too_many_arguments)]
3078 fn emit_unary_op_with_result(
3079 &mut self,
3080 func: &Function,
3081 a: ValueId,
3082 opcode: u8,
3083 result: Option<ValueId>,
3084 liveness: &Liveness,
3085 block: BlockId,
3086 inst_idx: usize,
3087 ) {
3088 self.emit_value(func, a);
3089 self.spill_top_value_if_live(func, liveness, block, inst_idx, a);
3090
3091 self.asm.emit_op(opcode);
3092 self.scheduler.instruction_executed(1, result);
3093 }
3094
3095 #[allow(clippy::too_many_arguments)]
3099 fn emit_store_op_live_aware(
3100 &mut self,
3101 func: &Function,
3102 addr: ValueId,
3103 val: ValueId,
3104 opcode: u8,
3105 liveness: &Liveness,
3106 block: BlockId,
3107 inst_idx: usize,
3108 ) {
3109 let addr_is_live = !liveness.is_dead_after(addr, block, inst_idx);
3111
3112 self.emit_value(func, val);
3114 self.spill_top_value_if_live(func, liveness, block, inst_idx, val);
3115
3116 self.emit_operand(func, addr);
3118 if addr_is_live && !Self::is_rematerializable_value(func, addr) {
3120 self.spill_value_if_needed(func, addr);
3121 }
3122
3123 self.asm.emit_op(opcode);
3124 self.scheduler.instruction_executed(2, None);
3125 }
3126
3127 fn emit_copy_op_live_aware(
3132 &mut self,
3133 func: &Function,
3134 operands: &[ValueId],
3135 opcode: u8,
3136 liveness: &Liveness,
3137 block: BlockId,
3138 inst_idx: usize,
3139 ) {
3140 for (i, &op) in operands.iter().enumerate() {
3141 if i == 0 {
3142 self.emit_value(func, op);
3143 } else {
3144 self.emit_operand(func, op);
3146 }
3147 self.spill_top_value_if_live(func, liveness, block, inst_idx, op);
3148 }
3149
3150 self.asm.emit_op(opcode);
3151 self.scheduler.instruction_executed(operands.len(), None);
3152 }
3153
3154 fn emit_ternary_op(
3156 &mut self,
3157 func: &Function,
3158 a: ValueId,
3159 b: ValueId,
3160 c: ValueId,
3161 opcode: u8,
3162 result: Option<ValueId>,
3163 ) {
3164 self.emit_value(func, c);
3165 self.emit_operand(func, b);
3166 self.emit_operand(func, a);
3167 self.asm.emit_op(opcode);
3168 self.scheduler.instruction_executed(3, result);
3169 }
3170
3171 fn generate_copy(
3177 &mut self,
3178 func: &Function,
3179 copy: &ParallelCopy,
3180 temps: &mut FxHashMap<u32, ValueId>,
3181 ) {
3182 match ©.src {
3184 CopySource::Value(val) => {
3185 self.emit_operand(func, *val);
3186 }
3187 CopySource::Temp(temp_id) => {
3188 if let Some(&temp_val) = temps.get(temp_id) {
3190 if let Some(depth) = self.scheduler.stack.find(temp_val) {
3192 let dup_n = (depth + 1) as u8;
3193 self.asm.emit_op(op::dup(dup_n));
3194 self.scheduler.stack.dup(dup_n);
3195 }
3196 }
3197 }
3198 }
3199
3200 match ©.dst {
3202 CopyDest::Value(dst_val) => {
3203 let slot = self.scheduler.spills.allocate(*dst_val);
3206 self.emit_spill_slot_addr(func, slot);
3207 self.scheduler.stack.push_unknown();
3208 self.asm.emit_op(op::MSTORE);
3209 self.scheduler.stack.pop(); self.scheduler.stack.pop(); self.scheduler.spills.mark_stored(*dst_val);
3212 }
3213 CopyDest::Temp(temp_id) => {
3214 if let Some(val_on_top) = self.scheduler.stack.top() {
3217 temps.insert(*temp_id, val_on_top);
3218 }
3219 }
3220 }
3221 }
3222
3223 fn pop_all_stack_values(&mut self) {
3226 while self.scheduler.stack_depth() > 0 {
3227 self.asm.emit_op(op::POP);
3228 self.scheduler.stack.pop();
3229 }
3230 }
3231
3232 fn emit_internal_return(&mut self, func: &Function, values: &[ValueId]) {
3233 let return_base = 64 + (func.params.len() as u64) * 32;
3234 for (i, &value) in values.iter().enumerate() {
3235 self.emit_operand(func, value);
3236 self.emit_current_internal_frame_addr(return_base + (i as u64) * 32);
3237 self.asm.emit_op(op::MSTORE);
3238 self.scheduler.stack.pop();
3239 }
3240
3241 self.pop_all_stack_values();
3242 self.emit_current_internal_frame_addr(0);
3243 self.asm.emit_op(op::MLOAD);
3244 self.asm.emit_op(op::JUMP);
3245 }
3246
3247 fn generate_terminator(
3249 &mut self,
3250 func: &Function,
3251 term: &Terminator,
3252 fallthrough: Option<BlockId>,
3253 preserve_stack: bool,
3254 ) {
3255 match term {
3256 Terminator::Jump(target) => {
3257 if Some(*target) == fallthrough {
3262 if !preserve_stack {
3263 self.pop_all_stack_values();
3264 }
3265 return;
3266 }
3267 if !preserve_stack {
3268 self.pop_all_stack_values();
3269 }
3270 self.asm.emit_push_label(self.block_labels[target]);
3271 self.asm.emit_op(op::JUMP);
3272 }
3273
3274 Terminator::Branch { condition, then_block, else_block } => {
3275 self.emit_value(func, *condition);
3277
3278 if !preserve_stack {
3283 while self.scheduler.depth() > 1 {
3284 self.asm.emit_op(op::SWAP1);
3286 self.scheduler.stack_swapped();
3287 self.asm.emit_op(op::POP);
3288 self.scheduler.stack.pop();
3289 }
3290 }
3291
3292 match fallthrough {
3293 Some(next) if *else_block == next => {
3294 self.asm.emit_push_label(self.block_labels[then_block]);
3296 self.asm.emit_op(op::JUMPI);
3297 self.scheduler.stack.pop(); }
3299 Some(next) if *then_block == next => {
3300 self.asm.emit_op(op::ISZERO);
3302 self.scheduler.instruction_executed_untracked(1);
3303 self.asm.emit_push_label(self.block_labels[else_block]);
3304 self.asm.emit_op(op::JUMPI);
3305 self.scheduler.stack.pop(); }
3307 _ => {
3308 if Self::block_is_cold(func, *then_block)
3314 && !Self::block_is_cold(func, *else_block)
3315 {
3316 self.asm.emit_op(op::ISZERO);
3317 self.scheduler.instruction_executed_untracked(1);
3318 self.asm.emit_push_label(self.block_labels[else_block]);
3319 self.asm.emit_op(op::JUMPI);
3320 self.scheduler.stack.pop(); self.asm.emit_push_label(self.block_labels[then_block]);
3323 self.asm.emit_op(op::JUMP);
3324 } else {
3325 self.asm.emit_push_label(self.block_labels[then_block]);
3327 self.asm.emit_op(op::JUMPI);
3328 self.scheduler.stack.pop(); self.asm.emit_push_label(self.block_labels[else_block]);
3331 self.asm.emit_op(op::JUMP);
3332 }
3333 }
3334 }
3335 }
3336
3337 Terminator::Switch { value, default, cases } => {
3338 let mut operands = Vec::with_capacity(cases.len() + 1);
3339 operands.push(*value);
3340 operands.extend(cases.iter().map(|(case_val, _)| *case_val));
3341 self.spill_values_before_stack_clear(func, &operands);
3342
3343 self.pop_all_stack_values();
3345
3346 self.emit_value(func, *value);
3348
3349 for (case_val, target) in cases {
3350 self.asm.emit_op(op::DUP1);
3352 self.scheduler.stack.dup(1);
3353 self.emit_operand(func, *case_val);
3354 self.asm.emit_op(op::EQ);
3355 self.scheduler.instruction_executed_untracked(2);
3356 self.asm.emit_push_label(self.block_labels[target]);
3357 self.asm.emit_op(op::JUMPI);
3358 self.scheduler.instruction_executed(1, None); }
3360
3361 self.asm.emit_op(op::POP);
3363 self.scheduler.stack.pop();
3364 if Some(*default) != fallthrough {
3365 self.asm.emit_push_label(self.block_labels[default]);
3366 self.asm.emit_op(op::JUMP);
3367 }
3368 }
3369
3370 Terminator::Return { values } => {
3371 if self.in_internal_function {
3372 self.emit_internal_return(func, values);
3373 return;
3374 }
3375
3376 assert!(values.is_empty(), "external ABI returns with values must use ReturnData");
3377 self.asm.emit_push(U256::ZERO);
3378 self.asm.emit_push(U256::ZERO);
3379 self.asm.emit_op(op::RETURN);
3380 }
3381
3382 Terminator::Revert { offset, size } => {
3383 self.emit_value(func, *size);
3384 self.emit_operand(func, *offset);
3385 self.asm.emit_op(op::REVERT);
3386 }
3387
3388 Terminator::ReturnData { offset, size } => {
3389 debug_assert!(!self.in_internal_function);
3390 self.emit_value(func, *size);
3391 self.emit_operand(func, *offset);
3392 self.asm.emit_op(op::RETURN);
3393 }
3394
3395 Terminator::Stop => {
3396 if self.in_internal_function {
3397 self.emit_internal_return(func, &[]);
3398 } else {
3399 self.asm.emit_op(op::STOP);
3400 }
3401 }
3402
3403 Terminator::SelfDestruct { recipient } => {
3404 self.emit_value(func, *recipient);
3405 self.asm.emit_op(op::SELFDESTRUCT);
3406 }
3407
3408 Terminator::Invalid => {
3409 self.asm.emit_op(op::INVALID);
3410 }
3411 }
3412 }
3413}
3414
3415impl Default for EvmCodegen {
3416 fn default() -> Self {
3417 Self::new(EvmCodegenConfig::default())
3418 }
3419}
3420
3421#[derive(Clone, Debug, Default)]
3423pub struct EvmArtifact {
3424 pub deployment: Vec<u8>,
3426 pub runtime: Vec<u8>,
3428}
3429
3430impl crate::backend::Backend for EvmCodegen {
3431 type Output = EvmArtifact;
3432
3433 fn name(&self) -> &str {
3434 "evm"
3435 }
3436
3437 fn lower_module(&mut self, module: &mut Module) -> EvmArtifact {
3438 let (deployment, runtime) = self.generate_deployment_bytecode(module);
3439 EvmArtifact { deployment, runtime }
3440 }
3441}
3442
3443#[cfg(test)]
3444mod tests {
3445 use super::*;
3446 use crate::lower;
3447 use solar_config::{CompileOpts, UnstableOpts};
3448 use solar_interface::{Session, sym};
3449 use solar_sema::Compiler;
3450 use std::{ops::ControlFlow, path::PathBuf};
3451
3452 fn compile_source(source: &str) -> Result<Vec<u8>, String> {
3454 compile_source_with_stack_schedule(source, false)
3455 }
3456
3457 fn compile_source_with_stack_schedule(
3460 source: &str,
3461 evm_ir_stack_schedule: bool,
3462 ) -> Result<Vec<u8>, String> {
3463 let opts = CompileOpts {
3464 unstable: UnstableOpts { codegen: true, ..Default::default() },
3465 ..Default::default()
3466 };
3467 let sess = Session::builder().with_buffer_emitter(Default::default()).opts(opts).build();
3468 let mut compiler = Compiler::new(sess);
3469
3470 let parse_result = compiler.enter_mut(|c| -> solar_interface::Result<_> {
3472 let mut ctx = c.parse();
3473 let file = c
3474 .sess()
3475 .source_map()
3476 .new_source_file(PathBuf::from("test.sol"), source.to_string())
3477 .unwrap();
3478 ctx.add_file(file);
3479 ctx.parse();
3480 Ok(())
3481 });
3482 if parse_result.is_err() {
3483 return Err("Parse error".to_string());
3484 }
3485
3486 compiler.enter_mut(|c| -> Result<Vec<u8>, String> {
3488 let ControlFlow::Continue(()) = c.lower_asts().map_err(|_| "Lower AST error")? else {
3489 return Err("Lower AST break".to_string());
3490 };
3491 let ControlFlow::Continue(()) = c.analysis().map_err(|_| "Analysis error")? else {
3492 return Err("Analysis break".to_string());
3493 };
3494
3495 let gcx = c.gcx();
3496 for (contract_id, contract) in gcx.hir.contracts_enumerated() {
3497 if contract.name.name == sym::Test {
3498 let mut module = lower::lower_contract(gcx, contract_id);
3499 let config =
3500 EvmCodegenConfig { evm_ir_stack_schedule, ..EvmCodegenConfig::from(gcx) };
3501 let mut codegen = EvmCodegen::new(config);
3502 let bytecode = codegen.generate_module(&mut module);
3503 return Ok(bytecode);
3504 }
3505 }
3506 Err("Contract 'Test' not found".to_string())
3507 })
3508 }
3509
3510 #[test]
3511 fn test_local_var_in_conditional_ice() {
3512 let source = r#"
3517 // SPDX-License-Identifier: MIT
3518 pragma solidity ^0.8.0;
3519 contract Test {
3520 uint256 public value;
3521 function test() public {
3522 uint256 v = value;
3523 if (v != 0) value = v - 1;
3524 }
3525 }
3526 "#;
3527
3528 let result = compile_source(source);
3529 assert!(result.is_ok(), "Compilation failed: {:?}", result.err());
3530 let bytecode = result.unwrap();
3531 assert!(!bytecode.is_empty(), "Bytecode should not be empty");
3532 }
3533
3534 #[test]
3535 fn test_direct_storage_in_conditional_works() {
3536 let source = r#"
3538 // SPDX-License-Identifier: MIT
3539 pragma solidity ^0.8.0;
3540 contract Test {
3541 uint256 public value;
3542 function test() public {
3543 if (value != 0) value = value - 1;
3544 }
3545 }
3546 "#;
3547
3548 let result = compile_source(source);
3549 assert!(result.is_ok(), "Compilation failed: {:?}", result.err());
3550 }
3551
3552 #[test]
3553 fn test_phi_value_used_after_if_else() {
3554 let source = r#"
3558 // SPDX-License-Identifier: MIT
3559 pragma solidity ^0.8.0;
3560 contract Test {
3561 uint256 public totalSupply;
3562 function mint() external returns (uint256 liquidity) {
3563 if (totalSupply == 0) {
3564 liquidity = 1;
3565 } else {
3566 liquidity = 2;
3567 }
3568 totalSupply += liquidity;
3569 }
3570 }
3571 "#;
3572
3573 let result = compile_source(source);
3574 assert!(result.is_ok(), "Compilation failed: {:?}", result.err());
3575 let bytecode = result.unwrap();
3576 assert!(!bytecode.is_empty(), "Bytecode should not be empty");
3577 }
3578
3579 #[test]
3580 fn test_phi_value_used_multiple_times_after_if_else() {
3581 let source = r#"
3583 // SPDX-License-Identifier: MIT
3584 pragma solidity ^0.8.0;
3585 contract Test {
3586 uint256 public totalSupply;
3587 function mint() external returns (uint256 result) {
3588 uint256 liquidity;
3589 if (totalSupply == 0) {
3590 liquidity = 1;
3591 } else {
3592 liquidity = 2;
3593 }
3594 totalSupply += liquidity;
3595 uint256 x = liquidity * 2;
3596 result = x + liquidity;
3597 }
3598 }
3599 "#;
3600
3601 let result = compile_source(source);
3602 assert!(result.is_ok(), "Compilation failed: {:?}", result.err());
3603 let bytecode = result.unwrap();
3604 assert!(!bytecode.is_empty(), "Bytecode should not be empty");
3605 }
3606
3607 #[test]
3615 fn stack_schedule_bridge_flag_is_bytecode_neutral() {
3616 let samples = [
3617 r#"
3619 // SPDX-License-Identifier: MIT
3620 pragma solidity ^0.8.0;
3621 contract Test {
3622 uint256 public value;
3623 function test() public {
3624 uint256 v = value;
3625 if (v != 0) value = v - 1;
3626 }
3627 }
3628 "#,
3629 r#"
3631 // SPDX-License-Identifier: MIT
3632 pragma solidity ^0.8.0;
3633 contract Test {
3634 uint256 public totalSupply;
3635 function mint() external returns (uint256 liquidity) {
3636 if (totalSupply == 0) {
3637 liquidity = 1;
3638 } else {
3639 liquidity = 2;
3640 }
3641 totalSupply += liquidity;
3642 }
3643 }
3644 "#,
3645 r#"
3647 // SPDX-License-Identifier: MIT
3648 pragma solidity ^0.8.0;
3649 contract Test {
3650 function sum(uint256 n) public pure returns (uint256 acc) {
3651 for (uint256 i = 0; i < n; i++) {
3652 acc += i;
3653 }
3654 }
3655 }
3656 "#,
3657 ];
3658
3659 for source in samples {
3660 let off = compile_source_with_stack_schedule(source, false);
3661 let on = compile_source_with_stack_schedule(source, true);
3662 let off = off.expect("baseline compilation should succeed");
3663 let on = on.expect("stack-schedule compilation should succeed");
3664 assert!(!off.is_empty(), "baseline bytecode should not be empty");
3665 assert_eq!(
3666 off, on,
3667 "enabling evm_ir_stack_schedule changed produced bytecode for sample:\n{source}"
3668 );
3669 }
3670 }
3671
3672 #[test]
3673 fn test_phi_with_ternary_in_branch() {
3674 let source = r#"
3676 // SPDX-License-Identifier: MIT
3677 pragma solidity ^0.8.0;
3678 contract Test {
3679 uint256 public totalSupply;
3680 uint256 public reserve0;
3681 uint256 public reserve1;
3682
3683 function mint() external returns (uint256 liquidity) {
3684 uint256 amount0 = 100;
3685 uint256 amount1 = 200;
3686
3687 if (totalSupply == 0) {
3688 liquidity = amount0 * amount1;
3689 } else {
3690 uint256 l1 = (amount0 * totalSupply) / reserve0;
3691 uint256 l2 = (amount1 * totalSupply) / reserve1;
3692 liquidity = l1 < l2 ? l1 : l2;
3693 }
3694
3695 totalSupply += liquidity;
3696 }
3697 }
3698 "#;
3699
3700 let result = compile_source(source);
3701 assert!(result.is_ok(), "Compilation failed: {:?}", result.err());
3702 let bytecode = result.unwrap();
3703 assert!(!bytecode.is_empty(), "Bytecode should not be empty");
3704 }
3705}