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        if value.size != 10 {
1617            return None;
1618        }
1619        let source = X87DoubleExtended::from_bits(value.as_bits());
1620        let mut loses_info = false;
1621        match size {
1622            4 => {
1623                let converted: rustc_apfloat::StatusAnd<Single> =
1624                    source.convert_r(round, &mut loses_info);
1625                Some((
1626                    SizedValue::from_bits(converted.value.to_bits(), 4),
1627                    converted.status,
1628                ))
1629            }
1630            8 => {
1631                let converted: rustc_apfloat::StatusAnd<Double> =
1632                    source.convert_r(round, &mut loses_info);
1633                Some((
1634                    SizedValue::from_bits(converted.value.to_bits(), 8),
1635                    converted.status,
1636                ))
1637            }
1638            _ => None,
1639        }
1640    }
1641
1642    /// Convert a floating value to a two's-complement integer of `size` bytes
1643    /// under an explicit rounding mode.  A NaN, an infinity or a value outside
1644    /// the destination range is invalid; the architectural replacement value
1645    /// for that case is the caller's business, not this operation's.
1646    fn ieee_to_int(value: SizedValue, size: u128, round: Round) -> Option<(SizedValue, Status)> {
1647        if value.size != 10 {
1648            return None;
1649        }
1650        let size = usize::try_from(size).ok()?;
1651        if !matches!(size, 2 | 4 | 8) {
1652            return None;
1653        }
1654        let mut exact = false;
1655        let converted =
1656            X87DoubleExtended::from_bits(value.as_bits()).to_i128_r(size * 8, round, &mut exact);
1657        let mask = (1u128 << (size * 8)) - 1;
1658        Some((
1659            SizedValue::from_bits(converted.value as u128 & mask, size),
1660            converted.status,
1661        ))
1662    }
1663
1664    fn ieee_flags(status: Status) -> SizedValue {
1665        let mut flags = 0u128;
1666        if status.contains(Status::INVALID_OP) {
1667            flags |= 1;
1668        }
1669        if status.contains(Status::DIV_BY_ZERO) {
1670            flags |= 1 << 2;
1671        }
1672        if status.contains(Status::OVERFLOW) {
1673            flags |= 1 << 3;
1674        }
1675        if status.contains(Status::UNDERFLOW) {
1676            flags |= 1 << 4;
1677        }
1678        if status.contains(Status::INEXACT) {
1679            flags |= 1 << 5;
1680        }
1681        SizedValue::from_bits(flags, 1)
1682    }
1683
1684    /// Interpret the packed-integer SLEIGH user-ops that x86's MMX/SSE
1685    /// constructors leave as `pcodeop` applications. Returns `None` for every
1686    /// other user-op so the generic interpreter stays the implementation.
1687    ///
1688    /// `pavgb`/`pavgw` are applied by the spec to one lane at a time, so they
1689    /// are scalar here. `pmaddwd`/`pmulhuw` receive a whole vector and are
1690    /// width-generic, covering the 8-byte MMX and 16-byte XMM forms alike.
1691    fn interpret_packed_pcode_op(
1692        &mut self,
1693        ctx: &Context<'_>,
1694        insn: &InstructionRef<'_, '_>,
1695        mnemonic: &Mnemonic,
1696    ) -> Result<Option<SizedValue>, EmulatorErrorKind> {
1697        let Mnemonic::PCodeOp(op) = mnemonic else {
1698            return Ok(None);
1699        };
1700        let name = ctx.shared.pcode_ops[op.id].clone();
1701        let func = insn.id.func;
1702
1703        // The significand/exponent split is a pure decomposition of the
1704        // encoding and needs no context at all.
1705        if let [src] = op.args.as_slice() {
1706            let value = self.scalar_value(ctx, src.qualify(func))?;
1707            if value.size != 10 {
1708                return Ok(None);
1709            }
1710            return Ok(match name.as_ref() {
1711                "extract_significand" => Some(SizedValue::from_f80_bits(
1712                    float80::extract_significand(value.as_bits()),
1713                )),
1714                "extract_exponent" => Some(SizedValue::from_f80_bits(
1715                    float80::extract_exponent(value.as_bits()).bits,
1716                )),
1717                _ => None,
1718            });
1719        }
1720
1721        // The three-argument form is the explicit, architecture-neutral IEEE
1722        // interface.  Leave two-argument user-ops on the legacy dispatch below.
1723        if let [lhs, rhs, rounding_mode] = op.args.as_slice() {
1724            let lhs = self.scalar_value(ctx, lhs.qualify(func))?;
1725            let rhs = self.scalar_value(ctx, rhs.qualify(func))?;
1726            let rounding_mode = self.scalar_value(ctx, rounding_mode.qualify(func))?;
1727
1728            // The partial remainder's third operand selects the quotient's
1729            // rounding rule rather than the result's: zero truncates it toward
1730            // zero, non-zero rounds it to nearest even. The remainder itself is
1731            // exact under either rule, so no result rounding mode applies.
1732            if matches!(name.as_ref(), "float_rem_partial" | "float_rem_quotient")
1733                && lhs.size == 10
1734                && rhs.size == 10
1735            {
1736                let to_nearest = rounding_mode.as_bits() != 0;
1737                let result = float80::remainder(lhs.as_bits(), rhs.as_bits(), to_nearest);
1738                return Ok(Some(if name.as_ref() == "float_rem_quotient" {
1739                    // Bits 0-2 hold the low three bits of the quotient's
1740                    // magnitude; bit 3 reports that the reduction was partial,
1741                    // in which case no quotient bits are available and bits 0-2
1742                    // are zero.
1743                    let code = if result.incomplete {
1744                        8
1745                    } else {
1746                        u128::from(result.quotient & 7)
1747                    };
1748                    SizedValue::from_bits(code, 1)
1749                } else {
1750                    SizedValue::from_f80_bits(result.bits)
1751                }));
1752            }
1753
1754            let Some(round) = Self::ieee_rounding_mode(rounding_mode.as_bits()) else {
1755                return Ok(None);
1756            };
1757            let evaluated = match name.as_ref() {
1758                "float_round_to_precision" | "float_round_to_precision_flags" => {
1759                    Self::ieee_round_to_precision(lhs, rhs.as_bits(), round)
1760                }
1761                "float_narrow" | "float_narrow_flags" => {
1762                    Self::ieee_narrow(lhs, rhs.as_bits(), round)
1763                }
1764                "float_to_int" | "float_to_int_flags" => {
1765                    Self::ieee_to_int(lhs, rhs.as_bits(), round)
1766                }
1767                "float_scalb" | "float_scalb_flags" if lhs.size == 10 => {
1768                    let steps = i32::try_from(rhs.as_bits() as i64).unwrap_or(
1769                        if (rhs.as_bits() as i64) < 0 {
1770                            i32::MIN
1771                        } else {
1772                            i32::MAX
1773                        },
1774                    );
1775                    let result = float80::scalb_ieee(lhs.as_bits(), steps, round);
1776                    Some((SizedValue::from_f80_bits(result.bits), result.status))
1777                }
1778                _ => Self::ieee_arithmetic(lhs, rhs, round, name.as_ref()),
1779            };
1780            if let Some((result, status)) = evaluated {
1781                return Ok(Some(if name.ends_with("_flags") {
1782                    Self::ieee_flags(status)
1783                } else {
1784                    result
1785                }));
1786            }
1787            return Ok(None);
1788        }
1789
1790        let [lhs, rhs] = op.args.as_slice() else {
1791            return Ok(None);
1792        };
1793        let lhs = self.scalar_value(ctx, lhs.qualify(func))?;
1794        let rhs = self.scalar_value(ctx, rhs.qualify(func))?;
1795
1796        // The explicit IEEE unary operations take their rounding mode as the
1797        // second operand and report architecture-neutral facts, exactly like
1798        // their binary counterparts.
1799        if lhs.size == 10 {
1800            let unary = Self::ieee_rounding_mode(rhs.as_bits()).and_then(|round| {
1801                Some(match name.as_ref() {
1802                    "float_sqrt" | "float_sqrt_flags" => float80::sqrt_ieee(lhs.as_bits(), round),
1803                    "float_round_to_integral" | "float_round_to_integral_flags" => {
1804                        float80::round_to_integral_ieee(lhs.as_bits(), round)
1805                    }
1806                    "float_log2" | "float_log2_flags" => float80::log2_ieee(lhs.as_bits()),
1807                    "to_bcd" | "to_bcd_flags" => float80::to_bcd(lhs.as_bits(), round),
1808                    _ => return None,
1809                })
1810            });
1811            if let Some(result) = unary {
1812                return Ok(Some(if name.ends_with("_flags") {
1813                    Self::ieee_flags(result.status)
1814                } else if name.starts_with("to_bcd") {
1815                    // The packed decimal is ten bytes of digits, not a float.
1816                    SizedValue::from_bits(result.bits, 10)
1817                } else {
1818                    SizedValue::from_f80_bits(result.bits)
1819                }));
1820            }
1821        }
1822
1823        // Unsigned rounded average of one lane: (a + b + 1) >> 1, computed
1824        // wide enough that the carry out of the lane is kept.
1825        let average = |width: usize| -> Option<SizedValue> {
1826            (lhs.size as usize == width && rhs.size as usize == width).then(|| {
1827                let sum = lhs.as_bits() + rhs.as_bits() + 1;
1828                SizedValue::from_bits(sum >> 1, width)
1829            })
1830        };
1831
1832        let value = match name.as_ref() {
1833            "pavgb" => average(1),
1834            "pavgw" => average(2),
1835            // Unsigned 16x16 multiply per word lane, keeping the high half.
1836            "pmulhuw" => Self::packed_lanes(&lhs, &rhs, 2, |a, b| ((a * b) >> 16) & 0xffff),
1837            // Saturating packed add/subtract. A signed lane clamps to its
1838            // width's bounds; an unsigned lane clamps to zero and its maximum.
1839            "paddsb" => Self::saturating(&lhs, &rhs, 1, true, false),
1840            "paddsw" => Self::saturating(&lhs, &rhs, 2, true, false),
1841            "psubsb" => Self::saturating(&lhs, &rhs, 1, true, true),
1842            "psubsw" => Self::saturating(&lhs, &rhs, 2, true, true),
1843            "paddusb" => Self::saturating(&lhs, &rhs, 1, false, false),
1844            "paddusw" => Self::saturating(&lhs, &rhs, 2, false, false),
1845            "psubusb" => Self::saturating(&lhs, &rhs, 1, false, true),
1846            "psubusw" => Self::saturating(&lhs, &rhs, 2, false, true),
1847            // Signed 16x16 multiplies summed in pairs into each dword lane.
1848            "pmaddwd" => Self::packed_lanes(&lhs, &rhs, 4, |a, b| {
1849                let word =
1850                    |v: u128, half: u32| i64::from(((v >> (half * 16)) & 0xffff) as u16 as i16);
1851                let product = word(a, 0) * word(b, 0) + word(a, 1) * word(b, 1);
1852                u128::from(product as u32)
1853            }),
1854            _ => None,
1855        };
1856        Ok(value)
1857    }
1858
1859    /// Saturating packed add (`subtract` false) or subtract, per `width`-byte
1860    /// lane. `signed` selects signed bounds over unsigned ones.
1861    fn saturating(
1862        lhs: &SizedValue,
1863        rhs: &SizedValue,
1864        width: usize,
1865        signed: bool,
1866        subtract: bool,
1867    ) -> Option<SizedValue> {
1868        let bits = width * 8;
1869        Self::packed_lanes(lhs, rhs, width, |a, b| {
1870            if signed {
1871                let sign =
1872                    |v: u128| (v as i128) - (((v >> (bits - 1)) & 1) as i128) * (1i128 << bits);
1873                let (a, b) = (sign(a), sign(b));
1874                let value = if subtract { a - b } else { a + b };
1875                let max = (1i128 << (bits - 1)) - 1;
1876                let min = -(1i128 << (bits - 1));
1877                (value.clamp(min, max) as u128) & ((1u128 << bits) - 1)
1878            } else if subtract {
1879                a.saturating_sub(b)
1880            } else {
1881                (a + b).min((1u128 << bits) - 1)
1882            }
1883        })
1884    }
1885
1886    /// Apply `lane` to each `width`-byte lane of two equally sized vectors.
1887    /// Returns `None` unless both operands share a width that divides evenly
1888    /// into lanes.
1889    fn packed_lanes(
1890        lhs: &SizedValue,
1891        rhs: &SizedValue,
1892        width: usize,
1893        lane: impl Fn(u128, u128) -> u128,
1894    ) -> Option<SizedValue> {
1895        let size = lhs.size as usize;
1896        if size != rhs.size as usize || size == 0 || !size.is_multiple_of(width) {
1897            return None;
1898        }
1899        let bits = width * 8;
1900        let mask = (1u128 << bits) - 1;
1901        let mut out = 0u128;
1902        for index in 0..size / width {
1903            let shift = index * bits;
1904            let a = (lhs.as_bits() >> shift) & mask;
1905            let b = (rhs.as_bits() >> shift) & mask;
1906            out |= (lane(a, b) & mask) << shift;
1907        }
1908        Some(SizedValue::from_bits(out, size))
1909    }
1910
1911    fn step_with_event(&mut self, ctx: &Context<'_>) -> crate::Result<StepEvent> {
1912        self.memory.configure_spaces(ctx);
1913        let block_id = self.block;
1914        if self.cached_block != Some(block_id) || self.idx == 0 {
1915            // The module only gains types while nothing is part-way through a
1916            // block — lifting happens on block entry — so this rides the same
1917            // refresh as the instruction list rather than paying per step.
1918            self.refresh_sequence_types(ctx);
1919            self.cached_insns.clear();
1920            self.cached_insns
1921                .extend_from_slice(ctx.block(block_id).instruction_ids());
1922            self.cached_block = Some(block_id);
1923        }
1924        // A degenerate block — empty, or exhausted without a terminator — is
1925        // malformed lifter output, not an emulator bug. Report it so a bounded
1926        // consumer (and a VM running lifted-on-demand code) can stop with a
1927        // reason instead of aborting the process.
1928        let Some(&local) = self.cached_insns.get(self.idx) else {
1929            return Err(self.make_empty_block_error(ctx));
1930        };
1931        let insn_id = InstructionId::new(block_id.func, local);
1932        let insn = InstructionRef::from_id(ctx, insn_id);
1933        let id = insn.id;
1934
1935        if let Some(hook) = self.instruction_hook.as_ref() {
1936            hook(&insn, self)
1937        }
1938
1939        // Resolved once and threaded through the step. Each `insn.mnemonic()`
1940        // re-walks `view.instruction(id)`, two registry lookups deep, and the
1941        // step path asked for the same instruction's mnemonic several times.
1942        let mnemonic = insn.mnemonic();
1943
1944        match mnemonic {
1945            Mnemonic::Branch(Branch { target, args }) => {
1946                // Terminator targets are bare body-local indices in the terminator's
1947                // own arena (`id.func`); qualify to the current block's function.
1948                let target = BlockId::new(id.func, *target);
1949                self.bind_block_args(ctx, id.func, target, args)
1950                    .map_err(|kind| self.make_error(ctx, kind))?;
1951                self.block = target;
1952                self.idx = 0;
1953            }
1954
1955            Mnemonic::Call(call) => {
1956                if let Some(event) = self.intercept_call(ctx, block_id, insn_id, call)? {
1957                    return Ok(event);
1958                }
1959                let target =
1960                    require_real_callee(call.target).map_err(|kind| self.make_error(ctx, kind))?;
1961                self.block = FunctionBody::from_id(ctx, target)
1962                    .root()
1963                    .ok_or_else(|| {
1964                        self.make_error(ctx, EmulatorErrorKind::EmptyFunctionRoot(target))
1965                    })?
1966                    .id;
1967                self.idx = 0;
1968                // Remember this call so the matching `Return` can deposit the
1969                // callee's `Return.value` as this call's (aggregate) result.
1970                self.call_site_stack.push(insn_id);
1971                return Ok(StepEvent::DirectCallEntered(target));
1972            }
1973
1974            Mnemonic::TailCall(tc) => {
1975                // A tail call pops our frame and transfers to the callee's entry;
1976                // the callee's `Return` returns directly to *our* caller. Mirror the
1977                // old tail-`Branch`-into-entry behavior: jump to the callee root
1978                // without pushing a call frame.
1979                let target =
1980                    require_real_callee(tc.target).map_err(|kind| self.make_error(ctx, kind))?;
1981                self.block = FunctionBody::from_id(ctx, target)
1982                    .root()
1983                    .ok_or_else(|| {
1984                        self.make_error(ctx, EmulatorErrorKind::EmptyFunctionRoot(target))
1985                    })?
1986                    .id;
1987                self.idx = 0;
1988            }
1989
1990            Mnemonic::Apply(apply) => {
1991                const APPLY_STEP_BUDGET: usize = 100_000;
1992                let target =
1993                    require_real_callee(apply.target).map_err(|kind| self.make_error(ctx, kind))?;
1994                let args = self
1995                    .collect_block_args(ctx, id.func, &apply.args)
1996                    .map_err(|kind| self.make_error(ctx, kind))?;
1997                let root = FunctionBody::from_id(ctx, target)
1998                    .root()
1999                    .ok_or_else(|| {
2000                        self.make_error(ctx, EmulatorErrorKind::EmptyFunctionRoot(target))
2001                    })?
2002                    .id;
2003                let mut nested = StandaloneEmulator::<M>::new_in(root);
2004                nested
2005                    .run_pure(ctx, target, &args, APPLY_STEP_BUDGET)
2006                    .map_err(|e| self.make_error(ctx, e.kind))?;
2007                let ret_value = lambda_return_value(ctx, nested.current_block())
2008                    .ok_or_else(|| self.make_error(ctx, EmulatorErrorKind::ValueError(0)))?;
2009                if let Some(value) = nested.get_value(ctx, ret_value) {
2010                    let size = ctx
2011                        .stored_type_of(ret_value)
2012                        .map(|ty| ctx.shared.types.size_of(ty))
2013                        .unwrap_or(8);
2014                    self.insn_values
2015                        .insert(insn_id, SizedValue::new(value, size));
2016                } else if let ValueId::Instruction(ret_id) = ret_value
2017                    && let Some(agg) = nested.aggregate_values.get(&ret_id).cloned()
2018                {
2019                    // The lambda returns an aggregate (e.g. the result tuple produced
2020                    // by accumulator elimination); propagate it field-wise so the
2021                    // caller's `extract(apply, i)` resolves — mirroring the scalar
2022                    // case above and the `Return` arm's call-result handling.
2023                    self.aggregate_values.insert(insn_id, agg);
2024                }
2025                self.idx += 1;
2026            }
2027
2028            Mnemonic::CBranch(CBranch {
2029                condition,
2030                success_block: target,
2031                success_args,
2032                failure_block: fallthrough,
2033                failure_args,
2034            }) => {
2035                let cond_val = self.get_value(ctx, condition.qualify(id.func)).unwrap();
2036                let target = BlockId::new(id.func, *target);
2037                let fallthrough = BlockId::new(id.func, *fallthrough);
2038                if cond_val != 0 {
2039                    self.bind_block_args(ctx, id.func, target, success_args)
2040                        .map_err(|kind| self.make_error(ctx, kind))?;
2041                    self.block = target;
2042                } else {
2043                    self.bind_block_args(ctx, id.func, fallthrough, failure_args)
2044                        .map_err(|kind| self.make_error(ctx, kind))?;
2045                    self.block = fallthrough;
2046                }
2047                self.idx = 0;
2048            }
2049
2050            Mnemonic::Switch(switch) => {
2051                let value = self
2052                    .get_value(ctx, switch.scrutinee.qualify(id.func))
2053                    .unwrap();
2054                let arm = switch
2055                    .cases
2056                    .iter()
2057                    .find(|case| case.value == value)
2058                    .map(|case| (case.target, &case.args));
2059                let (target, args) = match arm
2060                    .or_else(|| switch.default.map(|target| (target, &switch.default_args)))
2061                {
2062                    Some(arm) => arm,
2063                    // No arm matches and there is no default. A table behind a
2064                    // bounds check is total over its listed values, so arriving
2065                    // here means the guard that guaranteed that was wrong.
2066                    None => {
2067                        return Err(
2068                            self.make_error(ctx, EmulatorErrorKind::InvalidBlockAddress(value))
2069                        );
2070                    }
2071                };
2072                let target = BlockId::new(id.func, target);
2073                self.bind_block_args(ctx, id.func, target, args)
2074                    .map_err(|kind| self.make_error(ctx, kind))?;
2075                self.block = target;
2076                self.idx = 0;
2077            }
2078
2079            Mnemonic::BranchInd(BranchInd { ptr }) => {
2080                let addr = self.get_value(ctx, ptr.qualify(id.func)).unwrap();
2081                let target = self.block_at(ctx, addr).ok_or_else(|| {
2082                    self.make_error(ctx, EmulatorErrorKind::InvalidBlockAddress(addr))
2083                })?;
2084                self.block = target;
2085                self.idx = 0;
2086            }
2087
2088            Mnemonic::CallInd(CallInd { ptr, .. }) => {
2089                let addr = self.get_value(ctx, ptr.qualify(id.func)).unwrap();
2090                let target = self.block_at(ctx, addr).ok_or_else(|| {
2091                    self.make_error(ctx, EmulatorErrorKind::InvalidBlockAddress(addr))
2092                })?;
2093                self.block = target;
2094                self.idx = 0;
2095                self.call_site_stack.push(insn_id);
2096                return Ok(StepEvent::IndirectCallEntered);
2097            }
2098
2099            Mnemonic::Return(Return { ptr, value, .. }) => {
2100                // Deposit the callee's return value as the result of the call that
2101                // entered it: an aggregate (the functional write-set) is copied
2102                // field-wise; a scalar return is copied through. This is what makes
2103                // a caller's `extract(call, i)` see the callee's effects.
2104                if let Some(call_id) = self.call_site_stack.pop() {
2105                    if let Some(LocalValueId::Instruction(src_local)) = value {
2106                        let src = InstructionId::new(id.func, *src_local);
2107                        if let Some(agg) = self.aggregate_values.get(&src).cloned() {
2108                            self.aggregate_values.insert(call_id, agg);
2109                        } else if let Some(scalar) = self.insn_values.get(&src).copied() {
2110                            self.insn_values.insert(call_id, scalar);
2111                        }
2112                    }
2113                    // v2 implicit convention: an `Opaque` (non-regpure) call to a
2114                    // *materialized* callee binds outputs by storing each return-pack
2115                    // slot back to its mapped register (post-mem2reg the callee body
2116                    // may no longer write those registers directly). A regpure site
2117                    // replays the pack itself, so it is skipped.
2118                    self.writeback_materialized_outputs(ctx, call_id, id.func);
2119                }
2120
2121                let addr = self.get_value(ctx, ptr.qualify(id.func)).unwrap();
2122                let target = self.block_at(ctx, addr).ok_or_else(|| {
2123                    self.make_error(ctx, EmulatorErrorKind::InvalidBlockAddress(addr))
2124                })?;
2125                self.block = target;
2126                self.idx = 0;
2127                return Ok(StepEvent::Return);
2128            }
2129
2130            Mnemonic::ReturnValue(_) => {
2131                return Ok(StepEvent::ReturnValue);
2132            }
2133
2134            // Aggregate construction: evaluate each field and stash the field
2135            // vector. Fields are scalar for the register write-set (nested
2136            // aggregates, e.g. the RAM channel, are not modelled here yet).
2137            Mnemonic::Tuple(Tuple { fields }) => {
2138                let fields = fields.clone();
2139                let mut vals: Vec<SizedValue> = Vec::with_capacity(fields.len());
2140                for f in fields {
2141                    let val = {
2142                        let mut tmp = TempInterpreter {
2143                            memory: &mut self.memory,
2144                            literals: &mut self.literal_cache,
2145                            insn_values: &mut self.insn_values,
2146                            block_param_values: &mut self.block_param_values,
2147                            poison_params: &self.poison_params,
2148                            ctx,
2149                        };
2150                        tmp.get_value(f.qualify(id.func))
2151                    };
2152                    vals.push(val.map_err(|kind| self.make_error(ctx, kind))?);
2153                }
2154                self.aggregate_values.insert(insn_id, vals);
2155                self.idx += 1;
2156            }
2157
2158            // Aggregate projection: pull field `index` out of the stashed vector.
2159            Mnemonic::Extract(Extract { agg, index }) => {
2160                let field = match agg {
2161                    LocalValueId::Instruction(agg_local) => self
2162                        .aggregate_values
2163                        .get(&InstructionId::new(id.func, *agg_local))
2164                        .and_then(|v| v.get(*index))
2165                        .copied(),
2166                    // A `map` body's `enumerate` lane arrives as an aggregate
2167                    // block param seeded by `run_map_body`.
2168                    LocalValueId::BlockParam(pid_local) => self
2169                        .block_param_aggregates
2170                        .get(&BlockParamId::new(id.func, *pid_local))
2171                        .and_then(|v| v.get(*index))
2172                        .copied(),
2173                    _ => None,
2174                };
2175                if let Some(field) = field {
2176                    self.insn_values.insert(insn_id, field);
2177                }
2178                self.idx += 1;
2179            }
2180
2181            // Whole-array load: the region snapshot `%l0 = load(ram:{N*esz}, base)`
2182            // that `array_promote` reads once before an original-array scan/map.
2183            // Its result is array-typed, so it lives in `array_values`; a scalar
2184            // load falls through to the generic interpreter below.
2185            Mnemonic::Load(load) if self.is_array_operand(ctx, ValueId::Instruction(insn_id)) => {
2186                let (space, ptr, size) = (load.space, load.ptr.qualify(id.func), load.size);
2187                let addr = self
2188                    .get_value(ctx, ptr)
2189                    .ok_or_else(|| self.make_error(ctx, EmulatorErrorKind::ValueError(0)))?;
2190                let buf = self
2191                    .read_memory(ctx, space.qualify(id.func), addr, size)
2192                    .map_err(|kind| self.make_error(ctx, kind))?;
2193                self.array_values.insert(insn_id, buf);
2194                self.idx += 1;
2195            }
2196
2197            // Whole-array store: the promoted buffer written back to memory in one
2198            // shot (`store(ram, base <- arr)` at loop exit). A scalar store falls
2199            // through to the generic interpreter below.
2200            Mnemonic::Store(store)
2201                if self
2202                    .register_range_store_address(ctx, id.func, store)
2203                    .is_some() =>
2204            {
2205                let address = self
2206                    .register_range_store_address(ctx, id.func, store)
2207                    .expect("guard checked register range store address");
2208                let mut tmp = TempInterpreter {
2209                    memory: &mut self.memory,
2210                    literals: &mut self.literal_cache,
2211                    insn_values: &mut self.insn_values,
2212                    block_param_values: &mut self.block_param_values,
2213                    poison_params: &self.poison_params,
2214                    ctx,
2215                };
2216                let value = tmp.get_value(store.src.qualify(id.func));
2217                let value = value.map_err(|kind| self.make_error(ctx, kind))?;
2218                self.memory
2219                    .write(
2220                        store.space.qualify(id.func),
2221                        SizedValue::from_u64(address),
2222                        store.size,
2223                        value,
2224                    )
2225                    .map_err(|kind| self.make_error(ctx, kind))?;
2226                self.idx += 1;
2227            }
2228
2229            Mnemonic::Store(store) if self.is_array_operand(ctx, store.src.qualify(id.func)) => {
2230                let (space, ptr, src) = (
2231                    store.space,
2232                    store.ptr.qualify(id.func),
2233                    store.src.qualify(id.func),
2234                );
2235                let buf = self
2236                    .resolve_array(ctx, src)
2237                    .ok_or_else(|| self.make_error(ctx, EmulatorErrorKind::ValueError(0)))?;
2238                let addr = self
2239                    .get_value(ctx, ptr)
2240                    .ok_or_else(|| self.make_error(ctx, EmulatorErrorKind::ValueError(0)))?;
2241                self.write_memory(ctx, space.qualify(id.func), addr, &buf)
2242                    .map_err(|kind| self.make_error(ctx, kind))?;
2243                self.idx += 1;
2244            }
2245
2246            // Total left-scan: thread the accumulator through every lane, running
2247            // the (pure) binary body once per element, and materialize the result
2248            // as an array buffer.
2249            Mnemonic::Scan(scan) => {
2250                let scan = scan.clone();
2251                self.eval_scan(ctx, insn_id, &scan)
2252                    .map_err(|kind| self.make_error(ctx, kind))?;
2253                self.idx += 1;
2254            }
2255
2256            // Total map: apply the pure unary body to every source element.
2257            Mnemonic::Map(map) => {
2258                let map = map.clone();
2259                self.eval_map(ctx, insn_id, &map)
2260                    .map_err(|kind| self.make_error(ctx, kind))?;
2261                self.idx += 1;
2262            }
2263
2264            // Array slice: `arr[start:start+size]` on an array-typed source is a
2265            // sub-buffer (e.g. `l0[1..]`, the original-array scan source), kept in
2266            // the `array_values` domain. A scalar `Range` (bit-field extract) falls
2267            // through to the generic interpreter below.
2268            Mnemonic::Range(range) if self.is_array_operand(ctx, range.src.qualify(id.func)) => {
2269                let (src, start, size) = (range.src.qualify(id.func), range.start, range.size);
2270                let buf = self
2271                    .resolve_array(ctx, src)
2272                    .ok_or_else(|| self.make_error(ctx, EmulatorErrorKind::ValueError(0)))?;
2273                let end = (start + size).min(buf.len());
2274                let slice = buf.get(start..end).unwrap_or(&[]).to_vec();
2275                self.array_values.insert(insn_id, slice);
2276                self.idx += 1;
2277            }
2278
2279            // The sequence intrinsics whose value is an *array* (or a lane read out
2280            // of one) live in the `array_values` domain rather than scalar
2281            // `insn_values`; every other (scalar) intrinsic falls through to the
2282            // generic interpreter.
2283            Mnemonic::Intrinsic(app) if is_array_intrinsic(app.id.name()) => {
2284                let name = app.id.name();
2285                let args: Vec<ValueId> = app.args.iter().map(|a| a.qualify(id.func)).collect();
2286                self.eval_array_intrinsic(ctx, insn_id, name, &args)
2287                    .map_err(|kind| self.make_error(ctx, kind))?;
2288                self.idx += 1;
2289            }
2290
2291            _ => {
2292                if let Some(value) = self
2293                    .interpret_packed_pcode_op(ctx, &insn, mnemonic)
2294                    .map_err(|kind| self.make_error(ctx, kind))?
2295                {
2296                    self.insn_values.insert(id, value);
2297                    self.idx += 1;
2298                    return Ok(StepEvent::Normal);
2299                }
2300                let mut tmp = TempInterpreter {
2301                    memory: &mut self.memory,
2302                    literals: &mut self.literal_cache,
2303                    insn_values: &mut self.insn_values,
2304                    block_param_values: &mut self.block_param_values,
2305                    poison_params: &self.poison_params,
2306                    ctx,
2307                };
2308                if let Some(value) = tmp.interpret(insn, mnemonic)? {
2309                    self.insn_values.insert(id, value);
2310                }
2311                self.idx += 1;
2312            }
2313        }
2314
2315        Ok(StepEvent::Normal)
2316    }
2317
2318    pub fn step(&mut self, ctx: &Context<'_>) -> crate::Result<()> {
2319        self.step_with_event(ctx).map(|_| ())
2320    }
2321
2322    pub fn run_block(&mut self, ctx: &Context<'_>) -> crate::Result<()> {
2323        loop {
2324            self.step(ctx)?;
2325            if self.idx == 0 {
2326                break;
2327            }
2328        }
2329        Ok(())
2330    }
2331
2332    /// Runs blocks until the current block starts at `addr`.
2333    pub fn run_until(&mut self, ctx: &Context<'_>, addr: u64) -> crate::Result<()> {
2334        let target = self
2335            .block_at(ctx, addr)
2336            .ok_or_else(|| self.make_error(ctx, EmulatorErrorKind::UnknownAddress(addr)))?;
2337        while self.block != target {
2338            self.run_block(ctx)?;
2339        }
2340        Ok(())
2341    }
2342
2343    /// Runs the given function from its root block, stopping when the
2344    /// outermost `Return` is reached (without executing it).
2345    /// Nested calls are tracked via `call_depth` so inner returns are handled normally.
2346    /// The `call_stack` field is updated throughout execution.
2347    /// Seed a function's entry-block params with the values of the registers
2348    /// they were promoted from.
2349    ///
2350    /// `mem2reg` turns each register that is live-in to a function into a
2351    /// root-block parameter named after that register — these are the function's
2352    /// arguments. Entering the function (at the top level or via a call) binds
2353    /// those params from the current register file, so the callee receives the
2354    /// caller's register state through the calling convention. Params with no
2355    /// matching register (e.g. promoted stack slots) are left unbound.
2356    /// v2 implicit binding convention: store a materialized callee's return-pack
2357    /// slots back into their mapped registers on return from an `Opaque` call
2358    /// site. A `regpure` site is skipped (it replays the pack in its own body),
2359    /// as is any callee that is not materialized (no mapping to write back).
2360    fn writeback_materialized_outputs(
2361        &mut self,
2362        ctx: &Context<'_>,
2363        call_id: InstructionId,
2364        callee: FunctionId,
2365    ) {
2366        if call_is_regpure(ctx, call_id) {
2367            return;
2368        }
2369        let outputs = match &FunctionBody::from_id(ctx, callee).effects().register {
2370            qcode::value::RegisterChannelState::Materialized(map) => map.outputs.clone(),
2371            _ => return,
2372        };
2373        let Some(agg) = self.aggregate_values.get(&call_id).cloned() else {
2374            return;
2375        };
2376        for (field, &reg) in agg.iter().zip(&outputs) {
2377            let _ = self.set_varnode_u128(ctx, reg, field.as_bits());
2378        }
2379    }
2380
2381    fn seed_entry_params(&mut self, ctx: &Context<'_>, func: FunctionId) {
2382        let Some(root) = FunctionBody::from_id(ctx, func).root() else {
2383            return;
2384        };
2385        let root_id = root.id;
2386        enum Seed {
2387            Reg(VarnodeId),
2388            /// A materialized global value input: the param's origin is its
2389            /// address literal, and its value is the *contents* at that address
2390            /// (the RAM channel threads `mem[addr]` by value — see
2391            /// `argpromote::ram`'s global materialization). Implicit binding
2392            /// therefore reads memory at the literal, not the literal itself.
2393            Lit(u64),
2394        }
2395        let params: Vec<(BlockParamId, Option<Seed>, usize)> = BasicBlock::from_id(ctx, root_id)
2396            .params()
2397            .map(|param| {
2398                let src = param
2399                    .name()
2400                    .and_then(|name| ctx.get_named(name))
2401                    .and_then(|value| match value {
2402                        ValueId::Varnode(id) => Some(Seed::Reg(id)),
2403                        _ => None,
2404                    })
2405                    .or_else(|| match param.origin() {
2406                        Some(ValueId::Literal(_)) => {
2407                            let ValueRef::Literal(lit) = ValueRef::new(param.origin()?, ctx) else {
2408                                return None;
2409                            };
2410                            Some(Seed::Lit(lit.value()))
2411                        }
2412                        _ => None,
2413                    });
2414                (param.id, src, param.size())
2415            })
2416            .collect();
2417        for (param_id, src, size) in params {
2418            let value = match src {
2419                Some(Seed::Reg(varnode_id)) => self.read_varnode(ctx, varnode_id),
2420                Some(Seed::Lit(addr)) => {
2421                    // The literal is the global's *address*; the param carries the
2422                    // value stored there. Seed from memory (little-endian, param
2423                    // width) rather than the raw literal.
2424                    let space = ctx.shared.default_space;
2425                    self.read_memory(ctx, space, addr, size).ok().map(|bytes| {
2426                        let mut buf = [0u8; 8];
2427                        let n = bytes.len().min(8);
2428                        buf[..n].copy_from_slice(&bytes[..n]);
2429                        u64::from_le_bytes(buf)
2430                    })
2431                }
2432                None => None,
2433            };
2434            if let Some(value) = value {
2435                self.block_param_values
2436                    .insert(param_id, SizedValue::new(value, size));
2437            }
2438        }
2439    }
2440
2441    /// Bind a functionalized (`pure_reg`) callee's entry params positionally from
2442    /// the call's arguments, evaluated in the *caller's* frame.
2443    ///
2444    /// `argpromote_registers` makes such a callee a pure value function whose
2445    /// inputs flow through `Call.args` (not ambient register state), so the args
2446    /// are the source of truth — this replaces the register-file seeding
2447    /// [`seed_entry_params`](Self::seed_entry_params) does for conventional
2448    /// callees. The arg/param alignment invariant (`arg[i] ↔ param[i]`, built in
2449    /// lockstep by argpromote and preserved by mem2reg + `remove_entry_param`)
2450    /// makes the positional binding sound. Every arg is read before any param is
2451    /// written, so a self-recursive call still sees the caller's values.
2452    fn bind_entry_params_from_args(
2453        &mut self,
2454        ctx: &Context<'_>,
2455        call_id: InstructionId,
2456        target: FunctionId,
2457    ) {
2458        let args = match ctx.get_insn(call_id).mnemonic() {
2459            Mnemonic::Call(call) => call.args.clone(),
2460            _ => return,
2461        };
2462        let Some(root) = FunctionBody::from_id(ctx, target).root() else {
2463            return;
2464        };
2465        let params: Vec<(BlockParamId, usize)> = BasicBlock::from_id(ctx, root.id)
2466            .params()
2467            .map(|p| (p.id, p.size()))
2468            .collect();
2469        if args.len() != params.len() {
2470            // The alignment invariant is violated; fall back to register seeding
2471            // rather than mis-bind by position.
2472            self.seed_entry_params(ctx, target);
2473            return;
2474        }
2475        // Evaluate every arg in the caller frame first (recursion-safe), then bind.
2476        let values: Vec<SizedValue> = args
2477            .iter()
2478            .zip(&params)
2479            .map(|(&arg, &(_, size))| {
2480                let raw = self.get_value(ctx, arg.qualify(call_id.func)).unwrap_or(0);
2481                SizedValue::new(raw, size)
2482            })
2483            .collect();
2484        for ((param_id, _), value) in params.into_iter().zip(values) {
2485            self.block_param_values.insert(param_id, value);
2486        }
2487    }
2488
2489    pub fn run_function(&mut self, ctx: &Context<'_>, func: FunctionId) -> crate::Result<()> {
2490        let root = FunctionBody::from_id(ctx, func)
2491            .root()
2492            .ok_or_else(|| self.make_error(ctx, EmulatorErrorKind::EmptyFunctionRoot(func)))?
2493            .id;
2494        self.block = root;
2495        self.idx = 0;
2496        self.call_stack.push(func);
2497        self.seed_entry_params(ctx, func);
2498
2499        let mut call_depth: i32 = 0;
2500
2501        let result = loop {
2502            let insn_ids = BasicBlock::from_id(ctx, self.block)
2503                .instruction_ids()
2504                .to_vec();
2505            let insn = InstructionRef::from_id(ctx, insn_ids[self.idx]);
2506
2507            if matches!(insn.mnemonic(), Mnemonic::Return(_)) && call_depth == 0 {
2508                break Ok(());
2509            }
2510
2511            match self.step_with_event(ctx)? {
2512                StepEvent::DirectCallEntered(target) => {
2513                    call_depth += 1;
2514                    self.call_stack.push(target);
2515                    // Dual binding convention (argpromote v2): a `regpure`-tagged
2516                    // call site passes its inputs explicitly through `Call.args`
2517                    // (bound positionally); an `Opaque` (implicit) call — and a
2518                    // conventional callee — reads them from the register file the
2519                    // calling convention set up. The legacy `pure_reg` flag is
2520                    // still honored during the migration.
2521                    let regpure_site = self
2522                        .call_site_stack
2523                        .last()
2524                        .copied()
2525                        .is_some_and(|call_id| call_is_regpure(ctx, call_id));
2526                    if regpure_site || FunctionBody::from_id(ctx, target).is_reg_materialized() {
2527                        if let Some(&call_id) = self.call_site_stack.last() {
2528                            self.bind_entry_params_from_args(ctx, call_id, target);
2529                        }
2530                    } else {
2531                        self.seed_entry_params(ctx, target);
2532                    }
2533                }
2534                StepEvent::IndirectCallEntered => {
2535                    call_depth += 1;
2536                    // Infer the callee from the block we landed in.
2537                    if let Some(parent) = BasicBlock::from_id(ctx, self.block).parent() {
2538                        let callee = parent.id;
2539                        self.call_stack.push(callee);
2540                        self.seed_entry_params(ctx, callee);
2541                    }
2542                }
2543                StepEvent::Return | StepEvent::ReturnValue => {
2544                    self.call_stack.pop();
2545                    call_depth -= 1;
2546                }
2547                StepEvent::Normal | StepEvent::InterceptedCall => {}
2548            }
2549        };
2550
2551        self.call_stack.pop(); // pop the outermost function
2552        result
2553    }
2554
2555    /// Emulate a **pure** function in isolation: bind its root params positionally
2556    /// from `args` (so symbolic caller inputs can be passed an arbitrary poison
2557    /// value) and run to the first top-level `Return` *without executing it*,
2558    /// leaving the body's computed values readable via [`get_value`](Self::get_value)
2559    /// at the block returned by [`current_block`](Self::current_block).
2560    ///
2561    /// `args` must align with the root params index-for-index (the `pure_reg`
2562    /// call interface). The run is bounded by `max_steps`; exceeding it yields
2563    /// [`EmulatorErrorKind::StepBudgetExceeded`]. Intended for v1 **leaf** pure
2564    /// functions (no nested calls), so call bookkeeping is intentionally minimal.
2565    pub fn run_pure(
2566        &mut self,
2567        ctx: &Context<'_>,
2568        func: FunctionId,
2569        args: &[SizedValue],
2570        max_steps: usize,
2571    ) -> crate::Result<()> {
2572        let opts: Vec<Option<SizedValue>> = args.iter().map(|&v| Some(v)).collect();
2573        self.run_pure_partial(ctx, func, &opts, max_steps)
2574    }
2575
2576    /// Like [`run_pure`](Self::run_pure), but each positional argument may be
2577    /// [`None`] to bind that root param to **poison** (a symbolic value with
2578    /// undefined bits). Reading a poison param during emulation is a hard error
2579    /// (`PoisonRead`), so a consumer such as pure-call folding bails when the
2580    /// result actually depends on a symbolic argument, rather than computing on a
2581    /// bogus concrete value (argpromote v2, `ARGPROMOTE_REGISTERS_V2.md`).
2582    pub fn run_pure_partial(
2583        &mut self,
2584        ctx: &Context<'_>,
2585        func: FunctionId,
2586        args: &[Option<SizedValue>],
2587        max_steps: usize,
2588    ) -> crate::Result<()> {
2589        let root = FunctionBody::from_id(ctx, func)
2590            .root()
2591            .ok_or_else(|| self.make_error(ctx, EmulatorErrorKind::EmptyFunctionRoot(func)))?
2592            .id;
2593        self.block = root;
2594        self.idx = 0;
2595        self.call_stack.push(func);
2596
2597        // Bind root params positionally from `args`: a concrete `Some(v)` seeds
2598        // the param value, a `None` marks it poison (read ⇒ hard error).
2599        let param_ids: Vec<BlockParamId> = BasicBlock::from_id(ctx, root)
2600            .params()
2601            .map(|p| p.id)
2602            .collect();
2603        for (param_id, arg) in param_ids.into_iter().zip(args) {
2604            match arg {
2605                Some(value) => {
2606                    self.block_param_values.insert(param_id, *value);
2607                }
2608                None => {
2609                    self.poison_params.insert(param_id);
2610                }
2611            }
2612        }
2613
2614        self.drive_to_return(ctx, root, func, max_steps)
2615    }
2616
2617    /// Like [`run_pure`](Self::run_pure), but for a `map` body — its leading
2618    /// element param may be an **aggregate** (the `enumerate` `(index, elem)`
2619    /// lane), seeded so the body's `Extract`s on it resolve. `args` align with
2620    /// the root params index-for-index: a [`BodyArg::Scalar`] seeds a scalar
2621    /// param, a [`BodyArg::Aggregate`] seeds an `Extract`-able tuple param.
2622    /// Whether `id` is an array/list-typed value (routed through
2623    /// [`array_values`](Self::array_values) rather than scalar `insn_values`).
2624    /// Refreshes [`sequence_types`](Self::sequence_types) when the module has
2625    /// gained types since it was last answered. The probe is lock-free; only a
2626    /// genuine change pays for the locked question behind it.
2627    fn refresh_sequence_types(&mut self, ctx: &Context<'_>) {
2628        let published = ctx.shared.types.published_len();
2629        if self.sequence_types_checked_at == Some(published) {
2630            return;
2631        }
2632        self.sequence_types = ctx.shared.types.has_sequence_types();
2633        self.sequence_types_checked_at = Some(published);
2634    }
2635
2636    fn is_array_operand(&self, ctx: &Context<'_>, id: ValueId) -> bool {
2637        // Nothing in this module is sequence-typed, so no operand can be.
2638        if !self.sequence_types {
2639            return false;
2640        }
2641        match ctx.stored_type_of(id) {
2642            Some(ty) => {
2643                ctx.shared.types.array_of(ty).is_some() || ctx.shared.types.list_of(ty).is_some()
2644            }
2645            None => false,
2646        }
2647    }
2648
2649    /// Resolve an array-typed operand to its little-endian byte buffer: a `Bytes`
2650    /// blob's data, a previously-computed `array_values` entry, or a short array
2651    /// materialized as a scalar literal/result.
2652    fn resolve_array(&mut self, ctx: &Context<'_>, id: ValueId) -> Option<Vec<u8>> {
2653        match id {
2654            ValueId::Bytes(b) => Some(ctx.shared.values.bytes[b].data.clone()),
2655            ValueId::Instruction(i) => self
2656                .array_values
2657                .get(&i)
2658                .cloned()
2659                .or_else(|| self.get_value_bytes(ctx, id)),
2660            ValueId::Literal(_) => self.get_value_bytes(ctx, id),
2661            // A root/block param bound by `run_pure` (e.g. an argpromote-minted
2662            // `[i8;N]` array param): its little-endian bytes come from the bound
2663            // `SizedValue`. Defensive width check — a short buffer must fail
2664            // resolution rather than silently produce clamped `Range` slices for
2665            // callers that lack the projection guarantee.
2666            ValueId::BlockParam(_) => {
2667                let bytes = self.get_value_bytes(ctx, id)?;
2668                let ty_size = ctx
2669                    .stored_type_of(id)
2670                    .map(|ty| ctx.shared.types.size_of(ty))?;
2671                (bytes.len() == ty_size).then_some(bytes)
2672            }
2673            _ => None,
2674        }
2675    }
2676
2677    /// Evaluate one array-valued (or lane-reading) sequence intrinsic, depositing
2678    /// its result in `array_values` (arrays) or `insn_values` (`at`).
2679    fn eval_array_intrinsic(
2680        &mut self,
2681        ctx: &Context<'_>,
2682        insn_id: InstructionId,
2683        name: &str,
2684        args: &[ValueId],
2685    ) -> Result<(), EmulatorErrorKind> {
2686        match name {
2687            "iota" => {
2688                let n = self
2689                    .get_value(ctx, args[0])
2690                    .ok_or(EmulatorErrorKind::ValueError(0))?;
2691                let mut buf = Vec::with_capacity(n as usize * 8);
2692                for i in 0..n {
2693                    buf.extend_from_slice(&i.to_le_bytes());
2694                }
2695                self.array_values.insert(insn_id, buf);
2696            }
2697            "singleton" => {
2698                let buf = self
2699                    .get_value_bytes(ctx, args[0])
2700                    .ok_or(EmulatorErrorKind::ValueError(0))?;
2701                self.array_values.insert(insn_id, buf);
2702            }
2703            "concat" => {
2704                let mut a = self
2705                    .resolve_array(ctx, args[0])
2706                    .ok_or(EmulatorErrorKind::ValueError(0))?;
2707                let b = self
2708                    .resolve_array(ctx, args[1])
2709                    .ok_or(EmulatorErrorKind::ValueError(0))?;
2710                a.extend_from_slice(&b);
2711                self.array_values.insert(insn_id, a);
2712            }
2713            "insert" => {
2714                let mut buf = self
2715                    .resolve_array(ctx, args[0])
2716                    .ok_or(EmulatorErrorKind::ValueError(0))?;
2717                let i = self
2718                    .get_value(ctx, args[1])
2719                    .ok_or(EmulatorErrorKind::ValueError(0))? as usize;
2720                let vbytes = self
2721                    .get_value_bytes(ctx, args[2])
2722                    .ok_or(EmulatorErrorKind::ValueError(0))?;
2723                let esz = vbytes.len();
2724                let off = i * esz;
2725                if off + esz <= buf.len() {
2726                    buf[off..off + esz].copy_from_slice(&vbytes);
2727                }
2728                self.array_values.insert(insn_id, buf);
2729            }
2730            "enumerate" => {
2731                // `enumerate(arr) = [(index: i64, elem: T); N]`, materialized only
2732                // over a fixed array (or bounded list). A length-erased unbounded
2733                // list has no concrete count, so bail recoverably rather than
2734                // fabricate one — matching `enumerate`'s deferred `eval`.
2735                let src_ty = ctx
2736                    .stored_type_of(args[0])
2737                    .ok_or(EmulatorErrorKind::ValueError(0))?;
2738                if matches!(ctx.shared.types.list_of(src_ty), Some((_, None))) {
2739                    return Err(EmulatorErrorKind::UnsupportedIntrinsic(Box::from(
2740                        "enumerate",
2741                    )));
2742                }
2743                let in_elem = ctx
2744                    .shared
2745                    .types
2746                    .seq_elem_of(src_ty)
2747                    .ok_or(EmulatorErrorKind::ValueError(0))?;
2748                let isz = ctx.shared.types.size_of(in_elem).max(1);
2749                let buf = self
2750                    .resolve_array(ctx, args[0])
2751                    .ok_or(EmulatorErrorKind::ValueError(0))?;
2752                // The `(index, elem)` result tuple is a *structural* aggregate:
2753                // its fields are addressed by index, not byte offset, so lay them
2754                // out sequentially by field size (field 0 = i64 index, field 1 =
2755                // elem). This is the same layout `eval_scan` splits back out.
2756                let tuple_ty = ctx
2757                    .stored_type_of(ValueId::Instruction(insn_id))
2758                    .and_then(|ty| ctx.shared.types.seq_elem_of(ty))
2759                    .ok_or(EmulatorErrorKind::ValueError(0))?;
2760                let (idx_sz, elem_off) = {
2761                    let fields = ctx
2762                        .shared
2763                        .types
2764                        .aggregate_fields(tuple_ty)
2765                        .ok_or(EmulatorErrorKind::ValueError(0))?;
2766                    let [idx_f, _elem_f] = fields else {
2767                        return Err(EmulatorErrorKind::ValueError(0));
2768                    };
2769                    let idx_sz = ctx.shared.types.size_of(idx_f.type_id).min(8);
2770                    (idx_sz, idx_sz)
2771                };
2772                let tsz = idx_sz + isz;
2773                let count = buf.len() / isz;
2774                let mut out = vec![0u8; count * tsz];
2775                for i in 0..count {
2776                    let base = i * tsz;
2777                    let idx_bytes = (i as u64).to_le_bytes();
2778                    out[base..base + idx_sz].copy_from_slice(&idx_bytes[..idx_sz]);
2779                    out[base + elem_off..base + elem_off + isz]
2780                        .copy_from_slice(&buf[i * isz..i * isz + isz]);
2781                }
2782                self.array_values.insert(insn_id, out);
2783            }
2784            "at" => {
2785                let buf = self
2786                    .resolve_array(ctx, args[0])
2787                    .ok_or(EmulatorErrorKind::ValueError(0))?;
2788                let i = self
2789                    .get_value(ctx, args[1])
2790                    .ok_or(EmulatorErrorKind::ValueError(0))? as usize;
2791                let esz = ctx
2792                    .stored_type_of(ValueId::Instruction(insn_id))
2793                    .map(|ty| ctx.shared.types.size_of(ty))
2794                    .unwrap_or(8);
2795                let off = i * esz;
2796                let lane = buf
2797                    .get(off..off + esz)
2798                    .ok_or(EmulatorErrorKind::ValueError(0))?;
2799                self.insn_values
2800                    .insert(insn_id, SizedValue::from_bits(le_bits(lane), esz));
2801            }
2802            other => panic!("eval_array_intrinsic called on non-array intrinsic `{other}`"),
2803        }
2804        Ok(())
2805    }
2806
2807    /// Thread a scan's accumulator across every lane of its source array, running
2808    /// the pure binary body `(acc, elem) -> acc'` once per element in a fresh
2809    /// nested emulator, and store the concatenated per-step accumulators as the
2810    /// result array buffer.
2811    fn eval_scan(
2812        &mut self,
2813        ctx: &Context<'_>,
2814        insn_id: InstructionId,
2815        scan: &Scan,
2816    ) -> Result<(), EmulatorErrorKind> {
2817        const SCAN_STEP_BUDGET: usize = 100_000;
2818
2819        let src = self
2820            .resolve_array(ctx, scan.src.qualify(insn_id.func))
2821            .ok_or(EmulatorErrorKind::ValueError(0))?;
2822        // Element sizes come from the operand/result element *types* (which are
2823        // known even for a length-erased `[T;*]` result); the lane count is the
2824        // source buffer's length in input elements. This handles both a folded
2825        // fixed-array source and a symbolic-length `iota`.
2826        let in_elem = ctx
2827            .stored_type_of(scan.src.qualify(insn_id.func))
2828            .and_then(|ty| ctx.shared.types.seq_elem_of(ty))
2829            .ok_or(EmulatorErrorKind::ValueError(0))?;
2830        let isz = ctx.shared.types.size_of(in_elem).max(1);
2831        let out_elem = ctx
2832            .stored_type_of(ValueId::Instruction(insn_id))
2833            .and_then(|ty| ctx.shared.types.seq_elem_of(ty))
2834            .ok_or(EmulatorErrorKind::ValueError(0))?;
2835        let osz = ctx.shared.types.size_of(out_elem);
2836        let count = src.len() / isz;
2837        let body = require_real_callee(scan.body)?;
2838        if count == 0 {
2839            self.array_values.insert(insn_id, Vec::new());
2840            return Ok(());
2841        }
2842
2843        // Loop-invariant captures, resolved once as scalars.
2844        let capture_args: Vec<BodyArg> = scan
2845            .captures
2846            .iter()
2847            .map(|&c| {
2848                let v = self
2849                    .get_value(ctx, c.qualify(insn_id.func))
2850                    .ok_or(EmulatorErrorKind::ValueError(0))?;
2851                let sz = ctx
2852                    .stored_type_of(c.qualify(insn_id.func))
2853                    .map(|ty| ctx.shared.types.size_of(ty))
2854                    .unwrap_or(8);
2855                Ok(BodyArg::Scalar(SizedValue::new(v, sz)))
2856            })
2857            .collect::<Result<_, EmulatorErrorKind>>()?;
2858
2859        let init = self
2860            .get_value(ctx, scan.init.qualify(insn_id.func))
2861            .ok_or(EmulatorErrorKind::ValueError(0))?;
2862        let mut acc = SizedValue::new(init, osz);
2863
2864        let root = FunctionBody::from_id(ctx, body)
2865            .root()
2866            .ok_or(EmulatorErrorKind::EmptyFunctionRoot(body))?
2867            .id;
2868
2869        // When the source element is a tuple (the `enumerate` `(index, elem)`
2870        // lane), each lane is passed as an aggregate so the body's `Extract`s
2871        // resolve; a plain scalar element is passed as-is. The tuple is a
2872        // structural aggregate (fields addressed by index), so its bytes are laid
2873        // out sequentially by field size — the same layout `enumerate` writes.
2874        let elem_fields: Option<Vec<(usize, usize)>> =
2875            ctx.shared.types.aggregate_fields(in_elem).map(|fs| {
2876                let mut off = 0;
2877                fs.iter()
2878                    .map(|f| {
2879                        let sz = ctx.shared.types.size_of(f.type_id);
2880                        let field = (off, sz);
2881                        off += sz;
2882                        field
2883                    })
2884                    .collect()
2885            });
2886
2887        let mut out = Vec::with_capacity(count * osz);
2888        for k in 0..count {
2889            let elem = &src[k * isz..k * isz + isz];
2890            let elem_arg = match &elem_fields {
2891                Some(fields) => BodyArg::Aggregate(
2892                    fields
2893                        .iter()
2894                        .map(|&(off, sz)| SizedValue::from_bits(le_bits(&elem[off..off + sz]), sz))
2895                        .collect(),
2896                ),
2897                None => BodyArg::Scalar(SizedValue::from_bits(le_bits(elem), isz)),
2898            };
2899            let mut body_args = Vec::with_capacity(2 + capture_args.len());
2900            body_args.push(BodyArg::Scalar(acc));
2901            body_args.push(elem_arg);
2902            body_args.extend(capture_args.iter().cloned());
2903
2904            let mut emu = StandaloneEmulator::new(root);
2905            emu.run_map_body(ctx, body, &body_args, SCAN_STEP_BUDGET)
2906                .map_err(|e| e.kind)?;
2907            let ret = body_return_value(ctx, emu.current_block())
2908                .ok_or(EmulatorErrorKind::ValueError(0))?;
2909            let mut lane = emu
2910                .get_value_bytes(ctx, ret)
2911                .ok_or(EmulatorErrorKind::ValueError(0))?;
2912            lane.resize(osz, 0);
2913            acc = SizedValue::from_bits(le_bits(&lane), osz);
2914            out.extend_from_slice(&lane);
2915        }
2916        self.array_values.insert(insn_id, out);
2917        Ok(())
2918    }
2919
2920    /// Total map: run the (pure) unary body once per source element and
2921    /// materialize the results as an array buffer. Mirrors [`Self::eval_scan`]
2922    /// without the threaded accumulator.
2923    fn eval_map(
2924        &mut self,
2925        ctx: &Context<'_>,
2926        insn_id: InstructionId,
2927        map: &qcode::value::insn::Map,
2928    ) -> Result<(), EmulatorErrorKind> {
2929        const MAP_STEP_BUDGET: usize = 100_000;
2930
2931        let src = self
2932            .resolve_array(ctx, map.src.qualify(insn_id.func))
2933            .ok_or(EmulatorErrorKind::ValueError(0))?;
2934        let in_elem = ctx
2935            .stored_type_of(map.src.qualify(insn_id.func))
2936            .and_then(|ty| ctx.shared.types.seq_elem_of(ty))
2937            .ok_or(EmulatorErrorKind::ValueError(0))?;
2938        let isz = ctx.shared.types.size_of(in_elem).max(1);
2939        let out_elem = ctx
2940            .stored_type_of(ValueId::Instruction(insn_id))
2941            .and_then(|ty| ctx.shared.types.seq_elem_of(ty))
2942            .ok_or(EmulatorErrorKind::ValueError(0))?;
2943        let osz = ctx.shared.types.size_of(out_elem);
2944        let count = src.len() / isz;
2945        let body = require_real_callee(map.body)?;
2946
2947        let capture_args: Vec<BodyArg> = map
2948            .captures
2949            .iter()
2950            .map(|&c| {
2951                let v = self
2952                    .get_value(ctx, c.qualify(insn_id.func))
2953                    .ok_or(EmulatorErrorKind::ValueError(0))?;
2954                let sz = ctx
2955                    .stored_type_of(c.qualify(insn_id.func))
2956                    .map(|ty| ctx.shared.types.size_of(ty))
2957                    .unwrap_or(8);
2958                Ok(BodyArg::Scalar(SizedValue::new(v, sz)))
2959            })
2960            .collect::<Result<_, EmulatorErrorKind>>()?;
2961
2962        // The element may be an `enumerate` tuple `(index, elem)`; pass it as an
2963        // aggregate so the body's `Extract`s resolve (same layout as `eval_scan`).
2964        let elem_fields: Option<Vec<(usize, usize)>> =
2965            ctx.shared.types.aggregate_fields(in_elem).map(|fs| {
2966                let mut off = 0;
2967                fs.iter()
2968                    .map(|f| {
2969                        let sz = ctx.shared.types.size_of(f.type_id);
2970                        let field = (off, sz);
2971                        off += sz;
2972                        field
2973                    })
2974                    .collect()
2975            });
2976
2977        let mut out = Vec::with_capacity(count * osz);
2978        for k in 0..count {
2979            let elem = &src[k * isz..k * isz + isz];
2980            let elem_arg = match &elem_fields {
2981                Some(fields) => BodyArg::Aggregate(
2982                    fields
2983                        .iter()
2984                        .map(|&(off, sz)| SizedValue::from_bits(le_bits(&elem[off..off + sz]), sz))
2985                        .collect(),
2986                ),
2987                None => BodyArg::Scalar(SizedValue::from_bits(le_bits(elem), isz)),
2988            };
2989            let mut body_args = Vec::with_capacity(1 + capture_args.len());
2990            body_args.push(elem_arg);
2991            body_args.extend(capture_args.iter().cloned());
2992
2993            let mut emu = StandaloneEmulator::new(
2994                FunctionBody::from_id(ctx, body)
2995                    .root()
2996                    .ok_or(EmulatorErrorKind::EmptyFunctionRoot(body))?
2997                    .id,
2998            );
2999            emu.run_map_body(ctx, body, &body_args, MAP_STEP_BUDGET)
3000                .map_err(|e| e.kind)?;
3001            let ret = body_return_value(ctx, emu.current_block())
3002                .ok_or(EmulatorErrorKind::ValueError(0))?;
3003            let mut lane = emu
3004                .get_value_bytes(ctx, ret)
3005                .ok_or(EmulatorErrorKind::ValueError(0))?;
3006            lane.resize(osz, 0);
3007            out.extend_from_slice(&lane);
3008        }
3009        self.array_values.insert(insn_id, out);
3010        Ok(())
3011    }
3012
3013    pub fn run_map_body(
3014        &mut self,
3015        ctx: &Context<'_>,
3016        func: FunctionId,
3017        args: &[BodyArg],
3018        max_steps: usize,
3019    ) -> crate::Result<()> {
3020        let root = FunctionBody::from_id(ctx, func)
3021            .root()
3022            .ok_or_else(|| self.make_error(ctx, EmulatorErrorKind::EmptyFunctionRoot(func)))?
3023            .id;
3024        self.block = root;
3025        self.idx = 0;
3026        self.call_stack.push(func);
3027
3028        let param_ids: Vec<BlockParamId> = BasicBlock::from_id(ctx, root)
3029            .params()
3030            .map(|p| p.id)
3031            .collect();
3032        for (param_id, arg) in param_ids.into_iter().zip(args) {
3033            match arg {
3034                BodyArg::Scalar(v) => {
3035                    self.block_param_values.insert(param_id, *v);
3036                }
3037                BodyArg::Aggregate(fields) => {
3038                    self.block_param_aggregates.insert(param_id, fields.clone());
3039                }
3040            }
3041        }
3042
3043        self.drive_to_return(ctx, root, func, max_steps)
3044    }
3045
3046    /// Shared drive loop for the bounded `run_pure`/`run_map_body` entry points:
3047    /// run from the current position to the first top-level value or machine
3048    /// `Return` (without
3049    /// executing it), popping the call frame. Params must already be seeded and
3050    /// `func` pushed onto the call stack.
3051    fn drive_to_return(
3052        &mut self,
3053        ctx: &Context<'_>,
3054        _root: BlockId,
3055        _func: FunctionId,
3056        max_steps: usize,
3057    ) -> crate::Result<()> {
3058        let mut steps = 0usize;
3059        let result = loop {
3060            let insn_ids = BasicBlock::from_id(ctx, self.block)
3061                .instruction_ids()
3062                .to_vec();
3063            // A well-formed block ends in a terminator, so `self.idx` should always
3064            // point at a real instruction. Lifting can leave degenerate empty blocks
3065            // behind, though; bail with a recoverable error instead of indexing out
3066            // of bounds (which would crash the whole analysis via GVN pure-call
3067            // folding). `make_error` can't be used here — it also indexes the block.
3068            if self.idx >= insn_ids.len() {
3069                break Err(self.make_empty_block_error(ctx));
3070            }
3071            let insn = InstructionRef::from_id(ctx, insn_ids[self.idx]);
3072            if matches!(
3073                insn.mnemonic(),
3074                Mnemonic::Return(_) | Mnemonic::ReturnValue(_)
3075            ) {
3076                break Ok(());
3077            }
3078            steps += 1;
3079            if steps > max_steps {
3080                break Err(self.make_error(ctx, EmulatorErrorKind::StepBudgetExceeded(max_steps)));
3081            }
3082            if let Err(e) = self.step(ctx) {
3083                break Err(e);
3084            }
3085        };
3086
3087        self.call_stack.pop();
3088        result
3089    }
3090}
3091
3092fn lambda_return_value(ctx: &Context<'_>, block: BlockId) -> Option<ValueId> {
3093    let last = BasicBlock::from_id(ctx, block).iter().last()?;
3094    match last.mnemonic() {
3095        Mnemonic::ReturnValue(ret) => Some(ret.value.qualify(last.id.func)),
3096        _ => None,
3097    }
3098}
3099
3100/// The value a scan/map body block returns — via either a machine `Return` (the
3101/// outlined-body form) or a lambda `ReturnValue`. `None` if the block does not
3102/// end in a value-carrying return.
3103fn body_return_value(ctx: &Context<'_>, block: BlockId) -> Option<ValueId> {
3104    let last = BasicBlock::from_id(ctx, block).iter().last()?;
3105    match last.mnemonic() {
3106        Mnemonic::Return(ret) => ret.value.map(|v| v.qualify(last.id.func)),
3107        Mnemonic::ReturnValue(ret) => Some(ret.value.qualify(last.id.func)),
3108        _ => None,
3109    }
3110}
3111
3112/// The array-valued (or lane-reading) sequence intrinsics the emulator evaluates
3113/// over its [`array_values`](StandaloneEmulator::array_values) domain rather than
3114/// the scalar interpreter.
3115fn is_array_intrinsic(name: &str) -> bool {
3116    matches!(
3117        name,
3118        "iota" | "singleton" | "concat" | "insert" | "at" | "enumerate"
3119    )
3120}
3121
3122/// Fold a little-endian byte slice (≤ 16 bytes) into a `u128`.
3123fn le_bits(bytes: &[u8]) -> u128 {
3124    let mut buf = [0u8; 16];
3125    let n = bytes.len().min(16);
3126    buf[..n].copy_from_slice(&bytes[..n]);
3127    u128::from_le_bytes(buf)
3128}
3129
3130/// A positional argument to a `map` body for [`run_map_body`](StandaloneEmulator::run_map_body):
3131/// a scalar param value, or the field vector of an aggregate (tuple) param.
3132#[derive(Debug, Clone)]
3133pub enum BodyArg {
3134    Scalar(SizedValue),
3135    Aggregate(Vec<SizedValue>),
3136}
3137
3138/// Private helper that pairs `&mut StandaloneEmulator` fields with `&Context<'_>`
3139/// so the default `Interpreter::interpret()` impl can be reused.
3140struct TempInterpreter<'a, 'ctx, M> {
3141    memory: &'a mut M,
3142    literals: &'a mut LiteralCache,
3143    insn_values: &'a mut InsnValues,
3144    block_param_values: &'a mut FxHashMap<BlockParamId, SizedValue>,
3145    poison_params: &'a FxHashSet<BlockParamId>,
3146    ctx: &'ctx Context<'ctx>,
3147}
3148
3149impl<'ctx, M: EmulatorMemory> Interpreter for TempInterpreter<'_, 'ctx, M> {
3150    type V = SizedValue;
3151    type M = M;
3152
3153    fn memory(&mut self) -> &mut Self::M {
3154        self.memory
3155    }
3156
3157    fn ctx(&self) -> &Context<'_> {
3158        self.ctx
3159    }
3160
3161    fn get_value(&mut self, id: ValueId) -> Result<Self::V, EmulatorErrorKind> {
3162        // Taken before `ValueRef::new`, which would resolve the literal through
3163        // the interner's lock.
3164        if let ValueId::Literal(literal) = id {
3165            return Ok(self.literals.get(self.ctx, literal));
3166        }
3167        match ValueRef::new(id, self.ctx) {
3168            ValueRef::Literal(literal) => Ok(SizedValue::new(literal.value(), literal.size())),
3169            // Byte blobs are wider than the emulator's scalar SizedValue.
3170            ValueRef::Bytes(_) => Err(EmulatorErrorKind::ValueError(0)),
3171            ValueRef::Instruction(insn) => self
3172                .insn_values
3173                .get(&insn.id)
3174                .copied()
3175                .ok_or(EmulatorErrorKind::ValueError(0)),
3176            ValueRef::Varnode(varnode) => Ok(SizedValue::new(varnode.address() as u64, 8)),
3177            ValueRef::Temp(temp) => Ok(SizedValue::new(temp.address() as u64, 8)),
3178            ValueRef::BasicBlock(_) => panic!("Cannot get value of a block"),
3179            ValueRef::BlockParam(param) => {
3180                if self.poison_params.contains(&param.id) {
3181                    return Err(EmulatorErrorKind::PoisonRead);
3182                }
3183                self.block_param_values
3184                    .get(&param.id)
3185                    .copied()
3186                    .ok_or(EmulatorErrorKind::ValueError(0))
3187            }
3188            ValueRef::Function(f) => f
3189                .address()
3190                .map(SizedValue::from_u64)
3191                .ok_or(EmulatorErrorKind::EmptyFunctionRoot(f.id)),
3192            // Poison has undefined bits: demanding its concrete value is a hard
3193            // error (propagating it as an unread operand never reaches here).
3194            ValueRef::Poison(_) => Err(EmulatorErrorKind::PoisonRead),
3195        }
3196    }
3197}
3198
3199pub struct Emulator<'ctx, M = EmulatedMemory> {
3200    inner: StandaloneEmulator<M>,
3201    ctx: &'ctx Context<'ctx>,
3202}
3203
3204impl<'ctx> Emulator<'ctx, EmulatedMemory> {
3205    /// Builds an emulator over the default flat memory.
3206    pub fn new(ctx: &'ctx Context<'ctx>, entry: BlockId) -> Self {
3207        Self::new_in(ctx, entry)
3208    }
3209
3210    pub fn from_function(ctx: &'ctx Context<'ctx>, func: FunctionId) -> Self {
3211        Self::from_function_in(ctx, func)
3212    }
3213
3214    pub fn from_block(ctx: &'ctx Context<'ctx>, block: BlockId) -> Self {
3215        Self::new_in(ctx, block)
3216    }
3217
3218    pub fn from_address(ctx: &'ctx Context<'ctx>, addr: u64) -> Self {
3219        Self::from_address_in(ctx, addr)
3220    }
3221}
3222
3223impl<'ctx, M: EmulatorMemory + Default> Emulator<'ctx, M> {
3224    /// Builds an emulator over an explicit memory backend.
3225    pub fn new_in(ctx: &'ctx Context<'ctx>, entry: BlockId) -> Self {
3226        let mut inner =
3227            StandaloneEmulator::<M>::with_address_index(entry, AddressIndex::analyze(ctx));
3228        inner.memory.configure_spaces(ctx);
3229        Self { inner, ctx }
3230    }
3231
3232    pub fn set_instruction_hook(
3233        &mut self,
3234        hook: impl Fn(&InstructionRef<'_, '_>, &StandaloneEmulator<M>) + Send + Sync + 'static,
3235    ) {
3236        self.inner.instruction_hook = Some(Box::new(hook));
3237    }
3238
3239    pub fn set_call_interceptor(
3240        &mut self,
3241        interceptor: impl FnMut(
3242            &Context<'_>,
3243            &mut StandaloneEmulator<M>,
3244            &CallSite,
3245        ) -> Result<CallInterception, Box<str>>
3246        + Send
3247        + Sync
3248        + 'static,
3249    ) {
3250        self.inner.set_call_interceptor(interceptor);
3251    }
3252
3253    pub fn clear_call_interceptor(&mut self) {
3254        self.inner.clear_call_interceptor();
3255    }
3256
3257    /// Builds an emulator at a function's root block, over an explicit backend.
3258    pub fn from_function_in(ctx: &'ctx Context<'ctx>, func: FunctionId) -> Self {
3259        let entry = FunctionBody::from_id(ctx, func)
3260            .root()
3261            .expect("Cannot create emulator for function with empty root block")
3262            .id;
3263        Self::new_in(ctx, entry)
3264    }
3265
3266    /// Builds an emulator positioned at `addr`, over an explicit backend.
3267    pub fn from_address_in(ctx: &'ctx Context<'ctx>, addr: u64) -> Self {
3268        Self {
3269            inner: StandaloneEmulator::<M>::from_address_in(ctx, addr),
3270            ctx,
3271        }
3272    }
3273
3274    /// Debugging method to view a value at a given address
3275    pub fn inspect_memory(&mut self, space: SpaceId, addr: u64, size: usize) -> Option<Vec<u8>> {
3276        self.inner
3277            .memory
3278            .read_bytes(MemorySpaceId::Shared(space), addr, size)
3279            .ok()
3280    }
3281
3282    /// Sets the value of a varnode
3283    pub fn set_varnode(&mut self, id: VarnodeId, value: u64) -> Result<(), EmulatorErrorKind> {
3284        self.inner.set_varnode(self.ctx, id, value)
3285    }
3286
3287    /// Sets the value of a varnode using full 128-bit precision.
3288    pub fn set_varnode_u128(
3289        &mut self,
3290        id: VarnodeId,
3291        value: u128,
3292    ) -> Result<(), EmulatorErrorKind> {
3293        self.inner.set_varnode_u128(self.ctx, id, value)
3294    }
3295
3296    /// Sets the value of a register
3297    pub fn set_register(&mut self, id: RegisterId, value: u64) -> Result<(), EmulatorErrorKind> {
3298        let id = self.ctx.get_register(id).id;
3299        self.set_varnode(id, value)
3300    }
3301
3302    /// Writes a value to memory
3303    pub fn write_memory(
3304        &mut self,
3305        space: SpaceId,
3306        addr: u64,
3307        value: &[u8],
3308    ) -> Result<(), EmulatorErrorKind> {
3309        self.inner.write_memory(self.ctx, space, addr, value)
3310    }
3311
3312    pub fn read_memory(
3313        &mut self,
3314        space: SpaceId,
3315        addr: u64,
3316        size: usize,
3317    ) -> Result<Vec<u8>, EmulatorErrorKind> {
3318        self.inner.read_memory(self.ctx, space, addr, size)
3319    }
3320
3321    /// Sets the value of a register using full 128-bit precision.
3322    pub fn set_register_u128(
3323        &mut self,
3324        id: RegisterId,
3325        value: u128,
3326    ) -> Result<(), EmulatorErrorKind> {
3327        let id = self.ctx.get_register(id).id;
3328        self.set_varnode_u128(id, value)
3329    }
3330
3331    pub fn read_varnode(&mut self, id: VarnodeId) -> Option<u64> {
3332        self.inner.read_varnode(self.ctx, id)
3333    }
3334
3335    pub fn read_varnode_u128(&mut self, id: VarnodeId) -> Option<u128> {
3336        self.inner.read_varnode_u128(self.ctx, id)
3337    }
3338
3339    pub fn read_register(&mut self, id: RegisterId) -> Option<u64> {
3340        let id = self.ctx.get_register(id).id;
3341        self.read_varnode(id)
3342    }
3343
3344    pub fn read_register_u128(&mut self, id: RegisterId) -> Option<u128> {
3345        let id = self.ctx.get_register(id).id;
3346        self.read_varnode_u128(id)
3347    }
3348
3349    /// Writes a single 64-bit lane of a wide register.
3350    /// Lane `n` covers bytes `[n*8 .. n*8+8]` relative to the register's base address.
3351    pub fn set_register_lane(&mut self, id: RegisterId, lane: usize, value: u64) {
3352        let (space_id, base_addr) = {
3353            let vn = self.ctx.get_register(id);
3354            (vn.space().id, vn.address() as u64)
3355        };
3356        let base = base_addr + (lane as u64) * 8;
3357        let _ = self
3358            .inner
3359            .memory
3360            .write_bytes(space_id.into(), base, &value.to_le_bytes());
3361    }
3362
3363    /// Reads a single 64-bit lane of a wide register (little-endian).
3364    /// Lane `n` covers bytes `[n*8 .. n*8+8]` relative to the register's base address.
3365    pub fn read_register_lane(&mut self, id: RegisterId, lane: usize) -> u64 {
3366        let (space_id, base_addr) = {
3367            let vn = self.ctx.get_register(id);
3368            (vn.space().id, vn.address() as u64)
3369        };
3370        let base = base_addr + (lane as u64) * 8;
3371        // An unwritten lane reads as zero: register space is architectural
3372        // state that exists whether or not a harness has seeded it.
3373        let bytes = self
3374            .inner
3375            .memory
3376            .read_bytes(space_id.into(), base, 8)
3377            .unwrap_or_else(|_| vec![0; 8]);
3378        u64::from_le_bytes(bytes.try_into().expect("read_bytes returns 8 bytes"))
3379    }
3380
3381    /// Gets the current block
3382    pub fn block(&self) -> BlockRef<'ctx, 'ctx> {
3383        BasicBlock::from_id(self.ctx, self.inner.block)
3384    }
3385
3386    /// Gets the current instruction
3387    pub fn insn(&self) -> Option<InstructionRef<'ctx, 'ctx>> {
3388        let block = self.block();
3389        if self.inner.idx >= block.instruction_count() {
3390            None
3391        } else {
3392            let id = block.instruction_ids()[self.inner.idx];
3393            Some(InstructionRef::from_id(self.ctx, id))
3394        }
3395    }
3396
3397    /// Executes a single pcode instruction
3398    pub fn step(&mut self) -> crate::Result<()> {
3399        self.inner.step(self.ctx)
3400    }
3401
3402    /// Executes instructions until the end of the current block
3403    pub fn run_block(&mut self) -> crate::Result<()> {
3404        self.inner.run_block(self.ctx)
3405    }
3406
3407    /// Runs blocks until the current block starts at `addr`.
3408    pub fn run_until(&mut self, addr: u64) -> crate::Result<()> {
3409        self.inner.run_until(self.ctx, addr)
3410    }
3411
3412    /// Runs the given function from its root block, stopping before the outermost `Return`.
3413    pub fn run_function(&mut self, func: FunctionId) -> crate::Result<()> {
3414        self.inner.run_function(self.ctx, func)
3415    }
3416
3417    /// Returns the current emulator call stack (outermost function first).
3418    /// Only populated during `run_function` execution.
3419    pub fn call_stack(&self) -> &[FunctionId] {
3420        &self.inner.call_stack
3421    }
3422}
3423
3424impl<'ctx, M: EmulatorMemory> Interpreter for Emulator<'ctx, M> {
3425    type V = SizedValue;
3426    type M = M;
3427
3428    fn memory(&mut self) -> &mut Self::M {
3429        &mut self.inner.memory
3430    }
3431
3432    fn ctx(&self) -> &Context<'ctx> {
3433        self.ctx
3434    }
3435
3436    fn get_value(&mut self, id: ValueId) -> Result<Self::V, EmulatorErrorKind> {
3437        if let ValueId::Literal(literal) = id {
3438            let value = self.inner.literal_cache.get(self.ctx, literal);
3439            return Ok(value);
3440        }
3441        match ValueRef::new(id, self.ctx) {
3442            ValueRef::Literal(literal) => Ok(SizedValue::new(literal.value(), literal.size())),
3443            // Byte blobs are wider than the emulator's scalar SizedValue.
3444            ValueRef::Bytes(_) => Err(EmulatorErrorKind::ValueError(0)),
3445            ValueRef::Instruction(insn) => self
3446                .inner
3447                .insn_values
3448                .get(&insn.id)
3449                .copied()
3450                .ok_or(EmulatorErrorKind::ValueError(0)),
3451            ValueRef::Varnode(varnode) => Ok(SizedValue::new(varnode.address() as u64, 8)),
3452            ValueRef::Temp(temp) => Ok(SizedValue::new(temp.address() as u64, 8)),
3453            ValueRef::BasicBlock(_) => panic!("Cannot get value of a block"),
3454            ValueRef::BlockParam(param) => self
3455                .inner
3456                .block_param_values
3457                .get(&param.id)
3458                .copied()
3459                .ok_or(EmulatorErrorKind::ValueError(0)),
3460            ValueRef::Function(f) => f
3461                .address()
3462                .map(SizedValue::from_u64)
3463                .ok_or(EmulatorErrorKind::EmptyFunctionRoot(f.id)),
3464            // Poison has undefined bits: demanding its concrete value is a hard
3465            // error (propagating it as an unread operand never reaches here).
3466            ValueRef::Poison(_) => Err(EmulatorErrorKind::PoisonRead),
3467        }
3468    }
3469}
3470
3471#[cfg(test)]
3472mod tests {
3473    use super::*;
3474    use qcode::context::Context;
3475    use qcode::space::{Space, SpaceType};
3476    use qcode::value::QCodeMut;
3477    use qcode::value::TempSpace;
3478    use std::sync::{Arc, Mutex};
3479    use wazabin_qcode_macro::qcode;
3480
3481    /// Reading a poison value is a hard error (`PoisonRead`); propagating it as
3482    /// an unread operand never reaches `get_value`.
3483    #[test]
3484    fn reading_poison_is_a_hard_error() {
3485        let mut ctx = Context::new();
3486        let func = ctx.anon_function();
3487        let block = BasicBlock::make(&mut ctx, func).with_address(0x1000).id;
3488        let i32_ty = ctx.shared.types.get_or_make_int(4);
3489        let poison = ctx.get_poison(i32_ty);
3490        let mut emu = StandaloneEmulator::new(block);
3491        let mut tmp = TempInterpreter {
3492            memory: &mut emu.memory,
3493            literals: &mut emu.literal_cache,
3494            insn_values: &mut emu.insn_values,
3495            block_param_values: &mut emu.block_param_values,
3496            poison_params: &emu.poison_params,
3497            ctx: &ctx,
3498        };
3499        assert!(matches!(
3500            tmp.get_value(poison),
3501            Err(EmulatorErrorKind::PoisonRead)
3502        ));
3503    }
3504
3505    #[test]
3506    fn minted_callee_is_not_executable() {
3507        assert!(matches!(
3508            require_real_callee(Callee::Minted(7)),
3509            Err(EmulatorErrorKind::UnresolvedMintedCallee(7))
3510        ));
3511    }
3512
3513    #[test]
3514    fn sized_value_masks_to_declared_width() {
3515        let value = SizedValue::new(0x1234, 1);
3516        assert_eq!(value.size().unwrap(), 1);
3517        assert_eq!(value.value().unwrap(), 0x34);
3518    }
3519
3520    #[test]
3521    fn from_address_resolves_function_entry_to_root() {
3522        let mut ctx = Context::new();
3523        let function = FunctionBody::make_at_addr(&mut ctx, 0x1000, None).id;
3524        let root = BasicBlock::make(&mut ctx, function).with_address(0x1000).id;
3525
3526        let emulator = StandaloneEmulator::from_address(&ctx, 0x1000);
3527
3528        assert_eq!(emulator.current_block(), root);
3529        assert!(emulator.address_index.is_some());
3530    }
3531
3532    #[test]
3533    fn standalone_address_lookup_builds_one_lazy_snapshot() {
3534        let mut ctx = Context::new();
3535        let function = ctx.anon_function();
3536        let block = BasicBlock::make(&mut ctx, function).with_address(0x2000).id;
3537        ctx.block_mut(block).extra_addresses.push(0x2001);
3538        let mut emulator = StandaloneEmulator::new(block);
3539
3540        assert!(emulator.address_index.is_none());
3541        assert_eq!(emulator.block_at(&ctx, 0x2001), Some(block));
3542        assert!(emulator.address_index.is_some());
3543        assert_eq!(emulator.block_at(&ctx, 0x2000), Some(block));
3544    }
3545
3546    #[test]
3547    fn int_add_wraps_by_width_and_sets_carry() {
3548        let lhs = SizedValue::new(0xff, 1);
3549        let rhs = SizedValue::new(0x01, 1);
3550
3551        let sum = lhs.int_add(&rhs).unwrap();
3552        let carry = lhs.carry(&rhs).unwrap();
3553
3554        assert_eq!(sum.value().unwrap(), 0x00);
3555        assert_eq!(sum.size().unwrap(), 1);
3556        assert_eq!(carry.value().unwrap(), 1);
3557        assert_eq!(carry.size().unwrap(), 1);
3558    }
3559
3560    #[test]
3561    fn branch_args_bind_block_params() {
3562        let mut ctx = Context::new();
3563        qcode!(
3564            ctx,
3565            "
3566            <src>
3567                goto <dst @x=0x2>;
3568            <dst @x>
3569                %sum = i64 @x + 0x3;
3570                goto <0x1001>;
3571            "
3572        );
3573
3574        let mut emu = StandaloneEmulator::new(src);
3575        emu.step(&ctx).expect("branch binds block params");
3576        emu.step(&ctx).expect("destination uses block param");
3577
3578        assert_eq!(emu.get_value(&ctx, sum.into()), Some(5));
3579    }
3580
3581    #[test]
3582    fn apply_evaluates_recursive_lambda_value_return() {
3583        let mut ctx = Context::new();
3584        qcode!(
3585            ctx,
3586            "
3587            lambda dec:
3588            <entry @n:i64>
3589                %is_zero = @n == 0;
3590                if %is_zero goto <done @r=@n> else goto <step @m=@n>;
3591
3592            <step @m:i64>
3593                %next = @m - 1;
3594                %out = apply dec(%next);
3595                return %out;
3596
3597            <done @r:i64>
3598                return @r;
3599            "
3600        );
3601
3602        let dec = qcode::value::FunctionBody::from_name(&ctx, "dec")
3603            .expect("lambda exists")
3604            .id;
3605        let root = qcode::value::FunctionBody::from_id(&ctx, dec)
3606            .root()
3607            .expect("lambda has root")
3608            .id;
3609        let mut emu = StandaloneEmulator::new(root);
3610        emu.run_pure(&ctx, dec, &[SizedValue::new(3, 8)], 1000)
3611            .expect("recursive lambda evaluates");
3612        let ret = lambda_return_value(&ctx, emu.current_block()).expect("lambda returned a value");
3613        assert_eq!(emu.get_value(&ctx, ret), Some(0));
3614    }
3615
3616    /// Test setup is the publication barrier: `result_type` only reads, so the
3617    /// types an intrinsic resolves to must exist before it is pushed.
3618    fn publish_iota_result(ctx: &mut Context) {
3619        use qcode::types::TypeRequest;
3620        let i64_ty = ctx.shared.types.get_or_make_int(8);
3621        ctx.shared
3622            .types
3623            .create_requested_types(&[TypeRequest::list(i64_ty, None)]);
3624    }
3625
3626    /// `map @f arr` runs the pure unary body over every element and materializes
3627    /// the result buffer. `f(x) = x * 3`, `arr = [1, 2, 3, 4]` ⇒ `[3, 6, 9, 12]`.
3628    #[test]
3629    fn map_over_array_is_emulated() {
3630        let mut ctx = Context::new();
3631        publish_iota_result(&mut ctx);
3632        qcode!(
3633            ctx,
3634            "
3635            lambda triple:
3636            <tb @x:i64>
3637                %r = @x * 3;
3638                return %r;
3639            fn main:
3640            <me>
3641                %src = $iota(i64 0x4);
3642                %m = triple <$> %src;
3643                goto <0x1001>;
3644            "
3645        );
3646
3647        let mut emu = StandaloneEmulator::new(me);
3648        emu.step(&ctx).expect("iota");
3649        emu.step(&ctx).expect("map");
3650
3651        let buf = emu.array_values.get(&m).expect("map produced an array");
3652        let words: Vec<u64> = buf
3653            .as_chunks::<8>()
3654            .0
3655            .iter()
3656            .map(|&c| u64::from_le_bytes(c))
3657            .collect();
3658        // iota(4) = [0,1,2,3]; triple ⇒ [0, 3, 6, 9].
3659        assert_eq!(words, vec![0, 3, 6, 9]);
3660    }
3661
3662    /// End-to-end array emulation: `scanl @step init (iota n)` threads the
3663    /// accumulator through the driver array and materializes the result buffer.
3664    /// `step(acc, x) = acc + x`, `init = 10`, `iota(3) = [0, 1, 2]` ⇒
3665    /// `[10, 11, 13]` (out[i] = acc after adding x_i, prefix-fold style).
3666    #[test]
3667    fn scan_over_iota_is_emulated() {
3668        let mut ctx = Context::new();
3669        publish_iota_result(&mut ctx);
3670        qcode!(
3671            ctx,
3672            "
3673            lambda step:
3674            <sb @acc:i64 @x:i64>
3675                %r = @acc + @x;
3676                return %r;
3677            fn main:
3678            <me>
3679                %src = $iota(i64 0x3);
3680                %s = scanl @step i64 0xa %src;
3681                goto <0x1001>;
3682            "
3683        );
3684
3685        let mut emu = StandaloneEmulator::new(me);
3686        // Step: iota, then scan (do not execute the terminating goto).
3687        emu.step(&ctx).expect("iota");
3688        emu.step(&ctx).expect("scan");
3689
3690        let buf = emu.array_values.get(&s).expect("scan produced an array");
3691        let words: Vec<u64> = buf
3692            .as_chunks::<8>()
3693            .0
3694            .iter()
3695            .map(|&c| u64::from_le_bytes(c))
3696            .collect();
3697        assert_eq!(words, vec![10, 11, 13]);
3698    }
3699
3700    #[test]
3701    fn enumerate_over_array_is_emulated() {
3702        use qcode::value::{FunctionBody, ValueId, insn::IntrinsicId};
3703
3704        let mut ctx = Context::new();
3705        let f = FunctionBody::make(&mut ctx, "f".into()).unwrap().id;
3706        let entry = ctx.get_or_make_block(0x1000, f);
3707        {
3708            let mut fm = FunctionBody::from_id_mut(&mut ctx, f);
3709            fm.set_root(entry).unwrap();
3710            fm.add_block(entry);
3711        }
3712        // A fixed `[i64; 4]` source `[10, 20, 30, 40]`.
3713        let i64_ty = ctx.shared.types.get_or_make_int(8);
3714        let arr_ty = ctx.shared.types.get_or_make_array(i64_ty, 4);
3715        let data: Vec<u8> = [10u64, 20, 30, 40]
3716            .iter()
3717            .flat_map(|w| w.to_le_bytes())
3718            .collect();
3719        let src = ctx.get_bytes(data).id();
3720        if let ValueId::Bytes(bid) = src {
3721            ctx.shared.values.bytes[bid].type_id = arr_ty;
3722        }
3723        // Publish the `(index, elem)` tuple and its array before pushing the
3724        // intrinsic: `result_type` only reads.
3725        {
3726            use qcode::types::{AggregateField, TypeRequest};
3727            let fields = vec![
3728                AggregateField::new("index", i64_ty),
3729                AggregateField::new("elem", i64_ty),
3730            ];
3731            let tuple = ctx
3732                .shared
3733                .types
3734                .create_requested_types(&[TypeRequest::aggregate(fields)])[0];
3735            ctx.shared
3736                .types
3737                .create_requested_types(&[TypeRequest::array(tuple, 4)]);
3738        }
3739        let enum_id = IntrinsicId::from_name("enumerate").unwrap();
3740        let e = {
3741            let mut b = ctx.builder(entry);
3742            let e = b.push_intrinsic(enum_id, vec![src]).id();
3743            let ptr = b.shr().get_const(0, 8);
3744            b.push_return(ptr);
3745            e
3746        };
3747        let ValueId::Instruction(eid) = e else {
3748            unreachable!()
3749        };
3750
3751        let mut emu = StandaloneEmulator::new(entry);
3752        emu.step(&ctx).expect("enumerate");
3753
3754        // `enumerate([10,20,30,40]) = [(0,10),(1,20),(2,30),(3,40)]`: each lane is
3755        // an `(index: i64, elem: i64)` tuple.
3756        let buf = emu
3757            .array_values
3758            .get(&eid)
3759            .expect("enumerate produced an array");
3760        let words: Vec<u64> = buf
3761            .as_chunks::<8>()
3762            .0
3763            .iter()
3764            .map(|&c| u64::from_le_bytes(c))
3765            .collect();
3766        assert_eq!(words, vec![0, 10, 1, 20, 2, 30, 3, 40]);
3767    }
3768
3769    /// Emulating a function that returns `enumerate` over an *unbounded* list
3770    /// bails with a recoverable `UnsupportedIntrinsic` rather than fabricating a
3771    /// length — a length-erased list has no concrete count to materialize. (A
3772    /// fixed array *is* materialized; see the `mt_scan` differential test.)
3773    #[test]
3774    fn enumerate_of_unbounded_list_bails_recoverably() {
3775        use qcode::value::{
3776            BasicBlock, FunctionBody, InstructionRef, ValueId,
3777            insn::{IntrinsicApp, IntrinsicId, Return},
3778        };
3779
3780        let mut ctx = Context::new();
3781        let f = FunctionBody::make(&mut ctx, "f".into()).unwrap().id;
3782        let entry = ctx.get_or_make_block(0x1000, f);
3783        {
3784            let mut fm = FunctionBody::from_id_mut(&mut ctx, f);
3785            fm.set_root(entry).unwrap();
3786            fm.add_block(entry);
3787        }
3788        let i8 = ctx.shared.types.get_or_make_int(1);
3789        let list_ty = ctx.shared.types.get_or_make_unbounded_list(i8);
3790        let src = {
3791            let mut b = ctx.builder(entry);
3792            b.push_param(8).id()
3793        };
3794        if let ValueId::BlockParam(pid) = src {
3795            ctx.block_param_mut(pid).type_id = list_ty;
3796        }
3797        // Build `enumerate` over the unbounded list with an explicit result type:
3798        // its `result_type` declines an unbounded operand (no static length), so
3799        // the intrinsic is only ever constructed this way, never via inference.
3800        let enum_id = IntrinsicId::from_name("enumerate").unwrap();
3801        // Create the intrinsic in the block's own storage arena so the pushed
3802        // instruction stays strict-local (its parent block lives in the same
3803        // function) — a foreign instruction placement is a locality violation the
3804        // in-body-id localization (ruling 2) forbids.
3805        let env = {
3806            let insn = InstructionRef::from_mnemonic_with_type(
3807                &mut ctx,
3808                entry.func,
3809                Mnemonic::Intrinsic(IntrinsicApp {
3810                    id: enum_id,
3811                    args: vec![src.localize(entry.func)],
3812                }),
3813                list_ty,
3814            )
3815            .id;
3816            BasicBlock::from_id_mut(&mut ctx, entry).push_insn(insn);
3817            ValueId::Instruction(insn)
3818        };
3819        let ptr = ctx.get_const(0, 8).id();
3820        {
3821            let mut b = ctx.builder(entry);
3822            b.push_return(ptr);
3823        }
3824        let rid = BasicBlock::from_id(&ctx, entry).iter().last().unwrap().id;
3825        ctx.replace_instruction_mnemonic(
3826            rid,
3827            Mnemonic::Return(Return {
3828                ptr: ptr.localize(rid.func),
3829                value: Some(env.localize(rid.func)),
3830            }),
3831        );
3832
3833        let mut emu = StandaloneEmulator::new(entry);
3834        let err = emu
3835            .run_pure(&ctx, f, &[SizedValue::new(0, 4)], 1000)
3836            .expect_err("enumerate must not be emulated");
3837        assert!(
3838            matches!(err.kind, EmulatorErrorKind::UnsupportedIntrinsic(ref n) if &**n == "enumerate"),
3839            "expected recoverable UnsupportedIntrinsic, got {:?}",
3840            err.kind
3841        );
3842    }
3843
3844    /// Build pure `f(arr: [i8; n])` returning `at(arr, idx)` and run it with the
3845    /// array param bound to `bound`. Returns the emulated scalar lane, or `None`
3846    /// if `resolve_array` refuses the binding (e.g. an oversize param).
3847    fn run_at_over_array_param(n: usize, idx: u64, bound: SizedValue) -> Option<u64> {
3848        use qcode::value::{
3849            BasicBlock, FunctionBody, ValueId,
3850            insn::{IntrinsicId, Return},
3851        };
3852
3853        let mut ctx = Context::new();
3854        let arr_ty = {
3855            let i8 = ctx.shared.types.get_or_make_int(1);
3856            ctx.shared.types.get_or_make_array(i8, n)
3857        };
3858        let f = FunctionBody::make(&mut ctx, "f".into()).unwrap().id;
3859        let entry = ctx.get_or_make_block(0x1000, f);
3860        {
3861            let mut fm = FunctionBody::from_id_mut(&mut ctx, f);
3862            fm.set_root(entry).unwrap();
3863            fm.add_block(entry);
3864        }
3865        let arr_pid = BasicBlock::from_id_mut(&mut ctx, entry).push_param(n).id;
3866        ctx.block_param_mut(arr_pid).type_id = arr_ty;
3867
3868        let at_id = IntrinsicId::from_name("at").unwrap();
3869        let (ret, ptr, lane);
3870        {
3871            let mut b = ctx.builder(entry);
3872            let arr = ValueId::BlockParam(arr_pid);
3873            let i = b.shr().get_const(idx, 8);
3874            lane = b.push_intrinsic(at_id, vec![arr, i]).id();
3875            ptr = b.shr().get_const(0, 8);
3876            ret = b.push_return(ptr).id();
3877        }
3878        let ValueId::Instruction(rid) = ret else {
3879            unreachable!()
3880        };
3881        ctx.replace_instruction_mnemonic(
3882            rid,
3883            Mnemonic::Return(Return {
3884                ptr: ptr.localize(rid.func),
3885                value: Some(lane.localize(rid.func)),
3886            }),
3887        );
3888
3889        let mut emu = StandaloneEmulator::new(entry);
3890        emu.run_pure(&ctx, f, &[bound], 1000).ok()?;
3891        emu.get_value(&ctx, lane)
3892    }
3893
3894    /// Dispatch through a `switch`: the arm whose case matches the scrutinee is
3895    /// taken, an unmatched value falls to the default, and an arm's block
3896    /// arguments are bound on the way through.
3897    fn run_switch(scrutinee: u64) -> Option<u64> {
3898        let mut ctx = Context::new();
3899        qcode!(
3900            ctx,
3901            "
3902            lambda sw:
3903            <entry @i:i64>
3904                switch @i { 0x0 => <a>, 0x3 => <b @v=0x63>, default => <d> };
3905            <a>
3906                return 0x11;
3907            <b @v:i64>
3908                return @v;
3909            <d>
3910                return 0x99;
3911            "
3912        );
3913        let root = FunctionBody::from_id(&ctx, sw).root().expect("root").id;
3914        let mut emu = StandaloneEmulator::new(root);
3915        emu.run_pure(&ctx, sw, &[SizedValue::new(scrutinee, 8)], 1000)
3916            .ok()?;
3917        let term = BasicBlock::from_id(&ctx, emu.block)
3918            .instruction_ids()
3919            .last()
3920            .copied()?;
3921        let Mnemonic::ReturnValue(r) = Instruction::from_id(&ctx, term).mnemonic() else {
3922            return None;
3923        };
3924        emu.get_value(&ctx, r.value.qualify(term.func))
3925    }
3926
3927    #[test]
3928    fn switch_selects_the_matching_arm() {
3929        assert_eq!(run_switch(0), Some(0x11));
3930        // The matching arm binds its target's block parameter.
3931        assert_eq!(run_switch(3), Some(0x63));
3932        // No case matches 7, so control reaches the default.
3933        assert_eq!(run_switch(7), Some(0x99));
3934    }
3935
3936    /// A literal-bound `[i8;4]` array param resolves through the `BlockParam` arm
3937    /// of `resolve_array`: each little-endian byte of `0x2f76bfc2` is readable via
3938    /// `at`, exactly the read the pure-call folder needs.
3939    #[test]
3940    fn run_pure_reads_array_param_bytes_little_endian() {
3941        let arg = SizedValue::new(0x2f76bfc2, 4);
3942        // LE layout of 0x2f76bfc2 = [0xc2, 0xbf, 0x76, 0x2f].
3943        assert_eq!(run_at_over_array_param(4, 0, arg), Some(0xc2));
3944        assert_eq!(run_at_over_array_param(4, 1, arg), Some(0xbf));
3945        assert_eq!(run_at_over_array_param(4, 2, arg), Some(0x76));
3946        assert_eq!(run_at_over_array_param(4, 3, arg), Some(0x2f));
3947    }
3948
3949    /// An oversize array param (declared width beyond `SizedValue`'s 16-byte cap)
3950    /// can't be materialized from a scalar binding, so the defensive width check
3951    /// fails resolution rather than hand back a clamped, misaligned buffer.
3952    #[test]
3953    fn run_pure_rejects_oversize_array_param() {
3954        // A 20-byte `[i8;20]` param: the bound `SizedValue` clamps to 16 bytes, so
3955        // `bytes.len() (16) != ty_size (20)` and resolution must fail.
3956        assert_eq!(
3957            run_at_over_array_param(20, 0, SizedValue::new(0xff, 20)),
3958            None
3959        );
3960    }
3961
3962    #[test]
3963    fn int_mul_wraps_for_64_bit_values() {
3964        let lhs = SizedValue::new(u64::MAX, 8);
3965        let rhs = SizedValue::new(2, 8);
3966
3967        let product = lhs.int_mul(&rhs).unwrap();
3968
3969        assert_eq!(product.value().unwrap(), u64::MAX.wrapping_mul(2));
3970        assert_eq!(product.size().unwrap(), 8);
3971    }
3972
3973    #[test]
3974    fn int_mul_wraps_for_128_bit_values() {
3975        let lhs = SizedValue::from_bits(u128::MAX, 16);
3976        let rhs = SizedValue::from_bits(u128::from(2u8), 16);
3977
3978        let product = lhs.int_mul(&rhs).unwrap();
3979
3980        assert_eq!(product.as_bits(), u128::MAX.wrapping_mul(u128::from(2u8)));
3981        assert_eq!(product.size().unwrap(), 16);
3982        assert!(matches!(
3983            product.value(),
3984            Err(EmulatorErrorKind::ValueError(_))
3985        ));
3986    }
3987
3988    #[test]
3989    fn int_div_and_rem_work_for_128_bit_values() {
3990        let lhs = SizedValue::from_bits(u128::MAX, 16);
3991        let rhs = SizedValue::from_bits(u128::from(3u8), 16);
3992
3993        let q = lhs.int_div(&rhs).unwrap();
3994        let r = lhs.int_rem(&rhs).unwrap();
3995
3996        assert_eq!(q.as_bits(), u128::MAX / u128::from(3u8));
3997        assert_eq!(r.as_bits(), u128::MAX % u128::from(3u8));
3998        assert_eq!(q.size().unwrap(), 16);
3999        assert_eq!(r.size().unwrap(), 16);
4000    }
4001
4002    #[test]
4003    fn int_sdiv_and_srem_work_for_128_bit_values() {
4004        let lhs = SizedValue::from_bits(u128::from(0xffff_ffff_ffff_ffffu64), 16);
4005        let rhs = SizedValue::from_bits(u128::from(2u8), 16);
4006
4007        let q = lhs.int_sdiv(&rhs).unwrap();
4008        let r = lhs.int_srem(&rhs).unwrap();
4009
4010        assert_eq!(q.as_bits(), u128::from(0x7fff_ffff_ffff_ffffu64));
4011        assert_eq!(r.as_bits(), u128::from(1u8));
4012        assert_eq!(q.size().unwrap(), 16);
4013        assert_eq!(r.size().unwrap(), 16);
4014    }
4015
4016    #[test]
4017    fn signed_extension_and_shift_behave_as_expected() {
4018        let negative_byte = SizedValue::new(0x80, 1);
4019        let extended = negative_byte.sext(8).unwrap();
4020        let shifted = negative_byte
4021            .int_sshift_right(&SizedValue::new(1, 1))
4022            .unwrap();
4023
4024        assert_eq!(extended.value().unwrap(), 0xffff_ffff_ffff_ff80);
4025        assert_eq!(extended.size().unwrap(), 8);
4026        assert_eq!(shifted.value().unwrap(), 0xc0);
4027        assert_eq!(shifted.size().unwrap(), 1);
4028    }
4029
4030    #[test]
4031    fn signed_comparisons_use_value_width() {
4032        let lhs = SizedValue::new(0xff, 1);
4033        let rhs = SizedValue::new(0x01, 1);
4034
4035        assert_eq!(lhs.int_sless(&rhs).and_then(|v| v.value()).unwrap(), 1);
4036        assert_eq!(rhs.int_sless(&lhs).and_then(|v| v.value()).unwrap(), 0);
4037    }
4038
4039    #[test]
4040    fn int_sub_uses_lhs_width_with_default_u64_immediate() {
4041        let lhs = SizedValue::new(0, 4);
4042        let rhs = SizedValue::from_u64(1);
4043
4044        let diff = lhs.int_sub(&rhs).unwrap();
4045
4046        assert_eq!(diff.size().unwrap(), 4);
4047        assert_eq!(diff.value().unwrap(), 0xffff_ffff);
4048    }
4049
4050    #[test]
4051    fn sborrow_uses_lhs_width_with_default_u64_immediate() {
4052        let lhs = SizedValue::new(0x80, 1);
4053        let rhs = SizedValue::new(1, 1);
4054
4055        // 0x80 - 1 overflows in signed 8-bit arithmetic.
4056        assert_eq!(lhs.sborrow(&rhs).and_then(|v| v.value()).unwrap(), 1);
4057    }
4058
4059    #[test]
4060    fn scarry_uses_lhs_width_with_default_u64_immediate() {
4061        let lhs = SizedValue::new(0x7f, 1);
4062        let rhs = SizedValue::new(1, 1);
4063
4064        // 0x7f + 1 overflows in signed 8-bit arithmetic.
4065        assert_eq!(lhs.scarry(&rhs).and_then(|v| v.value()).unwrap(), 1);
4066    }
4067
4068    #[test]
4069    fn sborrow_neg() {
4070        let lhs = SizedValue::new(0x0, 1);
4071        let rhs = SizedValue::new(0x80, 1);
4072
4073        // 0 - 0x80  overflows in signed 8-bit arithmetic.
4074        assert_eq!(lhs.sborrow(&rhs).and_then(|v| v.value()).unwrap(), 1);
4075    }
4076
4077    #[test]
4078    fn lz_count_respects_width() {
4079        let value = SizedValue::new(0x01, 1);
4080        let lz = value.lz_count().unwrap();
4081
4082        assert_eq!(lz.value().unwrap(), 7);
4083        assert_eq!(lz.size().unwrap(), 1);
4084    }
4085
4086    #[test]
4087    fn float_conversion_handles_f32_and_f64() {
4088        let minus_one = SizedValue::new(0xff, 1);
4089        let as_f32 = minus_one.int_to_float(4).unwrap();
4090        assert_eq!(as_f32.value().unwrap(), (-1.0f32).to_bits() as u64);
4091        assert_eq!(as_f32.size().unwrap(), 4);
4092
4093        let f32_value = SizedValue::new((1.5f32).to_bits() as u64, 4);
4094        let promoted = f32_value.float_to_float(8).unwrap();
4095        let promoted_bits = promoted.value().unwrap();
4096        assert_eq!(f64::from_bits(promoted_bits), 1.5f64);
4097        assert_eq!(promoted.size().unwrap(), 8);
4098
4099        let demoted = promoted.float_to_float(4).unwrap();
4100        assert_eq!(demoted.value().unwrap(), (1.5f32).to_bits() as u64);
4101        assert_eq!(demoted.size().unwrap(), 4);
4102    }
4103
4104    #[test]
4105    fn x87_precision_and_store_rounding_are_separate_from_generic_arithmetic() {
4106        let one = 0x3fff_8000_0000_0000_0000u128;
4107        // Precision rounding narrows the f80 significand without narrowing
4108        // the exponent, and follows the IEEE rounding mode it is given.
4109        let one_plus_half_single_ulp = one + (1u128 << 39);
4110        assert_eq!(
4111            float80::round_to_precision(one_plus_half_single_ulp, 24, Round::NearestTiesToEven)
4112                .bits,
4113            one
4114        );
4115        assert_eq!(
4116            float80::round_to_precision(one_plus_half_single_ulp, 24, Round::TowardPositive).bits,
4117            one + (1u128 << 40)
4118        );
4119        // 64 significand bits is the identity.
4120        assert_eq!(
4121            float80::round_to_precision(one_plus_half_single_ulp, 64, Round::TowardPositive).bits,
4122            one_plus_half_single_ulp
4123        );
4124
4125        // Narrowing takes an explicit rounding mode and no control word: the
4126        // exact halfway value narrows to 1.0 under nearest-even and to the
4127        // next f32 under round-up.
4128        let extended = SizedValue::from_bits(one_plus_half_single_ulp, 10);
4129        assert_eq!(
4130            StandaloneEmulator::<EmulatedMemory>::ieee_narrow(
4131                extended,
4132                4,
4133                Round::NearestTiesToEven
4134            )
4135            .unwrap()
4136            .0
4137            .as_bits(),
4138            u128::from(1.0f32.to_bits())
4139        );
4140        assert_eq!(
4141            StandaloneEmulator::<EmulatedMemory>::ieee_narrow(extended, 4, Round::TowardPositive)
4142                .unwrap()
4143                .0
4144                .as_bits(),
4145            u128::from((1.0f32).to_bits() + 1)
4146        );
4147
4148        // Generic f80 arithmetic is architecture-neutral: it computes a value
4149        // and records no x87 status.  x87 constructors use the explicit IEEE
4150        // p-code operations instead and apply their own exception policy.
4151        let mut ctx = Context::new();
4152        qcode!(
4153            ctx,
4154            "
4155            varnode i16 FPUControlWord;
4156            varnode i16 FPUStatusWord;
4157            varnode f80 A;
4158            varnode f80 B;
4159
4160        <block>
4161            %a = load(A:10, &A);
4162            %b = load(B:10, &B);
4163            %result = %a f/ %b;
4164            goto <0x1001>;
4165        "
4166        );
4167        let mut emu = Emulator::from_block(&ctx, block);
4168        emu.set_varnode(FPUControlWord, 0x037b).unwrap(); // ZE unmasked
4169        emu.set_varnode_u128(A, one).unwrap();
4170        emu.set_varnode_u128(B, 0).unwrap();
4171        emu.run_block().unwrap();
4172        assert_eq!(emu.get_value(result.into()).unwrap().size().unwrap(), 10);
4173        // Never written: the status word stays untouched by the operation.
4174        assert_eq!(emu.read_varnode(FPUStatusWord), None);
4175    }
4176
4177    /// An f80 comparison is a pure predicate. A signalling NaN operand is
4178    /// invalid under x87's rules, but raising it is the specification's job:
4179    /// the emulator writes no status word for a comparison either.
4180    #[test]
4181    fn f80_comparison_records_no_status() {
4182        let mut ctx = Context::new();
4183        qcode!(
4184            ctx,
4185            "
4186            varnode i16 FPUControlWord;
4187            varnode i16 FPUStatusWord;
4188            varnode f80 A;
4189            varnode f80 B;
4190
4191        <block>
4192            %a = load(A:10, &A);
4193            %b = load(B:10, &B);
4194            %equal = %a f== %b;
4195            goto <0x1001>;
4196        "
4197        );
4198        let mut emu = Emulator::from_block(&ctx, block);
4199        emu.set_varnode(FPUControlWord, 0x037f).unwrap();
4200        // A signalling NaN: exponent all ones, integer bit set, quiet bit
4201        // clear, non-zero fraction.
4202        emu.set_varnode_u128(A, 0x7fff_8000_0000_0000_0001).unwrap();
4203        emu.set_varnode_u128(B, 0x3fff_8000_0000_0000_0000).unwrap();
4204        emu.run_block().unwrap();
4205        assert_eq!(emu.get_value(equal.into()).unwrap().value().unwrap(), 0);
4206        assert_eq!(emu.read_varnode(FPUStatusWord), None);
4207    }
4208
4209    /// A partial reduction consumes 63 quotient bits per step, leaving the
4210    /// remainder exact. Hardware reduces (2 - 2^-63) * 2^16383 modulo 1.0 to
4211    /// exactly zero, which only happens when the whole reducible span is
4212    /// consumed: a 32-bit partial quotient leaves a large non-zero remainder.
4213    #[test]
4214    fn float80_partial_remainder_is_exact() {
4215        let dividend = 0x7ffe_ffff_ffff_ffff_ffffu128;
4216        let one = 0x3fff_8000_0000_0000_0000u128;
4217        for ieee in [false, true] {
4218            let result = float80::remainder(dividend, one, ieee);
4219            assert!(result.incomplete);
4220            assert_eq!(result.bits, 0);
4221        }
4222
4223        // One exponent more of span drops a whole 32-bit group of quotient
4224        // bits, leaving 32 behind: the remainder is the dividend's low 32
4225        // exponents, not zero. Hardware reduces in 32-bit groups.
4226        let half = 0x3ffe_8000_0000_0000_0000u128;
4227        let result = float80::remainder(dividend, half, false);
4228        assert!(result.incomplete);
4229        assert_eq!(result.bits, 0x7fdd_ffff_fffe_0000_0000);
4230
4231        // A span under 64 exponents still completes in one step and reports
4232        // the quotient's low bits.
4233        let three = 0x4000_c000_0000_0000_0000u128;
4234        let result = float80::remainder(three, one, false);
4235        assert!(!result.incomplete);
4236        assert_eq!(result.bits, 0);
4237        assert_eq!(result.quotient & 7, 3);
4238    }
4239
4240    /// The 80-bit square root is computed on the integer significand, so it
4241    /// keeps all 64 bits. Routing it through f64 would lose eleven of them.
4242    #[test]
4243    fn float80_sqrt_is_correctly_rounded_at_extended_precision() {
4244        let two = 0x4000_8000_0000_0000_0000;
4245        let four = 0x4001_8000_0000_0000_0000;
4246        let one = 0x3fff_8000_0000_0000_0000;
4247
4248        // sqrt(2) rounds up into the last significand bit.
4249        let root_two = float80::sqrt_ieee(two, Round::NearestTiesToEven);
4250        assert_eq!(root_two.bits, 0x3fff_b504_f333_f9de_6484);
4251        assert!(root_two.status.contains(Status::INEXACT));
4252
4253        // Exact roots stay exact and report nothing.
4254        for (input, expect) in [(four, two), (one, one), (0, 0)] {
4255            let result = float80::sqrt_ieee(input, Round::NearestTiesToEven);
4256            assert_eq!(result.bits, expect);
4257            assert_eq!(result.status, Status::OK);
4258        }
4259
4260        // sqrt(3) is a case where the integer root does round up, so nearest
4261        // and truncation land on different significands.
4262        let three = 0x4000_c000_0000_0000_0000;
4263        assert_eq!(
4264            float80::sqrt_ieee(three, Round::NearestTiesToEven).bits,
4265            0x3fff_ddb3_d742_c265_539e
4266        );
4267        assert_eq!(
4268            float80::sqrt_ieee(three, Round::TowardZero).bits,
4269            0x3fff_ddb3_d742_c265_539d
4270        );
4271
4272        // A negative operand is invalid. What is delivered in its place is
4273        // the specification's choice, so the operation returns the operand.
4274        let negative = float80::sqrt_ieee(0xbfff_8000_0000_0000_0000, Round::NearestTiesToEven);
4275        assert_eq!(negative.bits, 0xbfff_8000_0000_0000_0000);
4276        assert!(negative.status.contains(Status::INVALID_OP));
4277    }
4278
4279    #[test]
4280    fn float80_arithmetic_preserves_extended_precision_bits() {
4281        // x87 80-bit encodings: significand in bits 0..64, exponent/sign in
4282        // bits 64..80. 3.0 is not representable by merely treating f80 as an
4283        // f64 bit-pattern, which was the former behavior.
4284        let one = SizedValue::from_bits(0x3fff_8000_0000_0000_0000, 10);
4285        let two = SizedValue::from_bits(0x4000_8000_0000_0000_0000, 10);
4286        let three = one.float_add(&two).unwrap();
4287
4288        assert_eq!(three.as_bits(), 0x4000_c000_0000_0000_0000);
4289        assert_eq!(three.size().unwrap(), 10);
4290        assert_eq!(two.float_to_float(10).unwrap().as_bits(), two.as_bits());
4291        assert_eq!(
4292            SizedValue::new(3, 1).int_to_float(10).unwrap().as_bits(),
4293            three.as_bits()
4294        );
4295    }
4296
4297    #[test]
4298    fn explicit_ieee_arithmetic_pairs_results_and_flags_in_every_rounding_mode() {
4299        // Each tuple has an inexact operand pair for its respective operation.
4300        // Exercise all supported formats as well as every rounding direction;
4301        // the result and flags calls must be two views of exactly one IEEE
4302        // evaluation, not host arithmetic with independently inferred flags.
4303        let cases = [
4304            (
4305                4,
4306                0x3f80_0000,
4307                0x3380_0000,
4308                0x3f80_0001,
4309                0x3fc0_0000,
4310                0x3f80_0000,
4311                0x4040_0000,
4312            ),
4313            (
4314                8,
4315                0x3ff0_0000_0000_0000,
4316                0x3ca0_0000_0000_0000,
4317                0x3ff0_0000_0000_0001,
4318                0x3ff8_0000_0000_0000,
4319                0x3ff0_0000_0000_0000,
4320                0x4008_0000_0000_0000,
4321            ),
4322            (
4323                10,
4324                0x3fff_8000_0000_0000_0000,
4325                0x3fbf_8000_0000_0000_0000,
4326                0x3fff_8000_0000_0000_0001,
4327                0x3fff_c000_0000_0000_0000,
4328                0x3fff_8000_0000_0000_0000,
4329                0x4000_c000_0000_0000_0000,
4330            ),
4331        ];
4332        let operations = [
4333            ("float_add", "float_add_flags", 0usize),
4334            ("float_sub", "float_sub_flags", 1),
4335            ("float_mul", "float_mul_flags", 2),
4336            ("float_div", "float_div_flags", 3),
4337        ];
4338
4339        for (size, add_lhs, add_rhs, mul_lhs, mul_rhs, div_lhs, div_rhs) in cases {
4340            // At one, the spacing below is half the spacing above. Use a
4341            // quarter of the upward ULP for subtraction so it is inexact too.
4342            let sub_rhs = match size {
4343                4 => 0x3300_0000,
4344                8 => 0x3c90_0000_0000_0000,
4345                10 => 0x3fbe_8000_0000_0000_0000,
4346                _ => unreachable!(),
4347            };
4348            let operands = [
4349                (add_lhs, add_rhs),
4350                (add_lhs, sub_rhs),
4351                (mul_lhs, mul_rhs),
4352                (div_lhs, div_rhs),
4353            ];
4354            for (result_name, flags_name, pair) in operations {
4355                let (lhs, rhs) = operands[pair];
4356                for mode in 0..4 {
4357                    let round =
4358                        StandaloneEmulator::<EmulatedMemory>::ieee_rounding_mode(mode).unwrap();
4359                    let (result, status) = StandaloneEmulator::<EmulatedMemory>::ieee_arithmetic(
4360                        SizedValue::from_bits(lhs, size),
4361                        SizedValue::from_bits(rhs, size),
4362                        round,
4363                        result_name,
4364                    )
4365                    .unwrap();
4366                    let (_, flag_status) = StandaloneEmulator::<EmulatedMemory>::ieee_arithmetic(
4367                        SizedValue::from_bits(lhs, size),
4368                        SizedValue::from_bits(rhs, size),
4369                        round,
4370                        flags_name,
4371                    )
4372                    .unwrap();
4373                    assert_eq!(
4374                        status,
4375                        flag_status,
4376                        "{result_name}, f{}, mode {mode}",
4377                        size * 8
4378                    );
4379                    let flags = StandaloneEmulator::<EmulatedMemory>::ieee_flags(flag_status);
4380                    assert_ne!(
4381                        flags.as_bits() & (1 << 5),
4382                        0,
4383                        "{result_name}, f{}, mode {mode}",
4384                        size * 8
4385                    );
4386                    assert_eq!(result.size as usize, size);
4387                }
4388            }
4389        }
4390    }
4391
4392    #[test]
4393    fn simple_addition() {
4394        let mut ctx = Context::new();
4395
4396        qcode!(
4397            ctx,
4398            "
4399            varnode i64 V0;
4400            varnode i64 V1;
4401
4402        <block>
4403            %v0 = load(V0:8, &V0);
4404            %v1 = load(V1:8, &V1);
4405            %res = %v0 + %v1;
4406            goto <0x1001>;
4407        "
4408        );
4409
4410        let mut emu = Emulator::from_block(&ctx, block);
4411        emu.set_varnode(V0, 2).unwrap();
4412        emu.set_varnode(V1, 3).unwrap();
4413        emu.run_block().unwrap();
4414
4415        assert_eq!(
4416            emu.get_value(res.into()).and_then(|v| v.value()).unwrap(),
4417            5
4418        );
4419    }
4420
4421    #[test]
4422    fn gep_emulates_as_base_plus_offset() {
4423        let mut ctx = Context::new();
4424
4425        // `Inner { val: i32 @ 0x08 }` (0x08 via leading padding), `%p : Inner*`.
4426        qcode!(
4427            ctx,
4428            "
4429            type Inner { _: 8, val: 4 };
4430            varnode i64 V0;
4431
4432        <block>
4433            Inner* %p = load(V0:8, &V0);
4434            %fld = gep(%p.val);
4435            goto <0x1001>;
4436        "
4437        );
4438
4439        let mut emu = Emulator::from_block(&ctx, block);
4440        emu.set_varnode(V0, 0x1000).unwrap();
4441        emu.run_block().unwrap();
4442
4443        let fld = emu.get_value(fld.into()).unwrap();
4444        assert_eq!(fld.value().unwrap(), 0x1008);
4445        // Width follows the pointer base, not the immediate's default u64.
4446        assert_eq!(fld.size().unwrap(), 8);
4447    }
4448
4449    #[test]
4450    fn emulator_int_div_works_with_128_bit_operands() {
4451        let mut ctx = Context::new();
4452        qcode!(
4453            ctx,
4454            "
4455            varnode i128 V0;
4456            varnode i128 V1;
4457
4458        <block>
4459            %v0 = load(V0:16, &V0);
4460            %v1 = load(V1:16, &V1);
4461
4462            %res = %v0 / %v1;
4463            goto <0x1001>;
4464        "
4465        );
4466
4467        let mut emu = Emulator::from_block(&ctx, block);
4468        let v0_bits = u128::from(1u8) << 100;
4469        let v1_bits = u128::from(1u8) << 99;
4470
4471        emu.set_varnode_u128(V0, v0_bits).unwrap();
4472        emu.set_varnode_u128(V1, v1_bits).unwrap();
4473        emu.run_block().unwrap();
4474
4475        // Quotient is small enough to also be visible through legacy u64 extraction.
4476        assert_eq!(
4477            emu.get_value(res.into()).and_then(|v| v.value()).unwrap(),
4478            2
4479        );
4480    }
4481
4482    // -----------------------------------------------------------------------
4483    // Memory error-handling tests
4484    // -----------------------------------------------------------------------
4485
4486    #[test]
4487    fn uninitialized_memory_reads_error() {
4488        let space = EmulatedSpace::default();
4489        assert!(matches!(
4490            space.read_byte(0xdead_beef),
4491            Err(EmulatorErrorKind::MemoryReadError(0xdead_beef))
4492        ));
4493        assert!(matches!(
4494            space.read(0x1000, 4),
4495            Err(EmulatorErrorKind::MemoryReadError(0x1000))
4496        ));
4497    }
4498
4499    #[test]
4500    fn configured_register_and_body_temporary_spaces_zero_fill_missing_bytes() {
4501        let mut ctx = Context::new();
4502        let mut register = Space::new(Some("register"), 1, 8);
4503        register.ty = SpaceType::Register;
4504        let register = ctx.add_space(register);
4505        let function = ctx.anon_function();
4506        let temporary = MemorySpaceId::Temp(ctx.bodies[function].push_temp_space(TempSpace::new(
4507            Some("scratch"),
4508            1,
4509            8,
4510        )));
4511        let mut memory = EmulatedMemory::default();
4512        memory.configure_spaces(&ctx);
4513
4514        for space in [register.into(), temporary] {
4515            assert_eq!(
4516                memory
4517                    .read(space, SizedValue::from_u64(0x1000), 4)
4518                    .unwrap()
4519                    .value()
4520                    .unwrap(),
4521                0
4522            );
4523        }
4524
4525        memory
4526            .write(
4527                ctx.shared.default_space.into(),
4528                SizedValue::from_u64(0x1000),
4529                1,
4530                SizedValue::new(0xaa, 1),
4531            )
4532            .unwrap();
4533        assert!(matches!(
4534            memory.read(
4535                ctx.shared.default_space.into(),
4536                SizedValue::from_u64(0x1001),
4537                1
4538            ),
4539            Err(EmulatorErrorKind::MemoryReadError(0x1001))
4540        ));
4541    }
4542
4543    #[test]
4544    fn temporary_spaces_with_the_same_address_are_isolated() {
4545        use qcode::value::TempSpace;
4546
4547        let mut ctx = Context::new();
4548        let first_fn = FunctionBody::make(&mut ctx, "first".into()).unwrap().id;
4549        let second_fn = FunctionBody::make(&mut ctx, "second".into()).unwrap().id;
4550        let first = ctx.bodies[first_fn].push_temp_space(TempSpace::new(None, 1, 8));
4551        let second = ctx.bodies[second_fn].push_temp_space(TempSpace::new(None, 1, 8));
4552        assert_eq!(first.local, second.local, "fixture must collide local IDs");
4553        let first = MemorySpaceId::Temp(first);
4554        let second = MemorySpaceId::Temp(second);
4555        let mut memory = EmulatedMemory::default();
4556        memory.configure_spaces(&ctx);
4557
4558        let address = SizedValue::from_u64(0x20);
4559        memory
4560            .write(first, address, 1, SizedValue::new(0xaa, 1))
4561            .unwrap();
4562        memory
4563            .write(second, address, 1, SizedValue::new(0x55, 1))
4564            .unwrap();
4565
4566        assert_eq!(
4567            memory.read(first, address, 1).unwrap().value().unwrap(),
4568            0xaa
4569        );
4570        assert_eq!(
4571            memory.read(second, address, 1).unwrap().value().unwrap(),
4572            0x55
4573        );
4574    }
4575
4576    #[test]
4577    fn interpreter_qualifies_colliding_local_spaces_by_function() {
4578        use qcode::value::TempSpace;
4579
4580        fn make_writer(
4581            ctx: &mut Context<'static>,
4582            name: &'static str,
4583            byte: u64,
4584        ) -> (FunctionId, qcode::value::TempSpaceId) {
4585            let fid = FunctionBody::make(ctx, name.into()).unwrap().id;
4586            let root = BasicBlock::make(ctx, fid).id;
4587            FunctionBody::from_id_mut(ctx, fid).set_root(root).unwrap();
4588            let space = ctx.bodies[fid].push_temp_space(TempSpace::new(None, 1, 8));
4589            let mut b = (ctx).builder(root);
4590            let ptr = b.shr().get_const(0x20, 8);
4591            let value = b.shr().get_const(byte, 1);
4592            b.push_store(
4593                value,
4594                ptr,
4595                qcode::space::LocalMemorySpaceId::Temp(space.local),
4596            );
4597            b.push_return(ptr);
4598            (fid, space)
4599        }
4600
4601        let mut ctx = Context::new();
4602        let (first, first_space) = make_writer(&mut ctx, "first", 0xaa);
4603        let (second, second_space) = make_writer(&mut ctx, "second", 0x55);
4604        assert_eq!(first_space.local, second_space.local);
4605
4606        let root = FunctionBody::from_id(&ctx, first).root().unwrap().id;
4607        let mut emulator = StandaloneEmulator::new(root);
4608        emulator.run_function(&ctx, first).unwrap();
4609        emulator.run_function(&ctx, second).unwrap();
4610
4611        let address = SizedValue::from_u64(0x20);
4612        assert_eq!(
4613            emulator
4614                .memory
4615                .read(MemorySpaceId::Temp(first_space), address, 1)
4616                .unwrap()
4617                .value()
4618                .unwrap(),
4619            0xaa
4620        );
4621        assert_eq!(
4622            emulator
4623                .memory
4624                .read(MemorySpaceId::Temp(second_space), address, 1)
4625                .unwrap()
4626                .value()
4627                .unwrap(),
4628            0x55
4629        );
4630    }
4631
4632    #[test]
4633    fn sized_value_byte_swap_preserves_width() {
4634        let value = SizedValue::new(0x1234, 2).byte_swap().unwrap();
4635        assert_eq!(value.value().unwrap(), 0x3412);
4636        assert_eq!(value.size().unwrap(), 2);
4637    }
4638
4639    #[test]
4640    fn swap_bytes_pcode_op_is_emulated() {
4641        let mut ctx = Context::new();
4642        let op = ctx.shared.pcode_ops.push(Box::from("swap_bytes"));
4643        let block_id = {
4644            let __f = ctx.anon_function();
4645            ctx.get_or_make_block(0x1000, __f)
4646        };
4647        let target = ctx.get_or_make_block(0x1001, block_id.func);
4648        let result = {
4649            let src = ctx.get_const(0x1234, 2).id();
4650            let mut builder = ctx.builder(block_id);
4651            let result = builder.push_pcode_op(op, vec![src], None, 2).id;
4652            builder.finalize(target);
4653            result
4654        };
4655        let mut emulator = Emulator::from_block(&ctx, block_id);
4656
4657        emulator.step().unwrap();
4658
4659        assert_eq!(
4660            emulator
4661                .get_value(result.into())
4662                .and_then(|value| value.value())
4663                .unwrap(),
4664            0x3412
4665        );
4666    }
4667
4668    #[test]
4669    fn undef_pcode_op_is_zero_at_its_declared_width() {
4670        let mut ctx = Context::new();
4671        let op = ctx.shared.pcode_ops.push(Box::from("undef"));
4672        let block_id = {
4673            let function = ctx.anon_function();
4674            ctx.get_or_make_block(0x1000, function)
4675        };
4676        let target = ctx.get_or_make_block(0x1001, block_id.func);
4677        let result = {
4678            let mut builder = ctx.builder(block_id);
4679            let result = builder.push_pcode_op(op, vec![], None, 1).id;
4680            builder.finalize(target);
4681            result
4682        };
4683        let mut emulator = Emulator::from_block(&ctx, block_id);
4684
4685        emulator.step().unwrap();
4686
4687        let value = emulator.get_value(result.into()).unwrap();
4688        assert_eq!(value.value().unwrap(), 0);
4689        assert_eq!(value.size().unwrap(), 1);
4690    }
4691
4692    #[test]
4693    fn rol_intrinsic_is_emulated() {
4694        use qcode::value::insn::IntrinsicId;
4695        let mut ctx = Context::new();
4696        let rol = IntrinsicId::from_name("rol").unwrap();
4697        let block_id = {
4698            let __f = ctx.anon_function();
4699            ctx.get_or_make_block(0x1000, __f)
4700        };
4701        let target = ctx.get_or_make_block(0x1001, block_id.func);
4702        let result = {
4703            let x = ctx.get_const(0x1234_5678, 4).id();
4704            let k = ctx.get_const(8, 4).id();
4705            let mut builder = ctx.builder(block_id);
4706            let result = builder.push_intrinsic(rol, vec![x, k]).id;
4707            builder.finalize(target);
4708            result
4709        };
4710        let mut emulator = Emulator::from_block(&ctx, block_id);
4711
4712        emulator.step().unwrap();
4713
4714        assert_eq!(
4715            emulator
4716                .get_value(result.into())
4717                .and_then(|value| value.value())
4718                .unwrap(),
4719            0x1234_5678u32.rotate_left(8) as u64,
4720        );
4721    }
4722
4723    #[test]
4724    fn unknown_pcode_op_returns_typed_error() {
4725        let mut ctx = Context::new();
4726        let op = ctx.shared.pcode_ops.push(Box::from("rdpmc"));
4727        let block_id = {
4728            let __f = ctx.anon_function();
4729            ctx.get_or_make_block(0x1000, __f)
4730        };
4731        let target = ctx.get_or_make_block(0x1001, block_id.func);
4732        {
4733            let mut builder = ctx.builder(block_id);
4734            builder.push_pcode_op(op, vec![], None, 0);
4735            builder.finalize(target);
4736        }
4737        let mut emulator = Emulator::from_block(&ctx, block_id);
4738
4739        let error = emulator.step().unwrap_err();
4740
4741        assert!(matches!(
4742            error.kind,
4743            EmulatorErrorKind::UnsupportedPCodeOp(operation) if operation.as_ref() == "rdpmc"
4744        ));
4745    }
4746
4747    #[test]
4748    fn get_region_overflow_does_not_panic() {
4749        let mut space = EmulatedSpace::default();
4750        // addr + size would overflow u64 without checked_add
4751        assert!(matches!(
4752            space.get_mut_region(u64::MAX - 2, 8),
4753            Err(EmulatorErrorKind::AddressOverflow(_, _))
4754        ));
4755    }
4756
4757    // -----------------------------------------------------------------------
4758    // run_function happy-path tests
4759    // -----------------------------------------------------------------------
4760
4761    #[test]
4762    fn run_function_returns_ok_for_trivial_function() {
4763        let mut ctx = Context::new();
4764        qcode!(
4765            ctx,
4766            "
4767            fn function:
4768            <entry>
4769                return at i64 0;
4770            "
4771        );
4772
4773        let mut emu = Emulator::from_function(&ctx, function);
4774        assert!(emu.run_function(function).is_ok());
4775    }
4776
4777    #[test]
4778    fn run_function_executes_instructions_before_return() {
4779        let mut ctx = Context::new();
4780        qcode!(
4781            ctx,
4782            "
4783            varnode i64 A;
4784            varnode i64 B;
4785
4786            fn function:
4787            <entry>
4788                %a = load(A:8, &A);
4789                %b = load(B:8, &B);
4790                %sum = %a + %b;
4791                return at i64 0;
4792            "
4793        );
4794
4795        let mut emu = Emulator::from_function(&ctx, function);
4796        emu.set_varnode(A, 7).unwrap();
4797        emu.set_varnode(B, 5).unwrap();
4798        emu.run_function(function).unwrap();
4799
4800        assert_eq!(
4801            emu.get_value(sum.into()).and_then(|v| v.value()).unwrap(),
4802            12
4803        );
4804    }
4805
4806    #[test]
4807    fn run_function_call_stack_empty_after_successful_return() {
4808        let mut ctx = Context::new();
4809
4810        qcode!(
4811            ctx,
4812            "
4813            varnode i64 A;
4814            varnode i64 B;
4815
4816            fn function:
4817            <entry>
4818                %a = load(A:8, &A);
4819                %b = load(B:8, &B);
4820                %sum = %a + %b;
4821                return at i64 0;
4822            "
4823        );
4824
4825        let mut emu = Emulator::from_function(&ctx, function);
4826        emu.set_varnode(A, 0).unwrap();
4827        emu.set_varnode(B, 0).unwrap();
4828        emu.run_function(function).unwrap();
4829
4830        assert!(emu.call_stack().is_empty());
4831    }
4832
4833    #[test]
4834    fn unhandled_direct_call_still_enters_callee() {
4835        let mut ctx = Context::new();
4836        qcode!(
4837            ctx,
4838            "
4839            fn callee:
4840            <callee_entry>
4841                return at i64 0;
4842
4843            <caller>
4844                call <callee>;
4845            "
4846        );
4847
4848        let mut emu = Emulator::from_block(&ctx, caller);
4849        emu.step().unwrap();
4850
4851        assert_eq!(emu.block().id, callee_entry);
4852    }
4853
4854    #[test]
4855    fn handled_direct_call_resumes_at_selected_block() {
4856        let mut ctx = Context::new();
4857        qcode!(
4858            ctx,
4859            "
4860            varnode i64 RET;
4861
4862            fn library:
4863            <library_entry>
4864                return at i64 0;
4865
4866            fn function:
4867            <entry>
4868                call <library>;
4869            <after_call>
4870                %ret = load(RET:8, &RET);
4871                return at i64 0;
4872            "
4873        );
4874
4875        let mut emu = Emulator::from_function(&ctx, function);
4876        emu.set_call_interceptor(move |ctx, emu, site| {
4877            if site.target == library {
4878                emu.set_varnode(ctx, RET, 42)
4879                    .map_err(|err| err.to_string().into_boxed_str())?;
4880                Ok(CallInterception::Handled(CallContinuation::Block(
4881                    after_call,
4882                )))
4883            } else {
4884                Ok(CallInterception::PassThrough)
4885            }
4886        });
4887
4888        emu.run_function(function).unwrap();
4889
4890        assert_eq!(
4891            emu.get_value(ret.into()).and_then(|v| v.value()).unwrap(),
4892            42
4893        );
4894        assert!(emu.call_stack().is_empty());
4895    }
4896
4897    #[test]
4898    fn handled_direct_call_can_resume_by_address() {
4899        let mut ctx = Context::new();
4900        qcode!(
4901            ctx,
4902            "
4903            fn library:
4904            <library_entry>
4905                return at i64 0;
4906
4907            <entry>
4908                call <library>;
4909            <0x2000>
4910                return at i64 0;
4911            "
4912        );
4913
4914        let mut emu = Emulator::from_block(&ctx, entry);
4915        emu.set_call_interceptor(move |_, _, site| {
4916            if site.target == library {
4917                Ok(CallInterception::Handled(CallContinuation::Address(0x2000)))
4918            } else {
4919                Ok(CallInterception::PassThrough)
4920            }
4921        });
4922
4923        emu.step().unwrap();
4924
4925        assert_eq!(emu.block().address(), Some(0x2000));
4926    }
4927
4928    #[test]
4929    fn handled_direct_call_reports_unknown_continuation_address() {
4930        let mut ctx = Context::new();
4931        qcode!(
4932            ctx,
4933            "
4934            fn library:
4935            <library_entry>
4936                return at i64 0;
4937
4938            <entry>
4939                call <library>;
4940            "
4941        );
4942
4943        let mut emu = Emulator::from_block(&ctx, entry);
4944        emu.set_call_interceptor(move |_, _, site| {
4945            if site.target == library {
4946                Ok(CallInterception::Handled(CallContinuation::Address(0xdead)))
4947            } else {
4948                Ok(CallInterception::PassThrough)
4949            }
4950        });
4951
4952        let err = emu.step().unwrap_err();
4953
4954        assert!(matches!(
4955            err.kind,
4956            EmulatorErrorKind::InvalidBlockAddress(0xdead)
4957        ));
4958    }
4959
4960    #[test]
4961    fn call_interceptor_errors_are_reported_at_call_site() {
4962        let mut ctx = Context::new();
4963        qcode!(
4964            ctx,
4965            "
4966            fn library:
4967            <library_entry>
4968                return at i64 0;
4969
4970            <entry>
4971                call <library>;
4972            "
4973        );
4974
4975        let mut emu = Emulator::from_block(&ctx, entry);
4976        emu.set_call_interceptor(|_, _, _| Err("model failed".into()));
4977
4978        let err = emu.step().unwrap_err();
4979
4980        assert!(matches!(
4981            err.kind,
4982            EmulatorErrorKind::InterceptError(message) if message.as_ref() == "model failed"
4983        ));
4984        assert!(err.ctx.contains("call fn library();"));
4985    }
4986
4987    #[test]
4988    fn interceptor_can_model_state_across_calls() {
4989        let mut ctx = Context::new();
4990        qcode!(
4991            ctx,
4992            "
4993            fn make_object:
4994            <make_object_entry>
4995                return at i64 0;
4996
4997            fn append_byte:
4998            <append_byte_entry>
4999                return at i64 0;
5000
5001            fn function:
5002            <entry>
5003                call <make_object>;
5004            <append>
5005                call <append_byte>;
5006            <done>
5007                return at i64 0;
5008            "
5009        );
5010
5011        let modeled = Arc::new(Mutex::new(Vec::<u8>::new()));
5012        let modeled_for_hook = Arc::clone(&modeled);
5013        let mut emu = Emulator::from_function(&ctx, function);
5014        emu.set_call_interceptor(move |_, _, site| {
5015            let mut model = modeled_for_hook.lock().unwrap();
5016            if site.target == make_object {
5017                model.clear();
5018                Ok(CallInterception::Handled(CallContinuation::Block(append)))
5019            } else if site.target == append_byte {
5020                model.push(0x41);
5021                Ok(CallInterception::Handled(CallContinuation::Block(done)))
5022            } else {
5023                Ok(CallInterception::PassThrough)
5024            }
5025        });
5026
5027        emu.run_function(function).unwrap();
5028
5029        assert_eq!(*modeled.lock().unwrap(), vec![0x41]);
5030    }
5031
5032    // -----------------------------------------------------------------------
5033    // run_function error tests
5034    // -----------------------------------------------------------------------
5035
5036    #[test]
5037    fn branchind_to_unknown_address_returns_error() {
5038        let mut ctx = Context::new();
5039        qcode!(
5040            ctx,
5041            "
5042            fn function:
5043            <entry>
5044                # Branching to literal 0 — no block lives at address 0
5045                goto [i64 0];
5046            "
5047        );
5048
5049        let mut emu = Emulator::from_function(&ctx, function);
5050        let err = emu.run_function(function).unwrap_err();
5051        assert!(matches!(
5052            err.kind,
5053            EmulatorErrorKind::InvalidBlockAddress(0)
5054        ));
5055    }
5056
5057    #[test]
5058    fn error_includes_faulting_instruction_id() {
5059        let mut ctx = Context::new();
5060        qcode!(
5061            ctx,
5062            "
5063            fn function:
5064            <entry>
5065                # Null pointer dereference
5066                %bad_load = load(ram:8, i64 0);
5067                return at i64 0;
5068            "
5069        );
5070
5071        let mut emu = Emulator::from_function(&ctx, function);
5072        let err = emu.run_function(function).unwrap_err();
5073
5074        assert!(
5075            err.ctx.contains(
5076                &Instruction::from_id(&ctx, bad_load)
5077                    .as_statement()
5078                    .to_string()
5079            )
5080        );
5081    }
5082
5083    #[test]
5084    fn error_call_stack_reflects_active_frames_at_fault() {
5085        // Build callee: immediately does a BranchInd to address 0 (always fails)
5086        let mut ctx = Context::new();
5087        qcode!(
5088            ctx,
5089            "
5090            fn callee:
5091            <entry1>
5092                # Branching to literal 0 — no block lives at address 0
5093                goto [i64 0];
5094
5095            fn caller:
5096            <entry2>
5097                call <callee>;
5098            "
5099        );
5100
5101        // Build caller: calls callee
5102        let mut emu = Emulator::from_function(&ctx, caller);
5103        let err = emu.run_function(caller).unwrap_err();
5104
5105        assert!(matches!(
5106            err.kind,
5107            EmulatorErrorKind::InvalidBlockAddress(0)
5108        ));
5109        assert_eq!(emu.call_stack(), &[caller, callee]);
5110    }
5111}