Skip to main content

solar_codegen/lower/
mod.rs

1//! HIR to MIR lowering.
2//!
3//! This module transforms the high-level IR from solar-sema into MIR.
4
5mod abi_encode;
6mod abi_packed;
7mod bytes;
8mod call;
9mod checked_arith;
10mod expr;
11mod index;
12mod stmt;
13mod storage;
14mod type_query;
15
16use crate::{
17    IMMUTABLE_SCRATCH_BASE,
18    mir::{
19        BlockId, Function, FunctionAttributes, FunctionBuilder, FunctionId, IMMUTABLE_WORD_SIZE,
20        ImmutableSlot, MirType, Module, StorageSlot, ValueId,
21    },
22};
23use alloy_primitives::U256;
24use solar_data_structures::{
25    Never,
26    map::{FxHashMap, FxHashSet},
27};
28use solar_interface::{Ident, Span, diagnostics::DiagMsg, kw, sym};
29use solar_sema::{
30    hir::{self, ContractId, ElementaryType, FunctionId as HirFunctionId, VariableId, Visit},
31    ty::{Gcx, Ty, TyKind},
32};
33use std::ops::ControlFlow;
34
35use self::storage::StorageLocation;
36
37/// Context for a loop (tracks break/continue targets).
38#[derive(Clone, Copy)]
39pub struct LoopContext {
40    /// Block to jump to on `break`.
41    pub break_target: BlockId,
42    /// Block to jump to on `continue`.
43    pub continue_target: BlockId,
44}
45
46#[derive(Clone, Copy)]
47enum AbiParamSource {
48    ExternalCalldata,
49    ConstructorMemory,
50}
51
52/// Lowering context for converting HIR to MIR.
53pub struct Lowerer<'gcx> {
54    /// The global context.
55    gcx: Gcx<'gcx>,
56    /// The current module being built.
57    module: Module,
58    /// The current contract being lowered.
59    current_contract_id: Option<ContractId>,
60    /// Mapping from HIR variable IDs to storage slots.
61    storage_slots: FxHashMap<VariableId, u64>,
62    /// Mapping from HIR variable IDs to full storage locations.
63    storage_locations: FxHashMap<VariableId, StorageLocation>,
64    /// Next available storage slot.
65    next_storage_slot: u64,
66    /// Next available byte offset in `next_storage_slot` for packed variables.
67    next_storage_offset: u8,
68    /// Mapping from HIR immutable variable IDs to runtime immutable byte offsets.
69    immutable_slots: FxHashMap<VariableId, u32>,
70    /// Next available immutable byte offset.
71    next_immutable_offset: u32,
72    /// Mapping from HIR variable IDs to MIR values (for local variables).
73    /// For SSA-style immutable variables (function params and non-mutated locals).
74    locals: FxHashMap<VariableId, ValueId>,
75    /// Mapping from HIR variable IDs to memory offsets (for mutable locals).
76    /// Memory layout: starts at offset 0x80 (after scratch space).
77    local_memory_slots: FxHashMap<VariableId, u64>,
78    /// Next available memory offset for locals.
79    next_local_memory_offset: u64,
80    /// Bytecodes of other contracts (for `new` expressions).
81    /// Maps contract ID to (deployment_bytecode, data_segment_index).
82    contract_bytecodes: FxHashMap<ContractId, (Vec<u8>, usize)>,
83    /// Stack of loop contexts for nested loops.
84    loop_stack: Vec<LoopContext>,
85    /// Variables that are assigned after declaration (need memory storage).
86    /// Variables not in this set can be kept as SSA values.
87    assigned_vars: FxHashSet<VariableId>,
88    /// Local variables that are storage references (pointers). Their value in
89    /// `locals` is a storage *slot*, so `r.field` reads `sload(slot + offset)`
90    /// and `r.field = v` writes `sstore(slot + offset, v)`, rather than treating
91    /// the value as a memory pointer.
92    storage_ref_locals: FxHashSet<VariableId>,
93    /// Stack of function IDs currently being inlined (for cycle detection).
94    inline_stack: Vec<HirFunctionId>,
95    /// HIR functions already lowered into this MIR module.
96    hir_to_mir_functions: FxHashMap<HirFunctionId, FunctionId>,
97    /// Internal-convention copies of public functions, lowered on demand so that
98    /// public functions can be called internally/recursively via `internal_call`.
99    hir_to_internal_mir_functions: FxHashMap<HirFunctionId, FunctionId>,
100    /// Cache of whether a function is (directly) self-recursive.
101    recursive_functions: FxHashMap<HirFunctionId, bool>,
102    /// Functions currently being lowered on demand.
103    lowering_functions: FxHashSet<HirFunctionId>,
104    /// Whether the current function body is constructor code.
105    lowering_constructor: bool,
106    /// Whether local memory slots should be addressed through the internal-call frame.
107    lowering_internal_function: bool,
108    /// Whether arithmetic should use wrapping Solidity `unchecked` semantics.
109    in_unchecked_block: bool,
110    /// Sema return types of the function currently being lowered (one per declared
111    /// return), used to ABI-encode external returns.
112    current_return_tys: Vec<Ty<'gcx>>,
113    /// Mapping from struct state variable ID to base storage slot.
114    pub struct_storage_base_slots: FxHashMap<VariableId, u64>,
115    /// Cached struct field slot offsets: (struct_type_id, field_index) -> slot offset from base.
116    pub struct_field_offsets: FxHashMap<(hir::StructId, usize), u64>,
117    /// Cached struct field memory offsets: (struct_type_id, field_index) -> byte offset from base.
118    pub struct_field_memory_offsets: FxHashMap<(hir::StructId, usize), u64>,
119}
120
121impl<'gcx> Lowerer<'gcx> {
122    /// Reports a lowering error and returns the error sentinel value carrying
123    /// the emitted diagnostic's guarantee, mirroring HIR's error types.
124    pub(super) fn err_value(
125        &self,
126        builder: &mut FunctionBuilder<'_>,
127        span: Span,
128        msg: impl Into<DiagMsg>,
129    ) -> ValueId {
130        let guar = self.gcx.dcx().err(msg).span(span).emit();
131        builder.error_value(guar)
132    }
133
134    /// Creates a new lowerer.
135    pub fn new(gcx: Gcx<'gcx>, name: Ident) -> Self {
136        if !gcx.has_typeck_results() {
137            gcx.dcx().emit_err(
138                name.span,
139                "tried to lower contract without typeck results; likely missing -Zcodegen",
140            );
141        }
142        Self {
143            gcx,
144            module: Module::new(name),
145            current_contract_id: None,
146            storage_slots: FxHashMap::default(),
147            storage_locations: FxHashMap::default(),
148            next_storage_slot: 0,
149            next_storage_offset: 0,
150            immutable_slots: FxHashMap::default(),
151            next_immutable_offset: 0,
152            locals: FxHashMap::default(),
153            local_memory_slots: FxHashMap::default(),
154            next_local_memory_offset: 0x80, // Start after Solidity's scratch space
155            contract_bytecodes: FxHashMap::default(),
156            loop_stack: Vec::new(),
157            assigned_vars: FxHashSet::default(),
158            storage_ref_locals: FxHashSet::default(),
159            inline_stack: Vec::new(),
160            hir_to_mir_functions: FxHashMap::default(),
161            hir_to_internal_mir_functions: FxHashMap::default(),
162            recursive_functions: FxHashMap::default(),
163            lowering_functions: FxHashSet::default(),
164            lowering_constructor: false,
165            lowering_internal_function: false,
166            in_unchecked_block: false,
167            current_return_tys: Vec::new(),
168            struct_storage_base_slots: FxHashMap::default(),
169            struct_field_offsets: FxHashMap::default(),
170            struct_field_memory_offsets: FxHashMap::default(),
171        }
172    }
173
174    /// Pushes a loop context onto the stack.
175    pub fn push_loop(&mut self, ctx: LoopContext) {
176        self.loop_stack.push(ctx);
177    }
178
179    /// Pops a loop context from the stack.
180    pub fn pop_loop(&mut self) {
181        self.loop_stack.pop();
182    }
183
184    /// Gets the current loop context, if any.
185    pub fn current_loop(&self) -> Option<&LoopContext> {
186        self.loop_stack.last()
187    }
188
189    /// Maximum inline depth to prevent excessive recursion.
190    const MAX_INLINE_DEPTH: usize = 32;
191    /// Historical base used by local memory slots in external function bodies.
192    const LOCAL_MEMORY_BASE: u64 = 0x80;
193    /// Attempts to enter inlining for a function. Returns false if a cycle is detected
194    /// or the max inline depth is exceeded.
195    fn try_enter_inline(&mut self, func_id: HirFunctionId) -> bool {
196        // Check for cycle
197        if self.inline_stack.contains(&func_id) {
198            return false;
199        }
200        // Check depth limit
201        if self.inline_stack.len() >= Self::MAX_INLINE_DEPTH {
202            return false;
203        }
204        self.inline_stack.push(func_id);
205        true
206    }
207
208    /// Exits inlining for a function.
209    fn exit_inline(&mut self) {
210        self.inline_stack.pop();
211    }
212
213    /// Allocates a memory slot for a local variable.
214    /// Returns the memory offset.
215    pub fn alloc_local_memory(&mut self, var_id: VariableId) -> u64 {
216        let offset = self.next_local_memory_offset;
217        self.next_local_memory_offset += 32; // Each slot is 32 bytes
218        self.local_memory_slots.insert(var_id, offset);
219        offset
220    }
221
222    /// Gets the memory offset for a local variable, if it's stored in memory.
223    pub fn get_local_memory_offset(&self, var_id: &VariableId) -> Option<u64> {
224        self.local_memory_slots.get(var_id).copied()
225    }
226
227    /// Returns the address for a local memory slot in the current lowering context.
228    pub fn local_memory_addr(&self, builder: &mut FunctionBuilder<'_>, offset: u64) -> ValueId {
229        if self.lowering_internal_function {
230            let header_size = 64;
231            let arg_size = (builder.func().params.len() as u64) * 32;
232            let return_size = (builder.func().returns.len() as u64) * 32;
233            let local_offset = offset.saturating_sub(Self::LOCAL_MEMORY_BASE);
234            builder.internal_frame_addr(header_size + arg_size + return_size + local_offset)
235        } else {
236            builder.imm_u64(offset)
237        }
238    }
239
240    /// Returns the constructor scratch address for an immutable word.
241    pub fn immutable_scratch_addr(offset: u32) -> u64 {
242        IMMUTABLE_SCRATCH_BASE + u64::from(offset)
243    }
244
245    /// Stages an immutable word in constructor memory.
246    pub fn store_immutable_value(
247        &self,
248        builder: &mut FunctionBuilder<'_>,
249        offset: u32,
250        value: ValueId,
251    ) {
252        let addr = builder.imm_u64(Self::immutable_scratch_addr(offset));
253        builder.mstore(addr, value);
254    }
255
256    /// Loads an immutable word.
257    ///
258    /// Runtime code reads a `PUSH32` placeholder that the constructor patches
259    /// with the staged value before returning the runtime code. The running
260    /// constructor's own placeholders are never patched, so constructor-context
261    /// reads load the staged scratch word instead.
262    pub fn load_immutable_value(&self, builder: &mut FunctionBuilder<'_>, offset: u32) -> ValueId {
263        if self.lowering_constructor {
264            let addr = builder.imm_u64(Self::immutable_scratch_addr(offset));
265            builder.mload(addr)
266        } else {
267            builder.load_immutable(offset)
268        }
269    }
270
271    /// Registers a contract's bytecode for use in `new` expressions.
272    pub fn register_contract_bytecode(&mut self, contract_id: ContractId, bytecode: Vec<u8>) {
273        let segment_idx = self.module.add_data_segment(bytecode.clone());
274        self.contract_bytecodes.insert(contract_id, (bytecode, segment_idx));
275    }
276
277    /// Gets the bytecode for a contract, if registered.
278    pub fn get_contract_bytecode(&self, contract_id: ContractId) -> Option<&(Vec<u8>, usize)> {
279        self.contract_bytecodes.get(&contract_id)
280    }
281
282    /// Lowers a contract to MIR.
283    pub fn lower_contract(&mut self, contract_id: ContractId) {
284        let contract = self.gcx.hir.contract(contract_id);
285
286        // Track the current contract for using directive resolution.
287        self.current_contract_id = Some(contract_id);
288
289        // Mark interfaces - they don't generate deployable bytecode.
290        if contract.kind == hir::ContractKind::Interface {
291            self.module.is_interface = true;
292        }
293
294        self.allocate_storage(contract_id);
295
296        // Collect all functions from the inheritance chain, handling overrides.
297        // Functions are collected from most-derived to most-base, so if a function
298        // with the same selector already exists, we skip the base version.
299        let functions = self.collect_inherited_functions(contract_id);
300
301        // Generate a constructor for inherited construction/state-variable
302        // initialization when the current contract does not declare one.
303        if contract.ctor.is_none() {
304            self.generate_synthetic_constructor(contract_id);
305        }
306
307        for func_id in functions {
308            self.ensure_function_lowered(func_id);
309        }
310
311        self.current_contract_id = None;
312    }
313
314    /// Collects all functions from the inheritance chain, handling overrides.
315    ///
316    /// Functions from more-derived contracts take precedence over base contracts.
317    /// For regular functions, we use the selector to determine uniqueness.
318    /// For constructor/fallback/receive, we use the function kind.
319    fn collect_inherited_functions(&self, contract_id: ContractId) -> Vec<HirFunctionId> {
320        let contract = self.gcx.hir.contract(contract_id);
321        let linearized_bases = contract.linearized_bases;
322
323        let mut seen_selectors: FxHashSet<[u8; 4]> = FxHashSet::default();
324        let mut has_constructor = false;
325        let mut has_fallback = false;
326        let mut has_receive = false;
327        let mut functions = Vec::new();
328
329        // Iterate from most-derived (index 0) to most-base (last index).
330        // The first function with a given selector wins (override behavior).
331        for &base_id in linearized_bases.iter() {
332            let base_contract = self.gcx.hir.contract(base_id);
333
334            for func_id in base_contract.all_functions() {
335                let func = self.gcx.hir.function(func_id);
336
337                // Handle special functions by kind
338                match func.kind {
339                    hir::FunctionKind::Constructor => {
340                        // Constructors are not inherited. Base constructors
341                        // are called from the current contract's constructor
342                        // prelude instead.
343                        if base_id == contract_id && !has_constructor {
344                            has_constructor = true;
345                            functions.push(func_id);
346                        }
347                    }
348                    hir::FunctionKind::Fallback => {
349                        if !has_fallback {
350                            has_fallback = true;
351                            functions.push(func_id);
352                        }
353                    }
354                    hir::FunctionKind::Receive => {
355                        if !has_receive {
356                            has_receive = true;
357                            functions.push(func_id);
358                        }
359                    }
360                    hir::FunctionKind::Function | hir::FunctionKind::Modifier => {
361                        // Skip private functions from base contracts - they're not inherited
362                        if base_id != contract_id && func.visibility == hir::Visibility::Private {
363                            continue;
364                        }
365
366                        // For regular functions, use selector to determine uniqueness.
367                        // Only external/public functions have selectors.
368                        let is_external_abi = matches!(
369                            func.visibility,
370                            hir::Visibility::External | hir::Visibility::Public
371                        );
372                        if is_external_abi {
373                            let selector = self.function_selector(func_id);
374                            if seen_selectors.insert(selector) {
375                                functions.push(func_id);
376                            }
377                        } else {
378                            // Internal functions: use function identity
379                            // For simplicity, we include internal functions from all bases
380                            // (they won't have selectors in the dispatcher anyway)
381                            functions.push(func_id);
382                        }
383                    }
384                }
385            }
386        }
387
388        functions
389    }
390
391    /// Generates a synthetic constructor to initialize state variables and run
392    /// inherited constructors when the current contract does not declare one.
393    fn generate_synthetic_constructor(&mut self, contract_id: ContractId) {
394        let contract = self.gcx.hir.contract(contract_id);
395        let linearized_bases = contract.linearized_bases;
396
397        let has_state_initializers = linearized_bases.iter().any(|&base_id| {
398            self.gcx.hir.contract(base_id).variables().any(|var_id| {
399                let var = self.gcx.hir.variable(var_id);
400                var.is_state_variable() && !var.is_constant() && var.initializer.is_some()
401            })
402        });
403        let has_base_constructors = linearized_bases.iter().any(|&base_id| {
404            base_id != contract_id && self.gcx.hir.contract(base_id).ctor.is_some()
405        });
406
407        if !has_state_initializers && !has_base_constructors {
408            return;
409        }
410
411        // Create constructor function
412        let ctor_name = Ident::new(kw::Constructor, Span::DUMMY);
413        let mut mir_func = Function::new(ctor_name);
414        mir_func.attributes = FunctionAttributes {
415            visibility: hir::Visibility::Public,
416            state_mutability: hir::StateMutability::NonPayable,
417            is_constructor: true,
418            is_fallback: false,
419            is_receive: false,
420        };
421
422        {
423            let mut builder = FunctionBuilder::new(&mut mir_func);
424            let saved_lowering_constructor = self.lowering_constructor;
425            let saved_lowering_internal_function = self.lowering_internal_function;
426            let saved_in_unchecked_block = self.in_unchecked_block;
427            let saved_current_return_tys = std::mem::take(&mut self.current_return_tys);
428            self.lowering_constructor = true;
429            self.lowering_internal_function = false;
430            self.in_unchecked_block = false;
431
432            self.lower_constructor_prelude(&mut builder, contract_id);
433            builder.stop();
434            self.lowering_constructor = saved_lowering_constructor;
435            self.lowering_internal_function = saved_lowering_internal_function;
436            self.in_unchecked_block = saved_in_unchecked_block;
437            self.current_return_tys = saved_current_return_tys;
438        }
439
440        self.module.add_function(mir_func);
441    }
442
443    /// Allocates storage slots for state variables.
444    ///
445    /// For inheritance, state variables are allocated starting from the most base contract
446    /// (last in linearized_bases) to the most derived (first in linearized_bases).
447    /// This ensures parent storage comes before child storage in the layout.
448    fn allocate_storage(&mut self, contract_id: ContractId) {
449        let contract = self.gcx.hir.contract(contract_id);
450        let linearized_bases = contract.linearized_bases;
451
452        // Iterate in reverse order (most base first) to get correct storage layout.
453        // Skip index 0 since that's the contract itself - we handle it last.
454        for &base_id in linearized_bases.iter().rev() {
455            let base_contract = self.gcx.hir.contract(base_id);
456            for var_id in base_contract.variables() {
457                // Skip if we already allocated this variable (shouldn't happen, but safety check)
458                if self.storage_slots.contains_key(&var_id) {
459                    continue;
460                }
461
462                let var = self.gcx.hir.variable(var_id);
463                // Constants are inlined. Immutables are patched into the
464                // runtime code's `PUSH32` placeholders at deploy time.
465                if var.is_state_variable() && var.is_immutable() {
466                    let offset = self.next_immutable_offset;
467                    self.next_immutable_offset = self
468                        .next_immutable_offset
469                        .checked_add(IMMUTABLE_WORD_SIZE as u32)
470                        .expect("immutable offset overflow");
471                    self.immutable_slots.insert(var_id, offset);
472
473                    let mir_ty = self.lower_type_from_var(var);
474                    self.module.add_immutable_slot(ImmutableSlot {
475                        offset,
476                        ty: mir_ty,
477                        name: var.name,
478                    });
479                } else if var.is_state_variable() && !var.is_constant() {
480                    let var_ty = self.gcx.type_of_hir_ty(&var.ty);
481                    let location = self.allocate_storage_location(var_ty, var.ty.span);
482                    let base_slot = location.slot;
483
484                    // Track struct base slots for field access
485                    if matches!(var_ty.peel_refs().kind, TyKind::Struct(_)) {
486                        self.struct_storage_base_slots.insert(var_id, base_slot);
487                    }
488
489                    self.storage_slots.insert(var_id, base_slot);
490                    self.storage_locations.insert(var_id, location);
491
492                    let mir_ty = self.lower_type_from_var(var);
493                    self.module.add_storage_slot(StorageSlot {
494                        slot: base_slot,
495                        offset: location.offset,
496                        ty: mir_ty,
497                        name: var.name,
498                    });
499                }
500            }
501        }
502    }
503
504    /// Returns the constant length of a fixed-size array parameter whose elements are single
505    /// ABI words, for prologue decoding. Other parameter shapes return `None`.
506    fn fixed_word_array_param_len(&self, param: &hir::Variable<'_>) -> Option<u64> {
507        let TyKind::Array(elem, len) = self.gcx.type_of_hir_ty(&param.ty).peel_refs().kind else {
508            return None;
509        };
510        (self.abi_is_word_element(elem) && len <= U256::from(u16::MAX)).then(|| len.to::<u64>())
511    }
512
513    /// Whether a parameter is a memory-located dynamic array of single-word elements, which
514    /// the prologue decodes from calldata into Solidity's `[length][data...]` memory layout.
515    fn is_dyn_word_array_memory_param(&self, param: &hir::Variable<'_>) -> bool {
516        if param.data_location != Some(solar_ast::DataLocation::Memory) {
517            return false;
518        }
519        match self.gcx.type_of_hir_ty(&param.ty).peel_refs().kind {
520            TyKind::DynArray(elem) => self.abi_is_word_element(elem),
521            _ => false,
522        }
523    }
524
525    /// Lowers a function to MIR.
526    pub(super) fn ensure_function_lowered(&mut self, func_id: hir::FunctionId) -> FunctionId {
527        if let Some(&mir_id) = self.hir_to_mir_functions.get(&func_id) {
528            return mir_id;
529        }
530
531        if self.lowering_functions.contains(&func_id) {
532            return self
533                .module
534                .add_function(Function::new(Ident::new(sym::_recursive_internal, Span::DUMMY)));
535        }
536
537        let saved_locals = std::mem::take(&mut self.locals);
538        let saved_local_memory_slots = std::mem::take(&mut self.local_memory_slots);
539        let saved_next_local_memory_offset = self.next_local_memory_offset;
540        let saved_assigned_vars = std::mem::take(&mut self.assigned_vars);
541        let saved_current_contract_id = self.current_contract_id;
542        let saved_lowering_constructor = self.lowering_constructor;
543        let saved_lowering_internal_function = self.lowering_internal_function;
544        let saved_in_unchecked_block = self.in_unchecked_block;
545        let saved_current_return_tys = std::mem::take(&mut self.current_return_tys);
546
547        self.lowering_functions.insert(func_id);
548        self.current_contract_id = self.gcx.hir.function(func_id).contract;
549        self.in_unchecked_block = false;
550        let mir_id = self.lower_function(func_id, false);
551        self.lowering_functions.remove(&func_id);
552
553        self.locals = saved_locals;
554        self.local_memory_slots = saved_local_memory_slots;
555        self.next_local_memory_offset = saved_next_local_memory_offset;
556        self.assigned_vars = saved_assigned_vars;
557        self.current_contract_id = saved_current_contract_id;
558        self.lowering_constructor = saved_lowering_constructor;
559        self.lowering_internal_function = saved_lowering_internal_function;
560        self.in_unchecked_block = saved_in_unchecked_block;
561        self.current_return_tys = saved_current_return_tys;
562
563        mir_id
564    }
565
566    /// Lowers a public function with the internal-frame calling convention so it
567    /// can be called via `internal_call` (e.g. recursion). The result is cached
568    /// separately from the external entry; the id is registered before the body
569    /// is lowered so the copy's own recursive call resolves to itself.
570    pub(super) fn ensure_internal_mir_function(&mut self, func_id: hir::FunctionId) -> FunctionId {
571        if let Some(&mir_id) = self.hir_to_internal_mir_functions.get(&func_id) {
572            return mir_id;
573        }
574
575        let saved_locals = std::mem::take(&mut self.locals);
576        let saved_local_memory_slots = std::mem::take(&mut self.local_memory_slots);
577        let saved_next_local_memory_offset = self.next_local_memory_offset;
578        let saved_assigned_vars = std::mem::take(&mut self.assigned_vars);
579        let saved_current_contract_id = self.current_contract_id;
580        let saved_lowering_constructor = self.lowering_constructor;
581        let saved_lowering_internal_function = self.lowering_internal_function;
582        let saved_in_unchecked_block = self.in_unchecked_block;
583        let saved_current_return_tys = std::mem::take(&mut self.current_return_tys);
584
585        self.current_contract_id = self.gcx.hir.function(func_id).contract;
586        self.in_unchecked_block = false;
587        let mir_id = self.lower_function(func_id, true);
588
589        self.locals = saved_locals;
590        self.local_memory_slots = saved_local_memory_slots;
591        self.next_local_memory_offset = saved_next_local_memory_offset;
592        self.assigned_vars = saved_assigned_vars;
593        self.current_contract_id = saved_current_contract_id;
594        self.lowering_constructor = saved_lowering_constructor;
595        self.lowering_internal_function = saved_lowering_internal_function;
596        self.in_unchecked_block = saved_in_unchecked_block;
597        self.current_return_tys = saved_current_return_tys;
598
599        mir_id
600    }
601
602    /// Lowers a function to MIR. When `force_internal` is set, the function is
603    /// lowered with the internal-frame convention (no selector) regardless of its
604    /// visibility, and registered in `hir_to_internal_mir_functions`.
605    fn lower_function(&mut self, func_id: hir::FunctionId, force_internal: bool) -> FunctionId {
606        let hir_func = self.gcx.hir.function(func_id);
607
608        let func_name = hir_func.name.unwrap_or_else(|| Ident::new(sym::_anonymous, Span::DUMMY));
609
610        // Reserve and register the MIR id before lowering the body so recursive
611        // self-calls can resolve to this function.
612        let mir_id = self.module.add_function(Function::new(func_name));
613        if force_internal {
614            self.hir_to_internal_mir_functions.insert(func_id, mir_id);
615        } else {
616            self.hir_to_mir_functions.insert(func_id, mir_id);
617        }
618
619        let mut mir_func = Function::new(func_name);
620
621        mir_func.attributes = FunctionAttributes {
622            visibility: hir_func.visibility,
623            state_mutability: hir_func.state_mutability,
624            is_constructor: hir_func.kind == hir::FunctionKind::Constructor,
625            is_fallback: hir_func.kind == hir::FunctionKind::Fallback,
626            is_receive: hir_func.kind == hir::FunctionKind::Receive,
627        };
628
629        // Only regular public/external functions get selectors. An internal copy
630        // (force_internal) uses the internal-frame convention with no selector.
631        // Constructor, receive, and fallback don't have selectors.
632        let is_special = mir_func.attributes.is_constructor
633            || mir_func.attributes.is_receive
634            || mir_func.attributes.is_fallback;
635        let uses_external_abi = mir_func.is_public() && !is_special && !force_internal;
636        let decodes_abi_params = uses_external_abi || mir_func.attributes.is_constructor;
637        if uses_external_abi {
638            mir_func.selector = Some(self.function_selector(func_id));
639        }
640        let uses_internal_frame = !uses_external_abi && !is_special;
641
642        self.locals.clear();
643        self.local_memory_slots.clear();
644        self.next_local_memory_offset = 0x80;
645        self.assigned_vars.clear();
646        self.lowering_constructor = hir_func.kind == hir::FunctionKind::Constructor;
647        self.lowering_internal_function = uses_internal_frame;
648        self.in_unchecked_block = false;
649        self.current_return_tys = hir_func
650            .returns
651            .iter()
652            .map(|&id| self.gcx.type_of_hir_ty(&self.gcx.hir.variable(id).ty))
653            .collect();
654
655        // Pre-analyze function body to find variables that are assigned after declaration.
656        // Variables that are only initialized (never reassigned) can stay as SSA values.
657        if let Some(body) = &hir_func.body {
658            self.collect_assigned_vars_block(body);
659        }
660
661        let external_arg_head_size = if uses_external_abi {
662            hir_func
663                .parameters
664                .iter()
665                .map(|&id| {
666                    let param = self.gcx.hir.variable(id);
667                    let ty = self.gcx.type_of_hir_ty(&param.ty);
668                    self.abi_head_size(ty)
669                })
670                .sum()
671        } else {
672            0
673        };
674
675        {
676            let mut builder = FunctionBuilder::new(&mut mir_func);
677
678            if uses_external_abi {
679                Self::emit_external_calldata_head_size_check(&mut builder, external_arg_head_size);
680            }
681
682            for &param_id in hir_func.parameters {
683                let param = self.gcx.hir.variable(param_id);
684                let param_ty = self.gcx.type_of_hir_ty(&param.ty);
685                let ty = self.lower_type_from_var(param);
686
687                // Check if this is a struct parameter that needs special handling
688                let abi_param_source = if self.lowering_constructor {
689                    AbiParamSource::ConstructorMemory
690                } else {
691                    AbiParamSource::ExternalCalldata
692                };
693
694                if decodes_abi_params && let TyKind::Struct(struct_id) = param_ty.peel_refs().kind {
695                    // Struct parameters: copy fields from calldata to memory
696                    let strukt = self.gcx.hir.strukt(struct_id);
697                    let field_ids = strukt.fields;
698                    let num_fields = field_ids.len();
699
700                    // Allocate memory for the struct
701                    let struct_size = (num_fields as u64) * 32;
702                    let struct_ptr = self.allocate_memory(&mut builder, struct_size);
703
704                    // Add MIR params for each struct field (they come from calldata)
705                    for (field_idx, &field_id) in field_ids.iter().enumerate() {
706                        let arg_index = builder.func().params.len() as u64;
707                        let field_ty = MirType::uint256();
708                        let field_val = builder.add_param(field_ty);
709                        let field_var = self.gcx.hir.variable(field_id);
710                        self.emit_abi_param_validation(
711                            &mut builder,
712                            arg_index,
713                            &field_var.ty,
714                            abi_param_source,
715                        );
716
717                        // Store the field value into the struct memory
718                        let field_offset = (field_idx as u64) * 32;
719                        if field_offset == 0 {
720                            builder.mstore(struct_ptr, field_val);
721                        } else {
722                            let offset_val = builder.imm_u64(field_offset);
723                            let field_addr = builder.add(struct_ptr, offset_val);
724                            builder.mstore(field_addr, field_val);
725                        }
726                    }
727
728                    // Store the memory pointer as the local (not the Arg value)
729                    self.locals.insert(param_id, struct_ptr);
730                } else if decodes_abi_params
731                    && let Some(len) = self.fixed_word_array_param_len(param)
732                {
733                    // Fixed-size array of word elements (memory or calldata):
734                    // the ABI head is `len` inline words. Add one MIR param per
735                    // element and copy them to memory, like struct params.
736                    let array_ptr = self.allocate_memory(&mut builder, len * 32);
737                    let elem_hir_ty = match &param.ty.kind {
738                        hir::TypeKind::Array(array) => &array.element,
739                        _ => &param.ty,
740                    };
741                    for elem_idx in 0..len {
742                        let arg_index = builder.func().params.len() as u64;
743                        let elem_val = builder.add_param(MirType::uint256());
744                        self.emit_abi_param_validation(
745                            &mut builder,
746                            arg_index,
747                            elem_hir_ty,
748                            abi_param_source,
749                        );
750                        if elem_idx == 0 {
751                            builder.mstore(array_ptr, elem_val);
752                        } else {
753                            let offset_val = builder.imm_u64(elem_idx * 32);
754                            let elem_addr = builder.add(array_ptr, offset_val);
755                            builder.mstore(elem_addr, elem_val);
756                        }
757                    }
758                    self.locals.insert(param_id, array_ptr);
759                } else if decodes_abi_params && self.is_dyn_word_array_memory_param(param) {
760                    // Dynamic array of word elements in memory: the ABI head is
761                    // an offset to `[length][elements...]` in the ABI argument
762                    // blob. Runtime calls read it from calldata after the
763                    // selector; constructors read it from the copied argument
764                    // blob at memory 0x80.
765                    let head = builder.add_param(ty);
766                    let abi_base =
767                        builder.imm_u64(if self.lowering_constructor { 0x80 } else { 4 });
768                    let len_pos = builder.add(abi_base, head);
769                    let len = if self.lowering_constructor {
770                        builder.mload(len_pos)
771                    } else {
772                        builder.calldataload(len_pos)
773                    };
774                    let word = builder.imm_u64(32);
775                    let data_bytes = builder.mul(len, word);
776                    let total_bytes = builder.add(data_bytes, word);
777                    let free_ptr_addr = builder.imm_u64(0x40);
778                    let array_ptr = builder.mload(free_ptr_addr);
779                    let new_free_ptr = builder.add(array_ptr, total_bytes);
780                    let free_ptr_addr = builder.imm_u64(0x40);
781                    builder.mstore(free_ptr_addr, new_free_ptr);
782                    builder.mstore(array_ptr, len);
783                    let dst = builder.add(array_ptr, word);
784                    let src = builder.add(len_pos, word);
785                    if self.lowering_constructor {
786                        self.mcopy(&mut builder, dst, src, data_bytes, None);
787                    } else {
788                        builder.calldatacopy(dst, src, data_bytes);
789                    }
790                    self.locals.insert(param_id, array_ptr);
791                } else if decodes_abi_params
792                    && param.data_location == Some(solar_ast::DataLocation::Memory)
793                    && matches!(
794                        param_ty.peel_refs().kind,
795                        TyKind::Elementary(ElementaryType::Bytes | ElementaryType::String)
796                    )
797                {
798                    // `bytes`/`string` memory parameter: the ABI head word is
799                    // the payload's offset relative to the start of the ABI
800                    // arguments. Runtime calls read it from calldata after the
801                    // selector; constructors read it from the copied argument
802                    // blob at memory 0x80.
803                    let head = builder.add_param(ty);
804                    let abi_base =
805                        builder.imm_u64(if self.lowering_constructor { 0x80 } else { 4 });
806                    let len_pos = builder.add(abi_base, head);
807                    let len = if self.lowering_constructor {
808                        builder.mload(len_pos)
809                    } else {
810                        builder.calldataload(len_pos)
811                    };
812                    let thirty_one = builder.imm_u64(31);
813                    let rounded = builder.add(len, thirty_one);
814                    let mask = builder.not(thirty_one);
815                    let padded = builder.and(rounded, mask);
816                    let word = builder.imm_u64(32);
817                    let total = builder.add(padded, word);
818                    let ptr = self.allocate_memory_dynamic(&mut builder, total);
819                    builder.mstore(ptr, len);
820                    let data_ptr = builder.add(ptr, word);
821                    let src = builder.add(len_pos, word);
822                    if self.lowering_constructor {
823                        self.mcopy(&mut builder, data_ptr, src, len, None);
824                    } else {
825                        builder.calldatacopy(data_ptr, src, len);
826                    }
827                    self.locals.insert(param_id, ptr);
828                } else {
829                    // Non-struct parameters: use normal Arg handling
830                    let arg_index = builder.func().params.len() as u64;
831                    let val = builder.add_param(ty);
832                    if decodes_abi_params {
833                        self.emit_abi_param_validation(
834                            &mut builder,
835                            arg_index,
836                            &param.ty,
837                            abi_param_source,
838                        );
839                    }
840                    self.locals.insert(param_id, val);
841                }
842            }
843
844            for &ret_id in hir_func.returns {
845                let ret_var = self.gcx.hir.variable(ret_id);
846                let ret_ty = self.gcx.type_of_hir_ty(&ret_var.ty);
847                let ty = self.lower_type_from_var(ret_var);
848                builder.add_return(ty);
849                // Allocate memory for return variables so they can be assigned to
850                // within the function body (e.g., `liquidity = 1` in if/else branches)
851                let offset = self.alloc_local_memory(ret_id);
852                let offset_val = self.local_memory_addr(&mut builder, offset);
853                if matches!(ret_ty.peel_refs().kind, TyKind::Struct(_)) {
854                    let struct_size = self.calculate_memory_words_for_ty(ret_ty) * 32;
855                    let struct_ptr = self.allocate_memory(&mut builder, struct_size);
856                    builder.mstore(offset_val, struct_ptr);
857                } else if self.is_fixed_memory_array_type(&ret_var.ty, ret_var.data_location)
858                    && let Some(array_ptr) =
859                        self.allocate_zeroed_fixed_memory_array(&mut builder, &ret_var.ty)
860                {
861                    // A named fixed-array return must point at real zeroed
862                    // memory like a local declaration; a zero pointer aliases
863                    // the scratch space.
864                    builder.mstore(offset_val, array_ptr);
865                } else {
866                    let zero = builder.imm_u256(U256::ZERO);
867                    builder.mstore(offset_val, zero);
868                }
869            }
870
871            if hir_func.kind == hir::FunctionKind::Constructor
872                && let Some(contract_id) = hir_func.contract
873            {
874                self.lower_constructor_prelude(&mut builder, contract_id);
875            }
876
877            if let Some(body) = &hir_func.body {
878                self.lower_block(&mut builder, body);
879            }
880
881            if !builder.func().block(builder.current_block()).is_terminated() {
882                if builder.func().returns.is_empty() {
883                    builder.stop();
884                } else {
885                    // Load each return variable's word (the value for value types,
886                    // a memory pointer for reference types).
887                    let mut items: Vec<(ValueId, Ty<'gcx>)> = Vec::new();
888                    for &ret_id in hir_func.returns {
889                        let ret_var = self.gcx.hir.variable(ret_id);
890                        let ret_val = if let Some(offset) = self.get_local_memory_offset(&ret_id) {
891                            let offset_val = self.local_memory_addr(&mut builder, offset);
892                            builder.mload(offset_val)
893                        } else {
894                            builder.imm_u256(U256::ZERO)
895                        };
896                        items.push((ret_val, self.gcx.type_of_hir_ty(&ret_var.ty)));
897                    }
898                    self.finish_external_or_internal_return(&mut builder, items, uses_external_abi);
899                }
900            }
901        }
902
903        self.lowering_constructor = false;
904        self.lowering_internal_function = false;
905        mir_func.internal_frame_size =
906            self.next_local_memory_offset.saturating_sub(Self::LOCAL_MEMORY_BASE);
907        if uses_external_abi && !self.current_return_tys.iter().any(|&ty| self.abi_is_dynamic(ty)) {
908            mir_func.external_static_return_size =
909                self.current_return_tys.iter().map(|&ty| self.abi_head_size(ty)).sum();
910        }
911
912        *self.module.function_mut(mir_id) = mir_func;
913        mir_id
914    }
915
916    /// Reverts when calldata does not contain the complete ABI head.
917    ///
918    /// `calldataload` returns zero for missing bytes, so this guard must run
919    /// before parameter validation or short calldata can be accepted as a
920    /// canonical zero argument.
921    fn emit_external_calldata_head_size_check(builder: &mut FunctionBuilder<'_>, head_size: u64) {
922        if head_size == 0 {
923            return;
924        }
925        let calldatasize = builder.calldatasize();
926        let selector_size = builder.imm_u64(4);
927        let payload_size = builder.sub(calldatasize, selector_size);
928        let required_size = builder.imm_u64(head_size);
929        let is_short = builder.slt(payload_size, required_size);
930        Self::emit_revert_if(builder, is_short);
931    }
932
933    /// Validates the ABI encoding of a value-type external parameter.
934    ///
935    /// Solc via-ir reverts with empty revert data when the calldata word of a
936    /// value-type parameter is not its canonical encoding, and downstream code
937    /// (including our checked-arithmetic shapes) relies on arguments being
938    /// canonical. We mirror solc's `validator_revert_t_*` semantics:
939    /// - `uintN` (N < 256): high bits must be zero
940    /// - `intN` (N < 256): the word must equal its sign extension
941    /// - `address` / contract types: top 96 bits must be zero
942    /// - `bool`: the word must be 0 or 1
943    /// - `bytesN` (N < 32): low `32 - N` bytes must be zero
944    /// - enums: the value must be less than the member count
945    ///
946    /// Reference and dynamic types are not validated here.
947    ///
948    /// The check reads the raw word with an explicit `calldataload` instead of
949    /// reusing the `Arg` value: optimization passes are allowed to assume that
950    /// `Arg` values of external functions are canonical (this validation is
951    /// what establishes that invariant), so the validator itself must read the
952    /// unvalidated word opaquely or it would be folded away.
953    fn emit_abi_param_validation(
954        &mut self,
955        builder: &mut FunctionBuilder<'_>,
956        arg_index: u64,
957        hir_ty: &hir::Type<'_>,
958        source: AbiParamSource,
959    ) {
960        enum Validator {
961            /// The word must equal itself masked with the given mask.
962            Mask(U256),
963            /// The word must equal `signextend(byte_index, word)`.
964            SignExtend(u64),
965            /// The word must equal `iszero(iszero(word))`.
966            Bool,
967            /// The word must be less than the member count.
968            EnumRange(u64),
969        }
970
971        let mut ty = self.gcx.type_of_hir_ty(hir_ty);
972        if let TyKind::Udvt(underlying, _) = ty.kind {
973            ty = underlying;
974        }
975        let validator = match ty.kind {
976            TyKind::Elementary(elem) => match elem {
977                ElementaryType::UInt(size) => {
978                    let bits = size.bits();
979                    if bits >= 256 {
980                        return;
981                    }
982                    Validator::Mask(U256::MAX >> (256 - usize::from(bits)))
983                }
984                ElementaryType::Int(size) => {
985                    let bits = size.bits();
986                    if bits >= 256 {
987                        return;
988                    }
989                    Validator::SignExtend(u64::from(bits / 8) - 1)
990                }
991                ElementaryType::Address(_) => Validator::Mask(U256::MAX >> 96),
992                ElementaryType::Bool => Validator::Bool,
993                ElementaryType::FixedBytes(size) => {
994                    let bytes = size.bytes();
995                    if bytes >= 32 {
996                        return;
997                    }
998                    Validator::Mask(U256::MAX << (256 - 8 * usize::from(bytes)))
999                }
1000                _ => return,
1001            },
1002            TyKind::Contract(_) => Validator::Mask(U256::MAX >> 96),
1003            TyKind::Enum(enum_id) => {
1004                Validator::EnumRange(self.gcx.hir.enumm(enum_id).variants.len() as u64)
1005            }
1006            _ => return,
1007        };
1008
1009        let word = match source {
1010            AbiParamSource::ExternalCalldata => {
1011                // Runtime ABI encoding: selector (4 bytes) + one head word per parameter.
1012                let offset = builder.imm_u64(4 + arg_index * 32);
1013                builder.calldataload(offset)
1014            }
1015            AbiParamSource::ConstructorMemory => {
1016                // Constructor ABI arguments are copied to memory at 0x80 by the backend.
1017                let offset = builder.imm_u64(0x80 + arg_index * 32);
1018                builder.mload(offset)
1019            }
1020        };
1021        let ok = match validator {
1022            Validator::Mask(mask) => {
1023                let mask = builder.imm_u256(mask);
1024                let canonical = builder.and(word, mask);
1025                builder.eq(word, canonical)
1026            }
1027            Validator::SignExtend(byte_index) => {
1028                let byte_index = builder.imm_u64(byte_index);
1029                let canonical = builder.signextend(byte_index, word);
1030                builder.eq(word, canonical)
1031            }
1032            Validator::Bool => {
1033                let is_zero = builder.iszero(word);
1034                let canonical = builder.iszero(is_zero);
1035                builder.eq(word, canonical)
1036            }
1037            Validator::EnumRange(count) => {
1038                let count = builder.imm_u64(count);
1039                builder.lt(word, count)
1040            }
1041        };
1042        Self::emit_revert_unless(builder, ok);
1043    }
1044
1045    /// Branches to a plain `revert(0, 0)` when `cond` is zero, then continues
1046    /// lowering in the fallthrough block.
1047    fn emit_revert_unless(builder: &mut FunctionBuilder<'_>, cond: ValueId) {
1048        let revert_block = builder.create_block();
1049        let continue_block = builder.create_block();
1050        builder.branch(cond, continue_block, revert_block);
1051
1052        builder.switch_to_block(revert_block);
1053        let zero = builder.imm_u64(0);
1054        builder.revert(zero, zero);
1055
1056        builder.switch_to_block(continue_block);
1057    }
1058
1059    /// Reverts with empty data when `cond` is true, continuing otherwise.
1060    /// Branching directly on the condition avoids an `iszero` polarity flip.
1061    fn emit_revert_if(builder: &mut FunctionBuilder<'_>, cond: ValueId) {
1062        let revert_block = builder.create_block();
1063        let continue_block = builder.create_block();
1064        builder.branch(cond, revert_block, continue_block);
1065
1066        builder.switch_to_block(revert_block);
1067        let zero = builder.imm_u64(0);
1068        builder.revert(zero, zero);
1069
1070        builder.switch_to_block(continue_block);
1071    }
1072
1073    /// Lowers state-variable initializers and base constructors for an explicit constructor.
1074    fn lower_constructor_prelude(
1075        &mut self,
1076        builder: &mut FunctionBuilder<'_>,
1077        contract_id: ContractId,
1078    ) {
1079        let contract = self.gcx.hir.contract(contract_id);
1080
1081        // Solidity runs construction from base to derived. For each contract in that order,
1082        // initialize its state variables, then run its constructor body. The current contract's
1083        // own constructor body is lowered by the caller after this prelude.
1084        let construction_order: Vec<_> = contract
1085            .linearized_bases
1086            .iter()
1087            .enumerate()
1088            .map(|(idx, &base_id)| {
1089                let args = idx.checked_sub(1).and_then(|arg_idx| {
1090                    contract.linearized_bases_args.get(arg_idx).and_then(|m| *m)
1091                });
1092                (base_id, args)
1093            })
1094            .collect();
1095
1096        for (base_id, args) in construction_order.into_iter().rev() {
1097            let base_contract = self.gcx.hir.contract(base_id);
1098            for var_id in base_contract.variables() {
1099                let var = self.gcx.hir.variable(var_id);
1100                if var.is_state_variable()
1101                    && !var.is_constant()
1102                    && let Some(init) = var.initializer
1103                {
1104                    let init_val = self.lower_expr(builder, init);
1105                    if let Some(&offset) = self.immutable_slots.get(&var_id) {
1106                        self.store_immutable_value(builder, offset, init_val);
1107                    } else if let Some(&location) = self.storage_locations.get(&var_id) {
1108                        self.store_storage_location(builder, location, init_val);
1109                    }
1110                }
1111            }
1112
1113            if base_id != contract_id
1114                && let Some(ctor_id) = base_contract.ctor
1115            {
1116                self.lower_base_constructor_call(builder, ctor_id, args);
1117            }
1118        }
1119    }
1120
1121    fn function_selector(&self, func_id: HirFunctionId) -> [u8; 4] {
1122        self.gcx.function_selector(func_id).0
1123    }
1124
1125    pub(super) fn mcopy(
1126        &self,
1127        builder: &mut FunctionBuilder<'_>,
1128        dest: ValueId,
1129        src: ValueId,
1130        len: ValueId,
1131        span: Option<Span>,
1132    ) {
1133        if self.gcx.sess.opts.evm_version.has_mcopy() {
1134            builder.mcopy(dest, src, len);
1135        } else {
1136            let err = self.gcx.dcx().err("codegen requires Cancun-compatible EVM for memory copy");
1137            let err = if let Some(span) = span { err.span(span) } else { err };
1138            err.help("compile with `--evm-version cancun` or newer").emit();
1139        }
1140    }
1141
1142    /// Lowers a type from a variable declaration.
1143    fn lower_type_from_var(&self, var: &hir::Variable<'_>) -> MirType {
1144        self.lower_type_from_ty(self.gcx.type_of_hir_ty(&var.ty))
1145    }
1146
1147    /// Lowers a type-checked Solidity type to MIR's coarse value type.
1148    fn lower_type_from_ty(&self, ty: Ty<'gcx>) -> MirType {
1149        match ty.peel_refs().kind {
1150            TyKind::Elementary(elem) => match elem {
1151                ElementaryType::Bool => MirType::Bool,
1152                ElementaryType::Address(_) => MirType::Address,
1153                ElementaryType::Int(bits) => MirType::Int(bits.bits()),
1154                ElementaryType::UInt(bits) => MirType::UInt(bits.bits()),
1155                ElementaryType::Fixed(_, _) => MirType::Int(256),
1156                ElementaryType::UFixed(_, _) => MirType::UInt(256),
1157                ElementaryType::FixedBytes(n) => MirType::FixedBytes(n.bytes()),
1158                ElementaryType::String | ElementaryType::Bytes => MirType::MemPtr,
1159            },
1160            TyKind::Mapping(_, _) => MirType::StoragePtr,
1161            TyKind::DynArray(_) | TyKind::Array(_, _) | TyKind::Slice(_) => MirType::MemPtr,
1162            TyKind::Fn(_) => MirType::Function,
1163            TyKind::Struct(_) => MirType::MemPtr,
1164            TyKind::Enum(_) => MirType::UInt(8),
1165            TyKind::Contract(_) | TyKind::Super(_) => MirType::Address,
1166            TyKind::StringLiteral(_, _)
1167            | TyKind::IntLiteral(_, _, _)
1168            | TyKind::Tuple(_)
1169            | TyKind::Variadic
1170            | TyKind::Error(_, _)
1171            | TyKind::Event(_, _)
1172            | _ => MirType::uint256(),
1173        }
1174    }
1175
1176    /// Returns the completed module.
1177    #[must_use]
1178    pub fn finish(self) -> Module {
1179        self.module
1180    }
1181
1182    /// Collects variables that are assigned after declaration in a block.
1183    fn collect_assigned_vars_block(&mut self, block: &hir::Block<'_>) {
1184        for stmt in block.stmts {
1185            self.collect_assigned_vars_stmt(stmt);
1186        }
1187    }
1188
1189    /// Collects variables that are assigned after declaration in a statement.
1190    fn collect_assigned_vars_stmt(&mut self, stmt: &hir::Stmt<'_>) {
1191        use hir::StmtKind;
1192        match &stmt.kind {
1193            StmtKind::Expr(expr) => self.collect_assigned_vars_expr(expr),
1194            StmtKind::Block(block) | StmtKind::UncheckedBlock(block) => {
1195                self.collect_assigned_vars_block(block)
1196            }
1197            StmtKind::If(cond, then_stmt, else_stmt) => {
1198                self.collect_assigned_vars_expr(cond);
1199                self.collect_assigned_vars_stmt(then_stmt);
1200                if let Some(else_s) = else_stmt {
1201                    self.collect_assigned_vars_stmt(else_s);
1202                }
1203            }
1204            StmtKind::Loop(block, _) => self.collect_assigned_vars_block(block),
1205            StmtKind::Switch(switch) => {
1206                self.collect_assigned_vars_expr(switch.selector);
1207                for case in switch.cases {
1208                    self.collect_assigned_vars_block(&case.body);
1209                }
1210            }
1211            StmtKind::Return(Some(expr)) | StmtKind::Revert(expr) | StmtKind::Emit(expr) => {
1212                self.collect_assigned_vars_expr(expr)
1213            }
1214            StmtKind::Try(try_stmt) => {
1215                self.collect_assigned_vars_expr(&try_stmt.expr);
1216                for clause in try_stmt.clauses {
1217                    self.collect_assigned_vars_block(&clause.block);
1218                }
1219            }
1220            StmtKind::AssemblyBlock(block) => self.collect_assigned_vars_block(block),
1221            StmtKind::DeclSingle(_)
1222            | StmtKind::DeclMulti(_, _)
1223            | StmtKind::Return(None)
1224            | StmtKind::Continue
1225            | StmtKind::Break
1226            | StmtKind::Placeholder
1227            | StmtKind::Err(_) => {}
1228        }
1229    }
1230
1231    /// Collects variables that are assigned in an expression.
1232    fn collect_assigned_vars_expr(&mut self, expr: &hir::Expr<'_>) {
1233        use hir::ExprKind;
1234        match &expr.kind {
1235            ExprKind::Assign(lhs, _, rhs) => {
1236                // Record assignment targets
1237                self.mark_assigned_var(lhs);
1238                self.collect_assigned_vars_expr(rhs);
1239            }
1240            ExprKind::Binary(lhs, _, rhs) => {
1241                self.collect_assigned_vars_expr(lhs);
1242                self.collect_assigned_vars_expr(rhs);
1243            }
1244            ExprKind::Unary(op, operand) => {
1245                // ++x, x++, --x, x-- are unary ops that mutate the operand
1246                use solar_ast::UnOpKind;
1247                if matches!(
1248                    op.kind,
1249                    UnOpKind::PreInc | UnOpKind::PostInc | UnOpKind::PreDec | UnOpKind::PostDec
1250                ) {
1251                    self.mark_assigned_var(operand);
1252                }
1253                self.collect_assigned_vars_expr(operand);
1254            }
1255            ExprKind::Ternary(cond, true_val, false_val) => {
1256                self.collect_assigned_vars_expr(cond);
1257                self.collect_assigned_vars_expr(true_val);
1258                self.collect_assigned_vars_expr(false_val);
1259            }
1260            ExprKind::Call(callee, args, _) => {
1261                self.collect_assigned_vars_expr(callee);
1262                for arg in args.kind.exprs() {
1263                    self.collect_assigned_vars_expr(arg);
1264                }
1265            }
1266            ExprKind::Index(base, idx) => {
1267                self.collect_assigned_vars_expr(base);
1268                if let Some(i) = idx {
1269                    self.collect_assigned_vars_expr(i);
1270                }
1271            }
1272            ExprKind::Slice(base, start, end) => {
1273                self.collect_assigned_vars_expr(base);
1274                if let Some(s) = start {
1275                    self.collect_assigned_vars_expr(s);
1276                }
1277                if let Some(e) = end {
1278                    self.collect_assigned_vars_expr(e);
1279                }
1280            }
1281            ExprKind::Member(base, _) | ExprKind::YulMember(base, _) => {
1282                self.collect_assigned_vars_expr(base)
1283            }
1284            ExprKind::Array(elems) => {
1285                for elem in elems.iter() {
1286                    self.collect_assigned_vars_expr(elem);
1287                }
1288            }
1289            ExprKind::Tuple(elems) => {
1290                for elem in elems.iter().flatten() {
1291                    self.collect_assigned_vars_expr(elem);
1292                }
1293            }
1294            ExprKind::Payable(inner) | ExprKind::Delete(inner) => {
1295                self.collect_assigned_vars_expr(inner)
1296            }
1297            ExprKind::New(_)
1298            | ExprKind::TypeCall(_)
1299            | ExprKind::Lit(_)
1300            | ExprKind::Ident(_)
1301            | ExprKind::Type(_)
1302            | ExprKind::Err(_) => {}
1303        }
1304    }
1305
1306    /// Marks a variable as being assigned (needs memory storage).
1307    fn mark_assigned_var(&mut self, expr: &hir::Expr<'_>) {
1308        if let Some(var_id) = self.ident_variable(expr) {
1309            self.assigned_vars.insert(var_id);
1310        }
1311    }
1312
1313    /// Returns true if a variable is assigned after declaration.
1314    pub fn is_var_assigned(&self, var_id: &VariableId) -> bool {
1315        self.assigned_vars.contains(var_id)
1316    }
1317
1318    /// Checks if an expression contains an external call.
1319    /// External calls write their return data to shared memory at offset 0,
1320    /// so variables initialized from them must be stored in memory to preserve the value
1321    /// across subsequent calls.
1322    pub fn has_external_call(&self, expr: &hir::Expr<'_>) -> bool {
1323        use hir::ExprKind;
1324        match &expr.kind {
1325            ExprKind::Call(callee, args, _) => {
1326                // Check if this is an external call (method call on a contract)
1327                if self.is_external_call(callee) {
1328                    return true;
1329                }
1330                // Check callee and arguments for nested external calls
1331                if self.has_external_call(callee) {
1332                    return true;
1333                }
1334                for arg in args.kind.exprs() {
1335                    if self.has_external_call(arg) {
1336                        return true;
1337                    }
1338                }
1339                false
1340            }
1341            ExprKind::Member(base, _) | ExprKind::YulMember(base, _) => {
1342                // Member access itself doesn't contain external calls
1343                // but the base might
1344                self.has_external_call(base)
1345            }
1346            ExprKind::Binary(lhs, _, rhs) => {
1347                self.has_external_call(lhs) || self.has_external_call(rhs)
1348            }
1349            ExprKind::Unary(_, operand) => self.has_external_call(operand),
1350            ExprKind::Ternary(cond, true_val, false_val) => {
1351                self.has_external_call(cond)
1352                    || self.has_external_call(true_val)
1353                    || self.has_external_call(false_val)
1354            }
1355            ExprKind::Index(base, idx) => {
1356                self.has_external_call(base) || idx.is_some_and(|i| self.has_external_call(i))
1357            }
1358            ExprKind::Array(elems) => elems.iter().any(|e| self.has_external_call(e)),
1359            ExprKind::Tuple(elems) => {
1360                elems.iter().any(|e| e.is_some_and(|expr| self.has_external_call(expr)))
1361            }
1362            ExprKind::Payable(inner) | ExprKind::Delete(inner) => self.has_external_call(inner),
1363            ExprKind::Slice(base, start, end) => {
1364                self.has_external_call(base)
1365                    || start.is_some_and(|s| self.has_external_call(s))
1366                    || end.is_some_and(|e| self.has_external_call(e))
1367            }
1368            ExprKind::Assign(lhs, _, rhs) => {
1369                self.has_external_call(lhs) || self.has_external_call(rhs)
1370            }
1371            ExprKind::New(_)
1372            | ExprKind::TypeCall(_)
1373            | ExprKind::Lit(_)
1374            | ExprKind::Ident(_)
1375            | ExprKind::Type(_)
1376            | ExprKind::Err(_) => false,
1377        }
1378    }
1379
1380    /// Checks if a call expression is an external call (method on a contract).
1381    fn is_external_call(&self, callee: &hir::Expr<'_>) -> bool {
1382        // External calls are Member expressions where the base is a contract
1383        if let hir::ExprKind::Member(base, _) = &callee.kind
1384            && let Some(var_id) = self.ident_variable(base)
1385        {
1386            let var = self.gcx.hir.variable(var_id);
1387            // Contract type variables are external call targets
1388            if matches!(var.ty.kind, hir::TypeKind::Custom(hir::ItemId::Contract(_))) {
1389                return true;
1390            }
1391        }
1392        false
1393    }
1394}
1395
1396/// Lowers a contract from HIR to MIR.
1397pub fn lower_contract(gcx: Gcx<'_>, contract_id: ContractId) -> Module {
1398    lower_contract_with_bytecodes(gcx, contract_id, &FxHashMap::default())
1399}
1400
1401/// Returns contracts whose creation bytecode is referenced by `contract_id`.
1402pub fn contract_bytecode_dependencies(
1403    gcx: Gcx<'_>,
1404    contract_id: ContractId,
1405) -> FxHashSet<ContractId> {
1406    let mut deps = FxHashSet::default();
1407    BytecodeDependencyCollector { gcx, deps: &mut deps }.collect_contract(contract_id);
1408    deps.remove(&contract_id);
1409    deps
1410}
1411
1412struct BytecodeDependencyCollector<'a, 'gcx> {
1413    gcx: Gcx<'gcx>,
1414    deps: &'a mut FxHashSet<ContractId>,
1415}
1416
1417impl<'a, 'gcx> BytecodeDependencyCollector<'a, 'gcx> {
1418    fn collect_contract(&mut self, contract_id: ContractId) {
1419        let contract = self.gcx.hir.contract(contract_id);
1420
1421        for modifier in contract.linearized_bases_args.iter().flatten() {
1422            let ControlFlow::Continue(()) = self.visit_modifier(modifier);
1423        }
1424
1425        for &base_id in contract.linearized_bases {
1426            let base = self.gcx.hir.contract(base_id);
1427
1428            for var_id in base.variables() {
1429                let ControlFlow::Continue(()) = self.visit_nested_var(var_id);
1430            }
1431
1432            for func_id in base.all_functions() {
1433                let func = self.gcx.hir.function(func_id);
1434
1435                for modifier in func.modifiers {
1436                    let ControlFlow::Continue(()) = self.visit_modifier(modifier);
1437                }
1438
1439                if let Some(body) = func.body {
1440                    for stmt in body.stmts {
1441                        let ControlFlow::Continue(()) = self.visit_stmt(stmt);
1442                    }
1443                }
1444            }
1445        }
1446    }
1447
1448    fn collect_type(&mut self, ty: &hir::Type<'gcx>) {
1449        if let hir::TypeKind::Custom(hir::ItemId::Contract(contract_id)) = &ty.kind {
1450            self.deps.insert(*contract_id);
1451        }
1452    }
1453}
1454
1455impl<'gcx> Visit<'gcx> for BytecodeDependencyCollector<'_, 'gcx> {
1456    type BreakValue = Never;
1457
1458    fn hir(&self) -> &'gcx hir::Hir<'gcx> {
1459        &self.gcx.hir
1460    }
1461
1462    fn visit_expr(&mut self, expr: &'gcx hir::Expr<'gcx>) -> ControlFlow<Self::BreakValue> {
1463        match &expr.kind {
1464            hir::ExprKind::New(ty) => self.collect_type(ty),
1465            hir::ExprKind::Member(base, member)
1466                if matches!(member.name, sym::creationCode | sym::runtimeCode) =>
1467            {
1468                if let hir::ExprKind::TypeCall(ty) = &base.kind {
1469                    self.collect_type(ty);
1470                }
1471            }
1472            _ => {}
1473        }
1474
1475        self.walk_expr(expr)
1476    }
1477}
1478
1479/// Lowers a contract from HIR to MIR with pre-compiled bytecodes available for `new` expressions.
1480pub fn lower_contract_with_bytecodes(
1481    gcx: Gcx<'_>,
1482    contract_id: ContractId,
1483    child_bytecodes: &FxHashMap<ContractId, Vec<u8>>,
1484) -> Module {
1485    let contract = gcx.hir.contract(contract_id);
1486    let mut lowerer = Lowerer::new(gcx, contract.name);
1487
1488    // Register all child contract bytecodes
1489    for (&child_id, bytecode) in child_bytecodes {
1490        lowerer.register_contract_bytecode(child_id, bytecode.clone());
1491    }
1492
1493    lowerer.lower_contract(contract_id);
1494    lowerer.finish()
1495}