Skip to main content

asm_rs/
assembler.rs

1//! Public assembler API — builder pattern and one-shot assembly.
2//!
3//! This module ties together the lexer, parser, encoder, and linker
4//! into a fluent API for assembling code.
5
6#[allow(unused_imports)]
7use alloc::format;
8use alloc::string::String;
9use alloc::string::ToString;
10#[allow(unused_imports)]
11use alloc::vec;
12use alloc::vec::Vec;
13
14use crate::encoder;
15use crate::error::{AsmError, Span};
16use crate::ir::*;
17use crate::lexer;
18use crate::linker::{AppliedRelocation, Linker};
19use crate::parser;
20use crate::preprocessor::Preprocessor;
21
22/// The result of a successful assembly operation.
23#[derive(Debug, Clone)]
24#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
25#[must_use]
26pub struct AssemblyResult {
27    /// The assembled machine code.
28    bytes: Vec<u8>,
29    /// Label addresses (name → absolute address).
30    labels: Vec<(String, u64)>,
31    /// Applied relocations in the output.
32    relocations: Vec<AppliedRelocation>,
33    /// Base address used during assembly.
34    base_address: u64,
35    /// Source text annotations: `(output_offset, source_text)` for listing.
36    source_annotations: Vec<(u64, String)>,
37}
38
39impl AssemblyResult {
40    /// Get the assembled bytes.
41    ///
42    /// # Examples
43    ///
44    /// ```
45    /// use asm_rs::{Assembler, Arch};
46    ///
47    /// let mut asm = Assembler::new(Arch::X86_64);
48    /// asm.emit("nop")?;
49    /// let result = asm.finish()?;
50    /// assert_eq!(result.bytes(), &[0x90]);
51    /// # Ok::<(), asm_rs::AsmError>(())
52    /// ```
53    #[must_use]
54    pub fn bytes(&self) -> &[u8] {
55        &self.bytes
56    }
57
58    /// Consume and return the bytes.
59    ///
60    /// # Examples
61    ///
62    /// ```
63    /// use asm_rs::{Assembler, Arch};
64    ///
65    /// let mut asm = Assembler::new(Arch::X86_64);
66    /// asm.emit("ret")?;
67    /// let bytes = asm.finish()?.into_bytes();
68    /// assert_eq!(bytes, vec![0xC3]);
69    /// # Ok::<(), asm_rs::AsmError>(())
70    /// ```
71    #[must_use]
72    pub fn into_bytes(self) -> Vec<u8> {
73        self.bytes
74    }
75
76    /// Get the byte count.
77    ///
78    /// # Examples
79    ///
80    /// ```
81    /// use asm_rs::{Assembler, Arch};
82    ///
83    /// let mut asm = Assembler::new(Arch::X86_64);
84    /// asm.emit("nop\nret")?;
85    /// let result = asm.finish()?;
86    /// assert_eq!(result.len(), 2); // nop(1) + ret(1)
87    /// # Ok::<(), asm_rs::AsmError>(())
88    /// ```
89    #[must_use]
90    pub fn len(&self) -> usize {
91        self.bytes.len()
92    }
93
94    /// Whether the result is empty.
95    ///
96    /// # Examples
97    ///
98    /// ```
99    /// use asm_rs::{Assembler, Arch};
100    ///
101    /// let result = Assembler::new(Arch::X86_64).finish()?;
102    /// assert!(result.is_empty());
103    /// # Ok::<(), asm_rs::AsmError>(())
104    /// ```
105    #[must_use]
106    pub fn is_empty(&self) -> bool {
107        self.bytes.is_empty()
108    }
109
110    /// Get label addresses (name, absolute address), sorted by name.
111    ///
112    /// The ordering is part of the contract:
113    /// [`label_address()`](Self::label_address) binary-searches this slice.
114    ///
115    /// # Examples
116    ///
117    /// ```
118    /// use asm_rs::{Assembler, Arch};
119    ///
120    /// let mut asm = Assembler::new(Arch::X86_64);
121    /// asm.emit("start: nop\nend: ret")?;
122    /// let result = asm.finish()?;
123    /// let labels = result.labels();
124    /// assert!(labels.iter().any(|(name, _)| name == "start"));
125    /// assert!(labels.iter().any(|(name, _)| name == "end"));
126    /// # Ok::<(), asm_rs::AsmError>(())
127    /// ```
128    #[must_use]
129    pub fn labels(&self) -> &[(String, u64)] {
130        &self.labels
131    }
132
133    /// Look up a label address by name.
134    ///
135    /// Runs in O(log n): [`labels()`](Self::labels) is kept sorted by name, so
136    /// this binary-searches rather than scanning. That matters for the common
137    /// pattern of resolving many symbols out of one large assembly.
138    ///
139    /// # Examples
140    ///
141    /// ```
142    /// use asm_rs::{Assembler, Arch};
143    ///
144    /// let mut asm = Assembler::new(Arch::X86_64);
145    /// asm.emit("start: nop\nnop\nend: ret")?;
146    /// let result = asm.finish()?;
147    /// assert_eq!(result.label_address("start"), Some(0));
148    /// assert_eq!(result.label_address("end"), Some(2));
149    /// assert_eq!(result.label_address("missing"), None);
150    /// # Ok::<(), asm_rs::AsmError>(())
151    /// ```
152    #[must_use]
153    pub fn label_address(&self, name: &str) -> Option<u64> {
154        self.labels
155            .binary_search_by(|(n, _)| n.as_str().cmp(name))
156            .ok()
157            .map(|i| self.labels[i].1)
158    }
159
160    /// Get the applied relocations — where label references were patched.
161    ///
162    /// # Examples
163    ///
164    /// ```
165    /// use asm_rs::{Assembler, Arch};
166    ///
167    /// let mut asm = Assembler::new(Arch::X86_64);
168    /// asm.emit("target: jmp target")?;
169    /// let result = asm.finish()?;
170    /// let relocs = result.relocations();
171    /// assert!(!relocs.is_empty());
172    /// assert_eq!(relocs[0].label, "target");
173    /// # Ok::<(), asm_rs::AsmError>(())
174    /// ```
175    #[must_use]
176    pub fn relocations(&self) -> &[AppliedRelocation] {
177        &self.relocations
178    }
179
180    /// Get the base address used during assembly.
181    ///
182    /// # Examples
183    ///
184    /// ```
185    /// use asm_rs::{Assembler, Arch};
186    ///
187    /// let mut asm = Assembler::new(Arch::X86_64);
188    /// asm.base_address(0x1000);
189    /// asm.emit("nop")?;
190    /// let result = asm.finish()?;
191    /// assert_eq!(result.base_address(), 0x1000);
192    /// # Ok::<(), asm_rs::AsmError>(())
193    /// ```
194    #[must_use]
195    pub fn base_address(&self) -> u64 {
196        self.base_address
197    }
198
199    /// Produce a human-readable listing of address, hex bytes.
200    ///
201    /// Labels are shown on their own line with their resolved address.
202    /// Machine code is shown in rows of up to 8 bytes each.
203    ///
204    /// # Example output
205    ///
206    /// ```text
207    /// 00000000                  entry:
208    /// 00000000  55              push rbp
209    /// 00000001  4889E5          mov rbp, rsp
210    /// ```
211    #[must_use]
212    pub fn listing(&self) -> String {
213        use core::fmt::Write;
214
215        let mut out = String::new();
216        let base = self.base_address;
217
218        // First, collect labels sorted by address
219        let mut sorted_labels = self.labels.clone();
220        sorted_labels.sort_by_key(|(_, addr)| *addr);
221
222        // Build a map: offset → list of label names
223        let mut label_at: alloc::collections::BTreeMap<u64, Vec<&str>> =
224            alloc::collections::BTreeMap::new();
225        for (name, addr) in &sorted_labels {
226            label_at.entry(*addr).or_default().push(name);
227        }
228
229        // Build a map: offset → source text annotation
230        let mut source_at: alloc::collections::BTreeMap<u64, &str> =
231            alloc::collections::BTreeMap::new();
232        for (offset, text) in &self.source_annotations {
233            if !text.is_empty() {
234                source_at.insert(*offset, text);
235            }
236        }
237
238        // Collect all label offsets as split points (where we must break a chunk)
239        let mut split_offsets: alloc::collections::BTreeSet<u64> =
240            label_at.keys().copied().collect();
241
242        // Also split at source annotation offsets so each instruction gets its own line
243        for &ann_off in source_at.keys() {
244            split_offsets.insert(ann_off);
245        }
246
247        // Walk through bytes, breaking at label and annotation boundaries
248        let bytes = &self.bytes;
249        let mut offset: u64 = base;
250        let mut i = 0;
251
252        while i < bytes.len() {
253            // Print any labels at this offset
254            if let Some(names) = label_at.get(&offset) {
255                for name in names {
256                    let _ = writeln!(out, "{:08X}                  {}:", offset, name);
257                }
258            }
259
260            // Determine chunk size: up to 8 bytes, but break at the next split point
261            let max_end = core::cmp::min(i + 8, bytes.len());
262            let mut chunk_end = max_end;
263
264            // Check if any split point falls within (offset+1..offset+chunk_len)
265            let range_end = offset + (max_end - i) as u64;
266            if range_end > offset + 1 {
267                for &split_off in split_offsets.range((offset + 1)..range_end) {
268                    let split_at = (split_off - base) as usize;
269                    if split_at < chunk_end && split_at > i {
270                        chunk_end = split_at;
271                        break;
272                    }
273                }
274            }
275
276            let chunk = &bytes[i..chunk_end];
277            let hex: String = chunk.iter().fold(String::new(), |mut acc, b| {
278                let _ = write!(acc, "{:02X}", b);
279                acc
280            });
281
282            // Look up source annotation for this offset
283            if let Some(source_text) = source_at.get(&offset) {
284                let _ = writeln!(out, "{:08X}  {:<16}  {}", offset, hex, source_text);
285            } else {
286                let _ = writeln!(out, "{:08X}  {:<16}", offset, hex);
287            }
288
289            let chunk_len = chunk.len();
290            i += chunk_len;
291            offset += chunk_len as u64;
292        }
293
294        // Print labels at the very end (e.g. a label after the last instruction)
295        if let Some(names) = label_at.get(&offset) {
296            for name in names {
297                let _ = writeln!(out, "{:08X}                  {}:", offset, name);
298            }
299        }
300
301        out
302    }
303}
304
305/// Configurable resource limits for defense against denial-of-service.
306///
307/// When processing untrusted assembly input, these limits prevent pathological
308/// inputs from consuming unbounded memory or CPU time. All limits default to
309/// generous values that are sufficient for any reasonable assembly program.
310///
311/// # Examples
312///
313/// ```rust
314/// use asm_rs::{Assembler, Arch};
315/// use asm_rs::assembler::ResourceLimits;
316///
317/// let mut asm = Assembler::new(Arch::X86_64);
318/// asm.limits(ResourceLimits {
319///     max_statements: 1_000,
320///     max_labels: 100,
321///     max_output_bytes: 4096,
322///     max_errors: 16,
323///     max_recursion_depth: 32,
324///     max_source_bytes: 64 * 1024 * 1024,
325///     max_iterations: 100_000,
326///     max_expanded_bytes: 1024 * 1024,
327/// });
328/// // Assembly of very large or pathological inputs will now error early.
329/// ```
330#[derive(Debug, Clone, Copy, PartialEq, Eq)]
331#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
332pub struct ResourceLimits {
333    /// Maximum number of parsed statements (instructions + directives + labels).
334    /// Default: 1,000,000.
335    pub max_statements: usize,
336    /// Maximum number of labels that can be defined. Default: 100,000.
337    pub max_labels: usize,
338    /// Maximum output size in bytes. Default: 16 MiB.
339    pub max_output_bytes: usize,
340    /// Maximum accumulated errors before bailing. Default: 64.
341    pub max_errors: usize,
342    /// Maximum macro expansion recursion depth. Default: 256.
343    pub max_recursion_depth: usize,
344    /// Maximum input source bytes per `emit()` call. Default: 64 MiB.
345    /// Guards against multi-gigabyte inputs consuming unbounded memory
346    /// during lexing/parsing before any other limit can fire.
347    pub max_source_bytes: usize,
348    /// Maximum total preprocessor iterations (`.rept`/`.irp`/`.irpc`).
349    /// Default: 100,000.
350    pub max_iterations: usize,
351    /// Maximum number of bytes the preprocessor may produce. Default: 64 MiB.
352    ///
353    /// Iteration and recursion counts alone do not bound expansion *size*:
354    /// a single `.rept 50000` around a 8 KiB body costs 50,000 iterations but
355    /// produces 400 MiB of text, and mutually-invoking macros grow
356    /// exponentially with depth. This limit caps the total expanded text so
357    /// such inputs fail fast instead of exhausting memory.
358    pub max_expanded_bytes: usize,
359}
360
361impl Default for ResourceLimits {
362    fn default() -> Self {
363        Self {
364            max_statements: 1_000_000,
365            max_labels: 100_000,
366            max_output_bytes: 16 * 1024 * 1024,
367            max_errors: 64,
368            // Each expansion level costs a stack frame; keep the default low
369            // enough that hitting the limit returns an error rather than
370            // overflowing the stack (which aborts and cannot be caught),
371            // including on the small stacks typical of embedded targets.
372            max_recursion_depth: 32,
373            max_source_bytes: 64 * 1024 * 1024,
374            max_iterations: 100_000,
375            max_expanded_bytes: 64 * 1024 * 1024,
376        }
377    }
378}
379
380/// Builder-pattern assembler.
381///
382/// # Examples
383///
384/// ```rust
385/// use asm_rs::{Assembler, Arch};
386///
387/// let mut asm = Assembler::new(Arch::X86_64);
388/// asm.emit("push rbp").unwrap();
389/// asm.emit("mov rbp, rsp").unwrap();
390/// asm.emit("pop rbp").unwrap();
391/// asm.emit("ret").unwrap();
392/// let result = asm.finish().unwrap();
393/// assert!(!result.is_empty());
394/// ```
395#[derive(Debug)]
396pub struct Assembler {
397    arch: Arch,
398    /// Current x86 encoding mode — tracks `.code16`/`.code32`/`.code64` switches.
399    /// Only meaningful when `arch` is `X86` or `X86_64`.
400    x86_mode: crate::ir::X86Mode,
401    syntax: Syntax,
402    opt_level: OptLevel,
403    linker: Linker,
404    /// Preprocessor for macros, conditionals, and loops.
405    preprocessor: Preprocessor,
406    /// Accumulated errors for multi-error mode.
407    errors: Vec<AsmError>,
408    /// Maps linker fragment index → source text for listing.
409    fragment_annotations: Vec<(usize, String)>,
410    /// Whether to collect source annotations for listing output.
411    /// Off by default to avoid per-statement String allocations.
412    listing_enabled: bool,
413    /// Resource limits for DoS protection.
414    resource_limits: ResourceLimits,
415    /// Running count of parsed statements so far.
416    statement_count: usize,
417    /// Running count of defined labels so far.
418    label_count: usize,
419    /// Pending literal pool entries: (value, size_bytes, synthetic_label).
420    /// Flushed at `.ltorg`, unconditional branches, or `finish()`.
421    literal_pool: Vec<LiteralPoolEntry>,
422    /// Index into `literal_pool` by `(value, size)`, so deduplicating an entry
423    /// is a map lookup rather than a scan of every pending entry.
424    literal_pool_index: alloc::collections::BTreeMap<(i128, u8), usize>,
425    /// Counter for generating unique literal pool labels.
426    literal_pool_counter: usize,
427    /// Whether RISC-V C extension auto-narrowing is enabled (`.option rvc`).
428    /// When true, 32-bit instructions are automatically compressed to 16-bit
429    /// equivalents when possible.
430    rvc_enabled: bool,
431    /// Whether the next label should be marked as a Thumb function (`.thumb_func`).
432    /// When true, the label's address will have the LSB set to indicate Thumb mode.
433    thumb_func_pending: bool,
434    /// Labels marked as Thumb functions via `.thumb_func`.
435    /// Their resolved addresses will have the LSB set.
436    thumb_labels: Vec<String>,
437    /// Running estimate of cumulative output bytes — incremented by builder
438    /// methods (`db`, `fill`, `space`, etc.) and `emit()` to catch
439    /// `max_output_bytes` overflows *before* the allocation happens.
440    estimated_output_bytes: usize,
441}
442
443/// A pending literal pool entry.
444#[derive(Debug, Clone)]
445struct LiteralPoolEntry {
446    /// The constant value to place in the pool.
447    value: i128,
448    /// Size in bytes (4 for W-regs, 8 for X-regs).
449    size: u8,
450    /// Synthetic label that the LDR references.
451    label: String,
452}
453
454impl Assembler {
455    /// Create a new assembler for the given architecture.
456    pub fn new(arch: Arch) -> Self {
457        let syntax = match arch {
458            Arch::Arm | Arch::Thumb | Arch::Aarch64 => Syntax::Ual,
459            Arch::Rv32 | Arch::Rv64 => Syntax::RiscV,
460            _ => Syntax::Intel,
461        };
462        let x86_mode = match arch {
463            Arch::X86 => crate::ir::X86Mode::Mode32,
464            Arch::X86_64 => crate::ir::X86Mode::Mode64,
465            _ => crate::ir::X86Mode::Mode64, // unused for non-x86
466        };
467        let resource_limits = ResourceLimits::default();
468        let mut linker = Linker::new();
469        linker.set_max_output_bytes(resource_limits.max_output_bytes);
470        let mut preprocessor = Preprocessor::new();
471        preprocessor.set_max_recursion_depth(resource_limits.max_recursion_depth);
472        preprocessor.set_max_iterations(resource_limits.max_iterations);
473        preprocessor.set_max_expanded_bytes(resource_limits.max_expanded_bytes);
474        Self {
475            arch,
476            x86_mode,
477            syntax,
478            opt_level: OptLevel::default(),
479            linker,
480            preprocessor,
481            errors: Vec::new(),
482            fragment_annotations: Vec::new(),
483            listing_enabled: false,
484            resource_limits,
485            statement_count: 0,
486            label_count: 0,
487            literal_pool: Vec::new(),
488            literal_pool_index: alloc::collections::BTreeMap::new(),
489            literal_pool_counter: 0,
490            rvc_enabled: false,
491            thumb_func_pending: false,
492            thumb_labels: Vec::new(),
493            estimated_output_bytes: 0,
494        }
495    }
496
497    /// Set resource limits for defense against pathological inputs.
498    ///
499    /// See [`ResourceLimits`] for the available limits and their defaults.
500    pub fn limits(&mut self, limits: ResourceLimits) -> &mut Self {
501        self.resource_limits = limits;
502        self.preprocessor
503            .set_max_recursion_depth(limits.max_recursion_depth);
504        self.preprocessor.set_max_iterations(limits.max_iterations);
505        self.preprocessor
506            .set_max_expanded_bytes(limits.max_expanded_bytes);
507        self.linker.set_max_output_bytes(limits.max_output_bytes);
508        self
509    }
510
511    /// Set the syntax dialect.
512    ///
513    /// Currently only [`Syntax::Intel`] is supported. Attempting to emit code
514    /// after selecting an unsupported dialect will return an error.
515    pub fn syntax(&mut self, syntax: Syntax) -> &mut Self {
516        self.syntax = syntax;
517        self
518    }
519
520    /// Set the optimization level.
521    ///
522    /// [`OptLevel::Size`] (the default) picks the shortest encoding using only
523    /// transforms that preserve all architectural state, including FLAGS.
524    /// [`OptLevel::None`] emits the instruction exactly as written.
525    /// [`OptLevel::Aggressive`] additionally enables FLAGS-clobbering rewrites
526    /// such as `mov reg, 0` → `xor reg, reg`; see [`OptLevel`] for when that
527    /// is safe.
528    ///
529    /// # Examples
530    ///
531    /// ```
532    /// use asm_rs::{Assembler, Arch, OptLevel};
533    ///
534    /// // Default: FLAGS are preserved, so `mov` stays a `mov`.
535    /// let mut asm = Assembler::new(Arch::X86_64);
536    /// asm.emit("mov eax, 0")?;
537    /// assert_eq!(asm.finish()?.bytes(), &[0xB8, 0x00, 0x00, 0x00, 0x00]);
538    ///
539    /// // Opt in to the zero idiom.
540    /// let mut asm = Assembler::new(Arch::X86_64);
541    /// asm.optimize(OptLevel::Aggressive);
542    /// asm.emit("mov eax, 0")?;
543    /// assert_eq!(asm.finish()?.bytes(), &[0x31, 0xC0]);
544    /// # Ok::<(), asm_rs::AsmError>(())
545    /// ```
546    pub fn optimize(&mut self, level: OptLevel) -> &mut Self {
547        self.opt_level = level;
548        self
549    }
550
551    /// Enable source annotations for listing output.
552    ///
553    /// When enabled, the assembler records source text for each emitted
554    /// fragment, making it available in [`AssemblyResult::listing()`].
555    /// This adds a per-statement `String` allocation; leave disabled (the
556    /// default) when listing output is not needed.
557    ///
558    /// # Examples
559    ///
560    /// ```
561    /// use asm_rs::{Assembler, Arch};
562    ///
563    /// let mut asm = Assembler::new(Arch::X86_64);
564    /// asm.enable_listing();
565    /// asm.emit("nop")?;
566    /// let result = asm.finish()?;
567    /// let listing = result.listing();
568    /// assert!(listing.contains("90")); // NOP opcode in hex listing
569    /// # Ok::<(), asm_rs::AsmError>(())
570    /// ```
571    pub fn enable_listing(&mut self) -> &mut Self {
572        self.listing_enabled = true;
573        self
574    }
575
576    /// Set the base virtual address for the assembly.
577    pub fn base_address(&mut self, addr: u64) -> &mut Self {
578        self.linker.set_base_address(addr);
579        self
580    }
581
582    /// Define an external label at a known absolute address.
583    ///
584    /// # Examples
585    ///
586    /// ```
587    /// use asm_rs::{Assembler, Arch};
588    ///
589    /// let mut asm = Assembler::new(Arch::X86_64);
590    /// asm.define_external("puts", 0x4000);
591    /// asm.emit("call puts")?;
592    /// let result = asm.finish()?;
593    /// assert!(!result.bytes().is_empty());
594    /// # Ok::<(), asm_rs::AsmError>(())
595    /// ```
596    pub fn define_external(&mut self, name: &str, addr: u64) -> &mut Self {
597        self.linker.define_external(name, addr);
598        self
599    }
600
601    /// Define a named constant value.
602    pub fn define_constant(&mut self, name: &str, value: i128) -> &mut Self {
603        self.linker.define_constant(name, value);
604        self
605    }
606
607    /// Emit assembly source text. Can be called multiple times.
608    ///
609    /// # Errors
610    ///
611    /// Returns [`AsmError`] on parse or encoding errors, unsupported syntax,
612    /// or if resource limits are exceeded.
613    pub fn emit(&mut self, source: &str) -> Result<&mut Self, AsmError> {
614        // Check source size limit before any work
615        if source.len() > self.resource_limits.max_source_bytes {
616            return Err(AsmError::ResourceLimitExceeded {
617                resource: String::from("source bytes"),
618                limit: self.resource_limits.max_source_bytes,
619            });
620        }
621        // Run preprocessor to expand macros, loops, and conditionals
622        let expanded = self.preprocessor.process(source)?;
623        // Statements are consumed as they are parsed rather than collected
624        // into a `Vec` first: `Statement` stores its operand list inline, so
625        // buffering a whole program costs hundreds of bytes per instruction
626        // and makes peak memory proportional to the source rather than to the
627        // output it produces.
628        //
629        let tokens = lexer::tokenize_with_syntax(&expanded, self.syntax)?;
630        // Read the dialect out before the closure takes `self` mutably.
631        let (arch, syntax) = (self.arch, self.syntax);
632        parser::parse_streaming(&tokens, arch, syntax, |mut stmt| {
633            self.process_statement(&mut stmt, &expanded)
634        })?;
635        Ok(self)
636    }
637
638    /// Define a preprocessor symbol for conditional assembly.
639    ///
640    /// Symbols defined here are available in `.ifdef`/`.ifndef` and `.if defined()`
641    /// conditionals within assembly source.
642    pub fn define_preprocessor_symbol(&mut self, name: &str, value: i128) -> &mut Self {
643        self.preprocessor.define_symbol(name, value);
644        self
645    }
646
647    /// Add a label at the current position (builder API).
648    ///
649    /// # Examples
650    ///
651    /// ```
652    /// use asm_rs::{Assembler, Arch};
653    ///
654    /// let mut asm = Assembler::new(Arch::X86_64);
655    /// asm.label("entry")?;
656    /// asm.emit("nop")?;
657    /// let result = asm.finish()?;
658    /// assert_eq!(result.label_address("entry"), Some(0));
659    /// # Ok::<(), asm_rs::AsmError>(())
660    /// ```
661    ///
662    /// # Errors
663    ///
664    /// Returns [`AsmError::DuplicateLabel`] if the label was already defined,
665    /// or [`AsmError::ResourceLimitExceeded`] if the label limit is reached.
666    pub fn label(&mut self, name: &str) -> Result<&mut Self, AsmError> {
667        self.label_count += 1;
668        if self.label_count > self.resource_limits.max_labels {
669            return Err(AsmError::ResourceLimitExceeded {
670                resource: String::from("labels"),
671                limit: self.resource_limits.max_labels,
672            });
673        }
674        self.linker.add_label(name, Span::new(0, 0, 0, 0))?;
675        Ok(self)
676    }
677
678    /// Emit raw bytes (builder API for `.byte`/`.db`).
679    ///
680    /// # Errors
681    ///
682    /// Returns [`AsmError::ResourceLimitExceeded`] if the output size limit
683    /// would be exceeded.
684    pub fn db(&mut self, bytes: &[u8]) -> Result<&mut Self, AsmError> {
685        self.check_output_limit(bytes.len())?;
686        self.linker.add_bytes(bytes.to_vec(), Span::new(0, 0, 0, 0));
687        Ok(self)
688    }
689
690    /// Emit a 16-bit value (builder API for `.word`/`.dw`).
691    ///
692    /// # Errors
693    ///
694    /// Returns [`AsmError::ResourceLimitExceeded`] if the output size limit
695    /// would be exceeded.
696    pub fn dw(&mut self, value: u16) -> Result<&mut Self, AsmError> {
697        self.check_output_limit(2)?;
698        self.linker
699            .add_bytes(value.to_le_bytes().to_vec(), Span::new(0, 0, 0, 0));
700        Ok(self)
701    }
702
703    /// Emit a 32-bit value (builder API for `.long`/`.dd`).
704    ///
705    /// # Errors
706    ///
707    /// Returns [`AsmError::ResourceLimitExceeded`] if the output size limit
708    /// would be exceeded.
709    pub fn dd(&mut self, value: u32) -> Result<&mut Self, AsmError> {
710        self.check_output_limit(4)?;
711        self.linker
712            .add_bytes(value.to_le_bytes().to_vec(), Span::new(0, 0, 0, 0));
713        Ok(self)
714    }
715
716    /// Emit a 64-bit value (builder API for `.quad`/`.dq`).
717    ///
718    /// # Errors
719    ///
720    /// Returns [`AsmError::ResourceLimitExceeded`] if the output size limit
721    /// would be exceeded.
722    pub fn dq(&mut self, value: u64) -> Result<&mut Self, AsmError> {
723        self.check_output_limit(8)?;
724        self.linker
725            .add_bytes(value.to_le_bytes().to_vec(), Span::new(0, 0, 0, 0));
726        Ok(self)
727    }
728
729    /// Emit a string without NUL terminator (builder API for `.ascii`).
730    ///
731    /// # Errors
732    ///
733    /// Returns [`AsmError::ResourceLimitExceeded`] if the output size limit
734    /// would be exceeded.
735    pub fn ascii(&mut self, s: &str) -> Result<&mut Self, AsmError> {
736        self.check_output_limit(s.len())?;
737        self.linker
738            .add_bytes(s.as_bytes().to_vec(), Span::new(0, 0, 0, 0));
739        Ok(self)
740    }
741
742    /// Emit a NUL-terminated string (builder API for `.asciz`/`.string`).
743    ///
744    /// # Errors
745    ///
746    /// Returns [`AsmError::ResourceLimitExceeded`] if the output size limit
747    /// would be exceeded.
748    pub fn asciz(&mut self, s: &str) -> Result<&mut Self, AsmError> {
749        self.check_output_limit(s.len() + 1)?;
750        let mut bytes = s.as_bytes().to_vec();
751        bytes.push(0);
752        self.linker.add_bytes(bytes, Span::new(0, 0, 0, 0));
753        Ok(self)
754    }
755
756    /// Align to a byte boundary (builder API for `.align`).
757    ///
758    /// Uses multi-byte NOP padding for x86/x86-64 architectures.
759    pub fn align(&mut self, alignment: u32) -> &mut Self {
760        let use_nop = matches!(self.arch, Arch::X86 | Arch::X86_64);
761        self.linker
762            .add_alignment(alignment, 0x00, None, use_nop, Span::new(0, 0, 0, 0));
763        self
764    }
765
766    /// Align to a byte boundary with explicit fill byte (builder API).
767    pub fn align_with_fill(&mut self, alignment: u32, fill: u8) -> &mut Self {
768        self.linker
769            .add_alignment(alignment, fill, None, false, Span::new(0, 0, 0, 0));
770        self
771    }
772
773    /// Set the location counter to an absolute address (builder API for `.org`).
774    pub fn org(&mut self, target: u64) -> &mut Self {
775        self.linker.add_org(target, 0x00, Span::new(0, 0, 0, 0));
776        self
777    }
778
779    /// Set the location counter with explicit fill byte (builder API for `.org`).
780    pub fn org_with_fill(&mut self, target: u64, fill: u8) -> &mut Self {
781        self.linker.add_org(target, fill, Span::new(0, 0, 0, 0));
782        self
783    }
784
785    /// Emit fill bytes (builder API for `.fill`).
786    ///
787    /// Produces `count * size` bytes, each `size`-byte unit filled with `value`.
788    ///
789    /// # Errors
790    ///
791    /// Returns [`AsmError::ResourceLimitExceeded`] if the output size limit
792    /// would be exceeded.
793    pub fn fill(&mut self, count: u32, size: u8, value: i64) -> Result<&mut Self, AsmError> {
794        let total = (count as usize).saturating_mul(size as usize);
795        self.check_output_limit(total)?;
796        let mut bytes = Vec::with_capacity(total);
797        // GAS semantics: value is a LE integer padded to `size` bytes
798        let val_bytes = value.to_le_bytes();
799        for _ in 0..count {
800            for &b in val_bytes.iter().take(size as usize) {
801                bytes.push(b);
802            }
803            // Pad with zeros if size > 8
804            if (size as usize) > 8 {
805                bytes.resize(bytes.len() + size as usize - 8, 0);
806            }
807        }
808        self.linker.add_bytes(bytes, Span::new(0, 0, 0, 0));
809        Ok(self)
810    }
811
812    /// Emit zero-filled space (builder API for `.space`/`.skip`).
813    ///
814    /// # Errors
815    ///
816    /// Returns [`AsmError::ResourceLimitExceeded`] if the output size limit
817    /// would be exceeded.
818    pub fn space(&mut self, n: u32) -> Result<&mut Self, AsmError> {
819        self.check_output_limit(n as usize)?;
820        let bytes = alloc::vec![0u8; n as usize];
821        self.linker.add_bytes(bytes, Span::new(0, 0, 0, 0));
822        Ok(self)
823    }
824
825    /// Returns the current number of fragments (instructions + data) emitted so far.
826    ///
827    /// Useful for estimating output size before calling [`finish()`](Assembler::finish).
828    pub fn current_fragment_count(&self) -> usize {
829        self.linker.fragment_count()
830    }
831
832    /// Assemble a single instruction and return its raw bytes immediately,
833    /// without label resolution.
834    ///
835    /// This is useful for one-shot encoding when labels are not needed.
836    /// The instruction is NOT added to the assembler's internal state.
837    ///
838    /// # Examples
839    ///
840    /// ```
841    /// use asm_rs::{Assembler, Arch};
842    ///
843    /// let asm = Assembler::new(Arch::X86_64);
844    /// let bytes = asm.encode_one("xor eax, eax")?;
845    /// assert_eq!(bytes, [0x31, 0xC0]);
846    /// # Ok::<(), asm_rs::AsmError>(())
847    /// ```
848    ///
849    /// # Errors
850    ///
851    /// Returns [`AsmError`] if the instruction cannot be parsed or encoded.
852    pub fn encode_one(&self, source: &str) -> Result<Vec<u8>, AsmError> {
853        use crate::encoder::encode_instruction;
854
855        let tokens = crate::lexer::tokenize_with_syntax(source, self.syntax)?;
856        let stmts = crate::parser::parse_with_syntax(&tokens, self.arch, self.syntax)?;
857        if stmts.is_empty() {
858            return Ok(Vec::new());
859        }
860        match &stmts[0] {
861            crate::ir::Statement::Instruction(instr) => {
862                // Resolve any constants defined via define_constant() / .equ / .set
863                let mut instr = instr.clone();
864                self.resolve_constants_in_instruction(&mut instr);
865                let encoded = encode_instruction(&instr, self.arch)?;
866                Ok(encoded.bytes.to_vec())
867            }
868            _ => Err(AsmError::Syntax {
869                msg: String::from("expected an instruction"),
870                span: crate::error::Span::new(0, 0, 0, 0),
871            }),
872        }
873    }
874
875    /// Reset the assembler to its initial state, keeping configuration
876    /// (architecture, syntax, optimization level, limits) intact.
877    ///
878    /// This allows reusing the same `Assembler` for multiple assembly operations
879    /// without reallocating configuration state.
880    ///
881    /// # Examples
882    ///
883    /// ```
884    /// use asm_rs::{Assembler, Arch};
885    ///
886    /// let mut asm = Assembler::new(Arch::X86_64);
887    /// asm.emit("nop")?;
888    /// asm.reset();
889    /// asm.emit("ret")?;
890    /// let result = asm.finish()?;
891    /// assert_eq!(result.bytes(), &[0xC3]); // only ret, nop was reset
892    /// # Ok::<(), asm_rs::AsmError>(())
893    /// ```
894    pub fn reset(&mut self) -> &mut Self {
895        let base = self.linker.base_address();
896        self.linker = Linker::new();
897        self.linker.set_base_address(base);
898        self.preprocessor = Preprocessor::new();
899        // Re-apply the configured limits — they are configuration, not state.
900        self.limits(self.resource_limits);
901        self.errors.clear();
902        self.fragment_annotations.clear();
903        // listing_enabled is configuration, preserved across resets
904        self.statement_count = 0;
905        self.label_count = 0;
906        self.literal_pool.clear();
907        self.literal_pool_index.clear();
908        self.literal_pool_counter = 0;
909        self.thumb_func_pending = false;
910        self.thumb_labels.clear();
911        self.estimated_output_bytes = 0;
912        // Note: rvc_enabled, x86_mode, and arch are configuration state
913        // deliberately preserved across resets (like syntax and opt_level).
914        self
915    }
916
917    /// Check that adding `n` bytes would not exceed the output size limit.
918    ///
919    /// Called by builder methods to enforce `max_output_bytes` eagerly —
920    /// *before* allocating the data — rather than only at `finish()` time.
921    fn check_output_limit(&mut self, additional: usize) -> Result<(), AsmError> {
922        // Saturating: `.fill`/`.space` sizes are attacker-controlled and can
923        // overflow `usize` on 32-bit targets (wasm32, thumbv7), where the
924        // wrapped total would slip under the limit.
925        self.estimated_output_bytes = self.estimated_output_bytes.saturating_add(additional);
926        if self.estimated_output_bytes > self.resource_limits.max_output_bytes {
927            return Err(AsmError::ResourceLimitExceeded {
928                resource: String::from("output bytes"),
929                limit: self.resource_limits.max_output_bytes,
930            });
931        }
932        Ok(())
933    }
934
935    /// Finalize assembly: resolve labels, apply relocations, return result.
936    ///
937    /// # Errors
938    ///
939    /// Returns [`AsmError`] if label resolution fails, relocations cannot be
940    /// applied, accumulated errors exist, or resource limits are exceeded.
941    pub fn finish(mut self) -> Result<AssemblyResult, AsmError> {
942        if !self.errors.is_empty() {
943            if self.errors.len() == 1 {
944                return Err(self.errors.remove(0));
945            }
946            return Err(AsmError::Multiple {
947                errors: self.errors,
948            });
949        }
950
951        let base = self.linker.base_address();
952
953        // Flush any remaining literal pool entries before resolving.
954        let flush_span = crate::error::Span::new(0, 0, 0, 0);
955        self.flush_literal_pool(flush_span)?;
956
957        let (bytes, mut labels, relocations, offsets) = self.linker.resolve()?;
958
959        // Set LSB on Thumb function label addresses for interworking
960        for (name, addr) in labels.iter_mut() {
961            if self.thumb_labels.iter().any(|t| t == name) {
962                *addr |= 1;
963            }
964        }
965
966        // `label_address()` binary-searches this slice. The linker already
967        // yields labels in name order (it stores them in a `BTreeMap`), so
968        // this is a linear-time confirmation of the invariant rather than a
969        // real sort — but it keeps the invariant local to where it is relied
970        // upon instead of spread across two modules.
971        labels.sort_by(|(a, _), (b, _)| a.cmp(b));
972
973        // Enforce output size limit
974        if bytes.len() > self.resource_limits.max_output_bytes {
975            return Err(AsmError::ResourceLimitExceeded {
976                resource: String::from("output bytes"),
977                limit: self.resource_limits.max_output_bytes,
978            });
979        }
980
981        // Build source annotations: map fragment index → output offset,
982        // then look up the source text for each annotated fragment.
983        let source_annotations = self.build_source_annotations(&offsets);
984
985        Ok(AssemblyResult {
986            bytes,
987            labels,
988            relocations,
989            base_address: base,
990            source_annotations,
991        })
992    }
993
994    /// Build source text annotations by mapping fragment indices to output
995    /// offsets and extracting the source text from the stored source strings.
996    fn build_source_annotations(&self, offsets: &[u64]) -> Vec<(u64, String)> {
997        let mut annotations = Vec::new();
998        for &(frag_idx, ref text) in &self.fragment_annotations {
999            if frag_idx < offsets.len() {
1000                annotations.push((offsets[frag_idx], text.clone()));
1001            }
1002        }
1003        annotations
1004    }
1005
1006    /// Process one parsed statement.
1007    ///
1008    /// Called from the streaming parse loop, so the statement is encoded and
1009    /// dropped before the next one is parsed.
1010    fn process_statement(&mut self, stmt: &mut Statement, source: &str) -> Result<(), AsmError> {
1011        self.statement_count += 1;
1012        if self.statement_count > self.resource_limits.max_statements {
1013            return Err(AsmError::ResourceLimitExceeded {
1014                resource: String::from("statements"),
1015                limit: self.resource_limits.max_statements,
1016            });
1017        }
1018
1019        {
1020            match stmt {
1021                Statement::Label(name, span) => {
1022                    self.label_count += 1;
1023                    if self.label_count > self.resource_limits.max_labels {
1024                        return Err(AsmError::ResourceLimitExceeded {
1025                            resource: String::from("labels"),
1026                            limit: self.resource_limits.max_labels,
1027                        });
1028                    }
1029                    self.linker.add_label(name, *span)?;
1030                    // Mark as Thumb function if .thumb_func was pending
1031                    if self.thumb_func_pending {
1032                        self.thumb_labels.push(name.clone());
1033                        self.thumb_func_pending = false;
1034                    }
1035                }
1036
1037                Statement::Instruction(instr) => {
1038                    let frag_idx = self.linker.fragment_count();
1039                    // Resolve any constant references in operands before encoding
1040                    self.resolve_constants_in_instruction(instr);
1041                    // Transform literal pool operands: =value → label reference
1042                    self.transform_literal_pool_operands(instr);
1043                    crate::optimize::optimize_instruction(instr, self.arch, self.opt_level);
1044                    let encode_result = if self.x86_mode == crate::ir::X86Mode::Mode16 {
1045                        #[cfg(feature = "x86")]
1046                        {
1047                            encoder::encode_instruction_16(instr)
1048                        }
1049                        #[cfg(not(feature = "x86"))]
1050                        {
1051                            encoder::encode_instruction(instr, self.arch)
1052                        }
1053                    } else {
1054                        encoder::encode_instruction(instr, self.arch)
1055                    };
1056                    // RISC-V auto-narrowing: when .option rvc is active and the
1057                    // instruction is a 4-byte standard form, try to compress it
1058                    // to a 16-bit C-extension equivalent.
1059                    #[cfg(feature = "riscv")]
1060                    let encode_result = if self.rvc_enabled
1061                        && matches!(self.arch, Arch::Rv32 | Arch::Rv64)
1062                        && !instr.mnemonic.starts_with("c.")
1063                    {
1064                        match encode_result {
1065                            Ok(ref enc) if enc.bytes.len() == 4 && enc.relocation.is_none() => {
1066                                let is_rv64 = self.arch == Arch::Rv64;
1067                                if let Some(hw) = crate::riscv::try_compress(
1068                                    &instr.mnemonic,
1069                                    &instr.operands,
1070                                    is_rv64,
1071                                    instr.span,
1072                                ) {
1073                                    Ok(crate::riscv::rvc_instr(hw))
1074                                } else {
1075                                    encode_result
1076                                }
1077                            }
1078                            _ => encode_result,
1079                        }
1080                    } else {
1081                        encode_result
1082                    };
1083                    match encode_result {
1084                        Ok(encoded) => {
1085                            self.check_output_limit(encoded.bytes.len())?;
1086                            self.linker.add_encoded(
1087                                encoded.bytes,
1088                                encoded.relocation,
1089                                encoded.relax,
1090                                instr.span,
1091                            )?;
1092                            self.annotate(frag_idx, source, instr.span);
1093                        }
1094                        Err(e) => {
1095                            self.errors.push(e);
1096                            if self.errors.len() >= self.resource_limits.max_errors {
1097                                return Err(AsmError::ResourceLimitExceeded {
1098                                    resource: String::from("errors"),
1099                                    limit: self.resource_limits.max_errors,
1100                                });
1101                            }
1102                        }
1103                    }
1104                }
1105
1106                Statement::Data(data) => {
1107                    let frag_idx = self.linker.fragment_count();
1108                    let span = data.span;
1109                    self.emit_data(data)?;
1110                    self.annotate(frag_idx, source, span);
1111                }
1112
1113                Statement::Align(align) => {
1114                    let frag_idx = self.linker.fragment_count();
1115                    let span = align.span;
1116                    // When no explicit fill byte is given and the target is
1117                    // x86/x86-64, pad with multi-byte NOP sequences instead
1118                    // of zero bytes — optimal for code-section alignment.
1119                    let use_nop =
1120                        align.fill.is_none() && matches!(self.arch, Arch::X86 | Arch::X86_64);
1121                    self.linker.add_alignment(
1122                        align.alignment,
1123                        align.fill.unwrap_or(0x00),
1124                        align.max_skip,
1125                        use_nop,
1126                        align.span,
1127                    );
1128                    self.annotate(frag_idx, source, span);
1129                }
1130
1131                Statement::Const(c) => {
1132                    self.linker.define_constant(&c.name, c.value);
1133                }
1134
1135                Statement::Fill(fill) => {
1136                    let frag_idx = self.linker.fragment_count();
1137                    let span = fill.span;
1138                    let total = (fill.count as usize).saturating_mul(fill.size as usize);
1139                    self.check_output_limit(total)?;
1140                    let mut bytes = Vec::with_capacity(total);
1141                    // GAS semantics: value is a LE integer padded to `size` bytes.
1142                    // .fill 2, 4, 0x90 → [90 00 00 00  90 00 00 00]
1143                    let val_bytes = fill.value.to_le_bytes();
1144                    for _ in 0..fill.count {
1145                        for &b in val_bytes.iter().take(fill.size as usize) {
1146                            bytes.push(b);
1147                        }
1148                        // Pad with zeros if size > 8
1149                        if (fill.size as usize) > 8 {
1150                            bytes.resize(bytes.len() + fill.size as usize - 8, 0);
1151                        }
1152                    }
1153                    self.linker.add_bytes(bytes, fill.span);
1154                    self.annotate(frag_idx, source, span);
1155                }
1156
1157                Statement::Space(space) => {
1158                    let frag_idx = self.linker.fragment_count();
1159                    let span = space.span;
1160                    self.check_output_limit(space.size as usize)?;
1161                    let bytes = alloc::vec![space.fill; space.size as usize];
1162                    self.linker.add_bytes(bytes, space.span);
1163                    self.annotate(frag_idx, source, span);
1164                }
1165
1166                Statement::Org(org) => {
1167                    let frag_idx = self.linker.fragment_count();
1168                    let span = org.span;
1169                    // .org sets the location counter to an absolute address.
1170                    // The linker emits fill bytes to pad from current position
1171                    // to the target.
1172                    self.linker.add_org(org.offset, org.fill, org.span);
1173                    self.annotate(frag_idx, source, span);
1174                }
1175
1176                Statement::CodeMode(mode, span) => {
1177                    // .code16 / .code32 / .code64 — switch x86 encoding mode
1178                    if !matches!(self.arch, Arch::X86 | Arch::X86_64) {
1179                        return Err(AsmError::Syntax {
1180                            msg: String::from(".code16/.code32/.code64 only valid for x86/x86-64"),
1181                            span: *span,
1182                        });
1183                    }
1184                    self.x86_mode = *mode;
1185                    // Update the arch to match the new mode for encoding dispatch
1186                    match mode {
1187                        crate::ir::X86Mode::Mode16 | crate::ir::X86Mode::Mode32 => {
1188                            self.arch = Arch::X86;
1189                        }
1190                        crate::ir::X86Mode::Mode64 => {
1191                            self.arch = Arch::X86_64;
1192                        }
1193                    }
1194                }
1195
1196                Statement::Ltorg(span) => {
1197                    // Flush pending literal pool entries
1198                    let span = *span;
1199                    self.flush_literal_pool(span)?;
1200                }
1201
1202                Statement::OptionRvc(enable, span) => {
1203                    // .option rvc / .option norvc — toggle RISC-V C extension auto-narrowing
1204                    if !matches!(self.arch, Arch::Rv32 | Arch::Rv64) {
1205                        return Err(AsmError::Syntax {
1206                            msg: String::from(".option rvc/norvc is only valid for RISC-V"),
1207                            span: *span,
1208                        });
1209                    }
1210                    self.rvc_enabled = *enable;
1211                }
1212
1213                Statement::ThumbMode(is_thumb, span) => {
1214                    // .thumb / .arm — switch between Thumb and ARM modes
1215                    if !matches!(self.arch, Arch::Arm | Arch::Thumb) {
1216                        return Err(AsmError::Syntax {
1217                            msg: String::from(".thumb/.arm only valid for ARM"),
1218                            span: *span,
1219                        });
1220                    }
1221                    self.arch = if *is_thumb { Arch::Thumb } else { Arch::Arm };
1222                }
1223
1224                Statement::ThumbFunc(span) => {
1225                    // .thumb_func — mark next label as Thumb function (LSB set)
1226                    if !matches!(self.arch, Arch::Arm | Arch::Thumb) {
1227                        return Err(AsmError::Syntax {
1228                            msg: String::from(".thumb_func only valid for ARM/Thumb"),
1229                            span: *span,
1230                        });
1231                    }
1232                    // Also switch to Thumb mode (GNU as behavior)
1233                    self.arch = Arch::Thumb;
1234                    self.thumb_func_pending = true;
1235                }
1236            }
1237        }
1238        Ok(())
1239    }
1240
1241    /// Record a source-text annotation for a fragment, if listing is enabled.
1242    #[inline]
1243    fn annotate(&mut self, frag_idx: usize, source: &str, span: Span) {
1244        if self.listing_enabled {
1245            let src_text = extract_source_line(source, span);
1246            if !src_text.is_empty() {
1247                self.fragment_annotations
1248                    .push((frag_idx, src_text.to_string()));
1249            }
1250        }
1251    }
1252
1253    /// Transform `Operand::LiteralPoolValue(val)` into `Operand::Label(label)`
1254    /// and queue the constant for emission in the next literal pool flush.
1255    ///
1256    /// The destination register width determines pool entry size:
1257    /// - X-registers → 8-byte entry (`.quad`)
1258    /// - W-registers → 4-byte entry (`.long`)
1259    ///
1260    /// Duplicate values with the same size are deduplicated to share a single
1261    /// pool entry.
1262    fn transform_literal_pool_operands(&mut self, instr: &mut Instruction) {
1263        // Determine pool entry size from the first register operand.
1264        // - ARM registers → always 4 bytes (32-bit)
1265        // - AArch64 X-registers → 8 bytes, W-registers → 4 bytes
1266        // - Default: 8 bytes (AArch64 64-bit) if no register is found
1267        let size: u8 = instr
1268            .operands
1269            .iter()
1270            .find_map(|op| {
1271                if let Operand::Register(r) = op {
1272                    if r.is_arm() {
1273                        return Some(4u8); // ARM32 is always 4 bytes
1274                    }
1275                    if r.is_aarch64() {
1276                        return Some(if r.is_a64_64bit() { 8u8 } else { 4u8 });
1277                    }
1278                }
1279                None
1280            })
1281            .unwrap_or(8);
1282
1283        for op in &mut instr.operands {
1284            if let Operand::LiteralPoolValue(val) = op {
1285                let val = *val;
1286
1287                // Check for an existing pool entry with the same value + size.
1288                let label = match self.literal_pool_index.get(&(val, size)) {
1289                    Some(&idx) => self.literal_pool[idx].label.clone(),
1290                    None => {
1291                        let label = alloc::format!(".Lpool_{}", self.literal_pool_counter);
1292                        self.literal_pool_counter += 1;
1293                        self.literal_pool_index
1294                            .insert((val, size), self.literal_pool.len());
1295                        self.literal_pool.push(LiteralPoolEntry {
1296                            value: val,
1297                            size,
1298                            label: label.clone(),
1299                        });
1300                        label
1301                    }
1302                };
1303
1304                *op = Operand::Label(label);
1305            }
1306        }
1307    }
1308
1309    /// Flush all pending literal pool entries as labeled data fragments.
1310    ///
1311    /// Emits alignment padding followed by each pool entry's label + data.
1312    /// Called at `.ltorg` directives, unconditional branches (future), or `finish()`.
1313    fn flush_literal_pool(&mut self, span: Span) -> Result<(), AsmError> {
1314        if self.literal_pool.is_empty() {
1315            return Ok(());
1316        }
1317
1318        // Align pool to the largest entry size for natural alignment.
1319        let max_align = self
1320            .literal_pool
1321            .iter()
1322            .map(|e| e.size as u32)
1323            .max()
1324            .unwrap_or(4);
1325        self.linker
1326            .add_alignment(max_align, 0x00, None, false, span);
1327
1328        // Drain and emit each entry.
1329        let entries: Vec<LiteralPoolEntry> = core::mem::take(&mut self.literal_pool);
1330        self.literal_pool_index.clear();
1331        for entry in &entries {
1332            self.linker.add_label(&entry.label, span)?;
1333            let bytes = match entry.size {
1334                4 => (entry.value as u32).to_le_bytes().to_vec(),
1335                8 => (entry.value as u64).to_le_bytes().to_vec(),
1336                _ => (entry.value as u64).to_le_bytes().to_vec(),
1337            };
1338            self.linker.add_bytes(bytes, span);
1339        }
1340
1341        Ok(())
1342    }
1343
1344    /// Replace label operands with immediate values when they refer to known constants.
1345    ///
1346    /// Also resolves constants inside `Operand::Expression` trees and collapses
1347    /// fully-numeric expressions to `Operand::Immediate`.
1348    fn resolve_constants_in_instruction(&self, instr: &mut Instruction) {
1349        for op in &mut instr.operands {
1350            match op {
1351                Operand::Label(name) => {
1352                    if let Some(&value) = self.linker.get_constant(name) {
1353                        *op = Operand::Immediate(value);
1354                    }
1355                }
1356                Operand::Expression(expr) => {
1357                    // Substitute any constants referenced inside the expression tree.
1358                    expr.resolve_constants(|name| self.linker.get_constant(name).copied());
1359                    // If the expression is now purely numeric, collapse to Immediate.
1360                    if let Some(val) = expr.eval() {
1361                        *op = Operand::Immediate(val);
1362                    }
1363                }
1364                Operand::Memory(mem) => {
1365                    // Resolve constants used as displacement labels (e.g., [rbp + MY_CONST])
1366                    if let Some(ref label) = mem.disp_label {
1367                        if let Some(&value) = self.linker.get_constant(label) {
1368                            mem.disp = mem.disp.wrapping_add(value as i64);
1369                            mem.disp_label = None;
1370                        }
1371                    }
1372                }
1373                _ => {}
1374            }
1375        }
1376    }
1377
1378    /// Emit a data declaration, handling label references via relocations.
1379    fn emit_data(&mut self, data: &DataDecl) -> Result<(), AsmError> {
1380        use crate::encoder::Relocation;
1381
1382        let data_item_size: usize = match data.size {
1383            DataSize::Byte => 1,
1384            DataSize::Word => 2,
1385            DataSize::Long => 4,
1386            DataSize::Quad => 8,
1387        };
1388
1389        // Accumulate contiguous non-label bytes, flush when we hit a label.
1390        let mut pending: Vec<u8> = Vec::new();
1391
1392        for value in &data.values {
1393            match value {
1394                DataValue::Integer(n) => match data.size {
1395                    DataSize::Byte => pending.push(*n as u8),
1396                    DataSize::Word => pending.extend_from_slice(&(*n as u16).to_le_bytes()),
1397                    DataSize::Long => pending.extend_from_slice(&(*n as u32).to_le_bytes()),
1398                    DataSize::Quad => pending.extend_from_slice(&(*n as u64).to_le_bytes()),
1399                },
1400                DataValue::Bytes(b) => {
1401                    pending.extend_from_slice(b);
1402                }
1403                DataValue::Label(name, addend) => {
1404                    // Check if this is a constant (defined via .equ) rather than a label
1405                    if let Some(&const_val) = self.linker.get_constant(name) {
1406                        let val = const_val.wrapping_add(*addend as i128);
1407                        match data.size {
1408                            DataSize::Byte => pending.push(val as u8),
1409                            DataSize::Word => {
1410                                pending.extend_from_slice(&(val as u16).to_le_bytes())
1411                            }
1412                            DataSize::Long => {
1413                                pending.extend_from_slice(&(val as u32).to_le_bytes())
1414                            }
1415                            DataSize::Quad => {
1416                                pending.extend_from_slice(&(val as u64).to_le_bytes())
1417                            }
1418                        }
1419                        continue;
1420                    }
1421
1422                    // Flush any pending plain bytes first
1423                    if !pending.is_empty() {
1424                        self.linker
1425                            .add_bytes(core::mem::take(&mut pending), data.span);
1426                    }
1427                    // Emit a zero-filled data slot with an absolute relocation for the label
1428                    let mut slot = encoder::InstrBytes::new();
1429                    for _ in 0..data_item_size {
1430                        slot.push(0);
1431                    }
1432                    let reloc = Relocation {
1433                        offset: 0,
1434                        size: data_item_size as u8,
1435                        label: alloc::rc::Rc::from(name.as_str()),
1436                        kind: encoder::RelocKind::Absolute,
1437                        addend: *addend,
1438                        trailing_bytes: 0,
1439                    };
1440                    // Use add_encoded which will make a Fixed fragment
1441                    self.linker
1442                        .add_encoded(slot, Some(reloc), None, data.span)?;
1443                }
1444            }
1445        }
1446
1447        // Flush remaining bytes
1448        if !pending.is_empty() {
1449            self.linker.add_bytes(pending, data.span);
1450        }
1451
1452        Ok(())
1453    }
1454}
1455
1456/// Extract the source text for a span from the original source string.
1457///
1458/// Returns the trimmed text of the line containing the span, or a brief
1459/// fallback if the span is out-of-range.
1460fn extract_source_line(source: &str, span: Span) -> &str {
1461    let offset = span.offset;
1462    if offset >= source.len() {
1463        return "";
1464    }
1465    // Find the start of the line containing this span
1466    let line_start = source[..offset].rfind('\n').map_or(0, |p| p + 1);
1467    // Find the end of the line
1468    let line_end = source[offset..]
1469        .find('\n')
1470        .map_or(source.len(), |p| offset + p);
1471    source[line_start..line_end].trim()
1472}
1473
1474#[cfg(test)]
1475mod tests {
1476    use super::*;
1477
1478    // === One-Shot API ===
1479
1480    #[test]
1481    fn assemble_nop() {
1482        let mut asm = Assembler::new(Arch::X86_64);
1483        asm.emit("nop").unwrap();
1484        let result = asm.finish().unwrap();
1485        assert_eq!(result.bytes(), &[0x90]);
1486    }
1487
1488    #[test]
1489    fn assemble_ret() {
1490        let mut asm = Assembler::new(Arch::X86_64);
1491        asm.emit("ret").unwrap();
1492        let result = asm.finish().unwrap();
1493        assert_eq!(result.bytes(), &[0xC3]);
1494    }
1495
1496    #[test]
1497    fn assemble_multiple_instructions() {
1498        let mut asm = Assembler::new(Arch::X86_64);
1499        asm.emit("nop\nret").unwrap();
1500        let result = asm.finish().unwrap();
1501        assert_eq!(result.bytes(), &[0x90, 0xC3]);
1502    }
1503
1504    #[test]
1505    fn assemble_push_pop() {
1506        let mut asm = Assembler::new(Arch::X86_64);
1507        asm.emit("push rbp").unwrap();
1508        asm.emit("mov rbp, rsp").unwrap();
1509        asm.emit("pop rbp").unwrap();
1510        asm.emit("ret").unwrap();
1511        let result = asm.finish().unwrap();
1512        let bytes = result.bytes();
1513        assert_eq!(bytes[0], 0x55); // push rbp
1514        assert_eq!(*bytes.last().unwrap(), 0xC3); // ret
1515    }
1516
1517    #[test]
1518    fn assemble_with_label() {
1519        let mut asm = Assembler::new(Arch::X86_64);
1520        asm.emit("jmp target\ntarget:\nnop").unwrap();
1521        let result = asm.finish().unwrap();
1522        let bytes = result.bytes();
1523        // Branch relaxation: short form EB rel8 since target is right after
1524        assert_eq!(bytes[0], 0xEB); // jmp rel8
1525        assert_eq!(bytes[1], 0x00); // rel8 = 0
1526        assert_eq!(bytes[2], 0x90); // nop
1527    }
1528
1529    #[test]
1530    fn assemble_backward_jump() {
1531        let mut asm = Assembler::new(Arch::X86_64);
1532        asm.emit("loop_start:\nnop\njmp loop_start").unwrap();
1533        let result = asm.finish().unwrap();
1534        let bytes = result.bytes();
1535        assert_eq!(bytes[0], 0x90); // nop
1536                                    // Branch relaxation: short form EB rel8
1537        assert_eq!(bytes[1], 0xEB); // jmp rel8
1538                                    // target=0, frag_end=1+2=3, disp=0-3=-3=0xFD
1539        assert_eq!(bytes[2], 0xFD);
1540    }
1541
1542    #[test]
1543    fn assemble_conditional_jump() {
1544        let mut asm = Assembler::new(Arch::X86_64);
1545        asm.emit("cmp rax, 0\nje done\nnop\ndone:\nret").unwrap();
1546        let result = asm.finish().unwrap();
1547        let bytes = result.bytes();
1548        // Should contain: cmp, je, nop, ret
1549        assert!(!bytes.is_empty());
1550        // Last byte should be ret
1551        assert_eq!(*bytes.last().unwrap(), 0xC3);
1552    }
1553
1554    #[test]
1555    fn assemble_xor_self() {
1556        let mut asm = Assembler::new(Arch::X86_64);
1557        asm.emit("xor eax, eax").unwrap();
1558        let result = asm.finish().unwrap();
1559        assert_eq!(result.bytes(), &[0x31, 0xC0]);
1560    }
1561
1562    #[test]
1563    fn assemble_syscall_stub() {
1564        let mut asm = Assembler::new(Arch::X86_64);
1565        asm.emit("mov eax, 60\nxor edi, edi\nsyscall").unwrap();
1566        let result = asm.finish().unwrap();
1567        let bytes = result.bytes();
1568        // mov eax, 60 → B8 3C 00 00 00
1569        assert_eq!(&bytes[0..5], &[0xB8, 0x3C, 0x00, 0x00, 0x00]);
1570        // Last 2 bytes: syscall → 0F 05
1571        assert_eq!(&bytes[bytes.len() - 2..], &[0x0F, 0x05]);
1572    }
1573
1574    // === Builder API ===
1575
1576    #[test]
1577    fn builder_api() {
1578        let mut asm = Assembler::new(Arch::X86_64);
1579        asm.emit("push rbp").unwrap();
1580        asm.db(&[0xCC]).unwrap(); // int3
1581        asm.emit("pop rbp").unwrap();
1582        asm.emit("ret").unwrap();
1583        let result = asm.finish().unwrap();
1584        let bytes = result.bytes();
1585        assert_eq!(bytes[0], 0x55); // push rbp
1586        assert_eq!(bytes[1], 0xCC); // int3
1587    }
1588
1589    #[test]
1590    fn builder_label() {
1591        let mut asm = Assembler::new(Arch::X86_64);
1592        asm.emit("jmp target").unwrap();
1593        asm.label("target").unwrap();
1594        asm.emit("ret").unwrap();
1595        let result = asm.finish().unwrap();
1596        let bytes = result.bytes();
1597        // Short form: EB 00 C3
1598        assert_eq!(bytes[0], 0xEB);
1599        assert_eq!(*bytes.last().unwrap(), 0xC3);
1600    }
1601
1602    #[test]
1603    fn builder_data_words() {
1604        let mut asm = Assembler::new(Arch::X86_64);
1605        asm.dw(0x1234).unwrap();
1606        asm.dd(0xDEADBEEF).unwrap();
1607        let result = asm.finish().unwrap();
1608        let bytes = result.bytes();
1609        assert_eq!(&bytes[0..2], &[0x34, 0x12]);
1610        assert_eq!(&bytes[2..6], &[0xEF, 0xBE, 0xAD, 0xDE]);
1611    }
1612
1613    // === Data Directives ===
1614
1615    #[test]
1616    fn assemble_byte_directive() {
1617        let mut asm = Assembler::new(Arch::X86_64);
1618        asm.emit(".byte 0x90, 0xCC, 0xC3").unwrap();
1619        let result = asm.finish().unwrap();
1620        assert_eq!(result.bytes(), &[0x90, 0xCC, 0xC3]);
1621    }
1622
1623    #[test]
1624    fn assemble_word_directive() {
1625        let mut asm = Assembler::new(Arch::X86_64);
1626        asm.emit(".word 0x1234").unwrap();
1627        let result = asm.finish().unwrap();
1628        assert_eq!(result.bytes(), &[0x34, 0x12]);
1629    }
1630
1631    #[test]
1632    fn assemble_asciz_directive() {
1633        let mut asm = Assembler::new(Arch::X86_64);
1634        asm.emit(".asciz \"hello\"").unwrap();
1635        let result = asm.finish().unwrap();
1636        assert_eq!(result.bytes(), b"hello\0");
1637    }
1638
1639    #[test]
1640    fn assemble_equ_constant() {
1641        let mut asm = Assembler::new(Arch::X86_64);
1642        asm.emit(".equ EXIT, 60\nmov eax, EXIT").unwrap();
1643        // Note: constants are resolved at link time if referenced by label
1644        let _result = asm.finish();
1645        // This test mainly verifies parsing succeeds
1646    }
1647
1648    #[test]
1649    fn assemble_fill_directive() {
1650        let mut asm = Assembler::new(Arch::X86_64);
1651        asm.emit(".fill 3, 1, 0x90").unwrap();
1652        let result = asm.finish().unwrap();
1653        assert_eq!(result.bytes(), &[0x90, 0x90, 0x90]);
1654    }
1655
1656    #[test]
1657    fn assemble_space_directive() {
1658        let mut asm = Assembler::new(Arch::X86_64);
1659        asm.emit(".space 4").unwrap();
1660        let result = asm.finish().unwrap();
1661        assert_eq!(result.bytes(), &[0, 0, 0, 0]);
1662    }
1663
1664    // === Error Cases ===
1665
1666    #[test]
1667    fn unknown_mnemonic_error() {
1668        let mut asm = Assembler::new(Arch::X86_64);
1669        asm.emit("foobar").unwrap(); // error is collected, not fail-fast
1670        let err = asm.finish().unwrap_err();
1671        assert!(matches!(err, AsmError::UnknownMnemonic { .. }));
1672    }
1673
1674    #[test]
1675    fn duplicate_label_error() {
1676        let mut asm = Assembler::new(Arch::X86_64);
1677        let err = asm.emit("foo:\nfoo:").unwrap_err();
1678        assert!(matches!(err, AsmError::DuplicateLabel { .. }));
1679    }
1680
1681    #[test]
1682    fn undefined_label_error() {
1683        let mut asm = Assembler::new(Arch::X86_64);
1684        asm.emit("jmp nowhere").unwrap();
1685        let err = asm.finish().unwrap_err();
1686        assert!(matches!(err, AsmError::UndefinedLabel { .. }));
1687    }
1688
1689    // === External Labels ===
1690
1691    #[test]
1692    fn assemble_with_external() {
1693        let mut asm = Assembler::new(Arch::X86_64);
1694        asm.define_external("printf", 0x400000);
1695        asm.emit("mov rax, printf").unwrap();
1696        let result = asm.finish().unwrap();
1697        let bytes = result.bytes();
1698        // movabs rax, imm64 with printf address
1699        assert_eq!(&bytes[bytes.len() - 8..], &0x400000u64.to_le_bytes());
1700    }
1701
1702    // === Base Address ===
1703
1704    #[test]
1705    fn assemble_with_base_address() {
1706        let mut asm = Assembler::new(Arch::X86_64);
1707        asm.base_address(0x1000);
1708        asm.emit("nop").unwrap();
1709        let result = asm.finish().unwrap();
1710        assert_eq!(result.bytes(), &[0x90]);
1711    }
1712
1713    // === Complex Programs ===
1714
1715    #[test]
1716    fn assemble_loop() {
1717        let mut asm = Assembler::new(Arch::X86_64);
1718        asm.emit(
1719            r#"
1720            mov ecx, 10
1721        loop_start:
1722            dec ecx
1723            jnz loop_start
1724            ret
1725        "#,
1726        )
1727        .unwrap();
1728        let result = asm.finish().unwrap();
1729        assert!(!result.is_empty());
1730        assert_eq!(*result.bytes().last().unwrap(), 0xC3);
1731    }
1732
1733    #[test]
1734    fn assemble_function_prologue_epilogue() {
1735        let mut asm = Assembler::new(Arch::X86_64);
1736        asm.emit(
1737            r#"
1738            push rbp
1739            mov rbp, rsp
1740            sub rsp, 0x20
1741            add rsp, 0x20
1742            pop rbp
1743            ret
1744        "#,
1745        )
1746        .unwrap();
1747        let result = asm.finish().unwrap();
1748        let bytes = result.bytes();
1749        assert_eq!(bytes[0], 0x55); // push rbp
1750        assert_eq!(*bytes.last().unwrap(), 0xC3); // ret
1751    }
1752
1753    #[test]
1754    fn result_length() {
1755        let mut asm = Assembler::new(Arch::X86_64);
1756        asm.emit("nop\nnop\nnop").unwrap();
1757        let result = asm.finish().unwrap();
1758        assert_eq!(result.len(), 3);
1759    }
1760
1761    #[test]
1762    fn result_into_bytes() {
1763        let mut asm = Assembler::new(Arch::X86_64);
1764        asm.emit("ret").unwrap();
1765        let result = asm.finish().unwrap();
1766        let bytes = result.into_bytes();
1767        assert_eq!(bytes, vec![0xC3]);
1768    }
1769
1770    // === Semicolon Separated ===
1771
1772    #[test]
1773    fn semicolon_separated_instructions() {
1774        let mut asm = Assembler::new(Arch::X86_64);
1775        asm.emit("nop; nop; ret").unwrap();
1776        let result = asm.finish().unwrap();
1777        assert_eq!(result.bytes(), &[0x90, 0x90, 0xC3]);
1778    }
1779
1780    // === Labels export ===
1781
1782    #[test]
1783    fn labels_returned() {
1784        let mut asm = Assembler::new(Arch::X86_64);
1785        asm.emit("start:\nnop\nnop\nend:\nret").unwrap();
1786        let result = asm.finish().unwrap();
1787        assert_eq!(result.label_address("start"), Some(0));
1788        // nop=1B, nop=1B → end is at offset 2
1789        assert_eq!(result.label_address("end"), Some(2));
1790    }
1791
1792    #[test]
1793    fn labels_with_base_address() {
1794        let mut asm = Assembler::new(Arch::X86_64);
1795        asm.base_address(0x400000);
1796        asm.emit("entry:\nnop").unwrap();
1797        let result = asm.finish().unwrap();
1798        assert_eq!(result.label_address("entry"), Some(0x400000));
1799    }
1800
1801    #[test]
1802    fn builder_label_address() {
1803        let mut asm = Assembler::new(Arch::X86_64);
1804        asm.label("before").unwrap();
1805        asm.emit("nop; nop; nop").unwrap();
1806        asm.label("after").unwrap();
1807        asm.emit("ret").unwrap();
1808        let result = asm.finish().unwrap();
1809        assert_eq!(result.label_address("before"), Some(0));
1810        assert_eq!(result.label_address("after"), Some(3));
1811    }
1812
1813    // === Syntax / OptLevel builder ===
1814
1815    #[test]
1816    fn builder_syntax_and_optimize() {
1817        let mut asm = Assembler::new(Arch::X86_64);
1818        asm.syntax(Syntax::Intel);
1819        asm.optimize(OptLevel::Size);
1820        asm.emit("nop").unwrap();
1821        let result = asm.finish().unwrap();
1822        assert_eq!(result.bytes(), &[0x90]);
1823    }
1824
1825    // === define_constant builder ===
1826
1827    #[test]
1828    fn builder_define_constant() {
1829        let mut asm = Assembler::new(Arch::X86_64);
1830        asm.define_constant("EXIT", 60);
1831        asm.emit("mov eax, EXIT").unwrap();
1832        let result = asm.finish().unwrap();
1833        // mov eax, 60 → B8 3C 00 00 00
1834        assert_eq!(result.bytes(), &[0xB8, 0x3C, 0x00, 0x00, 0x00]);
1835    }
1836
1837    // === Branch relaxation observable from public API ===
1838
1839    #[test]
1840    fn short_branch_uses_rel8() {
1841        let mut asm = Assembler::new(Arch::X86_64);
1842        asm.emit("je done\ndone:\nret").unwrap();
1843        let result = asm.finish().unwrap();
1844        // je rel8 = 74 00, ret = C3
1845        assert_eq!(result.bytes(), &[0x74, 0x00, 0xC3]);
1846    }
1847
1848    // === Data label references ===
1849
1850    #[test]
1851    fn quad_label_reference() {
1852        let mut asm = Assembler::new(Arch::X86_64);
1853        asm.base_address(0x1000);
1854        asm.emit("func:\nnop\nret\njump_table:\n.quad func")
1855            .unwrap();
1856        let result = asm.finish().unwrap();
1857        let bytes = result.bytes();
1858        // func is at 0x1000, nop=1, ret=1, so jump_table at 0x1002
1859        // .quad func → should contain 0x1000 as a u64 LE
1860        let qw = u64::from_le_bytes(bytes[2..10].try_into().unwrap());
1861        assert_eq!(qw, 0x1000);
1862    }
1863
1864    #[test]
1865    fn long_label_reference() {
1866        let mut asm = Assembler::new(Arch::X86_64);
1867        asm.base_address(0x2000);
1868        asm.emit("entry:\nnop\n.long entry").unwrap();
1869        let result = asm.finish().unwrap();
1870        let bytes = result.bytes();
1871        // entry at 0x2000, nop=1B, .long at offset 1 → value is 0x2000
1872        let dw = u32::from_le_bytes(bytes[1..5].try_into().unwrap());
1873        assert_eq!(dw, 0x2000);
1874    }
1875
1876    #[test]
1877    fn name_equals_constant_in_instruction() {
1878        let mut asm = Assembler::new(Arch::X86_64);
1879        asm.emit("ANSWER = 42\nmov eax, ANSWER").unwrap();
1880        let result = asm.finish().unwrap();
1881        // mov eax, 42 → B8 2A 00 00 00
1882        assert_eq!(result.bytes(), &[0xB8, 0x2A, 0x00, 0x00, 0x00]);
1883    }
1884
1885    // === Listing output ===
1886
1887    #[test]
1888    fn listing_simple() {
1889        let mut asm = Assembler::new(Arch::X86_64);
1890        asm.enable_listing();
1891        asm.emit("nop\nret").unwrap();
1892        let result = asm.finish().unwrap();
1893        let listing = result.listing();
1894        assert!(listing.contains("00000000"));
1895        assert!(listing.contains("90")); // nop
1896        assert!(listing.contains("C3")); // ret
1897                                         // Source text annotations
1898        assert!(listing.contains("nop"));
1899        assert!(listing.contains("ret"));
1900    }
1901
1902    #[test]
1903    fn listing_with_labels() {
1904        let mut asm = Assembler::new(Arch::X86_64);
1905        asm.enable_listing();
1906        asm.emit("start:\nnop\nend:\nret").unwrap();
1907        let result = asm.finish().unwrap();
1908        let listing = result.listing();
1909        assert!(listing.contains("start:"));
1910        assert!(listing.contains("end:"));
1911        // Source text with instructions
1912        assert!(listing.contains("nop"));
1913        assert!(listing.contains("ret"));
1914    }
1915
1916    #[test]
1917    fn listing_with_base_address() {
1918        let mut asm = Assembler::new(Arch::X86_64);
1919        asm.enable_listing();
1920        asm.base_address(0x401000);
1921        asm.emit("nop").unwrap();
1922        let result = asm.finish().unwrap();
1923        let listing = result.listing();
1924        assert!(listing.contains("00401000"));
1925        assert!(listing.contains("nop"));
1926    }
1927
1928    #[test]
1929    fn listing_base_address_accessor() {
1930        let mut asm = Assembler::new(Arch::X86_64);
1931        asm.base_address(0x1000);
1932        asm.emit("nop").unwrap();
1933        let result = asm.finish().unwrap();
1934        assert_eq!(result.base_address(), 0x1000);
1935    }
1936
1937    #[test]
1938    fn listing_hex_format() {
1939        let mut asm = Assembler::new(Arch::X86_64);
1940        asm.enable_listing();
1941        asm.emit("push rbp\nmov rbp, rsp").unwrap();
1942        let result = asm.finish().unwrap();
1943        let listing = result.listing();
1944        // push rbp = 55
1945        assert!(listing.contains("55"));
1946        // mov rbp, rsp = 48 89 E5
1947        assert!(listing.contains("4889E5"));
1948        // Source text appears
1949        assert!(listing.contains("push rbp"));
1950        assert!(listing.contains("mov rbp, rsp"));
1951    }
1952
1953    #[test]
1954    fn listing_source_annotations() {
1955        let mut asm = Assembler::new(Arch::X86_64);
1956        asm.enable_listing();
1957        asm.emit("mov eax, 1\nadd eax, 2\nret").unwrap();
1958        let result = asm.finish().unwrap();
1959        let listing = result.listing();
1960        // Each line should have source text
1961        assert!(listing.contains("mov eax, 1"));
1962        assert!(listing.contains("add eax, 2"));
1963        assert!(listing.contains("ret"));
1964    }
1965
1966    #[test]
1967    fn listing_data_annotation() {
1968        let mut asm = Assembler::new(Arch::X86_64);
1969        asm.enable_listing();
1970        asm.emit(".byte 0x90, 0xCC").unwrap();
1971        let result = asm.finish().unwrap();
1972        let listing = result.listing();
1973        assert!(listing.contains(".byte 0x90, 0xCC"));
1974    }
1975
1976    // === Relocations ===
1977
1978    #[test]
1979    fn relocations_returned() {
1980        let mut asm = Assembler::new(Arch::X86_64);
1981        asm.emit("jmp target\nnop\ntarget:\nret").unwrap();
1982        let result = asm.finish().unwrap();
1983        assert!(!result.relocations().is_empty());
1984        assert_eq!(result.relocations()[0].label, "target");
1985    }
1986
1987    #[test]
1988    fn relocations_for_call() {
1989        let mut asm = Assembler::new(Arch::X86_64);
1990        asm.emit("call func\nfunc:\nret").unwrap();
1991        let result = asm.finish().unwrap();
1992        let relocs = result.relocations();
1993        assert!(!relocs.is_empty());
1994        assert_eq!(relocs[0].label, "func");
1995        assert_eq!(relocs[0].kind, crate::encoder::RelocKind::X86Relative);
1996    }
1997
1998    // === Builder convenience methods ===
1999
2000    #[test]
2001    fn builder_ascii() {
2002        let mut asm = Assembler::new(Arch::X86_64);
2003        asm.ascii("AB").unwrap();
2004        let result = asm.finish().unwrap();
2005        assert_eq!(result.bytes(), &[0x41, 0x42]);
2006    }
2007
2008    #[test]
2009    fn builder_asciz() {
2010        let mut asm = Assembler::new(Arch::X86_64);
2011        asm.asciz("Hi").unwrap();
2012        let result = asm.finish().unwrap();
2013        assert_eq!(result.bytes(), &[0x48, 0x69, 0x00]);
2014    }
2015
2016    #[test]
2017    fn builder_align() {
2018        let mut asm = Assembler::new(Arch::X86_64);
2019        asm.db(&[0x90]).unwrap(); // 1 byte
2020        asm.align(4); // pad to 4-byte boundary
2021        asm.db(&[0xCC]).unwrap();
2022        let result = asm.finish().unwrap();
2023        assert_eq!(result.bytes().len(), 5); // 1 + 3 padding + 1
2024        assert_eq!(result.bytes()[4], 0xCC);
2025    }
2026
2027    #[test]
2028    fn builder_align_with_fill() {
2029        let mut asm = Assembler::new(Arch::X86_64);
2030        asm.db(&[0x90]).unwrap();
2031        asm.align_with_fill(4, 0xAA);
2032        asm.db(&[0xCC]).unwrap();
2033        let result = asm.finish().unwrap();
2034        assert_eq!(result.bytes()[1], 0xAA);
2035        assert_eq!(result.bytes()[2], 0xAA);
2036        assert_eq!(result.bytes()[3], 0xAA);
2037    }
2038
2039    #[test]
2040    fn builder_org() {
2041        let mut asm = Assembler::new(Arch::X86_64);
2042        asm.db(&[0x90]).unwrap();
2043        asm.org(4);
2044        asm.db(&[0xCC]).unwrap();
2045        let result = asm.finish().unwrap();
2046        assert_eq!(result.bytes(), &[0x90, 0x00, 0x00, 0x00, 0xCC]);
2047    }
2048
2049    #[test]
2050    fn builder_org_with_fill() {
2051        let mut asm = Assembler::new(Arch::X86_64);
2052        asm.db(&[0x90]).unwrap();
2053        asm.org_with_fill(4, 0xFF);
2054        asm.db(&[0xCC]).unwrap();
2055        let result = asm.finish().unwrap();
2056        assert_eq!(result.bytes(), &[0x90, 0xFF, 0xFF, 0xFF, 0xCC]);
2057    }
2058
2059    #[test]
2060    fn builder_fill() {
2061        // .fill 3, 2, 0xAB → 3 units of 2 bytes each, value=0xAB as LE integer
2062        // Each unit: [0xAB, 0x00] (LE encoding of 0xAB in 2 bytes)
2063        let mut asm = Assembler::new(Arch::X86_64);
2064        asm.fill(3, 2, 0xAB).unwrap();
2065        let result = asm.finish().unwrap();
2066        assert_eq!(result.bytes(), &[0xAB, 0x00, 0xAB, 0x00, 0xAB, 0x00]);
2067    }
2068
2069    #[test]
2070    fn builder_fill_size_1() {
2071        // .fill 4, 1, 0xCC → 4 units of 1 byte, value=0xCC → simple fill
2072        let mut asm = Assembler::new(Arch::X86_64);
2073        asm.fill(4, 1, 0xCC).unwrap();
2074        let result = asm.finish().unwrap();
2075        assert_eq!(result.bytes(), &[0xCC, 0xCC, 0xCC, 0xCC]);
2076    }
2077
2078    #[test]
2079    fn builder_fill_multi_byte_value() {
2080        // .fill 1, 4, 0xDEADBEEF → 1 unit of 4 bytes, value=0xDEADBEEF in LE
2081        let mut asm = Assembler::new(Arch::X86_64);
2082        asm.fill(1, 4, 0xDEADBEEFu32 as i64).unwrap();
2083        let result = asm.finish().unwrap();
2084        assert_eq!(result.bytes(), &[0xEF, 0xBE, 0xAD, 0xDE]);
2085    }
2086
2087    #[test]
2088    fn builder_fill_16bit_value() {
2089        // .fill 2, 2, 0x1234 → 2 units of 2 bytes
2090        let mut asm = Assembler::new(Arch::X86_64);
2091        asm.fill(2, 2, 0x1234).unwrap();
2092        let result = asm.finish().unwrap();
2093        assert_eq!(result.bytes(), &[0x34, 0x12, 0x34, 0x12]);
2094    }
2095
2096    #[test]
2097    fn builder_space() {
2098        let mut asm = Assembler::new(Arch::X86_64);
2099        asm.space(4).unwrap();
2100        let result = asm.finish().unwrap();
2101        assert_eq!(result.bytes(), &[0x00, 0x00, 0x00, 0x00]);
2102    }
2103
2104    // === Listing annotations for directives ===
2105
2106    #[test]
2107    fn listing_fill_annotation() {
2108        let mut asm = Assembler::new(Arch::X86_64);
2109        asm.enable_listing();
2110        asm.emit(".fill 2, 1, 0x90").unwrap();
2111        let result = asm.finish().unwrap();
2112        let listing = result.listing();
2113        assert!(listing.contains(".fill 2, 1, 0x90"));
2114    }
2115
2116    #[test]
2117    fn listing_space_annotation() {
2118        let mut asm = Assembler::new(Arch::X86_64);
2119        asm.enable_listing();
2120        asm.emit(".space 4").unwrap();
2121        let result = asm.finish().unwrap();
2122        let listing = result.listing();
2123        assert!(listing.contains(".space 4"));
2124    }
2125
2126    #[test]
2127    fn listing_align_annotation() {
2128        let mut asm = Assembler::new(Arch::X86_64);
2129        asm.enable_listing();
2130        asm.emit("nop\n.align 4\nnop").unwrap();
2131        let result = asm.finish().unwrap();
2132        let listing = result.listing();
2133        assert!(listing.contains(".align 4"));
2134    }
2135
2136    #[test]
2137    fn listing_org_annotation() {
2138        let mut asm = Assembler::new(Arch::X86_64);
2139        asm.enable_listing();
2140        asm.emit("nop\n.org 0x10\nnop").unwrap();
2141        let result = asm.finish().unwrap();
2142        let listing = result.listing();
2143        assert!(listing.contains(".org 0x10"));
2144    }
2145
2146    // === .org fill byte ===
2147
2148    #[test]
2149    fn org_with_fill_byte() {
2150        let mut asm = Assembler::new(Arch::X86_64);
2151        asm.emit("nop\n.org 0x04, 0xFF\nnop").unwrap();
2152        let result = asm.finish().unwrap();
2153        // nop (0x90) + 3 fill bytes (0xFF) + nop (0x90)
2154        assert_eq!(result.bytes(), &[0x90, 0xFF, 0xFF, 0xFF, 0x90]);
2155    }
2156
2157    // === AT&T Syntax ===
2158
2159    #[test]
2160    fn att_syntax_basic() {
2161        let mut asm = Assembler::new(Arch::X86_64);
2162        asm.syntax(Syntax::Att);
2163        asm.emit("movq $1, %rax").unwrap();
2164        let result = asm.finish().unwrap();
2165        // mov rax, 1 → optimizer narrows to mov eax, 1 = B8 01 00 00 00
2166        assert_eq!(result.bytes(), &[0xB8, 0x01, 0x00, 0x00, 0x00]);
2167    }
2168
2169    // === Resource Limits ===
2170
2171    #[test]
2172    fn resource_limit_max_statements() {
2173        let mut asm = Assembler::new(Arch::X86_64);
2174        asm.limits(ResourceLimits {
2175            max_statements: 3,
2176            ..ResourceLimits::default()
2177        });
2178        // 3 statements: ok
2179        asm.emit("nop; nop; nop").unwrap();
2180        // 2 more statements: total = 5 > 3, should fail
2181        let err = asm.emit("nop; nop").unwrap_err();
2182        match err {
2183            AsmError::ResourceLimitExceeded { resource, limit } => {
2184                assert_eq!(resource, "statements");
2185                assert_eq!(limit, 3);
2186            }
2187            other => panic!("expected ResourceLimitExceeded, got: {other:?}"),
2188        }
2189    }
2190
2191    #[test]
2192    fn resource_limit_max_labels() {
2193        let mut asm = Assembler::new(Arch::X86_64);
2194        asm.limits(ResourceLimits {
2195            max_labels: 2,
2196            ..ResourceLimits::default()
2197        });
2198        asm.label("a").unwrap();
2199        asm.label("b").unwrap();
2200        let err = asm.label("c").unwrap_err();
2201        match err {
2202            AsmError::ResourceLimitExceeded { resource, limit } => {
2203                assert_eq!(resource, "labels");
2204                assert_eq!(limit, 2);
2205            }
2206            other => panic!("expected ResourceLimitExceeded, got: {other:?}"),
2207        }
2208    }
2209
2210    #[test]
2211    fn resource_limit_max_labels_via_emit() {
2212        let mut asm = Assembler::new(Arch::X86_64);
2213        asm.limits(ResourceLimits {
2214            max_labels: 1,
2215            ..ResourceLimits::default()
2216        });
2217        asm.emit("a: nop").unwrap();
2218        let err = asm.emit("b: nop").unwrap_err();
2219        match err {
2220            AsmError::ResourceLimitExceeded { resource, limit } => {
2221                assert_eq!(resource, "labels");
2222                assert_eq!(limit, 1);
2223            }
2224            other => panic!("expected ResourceLimitExceeded, got: {other:?}"),
2225        }
2226    }
2227
2228    #[test]
2229    fn resource_limit_max_output_bytes() {
2230        let mut asm = Assembler::new(Arch::X86_64);
2231        asm.limits(ResourceLimits {
2232            max_output_bytes: 4,
2233            ..ResourceLimits::default()
2234        });
2235        asm.emit("nop; nop; nop; nop").unwrap(); // 4 bytes = exactly at limit: ok
2236        let result = asm.finish();
2237        assert!(result.is_ok());
2238
2239        // With eager checking, the limit is now caught at emit() time
2240        let mut asm2 = Assembler::new(Arch::X86_64);
2241        asm2.limits(ResourceLimits {
2242            max_output_bytes: 3,
2243            ..ResourceLimits::default()
2244        });
2245        let err = asm2.emit("nop; nop; nop; nop").unwrap_err(); // 4 bytes > 3: fail at emit
2246        match err {
2247            AsmError::ResourceLimitExceeded { resource, limit } => {
2248                assert_eq!(resource, "output bytes");
2249                assert_eq!(limit, 3);
2250            }
2251            other => panic!("expected ResourceLimitExceeded, got: {other:?}"),
2252        }
2253    }
2254
2255    #[test]
2256    fn resource_limits_default_does_not_interfere() {
2257        // Default limits should be generous enough for normal use
2258        let mut asm = Assembler::new(Arch::X86_64);
2259        // Emit a lot of instructions at once
2260        let source: String = (0..1000).map(|_| "nop; ").collect();
2261        asm.emit(&source).unwrap();
2262        let result = asm.finish().unwrap();
2263        assert_eq!(result.len(), 1000);
2264    }
2265
2266    #[test]
2267    fn resource_limit_max_recursion_depth() {
2268        let mut asm = Assembler::new(Arch::X86_64);
2269        asm.limits(ResourceLimits {
2270            max_recursion_depth: 3,
2271            ..ResourceLimits::default()
2272        });
2273        // A macro that calls itself — should hit the recursion limit quickly
2274        let result = asm.emit(".macro boom\nboom\n.endm\nboom");
2275        assert!(result.is_err());
2276        let err = result.unwrap_err();
2277        match err {
2278            AsmError::ResourceLimitExceeded { resource, limit } => {
2279                assert_eq!(resource, "macro recursion depth");
2280                assert_eq!(limit, 3);
2281            }
2282            _ => panic!("expected ResourceLimitExceeded, got {:?}", err),
2283        }
2284    }
2285
2286    // ─── encode_one ────────────────────────────────────────────────
2287
2288    #[test]
2289    fn encode_one_nop() {
2290        let asm = Assembler::new(Arch::X86_64);
2291        let bytes = asm.encode_one("nop").unwrap();
2292        assert_eq!(bytes, alloc::vec![0x90]);
2293    }
2294
2295    #[test]
2296    fn encode_one_ret() {
2297        let asm = Assembler::new(Arch::X86_64);
2298        let bytes = asm.encode_one("ret").unwrap();
2299        assert_eq!(bytes, alloc::vec![0xC3]);
2300    }
2301
2302    #[test]
2303    fn encode_one_empty_input() {
2304        let asm = Assembler::new(Arch::X86_64);
2305        let bytes = asm.encode_one("").unwrap();
2306        assert!(bytes.is_empty());
2307    }
2308
2309    #[test]
2310    fn encode_one_rejects_label() {
2311        let asm = Assembler::new(Arch::X86_64);
2312        assert!(asm.encode_one("foo:").is_err());
2313    }
2314
2315    #[test]
2316    fn encode_one_does_not_affect_state() {
2317        let asm = Assembler::new(Arch::X86_64);
2318        let _ = asm.encode_one("nop").unwrap();
2319        // Finish should produce empty output since encode_one doesn't
2320        // add to internal state
2321        let result = asm.finish().unwrap();
2322        assert!(result.is_empty());
2323    }
2324
2325    // ─── define_preprocessor_symbol ────────────────────────────────
2326
2327    #[test]
2328    fn define_preprocessor_symbol_ifdef() {
2329        let mut asm = Assembler::new(Arch::X86_64);
2330        asm.define_preprocessor_symbol("DEBUG", 1);
2331        asm.emit(".ifdef DEBUG\nnop\n.endif").unwrap();
2332        let result = asm.finish().unwrap();
2333        assert_eq!(result.bytes(), &[0x90]);
2334    }
2335
2336    #[test]
2337    fn define_preprocessor_symbol_skipped_when_missing() {
2338        let mut asm = Assembler::new(Arch::X86_64);
2339        // DEBUG is NOT defined — block should be skipped
2340        asm.emit(".ifdef DEBUG\nnop\n.endif\nret").unwrap();
2341        let result = asm.finish().unwrap();
2342        assert_eq!(result.bytes(), &[0xC3]); // only ret
2343    }
2344
2345    // ─── dq (64-bit data) ─────────────────────────────────────────
2346
2347    #[test]
2348    fn builder_dq() {
2349        let mut asm = Assembler::new(Arch::X86_64);
2350        asm.dq(0xDEAD_BEEF_CAFE_BABE).unwrap();
2351        let result = asm.finish().unwrap();
2352        assert_eq!(result.bytes(), &0xDEAD_BEEF_CAFE_BABEu64.to_le_bytes());
2353    }
2354
2355    // ─── reset ────────────────────────────────────────────────────
2356
2357    #[test]
2358    fn reset_clears_state_keeps_config() {
2359        let mut asm = Assembler::new(Arch::X86_64);
2360        asm.emit("nop").unwrap();
2361        asm.reset();
2362        asm.emit("ret").unwrap();
2363        let result = asm.finish().unwrap();
2364        // Only "ret" should be present — "nop" was cleared
2365        assert_eq!(result.bytes(), &[0xC3]);
2366    }
2367
2368    #[test]
2369    fn reset_allows_reuse() {
2370        let mut asm = Assembler::new(Arch::X86_64);
2371        asm.emit("nop").unwrap();
2372        // Reset discards the nop, then assemble fresh
2373        asm.reset();
2374        asm.emit("ret").unwrap();
2375        let result = asm.finish().unwrap();
2376        assert_eq!(result.bytes(), &[0xC3]);
2377    }
2378
2379    // ─── current_fragment_count ───────────────────────────────────
2380
2381    #[test]
2382    fn current_fragment_count_tracks_emissions() {
2383        let mut asm = Assembler::new(Arch::X86_64);
2384        assert_eq!(asm.current_fragment_count(), 0);
2385        asm.emit("nop").unwrap();
2386        assert!(asm.current_fragment_count() > 0);
2387    }
2388
2389    // ─── is_empty ─────────────────────────────────────────────────
2390
2391    #[test]
2392    fn empty_assembly_result() {
2393        let asm = Assembler::new(Arch::X86_64);
2394        let result = asm.finish().unwrap();
2395        assert!(result.is_empty());
2396        assert_eq!(result.len(), 0);
2397        assert!(result.bytes().is_empty());
2398    }
2399
2400    // ─── labels() direct access ───────────────────────────────────
2401
2402    #[test]
2403    fn labels_slice_access() {
2404        let mut asm = Assembler::new(Arch::X86_64);
2405        asm.emit("start:\nnop\nend:\nret").unwrap();
2406        let result = asm.finish().unwrap();
2407        let labels = result.labels();
2408        // Two labels defined
2409        assert_eq!(labels.len(), 2);
2410        // Check both are present (order may vary)
2411        assert!(labels.iter().any(|(name, _)| name == "start"));
2412        assert!(labels.iter().any(|(name, _)| name == "end"));
2413    }
2414
2415    // ─── assemble_with externals ──────────────────────────────────
2416
2417    #[test]
2418    fn assemble_with_external_labels() {
2419        use crate::assemble_with;
2420        let bytes =
2421            assemble_with("call target", Arch::X86_64, 0x1000, &[("target", 0x2000)]).unwrap();
2422        // call rel32: E8 xx xx xx xx — target is at 0x2000, PC after call = 0x1000+5 = 0x1005
2423        // rel32 = 0x2000 - 0x1005 = 0x0FFB
2424        assert_eq!(bytes[0], 0xE8);
2425        let rel = i32::from_le_bytes(bytes[1..5].try_into().unwrap());
2426        assert_eq!(rel, 0x0FFB);
2427    }
2428
2429    // ─── multiple errors (AsmError::Multiple) ────────────────────
2430
2431    #[test]
2432    fn multiple_errors_collected() {
2433        let mut asm = Assembler::new(Arch::X86_64);
2434        // Emit multiple bad mnemonics — errors are collected, not fail-fast
2435        asm.emit("badmnem1\nbadmnem2").unwrap();
2436        let err = asm.finish().unwrap_err();
2437        match err {
2438            AsmError::Multiple { errors } => assert_eq!(errors.len(), 2),
2439            _ => panic!("expected Multiple error, got: {err}"),
2440        }
2441    }
2442
2443    // ─── optimizer no-op for non-x86 ─────────────────────────────
2444
2445    #[cfg(feature = "arm")]
2446    #[test]
2447    fn optimizer_noop_for_arm() {
2448        let mut asm = Assembler::new(Arch::Arm);
2449        // ARM mov r0, 0 should NOT be optimized to xor (that's x86-only)
2450        asm.emit("mov r0, 0").unwrap();
2451        let result = asm.finish().unwrap();
2452        // ARM "mov r0, #0" encodes as: E3A00000 (condition AL, MOV, Rd=0, imm=0)
2453        assert_eq!(result.len(), 4);
2454        assert_eq!(result.bytes(), &[0x00, 0x00, 0xA0, 0xE3]);
2455    }
2456
2457    // ─── .org directive through assembler pipeline ───────────────
2458
2459    #[test]
2460    fn org_directive_via_emit() {
2461        let mut asm = Assembler::new(Arch::X86_64);
2462        asm.emit("nop\n.org 0x10\nnop").unwrap();
2463        let result = asm.finish().unwrap();
2464        // nop (1 byte) + padding to 0x10 (15 zero bytes) + nop (1 byte) = 17 bytes
2465        assert_eq!(result.len(), 17);
2466        assert_eq!(result.bytes()[0], 0x90); // first nop
2467        assert_eq!(result.bytes()[0x10], 0x90); // nop at offset 0x10
2468                                                // bytes 1..0x10 should be zero-fill
2469        for &b in &result.bytes()[1..0x10] {
2470            assert_eq!(b, 0x00);
2471        }
2472    }
2473
2474    // ─── listing output ──────────────────────────────────────────
2475
2476    #[test]
2477    fn listing_includes_label_and_hex() {
2478        let mut asm = Assembler::new(Arch::X86_64);
2479        asm.emit("start:\nnop\nret").unwrap();
2480        let result = asm.finish().unwrap();
2481        let listing = result.listing();
2482        // Listing should contain the label name
2483        assert!(
2484            listing.contains("start"),
2485            "listing should contain label 'start'"
2486        );
2487        // Listing should contain hex bytes (90 = nop, C3 = ret)
2488        assert!(
2489            listing.contains("90"),
2490            "listing should contain '90' for nop"
2491        );
2492        assert!(
2493            listing.contains("C3") || listing.contains("c3"),
2494            "listing should contain 'C3' for ret"
2495        );
2496    }
2497
2498    #[test]
2499    fn listing_with_base_address_format() {
2500        let mut asm = Assembler::new(Arch::X86_64);
2501        asm.base_address(0x401000);
2502        asm.emit("nop\nret").unwrap();
2503        let result = asm.finish().unwrap();
2504        let listing = result.listing();
2505        // Should include the base address in the listing
2506        assert!(
2507            listing.contains("00401000") || listing.contains("401000"),
2508            "listing should contain base address"
2509        );
2510    }
2511
2512    // ─── .org via builder method ─────────────────────────────────
2513
2514    #[test]
2515    fn org_builder_method() {
2516        let mut asm = Assembler::new(Arch::X86_64);
2517        asm.emit("nop").unwrap();
2518        asm.org(0x10);
2519        asm.emit("nop").unwrap();
2520        let result = asm.finish().unwrap();
2521        assert_eq!(result.len(), 17); // 1 + 15 padding + 1
2522    }
2523
2524    // ─── JECXZ relaxation (was BranchOutOfRange before relaxation support) ──
2525
2526    #[test]
2527    fn jecxz_relaxes_to_long_form() {
2528        // JECXZ targets beyond ±127 bytes now auto-relax to the compound
2529        // sequence: JECXZ +2 / JMP short +5 / JMP near rel32
2530        let mut asm = Assembler::new(Arch::X86_64);
2531        asm.emit("jecxz target").unwrap();
2532        asm.space(200).unwrap(); // 200 bytes > 127 (rel8 max)
2533        asm.emit("target:\nnop").unwrap();
2534        let result = asm.finish().unwrap();
2535        // Long form starts with 67 E3 02 EB 05 E9 [rel32]
2536        assert_eq!(result.bytes[0], 0x67);
2537        assert_eq!(result.bytes[1], 0xE3);
2538        assert_eq!(result.bytes[2], 0x02);
2539        assert_eq!(result.bytes[3], 0xEB);
2540        assert_eq!(result.bytes[4], 0x05);
2541        assert_eq!(result.bytes[5], 0xE9);
2542        // Target is at offset 10+200 = 210, RIP after JMP = 10
2543        // disp = 210 - 10 = 200 = 0xC8
2544        assert_eq!(result.bytes[6], 0xC8);
2545        assert_eq!(result.bytes[7], 0x00);
2546        assert_eq!(result.bytes[8], 0x00);
2547        assert_eq!(result.bytes[9], 0x00);
2548    }
2549
2550    #[test]
2551    fn jecxz_relaxes_to_short_form_when_near() {
2552        // JECXZ targets within ±127 bytes relax to the compact 67 E3 rel8
2553        let mut asm = Assembler::new(Arch::X86_64);
2554        asm.emit("jecxz target").unwrap();
2555        asm.emit("target:\nnop").unwrap();
2556        let result = asm.finish().unwrap();
2557        // Short form: 67 E3 rel8 (3 bytes)
2558        assert_eq!(result.bytes[0], 0x67);
2559        assert_eq!(result.bytes[1], 0xE3);
2560        // rel8 = 0 (target is immediately after the instruction)
2561        assert_eq!(result.bytes[2], 0x00);
2562        assert_eq!(result.bytes[3], 0x90); // NOP
2563    }
2564
2565    // ─── error collection: single error yields single, not Multiple ─
2566
2567    #[test]
2568    fn single_error_not_wrapped_in_multiple() {
2569        let mut asm = Assembler::new(Arch::X86_64);
2570        asm.emit("badmnem").unwrap();
2571        let err = asm.finish().unwrap_err();
2572        // A single encoding error should be returned directly, not wrapped
2573        assert!(matches!(err, AsmError::UnknownMnemonic { .. }));
2574    }
2575
2576    // ─── error collection: good + bad instructions ───────────────
2577
2578    #[test]
2579    fn errors_collected_with_valid_instructions() {
2580        let mut asm = Assembler::new(Arch::X86_64);
2581        // Mix valid and invalid instructions — valid ones still get encoded
2582        asm.emit("nop\nbadmnem\nret").unwrap();
2583        let err = asm.finish().unwrap_err();
2584        // Should be a single UnknownMnemonic (only 1 bad instruction)
2585        assert!(matches!(err, AsmError::UnknownMnemonic { .. }));
2586    }
2587
2588    // ─── error collection across multiple emit() calls ───────────
2589
2590    #[test]
2591    fn errors_collected_across_emit_calls() {
2592        let mut asm = Assembler::new(Arch::X86_64);
2593        asm.emit("bad1").unwrap();
2594        asm.emit("bad2").unwrap();
2595        asm.emit("bad3").unwrap();
2596        let err = asm.finish().unwrap_err();
2597        match err {
2598            AsmError::Multiple { errors } => assert_eq!(errors.len(), 3),
2599            _ => panic!("expected Multiple error with 3 errors, got: {err}"),
2600        }
2601    }
2602
2603    // ─── reset clears collected errors ───────────────────────────
2604
2605    #[test]
2606    fn reset_clears_errors() {
2607        let mut asm = Assembler::new(Arch::X86_64);
2608        asm.emit("badmnem").unwrap();
2609        asm.reset();
2610        asm.emit("nop").unwrap();
2611        let result = asm.finish().unwrap();
2612        assert_eq!(result.bytes(), &[0x90]);
2613    }
2614
2615    // ─── max_errors resource limit ───────────────────────────────
2616
2617    #[test]
2618    fn max_errors_limit_enforced() {
2619        let mut asm = Assembler::new(Arch::X86_64);
2620        asm.limits(ResourceLimits {
2621            max_errors: 2,
2622            ..ResourceLimits::default()
2623        });
2624        // Third bad mnemonic should trigger ResourceLimitExceeded
2625        let result = asm.emit("bad1\nbad2\nbad3");
2626        assert!(result.is_err());
2627        let err = result.unwrap_err();
2628        assert!(matches!(err, AsmError::ResourceLimitExceeded { .. }));
2629    }
2630
2631    // ─── literal pool ────────────────────────────────────────────
2632
2633    #[test]
2634    fn literal_pool_basic_x_reg() {
2635        // LDR X0, =0x12345678 → emits LDR (literal) + pool data at finish
2636        let mut asm = Assembler::new(Arch::Aarch64);
2637        asm.emit("ldr x0, =0x12345678").unwrap();
2638        let result = asm.finish().unwrap();
2639        let bytes = result.bytes();
2640        // First 4 bytes: LDR (literal) instruction
2641        assert!(
2642            bytes.len() >= 8,
2643            "expected at least 8 bytes, got {}",
2644            bytes.len()
2645        );
2646        // Pool data should contain 0x12345678 as 8 bytes LE
2647        let pool_start = bytes.len() - 8;
2648        let pool_val = u64::from_le_bytes(bytes[pool_start..pool_start + 8].try_into().unwrap());
2649        assert_eq!(pool_val, 0x12345678, "pool should contain the constant");
2650    }
2651
2652    #[test]
2653    fn literal_pool_basic_w_reg() {
2654        // LDR W0, =0x42 → emits LDR (literal) + 4-byte pool data
2655        let mut asm = Assembler::new(Arch::Aarch64);
2656        asm.emit("ldr w0, =0x42").unwrap();
2657        let result = asm.finish().unwrap();
2658        let bytes = result.bytes();
2659        // Pool data should contain 0x42 as 4 bytes LE
2660        let pool_start = bytes.len() - 4;
2661        let pool_val = u32::from_le_bytes(bytes[pool_start..pool_start + 4].try_into().unwrap());
2662        assert_eq!(pool_val, 0x42, "pool should contain the constant");
2663    }
2664
2665    #[test]
2666    fn literal_pool_with_ltorg() {
2667        // Explicit .ltorg flushes the pool
2668        let mut asm = Assembler::new(Arch::Aarch64);
2669        asm.emit("ldr x0, =0xCAFE\n.ltorg").unwrap();
2670        let result = asm.finish().unwrap();
2671        let bytes = result.bytes();
2672        // Should have: 4 bytes LDR + alignment + 8 bytes pool data
2673        assert!(bytes.len() >= 12);
2674        // Pool value at end
2675        let pool_start = bytes.len() - 8;
2676        let pool_val = u64::from_le_bytes(bytes[pool_start..pool_start + 8].try_into().unwrap());
2677        assert_eq!(pool_val, 0xCAFE);
2678    }
2679
2680    #[test]
2681    fn literal_pool_deduplication() {
2682        // Two LDR with same value should share one pool entry
2683        let mut asm = Assembler::new(Arch::Aarch64);
2684        asm.emit("ldr x0, =0x1234\nldr x1, =0x1234").unwrap();
2685        let result = asm.finish().unwrap();
2686        let bytes = result.bytes();
2687        // 2 LDR instructions (8 bytes) + alignment + 1 pool entry (8 bytes)
2688        // Without dedup: 8 + alignment + 16 = 24+
2689        // With dedup: 8 + alignment + 8 = 16+
2690        // The pool should contain exactly one 8-byte entry
2691        assert!(
2692            bytes.len() <= 24,
2693            "expected <= 24 bytes with dedup, got {}",
2694            bytes.len()
2695        );
2696    }
2697
2698    #[test]
2699    fn literal_pool_multiple_values() {
2700        // Two LDR with different values → two pool entries
2701        let mut asm = Assembler::new(Arch::Aarch64);
2702        asm.emit("ldr x0, =0xAAAA\nldr x1, =0xBBBB").unwrap();
2703        let result = asm.finish().unwrap();
2704        let bytes = result.bytes();
2705        // Should have both values in the pool
2706        let pool_end = bytes.len();
2707        let val2 = u64::from_le_bytes(bytes[pool_end - 8..pool_end].try_into().unwrap());
2708        let val1 = u64::from_le_bytes(bytes[pool_end - 16..pool_end - 8].try_into().unwrap());
2709        assert!(
2710            (val1 == 0xAAAA && val2 == 0xBBBB) || (val1 == 0xBBBB && val2 == 0xAAAA),
2711            "pool should contain both values, got {:#x} and {:#x}",
2712            val1,
2713            val2
2714        );
2715    }
2716
2717    #[test]
2718    fn literal_pool_ldr_encodes_pc_relative() {
2719        // Verify the LDR instruction word encodes imm19 pointing to the pool
2720        let mut asm = Assembler::new(Arch::Aarch64);
2721        asm.emit("ldr x0, =0xFF").unwrap();
2722        let result = asm.finish().unwrap();
2723        let bytes = result.bytes();
2724        // First 4 bytes are LDR (literal): opc=01 | 011000 | imm19 | Rt
2725        let word = u32::from_le_bytes(bytes[0..4].try_into().unwrap());
2726        // opc should be 01 (64-bit) at bits 31:30
2727        assert_eq!((word >> 30) & 0b11, 0b01, "opc should be 01 for 64-bit LDR");
2728        // bits 29:24 should be 011000
2729        assert_eq!(
2730            (word >> 24) & 0b111111,
2731            0b011000,
2732            "should be LDR literal encoding"
2733        );
2734        // Rt should be X0 = 0
2735        assert_eq!(word & 0x1F, 0, "Rt should be X0");
2736        // imm19 should be positive (pool is after the instruction)
2737        let imm19 = ((word >> 5) & 0x7FFFF) as i32;
2738        assert!(imm19 > 0, "imm19 should be positive (pool is after instr)");
2739    }
2740
2741    #[test]
2742    fn literal_pool_large_64bit_value() {
2743        let mut asm = Assembler::new(Arch::Aarch64);
2744        asm.emit("ldr x0, =0xDEADBEEFCAFEBABE").unwrap();
2745        let result = asm.finish().unwrap();
2746        let bytes = result.bytes();
2747        let pool_start = bytes.len() - 8;
2748        let pool_val = u64::from_le_bytes(bytes[pool_start..pool_start + 8].try_into().unwrap());
2749        assert_eq!(pool_val, 0xDEADBEEFCAFEBABE);
2750    }
2751
2752    #[test]
2753    fn literal_pool_negative_value() {
2754        let mut asm = Assembler::new(Arch::Aarch64);
2755        asm.emit("ldr x0, =-1").unwrap();
2756        let result = asm.finish().unwrap();
2757        let bytes = result.bytes();
2758        let pool_start = bytes.len() - 8;
2759        let pool_val = u64::from_le_bytes(bytes[pool_start..pool_start + 8].try_into().unwrap());
2760        // -1 as u64 = 0xFFFFFFFFFFFFFFFF
2761        assert_eq!(pool_val, 0xFFFFFFFFFFFFFFFF);
2762    }
2763
2764    #[test]
2765    fn literal_pool_pool_directive() {
2766        // .pool is an alias for .ltorg
2767        let mut asm = Assembler::new(Arch::Aarch64);
2768        asm.emit("ldr x0, =0xBEEF\n.pool").unwrap();
2769        let result = asm.finish().unwrap();
2770        let bytes = result.bytes();
2771        let pool_start = bytes.len() - 8;
2772        let pool_val = u64::from_le_bytes(bytes[pool_start..pool_start + 8].try_into().unwrap());
2773        assert_eq!(pool_val, 0xBEEF);
2774    }
2775
2776    #[test]
2777    fn literal_pool_reset_clears_pool() {
2778        let mut asm = Assembler::new(Arch::Aarch64);
2779        asm.emit("ldr x0, =0x1234").unwrap();
2780        asm.reset();
2781        // After reset, pool should be empty; emitting just a NOP should work
2782        asm.emit("nop").unwrap();
2783        let result = asm.finish().unwrap();
2784        assert_eq!(result.bytes(), &[0x1F, 0x20, 0x03, 0xD5]); // NOP only, no pool data
2785    }
2786
2787    // ─── ARM literal pool ────────────────────────────────────────
2788
2789    #[test]
2790    fn arm_literal_pool_basic() {
2791        // LDR R0, =0x12345678 on ARM → LDR (literal) + 4-byte pool entry
2792        let mut asm = Assembler::new(Arch::Arm);
2793        asm.emit("ldr r0, =0x12345678").unwrap();
2794        let result = asm.finish().unwrap();
2795        let bytes = result.bytes();
2796        // First 4 bytes: LDR instruction, then 4 bytes pool data
2797        assert!(
2798            bytes.len() >= 8,
2799            "expected at least 8 bytes, got {}",
2800            bytes.len()
2801        );
2802        // Pool data: 4 bytes LE for ARM
2803        let pool_start = bytes.len() - 4;
2804        let pool_val = u32::from_le_bytes(bytes[pool_start..pool_start + 4].try_into().unwrap());
2805        assert_eq!(pool_val, 0x12345678, "pool should contain the constant");
2806    }
2807
2808    #[test]
2809    fn arm_literal_pool_small_value() {
2810        // Even small values go through the literal pool path
2811        let mut asm = Assembler::new(Arch::Arm);
2812        asm.emit("ldr r3, =42").unwrap();
2813        let result = asm.finish().unwrap();
2814        let bytes = result.bytes();
2815        let pool_start = bytes.len() - 4;
2816        let pool_val = u32::from_le_bytes(bytes[pool_start..pool_start + 4].try_into().unwrap());
2817        assert_eq!(pool_val, 42);
2818    }
2819
2820    #[test]
2821    fn arm_literal_pool_negative_value() {
2822        let mut asm = Assembler::new(Arch::Arm);
2823        asm.emit("ldr r0, =-1").unwrap();
2824        let result = asm.finish().unwrap();
2825        let bytes = result.bytes();
2826        let pool_start = bytes.len() - 4;
2827        let pool_val = u32::from_le_bytes(bytes[pool_start..pool_start + 4].try_into().unwrap());
2828        // -1 as u32 = 0xFFFFFFFF
2829        assert_eq!(pool_val, 0xFFFFFFFF);
2830    }
2831
2832    #[test]
2833    fn arm_literal_pool_deduplication() {
2834        // Two LDR with same value should share one pool entry
2835        let mut asm = Assembler::new(Arch::Arm);
2836        asm.emit("ldr r0, =0xAABB\nldr r1, =0xAABB").unwrap();
2837        let result = asm.finish().unwrap();
2838        let bytes = result.bytes();
2839        // 2 LDR instructions (8 bytes) + alignment + 1 pool entry (4 bytes)
2840        // Without dedup: 8 + 8 = 16; with dedup: 8 + 4 = 12
2841        assert!(
2842            bytes.len() <= 16,
2843            "expected <=16 bytes with dedup, got {}",
2844            bytes.len()
2845        );
2846    }
2847
2848    #[test]
2849    fn arm_literal_pool_multiple_values() {
2850        // Different values → separate pool entries
2851        let mut asm = Assembler::new(Arch::Arm);
2852        asm.emit("ldr r0, =0x1111\nldr r1, =0x2222").unwrap();
2853        let result = asm.finish().unwrap();
2854        let bytes = result.bytes();
2855        // Two 4-byte pool entries at the end
2856        let pool_end = bytes.len();
2857        let val2 = u32::from_le_bytes(bytes[pool_end - 4..pool_end].try_into().unwrap());
2858        let val1 = u32::from_le_bytes(bytes[pool_end - 8..pool_end - 4].try_into().unwrap());
2859        assert!(
2860            (val1 == 0x1111 && val2 == 0x2222) || (val1 == 0x2222 && val2 == 0x1111),
2861            "pool should contain both values, got {:#x} and {:#x}",
2862            val1,
2863            val2
2864        );
2865    }
2866
2867    #[test]
2868    fn arm_literal_pool_with_ltorg() {
2869        // Explicit .ltorg flushes the pool
2870        let mut asm = Assembler::new(Arch::Arm);
2871        asm.emit("ldr r0, =0xCAFE\n.ltorg").unwrap();
2872        let result = asm.finish().unwrap();
2873        let bytes = result.bytes();
2874        assert!(bytes.len() >= 8);
2875        let pool_start = bytes.len() - 4;
2876        let pool_val = u32::from_le_bytes(bytes[pool_start..pool_start + 4].try_into().unwrap());
2877        assert_eq!(pool_val, 0xCAFE);
2878    }
2879
2880    #[test]
2881    fn arm_literal_pool_pool_directive() {
2882        // .pool is an alias for .ltorg
2883        let mut asm = Assembler::new(Arch::Arm);
2884        asm.emit("ldr r0, =0xBEEF\n.pool").unwrap();
2885        let result = asm.finish().unwrap();
2886        let bytes = result.bytes();
2887        let pool_start = bytes.len() - 4;
2888        let pool_val = u32::from_le_bytes(bytes[pool_start..pool_start + 4].try_into().unwrap());
2889        assert_eq!(pool_val, 0xBEEF);
2890    }
2891
2892    #[test]
2893    fn arm_literal_pool_ldr_encodes_pc_relative() {
2894        // Verify the LDR instruction uses PC-relative addressing to pool
2895        let mut asm = Assembler::new(Arch::Arm);
2896        asm.emit("ldr r0, =0xFF").unwrap();
2897        let result = asm.finish().unwrap();
2898        let bytes = result.bytes();
2899        // First 4 bytes: LDR Rd, [PC, #offset]
2900        let word = u32::from_le_bytes(bytes[0..4].try_into().unwrap());
2901        // bits [27:26] = 01 (load/store immediate offset)
2902        assert_eq!(
2903            (word >> 26) & 0b11,
2904            0b01,
2905            "should be load/store word encoding"
2906        );
2907        // bits [19:16] = Rn = 15 (PC)
2908        assert_eq!((word >> 16) & 0xF, 15, "Rn should be PC (R15)");
2909        // bits [15:12] = Rd = 0 (R0)
2910        assert_eq!((word >> 12) & 0xF, 0, "Rd should be R0");
2911        // L bit (bit 20) = 1 (load)
2912        assert_eq!((word >> 20) & 1, 1, "should be a load");
2913    }
2914
2915    #[test]
2916    fn arm_literal_pool_entry_always_4_bytes() {
2917        // ARM pool entries should always be 4 bytes regardless of register
2918        let mut asm = Assembler::new(Arch::Arm);
2919        asm.emit("ldr r0, =0x1\nldr r15, =0x2").unwrap();
2920        let result = asm.finish().unwrap();
2921        let bytes = result.bytes();
2922        // 2 LDR (8 bytes) + 2 pool entries (8 bytes) = 16 bytes
2923        // No alignment needed for 4-byte entries on 4-byte boundary
2924        assert!(
2925            bytes.len() <= 16,
2926            "ARM pool entries should be 4 bytes each, got {} total",
2927            bytes.len()
2928        );
2929    }
2930
2931    #[test]
2932    fn arm_literal_pool_hex_large() {
2933        let mut asm = Assembler::new(Arch::Arm);
2934        asm.emit("ldr r5, =0xDEADBEEF").unwrap();
2935        let result = asm.finish().unwrap();
2936        let bytes = result.bytes();
2937        let pool_start = bytes.len() - 4;
2938        let pool_val = u32::from_le_bytes(bytes[pool_start..pool_start + 4].try_into().unwrap());
2939        assert_eq!(pool_val, 0xDEADBEEF);
2940    }
2941}