1mod 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#[derive(Clone, Copy)]
39pub struct LoopContext {
40 pub break_target: BlockId,
42 pub continue_target: BlockId,
44}
45
46#[derive(Clone, Copy)]
47enum AbiParamSource {
48 ExternalCalldata,
49 ConstructorMemory,
50}
51
52pub struct Lowerer<'gcx> {
54 gcx: Gcx<'gcx>,
56 module: Module,
58 current_contract_id: Option<ContractId>,
60 storage_slots: FxHashMap<VariableId, u64>,
62 storage_locations: FxHashMap<VariableId, StorageLocation>,
64 next_storage_slot: u64,
66 next_storage_offset: u8,
68 immutable_slots: FxHashMap<VariableId, u32>,
70 next_immutable_offset: u32,
72 locals: FxHashMap<VariableId, ValueId>,
75 local_memory_slots: FxHashMap<VariableId, u64>,
78 next_local_memory_offset: u64,
80 contract_bytecodes: FxHashMap<ContractId, (Vec<u8>, usize)>,
83 loop_stack: Vec<LoopContext>,
85 assigned_vars: FxHashSet<VariableId>,
88 storage_ref_locals: FxHashSet<VariableId>,
93 inline_stack: Vec<HirFunctionId>,
95 hir_to_mir_functions: FxHashMap<HirFunctionId, FunctionId>,
97 hir_to_internal_mir_functions: FxHashMap<HirFunctionId, FunctionId>,
100 recursive_functions: FxHashMap<HirFunctionId, bool>,
102 lowering_functions: FxHashSet<HirFunctionId>,
104 lowering_constructor: bool,
106 lowering_internal_function: bool,
108 in_unchecked_block: bool,
110 current_return_tys: Vec<Ty<'gcx>>,
113 pub struct_storage_base_slots: FxHashMap<VariableId, u64>,
115 pub struct_field_offsets: FxHashMap<(hir::StructId, usize), u64>,
117 pub struct_field_memory_offsets: FxHashMap<(hir::StructId, usize), u64>,
119}
120
121impl<'gcx> Lowerer<'gcx> {
122 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 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, 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 pub fn push_loop(&mut self, ctx: LoopContext) {
176 self.loop_stack.push(ctx);
177 }
178
179 pub fn pop_loop(&mut self) {
181 self.loop_stack.pop();
182 }
183
184 pub fn current_loop(&self) -> Option<&LoopContext> {
186 self.loop_stack.last()
187 }
188
189 const MAX_INLINE_DEPTH: usize = 32;
191 const LOCAL_MEMORY_BASE: u64 = 0x80;
193 fn try_enter_inline(&mut self, func_id: HirFunctionId) -> bool {
196 if self.inline_stack.contains(&func_id) {
198 return false;
199 }
200 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 fn exit_inline(&mut self) {
210 self.inline_stack.pop();
211 }
212
213 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; self.local_memory_slots.insert(var_id, offset);
219 offset
220 }
221
222 pub fn get_local_memory_offset(&self, var_id: &VariableId) -> Option<u64> {
224 self.local_memory_slots.get(var_id).copied()
225 }
226
227 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 pub fn immutable_scratch_addr(offset: u32) -> u64 {
242 IMMUTABLE_SCRATCH_BASE + u64::from(offset)
243 }
244
245 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 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 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 pub fn get_contract_bytecode(&self, contract_id: ContractId) -> Option<&(Vec<u8>, usize)> {
279 self.contract_bytecodes.get(&contract_id)
280 }
281
282 pub fn lower_contract(&mut self, contract_id: ContractId) {
284 let contract = self.gcx.hir.contract(contract_id);
285
286 self.current_contract_id = Some(contract_id);
288
289 if contract.kind == hir::ContractKind::Interface {
291 self.module.is_interface = true;
292 }
293
294 self.allocate_storage(contract_id);
295
296 let functions = self.collect_inherited_functions(contract_id);
300
301 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 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 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 match func.kind {
339 hir::FunctionKind::Constructor => {
340 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 if base_id != contract_id && func.visibility == hir::Visibility::Private {
363 continue;
364 }
365
366 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 functions.push(func_id);
382 }
383 }
384 }
385 }
386 }
387
388 functions
389 }
390
391 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 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 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 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 if self.storage_slots.contains_key(&var_id) {
459 continue;
460 }
461
462 let var = self.gcx.hir.variable(var_id);
463 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 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 fn fixed_word_array_param_len(&self, param: &hir::Variable<'_>) -> Option<u64> {
507 let TyKind::Array(elem, len) = self.gcx.type_of_hir_ty(¶m.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 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(¶m.ty).peel_refs().kind {
520 TyKind::DynArray(elem) => self.abi_is_word_element(elem),
521 _ => false,
522 }
523 }
524
525 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 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 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 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 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 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(¶m.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 ¶m_id in hir_func.parameters {
683 let param = self.gcx.hir.variable(param_id);
684 let param_ty = self.gcx.type_of_hir_ty(¶m.ty);
685 let ty = self.lower_type_from_var(param);
686
687 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 let strukt = self.gcx.hir.strukt(struct_id);
697 let field_ids = strukt.fields;
698 let num_fields = field_ids.len();
699
700 let struct_size = (num_fields as u64) * 32;
702 let struct_ptr = self.allocate_memory(&mut builder, struct_size);
703
704 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 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 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 let array_ptr = self.allocate_memory(&mut builder, len * 32);
737 let elem_hir_ty = match ¶m.ty.kind {
738 hir::TypeKind::Array(array) => &array.element,
739 _ => ¶m.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 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 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 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 ¶m.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 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 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 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 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 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 Mask(U256),
963 SignExtend(u64),
965 Bool,
967 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 let offset = builder.imm_u64(4 + arg_index * 32);
1013 builder.calldataload(offset)
1014 }
1015 AbiParamSource::ConstructorMemory => {
1016 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 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 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 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 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 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 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 #[must_use]
1178 pub fn finish(self) -> Module {
1179 self.module
1180 }
1181
1182 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 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 fn collect_assigned_vars_expr(&mut self, expr: &hir::Expr<'_>) {
1233 use hir::ExprKind;
1234 match &expr.kind {
1235 ExprKind::Assign(lhs, _, rhs) => {
1236 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 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 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 pub fn is_var_assigned(&self, var_id: &VariableId) -> bool {
1315 self.assigned_vars.contains(var_id)
1316 }
1317
1318 pub fn has_external_call(&self, expr: &hir::Expr<'_>) -> bool {
1323 use hir::ExprKind;
1324 match &expr.kind {
1325 ExprKind::Call(callee, args, _) => {
1326 if self.is_external_call(callee) {
1328 return true;
1329 }
1330 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 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 fn is_external_call(&self, callee: &hir::Expr<'_>) -> bool {
1382 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 if matches!(var.ty.kind, hir::TypeKind::Custom(hir::ItemId::Contract(_))) {
1389 return true;
1390 }
1391 }
1392 false
1393 }
1394}
1395
1396pub fn lower_contract(gcx: Gcx<'_>, contract_id: ContractId) -> Module {
1398 lower_contract_with_bytecodes(gcx, contract_id, &FxHashMap::default())
1399}
1400
1401pub 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
1479pub 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 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}