Skip to main content

qcode_emulator/
lib.rs

1//! Concrete execution of the [QCode](https://docs.rs/qcode) IR.
2//!
3//! This crate answers *what does this IR compute*: it evaluates QCode over a
4//! pre-lifted, immutable module against concrete machine state. It is the
5//! reference execution strategy — the one [`qcode_jit`] is differentially
6//! tested against, and the one [`qcode_vm`] builds a machine on top of.
7//!
8//! # Abstract over the domain
9//!
10//! Execution is generic over the *interpretation domain*. [`DomainValue`],
11//! [`DomainMemory`] and [`Interpreter`] describe what a value, a memory and an
12//! evaluator have to provide; concrete execution is one instantiation, and a
13//! symbolic or abstract one is another. [`StandaloneEmulator`] is the concrete
14//! implementation, built on [`SizedValue`].
15//!
16//! # Fidelity
17//!
18//! Floating point goes through [`rustc_apfloat`](https://docs.rs/rustc_apfloat)
19//! rather than the host's `f64`, including correctly rounded 80-bit x87
20//! extended precision, and x87 arithmetic honours the guest's control word for
21//! rounding mode and precision control. Results match hardware rather than
22//! whatever the host FPU happens to do.
23//!
24//! # Example
25//!
26//! ```
27//! use qcode::{context::Context, qcode};
28//! use qcode_emulator::StandaloneEmulator;
29//!
30//! let mut ctx = Context::new();
31//! qcode!(
32//!     ctx,
33//!     "
34//!     <src>
35//!         goto <dst @x=0x2>;
36//!     <dst @x>
37//!         %sum = i64 @x + 0x3;
38//!         goto <0x1001>;
39//!     "
40//! );
41//!
42//! let mut emu = StandaloneEmulator::new(src);
43//! emu.step(&ctx).expect("the branch binds the block parameter");
44//! emu.step(&ctx).expect("the destination uses it");
45//!
46//! assert_eq!(emu.get_value(&ctx, sum.into()), Some(5));
47//! ```
48//!
49//! To *run a guest program* — mapped memory, page permissions, faults
50//! delivered as values, code lifted on demand — see [`qcode_vm`], which layers
51//! those on top of this crate.
52//!
53//! [`qcode_vm`]: https://docs.rs/qcode_vm
54//! [`qcode_jit`]: https://docs.rs/qcode_jit
55
56use qcode::{
57    context::Context,
58    space::MemorySpaceId,
59    value::{
60        BlockId, FunctionId, InstructionId, ValueId, Varnode,
61        insn::{
62            Binary, Binop, Carry, FloatBinop, FloatToFloat, FloatToInt, Gep, InstructionRef,
63            IntBinop, IntToFloat, IsFloatNaN, Load, LzCount, Mnemonic, PopCount, Range, SBorrow,
64            SCarry, Sext, Store, Unary, Unop, Zext,
65        },
66        varnode::{VarnodeId, register::RegisterId},
67    },
68};
69
70mod concrete;
71
72pub use concrete::{
73    BodyArg, EmulatedMemory, Emulator, EmulatorMemory, SizedValue, StandaloneEmulator,
74};
75
76#[derive(Debug, Clone)]
77pub struct CallSite {
78    pub instruction: InstructionId,
79    pub block: BlockId,
80    pub target: FunctionId,
81    pub args: Vec<ValueId>,
82}
83
84#[derive(Debug, Clone, Copy, PartialEq, Eq)]
85pub enum CallContinuation {
86    Block(BlockId),
87    Address(u64),
88}
89
90#[derive(Debug, Clone, PartialEq, Eq)]
91pub enum CallInterception {
92    PassThrough,
93    Handled(CallContinuation),
94}
95
96#[derive(Debug)]
97pub struct EmulatorError {
98    /// What went wrong
99    pub kind: EmulatorErrorKind,
100    /// The context in which the error occurred
101    /// Usually the instruction, it's address and function it belongs to
102    pub ctx: String,
103
104    /// The address at which the error occurred, if applicable.
105    pub address: Option<u64>,
106}
107
108impl EmulatorError {
109    pub fn new(kind: EmulatorErrorKind, insn: &InstructionRef<'_, '_>) -> Self {
110        Self {
111            kind,
112            ctx: format!(
113                "Instruction: {}\nBlock: {:?}\nFunction: {:?}",
114                insn.as_statement(),
115                insn.parent().map(|b| b.name()),
116                insn.function().map(|f| f.name())
117            ),
118            address: insn.parent().and_then(|b| b.address()),
119        }
120    }
121}
122
123#[derive(Debug)]
124pub enum EmulatorErrorKind {
125    /// Branch/call/return resolved to an address with no known block
126    InvalidBlockAddress(u64),
127    /// Called a function that has no root block
128    EmptyFunctionRoot(FunctionId),
129    /// A pass-local minted callee escaped its installation barrier.
130    UnresolvedMintedCallee(u32),
131    /// `run_until` was given an address with no corresponding block
132    UnknownAddress(u64),
133    /// Attempted to construct a memory region that would overflow the address space
134    AddressOverflow(u64, usize),
135    /// Attempted to read memory at an address that hasn't been written to
136    MemoryReadError(u64),
137    /// Attempted to write to an address that can't be read back (e.g. MMIO)
138    MemoryWriteError(u64),
139    /// This value is too large to be represented in the target type (e.g. trying to interpret a 128-bit value as a 64-bit value)
140    ValueError(u128),
141    /// Attempted to access a register that is not present in the context
142    UnknownRegister(RegisterId),
143    /// Attempted to read from a memory space that has not been initialised
144    UnknownSpace(MemorySpaceId),
145    /// Encountered an architecture-specific p-code operation without an emulator implementation
146    UnsupportedPCodeOp(Box<str>),
147    /// Execution reached a `vm.interrupt` op: the machine stopped *at* it, on
148    /// purpose, for the host to act. Not a failure; the VM turns it into a
149    /// resumable exit.
150    Interrupt,
151    /// An intrinsic's evaluator could not produce a result (e.g. a trap or
152    /// unsupported operand width)
153    UnsupportedIntrinsic(Box<str>),
154    /// A user-provided call interceptor failed while modeling a call
155    InterceptError(Box<str>),
156    /// A bounded emulation run (e.g. `run_pure`) exceeded its step budget.
157    StepBudgetExceeded(usize),
158    /// A mnemonic the interpreter does not model (e.g. `map`, whose whole-array
159    /// emulation is deferred). Recoverable: a best-effort consumer such as
160    /// pure-call folding simply declines to harvest, rather than crashing.
161    UnsupportedMnemonic(&'static str),
162    /// Execution reached a block with no instructions (a malformed/degenerate
163    /// block left behind by lifting). Recoverable: bounded consumers decline to
164    /// harvest rather than indexing out of bounds.
165    EmptyBlock(BlockId),
166    /// A [`poison`](qcode::value::poison) value was demanded as a concrete datum.
167    /// Poison has undefined bits, so reading it is a hard error (argpromote v2);
168    /// propagating it as an unread operand is fine. Bounded consumers (e.g.
169    /// pure-call folding) treat this as a bail signal.
170    PoisonRead,
171}
172
173impl std::fmt::Display for EmulatorErrorKind {
174    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
175        match self {
176            Self::InvalidBlockAddress(addr) => write!(f, "invalid block address {addr:#x}"),
177            Self::EmptyFunctionRoot(func) => write!(f, "function {func:?} has no root block"),
178            Self::UnresolvedMintedCallee(slot) => {
179                write!(f, "minted callee placeholder #{slot} is not executable")
180            }
181            Self::UnknownAddress(addr) => write!(f, "unknown address {addr:#x}"),
182            Self::AddressOverflow(addr, size) => {
183                write!(f, "address overflow at {addr:#x} with size {size}")
184            }
185            Self::MemoryReadError(addr) => write!(f, "memory read error at address {addr:#x}"),
186            Self::MemoryWriteError(addr) => write!(f, "memory write error at address {addr:#x}"),
187            Self::ValueError(value) => write!(f, "value {value} is too large to represent"),
188            Self::UnknownRegister(reg) => write!(f, "register {reg:?} not found in context"),
189            Self::UnknownSpace(space) => write!(f, "memory space {space:?} not initialised"),
190            Self::UnsupportedPCodeOp(op) => write!(f, "unsupported p-code operation `{op}`"),
191            Self::Interrupt => write!(f, "vm.interrupt"),
192            Self::UnsupportedIntrinsic(op) => write!(f, "unsupported intrinsic `{op}`"),
193            Self::InterceptError(message) => write!(f, "call interceptor failed: {message}"),
194            Self::StepBudgetExceeded(budget) => {
195                write!(f, "emulation exceeded step budget of {budget}")
196            }
197            Self::UnsupportedMnemonic(op) => write!(f, "unsupported mnemonic `{op}`"),
198            Self::EmptyBlock(block) => write!(f, "block {block:?} has no instructions"),
199            Self::PoisonRead => write!(f, "read of a poison value (undefined bits)"),
200        }
201    }
202}
203
204impl std::fmt::Display for EmulatorError {
205    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
206        write!(f, "emulator error: {}", self.kind)?;
207        write!(f, " (context: {})", self.ctx)?;
208        Ok(())
209    }
210}
211
212impl std::error::Error for EmulatorError {}
213
214pub type Result<T> = std::result::Result<T, EmulatorError>;
215
216/// A trait to describe a value
217/// This is used to abstract over different types of interpretations (symbolic, concrete, abstract, etc.)
218pub trait DomainValue: Clone + Copy {
219    /// Returns the size of this value in bytes
220    fn size(&self) -> std::result::Result<usize, EmulatorErrorKind>;
221
222    /// Attempt to read this value as a little-endian unsigned integer.
223    fn value(&self) -> std::result::Result<u64, EmulatorErrorKind>;
224
225    fn from_u64(value: u64) -> Self;
226
227    /// Creates an all-zero value at `size` bytes. Domains that track width
228    /// should override this; the default preserves compatibility for domains
229    /// that only have a machine-word zero representation.
230    fn zero(_size: usize) -> Self {
231        Self::from_u64(0)
232    }
233
234    fn is_float_nan(&self) -> std::result::Result<Self, EmulatorErrorKind>;
235    fn int_to_float(&self, size: usize) -> std::result::Result<Self, EmulatorErrorKind>;
236    fn float_to_float(&self, size: usize) -> std::result::Result<Self, EmulatorErrorKind>;
237    fn float_to_int(&self, size: usize) -> std::result::Result<Self, EmulatorErrorKind>;
238    fn zext(&self, size: usize) -> std::result::Result<Self, EmulatorErrorKind>;
239    fn sext(&self, size: usize) -> std::result::Result<Self, EmulatorErrorKind>;
240    fn range(&self, start: usize, size: usize) -> std::result::Result<Self, EmulatorErrorKind>;
241    fn byte_swap(&self) -> std::result::Result<Self, EmulatorErrorKind>;
242
243    /// Evaluate a pure intrinsic on its concrete operands, producing an
244    /// `out_size`-byte result via the intrinsic's shared evaluator.
245    fn intrinsic(
246        id: qcode::value::insn::IntrinsicId,
247        args: &[Self],
248        out_size: usize,
249    ) -> std::result::Result<Self, EmulatorErrorKind>;
250
251    fn pop_count(&self) -> std::result::Result<Self, EmulatorErrorKind>;
252    fn lz_count(&self) -> std::result::Result<Self, EmulatorErrorKind>;
253    fn carry(&self, other: &Self) -> std::result::Result<Self, EmulatorErrorKind>;
254    fn scarry(&self, other: &Self) -> std::result::Result<Self, EmulatorErrorKind>;
255    fn sborrow(&self, other: &Self) -> std::result::Result<Self, EmulatorErrorKind>;
256
257    fn int_not(&self) -> std::result::Result<Self, EmulatorErrorKind>;
258    fn int_negate(&self) -> std::result::Result<Self, EmulatorErrorKind>;
259    fn float_negate(&self) -> std::result::Result<Self, EmulatorErrorKind>;
260    fn float_abs(&self) -> std::result::Result<Self, EmulatorErrorKind>;
261    fn float_sqrt(&self) -> std::result::Result<Self, EmulatorErrorKind>;
262    fn float_ceil(&self) -> std::result::Result<Self, EmulatorErrorKind>;
263    fn float_floor(&self) -> std::result::Result<Self, EmulatorErrorKind>;
264    fn float_round(&self) -> std::result::Result<Self, EmulatorErrorKind>;
265
266    fn int_equal(&self, other: &Self) -> std::result::Result<Self, EmulatorErrorKind>;
267    fn int_not_equal(&self, other: &Self) -> std::result::Result<Self, EmulatorErrorKind>;
268    fn int_less(&self, other: &Self) -> std::result::Result<Self, EmulatorErrorKind>;
269    fn int_sless(&self, other: &Self) -> std::result::Result<Self, EmulatorErrorKind>;
270    fn int_less_equal(&self, other: &Self) -> std::result::Result<Self, EmulatorErrorKind>;
271    fn int_sless_equal(&self, other: &Self) -> std::result::Result<Self, EmulatorErrorKind>;
272    fn int_add(&self, other: &Self) -> std::result::Result<Self, EmulatorErrorKind>;
273    fn int_sub(&self, other: &Self) -> std::result::Result<Self, EmulatorErrorKind>;
274    fn int_xor(&self, other: &Self) -> std::result::Result<Self, EmulatorErrorKind>;
275    fn int_and(&self, other: &Self) -> std::result::Result<Self, EmulatorErrorKind>;
276    fn int_or(&self, other: &Self) -> std::result::Result<Self, EmulatorErrorKind>;
277    fn int_shift_left(&self, other: &Self) -> std::result::Result<Self, EmulatorErrorKind>;
278    fn int_shift_right(&self, other: &Self) -> std::result::Result<Self, EmulatorErrorKind>;
279    fn int_sshift_right(&self, other: &Self) -> std::result::Result<Self, EmulatorErrorKind>;
280    fn int_mul(&self, other: &Self) -> std::result::Result<Self, EmulatorErrorKind>;
281    fn int_div(&self, other: &Self) -> std::result::Result<Self, EmulatorErrorKind>;
282    fn int_rem(&self, other: &Self) -> std::result::Result<Self, EmulatorErrorKind>;
283    fn int_sdiv(&self, other: &Self) -> std::result::Result<Self, EmulatorErrorKind>;
284    fn int_srem(&self, other: &Self) -> std::result::Result<Self, EmulatorErrorKind>;
285
286    fn float_add(&self, other: &Self) -> std::result::Result<Self, EmulatorErrorKind>;
287    fn float_sub(&self, other: &Self) -> std::result::Result<Self, EmulatorErrorKind>;
288    fn float_mul(&self, other: &Self) -> std::result::Result<Self, EmulatorErrorKind>;
289    fn float_div(&self, other: &Self) -> std::result::Result<Self, EmulatorErrorKind>;
290    fn float_equal(&self, other: &Self) -> std::result::Result<Self, EmulatorErrorKind>;
291    fn float_not_equal(&self, other: &Self) -> std::result::Result<Self, EmulatorErrorKind>;
292    fn float_less(&self, other: &Self) -> std::result::Result<Self, EmulatorErrorKind>;
293    fn float_less_equal(&self, other: &Self) -> std::result::Result<Self, EmulatorErrorKind>;
294}
295
296pub trait DomainMemory {
297    type V: DomainValue;
298
299    /// Reads a value from the given address.
300    /// The size of the value must be less than or equal to the size of the region being read from.
301    fn read(
302        &self,
303        space: MemorySpaceId,
304        addr: Self::V,
305        size: usize,
306    ) -> std::result::Result<Self::V, EmulatorErrorKind>;
307
308    /// Writes a value to the given address.
309    /// The size of the value must be less than or equal to the size of the region being written to.
310    fn write(
311        &mut self,
312        space: MemorySpaceId,
313        addr: Self::V,
314        size: usize,
315        data: Self::V,
316    ) -> std::result::Result<(), EmulatorErrorKind>;
317}
318
319pub trait Interpreter {
320    type V: DomainValue;
321    type M: DomainMemory<V = Self::V>;
322
323    fn ctx(&self) -> &Context<'_>;
324
325    fn memory(&mut self) -> &mut Self::M;
326
327    /// Gets the value of the given value ID, if it exists in the context.
328    fn get_value(&mut self, id: ValueId) -> std::result::Result<Self::V, EmulatorErrorKind>;
329
330    /// Reads the value of a varnode from the emulated memory space.
331    fn get_varnode_value(
332        &mut self,
333        id: VarnodeId,
334    ) -> std::result::Result<Self::V, EmulatorErrorKind> {
335        let varnode = Varnode::from_id(self.ctx(), id);
336        let space = varnode.space().id;
337        let addr = Self::V::from_u64(varnode.address() as u64);
338        let size = varnode.size();
339        self.memory().read(space.into(), addr, size)
340    }
341
342    /// Writes a value to a varnode in the emulated memory space.
343    fn set_varnode_value(
344        &mut self,
345        id: VarnodeId,
346        value: Self::V,
347    ) -> std::result::Result<(), EmulatorErrorKind> {
348        let varnode = Varnode::from_id(self.ctx(), id);
349        let space = varnode.space().id;
350        let addr = Self::V::from_u64(varnode.address() as u64);
351        let size = varnode.size();
352        self.memory().write(space.into(), addr, size, value)?;
353        Ok(())
354    }
355
356    /// Returns the value of the given register, if it exists in the context.
357    fn get_register_value(
358        &mut self,
359        reg_id: RegisterId,
360    ) -> std::result::Result<Self::V, EmulatorErrorKind> {
361        let id = *self
362            .ctx()
363            .shared
364            .registers
365            .get(&reg_id)
366            .ok_or(EmulatorErrorKind::UnknownRegister(reg_id))?;
367        self.get_varnode_value(id)
368    }
369
370    /// Sets the value of the given register, if it exists in the context.
371    fn set_register_value(
372        &mut self,
373        reg_id: RegisterId,
374        value: Self::V,
375    ) -> std::result::Result<(), EmulatorErrorKind> {
376        let id = *self
377            .ctx()
378            .shared
379            .registers
380            .get(&reg_id)
381            .ok_or(EmulatorErrorKind::UnknownRegister(reg_id))?;
382        self.set_varnode_value(id, value)
383    }
384
385    /// Gets the value of the given instruction, if it has one.
386    /// This is used for instructions that produce a value, such as copy, load, and binary operations.
387    fn interpret_(
388        &mut self,
389        insn: &InstructionRef<'_, '_>,
390        mnemonic: &Mnemonic,
391    ) -> std::result::Result<Option<Self::V>, EmulatorErrorKind> {
392        // Operands are stored bare-local; qualify with the instruction's own
393        // function (strict IR locality: operands live in the same arena).
394        let func = insn.id.func;
395        // Supplied by the caller, which has already resolved it: each
396        // `insn.mnemonic()` walks the instruction out of the module registries.
397        let v = match mnemonic {
398            // ===== Memory operations =====
399            &Mnemonic::Load(Load { space, ptr, size }) => {
400                let addr = self.get_value(ptr.qualify(func))?;
401                Some(self.memory().read(space.qualify(func), addr, size)?)
402            }
403
404            &Mnemonic::Store(Store {
405                space,
406                ptr,
407                size,
408                src,
409            }) => {
410                let addr = self.get_value(ptr.qualify(func))?;
411                let value = self.get_value(src.qualify(func))?;
412                self.memory()
413                    .write(space.qualify(func), addr, size, value)?;
414                None
415            }
416
417            // ===== Control flow operations =====
418            Mnemonic::Branch(_)
419            | Mnemonic::CBranch(_)
420            | Mnemonic::BranchInd(_)
421            | Mnemonic::Call(_)
422            | Mnemonic::CallInd(_)
423            | Mnemonic::Return(_)
424            | Mnemonic::ReturnValue(_)
425            | Mnemonic::Apply(_) => None,
426
427            // ===== Unary operations =====
428            Mnemonic::Unop(Unary { op, src }) => {
429                let value = self.get_value(src.qualify(func))?;
430                let v = match op {
431                    Unop::IntNegate => value.int_negate(),
432                    Unop::IntNot => value.int_not(),
433                    Unop::FloatNegate => value.float_negate(),
434                    Unop::FloatAbs => value.float_abs(),
435                    Unop::FloatSqrt => value.float_sqrt(),
436                    Unop::FloatCeil => value.float_ceil(),
437                    Unop::FloatFloor => value.float_floor(),
438                    Unop::FloatRound => value.float_round(),
439                    _ => todo!("unimplemented unary operation: {:?}", op),
440                }?;
441                Some(v)
442            }
443
444            // ===== Binary operations =====
445            Mnemonic::Binop(Binary { op, lhs, rhs }) => {
446                let value1 = self.get_value(lhs.qualify(func))?;
447                let value2 = self.get_value(rhs.qualify(func))?;
448                let v = match *op {
449                    Binop::Int(IntBinop::Equal) => value1.int_equal(&value2),
450                    Binop::Int(IntBinop::NotEqual) => value1.int_not_equal(&value2),
451                    Binop::Int(IntBinop::Less) => value1.int_less(&value2),
452                    Binop::Int(IntBinop::SLess) => value1.int_sless(&value2),
453                    Binop::Int(IntBinop::LessEqual) => value1.int_less_equal(&value2),
454                    Binop::Int(IntBinop::SLessEqual) => value1.int_sless_equal(&value2),
455                    Binop::Int(IntBinop::Add) => value1.int_add(&value2),
456                    Binop::Int(IntBinop::Sub) => value1.int_sub(&value2),
457                    Binop::Int(IntBinop::Xor) => value1.int_xor(&value2),
458                    Binop::Int(IntBinop::And) => value1.int_and(&value2),
459                    Binop::Int(IntBinop::Or) => value1.int_or(&value2),
460                    Binop::Int(IntBinop::ShiftLeft) => value1.int_shift_left(&value2),
461                    Binop::Int(IntBinop::ShiftRight) => value1.int_shift_right(&value2),
462                    Binop::Int(IntBinop::SShiftRight) => value1.int_sshift_right(&value2),
463                    Binop::Int(IntBinop::Mul) => value1.int_mul(&value2),
464                    Binop::Int(IntBinop::Div) => value1.int_div(&value2),
465                    Binop::Int(IntBinop::Rem) => value1.int_rem(&value2),
466                    Binop::Int(IntBinop::Sdiv) => value1.int_sdiv(&value2),
467                    Binop::Int(IntBinop::Srem) => value1.int_srem(&value2),
468
469                    Binop::Float(FloatBinop::Add) => value1.float_add(&value2),
470                    Binop::Float(FloatBinop::Sub) => value1.float_sub(&value2),
471                    Binop::Float(FloatBinop::Mul) => value1.float_mul(&value2),
472                    Binop::Float(FloatBinop::Div) => value1.float_div(&value2),
473                    Binop::Float(FloatBinop::Equal) => value1.float_equal(&value2),
474                    Binop::Float(FloatBinop::NotEqual) => value1.float_not_equal(&value2),
475                    Binop::Float(FloatBinop::Less) => value1.float_less(&value2),
476                    Binop::Float(FloatBinop::LessEqual) => value1.float_less_equal(&value2),
477                    _ => todo!("unimplemented binary operation: {:?}", op),
478                }?;
479                Some(v)
480            }
481
482            // ===== Bit manipulation operations =====
483            &Mnemonic::PopCount(PopCount { src }) => {
484                let value = self.get_value(src.qualify(func))?;
485                Some(value.pop_count()?)
486            }
487
488            &Mnemonic::LzCount(LzCount { src }) => {
489                let value = self.get_value(src.qualify(func))?;
490                Some(value.lz_count()?)
491            }
492
493            &Mnemonic::Carry(Carry { lhs, rhs }) => {
494                let value1 = self.get_value(lhs.qualify(func))?;
495                let value2 = self.get_value(rhs.qualify(func))?;
496                Some(value1.carry(&value2)?)
497            }
498
499            &Mnemonic::SCarry(SCarry { lhs, rhs }) => {
500                let value1 = self.get_value(lhs.qualify(func))?;
501                let value2 = self.get_value(rhs.qualify(func))?;
502                Some(value1.scarry(&value2)?)
503            }
504
505            &Mnemonic::SBorrow(SBorrow { lhs, rhs }) => {
506                let value1 = self.get_value(lhs.qualify(func))?;
507                let value2 = self.get_value(rhs.qualify(func))?;
508                Some(value1.sborrow(&value2)?)
509            }
510
511            // ===== Casting operations =====
512            &Mnemonic::IsFloatNaN(IsFloatNaN { src }) => {
513                let value = self.get_value(src.qualify(func))?;
514                Some(value.is_float_nan()?)
515            }
516            &Mnemonic::IntToFloat(IntToFloat { src, size }) => {
517                let value = self.get_value(src.qualify(func))?;
518                Some(value.int_to_float(size)?)
519            }
520            &Mnemonic::FloatToFloat(FloatToFloat { src, size }) => {
521                let value = self.get_value(src.qualify(func))?;
522                Some(value.float_to_float(size)?)
523            }
524            &Mnemonic::FloatToInt(FloatToInt { src, size }) => {
525                let value = self.get_value(src.qualify(func))?;
526                Some(value.float_to_int(size)?)
527            }
528            &Mnemonic::Zext(Zext { src, size }) => {
529                let value = self.get_value(src.qualify(func))?;
530                Some(value.zext(size)?)
531            }
532            &Mnemonic::Sext(Sext { src, size }) => {
533                let value = self.get_value(src.qualify(func))?;
534                Some(value.sext(size)?)
535            }
536            &Mnemonic::Range(Range { src, start, size }) => {
537                let value = self.get_value(src.qualify(func))?;
538                Some(value.range(start, size)?)
539            }
540
541            // ===== Aggregate operations =====
542            // `Gep` is pure pointer arithmetic: base pointer + constant byte
543            // offset. The width follows the base (int_add uses the lhs width),
544            // so the immediate's default u64 width is harmless.
545            &Mnemonic::Gep(Gep { base, offset }) => {
546                let base = self.get_value(base.qualify(func))?;
547                let offset = Self::V::from_u64(offset as u64);
548                Some(base.int_add(&offset)?)
549            }
550
551            // ===== Other operations =====
552            Mnemonic::PCodeOp(op) => {
553                let name = self.ctx().shared.pcode_ops[op.id].clone();
554                match (name.as_ref(), op.args.as_slice()) {
555                    ("swap_bytes", [src]) => Some(self.get_value(src.qualify(func))?.byte_swap()?),
556                    // SLEIGH uses this zero-argument user-op as an explicit
557                    // write of an architecturally undefined value. Concrete
558                    // emulation deliberately chooses zero, while retaining
559                    // the p-code op and its destination in the IR for
560                    // analysis consumers.
561                    ("undef", []) => Some(Self::V::zero(insn.size())),
562                    // The LOCK prefix's bus semantics are not observable in a
563                    // single-threaded replay: it orders an access against other
564                    // agents, and constrains nothing about the resulting state.
565                    // The paired markers stay in the IR for analysis consumers
566                    // that care which region is atomic; they produce no value.
567                    ("LOCK" | "UNLOCK", []) => None,
568                    // The host's stop request. Everything before it in the
569                    // block has retired; the VM reports the stop and, on
570                    // resume, files the op's result itself.
571                    (qcode::value::insn::VM_INTERRUPT, _) => {
572                        return Err(EmulatorErrorKind::Interrupt);
573                    }
574                    _ => return Err(EmulatorErrorKind::UnsupportedPCodeOp(name)),
575                }
576            }
577
578            Mnemonic::Intrinsic(intr) => {
579                let out_size = insn.size();
580                let mut args = Vec::with_capacity(intr.args.len());
581                for &arg in &intr.args {
582                    args.push(self.get_value(arg.qualify(func))?);
583                }
584                Some(Self::V::intrinsic(intr.id, &args, out_size)?)
585            }
586
587            // `map` has no interpreter (whole-array emulation is deferred). Bail
588            // recoverably so a best-effort consumer — pure-call folding emulating a
589            // function whose return depends on a `map` — declines to harvest the
590            // field instead of crashing the whole analysis. (Element projection
591            // does not go through emulation; it inlines the body via `ArrayProject`.)
592            Mnemonic::Map(_) => return Err(EmulatorErrorKind::UnsupportedMnemonic("map")),
593
594            _ => todo!("unimplemented mnemonic: {mnemonic:?}"),
595        };
596
597        Ok(v)
598    }
599
600    fn interpret(
601        &mut self,
602        insn: InstructionRef<'_, '_>,
603        mnemonic: &Mnemonic,
604    ) -> Result<Option<Self::V>> {
605        self.interpret_(&insn, mnemonic)
606            .map_err(|kind| EmulatorError::new(kind, &insn))
607    }
608}