1use alloy_primitives::U256;
2use huff_neo_utils::bytecode::{BytecodeSegments, JumpPlaceholderData, PushOpcode};
3use huff_neo_utils::bytes_util::str_to_bytes32;
4use huff_neo_utils::prelude::*;
5use huff_neo_utils::scope::ScopeManager;
6use std::collections::BTreeMap;
7
8use crate::Codegen;
9use crate::irgen::arg_calls::bubble_arg_call;
10use crate::irgen::builtin_function;
11use crate::irgen::constants::{constant_gen, lookup_constant};
12
13type StatementGenResult = (BytecodeSegments, Vec<Option<(usize, usize)>>);
15
16pub(crate) fn evaluate_expression_with_context<'a>(
18 expr: &Expression,
19 contract: &Contract,
20 scope_mgr: &ScopeManager<'a>,
21) -> Result<[u8; 32], CodegenError> {
22 match expr {
23 Expression::ArgCall { name, span: arg_span, .. } => {
24 let mis = scope_mgr.invocation_vec();
26 for (_, parent_mi) in mis.iter().rev() {
27 if let Some(parent_macro) = contract.find_macro_by_name(&parent_mi.macro_name)
28 && let Some(param_idx) = parent_macro.parameters.iter().position(|p| p.name.as_deref() == Some(name))
29 && let Some(arg_value) = parent_mi.args.get(param_idx)
30 {
31 match arg_value {
33 MacroArg::HexLiteral(lit) => {
34 return Ok(*lit);
36 }
37 MacroArg::Ident(ident_name) => {
38 if let Ok(const_def) = lookup_constant(ident_name, contract, arg_span) {
40 match &const_def.value {
41 ConstVal::Bytes(bytes) => {
42 use huff_neo_utils::bytes_util::str_to_bytes32;
43 return Ok(str_to_bytes32(&bytes.as_str()));
44 }
45 ConstVal::String(_s) => {
46 return Err(CodegenError {
47 kind: CodegenErrorKind::StringConstantNotBytes(format!(
48 "String constant cannot be used in expressions. Use __BYTES([{}]) to convert it to bytes.",
49 ident_name
50 )),
51 span: arg_span.clone_box(),
52 token: None,
53 });
54 }
55 ConstVal::Expression(const_expr) => {
56 return contract.evaluate_constant_expression(const_expr);
57 }
58 _ => {
59 return Err(CodegenError {
60 kind: CodegenErrorKind::InvalidConstantExpression,
61 span: arg_span.clone_box(),
62 token: None,
63 });
64 }
65 }
66 } else {
67 return Err(CodegenError {
69 kind: CodegenErrorKind::InvalidMacroArgumentType(format!(
70 "Argument '{}' resolved to identifier '{}' which cannot be used in expression",
71 name, ident_name
72 )),
73 span: arg_span.clone_box(),
74 token: None,
75 });
76 }
77 }
78 MacroArg::BuiltinFunctionCall(bf) => {
79 let push_value = Codegen::gen_builtin_bytecode(contract, bf, bf.span.clone())?;
81 return Ok(push_value.value.0);
82 }
83 MacroArg::ArgCall(nested_arg) => {
84 return evaluate_expression_with_context(
86 &Expression::ArgCall {
87 macro_name: String::new(),
88 name: nested_arg.name.clone(),
89 span: nested_arg.span.clone(),
90 },
91 contract,
92 scope_mgr,
93 );
94 }
95 _ => {
96 return Err(CodegenError {
97 kind: CodegenErrorKind::InvalidMacroArgumentType(format!(
98 "Argument '{}' cannot be used in expression (unsupported type)",
99 name
100 )),
101 span: arg_span.clone_box(),
102 token: None,
103 });
104 }
105 }
106 }
107 }
108
109 Err(CodegenError { kind: CodegenErrorKind::MissingArgumentDefinition(name.clone()), span: arg_span.clone_box(), token: None })
111 }
112
113 Expression::Binary { left, op, right, span } => {
114 let left_val = evaluate_expression_with_context(left, contract, scope_mgr)?;
116 let right_val = evaluate_expression_with_context(right, contract, scope_mgr)?;
117
118 let l = U256::from_be_bytes(left_val);
120 let r = U256::from_be_bytes(right_val);
121
122 let result = match op {
124 BinaryOp::Add => l.wrapping_add(r),
125 BinaryOp::Sub => l.wrapping_sub(r),
126 BinaryOp::Mul => l.wrapping_mul(r),
127 BinaryOp::Div => {
128 if r.is_zero() {
129 return Err(CodegenError { kind: CodegenErrorKind::DivisionByZero, span: span.clone_box(), token: None });
130 }
131 l / r
132 }
133 BinaryOp::Mod => {
134 if r.is_zero() {
135 return Err(CodegenError { kind: CodegenErrorKind::DivisionByZero, span: span.clone_box(), token: None });
136 }
137 l % r
138 }
139 BinaryOp::Eq => {
140 if l == r {
141 U256::from(1u64)
142 } else {
143 U256::ZERO
144 }
145 }
146 BinaryOp::NotEq => {
147 if l != r {
148 U256::from(1u64)
149 } else {
150 U256::ZERO
151 }
152 }
153 BinaryOp::Lt => {
154 if l < r {
155 U256::from(1u64)
156 } else {
157 U256::ZERO
158 }
159 }
160 BinaryOp::Gt => {
161 if l > r {
162 U256::from(1u64)
163 } else {
164 U256::ZERO
165 }
166 }
167 BinaryOp::LtEq => {
168 if l <= r {
169 U256::from(1u64)
170 } else {
171 U256::ZERO
172 }
173 }
174 BinaryOp::GtEq => {
175 if l >= r {
176 U256::from(1u64)
177 } else {
178 U256::ZERO
179 }
180 }
181 };
182
183 Ok(result.to_be_bytes())
184 }
185
186 Expression::Unary { op, expr: inner_expr, .. } => {
187 let val = evaluate_expression_with_context(inner_expr, contract, scope_mgr)?;
189 let v = U256::from_be_bytes(val);
190
191 let result = match op {
193 UnaryOp::Neg => U256::ZERO.wrapping_sub(v),
194 UnaryOp::Not => {
195 if v.is_zero() {
196 U256::from(1u64)
197 } else {
198 U256::ZERO
199 }
200 }
201 };
202
203 Ok(result.to_be_bytes())
204 }
205
206 Expression::Grouped { expr: inner_expr, .. } => {
207 evaluate_expression_with_context(inner_expr, contract, scope_mgr)
209 }
210
211 _ => contract.evaluate_constant_expression(expr),
213 }
214}
215
216#[allow(clippy::too_many_arguments)]
219pub fn statement_gen<'a>(
220 evm_version: &EVMVersion,
221 s: &Statement,
222 contract: &'a Contract,
223 macro_def: &'a MacroDefinition,
224 scope_mgr: &mut ScopeManager<'a>,
225 offset: &mut usize,
226 jump_table: &mut JumpTable,
227 label_indices: &mut LabelIndices,
228 table_instances: &mut Jumps,
229 utilized_tables: &mut Vec<TableDefinition>,
230 embedded_tables: &mut BTreeMap<String, usize>,
231 starting_offset: usize,
232 relax_jumps: bool,
233) -> Result<StatementGenResult, CodegenError> {
234 let mut bytes = BytecodeSegments::new();
235 let mut spans = vec![];
236
237 tracing::debug!(target: "codegen", "Got Statement: {}", s.ty);
238
239 match &s.ty {
240 StatementType::MacroInvocation(mi) => {
241 let ir_macro = if let Some(m) = contract.find_macro_by_name(&mi.macro_name) {
243 m
244 } else {
245 tracing::error!(
246 target: "codegen",
247 "MISSING MACRO INVOCATION \"{}\"",
248 mi.macro_name
249 );
250 return Err(CodegenError {
251 kind: CodegenErrorKind::InvalidMacroInvocation(mi.macro_name.clone()),
252 span: mi.span.clone_box(),
253 token: None,
254 });
255 };
256
257 tracing::info!(target: "codegen", "FOUND INNER MACRO: {}", ir_macro.name);
258
259 if ir_macro.parameters.len() != mi.args.len() {
260 tracing::error!(target: "codegen", "MISMATCHED ARGUMENT COUNT FOR {}: {} != {}", ir_macro.name, ir_macro.parameters.len(), mi.args.len());
261 return Err(CodegenError {
262 kind: CodegenErrorKind::InvalidArgumentCount(ir_macro.name.clone(), ir_macro.parameters.len(), mi.args.len()),
263 span: mi.span.clone_box(),
264 token: None,
265 });
266 }
267
268 if ir_macro.test {
270 tracing::error!(target: "codegen", "Tests may not be invoked: {}", ir_macro.name);
271 return Err(CodegenError {
272 kind: CodegenErrorKind::TestInvocation(ir_macro.name.clone()),
273 span: ir_macro.span.clone_box(),
274 token: None,
275 });
276 }
277
278 if scope_mgr.macro_stack().iter().any(|m| m.name == ir_macro.name) {
281 tracing::error!(
282 target: "codegen",
283 "CIRCULAR RECURSION DETECTED: Macro \"{}\" is already in the call stack",
284 ir_macro.name
285 );
286 return Err(CodegenError {
287 kind: CodegenErrorKind::CircularMacroInvocation(ir_macro.name.clone()),
288 span: mi.span.clone_box(),
289 token: None,
290 });
291 }
292
293 const MAX_RECURSION_DEPTH: usize = 100;
295 if scope_mgr.depth() >= MAX_RECURSION_DEPTH {
296 tracing::error!(
297 target: "codegen",
298 "Maximum recursion depth of {} exceeded",
299 MAX_RECURSION_DEPTH
300 );
301 return Err(CodegenError {
302 kind: CodegenErrorKind::CircularMacroInvocation(ir_macro.name.clone()),
303 span: mi.span.clone_box(),
304 token: None,
305 });
306 }
307
308 if ir_macro.outlined {
312 let stack_swaps = (0..ir_macro.takes).rev().map(|i| format!("{:02x}", 0x90 + i)).collect::<Vec<_>>();
315
316 let scope_id = huff_neo_utils::scope::ScopeId::new(vec![], 0);
319 jump_table.insert(
320 *offset + stack_swaps.len() + 3, vec![Jump { label: format!("goto_{}", ir_macro.name), bytecode_index: 0, span: s.span.clone(), scope_id }],
322 );
323
324 let span_info = if !s.span.0.is_empty() {
327 s.span.0.first().and_then(|sp| {
328 if sp.start != 0 || sp.end != 0 {
329 contract.map_original_span_to_flattened(sp)
331 } else {
332 None
333 }
334 })
335 } else {
336 None
337 };
338
339 bytes.push_with_offset(
342 *offset,
343 Bytes::Raw(format!("{}{:04x}{}", Opcode::Push2, *offset + stack_swaps.len() + 7, stack_swaps.join(""))),
344 );
345 spans.push(span_info);
346 bytes.push_with_offset(
348 *offset + stack_swaps.len() + 3, Bytes::JumpPlaceholder(JumpPlaceholderData::new(
350 format!("goto_{}", ir_macro.name),
351 PushOpcode::Push2,
352 format!("{}{}", Opcode::Jump, Opcode::Jumpdest),
353 )),
354 );
355 spans.push(span_info);
356 *offset += stack_swaps.len() + 8;
358 } else {
359 let mut resolved_args = Vec::new();
362 for arg in &mi.args {
363 match arg {
364 MacroArg::ArgCall(arg_call) => {
365 let mut resolved = None;
369
370 let current_has_param = macro_def.parameters.iter().any(|p| p.name.as_deref() == Some(&arg_call.name));
372
373 if !current_has_param {
375 let mut passed_to_current = false;
378
379 if let Some(current_mi) = scope_mgr.current_invocation() {
380 fn contains_arg_call(args: &[MacroArg], name: &str) -> bool {
382 args.iter().any(|arg| match arg {
383 MacroArg::ArgCall(ac) => ac.name == name,
384 MacroArg::MacroCall(mc) => contains_arg_call(&mc.args, name),
385 _ => false,
386 })
387 }
388
389 passed_to_current = contains_arg_call(¤t_mi.args, &arg_call.name);
390 }
391
392 if !passed_to_current {
394 return Err(CodegenError {
395 kind: CodegenErrorKind::MissingArgumentDefinition(arg_call.name.clone()),
396 span: mi.span.clone_box(),
397 token: None,
398 });
399 }
400 }
401
402 let mis = scope_mgr.invocation_vec();
404 for (_, parent_mi) in mis.iter().rev() {
405 if let Some(parent_macro) = contract.find_macro_by_name(&parent_mi.macro_name)
406 && let Some(param_idx) =
407 parent_macro.parameters.iter().position(|p| p.name.as_deref() == Some(&arg_call.name))
408 && let Some(arg_value) = parent_mi.args.get(param_idx)
409 {
410 resolved = Some(arg_value.clone());
411 break;
412 }
413 }
414
415 resolved_args.push(resolved.unwrap_or_else(|| arg.clone()));
417 }
418 MacroArg::ArgCallMacroInvocation(arg_name, invoc_args) => {
419 let mis = scope_mgr.invocation_vec();
421 let mut resolved_macro_name = None;
422
423 for (_, parent_mi) in mis.iter().rev() {
425 if let Some(parent_macro) = contract.find_macro_by_name(&parent_mi.macro_name)
426 && let Some(param_idx) =
427 parent_macro.parameters.iter().position(|p| p.name.as_deref() == Some(arg_name))
428 {
429 if let Some(arg_value) = parent_mi.args.get(param_idx) {
431 match arg_value {
432 MacroArg::Ident(macro_name) => {
433 resolved_macro_name = Some(macro_name.clone());
435 break;
436 }
437 MacroArg::MacroCall(macro_call) => {
438 resolved_macro_name = Some(macro_call.macro_name.clone());
440 break;
441 }
442 _ => {
443 continue;
445 }
446 }
447 }
448 }
449 }
450
451 if let Some(macro_name) = resolved_macro_name {
453 resolved_args.push(MacroArg::MacroCall(MacroInvocation {
454 macro_name,
455 args: invoc_args.clone(),
456 span: AstSpan(vec![]),
457 }));
458 } else {
459 resolved_args.push(arg.clone());
461 }
462 }
463 MacroArg::MacroCall(macro_call) => {
464 fn resolve_macro_call_args<'a>(
466 macro_call: &MacroInvocation,
467 scope_mgr: &ScopeManager<'a>,
468 contract: &Contract,
469 macro_def: &MacroDefinition,
470 ) -> Result<Vec<MacroArg>, CodegenError> {
471 let mis = scope_mgr.invocation_vec();
472 let mut resolved_args = Vec::new();
473 for arg in ¯o_call.args {
474 match arg {
475 MacroArg::ArgCall(arg_call) => {
476 let current_has_param =
478 macro_def.parameters.iter().any(|p| p.name.as_deref() == Some(&arg_call.name));
479
480 if !current_has_param {
482 let mut passed_to_current = false;
485
486 if let Some((_, current_mi)) = mis.last() {
487 fn contains_arg_call(args: &[MacroArg], name: &str) -> bool {
489 args.iter().any(|arg| match arg {
490 MacroArg::ArgCall(ac) => ac.name == name,
491 MacroArg::MacroCall(mc) => contains_arg_call(&mc.args, name),
492 _ => false,
493 })
494 }
495
496 passed_to_current = contains_arg_call(¤t_mi.args, &arg_call.name);
497 }
498
499 if !passed_to_current {
501 return Err(CodegenError {
502 kind: CodegenErrorKind::MissingArgumentDefinition(arg_call.name.clone()),
503 span: macro_call.span.clone_box(),
504 token: None,
505 });
506 }
507 }
508
509 let mut resolved = None;
511 for (_, parent_mi) in mis.iter().rev() {
512 if let Some(parent_macro) = contract.find_macro_by_name(&parent_mi.macro_name)
513 && let Some(param_idx) = parent_macro
514 .parameters
515 .iter()
516 .position(|p| p.name.as_deref() == Some(&arg_call.name))
517 && let Some(arg_value) = parent_mi.args.get(param_idx)
518 {
519 resolved = Some(arg_value.clone());
520 break;
521 }
522 }
523 resolved_args.push(resolved.unwrap_or_else(|| arg.clone()));
524 }
525 MacroArg::MacroCall(nested_macro_call) => {
526 let nested_resolved_args =
528 resolve_macro_call_args(nested_macro_call, scope_mgr, contract, macro_def)?;
529 resolved_args.push(MacroArg::MacroCall(MacroInvocation {
530 macro_name: nested_macro_call.macro_name.clone(),
531 args: nested_resolved_args,
532 span: nested_macro_call.span.clone(),
533 }));
534 }
535 _ => resolved_args.push(arg.clone()),
536 }
537 }
538 Ok(resolved_args)
539 }
540
541 let resolved_macro_args = resolve_macro_call_args(macro_call, scope_mgr, contract, macro_def)?;
542 resolved_args.push(MacroArg::MacroCall(MacroInvocation {
543 macro_name: macro_call.macro_name.clone(),
544 args: resolved_macro_args,
545 span: macro_call.span.clone(),
546 }));
547 }
548 _ => resolved_args.push(arg.clone()),
549 }
550 }
551
552 let resolved_mi = MacroInvocation { macro_name: mi.macro_name.clone(), args: resolved_args, span: mi.span.clone() };
554
555 scope_mgr.push_macro_with_invocation(ir_macro, *offset, resolved_mi);
558
559 let mut res: BytecodeRes =
560 match Codegen::macro_to_bytecode(evm_version, ir_macro, contract, scope_mgr, *offset, relax_jumps) {
561 Ok(r) => r,
562 Err(e) => {
563 tracing::error!(
564 target: "codegen",
565 "FAILED TO RECURSE INTO MACRO \"{}\"",
566 ir_macro.name
567 );
568 scope_mgr.pop_macro();
569 return Err(e);
570 }
571 };
572
573 tracing::debug!(target: "codegen", "Unmatched jumps: {:?}", res.unmatched_jumps.iter().map(|uj| uj.label.clone()).collect::<Vec<String>>());
578 for j in res.unmatched_jumps.iter_mut() {
579 let new_index = j.bytecode_index;
580 j.bytecode_index = 0;
581 let mut new_jumps = if let Some(jumps) = jump_table.get(&new_index) { jumps.clone() } else { vec![] };
582 new_jumps.push(j.clone());
583 jump_table.insert(new_index, new_jumps);
584 }
585 tracing::debug!(target: "codegen", "Extending table_instances with {} items from macro {}", res.table_instances.len(), ir_macro.name);
586 table_instances.extend(res.table_instances);
587 label_indices.extend(res.label_indices);
588 let res_unique_tables =
590 res.utilized_tables.iter().filter(|t| !utilized_tables.contains(t)).cloned().collect::<Vec<TableDefinition>>();
591 utilized_tables.extend(res_unique_tables);
592
593 *offset += res.bytes.iter().map(|seg| seg.bytes.len()).sum::<usize>();
595 bytes.extend(res.bytes);
597 spans.extend(res.spans)
599 }
600 }
601 StatementType::ImmediateOpcode { opcode, operands } => {
602 let mut values = Vec::with_capacity(operands.len());
604 for operand in operands {
605 values.push(U256::from_be_bytes(evaluate_expression_with_context(operand, contract, scope_mgr)?));
606 }
607 let immediate = opcode.encode_immediate(&values).map_err(|msg| CodegenError {
608 kind: CodegenErrorKind::InvalidOpcodeOperand(msg),
609 span: s.span.clone_box(),
610 token: None,
611 })?;
612 let code = format!("{opcode}{immediate}");
613 tracing::info!(target: "codegen", "IMMEDIATE OPCODE: {:?} {:?} => {}", opcode, values, code);
614
615 bytes.push_with_offset(*offset, Bytes::Raw(code.clone()));
616 let span_info = s
617 .span
618 .0
619 .first()
620 .and_then(|sp| if sp.start != 0 || sp.end != 0 { contract.map_original_span_to_flattened(sp) } else { None });
621 spans.push(span_info);
622 *offset += code.len() / 2;
623 }
624 StatementType::Label(label) => {
625 tracing::info!(target: "codegen", "RECURSE BYTECODE GOT LABEL: {:?}", label.name);
627
628 let scope_id = scope_mgr.current_scope();
631
632 if let Err(err_msg) = label_indices.insert(label.name.clone(), *offset, &scope_id) {
634 tracing::error!(target: "codegen", "DUPLICATE LABEL: {}", err_msg);
635 return Err(CodegenError {
636 kind: CodegenErrorKind::DuplicateLabelInScope(label.name.clone()),
637 span: s.span.clone_box(),
638 token: None,
639 });
640 }
641
642 bytes.push_with_offset(*offset, Bytes::Raw(Opcode::Jumpdest.to_string()));
643 let span_info = if !s.span.0.is_empty() {
645 s.span.0.first().and_then(|sp| {
646 if sp.start != 0 || sp.end != 0 {
647 contract.map_original_span_to_flattened(sp)
649 } else {
650 None
651 }
652 })
653 } else {
654 None
655 };
656 spans.push(span_info);
657 *offset += 1;
658 }
659 StatementType::LabelCall(label) => {
660 tracing::info!(target: "codegen", "RECURSE BYTECODE GOT LABEL CALL: {}", label);
663
664 let scope_id = scope_mgr.current_scope();
666
667 jump_table.insert(*offset, vec![Jump { label: label.to_string(), bytecode_index: 0, span: s.span.clone(), scope_id }]);
668 bytes.push_with_offset(
669 *offset,
670 Bytes::JumpPlaceholder(JumpPlaceholderData::new(label.to_string(), PushOpcode::Push2, String::new())),
671 );
672 let span_info = if !s.span.0.is_empty() {
674 s.span.0.first().and_then(|sp| {
675 if sp.start != 0 || sp.end != 0 {
676 contract.map_original_span_to_flattened(sp)
678 } else {
679 None
680 }
681 })
682 } else {
683 None
684 };
685 spans.push(span_info);
686 *offset += 3;
687 }
688 StatementType::BuiltinFunctionCall(bf) => {
689 let bytes_before = bytes.len();
690 builtin_function::builtin_function_gen(
691 evm_version,
692 contract,
693 macro_def,
694 scope_mgr,
695 offset,
696 table_instances,
697 utilized_tables,
698 embedded_tables,
699 starting_offset,
700 &mut bytes,
701 bf,
702 relax_jumps,
703 )?;
704 let bytes_added = bytes.len() - bytes_before;
706 let span_info = if !s.span.0.is_empty() {
707 s.span.0.first().and_then(|sp| {
708 if sp.start != 0 || sp.end != 0 {
709 contract.map_original_span_to_flattened(sp)
711 } else {
712 None
713 }
714 })
715 } else {
716 None
717 };
718 for _ in 0..bytes_added {
719 spans.push(span_info);
720 }
721 }
722 StatementType::ArgMacroInvocation(_parent_macro_name, arg_name, args) => {
723 tracing::info!(target: "codegen", "Processing ArgMacroInvocation: <{}>()", arg_name);
726
727 fn resolve_argument<'a>(arg_name: &str, scope_mgr: &ScopeManager<'a>, contract: &Contract) -> Result<String, CodegenErrorKind> {
729 let mis = scope_mgr.invocation_vec();
730 for (_, mi) in mis.iter().rev() {
732 if let Some(invoked_macro) = contract.find_macro_by_name(&mi.macro_name) {
734 if let Some(param_index) = invoked_macro.parameters.iter().position(|p| p.name.as_deref() == Some(arg_name)) {
736 if let Some(arg_value) = mi.args.get(param_index) {
738 match arg_value {
739 MacroArg::Ident(macro_name) => {
740 return Ok(macro_name.clone());
742 }
743 MacroArg::ArgCall(arg_call) => {
744 return resolve_argument(&arg_call.name, scope_mgr, contract);
746 }
747 MacroArg::ArgCallMacroInvocation(inner_arg_name, _) => {
748 return resolve_argument(inner_arg_name, scope_mgr, contract);
750 }
751 MacroArg::Noop => {
752 return Err(CodegenErrorKind::InvalidMacroArgumentType(format!(
754 "Cannot invoke __NOOP as a macro in argument '{}'",
755 arg_name
756 )));
757 }
758 _ => {
759 continue;
761 }
762 }
763 }
764 }
765 }
766 }
767 Err(CodegenErrorKind::MissingArgumentDefinition(arg_name.to_string()))
768 }
769
770 let macro_name = match resolve_argument(arg_name, scope_mgr, contract) {
772 Ok(name) => {
773 tracing::info!(target: "codegen", "Resolved <{}> to macro: {}", arg_name, name);
774 name
775 }
776 Err(error_kind) => {
777 tracing::error!(target: "codegen", "Failed to resolve argument '{}': {:?}", arg_name, error_kind);
778 return Err(CodegenError { kind: error_kind, span: s.span.clone_box(), token: None });
779 }
780 };
781
782 let resolved_invocation = MacroInvocation { macro_name, args: args.clone(), span: s.span.clone() };
784
785 let macro_invocation_statement =
787 Statement { ty: StatementType::MacroInvocation(resolved_invocation.clone()), span: s.span.clone() };
788
789 return statement_gen(
791 evm_version,
792 ¯o_invocation_statement,
793 contract,
794 macro_def,
795 scope_mgr,
796 offset,
797 jump_table,
798 label_indices,
799 table_instances,
800 utilized_tables,
801 embedded_tables,
802 starting_offset,
803 relax_jumps,
804 );
805 }
806 StatementType::IfStatement { condition, then_branch, else_if_branches, else_branch } => {
807 let condition_val = evaluate_expression_with_context(condition, contract, scope_mgr)?;
809 let condition_true = !U256::from_be_bytes(condition_val).is_zero();
810
811 let selected_branch = if condition_true {
813 then_branch.as_slice()
814 } else {
815 let mut matched_branch = None;
817 for (else_if_condition, else_if_body) in else_if_branches {
818 let else_if_val = evaluate_expression_with_context(else_if_condition, contract, scope_mgr)?;
819 let else_if_true = !U256::from_be_bytes(else_if_val).is_zero();
820
821 if else_if_true {
822 matched_branch = Some(else_if_body.as_slice());
823 break;
824 }
825 }
826
827 matched_branch.or(else_branch.as_ref().map(|b| b.as_slice())).unwrap_or(&[])
829 };
830
831 let branch_irbytes = MacroDefinition::to_irbytes(evm_version, selected_branch);
833
834 for ir_byte in branch_irbytes {
836 match &ir_byte.ty {
837 IRByteType::Statement(stmt) => {
838 let (stmt_bytes, mut stmt_spans) = statement_gen(
839 evm_version,
840 stmt,
841 contract,
842 macro_def,
843 scope_mgr,
844 offset,
845 jump_table,
846 label_indices,
847 table_instances,
848 utilized_tables,
849 embedded_tables,
850 starting_offset,
851 relax_jumps,
852 )?;
853 bytes.extend(stmt_bytes);
854 spans.append(&mut stmt_spans);
855 }
856 IRByteType::Bytes(b) => {
857 *offset += b.len();
858 bytes.push_with_offset(starting_offset, b.to_owned());
859 let span_info = if !ir_byte.span.0.is_empty() {
860 ir_byte.span.0.first().and_then(|sp| {
861 if sp.start != 0 || sp.end != 0 { contract.map_original_span_to_flattened(sp) } else { None }
862 })
863 } else {
864 None
865 };
866 spans.push(span_info);
867 }
868 IRByteType::Constant(name) => {
869 if let Some(push_value) = constant_gen(name, contract, ir_byte.span)? {
871 let push_bytes = push_value.to_hex_with_opcode(evm_version);
872 *offset += push_bytes.len() / 2;
873 bytes.push_with_offset(starting_offset, Bytes::Raw(push_bytes));
874 let span_info = if !ir_byte.span.0.is_empty() {
875 ir_byte.span.0.first().and_then(|sp| {
876 if sp.start != 0 || sp.end != 0 { contract.map_original_span_to_flattened(sp) } else { None }
877 })
878 } else {
879 None
880 };
881 spans.push(span_info);
882 }
883 }
884 IRByteType::ArgCall(parent_macro_name, arg_name) => {
885 let bytes_before = bytes.len();
887 bubble_arg_call(
888 evm_version,
889 parent_macro_name.to_owned(),
890 arg_name.to_owned(),
891 &mut bytes,
892 macro_def,
893 contract,
894 scope_mgr,
895 offset,
896 jump_table,
897 table_instances,
898 utilized_tables,
899 ir_byte.span,
900 relax_jumps,
901 )?;
902 let bytes_added = bytes.len() - bytes_before;
903 let span_info = if !ir_byte.span.0.is_empty() {
904 ir_byte.span.0.first().and_then(|sp| {
905 if sp.start != 0 || sp.end != 0 { contract.map_original_span_to_flattened(sp) } else { None }
906 })
907 } else {
908 None
909 };
910 for _ in 0..bytes_added {
911 spans.push(span_info);
912 }
913 }
914 }
915 }
916 }
917 StatementType::ForLoop { variable, start, end, step, body } => {
918 let start_val = evaluate_expression_with_context(start, contract, scope_mgr)?;
920 let end_val = evaluate_expression_with_context(end, contract, scope_mgr)?;
921 let step_val = if let Some(step_expr) = step {
922 evaluate_expression_with_context(step_expr, contract, scope_mgr)?
923 } else {
924 str_to_bytes32("1")
925 };
926
927 let start_u256 = U256::from_be_bytes(start_val);
929 let end_u256 = U256::from_be_bytes(end_val);
930 let step_u256 = U256::from_be_bytes(step_val);
931
932 if step_u256.is_zero() {
933 return Err(CodegenError { kind: CodegenErrorKind::InvalidLoopStep, span: s.span.clone_box(), token: None });
934 }
935
936 const MAX_LOOP_ITERATIONS: usize = 100_000;
938 let mut current = start_u256;
939 let mut iteration_count = 0;
940
941 while current < end_u256 {
943 if iteration_count >= MAX_LOOP_ITERATIONS {
944 return Err(CodegenError {
945 kind: CodegenErrorKind::LoopIterationLimitExceeded(MAX_LOOP_ITERATIONS),
946 span: s.span.clone_box(),
947 token: None,
948 });
949 }
950
951 let current_bytes = current.to_be_bytes::<32>();
953 let substituted_body = substitute_loop_variable_in_statements(body, variable, ¤t_bytes)?;
954
955 let loop_irbytes = MacroDefinition::to_irbytes(evm_version, &substituted_body);
957
958 for ir_byte in loop_irbytes {
960 match &ir_byte.ty {
961 IRByteType::Statement(stmt) => {
962 let (stmt_bytes, mut stmt_spans) = statement_gen(
963 evm_version,
964 stmt,
965 contract,
966 macro_def,
967 scope_mgr,
968 offset,
969 jump_table,
970 label_indices,
971 table_instances,
972 utilized_tables,
973 embedded_tables,
974 starting_offset,
975 relax_jumps,
976 )?;
977 bytes.extend(stmt_bytes);
978 spans.append(&mut stmt_spans);
979 }
980 IRByteType::Bytes(b) => {
981 *offset += b.len();
982 bytes.push_with_offset(starting_offset, b.to_owned());
983 let span_info = if !ir_byte.span.0.is_empty() {
984 ir_byte.span.0.first().and_then(|sp| {
985 if sp.start != 0 || sp.end != 0 { contract.map_original_span_to_flattened(sp) } else { None }
986 })
987 } else {
988 None
989 };
990 spans.push(span_info);
991 }
992 IRByteType::Constant(name) => {
993 if let Some(push_value) = constant_gen(name, contract, ir_byte.span)? {
994 let push_bytes = push_value.to_hex_with_opcode(evm_version);
995 *offset += push_bytes.len() / 2;
996 bytes.push_with_offset(starting_offset, Bytes::Raw(push_bytes));
997 let span_info = if !ir_byte.span.0.is_empty() {
998 ir_byte.span.0.first().and_then(|sp| {
999 if sp.start != 0 || sp.end != 0 { contract.map_original_span_to_flattened(sp) } else { None }
1000 })
1001 } else {
1002 None
1003 };
1004 spans.push(span_info);
1005 }
1006 }
1007 IRByteType::ArgCall(parent_macro_name, arg_name) => {
1008 let bytes_before = bytes.len();
1009 bubble_arg_call(
1010 evm_version,
1011 parent_macro_name.to_owned(),
1012 arg_name.to_owned(),
1013 &mut bytes,
1014 macro_def,
1015 contract,
1016 scope_mgr,
1017 offset,
1018 jump_table,
1019 table_instances,
1020 utilized_tables,
1021 ir_byte.span,
1022 relax_jumps,
1023 )?;
1024 let bytes_added = bytes.len() - bytes_before;
1025 let span_info = if !ir_byte.span.0.is_empty() {
1026 ir_byte.span.0.first().and_then(|sp| {
1027 if sp.start != 0 || sp.end != 0 { contract.map_original_span_to_flattened(sp) } else { None }
1028 })
1029 } else {
1030 None
1031 };
1032 for _ in 0..bytes_added {
1033 spans.push(span_info);
1034 }
1035 }
1036 }
1037 }
1038
1039 current = current.saturating_add(step_u256);
1040 iteration_count += 1;
1041 }
1042 }
1043 sty => {
1044 tracing::error!(target: "codegen", "CURRENT MACRO DEF: {}", macro_def.name);
1045 tracing::error!(target: "codegen", "UNEXPECTED STATEMENT: {:?}", sty);
1046 return Err(CodegenError { kind: CodegenErrorKind::InvalidMacroStatement, span: s.span.clone_box(), token: None });
1047 }
1048 }
1049
1050 Ok((bytes, spans))
1051}
1052
1053fn substitute_loop_variable_in_statements(
1055 statements: &[Statement],
1056 var_name: &str,
1057 value: &[u8; 32],
1058) -> Result<Vec<Statement>, CodegenError> {
1059 let mut substituted = Vec::new();
1060
1061 for statement in statements {
1062 let new_statement = substitute_loop_variable_in_statement(statement, var_name, value)?;
1063 substituted.push(new_statement);
1064 }
1065
1066 Ok(substituted)
1067}
1068
1069fn substitute_loop_variable_in_statement(statement: &Statement, var_name: &str, value: &[u8; 32]) -> Result<Statement, CodegenError> {
1071 let new_ty = match &statement.ty {
1072 StatementType::Constant(name) => {
1073 if name == &format!("__LOOP_VAR_{}", var_name) {
1075 StatementType::Literal(*value)
1077 } else {
1078 statement.ty.clone()
1079 }
1080 }
1081 StatementType::Label(label) => {
1082 let substituted_inner = substitute_loop_variable_in_statements(&label.inner, var_name, value)?;
1084 let iter_value = U256::from_be_bytes(*value);
1085 let mut new_label = label.clone();
1086 new_label.inner = substituted_inner;
1087 new_label.name = format!("{}_{}", label.name, iter_value);
1088 StatementType::Label(new_label)
1089 }
1090 StatementType::ImmediateOpcode { opcode, operands } => StatementType::ImmediateOpcode {
1091 opcode: *opcode,
1092 operands: operands.iter().map(|o| substitute_in_expression(o, var_name, value)).collect(),
1093 },
1094 StatementType::BuiltinFunctionCall(bf) => StatementType::BuiltinFunctionCall(BuiltinFunctionCall {
1095 args: bf
1096 .args
1097 .iter()
1098 .map(|arg| match arg {
1099 BuiltinFunctionArg::Expression(expr) => BuiltinFunctionArg::Expression(substitute_in_expression(expr, var_name, value)),
1100 arg => arg.clone(),
1101 })
1102 .collect(),
1103 ..bf.clone()
1104 }),
1105 StatementType::MacroInvocation(mi) => {
1106 StatementType::MacroInvocation(MacroInvocation { args: substitute_in_macro_args(&mi.args, var_name, value), ..mi.clone() })
1107 }
1108 StatementType::ArgMacroInvocation(parent_macro_name, arg_name, args) => {
1109 StatementType::ArgMacroInvocation(parent_macro_name.clone(), arg_name.clone(), substitute_in_macro_args(args, var_name, value))
1110 }
1111 StatementType::LabelCall(label_name) => {
1112 let iter_value = U256::from_be_bytes(*value);
1114 StatementType::LabelCall(format!("{}_{}", label_name, iter_value))
1115 }
1116 StatementType::ForLoop { variable: loop_var, start, end, step, body } => {
1117 let substituted_body = substitute_loop_variable_in_statements(body, var_name, value)?;
1119 StatementType::ForLoop {
1120 variable: loop_var.clone(),
1121 start: substitute_in_expression(start, var_name, value),
1122 end: substitute_in_expression(end, var_name, value),
1123 step: step.as_ref().map(|s| substitute_in_expression(s, var_name, value)),
1124 body: substituted_body,
1125 }
1126 }
1127 StatementType::IfStatement { condition, then_branch, else_if_branches, else_branch } => {
1128 let substituted_then = substitute_loop_variable_in_statements(then_branch, var_name, value)?;
1130 let substituted_else_if: Vec<_> = else_if_branches
1131 .iter()
1132 .map(|(cond, body)| {
1133 Ok((substitute_in_expression(cond, var_name, value), substitute_loop_variable_in_statements(body, var_name, value)?))
1134 })
1135 .collect::<Result<Vec<_>, CodegenError>>()?;
1136 let substituted_else =
1137 else_branch.as_ref().map(|body| substitute_loop_variable_in_statements(body, var_name, value)).transpose()?;
1138
1139 StatementType::IfStatement {
1140 condition: substitute_in_expression(condition, var_name, value),
1141 then_branch: substituted_then,
1142 else_if_branches: substituted_else_if,
1143 else_branch: substituted_else,
1144 }
1145 }
1146 _ => statement.ty.clone(),
1147 };
1148
1149 Ok(Statement { ty: new_ty, span: statement.span.clone() })
1150}
1151
1152fn substitute_in_expression(expr: &Expression, var_name: &str, value: &[u8; 32]) -> Expression {
1154 match expr {
1155 Expression::Constant { name, span } => {
1156 if name == &format!("__LOOP_VAR_{}", var_name) {
1158 Expression::Literal { value: *value, span: span.clone() }
1159 } else {
1160 expr.clone()
1161 }
1162 }
1163 Expression::Binary { left, op, right, span } => Expression::Binary {
1164 left: Box::new(substitute_in_expression(left, var_name, value)),
1165 op: op.clone(),
1166 right: Box::new(substitute_in_expression(right, var_name, value)),
1167 span: span.clone(),
1168 },
1169 Expression::Unary { op, expr: inner_expr, span } => {
1170 Expression::Unary { op: op.clone(), expr: Box::new(substitute_in_expression(inner_expr, var_name, value)), span: span.clone() }
1171 }
1172 Expression::Grouped { expr: inner_expr, span } => {
1173 Expression::Grouped { expr: Box::new(substitute_in_expression(inner_expr, var_name, value)), span: span.clone() }
1174 }
1175 _ => expr.clone(),
1176 }
1177}
1178
1179fn substitute_in_macro_args(args: &[MacroArg], var_name: &str, value: &[u8; 32]) -> Vec<MacroArg> {
1181 args.iter()
1182 .map(|arg| match arg {
1183 MacroArg::Ident(name) if name == &format!("__LOOP_VAR_{}", var_name) => MacroArg::HexLiteral(*value),
1184 MacroArg::MacroCall(mi) => {
1185 MacroArg::MacroCall(MacroInvocation { args: substitute_in_macro_args(&mi.args, var_name, value), ..mi.clone() })
1186 }
1187 MacroArg::ArgCallMacroInvocation(name, nested) => {
1188 MacroArg::ArgCallMacroInvocation(name.clone(), substitute_in_macro_args(nested, var_name, value))
1189 }
1190 _ => arg.clone(),
1191 })
1192 .collect()
1193}