Skip to main content

qcode_emulator/
concrete.rs

1use crate::{
2    CallContinuation, CallInterception, CallSite, DomainMemory, EmulatorError, EmulatorErrorKind,
3    Interpreter,
4};
5use qcode::{
6    address_index::{AddressIndex, AddressTarget},
7    context::Context,
8    space::{MemorySpaceId, Space, SpaceId, SpaceType},
9    value::{
10        BasicBlock, BlockId, BlockParamId, BlockRef, FunctionBody, FunctionId, Instruction,
11        LocalValueId, Value, ValueId, ValueRef, Varnode,
12        insn::{
13            Branch, BranchInd, CBranch, Call, CallInd, Callee, Carry, Extract, InstructionId,
14            InstructionRef, IntBinop, LzCount, Mnemonic, PopCount, Range, Return, SBorrow, SCarry,
15            Scan, Sext, Store, Tuple, Unop, Zext,
16        },
17        varnode::{VarnodeId, register::RegisterId},
18    },
19};
20use std::cmp;
21
22mod float80;
23
24use rustc_apfloat::{
25    Float, FloatConvert, Round, Status,
26    ieee::{Double, Single, X87DoubleExtended},
27};
28use rustc_hash::{FxHashMap, FxHashSet};
29
30use super::DomainValue;
31
32fn require_real_callee(callee: Callee) -> Result<FunctionId, EmulatorErrorKind> {
33    match callee {
34        Callee::Real(target) => Ok(target),
35        Callee::Minted(slot) => Err(EmulatorErrorKind::UnresolvedMintedCallee(slot)),
36    }
37}
38
39/// Whether the call instruction `call_id` carries the `regpure` binding
40/// convention (argpromote v2): its register interface is explicit at the site,
41/// so inputs are bound positionally from `Call.args` and outputs are replayed by
42/// the caller rather than by the emulator's implicit writeback.
43fn call_is_regpure(ctx: &Context<'_>, call_id: InstructionId) -> bool {
44    matches!(
45        ctx.get_insn(call_id).mnemonic(),
46        Mnemonic::Call(call) if call.tag.is_regpure()
47    )
48}
49
50#[derive(Debug, Default, Clone)]
51pub struct EmulatedSpace(FxHashMap<u64, u8>);
52
53impl EmulatedSpace {
54    pub fn read_byte(&self, addr: u64) -> Result<u8, EmulatorErrorKind> {
55        self.0
56            .get(&addr)
57            .copied()
58            .ok_or(EmulatorErrorKind::MemoryReadError(addr))
59    }
60
61    pub fn read(&self, addr: u64, size: usize) -> Result<Vec<u8>, EmulatorErrorKind> {
62        (0..size).map(|i| self.read_byte(addr + i as u64)).collect()
63    }
64
65    pub fn read_zero_filled(&self, addr: u64, size: usize) -> Vec<u8> {
66        (0..size)
67            .map(|i| self.0.get(&(addr + i as u64)).copied().unwrap_or(0))
68            .collect()
69    }
70
71    pub fn write_byte(&mut self, addr: u64, value: u8) {
72        self.0.insert(addr, value);
73    }
74
75    /// Reserves capacity for at least `additional` more bytes, so a bulk write
76    /// allocates once instead of rehashing the table on the way up.
77    pub fn reserve(&mut self, additional: usize) {
78        self.0.reserve(additional);
79    }
80
81    /// Returns an editable region of this space for the given address and size.
82    /// If `addr + size` would overflow, a zero-size region is returned (reads yield 0, writes are no-ops).
83    pub fn get_mut_region(
84        &mut self,
85        addr: u64,
86        size: usize,
87    ) -> Result<EmulatedSpaceRegion<'_>, EmulatorErrorKind> {
88        let end = addr
89            .checked_add(size as u64)
90            .ok_or(EmulatorErrorKind::AddressOverflow(addr, size))?;
91        Ok(EmulatedSpaceRegion::new(self, addr, end))
92    }
93
94    /// Reads a little-endian unsigned integer from the region
95    pub fn read_u128(&self, addr: u64, size: u64) -> Result<u128, EmulatorErrorKind> {
96        let mut res = 0u128;
97
98        for cur in addr..addr + cmp::min(size, 16) {
99            let byte = self.read_byte(cur)?;
100            res |= u128::from(byte) << ((cur - addr) * 8);
101        }
102
103        Ok(res)
104    }
105
106    /// Reads a little-endian unsigned integer, treating missing bytes as zero.
107    pub fn read_u128_zero_filled(&self, addr: u64, size: u64) -> u128 {
108        let mut res = 0u128;
109
110        for cur in addr..addr + cmp::min(size, 16) {
111            let byte = self.0.get(&cur).copied().unwrap_or(0);
112            res |= u128::from(byte) << ((cur - addr) * 8);
113        }
114
115        res
116    }
117}
118
119/// An exclusive region of an emulated space, used for reading/writing a contiguous range of addresses in a space.
120pub struct EmulatedSpaceRegion<'space> {
121    space: &'space mut EmulatedSpace,
122    start: u64,
123    end: u64,
124}
125
126impl<'space> EmulatedSpaceRegion<'space> {
127    pub fn new(space: &'space mut EmulatedSpace, start: u64, end: u64) -> Self {
128        Self { space, start, end }
129    }
130
131    /// The size of the region in bytes
132    pub fn size(&self) -> usize {
133        (self.end - self.start) as usize
134    }
135
136    /// Writes a little-endian unsigned integer to the region
137    pub fn write_u128(&mut self, value: u128) {
138        let end = self.start + cmp::min(16, self.size()) as u64;
139        for addr in self.start..end {
140            let byte = u8::try_from((value >> ((addr - self.start) * 8)) & 0xffu128).unwrap();
141            self.space.write_byte(addr, byte);
142        }
143    }
144}
145
146#[derive(Debug, Default, Clone)]
147pub struct EmulatedMemory {
148    spaces: FxHashMap<MemorySpaceId, EmulatedSpace>,
149    zero_filled_spaces: FxHashSet<MemorySpaceId>,
150    /// Space count the zero-fill set was last built for. Spaces are append-only
151    /// and their type is fixed at creation, so an unchanged count means the set
152    /// is still valid — this keeps the per-step call O(1) instead of rescanning.
153    configured_space_count: Option<usize>,
154}
155
156impl EmulatedMemory {
157    fn is_zero_filled(&self, space: MemorySpaceId) -> bool {
158        matches!(space, MemorySpaceId::Temp(_)) || self.zero_filled_spaces.contains(&space)
159    }
160
161    fn configure_spaces(&mut self, ctx: &Context<'_>) {
162        let space_count = ctx.space_count();
163        if self.configured_space_count == Some(space_count) {
164            return;
165        }
166        self.zero_filled_spaces.clear();
167        for index in 0..space_count {
168            let id = SpaceId::from(index);
169            let space = Space::from_id(ctx, id);
170            // x86's private x87 RAM file is architectural state like the
171            // register space, not process memory. A fresh CPU state has zero
172            // payload bytes there, so instructions such as FXSAVE can read it
173            // before a harness explicitly seeds an f80 slot.
174            if matches!(space.ty, SpaceType::Register) || space.name.as_deref() == Some("x87") {
175                self.zero_filled_spaces.insert(id.into());
176            }
177        }
178        self.configured_space_count = Some(space_count);
179    }
180
181    fn read_raw(
182        &self,
183        space: MemorySpaceId,
184        addr: u64,
185        size: usize,
186    ) -> Result<Vec<u8>, EmulatorErrorKind> {
187        match self.spaces.get(&space) {
188            Some(value) if self.is_zero_filled(space) => Ok(value.read_zero_filled(addr, size)),
189            Some(value) => value.read(addr, size),
190            None if self.is_zero_filled(space) => Ok(vec![0; size]),
191            None => Err(EmulatorErrorKind::UnknownSpace(space)),
192        }
193    }
194}
195
196fn bool_to_u64(value: bool) -> u64 {
197    if value { 1 } else { 0 }
198}
199
200fn mask_for_size(size: usize) -> u128 {
201    let bits = size.saturating_mul(8);
202    if bits >= u128::BITS as usize {
203        u128::MAX
204    } else if bits == 0 {
205        0u128
206    } else {
207        (1u128 << bits) - 1
208    }
209}
210
211fn u128_to_u64(value: u128) -> u64 {
212    u64::try_from(value & u128::from(u64::MAX)).unwrap()
213}
214
215#[derive(Debug, Clone, Copy)]
216pub struct SizedValue {
217    /// Raw bits for this value
218    value: u128,
219
220    /// Number of bytes that are actually used in this value.
221    size: u8,
222}
223
224impl SizedValue {
225    pub fn new(value: u64, size: usize) -> Self {
226        let size = cmp::min(size, 16) as u8;
227        let value = u128::from(value) & mask_for_size(size as usize);
228        Self { value, size }
229    }
230
231    pub fn from_bits(value: u128, size: usize) -> Self {
232        let size = cmp::min(size, 16) as u8;
233        let value = value & mask_for_size(size as usize);
234        Self { value, size }
235    }
236
237    fn as_u64(&self) -> u64 {
238        u128_to_u64(self.value & mask_for_size(self.size as usize))
239    }
240
241    pub fn as_bits(&self) -> u128 {
242        self.value & mask_for_size(self.size as usize)
243    }
244
245    fn signed_value(&self) -> i128 {
246        let bits = (self.size as usize).saturating_mul(8);
247        if bits == 0 {
248            return i128::from(0i8);
249        }
250        if bits >= u128::BITS as usize {
251            return self.as_bits() as i128;
252        }
253
254        let value = self.as_bits();
255        let sign_bit = u128::from(1u8) << (bits - 1);
256        let extended = if (value & sign_bit) != u128::from(0u8) {
257            value | !mask_for_size(self.size as usize)
258        } else {
259            value
260        };
261        extended as i128
262    }
263
264    /// Size, in bytes, at which a two-operand integer op (add/sub/and/.../carry/
265    /// scarry/sborrow) is evaluated.
266    ///
267    /// These p-code ops require both operands to share a size, so well-formed IR
268    /// always has `self.size == other.size`. When the lifter leaves them
269    /// mismatched it is the *left* operand that carries the operative width: it
270    /// is the destination/base that the right operand is being combined into
271    /// (e.g. address arithmetic `int_add(base:8, disp:4)` must stay 8 bytes, not
272    /// truncate to the displacement). The narrower side here is an immediate
273    /// whose value already fits, so taking the left width is correct.
274    ///
275    /// Cases where the *immediate* is on the left and wider than the real
276    /// operand — NEG's `OF = sborrow(0, AL)` — are instead fixed upstream, by
277    /// sizing the immediate to its sibling in the lifter, so this function never
278    /// sees that mismatch. See `emit_function_call` in `harbinger::emit`.
279    fn widen_size(&self, _other: &Self) -> usize {
280        self.size as usize
281    }
282
283    fn from_f80_bits(value: u128) -> Self {
284        Self::from_bits(value, 10)
285    }
286
287    fn f64_from_self(&self) -> f64 {
288        match self.size as usize {
289            0..=4 => f32::from_bits(self.as_u64() as u32) as f64,
290            8 => f64::from_bits(self.as_u64()),
291            10 => float80::to_f64(self.as_bits()),
292            _ => 0.0,
293        }
294    }
295
296    fn from_f64(value: f64, size: usize) -> Self {
297        match size {
298            0..=4 => Self::new((value as f32).to_bits() as u64, 4),
299            8 => Self::new(value.to_bits(), 8),
300            10 => Self::from_f80_bits(float80::from_f64(value)),
301            _ => Self::new(0, size),
302        }
303    }
304}
305
306impl DomainValue for SizedValue {
307    fn size(&self) -> Result<usize, EmulatorErrorKind> {
308        Ok(self.size as usize)
309    }
310
311    fn value(&self) -> Result<u64, EmulatorErrorKind> {
312        let value = self.as_bits();
313        if value > u128::from(u64::MAX) {
314            Err(EmulatorErrorKind::ValueError(value)) // Replace with appropriate error
315        } else {
316            Ok(u64::try_from(value).unwrap())
317        }
318    }
319
320    fn from_u64(value: u64) -> Self {
321        Self::new(value, 8)
322    }
323
324    fn zero(size: usize) -> Self {
325        Self::new(0, size)
326    }
327
328    fn is_float_nan(&self) -> Result<Self, EmulatorErrorKind> {
329        let is_nan = if self.size == 10 {
330            float80::is_nan(self.as_bits())
331        } else {
332            self.f64_from_self().is_nan()
333        };
334        Ok(Self::new(bool_to_u64(is_nan), 1))
335    }
336
337    fn int_to_float(&self, size: usize) -> Result<Self, EmulatorErrorKind> {
338        let signed = self.signed_value();
339        match size {
340            4 => Ok(Self::new((signed as f32).to_bits() as u64, 4)),
341            8 => Ok(Self::new((signed as f64).to_bits(), 8)),
342            10 => Ok(Self::from_f80_bits(float80::from_i128(signed))),
343            _ => Ok(Self::new(0, size)),
344        }
345    }
346
347    fn float_to_float(&self, size: usize) -> Result<Self, EmulatorErrorKind> {
348        if size == 10 {
349            let value = match self.size as usize {
350                0..=4 => float80::from_f32_bits(self.as_u64() as u32),
351                8 => float80::from_f64(f64::from_bits(self.as_u64())),
352                10 => self.as_bits(),
353                _ => 0,
354            };
355            return Ok(Self::from_f80_bits(value));
356        }
357        match size {
358            4 => Ok(Self::new((self.f64_from_self() as f32).to_bits() as u64, 4)),
359            8 => Ok(Self::new(self.f64_from_self().to_bits(), 8)),
360            _ => Ok(Self::new(0, size)),
361        }
362    }
363
364    fn float_to_int(&self, size: usize) -> Result<Self, EmulatorErrorKind> {
365        let value = if self.size == 10 {
366            float80::to_i128(self.as_bits(), size * 8) as u64
367        } else {
368            self.f64_from_self() as i64 as u64
369        };
370        Ok(Self::new(value, size))
371    }
372
373    fn zext(&self, size: usize) -> Result<Self, EmulatorErrorKind> {
374        Ok(Self::from_bits(Zext::eval(self.as_bits(), size), size))
375    }
376
377    fn sext(&self, size: usize) -> Result<Self, EmulatorErrorKind> {
378        Ok(Self::from_bits(
379            Sext::eval(self.as_bits(), self.size as usize, size),
380            size,
381        ))
382    }
383
384    fn range(&self, start: usize, size: usize) -> Result<Self, EmulatorErrorKind> {
385        Ok(Self::from_bits(
386            Range::eval(self.as_bits(), start, size),
387            size,
388        ))
389    }
390
391    fn byte_swap(&self) -> Result<Self, EmulatorErrorKind> {
392        let size = self.size as usize;
393        let mut value = 0u128;
394        for index in 0..size {
395            let byte = (self.as_bits() >> (index * 8)) & 0xff;
396            value |= byte << ((size - index - 1) * 8);
397        }
398        Ok(Self::from_bits(value, size))
399    }
400
401    fn intrinsic(
402        id: qcode::value::insn::IntrinsicId,
403        args: &[Self],
404        out_size: usize,
405    ) -> Result<Self, EmulatorErrorKind> {
406        let operands: Vec<(u128, usize)> = args
407            .iter()
408            .map(|a| (a.as_bits(), a.size as usize))
409            .collect();
410        let value = id
411            .desc()
412            .eval(&operands, out_size)
413            .ok_or_else(|| EmulatorErrorKind::UnsupportedIntrinsic(Box::from(id.name())))?;
414        Ok(Self::from_bits(value, out_size))
415    }
416
417    fn pop_count(&self) -> Result<Self, EmulatorErrorKind> {
418        let size = self.size as usize;
419        Ok(Self::from_bits(PopCount::eval(self.as_bits(), size), size))
420    }
421
422    fn lz_count(&self) -> Result<Self, EmulatorErrorKind> {
423        let size = self.size as usize;
424        Ok(Self::from_bits(LzCount::eval(self.as_bits(), size), size))
425    }
426
427    fn carry(&self, other: &Self) -> Result<Self, EmulatorErrorKind> {
428        let size = self.widen_size(other);
429        Ok(Self::from_bits(
430            Carry::eval(self.as_bits(), other.as_bits(), size),
431            1,
432        ))
433    }
434
435    fn scarry(&self, other: &Self) -> Result<Self, EmulatorErrorKind> {
436        let size = self.widen_size(other);
437        Ok(Self::from_bits(
438            SCarry::eval(self.as_bits(), other.as_bits(), size),
439            1,
440        ))
441    }
442
443    fn sborrow(&self, other: &Self) -> Result<Self, EmulatorErrorKind> {
444        let size = self.widen_size(other);
445        Ok(Self::from_bits(
446            SBorrow::eval(self.as_bits(), other.as_bits(), size),
447            1,
448        ))
449    }
450
451    fn int_not(&self) -> Result<Self, EmulatorErrorKind> {
452        let size = self.size as usize;
453        Ok(Self::from_bits(
454            Unop::IntNot.eval_int(self.as_bits(), size).unwrap(),
455            size,
456        ))
457    }
458
459    fn int_negate(&self) -> Result<Self, EmulatorErrorKind> {
460        let size = self.size as usize;
461        Ok(Self::from_bits(
462            Unop::IntNegate.eval_int(self.as_bits(), size).unwrap(),
463            size,
464        ))
465    }
466
467    fn float_negate(&self) -> Result<Self, EmulatorErrorKind> {
468        if self.size == 10 {
469            return Ok(Self::from_f80_bits(float80::negate(self.as_bits())));
470        }
471        Ok(Self::from_f64(-self.f64_from_self(), self.size as usize))
472    }
473
474    fn float_abs(&self) -> Result<Self, EmulatorErrorKind> {
475        if self.size == 10 {
476            return Ok(Self::from_f80_bits(float80::abs(self.as_bits())));
477        }
478        Ok(Self::from_f64(
479            self.f64_from_self().abs(),
480            self.size as usize,
481        ))
482    }
483
484    fn float_sqrt(&self) -> Result<Self, EmulatorErrorKind> {
485        Ok(Self::from_f64(
486            self.f64_from_self().sqrt(),
487            self.size as usize,
488        ))
489    }
490
491    fn float_ceil(&self) -> Result<Self, EmulatorErrorKind> {
492        Ok(Self::from_f64(
493            self.f64_from_self().ceil(),
494            self.size as usize,
495        ))
496    }
497
498    fn float_floor(&self) -> Result<Self, EmulatorErrorKind> {
499        Ok(Self::from_f64(
500            self.f64_from_self().floor(),
501            self.size as usize,
502        ))
503    }
504
505    fn float_round(&self) -> Result<Self, EmulatorErrorKind> {
506        Ok(Self::from_f64(
507            self.f64_from_self().round(),
508            self.size as usize,
509        ))
510    }
511
512    fn int_equal(&self, other: &Self) -> Result<Self, EmulatorErrorKind> {
513        Ok(Self::from_bits(
514            IntBinop::Equal.eval(self.as_bits(), other.as_bits(), self.size as usize),
515            1,
516        ))
517    }
518
519    fn int_not_equal(&self, other: &Self) -> Result<Self, EmulatorErrorKind> {
520        Ok(Self::from_bits(
521            IntBinop::NotEqual.eval(self.as_bits(), other.as_bits(), self.size as usize),
522            1,
523        ))
524    }
525
526    fn int_less(&self, other: &Self) -> Result<Self, EmulatorErrorKind> {
527        Ok(Self::from_bits(
528            IntBinop::Less.eval(self.as_bits(), other.as_bits(), self.size as usize),
529            1,
530        ))
531    }
532
533    fn int_sless(&self, other: &Self) -> Result<Self, EmulatorErrorKind> {
534        Ok(Self::from_bits(
535            IntBinop::SLess.eval(self.as_bits(), other.as_bits(), self.size as usize),
536            1,
537        ))
538    }
539
540    fn int_less_equal(&self, other: &Self) -> Result<Self, EmulatorErrorKind> {
541        Ok(Self::from_bits(
542            IntBinop::LessEqual.eval(self.as_bits(), other.as_bits(), self.size as usize),
543            1,
544        ))
545    }
546
547    fn int_sless_equal(&self, other: &Self) -> Result<Self, EmulatorErrorKind> {
548        Ok(Self::from_bits(
549            IntBinop::SLessEqual.eval(self.as_bits(), other.as_bits(), self.size as usize),
550            1,
551        ))
552    }
553
554    fn int_add(&self, other: &Self) -> Result<Self, EmulatorErrorKind> {
555        let size = self.widen_size(other);
556        Ok(Self::from_bits(
557            IntBinop::Add.eval(self.as_bits(), other.as_bits(), size),
558            size,
559        ))
560    }
561
562    fn int_sub(&self, other: &Self) -> Result<Self, EmulatorErrorKind> {
563        let size = self.widen_size(other);
564        Ok(Self::from_bits(
565            IntBinop::Sub.eval(self.as_bits(), other.as_bits(), size),
566            size,
567        ))
568    }
569
570    fn int_xor(&self, other: &Self) -> Result<Self, EmulatorErrorKind> {
571        let size = self.widen_size(other);
572        Ok(Self::from_bits(
573            IntBinop::Xor.eval(self.as_bits(), other.as_bits(), size),
574            size,
575        ))
576    }
577
578    fn int_and(&self, other: &Self) -> Result<Self, EmulatorErrorKind> {
579        let size = self.widen_size(other);
580        Ok(Self::from_bits(
581            IntBinop::And.eval(self.as_bits(), other.as_bits(), size),
582            size,
583        ))
584    }
585
586    fn int_or(&self, other: &Self) -> Result<Self, EmulatorErrorKind> {
587        let size = self.widen_size(other);
588        Ok(Self::from_bits(
589            IntBinop::Or.eval(self.as_bits(), other.as_bits(), size),
590            size,
591        ))
592    }
593
594    fn int_shift_left(&self, other: &Self) -> Result<Self, EmulatorErrorKind> {
595        let size = self.size as usize;
596        Ok(Self::from_bits(
597            IntBinop::ShiftLeft.eval(self.as_bits(), other.as_bits(), size),
598            size,
599        ))
600    }
601
602    fn int_shift_right(&self, other: &Self) -> Result<Self, EmulatorErrorKind> {
603        let size = self.size as usize;
604        Ok(Self::from_bits(
605            IntBinop::ShiftRight.eval(self.as_bits(), other.as_bits(), size),
606            size,
607        ))
608    }
609
610    fn int_sshift_right(&self, other: &Self) -> Result<Self, EmulatorErrorKind> {
611        let size = self.size as usize;
612        Ok(Self::from_bits(
613            IntBinop::SShiftRight.eval(self.as_bits(), other.as_bits(), size),
614            size,
615        ))
616    }
617
618    fn int_mul(&self, other: &Self) -> Result<Self, EmulatorErrorKind> {
619        let size = self.widen_size(other);
620        Ok(Self::from_bits(
621            IntBinop::Mul.eval(self.as_bits(), other.as_bits(), size),
622            size,
623        ))
624    }
625
626    fn int_div(&self, other: &Self) -> Result<Self, EmulatorErrorKind> {
627        let size = self.widen_size(other);
628        Ok(Self::from_bits(
629            IntBinop::Div.eval(self.as_bits(), other.as_bits(), size),
630            size,
631        ))
632    }
633
634    fn int_rem(&self, other: &Self) -> Result<Self, EmulatorErrorKind> {
635        let size = self.widen_size(other);
636        Ok(Self::from_bits(
637            IntBinop::Rem.eval(self.as_bits(), other.as_bits(), size),
638            size,
639        ))
640    }
641
642    fn int_sdiv(&self, other: &Self) -> Result<Self, EmulatorErrorKind> {
643        let size = self.widen_size(other);
644        Ok(Self::from_bits(
645            IntBinop::Sdiv.eval(self.as_bits(), other.as_bits(), size),
646            size,
647        ))
648    }
649
650    fn int_srem(&self, other: &Self) -> Result<Self, EmulatorErrorKind> {
651        let size = self.widen_size(other);
652        Ok(Self::from_bits(
653            IntBinop::Srem.eval(self.as_bits(), other.as_bits(), size),
654            size,
655        ))
656    }
657
658    fn float_add(&self, other: &Self) -> Result<Self, EmulatorErrorKind> {
659        let size = self.widen_size(other);
660        if size == 10 {
661            return Ok(Self::from_f80_bits(float80::add(
662                self.as_bits(),
663                other.as_bits(),
664            )));
665        }
666        Ok(Self::from_f64(
667            self.f64_from_self() + other.f64_from_self(),
668            size,
669        ))
670    }
671
672    fn float_sub(&self, other: &Self) -> Result<Self, EmulatorErrorKind> {
673        let size = self.widen_size(other);
674        if size == 10 {
675            return Ok(Self::from_f80_bits(float80::sub(
676                self.as_bits(),
677                other.as_bits(),
678            )));
679        }
680        Ok(Self::from_f64(
681            self.f64_from_self() - other.f64_from_self(),
682            size,
683        ))
684    }
685
686    fn float_mul(&self, other: &Self) -> Result<Self, EmulatorErrorKind> {
687        let size = self.widen_size(other);
688        if size == 10 {
689            return Ok(Self::from_f80_bits(float80::mul(
690                self.as_bits(),
691                other.as_bits(),
692            )));
693        }
694        Ok(Self::from_f64(
695            self.f64_from_self() * other.f64_from_self(),
696            size,
697        ))
698    }
699
700    fn float_div(&self, other: &Self) -> Result<Self, EmulatorErrorKind> {
701        let size = self.widen_size(other);
702        if size == 10 {
703            return Ok(Self::from_f80_bits(float80::div(
704                self.as_bits(),
705                other.as_bits(),
706            )));
707        }
708        Ok(Self::from_f64(
709            self.f64_from_self() / other.f64_from_self(),
710            size,
711        ))
712    }
713
714    fn float_equal(&self, other: &Self) -> Result<Self, EmulatorErrorKind> {
715        let equal = if self.size == 10 {
716            float80::equal(self.as_bits(), other.as_bits())
717        } else {
718            self.f64_from_self() == other.f64_from_self()
719        };
720        Ok(Self::new(bool_to_u64(equal), 1))
721    }
722
723    fn float_not_equal(&self, other: &Self) -> Result<Self, EmulatorErrorKind> {
724        let unequal = if self.size == 10 {
725            !float80::equal(self.as_bits(), other.as_bits())
726        } else {
727            self.f64_from_self() != other.f64_from_self()
728        };
729        Ok(Self::new(bool_to_u64(unequal), 1))
730    }
731
732    fn float_less(&self, other: &Self) -> Result<Self, EmulatorErrorKind> {
733        let less = if self.size == 10 {
734            float80::less(self.as_bits(), other.as_bits())
735        } else {
736            self.f64_from_self() < other.f64_from_self()
737        };
738        Ok(Self::new(bool_to_u64(less), 1))
739    }
740
741    fn float_less_equal(&self, other: &Self) -> Result<Self, EmulatorErrorKind> {
742        let less_equal = if self.size == 10 {
743            float80::less_equal(self.as_bits(), other.as_bits())
744        } else {
745            self.f64_from_self() <= other.f64_from_self()
746        };
747        Ok(Self::new(bool_to_u64(less_equal), 1))
748    }
749}
750
751/// The byte-addressable memory an emulator run needs, over and above the
752/// value-domain reads and writes of [`DomainMemory`].
753///
754/// [`DomainMemory`] speaks in whole values of a domain, which is all the
755/// interpreter itself needs. A *harness* needs more: to seed a fixture, dump a
756/// region, or poke a single register lane, it has to talk in bytes. Keeping
757/// those on a separate trait is what lets [`StandaloneEmulator`] be generic over
758/// its memory, so a richer backend — one with mapped pages and permissions —
759/// can be substituted without the interpreter knowing.
760pub trait EmulatorMemory: DomainMemory<V = SizedValue> {
761    /// Prepares per-space bookkeeping for `ctx`. Called before any access, and
762    /// cheap to call repeatedly: spaces are append-only, so an implementation
763    /// can skip the work when nothing has been added.
764    fn configure_spaces(&mut self, ctx: &Context<'_>);
765
766    /// Reads `size` raw bytes, without the value-domain's width handling.
767    fn read_bytes(
768        &self,
769        space: MemorySpaceId,
770        addr: u64,
771        size: usize,
772    ) -> Result<Vec<u8>, EmulatorErrorKind>;
773
774    /// Writes raw bytes, creating the space if it does not exist yet.
775    fn write_bytes(
776        &mut self,
777        space: MemorySpaceId,
778        addr: u64,
779        bytes: &[u8],
780    ) -> Result<(), EmulatorErrorKind>;
781}
782
783impl EmulatorMemory for EmulatedMemory {
784    fn configure_spaces(&mut self, ctx: &Context<'_>) {
785        EmulatedMemory::configure_spaces(self, ctx)
786    }
787
788    fn read_bytes(
789        &self,
790        space: MemorySpaceId,
791        addr: u64,
792        size: usize,
793    ) -> Result<Vec<u8>, EmulatorErrorKind> {
794        self.read_raw(space, addr, size)
795    }
796
797    fn write_bytes(
798        &mut self,
799        space: MemorySpaceId,
800        addr: u64,
801        bytes: &[u8],
802    ) -> Result<(), EmulatorErrorKind> {
803        let space = self.spaces.entry(space).or_default();
804        space.reserve(bytes.len());
805        for (index, byte) in bytes.iter().enumerate() {
806            space.write_byte(addr + index as u64, *byte);
807        }
808        Ok(())
809    }
810}
811
812impl DomainMemory for EmulatedMemory {
813    type V = SizedValue;
814
815    fn read(
816        &self,
817        space: MemorySpaceId,
818        addr: Self::V,
819        size: usize,
820    ) -> Result<Self::V, EmulatorErrorKind> {
821        let addr = addr.value()?;
822        let zero_filled = self.is_zero_filled(space);
823        let bits = match self.spaces.get(&space) {
824            Some(s) if zero_filled => s.read_u128_zero_filled(addr, size as u64),
825            Some(s) => s.read_u128(addr, size as u64)?,
826            None if zero_filled => 0,
827            None => return Err(EmulatorErrorKind::UnknownSpace(space)),
828        };
829        Ok(SizedValue::from_bits(bits, size))
830    }
831
832    fn write(
833        &mut self,
834        space: MemorySpaceId,
835        addr: Self::V,
836        size: usize,
837        value: Self::V,
838    ) -> Result<(), EmulatorErrorKind> {
839        let addr = addr.value()?;
840
841        self.spaces
842            .entry(space)
843            .or_default()
844            .get_mut_region(addr, size)?
845            .write_u128(value.as_bits());
846        Ok(())
847    }
848}
849
850/// Values of literals, resolved once and kept by id.
851///
852/// Reading a literal goes through the module's interner, which takes an
853/// `RwLock` read guard per access so that constants can be minted through a
854/// shared reference. That is two atomic operations for what is morally a
855/// constant, and it measured at 14% of run time in a profile of an interpreter
856/// loop. A literal is immutable once interned — attaching a symbolic reference
857/// later changes neither its value nor its width — so caching it by id is
858/// sound, and turns the access into an array index.
859#[derive(Debug, Default, Clone)]
860pub struct LiteralCache(Vec<Option<SizedValue>>);
861
862impl LiteralCache {
863    fn get(&mut self, ctx: &Context<'_>, id: qcode::value::LiteralId) -> SizedValue {
864        let index: usize = id.into();
865        if index >= self.0.len() {
866            self.0.resize(index + 1, None);
867        }
868        match self.0[index] {
869            Some(value) => value,
870            None => {
871                // The miss pays the interner's lock, once per distinct literal.
872                let ValueRef::Literal(literal) = ValueRef::new(ValueId::Literal(id), ctx) else {
873                    unreachable!("a literal id resolves to a literal")
874                };
875                let value = SizedValue::new(literal.value(), literal.size());
876                self.0[index] = Some(value);
877                value
878            }
879        }
880    }
881}
882
883/// Values produced by instructions, stored densely.
884///
885/// Every p-code operation that yields a value writes one here and its consumers
886/// read it back, so this is one of the hottest structures in the interpreter. A
887/// `FxHashMap<InstructionId, _>` hashes a `(function, local)` pair on every
888/// access; instruction ids are dense and small, so a slot array indexed by
889/// those two components removes the hashing entirely.
890///
891/// Sparse ids cost only an unused slot, which is what makes this safe to use
892/// after a pass has removed instructions from a block.
893#[derive(Debug, Default, Clone)]
894pub struct InsnValues(Vec<Vec<Option<SizedValue>>>);
895
896impl InsnValues {
897    pub fn get(&self, id: &InstructionId) -> Option<&SizedValue> {
898        let func: usize = id.func.into();
899        let local: usize = id.local.into();
900        self.0.get(func)?.get(local)?.as_ref()
901    }
902
903    pub fn insert(&mut self, id: InstructionId, value: SizedValue) {
904        let func: usize = id.func.into();
905        let local: usize = id.local.into();
906        if func >= self.0.len() {
907            self.0.resize_with(func + 1, Vec::new);
908        }
909        let slots = &mut self.0[func];
910        if local >= slots.len() {
911            slots.resize(local + 1, None);
912        }
913        slots[local] = Some(value);
914    }
915
916    pub fn clear(&mut self) {
917        self.0.clear();
918    }
919}
920
921/// Type alias for an instruction hook function, which is called with the current instruction and emulator state after each instruction is executed.
922type InstructionHook<M> =
923    Box<dyn Fn(&InstructionRef<'_, '_>, &StandaloneEmulator<M>) + Send + Sync>;
924type CallInterceptor<M> = Box<
925    dyn FnMut(
926            &Context<'_>,
927            &mut StandaloneEmulator<M>,
928            &CallSite,
929        ) -> Result<CallInterception, Box<str>>
930        + Send
931        + Sync,
932>;
933
934#[derive(Debug, Clone, Copy, PartialEq, Eq)]
935enum StepEvent {
936    Normal,
937    DirectCallEntered(FunctionId),
938    IndirectCallEntered,
939    Return,
940    ReturnValue,
941    InterceptedCall,
942}
943
944/// A lifetime-free emulator that takes `&Context<'_>` explicitly on each call.
945/// Use this when you need to store an emulator without a lifetime (e.g., across an FFI boundary).
946pub struct StandaloneEmulator<M = EmulatedMemory> {
947    pub memory: M,
948    /// Literal values resolved once instead of per access.
949    literal_cache: LiteralCache,
950    /// Whether the module contains any array or list type, and the published
951    /// type count that answer was valid for.
952    ///
953    /// Every `Load`, `Store` and `Range` is guarded by "is this operand
954    /// sequence-typed", which otherwise costs an arena resolution plus two
955    /// virtual calls *per operation* — about 9% of run time. Almost every module
956    /// has no sequence types at all, and that is answerable once.
957    sequence_types: bool,
958    sequence_types_checked_at: Option<usize>,
959    /// The current block's instruction list, and which block it belongs to.
960    ///
961    /// Resolving a block means two registry indexes (`bodies[func].blocks[local]`)
962    /// and the interpreter did it on every step, though a block is entered once
963    /// and then walked. Refreshed whenever the block changes *or* execution is
964    /// at a block's first instruction — a block's contents can only change while
965    /// nothing is part-way through it, which is exactly the case a VM that lifts
966    /// on demand creates when it fills a placeholder block and re-enters it.
967    cached_block: Option<BlockId>,
968    cached_insns: Vec<qcode::value::LocalInsnId>,
969    pub insn_values: InsnValues,
970    pub block_param_values: FxHashMap<BlockParamId, SizedValue>,
971    /// Block params bound to **poison** (argpromote v2): a symbolic pure-call
972    /// argument whose bits are undefined. Reading one during emulation is a hard
973    /// error (`PoisonRead`), so a pure-call fold whose result actually depends on
974    /// a symbolic argument bails instead of computing on a bogus concrete value.
975    pub poison_params: FxHashSet<BlockParamId>,
976    /// Field values of aggregate-typed instruction results (`Tuple` results and,
977    /// on return, the call instruction that produced them). `Extract` projects a
978    /// field back out. Keeps the scalar `SizedValue` domain unchanged — the
979    /// functional `argpromote` write-set is the only producer/consumer.
980    pub aggregate_values: FxHashMap<InstructionId, Vec<SizedValue>>,
981    /// Field values of aggregate-typed **block params** seeded by
982    /// [`run_map_body`](Self::run_map_body) (the `enumerate` `(index, elem)` lane
983    /// fed to a `map` body). `Extract` on such a param projects a field back out.
984    pub block_param_aggregates: FxHashMap<BlockParamId, Vec<SizedValue>>,
985    /// Little-endian byte buffers of array-typed instruction results — the value
986    /// domain for the sequence intrinsics (`iota`/`singleton`/`insert`/`concat`),
987    /// `Scan`, and array-typed `Store`. Kept out of the scalar `SizedValue`
988    /// domain the same way [`aggregate_values`](Self::aggregate_values) keeps
989    /// tuples out of it; a scalar `at(arr, i)` reads one lane back into
990    /// `insn_values`.
991    pub array_values: FxHashMap<InstructionId, Vec<u8>>,
992    pub block: BlockId,
993    pub idx: usize,
994    /// Call stack maintained by `run_function` (outermost function first).
995    pub call_stack: Vec<FunctionId>,
996    /// The call instruction id for each active nested call, so a `Return` can
997    /// deposit the callee's `Return.value` as that call's result.
998    call_site_stack: Vec<InstructionId>,
999
1000    /// Disposable address lookup for the immutable module snapshot supplied by
1001    /// the caller. The lifetime-free emulator initializes this lazily because
1002    /// [`StandaloneEmulator::new`] intentionally takes no `Context`.
1003    address_index: Option<AddressIndex>,
1004
1005    pub instruction_hook: Option<InstructionHook<M>>,
1006    call_interceptor: Option<CallInterceptor<M>>,
1007}
1008
1009impl StandaloneEmulator<EmulatedMemory> {
1010    /// Builds an emulator over the default flat memory.
1011    pub fn new(entry: BlockId) -> Self {
1012        Self::new_in(entry)
1013    }
1014
1015    /// Builds an emulator positioned at `addr`, over the default flat memory.
1016    pub fn from_address(ctx: &Context<'_>, addr: u64) -> Self {
1017        Self::from_address_in(ctx, addr)
1018    }
1019}
1020
1021impl<M: EmulatorMemory + Default> StandaloneEmulator<M> {
1022    /// Builds an emulator over an explicit memory backend.
1023    ///
1024    /// [`new`](StandaloneEmulator::new) is the one to reach for with the default
1025    /// flat memory: Rust's default type parameters do not participate in
1026    /// inference, so a generic `new` would force every call site to name its
1027    /// backend.
1028    pub fn new_in(entry: BlockId) -> Self {
1029        Self {
1030            memory: M::default(),
1031            literal_cache: LiteralCache::default(),
1032            sequence_types: false,
1033            sequence_types_checked_at: None,
1034            cached_block: None,
1035            cached_insns: Vec::new(),
1036            insn_values: InsnValues::default(),
1037            block_param_values: FxHashMap::default(),
1038            poison_params: FxHashSet::default(),
1039            aggregate_values: FxHashMap::default(),
1040            block_param_aggregates: FxHashMap::default(),
1041            array_values: FxHashMap::default(),
1042            block: entry,
1043            idx: 0,
1044            call_stack: Vec::new(),
1045            call_site_stack: Vec::new(),
1046            address_index: None,
1047            instruction_hook: None,
1048            call_interceptor: None,
1049        }
1050    }
1051
1052    /// Drops the cached instruction list for the current block.
1053    ///
1054    /// The cache assumes a block's contents only change while nothing is
1055    /// part-way through it. A caller that runs a block's body by some other
1056    /// means and then positions the emulator *inside* that block breaks the
1057    /// assumption, and must say so.
1058    pub fn invalidate_block_cache(&mut self) {
1059        self.cached_block = None;
1060    }
1061
1062    /// Takes the cached address lookup, leaving the emulator without one.
1063    ///
1064    /// The index is derived state, built once from what was assumed to be an
1065    /// immutable module. A VM that lifts code on demand makes the module
1066    /// *mutable*, so it has to keep the index current instead. Moving the index
1067    /// out, updating it in place as blocks are added, and moving it back with
1068    /// [`set_address_index`](Self::set_address_index) keeps discovery O(1) —
1069    /// rebuilding it per lift is quadratic in the size of the module.
1070    pub fn take_address_index(&mut self) -> Option<AddressIndex> {
1071        self.address_index.take()
1072    }
1073
1074    /// Installs an address lookup, replacing any cached one.
1075    /// Borrows the cached address index, if one has been built.
1076    pub fn address_index(&self) -> Option<&AddressIndex> {
1077        self.address_index.as_ref()
1078    }
1079
1080    pub fn set_address_index(&mut self, address_index: AddressIndex) {
1081        self.address_index = Some(address_index);
1082    }
1083
1084    /// Resolves a guest address to the block that covers it, building the
1085    /// cached index if there is not one yet.
1086    pub fn block_at_address(&mut self, ctx: &Context<'_>, address: u64) -> Option<BlockId> {
1087        self.block_at(ctx, address)
1088    }
1089
1090    fn with_address_index(entry: BlockId, address_index: AddressIndex) -> Self {
1091        let mut emulator = Self::new_in(entry);
1092        emulator.address_index = Some(address_index);
1093        emulator
1094    }
1095
1096    fn resolve_block_at(ctx: &Context<'_>, index: &AddressIndex, address: u64) -> Option<BlockId> {
1097        match index.get(address) {
1098            Some(AddressTarget::Block(block)) => Some(block),
1099            Some(AddressTarget::Function(function)) => FunctionBody::from_id(ctx, function)
1100                .root()
1101                .map(|root| root.id),
1102            None => None,
1103        }
1104    }
1105
1106    fn block_at(&mut self, ctx: &Context<'_>, address: u64) -> Option<BlockId> {
1107        let index = self
1108            .address_index
1109            .get_or_insert_with(|| AddressIndex::analyze(ctx));
1110        Self::resolve_block_at(ctx, index, address)
1111    }
1112
1113    fn make_error(&self, ctx: &Context<'_>, kind: EmulatorErrorKind) -> EmulatorError {
1114        let block = BasicBlock::from_id(ctx, self.block);
1115        let instruction = block
1116            .instruction_ids()
1117            .get(self.idx)
1118            .copied()
1119            .or_else(|| block.instruction_ids().last().copied())
1120            .expect("cannot construct EmulatorError for empty block");
1121
1122        EmulatorError::new(kind, &Instruction::from_id(ctx, instruction))
1123    }
1124
1125    /// Construct an [`EmulatorErrorKind::EmptyBlock`] error for the current block
1126    /// without indexing into it. [`make_error`](Self::make_error) can't serve this
1127    /// case — it panics when the block has no instructions to attach context to.
1128    fn make_empty_block_error(&self, ctx: &Context<'_>) -> EmulatorError {
1129        let block = BasicBlock::from_id(ctx, self.block);
1130        EmulatorError {
1131            kind: EmulatorErrorKind::EmptyBlock(self.block),
1132            ctx: format!(
1133                "Block: {:?}\nFunction: {:?}",
1134                block.name(),
1135                block.function().map(|f| f.name())
1136            ),
1137            address: block.address(),
1138        }
1139    }
1140
1141    fn make_error_at(
1142        &self,
1143        ctx: &Context<'_>,
1144        instruction: InstructionId,
1145        kind: EmulatorErrorKind,
1146    ) -> EmulatorError {
1147        EmulatorError::new(kind, &Instruction::from_id(ctx, instruction))
1148    }
1149
1150    /// Builds an emulator positioned at `addr`, over an explicit backend.
1151    pub fn from_address_in(ctx: &Context<'_>, addr: u64) -> Self {
1152        let address_index = AddressIndex::analyze(ctx);
1153        let entry = Self::resolve_block_at(ctx, &address_index, addr)
1154            .expect("Invalid block or function address");
1155        let mut emulator = Self::with_address_index(entry, address_index);
1156        emulator.memory.configure_spaces(ctx);
1157        emulator
1158    }
1159
1160    pub fn set_varnode(
1161        &mut self,
1162        ctx: &Context<'_>,
1163        id: VarnodeId,
1164        value: u64,
1165    ) -> Result<(), EmulatorErrorKind> {
1166        self.set_varnode_u128(ctx, id, u128::from(value))
1167    }
1168
1169    pub fn set_varnode_u128(
1170        &mut self,
1171        ctx: &Context<'_>,
1172        id: VarnodeId,
1173        value: u128,
1174    ) -> Result<(), EmulatorErrorKind> {
1175        self.memory.configure_spaces(ctx);
1176        let varnode = Varnode::from_id(ctx, id);
1177        let space = varnode.space().id;
1178        let addr = varnode.address() as u64;
1179        let size = varnode.size();
1180        self.memory.write(
1181            space.into(),
1182            SizedValue::from_u64(addr),
1183            size,
1184            SizedValue::from_bits(value, size),
1185        )
1186    }
1187
1188    pub fn read_varnode(&self, ctx: &Context<'_>, id: VarnodeId) -> Option<u64> {
1189        let value = self.read_varnode_u128(ctx, id)?;
1190        u64::try_from(value).ok()
1191    }
1192
1193    pub fn read_varnode_u128(&self, ctx: &Context<'_>, id: VarnodeId) -> Option<u128> {
1194        let varnode = Varnode::from_id(ctx, id);
1195        let space = varnode.space().id;
1196        let addr = varnode.address() as u64;
1197        let size = varnode.size();
1198        self.memory
1199            .read(space.into(), SizedValue::from_u64(addr), size)
1200            .ok()
1201            .map(|v| v.as_bits())
1202    }
1203
1204    pub fn get_value(&mut self, ctx: &Context<'_>, id: ValueId) -> Option<u64> {
1205        let mut tmp = TempInterpreter {
1206            memory: &mut self.memory,
1207            literals: &mut self.literal_cache,
1208            insn_values: &mut self.insn_values,
1209            block_param_values: &mut self.block_param_values,
1210            poison_params: &self.poison_params,
1211            ctx,
1212        };
1213        tmp.get_value(id).ok().and_then(|v| v.value().ok())
1214    }
1215
1216    pub fn set_varnode_bytes(
1217        &mut self,
1218        ctx: &Context<'_>,
1219        id: VarnodeId,
1220        bytes: &[u8],
1221    ) -> Result<(), EmulatorErrorKind> {
1222        let varnode = Varnode::from_id(ctx, id);
1223        let space = varnode.space().id;
1224        let base_addr = varnode.address() as u64;
1225        for (i, chunk) in bytes.chunks(8).enumerate() {
1226            let addr = base_addr + (i * 8) as u64;
1227            let mut buf = [0u8; 8];
1228            buf[..chunk.len()].copy_from_slice(chunk);
1229            let value = u64::from_le_bytes(buf);
1230            self.memory.write(
1231                space.into(),
1232                SizedValue::from_u64(addr),
1233                chunk.len(),
1234                SizedValue::new(value, chunk.len()),
1235            )?;
1236        }
1237        Ok(())
1238    }
1239
1240    pub fn set_varnode_by_name(
1241        &mut self,
1242        ctx: &Context<'_>,
1243        name: &str,
1244        value: u64,
1245    ) -> Result<bool, EmulatorErrorKind> {
1246        self.set_varnode_by_name_u128(ctx, name, u128::from(value))
1247    }
1248
1249    pub fn set_varnode_by_name_u128(
1250        &mut self,
1251        ctx: &Context<'_>,
1252        name: &str,
1253        value: u128,
1254    ) -> Result<bool, EmulatorErrorKind> {
1255        match ctx.get_named(name) {
1256            Some(ValueId::Varnode(id)) => {
1257                self.set_varnode_u128(ctx, id, value)?;
1258                Ok(true)
1259            }
1260            _ => Ok(false),
1261        }
1262    }
1263
1264    pub fn set_varnode_by_name_bytes(
1265        &mut self,
1266        ctx: &Context<'_>,
1267        name: &str,
1268        bytes: &[u8],
1269    ) -> Result<bool, EmulatorErrorKind> {
1270        match ctx.get_named(name) {
1271            Some(ValueId::Varnode(id)) => {
1272                self.set_varnode_bytes(ctx, id, bytes)?;
1273                Ok(true)
1274            }
1275            _ => Ok(false),
1276        }
1277    }
1278
1279    pub fn read_varnode_by_name(&mut self, ctx: &Context<'_>, name: &str) -> Option<u64> {
1280        let value = self.read_varnode_by_name_u128(ctx, name)?;
1281        u64::try_from(value).ok()
1282    }
1283
1284    pub fn read_varnode_by_name_u128(&mut self, ctx: &Context<'_>, name: &str) -> Option<u128> {
1285        match ctx.get_named(name)? {
1286            ValueId::Varnode(id) => self.read_varnode_u128(ctx, id),
1287            _ => None,
1288        }
1289    }
1290
1291    pub fn read_varnode_bytes(&mut self, ctx: &Context<'_>, id: VarnodeId) -> Vec<u8> {
1292        let varnode = Varnode::from_id(ctx, id);
1293        let space = varnode.space().id;
1294        let addr = varnode.address() as u64;
1295        let size = varnode.size();
1296        self.memory
1297            .read_bytes(space.into(), addr, size)
1298            .unwrap_or_default()
1299    }
1300
1301    pub fn read_varnode_by_name_bytes(&mut self, ctx: &Context<'_>, name: &str) -> Option<Vec<u8>> {
1302        match ctx.get_named(name)? {
1303            ValueId::Varnode(id) => Some(self.read_varnode_bytes(ctx, id)),
1304            _ => None,
1305        }
1306    }
1307
1308    pub fn get_value_bytes(&mut self, ctx: &Context<'_>, id: ValueId) -> Option<Vec<u8>> {
1309        let mut tmp = TempInterpreter {
1310            memory: &mut self.memory,
1311            literals: &mut self.literal_cache,
1312            insn_values: &mut self.insn_values,
1313            block_param_values: &mut self.block_param_values,
1314            poison_params: &self.poison_params,
1315            ctx,
1316        };
1317        let sv = tmp.get_value(id).ok()?;
1318        let size = sv.size().ok()?;
1319        let bits = sv.as_bits();
1320        let mut bytes = vec![0u8; size];
1321        for (i, byte) in bytes.iter_mut().enumerate() {
1322            *byte = u8::try_from((bits >> (i * 8)) & u128::from(0xffu8)).unwrap();
1323        }
1324        Some(bytes)
1325    }
1326
1327    pub fn current_block(&self) -> BlockId {
1328        self.block
1329    }
1330
1331    pub fn set_call_interceptor(
1332        &mut self,
1333        interceptor: impl FnMut(
1334            &Context<'_>,
1335            &mut StandaloneEmulator<M>,
1336            &CallSite,
1337        ) -> Result<CallInterception, Box<str>>
1338        + Send
1339        + Sync
1340        + 'static,
1341    ) {
1342        self.call_interceptor = Some(Box::new(interceptor));
1343    }
1344
1345    pub fn clear_call_interceptor(&mut self) {
1346        self.call_interceptor = None;
1347    }
1348
1349    pub fn read_memory(
1350        &mut self,
1351        ctx: &Context<'_>,
1352        space: impl Into<MemorySpaceId>,
1353        addr: u64,
1354        size: usize,
1355    ) -> Result<Vec<u8>, EmulatorErrorKind> {
1356        self.memory.configure_spaces(ctx);
1357        self.memory.read_bytes(space.into(), addr, size)
1358    }
1359
1360    pub fn write_memory(
1361        &mut self,
1362        ctx: &Context<'_>,
1363        space: impl Into<MemorySpaceId>,
1364        addr: u64,
1365        value: &[u8],
1366    ) -> Result<(), EmulatorErrorKind> {
1367        self.memory.configure_spaces(ctx);
1368        self.memory.write_bytes(space.into(), addr, value)
1369    }
1370
1371    /// Resolve a terminator's bare-local argument list; `func` is the owning
1372    /// function of the instruction the args came from (strict IR locality).
1373    fn collect_block_args(
1374        &mut self,
1375        ctx: &Context<'_>,
1376        func: FunctionId,
1377        args: &[LocalValueId],
1378    ) -> Result<Vec<SizedValue>, EmulatorErrorKind> {
1379        let mut tmp = TempInterpreter {
1380            memory: &mut self.memory,
1381            literals: &mut self.literal_cache,
1382            insn_values: &mut self.insn_values,
1383            block_param_values: &mut self.block_param_values,
1384            poison_params: &self.poison_params,
1385            ctx,
1386        };
1387        args.iter()
1388            .map(|&arg| tmp.get_value(arg.qualify(func)))
1389            .collect()
1390    }
1391
1392    fn bind_block_args(
1393        &mut self,
1394        ctx: &Context<'_>,
1395        func: FunctionId,
1396        target: BlockId,
1397        args: &[LocalValueId],
1398    ) -> Result<(), EmulatorErrorKind> {
1399        let values = self.collect_block_args(ctx, func, args)?;
1400        let params = BasicBlock::from_id(ctx, target)
1401            .params()
1402            .map(|param| param.id)
1403            .collect::<Vec<_>>();
1404        if values.len() != params.len() {
1405            return Err(EmulatorErrorKind::ValueError(values.len() as u128));
1406        }
1407        for (param, value) in params.into_iter().zip(values) {
1408            self.block_param_values.insert(param, value);
1409        }
1410        Ok(())
1411    }
1412
1413    /// Resolve a range of a register varnode used as a store destination.
1414    ///
1415    /// The flat SLEIGH emitter represents `ST1[8:10] = value` with a `range`
1416    /// value so it retains the destination byte offset. As a source, `range`
1417    /// means extracted bits; as a register-space store pointer, it is an
1418    /// lvalue and must instead mean the base register address plus that offset.
1419    /// Treating it as extracted bits writes to an address derived from the old
1420    /// high word, leaving the high 16 bits of an i80 register stale.
1421    fn register_range_store_address(
1422        &self,
1423        ctx: &Context<'_>,
1424        func: FunctionId,
1425        store: &Store,
1426    ) -> Option<u64> {
1427        let space = store.space.qualify(func);
1428        let space_id = space.shared()?;
1429        if !matches!(Space::from_id(ctx, space_id).ty, SpaceType::Register) {
1430            return None;
1431        }
1432        let ValueRef::Instruction(range) = ValueRef::new(store.ptr.qualify(func), ctx) else {
1433            return None;
1434        };
1435        let Mnemonic::Range(Range { src, start, .. }) = range.mnemonic() else {
1436            return None;
1437        };
1438        let ValueRef::Varnode(varnode) = ValueRef::new(src.qualify(func), ctx) else {
1439            return None;
1440        };
1441        let varnode = Varnode::from_id(ctx, varnode.id);
1442        (varnode.space().id == space_id).then_some(varnode.address() as u64 + *start as u64)
1443    }
1444
1445    fn apply_call_continuation(
1446        &mut self,
1447        ctx: &Context<'_>,
1448        continuation: CallContinuation,
1449    ) -> Result<(), EmulatorErrorKind> {
1450        let target = match continuation {
1451            CallContinuation::Block(block) => block,
1452            CallContinuation::Address(addr) => self
1453                .block_at(ctx, addr)
1454                .ok_or(EmulatorErrorKind::InvalidBlockAddress(addr))?,
1455        };
1456        self.block = target;
1457        self.idx = 0;
1458        Ok(())
1459    }
1460
1461    fn intercept_call(
1462        &mut self,
1463        ctx: &Context<'_>,
1464        block: BlockId,
1465        instruction: InstructionId,
1466        call: &Call,
1467    ) -> crate::Result<Option<StepEvent>> {
1468        let Some(mut interceptor) = self.call_interceptor.take() else {
1469            return Ok(None);
1470        };
1471        let target = require_real_callee(call.target)
1472            .map_err(|kind| self.make_error_at(ctx, instruction, kind))?;
1473
1474        let site = CallSite {
1475            instruction,
1476            block,
1477            target,
1478            // Operands are stored bare-local; the CallSite is a boundary object,
1479            // so qualify with the call instruction's own function.
1480            args: call
1481                .args
1482                .iter()
1483                .map(|a| a.qualify(instruction.func))
1484                .collect(),
1485        };
1486        let result = interceptor(ctx, self, &site);
1487        self.call_interceptor = Some(interceptor);
1488
1489        match result {
1490            Ok(CallInterception::PassThrough) => Ok(None),
1491            Ok(CallInterception::Handled(continuation)) => {
1492                self.apply_call_continuation(ctx, continuation)
1493                    .map_err(|kind| self.make_error_at(ctx, instruction, kind))?;
1494                Ok(Some(StepEvent::InterceptedCall))
1495            }
1496            Err(message) => Err(self.make_error_at(
1497                ctx,
1498                instruction,
1499                EmulatorErrorKind::InterceptError(message),
1500            )),
1501        }
1502    }
1503
1504    /// Evaluate an operand through the scalar interpreter while retaining its
1505    /// full f80 payload (the public convenience getter intentionally returns
1506    /// only u64 values).
1507    fn scalar_value(
1508        &mut self,
1509        ctx: &Context<'_>,
1510        id: ValueId,
1511    ) -> Result<SizedValue, EmulatorErrorKind> {
1512        let mut interpreter = TempInterpreter {
1513            memory: &mut self.memory,
1514            literals: &mut self.literal_cache,
1515            insn_values: &mut self.insn_values,
1516            block_param_values: &mut self.block_param_values,
1517            poison_params: &self.poison_params,
1518            ctx,
1519        };
1520        interpreter.get_value(id)
1521    }
1522
1523    /// Decode the IEEE rounding-direction argument used by the supplemental
1524    /// floating p-code operations.  Its encoding is deliberately independent
1525    /// of architectural control words: 0 = nearest-even, 1 = down, 2 = up,
1526    /// 3 = toward zero.
1527    fn ieee_rounding_mode(value: u128) -> Option<Round> {
1528        match value {
1529            0 => Some(Round::NearestTiesToEven),
1530            1 => Some(Round::TowardNegative),
1531            2 => Some(Round::TowardPositive),
1532            3 => Some(Round::TowardZero),
1533            _ => None,
1534        }
1535    }
1536
1537    /// Evaluate one of the explicit IEEE arithmetic p-code operations in the
1538    /// format carried by its operands.  It reports IEEE facts only: no
1539    /// architectural payload selection and no precision-control policy.
1540    fn ieee_arithmetic(
1541        lhs: SizedValue,
1542        rhs: SizedValue,
1543        round: Round,
1544        name: &str,
1545    ) -> Option<(SizedValue, Status)> {
1546        if lhs.size != rhs.size {
1547            return None;
1548        }
1549        macro_rules! operation {
1550            ($lhs:expr, $rhs:expr) => {
1551                match name {
1552                    "float_add" | "float_add_flags" => $lhs.add_r($rhs, round),
1553                    "float_sub" | "float_sub_flags" => $lhs.sub_r($rhs, round),
1554                    "float_mul" | "float_mul_flags" => $lhs.mul_r($rhs, round),
1555                    "float_div" | "float_div_flags" => $lhs.div_r($rhs, round),
1556                    _ => return None,
1557                }
1558            };
1559        }
1560        // Keeping the formats separate is important: routing f32 through f64
1561        // changes both the rounded result and its exception facts.
1562        match lhs.size {
1563            4 => {
1564                let lhs = Single::from_bits(lhs.as_bits());
1565                let rhs = Single::from_bits(rhs.as_bits());
1566                let value = operation!(lhs, rhs);
1567                Some((
1568                    SizedValue::from_bits(value.value.to_bits(), 4),
1569                    value.status,
1570                ))
1571            }
1572            8 => {
1573                let lhs = Double::from_bits(lhs.as_bits());
1574                let rhs = Double::from_bits(rhs.as_bits());
1575                let value = operation!(lhs, rhs);
1576                Some((
1577                    SizedValue::from_bits(value.value.to_bits(), 8),
1578                    value.status,
1579                ))
1580            }
1581            10 => {
1582                let lhs = X87DoubleExtended::from_bits(lhs.as_bits());
1583                let rhs = X87DoubleExtended::from_bits(rhs.as_bits());
1584                let value = operation!(lhs, rhs);
1585                Some((
1586                    SizedValue::from_bits(value.value.to_bits(), 10),
1587                    value.status,
1588                ))
1589            }
1590            _ => None,
1591        }
1592    }
1593
1594    /// Round a floating value to a narrower significand precision within its
1595    /// own exponent range.  This is an IEEE-level operation: the precision is
1596    /// an explicit argument, not read from any architectural control word.
1597    /// Only the 80-bit format is implemented; f32/f64 operands return `None`
1598    /// so the caller falls through to the generic interpreter.
1599    fn ieee_round_to_precision(
1600        value: SizedValue,
1601        precision: u128,
1602        round: Round,
1603    ) -> Option<(SizedValue, Status)> {
1604        if value.size != 10 {
1605            return None;
1606        }
1607        let precision = u32::try_from(precision).ok()?;
1608        let result = float80::round_to_precision(value.as_bits(), precision, round);
1609        Some((SizedValue::from_bits(result.bits, 10), result.status))
1610    }
1611
1612    /// Narrow an extended value to a smaller IEEE format under an explicit
1613    /// rounding mode.  This is the conversion alone: no architectural
1614    /// indefinite substitution, no status word, no store policy.
1615    fn ieee_narrow(value: SizedValue, size: u128, round: Round) -> Option<(SizedValue, Status)> {
1616        let mut loses_info = false;
1617        macro_rules! to {
1618            ($source:expr, $target:ty, $bytes:expr) => {{
1619                let converted: rustc_apfloat::StatusAnd<$target> =
1620                    $source.convert_r(round, &mut loses_info);
1621                Some((
1622                    SizedValue::from_bits(converted.value.to_bits(), $bytes),
1623                    converted.status,
1624                ))
1625            }};
1626        }
1627        match (value.size, size) {
1628            (8, 4) => to!(Double::from_bits(value.as_bits()), Single, 4),
1629            (10, 4) => to!(X87DoubleExtended::from_bits(value.as_bits()), Single, 4),
1630            (10, 8) => to!(X87DoubleExtended::from_bits(value.as_bits()), Double, 8),
1631            _ => None,
1632        }
1633    }
1634
1635    /// Widen a 32- or 64-bit IEEE value to the extended format.  Every such
1636    /// value is exactly representable there, so the widening is lossless and
1637    /// operations implemented for the extended format alone can serve the
1638    /// narrower ones through it.
1639    /// True when a 32-, 64- or 80-bit IEEE value is a signalling NaN.  The
1640    /// widening below quiets one, so the fact has to be read from the source.
1641    fn is_signaling_nan(value: SizedValue) -> bool {
1642        let bits = value.as_bits();
1643        match value.size {
1644            4 => Single::from_bits(bits).is_signaling(),
1645            8 => Double::from_bits(bits).is_signaling(),
1646            10 => X87DoubleExtended::from_bits(bits).is_signaling(),
1647            _ => false,
1648        }
1649    }
1650
1651    fn widen_to_f80(value: SizedValue) -> Option<X87DoubleExtended> {
1652        let mut loses_info = false;
1653        match value.size {
1654            4 => Some(
1655                Single::from_bits(value.as_bits())
1656                    .convert_r(Round::NearestTiesToEven, &mut loses_info)
1657                    .value,
1658            ),
1659            8 => Some(
1660                Double::from_bits(value.as_bits())
1661                    .convert_r(Round::NearestTiesToEven, &mut loses_info)
1662                    .value,
1663            ),
1664            10 => Some(X87DoubleExtended::from_bits(value.as_bits())),
1665            _ => None,
1666        }
1667    }
1668
1669    /// Narrow an extended intermediate that carries a sticky inexactness back
1670    /// to `size` bytes without double rounding.  Forcing the extended
1671    /// significand's low bit when the intermediate was inexact (round to odd)
1672    /// keeps the exact value on the correct side of every boundary of the
1673    /// narrower format, which has at least two fewer significand bits.
1674    fn narrow_with_sticky(
1675        bits: u128,
1676        inexact: bool,
1677        size: u8,
1678        round: Round,
1679    ) -> Option<(SizedValue, Status)> {
1680        if size == 10 {
1681            return Some((
1682                SizedValue::from_bits(bits, 10),
1683                if inexact { Status::INEXACT } else { Status::OK },
1684            ));
1685        }
1686        let sticky = if inexact { bits | 1 } else { bits };
1687        let (mut result, mut status) =
1688            Self::ieee_narrow(SizedValue::from_bits(sticky, 10), u128::from(size), round)?;
1689        if inexact {
1690            status |= Status::INEXACT;
1691        }
1692        result = SizedValue::from_bits(result.as_bits(), size as usize);
1693        Some((result, status))
1694    }
1695
1696    /// Convert a floating value to a two's-complement integer of `size` bytes
1697    /// under an explicit rounding mode.  A NaN, an infinity or a value outside
1698    /// the destination range is invalid; the architectural replacement value
1699    /// for that case is the caller's business, not this operation's.
1700    fn ieee_to_int(value: SizedValue, size: u128, round: Round) -> Option<(SizedValue, Status)> {
1701        // Every narrower format widens losslessly, so one extended conversion
1702        // serves f32, f64 and f80 alike.
1703        let signaling = Self::is_signaling_nan(value);
1704        let value = SizedValue::from_bits(Self::widen_to_f80(value)?.to_bits(), 10);
1705        let size = usize::try_from(size).ok()?;
1706        if !matches!(size, 2 | 4 | 8) {
1707            return None;
1708        }
1709        let mut exact = false;
1710        let converted =
1711            X87DoubleExtended::from_bits(value.as_bits()).to_i128_r(size * 8, round, &mut exact);
1712        let mask = (1u128 << (size * 8)) - 1;
1713        let mut status = converted.status;
1714        if signaling {
1715            status |= Status::INVALID_OP;
1716        }
1717        Some((
1718            SizedValue::from_bits(converted.value as u128 & mask, size),
1719            status,
1720        ))
1721    }
1722
1723    /// Convert a two's-complement integer of the operand's own width to an
1724    /// IEEE value of `size` bytes under an explicit rounding mode.  Only
1725    /// inexact is possible; what the conversion means architecturally is the
1726    /// caller's business.
1727    fn ieee_from_int(value: SizedValue, size: u128, round: Round) -> Option<(SizedValue, Status)> {
1728        let source = value.signed_value();
1729        let width = usize::from(value.size) * 8;
1730        match size {
1731            4 => {
1732                let converted = Single::from_i128_r(source, round);
1733                Some((
1734                    SizedValue::from_bits(converted.value.to_bits(), 4),
1735                    converted.status,
1736                ))
1737            }
1738            8 => {
1739                let converted = Double::from_i128_r(source, round);
1740                Some((
1741                    SizedValue::from_bits(converted.value.to_bits(), 8),
1742                    converted.status,
1743                ))
1744            }
1745            10 => {
1746                let converted = X87DoubleExtended::from_i128_r(source, round);
1747                Some((
1748                    SizedValue::from_bits(converted.value.to_bits(), 10),
1749                    converted.status,
1750                ))
1751            }
1752            _ => {
1753                let _ = width;
1754                None
1755            }
1756        }
1757    }
1758
1759    fn ieee_flags(status: Status) -> SizedValue {
1760        let mut flags = 0u128;
1761        if status.contains(Status::INVALID_OP) {
1762            flags |= 1;
1763        }
1764        if status.contains(Status::DIV_BY_ZERO) {
1765            flags |= 1 << 2;
1766        }
1767        if status.contains(Status::OVERFLOW) {
1768            flags |= 1 << 3;
1769        }
1770        if status.contains(Status::UNDERFLOW) {
1771            flags |= 1 << 4;
1772        }
1773        if status.contains(Status::INEXACT) {
1774            flags |= 1 << 5;
1775        }
1776        SizedValue::from_bits(flags, 1)
1777    }
1778
1779    /// Interpret the packed-integer SLEIGH user-ops that x86's MMX/SSE
1780    /// constructors leave as `pcodeop` applications. Returns `None` for every
1781    /// other user-op so the generic interpreter stays the implementation.
1782    ///
1783    /// `pavgb`/`pavgw` are applied by the spec to one lane at a time, so they
1784    /// are scalar here. `pmaddwd`/`pmulhuw` receive a whole vector and are
1785    /// width-generic, covering the 8-byte MMX and 16-byte XMM forms alike.
1786    fn interpret_packed_pcode_op(
1787        &mut self,
1788        ctx: &Context<'_>,
1789        insn: &InstructionRef<'_, '_>,
1790        mnemonic: &Mnemonic,
1791    ) -> Result<Option<SizedValue>, EmulatorErrorKind> {
1792        let Mnemonic::PCodeOp(op) = mnemonic else {
1793            return Ok(None);
1794        };
1795        let name = ctx.shared.pcode_ops[op.id].clone();
1796        let func = insn.id.func;
1797
1798        // The significand/exponent split is a pure decomposition of the
1799        // encoding and needs no context at all.
1800        if let [src] = op.args.as_slice() {
1801            let value = self.scalar_value(ctx, src.qualify(func))?;
1802            if value.size != 10 {
1803                return Ok(None);
1804            }
1805            return Ok(match name.as_ref() {
1806                "extract_significand" => Some(SizedValue::from_f80_bits(
1807                    float80::extract_significand(value.as_bits()),
1808                )),
1809                "extract_exponent" => Some(SizedValue::from_f80_bits(
1810                    float80::extract_exponent(value.as_bits()).bits,
1811                )),
1812                _ => None,
1813            });
1814        }
1815
1816        // The three-argument form is the explicit, architecture-neutral IEEE
1817        // interface.  Leave two-argument user-ops on the legacy dispatch below.
1818        if let [lhs, rhs, rounding_mode] = op.args.as_slice() {
1819            let lhs = self.scalar_value(ctx, lhs.qualify(func))?;
1820            let rhs = self.scalar_value(ctx, rhs.qualify(func))?;
1821            let rounding_mode = self.scalar_value(ctx, rounding_mode.qualify(func))?;
1822
1823            // The partial remainder's third operand selects the quotient's
1824            // rounding rule rather than the result's: zero truncates it toward
1825            // zero, non-zero rounds it to nearest even. The remainder itself is
1826            // exact under either rule, so no result rounding mode applies.
1827            if matches!(name.as_ref(), "float_rem_partial" | "float_rem_quotient")
1828                && lhs.size == 10
1829                && rhs.size == 10
1830            {
1831                let to_nearest = rounding_mode.as_bits() != 0;
1832                let result = float80::remainder(lhs.as_bits(), rhs.as_bits(), to_nearest);
1833                return Ok(Some(if name.as_ref() == "float_rem_quotient" {
1834                    // Bits 0-2 hold the low three bits of the quotient's
1835                    // magnitude; bit 3 reports that the reduction was partial,
1836                    // in which case no quotient bits are available and bits 0-2
1837                    // are zero.
1838                    let code = if result.incomplete {
1839                        8
1840                    } else {
1841                        u128::from(result.quotient & 7)
1842                    };
1843                    SizedValue::from_bits(code, 1)
1844                } else {
1845                    SizedValue::from_f80_bits(result.bits)
1846                }));
1847            }
1848
1849            let Some(round) = Self::ieee_rounding_mode(rounding_mode.as_bits()) else {
1850                return Ok(None);
1851            };
1852            let evaluated = match name.as_ref() {
1853                "float_round_to_precision" | "float_round_to_precision_flags" => {
1854                    Self::ieee_round_to_precision(lhs, rhs.as_bits(), round)
1855                }
1856                "float_narrow" | "float_narrow_flags" => {
1857                    Self::ieee_narrow(lhs, rhs.as_bits(), round)
1858                }
1859                "float_to_int" | "float_to_int_flags" => {
1860                    Self::ieee_to_int(lhs, rhs.as_bits(), round)
1861                }
1862                "float_scalb" | "float_scalb_flags" if lhs.size == 10 => {
1863                    let steps = i32::try_from(rhs.as_bits() as i64).unwrap_or(
1864                        if (rhs.as_bits() as i64) < 0 {
1865                            i32::MIN
1866                        } else {
1867                            i32::MAX
1868                        },
1869                    );
1870                    let result = float80::scalb_ieee(lhs.as_bits(), steps, round);
1871                    Some((SizedValue::from_f80_bits(result.bits), result.status))
1872                }
1873                "float_from_int" | "float_from_int_flags" => {
1874                    Self::ieee_from_int(lhs, rhs.as_bits(), round)
1875                }
1876                _ => Self::ieee_arithmetic(lhs, rhs, round, name.as_ref()),
1877            };
1878            if let Some((result, status)) = evaluated {
1879                return Ok(Some(if name.ends_with("_flags") {
1880                    Self::ieee_flags(status)
1881                } else {
1882                    result
1883                }));
1884            }
1885            return Ok(None);
1886        }
1887
1888        let [lhs, rhs] = op.args.as_slice() else {
1889            return Ok(None);
1890        };
1891        let lhs = self.scalar_value(ctx, lhs.qualify(func))?;
1892        let rhs = self.scalar_value(ctx, rhs.qualify(func))?;
1893
1894        // The explicit IEEE unary operations take their rounding mode as the
1895        // second operand and report architecture-neutral facts, exactly like
1896        // their binary counterparts.
1897        if let (true, Some(round)) = (
1898            matches!(lhs.size, 4 | 8 | 10),
1899            Self::ieee_rounding_mode(rhs.as_bits()),
1900        ) {
1901            // Each operation is evaluated in the extended format, which holds
1902            // every f32 and f64 exactly; the intermediate is then narrowed back
1903            // with its inexactness kept sticky, so a single rounding reaches
1904            // the operand's own format.
1905            let extended_only = matches!(
1906                name.as_ref(),
1907                "float_log2" | "float_log2_flags" | "to_bcd" | "to_bcd_flags"
1908            );
1909            let wide = (!extended_only || lhs.size == 10)
1910                .then(|| Self::widen_to_f80(lhs))
1911                .flatten();
1912            let unary = wide.and_then(|wide| {
1913                let wide = wide.to_bits();
1914                // A narrower format is served by an extended evaluation that
1915                // truncates, so that the sticky narrowing below performs the
1916                // operand format's single rounding. Rounding twice would move
1917                // an inexact result two steps under a directed mode.
1918                let inner = if lhs.size == 10 {
1919                    round
1920                } else {
1921                    Round::TowardZero
1922                };
1923                Some(match name.as_ref() {
1924                    "float_sqrt" | "float_sqrt_flags" => float80::sqrt_ieee(wide, inner),
1925                    "float_round_to_integral" | "float_round_to_integral_flags" => {
1926                        float80::round_to_integral_ieee(wide, round)
1927                    }
1928                    "float_log2" | "float_log2_flags" => float80::log2_ieee(wide),
1929                    "to_bcd" | "to_bcd_flags" => float80::to_bcd(wide, round),
1930                    _ => return None,
1931                })
1932            });
1933            if let Some(result) = unary {
1934                if name.starts_with("to_bcd") {
1935                    return Ok(Some(if name.ends_with("_flags") {
1936                        Self::ieee_flags(result.status)
1937                    } else {
1938                        // The packed decimal is ten bytes of digits, not a float.
1939                        SizedValue::from_bits(result.bits, 10)
1940                    }));
1941                }
1942                // Widening quiets a signalling NaN, so its invalid report is
1943                // read from the source operand instead.
1944                let mut result = result;
1945                if Self::is_signaling_nan(lhs) {
1946                    result.status |= Status::INVALID_OP;
1947                }
1948                let inexact = result.status.contains(Status::INEXACT);
1949                let (value, status) =
1950                    match Self::narrow_with_sticky(result.bits, inexact, lhs.size, round) {
1951                        Some((value, status)) => {
1952                            (value, status | (result.status & !Status::INEXACT))
1953                        }
1954                        None => (
1955                            SizedValue::from_bits(result.bits, lhs.size as usize),
1956                            result.status,
1957                        ),
1958                    };
1959                return Ok(Some(if name.ends_with("_flags") {
1960                    Self::ieee_flags(status)
1961                } else {
1962                    value
1963                }));
1964            }
1965        }
1966
1967        // Unsigned rounded average of one lane: (a + b + 1) >> 1, computed
1968        // wide enough that the carry out of the lane is kept.
1969        let average = |width: usize| -> Option<SizedValue> {
1970            (lhs.size as usize == width && rhs.size as usize == width).then(|| {
1971                let sum = lhs.as_bits() + rhs.as_bits() + 1;
1972                SizedValue::from_bits(sum >> 1, width)
1973            })
1974        };
1975
1976        let value = match name.as_ref() {
1977            "pavgb" => average(1),
1978            "pavgw" => average(2),
1979            // Unsigned 16x16 multiply per word lane, keeping the high half.
1980            "pmulhuw" => Self::packed_lanes(&lhs, &rhs, 2, |a, b| ((a * b) >> 16) & 0xffff),
1981            // Saturating packed add/subtract. A signed lane clamps to its
1982            // width's bounds; an unsigned lane clamps to zero and its maximum.
1983            "paddsb" => Self::saturating(&lhs, &rhs, 1, true, false),
1984            "paddsw" => Self::saturating(&lhs, &rhs, 2, true, false),
1985            "psubsb" => Self::saturating(&lhs, &rhs, 1, true, true),
1986            "psubsw" => Self::saturating(&lhs, &rhs, 2, true, true),
1987            "paddusb" => Self::saturating(&lhs, &rhs, 1, false, false),
1988            "paddusw" => Self::saturating(&lhs, &rhs, 2, false, false),
1989            "psubusb" => Self::saturating(&lhs, &rhs, 1, false, true),
1990            "psubusw" => Self::saturating(&lhs, &rhs, 2, false, true),
1991            // Signed 16x16 multiplies summed in pairs into each dword lane.
1992            "pmaddwd" => Self::packed_lanes(&lhs, &rhs, 4, |a, b| {
1993                let word =
1994                    |v: u128, half: u32| i64::from(((v >> (half * 16)) & 0xffff) as u16 as i16);
1995                let product = word(a, 0) * word(b, 0) + word(a, 1) * word(b, 1);
1996                u128::from(product as u32)
1997            }),
1998            _ => None,
1999        };
2000        Ok(value)
2001    }
2002
2003    /// Saturating packed add (`subtract` false) or subtract, per `width`-byte
2004    /// lane. `signed` selects signed bounds over unsigned ones.
2005    fn saturating(
2006        lhs: &SizedValue,
2007        rhs: &SizedValue,
2008        width: usize,
2009        signed: bool,
2010        subtract: bool,
2011    ) -> Option<SizedValue> {
2012        let bits = width * 8;
2013        Self::packed_lanes(lhs, rhs, width, |a, b| {
2014            if signed {
2015                let sign =
2016                    |v: u128| (v as i128) - (((v >> (bits - 1)) & 1) as i128) * (1i128 << bits);
2017                let (a, b) = (sign(a), sign(b));
2018                let value = if subtract { a - b } else { a + b };
2019                let max = (1i128 << (bits - 1)) - 1;
2020                let min = -(1i128 << (bits - 1));
2021                (value.clamp(min, max) as u128) & ((1u128 << bits) - 1)
2022            } else if subtract {
2023                a.saturating_sub(b)
2024            } else {
2025                (a + b).min((1u128 << bits) - 1)
2026            }
2027        })
2028    }
2029
2030    /// Apply `lane` to each `width`-byte lane of two equally sized vectors.
2031    /// Returns `None` unless both operands share a width that divides evenly
2032    /// into lanes.
2033    fn packed_lanes(
2034        lhs: &SizedValue,
2035        rhs: &SizedValue,
2036        width: usize,
2037        lane: impl Fn(u128, u128) -> u128,
2038    ) -> Option<SizedValue> {
2039        let size = lhs.size as usize;
2040        if size != rhs.size as usize || size == 0 || !size.is_multiple_of(width) {
2041            return None;
2042        }
2043        let bits = width * 8;
2044        let mask = (1u128 << bits) - 1;
2045        let mut out = 0u128;
2046        for index in 0..size / width {
2047            let shift = index * bits;
2048            let a = (lhs.as_bits() >> shift) & mask;
2049            let b = (rhs.as_bits() >> shift) & mask;
2050            out |= (lane(a, b) & mask) << shift;
2051        }
2052        Some(SizedValue::from_bits(out, size))
2053    }
2054
2055    fn step_with_event(&mut self, ctx: &Context<'_>) -> crate::Result<StepEvent> {
2056        self.memory.configure_spaces(ctx);
2057        let block_id = self.block;
2058        if self.cached_block != Some(block_id) || self.idx == 0 {
2059            // The module only gains types while nothing is part-way through a
2060            // block — lifting happens on block entry — so this rides the same
2061            // refresh as the instruction list rather than paying per step.
2062            self.refresh_sequence_types(ctx);
2063            self.cached_insns.clear();
2064            self.cached_insns
2065                .extend_from_slice(ctx.block(block_id).instruction_ids());
2066            self.cached_block = Some(block_id);
2067        }
2068        // A degenerate block — empty, or exhausted without a terminator — is
2069        // malformed lifter output, not an emulator bug. Report it so a bounded
2070        // consumer (and a VM running lifted-on-demand code) can stop with a
2071        // reason instead of aborting the process.
2072        let Some(&local) = self.cached_insns.get(self.idx) else {
2073            return Err(self.make_empty_block_error(ctx));
2074        };
2075        let insn_id = InstructionId::new(block_id.func, local);
2076        let insn = InstructionRef::from_id(ctx, insn_id);
2077        let id = insn.id;
2078
2079        if let Some(hook) = self.instruction_hook.as_ref() {
2080            hook(&insn, self)
2081        }
2082
2083        // Resolved once and threaded through the step. Each `insn.mnemonic()`
2084        // re-walks `view.instruction(id)`, two registry lookups deep, and the
2085        // step path asked for the same instruction's mnemonic several times.
2086        let mnemonic = insn.mnemonic();
2087
2088        match mnemonic {
2089            Mnemonic::Branch(Branch { target, args }) => {
2090                // Terminator targets are bare body-local indices in the terminator's
2091                // own arena (`id.func`); qualify to the current block's function.
2092                let target = BlockId::new(id.func, *target);
2093                self.bind_block_args(ctx, id.func, target, args)
2094                    .map_err(|kind| self.make_error(ctx, kind))?;
2095                self.block = target;
2096                self.idx = 0;
2097            }
2098
2099            Mnemonic::Call(call) => {
2100                if let Some(event) = self.intercept_call(ctx, block_id, insn_id, call)? {
2101                    return Ok(event);
2102                }
2103                let target =
2104                    require_real_callee(call.target).map_err(|kind| self.make_error(ctx, kind))?;
2105                self.block = FunctionBody::from_id(ctx, target)
2106                    .root()
2107                    .ok_or_else(|| {
2108                        self.make_error(ctx, EmulatorErrorKind::EmptyFunctionRoot(target))
2109                    })?
2110                    .id;
2111                self.idx = 0;
2112                // Remember this call so the matching `Return` can deposit the
2113                // callee's `Return.value` as this call's (aggregate) result.
2114                self.call_site_stack.push(insn_id);
2115                return Ok(StepEvent::DirectCallEntered(target));
2116            }
2117
2118            Mnemonic::TailCall(tc) => {
2119                // A tail call pops our frame and transfers to the callee's entry;
2120                // the callee's `Return` returns directly to *our* caller. Mirror the
2121                // old tail-`Branch`-into-entry behavior: jump to the callee root
2122                // without pushing a call frame.
2123                let target =
2124                    require_real_callee(tc.target).map_err(|kind| self.make_error(ctx, kind))?;
2125                self.block = FunctionBody::from_id(ctx, target)
2126                    .root()
2127                    .ok_or_else(|| {
2128                        self.make_error(ctx, EmulatorErrorKind::EmptyFunctionRoot(target))
2129                    })?
2130                    .id;
2131                self.idx = 0;
2132            }
2133
2134            Mnemonic::Apply(apply) => {
2135                const APPLY_STEP_BUDGET: usize = 100_000;
2136                let target =
2137                    require_real_callee(apply.target).map_err(|kind| self.make_error(ctx, kind))?;
2138                let args = self
2139                    .collect_block_args(ctx, id.func, &apply.args)
2140                    .map_err(|kind| self.make_error(ctx, kind))?;
2141                let root = FunctionBody::from_id(ctx, target)
2142                    .root()
2143                    .ok_or_else(|| {
2144                        self.make_error(ctx, EmulatorErrorKind::EmptyFunctionRoot(target))
2145                    })?
2146                    .id;
2147                let mut nested = StandaloneEmulator::<M>::new_in(root);
2148                nested
2149                    .run_pure(ctx, target, &args, APPLY_STEP_BUDGET)
2150                    .map_err(|e| self.make_error(ctx, e.kind))?;
2151                let ret_value = lambda_return_value(ctx, nested.current_block())
2152                    .ok_or_else(|| self.make_error(ctx, EmulatorErrorKind::ValueError(0)))?;
2153                if let Some(value) = nested.get_value(ctx, ret_value) {
2154                    let size = ctx
2155                        .stored_type_of(ret_value)
2156                        .map(|ty| ctx.shared.types.size_of(ty))
2157                        .unwrap_or(8);
2158                    self.insn_values
2159                        .insert(insn_id, SizedValue::new(value, size));
2160                } else if let ValueId::Instruction(ret_id) = ret_value
2161                    && let Some(agg) = nested.aggregate_values.get(&ret_id).cloned()
2162                {
2163                    // The lambda returns an aggregate (e.g. the result tuple produced
2164                    // by accumulator elimination); propagate it field-wise so the
2165                    // caller's `extract(apply, i)` resolves — mirroring the scalar
2166                    // case above and the `Return` arm's call-result handling.
2167                    self.aggregate_values.insert(insn_id, agg);
2168                }
2169                self.idx += 1;
2170            }
2171
2172            Mnemonic::CBranch(CBranch {
2173                condition,
2174                success_block: target,
2175                success_args,
2176                failure_block: fallthrough,
2177                failure_args,
2178            }) => {
2179                let cond_val = self.get_value(ctx, condition.qualify(id.func)).unwrap();
2180                let target = BlockId::new(id.func, *target);
2181                let fallthrough = BlockId::new(id.func, *fallthrough);
2182                if cond_val != 0 {
2183                    self.bind_block_args(ctx, id.func, target, success_args)
2184                        .map_err(|kind| self.make_error(ctx, kind))?;
2185                    self.block = target;
2186                } else {
2187                    self.bind_block_args(ctx, id.func, fallthrough, failure_args)
2188                        .map_err(|kind| self.make_error(ctx, kind))?;
2189                    self.block = fallthrough;
2190                }
2191                self.idx = 0;
2192            }
2193
2194            Mnemonic::Switch(switch) => {
2195                let value = self
2196                    .get_value(ctx, switch.scrutinee.qualify(id.func))
2197                    .unwrap();
2198                let arm = switch
2199                    .cases
2200                    .iter()
2201                    .find(|case| case.value == value)
2202                    .map(|case| (case.target, &case.args));
2203                let (target, args) = match arm
2204                    .or_else(|| switch.default.map(|target| (target, &switch.default_args)))
2205                {
2206                    Some(arm) => arm,
2207                    // No arm matches and there is no default. A table behind a
2208                    // bounds check is total over its listed values, so arriving
2209                    // here means the guard that guaranteed that was wrong.
2210                    None => {
2211                        return Err(
2212                            self.make_error(ctx, EmulatorErrorKind::InvalidBlockAddress(value))
2213                        );
2214                    }
2215                };
2216                let target = BlockId::new(id.func, target);
2217                self.bind_block_args(ctx, id.func, target, args)
2218                    .map_err(|kind| self.make_error(ctx, kind))?;
2219                self.block = target;
2220                self.idx = 0;
2221            }
2222
2223            Mnemonic::BranchInd(BranchInd { ptr }) => {
2224                let addr = self.get_value(ctx, ptr.qualify(id.func)).unwrap();
2225                let target = self.block_at(ctx, addr).ok_or_else(|| {
2226                    self.make_error(ctx, EmulatorErrorKind::InvalidBlockAddress(addr))
2227                })?;
2228                self.block = target;
2229                self.idx = 0;
2230            }
2231
2232            Mnemonic::CallInd(CallInd { ptr, .. }) => {
2233                let addr = self.get_value(ctx, ptr.qualify(id.func)).unwrap();
2234                let target = self.block_at(ctx, addr).ok_or_else(|| {
2235                    self.make_error(ctx, EmulatorErrorKind::InvalidBlockAddress(addr))
2236                })?;
2237                self.block = target;
2238                self.idx = 0;
2239                self.call_site_stack.push(insn_id);
2240                return Ok(StepEvent::IndirectCallEntered);
2241            }
2242
2243            Mnemonic::Return(Return { ptr, value, .. }) => {
2244                // Deposit the callee's return value as the result of the call that
2245                // entered it: an aggregate (the functional write-set) is copied
2246                // field-wise; a scalar return is copied through. This is what makes
2247                // a caller's `extract(call, i)` see the callee's effects.
2248                if let Some(call_id) = self.call_site_stack.pop() {
2249                    if let Some(LocalValueId::Instruction(src_local)) = value {
2250                        let src = InstructionId::new(id.func, *src_local);
2251                        if let Some(agg) = self.aggregate_values.get(&src).cloned() {
2252                            self.aggregate_values.insert(call_id, agg);
2253                        } else if let Some(scalar) = self.insn_values.get(&src).copied() {
2254                            self.insn_values.insert(call_id, scalar);
2255                        }
2256                    }
2257                    // v2 implicit convention: an `Opaque` (non-regpure) call to a
2258                    // *materialized* callee binds outputs by storing each return-pack
2259                    // slot back to its mapped register (post-mem2reg the callee body
2260                    // may no longer write those registers directly). A regpure site
2261                    // replays the pack itself, so it is skipped.
2262                    self.writeback_materialized_outputs(ctx, call_id, id.func);
2263                }
2264
2265                let addr = self.get_value(ctx, ptr.qualify(id.func)).unwrap();
2266                let target = self.block_at(ctx, addr).ok_or_else(|| {
2267                    self.make_error(ctx, EmulatorErrorKind::InvalidBlockAddress(addr))
2268                })?;
2269                self.block = target;
2270                self.idx = 0;
2271                return Ok(StepEvent::Return);
2272            }
2273
2274            Mnemonic::ReturnValue(_) => {
2275                return Ok(StepEvent::ReturnValue);
2276            }
2277
2278            // Aggregate construction: evaluate each field and stash the field
2279            // vector. Fields are scalar for the register write-set (nested
2280            // aggregates, e.g. the RAM channel, are not modelled here yet).
2281            Mnemonic::Tuple(Tuple { fields }) => {
2282                let fields = fields.clone();
2283                let mut vals: Vec<SizedValue> = Vec::with_capacity(fields.len());
2284                for f in fields {
2285                    let val = {
2286                        let mut tmp = TempInterpreter {
2287                            memory: &mut self.memory,
2288                            literals: &mut self.literal_cache,
2289                            insn_values: &mut self.insn_values,
2290                            block_param_values: &mut self.block_param_values,
2291                            poison_params: &self.poison_params,
2292                            ctx,
2293                        };
2294                        tmp.get_value(f.qualify(id.func))
2295                    };
2296                    vals.push(val.map_err(|kind| self.make_error(ctx, kind))?);
2297                }
2298                self.aggregate_values.insert(insn_id, vals);
2299                self.idx += 1;
2300            }
2301
2302            // Aggregate projection: pull field `index` out of the stashed vector.
2303            Mnemonic::Extract(Extract { agg, index }) => {
2304                let field = match agg {
2305                    LocalValueId::Instruction(agg_local) => self
2306                        .aggregate_values
2307                        .get(&InstructionId::new(id.func, *agg_local))
2308                        .and_then(|v| v.get(*index))
2309                        .copied(),
2310                    // A `map` body's `enumerate` lane arrives as an aggregate
2311                    // block param seeded by `run_map_body`.
2312                    LocalValueId::BlockParam(pid_local) => self
2313                        .block_param_aggregates
2314                        .get(&BlockParamId::new(id.func, *pid_local))
2315                        .and_then(|v| v.get(*index))
2316                        .copied(),
2317                    _ => None,
2318                };
2319                if let Some(field) = field {
2320                    self.insn_values.insert(insn_id, field);
2321                }
2322                self.idx += 1;
2323            }
2324
2325            // Whole-array load: the region snapshot `%l0 = load(ram:{N*esz}, base)`
2326            // that `array_promote` reads once before an original-array scan/map.
2327            // Its result is array-typed, so it lives in `array_values`; a scalar
2328            // load falls through to the generic interpreter below.
2329            Mnemonic::Load(load) if self.is_array_operand(ctx, ValueId::Instruction(insn_id)) => {
2330                let (space, ptr, size) = (load.space, load.ptr.qualify(id.func), load.size);
2331                let addr = self
2332                    .get_value(ctx, ptr)
2333                    .ok_or_else(|| self.make_error(ctx, EmulatorErrorKind::ValueError(0)))?;
2334                let buf = self
2335                    .read_memory(ctx, space.qualify(id.func), addr, size)
2336                    .map_err(|kind| self.make_error(ctx, kind))?;
2337                self.array_values.insert(insn_id, buf);
2338                self.idx += 1;
2339            }
2340
2341            // Whole-array store: the promoted buffer written back to memory in one
2342            // shot (`store(ram, base <- arr)` at loop exit). A scalar store falls
2343            // through to the generic interpreter below.
2344            Mnemonic::Store(store)
2345                if self
2346                    .register_range_store_address(ctx, id.func, store)
2347                    .is_some() =>
2348            {
2349                let address = self
2350                    .register_range_store_address(ctx, id.func, store)
2351                    .expect("guard checked register range store address");
2352                let mut tmp = TempInterpreter {
2353                    memory: &mut self.memory,
2354                    literals: &mut self.literal_cache,
2355                    insn_values: &mut self.insn_values,
2356                    block_param_values: &mut self.block_param_values,
2357                    poison_params: &self.poison_params,
2358                    ctx,
2359                };
2360                let value = tmp.get_value(store.src.qualify(id.func));
2361                let value = value.map_err(|kind| self.make_error(ctx, kind))?;
2362                self.memory
2363                    .write(
2364                        store.space.qualify(id.func),
2365                        SizedValue::from_u64(address),
2366                        store.size,
2367                        value,
2368                    )
2369                    .map_err(|kind| self.make_error(ctx, kind))?;
2370                self.idx += 1;
2371            }
2372
2373            Mnemonic::Store(store) if self.is_array_operand(ctx, store.src.qualify(id.func)) => {
2374                let (space, ptr, src) = (
2375                    store.space,
2376                    store.ptr.qualify(id.func),
2377                    store.src.qualify(id.func),
2378                );
2379                let buf = self
2380                    .resolve_array(ctx, src)
2381                    .ok_or_else(|| self.make_error(ctx, EmulatorErrorKind::ValueError(0)))?;
2382                let addr = self
2383                    .get_value(ctx, ptr)
2384                    .ok_or_else(|| self.make_error(ctx, EmulatorErrorKind::ValueError(0)))?;
2385                self.write_memory(ctx, space.qualify(id.func), addr, &buf)
2386                    .map_err(|kind| self.make_error(ctx, kind))?;
2387                self.idx += 1;
2388            }
2389
2390            // Total left-scan: thread the accumulator through every lane, running
2391            // the (pure) binary body once per element, and materialize the result
2392            // as an array buffer.
2393            Mnemonic::Scan(scan) => {
2394                let scan = scan.clone();
2395                self.eval_scan(ctx, insn_id, &scan)
2396                    .map_err(|kind| self.make_error(ctx, kind))?;
2397                self.idx += 1;
2398            }
2399
2400            // Total map: apply the pure unary body to every source element.
2401            Mnemonic::Map(map) => {
2402                let map = map.clone();
2403                self.eval_map(ctx, insn_id, &map)
2404                    .map_err(|kind| self.make_error(ctx, kind))?;
2405                self.idx += 1;
2406            }
2407
2408            // Array slice: `arr[start:start+size]` on an array-typed source is a
2409            // sub-buffer (e.g. `l0[1..]`, the original-array scan source), kept in
2410            // the `array_values` domain. A scalar `Range` (bit-field extract) falls
2411            // through to the generic interpreter below.
2412            Mnemonic::Range(range) if self.is_array_operand(ctx, range.src.qualify(id.func)) => {
2413                let (src, start, size) = (range.src.qualify(id.func), range.start, range.size);
2414                let buf = self
2415                    .resolve_array(ctx, src)
2416                    .ok_or_else(|| self.make_error(ctx, EmulatorErrorKind::ValueError(0)))?;
2417                let end = (start + size).min(buf.len());
2418                let slice = buf.get(start..end).unwrap_or(&[]).to_vec();
2419                self.array_values.insert(insn_id, slice);
2420                self.idx += 1;
2421            }
2422
2423            // The sequence intrinsics whose value is an *array* (or a lane read out
2424            // of one) live in the `array_values` domain rather than scalar
2425            // `insn_values`; every other (scalar) intrinsic falls through to the
2426            // generic interpreter.
2427            Mnemonic::Intrinsic(app) if is_array_intrinsic(app.id.name()) => {
2428                let name = app.id.name();
2429                let args: Vec<ValueId> = app.args.iter().map(|a| a.qualify(id.func)).collect();
2430                self.eval_array_intrinsic(ctx, insn_id, name, &args)
2431                    .map_err(|kind| self.make_error(ctx, kind))?;
2432                self.idx += 1;
2433            }
2434
2435            _ => {
2436                if let Some(value) = self
2437                    .interpret_packed_pcode_op(ctx, &insn, mnemonic)
2438                    .map_err(|kind| self.make_error(ctx, kind))?
2439                {
2440                    self.insn_values.insert(id, value);
2441                    self.idx += 1;
2442                    return Ok(StepEvent::Normal);
2443                }
2444                let mut tmp = TempInterpreter {
2445                    memory: &mut self.memory,
2446                    literals: &mut self.literal_cache,
2447                    insn_values: &mut self.insn_values,
2448                    block_param_values: &mut self.block_param_values,
2449                    poison_params: &self.poison_params,
2450                    ctx,
2451                };
2452                if let Some(value) = tmp.interpret(insn, mnemonic)? {
2453                    self.insn_values.insert(id, value);
2454                }
2455                self.idx += 1;
2456            }
2457        }
2458
2459        Ok(StepEvent::Normal)
2460    }
2461
2462    pub fn step(&mut self, ctx: &Context<'_>) -> crate::Result<()> {
2463        self.step_with_event(ctx).map(|_| ())
2464    }
2465
2466    pub fn run_block(&mut self, ctx: &Context<'_>) -> crate::Result<()> {
2467        loop {
2468            self.step(ctx)?;
2469            if self.idx == 0 {
2470                break;
2471            }
2472        }
2473        Ok(())
2474    }
2475
2476    /// Runs blocks until the current block starts at `addr`.
2477    pub fn run_until(&mut self, ctx: &Context<'_>, addr: u64) -> crate::Result<()> {
2478        let target = self
2479            .block_at(ctx, addr)
2480            .ok_or_else(|| self.make_error(ctx, EmulatorErrorKind::UnknownAddress(addr)))?;
2481        while self.block != target {
2482            self.run_block(ctx)?;
2483        }
2484        Ok(())
2485    }
2486
2487    /// Runs the given function from its root block, stopping when the
2488    /// outermost `Return` is reached (without executing it).
2489    /// Nested calls are tracked via `call_depth` so inner returns are handled normally.
2490    /// The `call_stack` field is updated throughout execution.
2491    /// Seed a function's entry-block params with the values of the registers
2492    /// they were promoted from.
2493    ///
2494    /// `mem2reg` turns each register that is live-in to a function into a
2495    /// root-block parameter named after that register — these are the function's
2496    /// arguments. Entering the function (at the top level or via a call) binds
2497    /// those params from the current register file, so the callee receives the
2498    /// caller's register state through the calling convention. Params with no
2499    /// matching register (e.g. promoted stack slots) are left unbound.
2500    /// v2 implicit binding convention: store a materialized callee's return-pack
2501    /// slots back into their mapped registers on return from an `Opaque` call
2502    /// site. A `regpure` site is skipped (it replays the pack in its own body),
2503    /// as is any callee that is not materialized (no mapping to write back).
2504    fn writeback_materialized_outputs(
2505        &mut self,
2506        ctx: &Context<'_>,
2507        call_id: InstructionId,
2508        callee: FunctionId,
2509    ) {
2510        if call_is_regpure(ctx, call_id) {
2511            return;
2512        }
2513        let outputs = match &FunctionBody::from_id(ctx, callee).effects().register {
2514            qcode::value::RegisterChannelState::Materialized(map) => map.outputs.clone(),
2515            _ => return,
2516        };
2517        let Some(agg) = self.aggregate_values.get(&call_id).cloned() else {
2518            return;
2519        };
2520        for (field, &reg) in agg.iter().zip(&outputs) {
2521            let _ = self.set_varnode_u128(ctx, reg, field.as_bits());
2522        }
2523    }
2524
2525    fn seed_entry_params(&mut self, ctx: &Context<'_>, func: FunctionId) {
2526        let Some(root) = FunctionBody::from_id(ctx, func).root() else {
2527            return;
2528        };
2529        let root_id = root.id;
2530        enum Seed {
2531            Reg(VarnodeId),
2532            /// A materialized global value input: the param's origin is its
2533            /// address literal, and its value is the *contents* at that address
2534            /// (the RAM channel threads `mem[addr]` by value — see
2535            /// `argpromote::ram`'s global materialization). Implicit binding
2536            /// therefore reads memory at the literal, not the literal itself.
2537            Lit(u64),
2538        }
2539        let params: Vec<(BlockParamId, Option<Seed>, usize)> = BasicBlock::from_id(ctx, root_id)
2540            .params()
2541            .map(|param| {
2542                let src = param
2543                    .name()
2544                    .and_then(|name| ctx.get_named(name))
2545                    .and_then(|value| match value {
2546                        ValueId::Varnode(id) => Some(Seed::Reg(id)),
2547                        _ => None,
2548                    })
2549                    .or_else(|| match param.origin() {
2550                        Some(ValueId::Literal(_)) => {
2551                            let ValueRef::Literal(lit) = ValueRef::new(param.origin()?, ctx) else {
2552                                return None;
2553                            };
2554                            Some(Seed::Lit(lit.value()))
2555                        }
2556                        _ => None,
2557                    });
2558                (param.id, src, param.size())
2559            })
2560            .collect();
2561        for (param_id, src, size) in params {
2562            let value = match src {
2563                Some(Seed::Reg(varnode_id)) => self.read_varnode(ctx, varnode_id),
2564                Some(Seed::Lit(addr)) => {
2565                    // The literal is the global's *address*; the param carries the
2566                    // value stored there. Seed from memory (little-endian, param
2567                    // width) rather than the raw literal.
2568                    let space = ctx.shared.default_space;
2569                    self.read_memory(ctx, space, addr, size).ok().map(|bytes| {
2570                        let mut buf = [0u8; 8];
2571                        let n = bytes.len().min(8);
2572                        buf[..n].copy_from_slice(&bytes[..n]);
2573                        u64::from_le_bytes(buf)
2574                    })
2575                }
2576                None => None,
2577            };
2578            if let Some(value) = value {
2579                self.block_param_values
2580                    .insert(param_id, SizedValue::new(value, size));
2581            }
2582        }
2583    }
2584
2585    /// Bind a functionalized (`pure_reg`) callee's entry params positionally from
2586    /// the call's arguments, evaluated in the *caller's* frame.
2587    ///
2588    /// `argpromote_registers` makes such a callee a pure value function whose
2589    /// inputs flow through `Call.args` (not ambient register state), so the args
2590    /// are the source of truth — this replaces the register-file seeding
2591    /// [`seed_entry_params`](Self::seed_entry_params) does for conventional
2592    /// callees. The arg/param alignment invariant (`arg[i] ↔ param[i]`, built in
2593    /// lockstep by argpromote and preserved by mem2reg + `remove_entry_param`)
2594    /// makes the positional binding sound. Every arg is read before any param is
2595    /// written, so a self-recursive call still sees the caller's values.
2596    fn bind_entry_params_from_args(
2597        &mut self,
2598        ctx: &Context<'_>,
2599        call_id: InstructionId,
2600        target: FunctionId,
2601    ) {
2602        let args = match ctx.get_insn(call_id).mnemonic() {
2603            Mnemonic::Call(call) => call.args.clone(),
2604            _ => return,
2605        };
2606        let Some(root) = FunctionBody::from_id(ctx, target).root() else {
2607            return;
2608        };
2609        let params: Vec<(BlockParamId, usize)> = BasicBlock::from_id(ctx, root.id)
2610            .params()
2611            .map(|p| (p.id, p.size()))
2612            .collect();
2613        if args.len() != params.len() {
2614            // The alignment invariant is violated; fall back to register seeding
2615            // rather than mis-bind by position.
2616            self.seed_entry_params(ctx, target);
2617            return;
2618        }
2619        // Evaluate every arg in the caller frame first (recursion-safe), then bind.
2620        let values: Vec<SizedValue> = args
2621            .iter()
2622            .zip(&params)
2623            .map(|(&arg, &(_, size))| {
2624                let raw = self.get_value(ctx, arg.qualify(call_id.func)).unwrap_or(0);
2625                SizedValue::new(raw, size)
2626            })
2627            .collect();
2628        for ((param_id, _), value) in params.into_iter().zip(values) {
2629            self.block_param_values.insert(param_id, value);
2630        }
2631    }
2632
2633    pub fn run_function(&mut self, ctx: &Context<'_>, func: FunctionId) -> crate::Result<()> {
2634        let root = FunctionBody::from_id(ctx, func)
2635            .root()
2636            .ok_or_else(|| self.make_error(ctx, EmulatorErrorKind::EmptyFunctionRoot(func)))?
2637            .id;
2638        self.block = root;
2639        self.idx = 0;
2640        self.call_stack.push(func);
2641        self.seed_entry_params(ctx, func);
2642
2643        let mut call_depth: i32 = 0;
2644
2645        let result = loop {
2646            let insn_ids = BasicBlock::from_id(ctx, self.block)
2647                .instruction_ids()
2648                .to_vec();
2649            let insn = InstructionRef::from_id(ctx, insn_ids[self.idx]);
2650
2651            if matches!(insn.mnemonic(), Mnemonic::Return(_)) && call_depth == 0 {
2652                break Ok(());
2653            }
2654
2655            match self.step_with_event(ctx)? {
2656                StepEvent::DirectCallEntered(target) => {
2657                    call_depth += 1;
2658                    self.call_stack.push(target);
2659                    // Dual binding convention (argpromote v2): a `regpure`-tagged
2660                    // call site passes its inputs explicitly through `Call.args`
2661                    // (bound positionally); an `Opaque` (implicit) call — and a
2662                    // conventional callee — reads them from the register file the
2663                    // calling convention set up. The legacy `pure_reg` flag is
2664                    // still honored during the migration.
2665                    let regpure_site = self
2666                        .call_site_stack
2667                        .last()
2668                        .copied()
2669                        .is_some_and(|call_id| call_is_regpure(ctx, call_id));
2670                    if regpure_site || FunctionBody::from_id(ctx, target).is_reg_materialized() {
2671                        if let Some(&call_id) = self.call_site_stack.last() {
2672                            self.bind_entry_params_from_args(ctx, call_id, target);
2673                        }
2674                    } else {
2675                        self.seed_entry_params(ctx, target);
2676                    }
2677                }
2678                StepEvent::IndirectCallEntered => {
2679                    call_depth += 1;
2680                    // Infer the callee from the block we landed in.
2681                    if let Some(parent) = BasicBlock::from_id(ctx, self.block).parent() {
2682                        let callee = parent.id;
2683                        self.call_stack.push(callee);
2684                        self.seed_entry_params(ctx, callee);
2685                    }
2686                }
2687                StepEvent::Return | StepEvent::ReturnValue => {
2688                    self.call_stack.pop();
2689                    call_depth -= 1;
2690                }
2691                StepEvent::Normal | StepEvent::InterceptedCall => {}
2692            }
2693        };
2694
2695        self.call_stack.pop(); // pop the outermost function
2696        result
2697    }
2698
2699    /// Emulate a **pure** function in isolation: bind its root params positionally
2700    /// from `args` (so symbolic caller inputs can be passed an arbitrary poison
2701    /// value) and run to the first top-level `Return` *without executing it*,
2702    /// leaving the body's computed values readable via [`get_value`](Self::get_value)
2703    /// at the block returned by [`current_block`](Self::current_block).
2704    ///
2705    /// `args` must align with the root params index-for-index (the `pure_reg`
2706    /// call interface). The run is bounded by `max_steps`; exceeding it yields
2707    /// [`EmulatorErrorKind::StepBudgetExceeded`]. Intended for v1 **leaf** pure
2708    /// functions (no nested calls), so call bookkeeping is intentionally minimal.
2709    pub fn run_pure(
2710        &mut self,
2711        ctx: &Context<'_>,
2712        func: FunctionId,
2713        args: &[SizedValue],
2714        max_steps: usize,
2715    ) -> crate::Result<()> {
2716        let opts: Vec<Option<SizedValue>> = args.iter().map(|&v| Some(v)).collect();
2717        self.run_pure_partial(ctx, func, &opts, max_steps)
2718    }
2719
2720    /// Like [`run_pure`](Self::run_pure), but each positional argument may be
2721    /// [`None`] to bind that root param to **poison** (a symbolic value with
2722    /// undefined bits). Reading a poison param during emulation is a hard error
2723    /// (`PoisonRead`), so a consumer such as pure-call folding bails when the
2724    /// result actually depends on a symbolic argument, rather than computing on a
2725    /// bogus concrete value (argpromote v2, `ARGPROMOTE_REGISTERS_V2.md`).
2726    pub fn run_pure_partial(
2727        &mut self,
2728        ctx: &Context<'_>,
2729        func: FunctionId,
2730        args: &[Option<SizedValue>],
2731        max_steps: usize,
2732    ) -> crate::Result<()> {
2733        let root = FunctionBody::from_id(ctx, func)
2734            .root()
2735            .ok_or_else(|| self.make_error(ctx, EmulatorErrorKind::EmptyFunctionRoot(func)))?
2736            .id;
2737        self.block = root;
2738        self.idx = 0;
2739        self.call_stack.push(func);
2740
2741        // Bind root params positionally from `args`: a concrete `Some(v)` seeds
2742        // the param value, a `None` marks it poison (read ⇒ hard error).
2743        let param_ids: Vec<BlockParamId> = BasicBlock::from_id(ctx, root)
2744            .params()
2745            .map(|p| p.id)
2746            .collect();
2747        for (param_id, arg) in param_ids.into_iter().zip(args) {
2748            match arg {
2749                Some(value) => {
2750                    self.block_param_values.insert(param_id, *value);
2751                }
2752                None => {
2753                    self.poison_params.insert(param_id);
2754                }
2755            }
2756        }
2757
2758        self.drive_to_return(ctx, root, func, max_steps)
2759    }
2760
2761    /// Like [`run_pure`](Self::run_pure), but for a `map` body — its leading
2762    /// element param may be an **aggregate** (the `enumerate` `(index, elem)`
2763    /// lane), seeded so the body's `Extract`s on it resolve. `args` align with
2764    /// the root params index-for-index: a [`BodyArg::Scalar`] seeds a scalar
2765    /// param, a [`BodyArg::Aggregate`] seeds an `Extract`-able tuple param.
2766    /// Whether `id` is an array/list-typed value (routed through
2767    /// [`array_values`](Self::array_values) rather than scalar `insn_values`).
2768    /// Refreshes [`sequence_types`](Self::sequence_types) when the module has
2769    /// gained types since it was last answered. The probe is lock-free; only a
2770    /// genuine change pays for the locked question behind it.
2771    fn refresh_sequence_types(&mut self, ctx: &Context<'_>) {
2772        let published = ctx.shared.types.published_len();
2773        if self.sequence_types_checked_at == Some(published) {
2774            return;
2775        }
2776        self.sequence_types = ctx.shared.types.has_sequence_types();
2777        self.sequence_types_checked_at = Some(published);
2778    }
2779
2780    fn is_array_operand(&self, ctx: &Context<'_>, id: ValueId) -> bool {
2781        // Nothing in this module is sequence-typed, so no operand can be.
2782        if !self.sequence_types {
2783            return false;
2784        }
2785        match ctx.stored_type_of(id) {
2786            Some(ty) => {
2787                ctx.shared.types.array_of(ty).is_some() || ctx.shared.types.list_of(ty).is_some()
2788            }
2789            None => false,
2790        }
2791    }
2792
2793    /// Resolve an array-typed operand to its little-endian byte buffer: a `Bytes`
2794    /// blob's data, a previously-computed `array_values` entry, or a short array
2795    /// materialized as a scalar literal/result.
2796    fn resolve_array(&mut self, ctx: &Context<'_>, id: ValueId) -> Option<Vec<u8>> {
2797        match id {
2798            ValueId::Bytes(b) => Some(ctx.shared.values.bytes[b].data.clone()),
2799            ValueId::Instruction(i) => self
2800                .array_values
2801                .get(&i)
2802                .cloned()
2803                .or_else(|| self.get_value_bytes(ctx, id)),
2804            ValueId::Literal(_) => self.get_value_bytes(ctx, id),
2805            // A root/block param bound by `run_pure` (e.g. an argpromote-minted
2806            // `[i8;N]` array param): its little-endian bytes come from the bound
2807            // `SizedValue`. Defensive width check — a short buffer must fail
2808            // resolution rather than silently produce clamped `Range` slices for
2809            // callers that lack the projection guarantee.
2810            ValueId::BlockParam(_) => {
2811                let bytes = self.get_value_bytes(ctx, id)?;
2812                let ty_size = ctx
2813                    .stored_type_of(id)
2814                    .map(|ty| ctx.shared.types.size_of(ty))?;
2815                (bytes.len() == ty_size).then_some(bytes)
2816            }
2817            _ => None,
2818        }
2819    }
2820
2821    /// Evaluate one array-valued (or lane-reading) sequence intrinsic, depositing
2822    /// its result in `array_values` (arrays) or `insn_values` (`at`).
2823    fn eval_array_intrinsic(
2824        &mut self,
2825        ctx: &Context<'_>,
2826        insn_id: InstructionId,
2827        name: &str,
2828        args: &[ValueId],
2829    ) -> Result<(), EmulatorErrorKind> {
2830        match name {
2831            "iota" => {
2832                let n = self
2833                    .get_value(ctx, args[0])
2834                    .ok_or(EmulatorErrorKind::ValueError(0))?;
2835                let mut buf = Vec::with_capacity(n as usize * 8);
2836                for i in 0..n {
2837                    buf.extend_from_slice(&i.to_le_bytes());
2838                }
2839                self.array_values.insert(insn_id, buf);
2840            }
2841            "singleton" => {
2842                let buf = self
2843                    .get_value_bytes(ctx, args[0])
2844                    .ok_or(EmulatorErrorKind::ValueError(0))?;
2845                self.array_values.insert(insn_id, buf);
2846            }
2847            "concat" => {
2848                let mut a = self
2849                    .resolve_array(ctx, args[0])
2850                    .ok_or(EmulatorErrorKind::ValueError(0))?;
2851                let b = self
2852                    .resolve_array(ctx, args[1])
2853                    .ok_or(EmulatorErrorKind::ValueError(0))?;
2854                a.extend_from_slice(&b);
2855                self.array_values.insert(insn_id, a);
2856            }
2857            "insert" => {
2858                let mut buf = self
2859                    .resolve_array(ctx, args[0])
2860                    .ok_or(EmulatorErrorKind::ValueError(0))?;
2861                let i = self
2862                    .get_value(ctx, args[1])
2863                    .ok_or(EmulatorErrorKind::ValueError(0))? as usize;
2864                let vbytes = self
2865                    .get_value_bytes(ctx, args[2])
2866                    .ok_or(EmulatorErrorKind::ValueError(0))?;
2867                let esz = vbytes.len();
2868                let off = i * esz;
2869                if off + esz <= buf.len() {
2870                    buf[off..off + esz].copy_from_slice(&vbytes);
2871                }
2872                self.array_values.insert(insn_id, buf);
2873            }
2874            "enumerate" => {
2875                // `enumerate(arr) = [(index: i64, elem: T); N]`, materialized only
2876                // over a fixed array (or bounded list). A length-erased unbounded
2877                // list has no concrete count, so bail recoverably rather than
2878                // fabricate one — matching `enumerate`'s deferred `eval`.
2879                let src_ty = ctx
2880                    .stored_type_of(args[0])
2881                    .ok_or(EmulatorErrorKind::ValueError(0))?;
2882                if matches!(ctx.shared.types.list_of(src_ty), Some((_, None))) {
2883                    return Err(EmulatorErrorKind::UnsupportedIntrinsic(Box::from(
2884                        "enumerate",
2885                    )));
2886                }
2887                let in_elem = ctx
2888                    .shared
2889                    .types
2890                    .seq_elem_of(src_ty)
2891                    .ok_or(EmulatorErrorKind::ValueError(0))?;
2892                let isz = ctx.shared.types.size_of(in_elem).max(1);
2893                let buf = self
2894                    .resolve_array(ctx, args[0])
2895                    .ok_or(EmulatorErrorKind::ValueError(0))?;
2896                // The `(index, elem)` result tuple is a *structural* aggregate:
2897                // its fields are addressed by index, not byte offset, so lay them
2898                // out sequentially by field size (field 0 = i64 index, field 1 =
2899                // elem). This is the same layout `eval_scan` splits back out.
2900                let tuple_ty = ctx
2901                    .stored_type_of(ValueId::Instruction(insn_id))
2902                    .and_then(|ty| ctx.shared.types.seq_elem_of(ty))
2903                    .ok_or(EmulatorErrorKind::ValueError(0))?;
2904                let (idx_sz, elem_off) = {
2905                    let fields = ctx
2906                        .shared
2907                        .types
2908                        .aggregate_fields(tuple_ty)
2909                        .ok_or(EmulatorErrorKind::ValueError(0))?;
2910                    let [idx_f, _elem_f] = fields else {
2911                        return Err(EmulatorErrorKind::ValueError(0));
2912                    };
2913                    let idx_sz = ctx.shared.types.size_of(idx_f.type_id).min(8);
2914                    (idx_sz, idx_sz)
2915                };
2916                let tsz = idx_sz + isz;
2917                let count = buf.len() / isz;
2918                let mut out = vec![0u8; count * tsz];
2919                for i in 0..count {
2920                    let base = i * tsz;
2921                    let idx_bytes = (i as u64).to_le_bytes();
2922                    out[base..base + idx_sz].copy_from_slice(&idx_bytes[..idx_sz]);
2923                    out[base + elem_off..base + elem_off + isz]
2924                        .copy_from_slice(&buf[i * isz..i * isz + isz]);
2925                }
2926                self.array_values.insert(insn_id, out);
2927            }
2928            "at" => {
2929                let buf = self
2930                    .resolve_array(ctx, args[0])
2931                    .ok_or(EmulatorErrorKind::ValueError(0))?;
2932                let i = self
2933                    .get_value(ctx, args[1])
2934                    .ok_or(EmulatorErrorKind::ValueError(0))? as usize;
2935                let esz = ctx
2936                    .stored_type_of(ValueId::Instruction(insn_id))
2937                    .map(|ty| ctx.shared.types.size_of(ty))
2938                    .unwrap_or(8);
2939                let off = i * esz;
2940                let lane = buf
2941                    .get(off..off + esz)
2942                    .ok_or(EmulatorErrorKind::ValueError(0))?;
2943                self.insn_values
2944                    .insert(insn_id, SizedValue::from_bits(le_bits(lane), esz));
2945            }
2946            other => panic!("eval_array_intrinsic called on non-array intrinsic `{other}`"),
2947        }
2948        Ok(())
2949    }
2950
2951    /// Thread a scan's accumulator across every lane of its source array, running
2952    /// the pure binary body `(acc, elem) -> acc'` once per element in a fresh
2953    /// nested emulator, and store the concatenated per-step accumulators as the
2954    /// result array buffer.
2955    fn eval_scan(
2956        &mut self,
2957        ctx: &Context<'_>,
2958        insn_id: InstructionId,
2959        scan: &Scan,
2960    ) -> Result<(), EmulatorErrorKind> {
2961        const SCAN_STEP_BUDGET: usize = 100_000;
2962
2963        let src = self
2964            .resolve_array(ctx, scan.src.qualify(insn_id.func))
2965            .ok_or(EmulatorErrorKind::ValueError(0))?;
2966        // Element sizes come from the operand/result element *types* (which are
2967        // known even for a length-erased `[T;*]` result); the lane count is the
2968        // source buffer's length in input elements. This handles both a folded
2969        // fixed-array source and a symbolic-length `iota`.
2970        let in_elem = ctx
2971            .stored_type_of(scan.src.qualify(insn_id.func))
2972            .and_then(|ty| ctx.shared.types.seq_elem_of(ty))
2973            .ok_or(EmulatorErrorKind::ValueError(0))?;
2974        let isz = ctx.shared.types.size_of(in_elem).max(1);
2975        let out_elem = ctx
2976            .stored_type_of(ValueId::Instruction(insn_id))
2977            .and_then(|ty| ctx.shared.types.seq_elem_of(ty))
2978            .ok_or(EmulatorErrorKind::ValueError(0))?;
2979        let osz = ctx.shared.types.size_of(out_elem);
2980        let count = src.len() / isz;
2981        let body = require_real_callee(scan.body)?;
2982        if count == 0 {
2983            self.array_values.insert(insn_id, Vec::new());
2984            return Ok(());
2985        }
2986
2987        // Loop-invariant captures, resolved once as scalars.
2988        let capture_args: Vec<BodyArg> = scan
2989            .captures
2990            .iter()
2991            .map(|&c| {
2992                let v = self
2993                    .get_value(ctx, c.qualify(insn_id.func))
2994                    .ok_or(EmulatorErrorKind::ValueError(0))?;
2995                let sz = ctx
2996                    .stored_type_of(c.qualify(insn_id.func))
2997                    .map(|ty| ctx.shared.types.size_of(ty))
2998                    .unwrap_or(8);
2999                Ok(BodyArg::Scalar(SizedValue::new(v, sz)))
3000            })
3001            .collect::<Result<_, EmulatorErrorKind>>()?;
3002
3003        let init = self
3004            .get_value(ctx, scan.init.qualify(insn_id.func))
3005            .ok_or(EmulatorErrorKind::ValueError(0))?;
3006        let mut acc = SizedValue::new(init, osz);
3007
3008        let root = FunctionBody::from_id(ctx, body)
3009            .root()
3010            .ok_or(EmulatorErrorKind::EmptyFunctionRoot(body))?
3011            .id;
3012
3013        // When the source element is a tuple (the `enumerate` `(index, elem)`
3014        // lane), each lane is passed as an aggregate so the body's `Extract`s
3015        // resolve; a plain scalar element is passed as-is. The tuple is a
3016        // structural aggregate (fields addressed by index), so its bytes are laid
3017        // out sequentially by field size — the same layout `enumerate` writes.
3018        let elem_fields: Option<Vec<(usize, usize)>> =
3019            ctx.shared.types.aggregate_fields(in_elem).map(|fs| {
3020                let mut off = 0;
3021                fs.iter()
3022                    .map(|f| {
3023                        let sz = ctx.shared.types.size_of(f.type_id);
3024                        let field = (off, sz);
3025                        off += sz;
3026                        field
3027                    })
3028                    .collect()
3029            });
3030
3031        let mut out = Vec::with_capacity(count * osz);
3032        for k in 0..count {
3033            let elem = &src[k * isz..k * isz + isz];
3034            let elem_arg = match &elem_fields {
3035                Some(fields) => BodyArg::Aggregate(
3036                    fields
3037                        .iter()
3038                        .map(|&(off, sz)| SizedValue::from_bits(le_bits(&elem[off..off + sz]), sz))
3039                        .collect(),
3040                ),
3041                None => BodyArg::Scalar(SizedValue::from_bits(le_bits(elem), isz)),
3042            };
3043            let mut body_args = Vec::with_capacity(2 + capture_args.len());
3044            body_args.push(BodyArg::Scalar(acc));
3045            body_args.push(elem_arg);
3046            body_args.extend(capture_args.iter().cloned());
3047
3048            let mut emu = StandaloneEmulator::new(root);
3049            emu.run_map_body(ctx, body, &body_args, SCAN_STEP_BUDGET)
3050                .map_err(|e| e.kind)?;
3051            let ret = body_return_value(ctx, emu.current_block())
3052                .ok_or(EmulatorErrorKind::ValueError(0))?;
3053            let mut lane = emu
3054                .get_value_bytes(ctx, ret)
3055                .ok_or(EmulatorErrorKind::ValueError(0))?;
3056            lane.resize(osz, 0);
3057            acc = SizedValue::from_bits(le_bits(&lane), osz);
3058            out.extend_from_slice(&lane);
3059        }
3060        self.array_values.insert(insn_id, out);
3061        Ok(())
3062    }
3063
3064    /// Total map: run the (pure) unary body once per source element and
3065    /// materialize the results as an array buffer. Mirrors [`Self::eval_scan`]
3066    /// without the threaded accumulator.
3067    fn eval_map(
3068        &mut self,
3069        ctx: &Context<'_>,
3070        insn_id: InstructionId,
3071        map: &qcode::value::insn::Map,
3072    ) -> Result<(), EmulatorErrorKind> {
3073        const MAP_STEP_BUDGET: usize = 100_000;
3074
3075        let src = self
3076            .resolve_array(ctx, map.src.qualify(insn_id.func))
3077            .ok_or(EmulatorErrorKind::ValueError(0))?;
3078        let in_elem = ctx
3079            .stored_type_of(map.src.qualify(insn_id.func))
3080            .and_then(|ty| ctx.shared.types.seq_elem_of(ty))
3081            .ok_or(EmulatorErrorKind::ValueError(0))?;
3082        let isz = ctx.shared.types.size_of(in_elem).max(1);
3083        let out_elem = ctx
3084            .stored_type_of(ValueId::Instruction(insn_id))
3085            .and_then(|ty| ctx.shared.types.seq_elem_of(ty))
3086            .ok_or(EmulatorErrorKind::ValueError(0))?;
3087        let osz = ctx.shared.types.size_of(out_elem);
3088        let count = src.len() / isz;
3089        let body = require_real_callee(map.body)?;
3090
3091        let capture_args: Vec<BodyArg> = map
3092            .captures
3093            .iter()
3094            .map(|&c| {
3095                let v = self
3096                    .get_value(ctx, c.qualify(insn_id.func))
3097                    .ok_or(EmulatorErrorKind::ValueError(0))?;
3098                let sz = ctx
3099                    .stored_type_of(c.qualify(insn_id.func))
3100                    .map(|ty| ctx.shared.types.size_of(ty))
3101                    .unwrap_or(8);
3102                Ok(BodyArg::Scalar(SizedValue::new(v, sz)))
3103            })
3104            .collect::<Result<_, EmulatorErrorKind>>()?;
3105
3106        // The element may be an `enumerate` tuple `(index, elem)`; pass it as an
3107        // aggregate so the body's `Extract`s resolve (same layout as `eval_scan`).
3108        let elem_fields: Option<Vec<(usize, usize)>> =
3109            ctx.shared.types.aggregate_fields(in_elem).map(|fs| {
3110                let mut off = 0;
3111                fs.iter()
3112                    .map(|f| {
3113                        let sz = ctx.shared.types.size_of(f.type_id);
3114                        let field = (off, sz);
3115                        off += sz;
3116                        field
3117                    })
3118                    .collect()
3119            });
3120
3121        let mut out = Vec::with_capacity(count * osz);
3122        for k in 0..count {
3123            let elem = &src[k * isz..k * isz + isz];
3124            let elem_arg = match &elem_fields {
3125                Some(fields) => BodyArg::Aggregate(
3126                    fields
3127                        .iter()
3128                        .map(|&(off, sz)| SizedValue::from_bits(le_bits(&elem[off..off + sz]), sz))
3129                        .collect(),
3130                ),
3131                None => BodyArg::Scalar(SizedValue::from_bits(le_bits(elem), isz)),
3132            };
3133            let mut body_args = Vec::with_capacity(1 + capture_args.len());
3134            body_args.push(elem_arg);
3135            body_args.extend(capture_args.iter().cloned());
3136
3137            let mut emu = StandaloneEmulator::new(
3138                FunctionBody::from_id(ctx, body)
3139                    .root()
3140                    .ok_or(EmulatorErrorKind::EmptyFunctionRoot(body))?
3141                    .id,
3142            );
3143            emu.run_map_body(ctx, body, &body_args, MAP_STEP_BUDGET)
3144                .map_err(|e| e.kind)?;
3145            let ret = body_return_value(ctx, emu.current_block())
3146                .ok_or(EmulatorErrorKind::ValueError(0))?;
3147            let mut lane = emu
3148                .get_value_bytes(ctx, ret)
3149                .ok_or(EmulatorErrorKind::ValueError(0))?;
3150            lane.resize(osz, 0);
3151            out.extend_from_slice(&lane);
3152        }
3153        self.array_values.insert(insn_id, out);
3154        Ok(())
3155    }
3156
3157    pub fn run_map_body(
3158        &mut self,
3159        ctx: &Context<'_>,
3160        func: FunctionId,
3161        args: &[BodyArg],
3162        max_steps: usize,
3163    ) -> crate::Result<()> {
3164        let root = FunctionBody::from_id(ctx, func)
3165            .root()
3166            .ok_or_else(|| self.make_error(ctx, EmulatorErrorKind::EmptyFunctionRoot(func)))?
3167            .id;
3168        self.block = root;
3169        self.idx = 0;
3170        self.call_stack.push(func);
3171
3172        let param_ids: Vec<BlockParamId> = BasicBlock::from_id(ctx, root)
3173            .params()
3174            .map(|p| p.id)
3175            .collect();
3176        for (param_id, arg) in param_ids.into_iter().zip(args) {
3177            match arg {
3178                BodyArg::Scalar(v) => {
3179                    self.block_param_values.insert(param_id, *v);
3180                }
3181                BodyArg::Aggregate(fields) => {
3182                    self.block_param_aggregates.insert(param_id, fields.clone());
3183                }
3184            }
3185        }
3186
3187        self.drive_to_return(ctx, root, func, max_steps)
3188    }
3189
3190    /// Shared drive loop for the bounded `run_pure`/`run_map_body` entry points:
3191    /// run from the current position to the first top-level value or machine
3192    /// `Return` (without
3193    /// executing it), popping the call frame. Params must already be seeded and
3194    /// `func` pushed onto the call stack.
3195    fn drive_to_return(
3196        &mut self,
3197        ctx: &Context<'_>,
3198        _root: BlockId,
3199        _func: FunctionId,
3200        max_steps: usize,
3201    ) -> crate::Result<()> {
3202        let mut steps = 0usize;
3203        let result = loop {
3204            let insn_ids = BasicBlock::from_id(ctx, self.block)
3205                .instruction_ids()
3206                .to_vec();
3207            // A well-formed block ends in a terminator, so `self.idx` should always
3208            // point at a real instruction. Lifting can leave degenerate empty blocks
3209            // behind, though; bail with a recoverable error instead of indexing out
3210            // of bounds (which would crash the whole analysis via GVN pure-call
3211            // folding). `make_error` can't be used here — it also indexes the block.
3212            if self.idx >= insn_ids.len() {
3213                break Err(self.make_empty_block_error(ctx));
3214            }
3215            let insn = InstructionRef::from_id(ctx, insn_ids[self.idx]);
3216            if matches!(
3217                insn.mnemonic(),
3218                Mnemonic::Return(_) | Mnemonic::ReturnValue(_)
3219            ) {
3220                break Ok(());
3221            }
3222            steps += 1;
3223            if steps > max_steps {
3224                break Err(self.make_error(ctx, EmulatorErrorKind::StepBudgetExceeded(max_steps)));
3225            }
3226            if let Err(e) = self.step(ctx) {
3227                break Err(e);
3228            }
3229        };
3230
3231        self.call_stack.pop();
3232        result
3233    }
3234}
3235
3236fn lambda_return_value(ctx: &Context<'_>, block: BlockId) -> Option<ValueId> {
3237    let last = BasicBlock::from_id(ctx, block).iter().last()?;
3238    match last.mnemonic() {
3239        Mnemonic::ReturnValue(ret) => Some(ret.value.qualify(last.id.func)),
3240        _ => None,
3241    }
3242}
3243
3244/// The value a scan/map body block returns — via either a machine `Return` (the
3245/// outlined-body form) or a lambda `ReturnValue`. `None` if the block does not
3246/// end in a value-carrying return.
3247fn body_return_value(ctx: &Context<'_>, block: BlockId) -> Option<ValueId> {
3248    let last = BasicBlock::from_id(ctx, block).iter().last()?;
3249    match last.mnemonic() {
3250        Mnemonic::Return(ret) => ret.value.map(|v| v.qualify(last.id.func)),
3251        Mnemonic::ReturnValue(ret) => Some(ret.value.qualify(last.id.func)),
3252        _ => None,
3253    }
3254}
3255
3256/// The array-valued (or lane-reading) sequence intrinsics the emulator evaluates
3257/// over its [`array_values`](StandaloneEmulator::array_values) domain rather than
3258/// the scalar interpreter.
3259fn is_array_intrinsic(name: &str) -> bool {
3260    matches!(
3261        name,
3262        "iota" | "singleton" | "concat" | "insert" | "at" | "enumerate"
3263    )
3264}
3265
3266/// Fold a little-endian byte slice (≤ 16 bytes) into a `u128`.
3267fn le_bits(bytes: &[u8]) -> u128 {
3268    let mut buf = [0u8; 16];
3269    let n = bytes.len().min(16);
3270    buf[..n].copy_from_slice(&bytes[..n]);
3271    u128::from_le_bytes(buf)
3272}
3273
3274/// A positional argument to a `map` body for [`run_map_body`](StandaloneEmulator::run_map_body):
3275/// a scalar param value, or the field vector of an aggregate (tuple) param.
3276#[derive(Debug, Clone)]
3277pub enum BodyArg {
3278    Scalar(SizedValue),
3279    Aggregate(Vec<SizedValue>),
3280}
3281
3282/// Private helper that pairs `&mut StandaloneEmulator` fields with `&Context<'_>`
3283/// so the default `Interpreter::interpret()` impl can be reused.
3284struct TempInterpreter<'a, 'ctx, M> {
3285    memory: &'a mut M,
3286    literals: &'a mut LiteralCache,
3287    insn_values: &'a mut InsnValues,
3288    block_param_values: &'a mut FxHashMap<BlockParamId, SizedValue>,
3289    poison_params: &'a FxHashSet<BlockParamId>,
3290    ctx: &'ctx Context<'ctx>,
3291}
3292
3293impl<'ctx, M: EmulatorMemory> Interpreter for TempInterpreter<'_, 'ctx, M> {
3294    type V = SizedValue;
3295    type M = M;
3296
3297    fn memory(&mut self) -> &mut Self::M {
3298        self.memory
3299    }
3300
3301    fn ctx(&self) -> &Context<'_> {
3302        self.ctx
3303    }
3304
3305    fn get_value(&mut self, id: ValueId) -> Result<Self::V, EmulatorErrorKind> {
3306        // Taken before `ValueRef::new`, which would resolve the literal through
3307        // the interner's lock.
3308        if let ValueId::Literal(literal) = id {
3309            return Ok(self.literals.get(self.ctx, literal));
3310        }
3311        match ValueRef::new(id, self.ctx) {
3312            ValueRef::Literal(literal) => Ok(SizedValue::new(literal.value(), literal.size())),
3313            // Byte blobs are wider than the emulator's scalar SizedValue.
3314            ValueRef::Bytes(_) => Err(EmulatorErrorKind::ValueError(0)),
3315            ValueRef::Instruction(insn) => self
3316                .insn_values
3317                .get(&insn.id)
3318                .copied()
3319                .ok_or(EmulatorErrorKind::ValueError(0)),
3320            ValueRef::Varnode(varnode) => Ok(SizedValue::new(varnode.address() as u64, 8)),
3321            ValueRef::Temp(temp) => Ok(SizedValue::new(temp.address() as u64, 8)),
3322            ValueRef::BasicBlock(_) => panic!("Cannot get value of a block"),
3323            ValueRef::BlockParam(param) => {
3324                if self.poison_params.contains(&param.id) {
3325                    return Err(EmulatorErrorKind::PoisonRead);
3326                }
3327                self.block_param_values
3328                    .get(&param.id)
3329                    .copied()
3330                    .ok_or(EmulatorErrorKind::ValueError(0))
3331            }
3332            ValueRef::Function(f) => f
3333                .address()
3334                .map(SizedValue::from_u64)
3335                .ok_or(EmulatorErrorKind::EmptyFunctionRoot(f.id)),
3336            // Poison has undefined bits: demanding its concrete value is a hard
3337            // error (propagating it as an unread operand never reaches here).
3338            ValueRef::Poison(_) => Err(EmulatorErrorKind::PoisonRead),
3339        }
3340    }
3341}
3342
3343pub struct Emulator<'ctx, M = EmulatedMemory> {
3344    inner: StandaloneEmulator<M>,
3345    ctx: &'ctx Context<'ctx>,
3346}
3347
3348impl<'ctx> Emulator<'ctx, EmulatedMemory> {
3349    /// Builds an emulator over the default flat memory.
3350    pub fn new(ctx: &'ctx Context<'ctx>, entry: BlockId) -> Self {
3351        Self::new_in(ctx, entry)
3352    }
3353
3354    pub fn from_function(ctx: &'ctx Context<'ctx>, func: FunctionId) -> Self {
3355        Self::from_function_in(ctx, func)
3356    }
3357
3358    pub fn from_block(ctx: &'ctx Context<'ctx>, block: BlockId) -> Self {
3359        Self::new_in(ctx, block)
3360    }
3361
3362    pub fn from_address(ctx: &'ctx Context<'ctx>, addr: u64) -> Self {
3363        Self::from_address_in(ctx, addr)
3364    }
3365}
3366
3367impl<'ctx, M: EmulatorMemory + Default> Emulator<'ctx, M> {
3368    /// Builds an emulator over an explicit memory backend.
3369    pub fn new_in(ctx: &'ctx Context<'ctx>, entry: BlockId) -> Self {
3370        let mut inner =
3371            StandaloneEmulator::<M>::with_address_index(entry, AddressIndex::analyze(ctx));
3372        inner.memory.configure_spaces(ctx);
3373        Self { inner, ctx }
3374    }
3375
3376    pub fn set_instruction_hook(
3377        &mut self,
3378        hook: impl Fn(&InstructionRef<'_, '_>, &StandaloneEmulator<M>) + Send + Sync + 'static,
3379    ) {
3380        self.inner.instruction_hook = Some(Box::new(hook));
3381    }
3382
3383    pub fn set_call_interceptor(
3384        &mut self,
3385        interceptor: impl FnMut(
3386            &Context<'_>,
3387            &mut StandaloneEmulator<M>,
3388            &CallSite,
3389        ) -> Result<CallInterception, Box<str>>
3390        + Send
3391        + Sync
3392        + 'static,
3393    ) {
3394        self.inner.set_call_interceptor(interceptor);
3395    }
3396
3397    pub fn clear_call_interceptor(&mut self) {
3398        self.inner.clear_call_interceptor();
3399    }
3400
3401    /// Builds an emulator at a function's root block, over an explicit backend.
3402    pub fn from_function_in(ctx: &'ctx Context<'ctx>, func: FunctionId) -> Self {
3403        let entry = FunctionBody::from_id(ctx, func)
3404            .root()
3405            .expect("Cannot create emulator for function with empty root block")
3406            .id;
3407        Self::new_in(ctx, entry)
3408    }
3409
3410    /// Builds an emulator positioned at `addr`, over an explicit backend.
3411    pub fn from_address_in(ctx: &'ctx Context<'ctx>, addr: u64) -> Self {
3412        Self {
3413            inner: StandaloneEmulator::<M>::from_address_in(ctx, addr),
3414            ctx,
3415        }
3416    }
3417
3418    /// Debugging method to view a value at a given address
3419    pub fn inspect_memory(&mut self, space: SpaceId, addr: u64, size: usize) -> Option<Vec<u8>> {
3420        self.inner
3421            .memory
3422            .read_bytes(MemorySpaceId::Shared(space), addr, size)
3423            .ok()
3424    }
3425
3426    /// Sets the value of a varnode
3427    pub fn set_varnode(&mut self, id: VarnodeId, value: u64) -> Result<(), EmulatorErrorKind> {
3428        self.inner.set_varnode(self.ctx, id, value)
3429    }
3430
3431    /// Sets the value of a varnode using full 128-bit precision.
3432    pub fn set_varnode_u128(
3433        &mut self,
3434        id: VarnodeId,
3435        value: u128,
3436    ) -> Result<(), EmulatorErrorKind> {
3437        self.inner.set_varnode_u128(self.ctx, id, value)
3438    }
3439
3440    /// Sets the value of a register
3441    pub fn set_register(&mut self, id: RegisterId, value: u64) -> Result<(), EmulatorErrorKind> {
3442        let id = self.ctx.get_register(id).id;
3443        self.set_varnode(id, value)
3444    }
3445
3446    /// Writes a value to memory
3447    pub fn write_memory(
3448        &mut self,
3449        space: SpaceId,
3450        addr: u64,
3451        value: &[u8],
3452    ) -> Result<(), EmulatorErrorKind> {
3453        self.inner.write_memory(self.ctx, space, addr, value)
3454    }
3455
3456    pub fn read_memory(
3457        &mut self,
3458        space: SpaceId,
3459        addr: u64,
3460        size: usize,
3461    ) -> Result<Vec<u8>, EmulatorErrorKind> {
3462        self.inner.read_memory(self.ctx, space, addr, size)
3463    }
3464
3465    /// Sets the value of a register using full 128-bit precision.
3466    pub fn set_register_u128(
3467        &mut self,
3468        id: RegisterId,
3469        value: u128,
3470    ) -> Result<(), EmulatorErrorKind> {
3471        let id = self.ctx.get_register(id).id;
3472        self.set_varnode_u128(id, value)
3473    }
3474
3475    pub fn read_varnode(&mut self, id: VarnodeId) -> Option<u64> {
3476        self.inner.read_varnode(self.ctx, id)
3477    }
3478
3479    pub fn read_varnode_u128(&mut self, id: VarnodeId) -> Option<u128> {
3480        self.inner.read_varnode_u128(self.ctx, id)
3481    }
3482
3483    pub fn read_register(&mut self, id: RegisterId) -> Option<u64> {
3484        let id = self.ctx.get_register(id).id;
3485        self.read_varnode(id)
3486    }
3487
3488    pub fn read_register_u128(&mut self, id: RegisterId) -> Option<u128> {
3489        let id = self.ctx.get_register(id).id;
3490        self.read_varnode_u128(id)
3491    }
3492
3493    /// Writes a single 64-bit lane of a wide register.
3494    /// Lane `n` covers bytes `[n*8 .. n*8+8]` relative to the register's base address.
3495    pub fn set_register_lane(&mut self, id: RegisterId, lane: usize, value: u64) {
3496        let (space_id, base_addr) = {
3497            let vn = self.ctx.get_register(id);
3498            (vn.space().id, vn.address() as u64)
3499        };
3500        let base = base_addr + (lane as u64) * 8;
3501        let _ = self
3502            .inner
3503            .memory
3504            .write_bytes(space_id.into(), base, &value.to_le_bytes());
3505    }
3506
3507    /// Reads a single 64-bit lane of a wide register (little-endian).
3508    /// Lane `n` covers bytes `[n*8 .. n*8+8]` relative to the register's base address.
3509    pub fn read_register_lane(&mut self, id: RegisterId, lane: usize) -> u64 {
3510        let (space_id, base_addr) = {
3511            let vn = self.ctx.get_register(id);
3512            (vn.space().id, vn.address() as u64)
3513        };
3514        let base = base_addr + (lane as u64) * 8;
3515        // An unwritten lane reads as zero: register space is architectural
3516        // state that exists whether or not a harness has seeded it.
3517        let bytes = self
3518            .inner
3519            .memory
3520            .read_bytes(space_id.into(), base, 8)
3521            .unwrap_or_else(|_| vec![0; 8]);
3522        u64::from_le_bytes(bytes.try_into().expect("read_bytes returns 8 bytes"))
3523    }
3524
3525    /// Gets the current block
3526    pub fn block(&self) -> BlockRef<'ctx, 'ctx> {
3527        BasicBlock::from_id(self.ctx, self.inner.block)
3528    }
3529
3530    /// Gets the current instruction
3531    pub fn insn(&self) -> Option<InstructionRef<'ctx, 'ctx>> {
3532        let block = self.block();
3533        if self.inner.idx >= block.instruction_count() {
3534            None
3535        } else {
3536            let id = block.instruction_ids()[self.inner.idx];
3537            Some(InstructionRef::from_id(self.ctx, id))
3538        }
3539    }
3540
3541    /// Executes a single pcode instruction
3542    pub fn step(&mut self) -> crate::Result<()> {
3543        self.inner.step(self.ctx)
3544    }
3545
3546    /// Executes instructions until the end of the current block
3547    pub fn run_block(&mut self) -> crate::Result<()> {
3548        self.inner.run_block(self.ctx)
3549    }
3550
3551    /// Runs blocks until the current block starts at `addr`.
3552    pub fn run_until(&mut self, addr: u64) -> crate::Result<()> {
3553        self.inner.run_until(self.ctx, addr)
3554    }
3555
3556    /// Runs the given function from its root block, stopping before the outermost `Return`.
3557    pub fn run_function(&mut self, func: FunctionId) -> crate::Result<()> {
3558        self.inner.run_function(self.ctx, func)
3559    }
3560
3561    /// Returns the current emulator call stack (outermost function first).
3562    /// Only populated during `run_function` execution.
3563    pub fn call_stack(&self) -> &[FunctionId] {
3564        &self.inner.call_stack
3565    }
3566}
3567
3568impl<'ctx, M: EmulatorMemory> Interpreter for Emulator<'ctx, M> {
3569    type V = SizedValue;
3570    type M = M;
3571
3572    fn memory(&mut self) -> &mut Self::M {
3573        &mut self.inner.memory
3574    }
3575
3576    fn ctx(&self) -> &Context<'ctx> {
3577        self.ctx
3578    }
3579
3580    fn get_value(&mut self, id: ValueId) -> Result<Self::V, EmulatorErrorKind> {
3581        if let ValueId::Literal(literal) = id {
3582            let value = self.inner.literal_cache.get(self.ctx, literal);
3583            return Ok(value);
3584        }
3585        match ValueRef::new(id, self.ctx) {
3586            ValueRef::Literal(literal) => Ok(SizedValue::new(literal.value(), literal.size())),
3587            // Byte blobs are wider than the emulator's scalar SizedValue.
3588            ValueRef::Bytes(_) => Err(EmulatorErrorKind::ValueError(0)),
3589            ValueRef::Instruction(insn) => self
3590                .inner
3591                .insn_values
3592                .get(&insn.id)
3593                .copied()
3594                .ok_or(EmulatorErrorKind::ValueError(0)),
3595            ValueRef::Varnode(varnode) => Ok(SizedValue::new(varnode.address() as u64, 8)),
3596            ValueRef::Temp(temp) => Ok(SizedValue::new(temp.address() as u64, 8)),
3597            ValueRef::BasicBlock(_) => panic!("Cannot get value of a block"),
3598            ValueRef::BlockParam(param) => self
3599                .inner
3600                .block_param_values
3601                .get(&param.id)
3602                .copied()
3603                .ok_or(EmulatorErrorKind::ValueError(0)),
3604            ValueRef::Function(f) => f
3605                .address()
3606                .map(SizedValue::from_u64)
3607                .ok_or(EmulatorErrorKind::EmptyFunctionRoot(f.id)),
3608            // Poison has undefined bits: demanding its concrete value is a hard
3609            // error (propagating it as an unread operand never reaches here).
3610            ValueRef::Poison(_) => Err(EmulatorErrorKind::PoisonRead),
3611        }
3612    }
3613}
3614
3615#[cfg(test)]
3616mod tests {
3617    use super::*;
3618    use qcode::context::Context;
3619    use qcode::space::{Space, SpaceType};
3620    use qcode::value::QCodeMut;
3621    use qcode::value::TempSpace;
3622    use std::sync::{Arc, Mutex};
3623    use wazabin_qcode_macro::qcode;
3624
3625    /// Reading a poison value is a hard error (`PoisonRead`); propagating it as
3626    /// an unread operand never reaches `get_value`.
3627    #[test]
3628    fn reading_poison_is_a_hard_error() {
3629        let mut ctx = Context::new();
3630        let func = ctx.anon_function();
3631        let block = BasicBlock::make(&mut ctx, func).with_address(0x1000).id;
3632        let i32_ty = ctx.shared.types.get_or_make_int(4);
3633        let poison = ctx.get_poison(i32_ty);
3634        let mut emu = StandaloneEmulator::new(block);
3635        let mut tmp = TempInterpreter {
3636            memory: &mut emu.memory,
3637            literals: &mut emu.literal_cache,
3638            insn_values: &mut emu.insn_values,
3639            block_param_values: &mut emu.block_param_values,
3640            poison_params: &emu.poison_params,
3641            ctx: &ctx,
3642        };
3643        assert!(matches!(
3644            tmp.get_value(poison),
3645            Err(EmulatorErrorKind::PoisonRead)
3646        ));
3647    }
3648
3649    #[test]
3650    fn minted_callee_is_not_executable() {
3651        assert!(matches!(
3652            require_real_callee(Callee::Minted(7)),
3653            Err(EmulatorErrorKind::UnresolvedMintedCallee(7))
3654        ));
3655    }
3656
3657    #[test]
3658    fn sized_value_masks_to_declared_width() {
3659        let value = SizedValue::new(0x1234, 1);
3660        assert_eq!(value.size().unwrap(), 1);
3661        assert_eq!(value.value().unwrap(), 0x34);
3662    }
3663
3664    #[test]
3665    fn from_address_resolves_function_entry_to_root() {
3666        let mut ctx = Context::new();
3667        let function = FunctionBody::make_at_addr(&mut ctx, 0x1000, None).id;
3668        let root = BasicBlock::make(&mut ctx, function).with_address(0x1000).id;
3669
3670        let emulator = StandaloneEmulator::from_address(&ctx, 0x1000);
3671
3672        assert_eq!(emulator.current_block(), root);
3673        assert!(emulator.address_index.is_some());
3674    }
3675
3676    #[test]
3677    fn standalone_address_lookup_builds_one_lazy_snapshot() {
3678        let mut ctx = Context::new();
3679        let function = ctx.anon_function();
3680        let block = BasicBlock::make(&mut ctx, function).with_address(0x2000).id;
3681        ctx.block_mut(block).extra_addresses.push(0x2001);
3682        let mut emulator = StandaloneEmulator::new(block);
3683
3684        assert!(emulator.address_index.is_none());
3685        assert_eq!(emulator.block_at(&ctx, 0x2001), Some(block));
3686        assert!(emulator.address_index.is_some());
3687        assert_eq!(emulator.block_at(&ctx, 0x2000), Some(block));
3688    }
3689
3690    #[test]
3691    fn int_add_wraps_by_width_and_sets_carry() {
3692        let lhs = SizedValue::new(0xff, 1);
3693        let rhs = SizedValue::new(0x01, 1);
3694
3695        let sum = lhs.int_add(&rhs).unwrap();
3696        let carry = lhs.carry(&rhs).unwrap();
3697
3698        assert_eq!(sum.value().unwrap(), 0x00);
3699        assert_eq!(sum.size().unwrap(), 1);
3700        assert_eq!(carry.value().unwrap(), 1);
3701        assert_eq!(carry.size().unwrap(), 1);
3702    }
3703
3704    #[test]
3705    fn branch_args_bind_block_params() {
3706        let mut ctx = Context::new();
3707        qcode!(
3708            ctx,
3709            "
3710            <src>
3711                goto <dst @x=0x2>;
3712            <dst @x>
3713                %sum = i64 @x + 0x3;
3714                goto <0x1001>;
3715            "
3716        );
3717
3718        let mut emu = StandaloneEmulator::new(src);
3719        emu.step(&ctx).expect("branch binds block params");
3720        emu.step(&ctx).expect("destination uses block param");
3721
3722        assert_eq!(emu.get_value(&ctx, sum.into()), Some(5));
3723    }
3724
3725    #[test]
3726    fn apply_evaluates_recursive_lambda_value_return() {
3727        let mut ctx = Context::new();
3728        qcode!(
3729            ctx,
3730            "
3731            lambda dec:
3732            <entry @n:i64>
3733                %is_zero = @n == 0;
3734                if %is_zero goto <done @r=@n> else goto <step @m=@n>;
3735
3736            <step @m:i64>
3737                %next = @m - 1;
3738                %out = apply dec(%next);
3739                return %out;
3740
3741            <done @r:i64>
3742                return @r;
3743            "
3744        );
3745
3746        let dec = qcode::value::FunctionBody::from_name(&ctx, "dec")
3747            .expect("lambda exists")
3748            .id;
3749        let root = qcode::value::FunctionBody::from_id(&ctx, dec)
3750            .root()
3751            .expect("lambda has root")
3752            .id;
3753        let mut emu = StandaloneEmulator::new(root);
3754        emu.run_pure(&ctx, dec, &[SizedValue::new(3, 8)], 1000)
3755            .expect("recursive lambda evaluates");
3756        let ret = lambda_return_value(&ctx, emu.current_block()).expect("lambda returned a value");
3757        assert_eq!(emu.get_value(&ctx, ret), Some(0));
3758    }
3759
3760    /// Test setup is the publication barrier: `result_type` only reads, so the
3761    /// types an intrinsic resolves to must exist before it is pushed.
3762    fn publish_iota_result(ctx: &mut Context) {
3763        use qcode::types::TypeRequest;
3764        let i64_ty = ctx.shared.types.get_or_make_int(8);
3765        ctx.shared
3766            .types
3767            .create_requested_types(&[TypeRequest::list(i64_ty, None)]);
3768    }
3769
3770    /// `map @f arr` runs the pure unary body over every element and materializes
3771    /// the result buffer. `f(x) = x * 3`, `arr = [1, 2, 3, 4]` ⇒ `[3, 6, 9, 12]`.
3772    #[test]
3773    fn map_over_array_is_emulated() {
3774        let mut ctx = Context::new();
3775        publish_iota_result(&mut ctx);
3776        qcode!(
3777            ctx,
3778            "
3779            lambda triple:
3780            <tb @x:i64>
3781                %r = @x * 3;
3782                return %r;
3783            fn main:
3784            <me>
3785                %src = $iota(i64 0x4);
3786                %m = triple <$> %src;
3787                goto <0x1001>;
3788            "
3789        );
3790
3791        let mut emu = StandaloneEmulator::new(me);
3792        emu.step(&ctx).expect("iota");
3793        emu.step(&ctx).expect("map");
3794
3795        let buf = emu.array_values.get(&m).expect("map produced an array");
3796        let words: Vec<u64> = buf
3797            .as_chunks::<8>()
3798            .0
3799            .iter()
3800            .map(|&c| u64::from_le_bytes(c))
3801            .collect();
3802        // iota(4) = [0,1,2,3]; triple ⇒ [0, 3, 6, 9].
3803        assert_eq!(words, vec![0, 3, 6, 9]);
3804    }
3805
3806    /// End-to-end array emulation: `scanl @step init (iota n)` threads the
3807    /// accumulator through the driver array and materializes the result buffer.
3808    /// `step(acc, x) = acc + x`, `init = 10`, `iota(3) = [0, 1, 2]` ⇒
3809    /// `[10, 11, 13]` (out[i] = acc after adding x_i, prefix-fold style).
3810    #[test]
3811    fn scan_over_iota_is_emulated() {
3812        let mut ctx = Context::new();
3813        publish_iota_result(&mut ctx);
3814        qcode!(
3815            ctx,
3816            "
3817            lambda step:
3818            <sb @acc:i64 @x:i64>
3819                %r = @acc + @x;
3820                return %r;
3821            fn main:
3822            <me>
3823                %src = $iota(i64 0x3);
3824                %s = scanl @step i64 0xa %src;
3825                goto <0x1001>;
3826            "
3827        );
3828
3829        let mut emu = StandaloneEmulator::new(me);
3830        // Step: iota, then scan (do not execute the terminating goto).
3831        emu.step(&ctx).expect("iota");
3832        emu.step(&ctx).expect("scan");
3833
3834        let buf = emu.array_values.get(&s).expect("scan produced an array");
3835        let words: Vec<u64> = buf
3836            .as_chunks::<8>()
3837            .0
3838            .iter()
3839            .map(|&c| u64::from_le_bytes(c))
3840            .collect();
3841        assert_eq!(words, vec![10, 11, 13]);
3842    }
3843
3844    #[test]
3845    fn enumerate_over_array_is_emulated() {
3846        use qcode::value::{FunctionBody, ValueId, insn::IntrinsicId};
3847
3848        let mut ctx = Context::new();
3849        let f = FunctionBody::make(&mut ctx, "f".into()).unwrap().id;
3850        let entry = ctx.get_or_make_block(0x1000, f);
3851        {
3852            let mut fm = FunctionBody::from_id_mut(&mut ctx, f);
3853            fm.set_root(entry).unwrap();
3854            fm.add_block(entry);
3855        }
3856        // A fixed `[i64; 4]` source `[10, 20, 30, 40]`.
3857        let i64_ty = ctx.shared.types.get_or_make_int(8);
3858        let arr_ty = ctx.shared.types.get_or_make_array(i64_ty, 4);
3859        let data: Vec<u8> = [10u64, 20, 30, 40]
3860            .iter()
3861            .flat_map(|w| w.to_le_bytes())
3862            .collect();
3863        let src = ctx.get_bytes(data).id();
3864        if let ValueId::Bytes(bid) = src {
3865            ctx.shared.values.bytes[bid].type_id = arr_ty;
3866        }
3867        // Publish the `(index, elem)` tuple and its array before pushing the
3868        // intrinsic: `result_type` only reads.
3869        {
3870            use qcode::types::{AggregateField, TypeRequest};
3871            let fields = vec![
3872                AggregateField::new("index", i64_ty),
3873                AggregateField::new("elem", i64_ty),
3874            ];
3875            let tuple = ctx
3876                .shared
3877                .types
3878                .create_requested_types(&[TypeRequest::aggregate(fields)])[0];
3879            ctx.shared
3880                .types
3881                .create_requested_types(&[TypeRequest::array(tuple, 4)]);
3882        }
3883        let enum_id = IntrinsicId::from_name("enumerate").unwrap();
3884        let e = {
3885            let mut b = ctx.builder(entry);
3886            let e = b.push_intrinsic(enum_id, vec![src]).id();
3887            let ptr = b.shr().get_const(0, 8);
3888            b.push_return(ptr);
3889            e
3890        };
3891        let ValueId::Instruction(eid) = e else {
3892            unreachable!()
3893        };
3894
3895        let mut emu = StandaloneEmulator::new(entry);
3896        emu.step(&ctx).expect("enumerate");
3897
3898        // `enumerate([10,20,30,40]) = [(0,10),(1,20),(2,30),(3,40)]`: each lane is
3899        // an `(index: i64, elem: i64)` tuple.
3900        let buf = emu
3901            .array_values
3902            .get(&eid)
3903            .expect("enumerate produced an array");
3904        let words: Vec<u64> = buf
3905            .as_chunks::<8>()
3906            .0
3907            .iter()
3908            .map(|&c| u64::from_le_bytes(c))
3909            .collect();
3910        assert_eq!(words, vec![0, 10, 1, 20, 2, 30, 3, 40]);
3911    }
3912
3913    /// Emulating a function that returns `enumerate` over an *unbounded* list
3914    /// bails with a recoverable `UnsupportedIntrinsic` rather than fabricating a
3915    /// length — a length-erased list has no concrete count to materialize. (A
3916    /// fixed array *is* materialized; see the `mt_scan` differential test.)
3917    #[test]
3918    fn enumerate_of_unbounded_list_bails_recoverably() {
3919        use qcode::value::{
3920            BasicBlock, FunctionBody, InstructionRef, ValueId,
3921            insn::{IntrinsicApp, IntrinsicId, Return},
3922        };
3923
3924        let mut ctx = Context::new();
3925        let f = FunctionBody::make(&mut ctx, "f".into()).unwrap().id;
3926        let entry = ctx.get_or_make_block(0x1000, f);
3927        {
3928            let mut fm = FunctionBody::from_id_mut(&mut ctx, f);
3929            fm.set_root(entry).unwrap();
3930            fm.add_block(entry);
3931        }
3932        let i8 = ctx.shared.types.get_or_make_int(1);
3933        let list_ty = ctx.shared.types.get_or_make_unbounded_list(i8);
3934        let src = {
3935            let mut b = ctx.builder(entry);
3936            b.push_param(8).id()
3937        };
3938        if let ValueId::BlockParam(pid) = src {
3939            ctx.block_param_mut(pid).type_id = list_ty;
3940        }
3941        // Build `enumerate` over the unbounded list with an explicit result type:
3942        // its `result_type` declines an unbounded operand (no static length), so
3943        // the intrinsic is only ever constructed this way, never via inference.
3944        let enum_id = IntrinsicId::from_name("enumerate").unwrap();
3945        // Create the intrinsic in the block's own storage arena so the pushed
3946        // instruction stays strict-local (its parent block lives in the same
3947        // function) — a foreign instruction placement is a locality violation the
3948        // in-body-id localization (ruling 2) forbids.
3949        let env = {
3950            let insn = InstructionRef::from_mnemonic_with_type(
3951                &mut ctx,
3952                entry.func,
3953                Mnemonic::Intrinsic(IntrinsicApp {
3954                    id: enum_id,
3955                    args: vec![src.localize(entry.func)],
3956                }),
3957                list_ty,
3958            )
3959            .id;
3960            BasicBlock::from_id_mut(&mut ctx, entry).push_insn(insn);
3961            ValueId::Instruction(insn)
3962        };
3963        let ptr = ctx.get_const(0, 8).id();
3964        {
3965            let mut b = ctx.builder(entry);
3966            b.push_return(ptr);
3967        }
3968        let rid = BasicBlock::from_id(&ctx, entry).iter().last().unwrap().id;
3969        ctx.replace_instruction_mnemonic(
3970            rid,
3971            Mnemonic::Return(Return {
3972                ptr: ptr.localize(rid.func),
3973                value: Some(env.localize(rid.func)),
3974            }),
3975        );
3976
3977        let mut emu = StandaloneEmulator::new(entry);
3978        let err = emu
3979            .run_pure(&ctx, f, &[SizedValue::new(0, 4)], 1000)
3980            .expect_err("enumerate must not be emulated");
3981        assert!(
3982            matches!(err.kind, EmulatorErrorKind::UnsupportedIntrinsic(ref n) if &**n == "enumerate"),
3983            "expected recoverable UnsupportedIntrinsic, got {:?}",
3984            err.kind
3985        );
3986    }
3987
3988    /// Build pure `f(arr: [i8; n])` returning `at(arr, idx)` and run it with the
3989    /// array param bound to `bound`. Returns the emulated scalar lane, or `None`
3990    /// if `resolve_array` refuses the binding (e.g. an oversize param).
3991    fn run_at_over_array_param(n: usize, idx: u64, bound: SizedValue) -> Option<u64> {
3992        use qcode::value::{
3993            BasicBlock, FunctionBody, ValueId,
3994            insn::{IntrinsicId, Return},
3995        };
3996
3997        let mut ctx = Context::new();
3998        let arr_ty = {
3999            let i8 = ctx.shared.types.get_or_make_int(1);
4000            ctx.shared.types.get_or_make_array(i8, n)
4001        };
4002        let f = FunctionBody::make(&mut ctx, "f".into()).unwrap().id;
4003        let entry = ctx.get_or_make_block(0x1000, f);
4004        {
4005            let mut fm = FunctionBody::from_id_mut(&mut ctx, f);
4006            fm.set_root(entry).unwrap();
4007            fm.add_block(entry);
4008        }
4009        let arr_pid = BasicBlock::from_id_mut(&mut ctx, entry).push_param(n).id;
4010        ctx.block_param_mut(arr_pid).type_id = arr_ty;
4011
4012        let at_id = IntrinsicId::from_name("at").unwrap();
4013        let (ret, ptr, lane);
4014        {
4015            let mut b = ctx.builder(entry);
4016            let arr = ValueId::BlockParam(arr_pid);
4017            let i = b.shr().get_const(idx, 8);
4018            lane = b.push_intrinsic(at_id, vec![arr, i]).id();
4019            ptr = b.shr().get_const(0, 8);
4020            ret = b.push_return(ptr).id();
4021        }
4022        let ValueId::Instruction(rid) = ret else {
4023            unreachable!()
4024        };
4025        ctx.replace_instruction_mnemonic(
4026            rid,
4027            Mnemonic::Return(Return {
4028                ptr: ptr.localize(rid.func),
4029                value: Some(lane.localize(rid.func)),
4030            }),
4031        );
4032
4033        let mut emu = StandaloneEmulator::new(entry);
4034        emu.run_pure(&ctx, f, &[bound], 1000).ok()?;
4035        emu.get_value(&ctx, lane)
4036    }
4037
4038    /// Dispatch through a `switch`: the arm whose case matches the scrutinee is
4039    /// taken, an unmatched value falls to the default, and an arm's block
4040    /// arguments are bound on the way through.
4041    fn run_switch(scrutinee: u64) -> Option<u64> {
4042        let mut ctx = Context::new();
4043        qcode!(
4044            ctx,
4045            "
4046            lambda sw:
4047            <entry @i:i64>
4048                switch @i { 0x0 => <a>, 0x3 => <b @v=0x63>, default => <d> };
4049            <a>
4050                return 0x11;
4051            <b @v:i64>
4052                return @v;
4053            <d>
4054                return 0x99;
4055            "
4056        );
4057        let root = FunctionBody::from_id(&ctx, sw).root().expect("root").id;
4058        let mut emu = StandaloneEmulator::new(root);
4059        emu.run_pure(&ctx, sw, &[SizedValue::new(scrutinee, 8)], 1000)
4060            .ok()?;
4061        let term = BasicBlock::from_id(&ctx, emu.block)
4062            .instruction_ids()
4063            .last()
4064            .copied()?;
4065        let Mnemonic::ReturnValue(r) = Instruction::from_id(&ctx, term).mnemonic() else {
4066            return None;
4067        };
4068        emu.get_value(&ctx, r.value.qualify(term.func))
4069    }
4070
4071    #[test]
4072    fn switch_selects_the_matching_arm() {
4073        assert_eq!(run_switch(0), Some(0x11));
4074        // The matching arm binds its target's block parameter.
4075        assert_eq!(run_switch(3), Some(0x63));
4076        // No case matches 7, so control reaches the default.
4077        assert_eq!(run_switch(7), Some(0x99));
4078    }
4079
4080    /// A literal-bound `[i8;4]` array param resolves through the `BlockParam` arm
4081    /// of `resolve_array`: each little-endian byte of `0x2f76bfc2` is readable via
4082    /// `at`, exactly the read the pure-call folder needs.
4083    #[test]
4084    fn run_pure_reads_array_param_bytes_little_endian() {
4085        let arg = SizedValue::new(0x2f76bfc2, 4);
4086        // LE layout of 0x2f76bfc2 = [0xc2, 0xbf, 0x76, 0x2f].
4087        assert_eq!(run_at_over_array_param(4, 0, arg), Some(0xc2));
4088        assert_eq!(run_at_over_array_param(4, 1, arg), Some(0xbf));
4089        assert_eq!(run_at_over_array_param(4, 2, arg), Some(0x76));
4090        assert_eq!(run_at_over_array_param(4, 3, arg), Some(0x2f));
4091    }
4092
4093    /// An oversize array param (declared width beyond `SizedValue`'s 16-byte cap)
4094    /// can't be materialized from a scalar binding, so the defensive width check
4095    /// fails resolution rather than hand back a clamped, misaligned buffer.
4096    #[test]
4097    fn run_pure_rejects_oversize_array_param() {
4098        // A 20-byte `[i8;20]` param: the bound `SizedValue` clamps to 16 bytes, so
4099        // `bytes.len() (16) != ty_size (20)` and resolution must fail.
4100        assert_eq!(
4101            run_at_over_array_param(20, 0, SizedValue::new(0xff, 20)),
4102            None
4103        );
4104    }
4105
4106    #[test]
4107    fn int_mul_wraps_for_64_bit_values() {
4108        let lhs = SizedValue::new(u64::MAX, 8);
4109        let rhs = SizedValue::new(2, 8);
4110
4111        let product = lhs.int_mul(&rhs).unwrap();
4112
4113        assert_eq!(product.value().unwrap(), u64::MAX.wrapping_mul(2));
4114        assert_eq!(product.size().unwrap(), 8);
4115    }
4116
4117    #[test]
4118    fn int_mul_wraps_for_128_bit_values() {
4119        let lhs = SizedValue::from_bits(u128::MAX, 16);
4120        let rhs = SizedValue::from_bits(u128::from(2u8), 16);
4121
4122        let product = lhs.int_mul(&rhs).unwrap();
4123
4124        assert_eq!(product.as_bits(), u128::MAX.wrapping_mul(u128::from(2u8)));
4125        assert_eq!(product.size().unwrap(), 16);
4126        assert!(matches!(
4127            product.value(),
4128            Err(EmulatorErrorKind::ValueError(_))
4129        ));
4130    }
4131
4132    #[test]
4133    fn int_div_and_rem_work_for_128_bit_values() {
4134        let lhs = SizedValue::from_bits(u128::MAX, 16);
4135        let rhs = SizedValue::from_bits(u128::from(3u8), 16);
4136
4137        let q = lhs.int_div(&rhs).unwrap();
4138        let r = lhs.int_rem(&rhs).unwrap();
4139
4140        assert_eq!(q.as_bits(), u128::MAX / u128::from(3u8));
4141        assert_eq!(r.as_bits(), u128::MAX % u128::from(3u8));
4142        assert_eq!(q.size().unwrap(), 16);
4143        assert_eq!(r.size().unwrap(), 16);
4144    }
4145
4146    #[test]
4147    fn int_sdiv_and_srem_work_for_128_bit_values() {
4148        let lhs = SizedValue::from_bits(u128::from(0xffff_ffff_ffff_ffffu64), 16);
4149        let rhs = SizedValue::from_bits(u128::from(2u8), 16);
4150
4151        let q = lhs.int_sdiv(&rhs).unwrap();
4152        let r = lhs.int_srem(&rhs).unwrap();
4153
4154        assert_eq!(q.as_bits(), u128::from(0x7fff_ffff_ffff_ffffu64));
4155        assert_eq!(r.as_bits(), u128::from(1u8));
4156        assert_eq!(q.size().unwrap(), 16);
4157        assert_eq!(r.size().unwrap(), 16);
4158    }
4159
4160    #[test]
4161    fn signed_extension_and_shift_behave_as_expected() {
4162        let negative_byte = SizedValue::new(0x80, 1);
4163        let extended = negative_byte.sext(8).unwrap();
4164        let shifted = negative_byte
4165            .int_sshift_right(&SizedValue::new(1, 1))
4166            .unwrap();
4167
4168        assert_eq!(extended.value().unwrap(), 0xffff_ffff_ffff_ff80);
4169        assert_eq!(extended.size().unwrap(), 8);
4170        assert_eq!(shifted.value().unwrap(), 0xc0);
4171        assert_eq!(shifted.size().unwrap(), 1);
4172    }
4173
4174    #[test]
4175    fn signed_comparisons_use_value_width() {
4176        let lhs = SizedValue::new(0xff, 1);
4177        let rhs = SizedValue::new(0x01, 1);
4178
4179        assert_eq!(lhs.int_sless(&rhs).and_then(|v| v.value()).unwrap(), 1);
4180        assert_eq!(rhs.int_sless(&lhs).and_then(|v| v.value()).unwrap(), 0);
4181    }
4182
4183    #[test]
4184    fn int_sub_uses_lhs_width_with_default_u64_immediate() {
4185        let lhs = SizedValue::new(0, 4);
4186        let rhs = SizedValue::from_u64(1);
4187
4188        let diff = lhs.int_sub(&rhs).unwrap();
4189
4190        assert_eq!(diff.size().unwrap(), 4);
4191        assert_eq!(diff.value().unwrap(), 0xffff_ffff);
4192    }
4193
4194    #[test]
4195    fn sborrow_uses_lhs_width_with_default_u64_immediate() {
4196        let lhs = SizedValue::new(0x80, 1);
4197        let rhs = SizedValue::new(1, 1);
4198
4199        // 0x80 - 1 overflows in signed 8-bit arithmetic.
4200        assert_eq!(lhs.sborrow(&rhs).and_then(|v| v.value()).unwrap(), 1);
4201    }
4202
4203    #[test]
4204    fn scarry_uses_lhs_width_with_default_u64_immediate() {
4205        let lhs = SizedValue::new(0x7f, 1);
4206        let rhs = SizedValue::new(1, 1);
4207
4208        // 0x7f + 1 overflows in signed 8-bit arithmetic.
4209        assert_eq!(lhs.scarry(&rhs).and_then(|v| v.value()).unwrap(), 1);
4210    }
4211
4212    #[test]
4213    fn sborrow_neg() {
4214        let lhs = SizedValue::new(0x0, 1);
4215        let rhs = SizedValue::new(0x80, 1);
4216
4217        // 0 - 0x80  overflows in signed 8-bit arithmetic.
4218        assert_eq!(lhs.sborrow(&rhs).and_then(|v| v.value()).unwrap(), 1);
4219    }
4220
4221    #[test]
4222    fn lz_count_respects_width() {
4223        let value = SizedValue::new(0x01, 1);
4224        let lz = value.lz_count().unwrap();
4225
4226        assert_eq!(lz.value().unwrap(), 7);
4227        assert_eq!(lz.size().unwrap(), 1);
4228    }
4229
4230    #[test]
4231    fn float_conversion_handles_f32_and_f64() {
4232        let minus_one = SizedValue::new(0xff, 1);
4233        let as_f32 = minus_one.int_to_float(4).unwrap();
4234        assert_eq!(as_f32.value().unwrap(), (-1.0f32).to_bits() as u64);
4235        assert_eq!(as_f32.size().unwrap(), 4);
4236
4237        let f32_value = SizedValue::new((1.5f32).to_bits() as u64, 4);
4238        let promoted = f32_value.float_to_float(8).unwrap();
4239        let promoted_bits = promoted.value().unwrap();
4240        assert_eq!(f64::from_bits(promoted_bits), 1.5f64);
4241        assert_eq!(promoted.size().unwrap(), 8);
4242
4243        let demoted = promoted.float_to_float(4).unwrap();
4244        assert_eq!(demoted.value().unwrap(), (1.5f32).to_bits() as u64);
4245        assert_eq!(demoted.size().unwrap(), 4);
4246    }
4247
4248    #[test]
4249    fn x87_precision_and_store_rounding_are_separate_from_generic_arithmetic() {
4250        let one = 0x3fff_8000_0000_0000_0000u128;
4251        // Precision rounding narrows the f80 significand without narrowing
4252        // the exponent, and follows the IEEE rounding mode it is given.
4253        let one_plus_half_single_ulp = one + (1u128 << 39);
4254        assert_eq!(
4255            float80::round_to_precision(one_plus_half_single_ulp, 24, Round::NearestTiesToEven)
4256                .bits,
4257            one
4258        );
4259        assert_eq!(
4260            float80::round_to_precision(one_plus_half_single_ulp, 24, Round::TowardPositive).bits,
4261            one + (1u128 << 40)
4262        );
4263        // 64 significand bits is the identity.
4264        assert_eq!(
4265            float80::round_to_precision(one_plus_half_single_ulp, 64, Round::TowardPositive).bits,
4266            one_plus_half_single_ulp
4267        );
4268
4269        // Narrowing takes an explicit rounding mode and no control word: the
4270        // exact halfway value narrows to 1.0 under nearest-even and to the
4271        // next f32 under round-up.
4272        let extended = SizedValue::from_bits(one_plus_half_single_ulp, 10);
4273        assert_eq!(
4274            StandaloneEmulator::<EmulatedMemory>::ieee_narrow(
4275                extended,
4276                4,
4277                Round::NearestTiesToEven
4278            )
4279            .unwrap()
4280            .0
4281            .as_bits(),
4282            u128::from(1.0f32.to_bits())
4283        );
4284        assert_eq!(
4285            StandaloneEmulator::<EmulatedMemory>::ieee_narrow(extended, 4, Round::TowardPositive)
4286                .unwrap()
4287                .0
4288                .as_bits(),
4289            u128::from((1.0f32).to_bits() + 1)
4290        );
4291
4292        // Generic f80 arithmetic is architecture-neutral: it computes a value
4293        // and records no x87 status.  x87 constructors use the explicit IEEE
4294        // p-code operations instead and apply their own exception policy.
4295        let mut ctx = Context::new();
4296        qcode!(
4297            ctx,
4298            "
4299            varnode i16 FPUControlWord;
4300            varnode i16 FPUStatusWord;
4301            varnode f80 A;
4302            varnode f80 B;
4303
4304        <block>
4305            %a = load(A:10, &A);
4306            %b = load(B:10, &B);
4307            %result = %a f/ %b;
4308            goto <0x1001>;
4309        "
4310        );
4311        let mut emu = Emulator::from_block(&ctx, block);
4312        emu.set_varnode(FPUControlWord, 0x037b).unwrap(); // ZE unmasked
4313        emu.set_varnode_u128(A, one).unwrap();
4314        emu.set_varnode_u128(B, 0).unwrap();
4315        emu.run_block().unwrap();
4316        assert_eq!(emu.get_value(result.into()).unwrap().size().unwrap(), 10);
4317        // Never written: the status word stays untouched by the operation.
4318        assert_eq!(emu.read_varnode(FPUStatusWord), None);
4319    }
4320
4321    /// An f80 comparison is a pure predicate. A signalling NaN operand is
4322    /// invalid under x87's rules, but raising it is the specification's job:
4323    /// the emulator writes no status word for a comparison either.
4324    #[test]
4325    fn f80_comparison_records_no_status() {
4326        let mut ctx = Context::new();
4327        qcode!(
4328            ctx,
4329            "
4330            varnode i16 FPUControlWord;
4331            varnode i16 FPUStatusWord;
4332            varnode f80 A;
4333            varnode f80 B;
4334
4335        <block>
4336            %a = load(A:10, &A);
4337            %b = load(B:10, &B);
4338            %equal = %a f== %b;
4339            goto <0x1001>;
4340        "
4341        );
4342        let mut emu = Emulator::from_block(&ctx, block);
4343        emu.set_varnode(FPUControlWord, 0x037f).unwrap();
4344        // A signalling NaN: exponent all ones, integer bit set, quiet bit
4345        // clear, non-zero fraction.
4346        emu.set_varnode_u128(A, 0x7fff_8000_0000_0000_0001).unwrap();
4347        emu.set_varnode_u128(B, 0x3fff_8000_0000_0000_0000).unwrap();
4348        emu.run_block().unwrap();
4349        assert_eq!(emu.get_value(equal.into()).unwrap().value().unwrap(), 0);
4350        assert_eq!(emu.read_varnode(FPUStatusWord), None);
4351    }
4352
4353    /// A partial reduction consumes 63 quotient bits per step, leaving the
4354    /// remainder exact. Hardware reduces (2 - 2^-63) * 2^16383 modulo 1.0 to
4355    /// exactly zero, which only happens when the whole reducible span is
4356    /// consumed: a 32-bit partial quotient leaves a large non-zero remainder.
4357    #[test]
4358    fn float80_partial_remainder_is_exact() {
4359        let dividend = 0x7ffe_ffff_ffff_ffff_ffffu128;
4360        let one = 0x3fff_8000_0000_0000_0000u128;
4361        for ieee in [false, true] {
4362            let result = float80::remainder(dividend, one, ieee);
4363            assert!(result.incomplete);
4364            assert_eq!(result.bits, 0);
4365        }
4366
4367        // One exponent more of span drops a whole 32-bit group of quotient
4368        // bits, leaving 32 behind: the remainder is the dividend's low 32
4369        // exponents, not zero. Hardware reduces in 32-bit groups.
4370        let half = 0x3ffe_8000_0000_0000_0000u128;
4371        let result = float80::remainder(dividend, half, false);
4372        assert!(result.incomplete);
4373        assert_eq!(result.bits, 0x7fdd_ffff_fffe_0000_0000);
4374
4375        // A span under 64 exponents still completes in one step and reports
4376        // the quotient's low bits.
4377        let three = 0x4000_c000_0000_0000_0000u128;
4378        let result = float80::remainder(three, one, false);
4379        assert!(!result.incomplete);
4380        assert_eq!(result.bits, 0);
4381        assert_eq!(result.quotient & 7, 3);
4382    }
4383
4384    /// The 80-bit square root is computed on the integer significand, so it
4385    /// keeps all 64 bits. Routing it through f64 would lose eleven of them.
4386    #[test]
4387    fn float80_sqrt_is_correctly_rounded_at_extended_precision() {
4388        let two = 0x4000_8000_0000_0000_0000;
4389        let four = 0x4001_8000_0000_0000_0000;
4390        let one = 0x3fff_8000_0000_0000_0000;
4391
4392        // sqrt(2) rounds up into the last significand bit.
4393        let root_two = float80::sqrt_ieee(two, Round::NearestTiesToEven);
4394        assert_eq!(root_two.bits, 0x3fff_b504_f333_f9de_6484);
4395        assert!(root_two.status.contains(Status::INEXACT));
4396
4397        // Exact roots stay exact and report nothing.
4398        for (input, expect) in [(four, two), (one, one), (0, 0)] {
4399            let result = float80::sqrt_ieee(input, Round::NearestTiesToEven);
4400            assert_eq!(result.bits, expect);
4401            assert_eq!(result.status, Status::OK);
4402        }
4403
4404        // sqrt(3) is a case where the integer root does round up, so nearest
4405        // and truncation land on different significands.
4406        let three = 0x4000_c000_0000_0000_0000;
4407        assert_eq!(
4408            float80::sqrt_ieee(three, Round::NearestTiesToEven).bits,
4409            0x3fff_ddb3_d742_c265_539e
4410        );
4411        assert_eq!(
4412            float80::sqrt_ieee(three, Round::TowardZero).bits,
4413            0x3fff_ddb3_d742_c265_539d
4414        );
4415
4416        // A negative operand is invalid. What is delivered in its place is
4417        // the specification's choice, so the operation returns the operand.
4418        let negative = float80::sqrt_ieee(0xbfff_8000_0000_0000_0000, Round::NearestTiesToEven);
4419        assert_eq!(negative.bits, 0xbfff_8000_0000_0000_0000);
4420        assert!(negative.status.contains(Status::INVALID_OP));
4421    }
4422
4423    #[test]
4424    fn float80_arithmetic_preserves_extended_precision_bits() {
4425        // x87 80-bit encodings: significand in bits 0..64, exponent/sign in
4426        // bits 64..80. 3.0 is not representable by merely treating f80 as an
4427        // f64 bit-pattern, which was the former behavior.
4428        let one = SizedValue::from_bits(0x3fff_8000_0000_0000_0000, 10);
4429        let two = SizedValue::from_bits(0x4000_8000_0000_0000_0000, 10);
4430        let three = one.float_add(&two).unwrap();
4431
4432        assert_eq!(three.as_bits(), 0x4000_c000_0000_0000_0000);
4433        assert_eq!(three.size().unwrap(), 10);
4434        assert_eq!(two.float_to_float(10).unwrap().as_bits(), two.as_bits());
4435        assert_eq!(
4436            SizedValue::new(3, 1).int_to_float(10).unwrap().as_bits(),
4437            three.as_bits()
4438        );
4439    }
4440
4441    #[test]
4442    fn explicit_ieee_arithmetic_pairs_results_and_flags_in_every_rounding_mode() {
4443        // Each tuple has an inexact operand pair for its respective operation.
4444        // Exercise all supported formats as well as every rounding direction;
4445        // the result and flags calls must be two views of exactly one IEEE
4446        // evaluation, not host arithmetic with independently inferred flags.
4447        let cases = [
4448            (
4449                4,
4450                0x3f80_0000,
4451                0x3380_0000,
4452                0x3f80_0001,
4453                0x3fc0_0000,
4454                0x3f80_0000,
4455                0x4040_0000,
4456            ),
4457            (
4458                8,
4459                0x3ff0_0000_0000_0000,
4460                0x3ca0_0000_0000_0000,
4461                0x3ff0_0000_0000_0001,
4462                0x3ff8_0000_0000_0000,
4463                0x3ff0_0000_0000_0000,
4464                0x4008_0000_0000_0000,
4465            ),
4466            (
4467                10,
4468                0x3fff_8000_0000_0000_0000,
4469                0x3fbf_8000_0000_0000_0000,
4470                0x3fff_8000_0000_0000_0001,
4471                0x3fff_c000_0000_0000_0000,
4472                0x3fff_8000_0000_0000_0000,
4473                0x4000_c000_0000_0000_0000,
4474            ),
4475        ];
4476        let operations = [
4477            ("float_add", "float_add_flags", 0usize),
4478            ("float_sub", "float_sub_flags", 1),
4479            ("float_mul", "float_mul_flags", 2),
4480            ("float_div", "float_div_flags", 3),
4481        ];
4482
4483        for (size, add_lhs, add_rhs, mul_lhs, mul_rhs, div_lhs, div_rhs) in cases {
4484            // At one, the spacing below is half the spacing above. Use a
4485            // quarter of the upward ULP for subtraction so it is inexact too.
4486            let sub_rhs = match size {
4487                4 => 0x3300_0000,
4488                8 => 0x3c90_0000_0000_0000,
4489                10 => 0x3fbe_8000_0000_0000_0000,
4490                _ => unreachable!(),
4491            };
4492            let operands = [
4493                (add_lhs, add_rhs),
4494                (add_lhs, sub_rhs),
4495                (mul_lhs, mul_rhs),
4496                (div_lhs, div_rhs),
4497            ];
4498            for (result_name, flags_name, pair) in operations {
4499                let (lhs, rhs) = operands[pair];
4500                for mode in 0..4 {
4501                    let round =
4502                        StandaloneEmulator::<EmulatedMemory>::ieee_rounding_mode(mode).unwrap();
4503                    let (result, status) = StandaloneEmulator::<EmulatedMemory>::ieee_arithmetic(
4504                        SizedValue::from_bits(lhs, size),
4505                        SizedValue::from_bits(rhs, size),
4506                        round,
4507                        result_name,
4508                    )
4509                    .unwrap();
4510                    let (_, flag_status) = StandaloneEmulator::<EmulatedMemory>::ieee_arithmetic(
4511                        SizedValue::from_bits(lhs, size),
4512                        SizedValue::from_bits(rhs, size),
4513                        round,
4514                        flags_name,
4515                    )
4516                    .unwrap();
4517                    assert_eq!(
4518                        status,
4519                        flag_status,
4520                        "{result_name}, f{}, mode {mode}",
4521                        size * 8
4522                    );
4523                    let flags = StandaloneEmulator::<EmulatedMemory>::ieee_flags(flag_status);
4524                    assert_ne!(
4525                        flags.as_bits() & (1 << 5),
4526                        0,
4527                        "{result_name}, f{}, mode {mode}",
4528                        size * 8
4529                    );
4530                    assert_eq!(result.size as usize, size);
4531                }
4532            }
4533        }
4534    }
4535
4536    #[test]
4537    fn simple_addition() {
4538        let mut ctx = Context::new();
4539
4540        qcode!(
4541            ctx,
4542            "
4543            varnode i64 V0;
4544            varnode i64 V1;
4545
4546        <block>
4547            %v0 = load(V0:8, &V0);
4548            %v1 = load(V1:8, &V1);
4549            %res = %v0 + %v1;
4550            goto <0x1001>;
4551        "
4552        );
4553
4554        let mut emu = Emulator::from_block(&ctx, block);
4555        emu.set_varnode(V0, 2).unwrap();
4556        emu.set_varnode(V1, 3).unwrap();
4557        emu.run_block().unwrap();
4558
4559        assert_eq!(
4560            emu.get_value(res.into()).and_then(|v| v.value()).unwrap(),
4561            5
4562        );
4563    }
4564
4565    #[test]
4566    fn gep_emulates_as_base_plus_offset() {
4567        let mut ctx = Context::new();
4568
4569        // `Inner { val: i32 @ 0x08 }` (0x08 via leading padding), `%p : Inner*`.
4570        qcode!(
4571            ctx,
4572            "
4573            type Inner { _: 8, val: 4 };
4574            varnode i64 V0;
4575
4576        <block>
4577            Inner* %p = load(V0:8, &V0);
4578            %fld = gep(%p.val);
4579            goto <0x1001>;
4580        "
4581        );
4582
4583        let mut emu = Emulator::from_block(&ctx, block);
4584        emu.set_varnode(V0, 0x1000).unwrap();
4585        emu.run_block().unwrap();
4586
4587        let fld = emu.get_value(fld.into()).unwrap();
4588        assert_eq!(fld.value().unwrap(), 0x1008);
4589        // Width follows the pointer base, not the immediate's default u64.
4590        assert_eq!(fld.size().unwrap(), 8);
4591    }
4592
4593    #[test]
4594    fn emulator_int_div_works_with_128_bit_operands() {
4595        let mut ctx = Context::new();
4596        qcode!(
4597            ctx,
4598            "
4599            varnode i128 V0;
4600            varnode i128 V1;
4601
4602        <block>
4603            %v0 = load(V0:16, &V0);
4604            %v1 = load(V1:16, &V1);
4605
4606            %res = %v0 / %v1;
4607            goto <0x1001>;
4608        "
4609        );
4610
4611        let mut emu = Emulator::from_block(&ctx, block);
4612        let v0_bits = u128::from(1u8) << 100;
4613        let v1_bits = u128::from(1u8) << 99;
4614
4615        emu.set_varnode_u128(V0, v0_bits).unwrap();
4616        emu.set_varnode_u128(V1, v1_bits).unwrap();
4617        emu.run_block().unwrap();
4618
4619        // Quotient is small enough to also be visible through legacy u64 extraction.
4620        assert_eq!(
4621            emu.get_value(res.into()).and_then(|v| v.value()).unwrap(),
4622            2
4623        );
4624    }
4625
4626    // -----------------------------------------------------------------------
4627    // Memory error-handling tests
4628    // -----------------------------------------------------------------------
4629
4630    #[test]
4631    fn uninitialized_memory_reads_error() {
4632        let space = EmulatedSpace::default();
4633        assert!(matches!(
4634            space.read_byte(0xdead_beef),
4635            Err(EmulatorErrorKind::MemoryReadError(0xdead_beef))
4636        ));
4637        assert!(matches!(
4638            space.read(0x1000, 4),
4639            Err(EmulatorErrorKind::MemoryReadError(0x1000))
4640        ));
4641    }
4642
4643    #[test]
4644    fn configured_register_and_body_temporary_spaces_zero_fill_missing_bytes() {
4645        let mut ctx = Context::new();
4646        let mut register = Space::new(Some("register"), 1, 8);
4647        register.ty = SpaceType::Register;
4648        let register = ctx.add_space(register);
4649        let function = ctx.anon_function();
4650        let temporary = MemorySpaceId::Temp(ctx.bodies[function].push_temp_space(TempSpace::new(
4651            Some("scratch"),
4652            1,
4653            8,
4654        )));
4655        let mut memory = EmulatedMemory::default();
4656        memory.configure_spaces(&ctx);
4657
4658        for space in [register.into(), temporary] {
4659            assert_eq!(
4660                memory
4661                    .read(space, SizedValue::from_u64(0x1000), 4)
4662                    .unwrap()
4663                    .value()
4664                    .unwrap(),
4665                0
4666            );
4667        }
4668
4669        memory
4670            .write(
4671                ctx.shared.default_space.into(),
4672                SizedValue::from_u64(0x1000),
4673                1,
4674                SizedValue::new(0xaa, 1),
4675            )
4676            .unwrap();
4677        assert!(matches!(
4678            memory.read(
4679                ctx.shared.default_space.into(),
4680                SizedValue::from_u64(0x1001),
4681                1
4682            ),
4683            Err(EmulatorErrorKind::MemoryReadError(0x1001))
4684        ));
4685    }
4686
4687    #[test]
4688    fn temporary_spaces_with_the_same_address_are_isolated() {
4689        use qcode::value::TempSpace;
4690
4691        let mut ctx = Context::new();
4692        let first_fn = FunctionBody::make(&mut ctx, "first".into()).unwrap().id;
4693        let second_fn = FunctionBody::make(&mut ctx, "second".into()).unwrap().id;
4694        let first = ctx.bodies[first_fn].push_temp_space(TempSpace::new(None, 1, 8));
4695        let second = ctx.bodies[second_fn].push_temp_space(TempSpace::new(None, 1, 8));
4696        assert_eq!(first.local, second.local, "fixture must collide local IDs");
4697        let first = MemorySpaceId::Temp(first);
4698        let second = MemorySpaceId::Temp(second);
4699        let mut memory = EmulatedMemory::default();
4700        memory.configure_spaces(&ctx);
4701
4702        let address = SizedValue::from_u64(0x20);
4703        memory
4704            .write(first, address, 1, SizedValue::new(0xaa, 1))
4705            .unwrap();
4706        memory
4707            .write(second, address, 1, SizedValue::new(0x55, 1))
4708            .unwrap();
4709
4710        assert_eq!(
4711            memory.read(first, address, 1).unwrap().value().unwrap(),
4712            0xaa
4713        );
4714        assert_eq!(
4715            memory.read(second, address, 1).unwrap().value().unwrap(),
4716            0x55
4717        );
4718    }
4719
4720    #[test]
4721    fn interpreter_qualifies_colliding_local_spaces_by_function() {
4722        use qcode::value::TempSpace;
4723
4724        fn make_writer(
4725            ctx: &mut Context<'static>,
4726            name: &'static str,
4727            byte: u64,
4728        ) -> (FunctionId, qcode::value::TempSpaceId) {
4729            let fid = FunctionBody::make(ctx, name.into()).unwrap().id;
4730            let root = BasicBlock::make(ctx, fid).id;
4731            FunctionBody::from_id_mut(ctx, fid).set_root(root).unwrap();
4732            let space = ctx.bodies[fid].push_temp_space(TempSpace::new(None, 1, 8));
4733            let mut b = (ctx).builder(root);
4734            let ptr = b.shr().get_const(0x20, 8);
4735            let value = b.shr().get_const(byte, 1);
4736            b.push_store(
4737                value,
4738                ptr,
4739                qcode::space::LocalMemorySpaceId::Temp(space.local),
4740            );
4741            b.push_return(ptr);
4742            (fid, space)
4743        }
4744
4745        let mut ctx = Context::new();
4746        let (first, first_space) = make_writer(&mut ctx, "first", 0xaa);
4747        let (second, second_space) = make_writer(&mut ctx, "second", 0x55);
4748        assert_eq!(first_space.local, second_space.local);
4749
4750        let root = FunctionBody::from_id(&ctx, first).root().unwrap().id;
4751        let mut emulator = StandaloneEmulator::new(root);
4752        emulator.run_function(&ctx, first).unwrap();
4753        emulator.run_function(&ctx, second).unwrap();
4754
4755        let address = SizedValue::from_u64(0x20);
4756        assert_eq!(
4757            emulator
4758                .memory
4759                .read(MemorySpaceId::Temp(first_space), address, 1)
4760                .unwrap()
4761                .value()
4762                .unwrap(),
4763            0xaa
4764        );
4765        assert_eq!(
4766            emulator
4767                .memory
4768                .read(MemorySpaceId::Temp(second_space), address, 1)
4769                .unwrap()
4770                .value()
4771                .unwrap(),
4772            0x55
4773        );
4774    }
4775
4776    #[test]
4777    fn sized_value_byte_swap_preserves_width() {
4778        let value = SizedValue::new(0x1234, 2).byte_swap().unwrap();
4779        assert_eq!(value.value().unwrap(), 0x3412);
4780        assert_eq!(value.size().unwrap(), 2);
4781    }
4782
4783    #[test]
4784    fn swap_bytes_pcode_op_is_emulated() {
4785        let mut ctx = Context::new();
4786        let op = ctx.shared.pcode_ops.push(Box::from("swap_bytes"));
4787        let block_id = {
4788            let __f = ctx.anon_function();
4789            ctx.get_or_make_block(0x1000, __f)
4790        };
4791        let target = ctx.get_or_make_block(0x1001, block_id.func);
4792        let result = {
4793            let src = ctx.get_const(0x1234, 2).id();
4794            let mut builder = ctx.builder(block_id);
4795            let result = builder.push_pcode_op(op, vec![src], None, 2).id;
4796            builder.finalize(target);
4797            result
4798        };
4799        let mut emulator = Emulator::from_block(&ctx, block_id);
4800
4801        emulator.step().unwrap();
4802
4803        assert_eq!(
4804            emulator
4805                .get_value(result.into())
4806                .and_then(|value| value.value())
4807                .unwrap(),
4808            0x3412
4809        );
4810    }
4811
4812    #[test]
4813    fn undef_pcode_op_is_zero_at_its_declared_width() {
4814        let mut ctx = Context::new();
4815        let op = ctx.shared.pcode_ops.push(Box::from("undef"));
4816        let block_id = {
4817            let function = ctx.anon_function();
4818            ctx.get_or_make_block(0x1000, function)
4819        };
4820        let target = ctx.get_or_make_block(0x1001, block_id.func);
4821        let result = {
4822            let mut builder = ctx.builder(block_id);
4823            let result = builder.push_pcode_op(op, vec![], None, 1).id;
4824            builder.finalize(target);
4825            result
4826        };
4827        let mut emulator = Emulator::from_block(&ctx, block_id);
4828
4829        emulator.step().unwrap();
4830
4831        let value = emulator.get_value(result.into()).unwrap();
4832        assert_eq!(value.value().unwrap(), 0);
4833        assert_eq!(value.size().unwrap(), 1);
4834    }
4835
4836    #[test]
4837    fn rol_intrinsic_is_emulated() {
4838        use qcode::value::insn::IntrinsicId;
4839        let mut ctx = Context::new();
4840        let rol = IntrinsicId::from_name("rol").unwrap();
4841        let block_id = {
4842            let __f = ctx.anon_function();
4843            ctx.get_or_make_block(0x1000, __f)
4844        };
4845        let target = ctx.get_or_make_block(0x1001, block_id.func);
4846        let result = {
4847            let x = ctx.get_const(0x1234_5678, 4).id();
4848            let k = ctx.get_const(8, 4).id();
4849            let mut builder = ctx.builder(block_id);
4850            let result = builder.push_intrinsic(rol, vec![x, k]).id;
4851            builder.finalize(target);
4852            result
4853        };
4854        let mut emulator = Emulator::from_block(&ctx, block_id);
4855
4856        emulator.step().unwrap();
4857
4858        assert_eq!(
4859            emulator
4860                .get_value(result.into())
4861                .and_then(|value| value.value())
4862                .unwrap(),
4863            0x1234_5678u32.rotate_left(8) as u64,
4864        );
4865    }
4866
4867    #[test]
4868    fn unknown_pcode_op_returns_typed_error() {
4869        let mut ctx = Context::new();
4870        let op = ctx.shared.pcode_ops.push(Box::from("rdpmc"));
4871        let block_id = {
4872            let __f = ctx.anon_function();
4873            ctx.get_or_make_block(0x1000, __f)
4874        };
4875        let target = ctx.get_or_make_block(0x1001, block_id.func);
4876        {
4877            let mut builder = ctx.builder(block_id);
4878            builder.push_pcode_op(op, vec![], None, 0);
4879            builder.finalize(target);
4880        }
4881        let mut emulator = Emulator::from_block(&ctx, block_id);
4882
4883        let error = emulator.step().unwrap_err();
4884
4885        assert!(matches!(
4886            error.kind,
4887            EmulatorErrorKind::UnsupportedPCodeOp(operation) if operation.as_ref() == "rdpmc"
4888        ));
4889    }
4890
4891    #[test]
4892    fn get_region_overflow_does_not_panic() {
4893        let mut space = EmulatedSpace::default();
4894        // addr + size would overflow u64 without checked_add
4895        assert!(matches!(
4896            space.get_mut_region(u64::MAX - 2, 8),
4897            Err(EmulatorErrorKind::AddressOverflow(_, _))
4898        ));
4899    }
4900
4901    // -----------------------------------------------------------------------
4902    // run_function happy-path tests
4903    // -----------------------------------------------------------------------
4904
4905    #[test]
4906    fn run_function_returns_ok_for_trivial_function() {
4907        let mut ctx = Context::new();
4908        qcode!(
4909            ctx,
4910            "
4911            fn function:
4912            <entry>
4913                return at i64 0;
4914            "
4915        );
4916
4917        let mut emu = Emulator::from_function(&ctx, function);
4918        assert!(emu.run_function(function).is_ok());
4919    }
4920
4921    #[test]
4922    fn run_function_executes_instructions_before_return() {
4923        let mut ctx = Context::new();
4924        qcode!(
4925            ctx,
4926            "
4927            varnode i64 A;
4928            varnode i64 B;
4929
4930            fn function:
4931            <entry>
4932                %a = load(A:8, &A);
4933                %b = load(B:8, &B);
4934                %sum = %a + %b;
4935                return at i64 0;
4936            "
4937        );
4938
4939        let mut emu = Emulator::from_function(&ctx, function);
4940        emu.set_varnode(A, 7).unwrap();
4941        emu.set_varnode(B, 5).unwrap();
4942        emu.run_function(function).unwrap();
4943
4944        assert_eq!(
4945            emu.get_value(sum.into()).and_then(|v| v.value()).unwrap(),
4946            12
4947        );
4948    }
4949
4950    #[test]
4951    fn run_function_call_stack_empty_after_successful_return() {
4952        let mut ctx = Context::new();
4953
4954        qcode!(
4955            ctx,
4956            "
4957            varnode i64 A;
4958            varnode i64 B;
4959
4960            fn function:
4961            <entry>
4962                %a = load(A:8, &A);
4963                %b = load(B:8, &B);
4964                %sum = %a + %b;
4965                return at i64 0;
4966            "
4967        );
4968
4969        let mut emu = Emulator::from_function(&ctx, function);
4970        emu.set_varnode(A, 0).unwrap();
4971        emu.set_varnode(B, 0).unwrap();
4972        emu.run_function(function).unwrap();
4973
4974        assert!(emu.call_stack().is_empty());
4975    }
4976
4977    #[test]
4978    fn unhandled_direct_call_still_enters_callee() {
4979        let mut ctx = Context::new();
4980        qcode!(
4981            ctx,
4982            "
4983            fn callee:
4984            <callee_entry>
4985                return at i64 0;
4986
4987            <caller>
4988                call <callee>;
4989            "
4990        );
4991
4992        let mut emu = Emulator::from_block(&ctx, caller);
4993        emu.step().unwrap();
4994
4995        assert_eq!(emu.block().id, callee_entry);
4996    }
4997
4998    #[test]
4999    fn handled_direct_call_resumes_at_selected_block() {
5000        let mut ctx = Context::new();
5001        qcode!(
5002            ctx,
5003            "
5004            varnode i64 RET;
5005
5006            fn library:
5007            <library_entry>
5008                return at i64 0;
5009
5010            fn function:
5011            <entry>
5012                call <library>;
5013            <after_call>
5014                %ret = load(RET:8, &RET);
5015                return at i64 0;
5016            "
5017        );
5018
5019        let mut emu = Emulator::from_function(&ctx, function);
5020        emu.set_call_interceptor(move |ctx, emu, site| {
5021            if site.target == library {
5022                emu.set_varnode(ctx, RET, 42)
5023                    .map_err(|err| err.to_string().into_boxed_str())?;
5024                Ok(CallInterception::Handled(CallContinuation::Block(
5025                    after_call,
5026                )))
5027            } else {
5028                Ok(CallInterception::PassThrough)
5029            }
5030        });
5031
5032        emu.run_function(function).unwrap();
5033
5034        assert_eq!(
5035            emu.get_value(ret.into()).and_then(|v| v.value()).unwrap(),
5036            42
5037        );
5038        assert!(emu.call_stack().is_empty());
5039    }
5040
5041    #[test]
5042    fn handled_direct_call_can_resume_by_address() {
5043        let mut ctx = Context::new();
5044        qcode!(
5045            ctx,
5046            "
5047            fn library:
5048            <library_entry>
5049                return at i64 0;
5050
5051            <entry>
5052                call <library>;
5053            <0x2000>
5054                return at i64 0;
5055            "
5056        );
5057
5058        let mut emu = Emulator::from_block(&ctx, entry);
5059        emu.set_call_interceptor(move |_, _, site| {
5060            if site.target == library {
5061                Ok(CallInterception::Handled(CallContinuation::Address(0x2000)))
5062            } else {
5063                Ok(CallInterception::PassThrough)
5064            }
5065        });
5066
5067        emu.step().unwrap();
5068
5069        assert_eq!(emu.block().address(), Some(0x2000));
5070    }
5071
5072    #[test]
5073    fn handled_direct_call_reports_unknown_continuation_address() {
5074        let mut ctx = Context::new();
5075        qcode!(
5076            ctx,
5077            "
5078            fn library:
5079            <library_entry>
5080                return at i64 0;
5081
5082            <entry>
5083                call <library>;
5084            "
5085        );
5086
5087        let mut emu = Emulator::from_block(&ctx, entry);
5088        emu.set_call_interceptor(move |_, _, site| {
5089            if site.target == library {
5090                Ok(CallInterception::Handled(CallContinuation::Address(0xdead)))
5091            } else {
5092                Ok(CallInterception::PassThrough)
5093            }
5094        });
5095
5096        let err = emu.step().unwrap_err();
5097
5098        assert!(matches!(
5099            err.kind,
5100            EmulatorErrorKind::InvalidBlockAddress(0xdead)
5101        ));
5102    }
5103
5104    #[test]
5105    fn call_interceptor_errors_are_reported_at_call_site() {
5106        let mut ctx = Context::new();
5107        qcode!(
5108            ctx,
5109            "
5110            fn library:
5111            <library_entry>
5112                return at i64 0;
5113
5114            <entry>
5115                call <library>;
5116            "
5117        );
5118
5119        let mut emu = Emulator::from_block(&ctx, entry);
5120        emu.set_call_interceptor(|_, _, _| Err("model failed".into()));
5121
5122        let err = emu.step().unwrap_err();
5123
5124        assert!(matches!(
5125            err.kind,
5126            EmulatorErrorKind::InterceptError(message) if message.as_ref() == "model failed"
5127        ));
5128        assert!(err.ctx.contains("call fn library();"));
5129    }
5130
5131    #[test]
5132    fn interceptor_can_model_state_across_calls() {
5133        let mut ctx = Context::new();
5134        qcode!(
5135            ctx,
5136            "
5137            fn make_object:
5138            <make_object_entry>
5139                return at i64 0;
5140
5141            fn append_byte:
5142            <append_byte_entry>
5143                return at i64 0;
5144
5145            fn function:
5146            <entry>
5147                call <make_object>;
5148            <append>
5149                call <append_byte>;
5150            <done>
5151                return at i64 0;
5152            "
5153        );
5154
5155        let modeled = Arc::new(Mutex::new(Vec::<u8>::new()));
5156        let modeled_for_hook = Arc::clone(&modeled);
5157        let mut emu = Emulator::from_function(&ctx, function);
5158        emu.set_call_interceptor(move |_, _, site| {
5159            let mut model = modeled_for_hook.lock().unwrap();
5160            if site.target == make_object {
5161                model.clear();
5162                Ok(CallInterception::Handled(CallContinuation::Block(append)))
5163            } else if site.target == append_byte {
5164                model.push(0x41);
5165                Ok(CallInterception::Handled(CallContinuation::Block(done)))
5166            } else {
5167                Ok(CallInterception::PassThrough)
5168            }
5169        });
5170
5171        emu.run_function(function).unwrap();
5172
5173        assert_eq!(*modeled.lock().unwrap(), vec![0x41]);
5174    }
5175
5176    // -----------------------------------------------------------------------
5177    // run_function error tests
5178    // -----------------------------------------------------------------------
5179
5180    #[test]
5181    fn branchind_to_unknown_address_returns_error() {
5182        let mut ctx = Context::new();
5183        qcode!(
5184            ctx,
5185            "
5186            fn function:
5187            <entry>
5188                # Branching to literal 0 — no block lives at address 0
5189                goto [i64 0];
5190            "
5191        );
5192
5193        let mut emu = Emulator::from_function(&ctx, function);
5194        let err = emu.run_function(function).unwrap_err();
5195        assert!(matches!(
5196            err.kind,
5197            EmulatorErrorKind::InvalidBlockAddress(0)
5198        ));
5199    }
5200
5201    #[test]
5202    fn error_includes_faulting_instruction_id() {
5203        let mut ctx = Context::new();
5204        qcode!(
5205            ctx,
5206            "
5207            fn function:
5208            <entry>
5209                # Null pointer dereference
5210                %bad_load = load(ram:8, i64 0);
5211                return at i64 0;
5212            "
5213        );
5214
5215        let mut emu = Emulator::from_function(&ctx, function);
5216        let err = emu.run_function(function).unwrap_err();
5217
5218        assert!(
5219            err.ctx.contains(
5220                &Instruction::from_id(&ctx, bad_load)
5221                    .as_statement()
5222                    .to_string()
5223            )
5224        );
5225    }
5226
5227    #[test]
5228    fn error_call_stack_reflects_active_frames_at_fault() {
5229        // Build callee: immediately does a BranchInd to address 0 (always fails)
5230        let mut ctx = Context::new();
5231        qcode!(
5232            ctx,
5233            "
5234            fn callee:
5235            <entry1>
5236                # Branching to literal 0 — no block lives at address 0
5237                goto [i64 0];
5238
5239            fn caller:
5240            <entry2>
5241                call <callee>;
5242            "
5243        );
5244
5245        // Build caller: calls callee
5246        let mut emu = Emulator::from_function(&ctx, caller);
5247        let err = emu.run_function(caller).unwrap_err();
5248
5249        assert!(matches!(
5250            err.kind,
5251            EmulatorErrorKind::InvalidBlockAddress(0)
5252        ));
5253        assert_eq!(emu.call_stack(), &[caller, callee]);
5254    }
5255}