Skip to main content

huff_neo_codegen/
lib.rs

1#![doc = include_str!("../README.md")]
2#![warn(missing_docs)]
3#![warn(unused_extern_crates)]
4#![forbid(unsafe_code)]
5
6use crate::irgen::builtin_function::builtin_pad;
7use alloy_dyn_abi::{DynSolType, DynSolValue};
8use alloy_primitives::{U256, hex};
9use huff_neo_utils::ast::huff::*;
10use huff_neo_utils::ast::span::AstSpan;
11use huff_neo_utils::builtin_eval::{PadDirection, eval_builtin_bytes, eval_event_hash, eval_function_signature};
12use huff_neo_utils::bytes_util::str_to_bytes32;
13use huff_neo_utils::file::file_source::FileSource;
14use huff_neo_utils::scope::ScopeManager;
15use huff_neo_utils::{
16    abi::*,
17    artifact::*,
18    bytecode::*,
19    bytes_util,
20    error::CodegenError,
21    opcodes::Opcode,
22    prelude::{CodegenErrorKind, EVMVersion, PushValue, Span, deploy_bootstrap, format_even_bytes, pad_n_bytes},
23    types::EToken,
24};
25use std::{
26    collections::{BTreeMap, HashMap},
27    fs,
28    path::Path,
29    sync::Arc,
30};
31
32mod irgen;
33use crate::irgen::prelude::*;
34
35/// Maximum macro recursion depth allowed during compilation.
36///
37/// This prevents infinite recursion in macro expansion or argument resolution,
38/// which can occur with circular macro calls or recursive argument references.
39pub(crate) const MAX_MACRO_RECURSION_DEPTH: usize = 100;
40
41/// Maximum number of iterations allowed for the jump relaxation optimization algorithm.
42///
43/// This limit prevents infinite loops in pathological cases where convergence fails,
44/// though typical contracts converge in a few iterations.
45const JUMP_RELAXATION_MAX_ITERATIONS: usize = 20;
46
47/// Maximum number of codegen passes of MAIN or the constructor used to converge `__codesize`.
48///
49/// When MAIN (or a constant referenced by MAIN) uses `__codesize(RUNTIME)`, the runtime size
50/// depends on the embedded value, which depends on the runtime size. Likewise, when MAIN and the
51/// constructor push each other's size, each size depends on the width of the other's push. Each
52/// pass recompiles with the previous pass's measured sizes. PUSH widths only grow (never shrink)
53/// across passes, and the size limits cap the values at PUSH3, so convergence takes 2–3 passes.
54const CODESIZE_MAX_ITERATIONS: usize = 10;
55
56/// The source span of the first `__argsstart()` call in generated code, if there is one
57fn args_start_span(res: &BytecodeRes) -> Option<&AstSpan> {
58    res.bytes.iter().find_map(|segment| match &segment.bytes {
59        Bytes::ArgsStartPlaceholder(span) => Some(span),
60        _ => None,
61    })
62}
63
64/// Runs codegen passes until the values a pass depends on are the ones it produces, starting
65/// from `initial`, and returns the output of the last pass
66fn converge_codesizes<T, K: PartialEq>(initial: K, mut pass: impl FnMut(&K) -> Result<(T, K), CodegenError>) -> Result<T, CodegenError> {
67    let mut key = initial;
68    for _ in 0..CODESIZE_MAX_ITERATIONS {
69        let (output, next) = pass(&key)?;
70        if next == key {
71            return Ok(output);
72        }
73        key = next;
74    }
75    Err(CodegenError { kind: CodegenErrorKind::CodesizeNotConverged(CODESIZE_MAX_ITERATIONS), span: AstSpan(vec![]).boxed(), token: None })
76}
77
78/// Compiles Huff contracts into EVM bytecode.
79///
80/// Transforms a Contract AST into executable bytecode, handling macro expansion,
81/// label resolution, and bytecode optimization.
82///
83/// ```rust
84/// use huff_neo_codegen::Codegen;
85/// let cg = Codegen::new();
86/// ```
87#[derive(Debug, Default, Clone)]
88pub struct Codegen {
89    /// The Input AST
90    pub ast: Option<Contract>,
91    /// A cached codegen output artifact
92    pub artifact: Option<Artifact>,
93    /// Intermediate main bytecode store
94    pub main_bytecode: Option<String>,
95    /// Intermediate constructor bytecode store
96    pub constructor_bytecode: Option<String>,
97}
98
99/// Output of [`Codegen::gen_table_bytecode`].
100///
101/// Body bytecode (with `__tablestart` placeholders filled), the table bytecode emitted by this call,
102/// and the absolute deployment-bytecode offsets at which those tables sit.
103#[derive(Debug, Default, Clone)]
104pub struct TableBytecodeOutput {
105    /// Body bytecode hex string, with all `__tablestart` placeholders replaced.
106    pub body: String,
107    /// Table bytecode hex string for tables emitted by this call. The caller decides where to place
108    /// this in the overall deployment bytecode.
109    pub tables: String,
110    /// Map of locally-emitted table name to its absolute deployment-bytecode offset.
111    pub local_table_offsets: HashMap<String, usize>,
112    /// Source map for the body bytecode.
113    pub source_map: Vec<SourceMapEntry>,
114}
115
116/// Output of [`Codegen::generate_main_bytecode_with_sourcemap`].
117///
118/// `bytecode` is the runtime bytes (body + appended tables). `table_offsets` maps each
119/// runtime-resident table to its offset within the runtime. The constructor finalization step uses
120/// `table_offsets` to dedupe any `__tablestart` reference shared with main.
121#[derive(Debug, Default, Clone)]
122pub struct MainBytecodeOutput {
123    /// Full main/runtime bytecode (body + appended tables) as a hex string.
124    pub bytecode: String,
125    /// Source map for the runtime bytecode body.
126    pub source_map: Vec<SourceMapEntry>,
127    /// Per-table runtime-relative offsets for tables appended to the runtime body.
128    pub table_offsets: HashMap<String, usize>,
129}
130
131impl MainBytecodeOutput {
132    /// Byte offset within the runtime at which the appended tables begin (equivalently, the length
133    /// of the runtime body). When no tables are appended, this equals the full runtime length.
134    pub fn body_len(&self) -> usize {
135        self.table_offsets.values().min().copied().unwrap_or(self.bytecode.len() / 2)
136    }
137}
138
139/// Constructor macro bytecode produced by phase 1, prior to table resolution and bootstrap insertion.
140///
141/// The unresolved [`BytecodeRes`] is forwarded to [`Codegen::assemble_artifact`] (phase 2) which
142/// knows the bootstrap size and runtime layout, and finalizes `__tablestart` resolution from there.
143#[derive(Debug, Clone)]
144pub struct ConstructorMacroBytecode {
145    /// Unresolved bytecode result for the constructor macro body.
146    pub bytecode_res: BytecodeRes,
147    /// `true` when the constructor body already contains a `RETURN` (`0xf3`) opcode, indicating the
148    /// user supplies their own deployment bootstrap and the compiler should not emit one.
149    pub has_custom_bootstrap: bool,
150}
151
152/// Direction in which span-dependent pushes (jump targets, self-referencing `__codesize`) may change
153#[derive(Debug, Clone, Copy, PartialEq, Eq)]
154enum ResizeMode {
155    /// Grow pushes whose value does not fit
156    Widen,
157    /// Shrink PUSH2 jumps to PUSH1 where the target fits
158    Shrink,
159}
160
161/// Mapping from segment offsets before resizing span-dependent pushes to the offsets after it
162pub type OffsetMapping = BTreeMap<usize, usize>;
163
164/// Bytecode whose span-dependent pushes have stable widths
165#[derive(Debug, Clone)]
166pub struct ResizedBytecode {
167    /// Bytecode with the resized pushes
168    pub bytes: BytecodeSegments,
169    /// Label positions after resizing
170    pub label_indices: LabelIndices,
171    /// Mapping from original to new segment offsets
172    pub offset_mapping: OffsetMapping,
173}
174
175/// Bytecode whose jumps are resolved as far as their labels are known
176#[derive(Debug, Clone)]
177pub struct FilledJumps {
178    /// Bytecode with resolved jumps
179    pub bytes: BytecodeSegments,
180    /// Jumps whose label is not defined in the bytecode
181    pub unmatched_jumps: Vec<Jump>,
182    /// Final label positions
183    pub label_indices: LabelIndices,
184    /// Mapping from original to final segment offsets
185    pub offset_mapping: OffsetMapping,
186}
187
188/// Translates a segment offset to its position after resizing; offsets without a segment stay put
189fn remap_offset(offset_mapping: &OffsetMapping, offset: usize) -> usize {
190    offset_mapping.get(&offset).copied().unwrap_or(offset)
191}
192
193/// Moves jump table entries to the offsets their jumps have after resizing
194fn remap_jump_table(jump_table: &JumpTable, offset_mapping: &OffsetMapping) -> JumpTable {
195    jump_table.iter().map(|(&offset, jumps)| (remap_offset(offset_mapping, offset), jumps.clone())).collect()
196}
197
198/// Translates a code boundary from original segment offsets to the segment offsets after resizing.
199///
200/// A boundary is either the start of a segment or the end of the code (`end_offset` after
201/// resizing).
202fn current_offset(original_offsets: &[usize], current_offsets: &[usize], end_offset: usize, original: usize) -> usize {
203    let segment_idx = original_offsets.partition_point(|&offset| offset < original);
204    current_offsets.get(segment_idx).copied().unwrap_or(end_offset)
205}
206
207impl Codegen {
208    /// Public associated function to instantiate a new Codegen instance.
209    pub fn new() -> Self {
210        Self { ast: None, artifact: None, main_bytecode: None, constructor_bytecode: None }
211    }
212
213    /// Compiles the contract's main macro into executable bytecode.
214    ///
215    /// # Arguments
216    ///
217    /// * `evm_version` - Target EVM version
218    /// * `contract` - The contract to compile
219    /// * `alternative_main` - Optional alternative entry point (defaults to "MAIN")
220    /// * `relax_jumps` - Enable jump optimization to reduce bytecode size
221    pub fn generate_main_bytecode(
222        evm_version: &EVMVersion,
223        contract: &Contract,
224        alternative_main: Option<String>,
225        relax_jumps: bool,
226    ) -> Result<String, CodegenError> {
227        let out = Self::generate_main_bytecode_with_sourcemap(evm_version, contract, alternative_main, relax_jumps)?;
228        Ok(out.bytecode)
229    }
230
231    /// Compiles the contract's main macro into bytecode with source map.
232    ///
233    /// Returns the full main bytecode (body + appended tables), its source map, the length of the
234    /// body section (offset at which the runtime tables begin), and the per-table runtime offsets.
235    /// `build_artifact` uses the body length and table offsets to dedupe constructor-side
236    /// `__tablestart` references into the runtime copy.
237    pub fn generate_main_bytecode_with_sourcemap(
238        evm_version: &EVMVersion,
239        contract: &Contract,
240        alternative_main: Option<String>,
241        relax_jumps: bool,
242    ) -> Result<MainBytecodeOutput, CodegenError> {
243        // Iterate to a fixed point on `__codesize(RUNTIME)` and on the macro sizes that `__codesize`
244        // values measured from another macro depend on. Each pass recompiles MAIN with the
245        // previous pass's sizes.
246        let mut iter_contract = contract.clone();
247        let initial = (iter_contract.runtime_size.unwrap_or(0), None);
248        converge_codesizes(initial, |(runtime_size, hints)| {
249            iter_contract.runtime_size = Some(*runtime_size);
250            let (output, sizes) =
251                Self::generate_main_bytecode_inner(evm_version, &iter_contract, alternative_main.as_deref(), relax_jumps, hints.as_ref())?;
252            let runtime_size = output.bytecode.len() / 2;
253            Ok((output, (runtime_size, sizes)))
254        })
255    }
256
257    /// Compiles MAIN once, with the macro sizes of the previous pass. Also returns the macro sizes
258    /// of this pass when a `__codesize` needed them, so they must reach a fixed point.
259    fn generate_main_bytecode_inner(
260        evm_version: &EVMVersion,
261        contract: &Contract,
262        alternative_main: Option<&str>,
263        relax_jumps: bool,
264        codesize_hints: Option<&BTreeMap<String, usize>>,
265    ) -> Result<(MainBytecodeOutput, Option<BTreeMap<String, usize>>), CodegenError> {
266        // Update table sizes
267        let contract = Self::update_table_size(evm_version, contract)?;
268
269        // If an alternative main is provided, then use it as the compilation target
270        let m_macro = Codegen::get_macro_by_name(alternative_main.unwrap_or("MAIN"), &contract)?;
271
272        // Create scope manager and push the main macro
273        let mut scope_mgr = ScopeManager::new();
274        scope_mgr.codesize_hints = codesize_hints.cloned().unwrap_or_default();
275        scope_mgr.push_macro(m_macro, 0);
276
277        // For each MacroInvocation Statement, recurse into bytecode
278        let bytecode_res: BytecodeRes = Codegen::macro_to_bytecode(evm_version, m_macro, &contract, &mut scope_mgr, 0, relax_jumps)?;
279        Codegen::reject_args_start(&bytecode_res)?;
280        let codesizes = scope_mgr.codesize_hints_used.then_some(scope_mgr.codesizes);
281
282        tracing::debug!(target: "codegen", "Generated main bytecode. Appending table bytecode...");
283
284        // Main bytecode is not shifted by a bootstrap and has no upstream dedup map.
285        let table_out = Codegen::gen_table_bytecode(&contract, bytecode_res, 0, &HashMap::new())?;
286        let bytecode = format!("{}{}", table_out.body, table_out.tables);
287        Ok((MainBytecodeOutput { bytecode, source_map: table_out.source_map, table_offsets: table_out.local_table_offsets }, codesizes))
288    }
289
290    /// Rejects `__argsstart()` in code that runs as the runtime, such as MAIN or a test, since
291    /// constructor arguments are only part of the deployment code.
292    pub fn reject_args_start(res: &BytecodeRes) -> Result<(), CodegenError> {
293        match args_start_span(res) {
294            Some(span) => Err(CodegenError { kind: CodegenErrorKind::ArgsStartOutsideConstructor, span: span.clone_box(), token: None }),
295            None => Ok(()),
296        }
297    }
298
299    /// Generates the constructor section in isolation: macro body with all `__tablestart`
300    /// placeholders resolved against locally-emitted tables.
301    ///
302    /// This standalone form (tables sitting directly after the body) does not include the
303    /// deployment-time auto-bootstrap and is **not** the layout that ends up in the deployed
304    /// artifact — use [`Codegen::churn`] for the final assembly. This function exists for tools
305    /// and tests that want to inspect just the constructor section. `__argsstart()` stays a
306    /// placeholder here, as the offset of the arguments depends on the deployment layout.
307    pub fn generate_constructor_bytecode(
308        evm_version: &EVMVersion,
309        contract: &Contract,
310        alternative_constructor: Option<String>,
311        relax_jumps: bool,
312    ) -> Result<(String, bool), CodegenError> {
313        let (ctor, contract) = Self::generate_constructor_macro_bytecode(evm_version, contract, alternative_constructor, relax_jumps)?;
314        let has_custom_bootstrap = ctor.has_custom_bootstrap;
315        let out = Codegen::gen_table_bytecode(&contract, ctor.bytecode_res, 0, &HashMap::new())?;
316        Ok((format!("{}{}", out.body, out.tables), has_custom_bootstrap))
317    }
318
319    /// Generates constructor macro bytecode without resolving `__tablestart` placeholders.
320    ///
321    /// This is phase 1 of constructor codegen. Table positions and `__tablestart` resolution depend
322    /// on the auto-bootstrap size and the runtime layout, which are only known once `build_artifact`
323    /// has both the constructor body and the compiled main bytecode in hand. Phase 2 runs inside
324    /// `build_artifact` via [`Codegen::gen_table_bytecode`].
325    pub fn generate_constructor_macro_bytecode(
326        evm_version: &EVMVersion,
327        contract: &Contract,
328        alternative_constructor: Option<String>,
329        relax_jumps: bool,
330    ) -> Result<(ConstructorMacroBytecode, Contract), CodegenError> {
331        // Update table sizes
332        let contract = Self::update_table_size(evm_version, contract)?;
333
334        // If an alternative constructor macro is provided, then use it as the compilation target
335        let constructor_macro = alternative_constructor.unwrap_or_else(|| String::from("CONSTRUCTOR"));
336
337        // Find the constructor macro
338        let c_macro = Codegen::get_macro_by_name(&constructor_macro, &contract)?;
339
340        // Iterate to a fixed point on the macro sizes that `__codesize` values measured from another
341        // macro depend on, as MAIN does
342        let bytecode_res = converge_codesizes(None, |hints: &Option<BTreeMap<String, usize>>| {
343            let mut scope_mgr = ScopeManager::new();
344            scope_mgr.codesize_hints = hints.clone().unwrap_or_default();
345            scope_mgr.push_macro(c_macro, 0);
346            let res = Codegen::macro_to_bytecode(evm_version, c_macro, &contract, &mut scope_mgr, 0, relax_jumps)?;
347            Ok((res, scope_mgr.codesize_hints_used.then_some(scope_mgr.codesizes)))
348        })?;
349
350        // A constructor body containing a RETURN means the user is supplying their own deployment
351        // bootstrap; suppress the compiler's auto-bootstrap in that case.
352        let has_custom_bootstrap = bytecode_res.bytes.iter().any(|seg| seg.bytes.as_str() == "f3");
353
354        tracing::info!(target: "codegen", "Constructor is self-generating: {}", has_custom_bootstrap);
355
356        Ok((ConstructorMacroBytecode { bytecode_res, has_custom_bootstrap }, contract))
357    }
358
359    /// Update the table size in the contract that was not know at the time of parsing
360    pub fn update_table_size(_evm_version: &EVMVersion, contract: &Contract) -> Result<Contract, CodegenError> {
361        // Update the table size from generated bytecode
362        let mut updated = vec![];
363        for table_definition in contract.tables.iter() {
364            if table_definition.size.is_some() {
365                updated.push(table_definition.clone());
366                continue;
367            }
368            // If the size is not provided we need to generate the bytecode to know the size
369            let byte_code = Codegen::gen_table_bytecode_builtin(contract, table_definition)?;
370            let size = str_to_bytes32(format!("{:02x}", byte_code.len() / 2).as_str());
371            let mut table = table_definition.clone();
372            table.size = Some(size);
373            updated.push(table);
374        }
375        let mut contract = contract.clone();
376        contract.tables = updated;
377        Ok(contract)
378    }
379
380    /// Helper function to find a macro or generate a CodegenError
381    pub(crate) fn get_macro_by_name<'a>(name: &str, contract: &'a Contract) -> Result<&'a MacroDefinition, CodegenError> {
382        if let Some(m) = contract.find_macro_by_name(name) {
383            Ok(m)
384        } else {
385            tracing::error!(target: "codegen", "MISSING \"{}\" MACRO!", name);
386            Err(CodegenError {
387                kind: CodegenErrorKind::MissingMacroDefinition(name.to_string()),
388                span: AstSpan(vec![Span { start: 0, end: 0, file: None }]).boxed(),
389                token: None,
390            })
391        }
392    }
393
394    /// Resolves a constant to the value `[NAME]` pushes, or `None` for a `__NOOP` constant
395    pub fn gen_constant_value(contract: &Contract, name: &str, span: &AstSpan) -> Result<Option<PushValue>, CodegenError> {
396        constant_gen(name, contract, span)
397    }
398
399    /// Generates bytecode for a builtin function call used for constants, code tables, etc.
400    /// Returns a PushValue that can be converted to bytecode with or without opcode
401    pub fn gen_builtin_bytecode(contract: &Contract, bf: &BuiltinFunctionCall, span: AstSpan) -> Result<PushValue, CodegenError> {
402        match bf.kind {
403            BuiltinFunctionKind::FunctionSignature => eval_function_signature(contract, bf),
404            BuiltinFunctionKind::Bytes => eval_builtin_bytes(bf),
405            BuiltinFunctionKind::EventHash => eval_event_hash(contract, bf),
406            BuiltinFunctionKind::LeftPad => builtin_pad(contract, bf, PadDirection::Left),
407            BuiltinFunctionKind::RightPad => builtin_pad(contract, bf, PadDirection::Right),
408            _ if is_runtime_codesize(bf) => {
409                let size = contract.runtime_size.ok_or_else(|| CodegenError {
410                    kind: CodegenErrorKind::RuntimeSizeNotComputed,
411                    span: span.clone_box(),
412                    token: None,
413                })?;
414                Ok(PushValue::from(U256::from(size).to_be_bytes::<32>()))
415            }
416            _ => Err(CodegenError {
417                kind: CodegenErrorKind::UnsupportedBuiltinFunction(format!("{}", bf.kind)),
418                span: span.boxed(),
419                token: None,
420            }),
421        }
422    }
423
424    /// Generates bytecode for a code table with builtin functions
425    pub fn gen_table_bytecode_builtin(contract: &Contract, table_definition: &TableDefinition) -> Result<String, CodegenError> {
426        let mut byte_code = String::new();
427        for statement in table_definition.statements.clone() {
428            match &statement.ty {
429                StatementType::Code(code) => {
430                    byte_code = format!("{byte_code}{code}");
431                }
432                StatementType::BuiltinFunctionCall(bf) => {
433                    if is_runtime_codesize(bf) {
434                        return Err(CodegenError {
435                            kind: CodegenErrorKind::CodesizeRuntimeInCodeTable,
436                            span: statement.span.clone_box(),
437                            token: None,
438                        });
439                    }
440                    let push_value = Codegen::gen_builtin_bytecode(contract, bf, statement.span.clone())?;
441                    // Use full hex for padding functions (always 32 bytes), trimmed for others
442                    let hex_data = match bf.kind {
443                        BuiltinFunctionKind::LeftPad | BuiltinFunctionKind::RightPad => push_value.to_hex_full(),
444                        _ => push_value.to_hex_trimmed(),
445                    };
446                    byte_code = format!("{byte_code}{hex_data}")
447                }
448                StatementType::Constant(name) => {
449                    let constant = lookup_constant(name, contract, &statement.span)?;
450                    let bytes = match &constant.value {
451                        ConstVal::Bytes(bytes) => bytes.clone(),
452                        ConstVal::Expression(expr) => {
453                            let literal = contract.evaluate_constant_expression(expr)?;
454                            Bytes::Raw(bytes_util::bytes32_to_hex_string(&literal, false))
455                        }
456                        ConstVal::BuiltinFunctionCall(bf) => {
457                            let push_value = Codegen::gen_builtin_bytecode(contract, bf, statement.span.clone())?;
458                            // Use full hex for padding functions (always 32 bytes), trimmed for others
459                            let hex_data = match bf.kind {
460                                BuiltinFunctionKind::LeftPad | BuiltinFunctionKind::RightPad => push_value.to_hex_full(),
461                                _ => push_value.to_hex_trimmed(),
462                            };
463                            Bytes::Raw(hex_data)
464                        }
465                        _ => {
466                            return Err(CodegenError {
467                                kind: CodegenErrorKind::InvalidArguments("Constant must be a hex literal or expression".to_string()),
468                                span: statement.span.clone_box(),
469                                token: None,
470                            });
471                        }
472                    };
473                    byte_code = format!("{byte_code}{}", bytes.as_str());
474                }
475                _ => {
476                    return Err(CodegenError {
477                        kind: CodegenErrorKind::UnsupportedStatementType(format!("{}", statement.ty)),
478                        span: table_definition.span.clone_box(),
479                        token: None,
480                    });
481                }
482            }
483        }
484        Ok(byte_code)
485    }
486
487    /// Appends table bytecode for tables referenced by `res` and fills `__tablestart` placeholders.
488    ///
489    /// `base_offset` is added to every locally-emitted table's offset before resolution — used by the
490    /// constructor pathway to push tables past the auto-bootstrap and main bytecode.
491    ///
492    /// `external_table_offsets` lets the caller resolve `__tablestart` to a table emitted somewhere
493    /// else (e.g. shared with the runtime). Tables found here are not emitted locally; their offset
494    /// is used directly when filling placeholders.
495    pub fn gen_table_bytecode(
496        contract: &Contract,
497        res: BytecodeRes,
498        base_offset: usize,
499        external_table_offsets: &HashMap<String, usize>,
500    ) -> Result<TableBytecodeOutput, CodegenError> {
501        if !res.unmatched_jumps.is_empty() {
502            let labels = res.unmatched_jumps.iter().map(|uj| uj.label.to_string()).collect::<Vec<String>>();
503            tracing::error!(
504                target: "codegen",
505                "Source contains unmatched jump labels \"{}\"",
506                labels.join(", ")
507            );
508
509            return Err(CodegenError {
510                kind: CodegenErrorKind::UnmatchedJumpLabels(labels),
511                span: AstSpan(res.unmatched_jumps.iter().flat_map(|uj| uj.span.0.clone()).collect::<Vec<Span>>()).boxed(),
512                token: None,
513            });
514        }
515
516        tracing::info!(target: "codegen", "GENERATING JUMPTABLE BYTECODE");
517        tracing::debug!(target: "codegen", "Contract has {} source files", contract.source_files.len());
518        tracing::debug!(target: "codegen", "Source map entries: {:?}", contract.source_map);
519        tracing::debug!(target: "codegen", "res.bytes.len() = {}", res.bytes.len());
520        tracing::debug!(target: "codegen", "res.spans.len() = {}", res.spans.len());
521
522        // Generate source map from bytecode segments and spans
523        // Create one entry per instruction for Solidity-compatible source maps
524        // The source map is indexed by IC, so we need an entry for every instruction
525        let mut source_map = Vec::new();
526        let mut program_counter = 0; // PC: byte offset in bytecode
527
528        for (instruction_counter, seg) in res.bytes.iter().enumerate() {
529            let instruction_bytecode_length = seg.bytes.len(); // Length in bytes
530
531            // Create source map entry for this instruction
532            // Use span information if available, otherwise use zeros (unmapped)
533            let (source_start, source_end, file_id) = if let Some(Some((start, end))) = res.spans.get(instruction_counter) {
534                // Use the Contract's helper method to map flattened positions to original file positions
535                let (file_id, original_start, original_end) = contract.map_flattened_position_to_source(*start, *end);
536
537                tracing::debug!(target: "codegen", "Source map entry: IC={}, flattened_pos={}..{}, mapped_pos={}..{}, file={}, bytes={}, PC={}, bytecode_len={}, source_len={}",
538                    instruction_counter, start, end, original_start, original_end, file_id, seg.bytes.as_str(), program_counter, instruction_bytecode_length, original_end - original_start);
539                (original_start, original_end, file_id)
540            } else {
541                // No source mapping for this instruction
542                tracing::debug!(target: "codegen", "Source map entry: IC={}, no span, using (0,0,0)", instruction_counter);
543                (0, 0, 0)
544            };
545
546            source_map.push(SourceMapEntry {
547                ic: instruction_counter,                      // IC: instruction index (0, 1, 2, ...)
548                pc: program_counter,                          // PC: byte offset in bytecode where this instruction starts
549                bytecode_length: instruction_bytecode_length, // Length of this instruction in bytecode (bytes)
550                source_start,
551                source_length: source_end - source_start, // Length in source code (chars, not bytecode bytes)
552                file_id,
553            });
554
555            program_counter += instruction_bytecode_length; // Advance PC by the bytecode length
556        }
557
558        let mut body = res.bytes.into_iter().map(|seg| seg.bytes.as_str().to_string()).collect::<String>();
559        let body_len = body.len() / 2;
560        let mut tables_bytecode = String::new();
561        let mut local_table_offsets: HashMap<String, usize> = HashMap::new();
562        let mut table_offset = body_len + base_offset;
563
564        res.utilized_tables.iter().try_for_each(|jt| {
565            // Skip tables that were embedded inline
566            if res.embedded_tables.contains_key(&jt.name) {
567                tracing::debug!(target: "codegen", "Skipping embedded table \"{}\" from end-placement", jt.name);
568                return Ok(());
569            }
570            // Skip tables resolved via an external (dedup) map
571            if external_table_offsets.contains_key(&jt.name) {
572                tracing::debug!(target: "codegen", "Resolving table \"{}\" via external offset map", jt.name);
573                return Ok(());
574            }
575
576            local_table_offsets.insert(jt.name.to_string(), table_offset);
577
578            let Some(table_size) = &jt.size else {
579                return Err(CodegenError {
580                    kind: CodegenErrorKind::MissingTableSize(jt.name.clone()),
581                    span: jt.span.clone_box(),
582                    token: None,
583                });
584            };
585
586            let size = match bytes_util::hex_to_usize(bytes_util::bytes32_to_hex_string(table_size, false).as_str()) {
587                Ok(s) => s,
588                Err(e) => {
589                    tracing::error!(target: "codegen", "Errored converting bytes32 to str. Bytes {:?} with error: {:?}", jt.size, e);
590                    return Err(CodegenError {
591                        kind: CodegenErrorKind::UsizeConversion(format!("{:?}", jt.size)),
592                        span: jt.span.clone_box(),
593                        token: None,
594                    });
595                }
596            };
597            table_offset += size;
598
599            if jt.kind == TableKind::CodeTable {
600                let table_code = Codegen::gen_table_bytecode_builtin(contract, jt)?;
601
602                tables_bytecode.push_str(&table_code);
603                return Ok(());
604            }
605
606            // For all other tables
607            tracing::info!(target: "codegen", "GENERATING BYTECODE FOR TABLE: \"{}\"", jt.name);
608
609            // Packed jump tables store each destination in 2 bytes, regular ones in 32 bytes
610            let is_packed = jt.kind == TableKind::JumpTablePacked;
611            let mut table_code = String::new();
612            jt.statements.iter().try_for_each(|s| {
613                match &s.ty {
614                    StatementType::LabelCall(label) => {
615                        // For jump table label lookups, try to find the label in any scope
616                        // Jump tables are generated at the top level but may reference labels in inner scopes
617                        let offset = match res.label_indices.get_any(label) {
618                            Some(l) => l,
619                            None => {
620                                tracing::error!(
621                                    target: "codegen",
622                                    "Definition not found for Jump Table Label: \"{}\"",
623                                    label
624                                );
625                                return Err(CodegenError {
626                                    kind: CodegenErrorKind::UnmatchedJumpLabels(vec![label.clone()]),
627                                    span: s.span.clone_box(),
628                                    token: None,
629                                });
630                            }
631                        };
632                        if is_packed && offset > 0xFFFF {
633                            return Err(CodegenError {
634                                kind: CodegenErrorKind::OffsetExceedsTwoBytes(
635                                    format!("label \"{label}\" in packed jump table \"{}\"", jt.name),
636                                    offset,
637                                ),
638                                span: s.span.clone_box(),
639                                token: None,
640                            });
641                        }
642                        let hex = format_even_bytes(format!("{offset:02x}"));
643
644                        table_code = format!("{table_code}{}", pad_n_bytes(hex.as_str(), if is_packed { 0x02 } else { 0x20 },));
645                    }
646                    _ => {
647                        return Err(CodegenError { kind: CodegenErrorKind::InvalidMacroStatement, span: jt.span.clone_box(), token: None });
648                    }
649                }
650                Ok(())
651            })?;
652            tracing::info!(target: "codegen", "SUCCESSFULLY GENERATED BYTECODE FOR TABLE: \"{}\"", jt.name);
653            tables_bytecode.push_str(&table_code);
654            Ok(())
655        })?;
656
657        for jump in res.table_instances.iter() {
658            // Resolution order: locally-emitted, then external (dedup) map, then embedded inline.
659            let offset_opt = local_table_offsets
660                .get(&jump.label)
661                .or_else(|| external_table_offsets.get(&jump.label))
662                .or_else(|| res.embedded_tables.get(&jump.label));
663
664            if let Some(&o) = offset_opt {
665                // `__tablestart` reserves a PUSH2, so the table must start within the first 64 KiB
666                if o > 0xFFFF {
667                    return Err(CodegenError {
668                        kind: CodegenErrorKind::OffsetExceedsTwoBytes(format!("table \"{}\" referenced by __tablestart", jump.label), o),
669                        span: jump.span.clone_box(),
670                        token: None,
671                    });
672                }
673                let before = &body[0..jump.bytecode_index * 2 + 2];
674                let after = &body[jump.bytecode_index * 2 + 6..];
675
676                body = format!("{before}{}{after}", pad_n_bytes(format!("{o:02x}").as_str(), 2));
677                tracing::info!(target: "codegen", "FILLED JUMPDEST FOR LABEL \"{}\"", jump.label);
678            } else {
679                tracing::error!(
680                    target: "codegen",
681                    "Jump table offset not present for jump label \"{}\"",
682                    jump.label
683                );
684            }
685        }
686
687        Ok(TableBytecodeOutput { body, tables: tables_bytecode, local_table_offsets, source_map })
688    }
689
690    /// Generates bytecode from a macro definition.
691    ///
692    /// Expands the macro body into bytecode, resolving labels, arguments, and nested macro calls.
693    ///
694    /// # Arguments
695    ///
696    /// * `evm_version` - Target EVM version for opcode compatibility
697    /// * `macro_def` - The macro to compile
698    /// * `contract` - Contract context for macro and constant lookups
699    /// * `scope_mgr` - Call stack for tracking macro invocations and scope
700    /// * `offset` - Starting bytecode offset
701    /// * `relax_jumps` - Enable jump size optimization
702    ///
703    /// # Returns
704    ///
705    /// Bytecode with label indices, unresolved jumps, and source map information.
706    #[allow(clippy::too_many_arguments)]
707    pub fn macro_to_bytecode<'a>(
708        evm_version: &EVMVersion,
709        macro_def: &'a MacroDefinition,
710        contract: &'a Contract,
711        scope_mgr: &mut ScopeManager<'a>,
712        mut offset: usize,
713        relax_jumps: bool,
714    ) -> Result<BytecodeRes, CodegenError> {
715        // Safety check to prevent infinite recursion
716        if scope_mgr.depth() > MAX_MACRO_RECURSION_DEPTH {
717            let macro_chain = scope_mgr.macro_stack().iter().map(|m| m.name.as_str()).collect::<Vec<_>>().join(" -> ");
718            return Err(CodegenError {
719                kind: CodegenErrorKind::InvalidMacroArgumentType(format!(
720                    "Maximum macro recursion depth ({}) exceeded while compiling macro '{}'.\nMacro call chain: {}\nThis likely indicates circular macro calls or infinitely recursive macro expansion.",
721                    MAX_MACRO_RECURSION_DEPTH, macro_def.name, macro_chain
722                )),
723                span: macro_def.span.clone_box(),
724                token: None,
725            });
726        }
727
728        // Get intermediate bytecode representation of the macro definition
729        let mut bytes = BytecodeSegments::new();
730        let mut spans: Vec<Option<(usize, usize)>> = Vec::default();
731        let ir_bytes = macro_def.to_irbytecode(evm_version)?.0;
732
733        // Define outer loop variables
734        let mut jump_table = JumpTable::new();
735        let mut label_indices = LabelIndices::new();
736        let mut table_instances = Jumps::new();
737        let mut utilized_tables: Vec<TableDefinition> = Vec::new();
738        let mut embedded_tables = std::collections::BTreeMap::new();
739        let start_offset = offset;
740
741        // Loop through all intermediate bytecode representations generated from the AST
742        for ir_byte in ir_bytes.iter() {
743            let starting_offset = offset;
744
745            // Extract span information if available
746            // Convert from original file coordinates to flattened coordinates
747            let span_info = if !ir_byte.span.0.is_empty() {
748                ir_byte.span.0.first().and_then(|s| {
749                    if s.start != 0 || s.end != 0 {
750                        // Convert original span to flattened coordinates
751                        contract.map_original_span_to_flattened(s)
752                    } else {
753                        None
754                    }
755                })
756            } else {
757                None
758            };
759
760            match &ir_byte.ty {
761                IRByteType::Bytes(b) => {
762                    offset += b.len(); // len() now returns byte count directly
763                    bytes.push_with_offset(starting_offset, b.to_owned());
764                    spans.push(span_info);
765                }
766                IRByteType::Constant(name) => {
767                    if let Some(push_value) = constant_gen(name, contract, ir_byte.span)? {
768                        let push_bytes = push_value.to_hex_with_opcode(evm_version);
769                        offset += push_bytes.len() / 2;
770                        tracing::debug!(target: "codegen", "OFFSET: {}, PUSH BYTES: {:?}", offset, push_bytes);
771                        bytes.push_with_offset(starting_offset, Bytes::Raw(push_bytes));
772                        spans.push(span_info);
773                    } // __NOOP generates no bytecode
774                }
775                IRByteType::Statement(s) => {
776                    let (push_bytes, mut push_spans) = statement_gen(
777                        evm_version,
778                        s,
779                        contract,
780                        macro_def,
781                        scope_mgr,
782                        &mut offset,
783                        &mut jump_table,
784                        &mut label_indices,
785                        &mut table_instances,
786                        &mut utilized_tables,
787                        &mut embedded_tables,
788                        starting_offset,
789                        relax_jumps,
790                    )?;
791                    // Use the spans from statement_gen (which preserves nested macro spans)
792                    // instead of duplicating the parent's span
793                    spans.append(&mut push_spans);
794                    bytes.extend(push_bytes);
795                }
796                IRByteType::ArgCall(parent_macro_name, arg_name) => {
797                    let bytes_before = bytes.len();
798                    // Bubble up arg call by looking through the previous scopes.
799                    // Once the arg value is found, add it to `bytes`
800                    bubble_arg_call(
801                        evm_version,
802                        parent_macro_name.to_owned(),
803                        arg_name.to_owned(),
804                        &mut bytes,
805                        macro_def,
806                        contract,
807                        scope_mgr,
808                        &mut offset,
809                        &mut jump_table,
810                        &mut table_instances,
811                        &mut utilized_tables,
812                        ir_byte.span,
813                        relax_jumps,
814                    )?;
815                    // Add span for each byte segment added by bubble_arg_call
816                    let bytes_added = bytes.len() - bytes_before;
817                    for _ in 0..bytes_added {
818                        spans.push(span_info);
819                    }
820                }
821            }
822        }
823
824        // Note: We don't pop from scope_mgr here because it's either:
825        // 1. Done by the caller after the recursive call returns (for inline macros)
826        // 2. Done at the end of this function (for top-level or function generation)
827
828        // Add functions (outlined macros) to the end of the bytecode if the scope length == 1
829        // (i.e., we're at the top level of recursion)
830        let is_top_level = scope_mgr.depth() == 1;
831        if is_top_level {
832            let bytes_before = bytes.len();
833            // A test may end by running off its code, which must stop instead of entering the
834            // functions and code tables appended after it
835            let has_appended_code = contract.macros.values().any(|m| m.outlined) || !utilized_tables.is_empty();
836            if macro_def.test && has_appended_code {
837                bytes.push_with_offset(offset, Bytes::Raw(Opcode::Stop.to_string()));
838                offset += 1;
839            }
840            bytes = Codegen::append_functions(
841                evm_version,
842                contract,
843                scope_mgr,
844                &mut offset,
845                &mut jump_table,
846                &mut label_indices,
847                &mut table_instances,
848                bytes,
849            )?;
850            // Add empty spans for appended functions (these don't have direct source mapping)
851            let bytes_added = bytes.len() - bytes_before;
852            for _ in 0..bytes_added {
853                spans.push(None);
854            }
855        } else {
856            // If the scope length is > 1, we're processing a child macro. Since we're done
857            // with it, it can be popped.
858            scope_mgr.pop_macro();
859        }
860
861        // `__codesize` self-references of this macro measure this invocation, which is complete now
862        bytes.measure_circular_codesize(&macro_def.name, start_offset, start_offset + bytes.total_byte_size());
863
864        // Fill JUMPDEST placeholders. Resizing a jump moves all code after it, so jumps are resized
865        // and resolved once by the top-level macro, when all code is laid out. Nested macros only
866        // settle the width of their own `__codesize` self-references and pass their jumps up.
867        let segment_count = bytes.len();
868        let FilledJumps { mut bytes, unmatched_jumps, label_indices, offset_mapping } = if is_top_level {
869            Codegen::fill_unmatched(bytes, &jump_table, &label_indices, relax_jumps)?
870        } else {
871            let widened = Codegen::widen_jump_offsets(bytes, &JumpTable::new(), &label_indices);
872            let unmatched_jumps = remap_jump_table(&jump_table, &widened.offset_mapping)
873                .into_iter()
874                .flat_map(|(offset, jumps)| jumps.into_iter().map(move |jump| Jump { bytecode_index: offset, ..jump }))
875                .collect();
876            FilledJumps {
877                bytes: widened.bytes,
878                unmatched_jumps,
879                label_indices: widened.label_indices,
880                offset_mapping: widened.offset_mapping,
881            }
882        };
883        // `__tablestart` placeholders are filled later by position, so move them with the resized code
884        for table_instance in table_instances.iter_mut() {
885            table_instance.bytecode_index = remap_offset(&offset_mapping, table_instance.bytecode_index);
886        }
887
888        // Adjust spans if bytes changed
889        if bytes.len() != segment_count {
890            // If the segment count changed, we need to rebuild the spans vector
891            // For now, just ensure it has the same length
892            while spans.len() < bytes.len() {
893                spans.push(None);
894            }
895        }
896
897        // Fill in `__codesize` self-references. Values inside nested macros are provisional: the
898        // top-level macro fills them again after resizing all jumps.
899        let growth = bytes.resolve_circular_codesize();
900        debug_assert_eq!(growth, 0, "circular codesize placeholders must be widened before jump resolution");
901        scope_mgr.codesizes.insert(macro_def.name.clone(), bytes.total_byte_size());
902
903        tracing::info!(target: "codegen", "BytecodeRes created with {} bytes and {} spans", bytes.len(), spans.len());
904        Ok(BytecodeRes { bytes, spans, label_indices, unmatched_jumps, table_instances, utilized_tables, embedded_tables })
905    }
906
907    /// Shrinks PUSH2 jumps to PUSH1 where the target fits in one byte.
908    ///
909    /// Jump relaxation for span-dependent instructions ("start big and shrink"), an iterative
910    /// variant of the technique described in Thomas G. Szymanski, "Assembling Code for Machines
911    /// with Span-Dependent Instructions", CACM 21(4), 1978, p. 300-308.
912    /// See also: <https://www.complang.tuwien.ac.at/anton/assembling-span-dependent.html>
913    ///
914    /// Shrinking only moves code backwards, so it never pushes a target out of range of a jump that
915    /// was widened before.
916    ///
917    /// Returns the relaxed bytecode, updated label indices, and a mapping from original to new
918    /// segment offsets.
919    pub fn relax_jump_offsets(bytes: BytecodeSegments, jump_table: &JumpTable, label_indices: &LabelIndices) -> ResizedBytecode {
920        Self::resize_span_dependent_pushes(bytes, jump_table, label_indices, ResizeMode::Shrink)
921    }
922
923    /// Widens jumps and self-referencing `__codesize` pushes whose value no longer fits.
924    ///
925    /// Jumps start as PUSH2 and self-referencing `__codesize` pushes as PUSH1. Code beyond 64 KiB
926    /// (possible in initcode from Amsterdam) needs PUSH3 targets, and a macro's own size can need a
927    /// wider push than first assumed. Widening moves all following code, so this repeats until no
928    /// push changes ("start small and grow"); it always terminates because pushes only grow.
929    ///
930    /// Returns the widened bytecode, updated label indices, and a mapping from original to new
931    /// segment offsets.
932    pub fn widen_jump_offsets(bytes: BytecodeSegments, jump_table: &JumpTable, label_indices: &LabelIndices) -> ResizedBytecode {
933        Self::resize_span_dependent_pushes(bytes, jump_table, label_indices, ResizeMode::Widen)
934    }
935
936    /// Repeatedly resizes span-dependent pushes until their widths are stable.
937    ///
938    /// After every round that changed a width, segment offsets and label positions are recomputed,
939    /// since a different width moves all code that follows.
940    fn resize_span_dependent_pushes(
941        mut bytes: BytecodeSegments,
942        jump_table: &JumpTable,
943        label_indices: &LabelIndices,
944        mode: ResizeMode,
945    ) -> ResizedBytecode {
946        // The jump table is keyed by the original segment offsets. Code of a nested macro does not
947        // start at 0, so recomputed offsets are relative to its first segment.
948        let original_offsets: Vec<usize> = bytes.iter().map(|segment| segment.offset).collect();
949        let base_offset = original_offsets.first().copied().unwrap_or(0);
950        let mut current_label_indices = label_indices.clone();
951        let mut cumulative_offset_mapping: OffsetMapping = original_offsets.iter().map(|&o| (o, o)).collect();
952        // Shrinking is an optimization and may stop early; widening must reach a fixed point and
953        // widens at least one push per round, so it is bounded by the number of segments
954        let max_iterations = match mode {
955            ResizeMode::Shrink => JUMP_RELAXATION_MAX_ITERATIONS,
956            ResizeMode::Widen => bytes.len() + 1,
957        };
958
959        let mut iteration = 0;
960        let mut changed = true;
961        while changed && iteration < max_iterations {
962            changed = false;
963            iteration += 1;
964
965            let offsets: Vec<usize> = bytes.calculate_offsets().into_iter().map(|offset| base_offset + offset).collect();
966            let total_length = bytes.total_byte_size();
967            let end_offset = base_offset + total_length;
968
969            for (segment_idx, segment) in bytes.iter_mut().enumerate() {
970                match segment.bytes {
971                    Bytes::JumpPlaceholder(ref mut placeholder) => {
972                        let Some(jump) = jump_table.get(&original_offsets[segment_idx]).and_then(|jumps| jumps.first()) else {
973                            continue;
974                        };
975                        // Unknown labels are reported by `fill_unmatched` after resizing
976                        let Ok(Some(target_offset)) = current_label_indices.get(&placeholder.label, &jump.scope_id) else {
977                            continue;
978                        };
979                        let new_opcode = match mode {
980                            ResizeMode::Widen if !placeholder.push_opcode.can_represent(target_offset) => {
981                                Some(PushOpcode::optimal_for(target_offset))
982                            }
983                            // A forward target at exactly 0x100 moves to 0xff once this jump shrinks
984                            ResizeMode::Shrink
985                                if placeholder.push_opcode == PushOpcode::Push2
986                                    && (target_offset <= 0xFF || (target_offset == 0x100 && target_offset > offsets[segment_idx])) =>
987                            {
988                                Some(PushOpcode::Push1)
989                            }
990                            _ => None,
991                        };
992                        if let Some(new_opcode) = new_opcode {
993                            tracing::debug!(
994                                target: "codegen",
995                                "Resizing jump to '{}' at offset {} from {:?} to {:?} (target={})",
996                                placeholder.label,
997                                offsets[segment_idx],
998                                placeholder.push_opcode,
999                                new_opcode,
1000                                target_offset
1001                            );
1002                            placeholder.push_opcode = new_opcode;
1003                            changed = true;
1004                        }
1005                    }
1006                    Bytes::CircularCodesizePlaceholder(ref mut placeholder) if mode == ResizeMode::Widen => {
1007                        let size = placeholder.size(
1008                            |(start, end)| {
1009                                current_offset(&original_offsets, &offsets, end_offset, end)
1010                                    - current_offset(&original_offsets, &offsets, end_offset, start)
1011                            },
1012                            total_length,
1013                        );
1014                        if !placeholder.push_opcode.can_represent(size) {
1015                            placeholder.push_opcode = PushOpcode::optimal_for(size);
1016                            changed = true;
1017                        }
1018                    }
1019                    _ => {}
1020                }
1021            }
1022
1023            if changed {
1024                // Recalculate all positions based on current segment sizes
1025                let new_offsets: Vec<usize> = bytes.calculate_offsets().into_iter().map(|offset| base_offset + offset).collect();
1026                let offset_mapping: OffsetMapping =
1027                    bytes.iter().zip(&new_offsets).map(|(segment, &new_offset)| (segment.offset, new_offset)).collect();
1028
1029                for final_offset in cumulative_offset_mapping.values_mut() {
1030                    *final_offset = remap_offset(&offset_mapping, *final_offset);
1031                }
1032                current_label_indices.update_offsets(&offset_mapping);
1033                for (segment, new_offset) in bytes.iter_mut().zip(new_offsets) {
1034                    segment.offset = new_offset;
1035                }
1036            }
1037        }
1038
1039        if changed {
1040            tracing::warn!(target: "codegen", "{:?} of span-dependent pushes hit max iterations ({})", mode, max_iterations);
1041        } else {
1042            tracing::info!(target: "codegen", "{:?} of span-dependent pushes converged after {} iterations", mode, iteration);
1043        }
1044
1045        // Move the code measured by `__codesize` self-references along with the resized code
1046        let final_offsets: Vec<usize> = bytes.iter().map(|segment| segment.offset).collect();
1047        let final_end_offset = base_offset + bytes.total_byte_size();
1048        for segment in bytes.iter_mut() {
1049            if let Bytes::CircularCodesizePlaceholder(ref mut placeholder) = segment.bytes
1050                && let Some((start, end)) = placeholder.measured_range
1051            {
1052                placeholder.measured_range = Some((
1053                    current_offset(&original_offsets, &final_offsets, final_end_offset, start),
1054                    current_offset(&original_offsets, &final_offsets, final_end_offset, end),
1055                ));
1056            }
1057        }
1058
1059        ResizedBytecode { bytes, label_indices: current_label_indices, offset_mapping: cumulative_offset_mapping }
1060    }
1061
1062    /// Resolves jump placeholders to their target offsets with optional size optimization.
1063    ///
1064    /// Replaces jump placeholders with actual bytecode offsets by looking up labels in the
1065    /// label index. Jumps whose target does not fit in PUSH2 are widened first; when
1066    /// `relax_jumps` is enabled, PUSH2 jumps are then shrunk to PUSH1 where targets fit within a
1067    /// single byte (0-255).
1068    ///
1069    /// ## Arguments
1070    ///
1071    /// * `bytes` - Bytecode segments with jump placeholders
1072    /// * `jump_table` - Jump metadata indexed by bytecode offset
1073    /// * `label_indices` - Scoped label positions
1074    /// * `relax_jumps` - Enable jump size optimization
1075    ///
1076    /// ## Returns
1077    ///
1078    /// Returns resolved bytecode, any unmatched jumps, the final label positions, and a mapping
1079    /// from original to final segment offsets, or an error for label conflicts or invalid jump
1080    /// targets.
1081    pub fn fill_unmatched(
1082        bytes: BytecodeSegments,
1083        jump_table: &JumpTable,
1084        label_indices: &LabelIndices,
1085        relax_jumps: bool,
1086    ) -> Result<FilledJumps, CodegenError> {
1087        // Widen pushes that do not fit first, then optionally shrink jumps to PUSH1. Shrinking only
1088        // moves code backwards, so widened jumps keep fitting.
1089        let ResizedBytecode { mut bytes, mut label_indices, mut offset_mapping } =
1090            Self::widen_jump_offsets(bytes, jump_table, label_indices);
1091        if relax_jumps {
1092            let relaxed = Self::relax_jump_offsets(bytes, &remap_jump_table(jump_table, &offset_mapping), &label_indices);
1093            bytes = relaxed.bytes;
1094            label_indices = relaxed.label_indices;
1095            for final_offset in offset_mapping.values_mut() {
1096                *final_offset = remap_offset(&relaxed.offset_mapping, *final_offset);
1097            }
1098        }
1099
1100        // Resolve all jumps using the new typed approach
1101        let resized_jump_table = remap_jump_table(jump_table, &offset_mapping);
1102        let unmatched_jumps = bytes.resolve_jumps(&resized_jump_table, &label_indices).map_err(|label_error| {
1103            // Match on the typed error variants
1104            match label_error {
1105                LabelError::DuplicateLabelAcrossSiblings(label_name) => {
1106                    // Find the first jump to get its span
1107                    let span = jump_table
1108                        .values()
1109                        .flat_map(|jumps| jumps.iter())
1110                        .find(|jump| jump.label == label_name)
1111                        .map(|jump| jump.span.clone_box())
1112                        .unwrap_or_else(|| Box::new(AstSpan::default()));
1113
1114                    CodegenError { kind: CodegenErrorKind::DuplicateLabelAcrossSiblings(label_name), span, token: None }
1115                }
1116                LabelError::JumpTargetTooLarge { label, target, opcode } => {
1117                    // Find the jump to get its span
1118                    let span = jump_table
1119                        .values()
1120                        .flat_map(|jumps| jumps.iter())
1121                        .find(|jump| jump.label == label)
1122                        .map(|jump| jump.span.clone_box())
1123                        .unwrap_or_else(|| Box::new(AstSpan::default()));
1124
1125                    CodegenError { kind: CodegenErrorKind::JumpTargetTooLarge { label, target, opcode }, span, token: None }
1126                }
1127                LabelError::DuplicateLabelInScope(label_name) => {
1128                    // This shouldn't happen during jump resolution, but handle it anyway
1129                    let span = jump_table
1130                        .values()
1131                        .flat_map(|jumps| jumps.iter())
1132                        .find(|jump| jump.label == label_name)
1133                        .map(|jump| jump.span.clone_box())
1134                        .unwrap_or_else(|| Box::new(AstSpan::default()));
1135
1136                    CodegenError { kind: CodegenErrorKind::DuplicateLabelInScope(label_name), span, token: None }
1137                }
1138            }
1139        })?;
1140
1141        // Log unmatched jumps
1142        for jump in &unmatched_jumps {
1143            tracing::warn!(
1144                target: "codegen",
1145                "UNMATCHED JUMP LABEL \"{}\" AT BYTECODE INDEX {} (scope_id={:?})",
1146                jump.label, jump.bytecode_index, jump.scope_id
1147            );
1148        }
1149
1150        // Validate __ASSERT_PC placeholders using post-relaxation offsets
1151        // This must happen AFTER jump relaxation to ensure correct offset validation
1152        // The segment.offset has been updated by relaxation, so we can use it directly
1153        let offsets = bytes.calculate_offsets();
1154        for (idx, segment) in bytes.iter().enumerate() {
1155            if let Bytes::AssertPcPlaceholder(data) = &segment.bytes {
1156                // Use the calculated offset which accounts for all relaxation
1157                let actual_offset = offsets[idx];
1158
1159                if actual_offset != data.expected_offset {
1160                    tracing::error!(
1161                        target: "codegen",
1162                        "PC assertion failed: expected 0x{:x}, got 0x{:x}",
1163                        data.expected_offset,
1164                        actual_offset
1165                    );
1166                    return Err(CodegenError {
1167                        kind: CodegenErrorKind::AssertPcFailed(data.expected_offset, actual_offset),
1168                        span: data.span.clone_box(),
1169                        token: None,
1170                    });
1171                }
1172
1173                tracing::debug!(
1174                    target: "codegen",
1175                    "PC assertion passed: position is 0x{:x} as expected (after relaxation)",
1176                    actual_offset
1177                );
1178            }
1179        }
1180
1181        // Filter out __ASSERT_PC placeholders since they don't generate bytecode
1182        let mut filtered_bytes = BytecodeSegments::new();
1183        for segment in bytes.iter() {
1184            if !matches!(segment.bytes, Bytes::AssertPcPlaceholder(_)) {
1185                filtered_bytes.push(segment.clone());
1186            }
1187        }
1188
1189        Ok(FilledJumps { bytes: filtered_bytes, unmatched_jumps, label_indices, offset_mapping })
1190    }
1191
1192    /// Helper associated function to append functions to the end of the bytecode.
1193    ///
1194    /// ## Overview
1195    ///
1196    /// Iterates over the contract's functions, generates their bytecode, fills unmatched jumps &
1197    /// label indices, and appends the functions' bytecode to the end of the contract's bytecode.
1198    ///
1199    /// On success, passes ownership of `bytes` back to the caller.
1200    /// On failure, returns a CodegenError.
1201    #[allow(clippy::too_many_arguments)]
1202    pub fn append_functions<'a>(
1203        evm_version: &EVMVersion,
1204        contract: &'a Contract,
1205        scope_mgr: &mut ScopeManager<'a>,
1206        offset: &mut usize,
1207        jump_table: &mut JumpTable,
1208        label_indices: &mut LabelIndices,
1209        table_instances: &mut Jumps,
1210        mut bytes: BytecodeSegments,
1211    ) -> Result<BytecodeSegments, CodegenError> {
1212        // First pass: Pre-register all function goto_ labels at their future offsets
1213        // This allows nested function calls to resolve correctly
1214        let functions: Vec<_> = contract.macros.values().filter(|m| m.outlined).collect();
1215        let mut current_offset = *offset;
1216        let goto_scope_id = huff_neo_utils::scope::ScopeId::new(vec![], 0);
1217
1218        // We need to calculate offsets without actually generating bytecode yet
1219        // Store the calculated offset for each function
1220        let mut function_offsets: Vec<(&MacroDefinition, usize)> = Vec::new();
1221
1222        for macro_def in &functions {
1223            // Register the goto_ label at the current offset
1224            if let Err(err_msg) = label_indices.insert(format!("goto_{}", macro_def.name.clone()), current_offset, &goto_scope_id) {
1225                // This shouldn't happen for auto-generated goto_ labels - indicates a compiler bug
1226                tracing::error!(target: "codegen", "INTERNAL ERROR: Duplicate goto_ label for function '{}': {}", macro_def.name, err_msg);
1227                return Err(CodegenError {
1228                    kind: CodegenErrorKind::DuplicateLabelInScope(format!("goto_{}", macro_def.name)),
1229                    span: macro_def.span.clone_box(),
1230                    token: None,
1231                });
1232            }
1233
1234            function_offsets.push((macro_def, current_offset));
1235
1236            // Estimate the size of this function for offset calculation
1237            // We'll need to generate it to get the exact size, but for now we need a placeholder
1238            // This is a chicken-and-egg problem: we need offsets to generate code, but need code to know offsets
1239            // Solution: Generate into a temporary buffer first
1240            scope_mgr.push_macro(macro_def, current_offset);
1241            let temp_res = Codegen::macro_to_bytecode(evm_version, macro_def, contract, scope_mgr, current_offset + 1, false)?;
1242            let macro_code_len = temp_res.bytes.iter().map(|seg| seg.bytes.len()).sum::<usize>();
1243            let stack_swaps_len = macro_def.returns;
1244            current_offset += macro_code_len + stack_swaps_len + 2; // JUMPDEST + MACRO_CODE_LEN + stack_swaps.len() + JUMP
1245            // Note: macro_to_bytecode already popped the macro (depth > 1)
1246        }
1247
1248        // Second pass: Actually generate the function bytecode
1249        // Now all goto_ labels are registered, so nested calls will resolve
1250        for (macro_def, func_offset) in function_offsets {
1251            // Push the function to the scope
1252            scope_mgr.push_macro(macro_def, func_offset);
1253
1254            // Add 1 to starting offset to account for the JUMPDEST opcode
1255            let mut res = Codegen::macro_to_bytecode(evm_version, macro_def, contract, scope_mgr, func_offset + 1, false)?;
1256
1257            for j in res.unmatched_jumps.iter_mut() {
1258                let new_index = j.bytecode_index;
1259                j.bytecode_index = 0;
1260                let mut new_jumps = if let Some(jumps) = jump_table.get(&new_index) { jumps.clone() } else { vec![] };
1261                new_jumps.push(j.clone());
1262                jump_table.insert(new_index, new_jumps);
1263            }
1264            table_instances.extend(res.table_instances);
1265            label_indices.extend(res.label_indices);
1266
1267            let macro_code_len = res.bytes.iter().map(|seg| seg.bytes.len()).sum::<usize>();
1268
1269            // Get necessary swap ops to reorder stack
1270            // PC of the return jumpdest should be above the function's outputs on the stack
1271            let stack_swaps = (0..macro_def.returns).map(|i| format!("{:02x}", 0x90 + i)).collect::<Vec<_>>();
1272
1273            // Insert JUMPDEST, stack swaps, and final JUMP back to the location of invocation.
1274            bytes.push_with_offset(func_offset, Bytes::Raw(Opcode::Jumpdest.to_string()));
1275            res.bytes.push_with_offset(func_offset + macro_code_len + 1, Bytes::Raw(format!("{}{}", stack_swaps.join(""), Opcode::Jump)));
1276            bytes.extend(res.bytes);
1277
1278            // Note: The function is already popped by macro_to_bytecode at the end
1279            // because depth > 1 when generating functions. We should NOT pop again here.
1280        }
1281
1282        *offset = current_offset;
1283        Ok(bytes)
1284    }
1285
1286    /// Back-compat shim for assembly from raw bytecode strings.
1287    ///
1288    /// Synthesizes [`MainBytecodeOutput`] and [`ConstructorMacroBytecode`] from raw hex strings and
1289    /// forwards to [`Codegen::assemble_artifact`]. Use this when you have pre-baked main and
1290    /// constructor bytecode (e.g. existing snapshot tests) and do not need the table-dedup pathway.
1291    ///
1292    /// Behaves identically to the structured API for inputs that contain no tables.
1293    #[allow(clippy::too_many_arguments)]
1294    pub fn churn(
1295        &mut self,
1296        evm_version: &EVMVersion,
1297        file: Arc<FileSource>,
1298        args: Vec<DynSolValue>,
1299        main_bytecode: &str,
1300        constructor_bytecode: &str,
1301        has_custom_bootstrap: bool,
1302        main_source_map: Option<Vec<SourceMapEntry>>,
1303        constructor_source_map: Option<Vec<SourceMapEntry>>,
1304        no_size_limit: bool,
1305    ) -> Result<Artifact, CodegenError> {
1306        let main = MainBytecodeOutput {
1307            bytecode: main_bytecode.to_string(),
1308            source_map: main_source_map.unwrap_or_default(),
1309            table_offsets: HashMap::new(),
1310        };
1311        let constructor = if constructor_bytecode.is_empty() {
1312            None
1313        } else {
1314            let mut bytes = BytecodeSegments::new();
1315            bytes.push_with_offset(0, Bytes::Raw(constructor_bytecode.to_string()));
1316            let bytecode_res = BytecodeRes { bytes, ..Default::default() };
1317            Some(ConstructorMacroBytecode { bytecode_res, has_custom_bootstrap })
1318        };
1319        // The shim does not have a fully-populated Contract — but with no utilized tables in the
1320        // synthesized constructor BytecodeRes, gen_table_bytecode never inspects table definitions.
1321        let contract = Contract::default();
1322        let mut artifact = self.assemble_artifact(evm_version, file, &contract, args, main, constructor, no_size_limit)?;
1323        // Preserve the caller-supplied constructor source map (the shim doesn't generate one).
1324        artifact.constructor_map = constructor_source_map;
1325        Ok(artifact)
1326    }
1327
1328    /// Generate a codegen artifact.
1329    ///
1330    /// Deployment bytecode is laid out as:
1331    ///   `[ctor_body][bootstrap?][main_bytecode][ctor_only_tables][constructor_args]`
1332    ///
1333    /// A `__tablestart(T)` reference inside the constructor is resolved against a runtime-resident
1334    /// copy of `T` when `main` already emits one (no duplicate); otherwise the table is appended
1335    /// once to the deployment tail and the constructor's reference points there.
1336    ///
1337    /// # Arguments
1338    ///
1339    /// * `evm_version` — target EVM version; selects the runtime and initcode size limits.
1340    /// * `contract` — contract AST with table sizes populated by `update_table_size`. Used to read
1341    ///   table definitions during constructor table finalization.
1342    /// * `args` — encoded constructor arguments, appended at the very end of the deployment bytes.
1343    /// * `main` — main/runtime bytecode plus its body length and per-table offsets within the runtime.
1344    /// * `constructor` — phase-1 constructor macro output; `None` when the contract has no
1345    ///   `CONSTRUCTOR` macro (deployment becomes just `bootstrap + main`).
1346    /// * `no_size_limit` — skip the runtime and initcode size checks.
1347    #[allow(clippy::too_many_arguments)]
1348    pub fn assemble_artifact(
1349        &mut self,
1350        evm_version: &EVMVersion,
1351        file: Arc<FileSource>,
1352        contract: &Contract,
1353        args: Vec<DynSolValue>,
1354        main: MainBytecodeOutput,
1355        constructor: Option<ConstructorMacroBytecode>,
1356        no_size_limit: bool,
1357    ) -> Result<Artifact, CodegenError> {
1358        let artifact: &mut Artifact = if let Some(art) = &mut self.artifact {
1359            art
1360        } else {
1361            self.artifact = Some(Artifact::default());
1362            self.artifact.as_mut().unwrap()
1363        };
1364
1365        let MainBytecodeOutput { bytecode: main_bytecode, source_map: main_source_map, table_offsets: main_table_offsets } = main;
1366
1367        let contract_length = main_bytecode.len() / 2;
1368
1369        // Check the runtime size limit (EIP-170, raised by EIP-7954 from Amsterdam)
1370        if !no_size_limit && contract_length > evm_version.max_code_size() {
1371            return Err(CodegenError {
1372                kind: CodegenErrorKind::ContractSizeLimitExceeded(contract_length, evm_version.max_code_size()),
1373                span: AstSpan(vec![Span { start: 0, end: 0, file: Some(file) }]).boxed(),
1374                token: None,
1375            });
1376        }
1377
1378        // Compute the constructor body length and bootstrap size first, since the bootstrap size
1379        // depends on the body length but not on constructor-only table sizes (under the new layout
1380        // where constructor-only tables sit past main).
1381        let (ctor_body_len, has_custom_bootstrap) = match &constructor {
1382            // `Bytes::len()` returns size in bytes (not hex chars), so no further division needed.
1383            Some(c) => (c.bytecode_res.bytes.iter().map(|seg| seg.bytes.len()).sum::<usize>(), c.has_custom_bootstrap),
1384            None => (0, false),
1385        };
1386
1387        // Bootstrap size depends only on body lengths (not table sizes).
1388        // Suppressed entirely when the user supplies their own bootstrap (constructor contains RETURN).
1389        let (bootstrap_code, effective_bootstrap_size) =
1390            if has_custom_bootstrap { (String::default(), 0) } else { deploy_bootstrap(contract_length, ctor_body_len) };
1391
1392        // Finalize constructor table resolution now that the layout is known.
1393        // Constructor-only tables sit past main_bytecode in the deployment tail.
1394        // Shared tables resolve into the runtime-resident copy at runtime_start + main_offset.
1395        let runtime_start_in_deployment = ctor_body_len + effective_bootstrap_size;
1396        // gen_table_bytecode internally adds body_len; only shift by what comes between body end
1397        // and the constructor-only tables (bootstrap + main_bytecode).
1398        let ctor_tables_base_offset = effective_bootstrap_size + contract_length;
1399        let external_table_offsets: HashMap<String, usize> =
1400            main_table_offsets.iter().map(|(name, off)| (name.clone(), runtime_start_in_deployment + off)).collect();
1401
1402        let args_start_span = constructor.as_ref().and_then(|c| args_start_span(&c.bytecode_res)).cloned();
1403        let (mut constructor_body, ctor_only_tables, constructor_source_map) = match constructor {
1404            Some(c) => {
1405                let out = Codegen::gen_table_bytecode(contract, c.bytecode_res, ctor_tables_base_offset, &external_table_offsets)?;
1406                (out.body, out.tables, Some(out.source_map))
1407            }
1408            None => (String::new(), String::new(), None),
1409        };
1410
1411        // `__argsstart()` in the constructor pushes the offset the arguments are appended at
1412        let args_start = runtime_start_in_deployment + contract_length + ctor_only_tables.len() / 2;
1413        if constructor_body.contains(ARGS_START_PLACEHOLDER) {
1414            if args_start > 0xFFFF {
1415                return Err(CodegenError {
1416                    kind: CodegenErrorKind::OffsetExceedsTwoBytes("constructor arguments for __argsstart()".to_string(), args_start),
1417                    span: args_start_span.unwrap_or_else(|| AstSpan(vec![Span { start: 0, end: 0, file: Some(file) }])).boxed(),
1418                    token: None,
1419                });
1420            }
1421            constructor_body = constructor_body.replace(ARGS_START_PLACEHOLDER, &format!("{}{args_start:04x}", Opcode::Push2));
1422        }
1423
1424        let constructor_args = Codegen::abi_encode_constructor_args(args);
1425        let deployment_bytecode = format!("{constructor_body}{bootstrap_code}{main_bytecode}{ctor_only_tables}{constructor_args}");
1426
1427        // Check the initcode size limit (EIP-3860, raised by EIP-7954 from Amsterdam)
1428        let initcode_length = deployment_bytecode.len() / 2;
1429        if !no_size_limit && initcode_length > evm_version.max_initcode_size() {
1430            return Err(CodegenError {
1431                kind: CodegenErrorKind::InitcodeSizeLimitExceeded(initcode_length, evm_version.max_initcode_size()),
1432                span: AstSpan(vec![Span { start: 0, end: 0, file: Some(file) }]).boxed(),
1433                token: None,
1434            });
1435        }
1436
1437        artifact.bytecode = deployment_bytecode.to_lowercase();
1438        artifact.runtime = main_bytecode.to_lowercase();
1439        artifact.has_custom_bootstrap = has_custom_bootstrap;
1440        artifact.file = file;
1441
1442        // Set source maps if provided
1443        artifact.runtime_map = Some(main_source_map);
1444        artifact.constructor_map = constructor_source_map;
1445
1446        Ok(artifact.clone())
1447    }
1448
1449    /// Reads constructor arguments given as text. With the declared constructor parameter types,
1450    /// each argument is read as its type and the count must match; otherwise each type is guessed
1451    /// from the text. Without arguments nothing is appended, so the deployer can append them later.
1452    pub fn encode_constructor_args(args: &[String], types: Option<&[String]>) -> Result<Vec<DynSolValue>, CodegenError> {
1453        let invalid =
1454            |msg: String| CodegenError { kind: CodegenErrorKind::InvalidConstructorArgs(msg), span: AstSpan(vec![]).boxed(), token: None };
1455        if args.is_empty() {
1456            return Ok(vec![]);
1457        }
1458        let Some(types) = types else {
1459            return args.iter().map(|arg| EToken::try_from(arg.clone()).map(|token| token.0).map_err(invalid)).collect();
1460        };
1461        if args.len() != types.len() {
1462            return Err(invalid(format!(
1463                "the constructor takes {} argument(s) ({}), but {} were given",
1464                types.len(),
1465                types.join(", "),
1466                args.len()
1467            )));
1468        }
1469        args.iter()
1470            .zip(types)
1471            .map(|(arg, ty)| {
1472                DynSolType::parse(ty).and_then(|ty| ty.coerce_str(arg)).map_err(|e| invalid(format!("\"{arg}\" is not a valid {ty}: {e}")))
1473            })
1474            .collect()
1475    }
1476
1477    /// The parameter types of the contract's `#define function constructor(...)`, if it declares one
1478    pub fn declared_constructor_types(contract: &Contract) -> Option<Vec<String>> {
1479        contract
1480            .functions
1481            .iter()
1482            .find(|function| function.name.eq_ignore_ascii_case("constructor"))
1483            .map(|function| function.inputs.iter().map(|input| input.arg_type.clone().unwrap_or_default()).collect())
1484    }
1485
1486    /// Encodes constructor arguments as hex, the way Solidity's `abi.encode` does
1487    pub fn abi_encode_constructor_args(args: Vec<DynSolValue>) -> String {
1488        if args.is_empty() {
1489            return String::new();
1490        }
1491        hex::encode(DynSolValue::Tuple(args).abi_encode_params())
1492    }
1493
1494    /// Export
1495    ///
1496    /// Writes a Codegen Artifact out to the specified file.
1497    ///
1498    /// # Arguments
1499    ///
1500    /// * `out` - Output location to write the serialized json artifact to.
1501    pub fn export(output: String, art: &Artifact) -> Result<(), CodegenError> {
1502        let serialized_artifact = serde_json::to_string_pretty(art).unwrap();
1503        // Try to create the parent directory
1504        let file_path = Path::new(&output);
1505        if let Some(p) = file_path.parent()
1506            && let Err(e) = fs::create_dir_all(p)
1507        {
1508            return Err(CodegenError {
1509                kind: CodegenErrorKind::IOError(e.to_string()),
1510                span: AstSpan(vec![Span {
1511                    start: 0,
1512                    end: 0,
1513                    file: Some(Arc::new(FileSource { path: output, source: None, access: None, dependencies: vec![] })),
1514                }])
1515                .boxed(),
1516                token: None,
1517            });
1518        }
1519        if let Err(e) = fs::write(file_path, serialized_artifact) {
1520            return Err(CodegenError {
1521                kind: CodegenErrorKind::IOError(e.to_string()),
1522                span: AstSpan(vec![Span {
1523                    start: 0,
1524                    end: 0,
1525                    file: Some(Arc::new(FileSource { path: output, source: None, access: None, dependencies: vec![] })),
1526                }])
1527                .boxed(),
1528                token: None,
1529            });
1530        }
1531        Ok(())
1532    }
1533
1534    /// Abi Generation
1535    ///
1536    /// Generates an ABI for the given Ast.
1537    /// Stores the generated ABI in the Codegen `artifact`.
1538    ///
1539    /// # Arguments
1540    ///
1541    /// * `ast` - The Contract Abstract Syntax Tree
1542    /// * `output` - An optional output path
1543    pub fn abi_gen(&mut self, ast: Contract, output: Option<String>) -> Result<Abi, CodegenError> {
1544        let abi: Abi = ast.into();
1545
1546        // Set the abi on self
1547        let art: &Artifact = match &mut self.artifact {
1548            Some(artifact) => {
1549                artifact.abi = Some(abi.clone());
1550                artifact
1551            }
1552            None => {
1553                self.artifact = Some(Artifact { abi: Some(abi.clone()), ..Default::default() });
1554                self.artifact.as_ref().unwrap()
1555            }
1556        };
1557
1558        // If an output's specified, write the artifact out
1559        if let Some(o) = output {
1560            // Error message is sent to tracing in `export` if an error occurs
1561            Codegen::export(o, art)?;
1562        }
1563
1564        // Return the abi
1565        Ok(abi)
1566    }
1567}